SUMÁRIO
3. Experimentos e Resultados
3.1. Atividade 1 - Instalando o SOAR
3.1.1. Objetivos específicos
- Instalação do SOAR apropriado para a plataforma da estação de trabalho.
3.1.2. Desenvolvimento
O primeiro passo para instalação do SOAR é o download do arquivo compactado disponibilizado no site do SOAR. Para tal, deve ser feito o acesso à pagina do SOAR e clicar sobre o link da respectiva plataforma da estação de trabalho: Soar Tutorial for 64-bit Linux. A identificação da plataforma, uma vez que o sistema operacional utilizado é o GNU/Linux Ubuntu, pode ser feita através do comando uname -a, conforme mostrado na Figura 1.
Figura 1 - Resultado do comando uname -a para identificação da plataforma em Linux.Após a conclusão do download do arquivo, deve ser feita a descompactação do arquivo já que este é disponibilizado em formato zip. A descompactação do arquivo cria então uma árvore de diretórios que corresponde aos arquivos binários do SOAR e a sua documentação. A Figura 2 mostra a estrutura de diretórios depois da descompactação.
Figura 2 - Estrutura de diretório e arquivos do SOAR.
A partir deste ponto, o SOAR está instalado e pronto para uso.
3.1.3. Resultados
A instalação do SOAR foi realizada com sucesso na estação de trabalho. A Figura 3 mostra a execução do aplicativo VisualSoar, utilizado para desenvolvimento e depuração de produções.
Figura 3 - Tela principal do aplicativo VisualSoar.
3.2. Atividade 2: Construindo um Agente SOAR Simples usando Regras
3.2.1 Objetivos específicos
- Criar de um agente simples para entender o funcionamento do Soar;
- Estudar o programa Hello World utilizando as ferramentas disponibilizadas;
- Explorar o módulo da Memória de Trabalho do Soar;
- Explorar o mapeamento de entrada e saída com o ambiente externo.
3.2.2. Desenvolvimento
O desenvolvimento de um agente Soar pode ser feito com a ajuda de um depurador interativo chamado:Soar Debugger.Ao executar o aplicativo, a tela principal é exibida contendo os painéis de interação, depuração de produções, visualização de estados e execução de comandos, e uma agente padrão é criado (soar1) para depuração. A Figura 4 mostra a tela principal do Soar Debugger.
Figura 4 - Tela principal do aplicativo Soar Debugger.
Para o típico caso do HelloWorld, o Soar já disponibiliza um arquivo de código-fonte contendo instruções para disparo da regras que imprime o texto "HelloWorld" no Soar Debugger.
Para a execução deste agente, deve-se primeiro carregar o arquivo <SOAR_DIR>/Agents/hello-world-rule/hello-world-rule.soar (onde <SOAR_DIR> é o diretório de instalação do Soar). Em seguida, digitar o comando run no editor ou acionar o botão <Run> na barra de botões de comando (parte inferior esquerda da tela). Este comando fará com que o Soar crie automaticamente um estado inicial, dispare a regra, imprima a mensagem no console e termine a execução. A Listagem 1 mostra o código-fonte do programa. A Figura 5 mostra o resultado da execução do HelloWorld.
sp {hello-world
(state <s> ^type state)
-->
(write |Hello World|)
(halt)
}
Listagem 1 - Código-fonte do programa hello-world-rule.soar (Laird, 2012).
Figura 5 - Tela principal do aplicativo Soar Debugger.
Memória de Trabalho
A memória de trabalho do Soar é o local onde informações de curto prazo são armazenadas (Laird, 2012). É o local que contém todas as informações dinâmicas de um programa Soar em execução.
Estas informações podem ser dados de sensores, cálculos intermediários, operadores em uso e os objetivos. A memória de trabalho pode ser representada por um grafo, mais especificamente, um dígrafo (grafo direcionado). Os vértices representam dois tipos de conceitos: identificadores e constantes. Os identificadores são vértices não-terminais (que originam arcos), ao passo que as constantes são vértices terminais (não há arcos originando-se destes vértices). Os arcos representam os atributos. Todo agente Soar contém o conteúdo mínimo na memória de trabalho conforme a estrutura mostrada na Figura 6.
Figura 6 - Grafo da Memória de Trabalho de um agente Soar (Laird, 2012)
A memória de trabalho é formada por elementos individuais chamados de Working Memory Elements (WMEs). Os WMEs são triplas formadas por um identificador, um atributo e um valor, nesta ordem. Abaixo são listados os WMEs mínimos da memória de trabalho de um agente Soar padrão:
(S1 ^superstate nil)
(S1 ^io I1)
(S1 ^type state)
(I1 ^output-link I2)
(I1 ^input-link I3)
Uma coleção de WMEs que compartilham o mesmo identificador recebe o nome de objeto. Um objeto pode ser expresso baseado na notação de um identificador seguido de um conjunto de pares atributo-valor. Por exemplo:
(S1 ^io I1 ^superstate nil ^type state)
(I1 ^output-link I2 ^input-link I3)
Mecanismo de entrada e saída do Soar (Soar I/O)
Um agente Soar interage com o mundo externo através de mecanismos próprios de entrada e saída que o permitem receber informações do ambiente e atuar sobre ele de alguma forma. As entradas e saídas no Soar são realizadas através de funções de entrada e saída (Laird e Congdon, 2012). As funções de entrada são chamadas no início de cada ciclo de decisão, ao passo que as funções de saída são chamadas no final (chamadas apenas se mudanças foram feitas na estrutura da memória de trabalho).
As estruturas para manipulação de entrada e saída no Soar são ligadas a um objeto especial chamado de io. Este objeto tem subestruturas para representar entradas de sensores (input links) e para representar comandos de ações motoras (output links).
Geralmente, funções de entrada criam e removem WMEs nas subestruturas de entrada de modo a atualizar a percepção do ambiente. As funções de saída correspondem a valores das WMEs que aparecem nas subestruturas de saída e são convertidas em ações no ambiente. A Figura 7 mostra os vértices que representam as ligações de entrada e saída na memória de trabalho.
Figura 7 - Representação da estrutura de entrada e saída na memória de trabalho (Laird, 2012)Controle do ambiente Soar usando SML
Novos elementos podem ser adicionados ou removidos nas subestruturas de io para representar a conexão com mundo externo por outros programas. Esta ligação com o ambiente externo é descrita usando uma linguagem de marcação específica do Soar: a Soar Markup Language (SML Quick Start Guide, 2012).
Programas clientes são escritos utilizando a interface de programação especificada (SML API) de forma a manipularem informações de entrada e saída na memória de trabalho dos agentes. A Listagem 2 apresenta um programa escrito em Java que demonstra a conexão com uma instância de agente do Soar.
import java.io.IOException;
import sml.Agent;
import sml.Identifier;
import sml.Kernel;
import sml.smlUpdateEventId;
import sml.Kernel.UpdateEventInterface;
/**
* Very simple environment that adds a WME to the agent's input link with
* attribute "hello" and value "world" and then stops when any command from the
* agent is received.
*
* <p>
* There is little to no error handling performed in this example, do not write
* programs without error handling (more complete examples are below).
*
* <p>
* Also see the Soar code in src/main/resources/helloworld.soar
*
* <p>
* http://code.google.com/p/soar/wiki/HelloWorld
*
* @author voigtjr
*/
public class HelloWorld
{
/*
* The Kernel and Agent objects (along with input-link identifiers and some
* output-link stuff) are usually cached on the object.
*/
private Kernel kernel;
private Agent agent;
public HelloWorld()
{
/*
* The first thing to do is to create the Soar kernel. The Soar kernel
* is essentially a container for Soar agents and a way to interface
* with the system in general as opposed to only a specific agent. Most
* systems only use one agent so this might seem confusing or
* unnecessary at first.
*/
System.out.println("Creating Kernel...");
kernel = Kernel.CreateKernelInNewThread();
/*
* The name of the function CreateKernelInNewThread is a bit
* misleading--Soar itself is single-threaded. See the ThreadsInSML
* document for details.
*
* The kernel object is used to create agents:
*/
System.out.println("Creating Agent...");
agent = kernel.CreateAgent("soar");
/*
* Once the agent is created it must be given Soar productions to run.
*/
if (!agent.LoadProductions("helloworld.soar")) {
System.err.println("Can't load helloworld.soar");
System.exit(1);
}
/*
* Next, the environment sets up communication with the agent. In this
* simple example, it is just going to put a WME on the input-link (I2
* ^hello world):
*/
System.out.println("Creating input link...");
agent.GetInputLink().CreateStringWME("hello", "world");
/*
* The environment now needs to register an event handler that fires
* after all agents (only one agent in this case) proceed past their
* output phases. The environment can check the output-link for commands
* and act on them during this event.
*/
System.out.println("Registering for update event...");
kernel.RegisterForUpdateEvent(
smlUpdateEventId.smlEVENT_AFTER_ALL_OUTPUT_PHASES,
new UpdateEventInterface()
{
public void updateEventHandler(int eventID, Object data,
Kernel kernel, int runFlags)
{
System.out.println("Update event...");
/*
* An output-link command is simply an identifier child
* on the output-link. Here, we iterate through all
* commands.
*/
for (int index = 0; index < agent.GetNumberCommands(); ++index)
{
Identifier command = agent.GetCommand(index);
String name = command.GetCommandName();
System.out.println("Received command: " + name);
kernel.StopAllAgents();
command.AddStatusComplete();
}
/*
* After iterating, the changes must be cleared so that
* the commands we already saw do not show up on future
* decision cycles.
*/
agent.ClearOutputLinkChanges();
System.out.println("Update event complete.");
}
}, null);
/*
* This simple environment tells Soar to stop when any command is
* received. In a more complex environment, different commands would be
* tested for and command parameters read off of the output link.
*
* Note that the output-link also serves as a mini-input link, usually
* in the form of "status" WMEs. There are two helper functions for
* simple versions of this, Identifier.AddStatusComplete() and
* Identifier.AddStatusError() which add ^status complete or ^status
* error on to the command identifier. This kind of feedback makes it
* easy for the agent to tell if the environment has received the
* command.
*/
System.out.println("Running agents...");
kernel.RunAllAgents(5);
/*
* With the productions, input and output set up, Soar executes for five
* decision cycles in this example. Normally, you would tell Soar to run
* forever, but it is capped at five in this example so that it will
* quickly fail if the agent doesn't perform as expected.
*
* Note that all execution commands (such as RunAllAgents and
* RunAllAgentsForever) block until the requested number of decision
* cycles are completed or Soar is interrupted. This means that if your
* environment needs to be running at the same time as Soar, that you
* need to spawn a thread for either your environment or for Soar. Note
* that Soar is single-threaded internally, meaning that--as long as you
* created the kernel locally--the events are fired in the same thread
* that calls run.
*
* Once the run has completed, it is time to shut down
*/
System.out.println("Shutting down...");
kernel.DestroyAgent(agent);
agent = null;
kernel.Shutdown();
kernel = null;
System.out.println("Done.");
}
public static void main(String[] args)
{
new HelloWorld();
}
}
Listagem 2 - Exemplo de código de interação com o ambiente Soar (SML Quick Start Guide, 2012)
3.2.3. Resultados
Neste experimento foi colocado em operação um agente Soar padrão usando a ferramenta de depuração do Soar (Soar Debugger). Especificamente, para entender como opera o mecanismo de depuração, o programa hello-world-rule.soar foi carregado e executado na ferramenta. A saída provida no log foi analisada para este exemplo de modo a identificar as mensagens padrões geradas pela ferramenta baseadas no ciclo de decisão do agente.
O aspecto mais relevante do experimento foi o entendimento de como as regras, ainda que simples, devem ser criadas no Soar além das estruturas e elementos automaticamente criados e mantidos pela arquitetura em tempo de execução.
Através da análise do código-fonte do exemplo foi possível:
- Entender incialmente a gramática utilizada pelo Soar para declaração de regras (produções);
- Identificar as partes requeridas para a composição de regras (condições e ações);
- Inserção de comentários em programas Soar.
Através da análise feita durante a execução do programa, no contexto e limitações do programa de exemplo, foi possível:
- Entender o conceito de memória de trabalho e a sua manipulação por parte da arquitetura;
- Entender o conceito dos elementos de memória de trabalho (a tripla: identificador, atributo e valor);
- Entender o mecanismo de entrada e saída do Soar (interação com o ambiente externo) e sua representação na memória de trabalho.
Por fim, pode-se afirmar que os conhecimentos obtidos durante este experimento permitem a criação de programas Soar baseados em regras simples e o uso da ferramenta Soar Debugger para fins de execução e depuração destes programas.
3.3. Atividade 3 - Agentes Simples utilizando Operadores
3.3.1. Objetivos específicos
- Explorar o uso de operadores no Soar;
- Explorar a ferramenta de edição VisualSoar.
3.3.2. Desenvolvimento
Os operadores são mecanismos para execução de ações. A operação básica do Soar é baseada em ciclos de decisão que continuamente propõe, selecionam e aplicam operadores. Estas atividades são executadas pela própria arquitetura a partir de regras definidas no programa e permitem a implementação do processo de tomada de decisão por parte do Soar. A Figura 8 mostra o ciclo de operação do Soar baseado em operadores.
Figura 8 - Ciclo de operação do Soar usando operadores (Laird, 2012)
Para constatarmos como os operadores funcionam, o experimento foi executado usando o Soar Debugger. Primeiro, o arquivo <SOAR_DIR>/Agents/hello-world-rule/hello-world-operator.soar foi carregado. Em seguida, o nível de depuração foi definido para 5 (Watch 5). Por último, o programa foi executado acionando o botão <Run> na barra de botões de comando (parte inferior esquerda da tela). A Listagem 3 mostra o código-fonte do programa hello-world-operator.soar.
sp {propose*hello-world
(state <s> ^type state)
-->
(<s> ^operator <o> +)
(<o> ^name hello-world)
}
sp {apply*hello-world
(state <s> ^operator <o>)
(<o> ^name hello-world)
-->
(write |Hello World|)
(halt)
}
Listagem 3 - Código-fonte do programa hello-world-operator.soar (Laird, 2012).
Ao executarmos o programa, o Soar cria automaticamente um estado inicial e começa o ciclo de decisão. De acordo com o programa da Listatem 3, os seguintes passos são executados:
- o operador propose*hello-world é proposto devido ao fato de que a condição <s> ^type state é satisfeita no estado inicial (WME é criado na memória de trabalho: <On ^name hello-world >, onde n é um número arbitrário gerado pelo Soar);
- o Soar seleciona o operador hello-world uma vez que ele foi único proposto neste ciclo;
- o Soar então aplica o operador executando as ações da regra apply*hello-world dado que as suas condição são satisfeita.
A Figura 9 mostra a execução do programa hello-world-operator.
Figura 9 - Execução do programa hello-world-operator.soar
A Listagem 4 mostra, em detalhes, a saída da depuração do programa. É possível observar a transição entre as fases de proposta, decisão e aplicação de operadores. Note ainda a existência do atributo criado pelo Soar para representar o operador hello-world (O1, no caso).
--- input phase ---
--- propose phase ---
Firing propose*hello-world
-->
(O1 ^name hello-world +)
(S1 ^operator O1 +)
=>WM: (15: S1 ^operator O1 +)
=>WM: (14: O1 ^name hello-world)
--- decision phase ---
=>WM: (16: S1 ^operator O1)
1: O: O1 (hello-world)
--- apply phase ---
--- Firing Productions (IE) For State At Depth 1 ---
Firing apply*hello-world
-->
Hello World
--- Change Working Memory (IE) ---
Interrupt received.
This Agent halted.
An agent halted during the run.
Listagem 4 - Depuração do programa hello-world-operator no Soar Debugger.
A seção a seguir descreve a etapa do experimento que relata o uso da ferramenta VisualSoar.
Visual Soar
O VisualSoar é uma ferramenta para criação e edição de programas Soar. Ele traz consigo uma estrutura interna baseada em árvore para suporte à gerência e visualização de arquivos de regras. Ele conta também com templates para geração de regras para proposta e aplicação de operadores.
No VisualSoar é possível também visualizar o conteúdo da memória de trabalho através de uma estrutura chamada de Datamap. A Figura 10 mostra a tela do VisualSoar com a estrutura do programa water-jug. A Figura 9 mostra a execução do programa hello-world-operator.
Figura 10 - Tela principal do VisualSoar
Como pode ser visto na Figura 10, o VisualSoar fornece uma maneira estruturada para o gerenciamento de arquivos de código-fonte de programas Soar, com verificação da consistência das regras. Estas facilidades podem representar vantagens no uso desta ferramenta no durante o desenvolvimento de programas Soar.
3.3.3. Resultados
Neste experimento foi explorado o mecanismo de operadores do Soar a partir do programa hello-world-rule.soar. O programa foi carregado e executado corretamente sendo possível a identificação do operador durante a depuração. Durante a execução, foi possível ainda verificar as fases envolvidas no processamento de operadores: proposta, seleção e aplicação.
Através da análise do código-fonte do exemplo foi possível:
- Entender a gramática utilizada pelo Soar para declaração de operadores e a preferência aceitável;
- Entender a concepção de regras para a proposta e a aplicação de operadores.
Através da análise feita durante a execução do programa foi possível:
- Entender o conceito de operadores e a sua manipulação por parte da arquitetura;
- Entender a relação de implicação entre as fases de proposta, seleção e aplicação de operadores;
- Entender o ciclo contínuo de decisão do Soar.
O aspecto mais relevante do experimento foi entender de como os operadores atuam no Soar e como eles são estruturas poderosas para a implementação de mecanismos de decisão. O uso de regras com operadores tem as seguintes vantagens:
- Clara separação entre quando as decisões estão sendo tomadas e quando ações estão sendo executadas;
- A possibilidade de vários operadores serem propostos para uma mesma ação com graus de preferência ou indiferença diferenciados para cada um deles;
- A oportunidade de atuação de um processo de decisão mais sofisticado implementado na arquitetura;
Por fim, pode-se afirmar que os conhecimentos obtidos com este experimento permitem a criação de programas Soar que trabalhem com a manipulação de estados e operadores, contando com o auxílio da ferramenta VisualSoar para um melhor gerenciamento do processo de desenvolvimento destes programas.
3.4. Atividade 4 - Criando um Agente para o Water Jug Problem
3.4.1. Objetivos específicos
- Estudar o problema clássico da IA conhecido como Jarros d'Água (Water Jug) e como representá-lo no Soar;
- Explorar o mecanismo de persistência de elementos da memória de trabalho;
- Explorar o processo de elaboração de estado;
- Explorar o mecanismo de proposição e aplicação de operadores;
- Explorar o monitoramento de estados e operadores;
- Entender o reconhecimento do estado desejado (meta);
- Entender o mecanismo de controle de busca.
3.4.2. Desenvolvimento
O problema dos Jarros d'Água é um problema combinacional clássico da Inteligência Artificial que explora a elaboração de estratégias de busca em um espaço de estados. Há diversas variações propostas do problema, no entanto, para este experimento será considerado o seguinte enunciado:
Há dois jarros d'água, um com capacidade para 5 litros de água e outro com capacidade para 3 litros. Há também um poço do qual se pode retirar água para encher o jarro por completo. É possível também esvaziar um jarro ou passar a água de um jarro para outro. O objetivo é deixar o jarro de 3 litros com 1 litro de água.
Figura 11 - O problema dos jarros d'água
A representação do problema deve contemplar os seguintes passos:
- Definir um espaço de estados com todas as combinações possíveis;
- Definir o estado inicial;
- Definir o estado final;
- Especificar um conjunto de regras que descrevem as ações possíveis;
- Especificar uma base de conhecimentos que contenham informações sobre o problema;
- Especificar uma estratégia de controle (busca de soluções).
Para resolver este problema no Soar, foram criadas regras que definem o estado inicial, o estado desejado (meta), os operadores para as ações possíveis (encher, esvaziar e passar a água de um jarro para outro) e algumas heurísticas para maior eficiência na busca pela solução. A representação essencial do problema é feita através de estados e operadores que são manipulados na memória de trabalho.
Persistência de WMEs
Durante a operação de um programa Soar, WMEs criados durante a seleção e aplicação de operadores são armazenados na memória de trabalho assim como as próprias instâncias dos operadores. É necessário um mecanismo capaz de atuar de acordo com critérios de validade destes elementos de forma a não esgotar a memória de trabalho.
A persistência de WMEs no Soar é implementada de forma a remover os operadores que não são mais pertinentes na memória de trabalho depois de um ciclo de decisão. Em outras palavras, as estruturas criadas pelo operador selecionado durante a fase de aplicação devem ser avaliadas de modo a se decidir quais WMEs devem ser persistidas e quais devem ser descartadas.
As regras que testam o operador selecionado e modificam o estado atual são chamadas de regras de aplicação de operador. Os WMEs criados por estas regras são persistidas (mantidas) na memória de trabalho porque são criados como parte da aplicação um operador, ou seja, são ditos ter suporte de operador (operator-support ou o-support). Estes WMEs permancem, por concepção da arquitetura, na memória e só podem ser removidos por outros operadores (rejeição) ou caso eles sejam desconectados do estado (nos casos de remoção de outros WMEs aos quais eles estão associados).
Para os outros tipos de regras (regras que não são de aplicação de operador, incluindo regras que propõe um operador, regras que comparam operadores, regras que elaboram operadores ou regras que elaboram estados), os WMEs são removidos assim que as regras que os criaram não mais satisfaçam as condições. Estes WMEs são ditos ter suporte de instanciação (instantiation-support ou i-support), o que significa que eles são mantidos na memória apenas se as regras que os criaram ainda são válidas.
Este é um importante mecanismo do Soar para manter as alterações feitas nos estados durante os passos no espaço de problemas. Sem ele, não seria possível memorizar os resultados de eventos anteriores que são úteis ao longo do processo de solução do problema.
Elaboração de Estados
Para iniciar a busca por soluções, é necessário que um estado inicial seja definido na memória de trabalho. A partir dele, novos passos no espaço de problemas modificarão o estado atual originando estruturas de extensão nos estados. Este ciclo perdura até que o programa consiga atingir o seu estado desejado (meta).
A elaboração de estados é feita através de regras que adicionam ou removam WMEs na memória de trabalho. Tais ações tem implicações nos ciclos contínuos de elaboração, decisão e aplicação de operadores e são feitas de acordo com os propósitos para solução do problema.
Para definir o estado inicial, por convenção, é recomendado que seja proposto por um operador de inicialização que verifica a existência de um WME específico: o nome da tarefa. Este elemento deve significar que há um problema sendo tratado e que o estado inicial já está elaborado. A Listagem 5 mostra a regra de elaboração do estado inicial do programa Water-Jug.
# Operator that initializes the water jug task
# initialize-water-jug
# If no task is selected,
# then propose the initialize-water-jug operator.
sp {water-jug*propose*initialize-water-jug
(state <s> ^superstate nil
-^name)
-->
(<s> ^operator <o> +)
(<o> ^name 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.name initialize-water-jug)
-->
(<s> ^name water-jug
^jug <i> <j>
^steps <s>)
(<s> ^count 0)
(<i> ^volume 3
^contents 0)
(<j> ^volume 5
^contents 0)}
Listagem 5 - Regra de elaboração do estado inicial no programa Water-Jug (Laird, 2012)
O operador initialize-water-jug será o único operador proposto e, portanto, selecionado para aplicação logo no início do programa.
Proposição de Operadores
Para viabilizar a ativação do processo de decisão, é necessário que operadores sejam propostos e que um deles seja escolhido para aplicação durante um ciclo de decisão. O Soar define uma notação particular para a proposição de operadores que é colocada na parte das ações de uma produção. A Listagem 6 mostra um exemplo de proposta de operador do programa Water-Jug.
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>)}
Listagem 6 - Exemplo de regra para proposição de um operador no programa Water-Jug (Laird, 2012).
Há situações em que o processo de decisão não tem conhecimento suficiente para determinar a aplicação de um operador específico quando mais de um foi proposto. Estas situações de conflito são conhecidas como impasse.
Para anular impasses durante a execução é recomendada a definição de critérios de preferência de operadores. A indicação de preferências por operadores serão observadas no processo de decisão. Elas significam se um determinado operador é aceitável e qual o critério de comparação com outros operadores propostos. Se nenhuma preferência por seleção é necessária, pode-se indicar a indiferença na escolha do operador, levando a uma escolha aleatória por parte da arquitetura.
Aplicação de Operadores
Depois de selecionado o operador, inicia-se a fase de aplicação em que regras são disparadas de forma a modificar os estados. Estas mudanças na memória de trabalho farão com que novas regras satisfaçam as condições e sejam disparadas, propondo eventualmente novos operadores, e assim por diante. O Soar permite a aplicação de apenas um operador por ciclo, o que impõe, de certa forma, um limite de trabalho por ciclo. A Listagem 7 mostra um exemplo de regra para aplicação de um operador do programa Water-Jug.
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> -)}
Listagem 7 - Exemplo de regra para aplicação de um operador no programa Water-Jug (Laird, 2012).
A execução da regra da water-jug*apply*fill altera o valor do atributo ^contents do identificador <j> conforme o valor de <volume>.
Monitoramento de Estados e Operadores
No Soar, os estados e os operadores são as estruturas básicas da arquitetura. Para um melhor acompanhamento do processamento dos ciclos de decisão, as estruturas da memória de trabalho podem ser monitoradas. O monitoramento deve ser feito também através de regras. Uma vez que tais as regras satisfaçam as condições, é possível mostrar estados e operadores através, por exemplo, da impressão do valor de seus atributos em tela. A Listagem 8 mostra exemplos de regras para monitoramento dos estados no programa Water-Jug.
sp {water-jug*monitor*state
(state <s> ^name water-jug
^jug <j> <i>)
(<j> ^volume 5 ^contents <jcon>)
(<i> ^volume 3 ^contents <icon>)
-->
(write (crlf) | 5:| <jcon> | 3:| <icon> )}
sp {water-jug*monitor*operator-application*empty
(state <s> ^name water-jug
^operator <o>)
(<o> ^name empty
^empty-jug.volume <volume>)
-->
(write (crlf) | EMPTY(| <volume> |)|)}
sp {water-jug*monitor*operator-application*fill
(state <s> ^name water-jug
^operator <o>)
(<o> ^name fill
^fill-jug.volume <volume>)
-->
(write (crlf) | FILL(| <volume> |)|)}
sp {water-jug*monitor*operator-application*pour
(state <s> ^name water-jug
^operator <o>)
(<o> ^name pour
^empty-jug <i>
^fill-jug <j>)
(<i> ^volume <ivol> ^contents <icon>)
(<j> ^volume <jvol> ^contents <jcon>)
-->
(write (crlf) | POUR(| <ivol> |:| <icon> |,| <jvol> |:| <jcon> |)|)}
Listagem 8 - Exemplos de regras para monitoramento de estados no programa Water-Jug (Laird, 2012).
O disparo destas regras provocam a impressão dos estados no log de operação do Soar Debugger.
Reconhecimento de Estado Desejado
Para que a busca por soluções no espaço de problemas seja concluída, é necessário definir a regra que representa o estado desejado, ou seja, a meta a ser atingida. A Listagem 9 mostra o estado desejado para o problema Water-Jug.
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)}
Listagem 9 - Regra que define o estado desejado no programa Water-Jug (Laird, 2012).
A meta para o enunciado é fazer com que o jarro de capacidade para três galões de água esteja com um galão de água. Caso este estado seja atingido, pode-se assumir que o problema está resolvido e o programa pode encerrar.
Controle de Busca
A busca por soluções no espaço de estados pode ser realizada de maneira aleatória, a critério da arquitetura e sem interferência por parte do designer. Entretanto, esta estratégia pode levar a um número elevado de passos para atingir o estado desejado, evidenciando assim a ineficiência da operação.
As estratégias gerais de busca no espaço de estados podem ser pautadas por algoritmos conhecidos como a busca em largura (amplitude) ou profundidade, subida de encosta (Hill-Climbing), melhor escolha, A* etc. Para o Soar, devem ser criadas regras que representem heurísticas gerais formuladas a partir do problema. O uso de heurísticas torna a busca por soluções mais eficiente se comparada com a busca aleatória. A Listagem 10 mostra a codificação de regras que representam algumas heurísticas possíveis para o problema Water-Jug.
sp {water-jug*select*operator*avoid*inverse*fill
(state <s> ^name water-jug
^operator <o> +
^last-operator <lo>)
(<o> ^name fill ^fill-jug <i>)
(<lo> ^name empty ^empty-jug <i>)
-->
(<s> ^operator <o> <)}
sp {water-jug*select*operator*avoid*inverse*empty
(state <s> ^name water-jug
^operator <o> +
^last-operator <lo>)
(<o> ^name empty ^empty-jug <i>)
(<lo> ^name fill ^fill-jug <i>)
-->
(<s> ^operator <o> <)}
sp {water-jug*select*avoid*inverse*pour
(state <s> ^name water-jug
^operator <o> +
^last-operator <lo>)
(<o> ^name pour ^fill-jug <j>)
(<lo> ^name pour ^empty-jug <j>)
-->
(<s> ^operator <o> <)}
Listagem 10 - Regras que representam heurísticas no programa Water-Jug (Laird, 2012).
As regras da Listagem 8 apresentam heurísticas baseadas na ordem de aplicação dos operadores que, no domínio do problema, representam a tentativa de evitar a repetição da mesma ação realizada no instante anterior.
3.4.3. Resultados
Neste experimento foi executado o programa para solução do programa dos Jarros d'água (Water Jug). A Listagem 11 mostra a os resultados da execução do programa Water-Jug.
source {C:\dev\Soar\Agents\water-jug-simple\water-jug.soar}
***************
Total: 15 productions sourced.
run
1: O: O1 (initialize-water-jug)
5:0 3:0
2: O: O3 (fill)
FILL(5)
5:5 3:0
3: O: O5 (empty)
EMPTY(5)
5:0 3:0
4: O: O2 (fill)
FILL(3)
5:0 3:3
5: O: O8 (empty)
EMPTY(3)
5:0 3:0
6: O: O9 (fill)
FILL(3)
5:0 3:3
7: O: O6 (fill)
FILL(5)
5:5 3:3
8: O: O12 (empty)
EMPTY(5)
5:0 3:3
9: O: O13 (fill)
FILL(5)
5:5 3:3
10: O: O15 (empty)
EMPTY(5)
5:0 3:3
11: O: O16 (fill)
FILL(5)
5:5 3:3
12: O: O18 (empty)
EMPTY(5)
5:0 3:3
13: O: O11 (empty)
EMPTY(3)
5:0 3:0
14: O: O21 (fill)
FILL(3)
5:0 3:3
15: O: O23 (empty)
EMPTY(3)
5:0 3:0
16: O: O24 (fill)
FILL(3)
5:0 3:3
17: O: O26 (empty)
EMPTY(3)
5:0 3:0
18: O: O27 (fill)
FILL(3)
5:0 3:3
19: O: O28 (pour)
POUR(3:3,5:0)
POUR(3:0,5:3)
5:3 3:0
20: O: O33 (pour)
POUR(5:3,3:0)
POUR(5:0,3:3)
5:0 3:3
21: O: O37 (pour)
POUR(3:3,5:0)
POUR(3:0,5:3)
5:3 3:0
22: O: O38 (empty)
EMPTY(5)
5:0 3:0
23: O: O42 (fill)
FILL(5)
5:5 3:0
24: O: O44 (empty)
EMPTY(5)
5:0 3:0
25: O: O40 (fill)
FILL(3)
5:0 3:3
26: O: O45 (fill)
FILL(5)
5:5 3:3
27: O: O47 (empty)
EMPTY(3)
5:5 3:0
28: O: O49 (fill)
FILL(3)
5:5 3:3
29: O: O48 (empty)
EMPTY(5)
5:0 3:3
30: O: O51 (empty)
EMPTY(3)
5:0 3:0
31: O: O52 (fill)
FILL(5)
5:5 3:0
32: O: O54 (fill)
FILL(3)
5:5 3:3
33: O: O56 (empty)
EMPTY(5)
5:0 3:3
34: O: O59 (pour)
POUR(3:3,5:0)
POUR(3:0,5:3)
5:3 3:0
35: O: O62 (fill)
FILL(3)
5:3 3:3
36: O: O64 (pour)
POUR(3:3,5:3)
POUR(3:1,5:5)
5:5 3:1
The problem has been solved.
Interrupt received.
This Agent halted.
An agent halted during the run.
Listagem 11 - Resultado da execução do programa water-jug.
O aspecto mais relevante do experimento foi o entendimento de como deve ser feita a formulação do problema e a sua representação na forma de produções no Soar. Estas regras orientam o agente instanciado pela arquitetura de modo que ele possa conduzir a busca por soluções percorrendo o espaço de problemas levando em conta o uso de heurísticas para otimização.
Através da análise do código-fonte do exemplo foi possível:
- Entender como devem ser criadas as regras de elaboração de estados;
- Entender como devem ser criadas as regras de proposição e aplicação de operadores;
- Entender como formular a regra para determinação do estado desejado;
- Entender como formular heurísticas.
Através da análise feita durante a execução do programa foi possível:
- Entender como o ciclo de decisão é executado para um passo no espaço de problemas;
- Entender a dinâmica de seleção de operadores a partir de indicações de preferência;
- Entender como o monitoramente deve ser implementado para facilitar a depuração de estados intermediários;
- Entender como as heurísticas são aplicadas na otimização de busca por soluções.
Por fim, pode-se afirmar que os conhecimentos obtidos durante estes experimentos comprovam o entendimento de como um domínio de problema deve ser formulado e representado na forma de produções no Soar.
4. Conclusão
O Soar é um sistema de produção baseado em símbolos físicos fundamentado no conceito de busca em espaços de problemas.
Assim como os sistemas de processamento de regras convencionais e sistemas especialistas, ele se baseia em regras de produção, memória de trabalho e interpretador de regras (motor de inferência). Ele apresenta, adicionalmente, uma estrutura de controle deliberaiva em dois níveis (seleção e aplicação de operadores) e um mecanismo de resolução de conflitos baseado na preferência por operadores.
O Soar difere dos sistemas de processamento de regras convencionais e sistemas especialistas no tocante à aprendizagem, feita por um mecanismo de aprendizagem contínua determinada por solução de impasses. A dinâmica desta mecanismo é fundamentada na detecção de conflitos, situação em que a arquitetura automaticamente inicia a criação de novos sub-estados no contexto da memória de trabalho. Forma-se então uma hierarquia de metas e sub-estados de forma que a solução de sub-problemas levam, no limite, a solução do impasse naquele contexto. Este novo conhecimento é incorporado pela arquitetura de modo a evitar novas situações de impasse equivalentes.
Os experimentos descritos e realizados aqui produziram resultados relevantes que colaboraram em grande medida para compreensão e aquisição de novos conhecimentos sobre o Soar, mesmo ainda sem considerar aspectos avançados da arquitetura. Estes conhecimentos já podem ser aplicados na construção de uma mente artificial, ainda que primitiva de certa forma, para o controle da criatura artificial do mundo virtual WorldServer3D.
5. Referências Bibliográficas
Laird, John E. (2012). The Soar 9 Tutorial. Universidade de Michigan, Michigan. Disponível em: <http://web.eecs.umich.edu/~soar/downloads/Documentation/SoarTutorial/Soar%20Tutorial%20Part%201.pdf>. Acesso em: 8 mar 2013.
Laird, John E. e Congdon, Clare B. (2012). The Soar User's Manual Version 9.3.2. Universidade de Michigan, Michigan. Disponível em: <http://web.eecs.umich.edu/~soar/downloads/Documentation/SoarManual.pdf>. Acesso em: 10 mar 2013.
SML Quick Start Guide (2012). Getting Started with SML Integration. Disponível em: <http://code.google.com/p/soar/wiki/SMLQuickStartGuide>. Acesso em: 12 mar 2013.