Jugando con Drools 5.0 – #3 Drools Expert

Continuando con los dos post anteriores (post1 y post2), vamos a analizar el comportamiento de la aplicación cuando la misma se ejecuta. La idea es explicar y aclarar el comportamiento del framework para esta situación en particular y además dejar planteado los comportamientos que por lo general se intentan modelar con estos motores de inferencias.

Primero que nada recordemos el código que armaba la sesión de Drools con la que vamos a interactuar:

KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
 kbuilder.add(ResourceFactory.newInputStreamResource(new App().getClass().getResourceAsStream("/rules/juego.drl")),ResourceType.DRL);

 if (kbuilder.hasErrors()) {
 System.out.println(kbuilder.getErrors());
 return;
 }
 Collection<KnowledgePackage> kpkgs = kbuilder.getKnowledgePackages();
 KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase();
 kbase.addKnowledgePackages( kpkgs );

 StatefulKnowledgeSession ksession = kbase.newStatefulKnowledgeSession();
 KnowledgeRuntimeLogger logger = KnowledgeRuntimeLoggerFactory.newConsoleLogger(ksession);

 ksession.insert(new Juego("Poker",2));
 ksession.insert(new Jugador("Jugador 1"));
 ksession.insert(new Jugador("Jugador 2"));
 ksession.fireAllRules();

Como podemos ver, una vez que creamos la sesión podemos interactuar con ella mediante el método insert(), que va a insertar nuevos Facts/Hechos a la memoria de trabajo que posee Drools para realizar las inferencias. Una vez ejecutado el método insert(), el objeto ya se encontrara dentro de la memoria de trabajo y automáticamente Drools analizara que deducciones puede realizar sobre el.

Análisis de la ejecución

A continuación vamos a ir viendo como se relaciona la salida que obtenemos por consola con la ejecución del código y las evaluaciones que se realizan las reglas sobre los facts.

Cuando realizamos el primer ksession.insert(new Juego(“Poker”,2)); la siguiente linea aparece por consola:

OBJECT ASSERTED value:Juego =Poker factId: 1

Lo mismo sucede cuando insertamos el primero Jugador:

OBJECT ASSERTED value:Jugador 1 - cartas: [] puntos =0 factId: 3

Ahora cuando vamos a realizar el siguiente insert, es decir cuando llamamos al método insert e insertamos un nuevo Jugador, debido a que hay una regla que esta esperando 2 jugadores para activarse, una Activación es creada. Esto significa que ya hay una regla lista para ejecutarse para un set de Facts determinados.

ACTIVATION CREATED rule:Comienza una ronda de Poker cuando hay n jugadores activationId:Comienza una ronda de Poker cuando hay n jugadores [2, 1] declarations: $jugadores=[Jugador 1 - cartas: [] puntos =0, Jugador 2 - cartas: [] puntos =0](2); $juego=Juego =Poker(1)
OBJECT ASSERTED value:Jugador 2 - cartas: [] puntos =0 factId: 4

Si analizamos la linea que comienza con “ACTIVATION CREATED” veremos que nos indica el nombre de la regla que esta cumpliendo con la condición (y sus restricciones) junto con los datos de los Facts que las satisfacen.

Ahora bien, en nuestra aplicación, no llamamos mas al método insert, ahora es el turno de llamar al famoso y querido método fireAllRules(). Este método, se encargara de ejecutar las Activaciones creadas, que se hayan generado con los inserts anteriores. Por esto, las siguientes lineas se mostraran en la consola cuando el metodo fireAllRules se ejecute:

BEFORE ACTIVATION FIRED rule:Comienza una ronda de Poker cuando hay n jugadores activationId:Comienza una ronda de Poker cuando hay n jugadores [2, 1] declarations: $jugadores=[Jugador 1 - cartas: [] puntos =0, Jugador 2 - cartas: [] puntos =0](2); $juego=Juego =Poker(1)
ACTIVATION CREATED rule:Iniciar Ronda Poker activationId:Iniciar Ronda Poker [5, 1] declarations: $dealer=Poker Dealer - cartas: [] puntos =0(5); $juego=Juego =Poker(1)
OBJECT ASSERTED value:Poker Dealer - cartas: [] puntos =0 factId: 5
AFTER ACTIVATION FIRED rule:Comienza una ronda de Poker cuando hay n jugadores activationId:Comienza una ronda de Poker cuando hay n jugadores [2, 1] declarations: $jugadores=[Jugador 1 - cartas: [] puntos =0, Jugador 2 - cartas: [] puntos =0, Poker Dealer - cartas: [] puntos =0](2); $juego=Juego =Poker(1)
BEFORE ACTIVATION FIRED rule:Iniciar Ronda Poker activationId:Iniciar Ronda Poker [5, 1] declarations: $dealer=Poker Dealer - cartas: [] puntos =0(5); $juego=Juego =Poker(1)
ACTIVATION CREATED rule:Nueva Ronda activationId:Nueva Ronda [18, 4, 1, 6] declarations: $juego=Juego =Poker(1); $jugador=Jugador 2 - cartas: [] puntos =0(4)
ACTIVATION CREATED rule:Nueva Ronda activationId:Nueva Ronda [19, 3, 1, 6] declarations: $juego=Juego =Poker(1); $jugador=Jugador 1 - cartas: [] puntos =0(3)
OBJECT ASSERTED value:Nueva Ronda factId: 6
AFTER ACTIVATION FIRED rule:Iniciar Ronda Poker activationId:Iniciar Ronda Poker [5, 1] declarations: $dealer=Poker Dealer - cartas: [13 de Pica, 13 de Diamante, 7 de Trebol, 8 de Corazon, 6 de Corazon, ] puntos =0(5); $juego=Juego =Poker(1)
BEFORE ACTIVATION FIRED rule:Nueva Ronda activationId:Nueva Ronda [18, 4, 1, 6] declarations: $juego=Juego =Poker(1); $jugador=Jugador 2 - cartas: [] puntos =0(4)
AFTER ACTIVATION FIRED rule:Nueva Ronda activationId:Nueva Ronda [18, 4, 1, 6] declarations: $juego=Juego =Poker(1); $jugador=Jugador 2 - cartas: [2 de Trebol, 4 de Corazon, ] puntos =0(4)
BEFORE ACTIVATION FIRED rule:Nueva Ronda activationId:Nueva Ronda [19, 3, 1, 6] declarations: $juego=Juego =Poker(1); $jugador=Jugador 1 - cartas: [] puntos =0(3)
AFTER ACTIVATION FIRED rule:Nueva Ronda activationId:Nueva Ronda [19, 3, 1, 6] declarations: $juego=Juego =Poker(1); $jugador=Jugador 1 - cartas: [9 de Pica, 13 de Trebol, ] puntos =0(3)

Todo esto sucede cuando el método fireAllRules() se ejecuta, pero, que exactamente hace este método? Como vemos en la consola, lo primero que este método va a realizar es la ejecución de la activación que se habia generado cuando estábamos insertando facts en la sesión. La ejecución de estas activaciones van a ser loggeadas en consolas antes y después de ejecutarse. Esto es para que nosotros podamos ver, si la consecuencia de la regla que se ejecuta nos modifica el estado actual de la memoria de trabajo.

Como vemos en el log anterior, en las primeras 4 lineas, sucede exactamente esto que estábamos describiendo.

BEFORE ACTIVATION FIRED rule:Comienza una ronda de Poker cuando hay n jugadores activationId:Comienza una ronda de Poker cuando hay n jugadores [2, 1] declarations: $jugadores=[Jugador 1 - cartas: [] puntos =0, Jugador 2 - cartas: [] puntos =0](2); $juego=Juego =Poker(1)
ACTIVATION CREATED rule:Iniciar Ronda Poker activationId:Iniciar Ronda Poker [5, 1] declarations: $dealer=Poker Dealer - cartas: [] puntos =0(5); $juego=Juego =Poker(1)
OBJECT ASSERTED value:Poker Dealer - cartas: [] puntos =0 factId: 5
AFTER ACTIVATION FIRED rule:Comienza una ronda de Poker cuando hay n jugadores activationId:Comienza una ronda de Poker cuando hay n jugadores [2, 1] declarations: $jugadores=[Jugador 1 - cartas: [] puntos =0, Jugador 2 - cartas: [] puntos =0, Poker Dealer - cartas: [] puntos =0](2); $juego=Juego =Poker(1)

Entre un BEFORE ACTIVATION FIRED y un AFTER ACTIVATION FIRED, de la misma regla podemos ver si la consecuencia de la misma, cambio el estado de algún hecho/fact, se inserto un nuevo hecho/fact o por alguna otra razón se genero una nueva activación. En este caso en particular vemos que se creo una nueva Activación de de una regla llamada “Iniciar Ronda Poker” al haber sido insertado un objeto de tipo Dealer en la consecuencia de la regla “Comienza una ronda de Poker cuando hay n jugadores“.

Si recordamos la consecuencia de la regla “Comienza una ronda de Poker cuando hay n jugadores“, podemos ver que efectivamente esta regla esta insertando un nuevo hecho de tipo Dealer, con lo cual causa una nueva activación para la regla llamada “Iniciar Ronda Poker“.

 then
    $juego.setMazo(MazoPoker());
    Dealer dealer =new Dealer("Poker Dealer");
    insert(dealer); 

Cuando este hecho/fact de tipo Dealer sea insertado en la memoria de trabajo de Drools, la regla llamada “Iniciar Ronda Poker“, que tiene la siguiente condición, genera una activación:

  $juego: Juego(nombre == "Poker")
  $dealer: Dealer() 

Esto se debe a que ya teniamos un Juego, el que nosotros habiamos insertado y ahora la regla anterior inserto un nuevo Dealer, entonces la consecuencia de la regla tiene que ser ejecutada.

Siguiendo esta misma lógica, pueden analizar el resto de las activaciones que se generan, asi modificar el ejemplo para que se generen nuevas o diferentes activaciones según el estado de la memoria de trabajo de Drools.

Consultas sobre la memoria de trabajo

Para ir terminando con este post, solo resta ver la ejecución de cada uno de los queries que se realiza al finalizar todas las inferencias. Es bueno aclarar también que cuando ya no quedan activaciones que ejecutar, el método fireAllRules devuelve el control a nuestra aplicación para que pueda continuar su ejecución.

Cuando el control retorna a la aplicación luego del método fireAllRules(), podemos consultar como quedaron nuestros hechos en la memoria de trabajo. Para esto podemos definir consultas/queries en nuestros archivos DRL que incluimos a la hora de armar nuestra sesión.

En este caso, se agrego la siguiente consulta:

query "Jugador por nombre" (String qnombre)
  jugador : Jugador( nombre ==  qnombre)
end 

Es bueno notar que la sintaxis para definir consultas es la misma que para expresar restricciones en las condiciones de nuestras reglas.

La salida a consola de la ejecución de esta consulta con diferentes parámetros es la siguiente:

El Dealer tiene las cartas:
OBJECT ASSERTED value:org.drools.base.DroolsQuery@f7a53d93 factId: 20
[Carta -> 13dePica, Carta -> 13deDiamante, Carta -> 7deTrebol, Carta -> 8deCorazon, Carta -> 6deCorazon]

El Jugador 1 tiene las cartas:
OBJECT ASSERTED value:org.drools.base.DroolsQuery@f7a53d93 factId: 21
[Carta -> 9dePica, Carta -> 13deTrebol]

El Jugador 2 tiene las cartas:
OBJECT ASSERTED value:org.drools.base.DroolsQuery@f7a53d93 factId: 22
[Carta -> 2deTrebol, Carta -> 4deCorazon]

Donde el código para ejecutar cada una de estas consultas es el siguiente:

 QueryResults results = ksession.getQueryResults("Jugador por nombre", new Object[]{"Poker Dealer"});
 for ( QueryResultsRow row : results ) {
   Jugador jugador = ( Jugador ) row.get( "jugador" );
   System.out.println( jugador.getCartas() + "\n" );
 }

Donde se puede apreciar que estas consultas se ejecutan obteniendolas por nombre y pasando los parámetros como un array de objetos. Luego para recuperar los resultados obtenemos los objetos mediante el nombre de su variable vinculada, en este caso llamada “jugador“. Vale la aclaración de que debería llamarse $jugador para seguir con las reglas de legibilidad.

Conclusión

Vimos un poco como es el flujo de ejecución de este ejemplo propuesto en particular. Espero que esta explicación, también genere dudas e inquietudes sobre como funciona el framework internamente. La idea principal de estos posts, es dejar precedencia para poder escribir artículos mas profundos sobre temas mas avanzados. Espero sus comentarios, criticas y dudas si es que las hay.

Saludos.

PD: nuevamente les dejo el link del ejemplo, para que no tenga que andar buscandolo por todos lados: www.jbug.com.ar/external/playingWithDrools-ExpertIntroduction.zip

11 thoughts on “Jugando con Drools 5.0 – #3 Drools Expert”

  1. Saludos

    Estoy ejecutando este ejemplo y desde eclipse funciona sin problemas pero al empaquetarlo para usarlo con axis2 como un web service me ha estado marcado un error con una libreria, ya verifique y si esta cargado el archivo .jar pero aun me marca este error:

    13:31:18,309 ERROR [AxisEngine] Unable to load dialect ‘org.drools.rule.builder.
    dialect.mvel.MVELDialectConfiguration:mvel:null’

    Las librerias que cargue son:
    antlr-runtime-3.1.1.jar
    drools-api-5.0.1.jar
    drools-compiler-5.0.1.jar
    drools-core-5.0.1.jar
    drools-jsr94-5.0.1.jar
    janino-2.5.15.jar
    joda-time-1.6.jar
    mvel2-2.0.10.jar
    org.eclipse.jdt.core_3.4.2.v_883_R34x.jar
    xstream-1.3.1.jar

    Tendre que configurar algo mas.

    Gracias

    Like

  2. Buenas,

    He intentado crear un knowledge base vacio, crear una stateful session, insertar objetos en la working memory y finalmente cargar las reglas en el kbase.El problema es que en este caso al cargas las reglas, como ya anteriormente la WM estaba poblada, no solo se crean las activaciones si no que además se ejecutan las reglas, haciendo que la llamada al fireAllRules sea innecesaria, pero lo que yo quiero es ir variando en un bucle los ficheros de reglas del kbase y saber cuantas reglas se han ejecutado en cada paso, asi como el estado de la agenda antes de lanzar las reglas.

    Según la documentación de Drools : “When KnowledgeBase modifications occur those modifications are applied against the data in the sessions.”

    Se podría variar de alguna forma este comportamiento y lanzar las activaciones con el fireAllRules ?

    Saludos y gracias de antemano.
    Pablo.

    Like

  3. Como estas pablo.
    Estaba viendo tu post en la lista de mails de desarrollo y usuarios de Drools.
    No he tenido tiempo de analizarla por completo a la pregunta.
    Pero se que existe un listener para saber cuando la KnowledgeBase se modifica.
    Por otro lado, me gustaría saber cual es el caso que te ha llevado a hacer eso, para también analizar si es necesario hacer eso.
    Saludos

    Like

  4. Hola Mauricio,

    Probablemente otro compañero mio te haya preguntado lo mismo recientemente por correo directo, porque esta investigando lo mismo que yo.
    Estamos sustituyendo el motor de reglas de Ilog Rules (C++) por Drools via JNI.
    El problema es que el programa que interactuaba con ILog Rules funcionaba de tal forma, que se cargaba la Working Memory una sola vez, y luego se podian ir cargando ficheros de reglas y lanzar el fireAllRules.
    De esta forma, aparte de saber si la agenda tenía reglas que ejecutar antes de lanzar el fireAllRules, sabiamos cuantas reglas se han ejecutado.

    Drools devuelve el numero de reglas ejecutadas y haciendo un pequeño truco podemos consultar el tamaño de la agenda antes de lanzar las reglas, pero claro, al variar el fichero de reglas, se propagan los cambios por las sesiones abiertas por lo cual el fireAllRules no hace absolutamente nada la siguiente vez que se ejecuta ya que al cargar las reglas no solo se han creado las activaciones si no que además se han ejecutado las reglas.

    Yo lo que quiero es evitar que se lleguen a ejecutar las reglas, que solo se creen las activaciones, asi la agenda no estará vacia y el fireAllRules podrá ejecutarse nuevamente.
    No sé si esto es posible o probablemente no lo este entendiendo bien.
    Entiendo que hacer una sesion por fichero de regla no es muy costoso, pero insertar los objetos en la working memory si , por lo que no sé si hay muchas opciones.

    Un saludo,
    y gracias por tu ayuda.

    Pablo.

    Like

  5. Voy a tratar de contestar las preguntas por aquí y asi queda rastro de la conversación para otras personas que puedan estar teniendo las mismas dudas.
    Por lo que me comenta tu companiero estan tratando de imitar la funcionalidad que provee ILog. Suponía que el tema venia por ese lado, porque es un requerimiento muy raro en el mundo de Drools hacer algo asi.
    Como sugirio Edson Terelli que es el guru en el tema, la solución a tu problema debería ser configurar las sesiones con el siguiente parámetro:

    KnowledgeBaseConfiguration conf = …
    ((RuleBaseConfiguration)conf).setRuleBaseUpdateHandler( null );

    Que te permitiría que cuando se haga el update de las reglas no se ejecute ni se propague ningún cambio.

    Nuevamente me gustaría recalcar que me gustaría conocer mas de la implementación para darle la visión y el análisis mas orientado a la forma correcta de usar Drools, ya que con el poco conocimiento que tengo de su implementación especifica, suena muy antinatural lo que estan haciendo.
    No digo que este mal, solo suena extranio.
    Saludos, si puedo ayudarlos con algo mas no duden en comentar.

    Like

    1. Muchisimas gracias Mauricio !

      El lunes cuando vuelva al trabajo lo probaré porque es algo que no he visto en la documentación.Desde luego y a mi parecer tambien es algo antinatural, pero me imagino que es debido a que Ilog Rules forzaba a hacer las cosas asi, es bastante antiguo y de hecho va a ser discontinuado, creo que ahora venderán solo JRules (me imagino que será un port en Java) de ahi la migración hacia Drools que estamos realizando.Desde luego la migración merece la pena ya solo por los costes de licencia, por la calidad del producto , el editor en Eclipse RCP, etc…otra cosa será el rendimiento, pero confió en que no sea mucho peor que la de ILog rules.
      El problema de hacerlo de la otra forma es que teniamos un requisito de la aplicación que era el de mostrar el número de reglas lanzadas por cada fichero de reglas procesado.

      De nuevo muchas gracias por tus excelentes articulos y ejemplos de drools y por toda tu ayuda.

      Un saludo.
      Pablo.

      Like

  6. Gracias a vos por tus comentarios y por compartir tus dudas en este espacio.
    Espero en este próximo mes estar publicando varios artículos sobre Drools, ya que estoy haciendo mucho y solo me falta el tiempo para redactarlos.
    Saludos!

    Like

  7. Buenas de nuevo Mauricio,

    Ha funcionado perfectamente ! Ahora crea las activaciones al cambiar de fichero de reglas, pero necesita el fireAllRules para ejecutarlas.

    Muchas gracias por todo.

    Un saludo,
    Pablo

    Like

  8. Pablo, gracias por comentarnos a todos que la solución ha dado resultado. Si tienen ganas de compartir sus experiencias con Drools e ILog, y quieren usar este canal, o usar el canal oficial de Drools, todos los usuarios se lo agradeceríamos.
    Saludos!

    Like

  9. Una inquietud: La primera de las tres reglas se llama “Empieza el juego porque hay n jugadores”, sin embargo en las condiciones se obliga a que haya exactamente dos jugadores, ni mas ni menos.

    Si se modifica el operador lógico de la condición de esta primera regla para que en lugar de ser “igual a 2” sea “mayor o igual a 2” (sin necesidad de añadir más jugadores a la memoria de trabajo) entonces el programa falla estrepitosamente, escupiendo una excepción org.drools.runtime.rule.ConsequenceException debida a otra excepción, en este caso una java.lang.ArithmeticException por error de división por cero.

    ¿Alguien podría explicar el porqué de esta división por cero?

    A mi lo único que se me ocurre es que al modificar esta regla para que el número de jugadores sea >= 2, al ser el dealer el tercer jugador (ya que mirando el modelo se puede ver que Dealer() hereda de Jugador()) esta regla siempre se evalúa como verdadera y se le asigna al juego “Poker” un nuevo mazo y se intenta crear e insertar un segundo dealer “Poker Dealer”. Pero de ahí al error de división por cero…

    Like

  10. Hola Victor, tu problema no esta en las reglas sino en el codigo que se usa para repartir las cartas de manera aleatoria.
    Como veras la exception empieza con: org.drools.runtime.rule.ConsequenceException, lo cual significa que estas teniendo algun problema en algun RHS.
    Como todas las reglas se encadenan, cuando esta repartiendo las cartas, el codigo puede no estar muy prolijo y no debe estar validando algun numero a la hora de repartir las cartas. Porfavor revisa esas clases y fijate exactamente en que linea te esta dando el ArithmeticException.
    Saludos

    Like

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.