• Nenhum resultado encontrado

As funções adaptativas no programa são definidas por uma lista de dois elementos. O primeiro é o nome da função, um símbolo único que identifica a função. O segundo é um bloco de código, que representa o comportamento da função adaptativa e será executado quando a função adaptativa for invocada.

O bloco de código que representa o comportamento da função adaptativa é uma função lambda que deve receber sempre dois parâmetros: o dispositivo sobre o qual ele é executado e a lista de parâmetros da função adaptativa.

Por ser uma função lambda, é possível fazer qualquer coisa no comportamento da função adaptativa, porém, recomenda-se que apenas as ações primitivas das funções adaptativas sejam executadas nelas.

As funções search-transition-*, insert-transition! e remove-transition! representam as ações elementares de busca, inserção e

remoção de transições. Além delas, existe a função generate-state!, que representa a ação elementar de criação de estados. Ela recebe como parâmetro o dispositivo e retorna o estado criado nele.

make-adaptive-function é a função que cria as funções adaptativas do dispositivo. Ela recebe sempre dois parâmetros: o nome da função e seu bloco de código (expressão lambda).

A execução de uma função adaptativa é feita através da função exec-adaptive- function, que será invocada toda vez que uma transição tenha uma função

adaptativa não nula. Essa função irá invocar o bloco de código através do nome da função adaptativa, passando sempre dois parâmetros: o autômato adaptativo e a lista de parâmetros da função adaptativa, definida na transição. Por este motivo, o bloco de código da função adaptativa deverá sempre ser uma expressão lambda com dois parâmetros.

6.3 Definição do autômato

Uma das preocupações no desenvolvimento do framework foi manter o programa o mais próximo das definições formais do autômato. Porém, por facilidade de implementação e entendimento, algumas definições foram simplificadas.

Como já mostrado, a representação formal desse modelo se dá por = , Σ, , , , , Γ . Já no framework, este dispositivo é definido por uma lista de 8 elementos, sendo:

• 'adaptive-automaton – uma constante que identifica a lista como sendo a definição de um autômato adaptativo;

• C - a lista inicial de estados; • Σ - a lista de símbolos;

• TA - a lista inicial de transições; • c0 - estado inicial;

• F - a lista de estados finais;

• AF - a lista de funções adaptativos; e

• Dicionário para a lista de estados – uma lista de pares ordenados que traduzem o nome dos estados para um único caractere (uso interno do framework).

Assim, para a criação de um autômato, é preciso defini-lo através desta lista e, para tal, foi criada a make-finite-state-automaton, função a qual recebe 6 parâmetros, que vão definir os itens 2 a 7 dessa lista, e retorna o dispositivo criado.

6.4 Fluxo de execução

Com a finalidade de tornar o desenvolvimento de novos modelos de autômatos uma tarefa simples, pensou-se em criar um framework de execução do autômato, que serviria de base para a criação de qualquer tipo de autômato (reconhecedor de linguagem ou transdutor). Com isso em mente, foi desenvolvida a função execute- transition, que nada mais é que o dispositivo de transição do autômato de um estado para outro. Ela é encarregada de verificar se há funções adaptativas e executá- las. Além disso, é nesta função que será feita a paralelização da execução das transições.

A função execute-transition, recebe 4 argumentos: a próxima transição a ser executada, o dispositivo, a cadeia de entrada e um flag de paralelismo. Essa função irá fazer a transição, executando as funções adaptativas que houver, alterando, assim, o autômato recebido por parâmetro. Se o flag de paralelismo estiver ligado, toda essa execução acontecerá numa thread nova. O retorno dessa função deve ser o autômato após a transição concluída (alterado por uma função adaptativa ou não).

A função que define o comportamento do dispositivo é automaton-move. Nesta função são definidos os critérios de parada, a forma de execução das transições (em paralelo ou seqüencialmente), a leitura da cadeia de entrada, etc. Ela recebe três parâmetros: o dispositivo adaptativo, sua cadeia de entrada e o estado corrente do autômato subjacente.

As funções automaton-move e execute-transition são recursivas entre si, isto é, uma faz chamada para outra. Essas funções foram feitas dessa maneira para simplificar a diferenciação entre a execução seqüencial e a paralela no framework. Dessa maneira, a única diferença entre seqüencial e o paralelo é a sua função principal automaton-move.

6.5 Exemplo

A seguir é representado o autômato finito adaptativo utilizado para resolver o problema do caixeiro viajante.

O caixeiro viajante é um problema clássico na computação e na matemática. Neste problema, um viajante deve visitar um número @ de cidades, passando por cada uma delas apenas uma vez, e retornar a cidade de onde partiu, fazendo o percurso menos custoso, ou seja, percorrer a menor distância possível. Sua importância se dá pelo fato de ser um problema de resolução muito complexa, com alto custo computacional (SCHRIJVER, 1991).

Muitos trabalhos apresentam diferentes maneiras de resolver este problema, utilizando algoritmos de solução exata ou usando algum tipo de heurística para uma solução aproximada, mas de menor custo computacional. Neste trabalho, optou-se por um autômato finito adaptativo na busca de solução exata. Desta maneira, será mostrada uma versão do autômato que percorrerá todas as possíveis rotas do viajante e calculará a menor distância a ser percorrida.

Dado um conjunto de cidade ` = e !, #, , f, com matriz de distâncias

•c= “ 0 3 3 0 4 46 2 4 6 4 2 0 88 0 –,

constrói-se o autômato finito adaptativo tal que cada estado do autômato representa uma cidade e as transições representam o caminho a ser percorrido pelo caixeiro viajante. Desta maneira, define-se, também, que os símbolos de entrada do autômato são as distâncias entre cada cidade. Assim, cada transição executada pelo autômato representa a passagem do viajante de uma cidade para outra. O autômato finito adaptativo construído para resolver tal problema acima é representado na figura 6.1.

Figura 6.1 – autômato que resolve o problema do caixeiro viajante, em sua configuração inicial

Este autômato irá reconhecer toda cadeia de entrada que se inicia em !, passa por todas as cidades apenas uma vez, e termina em . A transição dos estados #, e para simula o retorno a !. Para isso, definimos as funções adaptativas posteriores

am _ [jbℎa , [ m _ jŽ Ž^_ jb$jŽ e mj _ [jbℎa_Ž^_‚aF , as quais farão com que o caixeiro não passe pela mesma cidade mais de uma vez ( am _ [jbℎa ) e não o deixará retornar a cidade de origem sem antes passar por todas as cidades ([ m _ jŽ Ž^_ jb$jŽ e mj _ [jbℎa_Ž^_‚aF ). Essas funções são representadas na figura 6.2.

Assim, para encontrar o menor caminho a ser percorrido pelo viajante, basta abastecer o modelo com todas as possíveis combinações de distâncias na cadeia de entrada do autômato. Toda vez que o dispositivo reconhecer uma cadeia de entrada, guarda-se o modelo e a soma das distâncias percorridas (soma dos símbolos da cadeia de entrada). O modelo que tiver a menor das somas será a resposta para o problema.

A seguir, será mostrado como implementar o autômato finito adaptativo que resolve o problema descrito acima.

Figura 6.2 – Definição das funções adaptativas posteriores

6.5.1 Implementação

O framework para desenvolvimento e execução dos autômatos adaptativos, resultado deste trabalho, define o autômato finito adaptativo em duas fases: 1) a criação do autômato, definindo cada uma de suas partes separadamente, e 2) a lógica de

corta_caminhos() : {

; remove todos os caminhos que chegam a %2, ; a não ser os que saem de c1 e %1 (corrente)

? [ 1 ?p %2 f( %1 %2 ) ] ? [ %1 ?q %2 f( %1 %2 ) ] ? [ ?x ?y %2 f( %1 %2 ) ] - [ ?x ?y %2 f( %1 %2 ) ] + [ 1 ?p %2 f( %1 %2 ) ] + [ %1 ?q %2 f( %1 %2 ) ] } marca_cidade_atingida() : { + [ 0 m %1 ] } cria_caminho_de_volta() : {

; cria o caminho do estado %1 para 0, se todas as ; cidades já foram alcançadas

? [ ?x m 2 ] ? [ ?x m 3 ] ? [ ?x m 4 ] ? [ 1 ?y %1 corta_caminhos( %1 ) ] + [ %1 ?y ?x ] } f() : {

; Parâmetros: estados inicial e final da transição corta_caminhos( %1 %2 )

marca_cidade_atingida( %2 ) cria_caminho_de_volta ( %2 ) }

execução, onde é necessário definir também se sua computação se dará seqüencialmente ou em paralelo.

6.5.2 Criação

A criação de um autômato finito adaptativo é feita por passos. Primeiramente, é necessário definir o comportamento das funções adaptativas am _ [jbℎa , mj _ [jbℎa _Ž^_‚aF , [ m _ jŽ Ž^_ jb$jŽ e " . Para isso, implementa-se uma função (expressão lambda) para cada uma das funções adaptativas. As figuras 6.3, 6.4, 6.5 e 6.6 mostra como cada uma dessas funções é definida no Scheme.

Como mostrado no capítulo anterior, essas funções são definidas para receberem dois parâmetros: 1) o autômato finito adaptativo que sofrerá as alterações da ação adaptativa e 2) a lista de parâmetros da função adaptativa. Além disso, diferentemente da notação formal, as variáveis destas funções são definidas explicitamente.

É importante notar nessas funções as diferenças delas para a notação formal. No fundo, essas funções transcrevem o comportamento das funções adaptativas, ao invés de transcreverem as próprias. Neste exemplo, as funções cut-paths, create- return-path e mark-reached-city transcrevem o comportamento das funções

adaptativas am _ [jbℎa , mj _ [jbℎa _Ž^_‚aF e

[ m _ jŽ Ž^_ jb$jŽ respectivamente.

O melhor exemplo dessa diferença entre as funções adaptativas e as funções no

Scheme é a função cut-paths. Nesta função, o laço de remoção de transições é

uma função recursiva que remove as transições uma a uma. Além disso, nessa função são removidas todas as transições que chegam ao estado final da transição, a não ser a transição corrente e a transição que parte de 1 pare este estado. Já na função adaptativa, são removidas todas as transições que chegam a este estado e depois adicionado de volta a transição corrente e a transição de ! para este.

Uma vez definidos esses comportamentos, é preciso defini as funções adaptativas de tal forma que o autômato finito adaptativo poderá executá-los. Para tal, utilizamos a função make-adaptive-function, que recebe como parâmetros o nome da função adaptativa e a função Scheme que define seu comportamento. Dessa maneira, é possível executar uma ação adaptativa através da função exec-adaptive- function, que recebe o nome da função adaptativa e executa a função ligada a ela. A função make-automaton-transition é a função que cria as transições do autômato finito adaptativo. Ela recebe como parâmetros os componentes que definem a transição, na seguinte ordem: estado inicial, símbolo, estado final, chamada da função adaptativa anterior, chamada da função adaptativa posterior, parâmetros a ser passado para a função adaptativa anterior, parâmetros para a função adaptativa posterior.

Uma vez criadas essas estruturas, podemos definir o autômato finito adaptativo utilizando a função make-automaton-transition. A criação do autômato que

Figura 6.3 – Definição do comportamento da função adaptativa am _ [jbℎa

Figura 6.4 – Definição do comportamento da função adaptativa [ m _ jŽ Ž^_ jb$jŽ

resolve o problema do caixeiro viajante proposto aqui, e de suas estruturas, é mostrado na figura 6.7.

Figura 6.5 – Definição do comportamento da função adaptativa mj _ [jbℎa_Ž^_‚aF

Figura 6.6 – Definição do comportamento da função adaptativa "

Figura 6.7 – Criação do autômato finito adaptativo que resolve o problema do caixeiro viajante proposto

6.5.3 Definindo a execução

Para execução do autômato, é preciso implementar a função de execução e aceitação deste: automaton-move. Esta função é responsável por disparar todo o mecanismo de execução do autômato: ler a cadeia de entrada, fazer as transições e definir as condições de parada do autômato. Nela, também, defini-se se o autômato irá executar em paralelo ou seqüencialmente.

A interface da função automaton-move é definida por 3 parâmetros: o autômato adaptativo, a cadeia de entrada e o estado atual do autômato, sendo que, quando chamada pela primeira vez, deve receber o estado inicial do autômato.

Para o caso do autômato finito adaptativo que resolve o problema proposto, iremos definir a função automaton-move de forma diferente da habitual. Neste exemplo, esta função não irá ler uma cadeia de entrada, mas sim disparar todas as transições a partir do estado corrente. Além disso, os critérios de parada não se basearão na cadeia de entrada do autômato, mas sim em sua configuração atual: caso o estado corrente do autômato seja seu estado de aceitação e todos os outros estados já tenham sido atingidos, o autômato pára sua execução e calcula a distância percorrida pelo caixeiro viajante. Dessa forma, todos os caminhos possíveis que o caixeiro viajante poderá percorrer serão cobertos.

Duas versões deste método foram implementadas para este exemplo: uma delas executando todas as transições a partir do estado corrente em paralelo, outra executando o autômato seqüencialmente, utilizando o mecanismo de backtracking. Essas funções são mostradas nas figuras 6.8 (versão em paralelo) e 6.9 (versão seqüencial).

Figura 6.9 – Função de execução seqüencial do autômato finito adaptativo e definição da função automaton-move

6.5.4 Execução e análise

Nesta seção, será mostrado como o autômato finito adaptativo se comporta durante a execução da função de busca da solução do problema do caixeiro viajante apresentada na figura 6.9.

Decidiu-se por mostrar a solução utilizando o algoritmo seqüencial por questões e entendimento, uma vez que a execução do algoritmo paralelo não apresenta uma seqüência natural desta resolução, trilhando vários caminhos de uma só vez. Além disso, decidiu-se por mostrar um dos possíveis caminhos completos que o caixeiro viajante pode percorrer, saindo da ! (estado 1 do autômato finito adaptativo), passando por todas as outras 3 cidades, e chegando à cidade destino ! (estado 0 – que simula a volta ao estado 1).

O autômato finito adaptativo da figura 1 é representado, no programa, como a figura 7. Essa figura mostra o autômato, representado por uma lista de 7 elementos – sendo eles: 1) o identificador do autômato, 2) a lista de estados, 3) a lista símbolos de entrada, 4) a lista de transições, 5) o estado inicial do autômato, 6) a lista de estados de aceitação e 7) as funções adaptativas – e o estado em que se encontra o autômato.

Figura 6.10 – Representação do autômato finito adaptativo que resolve o problema do caixeiro em sua configuração inicial

Isto é, o autômato, definido pela lista de 8 elementos, contém os estados 0, 1, 2, 3 e 4, uma lista de símbolos composta pelas distâncias entre as cidades e a marcação m, a lista de transições, estado inicial 1, o conjunto de estados de aceitação {0} e o conjunto de funções adaptativas.

Diferentemente do modelo formal, este autômato finito adaptativo não fará leitura de uma cadeia de entrada. Isto porque o modelo implementado visa percorrer todos os possíveis caminhos para chegar ao seu destino. Desta forma, o comportamento do autômato será de percorrer todas as transições a partir do estado corrente. Além disso, o critério de parada do autômato também é diferente do formal. Ele irá se basear apenas no conjunto de estados de aceitação para decidir se deve para a execução.

Assim, a execução da função automaton-move irá procurar todas as transições que partem de 1, colocá-las numa lista, ordenando por estado final da transição, e executará a primeira delas. Portanto, ao iniciar a execução, a primeira transição a ser

automato: 'adaptive-finite-state-automaton (0 1 2 3 4) ('2 '3 '4 '6 '8 'm) #((1 '3 2 null-adaptive-function f () (1 2)) (1 '4 3 null-adaptive-function f () (1 3)) (1 '4 4 null-adaptive-function f () (1 4)) (2 '6 3 null-adaptive-function f () (2 3)) (3 '6 2 null-adaptive-function f () (3 2)) (2 '2 4 null-adaptive-function f () (2 4)) (4 '2 2 null-adaptive-function f () (4 2)) (3 '8 4 null-adaptive-function f () (3 4)) (4 '8 3 null-adaptive-function f () (4 3))) 1 (0) ((corta-caminhos #<procedure:cut-paths>) (marca-cidade-atingida #<procedure:mark-reached-city>) (cria-caminho-de-volta #<procedure:create-return-path>) (f #<procedure:adaptive-function>)) estado corrente: 1

executada será 1, ′4 → 2, " 1,2 , e a configuração resultante dessa transição é o autômato representado na figura 11.

Figura 6.11 – Autômato finito adaptativo após execução da transição 1, ′4 → 2, " 1,2

Consecutivamente, o autômato irá alterar sua configuração, executando as transições e as funções adaptativas, até atingir uma configuração de aceitação do autômato. As próximas configurações do autômato em questão são mostradas na figura 12.

automato: 'adaptive-finite-state-automaton (0 1 2 3 4) ('2 '3 '4 '6 '8 'm) #((1 '3 2 null-adaptive-function f () (1 2)) (1 '4 3 null-adaptive-function f () (1 3)) (1 '4 4 null-adaptive-function f () (1 4)) (2 '6 3 null-adaptive-function f () (2 3)) (2 '2 4 null-adaptive-function f () (2 4)) (3 '8 4 null-adaptive-function f () (3 4)) (4 '8 3 null-adaptive-function f () (4 3)) (0 'm 2 null-adaptive-function null-adaptive-function () ())) 1 (0) ((corta-caminhos #<procedure:cut-paths>) (marca-cidade-atingida #<procedure:mark-reached-city>) (cria-caminho-de-volta #<procedure:create-return-path>) (f #<procedure:adaptive-function>)) estado corrente: 2

Figura 6.12 – Configurações atingidas pelo autômato finito adaptativo automato: 'adaptive-finite-state-automaton (0 1 2 3 4) ('2 '3 '4 '6 '8 'm) #((1 '3 2 null-adaptive-function f () (1 2)) (1 '4 3 null-adaptive-function f () (1 3)) (1 '4 4 null-adaptive-function f () (1 4)) (2 '6 3 null-adaptive-function f () (2 3)) (2 '2 4 null-adaptive-function f () (2 4)) (3 '8 4 null-adaptive-function f () (3 4)) (0 'm 2 null-adaptive-function null-adaptive-function () ()) (0 'm 3 null-adaptive-function null-adaptive-function () ())) 1 (0) ((corta-caminhos #<procedure:cut-paths>) (marca-cidade-atingida #<procedure:mark-reached-city>) (cria-caminho-de-volta #<procedure:create-return-path>) (f #<procedure:adaptive-function>)) estado corrente: 4 automato: 'adaptive-finite-state-automaton (0 1 2 3 4) ('2 '3 '4 '6 '8 'm) #((1 '3 2 null-adaptive-function f () (1 2)) (1 '4 3 null-adaptive-function f () (1 3)) (1 '4 4 null-adaptive-function f () (1 4)) (2 '6 3 null-adaptive-function f () (2 3)) (3 '8 4 null-adaptive-function f () (3 4)) (0 'm 2 null-adaptive-function null-adaptive-function () ()) (0 'm 3 null-adaptive-function null-adaptive-function () ()) (0 'm 4 null-adaptive-function null-adaptive-function () ()) (4 '4 0 null-adaptive-function null-adaptive-function () ())) 1 (0) ((corta-caminhos #<procedure:cut-paths>) (marca-cidade-atingida #<procedure:mark-reached-city>) (cria-caminho-de-volta #<procedure:create-return-path>) (f #<procedure:adaptive-function>)) estado corrente: 0

7 Considerações finais

Os primeiros experimentos foram desenvolvidos para avaliar sem provar formalmente a corretude do programa. Para tal, foram criados alguns autômatos finitos adaptativos reconhecedores: autômatos com funções adaptativas anteriores, com funções posteriores, autômatos com funções anteriores e posteriores e autômatos sem funções adaptativas. Ainda, para cada autômato, escolheram-se dois tipos de cadeias de entrada diferentes: uma que o autômato deveria aceitar, outra que deveria rejeitar. Além disso, cada teste foi executado numa máquina seqüencial e em uma máquina paralela. Com isso, tentou-se cobrir todos os casos de teste necessários para garantir que o programa funciona corretamente.

Os experimentos com o algoritmo foram realizados em uma máquina seqüencial e outra paralela. Procurou-se avaliar o desempenho do algoritmo, comparando seu tempo de execução ao de um algoritmo seqüencial que implementa backtracking usando recursividade. Assim, foi desenvolvido, também, um modelo seqüencial dos autômatos adaptativos em Scheme.

Além da comparação básica com o modelo seqüencial de autômatos adaptativos, foi implementada uma solução para o problema do caixeiro viajante a fim de comparação dos resultados e desempenho de diferentes algoritmos com os autômatos adaptativos paralelos. Para comparação, foram desenvolvidas 3 diferentes técnicas para solução do problema: 1) usando força bruta com autômatos adaptativos paralelos; 2) usando força bruta com autômatos adaptativos seriais (modelo clássico); e 3) usando autômatos genéticos. Cada técnica utilizada foi executada em duas diferentes máquinas: um sistema com Athlon X2 64bits, com 1 Gb de memória e com sistema operacional Windows XP, e um cluster de 8 processadores Intel com sistema operacional Linux Ubuntu.

Por ser um problema de difícil resolução, usou-se o problema do caixeiro viajante como teste de desempenho dos autômatos adaptativos, no qual, partindo de uma cidade x, o caixeiro deve visitar todas as n cidades e voltar para a cidade x percorrendo a menor distância possível.

Além de ser um problema com solução de alta complexidade (complexidade exponencial), sua representação gráfica é compreensível. Por estes motivos, o problema foi escolhido como forma de mostrar o funcionamento e viabilidade do uso do framework desenvolvido neste trabalho. Além disso, propões-se comparar os resultados deste trabalho e de algoritmos já existentes a fim de comparação de eficiência.

Como parâmetro de comparação de eficiência, levou-se em conta o tempo médio de execução dos programas, que foram executados com 4 diferentes configurações: 5 cidades, 7 cidades e 9 cidades. Os resultados são apresentados na tabela 1.

Algoritmo Tempo de execução

5 cidades Tempo de execução 7 cidades Tempo de execução 9 cidades Força bruta – seqüencial

0,072 seg 5,291 seg 408,718 seg

Força bruta – paralelo

0,030 seg 3,084 seg *ERRO* - out of memory

Tabela 7.1 - Comparação do tempo de execução do algoritmo do caixeiro viajante A execução de uma linguagem funcional nas máquinas mais usualmente utilizadas apresenta problemas de desempenho e consumo de recursos, pois aqueles sistemas são baseados na arquitetura de von Neuman. Por este motivo, não foi possível rodar experimentos com uma configuração maior de cidades.

7.1 Contribuições

O resultado deste trabalho é um método de implementação de autômatos finitos adaptativos com execução em paralelo. Neste método, sempre que, dado sua configuração corrente e o símbolo lido da cadeia de entrada, exista mais de uma transição a ser executada, o modelo irá disparar a execução de todas as transições de uma só vez.

Desta forma, o modelo de autômato pode se tornar mais eficiente, diminuindo seu tempo de execução, como ilustrado nos experimentos realizados. Além disso, a

criação de threads para tratar o não-determinismo nos autômatos mostrou ser uma forma eficiente.

Além disso, um framework para criação e execução de autômatos finitos adaptativos foi desenvolvido. Este ambiente de desenvolvimento no qual qualquer autômato finito adaptativo pode ser formulado deve servir de apoio para grupos de estudos que fazem pesquisas utilizando este modelo.

Quanto aos resultados do exemplo acima, pode-se concluir que o objetivo de se construir um modelo mais eficiente que o modelo seqüencial foi atingido.

7.2 Trabalhos futuros

Ainda são necessários estudos mais aprofundados quanto a como implementar a paralelização das transições, a fim de melhorar o desempenho do programa. O esquema descrito neste trabalho, utilizando threads no Scheme se mostra eficiente quanto à velocidade de execução do código. Apesar disso, este esquema consome muitos recursos do sistema nas máquinas baseadas no modelo de von Neuman. O que indica que um controle de criação de threads, ou outro esquema de paralelização, é desejável.

Além disso, uma expansão do framework para desenvolvimento de outros modelos adaptativos, principalmente aqueles baseados no modelo de autômatos, pode ser útil para os pesquisadores da área.

É previsto, ainda, implementar a heurística da colônia de formigas (DORIGO, 1999) como um conjunto de funções adaptativas usando o framework, comparando o resultado de sua execução com outras heurísticas, como os algoritmos genéticos.

7.3 Conclusão

Este trabalho apresenta um modelo de execução em paralelo dos autômatos finitos e, conseqüentemente, dos autômatos finitos adaptativos. Ao definirmos mecanismos que

fazem as transições em paralelo, o modelo pode tornar-se mais eficiente que o modelo seqüencial, como mostrado na tabela 7.1.

Além disso, um framework para desenvolvimento de autômatos finitos adaptativos, paralelo ou seqüencial, é apresentado como resultado final deste trabalho. Esse desenvolvimento tem o intuito de auxiliar futuras pesquisas na área das técnicas adaptativas.

O desenvolvimento desse framework visa propor um padrão de implementação destes modelos. Espera-se que este padrão seja de simples compreensão e utilização.

8 Bibliografia

ABELSON, H.; SUSSMAN, G. J.; and SUSSMAN, J. Structure and Interpretation of

Documentos relacionados