Introdução
Depois de carregar o programa hello-world-rule.soar foi feito um run no programa. A Ilustração 1 mostra a saída do programa . A primeira coisa que o programa faz é criar automaticamente o primeiro estado S1 é depois é executada a ação que imprime “helow world”
Ilustração 1
Ilistração 1
A Ilustração 2 apresenta o código fonte do arquivo hello-world-rule.soar. Do arquivo pode-se obter alguma informação de como é a linguajem. O primeiro que pode-se observar é que a linha com comentários começam com #.
Todas as regras começam com as letras sp (Soar Production).
O carácter '{' começa o corpo de uma regra.
Imediatamente depois do carácter '{' vai o nome da regra. O nome da regra pode conter letras, números e os caracteres '-' e '*'.
A linha “(state <s> ^type state)” é a condição, a parte IF da regra.
Os caracteres “-->” separa a parte IF da parte THEN
As linhas “(write |Hello World|) e (halt)” representa as ações.
Finalmente a regra é fechada com o carácter '{'
Ilistração 2
Em geral as regras são do tipo:
sp{rule-name
(condition)
(condition)
...
-->
(action)
(action)
...}
Working Memory
O working memory contem toda a informação dinâmica dos agentes acerca de seu mundo e seus estados internos. Ele contem a informação dos sensores, cálculos intermédios operações atuais e metas.
A informação do working memory é organizada em forma de estados (states).
Dois bloques (block), uma mesa (table) e a relação entre eles são modelados no working momory como mostra a Ilustração 3.
Os nodos que estão ligado a outros nodos, ou seja que não são nodos terminais como S1 e B1, são chamados de nodos identificadores (identifiers) enquanto os outros, os que são nodos terminais como A, blue e block, são chamados constantes (constants)
Em SOAR os enlaces entre os nodos são chamados atributos (attributes) e começam com '^'
Ilustração 3
Em resumo cada elemento no working memory é uma tripleta de identifier, um attribute, e um value. Por exemplo os elementos do working memory da Ilustração 4 são:
identifier | attribute | value |
S1 | ^superstate | nil |
S1 | ^io | I1 |
S1 | ^type | state |
I1 | ^output-link | I2 |
I1 | ^input-link | I3 |
Ilustração 4
O conjunto de elementos na working memory que compartem o mesmo identificador inicial é chamado um objeto (object)
Os elementos que compõem um objeto são chamados augmentations.
Por exemplo os objetos para o working memory da Ilustração 4 são
(S1 ^io I1 ^superstate nil ^type state) |
(I1 ^input-link I3 ^output-link I2) |
Uma variable está definida como uma letra entre os caracteres <>, assim <s>.
Voltando ao prorama hello-world-rule.soar
Como regra, toda primeira condição deve começar com a palavra “state”. Assim por exemplo a linha (state <s> ^type state) é uma condição que testa se existe algum estado.
Na linha (write |Hello World|) o carácter '|' envolve texto. E a palavra write imprime texto no Interaction Window.
Building Simple Agents Using Operators
Para que o SOAR use operadores (operators) eles primeiro devem ser criados na working memory a traves de proposal rule. As proposal rules fazem provas no estado para determinar se o operador é apropriado e logo ele cria uma representação do operados no working memory associado com uma acceptable preference para esse operador. A preference é uma forma de dizer para a arquitetura que o operador é um candidato a selecionar.
Para usar um operador são necearias duas regras: uma para propor o operador e outra para aplicá-lo. Exemplo:
#Proposição de hello-world
If I exist, propose the hello-world operator.
# Aplicação de hello-world
If the hello-world operator is selected, write “Hello World” and halt.
O operador é selecionado por o processo de decisão de SOAR o qual pega todos os operadores propostos e seleciona um.
Um exemplo de como é feita a preposição e a aplicação do operador hellow-world é apresentada no arquivo hellow-world-operator, Ilustração 5.
Ilustração 5
A saída do SoarDebuger é apresentada na Ilustração 6
Ilustração 6
Na linha (<s> ^operator <o> +) o simbolo + representa que o operador <o> é um acceptable preference.
Uma preferencia (preference) é como qualquer outro elemento no working memory só que ele tem uma quarta propriedade: o tipo de preferencia, que neste caso é '+'.
Por convenção <s> é geralmente usado para igualar identificadores de estados e <o> é usada para igualar identificadores de operadores
A Ilustração 7 apresenta a saída do programa quando é digitada na área de comandos o comando print s1. Ele apresenta toda a informação de atributos e valores do elemento s1.
Ilustração 7
VisualSoar
A Ilustração 8 apresenta o entorno de desenvolvimento do SOAR chamado VisualSoar.
Ilustração 8
Aqui vai-se solucionar o problema chamado Water Jug. O primeiro passo ao formular um problema é determinar o espaço de estados onde o solucionador de problemas possa estar. O espaço de estados ou espaço de problema (problem space) é determinado pelos objetos que podem ser manipulados, neste exemplo os dois jarros, e seus possíveis valores (0-5 galões). O problema então é definido como um estado inicial, onde o solucionador de problemas possa iniciar, neste caso dois jarros vazios e um conjunto de estados desejados, neste caso qualquer estado onde o jarro de três galões tenha um galão de água. Assim solucionar um problema consiste em começar num estado inicial e buscar o estado desejado aplicando operadores.
Neste exemplo os operadores serão: vaziar o jarro, encher o jarro e verter água de um jarro para o outro.
Para tarefas que involucrem interação com o mundo exterior, muitos dos estados serão criados a partir da informação perceptiva que vem dos sensores. Mas neste caso é assumido que não existe interação externa então os estados inicial devem ser criados a partir de regras.
Uma boa pratica é separar os nomes das regras em partes separadas por um *. A primeira parte é a tarefa, a segunda parte é a função e a terceira é o nome do operador.
Neste caso o operador deve ser proposto somente no começo, antes que qualquer outra tarefa seja selecionada. Assim a proposta do operador seria:
water-jug*propose*initialize-water-jug
If no task is selected,
then propose the initialize-water-jug operator.
Esta proposta de operador será escrita em SOAR como
sp {water-jug*propose*initialize-water-jug # Nome da proposta
(state <s> ^superstate nil) # (1)
-(<s> ^name) # (2)
-->
(<s> ^operator <o> +)
(<o> ^name initialize-water-jug)}
(1) Em SOAR toda regra tem que ter alguma condição que seja positiva (a primeira) então pode-se comprovar que o estado exista usando ^superstate.
(2) Nesta linha é verificado que não exista nenhuma tarefa definida. Para fazer isso pode-se negar a condição com “-”, como o estado <s> não tem ^nome então não existe.
A Ilustração 9 apresenta a definição da proposta e da aplicação do operador chamado initialize-water-jug em VisualSoar.
Ilustração 9
Para aplicar o operador é precisa outra regra. A regra de inicialização agrega o nome do estado e cria os dois jarros com vazios. A regra precisa agregar elementos no working memory.
As regras em inglês e em Soar são apresentadas a seguir:
water-jug*apply*initialize-water-jug
# If the initialize water-jug operator is selected,
# then create an empty 5 gallon jug and an empty 3 gallon jug.
sp {water-jug*apply*initialize-water-jug
(state <s> ^operator <o>)
(<o> ^name initialize-water-jug)
-->
(<s> ^name water-jug
^jug <j1>
^jug <j2>)
(<j1> ^volume 5
^contents 0)
(<j2> ^volume 3
^contents 0)}
A Ilustração 10 apresenta a saída do VisualSoar com o operador de inicialização completo.
Ilustração 10
Persistência de WMEs
Apos de uma condição na preposição do operados não ser mais valida, ela é retraída, por exemplo a condição de não existência de tarefas na preposição do operador initialize-water-jug, apos ele criar os elementos no working memory a condição de inexistência não é mais valida e o operador pode ser removido para que outros operadores possam ser selecionados.
Mas, embora o operador seja removido não é desejável que os elementos no working memory criados por ele também sejam removidos. Neste caso não é desejado que os elementos J1 e J2 sejam removidos.
Para não eliminar todos os dados e entrar num loop infinito, o SOAR faz a distinção entre a persistência (persistence) dos elementos no working memory que foram criados ao aplicar as regras de um operador e aqueles elementos criados por outro tipo de regras.
Os elementos criados pelas regras de aplicação dos operadores devem permanecer porque eles permitem se movimentar a um novo estado no espaço do problema. Todos os outro tipo de regras calculam e manipulam elementos do estado atual sem modificá-lo e por tanto podem ser eliminadas quando elas não coincidam com o estado atual.
Soar classifica automaticamente as regras. Elas podem ser parte por um operador ou não. Uma regra é uma regra de aplicação de um operador se ela proba quem é o operador selecionado e modifica seu estado. Os elementos da working memory criados por este tipo de regras persistem e são chamados de ter operator-support, ou o-support. Porque eles são criador por um operador.
Para os elementos do working memory criados por regras que não faz parte da aplicação dum operador, como as regras da proposta do operador, regras que comparem operadores, regras que criem operadores o regras que criem estados, são eliminados do working memory se a regra já não corresponde. Esses elementos são chamados de instantiation-support ou i-support.
Water Jug State Elaboration
A estratégia de elaboração de estados é uma estratégia onde uma regra chamada regra de elaboração de estados (state elaboration rule) testa o estado atual e cria uma nova estrutura sobre ele.
A estratégia de elaboração de estados é muito utilizada nos programas de soar porque ela permite criar abstrações muito uteis a partir de outros elementos no working memory e representar eles diretamente no estado como um agregado a ele. Estes novos agregados podem ser testados em outras regras no lugar se usar combinações complexas de condições.
A state elaboration rule para o problema do WaterJug pode ser como sigue:
#water-jug*elaborate*empty
# If the state is named water-jug and a jug can hold volume v and currently has
# contents c, then add that it has v – c available (empty) space.
O codigo em soar é
sp {water-jug*elaborate*empty
(state <s> ^name water-jug
^jug <j>)
(<j> ^volume <v>
^contents <c>)
-->
(<j> ^empty (- <v> <c>))}
Water Jug Operator Proposals
As regras de proposição dos operadores em inglês são
# water-jug*propose*fill
# If the task is water-jug and there is a jug that is not full, then propose filling that jug.
# water-jug*propose*empty
# If the task is water-jug and there is a jug that is not empty, then propose emptying that jug.
# water-jug*propose*pour
# If the task is water-jug and there is a jug that is not full and the other jug is not empty, then propose pouring water from the second jug into the first jug.
A forma correta de escrever em SOAR a terceira condição seria
sp {water-jug*propose*pour
(state <s> ^name water-jug
^jug <i>
^jug { <j> <> <i> }) (1)
(<i> ^contents > 0)
(<j> ^empty > 0)
-->
(<s> ^operator <o> +)
(<o> ^name pour
^empty-jug <i>
^fill-jug <j>)}
(1) Devido a que neste operador é realizada a tarefa de levar água de um jarro para o outro tem que verificar que um dos jarros tenha espaço e o outro tenha água, para não correr o risco que o Soar pegue o mesmo jarro <i><j> é necessário adicionar uma condição para que o identificador de <i> e <j> sejam diferentes, para isso é usada a expressão {<j><><i>} que significa j diferente de i.
Para que Soar possa escolher entre dois o mais operadores de forma aleatória é possível agregar uma preferência (preference) como indiferente (indifferent) usando o carácter “=”, por exemplo:
sp {water-jug*propose*fill
(state <s> ^name water-jug
^jug <j>)
(<j> ^empty > 0)
-->
(<s> ^operator <o> +
^operator <o> =)
(<o> ^name fill
^fill-jug <j>)}
Operator Application
Nesta parte do processo vão-se criar as regras de aplicação dos três operadores. No primeiro caso, o caso do operador fill é necessário mudar o valor do atributo contents. Para cambiar valores nos atributos em Soar, é necessário eliminar o elemento da working memory e criar outro com o novo valor já que em Soar os valores dos atributos não podem ser modificados. Para eliminar um elemento do working memory basta com colocar o catacter “-” ao final do elemento. Por exemplo:
sp {water-jug*apply*fill
(state <s> ^name water-jug
^operator <o>
^jug <j>)
(<o> ^name fill
^fill-jug <j>)
(<j> ^volume <volume>
^contents <contents>)
-->
(<j> ^contents <volume>)
(<j> ^contents <contents> -)}
Igualmente é feito para os outros dois operadores.
A Ilustração 11 apresenta a saída do Soar até o momento.
Ilustração 11
Estado desejado
É necessária a criação de uma regra que determine qual é o estado desejado no problema. Nesse caso o estado desejado é que o jarro de 3 galões fique com um só galão, essa regra escrita em inglês é:
# water-jug*detect*goal*achieved
# If the task is water-jug and there is a jug with volume three and contents
# one, write that the problem has been solved and halt.
Na linguagem de Soar é:
sp {water-jug*detect*goal*achieved
(state <s> ^name water-jug
^jug <j>)
(<j> ^volume 3
^contents 1)
-->
(write (crlf) |The problem has been solved.|)
(halt)}
Com o novo estado a saída do Soar é como apresenta a Ilustração 12. O objetivo a sido atingido em 269 passos.
Ilustração 12
Pode-se fazer a procura mais eficiente criando regras que não permitam operações como vaziar um jarro se ele acabou dê-se encher ou verter 3 galões de água do jarro 5 ao 3 e depois verter 3 galões de água do jarro 3 ao 5. Para isso é necessário gravar o último estado. Apos acrescentar as regras necessárias é possível fazer a procura mais eficiente.
Finalmente foi obtida a saída do Soar como apresentada na Ilustração 13
Ilustração 13