Universidade Federal do ABC - Pós
Transcrição
Universidade Federal do ABC - Pós
UNIVERSIDADE FEDERAL DO ABC Centro de Matemática, Computação e Cognição (CMCC) Curso de Pós-Graduação em Ciência da Computação Dissertação de Mestrado Eduardo Batista Gomes Moreira ALGORITMOS PARALELOS EM GPUS PARA PROBLEMAS DE PROGRAMAÇÃO QUADRÁTICA BINÁRIA IRRESTRITA Santo André - SP 2013 Curso de Pós-Graduação em Ciência da Computação Dissertação de Mestrado Eduardo Batista Gomes Moreira ALGORITMOS PARALELOS EM GPUS PARA PROBLEMAS DE PROGRAMAÇÃO QUADRÁTICA BINÁRIA IRRESTRITA Trabalho apresentado como requisito parcial para obtenção do tı́tulo de Mestre em Ciência da Computação, sob orientação do Prof. Dr. Cláudio Nogueira de Meneses. Santo André - SP 2013 Este exemplar foi revisado e alterado em relação à versão original, de acordo com as observações levantadas pela banca no dia da defesa, sob responsabilidade única do autor e com a anuência de seu orientador. Santo André, 21 de outubro de 2013. Assinatura do autor: Assinatura do orientador: Centro de Matemática, Computação e Cognição (CMCC) Curso de Pós-Graduação em Ciência da Computação ALGORITMOS PARALELOS EM GPUS PARA PROBLEMAS DE PROGRAMAÇÃO QUADRÁTICA BINÁRIA IRRESTRITA Eduardo Batista Gomes Moreira Outubro de 2013 BANCA EXAMINADORA: • Prof. Dr. Cláudio Nogueira de Meneses (Presidente) (CMCC) Universidade Federal do ABC - UFABC • Prof. Dr. Alexandre Cláudio Botazzo Delbem (ICMC) Universidade de São Paulo - USP • Prof. Dr. Raphael Yokoingawa de Camargo (CMCC) Universidade Federal do ABC - UFABC • Prof. Dr. Daniel Morgato Martin (Suplente) (CMCC) Universidade Federal do ABC - UFABC • Prof.a Dr.a Maristela Oliveira dos Santos (Suplente) (ICMC) Universidade de São Paulo - USP Este trabalho contou com o auxı́lio financeiro das seguintes entidades: • Universidade Federal do ABC - UFABC (bolsa de mestrado, institucional), de outubro/2011 a março/2012; • Coordenação de Aperfeiçoamento de Pessoal de Nı́vel Superior - CAPES (bolsa de mestrado, demanda social), de abril/2012 a maio/2012; • Fundação de Amparo à Pesquisa do Estado de São Paulo - FAPESP (bolsa de mestrado), de junho/2012 a maio/2013. Resumo Consideramos o problema de otimização global: maximize f (x) = xT Qx, onde x ∈ {0, 1}n e Q é uma matriz assimétrica de dimensão n × n com coeficientes racionais. Este problema é comumente denominado problema de programação quadrática binária irrestrita (em inglês, Unconstrained binary Quadratic Problem - UQP) e tem sido muito estudado nos últimos 50 anos. Este problema tem diversas aplicações. Em economia, um exemplo bem conhecido é o de determinar um portfólio de investimento; uma aplicação tı́pica em estatı́stica é o problema de regressão linear; em otimização combinatória, uma aplicação é o problema de encontrar um clique máximo em um grafo. Nosso estudo se concentra: (1) no desenvolvimento de implementações paralelas de métodos heurı́sticos e exatos, que objetivam resolver instâncias do UQP e (2) na criação de fórmulas para recalcular o valor função objetivo, visto que o desempenho das implementações dependem significativamente da quantidade de vezes que o valor da função objetivo é calculado. As implementações usam unidades de processamento gráfico (em inglês, Graphics Processing Units - GPUs) e o ambiente de programação CUDA (Compute Unified Device Architecture). GPUs são dispositivos do tipo SIMT (Single Instruction, Multiple Thread) em computação paralela. Nossos resultados computacionais sobre instâncias do UQP, obtidas da literatura, evidenciam a robustez e eficácia das nossas abordagens. Com os métodos heurı́sticos tentamos resolver instâncias com até 2.500 variáveis e com o método exato foi possı́vel resolver instâncias com até 100 variáveis. 2 Abstract We consider the global optimization problem: maximize f (x) = xT Qx, where x ∈ {0, 1}n and Q is an asymmetric (n × n)-matrix with rational coefficients. This problem is commonly known as unconstrained binary quadratic problem (UQP) and has been studied for at least 50 years. It has many diverse applications. In economics, a well-known example arises from portfolio theory; a typical application in statistics is the linear regression problem; in combinatorial optimization, an application is the problem of finding a maximum clique in a graph (the maximum clique problem). Our work focus on: (1) parallel implementations of heuristic and exact methods to solve the UQP and (2) finding closed formulae to recompute the objective function values, since the performance of the implementations depend on the number of times the objective function value is computed. The implementations use modern programmable Graphic Processing Units (GPU) and the NVIDIA’s GPU programming framework, “Compute Unified Device Architecture” (CUDA). GPUs are SIMT (Single Instruction, Multiple Thread) devices that are dataparallel. Our computational results were done over a number of publically available data sets. The heuristics methods were tested on instances involving up to 2,500 variables and the exact method was able to solve instances with up to 100 variables. 3 Agradecimentos Expressar meus sentimentos de gratidão de forma adequada a todos que os merecem, requerem palavras e ações que não cabem no papel. Os próximos parágrafos mostram uma tentativa de sintetizar estes sentimentos. Agradeço a Deus pela minha saúde e pela oportunidade de continuar meus estudos. Agradeço profundamente a minha mãe, Angela, ao meu pai, Orlando, e a minha irmã, Dayane, pelo amor e carinho que sempre me deram, pela motivação, compreensão e apoio nos momentos mais difı́ceis e por todos momentos que passamos juntos. Viram, batalhei e consegui! Obrigado por sempre depositarem suas esperanças em mim. Agradeço imensamente ao Prof. Cláudio Meneses pelos ensinamentos, ajuda e colaboração com o meu trabalho, pelas conversas e conselhos ao longo do perı́odo do mestrado, mas acima de tudo, por ser um amigo. A todos os professores pelas novas experiências, pelos desafios que me foram apresentados e pelos conhecimentos adquiridos. Em especial aos professores Daniel Martin, Raphael Camargo, Ronaldo Prati e Siang Song. A meus amigos Fábio Beraldo, Omar Latorre e Grasielle Roberta por sempre me motivar e torcer por mim. Aos amigos do Clube de Anime da UFABC pelos momentos agradáveis que passamos juntos. Agradeço a CAPES, FAPESP e UFABC pelos financiamentos, em bolsas, para o desenvolvimento desta pesquisa. E por fim, agradeço a todos que um dia acreditaram em mim. 4 Sumário 1 Introdução 16 2 Definições 20 2.1 Definições Gerais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 2.2 Definições sobre Computação Paralela 3 Trabalhos Anteriores . . . . . . . . . . . . . . . . . . . . 23 26 3.1 Programação Quadrática . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 3.2 Programação Quadrática Binária Irrestrita . . . . . . . . . . . . . . . . . . 27 3.3 Artigos na Literatura . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 3.3.1 Métodos Heurı́sticos . . . . . . . . . . . . . . . . . . . . . . . . . . 27 3.3.2 Métodos Exatos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 3.4 UQP Linearizado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 3.5 Lista de Problemas Modelados na Forma UQP . . . . . . . . . . . . . . . . 30 3.6 Transformações Gerais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 3.7 Problemas de Otimização Combinatória Modelados na Forma UQP . . . . 34 3.7.1 Empacotamento de Conjuntos (Set Packing Problem) . . . . . . . . 34 3.7.2 Particionamento de Conjuntos (Set Partitioning) . . . . . . . . . . 36 3.7.3 Coloração de Vértices em um Grafo com no máximo K Cores (KColoring) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39 3.7.4 Conjunto Independente Generalizado (Generalized Independent Set Problem) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 3.7.5 Carregamento de Objetos em Estrados (Manufacturer’s Pallet Loading Problem) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 5 SUMÁRIO 3.8 3.9 6 Métodos Heurı́sticos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 3.8.1 Tabu Search . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 3.8.2 Busca Local 3.8.3 Variable Neighborhood Search . . . . . . . . . . . . . . . . . . . . . 46 3.8.4 Gradient Midpoint Method . . . . . . . . . . . . . . . . . . . . . . . 46 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 Métodos Exatos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 3.9.1 Limitantes e Regras para Determinar Valores das Variáveis em uma Solução Ótima . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 3.9.2 Branch and Bound . . . . . . . . . . . . . . . . . . . . . . . . . . . 51 4 Programação Paralela com GPUs 55 4.1 CPUs e GPUs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 4.2 Multicore e Manycore . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 4.3 Histórico das GPUs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 4.4 Arquitetura CUDA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58 4.5 Arquitetura Kepler GK104 . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 4.5.1 Visão Geral da Arquitetura Kepler . . . . . . . . . . . . . . . . . . 59 4.6 Memórias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 4.7 Limitações da Arquitetura CUDA . . . . . . . . . . . . . . . . . . . . . . . 62 4.8 Compilação do Código em CUDA . . . . . . . . . . . . . . . . . . . . . . . 63 4.9 Linguagem OpenCL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63 5 Contribuições e Implementações Paralelas 5.1 65 Cálculos do Valor da Função Objetivo e Limitantes Inferiores . . . . . . . . 65 5.1.1 Cálculo do Valor da Função Objetivo na Forma Básica . . . . . . . 66 5.1.2 Recálculo do Valor da Função Objetivo com Mudança do Valor de Uma Variável . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 5.1.3 Recálculo do Valor da Função Objetivo com Mudança do Valor em Duas Variáveis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70 5.1.4 Cálculos do Limitante Inferior à f (x) no Branch and Bound . . . . 73 SUMÁRIO 5.1.5 5.2 7 Resumo dos Métodos de Cálculo e Recálculo do Valor da Função Objetivo e do Limitante Inferior à f (x) . . . . . . . . . . . . . . . . 81 Implementações Paralelas . . . . . . . . . . . . . . . . . . . . . . . . . . . 82 5.2.1 Tabu Search na Forma Paralela . . . . . . . . . . . . . . . . . . . . 82 5.2.2 Branch and Bound na Forma Paralela . . . . . . . . . . . . . . . . . 87 6 Experimentos Computacionais 102 6.1 Ambiente dos Experimentos . . . . . . . . . . . . . . . . . . . . . . . . . . 102 6.2 Instâncias de Teste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103 6.3 6.4 6.2.1 Instâncias da OR Library . . . . . . . . . . . . . . . . . . . . . . . 103 6.2.2 Classe Difı́cil . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103 Resultados Obtidos por Heurı́sticas . . . . . . . . . . . . . . . . . . . . . . 104 6.3.1 Comparação entre as implementações do Tabu Search e do VNS . . 105 6.3.2 Comparações entre as implementações do Tabu Search Sequencial e Paralelo utilizando Recálculo com Mudança de uma Variável . . . . 106 6.3.3 Comparações entre as implementações do Tabu Search Sequencial e Paralelo com Cálculo do Valor da Função Objetivo na Forma Básica 110 Resultados Obtidos pelos Métodos Exatos . . . . . . . . . . . . . . . . . . 113 6.4.1 Resultados com o Solver CPLEX . . . . . . . . . . . . . . . . . . . 113 6.4.2 Comparações entre as implementações do Branch and Bound Sequencial e Paralelo com Cálculo do Valor da Função Objetivo na Forma Básica . . . . . . . . . . . . . . . . . . . . . . . . . 114 7 Comentários Finais e Trabalhos Futuros 120 7.1 Comentários Finais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120 7.2 Trabalhos Futuros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121 Lista de Figuras 3.1 Grafo de Exemplo para o Problema de Coloração em Vértices . . . . . . . 40 3.2 Grafo de Exemplo para o Problema do Conjunto Independente Generalizado 42 3.3 Iterações de um Tabu Search para a instância no Exemplo 3.5 . . . . . . . 45 3.4 Exemplo de uma árvore de enumeração do Branch and Bound . . . . . . . 53 4.1 Multicore e Manycore. Fonte: NVIDIA [42] . . . . . . . . . . . . . . . . . . 56 4.2 Evolução das GPUs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58 4.3 GeForce GTX 680 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 4.4 Diagrama da arquitetura Kepler GK104 . . . . . . . . . . . . . . . . . . . 60 4.5 Compilação de código CUDA . . . . . . . . . . . . . . . . . . . . . . . . . 63 5.1 Índices da matriz Q para o cálculo de f (y) . . . . . . . . . . . . . . . . . . 72 5.2 Comparação entre os valores de g1 (x) e g2 (x) ao longo da execução do Branch and Bound para a instância bqp50.6 . . . . . . . . . . . . . . . . . 77 5.3 Acesso a i-ésima linha e coluna da matriz Q . . . . . . . . . . . . . . . . . 87 5.4 Exemplo de operação de redução . . . . . . . . . . . . . . . . . . . . . . . 88 5.5 Matriz Linearizada Alocada com o Comando cudaMallocPitch . . . . . . . 93 5.6 Armazenamento da Pilha em Memória . . . . . . . . . . . . . . . . . . . . 96 6.1 Comparação entre as implementações sequencial e paralela do Tabu Search, com recálculo do valor da função objetivo. . . . . . . . . . . . . . . . . . . 106 6.2 Métrica speedup para a implementação do Tabu Search, com recálculo do valor da função objetivo com mudança no valor de uma variável. . . . . . . 109 6.3 Métrica speedup para a implementação do TS com cálculo do valor da função objetivo na forma básica. . . . . . . . . . . . . . . . . . . . . . . . . 112 8 LISTA DE FIGURAS 9 6.4 Speedup da implementação do B&B para instâncias da OR Library. . . . . 116 6.5 Speedup da implementação do B&B para instâncias da classe difı́cil. . . . . 117 6.6 Número de subproblemas resolvidos na implementação do B&B para instâncias da classe difı́cil . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118 Lista de Tabelas 4.1 Detalhes da arquitetura Kepler GK104. . . . . . . . . . . . . . . . . . . . . 60 4.2 Detalhes das Memórias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 5.1 Resumo do número de operações nos recálculos do valor da função objetivo. 81 5.2 Resumo do número de operações nos recálculos do valor do limitante inferior à f (x). . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82 6.1 Densidades das instâncias do UQP. . . . . . . . . . . . . . . . . . . . . . . 104 6.2 Comparação entre valores de soluções ótimas e de soluções obtidas por metaheurı́sticas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105 6.3 Comparação entre valores de soluções obtidas por metaheurı́sticas, considerando as implementações sequenciais. . . . . . . . . . . . . . . . . . . . . 107 6.4 Comparação entre as implementações sequencial e paralela do Tabu Search, com recálculo do valor da função objetivo com mudança no valor de uma variável. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108 6.5 Métricas para o TS com Recálculo com Mudança de uma Variável. . . . . . 108 6.6 Valores utilizados para os cálculos nas Lei de Amdahl da Tabela 6.5. Os tempos são dados em milissegundos. . . . . . . . . . . . . . . . . . . . . . . 110 6.7 Comparação entre as Implementações Sequencial e Paralela do TS com cálculo do valor da função objetivo na forma básica. . . . . . . . . . . . . . 111 6.8 Métricas para o TS com cálculo do valor da função objetivo na forma básica.111 6.9 Métricas para o TS com cálculo do valor da função objetivo na forma básica com memória compartilhada. . . . . . . . . . . . . . . . . . . . . . . . . . 112 6.10 Lei de Amdahl para o TS com cálculo do valor da função objetivo na forma básica. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112 6.11 Valores utilizados na Tabela 6.10 para o cálculo dos valores na Lei de Amdahl.113 10 LISTA DE TABELAS 11 6.12 Soluções e tempos obtidos pelo CPLEX. . . . . . . . . . . . . . . . . . . . 114 6.13 Médias e desvios padrão dos tempos de execução do CPLEX. . . . . . . . . 114 6.14 Resultados das implementações sequencial e paralelo do método Branch and Bound, com cálculo do valor da função objetivo na forma básica. . . . 116 6.15 Métricas para o B&B com cálculo do valor da função objetivo na forma básica. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117 Lista de Algoritmos 3.8.1 Pseudo-código do método básico do Tabu Search para o UQP . . . . . . . . 44 3.8.2 Pseudo-código da busca local TS do Algoritmo 3.8.1 . . . . . . . . . . . 46 3.8.3 Pseudo-código do método básico do VNS . . . . . . . . . . . . . . . . . . . 47 3.8.4 Pseudo-código da busca local VNS do Algoritmo 3.8.3 . . . . . . . . . . 47 3.8.5 Gradient Midpoint Method . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 3.9.1 Método Branch and Bound com busca em profundidade . . . . . . . . . . . 54 5.2.1 Pseudo-código do método Tabu Search paralelo com GPUs . . . . . . . . . 83 5.2.2 Pseudo-código do Kernel do Tabu Search . . . . . . . . . . . . . . . . . . . . 84 5.2.3 Pseudo-código do ReductionMax no Kernel do Tabu Search . . . . . . . . . 84 5.2.4 Pseudo-código do método Branch and Bound em paralelo com GPUs . . . . 90 12 Lista de Códigos-Fonte 5.1 Cópias de Dados e Chamada do Kernel no Tabu Search . . . . . . . . . . . 85 5.2 Kernel do Tabu Search . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86 5.3 Função ReductionMax() . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87 5.4 Cópias de Dados e Chamada do Kernel no Branch and Bound . . . . . . . 92 5.5 Kernel do Branch and Bound Parte 1 . . . . . . . . . . . . . . . . . . . . . 94 5.6 Kernel do Branch and Bound Parte 2 . . . . . . . . . . . . . . . . . . . . . 95 5.7 Kernel do Branch and Bound Parte 3 . . . . . . . . . . . . . . . . . . . . . 97 5.8 Kernel do Branch and Bound Parte 4 . . . . . . . . . . . . . . . . . . . . . 98 5.9 Kernel do Branch and Bound Parte 5 . . . . . . . . . . . . . . . . . . . . . 99 5.10 Função calculaBounds do Kernel no Branch and Bound . . . . . . . . . . . 99 5.11 Função calculaG cuda do Kernel no Branch and Bound . . . . . . . . . . . 100 5.12 Função calculaG aux cuda do Kernel no Branch and Bound . . . . . . . . 101 13 Lista de Sı́mbolos Sı́mbolo Descrição U Conjunto universo N Conjunto dos números naturais N∗ Conjunto dos números naturais excluindo o zero Q Conjunto dos números racionais Q∗+ Conjunto dos números racionais positivos Qm×n R Conjunto das matrizes de coeficientes racionais de dimensões m × n Conjunto dos números reais ∂f (x) ∂xi Derivada parcial da função f (x) com relação a variável xi ∇f (x) Vetor gradiente da função f (x) 0 Vetor nulo 0T = [0, 0, . . . , 0] 1 Vetor unitário 1T = [1, 1, . . . , 1] x∗ Solução viável ótima 14 Lista de Siglas Sigla B&B Descrição Branch and Bound CPLEX Solver da IBM CUDA Compute Unified Device Architecture MIP Módulo de Programação Linear Inteira Mista do CPLEX QIP Módulo de Programação Quadrática Inteira do CPLEX TS Tabu Search VNS Variable Neighborhood Search UQP Unconstrained binary Quadratic Program 15 Capı́tulo 1 Introdução Conforme Bixby [12], um problema de otimização combinatória é definido da seguinte maneira: Sejam E um conjunto finito, S uma famı́lia de subconjuntos de E e w ∈ R|E| uma função peso de valores reais definida sobre os elementos de E. O problema de otimização combinatória associado à tripla (E, S, w) é encontrar um conjunto S ∗ ∈ S tal que w(S ∗ ) = max w(S) S∈S onde w(S) = P w(e). e∈S Problemas de otimização combinatória são estudados em várias áreas de conhecimento como engenharia, administração, logı́stica, economia, biologia, finanças, marketing, planejamento entre outras. Estas áreas estudam maneiras para otimizar suas operações, mas comumente encontram dificuldades em aplicar os modelos desenvolvidos a problemas reais, pois estes envolvem muitas variáveis de decisão. Com o avanço da informática, os computadores tornaram-se ferramentas indispensáveis para a realização dos inúmeros cálculos, que a resolução dos problemas de otimização combinatória requerem. Um problema de muito interesse na área de otimização combinatória é o de empacotar itens (caixas) em objetos maiores (pallets). O manufacturer’s pallet loading problem é o problema de empacotar itens de dimensões idênticas. Este problema é equivalente ao de encontrar um conjunto independente máximo em um grafo finito particular. Conforme descrito na Seção 3.7.5, este pode ser posto na forma UQP. Um problema de otimização combinatória pode, normalmente, ser modelado de diversas maneiras. Para cada modelo podem existir várias técnicas que melhor o resolve. Cabe à pessoa que está solucionando o problema decidir qual técnica e modelo utilizar. Estas várias possibilidades podem tornar difı́cil o processo de resolução do problema. Portanto, seria interessante desenvolver um modelo unificado, que pudesse representar diversos problemas de otimização combinatória. Um modelo deste tipo existe e é definido 16 CAPÍTULO 1. INTRODUÇÃO 17 como segue. O problema quadrático binário irrestrito (em inglês, Unconstrained binary Quadratic Program - UQP) pode ser escrito na forma: max f (x) = xT Qx sujeito a: x ∈ {0, 1}n onde Q ∈ Qn×n e f : {0, 1}n → Q. Por Q e Qn×n queremos dizer: o conjunto dos números racionais e o conjunto das matrizes de coeficientes racionais de dimensão n × n, respectivamente. Outros sı́mbolos utilizados nesta dissertação aparecem na Tabela de Sı́mbolos na Página 15. Em termos de complexidade computacional, o UQP pertence a classe NP-difı́cil [50]. Conforme dito em [10], este problema recebe vários nomes na literatura, a saber: 1. 2. 3. 4. 5. 6. 7. Unconstrained Quadratic Bivalent Programming Problem; Unconstrained Quadratic Zero-One Programming Problem; Quadratic Zero-One Programming Problem; Unconstrained Pseudo-Boolean Quadratic Problem; Unconstrained Pseudo-Boolean Quadratic Zero-One Programming Problem; Boolean Quadratic Programming Problem; Binary Quadratic Program. As primeiras publicações sobre o UQP apareceram na década de 1960 [23]. Desde lá foram encontrados diversos problemas que podem ser modelados na forma desse problema. Alguns exemplos são: resolução de problemas de satisfatibilidade [13], predição de ataques epiléticos [30], determinação de cliques máximos em grafos [13, 45, 46] e escalonamento de máquinas [8]. Existem vários métodos que resolvem (i.e. métodos exatos), ou tentam resolver (i.e. métodos heurı́sticos), instâncias do UQP. Neste trabalho, desenvolvemos versões sequenciais e paralelas de implementações de métodos para o UQP. As versões paralelas utilizam unidades de processamento gráfico (em inglês, Graphics Processing Units - GPUs) da NVIDIA e a arquitetura CUDA. GPUs são dispositivos com muitos núcleos de processamento, que podem ser utilizados para processamento paralelo. Devido a escassez de estudos neste tema, que é investigar o desempenho de implementações de métodos para resolver o UQP com GPUs, estabelecemos os seguintes objetivos nesta pesquisa: • Criar implementações sequenciais de heurı́sticas e de métodos exatos para resolver o UQP; CAPÍTULO 1. INTRODUÇÃO 18 • Criar implementações paralelas utilizando GPUs, tanto para métodos existentes na literatura bem como para métodos que desenvolvemos; • Realizar um estudo computacional comparativo dos resultados gerados pelos métodos implementados. Alguns resultados produzidos neste trabalho foram apresentados nos seguintes eventos: 1. XVI ELAVIO 2012. Tı́tulo do trabalho: Implementações Paralelas de Métodos de Programação Quadrática Irrestrita usando GPUs. O UQP foi resolvido usando a metaheurı́stica Tabu Search e as implementações usaram CPU para o processamento sequencial e GPUs para realizar o paralelismo da aplicação. Os resultados mostraram que é possı́vel obter uma aceleração de até 8 vezes no tempo de processamento da implementação, com o uso de paralelismo com GPUs. 2. III ERAD 2012. Tı́tulo do trabalho: Explorando o Espaço de Soluções do UQP por Meio de um VNS Paralelo Usando Propriedades de um Hipercubo. O espaço de solução do UQP foi visto como um hipercubo, que foi explorado pela metaheurı́stica Variable Neighborhood Search. A implementação foi sequencial e verificamos que o método possui bons resultados quando comparados às soluções encontradas por algoritmos exatos. 3. XVI CLAIO / XLIV SBPO 2012. Tı́tulo do trabalho: Algoritmos Paralelos em GPUs para Problemas de Programação Quadrática Binária Irrestrita. Apresentamos o UQP como um framework e mostramos como é possı́vel converter várias classes de problemas de otimização combinatória para o modelo UQP. Demonstramos uma forma rápida de recálculo do valor da função objetivo. Um Tabu Search e um Variable Neighborhood Search foram implementados. Os resultados mostraram que a implementação de um Tabu Search obteve melhores resultados e consumiu menos tempo de computação. Os próximos parágrafos resumem os conteúdos dos capı́tulos desta dissertação. No Capı́tulo 2 apresentamos definições gerais incluindo algumas especı́ficas sobre paralelismo, que são utilizadas ao longo do texto. No Capı́tulo 3 apresentamos os problemas de Programação Quadrática e o de Programação Quadrática Binária Irrestrita, sendo este último o foco desta dissertação. Descrevemos trabalhos anteriores, que buscaram resolver instâncias do UQP. Mostramos como converter o UQP para um problema linear inteiro. Exibimos seis transformações que podem ser utilizadas para converter problemas de otimização combinatória para a forma UQP. Especificamente convertemos os problemas Empacotamento de Conjuntos, Partição CAPÍTULO 1. INTRODUÇÃO 19 de Conjuntos, Coloração de Vértices em um Grafo com K Cores, Conjunto Independente Generalizado e Carregamento de Objetos em Estrados. Mostramos os métodos heurı́sticos que tentam resolver o UQP, Tabu Search, Variable Neighborhood Search, Gradient Midpoint Method e uma Busca Local. Apresentamos um Branch and Bound para resolver o UQP de maneira exata. No Capı́tulo 4 definimos alguns conceitos sobre programação paralela com GPUs. Apresentamos o que são GPUs, detalhes da arquitetura de placas da NVIDIA, e o que nos motivou a estudar GPUs para acelerar a execução dos métodos discutidos no Capı́tulo 3. No Capı́tulo 5 mostramos como realizar o cálculo do valor da função objetivo do UQP, f (x) = xT Qx, na forma básica e algumas maneiras de recalcular f (x) quando o vetor x sofre pequenas alterações. Estas maneiras são: (1) o recálculo com mudança do valor de uma variável, (2) o recálculo com mudança do valor de uma variável sem alteração do vetor de solução e (3) o recálculo com mudança dos valores em várias variáveis em um dado intervalo. Apresentamos também o cálculo do limitante inferior à f (x) na forma básica, utilizado no método Branch and Bound. Mostramos também uma maneira de efetuar o recálculo do valor do limitante inferior à f (x) de forma eficiente, quando há mudança do valor de uma variável. No Capı́tulo 6 apresentamos os experimentos computacionais. Primeiro descrevemos o ambiente de testes, depois as instâncias testadas e os resultados obtidos, tanto por heurı́sticas quanto por métodos exatos. Comparamos os resultados obtidos entre as implementações de um Tabu Search e de um Variable Neighborhood Search. Também comparamos as implementações sequencial com a paralela de um Tabu Search. Exibimos os resultados de métricas de desempenho, obtidos com o paralelismo das implementações dos métodos Tabu Search e Branch and Bound utilizando GPUs. Finalmente, no Capı́tulo 7 apresentamos algumas conclusões e possı́veis trabalhos futuros. Capı́tulo 2 Definições Este capı́tulo apresenta as definições que são utilizadas ao longo desta dissertação, servindo inicialmente como um alicerce de conhecimento e posteriormente como uma fonte rápida de consulta. Dividimos as definições em duas seções, as definições gerais e as que mais se relacionam com paralelismo. 2.1 Definições Gerais Nesta seção apresentamos definições e conceitos gerais. Definição 2.1. Sejam I um intervalo de R com mais de um ponto e f : I → R uma ∂f (x) função. Diz-se que f é continuamente diferenciável se f for derivável no ∂(x) intervalo I e sua derivada de primeira ordem for contı́nua. Definição 2.2. Se um subconjunto A ⊂ R estiver contido em um intervalo [a, +∞), diz-se que A é limitado inferiormente. Se A estiver contido em um intervalo (−∞, b], diz-se que A é limitado superiormente. Se A ⊆ [a, b], para a < b, então, A é limitado superiormente e inferiormente. Nesse caso diz-se que A é um conjunto limitado. Definição 2.3 (Referência [32]). Diz-se que o ponto a é interior ao conjunto A ⊂ R, se existe um escalar > 0 tal que o intervalo aberto (a − , a + ) está contido em A. O conjunto dos pontos interiores a A é chamado interior do conjunto A e representado pela notação int(A). A ⊂ R é chamado conjunto aberto se A = int(A), isto é, quando todos os pontos de A são interiores a A. Definição 2.4 (Referência [32]). Diz-se que um ponto a é aderente ao conjunto X ⊂ R quando a é limite de alguma sequência de pontos xn ∈ X. Evidentemente, todo ponto a ∈ X é aderente a X: basta tomar todos os xn = a. Chama-se fecho de um conjunto X ao conjunto X formado por todos os pontos aderentes a X. Tem-se X ⊂ X. Se X ⊂ Y então X ⊂ Y . Um conjunto X é chamado de 20 CAPÍTULO 2. DEFINIÇÕES 21 conjunto fechado quando X = X, isto é, quando todo ponto aderente a X pertence a X. Definição 2.5. Um conjunto A ⊆ R chama-se conjunto compacto se ele é limitado e fechado. Definição 2.6. Um conjunto A ⊆ Rn é chamado conjunto convexo se todo segmento de reta ligando quaisquer dois elementos de A está contido em A. De maneira precisa, ∀x, y ∈ A e ∀t ∈ [0, 1] tem-se que: (1 − t)x + ty ∈ A Definição 2.7. Se x ∈ Rn e f : Rn → R for uma função, então o vetor gradiente de f (x) é definido como ∂f (x) ∂x 1 ∂f (x) ∂x2 ∇f (x) = .. . ∂f (x) ∂xn Definição 2.8. Em um problema de otimização combinatória, a função objetivo é uma função que se deseja minimizar ou maximizar: f : X → V , onde X e V são respectivamente o conjunto domı́nio e o contradomı́nio da função. Definição 2.9. Espaço de soluções, X, de um problema de otimização combinatória é o espaço com todas as soluções viáveis. Definição 2.10. O espaço de soluções X = {x ∈ Qn : 0 ≤ x ≤ 1} é chamado de cubo unitário. Definição 2.11. Soluções ótimas são todas as soluções pertencentes ao espaço de soluções, que maximizam/minimizam o valor de uma função objetivo. Definição 2.12. Uma função de vizinhança permite alcançar uma outra solução, dentro do espaço de soluções, seguindo um procedimento sistemático. Exemplo 2.1. Em um espaço de soluções, cujas soluções são representadas por vetores binários, uma função de vizinhança, Fv , chamada 1-Flip é o conjunto de soluções obtidas da seguinte maneira: Fv (x, i) = 1 − xi para algum i ∈ {1, ..., n}, onde x ∈ {0, 1}n Definição 2.13. Vizinhança de uma solução s, denotada por N (s), é um subespaço do espaço de soluções, onde a partir desta solução pode-se gerar este subespaço por meio de uma função de vizinhança Fv . Exemplo 2.2. N (s) = Fv (s, i) para algum i ∈ {1, 2, ..., n}, onde n é o número de variáveis do vetor solução. CAPÍTULO 2. DEFINIÇÕES 22 Definição 2.14. Uma vizinhança k-Flip de uma solução s é formada por vetores que são diferentes de s em exatamente k posições. Definição 2.15. Um ótimo local, é uma solução s que tem o maior ou menor valor em uma vizinhança N (s), dependendo se o problema é de maximização ou de minimização. Definição 2.16. Heurı́stica é definida por um conjunto de regras e métodos que têm como objetivo resolver determinados problemas, mas sem garantias de se encontrar soluções ótimas. Definição 2.17. Metaheurı́stica é definida por um conjunto de regras e métodos que podem ser aplicados para resolver várias classes de problemas, mas sem garantias de encontrar soluções ótimas. Definição 2.18. Método exato é definido por um conjunto de regras aplicadas de forma sistemática a solucionar um problema. Com um método exato tem-se a garantia de encontrar soluções ótimas. Definição 2.19. |C| representa a cardinalidade do conjunto finito C, ou seja, é o número de elementos do conjunto. Definição 2.20. Denotamos por Xk a famı́lia de todos os subconjuntos de X com k elementos, isto é: X = {A : A ⊆ X e |A| = k} k Definição 2.21. Um grafo é um objeto matemático G = (V, E), onde V é um conjunto finito não vazio de elementos denominados vértices e E é um conjunto de pares não ordenados de elementos distintos de V denominados arestas, ou seja, E ⊆ V2 . Cada aresta indica que dois vértices possuem uma relação ou estão ligados. Definição 2.22. Dado um grafo G = (V, E), um caminho em um grafo é uma sequência de vértices v1 , v2 , ..., vk tal que (vi , vi+1 ) ∈ E ∀ 1 ≤ i ≤ k − 1. Definição 2.23. Um hipercubo n-dimensional é um grafo não-orientado contendo 2n vértices rotulados de forma binária, de 0 a 2n − 1, contendo arestas entre dois vértices se e somente se seus rótulos binários diferem em apenas um bit [53]. Um hipercubo satisfaz as seguintes propriedades: 1. Um vértice é ligado a outro através de uma aresta se e somente se as suas numerações, em binário, diferem em apenas um bit. 2. Dados dois vértices quaisquer do hipercubo n-dimensional, existe um caminho entre eles contendo no máximo n arestas. 3. É sempre possı́vel, a partir de um hipercubo com n dimensões, para n > 1, criar dois novos hipercubos com dimensão (n − 1), removendo 2(n−1) arestas. CAPÍTULO 2. DEFINIÇÕES 23 4. Dois vértices adjacentes não compartilham um outro vértice em sua vizinhança. As figuras abaixo mostram exemplares de hipercubos. 1110 1011 1111 1010 110 111 0110 010 0010 011 0111 0011 0100 0000 100 0101 0001 101 1101 000 001 1000 1100 1001 Como o espaço de solução do UQP é um hipercubo n-dimensional, podemos utilizar as propriedades acima para definir estruturas de vizinhança e explorá-las de forma paralela. Outras propriedades sobre hipercubos podem ser vistas na referência [53]. As metaheurı́sticas Tabu Search e Variable Neighborhood Search apresentadas, respectivamente, nas Seções 3.8.1 e 3.8.3, utilizam essas propriedades para “caminhar” pelos hipercubos dos espaços de soluções. Uma outra maneira de tentar resolver instâncias do UQP é via um método exato, que apresentamos na Seção 3.9.2. 2.2 Definições sobre Computação Paralela Nesta seção apresentamos definições e conceitos relacionados ao paralelismo das implementações. Definimos métricas e termos utilizados para analisar programas paralelos. Existem várias métricas que permitem avaliar o desempenho de aplicações paralelas, tendo como base a comparação entre a execução com múltiplos processadores e a execução com um só processador. Algumas delas levam em consideração aspectos que são desconsiderados por outras, como por exemplo, o tempo de comunicação entre processos. No Capı́tulo 6, de experimentos computacionais, usamos as definições destas métricas para analisar os resultados obtidos com as implementações dos algoritmos apresentados nesta dissertação. Definição 2.24 (Referência [54]). Processo é um programa de computador em execução. Definição 2.25. Uma thread pode ser vista como a forma de um processo dividir-se em duas ou mais tarefas, que podem ser executadas de forma concorrente. Segundo Tanenbaum [54], um processo de um sistema operacional tem uma única linha de controle e um contador de programa para cada processo. Entretanto, alguns sistemas CAPÍTULO 2. DEFINIÇÕES 24 operacionais modernos fornecem suporte para múltiplas linhas de controle dentro de um processo. Essas linhas de controle são chamadas de threads ou processos leves. Definição 2.26. O speedup mede quantas vezes mais rápido é a execução de um programa em paralelo em relação ao sequencial. Formalmente temos: S(p) = T (1) , T (p) onde T (1) é o tempo de execução com 1 processador e T (p) é o tempo de execução com p processadores. De forma geral: 0 ≤ S(p) ≤ p. Se S(p) = p então o speedup é chamado de linear. É raro ocorrer speedup linear, pois na maioria das aplicações paralelas há perda de tempo de processamento devido a sobrecargas, que podem ocorrer devido à distribuição dos dados e pela comunicação entre os processos. Algumas situações podem ocorrer com o speedup em aplicações paralelas, a saber: (a) Slowdown: T (1) < T (p) e portanto S(p) < 1. (b) Sublinear: 1 < S(p) < p. (c) Linear: S(p) = p. (d) Supralinear: p < S(p). A situação (a) é indesejável, (b) é o comportamento mais comum, (c) é a situação ideal, mas só ocorre se não houver sobrecarga e (d) é possı́vel. Um speedup supralinear ocorre, por exemplo, quando um programa sequencial não consegue armazenar todos os dados necessários para o processamento na memóra RAM, enquanto a versão distribuı́da consegue, ao utilizar mais computadores em que cada computador resolve parte do problema sem a necessidade de acessar o disco rı́gido novamente durante sua execução. Definição 2.27. A Eficiência mede o aproveitamento dos recursos computacionais disponı́veis. Ela é a razão entre o speedup S(p) e o número de processadores utilizados. Formalmente temos: T (1) S(p) = E(p) = p p × T (p) Definição 2.28. O Trabalho de um programa é a soma dos tempos de execução de cada processo. Formalmente: W (p) = pT (p) Definição 2.29. Sobrecarga é a diferença entre o trabalho realizado por um programa paralelo e sua versão sequencial. Formalmente: T0 = pT (p) − T (1) CAPÍTULO 2. DEFINIÇÕES 25 Definição 2.30. A Lei de Amdahl diz: Seja 0 ≤ f ≤ 1 a fração da computação que só pode ser realizada sequencialmente, o speedup máximo que uma aplicação paralela com p processadores pode obter é: 1 S(p) ≤ f + 1−f p O Capı́tulo 3 apresenta trabalhos anteriores, que tratam de técnicas que foram propostas para o problema estudado nesta dissertação. Capı́tulo 3 Trabalhos Anteriores A área de pesquisa que desenvolve métodos para resolver o UQP recebe grande atenção há mais de 50 anos [23]. Esta área se mantém ativa em virtude do vasto número de problemas, de otimização combinatória, que podem ser expressos na forma UQP. Assim, criar um bom método para resolver o UQP, pode significar criar uma boa maneira de solucionar vários problemas de otimização combinatória. A lista de trabalhos de pesquisa sobre UQP é extensa e pode ser dividida pelo tipo de método proposto: heurı́stico [2, 10, 35, 36, 37, 38, 44] ou exato [11, 14, 17, 28, 45, 56]. Esta é uma pequena lista do que foi publicado sobre o assunto. 3.1 Programação Quadrática Na sua forma geral, um problema de programação quadrática é representado da seguinte forma: 1 max f (x) = cT x + xT Qx 2 sujeito a: Ax ≤ b, Bx = d x≥0 onde Q ∈ Qn×n , A ∈ Qm×n , B ∈ Qk×n , c ∈ Qn , b ∈ Qm , d ∈ Qk e x é um vetor com variáveis de decisão em Qn . Um problema de otimização expresso por uma função objetivo quadrática e restrições lineares é denominado problema de programação quadrática. A programação quadrática tem uma grande importância dentro dos estudos de otimização combinatória, pois é possı́vel modelar diversos tipos de problemas dessa forma. 26 CAPÍTULO 3. TRABALHOS ANTERIORES 3.2 27 Programação Quadrática Binária Irrestrita Uma classe de problemas de programação quadrática que não contém restrições é chamada de programação quadrática irrestrita. Se nestes problemas as variáveis são binárias então eles são chamados problema de programação quadrática binária irrestrita, que chamaremos de UQP nesta dissertação. É possı́vel converter um problema de programação quadrática com restrições para um sem restrições, adicionando à função objetivo uma função de penalidade. Isto é explicado em detalhes na Seção 3.6. O problema quadrático binário irrestrito pode ser escrito na seguinte forma: max f (x) = cT x + 21 xT Qx onde c ∈ Qn , Q ∈ Qn×n e x ∈ {0, 1}n . Na próxima seção apresentamos uma breve revisão da literatura sobre métodos heurı́sticos e exatos que tratam o UQP. s 3.3 Artigos na Literatura A quantidade de trabalhos sobre o UQP apresentada na literatura é consideravelmente grande. Nesta seção fazemos uma revisão dos trabalhos que consideramos os mais relacionados à nossa pesquisa. As publicações desta revisão são separadas de acordo com o tipo de método abordado, heurı́stico ou exato. 3.3.1 Métodos Heurı́sticos Beasley [10] apresenta duas heurı́sticas para tentar resolver o UQP, um Tabu Search que se baseia em uma lista de restrições que buscam evitar com que o método fique preso em um ótimo local e um Simulated Annealing. As instâncias de teste estão disponı́veis na OR Library (ver Seção 6.2.1) e possuem tamanhos de até 2.500 variáveis. Bahram et al. [2] apresentam várias heurı́sticas “one-pass” para tentar resolver o UQP. Em seus resultados computacionais, eles apresentam estudos com instâncias de até 9.000 variáveis. Eles afirmam que estas heurı́sticas fornecem bons pontos de partida para métodos mais sofisticados. Merz e Freisleben [36] apresentam uma heurı́stica gulosa e duas heurı́sticas de busca local, 1-opt e k-opt, e informam que estes métodos também podem ser incorporados a CAPÍTULO 3. TRABALHOS ANTERIORES 28 metaheurı́sticas. Esses métodos foram testados em 115 instâncias de até 2.500 variáveis, apresentando soluções cujos valores são próximos aos melhores conhecidos. Merz e Freisleben [35] descrevem um algoritmo genético. Eles afirmam que para instâncias pequenas, um crossover uniforme é suficiente para encontrar soluções ótimas ou com valores iguais às melhores conhecidas. Para instâncias com mais de 200 variáveis, se faz necessário incorporar uma estratégia de busca local. Em seus estudos computacionais foram testadas instâncias de até 2.500 variáveis. Eles comparam o algoritmo com outros métodos, como Simulated Annealing e Tabu Search. Bahram et al. [4] discutem o uso do UQP como um framework para resolução de vários problemas. Tais problemas são modelados na forma UQP. Em [3], os autores apresentam o problema de coloração de vértices modelado como um UQP. Em [5], um novo modelo para o Number Partitioning Problem é estudado. Em [7], o problema de encontrar um clique máximo, com peso nas arestas, é modelado com o framework proposto. Em [6], o problema de encontrar conjuntos independentes generalizado é escrito na forma UQP. Todos estes artigos utilizam um Tabu Search ou variações deste método para resolver os problemas. 3.3.2 Métodos Exatos Palubeckis [44] apresenta um algoritmo de busca em árvore, incorporando soluções fornecidas por uma heurı́stica. Os resultados computacionais são apresentados para instâncias de até 247 variáveis. Pardalos e Rodgers [48] apresentam um método Branch and Bound, que usa limitantes baseados em fixação de variáveis para cada vértice da árvore de enumeração. Este método utiliza um teste que tenta fixar variáveis e um cálculo de limitantes baseado em um vetor gradiente da função objetivo. Os autores apresentam uma classe de instâncias que possui um número exponencial de ótimos locais, bem como dois programas em FORTRAN para gerar instâncias. Em seus experimentos computacionais foram testadas instâncias de até 200 variáveis. Billionet e Sutter [11] descrevem um método Branch and Bound. O limitante inferior é calculado em três fases para cada vértice da árvore de busca. A primeira fase é feita sobre o problema dual, que pode ser visto em detalhes em [1, 22, 56]. Os resultados computacionais utilizam instâncias de até 100 variáveis. Helmberg e Rendl [28] apresentam um método Branch and Bound que combina Relaxação Semidefinida com a técnica de Planos de Corte. Seus experimentos computacionais mostram que esta é uma técnica robusta e foram testadas instâncias de até 100 variáveis. Chardaire e Sutter [17] apresentam um método de decomposição para calcular limitan- CAPÍTULO 3. TRABALHOS ANTERIORES 29 tes para o UQP. Primeiro mostram que qualquer função quadrática pode ser decomposta em uma soma particular de funções quadráticas, que podem ser resolvidas facilmente por um métodos Branch and Bound. Assumindo algumas hipóteses, provam que entre todas as possı́veis decomposições, a decomposição Lagrangiana (ou Lagrangeana) encontra os melhores resultados. Seus experimentos computacionais foram realizados para instâncias de até 100 variáveis. Pardalos e Rodgers [45] apresentam um método Branch and Bound, com seleção dinâmica de variáveis para fixação para resolver o problema de encontrar cliques máximos em um grafo. Os resultados computacionais mostram experimentos com instâncias de até 1.000 vértices e 150.000 arestas. Adams e Dearing [1] apresentam três técnicas para computar limitantes superiores na função objetivo. Tais limitantes são importantes para reduzir o número de buscas feitas pela implementação do algoritmo. A primeira técnica gera duas classes de funções lineares. A segunda resolve uma relaxação contı́nua de um problema de programação linear inteira mista. A terceira utiliza complementação quadrática de variáveis. Embora o UQP pertença à classe NP-difı́cil, algumas de suas instâncias podem ser resolvidas em tempo polinomial, em relação ao número de variáveis. Barahona [9], Chakradhar e Bushnell [16], Gu [21] e Li et al. [31], discutem caracterı́sticas de tais instâncias e métodos para resolvê-las. É possı́vel modelar o UQP como um problema de Programação Linear Inteiro, como é mostrado na Seção 3.4. 3.4 UQP Linearizado O UQP pode ser transformado em um programa linear binário pela linearização do termo quadrático, como demonstra Beasley em [10]. Se yij é a variável que representa o produto xi xj , então o UQP pode ser escrito na seguinte forma: max f (x) = n X n X qij yij i=1 j=1 sujeito a: yij ≤ xi i = 1, . . . , n; j = 1, . . . , n (3.1) yij ≤ xj i = 1, . . . , n; j = 1, . . . , n (3.2) yij ≥ xi + xj − 1 i = 1, . . . , n; j = 1, . . . , n (3.3) yij ∈ {0, 1} i = 1, . . . , n; j = 1, . . . , n (3.4) xi ∈ {0, 1} i = 1, . . . , n (3.5) CAPÍTULO 3. TRABALHOS ANTERIORES 30 As restrições (3.1) e (3.2) garantem que se xi ou xj tiver valor zero, yij também deverá ter seu valor igual a zero. A restrição (3.3) garante que yij somente terá seu valor igual a um, se ambos xi e xj tiverem valor igual à um. As restrições (3.4) e (3.5) são de integralidade. As vantagens de se linearizar o UQP são: pode-se utilizar solvers disponı́veis para programação linear inteira e também fazer um estudo poliédrico do problema. A desvantagem é que o número de variáveis cresce rapidamente neste modelo linearizado, a saber na ordem de n2 , onde n é o número de variáveis no problema quadrático. Na próxima seção apresentamos uma lista de problemas que podem ser modelados na forma UQP. 3.5 Lista de Problemas Modelados na Forma UQP Vários problemas de otimização combinatória podem ser modelados na forma UQP, utilizando as transformações descritas na Seção 3.6. Dentre eles, citamos os problemas apresentados na referência [4]: Quadratic Assignment Problems Multiple Knapsack Problems Maximum Diversity Problems Asymmetric Assignment Problems Side Constrained Assignment Problems Constraint Satisfaction Problems Fixed Charge Warehouse Location Problems Maximum Independent Set Problems Graph Coloring Problems Number Partitioning Problems Linear Ordering problems 3.6 Capital Budgeting Problems Task Allocation Problems P-Median Problems Symmetric Assignment Problems Quadratic Knapsack Problems Set Partitioning Problems Maximum Clique Problems Maximum Cut Problems Graph Partitioning Problems Linear Ordering Problems Satisfiability Problems Transformações Gerais Um fato que torna o UQP muito útil é o de que diversos problemas de otimização combinatória podem ser convertidos em sua forma, via transformações simples. Há alguns artigos na literatura que mostram algumas maneiras de transformar problemas de otimização combinatória para a forma UQP (ver referências [3, 5, 7, 6]). O que apresentamos nessa seção é uma sı́ntese, de forma padronizada, das transformações utilizadas nestes trabalhos. Isso pode, eventualmente, possibilitar ao leitor desta dissertação, tentar converter outros problemas para a forma UQP. CAPÍTULO 3. TRABALHOS ANTERIORES 31 Transformação 1 Em muitos problemas de otimização combinatória a função objetivo, ou parte dela, é dada em uma forma linear com variáveis binárias: aT x min / max onde a ∈ Qn e x ∈ {0, 1}n . Por min / max queremos dizer que deseja-se minimizar ou maximizar a função. Este problema também pode ser escrito como: n X min / max ai xi i=1 Para converter estes problemas para o formato UQP, basta utilizar o mesmo vetor x de variáveis e construir a matriz Q ∈ Qn×n da seguinte forma: ( qi,j = ai se i = j 0 caso contrário. A prova de que o problema min / max aT x é equivalente ao problema min / max xT Qx, sendo Q uma matriz diagonal contendo os valores qii = ai é feita por meio dos seguintes argumentos: n X n X T x Qx = qi,j xi xj i=1 j=1 n X aT x = ai x i i=1 Como qi,j = 0 para i 6= j e x2i = xi pois xi ∈ {0, 1} para 1 ≤ i ≤ n, temos que n X n X i=1 j=1 qi,j xi xj = n X qi,i x2i i=1 = n X qi,i xi = i=1 n X ai x i i=1 Transformação 2 Vários problemas de otimização combinatória podem ser escritos na forma: min / max xT Qx sujeito a: Ax = b x ∈ {0, 1}n CAPÍTULO 3. TRABALHOS ANTERIORES 32 onde Q ∈ Qn×n , A ∈ Qm×n e b ∈ Qm . A seguinte transformação consiste em adicionar uma função de penalidade à função objetivo. Isto é, adicionando uma função de penalidade quadrática multiplicada por um escalar P , sendo P > 0 se o problema é de minimização e P < 0 se o problema é de maximização [23]. O modelo resultante para a transformação é xT Qx + P (Ax − b)T (Ax − b) min / max sujeito a: x ∈ {0, 1}n Lembramos que dadas duas matrizes, C e D de dimensões apropriadas, as operações (C − D)T = C T − DT e (CD)T = DT C T são válidas. Assim xT Qx + P (Ax − b)T (Ax − b) = xT Qx + P ((Ax)T − bT )(Ax − b) = xT Qx + P (xT AT − bT )(Ax − b) = xT Qx + P (xT AT Ax − xT AT b − bT Ax + bT b) = xT Qx + P (xT AT Ax − (Ax)T b − bT Ax + bT b) (3.6) = xT Qx + P (xT AT Ax − 2bT Ax + bT b) = xT Qx + P xT AT Ax − 2P bT Ax + P bT b = xT Qx + xT P AT Ax − v T x + P bT b onde v T = 2P bT A. Note que em (3.6), (Ax)T b = bT (Ax), pois se fizermos z = Ax, então (Ax)T b = z T b = bT z = bT (Ax). Como o vetor x é binário, segue que v T x = xT U x para a matriz U ∈ Qn×n construı́da da seguinte maneira: ( vi se i = j ui,j = 0 caso contrário. Assim, xT Qx + xT P AT Ax − v T x + P bT b = = = = xT Qx + xT P AT Ax − xT U x + P bT b xT Qx + xT (P AT A − U )x + P bT b xT (Q + P AT A − U )x + c xT Q̂x + c onde Q̂ = Q + P AT A − U e c = P bT b. Portanto, podemos finalmente escrever o problema na forma UQP: min / max xT Q̂x CAPÍTULO 3. TRABALHOS ANTERIORES 33 onde x ∈ {0, 1}n e Q̂ ∈ Qn×n . Transformação 3 Alguns problemas de otimização combinatória possuem restrições do tipo: xi + xj ≤ 1 para 1 ≤ i, j ≤ n, com i 6= j e x ∈ {0, 1}n . Neste caso adicionamos P xi xj à função objetivo para 1 ≤ i, j ≤ n. Isto pode ser escrito usando uma matriz simétrica D ∈ Qn×n com coeficientes: ( 1 se existe a restrição do tipo xi + xj ≤ 1, com i 6= j 2 di,j = 0 caso contrário Então, a Transformação 3 consiste em adicionar xT P Dx à função objetivo. Na Seção 3.7.2 mostramos um exemplo do uso da Transformação 3 quando modelamos o problema Set Partitioning. Transformação 4 De forma similar ao que foi visto na Transformação 3, podemos transformar restrições da forma xi ≤ xj . Neste caso precisamos adicionar à função objetivo o produto P xi (1−xj ). Definimos uma matriz simétrica M da seguinte maneira mi,j t se i = j e existir a restrição xi ≤ xk , para algum k = − 1 se existir a restrição xi ≤ xj 2 0 caso contrário Onde 1 ≤ i, j, k ≤ n e t é a quantidade de restrições existentes para xi . Exemplo: Se existirem duas restrições para xi (xi ≤ xj e xi ≤ xk ) então t = 2. Assim, a Transformação 4 consiste em adicionar xT P M x à função objetivo. Transformação 5 Suponha que o problema tem restrições em variáveis binárias de forma que x ∈ {0, 1}n×m . Neste caso x é uma matriz e a transformamos em um vetor da seguinte maneira: x̂k = x̂(i−1)m+j = xi,j (1 ≤ i ≤ n) e (1 ≤ j ≤ m) . onde k = (i − 1)m + j. Esta transformação permite utilizar um vetor, com as variáveis de decisão, ao invés CAPÍTULO 3. TRABALHOS ANTERIORES 34 de uma matriz. Transformação 6 Há casos em que existem mais de um vetor de variáveis binárias. Suponha que temos x ∈ {0, 1}n e y ∈ {0, 1}m . Podemos associá-los da seguinte maneira: ( x̂i = xi se 1 ≤ i ≤ n yi−n se n + 1 ≤ i ≤ n + m De acordo com Kochenberger e Glover [4], é sempre possı́vel escolher um escalar P suficientemente grande ou pequeno, de tal forma que o conjunto de soluções ótimas são iguais tanto para o problema original quanto para o problema o quadrático, após a sua transformação. Na Seção 3.7 mostramos exemplos, de problemas de otimização combinatória, que podem ser modelados na forma UQP utilizando as transformações descritas nesta seção. 3.7 Problemas de Otimização Combinatória Modelados na Forma UQP Nesta seção mostramos como transformar para a forma UQP alguns problemas de otimização combinatória. Nominalmente convertemos os seguintes problemas para a forma UQP: Empacotamento de Conjuntos [4], Partição de Conjuntos [4], Coloração de Vértices em um Grafo com K Cores [4], Conjunto Independente Generalizado [6] e Carregamento de Objetos em Estrados [18, 39]. 3.7.1 Empacotamento de Conjuntos (Set Packing Problem) Este problema tem inúmeras aplicações. Em [55] apresenta um extensivo survey do problema de empacotamento de conjuntos e modelos relacionados. Definição 3.1. Sejam A um conjunto finito não vazio, F um conjunto não vazio de subconjuntos de A. A cada elemento f ∈ F está associado um custo cf ∈ Q∗+ . O problema de empacotamento de conjuntos consiste em encontrar uma coleção de conjuntos disjuntos em F cuja a soma dos custos seja máxima. Uma formulação, em programação linear inteira, para o problema de empacotamento CAPÍTULO 3. TRABALHOS ANTERIORES 35 de conjuntos é: max f (x) = cT x Ax ≤ 1 Sujeito a: x ∈ {0, 1}n onde c ∈ Qn+ e Am×n é uma matriz de coeficientes 0/1. Para mostrar como podemos transformar o modelo acima para o formato UQP, utilizaremos a Transformação 1 na função objetivo e a Transformação 3 em cada restrição. Assim a função objetivo pode ser reescrita da seguinte forma: T c x= n X cj x j = n X n X j=1 qij xi xj = xT Qx i=1 j=1 onde qjj = cj e qij = 0 para i 6= j, e as restrições podem ser reescritas da seguinte maneira: n X aij xj ≤ 1 para i = 1, 2, . . . , m j=1 Considerando somente as parcelas em que aij = 1 para i = 1, 2, . . . , m e j = 1, 2, . . . , n temos n X xj ≤ 1 j=1 que pode ser escrita como uma função de penalidade, que é adicionada a função objetivo P n−1 X n X xi xj i=1 j=i+1 sendo P < 0 para problemas de maximização. Exemplo 3.1 (Referência [4]). Encontrar variáveis que resolvem: max f (x) Sujeito a: = x1 + x2 + x3 + x4 x1 + x3 + x4 ≤ 1 x1 + x2 ≤ 1 x ∈ {0, 1}4 Reescrevendo o problema na forma UQP e utilizando P = −2M onde M ∈ Q, temos: max f (x) = x1 + x2 + x3 + x4 − 2M x1 x3 − 2M x1 x4 − 2M x3 x4 − 2M x1 x2 que pode ser escrito como CAPÍTULO 3. TRABALHOS ANTERIORES 36 x1 0 −M −M −M x1 1 0 0 0 x2 −M 0 1 0 0 x2 0 0 0 max f (x) = [x1 , x2 , x3 , x4 ] 0 0 1 0 x + [x1 , x2 , x3 , x4 ] −M 0 0 −M x3 3 x4 −M 0 −M 0 x4 0 0 0 1 x1 0 −M −M −M 1 0 0 0 x2 0 1 0 0 −M 0 0 0 = [x1 , x2 , x3 , x4 ] x 0 0 1 0 + −M 0 0 −M 3 x4 −M 0 −M 0 0 0 0 1 x1 1 −M −M −M x2 −M 1 0 0 = [x1 , x2 , x3 , x4 ] x −M 0 1 −M 3 x4 −M 0 −M 1 3.7.2 Particionamento de Conjuntos (Set Partitioning) Nesta subseção mostramos como transformar o problema de particionamento de conjuntos para forma UQP. Primeiro definimos o que é uma partição de conjuntos e depois apresentamos formalmente o problema em questão. A transformação é feita em seguida. Definição 3.2. Seja A um conjunto finito e não vazio. Uma partição de A é uma famı́lia de subconjuntos não vazios A1 , A2 , . . . , An tais que: 1. n S Ai = A; i=1 2. Ai ∩ Aj = ∅, se i 6= j. Definição 3.3. Sejam A um conjunto finito não vazio, F um conjunto não vazio de subconjuntos de A. A cada elemento f ∈ F está associado um custo cf ∈ Q∗+ . O problema de particionamento de conjuntos consiste em encontrar um subconjunto de F que defina uma partição de A, cuja a soma dos custos seja mı́nima. Uma formulação, em programação linear inteira, para o problema de Particionamento de Conjuntos é: min f (x) = cT x Sujeito a: Ax = 1 x ∈ {0, 1}n onde c ∈ Qn+ e A é uma matriz de coeficientes 0/1. CAPÍTULO 3. TRABALHOS ANTERIORES 37 Para mostrar como podemos transformar o modelo acima para o formato UQP, utilizaremos a Transformação 1 na função objetivo e a Transformação 2 nas restrições. Assim a função objetivo pode ser reescrita da seguinte forma: T c x= n X cj x j = j=1 n X n X qij xi xj = xT Qx i=1 j=1 onde qjj = cj e qij = 0 para i 6= j. As restrições podem ser reescritas sob a forma de uma função de penalidade, que é adicionada à função objetivo da seguinte maneira: P (Ax − 1)T (Ax − 1) sendo P > 0 para problemas de minimização. Desta forma o problema acima torna-se: min f (x) = xT Q̂x Sujeito a: x ∈ {0, 1}n Exemplo 3.2 (Referência [4]). Encontrar variáveis que resolvem: min f (x) Sujeito a: = 3x1 + 2x2 + x3 + x4 + 3x5 + 2x6 x1 + x3 + x6 = 1 x2 + x3 + x5 + x6 = 1 x3 + x4 + x5 = 1 x1 + x2 + x4 + x6 = 1 x ∈ {0, 1}6 Reescrevendo o problema na forma UQP f (x) = xT Q̂x + c onde Q̂ = Q + P AT A − U , c = P bT b, v = 2P bT A e U é uma matriz diagonal tal que uii = vi para i = 1, 2, . . . , n e uij = 0 para i = 6 j. CAPÍTULO 3. TRABALHOS ANTERIORES 1 0 1 T min f (x) = x Q + P 0 0 1 2P P P T = x Q + P 0 2P 3 0 0 0 2 0 0 0 1 = xT 0 0 0 0 0 0 0 0 0 −2P + 3 P P = xT P 0 2P 0 1 1 0 1 1 0 0 1 1 1 0 P 2P P P P 2P 0 0 0 1 0 0 1 1 1 0 0 1 0 0 1 1 0 1 0 1 P P 3P P 2P 2P 0 0 0 0 3 0 1 1 1 0 P 0 P P P 2P 2P P P 2P P P −2P 0 P 0 0 P + 0 P 0 0 2P 2 P −2P + 2 P P P 2P P P −3P + 1 P 2P 2P 38 4 0 0 0 1 0 0 1 1 −P 0 1 1 0 0 1 0 1 0 4P 0 2P 2P 0 4P 0 2P 0 − 0 0 P 0 P 0 0 0 3P P −2P P P P 2P P P −3P P 2P 2P P P P −2P + 1 P P 0 4 0 0 0 0 0 0 6P 0 0 0 P P P −2P P P 0 P 2P P −2P + 3 P 0 0 6 0 0 0 0 0 0 4P 0 0 0 P 2P P −2P P 0 0 0 4 0 0 0 0 0 0 4 0 0 0 0 0 4P 0 0 0 0 x + 4P 0 0 6 0 0 0 x + 4P 0 0 6P 2P 2P 2P x + 4P P P −3P 2P 2P 2P x + 4P P P −3P + 2 Utilizando P = 10, temos: min f (x) = −17 10 10 10 0 20 x1 10 −18 10 10 10 20 x2 10 10 −29 10 20 20 x3 [x1 , x2 , x3 , x4 , x5 , x6 ] + 40 10 10 10 −19 10 10 x4 0 10 20 10 −17 10 x5 20 20 20 10 10 −28 x6 CAPÍTULO 3. TRABALHOS ANTERIORES 3.7.3 39 Coloração de Vértices em um Grafo com no máximo K Cores (K-Coloring) Nesta subseção tratamos o problema de coloração de vértices em um grafo. Primeiro, formulamos o problema geral, em seguida apresentamos o problema para o caso em que o número de cores é fixo. Definição 3.4. Dado um grafo não dirigido G = (V, E), onde V é o conjunto finito não vazio de vértices e E é o conjunto de arestas. Deseja-se atribuir cores para os vértices de G, de forma que os vértices adjacentes recebam cores diferentes. O problema de coloração de vértices com no máximo K cores em G consiste encontrar uma coloração que utilize até K cores, onde k ≤ |V |. As variáveis do modelo são definidas como segue: ( 1 Se a cor p for utilizada na coloração 0 Caso contrário ( 1 Se a cor p é atribuı́da ao vértice i 0 Caso contrário yp = xip = Este problema pode ser modelado, em programação linear inteira, da seguinte maneira: min f (x) = k X yp p=1 Sujeito a: k X xip = 1 i = 1, 2, . . . , n (3.7) ∀(i, j) ∈ E e p = 1, 2, . . . , k (3.8) ∀i ∈ V e p = 1, 2, . . . , k (3.9) p=1 xip + xjp ≤ 1 xip ≤ yp x ∈ {0, 1}n×k y ∈ {0, 1}k onde k é o número máximo de cores permitidas na coloração e n = |V |. A restrição (3.7) garante que cada vértice será colorido com exatamente uma cor. A restrição (3.8) garante uma coloração viável. A restrição (3.9) diz que um vértice somente poderá ser colorido com uma cor se esta estiver sido escolhida para a coloração. Obtemos o modelo no formato UQP aplicando a seguinte sequência de operações: Transformação 1 para a função objetivo, a Transformação 2 para as restrições (3.7), a Transformação 3 para as restrições (3.8) e a Transformação 4 para as restrições (3.9). CAPÍTULO 3. TRABALHOS ANTERIORES 40 Agora tratamos o caso especı́fico do problema, em que o número de cores é fixo. Definição 3.5. Dado um grafo não dirigido G = (V, E), onde V é o conjunto finito não vazio de vértices e E é o conjunto de arestas. Deseja-se atribuir cores para os vértices de G, de forma que os vértices adjacentes recebam cores diferentes. No problema de coloração de vértices, em um grafo, com exatamente K cores consiste em encontrar uma atribuição que utilize k cores, onde K ≤ |V |. O conjunto de restrições para este problema pode ser modelado, em programação linear inteira, da seguinte maneira: K X xip = 1 i = 1, 2, . . . , n p=1 xip + xjp ≤ 1 ∀(i, j) ∈ E e p = 1, 2, . . . , K x ∈ {0, 1}n×K onde n = |V |. Note que as restrições são similares àquelas para o modelo geral, com diferença de que o número máximo de cores, K, é conhecido e que não há função objetivo. Para converter as restrições acima para o formato UQP, utilizamos as mesmas operações indicadas no caso geral. Exemplo 3.3 (Referência [4]). Considere o grafo na Figura 3.1 e assuma que desejamos encontrar uma coloração dos vértices utilizando 3 cores. 1 5 2 4 3 Figura 3.1: Grafo de Exemplo para o Problema de Coloração em Vértices O conjunto de restrições a serem satisfeitas são: xi1 + xi2 + xi3 = 1 i = 1, 2, . . . , 5 xip + xip ≤ 1 p = 1, 2, 3 O problema no formato UQP torna-se: min f (x) = xT Q̂x ∀(i, j) ∈ E CAPÍTULO 3. TRABALHOS ANTERIORES 41 onde Q̂ é: Q̂ = −4 4 4 4 0 0 0 0 0 0 0 0 4 0 0 4 4 4 0 0 0 0 0 0 0 0 4 0 0 −4 4 0 4 0 0 0 0 0 0 0 0 4 0 4 −4 0 0 4 0 0 0 0 0 0 0 0 4 0 0 −4 4 4 4 0 0 4 0 0 4 0 0 4 0 4 −4 4 0 4 0 0 4 0 0 4 0 0 4 4 4 −4 0 0 4 0 0 4 0 0 4 0 0 4 0 0 −4 4 4 4 0 0 0 0 0 0 0 0 4 0 4 −4 4 0 4 0 0 0 0 0 0 0 0 4 4 4 −4 0 0 4 0 0 0 0 0 4 0 0 4 0 0 −4 4 4 4 0 0 0 0 0 4 0 0 4 0 4 −4 4 0 4 0 0 0 0 0 4 0 0 4 4 4 −4 0 0 4 0 0 4 0 0 0 0 0 4 0 0 −4 4 4 4 0 0 4 0 0 0 0 0 4 0 4 −4 4 0 4 0 0 4 0 0 0 0 0 4 4 0 −4 3.7.4 Conjunto Independente Generalizado (Generalized Independent Set Problem) Agora descrevemos uma generalização do problema do conjunto independente em um grafo. Definição 3.6. Sejam G = (V, E) um grafo não dirigido, onde V é o conjunto de vértices e E o conjunto de arestas, um custo wi > 0 é associado a cada vértice i ∈ V e um custo cij > 0 é associado a cada aresta (i, j) ∈ E. O problema do conjunto independente generalizado consiste em encontrar um subconjunto de vértices S ⊆ V que maximiza a diferença entre a soma dos custos dos vértices em S e a soma dos custos daquelas arestas que têm ambos os vértices em S. As variáveis do modelo são definidas como: ( 1 Se o vértice i ∈ S xi = 0 Caso contrário Uma formulação, em programação quadrática binária irrestrita, para o problema do conjunto independente generalizado é: max f (x) = X wi xi − i∈V x ∈ {0, 1} X (i,j)∈E n cij xi xj CAPÍTULO 3. TRABALHOS ANTERIORES 42 Exemplo 3.4. Neste exemplo apresentamos um grafo para o problema do conjunto independente generalizado e o modelo no formato UQP. 2 1 8 5 7 4 2 10 6 5 10 2 2 4 3 6 3 4 Figura 3.2: Grafo de Exemplo para o Problema do Conjunto Independente Generalizado Encontrar as variáveis que resolvem: max f (x) = 2x1 + 5x2 + 4x3 + 3x4 + 7x5 −(8x1 x2 + 10x1 x4 + 6x1 x5 + 2x2 x3 + 4x2 x5 + 6x3 x4 + 10x3 x5 + 2x4 x5 ) x ∈ {0, 1}5 No formato com matrizes temos: max f (x) = = = 2 0 xT 0 0 0 2 0 xT 0 0 0 [x1 , 0 5 0 0 0 0 0 4 0 0 0 5 0 0 0 0 0 4 0 0 x2 x3 0 0 8 0 10 6 0 0 2 0 4 0 T 0 x + x 0 0 0 6 10 x 0 0 0 0 0 2 7 0 0 0 0 0 0 0 0 4 0 5 3 4 0 1 0 2 0 0 T 0 0 x + x 0 1 0 3 5 x 3 0 5 0 3 0 1 0 7 3 2 5 1 0 2 4 0 5 3 x1 4 5 1 0 2 x2 x4 x5 ] 0 1 4 3 5 x3 5 0 3 3 1 x4 3 2 5 1 7 x5 0 0 0 3 0 CAPÍTULO 3. TRABALHOS ANTERIORES 3.7.5 43 Carregamento de Objetos em Estrados (Manufacturer’s Pallet Loading Problem) Em [39], Morabito e Morales afirmam que o processo de empacotamento de objetos (caixas) em objetos maiores (estrados, em inglês pallets) é similar ao processo de cortes de objetos em itens menores. Se todas as caixas tiverem as mesmas dimensões, tem-se o problema de carregamento de estrado, em inglês Manufacturer’s Pallet Loading Problem (MPL). Em [18], Dowsland apresenta uma abordagem para o MPL que consiste em encontrar um conjunto independente máximo de um grafo particular, onde os vértices representam as possı́veis posições das caixas, no estrado, tal que dois vértices são adjacentes se as posições das caixas se sobrepõem. A abordagem é baseada na observação de que existe uma correspondência biunı́voca entre conjuntos independentes máximos e layouts ótimos do MPL. Seguindo as ideias descritas na Seção 3.7.4, o MPL pode ser modelado na forma UQP. 3.8 Métodos Heurı́sticos Nesta seção descrevemos algumas heurı́sticas que foram desenvolvidas para tentar resolver instâncias do UQP. 3.8.1 Tabu Search Segundo Glover [20], o método Tabu Search (TS) é uma estratégia que ajuda heurı́sticas a escaparem de ótimos locais, ao tentar resolver problemas de otimização combinatória. O TS impõe restrições para guiar o processo de busca, com a finalidade de explorar regiões difı́ceis de se alcançar por meio de heurı́sticas. As restrições de busca no espaço de soluções no TS são criadas com o auxı́lio de uma lista tabu. Uma lista tabu, L, para o UQP é um vetor, com a mesma quantidade de elementos que o vetor solução x, onde Li > 0 para 1 ≤ i ≤ n representa que xi não pode ser modificado por uma quantidade K, constante pré-definida, de iterações. O Algoritmo 3.8.1 apresenta o esquema básico do TS sequencial. Neste algoritmo: Q é a matriz de entrada para o UQP, x representa uma solução viável para o problema, x∗ representa a melhor solução encontrada pelo algoritmo, L é uma lista tabu e K é um valor que representa a quantidade de iterações nas quais a variável xi não sofrerá alterações. O Algoritmo 3.8.2 descreve o pseudo-código do procedimento busca local TS. CAPÍTULO 3. TRABALHOS ANTERIORES Algoritmo 3.8.1: Pseudo-código do método básico do Tabu Search para o UQP 1 2 3 4 5 6 7 8 9 10 Entrada: n ∈ N∗ , Q ∈ Qn×n , K ∈ Z, Solução Inicial ∈ {0, 1}n Saı́da: x∗ ∈ {0, 1}n , f (x) ∈ Q inı́cio x ← Solução Inicial x∗ ← x Li ← 0 para i = 1, 2, . . . , n enquanto critério de parada não for satisfeito faça para i = 1 até n faça se Li = 0 então x0 ← x x0i ← 1 − x0i n P f (x0 ) ← f (x) + (1 − 2xi ) qii + (qij + qji )xj j=1 j6=i se f (x0 ) > f (x) então x ← x0 t←i fim 11 12 13 14 fim fim xt = 1 − xt para i = 1 até n faça se Li > 0 então Li ← Li − 1 fim fim Lt ← K se f (x) > f (x∗ ) então x ← busca local TS(n, Q, x, f (x∗ )) x∗ ← x fim 15 16 17 18 19 20 21 22 23 24 25 26 27 fim 28 29 fim 44 CAPÍTULO 3. TRABALHOS ANTERIORES 45 Exemplo 3.5. A Figura 3.3 mostra um exemplo com quatro iterações de um TS. É fornecido um vetor solução inicial x, neste exemplo x = [1, 0, 1, 0], uma lista tabu L = [0, 0, 0, 0], inicialmente vazia, e K = 2. Escolhe-se um conjunto de soluções na vizinhança N (x) e avalia-se as mesmas de acordo com a n P n P função objetivo, f (x) = xT Qx = (−1)i (i + j)xi xj e n = 4. A matriz do UQP que representa esta i=1 j=1 função é dada por: Q= Iteração 1 −2 3 −3 4 −4 5 −5 6 L(x) −4 5 −5 6 −6 7 −7 8 x 0 0 0 0 0 0 1 0 1 N(x) 1 f(x) = -6 Iteração 2 1 1 0 0 0 0 1 1 1 0 1 0 f(x) = -8 f(x) = -2 x 0 2 0 0 0 0 1 N(x) 0 1 0 1 0 0 0 f(x) = -2 0 1 f(x) = 2 f(x) = 0 L(x) 0 f(x) = 6 x 0 0 1 2 0 0 0 1 1 N(x) 1 0 1 0 0 f(x) = 6 1 1 f(x) = 22 f(x) = 8 Iteração 4 1 f(x) = -12 L(x) 0 Iteração 3 1 0 0 f(x) = -16 L(x) x 0 2 0 1 1 0 1 1 N(x) 0 f(x) = 24 0 1 1 f(x) = 22 1 x 0 1 0 1 1 1 f(x) = 8 1 f(x) = 24 Figura 3.3: Iterações de um Tabu Search para a instância no Exemplo 3.5 Em cada iteração investiga-se a vizinhança N (x), escolhe-se uma solução com o melhor valor de função objetivo, atualiza-se a lista tabu L fazendo com que Li ← Li − 1 para Li > 0 e define-se Lt = K, onde t é a posição da lista tabu referente ao vizinho com o melhor valor de solução. Este procedimento é repetido a cada iteração até que a condição de parada seja satisfeita. 3.8.2 Busca Local Nesta seção apresentamos o pseudo-código da função busca local TS que aparece em [10], aqui descrita no Algoritmo 3.8.2 e utilizada no método Tabu Search exibido no CAPÍTULO 3. TRABALHOS ANTERIORES 46 Algoritmo 3.8.1. Nesta busca local usa-se a função de vizinhança 1-Flip. Algoritmo 3.8.2: Pseudo-código da busca local TS do Algoritmo 3.8.1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 Entrada: n ∈ N∗ , Q ∈ Qn×n , x ∈ {0, 1}n , V ∗ ∈ Q Saı́da: x ∈ {0, 1}n inı́cio repita melhorou ← f also para t = 1 até n faça xt ← 1 − xt se f (x) > V ∗ então V ∗ ← f (x) melhorou ← verdadeiro senão xt = 1 − xt fim fim até melhorou = falso fim 3.8.3 Variable Neighborhood Search Variable Neighborhood Search (VNS) [26, 27] é uma metaheurı́stica, que é muito utilizada para tentar resolver problemas de otimização combinatória. Esta técnica baseia-se na mudança sistemática de vizinhanças junto com uma estratégia de busca local. O VNS foi utilizado com sucesso na resolução de vários problemas [15, 25, 51]. Devido ao excelente desempenho do VNS na resolução de diversos problemas, decidimos aplicá-lo para tentar resolver instâncias do UQP. O VNS permite que se crie, de forma flexı́vel, uma estrutura de vizinhança que seja adequada para o problema a ser resolvido. Nos estudos apresentados nesta dissertação, utilizamos a estrutura de vizinhança k-Flip. Denotamos por Nk (x) o conjunto de soluções diferentes de x em exatamente k posições. Esta estrutura de vizinhança tem relação direta com as propriedades 1 e 2 de um hipercubo, enunciadas na Seção 2.23. O Algoritmo 3.8.3 descreve o pseudo-código do VNS e o Algoritmo 3.8.4 apresenta a função busca local VNS utilizado no Algoritmo 3.8.3. Nesse algoritmo é possı́vel utilizar dois tipos de vizinhança, 1-Flip e 2-Flip controlados pelo valor da variável k fornecida na entrada. 3.8.4 Gradient Midpoint Method Pardalos et al. [29] apresenta a heurı́stica Gradient Midpoint Method que utiliza informações do vetor gradiente de f (x) para gerar, rapidamente, uma solução para o UQP. Dada CAPÍTULO 3. TRABALHOS ANTERIORES Algoritmo 3.8.3: Pseudo-código do método básico do VNS 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Entrada: n ∈ N∗ , Q ∈ Qn×n , kmax ∈ Z, Solução inicial ∈ {0, 1}n Saı́da: x ∈ {0, 1}n , f (x) ∈ Q inı́cio x ← Solução inicial enquanto critério de parada não for satisfeito faça k←1 enquanto k 6= kmax faça x0 ← Vetor solução aleatório na vizinhança Nk (x) x00 ← busca local VNS(n, Q, k, x0 ) se f (x00 ) > f (x) então x ← x00 k←1 senão k ←k+1 fim fim fim fim Algoritmo 3.8.4: Pseudo-código da busca local VNS do Algoritmo 3.8.3 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 Entrada: n ∈ N∗ , Q ∈ Qn×n , k ∈ Q, x ∈ {0, 1}n Saı́da: x ∈ {0, 1}n inı́cio F local ← f (x) Ind0 ← −1, Ind1 ← −1 se k = 1 então para i = 1 até n faça xi ← 1 − xi se f (x) > F local então F local ← f (x) Ind0 ← i fim xi ← 1 − xi fim senão para i = 1 até n − 1 faça xi ← 1 − xi para j = i + 1 até n faça xj ← 1 − xj se f (x) > F local então F local ← f (x) Ind0 ← i Ind1 ← j fim xj ← 1 − xj fim xi ← 1 − xi fim fim se Ind0 6= −1 então xInd0 ← 1 − xInd0 se Ind1 6= −1 então xInd1 ← 1 − xInd1 fim 47 CAPÍTULO 3. TRABALHOS ANTERIORES 48 que f (x) = xT Qx, calcula-se os limitantes inferiores e superiores para o vetor gradiente, que denotamos por a ∈ Qn e b ∈ Qn , da seguinte maneira: ai = n X qij− , j=1 bi = n X qij+ i = 1, . . . , n. (3.10) j=1 onde qij− = min(qij , 0) e qij+ = max(qij , 0) para i = 1, 2, . . . , n e j = 1, 2, . . . , n. Se 21 (ai + bi ) ≥ 0, então a variável correspondente xi terá seu valor igual a 0, caso contrário xi terá seu valor igual a 1. De acordo com os autores do artigo na referência [29], esta heurı́stica é capaz de resolver uma classe difı́cil de instâncias do UQP. Vemos estas instâncias na Seção 6.2.2. O Algoritmo 3.8.5 apresenta o pseudo-código do Gradient Midpoint Method. Note que ele é muito simples. Algoritmo 3.8.5: Gradient Midpoint Method 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 Entrada: n ∈ N∗ , Q ∈ Qn×n Saı́da: x ∈ {0, 1}n , f (x) ∈ Q inı́cio para i = 1 até n faça ai ← 0; bi ← 0 para j = 1 até n faça ai ← ai + min(qij , 0) bi ← bi + max(qij , 0) fim fim para i = 1 até n faça se (ai + bi )/2 ≥ 0 então xi ← 0 senão xi ← 1 fim fim c←0 para i = 1 até n faça para j = 1 até n faça c ← c + qij xi xj fim fim f (x) ← c fim 3.9 Métodos Exatos Nesta seção apresentamos como calcular limitantes para instâncias do UQP e regras para determinar os valores das variáveis em uma solução ótima. Mostramos também o Branch CAPÍTULO 3. TRABALHOS ANTERIORES 49 and Bound (B&B), que é um método exato baseado em enumeração implı́cita das soluções no espaço de soluções. As referências [29, 48, 49] são as fontes principais do texto desta seção. 3.9.1 Limitantes e Regras para Determinar Valores das Variáveis em uma Solução Ótima Nesta seção apresentamos um método para se calcular limitantes, que é útil para diminuir o número de vértices explorados na árvore de enumeração do método Branch and Bound. Teorema 3.1. [48] Sejam f uma função continuamente diferenciável em um conjunto aberto que contém um conjunto compacto e convexo S ⊂ Qn e x∗ uma solução ótima para o problema min sujeito a: f (x) = xT Qx x ∈ S. (3.11) Então x∗ é uma solução ótima para o problema min sujeito a: xT ∇f (x∗ ) x ∈ S. (3.12) Suponha que S = {x ∈ Qn : 0 ≤ x ≤ 1} é um cubo unitário e x∗ é uma solução ótima n P ∂f (x∗ ) para (3.11) e (3.12). Como xT ∇f (x∗ ) = xi , podemos concluir que ∂xi i=1 x∗i = ∂f (x∗ ) 0 se ≥ 0 ∂xi para i = 1, 2, . . . , n. ∂f (x∗ ) 1 se < 0 ∂xi Isto é verdade porque T ∗ min{x ∇f (x ) : 0 ≤ x ≤ 1} = n X i=1 min{xi ∂f (x∗ ) : 0 ≤ xi ≤ 1} ∂xi Como a função xT ∇f (x∗ ) é linear, pois a função f (x) é quadrática, o problema min xT ∇f (x∗ ) sujeito a: 0≤x≤1 (3.13) CAPÍTULO 3. TRABALHOS ANTERIORES 50 é um problema de programação linear. Assim, uma solução ótima para ele é um dos vértices do poliedro S = {x : 0 ≤ x ≤ 1}. Portanto, min{xT ∇f (x∗ ) : 0 ≤ x ≤ 1} = min{xT ∇f (x∗ ) : x ∈ {0, 1}n }. De fato, não assumimos que uma solução ótima x∗ é conhecida. Mas se pudermos encontrar limitantes ai e bi tais que ai ≤ então ( x∗i = ∂f (x) ≤ bi ∂xi 0 se ai ≥ 0 1 se bi < 0 ∀x ∈ S = [0, 1]n para i = 1, 2, . . . , n. (3.14) n P ∂f (x) = (qij + qji )xj . Os ∂xi j=1 valores dos limitantes podem ser determinados da seguinte maneira: Sendo f (x) = xT Qx, onde Q é uma matriz assimétrica e ai = qii + n X − (qij− + qji ) para i = 1, 2, . . . , n j=1 bi = qii + n X + (qij+ + qji ) para i = 1, 2, . . . , n j=1 − onde qij− = min{qij , 0}, qji = min{qji , 0}, qij+ = max{qij , 0} e qij+ = max{qij , 0} para i = 1, 2, . . . , n e j = 1, 2, . . . , n. n P ∂f (x) Se a matriz Q for simétrica, então = 2 qij xj . Os valores dos limitantes ∂xi j=1 podem ser determinados da seguinte maneira: ai = qii + 2 bi = qii + 2 n X j=1 n X qij− para i = 1, 2, . . . , n qij+ para i = 1, 2, . . . , n j=1 Estes limitantes são utilizados no método B&B mostrado no Algoritmo 3.9.1. Exemplo 3.6. Sejam f (x) = xT Qx 1 e Q = −2 3 −2 3 4 5 5 −6 CAPÍTULO 3. TRABALHOS ANTERIORES 51 Calculam-se os limitantes da seguinte maneira: a1 a2 a3 = 1 + 2(−2), = 4 + 2(−2), = −6, b1 b2 b3 = 1 + 2(3) = 4 + 2(5) = −6 + 2(3 + 5) ou seja, −3 a= 0 −6 x∗2 e 7 b = 14 10 A partir das condicionais em (3.14), concluı́mos que uma solução ótima para este exemplo, tem = 0. 3.9.2 Branch and Bound Branch and Bound (B&B) é um esquema que permite encontrar soluções ótimas para problemas de otimização combinatória, fazendo uso de uma busca em árvore com uma estratégia de divisão e conquista. O B&B divide o problema em vários subproblemas disjuntos, de forma que ao resolvê-los encontra-se também uma solução para o problema original. A decomposição em subproblemas pode ser visualizada como uma árvore de enumeração, onde o vértice raiz representa o problema original, cada vértice interno, na árvore de enumeração, é um subproblema e os vértices folhas fornecem soluções viáveis para o problema original. Uma caracterı́stica deste método é que pode-se encontrar limitantes, superiores ou inferiores, para os subproblemas e, ao utilizá-los, pode-se descartar parte da enumeração. É possı́vel implementar o método B&B fazendo busca em profundidade, em largura ou uma combinação destas. O Algoritmo 3.9.1 é um método B&B que utiliza uma busca em profundidade. Neste algoritmo considera-se que o problema UQP é de minimização. Vale ressaltar que este método aparece na referência [48], implementado em paralelo utilizando MPI (Message Passing Interface), usando um modelo mestre-escravo. Na Seção 5.2.2 (no capı́tulo de contribuições) apresentamos uma implementação paralela deste método B&B com GPUs, utilizando um modelo fork-join. No restante desta seção assumimos que a matriz Q é simétrica, tal como foi assumido na referência [48]. Uma solução inicial é gerada pelo método Gradient Midpoint Method (Algoritmo 3.8.4), então melhorada pelo método Tabu Search (Algoritmo 3.8.1) e fornecida como entrada para o método B&B, apresentado no Algoritmo 3.9.1, através do vetor x∗ e tem OP T = f (x∗ ). Denotamos por qij− = min{qij , 0} e qij+ = max{qij , 0} para i = 1, 2, . . . , n e j = CAPÍTULO 3. TRABALHOS ANTERIORES 52 1, 2, . . . , n. As seguintes variáveis e constantes são usadas na descrição do Algoritmo 3.9.1. qij n OP T x∗ lev p1 , . . . , plev plev+1 , . . . , pn xp1 , xp2 , . . . , xplev g lb ub p N SU BP Coeficientes na linha i e coluna j na matriz Q; Número de variáveis de uma instância; Valor da melhor solução encontrada até o momento; Melhor solução encontrada até o momento; Nı́vel atual da árvore de enumeração do B&B; Índices das variáveis fixadas no subproblema atual; Índices das variáveis livres no subproblema atual; Variáveis fixadas; Limitante inferior para f (x); Limitante inferior à ∇f (x) ∈ [0, 1]n ; Limitante superior à ∇f (x) ∈ [0, 1]n ; Vetor de permutações; Número de subproblemas resolvidos. Cada iteração do loop na Linha 5 trabalha com um vértice da árvore de enumeração do B&B. Uma pilha, estrutura de dados, é utilizada para armazenar informações de subproblemas quando um branching (ramificação da árvore) é necessário. Na Linha 2, a pilha é iniciada com o valor −1 para a variável lev, que indica que a pilha está vazia. O algoritmo termina quando este valor é desempilhado. Na Linha 4, mostramos como calcular os limitantes do gradiente da função f (x) = x Qx. Na Linha 6, calcula-se o limitante inferior para o valor de uma solução ótima para o subproblema corrente. A fórmula utilizada é a seguinte: T n X n X g = i=1 j=1 + lev X lev X qij− lev lev X n X X − − 2 qpi pj (1 − xpi ) + qp−i pj (1 − xpi ) i=1 j=i+1 qp+i pj xpi xpj i=1 (3.15) i=1 j=1 Na Linha 7, verificamos se é possı́vel podar um vértice interno, g ≥ OP T , ou um vértice folha é encontrado, lev = n. No último caso, uma nova solução foi encontrada e é avaliada na Linha 8. Na Linha 10 desempilha-se as informações de um subproblema ainda não resolvido. Na Linha 11, altera-se o valor da variável xplev para resolver um outro subproblema. O número de subproblemas resolvidos, N SU BP , é incrementado na Linha 13. Este valor é usado somente para medidas estatı́sticas, mas também pode ser utilizado como critério de parada como apresentado em [49]. Se nenhuma das condições da Linha 7 for satisfeita, uma busca em profundidade é iniciada na Linha 15. Se o nı́vel de profundidade na árvore de anumeração é diferente de zero (lev 6= 0), então atualiza-se os limitantes dos gradientes da função. Da Linha 15 até CAPÍTULO 3. TRABALHOS ANTERIORES 53 a Linha 21, é feita uma atualização eficiente, com complexidade computacional linear, dos limitantes do vetor gradiente para as variáveis ainda não fixadas (isto é, variáveis livres). Esta atualização leva em consideração o valor da última variável fixada. Esses limitantes são testados na Linha 24, verificando se alguma variável pode ter seu valor fixado. Se algum limitante inferior, lbi , for maior ou igual a zero ou um limitante superior, ubi , for menor ou igual a zero, então a variável correspondente, xi , tem seu valor fixado como apresentam as Linhas de 25 até 29. Uma vez que a diferença entre os limitantes vão diminuindo a cada nı́vel que se desce na árvore de enumeração, as variáveis são fixadas de maneira dinâmica. Este processo é chamado pelos autores de pré-processamento dinâmico. Se nenhuma variável pode ser fixada, então a regra da Linha 35 é utilizada. A regra define a próxima variável livre a ser fixada. A variável livre escolhida é aquela que possivelmente não conseguirá ser fixada nos nı́veis subsequentes da árvore de enumeração. Por isso as variáveis que têm maior possibilidade de serem fixadas em iterações seguintes, são deixadas livres. Depois que a variável é escolhida, o seu valor é determinado nas Linhas 36 a 43, definindo o valor que menos incrementa o limitante inferior. Este procedimento escolhe um subproblema que será resolvido e o outro subproblema é armazenado na pilha, Linha 44, para posterior análise. Na Linha 46, a variável que controla a altura da árvore de enumeração, lev, é incrementada. Na Linha 47, é feita uma troca de valores no vetor de permutação. s x0 = 0 s x1 = 0 s 3 0 x0 = 1 s 1 x1 = 0 x1 = 1 s 4 s 5 2 x1 = 1 s 6 Figura 3.4: Exemplo de uma árvore de enumeração do Branch and Bound A Figura 3.4 apresenta um exemplo de uma árvore de enumeração para um B&B. Nesta árvore o nó raiz S0 representa o problema original, os nós folhas S3 , S4 , S5 e S6 representam soluções viáveis para o problema. O algoritmo percorre esta árvore calculando limitantes para cada nó explorado e verificando a viabilidade de explorar os nós filhos. CAPÍTULO 3. TRABALHOS ANTERIORES Algoritmo 3.9.1: Método Branch and Bound com busca em profundidade 1 2 3 4 5 6 7 8 Entrada: n ∈ N∗ , Q ∈ Qn×n simétrica, x∗ ∈ Qn , OP T ∈ Q Saı́da: x ∈ {0, 1}n , f (x) ∈ Q pi ← i para i = 1, . . . , n empilha({-1,-,-,-},stack) lev ← 0, N SU BP ← 0 n n P P − + lbi ← 2 qij + qii , ubi ← 2 qij + qii para i = 1, . . . , n j=1 j=1 j6=i j6=i enquanto lev 6= −1 faça g ← Calcula o limitante inferior g se g ≥ OP T ou lev = n então se g < OP T então OP T ← g, x∗ ← x 9 desempilha({lev, lb, ub, g},stack) se lev 6= −1 então xplev ← 1 − xplev 10 11 12 N SU BP ← N SU BP + 1 senão se lev 6= 0 então se xplev = 1 então lbpi ← lbpi + 2qp+i plev para i = lev + 1, . . . , n ubpi ← ubpi + 2qp−i plev para i = lev + 1, . . . , n senão lbpi ← lbpi − 2qp−i plev para i = lev + 1, . . . , n ubpi ← ubpi − 2qp+i plev para i = lev + 1, . . . , n fim fim entrou = falso para i = lev até n faça se lbpi ≥ 0 ou ubpi ≤ 0 então se ubpi ≤ 0 então x pi ← 1 senão x pi ← 0 fim entrou = verdadeiro Sai do loop fim fim se entrou = falso então i ← j onde δj = max{min(−lbpt , ubpt )} para t = lev + 1, . . . , n 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 t x pi ← 0 gAux ← Calcula o limitante inferior g x pi ← 1 g ← Calcula o limitante inferior g se gAux < g então x pi ← 0 g ← gAux fim empilha({lev+1, lb, ub, g},stack) 36 37 38 39 40 41 42 43 44 fim lev ← lev + 1 plev ↔ pi %Troca de valores de plev e pi 45 46 47 fim 48 49 fim 54 Capı́tulo 4 Programação Paralela com GPUs Paralelizar a computação implica na capacidade de dividir o problema em problemas menores e conseguir resolvê-los de forma eficaz. Alguns problemas têm caracterı́stica paralela, ou seja, são problemas que se adaptam facilmente à computação paralela. Como exemplo de uma tarefa com caracterı́stica paralela citamos a soma de vetores, onde cada unidade de processamento pode ser responsável pela soma de uma parte do vetor. Embora o paralelismo nesta situação melhore bastante o desempenho de um algoritmo, há problemas em que o paralelismo é ineficaz e por vezes a performance do programa paralelo é pior que a do algoritmo sequencial. Nas seções que seguem, apresentamos os conceitos sobre computação paralela que são utilizados nesta dissertação. Na Seção 4.1 apresentamos a distinção entre CPUs e GPUs. Na Seção 4.2 apresentamos os conceitos de multicore e manycore. Na Seção 4.3 descrevemos um histórico das GPUs, seus motivos de interesse pela comunidade cientı́fica e a evolução que elas tiveram ao longo dos últimos anos. Na Seção 4.9 tratamos da arquitetura aberta de programação paralela chamada OpenCL. Na Seção 4.4 discorremos sobre a arquitetura CUDA criada pela NVIDIA. Finalmente na Seção 4.5 descrevemos alguns detalhes das GPUs, que utilizam a última geração da arquitetura CUDA. 4.1 CPUs e GPUs Tradicionalmente, a diferença fundamental entre os processadores (CPUs) as placas gráficas (GPUs) é o fato de que as CPUs são otimizadas para cálculos sequenciais, enquanto as GPUs são otimizadas para cálculos massivamente paralelos (processando gráficos 3D). Esta diferença era bem mais clara quando as GPUs começaram a ser comercializadas. Entretanto, com o inı́cio do uso de shaders (pequenos aplicativos destinados a executar tarefas especı́ficas na composição de cenas) elas ganharam a capacidade de também executar código sequencial, como os processadores. 55 CAPÍTULO 4. PROGRAMAÇÃO PARALELA COM GPUS 56 Embora as GPUs sejam otimizadas para o processamento de shaders e gráficos 3D, elas podem ser usadas para várias tarefas computacionais, indo desde a decodificação de vı́deos a aplicações cientı́ficas. 4.2 Multicore e Manycore Com o avanço da indústria de materiais tornou-se possı́vel colocar em um chip de silı́cio cada vez mais transistores, permitindo mais funcionalidades aos processadores. Junto com esta evolução veio o aumento da velocidade do clock que permitiu processamentos muito rápidos. Chegou-se a um momento crı́tico, em que uma placa de silı́cio, como é feita atualmente, não suporta velocidades maiores, pois esquentam muito e rompem as trilhas. A solução encontrada foi colocar mais núcleos de processamento dentro de um processador, tornando as tarefas possivelmente mais rápidas, não pela velocidade de processamento, mas sim pelo aumento da quantidade de núcleos de processamento. Figura 4.1: Multicore e Manycore. Fonte: NVIDIA [42] Os processadores podem ser categorizados em Multicore e Manycore dependendo da quantidade de núcleos de processamento. O modelo de GPU dedica mais transistores para as unidades de execução, diminuindo a área de controle e cache. 4.3 Histórico das GPUs Com o passar dos anos a exigência de aprimoramento no ambiente gráfico computacional aumentou. Gráficos que antes eram 2D agora são representados em 3D, com uma qualidade melhor. Este fato impulsionou os fabricantes de GPUs a se aprimorarem, o que gerou uma linha de produtos com uma grande capacidade de processamento paralelo e tornou as GPUs programáveis. CAPÍTULO 4. PROGRAMAÇÃO PARALELA COM GPUS 57 Esforços para explorar as GPUs com aplicações não gráficas, programas de propósito geral, estão em andamento desde 2003 e este tipo de programação é conhecida como Computação de Propósito Geral com GPUs (em inglês, General-Purpose Computation on Graphics Processing Units - GPGPU). A GPGPU atraiu a atenção de muitas pessoas porque apresentou um bom desempenho. A princı́pio esse modo de programar possuiu algumas inconveniências. Primeiro, ela exigiu que os programadores conhecessem muito bem as APIs (Application Programming Interface) gráficas e a arquitetura da GPU utilizada, ou seja, apesar de possuir uma linguagem de programação de alto nı́vel, o conhecimento especı́fico do hardware ainda era necessário. Segundo, a GPU não estava preparada para computação de propósito geral. Os programas tinham que ser expressos em termos de linguagens gráficas utilizando texturas e coordenadas de vértices, o que deixava o programa complexo. Terceiro, operações básicas como leituras e escritas aleatórias na memória não eram suportadas, o que restringia muito a programação. Por último, a falta de suporte a operações com ponto flutuante de precisão dupla foi algo que impossibilitou que várias aplicações cientı́ficas fossem executadas com GPUs. Para superar estas dificuldades, as empresas fabricantes de GPUs incorporaram suporte às necessidades mais básicas para a programação de propósito geral, proporcionando um grande aumento de interesse dos desenvolvedores de aplicativos de alto desempenho e de programação paralela. Com a evolução das GPUs, foi possı́vel realizar operações com pontos flutuantes de maneira rápida, permitindo assim o desenvolvimento de uma variedade de aplicações cientı́ficas. Com a finalidade de facilitar e difundir a programação em GPGPU, foram criadas arquiteturas para dar suporte ao desenvolvimento de softwares utilizando GPUs, dentre elas surgiram OpenCL e CUDA. Inicialmente o potencial de processamento das GPUs era só um pouco maior do que o das CPUs, mas com o passar dos anos as GPUs se desenvolveram e aumentaram largamente esta diferença. A Figura 4.2 mostra um gráfico comparativo entre o potencial de processamento das GPUs e o das CPUs. Nota-se que em 2003 a diferença entre as GPUs e as CPUs era relativamente pequena, tanto para o Theoretical GB/s (largura de banda) como para o Theoretical GFLOP/s (capacidade de processamento). Esta diferença cresceu significativamente desde então. As dificuldades de processamento com operações de ponto flutuante de precisão dupla foram superadas com a criação da linha de placas Tesla C2050 em 2009 [42], que deixou as GPUs com mais que o dobro de GFLOP/s com relação as CPUs. Devido a esse poder de processamento, as GPUs se tornaram importantes para a nova fase de processamento de alto desempenho. Hoje em dia alguns clusters possuem uma mescla de CPUs e GPUs, objetivando alcançar um melhor poder de processamento. O futuro da computação de alto desempenho tende a uma programação heterogênea, CAPÍTULO 4. PROGRAMAÇÃO PARALELA COM GPUS 58 Theoretical GFLOP/s Theoretical GB/s 3250 200 3000 180 CPU 2750 GPU 2500 160 140 2250 120 2000 NVIDIA GPU Single Precision NVIDIA GPU Double Precision Intel CPU Single Precision Intel CPU Double Precision 1750 100 1500 80 1250 60 1000 750 40 500 20 250 0 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 0 Sep-01 (a) Jun-04 Mar-07 Dec-09 Aug-12 (b) Figura 4.2: (a) Largura de banda da memória. (b) Operações por segundo com ponto flutuante. Fonte: NVIDIA [42] envolvendo processadores multicore e manycore. 4.4 Arquitetura CUDA Compute Unified Device Architecture (CUDA) é uma arquitetura de computação paralela de propósito geral desenvolvida pela empresa NVIDIA. Nesta arquitetura é possı́vel acelerar o desempenho dos programas, utilizando as GPUs como um co-processador matemático paralelo. A arquitetura CUDA explora este poder de processamento para que os desenvolvedores de softwares possam resolver problemas, que aparecem não só em aplicações gráficas, incluindo processamento de vı́deos, áudio e simulação de efeitos fı́sicos, exploração de gás e petróleo, design de produtos, softwares de criptografia, processamento de imagens na área médica, simulações biológicas e pesquisa cientı́fica. CUDA apresenta-se ao programador como uma extensão da linguagem C/C++. O fluxo de processamento de um programa escrito em CUDA não é complexo. No inı́cio do processamento alguns dados são copiados da memória principal para a memória da GPU. Depois disto o processamento é feito pela GPU, que então executa as tarefas. Por fim, alguns dados são copiados da memória da GPU para a memória principal do computador. CUDA utiliza o padrão SIMT(Single Instruction, Multiple Thread) [42], onde o código destinado ao kernel (nome dado à função que é executada na GPU) é executado por todas as threads. CAPÍTULO 4. PROGRAMAÇÃO PARALELA COM GPUS 4.5 59 Arquitetura Kepler GK104 A fonte principal do texto desta seção é o Whitepaper NVIDIA GeForce GTX 680 [40] é a fonte principal do texto desta seção. Kepler é uma arquitetura da NVIDIA para GPUs, sucessora da Fermi. Ao desenvolver a Kepler a NVIDIA buscou ter um melhor desempenho por watt, velocidade e prover mais facilidade de programação. A Figura 4.3 mostra a placa GeForce GTX 680 que é construı́da com arquitetura Kepler. Figura 4.3: GeForce GTX 680 4.5.1 Visão Geral da Arquitetura Kepler A arquitetura Kepler GK104 é construı́da com cerca de 3,5 bilhões de transistores e um total de 1536 CUDA Cores (núcleos de processamento). Os CUDA Cores são as menores unidades de computação da GPU e são organizados em 8 unidades chamadas Streaming Multiprocessor (SMX), que contém 192 CUDA Cores. Alguns detalhes da arquitetura Kepler GK104 são apresentados na Tabela 4.1, onde SFU são unidades de funções especiais, destinadas a funções transcendentais (ex: seno, cosseno, tangente de ângulos) e instruções de interpolação gráficas, LD/ST são unidades de carga e armazenamento, responsáveis por transportar valores entre as memórias da GPU, Tex são unidades que realizam o filtro de texturas e Warp Schedular é o tamanho do escalonador de warp 1 . 4.6 Memórias As referências [42, 43] são as principais fontes desta seção. 1 Warp é a menor unidade executável de paralelismo em CUDA, ela contém 32 threads que são executadas de forma sincronizadas. CAPÍTULO 4. PROGRAMAÇÃO PARALELA COM GPUS Figura 4.4: Diagrama da arquitetura Kepler GK104 Elementos CUDA Cores SFU LD/ST Tex Warp Scheduler Unidades 1536 256 256 128 32 Tabela 4.1: Detalhes da arquitetura Kepler GK104. 60 CAPÍTULO 4. PROGRAMAÇÃO PARALELA COM GPUS 61 As GPUs possuem várias áreas de memória, com diferentes caracterı́sticas que podem influenciar diretamente no desempenho dos programas. As memórias disponı́veis para o desenvolvedor são: registradores, compartilhada, global, local, constantes e textura como apresentamos na Tabela 4.2. Em [34], discute-se sobre as vantagens do uso de alguns tipos de memória em um algoritmo de busca local. Memória Registrador Compartilhada Global Local Constante Textura Localização: Dentro ou fora do chip Dentro Dentro Fora Fora Fora Fora Cache n/a n/a Sim Sim Sim Sim Tipo de Acesso R/W R/W R/W R/W R R Escopo 1 thread Todas thread no Bloco Todas thread + CPU 1 thread Todas thread + CPU Todas thread + CPU Tempo de Vida Thread Thread Thread Thread Thread Thread Tabela 4.2: Detalhes das Memórias Registradores É o espaço reservado para variáveis privadas para uma thread. O acesso a registradores é extremamente rápido e possui uma latência de poucos ciclos. Memória Compartilhada A memória compartilhada é uma memória que é comum entre todas as threads de um bloco. O acesso a esta memória é rápido e o seu tamanho varia dependendo da arquitetura. Memória Global A memória global é a maior área de memória disponı́vel dentro da GPU. Esta é uma memória de acesso lento. Seu acesso pode ser melhorado quando feito um acesso combinado. Acesso combinado à memória global Quando as threads de um mesmo warp acessam o mesmo segmento da memória global, os dados requisitados são reunidos em uma mesma transação e levados para as threads. Esta é uma forma de se otimizar o acesso à memória global e aumentar a largura de banda. A memória global é a única que possui este tipo de acesso. CAPÍTULO 4. PROGRAMAÇÃO PARALELA COM GPUS 62 Memória Local Esta memória tem este nome devido ao seu escopo e não por causa da sua localização fı́sica. De fato, a memória local fica em uma área fora do chip como a memória global. Assim o acesso à memória local é tão custoso quanto a memória global. A memória local é utilizada para armazenar variáveis automáticas. Isto é feito pelo compilador nvcc quando determina que não há espaço suficiente para armazenar as variáveis nos registradores. Grandes estruturas ou matrizes que consomem muito espaço, são sucetiveis a serem alocadas na memória local. Memória de Constante Esta é uma memória somente de leitura com cache. Ela é uma memória de acesso rápido e destinada a guardar valores que não serão alterados durante a execução do kernel. Memória de Textura Esta é uma memória somente de leitura com cache. Ela é uma memória de acesso rápido e normalmente é destinada para guardar as texturas em aplicações gráficas. Mas também é possı́vel utilizá-la para armazenar constantes em aplicações cientı́ficas. 4.7 Limitações da Arquitetura CUDA A arquitetura CUDA possui limitações que devem ser consideradas. Exemplo disto é o tempo gasto para realizar cópias entre a memória RAM e a memória interna da GPU, isto pode diminuir o desempenho dos programas. Entretanto, isto varia com o barramento do sistema. Outro problema a ser considerado refere-se à compatibilidade do software desenvolvido dessa forma. CUDA é uma tecnologia da NVIDIA e só funciona com placas de vı́deo fabricadas por esta empresa. Uma alternativa para essa limitação é utilizar OpenCL, cujo desenvolvimento avança mais lentamente que a CUDA por possuir objetivos mais gerais. Outro fato a ser considerado é que embora desenvolver um programa utilizando CUDA seja relativamente simples, manter todos os cores ocupados o maior tempo possı́vel pode exigir muito trabalho. Isto se deve às limitações de memórias de acesso rápido dentro das GPUs e da própria caracterı́stica dos multiprocessadores utilizados. CAPÍTULO 4. PROGRAMAÇÃO PARALELA COM GPUS 4.8 63 Compilação do Código em CUDA O modelo de programação da arquitetura CUDA é o ANSI C estendido com certas construções e palavras-chave. A GPU é tratada como um co-processador paralelo, a parte sequencial do código é executada no host (CPU) e a parte paralela no device (GPU). Os kernels podem ser escritos com um conjunto de instruções da arquitetura CUDA, chamado PTX (Parallel Thread eXecution). Entretanto, é mais fácil realizar uma programação de alto nı́vel, como em C/C++. Para que o compilador saiba qual parte executar em cada dispositivo, no código é incluı́do alguns comandos que especificam se a função será executada no host ou no device, desta forma o compilador (NVCC) sabe como processar cada parte do código fonte. O NVCC é um compilador que facilita o processo de compilação do código C/C++ ou PTX, efetuando a chamada de um conjunto de ferramentas que implementam etapas de compilação diferentes como apresenta a Figura 4.5. Código PTX Aplicação Cuda Driver GPU NVCC Código C Compilador C Processador Figura 4.5: Compilação de código CUDA 4.9 Linguagem OpenCL Open Computing Language (OpenCL) é uma arquitetura que possui um padrão aberto para a programação de aplicações paralelas para sistemas heterogêneos, gerido pelo Khronos Group. Esta arquitetura da suporte a uma ampla gama de aplicações para sistemas embarcados. A proposta do OpenCL é executar o mesmo código de forma transparente em diversos tipos de arquiteturas, obtendo uma boa performance do hardware. Dentre os vários tipos de processadores que o OpenCL dá suporte, encontram-se as GPUs. Os benefı́cios de uma plataforma unificada são imensos, mas com eles vêm também os desafios de extrair o melhor desempenho em cada placa gráfica. O avanço do OpenCL com as GPUs é mais lento do que as arquiteturas, softwares e compiladores distribuı́dos CAPÍTULO 4. PROGRAMAÇÃO PARALELA COM GPUS pelos fabricantes, que contudo não trazem o benefı́cio da generalização. 64 Capı́tulo 5 Contribuições e Implementações Paralelas Conforme dito nos Capı́tulo 1, dois dos três objetivos desta pesquisa são: criar métodos e implementá-los de forma sequencial e paralela, para resolver o seguinte problema de otimização combinatória. max f (x) = xT Qx x ∈ {0, 1}n sujeito a: Nos capı́tulos anteriores denotamos este problema pela sigla UQP. Lembramos que o UQP pertence à classe NP-difı́cil. Neste capı́tulo apresentamos as nossas contribuições ao desenvolvimento de métodos para resolver instâncias do UQP e descrevemos implementações sequenciais e paralelas com GPUs. Tanto nas abordagens heurı́sticas quanto nas exatas, faz-se necessário efetuar o cálculo do valor da função objetivo diversas vezes. Assim, saber fazer este cálculo de maneira rápida é primordial. A Seção 5.1 trata deste assunto. 5.1 Cálculos do Valor da Função Objetivo e Limitantes Inferiores Conforme exposto nas Seções 3.8.1, 3.8.2, 3.8.3 e 3.9.2, os métodos Tabu Search, Busca Local, Variable Neighborhood Search e Branch and Bound, necessitam computar diversas vezes o valor de f (x) = xT Qx. Esta computação requer O(n2 ) operações e pode ser determinante para o bom desempenho de um método. No caso do Branch and Bound precisa-se computar diversas vezes o valor da função g, limitante inferior ao valor de uma 65 CAPÍTULO 5. CONTRIBUIÇÕES E IMPLEMENTAÇÕES PARALELAS 66 solução ótima. Nesta seção apresentamos a forma básica de se efetuar o cálculo do valor da função objetivo e também formas mais eficientes, que aproveitam o valor de uma solução já computada. 5.1.1 Cálculo do Valor da Função Objetivo na Forma Básica O cálculo de f (x) = xT Qx na forma básica faz: f (x) = xT Qx = n X n X i=1 qij xj xi j=1 É fácil ver que este cálculo de f (x) requer n2 + n operações de multiplicação e (n − 1)2 operações de adição. Exemplo 5.1. Cálculo do valor da função objetivo f (x) = xT Qx, onde Q é uma matriz assimétrica. Se xT = [1, 0, 1 0] e Q = 4 7 Se alterarmos o vetor xT de [1, f (x) = [1, 1, 1 0] 4 7 −2 3 5 6 então f (x) = 1. −8 −9 0, 0] para [1, 1, 0] então: 1 −2 3 5 6 1 0 −8 −9 = [1 × 1 + 1 × 4 + 0 × 7, 1 × (−2) + 1 × 5 + 0 × (−8), 1 1 × 3 + 1 × 6 + 0 × (−9)] 1 0 1 = [5, 3, 9] 1 0 = 1 × 5 + 1 × 3 + 0 × 9 = 8. Esta maneira de recálculo utilizou 12 operações de multiplicação e 8 operações de adição, isto é, n2 + n e n2 − 1 para n = 3. Quando a matriz é simétrica utiliza-se o mesmo número de operações, tanto para as multiplicações quanto para as adições. CAPÍTULO 5. CONTRIBUIÇÕES E IMPLEMENTAÇÕES PARALELAS 5.1.2 67 Recálculo do Valor da Função Objetivo com Mudança do Valor de Uma Variável Esta maneira de recálculo leva em conta o valor da solução x e a variável xi , que foi modificada. Desta maneira, é necessário utilizar apenas uma parte da matriz Q para efetuar o recálculo. Notamos que o recálculo pode ser feito quando a instância possui matriz assimétrica ou simétrica. No segundo caso realiza-se menos operações que o primeiro. Em muitos casos, é útil explorar a vizinhança de uma solução, sem ter que gerar os vetores solução da vizinhança, como é o caso quando fazemos esta investigação de forma paralela, onde cada thread teria que alterar o vetor solução. Desta forma seria necessário replicar o vetor, o que consumiria bastante memória dos registradores (Seção 4.6), que é escassa nas GPUs ou utilizar a memória global, que tem um acesso mais lento (Seção 4.6). O que apresentamos nesta subseção é uma forma de efetuar o recálculo do valor da função objetivo sem alterar o vetor solução, sendo assim o mesmo vetor pode ser compartilhado por todas as threads. O seguinte método efetua o recálculo do valor da função objetivo sem precisar alterar o vetor solução, quando apenas uma variável, xi , muda de valor. Sejam f (x) = xT Qx, x ∈ {0, 1}n e Q ∈ Qn×n . Então: f (x) = h = h x1 , x 2 , . . . x1 , x 2 , . . . q11 q12 . . . q1n x1 i q21 q22 . . . q2n x2 xn .. . . .. .. . . . . .. . qn1 qn2 . . . qnn xn n P j=1 q1j xj n P i q2j xj xn j=1 .. . n P qnj xj j=1 = x1 n X j=1 q1j xj + x2 n X j=1 q2j xj + · · · + xn n X qnj xj j=1 Os resultados apresentados nesta seção foram obtidos de forma independente, embora a Fórmula (5.1) apareça em [33] sem uma prova. Proposição 5.1. Sejam f (x) o valor de xT Qx para um dado x ∈ {0, 1}n e Q ∈ Qn×n uma matriz assimétrica. Se y é um vetor idêntico à x, exceto pela i-ésima posição onde CAPÍTULO 5. CONTRIBUIÇÕES E IMPLEMENTAÇÕES PARALELAS 68 yi = 1 − xi com i ∈ {1, 2, . . . , n}, e se f (x) já estiver calculado, então o valor de f (y) pode ser determinado, sem utilizar informações do vetor y, da seguinte maneira: n X f (y) = f (x) + (1 − 2xi ) qii + (qij + qji )xj (5.1) j=1 j6=i Esta maneira de fazer o recálculo utiliza n + 1 operações de multiplicação e 2n operações de adição. Prova: Suponha que x ∈ {0, 1}n , Q ∈ Qn×n é uma matriz assimétrica e y é um vetor idêntico à x exceto pela i-ésima posição, onde yi = 1 − xi com i ∈ {1, 2, . . . , n}. Pode-se determinar f (y) a partir do valor de f (x) subtraindo as parcelas que envolvem xi e adicionando aquelas que envolvem 1 − xi . Como xi ∈ {0, 1}, segue que x2i = xi e (1 − xi )2 = 1 − xi . Então: f (y) = f (x) − xi +(1 − xi ) = f (x) − xi n X qij xj − xi n X j=1 j=1 j6=i n X j6=i qji xj − qii x2i qij xj + (1 − xi ) n X j=1 j=1 j6=i n X j6=i qji xj + qii (1 − xi )2 (qij + qji )xj − qii xi j=1 +(1 − xi ) j6=i n X (qij + qji )xj + qii (1 − xi ) j=1 j6=i n X = f (x) + (1 − 2xi ) (qij + qji )xj − qii xi + qii − qii xi j=1 = f (x) + (1 − 2xi ) j6=i n X (qij + qji )xj + qii (1 − 2xi ) j=1 j6=i n X = f (x) + (1 − 2xi ) qii + (qij + qji )xj j=1 j6=i Exemplo 5.2. Se f (x) = xT Qx, x = [1, 0, 0], i = 2 1 Q= 4 7 −2 3 5 6 −8 −9 e CAPÍTULO 5. CONTRIBUIÇÕES E IMPLEMENTAÇÕES PARALELAS 69 então f (y) n X = f (x) + (1 − 2xi ) qii + (qij + qji )xj j=1 j6=i = 1 + (1 − 2 × 0) 5 + (4 + (−2)) × 1 + (6 + (−8)) × 0 = 1 + (1)(5 + 2 × 1 + (−2) × 0) = 1 + (1)(7) = 8 Esta maneira de fazer o recálculo utilizou 4 multiplicações e 6 adições, que são respectivamente n + 1 e 2n para n = 3, conforme dito anteriormente. Se a instância do problema possuir uma matriz simétrica, é possı́vel reduzir o número de operações envolvidas no recálculo do valor da função objetivo. Corolário 5.1. Sejam f (x) o valor de xT Qx para um dado x ∈ {0, 1}n e Q ∈ Qn×n uma matriz simétrica. Se y é um vetor idêntico à x exceto pela i-ésima posição, onde yi = 1 − xi com i ∈ {1, 2, . . . , n}, e f (x) já estiver calculado, então o valor de f (y) pode ser determinado, sem utilizar informações o vetor y, da seguinte maneira: n X f (y) = f (x) + (1 − 2xi ) qii + 2 qij xj (5.2) j=1 j6=i Esta maneira de fazer o recálculo utiliza n+2 operações de multiplicação e n+1 operações de adição. Prova: Como a matriz Q é simétrica, segue que qij = qji . Então: f (y) = f (x) + (1 − 2xi ) qii + n X (qij + qji )xj j=1 j6=i n X = f (x) + (1 − 2xi ) qii + 2 qij xj j=1 j6=i Exemplo 5.3. Se f (x) = xT Qx, x = [1, 0, 0], i = 2 1 Q = −2 3 −2 3 5 6 6 −9 e CAPÍTULO 5. CONTRIBUIÇÕES E IMPLEMENTAÇÕES PARALELAS 70 Então f (y) n X = f (x) + (1 − 2xi ) qii + 2 qij xj j=1 j6=i = 1 + (1 − 2 × 0) 5 + 2 × ((−2) × 1 + 6 × 0) = 1 + (1)(5 + (−4)) = 1 + (1)(1) = 2 Esta maneira de fazer o recálculo utilizou 5 multiplicações e 4 adições, que são respectivamente n + 2 e n + 1 para n = 3, conforme dito anteriormente. Em resumo, na Fórmula (5.1) os números de operações são: Multiplicações: Adições: n−1+1+1=n+1 2(n − 1) + 1 + 1 = 2n − 2 + 2 = 2n Enquanto na Fórmula (5.2), os números de operações são: n−1+1+1+1=n+2 Multiplicações: n−1+1+1=n+1 Adições: 5.1.3 Recálculo do Valor da Função Objetivo com Mudança do Valor em Duas Variáveis Usando um raciocı́nio similar ao descrito na Seção 5.1.2, podemos obter uma fórmula geral para o recálculo do valor da função objetivo considerando que várias variáveis têm seus valores alterados. Teorema 5.1. Sejam x ∈ {0, 1}n , f (x) = xT Qx e Q ∈ Qn×n uma matriz assimétrica. Se y é um vetor idêntico à x exceto por duas posições distintas i, k ∈ {1, 2, . . . , n}, onde yi = 1 − xi e yk = 1 − xk , e f (x) já estiver calculado, então o valor de f (y) pode ser obtido por: n X f (y) = f (x) + (1 − 2xi ) qii + (qij + qji )xj j=1 j6=i,k n X +(1 − 2xk ) qkk + j=1 j6=i,k (qkj + qjk )xj + (qik + qik )(1 − xi − xk ) (5.3) CAPÍTULO 5. CONTRIBUIÇÕES E IMPLEMENTAÇÕES PARALELAS 71 Esta maneira de fazer o recálculo utiliza 2n + 1 operações de multiplicação e 4n + 2 operações de adição. Prova: Sejam x ∈ {0, 1}n , f (x) = xT Qx e Q ∈ Qn×n uma matriz assimétrica. Se y é um vetor idêntico à x exceto por duas posições distintas i, k ∈ {1, 2, . . . , n}, onde yi = 1 − xi e yk = 1 − xk , e f (x) já estiver calculado, então pode-se determinar f (y) a partir de f (x) subtraindo as parcelas que envolvem xi e xk e adicionando aquelas que envolvem (1 − xi ) e (1 − xk ). A Figura 5.1 mostra os ı́ndices na matriz Q que precisam ser considerados para o cálculo do valor de f (y). Assim, f (y) = f (x) − xi −xk n X qij xj − xi n X j=1 j=1 j6=i,k n X j6=i,k n X qkj xj − xk j=1 qji xj − qii x2i qjk xj − qkk x2k − qik xi xk − qki xk xi j=1 j6=i,k n X j6=i,k +(1 − xi ) qij xj + (1 − xi ) j=1 +(1 − xk ) n X qji xj + qii (1 − xi )2 j=1 j6=i,k n X qkj xj + (1 − xk ) j6=i,k n X j=1 j=1 j6=i,k j6=i,k qjk xj + qkk (1 − xk )2 +qki (1 − xi )(1 − xk ) + qik (1 − xi )(1 − xk ) n n X X = f (x) − xi (qij + qji )xj − qii xi − xk (qkj + qjk )xj j=1 j=1 j6=i,k j6=i,k −qkk xk − (qik + qki )xi xk n n X X (qkj + qjk )xj (qij + qji )xj + qii (1 − xi ) + (1 − xk ) +(1 − xi ) j=1 j=1 j6=i,k j6=i,k +qkk (1 − xk ) + (qik + qki )(1 − xi )(1 − xk ) n X = f (x) + (1 − 2xi ) (qij + qji )xj + qii (1 − 2xi ) j=1 +(1 − 2xk ) j6=i,k n X (qkj + qjk )xj + qkk (1 − 2xk ) j=1 j6=i,k +(qik + qki )(1 − xi )(1 − xk ) − (qik + qki )xi xk CAPÍTULO 5. CONTRIBUIÇÕES E IMPLEMENTAÇÕES PARALELAS 72 n X = f (x) + (1 − 2xi ) qii + (qij + qji )xj j=1 j6=i,k n X +(1 − 2xk ) qkk + (qkj + qjk )xj + (qik + qik )(1 − xi − xk ) j=1 j6=i,k i k i ii ik k ki kk Figura 5.1: Índices da matriz Q para o cálculo de f (y) Exemplo 5.4. Se f (x) = xT Qx, x = [1, 0, 0], i = 1, k = 2, f (x) = 1 e 1 Q= 4 7 −2 3 5 6 −8 −9 então: f (y) n X = f (x) + (1 − 2xi ) qii + (qij + qji )xj j=1 j6=i,k n X +(1 − 2xk ) qkk + (qkj + qjk )xj + (qik + qik )(1 − xi − xk ) j=1 j6=i,k = 1 + (1 − 2x1 ) q11 + (q13 + q31 )x3 +(1 − 2x2 ) q22 + (q23 + q32 )x3 + (q12 + q21 )(1 − x1 − x2 ) = 1 + (1 − 2 × 1) 1 + (3 + 7) × 0 +(1 − 2 × 0) 5 + (6 + (−8)) × 0 + ((−2) + 4)(1 − 1 − 0) = 1 − (1) + (5) = 5 CAPÍTULO 5. CONTRIBUIÇÕES E IMPLEMENTAÇÕES PARALELAS 5.1.4 73 Cálculos do Limitante Inferior à f (x) no Branch and Bound Nesta seção apresentamos um limitante inferior à f (x), Fórmula (5.4), na forma básica para o método Branch and Bound (B&B) discutido em [48]. Apresentamos também a nossa contribuição para este cálculo na Fórmula (5.5). Esta fórmula gera limitantes, para quaisquer instâncias, com valores sempre maiores ou iguais aos encontrados pela Fórmula (5.4). Para resolver o UQP por meio de um B&B, utiliza-se uma árvore de enumeração, B = (P, E), para representar o processo de decomposição. Sejam P um conjunto de vértices e E um conjunto de arcos. A raiz de B denotada por P0 , representa o problema original e os subproblemas de P0 são representados por Pi para i ∈ N∗ . O subproblema Pj é “filho” de Pi quando Pj é gerado a partir de uma decomposição de Pi , assim (Pi , Pj ) ∈ E. O método B&B tenta resolver P0 examinando vértices em B. Durante este processo, é feito um teste para verificar se os vértices Pi são incapazes de produzir uma solução ótima para P0 , podendo assim eliminar este vértice da investigação do método. Este teste utiliza uma função g : P → Q, que gera limitantes inferiores às soluções ótimas para Pi . Esta função precisa satisfazer três condições: 1. g(Pi ) ≤ f (Pi ) para Pi ∈ P ; 2. g(Pi ) = f (Pi ) para vértices folha de B; 3. g(Pj ) ≥ g(Pi ) se Pj é um “filho” de Pi em B. Em [48], Pardalos e Rodgers propõem a função dada na Fórmula 5.4 como um limitante inferior ao valor de qualquer solução ótima de P0 . A ideia por trás da fórmula é a seguinte: considere x ∈ {0, 1}n , a função f (x) é simplesmente a soma dos coeficientes qij , onde xi = xj = 1. Assim uma função que representa o limitante que satisfaz os três critérios de limitante inferior é: a soma de todos os coeficientes negativos de Q ∈ Qn×n , menos a soma dos coeficientes negativos que devem ser excluı́dos do valor da função, devido as variáveis fixadas em 0, mais a soma dos coeficientes positivos que devem ser incluı́dos no valor da função devido às variáveis fixadas em 1. Sejam lev o nı́vel da árvore de enumeração (isto é, o número de variáveis com valores fixados em 0 ou 1), p1 , p2 , . . . , plev os ı́ndices das variáveis fixadas e plev+1 , plev+2 , . . . , pn os ı́ndices das variáveis livres. A função do limitante inferior g1 (x) é: n n X X g1 (x) = i=1 j=1 + lev X lev X i=1 j=1 qij− lev X n lev X X − − − 2 qpi pj (1 − xpi ) + qpi pi (1 − xpi ) i=1 j=i+1 qp+i pj xpi xpj i=1 (5.4) CAPÍTULO 5. CONTRIBUIÇÕES E IMPLEMENTAÇÕES PARALELAS 74 onde qij− = min{qij , 0} e qij+ = max{qij , 0} são os coeficientes não positivos e não negativos de Q, respectivamente. Quando se tem lev = n, o valor da função g1 (x) deveria coincidir com o valor de f (x) = xT Qx. Mas conforme Exemplo 5.5 isso não ocorre para algumas instâncias. Fizemos uma alteração na Fórmula (5.4), para que seja possı́vel calcular um limitante inferior à f (x) do UQP para quaisquer instâncias, que apresentamos na Fórmula (5.5). Os valores dos limitantes inferiores à f (x) gerados por (5.5) são maiores ou iguais aos gerados por (5.4), conforme provado na Proposição 5.2. n X n X g2 (x) = + qij− − 2 lev X lev X i=1 j=1 i=1 lev X ! qp−i pi (1 − xpi ) + i=1 qp−i pj (1 − xpi xpj ) + j=i+1 lev lev X X n X qp−i pj (1 − xpi ) j=lev+1 qp+i pj xpi xpj (5.5) i=1 j=1 Exemplo 5.5. Se f (x) = xT Qx, x = [0, 1, 1, 0], p = [1, 2, 3, 4], n = 4, lev = 4 e Q= 0 28 49 0 28 0 0 −7 49 0 0 0 0 −7 0 33 então os valores de f (x), g1 (x) e g2 (x) são: 0 28 h i 28 0 f (x) = 0, 1, 1, 0 49 0 0 −7 0 h i 1 = 77, 0, 0, −7 1 0 = 0 0 49 0 1 0 −7 0 0 1 0 0 33 CAPÍTULO 5. CONTRIBUIÇÕES E IMPLEMENTAÇÕES PARALELAS g1 (x) = 4 X 4 X 75 min(qij , 0) − 2 min(q12 , 0)(1 − x1 ) + min(q13 , 0)(1 − x1 ) + min(q14 , 0)(1 − x1 ) i=1 j=1 + min(q23 , 0)(1 − x2 ) + min(q24 , 0)(1 − x2 ) + min(q34 , 0)(1 − x3 ) + min(q11 , 0)(1 − x1 ) X 4 X 4 + min(q22 , 0)(1 − x2 ) + min(q33 , 0)(1 − x3 ) + min(q44 , 0)(1 − x4 ) + max(qij , 0)xi xj i=1 j=1 = −14 − 2 0(1 − 0) + 0(1 − 0) + 0(1 − 0) + 0(1 − 1) + (−7)(1 − 1) + 0(1 − 1) + 0(1 − 0) + 0(1 − 1) + 0(1 − 1) + 0(1 − 0) + 0 = −14 − 2(0) = −14 g2 (x) = 4 X 4 X min(qij , 0) − 2 min(q12 , 0)(1 − x1 x2 ) + min(q13 , 0)(1 − x1 x3 ) i=1 j=1 + min(q14 , 0)(1 − x1 x4 ) + min(q23 , 0)(1 − x2 x3 ) + min(q24 , 0)(1 − x2 x4 ) + min(q34 , 0)(1 − x3 x4 ) + min(q11 , 0)(1 − x1 ) + min(q22 , 0)(1 − x2 ) X 4 4 X max(qij , 0)xi xj + min(q33 , 0)(1 − x3 ) + min(q44 , 0)(1 − x4 ) + i=1 j=1 = −14 − 2 0(1 − 0 × 1) + 0(1 − 0 × 1) + 0(1 − 0 × 0) + 0(1 − 1 × 1) +(−7)(1 − 1 × 0) + 0(1 − 1 × 0) + 0(1 − 0) + 0(1 − 1) + 0(1 − 1) + 0(1 − 0) + 0 = −14 − 2(−7) = −14 + 14 = 0 Note que o valor de g2 (x) ≥ g1 (x) e g2 (x) = f (x). A Proposição 5.2 relaciona as Fórmulas (5.4) e (5.5). Proposição 5.2. Suponha f (x) = xT Qx, x ∈ {0, 1}n e Q ∈ Qn×n uma matriz simétrica. As expressões nas Fórmulas (5.4) e (5.5) satisfazem g1 (x) ≤ g2 (x), onde xp1 , xp2 , . . . , xplev têm seus valores fixados em 0/1 e xplev+1 , xplev+2 , . . . , xpn não têm seus valores fixados. Prova: Utilizamos o seguinte fato na demonstração: max{1 − xpi } ≤ min{1 − xpi xpj }. O max{1 − xpi } = 1 ocorre quando xpi = 0, neste caso min{1 − xpi xpj } = 1. O min{1 − xpi xpj } = 0 ocorre quando xpi = xpj = 1, neste caso max{1 − xpi } = 0. Assim, max{1 − xpi } ≤ min{1 − xpi xpj } com xpi , xpj ∈ {0, 1}. CAPÍTULO 5. CONTRIBUIÇÕES E IMPLEMENTAÇÕES PARALELAS n X n X g1 (x) = qij− lev X n lev X X − − 2 qpi pj (1 − xpi ) + qp−i pi (1 − xpi ) i=1 j=1 + lev X lev X 76 i=1 j=i+1 i=1 qp+i pj xpi xpj i=1 j=1 n X n X = + qij− − 2 lev X lev X i=1 j=1 i=1 lev X ! qp−i pi (1 − xpi ) + i=1 + qij− − 2 qp−i pj (1 − xpi ) j=lev+1 lev X lev X lev X lev X i=1 j=1 i=1 lev X ! i=1 j=i+1 n X qp+i pj xpi xpj i=1 j=1 n X n X ≤ qp−i pj (1 − xpi ) + qp−i pi (1 − xpi ) + qp−i pj (1 − xpi xpj ) + j=i+1 lev X lev X n X qp−i pj (1 − xpi ) j=lev+1 qp+i pj xpi xpj i=1 j=1 = g2 (x) Resolvemos a instância bqp50.6 (sexta instância no arquivo de instâncias com 50 variáveis, ver Seção 6.2.1) utilizando o método Branch and Bound e calculamos os limitantes com as funções g1 (x) e g2 (x). A Figura 5.2 mostra os valores dos limitantes ao longo de várias iterações do B&B. Note que g2 (x) sempre forneceu valores maiores ou iguais aos obtidos por g1 (x). No texto a seguir, apresentamos como efetuar de maneira rápida o cálculo do limitante inferior à f (x) para o UQP. Recálculo do Limitante Inferior à f (x) com Acréscimo de um Nı́vel na Árvore de Enumeração O cálculo do limitante inferior à f (x) na sua forma básica tem complexidade computacional O(n2 ). Utilizando o Algoritmo 3.9.1, sempre que se desce na árvore de enumeração do Branch and Bound aumentando-se o nı́vel em uma unidade, é possı́vel realizar o cálculo do limitante inferior à f (x) com complexidade O(n). Proposição 5.3. Sejam f (x) o valor de xT Qx para um dado x ∈ {0, 1}n e Q ∈ Qn×n uma matriz simétrica. Suponha que lev ∈ {1, 2, . . . , n} é o número de variáveis fixadas em 0 ou 1, em um vértice da árvore de enumeração do Branch and Bound e g2 Anterior é o valor do limitante inferior à f (x) neste vértice, calculado pela Fórmula (5.5), no nı́vel lev − 1. CAPÍTULO 5. CONTRIBUIÇÕES E IMPLEMENTAÇÕES PARALELAS 77 0 Valores dos limitantes inferiores -1000 g1 g2 -2000 -3000 -4000 -5000 -6000 -7000 1 16 31 46 61 76 91 106 121 136 151 166 181 196 211 226 241 Número de Iterações no método Branch and Bound Figura 5.2: Comparação entre os valores de g1 (x) e g2 (x) ao longo da execução do Branch and Bound para a instância bqp50.6 Então pode-se recalcular o valor do limitante, para o nı́vel lev, da seguinte maneira: g2 (x) = g2 Anterior + D, onde D é: D = (xplev lev−1 n X X − − − qplev pj + qplev plev − 1) 2 qpi plev xpi + i=1 +xplev lev−1 X qp+i plev xpi i=1 j=lev+1 + lev X qp+lev pj xpj (5.6) j=1 Assim o recálculo é realizado em tempo O(n). Prova: Obtemos uma pxpressão para D realizando uma subtração entre os valores de CAPÍTULO 5. CONTRIBUIÇÕES E IMPLEMENTAÇÕES PARALELAS g2 (x) calculados nos nı́veis lev e lev − 1. A prova é como segue: ( D = n X n X X lev X lev n X − − 2 qpi pj (1 − xpi xpj ) + qp−i pj (1 − xpi ) qij− i=1 j=1 i=1 j=i+1 j=lev+1 ) X lev lev X lev X + qp−i pi (1 − xpi ) + qp+i pj xpi xpj i=1 ( − i=1 j=1 n X n X lev−1 n X lev−1 X X − − qij − 2 qpi pj (1 − xpi xpj ) + qp−i pj (1 − xpi ) i=1 j=1 + i=1 j=i+1 j=lev lev−1 X ) lev−1 X lev−1 X − + qpi pi (1 − xpi ) + qpi pj xpi xpj i=1 i=1 = −2 lev X lev X j=1 qp−i pj (1 − xpi xpj ) − 2 lev n X X i=1 j=i+1 + lev X lev X qp−i pj (1 − xpi ) − lev X i=1 j=lev+1 qp−i pi (1 − xpi ) i=1 qp+i pj xpi xpj i=1 j=1 +2 lev−1 X lev−1 X qp−i pj (1 − xpi xpj ) + 2 lev−1 X − qp−i pj (1 − xpi ) + lev−1 X qp−i pi (1 − xpi ) i=1 i=1 j=lev i=1 j=i+1 lev X lev X n X qp+i pj xpi xpj i=1 j=1 = h −2 lev X lev X qp−i pj (1 − xp i xp j ) + 2 −2 lev n X X qp−i pj (1 − xp i ) + 2 lev−1 X i=1 j=lev+1 h h − + lev X qp−i pi (1 i=1 lev X lev X i=1 j=1 qp−i pj (1 − xp i ) + n X i qp−i pj (1 − xpi ) + i=1 j=lev lev−1 X qp+i pj xpi xpj − qp−i pi (1 i=1 lev−1 X lev−1 X i=1 i − xpi ) + qp+i pj xpi xpj i j=1 Agora expandimos as quatro parcelas da fórmula acima: • Parcela 1: i − xp i xp j ) + i=1 j=i+1 i=1 j=i+1 h lev−1 X lev−1 X 78 CAPÍTULO 5. CONTRIBUIÇÕES E IMPLEMENTAÇÕES PARALELAS −2 = −2 = qp−i pj (1 − xpi xpj ) + 2 i=1 j=i+1 lev−1 P lev−1 P i=1 j=i+1 lev−1 P lev−1 P qp−i pj (1 − xpi xpj ) − lev−1 P lev−1 P qp−i pj (1 − xpi xpj ) i=1 j=i+1 lev−1 P − 2 qpi plev (1 i=1 − xpi xp lev) qp−i pj (1 − xpi xpj ) i=1 j=i+1 lev−1 P − qpi plev (1 − xpi xp lev) −2 i=1 lev−1 P − 2 qpi plev (xpi xp lev − 1) i=1 +2 = lev P lev P 79 lev P Observação: Se i = lev então j = i + 1 > lev, assim j=lev+1 qp−lev pj (1 − xplev xpj ) não se aplica. • Parcela 2: lev P −2 n P i=1 j=lev+1 lev−1 n P P qp−i pj (1 − xpi ) + 2 n P i=1 j=lev n P qp−i pj (1 − xpi ) qp−lev pj (1 − xplev ) j=lev+1 lev−1 P − qpi plev (1 − xpi ) +2 qp−i pj (1 − xpi ) + 2 i=1 i=1 j=lev+1 lev−1 n P − P qpi plev (1 − xpi ) −2 qp−lev pj (1 − xplev ) + 2 i=1 j=lev+1 = −2 i=1 j=lev+1 lev−1 n P P = lev−1 P qp−i pj (1 − xpi ) − 2 • Parcela 3: lev−1 lev P − P qpi pi (1 − xpi ) − qp−i pi (1 − xpi ) + i=1 = −qp−lev plev (1 − xplev ) − i=1 lev−1 P i=1 qp−i pi (1 − xpi ) + lev−1 P i=1 qp−i pi (1 − xpi ) = −qp−lev plev (1 − xplev ) • Parcela 4: lev P lev P = = qp+i pj xpi xpj − i=1 j=1 lev−1 P lev−1 P lev−1 P lev−1 P i=1 j=1 lev−1 P qp+i pj xpi xpj + qp+i pj xpi xpj qp+i plev xpi xplev i=1 j=1 i=1 lev lev−1 lev−1 P P P + + qp+lev pj xplev pj − q p i p j xp i xp j j=1 i=1 j=1 lev−1 lev P + P qpi plev xpi xplev + qp+lev pj xplev pj i=1 j=1 Ao por juntas as quatro expressões obtidas com o desenvolvimento das parcelas acima, CAPÍTULO 5. CONTRIBUIÇÕES E IMPLEMENTAÇÕES PARALELAS 80 obtemos: = 2 lev−1 X qp−i plev (xpi xplev n X − 1) − 2 i=1 qp−lev pj (1 lev−1 X qp+i plev xpi xplev + i=1 lev−1 X i=1 n X lev−1 X qp−i plev (xpi xplev − xp i ) − 2 qp−i plev (1 − xpi ) i=1 qp+i plev xpi + lev X qp+lev pj xpj j=1 n X qp−lev pj (1 − xplev ) − qp−lev plev (1 − xplev ) j=lev+1 +xplev lev−1 X qp+i plev xpi + i=1 lev−1 X lev X qp+lev pj xpj j=1 qp−i plev xpi (xplev − 1) + 2 i=1 n X qp−lev pj (xplev − 1) + qp−lev plev (xplev − 1) j=lev+1 +xplev lev−1 X qp+i plev xpi + i=1 = (xplev lev−1 X j=lev+1 lev−1 X i=1 = 2 qp+lev pj xplev pj qp−lev pj (1 − xplev ) + 2 i=1 = 2 lev X j=1 qp−i plev (xpi xplev − 1) − 2 −qp−lev plev (1 − xplev ) + xplev qp−i plev (1 − xpi ) i=1 j=lev+1 −qp−lev plev (1 − xplev ) + = 2 − xplev ) + 2 lev−1 X lev X qp+lev pj xpj j=1 lev−1 n X X − − − − 1) 2 qpi plev xpi + qplev pj + qplev plev i=1 +xplev lev−1 X i=1 j=lev+1 qp+i plev xpi + lev X qp+lev pj xpj j=1 A fórmula final poderia ser mais reduzida, mas este formato é útil para implementação do algoritmo. Note que se xplev = 0 somente a primeira parcela é utilizada para efetuar o cálculo, caso contrário somente a segunda parcela é utilizada. Exemplo 5.6. Se f (x) = xT Qx, x = [0, 1, 0, 1], p = [1, 2, 3, 4], n = 4, lev = 3, g2 Anterior = −105 e Q= −40 −2 37 −41 −2 −40 −11 40 37 −11 −42 20 −41 40 20 −1 CAPÍTULO 5. CONTRIBUIÇÕES E IMPLEMENTAÇÕES PARALELAS 81 Então g2 (x): g2 (x) lev−1 X = g2 Anterior + (xplev − 1) 2 qp−i plev xpi + i=1 +xplev lev−1 X qp+i plev xpi + i=1 lev X qp+lev pj xpj n X qp−lev pj + qp−lev plev j=lev+1 j=1 − − − − = g2 Anterior + (xp3 − 1) 2 qp1 p3 xp1 + qp2 p3 xp2 + qp3 p4 + qp3 p3 + + + + + xp3 qp1 p3 xp1 + qp2 p3 xp2 + qp3 p1 xp1 + qp3 p2 xp2 + qp3 p3 xp3 = −105 + (0 − 1) 2 0 × 0 + (−11) × 1 + 0 + (−42) h i = −105 − − 22 − 42 = −105 + 64 = −41 5.1.5 Resumo dos Métodos de Cálculo e Recálculo do Valor da Função Objetivo e do Limitante Inferior à f (x) A Tabela 5.1 apresenta um resumo das quantidades de operações de multiplicação e adição para o recálculo do valor da função objetivo, tanto para matrizes assimétricas quanto para simétricas. A Tabela 5.2 apresenta um resumo das quantidades de operações de multiplicação e adição para o recálculo do limitante inferior à f (x) no B&B para matrizes assimétricas. A coluna Método mostra o nome do método de recálculo, Matriz Assimétrica e Matriz Simétrica mostram os números de operações para matrizes assimétricas e simétricas respectivamente, Mult. e Adições exibem os números de multiplicações e adições necessárias para cada método. Método de Cálculo Matriz Assimétrica Mult. Adições 2 n +n n2 − 1 Matriz Simétrica Mult. Adições n2 + n n2 − 1 Recálculo com mudança no valor de Uma variável n+1 2n n+1 n+1 Recálculo com mudança no valor de Duas variável 2n + 1 4n + 2 2n + 1 2n + 5 Cálculo na forma básica Tabela 5.1: Resumo do número de operações nos recálculos do valor da função objetivo. CAPÍTULO 5. CONTRIBUIÇÕES E IMPLEMENTAÇÕES PARALELAS Método de Cálculo Cálculo do limitante Recálculo do limitante com acréscimo de um nı́vel 82 Matriz Assimétrica Multiplicações. Adições 2 2 2lev + nlev + 1 n + lev(2n + 1) − 1 − ~(n, lev) 3lev + 1 2lev + n + 3 Tabela 5.2: Resumo do número de operações nos recálculos do valor do limitante inferior à f (x) no B&B. Assumimos ~(n, lev) = 1 se n − lev 6= 0 e 0 caso contrário. 5.2 Implementações Paralelas Nesta seção abordamos os algoritmos que foram paralelizados nesta dissertação e suas implementações em CUDA. O método Tabu Search que foi apresentado na forma sequencial na Seção 3.8.1, tem sua forma paralela descrita na Seção 5.2.1. Os resultados computacionais comparando as implementações sequencial e a paralela são apresentados nas Seções 6.3.2 e 6.3.3. O método exato Branch and Bound (B&B) que foi apresentado na sua forma sequencial na Seção 3.9.2, foi implementado de forma paralela, e é apresentado na Seção 5.2.2. Os resultados computacionais do B&B, comparando as implementações sequencial e a paralela são mostrados na Seção 6.4.2. 5.2.1 Tabu Search na Forma Paralela A implementação do Tabu Search (TS) sequencial apresentou excelentes resultados, como descrito nos experimentos computacionais na Seção 6.3.1. Devido a estes resultados, desenvolvemos uma versão paralela para este método. O método TS tem uma dependência de dados a cada iteração, pois a variável fixada na iteração anterior, que é a que forneceu melhor incremento no valor da função objetivo, deve ter seu valor preservado na próxima iteração. Ao realizar este procedimento, em paralelo, faz-se necessário realizar um sincronismo entre todas as threads, o que em CUDA significa encerrar o kernel. Por este motivo não paralelizamos todo o método. Identificamos as partes que possivelmente requerem muito esforço computacional, para avaliar a possibilidade de paralelizá-las. As partes identificadas foram o cálculo do valor da função objetivo e a busca local. Como a busca local implementada tem uma grande dependência de dados e pouco custo computacional em cada iteração, provavelmente sua paralelização não traria ganho em termos de speedup. Assim, a parte do código paralelizada foi o cálculo do valor da função objetivo. O método Tabu Search é explicado em detalhes na Seção 3.8.1, aqui abordamos os aspectos da versão paralela com GPUs. Explicamos o Algoritmo 5.2.1 a seguir. CAPÍTULO 5. CONTRIBUIÇÕES E IMPLEMENTAÇÕES PARALELAS 83 Na linha 5, aloca-se espaços de memória na GPU para armazenar o vetor solução x, a lista tabu L, o vetor que armazena o valor da melhor solução de cada bloco V kernel e o vetor V kernelIndice que guarda os ı́ndices das variáveis xi que ao alterá-las forneceu a melhor solução para cada bloco. Na linha 7, é feita a cópia dos vetores x e L da CPU para a GPU. Na linha 8, é feita a chamada do kernel onde calcula-se o valor da função objetivo em paralelo. Na linha 9, é feita a cópia dos vetores V kernel e V kernelIndice da GPU para a CPU. Em seguida, percorre-se o vetor V kernel para encontrar o ı́ndice da variável que fornece o maior valor para a função objetivo e armazena-o na variável k. Algoritmo 5.2.1: Pseudo-código do método Tabu Search paralelo com GPUs 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 Entrada: n ∈ N∗ , Q ∈ Qn×n , Solução Inicial ∈ {0, 1}n Saı́da: x∗ ∈ {0, 1}n inı́cio x ← Solução Inicial x∗ ← x, V ← f (x) Li ← 0 para i = 1, 2, . . . , n Cria espaços de memória na GPU para os vetores x, L, V kernel e V kernelIndice enquanto critério de parada não for satisfeito faça Copia os vetores x e L da CPU para a GPU Calcula F O Kernel(Q, x, L, V kernel, V kernelIndice, n) %Chamada do kernel Copia os vetores V kernel e V kernelIndice da GPU para a CPU V ← V kernel1 ; t ← V kernelIndice1 para i = 2 até Blocos faça se V kerneli > V então V ← V kerneli , t ← V kernelIndicei fim fim xt ← 1 − xt para i = 1 até n e Li > 0 faça Li ← Li − 1 fim se f (x) > f (x∗ ) então x∗ ← busca local(n, Q, x); Lt ← K fim fim fim O Algoritmo 5.2.2 apresenta o pseudo-código do kernel. Na linha 4, é utilizada a lista tabu para selecionar as variáveis que não podem sofrer alterações. Na linha 5, é feito o recálculo do valor da função objetivo, levando em consideração uma possı́vel alteração na i-ésima variável, na forma xi = 1 − xi . Na linha 10, é feita uma chamada à função ReductionM ax(), que é apresentada no Algoritmo 5.2.3. O objetivo desta função é encontrar o maior valor do vetor V , seu ı́ndice correspondente em V indice e copiá-los para os vetores V kernel e V kernelIndice, na posição correspondente ao bloco atual. Desta maneira, no término, os vetores V kernel e V kernelIndice armazenaram os maiores valores das soluções e os ı́ndices correspondentes às variáveis que foram alteradas. Na próxima seção apresentamos trechos dos códigos da implementação do método CAPÍTULO 5. CONTRIBUIÇÕES E IMPLEMENTAÇÕES PARALELAS Algoritmo 5.2.2: Pseudo-código do Kernel do Tabu Search 1 2 3 4 5 Entrada: n ∈ N∗ , Q ∈ Qn×n , x ∈ {0, 1}n , L ∈ Qn , V kernel ∈ QBlocos , V kernelIndice ∈ QBlocos , f (x) ∈ Q inı́cio para i = 1 até n EM PARALELO faça Vi ← −∞; se Li > 0 então n P Vi ← f (x) + (1 − 2xi ) qii + (qij + qji )xj j=1 j6=i V Indicei ← i 6 fim 7 fim Sincroniza as threads ReductionM ax(V, V Indice, V kernel, V kernelIndice) 8 9 10 11 fim Algoritmo 5.2.3: Pseudo-código do ReductionMax no Kernel do Tabu Search 1 2 3 4 5 6 7 8 9 10 11 Entrada: V ∈ Qn , V Indice ∈ Qn , V kernel ∈ QBlocos , V kernelIndice ∈ QBlocos , Número do Bloco ∈ N, Tamanho do bloco ∈ N inı́cio b ← Número do Bloco V kernelb ← V1 V KernelIndiceb ← 1 para i = 2 até Tamanho do bloco faça se Vi > V kernelb então V kernelb ← Vi V kernelIndiceb ← i fim fim fim 84 CAPÍTULO 5. CONTRIBUIÇÕES E IMPLEMENTAÇÕES PARALELAS 85 Tabu Search paralelizado com GPUs. Códigos da Implementação do Tabu Search em Paralelo Nesta seção descrevemos em detalhes, aspectos da implementação e otimizações em CUDA do método Tabu Search paralelizado com GPUs. Apresentamos trechos dos códigos implementados em Linguagem C / CUDA e em seguida discutimos o código. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ... //Copia dados da CPU para a GPU cudaMemcpy(d_X, X, nVars * sizeof(int), cudaMemcpyHostToDevice); cudaMemcpy(d_L, L, nVars * sizeof(int), cudaMemcpyHostToDevice); TSKernel<<<Blocos, ThreadsPerBlocks, size>>>(d_Q, d_X, d_L, d_VKernel, d_VKernelIndex, ValSol, nVars); //Copia dados da GPU para a CPU cudaMemcpy(VKernel, d_VKernel, Blocos * sizeof(int), cudaMemcpyDeviceToHost); cudaMemcpy(VKernelIndice, d_VKernelIndice, Blocos * sizeof(int), cudaMemcpyDeviceToHost); V=VKernel[0]; k=VKernelIndice[0]; for(i=1;i<Blocos;i++){ if(V < VKernel[i]){ V = VKernel[i]; k = VKernelIndice[i]; } } ... Código 5.1: Cópias de Dados e Chamada do Kernel no Tabu Search O Código 5.1 apresenta parte do programa que é executado na CPU. Neste trecho apresentamos como é feita a cópia dos vetores X e L da CPU para a GPU. Em seguida é feita a chamada do kernel, onde é realizado o cálculo do valor da função objetivo. Depois é feita a cópia dos vetores VKernel e VKernelIndex da GPU para a CPU. Em VKernel é armazenado o valor da melhor solução encontrada em cada bloco e em VKernelIndex o ı́ndice da variável X[i], que ao alterá-la gerou a solução cujo valor foi armazenado em VKernel. __global__ void TSKernel(int *Q, int *X, int *L, int *VKernel, int *VKernelIndice, int ValSol, int nVars){ 3 extern __shared__ int Xs[]; 4 __shared__ int V[BLOCKSIZE]; 5 __shared__ int VIndice[BLOCKSIZE]; 6 int tid = (blockIdx.x * blockDim.x) + threadIdx.x; 7 int nThread = threadIdx.x; 8 int i, mult; 1 2 CAPÍTULO 5. CONTRIBUIÇÕES E IMPLEMENTAÇÕES PARALELAS V[threadIdx.x] = -INT_MAX; 9 //Copia o vetor X para memória compartilhada while (nThread < nVars){ Xs[nThread] = X[nThread]; nThread += BLOCKSIZE; } 10 11 12 13 14 if((tid < nVars) && (L[tid]==0)){ mul = Xs[tid]==0 ? 1 : -1; 15 16 for(i=0;i<nVars;i++) ValSol += 2 * Q[i*nVars + tid] * Xs[i] * mult; ValSol += Q[tid*nVars + tid]; 17 18 19 V[threadIdx.x] = ValSol; VIndice[threadIdx.x] = tid; 20 21 } __syncthreads(); //Faz uma reduç~ ao no bloco encontrando o maior valor de Vtmp ReductionMax(VKernel, VKernelIndice, V, VIndice); 22 23 24 25 26 86 } Código 5.2: Kernel do Tabu Search O Código 5.2 mostra como é feito o cálculo do valor da função objetivo dentro do kernel. Como o vetor X é acessado várias vezes por todas as threads com tid < nVars, fazemos uma cópia de seus dados para o vetor Xs, que está alocado na memória compartilhada, cujo acesso é mais rápido (ver Seção 4.6). Como o vetor X pode ter tamanhos variáveis dependendo da instância, criamos o vetor na memória compartilhada passando o seu tamanho por parâmetro na chamada do kernel. Isto é feito fornecendo o parâmetro size na chamada do kernel e com o parâmetro extern ao criar a variável Xs dentro do kernel. Na linha 5 do kernel (Algoritmo 5.2.2), vemos que na fórmula de recálculo do valor da função objetivo devemos multiplicar a i-ésima linha da matriz Q pelo vetor x. Ao fazer este cálculo cada thread acessa posições distantes na área de memória global, que é onde encontra-se a matriz Q. A Figura 5.3(a) ilustra este acesso. Como as matrizes Q das instâncias utilizadas são simétricas, tem-se que Qi x = xT Qi , onde Qi e Qi são respectivamente a i-ésima linha e a i-ésima coluna da matriz Q. Desta forma multiplicamos o vetor x pela i-ésima coluna da matriz Q. Assim, é feito um acesso combinado à memória global ao efetuar o recálculo, como ilustra a Figura 5.3(b). Antes de se fazer o recálculo do valor da função foi feita uma tentativa de se copiar a matriz Q para a memória compartilhada, mas tal tentativa não surtiu melhorias no desempenho do programa, já que o acesso à matriz já é feito de forma combinada. É interessante utilizar a memória compartilhada para armazenar a matriz Q, quando as instâncias forem assimétricas, ou se for feito o cálculo do valor da função objetivo na forma básica. CAPÍTULO 5. CONTRIBUIÇÕES E IMPLEMENTAÇÕES PARALELAS 87 Ao final é feita a chamada à função ReductionMax(), que encontra os maiores valores no vetor V, seu correspondente ı́ndice em VIndice e armazena-os em VKernel e VKernelIndice. Th0 Th1 Th0 Th1 Th2 Th3 Th2 Th3 (a)Sem acesso combinado (b)Com acesso combinado Figura 5.3: Acesso a i-ésima linha e coluna da matriz Q __device__ void ReductionMax(int *VKernel, int *VKernelIndice, int *V, int *VIndice){ int qtd = blockDim.x/2; 3 while(qtd > 0){ 4 if(threadIdx.x < qtd){ 5 if(V[threadIdx.x] < V[threadIdx.x + qtd]){ 6 V[threadIdx.x] = V[threadIdx.x + qtd]; 7 VIndice[threadIdx.x] = VIndice[threadIdx.x + qtd]; 8 } 9 __syncthreads(); 10 } 11 qtd /= 2; 12 } 13 if(threadIdx.x == 0){ 14 VKernel[blockIdx.x] = V[0]; 15 VKernelIndice[blockIdx.x] = VIndice[0]; 16 } 17 } 1 2 Código 5.3: Função ReductionMax() O Código 5.3 encontra o maior valor de V, seu correspondente ı́ndice em VIndice e o armazena nos vetores VKernel e VKernelIndice na posição correspondente ao seu bloco. Ou seja, o valor de V referente ao k-ésimo bloco é armazenado em VKernel[k]. O código ReductionMax foi baseado no código ReductionSum apresentado em [41] na Página 79. Esta maneira de implementar a redução conta com algumas otimizações, tais como: Faz acesso combinado a memória global e evita conflito de bancos de memória. A Figura 5.4 mostra um exemplo da operação de redução feita pelo Código 5.3. 5.2.2 Branch and Bound na Forma Paralela A referência [49] descreve uma implementação paralela do B&B, utilizando um modelo mestre-escravo. Nossa implementação paralela utiliza um modelo fork-join. CAPÍTULO 5. CONTRIBUIÇÕES E IMPLEMENTAÇÕES PARALELAS 3 < 1 < 3 7 < 2 0 4 8 4 < 8 4 0 4 8 4 4 8 4 0 4 8 4 4 8 4 0 4 8 4 4 88 < < 8 < 8 Figura 5.4: Exemplo de operação de redução Uma solução inicial é gerada pelo método Gradient Midpoint Method (Algoritmo 3.8.4), que é melhorada pelo método Tabu Search (Algoritmo 3.8.1) e então fornecida como entrada para o método B&B, apresentado no Algoritmo 5.2.4, através do vetor x∗ e tem OP T = f (x∗ ). Denotamos por qij− = min{qij , 0} e qij+ = max{qij , 0}. As seguintes variáveis e constantes são usadas na descrição do Algoritmo 5.2.4. qij n OP T gOP T0 gOP T1 x∗ levIni lev p1 , . . . , plev plev+1 , . . . , pn xp1 , xp2 , . . . , xplev g2 lb ub p N SU BP T id Coeficientes na linha i e coluna j na matriz Q; Número de variáveis de uma instância; Valor da melhor solução encontrada; Valor da melhor solução encontrada até o momento; Posição na memória que contém a melhor solução até o momento; Melhor solução encontrada até o momento; Nı́vel inicial na busca da árvore de enumeração do B&B; Nı́vel atual na árvore de enumeração do B&B; Índices das variáveis fixadas no subproblema atual; Índices das variáveis livres no subproblema atual; Variáveis fixadas; Limitante inferior à f (x); Limitante inferior à ∇f (x) ∈ [0, 1]n ; Limitante superior à ∇f (x) ∈ [0, 1]n ; Vetor de permutações; Número de subproblemas resolvidos; Identificador da thread. Os valores de gOP T0 e gOP T1 são compartilhados entre todas as threads do kernel. CAPÍTULO 5. CONTRIBUIÇÕES E IMPLEMENTAÇÕES PARALELAS 89 O Algoritmo 5.2.4 inicia fazendo alocações de áreas para dados na GPU e copiando os dados dos vetores x, x∗ , p, lb, ub, gOP T e da matriz Q da CPU para a GPU. Em seguida, na Linha 3, é feito um laço com a informação “para cada thread EM PARALELO”, que significa que cada thread executará uma única vez o conteúdo deste laço. Na Linha 4, um identificador da thread é atribuı́do a variável nT id. Cada thread resolverá um subproblema do problema principal. O número de threads em execução, será sempre um número que é potência de 2. A decomposição dos subproblemas é feita fornecendo um trecho inicial fixo e diferente, de levIni posições, no vetor x. Desta forma cada thread examinará uma parte da árvore de enumeração. O código entre as Linhas 5 e 10 preenche as primeiras levIni posições do vetor x, convertendo o identificador da thread, nT id, em sua representação binária. As demais posições do vetor são preenchidas com zeros. Na Linha 7, nT id mod 2 representa o resto da divisão de nT id por 2. A Linha 12, inicializa-se os limitantes de ∇f (x), lb e ub. Na Linha 13, atualiza estes limitantes levando em consideração as primeiras levIni posições fixadas do vetor x, da seguinte maneira: para i = 1, . . . , lev lbpj ← lbpj +2 xpi qp+i pj − (1 − xpi )qp−i pj para i = 1, . . . , lev ubpj ← ubpj +2 xpi qp−i pj − (1 − xpi )qp+i pj j = lev + 1, . . . , n j = lev + 1, . . . , n O vetor de permutações p é inicializado na Linha 14 e as variáveis lev, N SU BP e gOP T0 são inicializadas na Linha 15. Uma pilha, estrutura de dados, é utilizada para armazenar informações de subproblemas quando um branching (ramificação da árvore) é necessário. Na Linha 16, a pilha é inicializada com o valor −1 para a variável lev, que indica que a pilha está vazia. A thread termina seu trabalho quando este valor é desempilhado. O cálculo do limitante inferior à f (x) na Linha 18, é feito conforme a Fórmula (5.5). O restante do código, nas Linhas 17 a 50, faz o mesmo que o método B&B sequencial descrito no Algoritmo 3.9.1, com algumas pequenas alterações de implementação, mas não de funcionalidade, que discutiremos a seguir: • Na Linha 22 o identificador da thread que encontrou a melhor solução é armazenado na variável gOP T1 . • Nas Linhas 29 e 30, atualiza-se os valores dos vetores lb e ub. Estas linhas efetuam o mesmo cálculo que o apresentado no Algoritmo sequencial, porém eliminou-se o condicional (se), que em CUDA atrapalha o fluxo do programa. As Linhas 51 e 52 copiam os dados dos vetores gOP T e x∗ da GPU para a CPU. CAPÍTULO 5. CONTRIBUIÇÕES E IMPLEMENTAÇÕES PARALELAS 90 Algoritmo 5.2.4: Pseudo-código do método Branch and Bound em paralelo com GPUs 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 Entrada: n ∈ N∗ , Q ∈ Qn×n , x∗ ∈ Qn , OP T ∈ Q, levIni ∈ N, T id ∈ N Saı́da: x∗ ∈ {0, 1}n , f (x∗ ) ∈ Q Aloca área de memória na GPU para os vetores x, x∗ , p, lb, ub e gOP T Aloca área de memória na GPU para a matriz Q e copia os dados da matriz da CPU para a GPU para cada thread EM PARALELO faça nT id ← Tid para i = 1 até n faça se i ≤ levIni então xi ← nT id mod 2, nT id ← nT id/2 senão xi ← 0 fim fim P − P + lbi ← 2 qij + qii , ubi ← 2 qij + qii para i = 1, . . . , n j=1 j=1 j6=i j6=i Atualiza limitantes lbi e ubi para i = 1, 2, . . . , n pi ← i para i = 1, 2, . . . , n lev ← levIni, N SU BP ← 0, gOP T0 ← OP T empilha({-1,-,-,-},stack) enquanto lev 6= −1 faça g2 ← Calcula o limitante inferior g2 se g2 ≥ gOP T0 ou lev = n então se g2 < gOP T0 então gOP T0 ← g gOP T1 ← Tid x∗ ← x fim desempilha({lev, lb, ub, g},stack) se lev 6= −1 então xplev ← 1 − xplev senão se lev 6= 0 então lbpi ← lbpi + 2 xplev qp+i plev − (1 − xplev )qp−i plev para i = lev + 1, . . . , n ubpi ← ubpi + 2 xplev qp−i plev − (1 − xplev )qp+i plev para i = lev + 1, . . . , n 29 30 fim entrou = falso para i = lev até n faça se lbpi ≥ 0 ou ubpi ≤ 0 então se ubpi ≤ 0 então xpi ← 1 senão xpi ← 0 entrou = verdadeiro Sai do loop fim fim se entrou = falso então i ← j onde δj = max{min(−lbpt , ubpt ) para t = lev + 1, . . . , n 31 32 33 34 35 36 37 38 39 40 41 42 t xpi ← 0 ou 1 dependendo do valor que menos incrementa g empilha({lev+1, lb, ub, g},stack) 43 44 fim lev ← lev + 1 plev ↔ pi %Troca de valores de plev e pi 45 46 47 fim 48 49 50 51 52 fim fim Copia o vetor gOP T da GPU para a CPU Copia o vetor x∗ iniciando na posição indicada por gOP T1 da GPU para a CPU CAPÍTULO 5. CONTRIBUIÇÕES E IMPLEMENTAÇÕES PARALELAS 91 Códigos da Implementação do Branch and Bound em Paralelo Nesta seção abordaremos com mais detalhes os aspectos da implementação e otimizações em CUDA, do método Branch and Bound paralelizado com GPUs. Para isso, apresentamos trechos do código implementado em Linguagem C / CUDA e uma discussão sobre o código. O Código 5.4 apresenta parte do código sequencial que faz a alocação de espaço de memória na GPU, a cópia dos dados da CPU para a GPU, a chamada do kernel e a cópia dos dados da GPU para a CPU. Os Códigos 5.5 a 5.9 descrevem o kernel da aplicação. O Código 5.10 apresenta a função que calcula os limitantes de ∇f (x), enquanto os Códigos 5.11 e 5.12 mostram as funções que calculam o limitante inferior g, conforme a Fórmula (5.5). CAPÍTULO 5. CONTRIBUIÇÕES E IMPLEMENTAÇÕES PARALELAS 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 ... size_t size_t size_t size_t size_t size_t size sizenVars sizenSubProb sizeSB sizeP sizePlim = = = = = = 92 Inst->nVars * sizeof(int); Inst->nVars * sizeof(int); nSubProb * sizeof(int); nSubProb * Inst->nVars * sizeof(bool); nSubProb * Inst->nVars * sizeof(Pilha); nSubProb * Inst->nVars * Inst->nVars * sizeof(int); //Aloca espaço no device cudaMalloc((void **)d_gOPT, 2 * sizeof(int)); cudaMalloc((void **)d_x, sizeSB); cudaMalloc((void **)d_xInc, sizeSB); cudaMalloc((void **)d_pilha, sizeP); cudaMalloc((void **)d_pilhaLB, sizePlim); cudaMalloc((void **)d_pilhaUB, sizePlim); cudaMallocPitch((void cudaMallocPitch((void cudaMallocPitch((void cudaMallocPitch((void **)d_Q, &Pitch, sizenVars, Inst->nVars); **)d_p, &PitchSub, sizenSubProb, Inst->nVars); **)d_lb, &PitchSub, sizenSubProb, Inst->nVars); **)d_ub, &PitchSub, sizenSubProb, Inst->nVars); //Copia dados para o device cudaMemcpy2D(*d_Q, Pitch, Inst->getMatriz(), size, size, Inst->nVars, cudaMemcpyHostToDevice); //Espaço para a matriz Q e para os vetores ilb e iub int sMem = Inst->nVars * Inst->nVars * sizeof(int) + 2 * Inst->nVars * sizeof(int); BB_Kernel<<<Blocks,nThreadsPerBlock, sMem>>>(d_Q, Inst->nVars, d_x, d_xInc, d_p, d_lb, d_ub, *OPT, d_gOPT, d_pilha, d_pilhaLB, d_pilhaUB, pitch / sizeof(int), pitchsub/ sizeof(int), nSubProb, lev); cudaMemcpy(gOPT, d_gOPT, 2 * sizeof(int), cudaMemcpyDeviceToHost); cudaMemcpy(x, d_xInc + gOPT[1] * Inst->nVars, Inst->nVars * sizeof(bool), cudaMemcpyDeviceToHost); *OPT = gOPT[0]; ... Código 5.4: Cópias de Dados e Chamada do Kernel no Branch and Bound O Código 5.4 apresenta parte do programa que é executado na CPU. Neste trecho apresentamos como alocamos espaços de memória na GPU, com os comandos cudaMalloc e cudaMallocPitch. Ambos os comandos reservam uma seção contı́gua de memória, porém o comando cudaMallocPitch é destinado para matrizes. Ele cria um espaço vazio entre cada linha da matriz, gerando um alinhamento dos dados na memória global, ou seja, os dados de uma linha da matriz sempre começam em um endereço que é potência de 2. Com isso pode-se aumentar a performance do programa ao acessar os dados desta matriz, que inicialmente reside na memória global. O comando cudaMallocPitch retorna um valor, pitch, que indica o tamanho da área de dados somado a área vazia. A Figura 5.5 mostra um esquema do armazenamento dos dados na memória. As matrizes d_pilha, d_pilhaLB e d_pilhaUB são armazenadas em matrizes lineari- CAPÍTULO 5. CONTRIBUIÇÕES E IMPLEMENTAÇÕES PARALELAS 93 Pitch 1ª Linha n-ésima Linha 2ª Linha Área de dados Área vazia Figura 5.5: Matriz Linearizada Alocada com o Comando cudaMallocPitch zadas sem alinhamento de memória. Enquanto as matrizes d_Q, d_p, d_lb e d_ub são armazenadas em matrizes linearizadas com alinhamento de memória. A matriz Q é copiada da CPU para a GPU por meio do comando cudaMemcpy2D, que faz a cópia de dados para uma área alocada pelo comando cudaMallocPitch. A variável sMem guarda o valor do tamanho em bytes que será utilizado na memória compartilhada. Em seguida é feita a chamada do kernel, onde é executado todo o código do B&B. Por fim é feita a cópia dos vetores d_xInc e gOPT da GPU para a CPU. É importante notar que cada coluna nas matrizes d_p, d_lb e d_ub representam os vetores p, lb e ub, que cada thread utiliza individualmente. Os dados foram armazenados desta forma para possibilitar o acesso combinado à memória global, embora para d_lb e d_ub nem sempre isto seja possı́vel. Esta forma de armazenamento dos dados gerou um bom ganho em termos de speedup. A matriz a seguir ilustra o esquema de armazenamento da matriz d_p, onde n é o número de variáveis da instância, m é o número de subproblemas nos quais o problema principal foi dividido. Este número é dado por 2levIni e pij representa o j-ésimo elemento da i-ésima thread. p11 p12 d_p = p13 . .. p1n p21 p31 p22 p32 p23 p33 .. .. . . p2n p3n ... ... ... .. . ... pm1 pm2 pm3 .. . pmn Portanto, para que a i-ésima thread acesse a j-ésima posição do vetor p, deve-se utilizar o seguinte comando: p[j * pitch + i]. __global__ void BB_Kernel(int *d_Q, int nVars, bool *d_x, bool *d_xInc, int *p, int *lb, int *ub, int OPT, int *gOPT, struct Pilha *d_pilha, int *d_pilhaLB, 3 int *d_pilhaUB, int pitch, int pitchsub, int nSubProb, int lev){ 4 extern __shared__ int sharedMem[]; 5 int *Q = (int *)&sharedMem[0]; 6 int *ilb = (int *)&sharedMem[nVars * nVars]; 1 2 CAPÍTULO 5. CONTRIBUIÇÕES E IMPLEMENTAÇÕES PARALELAS 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 94 int *iub = (int *)&ilb[nVars]; __shared__ int sOPT; int tid = blockDim.x * blockIdx.x + threadIdx.x; int nThread = threadIdx.x; if(threadIdx.x == 0) sOPT = OPT; if(tid == 0) *gOPT = OPT; //Copia a matriz Q para memória compartilhada while (nThread < nVars * nVars){ int linha = nThread / nVars; Q[nThread] = d_Q[linha * pitch + (nThread % nVars)]; nThread += blockDim.x; } __syncthreads(); //Calcula os limitantes de ∇f (x) calculaBounds(Q, ilb, iub, nVars); __syncthreads(); Código 5.5: Kernel do Branch and Bound Parte 1 O Código 5.5 apresenta o inı́cio do kernel. O vetor sharedMem é alocado na memória compartilhada e armazenará os valores da matriz Q e dos vetores ilb iub. O vetor gOPT contém duas posições, na primeira é armazenado o valor da melhor solução encontrada até o momento e na segunda o identificador da thread que encontrou a solução. Este vetor é armazenado na memória global. Note que gOPT[0] = *gOPT. Como é necessário acessar o valor de gOPT[0] algumas vezes ao longo do programa, é feita uma cópia deste valor para a variável sOPT, que reside na memória compartilhada. Nas Linhas 14 a 18 é feita uma cópia da matriz Q para a memória compartilhada. Na Linha 21 calcula-se os limitantes, superiores e inferiores, do gradiente da função f (x). O código da função calculaBounds é mostrado no Código 5.10. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 if(tid < nSubProb){ bool *x = (bool *)&d_x[tid * nVars]; bool *xInc = (bool *)&d_xInc[tid * nVars]; int i, maior, menor, aux, gAux, g=0, levIni = lev; int plev, pi, Qaux, lbAux, ubAux, iAux; //Variáveis para otimizaç~ ao de performance bool xAux, entrou; //Variáveis para otimizaç~ ao e controle //Inicializa as variáveis para todas as threads (x, xInc, p, lb e ub) int nTid = tid; for(i = 0; i < nVars; i++){ if(i < lev){ xInc[lev - i - 1] = x[lev - i - 1] = nTid & 1; nTid = nTid >> 1; }else{ xInc[i] = x[i] = 0; } p[i * pitchsub + tid] = i; //Define os valores do vetor p CAPÍTULO 5. CONTRIBUIÇÕES E IMPLEMENTAÇÕES PARALELAS lb[i * pitchsub + tid] = ilb[i]; //Copia o vetor lb para todas as threads ub[i * pitchsub + tid] = iub[i]; //Copia o vetor ub para todas as threads 17 18 19 } 20 //Ajusta os vetores lb e ub com as variáveis fixadas for(i = 0; i < lev; i++){ xAux = x[i]; for(int j = lev; j < nVars; j++){ Qaux = Q[i * nVars + j]; lb[j * pitchsub + tid] += 2 * (xAux * positivo_cuda(Qaux) - (1-xAux) * negativo_cuda(Qaux)); ub[j * pitchsub + tid] += 2 * (xAux * negativo_cuda(Qaux) - (1-xAux) * positivo_cuda(Qaux)); } } 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 95 //Ajusta os ponteiros da pilha struct Pilha *pilha = &d_pilha[tid * nVars]; for(int i = 0; i < nVars; i++){ pilha[i].lb = &d_pilhaLB[tid * nVars * nVars + (i * nVars)]; pilha[i].ub = &d_pilhaUB[tid * nVars * nVars + (i * nVars)]; } int nElemPilha = 0; //Número de elementos da pilha //Empilha nó sentinela pilha[nElemPilha].lev = -1; pilha[nElemPilha].g = g; Código 5.6: Kernel do Branch and Bound Parte 2 Dentro do kernel foram utilizadas variáveis para otimização de performance, que nada mais são do que variáveis auxiliares alocadas nos registradores. Quando um valor que reside na memória global é utilizado mais de uma vez, armazenamos este valor em uma variável auxiliar e a reutilizamos em seu lugar. No Código 5.6 vemos a declaração destas variáveis auxiliares. No Código 5.6, das Linhas 8 a 15 são definidos valores iniciais para o vetor x, que contém nas primeiras levIni posições uma representação binária do identificador da thread, o que faz com que cada thread tenha um vetor x diferente, e assim trabalhem com um subproblema diferente. Em seguida o vetor p é iniciado com pi = i para i = 0, 1, . . . , n − 1 e os valores dos vetores ilb e iub são copiados para os vetores lb e ub de todas as threads. Como as primeiras levIni posições do vetor x foram fixadas, os limitantes lb e ub são atualizados no trecho do código entre as Linhas 21 à 30. As funções negativo(a) e positivo(a) equivalem, respectivamente, a max(a, 0) e min(a, 0). A pilha, estrutura de dados, também é armazenada em uma matriz linearizada. A sua estrutura é dada como segue: struct Pilha { int g, lev *lb, *ub; }; Das linhas 32 à 37 criam-se o ponteiro para as pilhas de cada thread e inicializam os ponteiros dos vetores pilha[i].lb e pilha[i].ub para i = 0, 1, . . . , n − 1. Nas Linhas 39 e 40, empilha-se um nó sentinela. O trabalho de cada thread termina quando este nó CAPÍTULO 5. CONTRIBUIÇÕES E IMPLEMENTAÇÕES PARALELAS 96 é desempilhado. A Figura 5.6 apresenta um diagrama de como os dados da pilha são armazenados na memória. pilha[0].lb pilha[1].lb * * * * * * pilha[n-1] pilha[1] pilha[0] pilha[0].ub pilha[n-1].lb pilha[1].ub pilha[n-1].ub g lev *lb * *ub * Célula da Pilha Figura 5.6: Armazenamento da Pilha em Memória 1 2 3 while(lev != -1){ if(threadIdx.x == 0) sOPT = *gOPT; 4 g = calculaG_cuda(Q, nVars, lev, x, p, pitchsub, tid); 5 if(g >= sOPT || lev == nVars){ if(g < sOPT){ atomicMin(&sOPT,g); //Atualiza a variável OPT da memória compartilhada atomicMin(&gOPT[0],g); //Atualiza a variável gOPT da memória global 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 if(gOPT[0] == g) atomicExch(&gOPT[1], tid); for(i = 0; i < nVars; i++) xInc[i] = x[i]; } //Desempilha nó lev = pilha[nElemPilha].lev; g = pilha[nElemPilha].g; for(i = 0; i < nVars; i++){ lb[i * pitchsub + tid] = pilha[nElemPilha].lb[i]; ub[i * pitchsub + tid] = pilha[nElemPilha].ub[i]; } nElemPilha--; CAPÍTULO 5. CONTRIBUIÇÕES E IMPLEMENTAÇÕES PARALELAS 97 if(lev != -1){ plev = p[lev * pitchsub + tid]; xAux = x[plev] = 1 - x[plev]; lev++; } 22 23 24 25 26 } 27 Código 5.7: Kernel do Branch and Bound Parte 3 No Código 5.7, a primeira thread de cada bloco faz uma cópia do valor de *gOPT, que está na memória global, para a variável sOPT que está na memória compartilhada. Na Linha 4 é feito o cálculo do limitante inferior, que é apresentado no Código 5.11. Na Linha 5, se g >= sOPT, indica que o subproblema atual pode ser podado e se lev == nVars, significa que o subproblema atual é um vértice folha da árvore de enumeração, então devese verificar se a solução encontrada é melhor que a melhor encontrada até o momento. Caso lev == nVars e g < sOPT, os valores de sOPT e gOPT[0] são atualizados utilizando funções atômicas2 e a thread que teve seu valor gravado em gOPT[0] armazenará em gOPT[1] o seu identificador. Nas Linhas 11 à 13, cada thread copia os vetores x para o vetor que armazena a melhor solução encontrada até o momento, xInc. Em seguida retira-se o nó do topo da pilha e se não for o nó sentinela, altera-se o vetor x na posição lev, fazendo com que x[lev] = 1 - x[lev]. O valor de x[lev] é armazenado na variável xAux, para ser reusado sem ter que acessar o vetor x que reside na memória global. Note que plev também é uma variável de otimização. else{ entrou = false; maior = -INT_MAX; 1 2 3 for(i = lev; i < nVars; i++){ pi = p[i * pitchsub + tid]; Qaux = Q[pi * nVars + p[(lev-1) * pitchsub + tid]]; lbAux = lb[pi * pitchsub + tid] + 2 * (xAux * positivo_cuda(Qaux) - (1-xAux) * negativo_cuda(Qaux)); ubAux = ub[pi * pitchsub + tid] + 2 * (xAux * negativo_cuda(Qaux) - (1-xAux) * positivo_cuda(Qaux)); 4 5 6 7 8 9 10 if(lev != levIni){ lb[pi * pitchsub + tid] = lbAux; ub[pi * pitchsub + tid] = ubAux; } 11 12 13 14 //Verifica se é possı́vel determinar o valor para xpi if(!entrou){ if(lbAux >= 0 || ubAux <= 0){ 15 16 17 2 Uma função atômica junta operações de leitura e escrita de dados em uma única transação. Quando duas ou mais threads tentam modificar dados de um mesmo endereço de memória, ela serializa estas operações. Para maiores informações ver [42] CAPÍTULO 5. CONTRIBUIÇÕES E IMPLEMENTAÇÕES PARALELAS xAux = x[pi] = ubAux <= 0 ? 1 : 0; iAux = i; entrou = true; goto PULA; 18 19 20 21 22 } 23 //Encontra a variável menos provável a ter seu valor determinado menor = min(-lbAux, ubAux); if(menor > maior){ maior = menor; iAux = i; } PULA:; 24 25 26 27 28 29 } 30 31 98 } Código 5.8: Kernel do Branch and Bound Parte 4 O Código 5.8 é executado quando o nó, que está sendo explorado na árvore de enumeração, não pode ser podado e nem é um nó folha. No Algoritmo 5.2.4, as Linhas 29, 30, 32 e 42 percorrem os vetores lb e ub das posições plev até pn . No Código 5.8, fazemos essa varredura uma única vez no loop que encontra-se entre as Linhas 4 e 31. Nas Linhas de 7 à 14 atualiza-se os limitantes de ∇f (x). Das Linhas 16 à 22, é verificado se é possı́vel determinar o valor de x[pi] e nas Linhas de 24 à 28 encontra-se a variável que é menos provável ter seu valor determinado. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 i = iAux; if(!entrou){ pi = p[i * pitchsub + tid]; x[pi] = 0; gAux = calculaG_aux_cuda(Q, nVars, lev, x, p, i, pitchsub, tid); xAux = x[pi] = 1; g = calculaG_aux_cuda(Q, nVars, lev, x, p, i, pitchsub, tid); if(gAux < g){ xAux = x[pi] = 0; g = gAux; } //Empilha nó nElemPilha++; pilha[nElemPilha].lev = lev; pilha[nElemPilha].g = g; for(int k = 0; k < nVars; k++){ pilha[nElemPilha].lb[k] = lb[k * pitchsub + tid]; pilha[nElemPilha].ub[k] = ub[k * pitchsub + tid]; } } aux = p[lev * pitchsub + tid]; p[lev * pitchsub + tid] = p[i * pitchsub + tid]; CAPÍTULO 5. CONTRIBUIÇÕES E IMPLEMENTAÇÕES PARALELAS p[i * pitchsub + tid] = aux; lev++; 23 24 } 25 } 26 } 27 28 99 } Código 5.9: Kernel do Branch and Bound Parte 5 Se não foi possı́vel determinar o valor da variável x[pi] no Código 5.8, então no Código 5.9, utiliza a informação da variável menos provável a ser determinada e verifica qual é o valor em x[pi] que menos incrementa o limitante inferior da função objetivo. O cálculo deste limitante, nesta situação, é feito através da função calculaG_aux_cuda, que é apresentado no Código 5.12. Como não foi possı́vel determinar o valor de x[pi], este nó da árvore de enumeração é armazenado na pilha, para ser investigado posteriormente. Em seguida é feita a troca de valores no vetor de permutações, fixando a variável x[pi] e incrementa-se em uma unidade a variável lev, que controla a altura da árvore de enumeração. __device__ void calculaBounds(int *Q, int *lb, int *ub, int nVars) { 3 if(threadIdx.x < nVars){ 4 lb[threadIdx.x] = ub[threadIdx.x] = 0; 5 for(int j = 0; j < nVars; j++){ 6 lb[threadIdx.x] += 2 * negativo_cuda(Q[threadIdx.x * nVars + j]); 7 ub[threadIdx.x] += 2 * positivo_cuda(Q[threadIdx.x * nVars + j]); 8 } 1 2 lb[threadIdx.x] += ( ub[threadIdx.x] += ( - 9 10 11 12 * * * * nVars nVars nVars nVars + + + + threadIdx.x]) threadIdx.x])); threadIdx.x]) threadIdx.x])); } 13 14 positivo_cuda(Q[threadIdx.x negativo_cuda(Q[threadIdx.x negativo_cuda(Q[threadIdx.x positivo_cuda(Q[threadIdx.x } Código 5.10: Função calculaBounds do Kernel no Branch and Bound O Código 5.10 calcula os limitantes de ∇f (x) de forma paralela, conforme as equações lbi = qii + 2 n X qij− para i = 1, 2, . . . , n qij+ para i = 1, 2, . . . , n j=1 ubi = qii + 2 j6=i n X j=1 j6=i onde qij− = min{qij , 0} e qij+ = max{qij , 0}. CAPÍTULO 5. CONTRIBUIÇÕES E IMPLEMENTAÇÕES PARALELAS 100 __host__ __device__ int calculaG_cuda(int *Q, int nVars, int lev, const bool *x, int *p, int pitchsub, int tid){ 3 int i, j, pi, pj, qAux, g = 0; 4 bool xpi, xpj, xpixpj; 5 int *__pi, *__pj, *__Q; 1 2 __pi = p+tid; for(i = 0; i < nVars; i++){ pi = *__pi; __pi += pitchsub; xpi = x[pi]; 6 7 8 9 if(i < lev) g -= negativo_cuda(Q[pi * nVars + pi] * (1 - xpi)); //Terceira parte 10 11 __pj = p+tid; __Q = &Q[pi * nVars]; for(j = 0; j < nVars; j++){ pj = *__pj; __pj += pitchsub; xpj = x[pj]; qAux = *(__Q + pj); xpixpj = xpi * xpj; 12 13 14 15 16 17 18 19 g += negativo_cuda(qAux); //Primeira parte 20 if((i < lev) && (j > i)){ if(j < lev) g -= 2 * negativo_cuda(qAux * (1 - xpixpj)); //Segunda parte else g -= 2 * negativo_cuda(qAux * (1 - xpi)); //Segunda parte } if(j < lev) g += positivo_cuda(qAux * xpixpj); //Quarta parte 21 22 23 24 25 26 27 } } return g; 28 29 30 31 } Código 5.11: Função calculaG cuda do Kernel no Branch and Bound O Código 5.11 efetua o cálculo do limitante inferior à função objetivo através da seguinte fórmula: n X n X g2 = qij− − 2 i=1 j=1 {z | i=1 } + |i=1 j=i+1 Segunda P arte + lev X lev X qp−i pj (1 − xpi ) j=lev+1 {z ! qp−i pi (1 − xpi ) n X qp−i pj (1 − xpi xpj ) + | P rimeira P arte lev X lev X lev X } qp+i pj xpi xpj i=1 j=1 {z T erceira P arte } | {z Quarta P arte } A função calculaG_cuda foi modelada para reduzir o acesso às variáveis que residem na memória global, utilizando as variáveis auxiliares pi, pj, qAux, xpi, xpj e xpixpj. Os ponteiros *__pi, *__pj e *__Q auxiliam no acesso às matrizes, reduzindo operações de CAPÍTULO 5. CONTRIBUIÇÕES E IMPLEMENTAÇÕES PARALELAS 101 multiplicação ao acessar os seus elementos. __host__ __device__ int calculaG_aux_cuda(int *Q, int nVars, int lev, const bool *x, int *p, int ind, int pitchsub, int tid) 3{ 4 int plev, pind, ret; 1 2 8 plev = p[lev * pitchsub pind = p[ind * pitchsub p[lev * pitchsub + tid] p[ind * pitchsub + tid] 9 ret = calculaG_cuda(Q, nVars, lev + 1, x, p, pitchsub, tid); 5 6 7 tid]; tid]; pind; plev; p[lev * pitchsub + tid] = plev; p[ind * pitchsub + tid] = pind; 10 11 return ret; 12 13 + + = = } Código 5.12: Função calculaG aux cuda do Kernel no Branch and Bound Nas Linhas 5 e 7 do Código 5.9 é feita a chamada para a função calculaG_aux_cuda. Nesse momento, o programa testa qual é o valor para x[pi] que menos incrementa o valor de g. O cálculo do valor de g é feito pela função calculaG_cuda que é mostrado no Código 5.11. Esta função presume que as primeiras lev posições dos vetores x e p estão fixadas, mas não é o que acontece no momento do teste. Para isso é chamada uma função auxiliar, calculaG_aux_cuda apresentada no Código 5.12, para preparar o vetor p para o cálculo do limitante inferior. Logo após o cálculo, o vetor p é restaurado. No Capı́tulo 6 mostramos os experimentos computacionais, que realizamos para analisar as implementações e os resultados teóricos que produzimos nesta dissertação. Capı́tulo 6 Experimentos Computacionais Neste capı́tulo apresentamos os resultados computacionais dos métodos que implementamos, para tentar resolver instâncias do UQP. Ao longo deste texto utilizamos a palavra solver para representar programas comerciais que resolvem problemas. Implementamos as metaheurı́sticas Tabu Search (TS) e Variable Neighborhood Search (VNS), uma versão sequencial e outra paralela de um Branch and Bound (B&B) que aparece em [48] . Este capı́tulo está dividido da seguinte forma. Na Seção 6.1 é apresentado o ambiente dos experimentos. Na Seção 6.2 indicamos as instâncias utilizadas para testar os métodos. Na Seção 6.3 apresentamos uma comparação entre as implementações dos métodos TS e VNS. Na Seção 6.3.2 fazemos uma comparação entre as versões sequencial e paralela da implementação do TS. Na Seção 6.3.3 fazemos uma comparação entre as versões do TS sequencial e paralelo com o cálculo do valor da função objetivo na forma básica. Na Seção 6.4.1 mostramos resultados obtidos utilizando o solver IBM ILOG CPLEX v12.4. Na Seção 6.4.2 fazemos uma comparação entre as versões sequencial e paralela da implementação do B&B. Neste capı́tulo, em algumas tabelas utilizamos os sı́mbolos s, m, h e d para representar segundos, minutos, horas e dias. 6.1 Ambiente dos Experimentos Os experimentos computacionais foram todos executados em um computador com a seguinte configuração: • Computador: Processador Intel Core i7 3930K, 6 Núcleos de 3.2 GHz, 32 GB de Memória RAM; • GPU: 2 placas NVIDIA EVGA GeForce GTX 680, 1006 MHz, 64 bits, 8 SMX (Nova geração de multi-processadores), 1536 Núcleos 2 GB Memória Global; 102 CAPÍTULO 6. EXPERIMENTOS COMPUTACIONAIS 103 • Sistema Operacional: Linux Ubuntu 12.04, 64 bits; • Compilador: nvcc release 5.0, V0.2.1221 para versão sequencial e paralela. 6.2 Instâncias de Teste Esta seção apresenta as instâncias que foram utilizadas para testar os métodos discutidos nesta dissertação. Lembramos que no UQP deseja-se min / max a função f (x) = xT Qx, onde Q ∈ Qn×n , x ∈ {0, 1}n e n é um número inteiro positivo que representa o número de variáveis da instância. 6.2.1 Instâncias da OR Library A OR Library é um acervo que contém vários conjuntos de instâncias para diversos problemas de otimização combinatória. Especificamente, as instâncias do UQP estão disponı́veis no site: http://people.brunel.ac.uk/~mastjjb/jeb/orlib/bqpinfo.html. Todas estas instâncias são para problemas de maximização. Cada instância do UQP é identificada por ‘bqpn’, onde bqp significa binary quadratic programming e n indica o número de variáveis. Assim, bqp50 representa uma instância do UQP com 50 variáveis. As instâncias testadas são separadas em conjuntos de 50, 100, 250, 500, 1000 e 2500 variáveis, contendo 10 elementos cada. Para estas instâncias foram calculadas as densidades da matriz Q, que é dada por: Densidade = c × 100 n2 onde c é o número de elementos diferentes de zero na matriz Q. A Tabela 6.1 mostra as densidades das instâncias testadas, onde cada coluna representa: Número da Instância é o número da instância em cada grupo de instâncias e Densidades indicam as densidades das instâncias. Pode-se concluir dos números dessa tabela, que a matriz Q tem aproximadamente 10% de constantes com valores diferentes de zero, ou seja, as matrizes das instâncias testadas são esparsas. 6.2.2 Classe Difı́cil Esta é uma classe de instâncias para problemas de minimização, que segundo Pardalos e Rodgers [47] são de difı́cil resolução. Elas têm coeficientes inteiros na matriz Q e possuem CAPÍTULO 6. EXPERIMENTOS COMPUTACIONAIS Número da instância 1 2 3 4 5 6 7 8 9 10 bqp50 8,88 9,84 10,88 9,20 11,04 9,20 10,16 11,76 10,32 8,88 bqp100 9,50 9,88 10,10 9,74 9,42 10,56 9,56 9,98 10,22 9,94 Densidades bqp250 bqp500 9,98 9,94 9,80 9,86 9,90 10,04 10,16 9,86 10,02 9,90 10,26 9,90 9,96 9,96 9,72 9,86 10,14 9,92 9,82 9,96 104 bqp1000 9,92 9,92 9,96 9,94 9,92 10,00 9,90 9,92 9,92 9,82 bqp2500 9,94 9,90 9,90 9,90 9,90 9,88 9,92 9,90 9,92 9,90 Tabela 6.1: Densidades das instâncias do UQP. um número exponencial de mı́nimos locais. A fórmula geral para estas instâncias é: min f (x) = −n(n − 1) n X i=1 sujeito a: xi − n/2 X xi + n i=1 X xi xj + n i<j X xi xj i>j n x ∈ {0, 1} . Esta classe de instâncias é interessante, porque a heurı́stica Gradient Midpoint Method (Seção 3.8.4) sempre encontra uma solução ótima, embora seja muito difı́cil prová-la com o método Branch and Bound. Toda solução com n/2 variáveis com valor igual a 2n 1 é um dos mı́nimos locais discretos. Uma solução para a instância é um vetor n+1 x∗ = [1, . . . , 1, 0, . . . , 0], com as primeiras n/2 variáveis com valor 1 e as demais com valor −n3 − 2n 0. O valor da solução é dado por . 4 Estas instâncias são identificadas por ‘cdn’, onde cd significa classe difı́cil e n representa o número de variáveis da instância. Exemplo 6.1. Instância com seis variáveis (referência [47]). Q= 6.3 −31 6 6 6 6 6 6 −31 6 6 6 6 6 6 −31 6 6 6 6 6 6 −30 6 6 6 6 6 6 −30 6 6 6 6 6 6 −30 Resultados Obtidos por Heurı́sticas Nesta seção apresentamos os resultados obtidos com das metaheurı́sticas Tabu Search e Variable Neighborhood Search, apresentadas nas Seções 3.8.1 e 3.8.3. Fazemos uma análise comparativa entre as versões sequencial e paralela do Tabu Search, utilizando as métricas de desempenho definidas na Seção 2.2. CAPÍTULO 6. EXPERIMENTOS COMPUTACIONAIS 6.3.1 105 Comparação entre as implementações do Tabu Search e do VNS Agora realizamos uma análise entre os valores das soluções obtidas e o desempenho das implementações das metaheurı́sticas TS e VNS. O critério de parada em ambas é de 50.000n vizinhos, onde n é o número de variáveis da instância. As Tabelas 6.2 e 6.3 mostram as comparações entre os métodos, onde as colunas: Exato apresenta o valor da solução obtida pelo solver IBM ILOG CPLEX (ver Seção 6.4.1) e Melhor valor é o melhor valor conhecido apresentado em [10]. As colunas Valor ótimo e Melhor valor foram incluı́das para fornecer uma referência sobre a qualidade das soluções obtidas; Tempo até solução é o tempo, em segundos, que o método leva para encontrar a solução; Tempo total é o tempo, em segundos, que o método consumiu em sua execução completa; Dif é a diferença entre o valor da solução encontrada pelo método e o valor de uma solução ótima ou da melhor solução conhecida. O sı́mbolo ‘-’ é usado nesta coluna para representar que a diferença tem valor zero. Exato Dif TS Tempo até solução (s) Tempo total (s) 2.098 3.702 4.626 3.544 4.012 3.693 4.520 4.216 3.780 3.507 - 0,00 0,00 0,00 0,00 0,00 0,00 0,00 0,00 0,02 0,10 7.970 11.036 12.723 10.368 9.083 10.210 10.125 11.435 11.455 12.565 - 0,02 0,04 0,02 0,00 0,08 0,07 0,06 0,00 0,00 0,00 Instâncias Valor ótimo bqp50 1 2 3 4 5 6 7 8 9 10 bqp100 1 2 3 4 5 6 7 8 9 10 Dif VNS Tempo até solução (s) Tempo total (s) 0,53 0,52 0,52 0,52 0,52 0,51 0,52 0,52 0,52 0,53 76 - 0,02 0,00 0,03 0,00 0,01 0,00 0,00 0,00 0,01 0,01 0,78 0,77 0,77 0,77 0,77 0,78 0,76 0,78 0,77 0,77 1,58 1,56 1,57 1,58 1,57 1,57 1,57 1,59 1,58 1,57 72 41 8 156 - 1,25 1,82 0,06 0,19 0,57 0,40 0,10 0,09 0,21 1,55 2,86 2,89 2,83 2,84 2,99 2,86 2,86 2,96 2,95 2,85 Tabela 6.2: Comparação entre valores de soluções ótimas e de soluções obtidas por metaheurı́sticas. Para instâncias com mais de 100 variáveis, não dispomos de soluções ótimas (ver Seção 6.4.1). Portanto, na Tabela 6.3 continuamos a comparação dos métodos utilizando a coluna Melhor valor, para termos uma referência sobre a qualidade das soluções encon- CAPÍTULO 6. EXPERIMENTOS COMPUTACIONAIS 106 tradas. Da Tabela 6.2 inferimos que: o TS gerou soluções ótimas em menos de 1 segundo e que a implementação do VNS não produziu soluções ótimas para cinco instâncias e consumiu mais tempo que a do TS. Da Tabela 6.3 concluı́mos que: o TS encontrou soluções com mesmos valores que as soluções reportadas em [10] para 22 instâncias e o VNS para uma instância. O TS encontrou soluções melhores que as reportadas em [10] para 13 instâncias. O TS consumiu menos tempo que o VNS para todas as instâncias e sempre obteve soluções com valores melhores ou iguais. Para as instâncias grandes (bqp2500) o TS e o VNS terminaram suas execuções em menos de 45 minutos, mas o TS encontrou sua melhor solução em até 29 minutos para três instâncias e em até 10 minutos para as outras, enquanto o VNS encontrou sua melhor solução em um tempo próximo dos 45 minutos. 6.3.2 Comparações entre as implementações do Tabu Search Sequencial e Paralelo utilizando Recálculo com Mudança de uma Variável Nesta seção são comparados os tempos de execução das versões sequencial e paralela das implementações do TS. Na Tabela 6.4 são apresentados resultados para uma análise dos tempos de execução das duas implementações. Nestes experimentos foi utilizado uma GPU. As colunas Tempo Médio e Desvio mostram os tempos médios e os desvios padrão dos tempos de execução, considerando as 10 instâncias com a mesma quantidade de variáveis. Da Tabela 6.4 e da Figura 6.1 notamos que para as instâncias pequenas (bqp50 e bqp100), a implementação sequencial executou mais rápido que a paralela. Para as instâncias bqp250, bqp500, bqp1000 e bqp2500 a implementação paralela foi mais rápida que Tempo (s) 10000 Programa Sequencial 1000 Programa Paralelo 100 10 1 0,1 bqp50 bqp100 bqp250 bqp500 bqp1000 bqp2500 Figura 6.1: Comparação entre as implementações sequencial e paralela do Tabu Search, com recálculo do valor da função objetivo com mudança no valor de uma variável. Os tempos estão em escala logarı́tmica. CAPÍTULO 6. EXPERIMENTOS COMPUTACIONAIS Instâncias bqp250 1 2 3 4 5 6 7 8 9 10 bqp500 1 2 3 4 5 6 7 8 9 10 bqp1000 1 2 3 4 5 6 7 8 9 10 bqp2500 1 2 3 4 5 6 7 8 9 10 Ref. [10] Melhor valor Dif TS Tempo até solução(s) Tempo total (s) 45.607 44.810 49.037 41.274 47.961 41.014 46.757 35.726 48.916 40.442 - 0,06 0,05 0,05 0,09 0,03 0,6 0,06 0,39 0,04 0,28 116.586 128.223 130.812 130.097 125.487 121.719 122.201 123.559 120.798 130.619 -116 1 - 371.438 354.932 371.226 370.560 352.736 359.452 370.999 351.836 348.732 351.415 1.515.011 1.468.850 1.413.083 1.506.943 1.491.796 1.468.427 1.478.654 1.484.199 1.482.306 1.482.354 107 Dif VNS Tempo até solução(s) Tempo total (s) 8,82 8,95 8,94 8,96 8,99 8,95 8,96 9,01 8,98 8,98 124 707 88 83 6 92 56 1.275 488 - 5,93 6,12 4,41 4,95 6,32 8,63 3,94 4,01 9,00 2,94 8,87 9,00 9,01 9,01 9,05 8,96 8,91 9,01 9,02 8,99 2,68 0,18 0,59 1,33 3,23 0,07 2,5 18,99 0,44 2,18 38,29 38,41 38,47 38,43 38,35 38,35 38,34 38,34 38,38 38,44 3.335 2.923 2.551 2.684 2.292 4.577 4.237 4.932 2.259 2.445 39,87 40,35 38,44 40,34 39,49 39,47 40,28 39,17 40,16 40,24 40,28 40,35 40,46 40,52 40,48 40,47 40,47 40,56 40,57 40,43 -10 -115 -24 -177 -194 -158 -110 - 118,37 15,66 0,74 33,04 59,8 1,49 52,17 198,91 60,78 31,05 299,27 298,42 298,14 298,63 298,33 298,66 298,58 298,4 298,72 298,51 369 4.576 2.549 3.882 542 1.865 2.886 1.169 2.380 2.711 318,51 317,91 319,17 318,25 311,39 318,10 317,74 318,17 320,87 320,95 318,51 318,87 319,17 318,25 318,92 318,10 317,74 318,17 320,87 322,22 -583 -1.629 -693 -758 -20 223 453 2.024 2 173,33 1.394,16 953,61 1.185,58 318,09 373,32 651,73 301,53 469,79 1.708,79 2.640,89 2.648,37 2.645,20 2.644,22 2.643,55 2.644,94 2.644,11 2.649,03 2.646,36 2.644,52 8.120 13.839 8.215 14.108 13.896 6.839 6.963 9.877 7.842 10.372 2.664,05 2.562,67 2.562,97 2.691,09 2.686,51 2.687,05 2.687,58 2.687,28 2.672,16 2.563,33 2.664,05 2.562,67 2.562,97 2.691,09 2.686,51 2.687,05 2.687,58 2.687,28 2.672,16 2.563,33 Tabela 6.3: Comparação entre valores de soluções obtidas por metaheurı́sticas, considerando as implementações sequenciais. CAPÍTULO 6. EXPERIMENTOS COMPUTACIONAIS Instâncias Nome bqp50 bqp100 bqp250 bqp500 bqp1000 bqp2500 Sequencial Tempo Médio (s) Desvio (s) 0,52 0,01 1,57 0,01 8,95 0,05 38,38 0,06 298,57 0,30 2.645,12 2,36 108 Paralelo Tempo Médio (s) Desvio (s) 2,24 0,01 2,60 0,00 3,47 0,01 6,88 0,03 12,48 0,10 59,05 2,54 Tabela 6.4: Comparação entre as implementações sequencial e paralela do Tabu Search, com recálculo do valor da função objetivo com mudança no valor de uma variável. a sequencial. A diferença entre os tempos de execução das duas implementações cresceu quando aumentou-se o número de variáveis. Os desvios padrão dos tempos de execução são pequenos, considerando todas instâncias nos dois métodos. Para realizar uma análise dos resultados usamos as métricas: speedup, eficiência, trabalho, sobrecarga e a Lei de Amdahl. As definições destas métricas encontram-se na Seção 2.2. A Tabela 6.5 apresenta os valores das métricas considerando as implementações sequencial e a paralela do TS. As colunas sob o tı́tulo Métricas apresentam os valores dos cálculos das métricas para cada classe de instâncias, onde a eficiência é dada em porcentagem e o trabalho e a sobrecarga são dados em minutos, horas e dias; a coluna Lei de Amdahl 1536 procs. é uma referência do máximo speedup teórico desse algoritmo paralelizado com 1536 processadores; A análise apresentada na Tabela 6.5 sobre as métricas, é feita para os tempos de execução apresentados na Tabela 6.4. A Lei de Amdahl é calculada com base na porcentagem não paralelizável do algoritmo, que é apresentada na Tabela 6.6. Instâncias Nome bqp50 bqp100 bqp250 bqp500 bqp1000 bqp2500 Speedup 0,23 0,61 2,58 5,58 23,92 44,79 Métricas Eficiência (%) Trabalho 0,02 57,46m 0,04 1,11h 0,17 1,48h 0,36 2,94h 1,56 5,33h 2,92 1,05d Sobrecarga 57,45m 1,11h 1,48h 2,92h 5,24h 1,02d Lei de Amdahl 1536 procs. 6,69 20,50 82,68 182,42 355,24 243,04 Tabela 6.5: Métricas para o TS com Recálculo com Mudança de uma Variável. Os sı́mbolos m, h e d significam minutos, horas e dias. Da Tabela 6.5 percebemos que para as instancias pequenas (bqp50 e bqp100) temos um speedup do tipo Slowdown. Para as outras instâncias ocorre um speedup Sublinear, chegando em até 44,79 para as instâncias bqp2500. As eficiências são menores que 3% e os trabalhos e sobrecarga chegam até 1 dia. De fato a sobrecarga realizada pelo programa paralelo é muito grande, só se atinge os speedups obtidos devido a grande quantidade de CAPÍTULO 6. EXPERIMENTOS COMPUTACIONAIS 109 núcleos na GPU. A Figura 6.2 mostra o gráfico para o speedup. As definições sobre os tipos de speedup encontram-se na Definição 2.26. Para realizar o cálculo da Lei de Amdahl, apresentado na Tabela 6.5, é necessário previamente obter o valor da constante f , que representa a fração da computação que só pode ser realizada sequencialmente, como apresentamos na Definição 2.30. Pode-se calcular o valor de f de duas maneiras: • Se o algoritmo tem um fluxo “bem comportado”, é possı́vel fazer uma análise do algoritmo e determinar qual a porção, número no intervalo [0, 1], do algoritmo que só pode ser executada sequencialmente; • Se o fluxo do algoritmo for “complexo”, uma abordagem empı́rica pode ser adotada. Dentro do programa sequencial criam-se dois contadores: Ts para a parte que só pode ser executada sequencialmente e Tp para a parte perfeitamente paralelizável. Atribuı́-se aos contadores a quantidade de tempo despendida para realizar cada Ts . parte do algoritmo. O cálculo é feito da seguinte forma f = Ts + Tp A Tabela 6.6 apresenta os valores de Ts, Tp, Total e f que foram obtidos de forma experimental, onde a coluna Ts, em milissegundos (ms), representa o tempo médio de execução da parte do algoritmo que só pode ser executada sequencialmente; a coluna Tp (ms) representa o tempo médio de execução da parte do algoritmo que pode ser perfeitamente paralelizável; a coluna Total (ms) representa a soma das colunas Tp e Ts; f representa a porcentagem do algoritmo que só pode ser executada de forma sequencial. Os valores dos tempos são apresentados em milissegundos para não se perder precisão numérica ao efetuar o cálculo do valor de f. Observando os resultados apresentados na Tabela 6.6, nota-se que ao aumentar o número de variáveis nas instâncias testadas, diminuiu o valor na coluna f, com exceção para as instâncias bqp2500. Isto ocorreu porque para estas instâncias, a implementação executou proporcionalmente mais vezes a busca local do que para as outras instâncias. Speedup 50 40 30 20 10 0 bqp50 bqp100 bqp250 bqp500 bqp1000 bqp2500 Figura 6.2: Métrica speedup para a implementação do Tabu Search, com recálculo do valor da função objetivo com mudança no valor de uma variável. CAPÍTULO 6. EXPERIMENTOS COMPUTACIONAIS 110 Como a busca local não foi paralelizada, isto causou um aumento na parte sequencial. Instâncias Nome bqp50 bqp100 bqp250 bqp500 bqp1000 bqp2500 Ts (ms) 77,53 75,77 102,53 185,53 696,69 9.167,57 Tempos Tp (ms) 442,87 1.497,38 8.850,80 38.195,48 297.868,71 2.635.950,99 Total (ms) 520,40 1.573,15 8.953,34 38.381,01 298.565,39 2.645.118,56 Parte Sequencial f (%) 14,8982 4,8164 1,1451 0,4833 0,2333 0,3465 Tabela 6.6: Valores utilizados para os cálculos nas Lei de Amdahl da Tabela 6.5. Os tempos são dados em milissegundos. 6.3.3 Comparações entre as implementações do Tabu Search Sequencial e Paralelo com Cálculo do Valor da Função Objetivo na Forma Básica De acordo com o que foi definido na Seção 5.1.1, chamamos cálculo na forma básica o n P n P qij xi xj . Desejamos cálculo do valor da função objetivo da seguinte maneira: i=1 j=1 investigar métricas de desempenho com relação às implementações do Tabu Search com esta forma de cálculo do valor da função objetivo. Esta forma de se calcular o valor da função objetivo utiliza mais operações que o recálculo, portanto é de se esperar que o tempo total de execução dos programas sejam maiores. Mas com isto podemos investigar como as GPUs se comportam ao executar este método com maior número de operações no kernel. Os demais aspectos da implementação continuam os mesmos. Para a implementação do TS com o cálculo do valor da função objetivo na forma básica, não foi possı́vel fazer um acesso combinado à memória global ao buscar os dados da matriz Q. Portanto, foram implementadas duas versões paralelas para comparação, uma que mantém a matriz na memória global e outra que efetua uma cópia dessa matriz para a memória compartilhada. A Tabela 6.7 exibe informações sobre os tempos de execução das implementações sequencial e das duas versões do TS paralelo com cálculo do valor da função objetivo na forma básica. As colunas sob o tı́tulo T. Médio e Desvio apresentam os tempos médios e os desvios padrão dos tempos de execução, considerando as 10 instâncias com a mesma quantidade de variáveis. As colunas sob o tı́tulo Sequencial contém valores referentes a implementação sequencial; As colunas sob os tı́tulos Paralelo Mem. Global e Paralelo Mem. Comp. contêm valores referentes às implementações paralelas utilizando somente a memória global para armazenar a matriz Q e a que faz uma cópia da matriz para a memória compartilhada. O sı́mbolo ‘-’ usado nesta tabela, representa que não foram coletadas informações para estas instâncias e o sı́mbolo ‘a.u.’ significa amostra única. CAPÍTULO 6. EXPERIMENTOS COMPUTACIONAIS Instâncias Nome bqp50 bqp100 bqp250 bqp500 bqp1000 bqp2500 Sequencial T. Médio Desvio 1,23s 0,01s 12,66s 0,02s 3,74m 0,01s 31,16m 0,63m 4,26h a.u. - Paralelo Mem. T. Médio 2,74s 10,14s 1,04m 4,37m 17,47m 3,64h Global Desvio 0,09s 0,00s 0,01s 0,00m 0,00m a.u. 111 Paralelo Mem. T. Médio 1,16s 3,71s 21,05s 1,43m 5,68m 1,76h Comp. Desvio 0,09s 0,00s 0,00s 0,01m 0,00m 0,00h Tabela 6.7: Comparação entre as Implementações Sequencial e Paralela do TS com cálculo do valor da função objetivo na forma básica. Na Tabela 6.7 podemos observar que para as instâncias bqp50, a implementação sequencial foi mais rápida que as duas paralelas. Os tempos médios da implementação com memória compartilhada são menores que os daqueles cuja implementação utiliza somente a memória global. Todos os desvios padrão, apresentados na tabela, são pequenos. Para a instância bqp2500 a versão paralela que utiliza a memória compartilhada consumiu 1,76 horas, a versão paralela com memória global consumiu 3,64 horas, enquanto a versão sequencial ultrapassou 10 horas de execução. A Tabela 6.8 apresenta os valores das métricas considerando as implementações do TS sequencial e a paralela utilizando somente a memória global da GPU, enquanto a tabela 6.9 considera as implementações sequencial e a paralela utilizando a memória global e a memória compartilhada da GPU. As colunas dessas tabelas têm os mesmos significados das que foram mostradas na Tabela 6.5. O sı́mbolo ‘-’ significa que não foram coletados os tempos para a implementação sequencial. A análise apresentada nas Tabelas 6.8 e 6.9 sobre as métricas, é feita para os tempos de execução apresentados na Tabela 6.7. Os valores na coluna Lei de Amdahl foram calculados com base na fração não paralelizável do algoritmo, que é apresentada na Tabela 6.11. Instâncias Nome bqp50 bqp100 bqp250 bqp500 bqp1000 bqp2500 Speedup 0,45 1,25 3,61 7,13 14,64 - Métricas (Memória Global) Eficiência (%) Trabalho (s) Sobrecarga (s) 0,03 1,17h 1,17h 0,08 4,33h 4,32h 0,23 1,10d 1,00d 0,46 4,66d 4,64d 0,95 19,73d 19,56d - Tabela 6.8: Métricas para o TS com cálculo do valor da função objetivo na forma básica. Das Tabelas 6.8 e 6.9 e da Figura 6.3 vemos que os speedups e as eficiências da implementação com memória compartilhada são sempre maiores do que os da implementação com memória global. Notamos também que o trabalho e a sobrecarga da implementação com memória compartilhada são sempre menores do que os da implementação com memória global. CAPÍTULO 6. EXPERIMENTOS COMPUTACIONAIS Instâncias Nome bqp50 bqp100 bqp250 bqp500 bqp1000 bqp2500 Speedup 1,06 3,41 10,65 21,78 45,06 - 112 Métricas (Memória Compartilhada) Eficiência (%) Trabalho (s) Sobrecarga (s) 0,07 29,78m 29,76m 0,22 1,58h 1,58h 0,69 8,98h 8,92h 1,42 1,53d 1,50d 2,93 6,05d 5,88d - Tabela 6.9: Métricas para o TS com cálculo do valor da função objetivo na forma básica com memória compartilhada. Speedup 45 40 35 30 25 20 15 10 5 0 bqp50 Mem. Compartilhada Mem. Global bqp100 bqp250 bqp500 bqp1000 bqp2500 Figura 6.3: Métrica speedup para a implementação do TS com cálculo do valor da função objetivo na forma básica. A Tabela 6.10 exibe os valores dos cálculos na Lei de Amdahl, para o TS com cálculo do valor da função objetivo na forma básica. As colunas 1536 procs. e ∞ procs. representam os máximos speedups teóricos utilizando respectivamente 1536 e infinitos processadores. Instâncias Nome bqp50 bqp100 bqp250 bqp500 bqp1000 bqp2500 Lei de Amdahl 1536 procs. ∞ procs. 13,89 14,01 45,06 46,39 126,27 137,49 100,57 107,54 81,10 85,56 - Tabela 6.10: Lei de Amdahl para o TS com cálculo do valor da função objetivo na forma básica. Da Tabela 6.10 podemos ver que os valores das colunas 1536 procs. e ∞ procs. são próximos. Para estas implementações o máximo speedup teórico que pode ser obtido é de 137,49. CAPÍTULO 6. EXPERIMENTOS COMPUTACIONAIS Instâncias Nome bqp50 bqp100 bqp250 bqp500 bqp1000 bqp2500 Ts (ms) 100,15 276,35 1.577,68 16.761,63 172.109,76 - Tempos Tp (ms) 1.302,53 12.542,84 215.331,17 1.785.823,84 14.554.338,11 - Total (ms) 1.402,68 12.819,19 216.908,84 1.802.585,47 14.726.447,87 - 113 Parte Sequencial f (%) 7,14 2,1557 0,7273 0,9298 1,1687 - Tabela 6.11: Valores utilizados na Tabela 6.10 para o cálculo dos valores na Lei de Amdahl. 6.4 Resultados Obtidos pelos Métodos Exatos Nesta seção apresentamos os resultados dos experimentos computacionais obtidos por meio de métodos exatos. 6.4.1 Resultados com o Solver CPLEX Para obter os valores ótimos para algumas instâncias do UQP, utilizamos o solver IBM ILOG CPLEX versão 12.4 e o método B&B descrito na Seção 3.9.2. Resolvemos as instâncias utilizando dois módulos desse software: o de Programação Quadrática Inteira (QIP) e o de Programação Linear Inteira Mista (MIP). Ao longo deste texto denotaremos por CPLEX o software da IBM. Os experimentos computacionais desta seção foram executados em uma máquina com configurações inferiores ao dos demais experimentos. Este computador possui um processador Intel Xeon 64 bits com 4 núcleos de 2.53 GHz e 5.8 GB de memória RAM. O objetivo desta seção é conhecer os valores de soluções ótimas e não o de competir em tempo com a implementação da IBM, mesmo porque não implementamos a versão do Branch and Bound com o recálculo do limitante inferior à ∇f (x) (ver Seção 5.1.4), o que possivelmente reduziria muito o tempo de execução da implementação do B&B. Antes de utilizar o módulo MIP tivemos que linearizar o UQP (ver Seção 3.4). Isto é, transformamos cada programa quadrático em seu correspondente programa linear inteiro, chamamos este processo de linearização do programa quadrático. Esta transformação é feita de forma rápida, O(n2 ) e portanto não contabilizamos o tempo da transformação na Tabela 6.12. Os resultados obtidos por meio dos módulos QIP e MIP encontram-se na Tabela 6.12, onde cada coluna representa: Valor e Tempo mostram os valores das soluções ótimas e os tempos de execução. Os tempos são dados em segundos. Precisa ficar claro que os valores ótimos obtidos pelos módulos QIP e MIP são iguais. A Tabela 6.13 mostra as médias dos tempos de execução e seus respectivos desvios CAPÍTULO 6. EXPERIMENTOS COMPUTACIONAIS Número da Instância 1 2 3 4 5 6 7 8 9 10 Instâncias bqp50 Quadrático Valor Tempo (s) 2.098 0,08 3.702 0,03 4.626 0,02 3.544 0,03 4.012 0,05 3.693 0,02 4.520 0,02 4.216 0,04 3.780 0,08 3.507 0,04 Linearizado Tempo (s) 0,5 0,64 0,59 0,51 0,43 0,42 0,63 0,7 0,75 0,87 114 Instâncias bqp100 Quadrático Linearizado Valor Tempo (s) Tempo (s) 7.970 137,08 85,58 11.036 43,55 35,6 12.723 8,92 23,84 10.368 8,95 44,07 9.083 14,01 34,74 10.210 249,09 94,68 10.125 495,46 53,1 11.435 19,38 26,28 11.455 1,75 33,79 12.565 6,07 27,97 Tabela 6.12: Soluções e tempos obtidos pelo CPLEX. padrão para os dados apresentados na Tabela 6.12. Vemos que o tempo médio de execução do modelo quadrático é menor para as instâncias bqp50, mas para as instâncias bqp100, isso nem sempre acontece. Vemos que o desvio padrão é grande tanto para o modelo quadrático como para o linearizado. Isto dá evidências de que a dificuldade de resolução de uma instância não depende somente do seu número de variáveis, mas também da estrutura da matriz Q e das várias decisões tomadas pelo método durante sua execução. Instância Nome bqp50 bqp100 Quadrático Média Desvio 0,04 0,02 98,43 160,59 Linearizado Média Desvio 0,60 0,14 45,97 24,90 Tabela 6.13: Médias e desvios padrão dos tempos de execução do CPLEX. Foi possı́vel executar os testes para instâncias com até 100 variáveis. Ao tentar resolver as instâncias maiores o CPLEX gerou um erro de falta de memória RAM e o processo foi automaticamente abortado. Vale ressaltar que o CPLEX utilizou os quatro núcleos disponı́veis no processador. 6.4.2 Comparações entre as implementações do Branch and Bound Sequencial e Paralelo com Cálculo do Valor da Função Objetivo na Forma Básica Nesta seção fazemos uma comparação entre as versões sequencial e paralela da implementação do B&B e exibimos métricas de desempenho para estas versões. Não fizemos uma análise da Lei de Amdahl para estas implementações, pois todo o método foi paralelizado. Assim, os valores para essa métrica são muito próximos ou iguais ao número de processadores da execução de maneira paralela. Inicialmente executamos a implementação sequencial do método Gradient Midpoint CAPÍTULO 6. EXPERIMENTOS COMPUTACIONAIS 115 Method, fornecemos o seu resultado como entrada para a implementação sequencial do Tabu Search e então executamos o método Branch and Bound fornecendo como entrada a solução encontrada pelo TS. O critério de parada do TS foi um número máximo de 5.000n iterações ou se não obtiver uma solução melhor que a melhor encontrada por 500n iterações, onde n é o número de variáveis da instância. A implementação paralela dividiu o problema principal de cada instância em 214 subproblemas de igual tamanho, que foram resolvidos utilizando o Algoritmo 5.2.4. A quantidade de subproblemas a ser utilizado foi decidida de forma empı́rica e 214 foi a que surtiu melhores resultados. A Tabela 6.14 exibe a comparação entre as versões sequencial e paralela do método B&B executadas para as instâncias de 50 variáveis da OR Library (que são problemas de maximização, ver Seção 6.2.1) e para instâncias da classe difı́cil (que são problemas de minimização, ver Seção 6.2.2). A coluna Instâncias indica a instância testada. As instâncias que começam com “bqp” são as obtidas na OR Library, de forma que bqp50.x representa a instância de 50 variáveis e x o número da instância. As instâncias que começam com “cd” são instâncias da classe difı́cil; A coluna GMM contém os valores das soluções obtidas pelo método Gradient Midpoint Method; A coluna TS contém os valores das soluções obtidas pelo método Tabu Search; A coluna B&B contém os valores das soluções obtidas pelo método Branch and Bound; A coluna NSUBP indica o número de subproblemas resolvidos pelo método B&B sequencial (ver Linha 13 do Algoritmo 3.9.1); A coluna Tempo Sequencial (ms) indica o tempo, em milissegundos, das execuções da implementação do método B&B sequencial; A coluna Tempo Paralelo (ms) indica o tempo, em milissegundos, das execuções da implementação do método B&B paralelo; Da Tabela 6.14 podemos concluir que o método Gradient Midpoint Method forneceu soluções com valor, em média, aproximadamente 80% do valor de uma solução ótima, para as instâncias “bqp50.x”. Já para as instâncias da classe difı́cil, esse método encontrou uma solução ótima, mas não podemos esquecer que esta é uma caracterı́stica especı́fica destas instâncias, conforme destacada na Seção 3.8.4. O método Tabu Search encontrou soluções com valores, em média, 99,9% do valor de uma solução ótima. Para as instâncias bqp50.x, a implementação sequencial do método B&B foi mais rápida que a paralela e a instância que precisou resolver mais subproblemas foi a bqp50.8, com 518 subproblemas resolvidos. Para as instâncias da classe difı́cil, a implementação paralela do B&B foi mais rápida que a sequencial e a instância que menos precisou resolver subproblemas foi a cd12 com 924 subproblemas resolvidos. É interessante notar que para as instâncias da classe difı́cil, os números de subproblemas crescem rapidamente com o aumento do número de variáveis. Talvez isto aconteça devido a grande quantidade de mı́nimos locais em cada instância. A Tabela 6.15 mostra os valores para as métricas envolvendo as implementações sequencial e a paralela do B&B, com relação aos tempos de execução exibidos na Tabela CAPÍTULO 6. EXPERIMENTOS COMPUTACIONAIS Instâncias bqp50.1 bqp50.2 bqp50.3 bqp50.4 bqp50.5 bqp50.6 bqp50.7 bqp50.8 bqp50.9 bqp50.10 cd12 cd14 cd16 cd18 cd20 cd22 cd24 cd26 cd28 cd30 Valores das Soluções GMM TS B&B 1.011 2.098 2.098 2.570 3.678 3.702 3.442 4.626 4.626 2.817 3.502 3.544 3.244 4.012 4.012 3.284 3.693 3.693 3.586 4.520 4.520 3.578 4.216 4.216 3.018 3.780 3.780 3.057 3.507 3.507 -438 -438 -438 -693 -693 -693 -1.032 -1.032 -1.032 -1.467 -1.467 -1.467 -2.010 -2.010 -2.010 -2.673 -2.673 -2.673 -3.468 -3.468 -3.468 -4.407 -4.407 -4.407 -5.502 -5.502 -5.502 -6.765 -6.765 -6.765 NSUBP 228 211 264 50 65 31 42 518 243 314 924 3.432 12.870 48.620 184.756 705.432 2.704.156 10.400.600 40.116.600 155.117.520 116 Tempos do B&B Sequencial (ms) Paralelo (ms) 328,85 565,64 300,95 634,45 326,05 431,55 248,46 310,21 251,48 336,3 236,63 1.031,27 252,27 443,99 419,90 718,59 356,71 1.116,65 528,33 787,89 33,10 2,61 86,27 2,65 400,40 3,91 1.895,58 23,98 9.209,26 103,17 43.024,38 481,03 196.950,53 2.241,91 854.008,21 10.196,92 3.823.914,82 44.737,21 16.986.797,41 202.012,91 Tabela 6.14: Resultados das implementações sequencial e paralelo do método Branch and Bound, com cálculo do valor da função objetivo na forma básica. 6.14. As colunas sob o tı́tulo Métricas apresentam o valor do cálculo das métricas para cada classe de instâncias, onde a eficiência é dada em porcentagem e o trabalho e a sobrecarga são dados em segundos, minutos, horas e dias; Speedup 0,30 0,25 0,20 0,15 0,10 0,05 0,00 bqp50.1 bqp50.2 bqp50.3 bqp50.4 bqp50.5 bqp50.6 bqp50.7 bqp50.8 bqp50.9 bqp50.10 Figura 6.4: Speedup da implementação do B&B para instâncias da OR Library. Da Tabela 6.15 podemos inferir que para as instâncias “bqp50.x” o speedup é do tipo Slowdown, que a eficiência é inferior a 1% e que o maior trabalho se deu para a instância bqp50.9 com 28,59 minutos. Para as instâncias da classe difı́cil o speedup é do tipo sublinear. O maior speedup foi de 102,40 que foi para a instância cd16 e para as instâncias de 20 a 30 variáveis o speedup é em média de 86,64. Notamos que a métrica sobrecarga tem valores muito próximos aos da métrica trabalho e que estas métricas não têm uma relação direta com o tamanho da instância. A figura 6.6 apresenta um gráfico do número de problemas resolvidos para cada ins- CAPÍTULO 6. EXPERIMENTOS COMPUTACIONAIS Instâncias Nome bqp50.1 bqp50.2 bqp50.3 bqp50.4 bqp50.5 bqp50.6 bqp50.7 bqp50.8 bqp50.9 bqp50.10 cd12 cd14 cd16 cd18 cd20 cd22 cd24 cd26 cd28 cd30 Speedup 0,58 0,47 0,76 0,80 0,75 0,23 0,57 0,58 0,32 0,67 12,68 32,55 102,40 79,05 89,26 89,44 87,85 83,75 85,48 84,09 117 Métricas Eficiência (%) Trabalho 0,04 14,48m 0,03 16,24m 0,05 11,05m 0,05 7,94m 0,05 8,61m 0,01 26,40m 0,04 11,37m 0,04 18,40m 0,02 28,59m 0,04 20,17m 0,83 4,01s 2,12 4,07s 6,67 6,01s 5,15 36,83s 5,81 2,64m 5,82 12,31m 5,72 57,39m 5,45 4,35h 5,56 19,09h 5,47 3,59d Sobrecarga 14,47m 16,24m 11,04m 7,94m 8,61m 26,40m 11,36m 18,39m 28,58m 20,16m 3,98s 3,98s 5,61s 34,94s 2,49m 11,60m 54,11m 4,11h 18,03h 3,39d Tabela 6.15: Métricas para o B&B com cálculo do valor da função objetivo na forma básica. Os sı́mbolos s, m, h e d correspondem a segundos, minutos, horas e dias respectivamente. Speedup 120 100 80 60 40 20 0 cd12 cd14 cd16 cd18 cd20 cd22 cd24 cd26 cd28 cd30 Figura 6.5: Speedup da implementação do B&B para instâncias da classe difı́cil. CAPÍTULO 6. EXPERIMENTOS COMPUTACIONAIS 118 Número de Subproblemas Resolvidos em Milhões 160 140 Subproblemas 120 Exponencial (Subproblemas) f(x) = 234,66 e1,3377x 100 80 60 40 20 0 cd12 cd14 cd16 cd18 cd20 cd22 cd24 cd26 cd28 cd30 Instâncias Figura 6.6: Número de subproblemas resolvidos na implementação do B&B para instâncias da classe difı́cil tância resolvida da classe difı́cil. Note que o crescimento é exponencial e aproximado pela função f (x) = 234, 66e1,3377x . Em resumo: • A implementação do VNS merece aprimoramentos, para que ela possa competir com as implementações do TS; • A implementação sequencial do TS resolveu muito bem as instâncias com 50 e 100 variáveis da OR Library; • A implementação paralela do TS foi executada para instâncias da OR Library e obteve um speedup slowdown para instâncias de até 100 variáveis e um speedup sublinear para instâncias de 250 a 2.500 variáveis, chegando a ter uma aceleração de até 44,79 vezes na execução do programa; • Quando utilizado o cálculo do valor da função objetivo na forma básica, o TS paralelo obteve um speedup de até 14,74 para a versão com memória global e de até 45,06 vezes com a versão com memória compartilhada para as instâncias de 1.000 variáveis; • A implementação sequencial do B&B resolveu instâncias da OR Library de 50 variáveis e instâncias da classe difı́cil com até 30 variáveis; CAPÍTULO 6. EXPERIMENTOS COMPUTACIONAIS 119 • A implementação paralela do B&B obteve um speedup slowdown para instâncias da OR Library com 50 variáveis e um speedup sublinear para instâncias da classe difı́cil com até 30 variáveis, chegando a ter uma aceleração de até 102,4 vezes na execução do programa; • Concluı́mos que resolver instâncias do UQP é realmente um grande desafio, mesmo utilizando processadores com grandes quantidades de núcleos, ou seja, utilizando GPUs. Capı́tulo 7 Comentários Finais e Trabalhos Futuros Neste capı́tulo discutimos sobre o trabalho de pesquisa que foi realizado nesta dissertação. Na Seção 7.1 apresentamos nossos comentários finais sobre nossos desenvolvimentos e na Seção 7.2 tratamos dos possı́veis trabalhos futuros. 7.1 Comentários Finais Nesta dissertação estudamos o problema de programação quadrática binária irrestrita, que chamamos de UQP, métodos para resolvê-lo e como implementá-los de maneira paralela com placas gráficas, GPUs. O UQP pertence a classe de problemas NP-difı́cil e é um problema interessante porque muitos problemas de otimização combinatória podem ser modelados desta maneira. Desenvolvemos versões sequenciais e paralelas de métodos não exatos, conhecidos como metaheurı́sticas, e um método exato, Branch and Bound. Nas versões paralelas utilizamos a arquitetura CUDA da NVIDIA. Com esta arquitetura é possı́vel usar os vários núcleos disponı́veis GPUs para executar programas de propósito geral que são executados nas GPUs. Realizamos também contribuições teórica para o estudo do UQP, especificamente para a forma como o recálculo do valor da função objetivo pode ser feito. O cálculo do valor da função objetivo na forma tradicional tem complexidade computacional de O(n2 ) e o recálculo, por nós apresentado, possui complexidade linear. Propusemos uma maneira de calcular um limitante inferior para o valor da função objetivo, que fornece valores maiores ou iguais aos métodos apresentados na literatura, que é utilizado no método Branch and Bound descrito na referência [48]. Também desenvolvemos um recálculo para o limitante inferior da função objetivo com complexidade computacional 120 CAPÍTULO 7. COMENTÁRIOS FINAIS E TRABALHOS FUTUROS 121 linear. Implementamos de maneira sequencial as heurı́sticas (Tabu Search, Variable Neighborhood Search, Gradient Midpoint Method e uma Busca Local) e o método exato Branch and Bound. De forma paralela com GPUs, implementamos os métodos Tabu Search e o Branch and Bound. Para analisar a eficiência de nossas implementações, utilizamos duas classes de instâncias reportadas na literatura. Uma classe de instâncias está disponı́vel na OR Library e a outra está definida em [48] e é considerada de difı́cil resolução. Quanto ao paralelismo, conseguimos acelerar a implementação do método Tabu Search em até 44 vezes e a implementação do método Branch and Bound em até 102 vezes, dependendo da instância testada. 7.2 Trabalhos Futuros No decorrer dos estudos tivemos muitas ideias. Em algumas foi possı́vel nos aprofundarmos, em outras nem tanto. Nesta seção apresentamos uma lista de tópicos que temos o interesse em investigar em trabalhos futuros. Em termos teóricos: 1. Propor uma função que seja um limitante inferior à f (x), que gere valores maiores ou iguais aos de g2 apresentado na Seção 5.1.4. 2. Criar formas eficientes de recálculo para o limitante inferior ao valor da função objetivo, quando um subproblema é desempilhado da pilha de problemas ainda não resolvidos. 3. Modelar problemas de corte e empacotamento na forma UQP. 4. Fazer um estudo poliédrico do espaço de soluções do UQP linearizado. Em termos de implementação: 1. Implementar o método Branch and Bound com o recálculo do limitante inferior ao valor da função objetivo descrito na Seção 5.1.4. 2. Implementar uma estrutura de pilha compartilhada entre as threads, possibilitando que cada thread resolva os próprios subproblemas, bem como os subproblemas gerados por outras threads, quando seu trabalho acabar. 3. Na implementação paralela do método Branch and Bound, poderı́amos fazer cópia dos vetores lb e ub na memória compartilhada antes de atualizar seus valores. CAPÍTULO 7. COMENTÁRIOS FINAIS E TRABALHOS FUTUROS 122 4. Implementar uma busca local paralela para o método Tabu Search. 5. Implementar os métodos Tabu Search e Branch and Bound de maneira distribuı́da com MPI e utilizando GPUs. 6. Verificar o desempenho de um Branch and Bound com busca em largura paralelizado com GPUs. 7. Melhorar a implementação do método VNS sequencial. 8. Implementar o método VNS em paralelo com GPUs. 9. Na implementação do Branch and Bound, trocar o vetor x por uma variável armazenada nos registradores, que guarde a representação decimal do vetor. 10. Na implementação do Branch and Bound utilizar um único vetor xInc na memória global, criando uma região crı́tica com funções atômicas quando for atualizar seus valores. Referências Bibliográficas [1] Adams, W. P. and Dearing, P. M., On the Equivalence between Roof Duality and Lagrangian Duality for Unconstrained 0-1 Quadratic Programming Problems, Discrete Applied Mathematics 48 (1994), no. 1, 1–20. [2] Bahram, A., Glover, F., Kochenberger, G. A. and Rego, C., One-Pass Heuristics for Large-Scale Unconstrained Binary Quadratic Programs, European Journal of Operational Research 137 (2002), 272–287. [3] , An Unconstrained Quadratic Binary Programming Approach to the Vertex Coloring Problem, Annals of operations research 139 (2003), no. 1, 229–141. [4] , A Unified Modeling and Solution Framework for Combinatorial Optimization Problems, OR Spectrum 26 (2004), no. 2, 237–250. [5] , A New Modeling and Solution Approach for the Number Partitioning Problem, Journal of Applied Mathematics and Decision Sciences (2005), 113–121. [6] Bahram, A., Glover, F., Kochenberger, G. A. and Wang, H., An Effective Modeling and Solution Approach for the Generalized Independent Set Problem, Optimization Letters 1 (2007), no. 1, 111–117. [7] , Solving the Maximum Edge Weight Clique Problem via Unconstrained Quadratic Programming, European Journal of Operational Research 181 (2007), no. 2, 592–597. [8] Bahram, A., Kochenberger, G. A. and Ahmadian, A., 0-1 Quadratic Programming Approach for the Optimal Solution of Two Scheduling Problems, International Journal of Systems Science (1994), 401–408. [9] Barahona, F., A Solvable Case of Quadratic 0-1 Programming, Discrete Applied Mathematics 13 (1986), no. 1, 23–26. [10] Beasley, J. E., Heuristic Algorithms for the Unconstrained Binary Quadratic Programming Problem, Tech. report, Working Paper, Imperial College, 1999. 123 REFERÊNCIAS BIBLIOGRÁFICAS 124 [11] Billionet, A. and Sutter, A., Minimization of a Quadratic Pseudo-Boolean Function, European Journal of Operational Research 78 (1994), 106–115. [12] Bixby, R. E., Notes on Combinatorial Optimization, 1987. [13] Boros, E. and Hammer, P. L., Pseudo-Boolean Optimization, Discrete Applied Mathematics 123 (2002), 155–225. [14] Boros, E., Hammer, P. L. and Sun, X., The DDT Method for Quadratic 0-1 Minimization, RUTCOR Research Center (1989), 39–89. [15] Caporossi, G. and Hansen, P. , Variable Neighborhood Search for Extremal Graphs: 1 The AutoGraphiX system, Discrete Mathematics 212 (2000), no. 1, 29 – 44. [16] Chakradhar, S. T. and Bushnell, M. L., A Solvable Class of Quadratic 0-1 Programming, Discrete Applied Mathematics 36 (1992), no. 3, 233–251. [17] Chardaire, P. and Sutter, A., A Decomposition Method for Quadratic Zero-One Programming, Management Science 41 (1994), no. 4, 704–712. [18] Dowsland, K. A., An exact algorithm for the pallet loading problem, European Journal of Operational Research 31 (1987), no. 1, 78 – 84. [19] Duives, J. and Lodi, A. and Malaguti, E., Test-assignment: a quadratic coloring problem, Journal of Heuristics 19 (2013), no. 4, 549–564. [20] Glover, F. and Laguna, M., Modern Heuristic Techniques for Combinatorial Problems, ch. Tabu Search, pp. 71–140, Blackwell Scientific, 1993. [21] Gu, S., A Polynomial Time Solvable Algorithm to Linearly Constrained Binary Quadratic Programming Problems with Q being a Tri-Diagonal Matrix, Advances in Information Sciences and Service Sciences 3 (2011), no. 6, 65–72. [22] Hammer, P. L. and Hansen, P. and Simeone, B., Roof Duality, Complementation and Persistency in Quadratic 0-1 Optimization, Mathematical Programming 28 (1984), no. 2, 121–155. [23] Hammer, P. L. and Rudeanu, S., Boolean Methods in Operations Research, SpringerVerlag, New York, 1968. [24] Hanafi, S. and Rebai, A. and Vasquez, M., Several versions of the devour digest tidyup heuristic for unconstrained binary quadratic problems, Journal of Heuristics 19 (2013), no. 4, 645–677. [25] Hansen, P. and Mladenović, N., Variable Neighborhood Search for the P-Median, Location Science 5 (1997), no. 4, 207 – 226. REFERÊNCIAS BIBLIOGRÁFICAS 125 [26] , Variable Neighborhood Search: Principles and Applications, European Journal of Operational Research 130 (2001), no. 3, 449 – 467. [27] , Variable Neighborhood Search, Search Methodologies (Edmund K. Burke and Graham Kendall, eds.), Springer US, 2005, pp. 211–238. [28] Helmberg, C. and Rendl, F., Solving Quadratic (0,1)-Problems by Semidefinite Programs and Cutting Planes, Mathematical Programming 82 (1998), 291–315. [29] Horst, R. and Pardalos, P. M. and Thoai, N. V., Introduction to Global Optimization, Kluwer Academic Publishers, 2000. [30] Iasemidis, L. D., Shiau, D. S., Sackellares, J.C. and Pardalos, P., Transition to Epileptic Seizures: Optimization, DIMACS Series in Discrete Math and Theoretical Computer Science 55 (2000), 55–73. [31] Li, D., Sun, X., Gu, S., Gao, J. and Liu, C., Polynomially Solvable Cases of Binary Quadratic Programs, Optimization and Optimal Control, Springer Optimization and Its Applications, 2010, pp. 199–225. [32] Lima, E. L., Análise Real, Volume 1, ch. Algumas Noções Topológicas, pp. 48–58, IMPA, 2006. [33] Lu, Z., Glover, F. and Hao, J., Neighborhood Combination for Unconstrained Binary Quadratic Problems, MIC 2009: The VIII Metaheuristics International Conference, 2009, pp. 1–7. [34] Luong, T. V. and Melab, N. and Talbi, E. G., Parallel Local Search on GPU, Tech. report, Institut National de Recherche en Informatique et en Automatique, 2009. [35] Merz, P. and Freisleben, B., Genetic Algorithms for Binary Quadratic Programming, in GECCO-1999: Proceedings of the Genetic and Evolutionary Computation Conference, Morgan Kauffman, 1999, pp. 417–424. [36] , Greedy and Local Search Heuristics for the Unconstrained Binary Quadratic Programming Problem, Journal of Heuristics 8 (2002), no. 2, 197–213. [37] Merz, P. and Katayama, K., A Hybrid Evolutionary Local Search Approach for the Unconstrained Binary Quadratic Program, Bio Systems (in press), 2003. [38] Mohammad, M. A., Bahram, A. and Kochenberger, G. A., A Scatter Search Approach to Unconstrained Quadratic Binary Programs, McGraw-Hill Ltd., UK, 1999. [39] Morabito, R. and Morales, S., A simple and effective recursive procedure for the manufacturer’s pallet loading problem, Journal of the Operational Research Society 49 (1998), 819–928. REFERÊNCIAS BIBLIOGRÁFICAS 126 [40] NVIDIA, C., Whitepaper NVIDIA GeForce GTX 680, http://www.geforce.com/ Active/en_US/en_US/pdf/GeForce-GTX-680-Whitepaper-FINAL.pdf. [41] , CUDA by Example, http://developer.download.nvidia.com/books/ cuda-by-example/cuda-by-example-sample.pdf, July 2010. [42] , NVIDIA CUDA C: Programming Guide, version 4.0, http: //developer.download.nvidia.com/compute/DevZone/docs/html/C/doc/CUDA_ C_Programming_Guide.pdf, May 2011. [43] , NVIDIA CUDA C Best Pratices Guide, Version 4.1, http://docs.nvidia. com/cuda/pdf/CUDA_C_Best_Practices_Guide.pdf, 2012. [44] Palubeckis, G., A Heuristic-Based Branch and Bound Algorithm for the Unconstrained Quadratic Zero-One Programming Problem, Computing (1995), 284–301. [45] Pardalos, P. and Rodgers, G. P., A Branch and Bound Algorithm for Maximum Clique problem, Computers & Operations Research 19 (1992), 363–375. [46] Pardalos, P. and Xue, J., The Maximum Clique Problem, Journal of Global Optimization (1994), 301–328. [47] Pardalos, P. M. and Rodgers, G. P., Parallel Branch and Bound Algorithms for Unconstrained Quadratic Zero-One Programming. In: Impacts of Recent Computer Advances on Operation Research, R. Sharda et al., 1989. [48] , Computational Aspects of a Branch and Bound Algorithm for Quadratic Zero-One Programming, Computing 45 (1990), no. 2, 131–144. [49] , Parallel Branch and Bound Algorithms for Quadratic Zero-One Programs on the Hypercube Architecture, Annals of Operations Research 22 (1990), no. 1, 271–292. [50] Pardalos, P. M. and Rosen, J. B., Constrained Global Optimization: Algorithms and Applications (Lecture Notes in Computer Science), Springer-Verlag, 1987. [51] Polacek, M. and Hartl, R. F. and Doerner, K. and Reimann, M., A Variable Neighborhood Search for the Multi Depot Vehicle Routing Problem with Time Windows, Journal of Heuristics 10 (2004), no. 6, 613–627. [52] Ravelo, S. V. and Meneses, C. N., Unconstrained Quadratic Binary Programs, Manuscript, 2011, pp. 1–28. [53] Saad, Y. and Schultz, M. H, Topological Properties of Hipercubes, Tech. report, YALEU/DCS/RR, 1985. REFERÊNCIAS BIBLIOGRÁFICAS 127 [54] Tanenbaum, A. S. and Woodhull, A. S., Sistemas Operacionais: Projeto e Implementação, Bookman, 2000. [55] Vemuganti, R., Applications of Set Covering, Set Packing and Set Partitioning Models: A Survey, Handbook of Combinatorial Optimization eds Du, D. and Pardalos, P. M., Kluwer Academic Publishers, 1998, pp. 573–746. [56] Williams, A. C., Quadratic 0-1 Programming using the Roof Duality with Computational Results, Tech. report, RUTCOR Research Report 8-85, Rutgers University, New Brunswick, NJ., 1985.