Universidade de Brasília
Instituto de Ciências ExatasDepartamento de Ciência da Computação
Alinhamento múltiplo de sequências com A-Star
paralelo em cluster MPI
Gabriel de C. Ferreira
Monografia apresentada como requisito parcial para conclusão do Curso de Engenharia da Computação
Orientadora
Prof.a Dr.a Alba Cristina M. A. de Melo
Brasília
2016
Universidade de Brasília
Instituto de Ciências ExatasDepartamento de Ciência da Computação
Alinhamento múltiplo de sequências com A-Star
paralelo em cluster MPI
Gabriel de C. Ferreira
Monografia apresentada como requisito parcial para conclusão do Curso de Engenharia da Computação
Prof.a Dr.a Alba Cristina M. A. de Melo (Orientadora) CIC/UnB
Prof. Dr. Li Weigang Prof.a Dr.a Maria Emília M. T. Walter
CIC/UnB CIC/UnB
Prof. Dr. Ricardo Pezzuol Jacobi
Coordenador do Curso de Engenharia da Computação
Dedicatória
Eu dedico este trabalho a Deus, que sempre olha por mim. Aos meus pais, que me deram apoio e condições. Aos meus irmãos, que me suportaram. Aos meus avós, que sempre me incentivaram. Aos professores Alba Melo, Alexandre Zaghetto, Gerson Pfitscher e Marcelo Ladeira, do departamento de ciência da computação, e ao professor José Edil, do departamento de engenharia elétrica, que para além das disciplinas, ensinam por palavras e gestos como ser uma pessoa e um profissional melhor.
Agradecimentos
Agradeço ao Dr. Daniel Sundfeld, que forneceu consultoria sobre o tema, à professora Dr. Alba Melo, que guiou todo o trabalho, e ao departamento de Ciência da Computação da Universidade de Brasília.
Resumo
O alinhamento múltiplo de sequências visa ressaltar as similaridades e diferenças em um conjunto de sequências biológicas. O alinhamento múltiplo com a soma de pares é um problema NP-Difícil e métodos heurísticos são usados para solucioná-lo, porém esses métodos não garantem que o resultado ótimo será produzido. Algumas das técnicas ex-atas que produzem o resultado ótimo são baseadas no algoritmo de busca A-Star, sendo uma delas o A-Star Paralelo (PA-Star). O PA-Star divide o espaço de busca entre múlti-plas threads, acelerando a obtenção de resultados, contudo tem sua execução limitada a uma única máquina. O objetivo deste trabalho de graduação é propor, implementar e avaliar o MPI-PAStar, uma estratégia que permita reduzir o tempo de busca ao executar o PA-Star em diversas máquinas, utilizando o ambiente MPI para trocar mensagens, dis-tribuindo carga de trabalho entre as máquinas. O MPI-PAStar adiciona ao PA-Star um
pool de threads de processamento de mensagens e duas threads responsáveis pelo envio
e recebimento de mensagens. Diversas estratégias são utilizadas para reduzir o tráfego de dados e a latência de rede, como a serialização de blocos de carga de trabalho e com-pactação destes antes do envio, reduzindo efeitos colaterais negativos da rede sobre a computação do alinhamento. Os resultados do MPI-PAStar apresentaram ganhos de até 36.8% no tempo de busca do alinhamento ótimo e de até 29,7% no tempo total de exe-cução do programa, quando comparado ao PA-Star, a depender do número e similaridade das sequências sendo alinhadas, além do comprimento da maior sequência.
Abstract
The multiple sequence alignment purpose is to highlight similarities and differences between a set of biological sequences. The multiple alignment is an NP-Hard problem and heuristic methods are used to solve it, however those do not guarantee that an optimal result is produced. Some exact techniques that can produce an optimal result are based on the A-Star graph search algorithm, being one of them the Parallel A-Star (PA-Star). The PA-Star divides the search space to multiple threads, accelerating the search for the result, but its execution is limited to a single machine. The objective of this undergraduate work is to propose, implement and evaluate the MPI-PAStar, a strategy that allows the reduction of the search time by executing the PA-Star on multiple machines, using the MPI environment to exchange messages, distributing the workload across different machines. The MPI-PAStar adds to PA-Star a pool of message processing threads and two threads responsible for sending and receiving messages. Multiple strategies are used to reduce network traffic and latency, like serialized workload blocks and compressing them before sending them, reducing negative network effects over the alignment computation. Results obtained with the MPI-PAStar showed that it can yield up to 36.8% reduction in terms of alignment time and up to 29.7% in terms of total execution time, depending on the number of sequences being aligned, the length of longest sequence and the content of the sequences.
Sumário
1 Introdução 1
2 Alinhamento múltiplo de sequências 3
2.1 Cálculo do escore de similaridade . . . 4
2.1.1 Soma de pares (SP) . . . 5
2.1.2 Soma de pares com pesos (WSP) . . . 7
2.2 Complexidade do alinhamento . . . 10
3 Métodos de redução do espaço de busca 12 3.1 Carrillo-Lipman . . . 12
3.2 A-Star . . . 15
3.3 Adaptação do A-Star ao MSA . . . 18
4 Computação paralela com troca de mensagens 22 4.1 Sockets . . . 23
4.2 MPI . . . 23
4.2.1 Estrutura básica . . . 24
4.2.2 Ranks, Comunicadores e Grupos . . . 25
4.2.3 Tipos de dados . . . 25
4.2.4 Troca síncrona de mensagens . . . 27
4.2.5 Troca assíncrona de mensagens . . . 28
4.2.6 Comunicação coletiva: MPI_Gather e MPI_Scatter . . . 29
4.3 Clusters . . . 31
5 Estratégias para o A-Star paralelo 34 5.1 A-Star paralelo e hash sensitivo à localidade . . . 34
5.2 Expansão parcial do A-Star . . . 37
5.3 Detecção de duplicata adiada (DDD) . . . 39
5.4 Busca em fronteira paralela . . . 40
5.6 Redução do espaço de busca . . . 42
6 Projeto do MPI Parallel A-Star (MPI-PAStar) 44 6.1 Visão geral . . . 44
6.2 MPI-PAStar . . . 45
6.2.1 Projeto da Thread Sender ( Tsender) . . . 45
6.2.2 Projeto da Thread Receiver (Treceiver) . . . 49
6.2.3 Projeto da Thread de processamento de mensagens (Tmsg_proc) . . 49
6.2.4 Projeto do pool de threads de processamento de mensagens . . . 49
6.2.5 Projeto do pool de threads de trabalho (Tworker) . . . 50
6.2.6 Projeto de comunicação entre threads trabalhadoras (Tworker) . . . . 50
6.2.7 Projeto da finalização . . . 52
6.2.8 Sincronização no acesso a buffers . . . 52
6.3 Otimizações propostas . . . 56
6.3.1 Leitura única do arquivo de entrada . . . 56
6.3.2 Simplificação na execução das threads de comunicação . . . 56
6.3.3 Enfileiramento e agrupamento de mensagens para envio . . . 57
6.3.4 Compressão e descompressão de mensagens . . . 57
6.3.5 Troca da serialização de mensagens de texto comprimidas para men-sagens binárias comprimidas . . . 58
6.3.6 Modificação da sincronização de dados finais . . . 59
6.3.7 Otimizações dependentes de sistema operacional . . . 59
7 Resultados experimentais 61 7.1 Ambiente de teste . . . 61
7.2 Conjuntos de sequências testadas . . . 62
7.3 Resultados gerais das comparações . . . 62
7.4 Tempo de busca . . . 64
7.5 Tempo total de execução . . . 65
7.6 Utilização da CPU . . . 67
7.7 Consumo de memória . . . 69
8 Conclusão 70
Lista de Figuras
2.1 Alinhamento global e local entre duas sequências . . . 4
2.2 Soma de pares para alinhamento múltiplo de sequências, entre duas muta-ções do gene NLGN1, e outra possível mutação arbitrária do mesmo gene. No exemplo, o escore SP é 2+24+32=58 . . . 5
2.3 Modelo de soma de pares para alinhamento múltiplo de sequências e subs-tituição dos valores pelos da tabela BLOSUM62. Sequências alinhadas são S0=TTMY, S1=ATCC e S2=AGCC. . . 7
2.4 Exemplo de grafo com problemas para cálculo do primeiro esquema de pesos 9 2.5 Exemplo de cálculo de pesos pela segunda metodologia de Altschul [1], para três sequências (NNN, NNC, NCC) . . . 10
3.1 Grafos para alinhamento de duas e três sequências . . . 13
3.2 Hipercubo mostrando espaço de busca no alinhamento entre três sequências [4] . . . 13
3.3 Grafo contendo nós e pesos das arestas que os ligam . . . 16
3.4 Diferentes níveis antidiagonáis do algoritmo de Schroedl [32] . . . 19
4.1 Exemplo de utilização dos métodos Scatter and Gather do MPI, para 4 processos . . . 31
4.2 Exemplo de utilização dos métodos All Scatter e All Gather, para 2 pro-cessos . . . 31
4.3 Exemplos de topologias de clusters . . . 33
5.1 Fluxograma da rotina de busca da thread trabalhadora . . . 36
5.2 Topologia A-Star Paralelo . . . 36
5.3 A-Star com expansão parcial . . . 38
5.4 Detecção de duplicata adiada . . . 39
5.5 Área de busca descartada pelo pruning (cinza), num alinhamento entre duas sequências [31] . . . 43
6.1 Visão geral proposta do MPI-PAStar, sendo: k o número de threads por pro-cesso MPI, que define o tamanho do pool de threads Tworker e Tmsg_proc;
n o número de processos MPI, um por máquina M; IB o buffer de entrada;
Tmsg_proc uma thread processadora de mensagens; WB o buffer de tra-balho; Tworker uma thread que executa a busca A-Star; OB o buffer de saída; Tsender a thread que envia mensagens; Treceiver a thread que recebe
mensagens . . . 46
6.2 Estrutura do buffer de saída (OB), sendo: n o número de processos MPI; k o número de threads por processo; p,q,r nós arbitrários do espaço de busca. 47 6.3 Esquema de transmissão da carga de trabalho . . . 48
6.4 Topologia do MPI-PAStar . . . 51
6.5 Esquema das threads trabalhadoras e verificação do resultado, no PA-Star . 54 6.6 Esquema das threads trabalhadoras e verificação do resultado, no MPI-PAStar 54 7.1 Comparação dos tempos de busca, em escala logarítmica . . . 65
7.2 Comparação dos tempos de busca, em escala linear . . . 65
7.3 Comparação dos tempos de execução, em escala logarítmica . . . 66
7.4 Comparação dos tempos de execução, em escala linear . . . 67
7.5 Comparação da utilização de CPU, entre o PA-Star e máquina M0 do MPI-PAStar . . . 68
7.6 Comparação da utilização de CPU, entre PA-Star e 2 máquinas do MPI-PAStar . . . 68
7.7 Comparação da utilização máxima de memória, entre PA-Star e rank 0 do MPI-PAStar . . . 69
Lista de Tabelas
2.1 Tabela BLOSUM 62 . . . 6
6.1 Comparativo serialização binária e de texto para mensagens com nós, para o arquivo synthetic_hard2.fasta . . . 59
6.2 Comparativo entre serialização binária e de texto das closed lists, para o arquivo synthetic_hard2.fasta . . . 59
7.1 Sequências comparadas . . . 62
7.2 Dados de execução do PA-Star no notebook 1 . . . 63
Capítulo 1
Introdução
Uma sequência é formada por um conjunto de elementos de um dado domínio de aplicação, elementos estes ordenadas por um determinado processo de construção. A de-finição acima é uma generalização da dede-finição de sequências de números naturais dada por Knopp em [17]. No contexto deste trabalho, as sequências são fragmentos de DNA, RNA, aminoácidos, onde os elementos das sequências são formados por bases nitrogenadas.
Existem diversos tipos de sequências, dentre elas sequências de nucleotídeos (DNA e RNA) e aminoácidos (proteínas), formadas por diferentes elementos, que são encontradas em todos os organismos [16].
O alinhamento de duas sequências biológicas tem como objetivo descobrir o grau de similaridade entre as mesmas, na tentativa de encontrar algum tipo de relacionamento funcional ou evolucionário entre estas sequências e, consequentemente, entre as espécies que as carregam. O alinhamento de múltiplas sequências permite que mais sequências sejam alinhadas, o que facilita a comparação e análise de diferentes sequências, porém o problema é NP-Difícil [36].
Uma das estratégias para alinhar múltiplas sequências baseia-se no trabalho de Carrilo-Lipman [4], que organiza os possíveis alinhamentos em um espaço de busca em forma de hipercubo n-dimensional. Essa abordagem pode ser combinada com o algoritmo de busca em grafos A-Star, proposto por Hart [13], permitindo tratar o problema do alinhamento múltiplo de sequências como um problema de busca. Ao utilizar um algoritmo de busca, é possível se utilizar de heurísticas para postergar a análise de alinhamentos que pare-çam pouco promissores, que geralmente resultam numa redução do espaço pesquisado, portanto acelerando o alinhamento múltiplo.
Sundfeld et al [34] propuseram o PA-Star, que é uma estratégia multi-threaded com atribuição de trabalho baseada em localidade, baseada no A-Star, para resolver o problema do alinhamento múltiplo. A proposta de Sundfeld et al é uma dentre outras propostas de paralelização do A-Star [14, 21, 27], que também apresentam melhorias consideráveis
na redução do tempo de busca, no uso de memória, e/ou na redução do espaço de busca. A solução de Sundfeld et al porém, não permite a execução do alinhamento múltiplo em diversas máquinas, tendo sua capacidade de alinhamento limitada pela capacidade de processamento pelo processador e capacidade máxima de memória em uma máquina, o que ou torna muito caro o alinhamento entre conjuntos com muitas sequências pouco similares, ou torna o alinhamento muito lento.
O objetivo deste trabalho de graduação é reduzir o tempo do alinhamento múltiplo do PA-Star, propondo, implementando e avaliando mecanismos que permitam distribuir a busca paralela em não apenas uma máquina, mas em múltiplas máquinas de um cluster, utilizando-se do ambiente MPI (Message Passing Interface) para trocar mensagens entre estas máquinas.
A estratégia utilizada no MPI-PAStar utiliza do mesmo hash de atribuição de trabalho utilizado pelo PA-Star, porém estendendo-se o número de threads de busca, que com auxílio do hash sensitivo à localidade, distribui áreas do espaço de busca dos alinhamentos entre as diversas threads das diversas máquinas, que transmitem carga de trabalho entre uma máquina e outra quando isso se faz necessário.
No projeto do MPI-PAStar, foram incorporadas diversas estratégias de otimização, que tinham como objetivo: reduzir a comunicação, tanto no número quanto tamanho das mensagens; reduzir overhead de processamento para transmissão e recebimento de mensagens e reduzir o número de alocações e liberações de memória.
Os resultados obtidos em um cluster com 2 máquinas mostram que o uso de mais de uma máquina somente é vantajoso quando as sequências comparadas não são pequenas (≥ 200 caracteres) e quando a similaridade entre as mesmas é média ou pequena. Na comparação do conjunto Balibase 5PTP, composto por 5 sequâncias com em média de 234 caracteres, o tempo de execução foi reduzido de 4 minutos e 1 segundo (1 máquina) para 2 minutos e 51 segundos (2 máquinas), com um speedup de 1.4x.
Este documento está organizado da seguinte maneira. No capítulo 2 é apresentado o conceito do alinhamento múltiplo de sequências. No capítulo 3 são detalhados conceitos relativos ao A-Star, seu funcionamento, sua organização, passos e heurísticas. No capítulo 4 são detalhados conceitos básicos do MPI, além das funcionalidades mais comuns e utilizadas neste trabalho. No capítulo 5 é apresentado o PA-Star, bem como outras propostas da literatura que usam o A-Star para o alinhamento múltiplo. No capítulo 6, a proposta deste trabalho é detalhada, mostrando uma visão geral, seguida do detalhamento dos mecanismos propostos e implementados, além de uma rápida descrição por otimizações feitas e seus efeitos. O capítulo 7 discute resultados obtidos com o MPI-PAStar. No capítulo 8 constam as conclusões do trabalho, além de serem sugeridos trabalhos futuros.
Capítulo 2
Alinhamento múltiplo de sequências
Neste capítulo, são apresentados os conceitos de alinhamento múltiplo de sequências, maneiras de se calcular a similaridade (seção 2.1) entre os alinhamentos com a soma de pares (SP) (seção 2.1.1) e soma de pares com pesos (seção 2.1.2), além da complexidade do alinhamento múltiplo (seção 2.2).
Um alinhamento é produzido pelo pareamento, coluna a coluna de duas ou mais sequências, de maneira com que suas similaridades sejam destacadas, sendo estes divi-didos em três tipos: alinhamento global, local e semi-global.
Gusfield [12] define o alinhamento global de k sequências como sendo o resultado obtido através de inserção de espaços, seja no meio, ou ao final, das sequências S1 a Sk, de
forma que todas tenham o mesmo comprimento e, após isto, sejam colocadas uma acima da outra.
No alinhamento global, toda a sequência é levada em conta na hora de ser alinhada com outra(s) sequência(s), diferentemente do alinhamento local, onde somente trechos das sequências são alinhados, encontrando pares de trechos cujo tenham melhor alinhamento global entre si [12]. No alinhamento semi-global, por sua vez, é feito com o objetivo de se encontrar o intervalo de uma sequência maior que mais se aproxima de uma sequência menor [22].
A Figura 2.1 apresenta um alinhamento global entre as sequências S0 = ATTGAC-TAGT e S1 = ATTCTGT, e um alinhamento local entre as sequencias S0 e S2 = CGT-GATATG.
O alinhamento múltiplo de sequências visa alinhar três ou mais sequências, de forma a relacioná-las quanto ao seu grau similaridade, que pode ser calculado de diversas formas, como: alguma forma de escore/pontuação; ordenação com base em árvore filogenética.
A forma mais comum de pontuação de alinhamentos é feita com a soma de pares (SP). Nesse caso, primeiramente são calculados escores para cada alinhamento possível de duas sequências (alinhamento par a par). Os escores dos alinhamentos par a par são somados
de maneira a obter o escore final do alinhamento múltiplo (escore soma de pares). A maximização ou minimização dos escores globalmente indicam um menor ou maior grau de similaridade entre as sequências alinhadas.
O alinhamento múltiplo de sequências pode ser usado durante procedimentos de DNA assembly, na clonagem de cromossomos artificiais por bactéria (BAC), no dobramento de proteínas, entre outros [25].
No estudo do genoma, o alinhamento pode ser usado para predição sequências de mRNA expressos em genes de uma célula, que são muito difíceis de serem completamente copiadas de um DNA, mas nem tanto quando se trata de cópias parciais. Um conjunto de cópias parciais de mRNA é chamado de EST (expressed sequence tag), e representa dife-rentes partes da sequência original. Através do alinhamento múltiplo de ESTs é possível então remontar cópias parciais de DNA, referentes aos mRNAs de uma dada célula, em sequências contíguas que representam sequências de mRNA completas de uma célula [25].
2.1
Cálculo do escore de similaridade
Um dos problemas que deve ser ressaltado no alinhamento múltiplo de sequências é que o cálculo de um escore biologicamente significativo é de extrema importância para a obtenção de bons alinhamentos, já que existem casos onde a falta ou presença de afinidade química determina se dadas colunas de um par de sequências podem ser alinhadas, ou então necessitam a inserção de espaços que evitem seu alinhamento.
Mount [25] descreve algumas possíveis abordagens para cálculos de escore, que são: soma de pares (SP) e soma de pares com pesos (WSP), que são os métodos com maior número de implementações; métodos baseados em árvores; filoginia de estrela; conteúdo de informação e método de rastro.
A T T G A C T A G T
| | | | | | |
A T T - - C T - G T (a) Exemplo de alinhamento global A T T G A C T A G T
| | | | |
C G T G A - T A T G (b) Exemplo de alinhamento local.
2.1.1
Soma de pares (SP)
O método de soma de pares fornece um meio de se calcular um escore de um alinha-mento múltiplo de sequências somando-se os escores de todas as possíveis combinações de sequências. Este método assume um modelo evolucionário onde qualquer sequência pode ser ancestral da outra e, portanto, a cada coluna avaliada do alinhamento, os elementos podem ser vistos como vértices duma malha fechada, ou seja, todos os elementos ligam-se aos outros [25].
Na Figura 2.2, é mostrado o escore calculado pela soma de pares entre as sequências S0 = TTMYCMYTAT, S1 = ATCCCATTAT e S3 = AGCC–TTAT, onde os pares foram substituídos por valores obtidos através de tabelas de substituição, que serão tratadas a seguir. No exemplo, o custo de penalização dos espaços, representados por hifens na Figura 2.2, foi arbitrado em -4, que é representado como asterisco na tabela BLOSUM62 2.1. T T M Y C M Y T A T A A A A C C C C C G T T T T T T T A 24 32 2
Figura 2.2: Soma de pares para alinhamento múltiplo de sequências, entre duas mutações do gene NLGN1, e outra possível mutação arbitrária do mesmo gene. No exemplo, o escore SP é 2+24+32=58
A redução do número de pares de elementos idênticos, ligados pela malha anterior-mente citada, faz com que o escore da coluna caia rapidaanterior-mente, o que causa penalidades no escore daquela coluna, que depois é substituído por valores de escore de uma tabela BLOSUM (Blocks Substitution Matrix) ou PAM (Point Accepted Mutation), que serão tratadas futuramente.
O escore de uma dada coluna pode ser modelado pela equação 2.1
S(mi) =
X
k<l
s(mki, mli) (2.1)
onde as pontuações s(a, b) são advindas de uma tabela de substituição de valores, como a PAM ou BLOSUM.
A equação (1) não inclui o efeito de espaços (gaps) no escore final, o que pode ser feito considerando, linearmente, assumindo valores s(−, a) e s(a, −) como tendo um escore e s(−, −) com custo zero. Outra possibilidade de tratamento para espaços é utilizar o esquema de espaços afins (affine gaps), onde o primeiro espaço tem uma contribuição maior no escore e os espaços subsequentes a ele tem custos menores [25].
Tabela 2.1: Tabela BLOSUM 62 A R N D C Q E G H I L K M F P S T W Y V B Z X * A 4 -1 -2 -2 0 -1 -1 0 -2 -1 -1 -1 -1 -2 -1 1 0 -3 -2 0 -2 -1 0 -4 R -1 5 0 -2 -3 1 0 -2 0 -3 -2 2 -1 -3 -2 -1 -1 -3 -2 -3 -1 0 -1 -4 N -2 0 6 1 -3 0 0 0 1 -3 -3 0 -2 -3 -2 1 0 -4 -2 -3 3 0 -1 -4 D -2 -2 1 6 -3 0 2 -1 -1 -3 -4 -1 -3 -3 -1 0 -1 -4 -3 -3 4 1 -1 -4 C 0 -3 -3 -3 9 -3 -4 -3 -3 -1 -1 -3 -1 -2 -3 -1 -1 -2 -2 -1 -3 -3 -2 -4 Q -1 1 0 0 -3 5 2 -2 0 -3 -2 1 0 -3 -1 0 -1 -2 -1 -2 0 3 -1 -4 E -1 0 0 2 -4 2 5 -2 0 -3 -3 1 -2 -3 -1 0 -1 -3 -2 -2 1 4 -1 -4 G 0 -2 0 -1 -3 -2 -2 6 -2 -4 -4 -2 -3 -3 -2 0 -2 -2 -3 -3 -1 -2 -1 -4 H -2 0 1 -1 -3 0 0 -2 8 -3 -3 -1 -2 -1 -2 -1 -2 -2 2 -3 0 0 -1 -4 I -1 -3 -3 -3 -1 -3 -3 -4 -3 4 2 -3 1 0 -3 -2 -1 -3 -1 3 -3 -3 -1 -4 L -1 -2 -3 -4 -1 -2 -3 -4 -3 2 4 -2 2 0 -3 -2 -1 -2 -1 1 -4 -3 -1 -4 K -1 2 0 -1 -3 1 1 -2 -1 -3 -2 5 -1 -3 -1 0 -1 -3 -2 -2 0 1 -1 -4 M -1 -1 -2 -3 -1 0 -2 -3 -2 1 2 -1 5 0 -2 -1 -1 -1 -1 1 -3 -1 -1 -4 F -2 -3 -3 -3 -2 -3 -3 -3 -1 0 0 -3 0 6 -4 -2 -2 1 3 -1 -3 -3 -1 -4 P -1 -2 -2 -1 -3 -1 -1 -2 -2 -3 -3 -1 -2 -4 7 -1 -1 -4 -3 -2 -2 -1 -2 -4 S 1 -1 1 0 -1 0 0 0 -1 -2 -2 0 -1 -2 -1 4 1 -3 -2 -2 0 0 0 -4 T 0 -1 0 -1 -1 -1 -1 -2 -2 -1 -1 -1 -1 -2 -1 1 5 -2 -2 0 -1 -1 0 -4 W -3 -3 -4 -4 -2 -2 -3 -2 -2 -3 -2 -3 -1 1 -4 -3 -2 11 2 -3 -4 -3 -2 -4 Y -2 -2 -2 -3 -2 -1 -2 -3 2 -1 -1 -2 -1 3 -3 -2 -2 2 7 -1 -3 -2 -1 -4 V 0 -3 -3 -3 -1 -2 -2 -3 -3 3 1 -2 1 -1 -2 -2 0 -3 -1 4 -3 -2 -1 -4 B -2 -1 3 4 -3 0 1 -1 0 -3 -4 0 -3 -3 -2 0 -1 -4 -3 -3 4 1 -1 -4 Z -1 0 0 1 -3 3 4 -2 0 -3 -3 1 -1 -3 -1 0 -1 -3 -2 -2 1 4 -1 -4 X 0 -1 -1 -1 -2 -1 -1 -1 -1 -1 -1 -1 -1 -1 -2 0 0 -2 -1 -1 -1 -1 -1 -4 * -4 -4 -4 -4 -4 -4 -4 -4 -4 -4 -4 -4 -4 -4 -4 -4 -4 -4 -4 -4 -4 -4 -4 1
Na Figura 2.3, é mostrado o cálculo de afinidade entre as colunas das sequências S0 = TTMY, S1 = ATCC e S2 = AGCC, através do somatório da pontuação obtida ao realizar a substituição dos pares encontrados nas colunas pelo escore da tabela BLOSUM62??.
Levando em conta um número n de sequências, o número de combinações de pares em uma coluna é dado por n(n−1)
2 , que vem da série
Pn
i=1(i − 1) = n(n−1)
2 , que conta o número
de conexões entre n elementos. Como mostrado por [25], o uso de uma tabela BLOSUM62 com soma de pares pode apresentar resultados biologicamente errados, pois quantas mais forem as sequências presentes na coluna, a diferença relativa entre as sequências aumenta, que representa o contrário do comportamento esperado, levando a sua afirmação sobre resultados não muito bons com o uso da soma de pares e este tipo de tabela de escore.
Durbin [6] explica que a soma de pares tem falhas, pois eventos evolutivos são contados mais de uma vez, resultando em problemas, que crescem conforme o número de sequências comparadas entre si. O problema da tabela de substituição citado anteriormente também pode resultar em valores não razoáveis, calculados pela substituição direta dos valores dos pares pelos da tabela, que não representam o correto cálculo para se encontrar o valor da substituição, já que a tabela é baseada em cálculos logarítmicos, que não correspon-dem diretamente às operações de aritmética básica convencional, como soma, subtração, divisão e multiplicação. [6]
Um exemplo deste problema na tabela de substituição é dado no exemplo a seguir: supondo-se três aminoácidos i, j e k, com probabilidades qi, qj e qk de ocorrerem numa
substitui-Sequˆencia
Coluna 1
Coluna 2
Coluna 3
Coluna 4
S0
T
T
M
Y
S1
A
T
C
C
S2
A
G
C
C
AT AA AT TT TG TG MC CC MC YC CC YCPar (pontuação) Coluna 1 Coluna 2 Coluna 3 Coluna 4 Pontuação total
AA (4) 1 AT (0) 2 TT (5) 1 TG (-2) 2 MC (-1) 2 CC (9) 1 1 YC (-2) 2 Somatório 4 1 7 5 17
Figura 2.3: Modelo de soma de pares para alinhamento múltiplo de sequências e subs-tituição dos valores pelos da tabela BLOSUM62. Sequências alinhadas são S0=TTMY, S1=ATCC e S2=AGCC.
ção de um aminoácido por outro numa sequência homóloga, e Pijk é a probabilidade de
substituição de um aminoácido qualquer numa sequência homóloga.
Calculando a pontuação pela soma de pares entre três sequências, sem levar em conta um fator de ajuste em cada um dos logaritmos, tem-se a fórmula 2.2
log( Pij qi× qj ) + log( Pjk qj × qkj ) + log( Pki qk× qi ) (2.2)
Também sem o fator de ajuste, a pontuação correta se daria pela fórmula 2.3, onde é levada em conta a probabilidade da substituição de cada um dos aminoácidos por qualquer um dos outros dois.
log( Pijk
qi× qj× qk) (2.3)
2.1.2
Soma de pares com pesos (WSP)
A soma de pares com pesos, sugerida por Altschul [1], foi desenvolvida com dois possíveis esquemas para compensar parcialmente o defeito dos escores obtidos pela soma de pares.
Os pesos visam corrigir principalmente dois problemas em específico: o primeiro é que diferentes organismos que sofreram menos modificações fornecem sequências mais próximas e, por isto, deveriam ter um peso maior quando comparadas com sequências provenientes de outros organismos, que tendem a se diferenciar mais fortemente; o
se-gundo problema é que organismos extremamente relacionados tendem a ter propriedades similares e, portanto, dar pesos iguais para sequências de diversos organismos similares, seguindo o exemplo de Altschul [1], é como querer comparar várias cópias de um mesmo elemento ou seja, dar mais relevância a sequências parecidas pode não fornecer bons re-sultados, se o objetivo do alinhamento for de encontrar não só semelhanças, mas também ressaltar diferenças entre as sequências.
Altschul [1] citam que a estimativa de similaridade máxima, proposta por Felsenstein [8], leva em conta as variâncias, que são proporcionais às distâncias de elementos até a origem da árvore que pode ser montada com as sequências, e as covariâncias, que são proporcionais às distâncias entre duas folhas de uma árvore até seu ancestral comum. Estas variâncias e covariâncias podem ser calculadas em tempo linear e servem como base para cálculo de pesos, formando uma pontuação de similaridade bem mais eficiente que a média aritmética, que é aproximada pela soma de pares convencional.
São propostas duas formas de se calcular os pesos para a soma de pares, sendo que a primeira é feita a partir de uma árvore evolucionária sem raiz, que relaciona todas as sequências. Nesta árvore existe um caminho único p que conecta qualquer par de folhas, e cada p usa todas as arestas por onde ele passa. Neste contexto, uma mutação em uma única aresta da árvore evolucionária impacta na mudança da distância de todos os caminhos que fazem uso desta mesma aresta, de forma que os pesos devem ser recalculados ao longo da execução de um alinhamento.
Se desejamos que todas as mutações sejam contabilizadas igualmente, não importando a direção em que se passa pela aresta, precisamos que os pesos para todos os pares usando a aresta somem um. O mesmo procedimento deve ser usado para todas as outras arestas, que resultarão num conjunto de equações lineares, uma para cada aresta, relacionando os pesos desconhecidos. Para árvores com mais de três folhas, existem menos equações que incógnitas, o que por um lado facilita a resolução do problema, porém limita a generalidade da solução [1].
A ordem de um nó na árvore é o número de arestas chegando naquele nó. Supondo p um caminho que liga um par de nós, passando por nós de ordem a1, ..., ak, temos que o
peso de p é dado por 2.4:
wp = k Y i=1 1 (ai−1) (2.4) Altschul [1] citam como exemplo deste procedimento o seguinte caso: considere dois nós de grau três e quatro, ligados entre si por um caminho P . Com base na equação 2.4, o cálculo do peso do caminho pode ser achado resolvendo a equação
A
C B
X
Figura 2.4: Exemplo de grafo com problemas para cálculo do primeiro esquema de pesos
wp =
1
((3 − 1) × (4 − 1)) = 1
6 (2.5)
Este método, segundo Altschul [1], apresenta alguns problemas, como ser dependente apenas da topologia da árvore que relaciona as sequências, mas não dos tamanhos das arestas, que tiveram seus pesos calculados anteriormente. Alguns destes pesos atrelados às arestas podem se aproximar cada vez mais de zero, indicando um ramo com menor similaridade entre as sequências. Se estes valores forem nulos, podem haver erros nos cálculos e fazem com que sejam percebidos valores altos incorretos. Esta nulidade do peso de uma aresta pode ser observada na Figura 2.4, onde a nulidade do peso de uma aresta faz com que os pesos fiquem estáveis, mas ao desaparecerem, o peso salta para valores altos, já que algumas das outras combinações desaparecem e a probabilidade das outras combinações ainda existentes aumenta. No exemplo da Figura 2.4, os pares com A (A-B e A-C) se destacam do outro par possível, B-C, simplesmente por ser o que tem mais cópias. Se o valor de x for nulo, as sequências possíveis para o alinhamento são A-B e A-C.
A segunda forma de se calcular os pesos é análoga ao esquema de pesos de Felsenstein [8], porém sem a distribuição normal de variáveis. No caso, presume-se que há informação sobre homologia das sequências e, portanto, tentamos combinar a informação da melhor maneira possível para estimar a similaridade entre diversas sequências. [1]
Assume-se que qualquer par de sequências é uma boa referência para comparação, e que quanto mais perto um par p de sequências for, menos informação o par carrega sobre variação de elementos em posições diferentes, porém esta informação é inversamente pro-porcional à informação sobre o alinhamento das sequências. Para evitar problemas citados previamente, como múltipla contabilização de pares, precisamos definir um conjunto de coeficientes de correlação entre os pares das sequências.
Se existem dois caminhos p e q, de tamanhos lp e lq, que ligam, cada um deles, dois nós
folha da árvore filogenética construída a partir das sequências de entrada, possivelmente teremos uma sobreposição entre estes caminhos, e esta sobreposição tem comprimento lpq.
Figura 2.5: Exemplo de cálculo de pesos pela segunda metodologia de Altschul [1], para três sequências (NNN, NNC, NCC)
2.4
A partir destes caminhos, podemos calcular coeficientes ρpq = l2
pq
lp×lq, que relacionam cada par de nós da árvore com outros. Estes coeficientes podem ser dispostos numa matriz simétrica com M linhas e colunas acessáveis pelo conjunto de pares de folhas, onde as entradas são os coeficientes ρpq. A partir da inversão desta matriz M, e somando os
coeficientes das linhas, obtemos os pesos entre pares de nós. Estes pesos têm diversas propriedades interessantes, que são citadas em [1], como: à medida que comprimento dos caminhos se aproxima de zero, os pesos associados se aproximam de um, enquanto o peso do alinhamento B-C se aproxima de zero.
Outro desafio no cálculo dos escores é levar em conta critérios biológicos relevantes no alinhamento das sequências, algo que necessita do auxílio de especialistas com expe-riência da área, e que deve ser traduzido nas diversas soluções de alinhamento múltiplo de sequências. Alguns destes critérios podem ser resumidos pelas tabelas de pontuação, como nas já citadas BLOSUM e PAM, que diferem quanto as informações utilizadas em sua construção [25].
2.2
Complexidade do alinhamento
O alinhamento múltiplo de sequências com a soma de pares foi provado NP-Difícil e NP-Completo por Wang et al [36]. A complexidade espacial do alinhamento é dada por
O(N, L) = NL, onde N é o número de sequências sendo alinhadas e L o comprimento da
Por tratar-se de um problema NP-Completo, duas linhas de pesquisas surgiram com o intuito de produzir o alinhamento em tempo hábil, mesmo para um número grande de sequências e sequências mais longas: a primeira dela utilizando métodos heurísticos e a segunda utilizando técnicas de redução de espaço de busca.
Os métodos heurísticos buscam um bom alinhamento de maneira não exata, e portanto não garantindo que o alinhamento ótimo seja encontrado. Ao não garantir o alinhamento ótimo, diversos outros alinhamentos não ótimos podem ser produzidos numa fração do tempo de um método exato, onde cada resultado parcial é checado de maneira a garantir que o resultado de fato seja ótimo.
Os métodos de redução de espaço de busca garantem a obtenção do resultado ótimo, não processando áreas do espaço de busca do alinhamento que com certeza não contri-buirão para a obtenção da melhor solução ou postergando buscar o alinhamento em áreas menos promissoras.
No presente trabalho de graduação, serão considerados somente métodos de redução de espaço de busca e os mesmos serão detalhados nos capítulos 3 e 5.
Capítulo 3
Métodos de redução do espaço de
busca
Neste capítulo são tratados dois métodos de redução do espaço de busca, sendo eles o proposto por Carrillo-Lipman (seção 3.1) e o A-Star (seção 3.2), seguido da adaptação do algoritmo A-Star para o alinhamento múltiplo de sequências (seção 3.3).
Na busca por caminhos mais curtos de um grafo, é possível classificar as abordagens mais comuns para solução em: heurística e exata.
Os métodos heurísticos são baseados no conhecimento aprofundado sobre um deter-minado domínio, que é explorado para se achar resultados próximos ao(s) ótimo(s). São ditos próximos já que não há garantias de que os valores ótimos serão encontrados através destes métodos, considerados não exatos.
Os métodos exatos partem de provas matemáticas, que garantem que parte do espaço de busca possa ser descartado sem prejudicar o resultado exato buscado. Por garantirem que o resultado ótimo seja encontrado, caso este exista, os método exatos ainda são mais custosos que muitos dos métodos heurísticos, onde o espaço de busca é cortado mais agressivamente.
3.1
Carrillo-Lipman
Carrillo e Lipman [4] desenvolveram seu algoritmo que trata as sequências de entrada como arestas de um hipercubo N-dimensional, como mostrado nas Figuras 3.1 e 3.2 de forma que as coordenadas N-dimensionais representam o alinhamento entre as colunas das diversas sequências. Como citado previamente, o alinhamento ótimo pode ser encontrado ao buscar o menor caminho que ligue o ponto inicial e o ponto final do espaço de busca, que se encontram no começo de todas as sequências e ao final delas, respectivamente, de maneira que este percurso obtenha a melhor pontuação, garantindo que o caminho e seu
Figura 3.1: Grafos para alinhamento de duas e três sequências
Figura 3.2: Hipercubo mostrando espaço de busca no alinhamento entre três sequências [4] alinhamento associado são ótimos. Uma representação dos alinhamentos entre duas e três sequências pode ser vista na Figura 3.1.
O percurso traçado através do cubo é feito de forma que tenha interseção apenas em uma das faces dos hipercubos formados pelo produto das colunas das sequências, e que formam o hipercubo que representa o espaço de busca completo. Outro caso de interseção possível é na junção de cantos de hipercubos, onde o caminho troca de uma coluna para outra, de uma ou mais sequências.
Carrilo e Lipman também tratam, em [4], dos caminhos ótimos para o alinhamento múltiplo das sequências, caminhos estes que possuem escores calculados a partir das com-parações individuais, que levam em conta operações de remoção, inserção e substituição dos termos durante o alinhamento.
busca pelo alinhamento ótimo, como, por exemplo, o fato de que a probabilidade de duas remoções adjacentes não ser o quadrado da probabilidade do que uma remoção simples, porque um mesmo evento mutacional pode ser responsável por ambas as remoções, assim como mostrado na equação 2.3 para três possíveis combinações [4].
Para se achar o caminho que leve ao alinhamento ótimo, é necessário buscar o caminho
γ com menor valor de M(γ), que é a função que calcula a similaridade entre todas as
sequências sendo alinhadas. Para cada medida M(γ), existe pelo menos um caminho
γ∗(S1, ..., Sn) tal que M tenha o valor mínimo para o caminho γ∗, também chamado de
caminho ótimo, que podem ser encontrados com métodos de programação dinâmica, como por exemplo, o algoritmo de Needleman e Wunsch [26].
Uma das formas de se buscar o caminho ótimo é quebrar a busca pelo caminho num hipercubo N-dimensional L(S1, ..., Sn), por buscas de caminhos em seus subcubos
L(S1(i1), ..., Sn(in)), acessíveis através de seus vértices, particionando o espaço de busca,
e permitindo inclusive buscas concorrentes e paralelas por frações da solução do problema, que serão vistas posteriormente. A busca recursiva pelos caminhos ótimos nos cubos e cubos adjacentes, sempre buscando o caminho partindo do vértice de origem, no caso o de menor coordenada no subcubo, até o vértice de destino, tipicamente o vértice de maior coordenada.
Carrilo e Lipman propõem além do seu algoritmo, um limite superior, que é utilizado para descartar caminhos na busca pelo alinhamento ótimo, afim de reduzir o espaço de busca do algoritmo, que como já citado, cresce exponencialmente, acelerando a obtenção do alinhamento múltiplo ótimo e reduzindo custo computacional geral médio do algoritmo. O limite superior é dado pela diferença entre a medida M(γe), onde γe é um caminho
estimado, e a medida M(γ∗), onde γ∗ é o caminho mínimo. Essa diferença deve ser maior
ou igual a zero, ou M(γe) − M(γ∗) > 0, conforme Carrilo e Lipman demonstram em [4].
Considerando pij a projeção de um caminho γ num plano determinado pelas sequências
Si e Sj, µij a medida de similaridade entre duas sequências num plano formado por
L(Si, Sj), e aplicado à espaços N-dimensionais, podem ser considerados um conjunto de
elementos limiares superiores para a medida de comprimento do caminho ótimo, dado entre sequências Sk e Sl, onde k é cada sequência e l = 1, . . . , N, que pode ser escrita
como [4] : Ukl= N X i<j µij(pij(γe)) − N X i<j,(i,j)6=(k,l) µij(γ∗ij) (3.1)
A equação 3.1 pode ser usada para se montar um conjunto X de caminhos candidatos à ótimos Xkl, que podem ser obtidos procurando por caminhos γ∗que satisfaçam a condição
dinâmica de forma a achar γ∗ contido numa sub-região Y de L(S
1, . . . , SN), sub-região
esta que também pode ser determinada através de programação dinâmica [4].
De forma simplificada, o objetivo do algoritmo de Carrilo-Lipman é reduzir o espaço de busca para um poliedro, onde existe ao menos um caminho com um alinhamento múltiplo entre as sequências que se deseja alinhar, onde o alinhamento ótimo pode ser pesquisado numa determinada área do poliedro, centrada num dos alinhamentos não ótimos encon-trados [2, 4]. Para que isto seja feito, é calculada a projeção do caminho que forma o alinhamento, de maneira que esteja dentro de uma margem razoável, calculando um valor para cada possível projeção do alinhamento entre as sequências. Tanto a determinação um alinhamento heurístico quanto a sub-região ao seu redor, que forma o poliedro em questão, podem ser obtidos através da programação dinâmica [2,4,11].
3.2
A-Star
Uma das técnicas utilizadas para se alinhar múltiplas sequências é o algoritmo A-Star, que é uma extensão do algoritmo de Dijkstra, baseado no trabalho de Hart [13], e que busca o menor caminho entre dois nós de um grafo partindo o espaço de busca numa forma de árvore de busca, onde a computação de alguns dos ramos fica paralisada enquanto o escore do ramo sendo atualmente calculado é melhor do que os mesmos. O escore do A-Star é dado por uma função específica do problema, seja ele de maximização, buscando o caminho de maior pontuação, ou de minimização, buscando o caminho de menor pontuação.
O algoritmo A-Star tem por objetivo minimizar a expansão de nós na busca do caminho ótimo, cortando o espaço de busca e reduzindo o esforço total da busca. Para que este esforço seja minimizado, é necessário que o algoritmo seja constantemente alimentado de informações dos nós que podem ser expandidos, e a partir destas informações escolher se um nó será ou não expandido. A necessidade de descartar caminhos não ótimos apresenta uma série de dificuldades, como por exemplo se um caminho considerado no momento não for ótimo, é necessário reprocessar parte dos nós do grafo, de maneira a achar ao menos um caminho ótimo. Além deste, o A-Star possui problema quanto à escalabilidade, visto que o número de nós a serem expandidos cresce exponencialmente com o número de sequências comparadas e linearmente com o tamanho da maior sequência comparada. [13]
Hart [13] organizam o algoritmo A-Star em quatro passos básicos: (1) um nó s é marcado como sendo aberto e uma função de avaliação f é calculada sobre este nó; (2) um nó aberto n, com o menor valor calculado de f, é selecionado, abrindo em seguida nós adjacentes de forma arbitraria, favorecendo qualquer nó n ∈ T (sendo T o conjunto de nós alvo do grafo); (3) se n ∈ T , marque o nó n como fechado e termine o algoritmo,
7 3 3 2 s n2 n1 n3
Figura 3.3: Grafo contendo nós e pesos das arestas que os ligam
pois achou-se o nó de destino e por isto um caminho, caso não, vá para o próximo passo; (4) marque n como fechado e aplique um operador Γ no nó n, achando o seu sucessor. Ainda no passo (4), calcule a função de avaliação f para cada sucessor de n e marque-os como abertos, se já não estiverem marcados como fechados. Marque como abertos todos os nós fechados ni que sejam sucessores de n e para os quais f(ni) seja menor agora que
quando ni era marcado como fechado. Repita o passo (2).
Se a função f for bem escolhida, e será tratado logo em seguida como fazer uma boa escolha, o algoritmo descrito anteriormente garante que o caminho encontrado entre um nó inicial e um nó alvo é o caminho ótimo.
Hart [13]definem a função f(n) como uma função que retorna o valor do caminho ótimo entre um nó de partida s e um nó alvo n. Como o objetivo é se achar justamente o caminho ótimo entre um ponto inicial e um ponto de destino, o valor final de f só é descoberto após chegar nele, ou seja, é necessário fazer algum tipo de estimativa, que se aproxime do valor esperado, de forma a fazer com que o resultado do algoritmo do A-Star convirja para o valor ótimo de f [13].
A aproximação de f(n) pode ser feita a partir da soma de duas outras funções, uma que guarda o custo do nó inicial até o nó atual, g(n), e outra que estima o valor do nó atual até o nó de destino, h(n). Se pudermos calcular o valor de g(n) e estimar o de h(n), podemos somá-los e estimar o valor calculado de f , de maneira que f(n) = g(n) + h(n), como demonstrado em [13].
Com base na Figura 3.3, partindo do nó s em direção aos nós n1, n2, n3, com os custos
escritos em de cada arco do grafo, pode-se obter valores de g dos nós n1 e n2, que são
g(n1) = 3 e g(n2) = 7, respectivamente. Partindo do nó n1, podemos alcançar n2 e n3,
donde podemos calcular ˆg(n2) e ˆg(n3), que são dados por ˆg(n2) = 3 + 3 = 6, que é menor
que o custo anteriormente conhecido, e por isso é substituído pelo menor custo atualmente conhecido, enquanto o valor aproximado de ˆg(n3) = 3 + 2 = 5.
Também é provado em [13] que o algoritmo é ótimo e, comparado aos outros algoritmos da mesma época que trabalham sem outras informações adicionais, é o algoritmo que expande o menor número de nós e garante a descoberta do caminho ótimo. Quando a estimativa de h, ˆh, é igual a zero, corresponde ao caso de desconhecimento do domínio do
Algoritmo 1 Algoritmo A-Star, partindo do nó Ni até o nó Nf
problema, ao passo que é possível se valer de heurísticas que podem ajudar a selecionar mais rapidamente os nós e achar o resultado expandindo menos nós, se houver algum conhecimento prévio do domínio do problema. No caso deste trabalho, o domínio é o alinhamento múltiplo de sequências, mas os conceitos relativos a esta aproximação será tratado adiante, em conjunto com o tema relativo a adaptação do A-Star para o alinhamento múltiplo.
Spouge [33] demonstrou que a função que calcula os limites inferiores de Carrilo-Lipman (seção 3.1), também chamada de h2,all, é uma heurística admissível para o
A-Star, implicando que seja passível de uso desta para descarte de parte do espaço de busca, ao estimar que a partir do nó atual mais o limite inferior não deve ultrapassar o patamar superior ao ótimo, de forma que os nós que não contribuem de maneira suficiente para continuarem sendo considerados na busca pela solução ótima. Seu cálculo é feito previamente ao procedimento do alinhamento [32], onde é computado o menor custo até o nó objetivo, para cada alinhamento par a par e seus elementos, o que é calculado com programação dinâmica. Como o objetivo é minimizar h, o algoritmo parte do nó final em direção ao nó inicial, de maneira que descubra, a partir de um dos ramos mais curtos que chegue ao nó final, valores razoáveis para h, de maneira a facilitar a computação do A-Star.
as pontuações atuais mediante movimentação para nós adjacentes a estes, obtendo o escore dessas expansões, de forma que seja possível obter o caminho de menor pontuação pelo valor mínimo dos nós, e ao final da execução, se houver um caminho, o valor do escore deste será retornado pelo valor do nó final do alinhamento.
3.3
Adaptação do A-Star ao MSA
O alinhamento múltiplo de sequências visa obter o melhor alinhamento entre as sequên-cias, assim como explicitado no capítulo 2.
Schroedl propõe em [32] um algoritmo que tenta reunir vantagens do A-Star, como a heurística de corte do espaço de busca, reduzindo o tamanho das listas de nós Aber-tos e Fechados até o tamanho máximo O(kNk−1), assim como na versão do algoritmo
implementado com programação dinâmica.
A adaptação proposta por Schroedl se baseia em algumas observações. A primeira observação delas é que o fato de que alguns nós fechados não são alcançáveis mais de uma vez durante uma busca, o que permite apagar todos os nós que não podem estar em nenhum dos caminhos mais curtos passando pelos nós atualmente abertos. A segunda observação citada é que, além do tamanho da lista fechada, a memória necessária para a lista aberta é determinada pelo número máximo de nós abertos simultaneamente durante a execução. Ao usar uma função f para se determinar a prioridade da fila dos nós a serem expandidos, a lista de nós abertos contém os nós num intervalo (fmin, fmin + δ),
que está distribuído por todo o espaço de busca, já que a função g, citada na seção 3.2, varia arbitrariamente entre 0 e fmin+ δ. Como este número pode ser muito grande, outra
estratégia para manter o controle do uso de memória é usar a estratégia de programação dinâmica, onde os nós são processados pelos níveis antidiagonáis, onde em qualquer in-teração, o máximo de nós que devem ser mantidos ao mesmo tempo é no máximo os k níveis, ou seja, a largura do espaço de busca, o que pode ser realizado para sequências curtas e em alinhamentos de poucas sequências.
O terceiro e última observação citado Schroedl [32] é que o tempo de execução não deve ser contado em termo de expansão de nós, mas sim no tempo de execução para cada expansão de um nó, já que um número maior de nós expandidos não necessariamente indica melhorias, apenas a busca maior, faça ela sentido ou não, enquanto o tempo de expansão de cada nó serve de parâmetro adequado para comparações. A depender da maneira como a exploração é executada, grande parte da computação pode ser guardada em cache, acelerando a computação da solução do problema, além de reduzir o tempo de expansão de nós. A sugestão feita é que as bordas, que tem no máximo 2k −1 nós
Além destas três observações, o problema restante da adaptação do A-Star passa a ser calibrar corretamente o espaço de busca com os valores obtidos com a função h = f − g, citada na seção 3.2, onde conhecendo previamente o custo g∗(t) do caminho mais curto,
é possível proceder pelo espaço de busca cortando imediatamente quaisquer bordas e quando f(e) > g∗(t).
Outra estratégia a ser levada em conta é o patamar superior dos valores de h, que ajuda a diminuir o tamanho da lista fechada de nós, visto que permite o corte de todos os nós que passem deste patamar, e no caso de todos os nós em um dado ramo terem um único nó filho cada, este ramo inteiro pode ser removido, já que a remoção dos nós é propagada. Este limite superior pode ser calculado baseando-se nos limites de Carrillo-Lipman, citados na seção 3.1.
Schroedl [32] define o nome de seu algoritmo proposto como Interactive-Deepening
Dynamic Programming (IDDP), onde um laço externo inicializa o patamar com um valor
baixo, como h(s), e caso nenhuma solução seja encontrada, o valor do patamar é aumen-tado, de forma a incluir mais nós na próxima iteração de expansão dos nós, e de forma a incluir pelo menos uma borda adicional a ser explorada.
Em um nó interno, são selecionados e removidos nós com prioridade mínima da fila de prioridades, que são expandidos parcialmente, visto que alguns de seus nós filhos podem já ter sido expandidos anteriormente, e é necessário testar se é possível diminuir o valor de
g do caminho. Caso não seja possível, uma nova borda é gerada e usada para se calcular o
valor f, que caso ultrapasse o patamar superior definido, é logo descartado junto da borda, e caso não ultrapasse, é utilizado para se atualizar o novo valor do patamar superior.
A função de expansão das bordas conta com um procedimento interno para atualizar a borda, onde são calculados valores f e g dos nós filhos, além de ajustar os ponteiros que apontam para o nó de borda pai e o insere na lista de nós abertos, se ainda não estiver nesta. Esta lista é alocada em uma heap, já que se o caminho for descartado, a memória é descartada conjuntamente. Caso um nó ancestral seja removido, a remoção é propagada até alcançar um nó fechado, que continua armazenado por fazer parte de algum caminho. Uma outra causa para remoções é se imediatamente após a expansão de um nó, nenhum filho apontar para ele, que significa que os nós filhos podem ser alcançados com menor custo por outro nó ou o valor f excedeu o patamar superior.
Schroedl [32] prova a correção de seu algoritmo proposto, de maneira similar a prova do algoritmo A-Star convencional, já que se trata apenas de uma adaptação ao escopo do alinhamento múltiplo de sequências. Também destaca que se o patamar superior for menor que g∗(t), então a busca pela programação dinâmica falhará sem encontrar a solução e,
caso contrário, apenas nós cortados da busca não podem fazer parte do caminho ótimo. O teorema supõe que sempre há um nó em cada nível que está no caminho ótimo e este
está na lista de nós abertos, portanto se o algoritmo termina quando a heap está vazia, a melhor solução encontrada será a ótima.
Existem diversas técnicas de otimizações, que são também citadas por Schroedl [32], a fim de reduzir o consumo de memória e tempo de execução do problema, sendo a mais conhecida a da estratégia Dividir-para-Conquistar, onde prova que o número ideal de divisão do trabalho é expansão de dois nós para cada nó expandido previamente, porém expandindo quatro vezes mais nós que o A-Star, que causam efeitos indesejados já citados, como maior utilização de memória.
Capítulo 4
Computação paralela com troca de
mensagens
A computação paralela é cada vez mais importante no mundo contemporâneo, à me-dida que tecnologias de processamento puramente sequencial tem sua capacidade de pro-cessamento limitado por diversos fatores, sendo um deles dissipação de potência, que além de aumentar o consumo energético também faz com que mais calor seja dissipado no chaveamento dos transistores dos quais são feitos os processadores [30].
Existem diversas abordagens de como se distribuir a computação de determinados problemas de forma paralela, cada uma com suas vantagens e desvantagens. As principais maneiras de programação paralela são: (1) utilizando de memória de trabalho e controle compartilhada entre os nós de processamento, permitindo que estes tenham acesso aos mesmos dados de maneira direta, sendo que a lógica de controle e compartilhamento de memória entre diversos processadores e máquinas é feito de maneira transparente. Nesse caso, são necessárias técnicas de controle de concorrência nos acessos para leitura e escrita, a fim de garantir que o resultado correto será produzido com o mínimo de overhead; (2) através de troca de mensagens entre os processos, também conhecida como comunicação indireta, que pode ser feita via sockets, ou através de ambientes de programação como MPI (message passing interface), permitindo uma programação mais genérica e comunicação entre processos em máquinas diferentes [24].
A abordagem utilizada neste trabalho é a com programação em MPI, que é um padrão usado internacionalmente para facilitar a portabilidade de código paralelo parar diversos sistemas e arquiteturas, e é implementado se utilizando de sockets [29].
Na seção 4.1, uma breve introdução é feita sobre sockets. Na seção 4.2, é feita uma introdução do MPI e funcionalidades básicas.
4.1
Sockets
Sockets são mecanismos de comunicação inter-processos, que podem se comunicar
entre processos numa mesma máquina, também conhecido como domínio local, ou em máquinas diferentes, se comunicando através de TCP no domínio da internet [24]. Estes domínios determinam se o socket tem ou não um endereço, caso seja internet ou local, respectivamente. Se o socket for no domínio local, um descritor de arquivo é criado pelo sistema de arquivos, que usa este como meio de comunicação, em alternativa à pilha de comunicação dos protocolos de internet, onde, apesar de tratado através de um descritor de arquivo, a estrutura de comunicação não é de fato um arquivo [24]. Os dados são transmitidos em forma de stream, os dados são transmitidos do socket emissor para o
socket receptor.
As implementações de sockets são baseadas na implementação feita inicialmente no
Berkeley Software Distribution (BSD), onde esta utilidade está integrada no kernel do
sistema operacional [24]. Em outros sistemas operacionais, foram feitas bibliotecas que implementam sockets, como é o caso da família de sistemas operacionais Windows, que contam com o Winsock [23].
Sockets usados neste trabalho serão gerenciados pela biblioteca MPI, que será
deta-lhada na seção 4.2.
4.2
MPI
Historicamente, o MPI surgiu num momento onde pesquisadores desejavam computar mais dados de maneira paralela, sendo que dois grupos desenvolveram paralelamente duas tentativas de padrões para programação de sistemas paralelos, sendo: (1) HPF (high
performance Fortran), que é um padrão que se baseava em um conjunto de extensões
para o Fortran 90, provendo maneiras de programar com paralelismo de dados, onde um mesmo laço era operado por todos os processadores sobre dados diferentes; (2) MPI (message passing interface), onde o padrão se baseava não na criação de uma extensão para uma linguagem, mas sim numa biblioteca que poderia ser utilizada por outras linguagens, contendo funções que manipulam a troca mensagens [29].
O MPI, por ser baseado em troca de mensagens, pode ser utilizado para programação de programas paralelos complexos. Além de paralelismo de dados, é capaz de realizar um tipo específico de paralelismo chamado de SPMD (single-program multiple data) com paralelismo de instruções (MIMD, multiple-instruction multiple-data), executando trechos diferentes do código de um programa por cada processo [29].
Pacheco destaca em [29] que embora o MPI possa produzir programas paralelos muito poderosos, existem diversos problemas na programação devido à: (1) complexidade que pode ser inserida na lógica do programa; (2) dificuldade na depuração; (3) diferentes implementações do MPI podem conter mais ou menos features previstas no padrão, além de terem comportamentos variados em áreas não detalhadas do padrão, como entrada e saída, que pode ser ou não individualizado. Um exemplo do terceiro problema citado anteriormente é o suporte ao modo MPI_THREAD_MULTIPLE, presente no MPICH, mas não implementado pelo OpenMPI.
Algumas das principais funcionalidades do MPI que merecem destaque são: ranks, comunicadores, grupos, troca síncrona e assíncrona de mensagens, além de troca de in-formação entre processos com funções gather e scatter, que serão detalhadas nas seções a seguir.
4.2.1
Estrutura básica
A estrutura básica de um programa em MPI, em C, possui inicialmente um include do cabeçalho da biblioteca. A seguir, a função MPI_Init(&argc, &argv) deve ser chamada, de maneira a iniciar todos os processos e preparar para futuras chamadas de funções MPI. Depois disso, as funções MPI podem ser utilizadas. Por fim, a função MPI_Finalize() deve ser chamada após todas as chamadas MPI do programa, visto que ela é utilizada para liberar quaisquer recursos que esteja mantendo para a execução correta do MPI, e após sua chamada, o comportamento de qualquer função MPI pode apresentar resultados inesperados [29].
Um exemplo da estrutura básica é demonstrado no algoritmo 2.
Algoritmo 2 Corpo de função C com MPI
#include <mpi.h>
int main(int argc, char ∗ argv[]) {
MPI_Init(&argc, &argv); //Insira seu código MPI aqui MPI_Finalize();
4.2.2
Ranks, Comunicadores e Grupos
Dois dos conceitos fundamentais do MPI são os de ranks e de comunicadores, onde o primeiro se trata de um número que serve de índice para os processos MPI criados dentro de um comunicador, e é através destes índices que se identifica cada processo MPI. Os comunicadores servem de coletânea de processos, onde é possível ajustar grupos de comunicações, por exemplo, separando processos em grupos por atividade dedicada destes [29].
O rank de um processo pode ser obtido através da função int MPI_Comm_rank (MPI_Comm comm, int * rank), onde os parâmetros de entrada são o comunicador do qual se deseja descobrir o índice do processo, e o segundo parâmetro serve como saída da chamada, ou seja, onde o programa grava o índice do programa chamador relativo ao comunicador passado. Os possíveis valores de retorno da função são MPI_SUCCESS ou MPI_ERR_COMM, que indicam sucesso da chamada ou erro devido à comunicador inexistente ou endereço inválido [29].
Um exemplo muito comum durante o aprendizado sobre MPI é um programa simples que faz com que diversos processos descubram seu índice e os enviem junto a uma saudação para o processo de índice 0 que, geralmente, é o processo que tem acesso ao terminal. Este exemplo pode ser visto no algoritmo 3 [29]
Outra função básica utilizada no exemplo do algoritmo 3 é a int MPI_Comm_size( MPI_Comm comm, int * num), que retorna o número de processos que participam dum dado comunicador, algo extremamente relevante, mesmo porque os comunicadores po-dem ser modificados em tempo de execução, e os processos popo-dem necessitar ajustar seu funcionamento de acordo com o número de processos disponíveis.
Além destas funções básicas, outras funções mais avançadas são as de formação de gru-pos, que permitem definir topologias diversas além de malha completa, que é a constante MPI_COMM_WORLD [29].
4.2.3
Tipos de dados
As trocas de mensagens por MPI se baseiam em tipos de dados primários definidos [29]. Porém, assim como o C permite agrupamento destes em structs, o MPI permite a agregação de dados para transmissão em mensagens. Os tipos de dados padrão são variados, cobrindo a maioria dos tipos primários do C e Fortran, e são dados, em sua maioria, em caixa alto, anexado o prefixo MPI_, como em MPI_CHAR ou MPI_INT [29]. A criação de tipos abstratos pode ser feita a partir de diversas funções, que criam tipos de dados, como: contiguous, onde um mesmo tipo primário é repetido num vetor homogêneo; vector e hvector, onde também é formado um vetor de tipo homogêneo, mas
Algoritmo 3 Exemplo de obtenção de rank e uso de comunicador
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <mpi.h>
int main(int argc, char∗ argv[])
{ /∗ Alocação de variáveis de controle e programa∗/ int rank, p, source, dest, tag 0;
char message[100]; MPI_Status status;
/∗ Inicialização da biblioteca, índice do processo
atual e número de processos do comunicador∗/ MPI_Init(&argc, (char ∗∗∗)&argv);
MPI_Comm_rank(MPI_COMM_WORLD, &rank); MPI_Comm_size(MPI_COMM_WORLD, &p);
if (rank !← 0)
{
sprintf(message,
"Olá mundo, aqui é o processo %d!", rank); dest ← 0; MPI_Send(message,strlen(message)+1, MPI_CHAR,dest,tag,MPI_COMM_WORLD); } else {
for (source ← 1; source < p; source++) { MPI_Recv(message,100,MPI_CHAR, source,tag,MPI_COMM_WORLD, &status); printf("%s\n", message;) } } /∗ Finalização da biblioteca ∗/ MPI_Finalize(); }
que pode ter espaçamento entre elementos do tamanho do tipo dos elementos usado ou de bytes, respectivamente; indexed e hindexed, onde um vetor de deslocamento e tamanho são passados, de forma a mapear um tipo de dados antigo em um novo tipo; onde o deslocamento é dado em elementos ou em bytes, respectivamente; struct, onde o novo tipo é formado pelo mapeamento de outros tipos de dados [28].
4.2.4
Troca síncrona de mensagens
A troca síncrona de mensagens pode ser vista no algoritmo 3, onde os diversos pro-cessos enviam ao processo de rank 0 uma mensagem, e ficam bloqueadas até que o rank 0 receba a mensagem. Assim que o rank 0 confirme o recebimento da mensagem, o rank que enviou continua seu fluxo de execução, que é finalizar o MPI e terminar sua execução [37]. As funções de envio e recepção
int MPI_Send(const void ∗buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm)
e
int MPI_Recv(void ∗buf, int count, MPI_Datatype datatype,
int source, int tag, MPI_Comm comm, MPI_Status ∗status)
fazem, respectivamente, o envio e o recepção de uma mensagem de tipo bem definido, ou estabelecido pelo MPI ou definido pelo usuário, e para ou de um outro processo alvo [37].
O princípio descrito acima, onde um processo aguarda o outro informar o recebimento de uma mensagem é chamado de comunicação síncrona, e apresenta vantagens de ter uma implementação mais simples, não necessita desenvolver uma lógica mais complexa para tratamento de mensagens, nem se preocupar com acúmulo de buffers de mensagens, que pode levar à perda de mensagens. Por outro lado, o uso de mensagens síncronas limita a velocidade de processamento dos dados, já que os diferentes ranks ficam bloqueados até receberem o aviso de recepção das mensagens que enviaram, fazendo com que além do overhead de transmissão e processamento das mensagens, tenha-se um novo overhead com troca de mensagens de controle [29].
A sincronização também pode ser feita de outras formas, além das funções de envio e recepção síncronas, como por exemplo unindo envio e recepção assíncronos com funções
os outros até continuar sua execução [37]. A espera pode se dar de quatro maneiras: MPI_wait espera uma comunicação específica; MPI_Waitall, aguarda para que todas as comunicações ocorram; MPI_Waitany aguarda que qualquer comunicação ocorra e MPI_Waitsome, que aguarda que algumas comunicações sejam completadas antes de liberar a execução [37].
Além de funções barreira do tipo MPI_Wait, onde o foco é em comunicações, existem barreiras do tipo MPI_Barrier, que fazem com que os processos aguardem até todos os outros pararem em uma barreira [37].
Eijkhout, em [7], diz que apesar das inúmeras vantagens das comunicações síncronas, um pequeno bug pode introduzir deadlocks no programa e atrapalhar o fluxo da execução de toda a aplicação paralela, levando não só à perda de processamento, como também tempo e dinheiro gasto com ele.
4.2.5
Troca assíncrona de mensagens
Alternativamente a troca síncrona de mensagens, o padrão MPI também oferece troca assíncrona de mensagens, que podem ser feitas chamando funções de prefixo MPI_I, como
int MPI_Isend( const void ∗buf, int count, MPI_Datatype datatype,
int dest, int tag, MPI_Comm comm, MPI_Request ∗request)
int MPI_Irecv( void ∗buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Request ∗request)
que executam as mesmas operações que as funções síncronas, porém de forma não blocante, ou seja, após o envio o processo continua seu fluxo de execução sem esperar o receptor responder ao envio da mensagem. Apesar dos ganhos de desempenho por perder menos tempo esperando a comunicação ser efetivada, os programas devem ser preparados para receber mais mensagens que no caso da comunicação síncrona, já que um buffer pequeno de mensagens pode acarretar em perda de mensagens e, consequentemente, acarretar em perdas na computação [7,37].
Outro problema, já citado na seção 4.2.4, é que as trocas assíncronas de mensagens não têm a garantia do envio e recebimento da mensagem após o retorno da função, sendo necessário o uso de alguma função de espera, como MPI_Wait, para garantir a finalização da transmissão. Este comportamento necessita de maiores cuidados, como por exemplo na manipulação dos buffers de envio e recebimento, que podem não ter sido esvaziados ou