Sumário
Para a realização das atividades eu fiz o download do Soar (arquitetura cognitiva para o desenvolvimento de sistemas com comportamento inteligente) na versão Linux 64 bits, que pode ser encontrado no seguinte link: http://code.google.com/p/Soar/wiki/SoarTutorial. Nesse link podemos fazer o download do pacote Soar, que inclui tutoriais, exemplos de códigos de programas feitos usando essa arquitetura cognitiva, além de softwares como o Soar Debugger e VisualSoar que servem para a execução e criação de projetos no Soar.
É importante observar que deve-se fazer o download da versão do Soar compatível com o sistema operacional do computador onde ele será executado. A versão Windows 64 bits dessa arquitetura cognitiva não funciona em computadores com Windows 32 bits. O mesmo se aplica às versões compatíveis com o sistema operacional Linux.
O objetivo dessa atividade é construir um agente Soar simples, para entender o funcionamento dessa arquitetura cognitiva, e gerar um Hello World utilizando a infra-estrutura do Soar.
Conforme foi solicitado nesse exercício, eu executei o Soar Debugger, cuja tela é apresentada na Figura 1.
Figura 1: Tela do Soar Debugger.
Observe que, ao abrir o Soar Debugger, ele já cria uma agente Soar automaticamente, como o nome de Soar1, conforme é destacado na Figura 2.
Figura 2: Tela do Soar Debugger - novo agente.
Seguindo os passos mostrados no tutorial 1 do Soar eu abri o arquivo de exemplo de contrução de um simples Hello World.
Para abrir um arquivo usando o Soar Debugger deve-se seguir os seguintes passos: clicar no menu File, depois em Load Source File, conforme apresentado na Figura 3. Após isso, basta selecionar o arquivo e clicar em Ok (Figura 4).
Figura 3: Abrindo um arquivo no Soar Debugger.
Figura 4: Abrindo um arquivo no Soar Debugger - parte 2.
Ao abrir o programa Hello World podemos executá-lo clicando no botão do Soar Debugger ou digitando o comando run (Figura 5). Após fazer isso, a mensagem Hello World será exebida, conforme mostra a Figura 6.
Figura 5: Botão e comando run.
Figura 6: Resultado da execução do programa Hello World.
O código fonte do programa Hello World é apresentado na Figura 7.
Figura 7: Código fonte do programa Hello World.
O significado de cada parte do código apresentado na Figura 7 é apresentado abaixo:
- sp: significa Soar production e é usado para iniciar cada regra.
- {: inicia o corpo da regra.
- hello-world: é o nome do programa.
- (state <s> ^type state): é uma condição (equivalente a um if de linguagens de programação). Uma regra pode ter várias condições, mas no caso do programa Hello World apenas uma condição foi elaborada.
- -->: é usado para separar a condição da ação associada a ela (equivalente a separar um if de um then em linguagens de programação).
- (write |Hello World|): essa é a primeira ação associada a condição.
- (halt): essa é a segunda ação associada a condição.
- }: indica o fim da regra
Toda regra do Soar segue a estrutura apresentada acima e que pode ser formalizada como mostrado na Figura 7.2. <;
/a>
Figura 7.2: Estrutura geral de uma regra.
A memória de trabalho do Soar possui todas as informações dinâmicas de um agente sobre seu mundo e seu raciocínio interno. É na memória de trabalho que ficam armazenados dados de sensores, cálculos intermediários, operadores e metas.
A memória de trabalho do Soar é organizada como uma estrutura de grafos, conforme pode ser observado na Figura 8.
Figura 8: Estrutura da memória de trabalho do Soar.
Diante do que é apresentado na Figura 8, pode-se observar que, assim como um grafo, a memória de trabalho possui nós e arestas. Os nós são S1, B1, B2, T1, A, blue, block, B, yellow, table e gray. Algumas arestas são: block, table, name, color e type. As arestas são chamadas de atributos e sempre são precedidas por "^". Os nós do Soar que não são terminais, ou seja, são origem de alguma aresta, são chamados de identificadores (Exemplo: S1, B1, B2 e T1). Já os nós que são terminais, ou seja, que não são origem de nenhuma aresta, são chamados de constantes (Exemplo: A, blue, block, B, yellow, table e gray). Além disso, os nós que são apontados por algum atributo são chamados de valores. Então, por exemplo, os nós B1 e T1 são valores, pois são apontados, respectivamente, pelos atributos block e table.
Além da estrutura apresentada na Figura 8, a memória de trabalho cria automaticamente uma estrutura adicional para cada agente, conforme é apresentado na Figura 9.
Figura 9: Estrutura adicional da memória de trabalho do Soar.
Observe que a estrutura adicional criada automaticamente pelo Soar também possui identificadores, atributos e valores. Esta pode ser considerada uma estrutura mínima de uma memória de trabalho.
Alguns elementos da memória apresentada na Figura 9 merecem atenção. O elemento (s1 ^type state) deve sempre existir na memória de trabalho para determinar se o agente existe. Essa condição é sempre necessária para que o programe execute as ações. Por exemplo, no programa Hello World, apresentado na Figura 7, a única condição da regra é que o agente exista. Se essa condição for verdadeira, então as ações são executadas: imprimi a mensagem "Hello World" e depois para. O atributo input-link é onde as informações sensoriais do agente são avaliadas. Já, o atributo output-link é onde as ações a serem executadas pelo agente devem ser criadas, para que ele se mova pelo mundo. Logo, considerando o programa Hello World, o Soar cria automaticamente esse atributo, onde é criada a ação a ser realizada pelo agente: imprimir a mensagem "Hello World".
O conjunto de elementos da memória de trabalho que compartilham o mesmo identificador é chamado de objeto. Os objetos no Soar são usados para representar algo sobre o mundo, tal como um carro, uma comida ou um predador. Nos programas em Soar os objetos são cercados por parenteses. Os elementos que compôem um objeto são chamados de argumentos (augumentations). Logo, no objeto (S1 ^io I1 ^superstate nil ^type state), podemos dizer que existem os seguintes argumentos: ^io I1, ^superstate nil e ^type state. Os argumentos são usados para representar propriedades dos objetos, tais como a cor, o tamanho ou a posição em relação a outros objetos.
Como pode ser observado, na atividade anterior foi analisado um programa em Soar que possui e executa uma única regra. Esse modelo é muito simples e não corresponde ao que normalmente ocorre no mecanismo de operação do Soar. O mecanismo mais utilizado é o que faz uso dos chamados "operadores". Nele, algumas regras são utilizadas para propor "operadores", que depois vão concorrer para serem selecionadas pelo procedimento de decisão, para que algum deles seja executado. Nessa atividade, o funcionamento desse mecanismo será explicado.
Os operadores são mecanismos que executam ações no mundo ou internamente na "mente" de um agente. Os operadores podem ser usados para criar comandos para mover o agente pelo mundo, lembrar onde estão os alimentos, quais são os alimentos bons, enviar mensagens, interpretar mensagens, entre outros. Isso significa que os operadores são usados no processo de tomada de decisões do agente.
Diante disso pode-se afirmar que a operação básica do Soar é um ciclo em que operadores são continamente propostos, selecionados e aplicados, conforme é ilustrado na Figura 10.
Figura 10: Operação básica do Soar: operadores.
Conforme pode ser observado na Figura 10, a proposição de operadores é feito por meio de regras. Elas testam os estados de memória do agente para garantir que um determinado operador é apropriado e cria uma representação desse operador na memória de trabalho, juntamente com uma preferência, que é usada para mostrar que aquele determinado operador é candidato a ser selecinado. Depois disso, um procedimento de decisão é usado para selecionar um dos operadores. Por fim, depois que um operador é selecionado, a condição da regra associada ao operador é satisfetita e executa as ações apropriadas. Então, por exemplo, em um jogo em que o agente se depara com um alimento, a preferência atribuída aos operadores vai indicar para a arquitetura qual a melhor ação a ser tomada: comer o alimento ou se distanciar do alimento. Se o operador relacionado a ação de comer o alimento tiver maior preferência, o agente irá se alimentar. Já em casos em que os operadores tem a mesma preferencia, será selecionado qualquer um deles de forma aleatória ou por alguma outro procedimento de decisão.
Para analisar o funcionamento dos operadores, o programa Hello World foi reescrito para usar esse mecanismo. O novo código do programa é apresentado na Figura 11.
Figura 11: Hello World com operadores.
Diante do que é apresentado na Figura 11, podemos ver que agora o programa Hello World possui duas regras, o que difere da versão anterior que possuia apenas uma regra. Essa diferença entre as duas versões existe porque agora estamos usando um operador. Para usar um operador, é necessário duas regras: uma para propor o operador e outra para aplicá-lo quando ele é selecionado.
Para ver o funcionamento do operador do programa "Hello World", nós o abrimos novamente no programa Soar Debugger. Antes de executá-lo, nós usamos o comando excise --all para destruir agentes executados anteriormente e limpar todas as regras existentes na memória.
Depois disso, alteramos o nível watch para watch 5, o que faz com que o Soar Debugger apresente mais detalhes da execução do arquitetura cognitiva do que é apresentado quando usamos níveis menores, tais como watch 4 ou watch 3. Veja na Figura 12 como executar esses procedimentos.
Figura 12: Comandos excise --all e watch 5 no programa Soar Debugger.
Após executar o programa Hello World no Soar Debugger, foi apresentado o resultado mostrado na Figura 13.
Figura 13: Execução do programa Hello World no Soar Debugger.
Pode-se notar na Figura 13, todo o ciclo de um operador. Vemos que primeiro o operador foi proposto, depois foi selecionado e por fim foi aplicado.
Diante do que entendi sobre o funcionamento de um operador no Soar, eu concluo que esse mecanismo permite que o agente consiga saber qual ação tomar dependendo da situação atual em que se encontra no mundo virtual e da complexidade das regras implementadas na arquitetura cognitiva. O mais importante é que somente com as regras deveria-se implementar o decisão do agente em cada situação (semelhante a um simples algoritmo, sem nenhuma inteligência), enquanto que usando operadores, é possível combinar regras e aumentar a complexidade do processo de decisão, de forma que por meio das preferências de cada operador, o agente saberá qual ação é a melhor, sem que isso tenha sido diretamente programado. A capacidade de manipulação da memória por parte dos operadores e a combinação de regras são recursos poderosos desse mecanismo, pois as ações executadas pelo agente e as respostas de objetos, criaturas e do próprio mundo virtual, podem mudar as ações e decisões do agente no mundo, criando um comportamento inteligente no mesmo.
Usando o Soar Debugger é possível entender melhor como funciona a memória de trabalho so Soar. Podemos usar comando de impressão print para visualizar os atributos e valores dos operadores. Por exemplo, usando o comando print S1 na execução do programa Hello World, obtivemos o resultado apresentado na Figura 14.
Figura 14: Resultado do uso do comando print.
Observe, na Figura 14, que o Soar Debugger apresentou os atributos e valores do identificador de estado S1. Se olharmos o código do programa Hello World, apresentado na Figura 11, veremos que não foi criado nenhum estado com o identificador S1, mas sim com o identificador S. O identificador S1 é criado internamente na memória de trabalho para referenciar o estado S. O mesmo ocorre com o estado O, que foi referenciado na memória de trabalho com o identificador O1. Além disso, alguns atributos do estado S1, tais como io, superstate e type também foram criados automaticamente pelo Soar e fazem parte da estrutura adicional da memória de trabalho, conforme apresentado na Figura 9.
Outro ponto que deve ser observado é que na memória de trabalho todos os operadores que possui alguma preferência, em determinado estado, possuem um sinal de + após o seu identificador. Na Figura 14, podemos observar que o estado O1 não possui esse sinal após o identificador, mas na criação do programa foi criada uma preferência para ele, conforme pode ser visto no código do programa Hello World. Isso acontece porque o operador O1 foi selecionado e apenas operadores não selecionados exibem o sinal de + após seu identicador.
O Soar Debugger permite que também vejamos a estrutura dos atributos de um estado. Por exemplo, no estado S1 há um atributo io. Esse atributo também é criado automaticamente na memória de trabalho. Veja que na Figura 14, também usamos o comando print para mostrar os valores desse atributo. Veja que esse atributo possui os valores input-link e output-link, que conforme foi mencionado anteriormente são usados, respectivamente, para avaliar as informações sensoriais do agente e criar as ações que devem ser executadas pelo agente para que ele se mova pelo mundo.
O Visual Soar é usado para criar programas na arquitetura Soar. Esse editor possui algumas vantagens em relação a editores comuns de texto na criação de programas Soar, pois permite uma melhor experiência no desenvolvimento de sistemas com o Soar.
Para criar um projeto no Visual Soar deve-se clicar em File e depois em New Project, conforme ilustrado na Figura 15.
Figura 15: Criando um novo projeto no Visual Soar.
Depois disso é só atribuir um nome ao projeto e escolher o diretório onde ele deverá ser criado.
Figura 16: Atribuindo um nome ao projeto e escolhendo seu diretório.
Depois que o projeto foi criado, basta dar dois cliques sobre _firstload para abrir o editor onde o programa será criado. Veja o editor na Figura 17.
Figura 17: Escrevendo o programa.
O nome _firstload pode ser alterado, bastando clicar com o botão direito em cima dele e depois clicando na opção rename. Além disso, pode-se criar novos arquivos no projeto, bastando que se clique com o botão direito do mouse no nome do projeto e depois clique na opção Add a File....
Outro recurso importante do Visual Soar é chamado de DataMap, que é usada para descrever a estrutura da memória de trabalho do Soar. Com esse recurso fica fácil visualizar os estados, operadores, atributos e valores da memória de trabalho, o que pode facilitar o entendimento do funcionamento do programa. Para visualizar o DataMap deve-se clica com o botão direito do mouse no nome do projeto e depois em Open DataMap, conforme ilustrado na Figura 18.
Figura 18:Mostrando o Datamap.
Depois que o DataMap for aberto podemos dizer que o ambiente de trabalho do Visual Soar está completo. No lado esquerdo do ambiente fica a janela Operator, no centro fica o DataMap, do lado direito fica o editor de códigos e embaixo se localiza a janela de feedback. Essa estrutura é apresentada na Figura 19.
Figura 19: Ambiente de trabalho completo do Visual Soar.
Diante do que foi apresentado podemos ver que o Visual Soar melhora a experiência de criação de programas no Soar. Entre as vantagens que ele oferece, está o editor de códigos, que destaca as palavras chaves do Soar, o que facilita muito a programação. Outra vantagem é o uso do DataMap, que ao apresentar a estrutura da memória do programa que está sendo criado, facilita a identificação de erros, de inconsistências entre as regras e, principalmente, facilita uma melhor compreensão do comportamento que o agente pode vir a apresentar. Além disso, há também a janela de feedback que retorna mensagens de erro e ajuda a identificar os possíveis trechos que possam estar com problemas.
O problema Water Jug (jarro d'água) é um problema clássico da área de inteligência artificial, no qual é necessário fazer uma busca em um espaço de estado finito e limitado. O enunciado desse problema é mostrado a seguir:
- Existem dois jarros d'água vazios. Um deles tem capacidade para cinco litros d'água, enquanto o outro tem capacidade para epenas três litros. Há também uma fonte d'água ilimitada que pode ser usada para encher os jarros. Você pode esvaziar um jarro os derramar água de um jarro para o outro. Não há nenhuma marca intermediária nos jarros que ajude a identificar a quantidade atual de litros d'água. A meta é preencher o jarro que tem capacidade para três litros com um litro d'água.
O objetivo dessa atividade é entender o problema e representá-lo de tal forma que ele possa ser resolvido por meio do Soar. Para isso, o primeiro passo é determinbar o espaço de estados do problema. O estado inicial do problema é: dois jarros vazios. Já o estado final desejado é: o jarro de capacidade de três litros deve conter apenas um litro d'água. Então, o problema consiste em sair do estado inicial, procurar estados desejados por meio da aplicação de operadores que transformam um estado em outro. No problema Water Jug, esse operadores são encher um jarro, esvaziar um jarro e despejar a água de um jarro para outro.
Para representar os estados existem dois valores: a quantidade de água no jarro de cinco litros e a quantidade de água no jarro de três litros. Então, o estado inicial do problema pode ser representado por (5:0, 3:0), que significa que o galão de 5 litros contém 0 litros de água e o galão de 3 litros também. Então, uma das soluções possíveis seria:
- (5:0,3:0) → encher o jarro de 3 litros;
- (5:0,3:3) → despejar a água do jarro de 3 litros no jarro de 5 litros;
- (5:3,3:0) → encher o jarro de 3 litros;
- (5:3,3:3) → despejar a água do jarro de 3 litros no jarro de 5 litros;
- (5:5,3:1)
Para representar os estados no Soar, pode-se usar as seguintes informações:
- a capacidade de cada jarro, que pode ser representada pelo atributo ^volume;
- a quantidade de litros d'água que cada jarro possui, que pode ser representada pelo atributo ^contents.
- o espaço (em litros) vazio em cada jarro ^empty.
Então, o estado inicial do problema pode ser representado como:
(state <s> ^jug <j1>
^jug <j2>)
(<j1> ^volume 5
^contents 0)
(<j2> ^volume 3
^contents 0)
Agora o próximo passo é definir um operador que criará o estado inicial. Conforme foi feito no programa Hello World, todo operador deve ser definido por dois tipos de regras: uma regra para propor o operador e uma para aplicá-lo.
Porém, um operador precisa ser proposto antes que qualquer tarefa tenha sido selecionada. Isso significa que devemos verificar se a tarefa já está na memória de trabalho e para isso, basta que o atributo seja precedido por -. Então, a proposição do operador pode ser representada como:
problema-jarros*propoe*inicializa-problema-jarros
SE nenhuma tarefa é selecinada,
ENTÃO propoe o operador inicializa-problema-jarros.
A formalização em Soar da proposição do operador é a seguinte:
Figura 20: Proposição do operador inicializa-problema-jarros.
Observe que no código acima, (^superstate nil) testa a existência do estado. Além disso, ^name é precedido pelo sinal de -, pois testa se nenhum estado com o atributo name já está definido na memória de trabalho.
Como mencionado anteriormente, depois de criar uma regra para propor o operador, devemos criar uma regra para aplicá-lo. Essa regra deverá adicionar o nome para o estado e criar os jarros vazios, ou seja, com conteúdo 0.
Figura 21: Aplicação do operador inicializa-problema-jarros.
Quando uma regra é executada, todas as estruturas em sua ação são adicionadas a memória de trabalho. Porém, a memória de trabalho não consegue armazenar tudo, principalmente em programas mais complexos. Logo, é necessário que alguns dos elementos que foram armazenados sejam removidos. Porém, deve ser analisado quais elementos podem ser removidos, uma vez que alguns deles são importantes para a continuação da execução do programa. Por exemplo, elementos da memória de trabalho que foram gerados por regras que aplicam um operador, não podem ser removidos. Logo, o Soar identifica esses elementos e remove apenas aqueles gerados por outros tipos de regras (que não fazem a aplicação de operadores).
As alterações na memória de trabalho provocadas por regras de aplicação de operadores só podem ser alteradas com a aplicação de outros operadores. Caso contrário devem persistir na memória, pois são compromissos que o sistema fez e são necessários para que não haja inconsistência, tal como a geração de loops infinitos, pois se um operador é removido da memória de trabalho, a regra de proposição de operadores será acionada novamente, e se a removação for repetida a regra de proposição continuará sendo acionada infinitamente.
O Soar automaticamente classifica uma regra como sendo parte da aplicação de um operador ou não. Se uma regra seleciona um operador e modifica seu estado, então ela pode ser considerada uma regra de aplicação de um operador e os elementos criados por ela persistem na memória de trabalho. Esses elementos são chamados de operator-support ou o-support, pois são criados como parte de um operador. Os elementos criados por regras que não são parte da aplicação de um operador são removidos da memória de trabalho quando as condições da regra não se apliquem mais ao estado atual da memória de trabalho.
Como foi mencionado anteriormente, para representar os estados no problema Water Jug serão usadas as informação relativas a capacidade de cada jarro (^volume), a quantidade de litros d'água que cada jarro possui (^contents) e o espaço vazio em cada jarro (^empty). Porém, se observar-mos o estado inicial apresentado anteriormente, veremos que o atributo ^empty não foi definido. Isso aconteceu porque tornaria muito complexa a regra de inicialização. Então, para facilitar será criada uma regra exclusiva para o cálculo do volume disponível para cada jarro. Essa regra testará e criará uma nova estrutra no estado.
Regras de elaboração de estados criam abstrações úteis de combinações de outros elementos da memória de trabalho e os representa no estado corrente como um novo argumento (augumentation). Esses novos argumentos podem ser testados em novas regras, o que elimina a necessidade da criação de complexas combinações de condições.
Diante disso, a regra que calculará o volume disponível para o jarro é apresentada abaixo.
Figura 22: Regra que calcula o volume disponível para o jarro.
No código apresentado acima, a primeira condição está testando se o nome do estado é water-jug, pois ela só se aplica a esse problema. Esse tipo de teste é realizado em todas as regras do problema Water Jug. Depois ela testa a existência de uma jarra, e dos atributos volume (<v>) e contents (<v>). Após isso, ele adiciona um novo atributo, o empty. O valor desse atributo será a diferença entre volume e contents. As operações matemáticas no Soar aparecem antes dos argumentos e, por isso, o valor do empty foi atribuído como (- ).
Agora, sempre que o conteúdo de um jarro mudar através de uma regra de aplicação do operador, um novo valor será calculado para o atributo empty.
O problema Water Jug possui três operadores:
- fill: encher algum jarro, caso ele não esteja cheio;
- empty: derramar toda a água do jarro, caso ele possua água;
- pour: despejar a água de uma jarro para o outro, se o jarro de origem tiver água e se o jarro de destino não estiver completamente cheio.
A regra para propor o operador fill pode ser representado pelo código mostrado abaixo:
Figura 23: Regra para propor o operador fill.
Podemos ver que no código apresentado acima, a primeira condição está testando se o nome do estado é water-jug. Depois disso, ele verifica se existe existe um objeto jug jarra na memória de trabalho. Por fim, ele verifica se a jarra está vazia por meio da operação (^empty > 0). Caso estas condições sejam satisfeitas, propôe-se encher o jarro.
A regra para propor o operador empty pode ser representado pelo código mostrado a seguir:
Figura 24: Regra para propor o operador empty.
Como pode ser observado, a regra para propor o operador empty é semelhante a regra do operador fill. A única diferença é que agora está sendo verificado se a jarra está cheia, por meio da operação (^contents > 0)
Por fim, a regra para propor o operador pour pode ser representado pelo código mostrado a seguir:
Figura 25: Regra para propor o operador pour.
Na regra que propôe o operador despejar, será analisado a quantidade de água nos dois jarros, pois o jarro de origem precisa ter água e o de destino não pode estar cheio. O teste referente ao primeiro vaso foi realizado da maneira padrão ^jug <i>. Porém, o teste referente ao segundo vaso não foi feito da mesma forma, pois se o teste fosse feito de forma semelhante (^jug <j>), o Soar poderia usar os identificadores i e j para representar o mesmo jarro, já que ele não faz nenhum tratamento para impedir isso. Diante disso, para que esse problema não ocorra, o correto é usar o teste da seguinte forma: { <j> < > <i> }. O sinal < >, significa diferente, logo, essa condição garante que o identificador j corresponderá a um jarro diferente do que será representado pelo identificador i.
Outro ponto que deve ser destacado é que em todos as regras mostradas nessa seção temos o seguinte trecho de código: (<s> ^operator <o> + =). O sinal de + significa que aquele operador é candidato a ser selecionado, ou seja, é um sinal de preferência. Porém, como os operadores fill, empty e pour tem a mesma preferência, é adicionado o sinal de =, que indica que um dos três deve ser escolhido aleatoriamente. Isso evita que o Soar selecione sempre o mesmo operador.
Agora que os operadores foram propostos, devemos implementar as regras que irão aplicar os operadores. Essas regras irão adicionar ou remover elementos da mémoria de trabalho ou alterar alguma coisa no mundo virtual de forma que mude a percepção do agente sobre as açõesque precisam ser tomadas. Issas alterações por si só aumentam a prefência de seleção e aplicação de alguns operadores, evitando que um mesmo operador seja sempre selecionado.
Primeiro vamos criar a regra para a aplicação do operador fill. Essa regra precisa mudar o valor atual do conteúdo do jarro. Para isso, o Soar remove da memória de trabalho o elemento que guarda esse valor e adiciona um novo elemento com o novo valor. Para remover o elemento, basta colocar um sinal de - após o seu nome. A regra que aplica o operador fill é apresentada abaixo:
Figura 26: Regra de aplicação do operador fill.
A regra para a aplicação do operador empty é bem semelhante a regra de aplicação do operador fill, conforme é apresentado a seguir:
Figura 27: Regra de aplicação do operador empty.
Para a regra de aplicação do operador pour devemos observar duas situações possíveis: a primeira acontece quando o jarro que está recebendo a água pode conter toda a água despejada do outro jarro e mesmo assim não fica cheio, tal como adicinar toda a água do jarro de três litros no jarro de cindo litros; a segunda situação é aquela em que o jarro que está recebendo a água não pode conter toda a água que está sendo despejada e, assim, um pouco de água ainda deve continuar no jarro de origem. Essas duas situações são contempladas em duas regras diferentes, conforme é apresentado a seguir:
Figura 28: Regras de aplicação do operador pour.
O Soar permite monitorar o estado que está sendo aplicado e os conteúdo de cada estado. Além disso, as regras de monitoramento são executadas em paralelo e, por isso, não interferem na resolução do problema. As regras de monitoramento para o problema Water Jug são apresentadas abaixo:
Figura 29: Regra de monitoramento dos estados da memória de trabalho.
Figura 30: Regra de monitoramento do operador fill.
Figura 31: Regra de monitoramento do operador empty.
Figura 32: Regra de monitoramento do estado pour.
O comando write das regras apresentadas acima, imprime os detalhes do estado selecionado e do conteúdo do mesmo.
Um ponto importante que ainda não foi contemplado pelo programa Water Jug é saber quando o problema foi resolvido. Então, na figura 0, é apresentada a regra que reconhece que o jarro de três litros está preenchido com apenas 1 litro de água e para o agente.
Figura 33: Regra para reconhecer o estado desejado.
Para tornar a busca por soluções mais eficiente no Soar, devemos criar regras que aumentem a chance de operadores mais promissores para o estado atual serem selecionados. Uma das formas de fazer isso no programa Water Jug é evitar desfazer o último operador que foi aplicado, tal como esvaziar um jarro que acabou de ser cheio, encher um jarro depois que ele acabou de ser esvaziado ou derramar a água do jarro i para o jarro j, depois que o jarro i acabou de receber água do j.
Para evitar que o último operador seja desfeito, o programa deve lembrar qual o último operador que foi aplicado. Como o Soar remove da memória de trabalho o registro do último operador, depois que ele é aplicado, devemos criar regras que guardem essa informação. Esse procedimento é dividido em duas partes: a primeira será criar uma estrutura em que o estado será a memória do operador mais recente; a segunda é a remoção de qualquer registro de um operador antigo. As regras criadas para melhor o controle de busca são apresentadas abaixo:
Figura 34: Regras para adicionar e remover o último operador na memória de trabalho.
Figura 35: Regras para evitar a ação do último operador seja desfeita.
Agora, executando o programa após seguir os passos descrito no tutorial, foi possível encontrar a solução desejada, conforme apresentado na Figura 0.
Figura 36: Execução do programa Water Jug.
Diante dos experimentos realizados na aula de hoje, posso concluir que o Soar é uma arquitetura cognitiva prática e de fácil compreensão, mas que consegue simular comportamento inteligente por meio da interação complexa entre mecanismos simples, tais como estados, regras, operadores e memória de trabalho.
Como o Soar usa regras no processo de decisão, podemos dizer que nesse ponto, ele é comparável a um sistema de processamento de regras convencional. Porém, ele se difere desse tipo de sistema, pois usa uma sequência de decisões para varrer o espaço de busca de um problema, onde as regras são usadas para propor e aplicar mecanismos chamados de operadores. Porém, a ordem de aplicão desses operadores não é diretamente programada, como seria se fosse usada uma regra simples. Cada operador possui um preferência, que pode ser alterada de acordo com o estado atual da solução do problema. Essa iteração entre proposta e aplicação de operadores, que naturalmente alteram as ações do agente e a maneira com o agente percebe o mundo virtual, além da permissão de alteração da preferência de um operador por outro operador dada uma determinada situação, aumenta a complexidade e a capacidade de resolução de um problema, superior ao que pode ser observado em um sistema especialista comum.