• Nenhum resultado encontrado

Novamente, com uso do Devito, ´e poss´ıvel escrever tal equa¸c˜ao de forma muito mais sucinta e compreens´ıvel, de modo que ao final desta primeira etapa, temos:

s t e n c i l = Eq ( u . f o r w a r d , s o l v e ( pde , u . f o r w a r d ) )

6.2

Tradu¸c˜ao da DSL com Sympy

Nesta etapa, almeja-se traduzir a representa¸c˜ao simb´olica do estˆencil obtido na etapa anterior em uma express˜ao com indexamento expl´ıcito. Pode-se dizer, ent˜ao, que deseja- se reproduzir parte do comportamento do objeto “Operator” do Devito, fazendo uso do SymPy.

Para executar essa tarefa, implementou-se uma classe chamada “Parser”, ou seja, um analisador sint´atico, que lˆe a representa¸c˜ao em alto n´ıvel da equa¸c˜ao e gera a representa¸c˜ao correspondente em baixo n´ıvel.

Nesta tradu¸c˜ao, as duas ferramentas indispens´aveis s˜ao o pr´oprio SymPy, j´a introdu- zido neste documento, e o Regex, ou express˜oes regulares.

Esta ferramenta, dispon´ıvel em in´umeras linguagens de programa¸c˜ao e at´e em busca- dores de palavras em editores de texto, permite que quaisquer sequˆencias de caracteres

desejadas sejam identificadas em uma outra sequˆencia de caracteres, normalmente uma frase, um par´agrafo, ou um texto inteiro. Essa ferramenta permite, por exemplo, identi- ficar todos os n´umeros em um texto, ou todas as palavras que come¸cam por certa letra, ou at´e mesmo por¸c˜oes inteiras que incluem determinado padr˜ao de caracteres.

Descrever minuciosamente como essa ferramenta funciona n˜ao conv´em aos objetivos deste documento. Por´em os padr˜oes de identifica¸c˜ao usados no analisador sint´atico ser˜ao devidamente descritos, assim como o motivo de seu uso.

A primeira coisa que o Parser faz ´e transformar o estˆencil em uma string, ou seja, uma cadeia de caracteres. No exemplo anterior, obt´em-se:

Eq ( u ( t + dt , x , y ) , 1 . 0 ∗ dt ∗∗2∗( −2.0∗ u ( t , x , y ) / h y ∗∗2 + u ( t , x , y − h y ) / h y ∗∗2 + u ( t , x , y + h y ) / h y ∗∗2 − 2 . 0 ∗ u ( t , x , y ) / h x ∗∗2 + u ( t , x − h x , y ) / h x ∗∗2 + u ( t , x + h x , y ) / h x ∗∗2 + 2 . 0 ∗ u ( t , x , y ) / dt ∗∗2 − 1 . 0 ∗ u ( t − dt , x , y ) / dt ∗ ∗ 2 ) )

Em seguida, deve-se identificar os s´ımbolos livres do estˆencil, isto ´e, aqueles s´ımbolos que deveriam, mas ainda n˜ao est˜ao, atrelados a uma vari´avel espec´ıfica que represente este s´ımbolo. No presente caso, os s´ımbolos livres s˜ao: ‘h y’, ‘x’, ‘y’, ‘dt’, ‘t’ e ‘h x’. O s´ımbolo ‘u’ n˜ao ´e uma vari´avel livre, pois ele j´a est´a vinculado a uma vari´avel. A lista de s´ımbolos ´e facilmente obtida com uma fun¸c˜ao do Devito origin´aria de uma fun¸c˜ao de mesmo nome do SymPy.

Dado esse preparo, ´e poss´ıvel atacar de fato a tradu¸c˜ao da express˜ao do Devito em uma express˜ao de indexamento expl´ıcito. E isso se faz, antes, com a tradu¸c˜ao do Devito em uma express˜ao puramente SymPy, sem as abrevia¸c˜oes sint´aticas que o Devito proporciona. A raz˜ao de se realizar esta opera¸c˜ao, ao inv´es de realizar uma tradu¸c˜ao direta, ´e que o SymPy, como j´a descrito, possui uma fun¸c˜ao que transforma a express˜ao simb´olica diretamente em c´odigo C. Em outras palavras, assim que se obtiver a express˜ao em alto n´ıvel em formato SymPy, ´e poss´ıvel gerar a express˜ao de baixo n´ıvel com uma ´unica chamada da fun¸c˜ao ‘ccode’.

A tradu¸c˜ao Devito-SymPy possui 3 partes:

1. Substituir as fun¸c˜oes

2. Substituir os s´ımbolos livres

3. Substituir os n´umeros decimais, ou floats

A primeira substitui¸c˜ao utiliza o padr˜ao Regex u\((.*?)\). Isso quer dizer que ele identificar´a todas as fun¸c˜oes u(...), n˜ao importa seu conte´udo. Al´em disso, cada fun¸c˜ao

identificada ser´a substitu´ıda pela string do seu equivalente SymPy, o objeto Element descrito anteriormente. Essa transforma¸c˜ao ´e necess´aria, pois a fun¸c˜ao ‘ccode’ n˜ao suporta o atual estado do estˆencil.

Deve-se adicionar que nesta substitui¸c˜ao ´e realizada a solu¸c˜ao de um problema que, se n˜ao feita aqui, exigiria mais processamento p´ostumo.

Do jeito que o Regex est´a formulado, busca-se a fun¸c˜ao completa, mas na hora da substitui¸c˜ao pode-se facilmente trabalhar ou com a fun¸c˜ao como um todo, ou com seus parˆametros. Isso ´e necess´ario para se declarar o elemento como “Element(’u’, parame- ters.split(’, ’)), o que nos devolve, por exemplo, “Element(u, indices=(t, h x + x, y))”.

Acontece que os termos ‘h x’, ‘h y’, e ‘dt’, enquanto ´ındices da fun¸c˜ao, fazem parte da equa¸c˜ao apenas em sua express˜ao de alto n´ıvel. Por outro lado, durante o indexamento expl´ıcito, deseja-se poder acessar as posi¸c˜oes de um vetor multidimensional uma a uma.

Quando a fun¸c˜ao foi criada com base em uma grade, j´a se deixou impl´ıcito que cada ponto no espa¸co seria separado por uma distˆancia de h x e h y em cada eixo. Portanto, ao se transformar a equa¸c˜ao em um vetor multidimensional, um incremento de uma unidade no ´ındice de cada dimens˜ao representaria exatamente esse passo, representado por esses s´ımbolos. Em outras palavras, em vez de se usar u[t][x + h x][y], por exemplo, usa-se u[t][x + 1][y].

Assim, todos os s´ımbolos que representam um passo em alguma dimens˜ao devem ser transformados no n´umero 1. Para o exemplo, obt´em-se:

Eq ( Element ( u , i n d i c e s =( t + 1 , x , y ) ) , 1 . 0 ∗ dt ∗ ∗ 2 ∗ ( −2.0∗ Element ( u , i n d i c e s =(t , x , y ) ) / h y ∗∗2 + Element ( u , i n d i c e s =(t , x , y − 1 ) ) / h y ∗∗2 + Element ( u , i n d i c e s =(t , x , y + 1 ) ) / h y ∗∗2 − 2 . 0 ∗ Element ( u , i n d i c e s =(t , x , y ) ) / h x ∗∗2 + Element ( u , i n d i c e s =(t , x − 1 , y ) ) / h x ∗∗2 + Element ( u , i n d i c e s =(t , x + 1 , y ) ) / h x ∗∗2 + 2 . 0 ∗ Element ( u , i n d i c e s =(t , x , y ) ) / dt ∗∗2 − 1 . 0 ∗ Element ( u , i n d i c e s =( t − 1 , x , y ) ) / dt ∗ ∗ 2 ) )

A segunda substitui¸c˜ao utiliza o padr˜ao Regex “\w+”. Isso quer dizer que ele identi- ficar´a todas as palavras compostas somente pelas letras do alfabeto. Aqui ´e que entram os s´ımbolos livres identificados anteriormente, pois verifica-se se cada palavra est´a entre os s´ımbolos e, em caso positivo, a palavra identificada ser´a substitu´ıda pela string do seu equivalente SymPy, o objeto Symbol descrito anteriormente.

quaisquer vari´aveis, nˆao se pode avaliar a string em uma express˜ao que possa ser convertida em c´odigo C pela fun¸c˜ao “ccode”.

Ao fim desta opera¸c˜ao, obt´em-se:

Eq (

Element ( u , i n d i c e s =(Symbol ( ’ t ’ ) + 1 , Symbol ( ’ x ’ ) , Symbol ( ’ y ’ ) ) ) , 1 . 0 ∗ Symbol ( ’ dt ’ ) ∗ ∗ 2 ∗ (

−2.0∗ Element ( u , i n d i c e s=

( Symbol ( ’ t ’ ) , Symbol ( ’ x ’ ) , Symbol ( ’ y ’ ) ) ) / Symbol ( ’ h y ’ ) ∗ ∗ 2

+ Element ( u , i n d i c e s=

( Symbol ( ’ t ’ ) , Symbol ( ’ x ’ ) , Symbol ( ’ y ’ ) − 1 ) ) / Symbol ( ’ h y ’ ) ∗ ∗ 2

+ Element ( u , i n d i c e s=

( Symbol ( ’ t ’ ) , Symbol ( ’ x ’ ) , Symbol ( ’ y ’ ) + 1 ) ) / Symbol ( ’ h y ’ ) ∗ ∗ 2

− 2 . 0 ∗ Element ( u , i n d i c e s=

( Symbol ( ’ t ’ ) , Symbol ( ’ x ’ ) , Symbol ( ’ y ’ ) ) ) / Symbol ( ’ h x ’ ) ∗ ∗ 2

+ Element ( u , i n d i c e s=

( Symbol ( ’ t ’ ) , Symbol ( ’ x ’ ) − 1 , Symbol ( ’ y ’ ) ) / Symbol ( ’ h x ’ ) ∗ ∗ 2

+ Element ( u , i n d i c e s=

( Symbol ( ’ t ’ ) , Symbol ( ’ x ’ ) + 1 , Symbol ( ’ y ’ ) ) ) / Symbol ( ’ h x ’ ) ∗ ∗ 2

+ 2 . 0 ∗ Element ( u , i n d i c e s=

( Symbol ( ’ t ’ ) , Symbol ( ’ x ’ ) , Symbol ( ’ y ’ ) ) ) / Symbol ( ’ dt ’ ) ∗ ∗ 2

− 1 . 0 ∗ Element ( u , i n d i c e s=

( Symbol ( ’ t ’ ) − 1 , Symbol ( ’ x ’ ) , Symbol ( ’ y ’ ) ) / Symbol ( ’ dt ’ ) ∗ ∗ 2 ) )

A terceira, e ´ultima, substitui¸c˜ao utiliza o padr˜ao Regex “\d+\.\d+”. Isso que dizer que ele identificar´a todos os n´umeros decimais, ou seja, aqueles separados por um ponto. Al´em disso, cada n´umero identificado ser´a substitu´ıdo pela string do seu equivalente SymPy, o objeto Float, que representa, naturalmente, o tipo “float” de n´umeros decimais. Esta ´ultima substitui¸c˜ao pode parecer sup´erflua, por´em sem ela o c´odigo n˜ao funciona

dado que n˜ao se pode avaliar a string em uma express˜ao quando ela apresenta opera¸c˜oes aritm´eticas entre Elements e n´umeros literais. Da´ı a necessidade de transformar o n´umero multiplicador de cada Element em um Float espec´ıfico do SymPy.

O resultado desta opera¸c˜ao ´e quase idˆentico ao anterior, com exce¸c˜ao dos Floats, portanto ele n˜ao ser´a mostrado. Ainda, o retorno final do analisador sint´atico ´e esse mesmo resultado, por´em avaliado, de forma que ele deixe de ser uma string e de fato se torna uma express˜ao que combina objetos do SymPy.

Finalmente, para se obter a t˜ao necess´aria express˜ao com indexamento expl´ıcito, deve- se chamar a fun¸c˜ao “ccode”, que retorna:

u [ t + 1 ] [ x ] [ y ] = 1 . 0 ∗ pow ( dt , 2 ) ∗ ( −2.0∗u [ t ] [ x ] [ y ] / pow ( h y , 2 ) + u [ t ] [ x ] [ y − 1 ] / pow ( h y , 2 ) + u [ t ] [ x ] [ y + 1 ] / pow ( h y , 2 ) − 2 . 0 ∗ u [ t ] [ x ] [ y ] / pow ( h x , 2 ) + u [ t ] [ x − 1 ] [ y ] / pow ( h x , 2 ) + u [ t ] [ x + 1 ] [ y ] / pow ( h x , 2 ) + 2 . 0 ∗ u [ t ] [ x ] [ y ] / pow ( dt , 2 ) − 1 . 0 ∗ u [ t − 1 ] [ x ] [ y ] / pow ( dt , 2 ) ) ;

6.3

Gera¸c˜ao de C´odigo: Template

Esta etapa, apesar de em grande parte representar apenas uma transi¸c˜ao para um modelo subsequente mais aprimorado, ´e necess´aria, pois ela permite avaliar se o estˆencil representado por indexamento expl´ıcito e alguns outros dados do problema s˜ao suficientes para produzir um c´odigo C compil´avel. Al´em disso, esta etapa ´e necess´aria para dar forma ao template gen´erico que ser´a completado com as informa¸c˜oes que solucionam o problema, o que tamb´em ser´a feito de maneira mais aprofundada e espec´ıfica caso a caso na etapa seguinte.

Para descobrir quais dados deveriam ser enviados ao template e como o template deveria ser constru´ıdo, foi gerado um c´odigo atrav´es do pr´oprio Devito. Para o caso de uso com que se implementou o c´odigo e se elaborou os exemplos anteriores, temos: #include ” s t d l i b . h”

#include ”math . h”

s t r uc t d a t a o b j {

void ∗ r e s t r i c t d a t a ; i n t ∗ s i z e ;

} ;

i n t K e r n e l ( const f l o a t dt , const f l o a t h x , const f l o a t h y , s t r uc t d a t a o b j ∗ r e s t r i c t u v e c , const i n t time M , const i n t time m , const i n t x M , const i n t x m , const i n t y M , const i n t y m ) { f l o a t ( ∗ r e s t r i c t u ) [ u v e c −>s i z e [ 1 ] ] [ u v e c −>s i z e [ 2 ] ] = ( f l o a t ( ∗ ) [ u v e c −>s i z e [ 1 ] ] [ u v e c −>s i z e [ 2 ] ] ) u v e c −>d a t a ; f o r ( i n t t i m e = time m , t 0 = ( t i m e + 2 ) % ( 3 ) , t 1 = ( t i m e ) % ( 3 ) , t 2 = ( t i m e + 1 ) % ( 3 ) ; t i m e <= time M ; t i m e += 1 , t 0 = ( t i m e + 2 ) % ( 3 ) , t 1 = ( t i m e ) % ( 3 ) , t 2 = ( t i m e + 1 ) % ( 3 ) ) { f o r ( i n t x = x m ; x <= x M ; x += 1 ) { f o r ( i n t y = y m ; y <= y M ; y += 1 ) { f l o a t r 6 = −2.0F∗u [ t 1 ] [ x + 2 ] [ y + 2 ] ; f l o a t r 5 = dt ∗ dt ; u [ t 2 ] [ x + 2 ] [ y + 2 ] = 1 . 0 F∗ r 5 ∗ ( ( r 6 + u [ t 1 ] [ x + 2 ] [ y + 1 ] + u [ t 1 ] [ x + 2 ] [ y + 3 ] ) / ( ( h y ∗ h y ) ) + ( r 6 + u [ t 1 ] [ x + 1 ] [ y + 2 ] + u [ t 1 ] [ x + 3 ] [ y + 2 ] ) / ( ( h x ∗ h x ) ) + ( −1.0F∗u [ t 0 ] [ x + 2 ] [ y + 2 ] + 2 . 0 F∗u [ t 1 ] [ x + 2 ] [ y + 2 ] ) / r 5 ) ; } } } return 0 ; }

esperado, pois a fun¸c˜ao gerada ´e o algoritmo b´asico de solu¸c˜ao pelo m´etodo de diferen¸cas finitas, iterando sobre todas as dimens˜oes do problema (t, x, y), e no centro h´a o estˆencil com indexamento expl´ıcito. Existem, por´em, alguns pontos que trouxeram clareza sobre certos aspectos do c´odigo a ser gerado.

O primeiro deles ´e sobre a estrutura de dados que cont´em os valores de cada ponto na grade do problema e em passos de tempo diferente. Aqui, define-se o tipo “dataobj”, que cont´em tanto esses valores (data), como o tamanho de cada dimens˜ao do problema (size). Al´em disso, a fun¸c˜ao de resolu¸c˜ao come¸ca atribuindo tais valores para um vetor. O segundo e terceiro pontos dizem respeito ao estˆencil que, como evidente no c´odigo acima, ´e ligeiramente diferente daquele mostrado no fim da se¸c˜ao anterior.

A primeira diferen¸ca que se percebe ´e que h´a um deslocamento no indexamento de x e y de duas unidades. Isso, por´em, n˜ao constitui um algoritmo diferente; ´e exatamente o mesmo algoritmo e produz o mesmo resultado, mas os valores de in´ıcio e fim do ciclo de itera¸c˜oes devem ser ajustados de acordo com o indexamento utilizado. No estˆencil gerado por este projeto, por exemplo, a posi¸c˜ao [x - 1] ´e acessada, portanto ´e preciso come¸car as itera¸c˜oes com x = 1 e n˜ao x = 0, o que garante que n˜ao sejam acessadas posi¸c˜oes de mem´oria fora do alcance do vetor.

A segunda diferen¸ca tamb´em se d´a no n´ıvel do indexamento, por´em na dimens˜ao do tempo. Aqui, deve-se considerar a mesma quest˜ao do deslocamento anterior, tomando o devido cuidado com os limites de itera¸c˜ao dado o indexamento usado. Por´em, h´a um novo artif´ıcio: aplica-se o m´odulo de 3 em todos os ´ındices usados. A raz˜ao disto ´e para economizar espa¸co de mem´oria, constituindo uma pequena otimiza¸c˜ao, e seu motivo e princ´ıpio de funcionamento s˜ao bem simples. Dado que para calcular o ponto no tempo seguinte [t + 1] s˜ao usados o ponto atual [t] e o ponto passado [t - 1], ´e sempre necess´ario acessar apenas 3 pontos para efetuar o c´alculo. N˜ao h´a sentido, portanto, em manter na mem´oria os valores de itera¸c˜oes passadas na dimens˜ao do tempo. Caso se tenha 3000 passos no tempo, por exemplo, deve-se alocar 1000 vezes mais espa¸co na mem´oria do que com a otimiza¸c˜ao. Usando o m´odulo de 3, garante-se que o vetor seja reutilizado de modo a sempre conter apenas os 3 pontos necess´arios para efetuar o c´alculo correto.

J´a com rela¸c˜ao aos dados que devem ser enviados ao template, tamb´em n˜ao h´a nada fora do esperado. Deve-se enviar o estˆencil, os valores dos pontos, os passos em cada dimens˜ao (dt, h x, h y), e os valores m´aximo e m´ınimo dos ´ındices de itera¸c˜ao em cada dimens˜ao (time M, time m, x M, x m, y M, y m). Todos esses parˆametros s˜ao de f´acil obten¸c˜ao no c´odigo Python, o que ´e feito por uma classe criada chamada “Renderer”que, logicamente, se dedica `a renderiza¸c˜ao do template.

a fun¸c˜ao u, todos os pontos s˜ao inicializados com o valor 0. Ent˜ao, para se trabalhar com algum problema de real significado ´e preciso dar uma condi¸c˜ao inicial ao problema, o que n˜ao ´e feito pelo programa dado que o usu´ario que deve inicializar sua fun¸c˜ao com os dados desejados. Na se¸c˜ao dedicada aos testes, ser´a mostrado o meio pelo qual se inicializou o exemplo descrito ao longo deste documento.

Finalmente, resta acrescentar que o template deve conter uma fun¸c˜ao main para que o algoritmo possa ser testado. Uma discuss˜ao sobre os testes tem uma se¸c˜ao apropriada, mas a main do template deve ser mostrada aqui.

i n t main ( ) { i n t i = {{ params . u s h a p e [ 0 ] } } ; i n t j = {{ params . u s h a p e [ 1 ] } } ; i n t k = {{ params . u s h a p e [ 2 ] } } ; i n t s i z e [ 3 ] = { i , j , k } ; d o u b l e myarray [ { { params . u s h a p e [ 0 ] } } ] [ { { params . u s h a p e [ 1 ] } } ] [ { { params . u s h a p e [ 2 ] } } ] = {{ params . u d a t a } } ; s t r u c t d a t a o b j u v e c = { . d a t a = myarray , . s i z e = s i z e } ;

K e r n e l ( { { params . dt } } , {{ params . s t e p x } } , {{ params . s t e p y } } , &u v e c , {{ params . max time } } , {{ params . m i n t i m e } } ,

{{ params . max x } } , {{ params . min x } } , {{ params . max y } } , {{ params . min y } } ) ;

r e t u r n 0 ; }

Como j´a mencionado, o motor de template Jinja2 foi utilizado. Os elementos no formato {{...}} s˜ao justamente os pontos que s˜ao substitu´ıdos pelos dados vindos do c´odigo Python. E, com rela¸c˜ao ao que a main executa, basicamente cria-se o objeto de tipo “dataobj”e se faz uma chamada para a fun¸c˜ao do algoritmo com os devidos parˆametros.

O resto do template n˜ao ser´a exibido pois seria uma repeti¸c˜ao quase linha por linha do c´odigo criado pelo Devito, com a diferen¸ca de que no lugar do estˆencil ter´ıamos {{code}}, ou seja, seria substitu´ıdo o estˆencil do Devito pelo estˆencil que foi criado pelo programa

desenvolvido.

Documentos relacionados