9 JASON: AMBIENTE DE DESARROLLO Jason [3, 5, 44, 6] es una implementación en Java de AgentSpeak(L) con su semántica operacional completa y extendida. Las extensiones incluyen comunicación basada en actos de habla [44] y herramientas para simulación social [7]. En este capítulo introduciremos el ambiente de desarrollo en torno a Jason y ejemplificaremos su uso con un caso de estudio que se incluye en la distribución de este lenguaje. El lenguaje se encuentra disponible con sus fuentes abiertas bajo licencia GNU LGPL. �.� ����������� �.� �������� �� ��������� ������ �� ����� �.� �������������� �� ������ ��������� Jason puede descargarse desde su página principal en sourceforge1 , donde además encontrarán una descripción del lenguaje, documentación, ejemplos, demos y proyectos relacionados. Tambien hay una liga a la página del libro2 Programando Sistemas Multi-Agentes en AgentSpeak(L) usando Jason, la mejor fuente documental de este lenguaje. Al descargar el archivo de instalación, obtendrán un directorio como el mostrado en la Figure ??. Lo importante es que el ejecutable, en este caso Jason.app esté en algún sitio accesible. Por ejemplo, el folder completo Jason-1.3.2 está en el folder de aplicaciones de mi MacBook Pro. Observen que el código fuente está disponible en src y que los ejemplos y demos del sitio web del lenguaje están includos en los folders examples y demos respectivamente. La documentación en doc incluye algunos artículos relevantes y la descripción del API de Jason. Como se menciona en el README es necesario tener instalado Java 1.5 para comenzar a trabajar. Si ejecutan Jason.app tendrán acceso a la ventana principal de un ambiente de desarrollo basado en jEdit (Figure ??. Los agentes están situados en su medio ambiente y los lenguajes de programación orientados a agentes deberían proveer una noción explícita de éste. Aunque esto no pareciera ser mandatorio en el caso de los agentes puramente comunicativos, recuerden que los actos de habla buscan ajustar el medio ambiente indirectamente a los 1 http://jason.sourceforge.net/JasonWebSite/Jason %20Home.app 2 http://jason.sourceforge.net/jBook/jBookWebSite/Home.php 117 118 �����: �������� �� ���������� Figura 9.1: El directorio principal de Jason. Figura 9.2: La ventana principal de Jason. �.� �������������� �� ������ ��������� Agent Architecture User Environment getPercepts executeAction change percepts Figura 9.3: Interacción entre la implementación del ambiente y la arquitectura del agente. estados Intencionales del agente; y que las creencias son representaciones del agente ajustadas al medio ambiente (ver Sección ??, pág. ??). En el caso de agentes situados en medios ambientes reales, aunque la simulación no es mandatoria, tiene algunas ventajas a saber: Los agentes y los Sistemas MultiAgentes son sistemas distribuidos de un alto grado de complejidad. Aunque existen herramientas formales para la verificación de estos sistemas, la validación mediante simulación sigue siendo una práctica muy extendida. En todo caso, simulado o real, el acceso de Jason al medio ambiente se da a través de Java. Y es muy probable que los programadores quieran tener un modelo Java del mundo real. De esta forma, situar a los agentes en su medio ambiene real tras las simulaciones, no es en extremo difícil gracias a que la arquitectura general de los agentes es configurable. La idea es que los métodos que actuaban y percibían sobre el ambiente simulado, sean substituidos por métodos que actuan y perciben en el ambiente real. �.�.� Soporte para la definición de ambientes simulados Todo agente tendrá que interaccionar con su medio ambiente. La arquitectura general de un agente incluirá los métodos Java que definen esta interacción, como se muestra en diagrama de secuencia UML de la Figura 9.3. La arquitectura existente de un agente utiliza el método getPercepts para recuperar, del ambiente simulado, las percepciones a las cuales el agente tiene acceso. Estas pueden verse como propiedades del ambiente accesibles al agente. A partir de esta información el agente actualiza sus creencias, normalmente cuando su ciclo de razonamiento está en el estado ProcMsg. Ahora bien, cuando el agente ejecuta una acción a llevar a cabo una de sus intenciones, la arquitectura solicita al ambiente la ejecución de la acción y suspende la intención asociada hasta que el ambiente provee retroalimentación sobre la ejecución de la acción, normalmente, que la acción ha sido ejecutada. La verificación de si los efectos esperados de la acción se cumplieron o no, está asociada normalmente a la percepción y no a esta retroalimentación. Observen que el ciclo del razonamiento del agente continua mientras la intención asociada a la acción ejecutada está suspendida. Esto tiene un efecto similar a si el 119 120 �����: �������� �� ���������� Environment - globalPercepts : List<Literal> - agPercepts : Map<String,List<Literal>> + init(String[] args) + stop() User Environment + getPercepts(String ag) : List<Literal> + executeAction(String ag, Structure action) : boolean + init(String[] args) + executeAction(String ag, Structure action) : boolean + addPercept(Literal p) + addPercept(String ag, Literal p) + removePercept(Literal p) + removePercept(String ag, Literal p) + clearPercepts() + clearPercepts(String ag) Figura 9.4: Implementación de un ambiente extendiendo la clase Environment. método executeAction fuese invocado de forma asíncrona. Si el ambiente está siendo ejecutado en otra máquina, el lapso de esta suspensión puede ser considerable. La clase Environment provista por Jason soporta la percepción individual de los agentes, facilitando la tarea de asociar ciertas percepciones a ciertos agentes. La clase mantiene estructuras de datos que almacenan las percepciones a las que cada agente tiene acceso, así como percepciones globales accesibles a todos los agentes en el ambiente. El método getPercepts busca en estas estructuras, de modo que todas las percepciones accesibles al agente que llama al método son recuperadas. Para implementar un ambiente en Jason, el programador normalmente extiende la clase Environment y sobre carga los métodos executeAction e init. La Figura 9.4 muestra un diagrama de clase mostrando esta relación. Una implementación de la clase ambiente extendida suele tener la estructura mostrada en el Cuadro 7. El método init puede usarse para recibir parámetros para la clase del ambiente. También suele usarse para iniciar la lista de percepciones con aquellas propiedades del ambiente que serán accesibles a los agentes cuando el sistema inicie su ejecución. Las percepciones son más fáciles de crear con el método parseLiteral de la clase Literal, pero el método preferido actualmente es usar createLiteral de la clase ASSyntax. Es posible incluir literales positivas y negativas en la lista de percepciones de un ambiente. Las percepciones negativas son literales sujetas al operador de negación fuerte. El Cuadro 8 resume los métodos de Java que pueden usarse para programar un ambiente Jason. Solo objetos de la clase Literal, que es parte del paquete jason pueden agregarse a las listas de percepciones mantenidas por la clase Environment. En esta parte no debería considerarse agregar anotaciones a las literales, pues todas son anotadas automáticamente con source(percept). La mayor parte del código relacionado con la implementación de ambientes debe ser referenciado en el cuerpo del método executeAction, que debe declararse tal y como se muestra en el Cuadro 7. Siempre que un agente trata de ejecutar una acción en el ambiente, el nombre del agente y una estructura representando la acción solicitada son enviadas a este método como parámetros. El código en executeAction suele verificar la estructura à la Prolog que representa la acción y el agente que intenta ejecutar la acción. Luego, para cada combinación acción/agente que sea relevante, el código hace lo necesario en el modelo del ambiente. Normalmente esto incluye cambiar ciertas percepciones. Observen que la ejecución de una acción es booleana y �.� �������������� �� ������ ��������� 1 2 import jason.asSyntax.*; import jason.environment.*; 3 4 public class <EnvironmentName> extends Environment { 5 // Los miembros de la clase necesarios... 6 7 @Override public void init(String[] args) { // Inicializar percepciones globales ... addPercept(Literal.parseLiteral("p(a)")); // Si se usa la modalidad open-world, puede haber // literales negadas, como ... addPercept(Literal.parseLiteral("~q(b)")); // Si lo que sigue solo lo percibe ag1 addPercept("ag1", Literal.parseLiteral("p(a)")); } 8 9 10 11 12 13 14 15 16 17 @Override public void stop() { // Cualquier cosa que se tenga que ejecutar en el ambiente // cuando el sistema se detenga... } 18 19 20 21 22 @Override public boolean executeAction(String ag, Structure act) { // Este es el método más importante, donde los // efetos de las acciones sobre las percepciones // en el ambiente son definidas... } 23 24 25 26 27 28 } Cuadro 7: Implementación del ambiente del usuario. Método addPercept(L) addPercept(A,L) removePercept(L) removePercept(A,L) clearPercepts() clearPercepts(A) Semántica Agrega la literal L a la lista global de percepciones. Agrega la literal L a la lista de percepciones del agente A. Remueve la literal L de la lista global de percepciones Remueve la literal L de la lista de percepciones del agente A. Borra las percepciones de la lista global. Borra las percepciones del agente A. Cuadro 8: Métodos Java para programar ambientes Jason. 121 122 �����: �������� �� ���������� regresa falso si la solicitud de ejecución al ambiente fallo. Un plan falla si alguna de sus acciones falla al ser ejecutada. La percepción y la actualización de creencias no son procesos equivalentes. Esta posible confusión es causa de algunos errores al implementar ambientes y su interacción con los agentes mediante las clases y métodos definidos en Jason. Se suele esperar que los agentes mantengan en su estado mental las percepciones aún cuando estás solo estén presentes durante un ciclo de razonamiento. Esto es falso. Si un agente necesita recordar percepciones pasadas que ya no se dan en el ambiente, es necesario que cree notas mentales al percibir la propiedad en cuestión a través de sus planes. Las notas mentales se recuerdan hasta que explícitamente son olvidadas. Las creencias asociadas a una percepción son eliminadas en cuanto la percepción se deja de observar en el ambiente. También es posible que una percepción desaparezca como efecto de la ejecución de una acción, antes de que el agente pueda formar una creencia acerca de ella. Aunque consideren que el proceso de actualización de creencias genera eventos asociados a agregar y borrar creencias. Antes de pasar a un ejemplo de mayor complejidad, consideremos un caso sencillo para ilustrar el uso de los métodos mencionados al implementar ambientes en Jason. El ejemplo gira en torno a un agente aspiradora que puede moverse a su izquierda y a su derecha, así como aspirar basura en cualquiera de las posiciones donde puede estar (a la izquierda o a la derecha). De forma que las acciones del agente serán izq, der y aspirar. Las percepciones del agente incluyen cuando su posición actual está sucia y cual es su posición actual: pos(i) ó pos(d). Observen que hay cierto grado de indeterminismo en este ambiente simple: sucia solo se percibe si la posición actual lo está. El Cuadro 9 muestra el código parcial del ambiente en cuestión (no se consideran aún las acciones del agente). �.�.� Creación de Ambientes Los agentes pueden estar situados en ambientes reales o simulados. En el primer caso, el usuario tendría que personalizar la “arquitectura de agente general”, como será descrito en la siguiente subsección; mientras que en el último caso, debe proporcionar una implementación del ambiente simulado a través de una clase en Java, que extiende la clase Environment. Como ejemplo, a continuación se presenta el ambiente simulado para el escenario de los robots limpiadores que se presentó en la sección ??. Las líneas 1–5 importan los elementos de Jason necesarios. Observen que estamos especializando una clase de ambiente llamada grid. Las líneas 7–11 importan los componentes de Java necesarios para la creación de nuestro ambiente. Observen que es necesario declarar los términos y literales que usaremos al definir la clase (líneas 18–23). La definición de este ambiente sigue un esquema vista-modelo (líneas 27–28). , 1 2 3 4 5 6 7 import import import import import jason.asSyntax.∗; jason.environment.Environment; jason.environment.grid.GridWorldModel; jason.environment.grid.GridWorldView; jason.environment.grid.Location; import java.awt.Color; �.� �������������� �� ������ ��������� 1 // Environment code for project aspiradora.mas2j 2 3 4 5 6 7 import import import import import jason.asSyntax.*; static jason.asSyntax.ASSyntax.*; jason.environment.*; java.util.logging.*; java.util.*; 8 9 public class aspiradoraEnv extends Environment { 10 private Logger logger = Logger.getLogger("aspiradora.mas2j."+aspiradoraEnv. class.getName()); 11 12 // basura[0] es la localidad izq, basura[1] es la der boolean[] basura = { true, true }; 13 14 15 // la aspiradora está a la izquierda char posAspiradora = 0; 16 17 18 Random r = new Random(); 19 20 // Called before the MAS execution with the args informed in .mas2j @Override public void init(String[] args) { updatePercepts(); } 21 22 23 24 25 private void updatePercepts() { // Agregar basura dinámicamente con 0.2 de probabilidad if (r.nextInt(100) < 20) { basura[r.nextInt(2)] = true; } clearPercepts(); if (basura[posAspiradora]) { addPercept(Literal.parseLiteral("sucio")); } else { addPercept(Literal.parseLiteral("limpio")); } if (posAspiradora==0) { addPercept(createLiteral("pos", createAtom("izq"))); } else { addPercept(createLiteral("pos", createAtom("der"))); } } 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 } Cuadro 9: Actualización de creencias en el ejemplo de la aspiradora. 123 124 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 �����: �������� �� ���������� import import import import java.awt.Font; java.awt.Graphics; java.util.Random; java.util.logging.Logger; public class MarsEnv extends Environment { public static final int GSize = 7; // grid size public static final int GARB = 16; // garbage code in grid model public public public public public public static static static static static static final final final final final final Term ns = Literal.parseLiteral("next(slot)"); Term pg = Literal.parseLiteral("pick(garb)"); Term dg = Literal.parseLiteral("drop(garb)"); Term bg = Literal.parseLiteral("burn(garb)"); Literal g1 = Literal.parseLiteral("garbage(r1)"); Literal g2 = Literal.parseLiteral("garbage(r2)"); static Logger logger = Logger.getLogger(MarsEnv.class.getName()); private MarsModel model; private MarsView view; @Override public void init(String[] args) { model = new MarsModel(); view = new MarsView(model); model.setView(view); updatePercepts(); } @Override public boolean executeAction(String ag, Structure action) { logger.info(ag+" doing: "+ action); try { if (action.equals(ns)) { model.nextSlot(); } else if (action.getFunctor().equals("move_towards")) { int x = (int)((NumberTerm)action.getTerm(0)).solve(); int y = (int)((NumberTerm)action.getTerm(1)).solve(); model.moveTowards(x,y); } else if (action.equals(pg)) { model.pickGarb(); } else if (action.equals(dg)) { model.dropGarb(); } else if (action.equals(bg)) { model.burnGarb(); } else { return false; } } catch (Exception e) { e.printStackTrace(); } updatePercepts(); try { Thread.sleep(200); } catch (Exception e) {} return true; } /** creates the agents perception based on the MarsModel */ void updatePercepts() { clearPercepts(); Location r1Loc = model.getAgPos(0); Location r2Loc = model.getAgPos(1); 74 75 Literal pos1 = Literal.parseLiteral("pos(r1," + r1Loc.x + "," + r1Loc.y + ")"); Literal pos2 = Literal.parseLiteral("pos(r2," + r2Loc.x + "," + r2Loc.y + ")"); 76 77 78 79 80 addPercept(pos1); addPercept(pos2); 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 } if (model.hasObject(GARB, r1Loc)) { addPercept(g1); } if (model.hasObject(GARB, r2Loc)) { addPercept(g2); } class MarsModel extends GridWorldModel { public static final int MErr = 2; // max error in pick garb int nerr; // number of tries of pick garb boolean r1HasGarb = false; // whether r1 is carrying garbage or not Random random = new Random(System.currentTimeMillis()); private MarsModel() { super(GSize, GSize, 2); // initial location of agents try { setAgPos(0, 0, 0); Location r2Loc = new Location(GSize/2, GSize/2); setAgPos(1, r2Loc); } catch (Exception e) { e.printStackTrace(); } // initial location of garbage 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 } 132 135 136 137 138 139 3, 0); GSize-1, 0); 1, 2); 0, GSize-2); GSize-1, GSize-1); // finished searching the whole grid 131 134 add(GARB, add(GARB, add(GARB, add(GARB, add(GARB, void nextSlot() throws Exception { Location r1 = getAgPos(0); r1.x++; if (r1.x == getWidth()) { r1.x = 0; r1.y++; } 130 133 �.� �������������� �� ������ ��������� } if (r1.y == getHeight()) { return; } setAgPos(0, r1); setAgPos(1, getAgPos(1)); // just to draw it in the view void moveTowards(int x, int y) throws Exception { Location r1 = getAgPos(0); if (r1.x < x) r1.x++; 125 126 140 �����: �������� �� ���������� 141 142 143 144 145 146 147 } 148 149 void pickGarb() { 150 151 // sometimes the "picking" action doesn’t work // but never more than MErr times 153 154 if (random.nextBoolean() || nerr == MErr) { remove(GARB, getAgPos(0)); nerr = 0; r1HasGarb = true; } else { nerr++; } 155 156 157 158 159 160 161 } } void dropGarb() { if (r1HasGarb) { r1HasGarb = false; add(GARB, getAgPos(0)); } } void burnGarb() { 162 163 164 165 166 167 168 169 170 // r2 location has garbage 171 172 173 174 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 // r1 location has garbage if (model.hasObject(GARB, getAgPos(0))) { 152 175 else if (r1.x > x) r1.x--; if (r1.y < y) r1.y++; else if (r1.y > y) r1.y--; setAgPos(0, r1); setAgPos(1, getAgPos(1)); // just to draw it in the view } } if (model.hasObject(GARB, getAgPos(1))) { remove(GARB, getAgPos(1)); } class MarsView extends GridWorldView { public MarsView(MarsModel model) { super(model, "Mars World", 600); defaultFont = new Font("Arial", Font.BOLD, 18); // change default font } setVisible(true); repaint(); /** draw application objects */ @Override public void draw(Graphics g, int x, int y, int object) { switch (object) { case MarsEnv.GARB: drawGarb(g, x, y); break; } } @Override public void drawAgent(Graphics g, int x, int y, Color c, int id) { String label = "R"+(id+1); c = Color.blue; if (id == 0) { c = Color.yellow; if (((MarsModel)model).r1HasGarb) { label += " - G"; c = Color.orange; } �.� �������������� �� ������ ��������� 206 207 208 209 210 211 212 213 } 214 215 216 217 218 219 220 221 222 223 } } } super.drawAgent(g, x, y, c, -1); if (id == 0) { g.setColor(Color.black); } else { g.setColor(Color.white); } super.drawString(g, x, y, defaultFont, label); 127 public void drawGarb(Graphics g, int x, int y) { super.drawObstacle(g, x, y); g.setColor(Color.white); drawString(g, x, y, defaultFont, "G"); } Siempre que un agente intente ejecutar una acción básica (aquella que cambia el estado del ambiente), el nombre de este agente y un Structure que representa la acción elegida se enviarán como parámetros al método executeAction. En el ejemplo, se puede apreciar que executeAction ejecuta las acciones básicas: avanzar un paso (model.nextSlot()), moverse hacia donde se encuentra el otro agente (model.moveTowards(x,y)), recoger la basura (model.pickGarb()), tirar la basura (model.dropGarb()) y quemar la basura (model.burnGarb()). Por último, este método actualiza las percepciones de los agentes (updatePercepts()), al indicarles su nueva localización (addPercept(pos1), addPercept(pos2)), y si ésta tiene basura (addPercept(g1), addPercept(g2)). Las dos clases que aparencen al final del ambiente, MarsModel y MarsView, se encargan de implementar las acciones básicas de los agentes y de recrear la animación de este ambiente, respectivamente. �.�.� Definición de Nuevas Acciones Internas Las acciones internas permiten que los agentes AgentSpeak permanezcan en un nivel de abstracción alto, además proporcionan una forma fácil de extender el código y de usar código heredado. Las acciones internas que son distribuidas con Jason (como parte de la librería estándar de acciones internas) comienzan con un ’.’. Las acciones internas que son implementadas por los usuarios deben ser organizadas en librerías específicas. En el programa AgentSpeak, la acción se accesa por el nombre de la librería, seguido de un ’.’ y luego por el nombre de la acción. Las librerías se definen como paquetes en Java y cada acción en la librería debe ser una clase en Java, el paquete y la clase se nombrarán de la misma forma como la librería y la acción serán referidas en los programas AgentSpeak. En el caso del ejemplo, las acciones internas se definen al extender la clase GridWorldModel para definir la clase MarsModel (líneas 92–176).