You are here

Aula 11 - Clarion: Controlando o WorldServer3D

Aula 11 - Clarion: Controlando o WorldServer3D.

 

Atividade 1

Esta aula, irá estudar e experênciar a utilização da arquitetura cognitiva artificial CLARION como um sistema de controle em um ambiente virtual. Para execução desta aula, será utilizado os seguintes códigos fontes de softwares:

  • DemoCLARION: Software controlador (Mente Artificial) utilizando CLARION.
  • WorldServer3D: Ambiente Virtual.

 

1.1 - DemoClarion e o Clarion.

O DemoCLARION é um software que implementa o conceito de mente artificial que controla Animats presente no ambiente virtual proposto pelo software WorldServer3D. Ele utiliza-se da arquitetura cognitiva artificial denominada de CLARION, que foi desenvolvida pelo Professor Run Son na Universidade de Columbia em Missouri - EUA. Resumidamente, o CLARION serve como controlador do Animat no mundo virtual.
O Clarion apresenta dois tipos de cognição que são nas formas Implícita e Explícita. A Explicita é projetada/executada através de Regras de Produção e Chunk das mesmas, produzindo assim um resultado esperado daquela regra selecionada. A Implícita é projeta/executada através da utilização das Redes Neurais Artificiais que por sua vez apresentam um treinamento supervisionado através das tecnicas Hopfield e BackPropagation. Desta forma, o Clarion apresenta estrutura capaz de gerar comportamento através do "Action Centered Subsystem" e conhecimento através do "Non-Action Centered Subsystem" que podem ser das cognição Explícita ou Implícita.

 

1.2 - Implementação do Controle de Leaflet.

Inicialmente, a classe principal do ClarionDEMO é a classe "Main". Portanto, nesta classe onde gera-se os objetos referente à comunicação(WorldServer) com o WorldServer3D, ClarionAgent que é o objeto que implementa a mente artificial usando Clarion. Além das implementações presentes no ClarionDEMO, necessitou-se de desenvolver uma "Thread" para gerar "Thing" dentro no WorldServer3D, que no caso são objetos do tipo "Foods" e "Jewels". Esta é executada a cada 30 segundos, onde gera os objetos através dos métodos seguir: "generateJewels()" e "generateJewels()". Os valores referente á quantidade de objetos e a posição dos objetos são determinados de forma randômica como é demonstrado no código fonte abaixo.


private void Run(object obj){
	while (true) {
		this.generateJewels ();
		this.generateFoods ();
		Thread.Sleep (30000);
	}
}

private void generateJewels(){
	Random random = new Random ();
	int count = random.Next (5, 15);
	for (int i=0; i < count; i++) {
	        int posX = random.Next (100, 700);
	        int posY = random.Next (100, 500);
		worldServer.NewJewel (random.Next (0, 6), posX, posY);
	}

}

private void generateFoods(){
	Random random = new Random ();
	int count = random.Next (1, 5);
	for (int i=0; i < count; i++) {
		int posX = random.Next (100, 700);
		int posY = random.Next (100, 500);
		worldServer.NewFood(1, posX, posY);
	}

}

O método "Run()" demonstrado a cima é a implementação de uma "Thread" que é executada, que por sua vez, gera os objetos "Food" e "Jewel". Pose-se observar que tanto no método "generateFoods()" quanto no "generateJewels()" ambos apresentam a posições randômica dos objetos no WorldServer3D, que são de 100 até 700 para o eixo "X" e de 100 até 500 para o eixo "Y". Isso ocorre porque no WorldServer3D o mapa do ambiente é de 800 por 600 e além do tamanho que restringe as posições dos objetos, o mapa apresenta muros que são gerados na execução do ClarionDEMO. Por padrão, existe também um numero randômico de objetos presentes em cena que para o objeto do tipo "Food" é de 1 até 5 já para objetos do tipo "Jewels" é uma quantidade que varia entre 5 á 15 "Jewels" geradas por ciclo na "Thread" Run. Continuando na Classe "Main", necessitou-se da implementação para novas ações que agente Clarion pode tomar são: DO_NOTHING, ROTATE, MOVE_THING e GET_THING. Essas constantes de decisões representam as possibilidades das tomadas de decisão que o agente pode resultar de seu processo de cognição. Pode-se perceber que para ações a serem executadas utiliza-se do enum "ClarionAgentActionType" que está dentro na classe "ClarionAgent".


void agent_OnNewExternalActionSelected(ClarionAgentActionType externalAction)
{   Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
	if (worldServer != null && worldServer.IsConnected)
	{
		switch (externalAction)
		{
			case ClarionAgentActionType.DO_NOTHING:
				agent.Abort (true);
			break;
			case ClarionAgentActionType.ROTATE:
				double newprad = prad + Math.PI/1000;
				while (newprad > Math.PI) newprad -= 2 * Math.PI;
				while (newprad < - Math.PI) newprad += 2 * Math.PI;
				worldServer.SendSetAngle(creatureId, 0, 0, newprad);
			break;

			case ClarionAgentActionType.MOVE_THING:
				worldServer.SendSetGoTo(creatureId, 1, 1, agent.getCurrentThing().X1, agent.getCurrentThing().Y1);
			break;

			case ClarionAgentActionType.GET_THING:
				if(agent.getCurrentThing().CategoryId == Thing.CATEGORY_JEWEL)
					worldServer.SendSackIt(creatureId, agent.getCurrentThing().Name);
				else
					worldServer.SendEatIt(creatureId, agent.getCurrentThing().Name);
			break;

			default:
			break;
		}
	}
}

No código demonstrado acima apresenta ações como:

  • DO_NOTHING: Determina o fim do agente.
  • ROTATE: Determina a ação de rotacionar o agente.
  • MOVE_THING: Determina a ação de mover o agente até o objeto.
  • GET_THING: Determina a ação de capturar um objeto.

As modificações fundamentais foram desenvolvidas na classe "ClarioAgent". Nas primeiras linhas esta a definição do enum "ClarionAgentActionType", que é utilizado pela classe "Main" como demonstrado anteriormente. Em seguida, são definadas constantes para determinar as dimensões das entradas sensorial do Clarion, juntamente com o obejeto Sack. Mais adiante estão as declarações das entradas sensoriais e as respectivas saídas, conforme o código abaixo.


public enum ClarionAgentActionType
{
	DO_NOTHING,
	ROTATE,
	MOVE_THING,
	GET_THING
}

public class ClarionAgent
{
	#region Constants
	private String SENSOR_VISUAL_DIMENSION = "VisualSensor";
	private String ROTATE_MOVE_THING = "MoveRotateThing";
	private String GET_THING = "GetThing";
	private String NOTHING = "Nothing";
	private Thing targetThing = null;
	#endregion

..........
       
        public Sack sack;

..........
        
        #region Perception Input
	private DimensionValuePair InputNothing;
	private DimensionValuePair InputGetThing;
	private DimensionValuePair InputMoveRotateThing;                
	#endregion

	#region Action Output
	private ExternalActionChunk OutputNothing;
	private ExternalActionChunk OutputRotateClockwise;
	private ExternalActionChunk OutputMoveThing;
	private ExternalActionChunk OutputGetThing;
	#endregion

..........

}

Já no construtor da classe, foram instanciados o agente, as entradas e as saidas do agente Clarion juntamente com a Thread que dá o inicio do processo de treinamento e resposta do agente. Resumidamente o treinamento é um processo de apresentação das entradas sensoriais para o agente, consequentemente um processo adaptativo/supervisionado de saidas desejadas e obtenção da resposta correspondente as entradas corrente. Segue o código fonte do construtor da classe.


public class ClarionAgent
{
  ........
  #region Constructor
   public ClarionAgent()
   {
   // Initialize the agent
   CurrentAgent = World.NewAgent("Current Agent");
   
   // Initialize Input Information
   InputNothing = World.NewDimensionValuePair(SENSOR_VISUAL_DIMENSION, NOTHING);
   InputMoveRotateThing = World.NewDimensionValuePair(SENSOR_VISUAL_DIMENSION, ROTATE_MOVE_THING);
   InputGetThing = World.NewDimensionValuePair(SENSOR_VISUAL_DIMENSION, GET_THING);

   // Initialize Output actions
   OutputRotateClockwise = World.NewExternalActionChunk(ClarionAgentActionType.ROTATE.ToString());
   OutputMoveThing = World.NewExternalActionChunk (ClarionAgentActionType.MOVE_THING.ToString());
   OutputGetThing = World.NewExternalActionChunk (ClarionAgentActionType.GET_THING.ToString());
   OutputNothing = World.NewExternalActionChunk(ClarionAgentActionType.DO_NOTHING.ToString());
   
   //Create thread to simulation
   runThread = new Thread(RunThread);
   Console.WriteLine("Agent started");
   }

   #endregion
  ........
}


No projeto, a cognição do Clarion é baseada em chunks de regras fixas. O código a seguir demonstra o método "SetupACS()" que é responsável por instânciar as regras e submete-las ao agente Clarion. No caso desse projeto são 4 regras como: ruleRotate, ruleMoveThing, ruleGetThing e ruleNothing. Pode-se perceber no código que existe somente três entradas para quatro saídas possíveis, que neste caso significa que existe uma entrada que abrange duas sáidas que são "rotate" e "moveThing"(entrada correspondente "InputMoveRotateThing"), quando a entrada está em ativação minima(MIN_ACTIVATION = 0) é realizado o chunk para a regra "ruleRotate", se a ativação for maxíma(MAX_ACTIVATION = 1) é realizado o chunk para "ruleMoveThing". Isso é perceptivel nos métodos "delegate's" que são responsavel por ativar ou não a regra que mesmo pertence.


private void SetupACS()
{
	// Create Colission Wall Rule
	SupportCalculator avoidRotateSupportCalculator = FixedRuleDelegateToRotate;
	FixedRule ruleRotate = AgentInitializer.InitializeActionRule(CurrentAgent, FixedRule.Factory, OutputRotateClockwise, avoidRotateSupportCalculator);

	// Commit this rule to Agent (in the ACS)
	CurrentAgent.Commit(ruleRotate);
	SupportCalculator moveThingSupportCalculator = FixedRuleDelegateToMoveToThing;
	FixedRule ruleMoveThing = AgentInitializer.InitializeActionRule(CurrentAgent, FixedRule.Factory, OutputMoveThing, moveThingSupportCalculator);

	// Commit this rule to Agent (in the ACS)
	CurrentAgent.Commit(ruleMoveThing);

	SupportCalculator getThingSupportCalculator = FixedRuleDelegateToGetThing;
	FixedRule ruleGetThing = AgentInitializer.InitializeActionRule(CurrentAgent, FixedRule.Factory, OutputGetThing, getThingSupportCalculator);

	// Commit this rule to Agent (in the ACS)
	CurrentAgent.Commit(ruleGetThing);

	SupportCalculator nothingSupportCalculator = FixedRuleDelegateToNothing;
	FixedRule ruleNothing = AgentInitializer.InitializeActionRule(CurrentAgent, FixedRule.Factory, OutputNothing, nothingSupportCalculator);

	// Commit this rule to Agent (in the ACS)
	CurrentAgent.Commit(ruleNothing);

	// Disable Rule Refinement
	CurrentAgent.ACS.Parameters.PERFORM_RER_REFINEMENT = false;

	// The selection type will be probabilistic
	CurrentAgent.ACS.Parameters.LEVEL_SELECTION_METHOD = ActionCenteredSubsystem.LevelSelectionMethods.STOCHASTIC;

	// The selection of the action will be fixed (not variable) i.e. only the statement defined above.
	CurrentAgent.ACS.Parameters.LEVEL_SELECTION_OPTION = ActionCenteredSubsystem.LevelSelectionOptions.FIXED;

	// Define Probabilistic values
	CurrentAgent.ACS.Parameters.FIXED_FR_LEVEL_SELECTION_MEASURE = 1;
	CurrentAgent.ACS.Parameters.FIXED_IRL_LEVEL_SELECTION_MEASURE = 0;
	CurrentAgent.ACS.Parameters.FIXED_BL_LEVEL_SELECTION_MEASURE = 0;
	CurrentAgent.ACS.Parameters.FIXED_RER_LEVEL_SELECTION_MEASURE = 0;
}

..........

private double FixedRuleDelegateToRotate(ActivationCollection currentInput, Rule target)
{
	return ((currentInput.Contains(InputMoveRotateThing, CurrentAgent.Parameters.MIN_ACTIVATION))) ? 1.0 : 0.0;
}       

private double FixedRuleDelegateToMoveToThing(ActivationCollection currentInput, Rule target)
{
	return ((currentInput.Contains(InputMoveRotateThing, CurrentAgent.Parameters.MAX_ACTIVATION))) ? 1.0 : 0.0;
}

private double FixedRuleDelegateToGetThing(ActivationCollection currentInput, Rule target)
{
	return ((currentInput.Contains(InputGetThing, CurrentAgent.Parameters.MAX_ACTIVATION))) ? 1.0 : 0.0;
}

private double FixedRuleDelegateToNothing(ActivationCollection currentInput, Rule target)
{
	return ((currentInput.Contains(InputNothing, CurrentAgent.Parameters.MAX_ACTIVATION))) ? 1.0 : 0.0;
}

..........

Os metódos a seguir são primordiais para a execução da captura das "Jewels" presente no leaflet do agent. Os métodos "isCompleted()" e "MakePerceptionFromSensorialInput()" são responsáveis por:

  • "isCompleted()": Método com sobrecarga onde é responsavel por verificar se o está completo o leaflet da criatura e verificar se uma "Jewel" de determinada cor está completa. Obs.: Os métodos verificam o objeto "Sack" que contém os objetos que a criatura capturou. Para isso é utilizado o métodos "getListOfLeafletCollected()".
  • "MakePerceptionFromSensorialInput()": Métodos responsável por "traduzir" os dados sensorais em informações para agente Clarion.


private SensoryInformation MakePerceptionFromSensorialInput(IList sensorialInformation)
{
	// New sensory information
	SensoryInformation si = World.NewSensoryInformation(CurrentAgent);
	//Console.WriteLine(sensorialInformation);
	Creature c = (Creature) sensorialInformation.Where(item => (item.CategoryId == Thing.CATEGORY_CREATURE)).First();

	bool thingAction = false;

	List listOfjewel = sensorialInformation.Where (item => (item.CategoryId == Thing.CATEGORY_JEWEL)).OrderBy(x=>x.DistanceToCreature).ToList();
	List listOfFood = sensorialInformation.Where (item => (item.CategoryId == Thing.CATEGORY_NPFOOD)).OrderBy(x=>x.DistanceToCreature).ToList();

	c.PrintLeaflet();
	double rotateMoveThingActivationValue = 0;
	double getThingActivationValue = 0;
	double nothingActivationValue = 0;


	foreach (Thing thing in listOfjewel) {
		thingAction = !isCompleted (c, thing);
		if (thingAction) {
			targetThing = thing;
			break;
		}
	}

	foreach (Thing thing in listOfFood) {
		if (thingAction) {
			if (targetThing.DistanceToCreature > thing.DistanceToCreature) {
				targetThing = thing;
				break;
			  }
		} else {
		       targetThing = thing;
		       thingAction = true;
			break;
		}
	}

	if (thingAction) {
	    if (targetThing.DistanceToCreature >= 25) {
				rotateMoveThingActivationValue = 1;
	     } else {
		getThingActivationValue = 1;
	       }
	} 
	else {
		if (!isCompleted (c))
			rotateMoveThingActivationValue = 0;
		else
			nothingActivationValue = 1;
        	}


	si.Add(InputMoveRotateThing, rotateMoveThingActivationValue);
	si.Add(InputGetThing, getThingActivationValue);
	si.Add(InputNothing, nothingActivationValue);

	return si;
}

public bool isCompleted(Creature c){
	string[] colors = {"White", "Green", "Blue", "Red", "Magenta", "Yellow"};
	bool result = false;

	foreach(string color in colors){

		List listLeafletItem = c.leaflets.SelectMany (x => x.items).ToList ().Where (i => i.itemKey == color).ToList ();

		LeafletItem leafletItem = new LeafletItem ();
		leafletItem.itemKey = color;

			foreach (LeafletItem item in listLeafletItem) {
				leafletItem.totalNumber += item.totalNumber;
			}

			result = leafletItem.totalNumber == this.getListOfLeafletCollected ().Where (i => i.itemKey == leafletItem.itemKey).FirstOrDefault ().collected ? true : false;

		if(!result)
			break;
		}

		return result;
}

public bool isCompleted(Creature c, Thing jewel){
	List listLeafletItem = c.leaflets.SelectMany (x => x.items).ToList ().Where (i => i.itemKey == jewel.Material.Color).ToList ();

	LeafletItem leafletItem = new LeafletItem ();
	leafletItem.itemKey = jewel.Material.Color;

	foreach (LeafletItem item in listLeafletItem) {
		leafletItem.totalNumber += item.totalNumber;
	}

	return leafletItem.totalNumber == this.getListOfLeafletCollected ().Where (i => i.itemKey == leafletItem.itemKey).FirstOrDefault ().collected ? 
				true : false;
}

public List getListOfLeafletCollected(){

	List collected = new List();
	collected.Add (new LeafletItem ("Green", 0, sack.green_crystal));
	collected.Add (new LeafletItem ("Red", 0, sack.red_crystal));
	collected.Add (new LeafletItem ("Blue", 0, sack.blue_crystal));
	collected.Add (new LeafletItem ("Yellow", 0, sack.yellow_crystal));
	collected.Add (new LeafletItem ("Magenta", 0, sack.magenta_crystal));
	collected.Add (new LeafletItem ("White", 0, sack.white_crystal));

	return collected;
}

No método "MakePerceptionFromSensorialInput()" são definidas as circunstâncias á qual as ações que a criatura deve tomar. Os três primeiros passos são correspondente á seleção de objetos a capturar. Os demais são respondente á seleção da ação ser tomada. Conforme o algoritmo apresentado acima segue á lógica abaixo:

  • 1º: Captura os dados sensoriais do agente.
  • 2º: Verifica se existe um "Jewel" dentro campo de visão do agente que está a uma menor distância do agente e não está com leaflet completo.
  • 3º:Satisfazendo as codições anteriores, aguarda o "Jewel" selecionado em um objeto meta(objeto que o agente deve consumir). Se existir um "Food" dentro do campo de visão do agente, e ele está a uma menor distância, prevalece o objeto "Food" como objeto meta.
  • 4º:Se objeto meta é diferente de nulo.
  • 5º:Se sim, então verifica-se se a distancia do agente até o objeto é maior igual a 25. Se verdadeiro ativa-se(com valor 1) a regra "moveThing", se não ativa-se(com o valor 1) a regra "getThing".
  • 6º:Se não, então verifica-se o leaflet da criatura está completo. Se sim, a regra "nothingDo" é ativada(com o valor 1). Se não, ativa-se a regra "rotate"(com valor 0).

1.2 - Demos: SOAR vs CLARION.

Na execução dos demos no ambiente WorldServer3D percebeu-se que o DemoCLARION se movimento em busca das "Jewels" que estavam dentro de seu Leaflet. Já o DemoSOAR não conseguiu se movimentar para encontrar os seus "Jewels". Nesta comparação mostrou algumas diferenças entre as arquiteturas propostas. Até esse momento a arquitetura cognitiva SOAR tem uma leve vantagem sobre a CLARION pelo simples fato que o SOAR está mais desenvolvida com relação a sua estrutura complexa. Já o CLARION é menos desenvolvido, porem é de implementação mais fácil em comparação a forma de trabalho do SOAR. O Link abaixo contem o código fonte do EduardoFroesCLARION e em seguida a comparação entre as duas arquiteturas conigtivas no WorldServer3D.

EduardoFroesCLARION

SOARvsCLARION.

Segue o video da movimentação do SOAR(Primeiro) e CLARION(Segundo).

Theme by Danetsoft and Danang Probo Sayekti inspired by Maksimer