Práctica 2 de Inteligencia Artificial

Sistema experto de turismo por Barcelona

Enero 2006.



Introducción

Se pide un sistema basado en el conocimiento (SBC) en lenguaje CLIPS, que recomiende actividades turísticas para varios días en Barcelona a partir de los gustos del usuario.

Análisis del problema

Identificación

El primer paso es saber en qué consiste el problema, pensar en si es apropiado para resolverlo con un SBC, y encontrar fuentes de conocimiento que ayuden a solucionarlo.

Mejor solución

Al principio hicimos un error importante, que fue suponer que debíamos dar el mejor horario, de entre todos los posibles (y empezamos a hacer el código así), pero después de leer el enunciado más veces, vimos que en ningún sitio se pide la planificación que mejor se adapte a los gustos, sino simplemente una que concuerde con los gustos y restricciones.

Eso hace el problema mucho más sencillo, ya que si tuviéramos que buscar el mejor horario, haría falta un algoritmo de ordenación de actividades según bondad, y eso -aparte de ser muy parecido a la primera práctica- funcionaría mejor en un lenguaje de programación convencional que en CLIPS. Sí que se puede hacer en CLIPS (y lo intentamos) pero sería demasiado complicado.

En cambio, encontrar uno de los horarios que cumpla las restricciones dadas por el usuario es una tarea díficil para hacer con un algoritmo, pero fácil para un sistema basado en hechos y reglas. Por tanto, el problema es apropiado para ser resuelto por un SBC

Fuentes de información

Como el tema es bastante sencillo para casi cualquier persona, los "expertos" hemos sido nosotros mismos, ya que conocemos bastantes sitios de Barcelona y hemos sido turistas alguna vez. Además, uno de nosotros está aquí de Erasmus, y disponíamos de guías de turismo y actividades y horarios reales para añadir al sistema.

Para la parte técnica, hemos consultado programas parecidos, como el sistema experto de "compra de PC". Éste es muy parecido al nuestro, ya que tiene que hacer varias preguntas para extraer información del usuario, y entonces llenar huecos del objeto solución con instancias que cumplan las condiciones que ha dicho el usuario. En cambio, el programa de "reparación de averías de coche" es demasiado sencillo comparado con el nuestro.

Para la ontología, hemos consultado los ejemplos de Protégé (como el de los vinos), y hemos usado los conceptos de UML que nos enseñan en Ingeniería del Software.

Además, como en el enunciado del problema es poco preciso, hemos preguntado más información a los "expertos" en el problema (los profesores), que nos han dado ideas como la de crear perfiles para distintos tipos de usuario, y nos han resuelto dudas.

Descripción del problema

Al final, nuestro problema queda en:

  1. Preguntar datos sobre el usuario. Éstos incluyen, como mínimo:
  2. Generar una planificación, para el número de días dicho, que:
  3. Escribir en pantalla el horario resultante.

Por tanto, en un día puede haber de 3 a 9 (3 franjas * 3 recomendaciones) actividades recomendadas, pero el usuario siempre será libre de hacer lo que quiera; son recomendaciones. Ni siquiera sabemos si irá a alguna actividad o se las saltará todas, así que no podemos hacer suposiciones de que siga el horario al pie de la letra.

También decidimos que si el turista quiere una actividad obligatoria, nos basta con ponerla una vez en uno de los días (no es necesario que aparezca cada día).

En la ontología (hecha con Protégé) tiene que haber como mínimo la información del enunciado: actividades lúdicas (conciertos, discotecas, cine y teatro) y culturales (museos, monumentos, lugares pintorescos), y restaurantes y bares. Siempre daremos recomendaciones sobre sitios donde comer, uno para la comida (entre la mañana y la tarde) y otro para cenar (entre la tarde y la noche).

Se explicará de forma más específica en el siguiente apartado.

Conceptualización

Hay que descubrir qué elementos tiene el problema, y qué relación hay entre ellos. También saber cómo lo hace un experto de verdad para solucionar el problema: qué conocimientos necesita, que hipótesis maneja, qué reglas considera como sentido común, etc.

Conceptos que aparecen

Leyendo el enunciado, vemos que una actividad necesita tener:

Las actividades pueden ser:

Un turista tiene:

Los gustos de un usuario clasifican a las actividades en:

Fases

El problema consiste en tres partes:

Preguntas

Para saber los gustos del usuario, pensamos en varias opciones:

Nos decidimos por la tercera, ya que con pocas preguntas nos permite sacar mucha información, aunque seguimos preguntando por clases concretas (segundo método). Además, hacemos varias preguntas fijas: edad, si va en grupo, si le importa el precio, ...

Hemos usado el sistema de las preguntas a falta de poder hacer cosas más complejas, como menús gráficos. Para el usuario sería más fácil marcar en una lista las actividades que le gustan y las que no (como una encuesta), y saltarse lo que vea poco interesante.

También teníamos la idea de hacer perfiles de usuario (aventurero, cansado, culto, ...) y permitir al usuario modificar uno de ellos para acabar de configurarlo a su gusto, pero eso necesitaba menús más complejos y más interacción.

Cantidad de información

No todas las respuestas aportan la misma información. Por ejemplo, la pregunta "¿Te gustan los museos de arte?" tiene como respuesta y no. Cuando el usuario responde "sí", sabemos que le gustan los museos de arte, pero cuando responde "no", no sabemos casi nada.

En algunas preguntas, la respuesta nos sirve para orientar las próximas preguntas: la primera que hacemos es "¿Prefieres un viaje tranquilo?" para saber si tenemos que consultarle sobre museos o sobre discotecas. Esta relación de hechos está basada en nuestro conocimiento, y naturalmente no cumple los gustos de todos los usuarios, pero sí de la mayoría. En esta parte es cuando el conocimiento del problema nos ayuda a simplificar la información.

Si al usuario no le gusta el cine, probablemente no es necesario preguntar cada tipo de cine (famoso, alternativo, clásico, ...). Por eso, las preguntas sobre temas generales no son "¿te gusta el cine?", sino "¿quieres opinar sobre cines?". Estas preguntas corresponden a superclases en la ontología (clases que no son hojas) y sirven sólo para orientar las preguntas; para ello usamos hechos internos de la forma (nombre_de_la_información valor).

Obligatorio y prohibido

En las preguntas concretas (las hojas), como "¿te gusta la comida china?", hay dos respuestas más aparte del "sí" y el "no": están el "sí, es obligatorio" y el "no, lo prohíbo". Esto nos permite hacer lo que pide el enunciado: que se pueda forzar al sistema a que siempre/nunca salga un tipo de actividad, sin importar el precio.

Por "obligatorio" hemos interpretado que el usuario quiere como mínimo una instancia de esa clase en toda la planificación (no en cada día). Una actividad obligatoria ha de ser muy buena, sin duda, pero tampoco conviene pasarse: si dice que quiere cine, no le vamos a poner cine cada día, por la mañana, tarde y noche. Puede que tampoco le sirva ir al cine una sola vez si va a estar un mes entero. Hay que encontrar un límite razonable que dependa de cuántos días va a estar, cosa que explicamos más adelante.

"Prohibido" es más sencillo: ninguna instancia de la clase prohibida ha de salir en el horario.

Esquema de las preguntas

Poner actividades

Ésta es la parte más importante del problema, y se explica en el apartado de Implementación.

Mostrar la planificación

La última fase es mostrar en pantalla la planificación final, en un formato que no sea muy complicado de entender.

Mostramos el nombre y dirección de cada actividad de una franja, con cabeceras para decir qué día y qué franja estamos mostrando. Otra alternativa es hacer una tabla pequeña con identificadores cortos (por ejemplo, [a010], [a142], [a857], ...), y poner la leyenda al final, para saber los datos completos de cada actividad.

Sería más interesante crear como salida un fichero HTML con una tabla que tenga enlaces a las descripciones largas.

Formalización

Una vez identificados los datos a tratar, tenemos que estudiar las generalizaciones, fiabilidad y completitud de la información. La propia representación de la información que hemos usado está en la siguiente sección, Implementación.

También habrá que saber cómo interacciona el motor de inferencia del sistema de búsqueda (que será el de CLIPS), con todo nuestro conocimiento, representado mediante hechos, reglas e instancias de una ontología.

Consistencia de la información

En la ontología está especificado el tipo de cada atributo (por ejemplo, precio es un Float), y algunos tienen restricciones especiales:

Es importante saber que no usamos valores nulos en todo el programa: para evitar problemas, decidimos incluir siempre toda la información en cada actividad, aunque no sea relevante. Por eso, una actividad que empieza a cualquier hora tiene como franja de inicio a las 3 franjas, y una que es para todos los públicos acepta edades de 1 a 99. Si es gratis, tiene precio 0. Por tanto, representamos la falta de información mediante datos concretos.

Aún así, hay algunas condiciones que pueden indicar un fallo en la información. Por ejemplo:

Evitamos esto creando bien las instancias de la ontología, y con cuidado de no poner estos datos (aunque no tiene consecuencias graves). Protégé tiene un lenguaje para expresar restricciones de integridad, pero no con el tipo de base de datos que estamos usando (sí con OWL).

También hay otras cosas que no pasarán, por ejemplo:

Incertidumbre

Toda la información que tenemos en la ontología es fiable, consistente, fija (excepto un atributo que indica si una actividad está puesta o no, el resto no cambia con el tiempo), y precisa (excepto la franja de inicio y duración, que es muy general). La planificación que creamos, en cambio, tiene información menos precisa, ya que se hacen abstracciones sobre caro/barato o sobre si la edad es adecuada o no; pero seguimos teniendo datos fiables y consistentes.

En ningún momento tenemos que corregir las decisiones tomadas, porque trabajamos siempre con hechos seguros. En parte, es porque el usuario no quiere información muy exacta (ver cada día hora a hora, saber el precio total exacto, ...).

Lo que no sabemos, claro, es si la planificación que damos es la mejor, ya que para eso habría que explorarlas todas.

Tipo y espacio de búsqueda

Nuestro problema es de síntesis (no de análisis), y se adapta perfectamente a la definición de resolución constructiva: tenemos casi infinitas soluciones, y tenemos que construir una poco a poco. Obtenemos información sobre unas restricciones (mediante preguntas al usuario) y luego intentamos diseñar la solución, combinando elementos (actividades) de forma que cumplan las restricciones.

Si hubiéramos usado perfiles de usuario (el tranquilo, el pijo, el culto, ...) para asociar a las actividades, el enfoque del problema podría cambiar: se podría hacer que, a partir de las preguntas que le hacemos al usuario, el sistema detectara aproximadamente en qué perfil o perfiles se encuentra. Entonces, la parte de las preguntas sería de análisis, en concreto se podría hacer con clasificación heurística. Pero nosotros no procesamos ni intentamos clasificar los datos del usuario, por tanto sólo hacemos síntesis.

No se trata de clasificación heurística porque no tenemos un conjunto de soluciones predefinidas, ni hacemos matching con ningún patrón para clasificar las entradas.

El mecanismo que usamos para hacer la resolución constructiva es el de proponer y aplicar, ya que:

Implementación

Incluye: cómo representamos los datos en la ontología y en CLIPS, y cómo hemos hecho el código para poner las actividades.

Pero antes...

Problemas con CLIPS

CLIPS nos ha dado bastantes problemas:

De estas cosas y otras ya hemos informado al autor. Con Protégé no ha habido ningún problema y nos gusta mucho.

Representación de los datos

A continuación se explican los atributos que usamos en estas clases.

Nombre y dirección

Datos sobre cada actividad; son los que se muestran en pantalla en el horario final.

Precio

Hemos estudiado varias formas de guardar el precio de cada actividad; por ejemplo, tener sólo un booleano que diga si la actividad es cara o no (ya que el precio exacto puede depender de muchas cosas, y cambia mucho). De esta forma, al usuario sólo habría que preguntarle que si quiere un viaje barato o caro, no que cuántos euros tiene para gastarse.

Otra alternativa es preguntar el presupuesto máximo para las vacaciones, y construir un horario tal que la suma de precios de todas las actividades no lo sobrepase. Pero esto es muy inexacto, ya que el usuario no va a hacer las 3 actividades posibles que hay en cada franja, sino sólo 1 o ninguna. Por tanto, no podemos saber exactamente el dinero total que va a costarle una planificación, y eso nos hizo alejarnos un poco de los números exactos y pensar sólo en caro/barato.

Al final pudimos encontrar una solución mixta, que usa precios en euros pero también clasifica en caro/barato para comunicarse con el usuario:

Después, al poner actividades, sólo se usarán las que tienen un precio menor al precio máximo por actividad (y no es necesario consultar si el usuario dijo barato o caro).

Esto tiene desventajas, ya que un cine de 18 euros cuenta como barato aunque para nosotros es caro, y gastarse 25 euros en un centro comercial contará como caro, aunque no es tan extraño. Pero éstos son ejemplos extremos; la mayoría de actividades sí que quedan en la categoría que les toca. Además, se graba el precio medio de cada actividad, que da más información sobre si una actividad es barata o cara.

Para los usuarios expertos, se puede incluir una pregunta más avanzada: ¿cuál es el máximo precio medio que estás dispuesto a pagar por una actividad?. Pero ya que eso son datos internos de nuestro sistema, lo ponemos nosotros a 20 ó 100 para que concuerde con los precios que tenemos en la ontología.

Hay que notar también que las actividades baratas también pueden salir en la planificación de un usuario al que no le importa el precio.

Edad permitida

Teníamos dos posibilidades: edad recomendada para cada actividad, o edad permitida en una actividad.

Hemos preguntado e investigado, y la mayoría de actividades en Barcelona no tienen una recomendación de edad concreta. Incluso es difícil clasificarlas entre niños/adultos: en un cine depende de la película, la comida es para todos, un parque de atracciones es para todo tipo de edades, los museos pueden interesar a jóvenes, etc. Quizás la única excepción son las discotecas y actividades de noche, donde haya alguna ley que prohíba la entrada a menores.

Por eso hemos interpretado las edades como edades permitidas, o sea, que si el usuario no está en este rango de edad no se las daremos (es como si las hubiese prohibido). O sea, que en una discoteca, la edad mínima será 18, y damos la posibilidad de poner una edad máxima (quizá no hay leyes sobre esto, pero algunos sitios pueden querer prohibir la entrada a los mayores).

Cada actividad tiene grabada una edad mínima y una edad máxima. Del grupo en el que va el usuario también guardamos la edad del más pequeño y del más mayor. Estas dos edades serán la misma si el usuario va sólo, cosa que también se pregunta al empezar.

En grupo

Igual que con la edad, no hemos encontrado actividades exclusivas para un tamaño de grupo concreto. A una actividad de grupo se puede ir solo, aunque quizás la condición inversa no es cierta: puede haber actividades necesariamente individuales, a las que no se puede ir en grupo (por ejemplo, ir a un bar pequeño donde no organizan comidas en grupo).

Aún así, usamos esta información al elegir actividades, aunque como condición secundaria (es más importante el precio y los gustos que esto).

Al usuario le preguntamos sólo si va en grupo o no, pero no el tamaño. Lo que contesta también sirve para preguntarle sólo su edad o las dos (del más jóven y del más viejo).

Hora de inicio y duración

Como con el precio, hemos pensado en grabar la hora de inicio exacta a la que empieza cada actividad, y el número de horas que dura. También pensamos en comprobar que el horario que dábamos no tenía actividades que se solaparan, pero vimos que sería repetir la primera práctica, y que no sabríamos hacerlo en CLIPS. Además, no sabemos qué camino de los que le ofrecemos seguirá el usuario, ni podemos ser tan precisos como para calcularlos.

La documentación dice que el día tiene tres franjas: mañana (de 8 a 13), tarde (de 13 a 20), y noche (de 21 a 3). Por eso hemos visto mejor guardar sólo, para cada actividad, la franja en la que empieza; pero no la hora exacta, ya que quizás no se sabe o es irrelevante.

De la misma manera, no tiene sentido guardar una duración en horas si no pretendemos ser precisos ni usar este dato para hacer otros cálculos. Sólo guardamos, para cada actividad, el número de franjas que dura, que puede ser 1, 2 ó 3. En la ontología no tendremos instancias incorrectas; por ejemplo, una actividad que empiece por la noche y dure 3 franjas.

La información sobre duración no la usamos al crear la planificación, y dejamos como responsabilidad del usuario saber que, si por la mañana elige hacer una actividad que dura dos franjas, entonces no podrá ir a ninguna de las que se hacen por la tarde (a no ser que la interrumpa a medias, pero eso son cosas que no controlamos). Lo que no haremos será dejar de dar actividades para hacer por la tarde.

Se permite el caso de que una actividad pueda empezar en varias franjas (el atributo será multivaluado). Y si para una actividad no importa la franja de inicio (o no es relevante), diremos que puede empezar tanto por la mañana como por la tarde como por la noche.

Gustos del usuario

Al hacer las preguntas del tipo "¿te gusta nombre_de_la_actividad?", el usuario puede decir que sí, no, obligatorio, o prohibido. Si dice que no, no aporta nada de información, pero en otro caso añadimos hechos (con assert):

Es importante lo de que usamos el nombre de la clase real como parámetro, por ejemplo, (gusta Cine_Famoso) porque Cine_Famoso es una clase de la ontología (subclase de Cine). Antes usábamos (gusta cine famoso), que nos aportaba información a nosotros, pero no permitía automatizar las reglas que eligen actividades. De la forma en que lo hemos dejado, comprobar si la actividad ?a te gusta es muy fácil, sólo hay que mirar si existe (gusta ?a).

Para no poner por error como una actividad los bares y restaurantes, los gustos de comida los tenemos separados: en vez de (gusta Comida_China) ponemos (gusta comida Comida_China). Eso ayuda en las reglas de poner actividades, porque podemos escribir algo como (gusta ?) y encontrará sólo actividades lúdico-comerciales-culturales, pero no las comidas.

Día del horario

Un día de la planificación (que es una instancia de la clase Horario) tiene los atributos comida_primera y comida_segunda, y uno para cada una de las 3 franjas: manana, tarde, noche; cada uno multivaluado (1 a 3) para poder poner las 3 alternativas.

Poner actividades en el horario

Ésta es la parte más importante, y la más difícil. Consiste en saber cómo piensa un turista de los de verdad cuando elige actividades. Naturalmente, las decisiones que toma no son universales, y dependen mucho del usuario.

Hemos tenido en cuenta muchas posibilidades; aquí mostramos algunas, y entre ellas la que hemos usado al final.

Condiciones que se comprueban

Lo primero es saber el algoritmo exacto que usa el turista. Ya sabemos en lo que se fija (en las características de la actividad), pero falta decidir el orden e importancia de cada concepto.

La primera lista de condiciones (ordenada) para decir si una actividad es buena fue:

  1. La actividad te gusta.
  2. La actividad es apta para tu bolsillo.
  3. La actividad tiene una duración que te parece bien.
  4. La actividad empieza en la franja que quieres o más tarde.
  5. La actividad es apropiada para tu tamaño de grupo.

A esto hay que añadir los "axiomas":

Ordenar la lista de actividades

La primera solución en la que pensamos (NO DEFINITIVA) fue crear una función de comparación que se basara en esas condiciones para decidir si la actividad ?a es mejor que la ?b (actividades cualquiera).

Entonces, podríamos hacer un algoritmo de ordenación según esta función de comparación, para que la lista de todas las instancias de nuestro sistema estuviera ordenada según bondad: las mejores primero, las peores al final.

La lista acabaría ordenada así (de mejor a peor):

  1. Las actividades obligatorias (que son las mejores de todas)
  2. 12345 : Las actividades perfectas: las que cumplen todas las condiciones
  3. 1234. : Las que cumplen las 4 primeras condiciones
  4. 123.. : Las que cumplen las 3 primeras
  5. 12... : Las que cumplen las 2 primeras
  6. 1.... : Las que cumplen la primera
  7. .2345 : Las que no cumplen la primera, pero sí las otras
  8. .234.
  9. .23..
  10. .2...
  11. ..345
  12. ..34.
  13. ..3..
  14. ...45
  15. ...4.
  16. ....5
  17. ..... : Las que no cumplen ninguna condición
  18. Las que son para una edad no permitida
  19. Las prohibidas

(Eso incluye toda una tabla de la verdad desordenada; media, en realidad, ya que para 5 variables salen 16 filas.)

Entonces, para poner actividades en el horario, sólo tendríamos que ir cogiendo el primer elemento de la lista y ponerlo en algún hueco del horario, y así hasta que se acabe la lista o los huecos libres.

Pero vimos que esto era demasiado parecido a un algoritmo típico de lenguajes secuenciales y programación orientada a objetos (lo haríamos mucho mejor con el JAVA), y también que no era necesario dar el mejor horario, que es lo que nos proponíamos. Así que al final no hicimos esto y usamos más reglas, hechos, y programación lógica.

De todas formas, intentamos implementarlo de dos maneras:

Perfectas, otras

La primera idea que tuvimos para poner buenas actividades fue dividirlas en dos grupos:

Naturalmente, las prohibidas no se pueden poner nunca.

En el horario pondríamos las perfectas, y luego el resto. Este método no es bueno, porque dentro de "el resto", hay algunas mejores que otras.

Simplificar las condiciones

Pensamos en hacer las más de 16 reglas para añadir actividades comprobando las 5 condiciones (con su tabla de la verdad), más las obligatorias, y las comidas. Pero como iban a ser muchas reglas y en realidad no era necesario el mejor horario, al final agrupamos las condiciones para tener menos de 5.

Nos quedamos con dos grupos: condiciones primarias (1 y 2), y secundarias (la condición 3):

  1. La actividad te gusta
  2. La actividad es apta para tu bolsillo
  3. La actividad dura lo que tú quieres, empieza a la hora que quieres, y es para tu tamaño de grupo

Estas tres condiciones necesitan 8 reglas.

  123
  ---
  ccc
  cc.
  c..
  .cc
  .c.
  ..c
  ...

Pero aún nos queda un problema: ¿qué quiere decir, por ejemplo, poner las perfectas? Formalizado es: para toda clase que cumpla las 3 condiciones, poner en el horario todas sus instancias. Esto está bien (ponemos muchas actividades muy buenas), pero puede que no sea aceptable para el usuario: si para el usuario la actividad Cine original es perfecta, no podemos ponerle en su planificación una visita a todos los cines de Barcelona, porque entonces ya no será tan perfecta. Le gustará más que haya variedad de actividades, aunque no todas sean perfectas (en esta decisión interviene el conocimiento que tenemos sobre el problema).

Decidir la cantidad

Pensamos en limitar la cantidad de actividades de cada clase: por ejemplo, no poner nunca más de 2 instancias de una clase perfecta, porque entonces dejaría de ser buena.

Pero claro, si las vacaciones son de sólo 1 día, repetir dos veces el mismo tipo de actividad puede no ser bueno. En cambio, si las vacaciones son de 30 días, 1 sola actividad perfecta es muy poco (sobre todo teniendo en cuenta lo buenas que son). Por eso necesitamos un límite variable, que dependa del número de días: el límite será N.

El proceso entonces es:

  1. Poner N instancias de cada clase obligatoria
  2. Poner N de todas las actividades perfectas (123)
  3. Poner N de las que cumplen 12.
  4. Poner N de las que cumplen 1..
  5. Poner N de las que cumplen .23
  6. Poner N de las que cumplen .2.
  7. Poner N de las que cumplen ..3
  8. Poner el resto de actividades (...)

Pero no se puede poner una actividad si: está prohibida por el usuario, si es para una edad no apropiada, o si ya está puesta en el horario.

N (entero) tiene que ser un porcentaje del número de días. Pusimos el 30% porque ya funciona bien, pero hay que sumarle 1 porque un viaje de un día no tiene tiene que tener 0 de cada tipo (0.3*1), sino 1.

  N = número_de_días * 0.3  +  1

Para un viaje de 1 a 3 días, se pone 1 de cada tipo. De 4 a 6 días, 2 de cada tipo. De 7 a 9, 3 de cada tipo. Etc.

Es importante lo que quiere decir el punto (.) en la notación usada arriba: las condiciones son no exclusivas, y el punto quiere decir que una condición no se comprueba (no que una condición ha de ser falsa). Cuando se ponen las que cumplen 123, no se ponen todas (sólo N de cada clase), así que cuando toca poner las de 12., aún hay posibilidad de coger alguna de tipo 123, ya que están incluidas en el conjunto 12.. Pero la posibilidad de coger una de 123 se va haciendo cada vez más pequeña a medida que aumenta el conjunto de actividades posibles. Así conseguimos que no sean exactamente N de cada tipo, sino que puede haber pequeñas variaciones.

De todas formas, el último paso (...) consiste en poner las que no cumplen ninguna condición en especial, o sea, todas. Ahí están incluidas tanto las perfectas, como las obligatorias, como buenas y malas. Es una buena forma de acabar de rellenar el horario.

Ésta es la solución que usamos.

Prueba

Como pruebas hemos usado varios ejemplos (que están listados en la última sección):

En general, el resultado es mejor cuantas más instancias hay en la ontología.

Otra cosa que hemos comprobado es que cada actividad se ponga en la franja permitida.

Ontología

Decisiones

Listado de clases

También adjuntamos una captura de pantalla.

  CLIPS> (browse-classes)
  OBJECT
    PRIMITIVE
      NUMBER
        INTEGER
        FLOAT
      LEXEME
        SYMBOL
        STRING
      MULTIFIELD
      ADDRESS
        EXTERNAL-ADDRESS
        FACT-ADDRESS
        INSTANCE-ADDRESS *
      INSTANCE
        INSTANCE-ADDRESS *
        INSTANCE-NAME
    USER
      INITIAL-OBJECT
      %3ACLIPS_TOP_LEVEL_SLOT_CLASS
      Actividad
        Actividad_no_comida
          L'udica
            Bar
              Bar_Irland'es
              Bar_para_Internacionales
              Bar_de_Cocktails
              Bar_Espanol *
            Club
            Discoteca
          Cultural
            Teatro
              Teatro_Cl'asico
              Improvisaci'on
              Teatro_Alternativo
            Museo
              Historia
              Arte
              Personas_C'elebres
              Tecnolog'ia
            Monumento
              Monumento_Religioso
              Edificio
              Escultura
            Cine
              Cine_Programa
              Cine_Alternativo
              Cine_Famoso
              Cine_Original
          Comercial
            Gu'ia_de_la_Ciudad
            Parque_de_Atracc'ion
            Viajes_a_fuera
        Actividad_Comida
          Comida_Espanola
            Bar_Espanol *
          Comida_China
          Comida_Mexicana
          Comida_Italiana
          Comida_Turca
          Comida_Japonesa
          Comida_R'apida
      Persona
      Horario
  CLIPS> 

Clase Actividad

Como ya hemos dicho, en sus atributos no se aceptan los valores nulos.

franja es una combinación de mañana, tarde y noche. Puede ser uno de ellos, dos o tres.

grupo acepta y no (más cómodo para el usuario, en vez de True y False). Indica si se puede hacer en grupo (lo normal es que sí).

hecho (/no) lo cambiamos a no nosotros durante el programa, pero el ingeniero del conocimiento lo puede usar para desactivar actividades.

  CLIPS> (describe-class Actividad)
  ================================================================================
  ********************************************************************************
  Concrete: direct instances of this class can be created.
  Reactive: direct instances of this class can match defrule patterns.
  
  Direct Superclasses: USER
  Inheritance Precedence: Actividad USER OBJECT
  Direct Subclasses: Actividad_no_comida Actividad_Comida
  --------------------------------------------------------------------------------
  SLOTS        : FLD DEF PRP ACC STO MCH SRC VIS CRT OVRD-MSG     SOURCE(S)
  franja       : MLT STC INH RW  LCL RCT EXC PRV RW  put-franja   Actividad
  direcci'on   : SGL STC INH RW  LCL RCT EXC PRV RW  put-direcci' Actividad
  edad_m'axima : SGL STC INH RW  LCL RCT EXC PRV RW  put-edad_m'a Actividad
  duraci'on    : SGL STC INH RW  LCL RCT EXC PRV RW  put-duraci'o Actividad
  precio       : SGL STC INH RW  LCL RCT EXC PRV RW  put-precio   Actividad
  edad_m'inima : SGL STC INH RW  LCL RCT EXC PRV RW  put-edad_m'i Actividad
  nombre       : SGL STC INH RW  LCL RCT EXC PRV RW  put-nombre   Actividad
  grupo        : SGL STC INH RW  LCL RCT EXC PRV RW  put-grupo    Actividad
  hecho        : SGL STC INH RW  LCL RCT EXC PRV RW  put-hecho    Actividad
  
  Constraint information for slots:
  
  SLOTS        : SYM STR INN INA EXA FTA INT FLT
  franja       :  #                              CRD:[1..3]
  direcci'on   :      +                          
  edad_m'axima :                          +      RNG:[1..99] 
  duraci'on    :                          +      RNG:[1..3] 
  precio       :                              +  RNG:[-oo..+oo] 
  edad_m'inima :                          +      RNG:[1..99] 
  nombre       :      +                          
  grupo        :  #                              
  hecho        :  #                              
  --------------------------------------------------------------------------------
  Recognized message-handlers:
  init primary in class USER
  delete primary in class USER
  create primary in class USER
  print primary in class USER
  direct-modify primary in class USER
  message-modify primary in class USER
  direct-duplicate primary in class USER
  message-duplicate primary in class USER
  get-franja primary in class Actividad
  put-franja primary in class Actividad
  get-direcci'on primary in class Actividad
  put-direcci'on primary in class Actividad
  get-edad_m'axima primary in class Actividad
  put-edad_m'axima primary in class Actividad
  get-duraci'on primary in class Actividad
  put-duraci'on primary in class Actividad
  get-precio primary in class Actividad
  put-precio primary in class Actividad
  get-edad_m'inima primary in class Actividad
  put-edad_m'inima primary in class Actividad
  get-nombre primary in class Actividad
  put-nombre primary in class Actividad
  get-grupo primary in class Actividad
  put-grupo primary in class Actividad
  get-hecho primary in class Actividad
  put-hecho primary in class Actividad
  ********************************************************************************
  ================================================================================
  CLIPS> 

Clase Horario

  CLIPS> (describe-class Horario)
  ================================================================================
  ********************************************************************************
  Concrete: direct instances of this class can be created.
  Reactive: direct instances of this class can match defrule patterns.
  
  Direct Superclasses: USER
  Inheritance Precedence: Horario USER OBJECT
  Direct Subclasses:
  --------------------------------------------------------------------------------
  SLOTS          : FLD DEF PRP ACC STO MCH SRC VIS CRT OVRD-MSG     SOURCE(S)
  noche          : MLT STC INH RW  LCL RCT EXC PRV RW  put-noche    Horario
  comida_primera : SGL STC INH RW  LCL RCT EXC PRV RW  put-comida_p Horario
  tarde          : MLT STC INH RW  LCL RCT EXC PRV RW  put-tarde    Horario
  manana         : MLT STC INH RW  LCL RCT EXC PRV RW  put-manana   Horario
  comida_segunda : SGL STC INH RW  LCL RCT EXC PRV RW  put-comida_s Horario
  
  Constraint information for slots:
  
  SLOTS          : SYM STR INN INA EXA FTA INT FLT
  noche          :          +   +                  CRD:[1..3]
  comida_primera :          +   +                  
  tarde          :          +   +                  CRD:[1..3]
  manana         :          +   +                  CRD:[1..3]
  comida_segunda :          +   +                  
  --------------------------------------------------------------------------------
  Recognized message-handlers:
  init primary in class USER
  delete primary in class USER
  create primary in class USER
  print primary in class USER
  direct-modify primary in class USER
  message-modify primary in class USER
  direct-duplicate primary in class USER
  message-duplicate primary in class USER
  get-noche primary in class Horario
  put-noche primary in class Horario
  get-comida_primera primary in class Horario
  put-comida_primera primary in class Horario
  get-tarde primary in class Horario
  put-tarde primary in class Horario
  get-manana primary in class Horario
  put-manana primary in class Horario
  get-comida_segunda primary in class Horario
  put-comida_segunda primary in class Horario
  imprime primary in class Horario
  ********************************************************************************
  ================================================================================
  CLIPS> 

Otras ideas

Pensamos en añadir más atributos a cada actividad:

Instancias

Hemos creado más de 150, de muchos tipos y atributos, para asegurarnos de que funciona.

Fragmento del Actividades.pins:

  ([Actividades_Instance_10042] of Parque_de_Atracc'ion
          (direcci'on "Serra de Collserola")
          (duraci'on 1)
          (edad_m'axima 99)
          (edad_m'inima 1)
          (franja tarde)
          (grupo s'i) 
          (hecho no) 
          (nombre "Parc del  Laberint d'Horta")
          (precio 4.0)) 
  
  ([Actividades_Instance_10043] of Tecnolog'ia
          (direcci'on "Carrer Roviralta 55")
          (duraci'on 1)
          (edad_m'axima 99)
          (edad_m'inima 4)
          (franja tarde)
          (grupo s'i) 
          (hecho no) 
          (nombre "Museu de la Ci'encia")
          (precio 8.0)) 
  
  ([Actividades_Instance_10044] of Tecnolog'ia
          (direcci'on "Montjuic")
          (duraci'on 1)
          (edad_m'axima 99)
          (edad_m'inima 1)
          (franja tarde)
          (grupo s'i) 
          (hecho no) 
          (nombre "Museu Militar")
          (precio 7.0)) 
  
  ([Actividades_Instance_10045] of Historia
          (direcci'on "Carrer Aristides Maillol")
          (duraci'on 1)
          (edad_m'axima 99)
          (edad_m'inima 1)
          (franja tarde)
          (grupo s'i) 
          (hecho no) 
          (nombre "Museo del Futbol Club Barcelona")
          (precio 10.0))
  
  ([Actividades_Instance_10046] of Arte
          (duraci'on 1)
          (edad_m'axima 99) 
          (edad_m'inima 1)
          (franja manana)
          (grupo s'i)
          (hecho no)
          (nombre "Museu de la Xocolata")
          (precio 5.0)) 
  
  ([Actividades_Instance_10047] of Personas_C'elebres
          (duraci'on 1)
          (edad_m'axima 99)
          (edad_m'inima 1)
          (franja tarde)
          (grupo s'i)
          (hecho no)
          (nombre "Museo de Cera")
          (precio 5.0))
  

Código fuente del sistema

  (defglobal MAIN 
      ?*manana* = (create$)
      ?*tarde*  = (create$)
      ?*noche*  = (create$)
  )
  
  (defclass MAIN::Horario "El horario de las actividades"
     (is-a USER)
     (role concrete)
     (multislot noche
        (type INSTANCE)
        (cardinality 1 3)
        (create-accessor read-write))
     (single-slot comida_primera
        (type INSTANCE)
        (create-accessor read-write))
     (multislot tarde
        (type INSTANCE)
        (cardinality 1 3)
        (create-accessor read-write))
     (multislot manana
        (type INSTANCE)
        (cardinality 1 3)
        (create-accessor read-write))
     (single-slot comida_segunda
        (type INSTANCE)
        (create-accessor read-write)))
  
  ;;; ########################################################################
  ;;; ### Reglas, funciones, etc. para imprimir la planificaci'on          ###
  ;;; ########################################################################
  
  
  ;; La función "imprime-poco" imprime sólo el nombre y la dirección de una 
  ;; actividad. Usamos para monstrar la planificaión en la pantalla
  
  (deffunction MAIN::imprime-poco
     (?v)
     (if (eq (nth$ 1 ?v) [nil]) then 
        (printout t "¡ ERROR !  0 actividades" crlf) (return)
     )
     (if (> (length$ ?v) 0)
        then
        (loop-for-count (?i 1 (length ?v))
           (printout t "nombre: " (send (nth$ ?i ?v ) get-nombre) crlf)
           (printout t "direcci'on: " (send (nth$ ?i ?v ) get-direcci'on) crlf)
           (printout t crlf)
        )))
  
  ;; Esta función hace la misma como "imprime-poco" pero hay una diferencia: 
  ;; en cada atributo hay sólo una actividad, no es multivaluado.
  
  (deffunction MAIN::imprime-comida
     (?v)
     (if (eq ?v [nil]) then 
        (printout t "¡ ERROR !  0 actividades" crlf) (return)
     )
     (printout t "nombre: " (send ?v get-nombre) crlf)
     (printout t "direcci'on: " (send ?v get-direcci'on) crlf)
     (printout t crlf)
  )
  
  (deffunction MAIN::imprime-poco-comida
      (?v)
      (if (eq ?v [nil]) then 
         (printout t "¡ ERROR !  0 actividades" crlf) (return)
      )
      (printout t (send ?v get-nombre) crlf)
  )
     
  ;; Para imprimir el horario con un message-handler. Se ejecuta cuando se 
  ;; escribe (send [instancia de horario] imprime)
  
  (deffunction imprime-muy-poco
     (?v)
     (if (eq (nth$ 1 ?v) [nil]) then
        (printout t "¡ ERROR !  0 actividades" crlf) (return)
     )
     (if (> (length$ ?v) 0)
        then
        (loop-for-count (?i 1 (length ?v))
            (printout t (send (nth$ ?i ?v ) get-nombre) " ; ")
     ))
     (printout t crlf))
  
  (defmessage-handler MAIN::Horario imprime primary
     ()
  
     (printout t "Manana:  ")
     (imprime-muy-poco ?self:manana)
     (printout t "Tarde :  ")
     (imprime-muy-poco ?self:tarde)
     (printout t "Noche :  ")
     (imprime-muy-poco ?self:noche)
     (printout t "Comida primera: ")
     (imprime-poco-comida ?self:comida_primera)
     (printout t "Comida segunda: ")
     (imprime-poco-comida ?self:comida_segunda)
  )
  		
  ;; Esta regla ejecutamos al final para imprimir todos los días de la
  ;; planificación recomendada
  
  (defrule imprimir-planificaci'on
     (declare  (salience -30))
     (d'ia ?d)
  =>
     (loop-for-count (?i 1 ?d)
        (printout t "D'ia " ?i ": " crlf)
        (send 
           (symbol-to-instance-name (string-to-field (str-cat "d" ?i))) imprime
        )
        (printout t crlf)
     ))
  
  ;;; ########################################################################
  ;;; ### Obtiene una respuesta de entre un conjunto de respuestas posibles###
  ;;; ########################################################################
  
  	(deffunction pregunta (?pregunta $?valores_posibles)
     (printout t ?pregunta)
     (bind ?respuesta (read))
     (while (not (member ?respuesta ?valores_posibles)) do
        (printout t ?pregunta)
        (bind ?respuesta (read))
     )
     ?respuesta)
  
  ;;; ########################################################################
  ;;; ### Hace una pregunta a la que hay que responder si o no             ###
  ;;; ########################################################################
  
  (deffunction si-o-no-p (?pregunta)
     (bind ?respuesta (pregunta ?pregunta si no s n))
     (if (or (eq ?respuesta si) (eq ?respuesta s))
         then TRUE 
         else FALSE))
  
  ;;; ########################################################################
  ;;; ### Hace una pregunta a la que hay que responder si, no, obl o proh  ###
  ;;; ########################################################################
  
  (deffunction todo-pos-p (?pregunta)
     (bind ?respuesta (pregunta ?pregunta si no s n obligatorio prohibido o p))
     (switch ?respuesta
        (case si then s)
        (case s then s)
        (case no then n)
        (case n then n)
        (case obligatorio then o)
        (case o then o)
        (case prohibido then p)
        (case p then p)
        (default none))
  )
  
  ;;; ########################################################################
  ;;; ### Las preguntas                                                    ###
  ;;; ########################################################################
  
  (defrule determinar-tipo
    (declare (salience 10))
  =>
    (if (si-o-no-p "¿Quieres un viaje tranquilo? (s/n) ") then
        (assert (tranquilo))
     else
        (assert (no tranquilo)))
    (if (si-o-no-p "¿Quieres visitar los lugares típicos de Barcelona? (s/n) ") 
     then (assert (local))
     else 
        (assert (no local)))
  )
  
  ;;; ########################################################################
  ;;; ### Preguntas para el tipo del cine                                  ###
  ;;; ########################################################################
  
  (defrule preg-cine
   ; ()
  =>
    (if (si-o-no-p "¿Quieres opinar sobre cines? (s/n) ")
         then (assert (cine))
    )
  )
  
  (defrule preg-cine-original
    (cine)
    (no local)
  =>
    (bind ?respuesta (todo-pos-p "¿Te gusta el cine original? (s/n/o/p) "))
        (switch ?respuesta
           (case s then (assert (gusta Cine_Original)))
           (case o then (assert (obligatorio Cine_Original)))
           (case p then (assert (prohibido Cine_Original)))
        )
  )
  
  (defrule preg-cine-famoso
    (cine)
  =>
    (bind ?respuesta (todo-pos-p "¿Te gusta el cine famoso? (s/n/o/p) "))
        (switch ?respuesta
           (case s then (assert (gusta Cine_Famoso)))
           (case o then (assert (obligatorio Cine_Famoso)))
           (case p then (assert (prohibido Cine_Famoso)))
        )
  )
  
  (defrule preg-cine-alternativo
    (cine)
  =>
    (bind ?respuesta (todo-pos-p "¿Te gusta el cine alternativo? (s/n/o/p) "))
        (switch ?respuesta
           (case s then (assert (gusta Cine_Alternativo)))
           (case o then (assert (obligatorio Cine_Alternativo)))
           (case p then (assert (prohibido Cine_Alternativo)))
        )
  )
  
  ;;; ########################################################################
  ;;; ### Preguntas para el tipo del teatro                                ###
  ;;; ########################################################################
  
  (defrule preg-teatro
    (tranquilo)
  =>
    (if (si-o-no-p "¿Quieres opinar sobre teatro? (s/n) ")
         then (assert (teatro))
    )
  )
  
  (defrule preg-teatro-cl'asico
    (teatro)
  =>
    (bind ?respuesta (todo-pos-p "¿Te gusta el teatro clásico? (s/n/o/p) "))
        (switch ?respuesta
           (case s then (assert (gusta Teatro_Cl'asico)))
           (case o then (assert (obligatorio Teatro_Cl'asico)))
           (case p then (assert (prohibido Teatro_Cl'asico)))
        )
  )
  
  (defrule preg-teatro-improvisaci'on
    (teatro)
  =>
    (bind ?respuesta 
        (todo-pos-p "¿Te gusta el teatro improvisación? (s/n/o/p) ")
    )
        (switch ?respuesta
           (case s then (assert (gusta Improvisaci'on)))
           (case o then (assert (obligatorio Improvisaci'on)))
           (case p then (assert (prohibido Improvisaci'on)))
        )
  )
  
  (defrule preg-teatro-alternativo
    (teatro)
  =>
    (bind ?respuesta (todo-pos-p "¿Te gusta el teatro alternativo? (s/n/o/p) "))
        (switch ?respuesta
           (case s then (assert (gusta Teatro_Alternativo)))
           (case o then (assert (obligatorio Teatro_Alternativo)))
           (case p then (assert (prohibido Teatro_Alternativo)))
        )
  )
  
  ;;; ########################################################################
  ;;; ### Preguntas para el tipo del museo                                 ###
  ;;; ########################################################################
  
  (defrule preg-museo
    (tranquilo)
    (local)
  =>
    (if (si-o-no-p "¿Quieres opinar sobre los museos? (s/n) ")
         then (assert (museo))
    )
  )
  
  (defrule preg-museo-historia
    (museo)
  =>
    (bind ?respuesta (todo-pos-p "¿Te gusta el museo historia? (s/n/o/p) "))
        (switch ?respuesta
           (case s then (assert (gusta Historia)))
           (case o then (assert (obligatorio Historia)))
           (case p then (assert (prohibido Historia)))
        )
  )
  
  (defrule preg-museo-arte
    (museo)
  =>
    (bind ?respuesta (todo-pos-p "¿Te gusta el museo de arte? (s/n/o/p) "))
        (switch ?respuesta
           (case s then (assert (gusta Arte)))
           (case o then (assert (obligatorio Arte)))
           (case p then (assert (prohibido Arte)))
        )
  )
  
  (defrule preg-museo-tecnolog'ia
    (museo)
  =>
    (bind ?respuesta (todo-pos-p "¿Te gusta el museo de tecnología? (s/n/o/p) "))
        (switch ?respuesta
           (case s then (assert (gusta Tecnolog'ia)))
           (case o then (assert (obligatorio Tecnolog'ia)))
           (case p then (assert (prohibido Tecnolog'ia)))
        )
  )
  
  (defrule preg-museo-personas-c'elebres
    (museo)
  =>
    (bind ?respuesta 
        (todo-pos-p "¿Te gusta el museo de personas célebres? (s/n/o/p) ")
    )
    (switch ?respuesta
        (case s then (assert (gusta Personas_C'elebres)))
        (case o then (assert (obligatorio Personas_C'elebres)))
        (case p then (assert (prohibido Personas_C'elebres)))
    )
  )
  
  ;;; ########################################################################
  ;;; ### Preguntas para el tipo del monumento                             ###
  ;;; ########################################################################
  
  (defrule preg-monumento
    (tranquilo)
    (local)
  =>
    (if (si-o-no-p "¿Quieres opinar sobre los monumentos? (s/n) ")
         then (assert (monumento))
    )
  )
  
  (defrule preg-monumento-edificio
    (monumento)
  =>
    (bind ?respuesta (todo-pos-p "¿Te gusta edificios típicos? (s/n/o/p) "))
    (switch ?respuesta
        (case s then (assert (gusta Edificio)))
        (case o then (assert (obligatorio Edificio)))
        (case p then (assert (prohibido Edificio)))
     )
  )
  
  (defrule preg-monumento-religioso
    (monumento)
  =>
    (bind ?respuesta 
        (todo-pos-p "¿Te gusta los monumentos religiosos? (s/n/o/p) ")
    )
    (switch ?respuesta
        (case s then (assert (gusta Monumento_Religioso)))
        (case o then (assert (obligatorio Monumento_Religioso)))
        (case p then (assert (prohibido Monumento_Religioso)))
    )
  )
  
  (defrule preg-monumento-escultura
    (monumento)
  =>
    (bind ?respuesta (todo-pos-p "¿Te gusta las esculturas? (s/n/o/p) "))
     (switch ?respuesta
        (case s then (assert (gusta Escultura)))
        (case o then (assert (obligatorio Escultura)))
        (case p then (assert (prohibido Escultura)))
     )
  )
  
  ;;; ########################################################################
  ;;; ### Preguntas para el tipo del bar                                   ###
  ;;; ########################################################################
  
  (defrule preg-bar
    
  =>
    (if (si-o-no-p "¿Te gustan los bares? (s/n) ")
         then (assert (bar))
    )
  )
  
  (defrule bar-espanol
    (bar)
    (local)
  =>
    (assert (gusta Bar_Espanol))
  )
  
  (defrule preg-bar-espanol
    (bar)
    (no local)
  =>
    (bind ?respuesta (todo-pos-p "¿Te gustan los bares españoles? (s/n/o/p) "))
    (switch ?respuesta
       (case s then (assert (gusta Bar_Espanol)))
       (case o then (assert (obligatorio Bar_Espanol)))
       (case p then (assert (prohibido Bar_Espanol)))
    )
  )
  
  (defrule preg-bar-irland'es
    (bar)
    (no local)
  =>
    (bind ?respuesta (todo-pos-p "¿Te gustan los bares irlandeses? (s/n/o/p) "))
    (switch ?respuesta
       (case s then (assert (gusta Bar_Irland'es)))
       (case o then (assert (obligatorio Bar_Irland'es)))
       (case p then (assert (prohibido Bar_Irland'es)))
    )
  )
  
  (defrule preg-bar-internacional
    (bar)
    (no local)
  =>
    (bind ?respuesta 
       (todo-pos-p "¿Te gustan los bares para internacionales? (s/n/o/p) ")
    )
    (switch ?respuesta
       (case s then (assert (gusta Bar_para_Internacionales)))
       (case o then (assert (obligatorio Bar_para_Internacionales)))
       (case p then (assert (prohibido Bar_para_Internacionales)))
    )
  )
  
  (defrule preg-bar-cocktail
    (bar)
    (no local)
  =>
    (bind ?respuesta 
        (todo-pos-p "¿Te gustan los bares de cocktails? (s/n/o/p) ")
    )
    (switch ?respuesta
       (case s then (assert (gusta Bar_de_Cocktails)))
       (case o then (assert (obligatorio Bar_de_Cocktails)))
       (case p then (assert (prohibido Bar_de_Cocktails)))
    )
  )
  
  ;;; ########################################################################
  ;;; ### Preguntas para el tipo de la actividad comercial                 ###
  ;;; ########################################################################
  
  (defrule preg-comercial
    (no tranquilo)
  =>
    (if (si-o-no-p "¿Quieres opinar sobre las actividades 
                                           organizadas y comerciales? (s/n) ")
         then (assert (comercial))
    )
  )
  
  (defrule preg-comercial-viaje_a_fuera
    (comercial)
    (local)
  =>
    (bind ?respuesta (todo-pos-p "¿Te gustan los viajes a fuera? (s/n/o/p) "))
    (switch ?respuesta
       (case s then (assert (gusta Viajes_a_fuera)))
       (case o then (assert (obligatorio Viajes_a_fuera)))
       (case p then (assert (prohibido Viajes_a_fuera)))
    )
  )
  
  (defrule preg-comercial-gu'ia_de_la_ciudad
    (comercial)
    (local)
  =>
    (bind ?respuesta 
        (todo-pos-p "¿Te gustan las guías de la ciudad? (s/n/o/p) ")
    )
    (switch ?respuesta
       (case s then (assert (gusta Gu'ia_de_la_Ciudad)))
       (case o then (assert (obligatorio Gu'ia_de_la_Ciudad)))
       (case p then (assert (prohibido Gu'ia_de_la_Ciudad)))
    )
  )
  
  (defrule preg-comercial-parque_de_atracci'on
    (comercial)
  =>
    (bind ?respuesta 
       (todo-pos-p "¿Te gustan las parques de atracciones? (s/n/o/p) ")
    )
    (switch ?respuesta
       (case s then (assert (gusta Parque_de_Atracci'on)))
       (case o then (assert (obligatorio Parque_de_Atracci'on)))
       (case p then (assert (prohibido Parque_de_Atracci'on)))
    )
  )
  
  ;;; ########################################################################
  ;;; ### Preguntas sobre dicotecas y clubs                                ###
  ;;; ########################################################################
  
  (defrule preg-discoteca
    (no tranquilo)
  =>
    (bind ?respuesta (todo-pos-p "¿Te gustan las discotecas? (s/n/o/p) "))
    (switch ?respuesta
       (case s then (assert (gusta Discoteca)))
       (case o then (assert (obligatorio Discoteca)))
       (case p then (assert (prohibido Discoteca)))
    )
  )
  
  (defrule preg-club
    (no tranquilo)
  =>
    (bind ?respuesta (todo-pos-p "¿Te gustan los clubs? (s/n/o/p) "))
    (switch ?respuesta
       (case s then (assert (gusta Club)))
       (case o then (assert (obligatorio Club)))
       (case p then (assert (prohibido Club)))
    )
  )
  
  ;;; ########################################################################
  ;;; ### Preguntas para el tipo de comida                                 ###
  ;;; ########################################################################
  
  (defrule preg-preferencia-comida
  =>
    (if (si-o-no-p "¿Tienes alguna preferencia de la comida? (s/n) ") then
       (assert (comida))
     else
       (assert (gusta comida Comida_R'apida))
       (assert (gusta comida Comida_Turca))
       (assert (gusta comida Comida_Japonesa))
       (assert (gusta comida Comida_Mexicana))
       (assert (gusta comida Comida_China))
       (assert (gusta comida Comida_Italiana))
       (assert (gusta comida Comida_Espanola))
    )
  )
  
  (defrule preg-lugar-comida
    (comida)
  =>
    (if (si-o-no-p "¿Prefieres un bar para comer? (s/n) ") then
       (assert (gusta comida Comida_Espanola))
     else
       (assert (lugar_igual))
    )
  )
  
  (defrule preg-comida-r'apida
    (lugar_igual)
  =>
    (bind ?respuesta (todo-pos-p "¿Te gusta la comida rápida? (s/n/o/p) "))
        (switch ?respuesta
           (case s then (assert (gusta comida Comida_R'apida)))
           (case o then (assert (obligatorio comida Comida_R'apida)))
           (case p then (assert (prohibido comida Comida_R'apida)))
        )
  )
  
  (defrule preg-comida-turca
    (lugar_igual)
  =>
    (bind ?respuesta (todo-pos-p "¿Te gusta la comida turca? (s/n/o/p) "))
        (switch ?respuesta
           (case s then (assert (gusta comida Comida_Turca)))
           (case o then (assert (obligatorio comida Comida_Turca)))
           (case p then (assert (prohibido comida Comida_Turca)))
        )
  )
  
  (defrule preg-comida-japonesa
    (lugar_igual)
  =>
    (bind ?respuesta (todo-pos-p "¿Te gusta la comida japonesa? (s/n/o/p) "))
        (switch ?respuesta
           (case s then (assert (gusta comida Comida_Japonesa)))
           (case o then (assert (obligatorio comida Comida_Japonesa)))
           (case p then (assert (prohibido comida Comida_Japonesa)))
        )
  )
  
  (defrule preg-comida-mexicana
    (lugar_igual)
  =>
    (bind ?respuesta (todo-pos-p "¿Te gusta la comida mexicana? (s/n/o/p) "))
        (switch ?respuesta
           (case s then (assert (gusta comida Comida_Mexicana)))
           (case o then (assert (obligatorio comida Comida_Mexicana)))
           (case p then (assert (prohibido comida Comida_Mexicana)))
        )
  )
  
  (defrule preg-comida-china
    (lugar_igual)
  =>
    (bind ?respuesta (todo-pos-p "¿Te gusta la comida china? (s/n/o/p) "))
        (switch ?respuesta
           (case s then (assert (gusta comida Comida_China)))
           (case o then (assert (obligatorio comida Comida_China)))
           (case p then (assert (prohibido comida Comida_China)))
        )
  )
  
  (defrule preg-comida-italiana
    (lugar_igual)
  =>
    (bind ?respuesta (todo-pos-p "¿Te gusta la comida italiana? (s/n/o/p) "))
        (switch ?respuesta
           (case s then (assert (gusto comida Comida_Italiana)))
           (case o then (assert (obligatorio comida Comida_Italiana)))
           (case p then (assert (prohibido comida Comida_Italiana)))
        )
  )
  
  (defrule preg-comida-espanola
    (lugar_igual)
  =>
    (bind ?respuesta (todo-pos-p "¿Te gusta la comida española? (s/n/o/p) "))
        (switch ?respuesta
           (case s then (assert (gusta comida Comida_Espanola)))
           (case o then (assert (obligatorio comida Comida_Espanola)))
           (case p then (assert (prohibido comida Comida_Espanola)))
        )
  )
  
  ;;; ########################################################################
  ;;; ### Preguntas comunes                                                ###
  ;;; ########################################################################
  
  (defrule preg-precio
  =>
    (if (si-o-no-p "¿Te importa el precio? (s/n) ")
       then (assert (precio_m'aximo 20))
     else
       (assert (precio_m'aximo 100))
    )
  )
  
  (defrule preg-grupo
  =>
    (if (si-o-no-p "¿Viajas sólo? (s/n) ")
       then (assert (grupo no))
     else
       (assert (grupo s'i))
    )
  )
  
  (defrule preg-edad-solo
    (grupo no)
  =>
    (printout t "¿Cuántos años tienes?")
    (bind ?respuesta (read))
    (while (not (integerp ?respuesta))
        (printout t "¿Cuántos años tienes?")
        (bind ?respuesta (read))
    )
    (assert (edad_m'inima ?respuesta))
    (assert (edad_m'axima ?respuesta))
  )
  
  (defrule preg-edad-grupo
    (grupo s'i)
  =>
    (printout t "¿Cuántos años tiene la persona más joven?")
    (bind ?respuesta (read))
    (while (not (integerp ?respuesta))
        (printout t "¿Cuántos años tiene la persona más joven?")
        (bind ?respuesta (read))
    )
    (assert (edad_m'inima ?respuesta))
    (printout t "¿Cuántos años tiene la persona más mayor?")
    (bind ?respuesta (read))
    (while (not (integerp ?respuesta))
        (printout t "¿Cuántos años tiene la persona más mayor?")
        (bind ?respuesta (read))
    )
    (assert (edad_m'axima ?respuesta))
  )
  
  (defrule preg-duraci'on
  =>
    (bind ?respuesta 
       (pregunta 
           "¿Cuántas franjas puede durar una actividad como máximo? (1/2/3) " 
        1 2 3)
    )
    (assert (duraci'on ?respuesta))
  )
  
  (defrule preg-cantidad-dia
  =>
    (printout t "¿Cuántos días quieres quedar in Barcelona?")
    (bind ?respuesta (read))
    (while (not (integerp ?respuesta))
        (printout t "¿Cuántos días quieres quedar in Barcelona?")
        (bind ?respuesta (read))
    )
    (assert (d'ia ?respuesta))
  )
  
  ;;; ########################################################################
  ;;; ### Crear las instancias del horario                                 ###
  ;;; ########################################################################
  
  ;; Esta regla solamente crea las instancias del horario después de la última
  ;; pregunta 
  
  (defrule crear-horario
     (d'ia ?d)
  =>
     (while (> ?d 0)
        (make-instance (string-to-field (str-cat "d" ?d)) of Horario)
        (bind ?d (- ?d 1))
     )
  )
  
  ;;; ########################################################################
  ;;; ### Funciones para simplificar la programación                       ###
  ;;; ########################################################################
  
  ;; Una función para añadir una actividad a un atributo en que ya están
  ;; actividades puestas. Primero se recibe el contenido de un atributo, se
  ;; crea un valor multivaluado de las informaciones del atributo y de la
  ;; actividad nueva. Al final se pone todo en el atributo.
  
  (deffunction anadir-algo (?instancia ?atributo ?actividad)
     (switch ?atributo
        (case manana then  
           (bind ?var (nth$ 1 (send ?instancia get-manana)))
  	 (if (eq ?var [nil]) then
  	     (send ?instancia put-manana ?actividad)
  	  else
               (bind ?var (send ?instancia get-manana))
  	     (send ?instancia put-manana (create$ ?var ?actividad))
  	 )
        )
        (case tarde then
           (bind ?var (nth$ 1 (send ?instancia get-tarde)))
           (if (eq ?var [nil]) then
               (send ?instancia put-tarde ?actividad)
            else
               (bind ?var (send ?instancia get-tarde))
               (send ?instancia put-tarde (create$ ?var ?actividad))
           )
        )
        (case noche then
           (bind ?var (nth$ 1 (send ?instancia get-noche)))
  	 (if (eq ?var [nil]) then
  	     (send ?instancia put-noche ?actividad)
  	  else
               (bind ?var (send ?instancia get-noche))
  	     (send ?instancia put-noche (create$ ?var ?actividad))
  	 )	 
        )
     )
  )
  
  ;; La función comprueba que se puede hacer una actividad en grupo o
  ;; sólo. Además una persona sola puede hacer una actividad recomendada
  ;; para un grupo sólo pero un grupo no puede hacer las otras en grupo.
  
  (deffunction grupo (?actividad ?grupo)
        (bind ?var (send ?actividad get-grupo))
        (bind ?respuesta FALSE)
        (switch ?var
            (case s'i then
                  (if (eq ?grupo s'i) then (bind ?respuesta TRUE))
             )
            (case no then
                  (if (eq ?grupo (or s'i no)) then (bind ?respuesta FALSE))
            )
            (default (bind ?respuesta FALSE))
         )
         (bind ?respuesta ?respuesta)
  )
  
  ;;; ########################################################################
  ;;; ### Funciones para distribuir las actividades no comida              ###
  ;;; ########################################################################
  
  ;; La función recibe todas las informaciones de los hechos y una actividad.
  ;; Después comprueba las condiciones según los parametros ?cond2 ?cond3.
  ;; Las condiciones son según de nuestra documentación: La segunda es para el 
  ;; precio y del presupuesto del usuario y la tercera es para el tamaño del
  ;; grupo y la duración de la activida. (La primera condición es el gusto del
  ;; usuario.)
  
  ;; En la segunda fase de la función buscamos un día de los posibles en que hay 
  ;; una franja libre en que podemos poner la actividad elegida. Para distribuir
  ;; las actividades hay dos bucles: un para los días y un para el limite de las
  ;; actividades en una franja. En el primer paso buscamos una franja libre o de
  ;; una actividad puesta, después con dos actividades, etc. Sin estos bucles el
  ;; algoritmo llena el primer día completo y después el próximo.
  
  ;; Al final cambiamos el valor del atributo "hecho" para olvidar poner una 
  ;; misma actividad dos veces en una planificación.
  
  (deffunction distribuir 
     (?actividad ?dia ?precio ?edad_m'axima ?edad_m'inima ?grupo ?duraci'on 
                                                                   ?cond2 ?cond3)
  
     (if (eq ?cond2 true) then 
         (if (not (< (send ?actividad get-precio) ?precio)) then 
            (return)
         )
     )
     (if (eq ?cond3 true) then 
         (if (not (< (send ?actividad get-duraci'on) ?duraci'on)) then 
              (return)
         )
     )
     (if (eq ?cond3 true) then (if (not (grupo ?actividad ?grupo)) then 
         (return))
     )
     (if (not (>= (send ?actividad get-edad_m'axima) ?edad_m'axima)) then 
         (return)
     )
     (if (not (<= (send ?actividad get-edad_m'inima) ?edad_m'inima)) then 
         (return)
     )
  
     (bind ?franjas (send ?actividad get-franja))
     (loop-for-count (?i 2 3)
         (loop-for-count (?j 1 ?dia)
             (bind ?dia_elegido (nth$ 1 (find-instance ((?horario Horario))
               (or
                 (and (< (length$ ?horario:manana) ?i) (member$ manana ?franjas))
                 (and (< (length$ ?horario:tarde) ?i) (member$ tarde ?franjas))
                 (and (< (length$ ?horario:noche) ?i) (member$ noche ?franjas))
               )
             )))
             (if (not (eq ?dia_elegido nil)) then (break))
         )
         (if (not (eq ?dia_elegido nil)) then (break))
     )
  
     (if (eq ?dia_elegido nil) then (return))   
     
     (bind ?c1 (and 
  	       (< (length$ (send ?dia_elegido get-manana)) 3) 
  	       (member$ manana ?franjas)
     ))
     (bind ?c2 (and 
  	       (< (length$ (send ?dia_elegido get-tarde)) 3) 
  	       (member$ tarde ?franjas)
     ))
     (bind ?c3 (and 
  	       (< (length$ (send ?dia_elegido get-noche)) 3) 
  	       (member$ noche ?franjas)
     ))
  
     (if ?c1 then
         (anadir-algo ?dia_elegido manana ?actividad)
         (printout t "manana" crlf)
      else
         (if ?c2 then
             (anadir-algo ?dia_elegido tarde ?actividad)
             (printout t "tarde" crlf)
          else
             (if ?c3 then
                  (anadir-algo ?dia_elegido noche ?actividad)
                  (printout t "noche" crlf)
             )
         )
     )
     (send ?actividad put-hecho s'i)
  )
  
  ;; Hay sólo una diferencia de esta función y la última: Con la condición 
  ;; "gusta" podemos buscar una actividad según el nombre de la clase. Por 
  ;; eso sólo damos el nombre de la clase a la función y elegimos la 
  ;; actividad dentro. Sin la condición primera no podemos buscar según 
  ;; del nombre. Ejecutamos al regla/función con cada actividad. Cuando hay 
  ;; una activida que no cumple todas las condiciones necesarias se acaba 
  ;; la función.
  
  (deffunction distribuir-gustas 
     (?gusta ?dia ?precio ?edad_m'axima ?edad_m'inima ?grupo 
                                                  ?duraci'on ?cond2 ?cond3)
     
     (bind ?limite (+ (* ?dia 0.3) 1))
     (loop-for-count (?i 1 ?limite)
        (bind ?actividad (nth$ 1 (find-instance ((?inst ?gusta))
           (and
              (eq ?inst:hecho no)
              (>= ?inst:edad_m'axima ?edad_m'axima)
              (<= ?inst:edad_m'inima ?edad_m'inima)
           )
        )))
     (if (eq ?actividad nil) then (return))
  
     (printout t "Actividad: " ?actividad crlf)
  
     (if (eq ?cond2 true) then 
         (if (not (< (send ?actividad get-precio) ?precio)) then 
            (return)
         )
     )
     (if (eq ?cond3 true) then 
         (if (not (< (send ?actividad get-duraci'on) ?duraci'on)) then 
              (return)
         )
     )
     (if (eq ?cond3 true) then (if (not (grupo ?actividad ?grupo)) then 
         (return))
     )
     (if (not (>= (send ?actividad get-edad_m'axima) ?edad_m'axima)) then 
         (return)
     )
     (if (not (<= (send ?actividad get-edad_m'inima) ?edad_m'inima)) then 
         (return)
     )
     
     (bind ?franjas (send ?actividad get-franja))
       (loop-for-count (?i 2 3)
         (loop-for-count (?j 1 ?dia)
           (bind ?dia_elegido (nth$ 1 (find-instance ((?horario Horario))
              (or
                (and (< (length$ ?horario:manana) ?i) (member$ manana ?franjas))
                (and (< (length$ ?horario:tarde) ?i) (member$ tarde ?franjas))
                (and (< (length$ ?horario:noche) ?i) (member$ noche ?franjas))
              )
            )))
            (if (not (eq ?dia_elegido nil)) then (break))
  	)      
          (if (not (eq ?dia_elegido nil)) then (break))
        )
  
        (if (eq ?dia_elegido nil) then (return))
  
        (bind ?c1 (and 
  		    (< (length$ (send ?dia_elegido get-manana)) 3) 
  		    (member$ manana ?franjas)
        ))
        (bind ?c2 (and 
  		    (< (length$ (send ?dia_elegido get-tarde)) 3) 
  		    (member$ tarde ?franjas)
        ))
        (bind ?c3 (and 
  		    (< (length$ (send ?dia_elegido get-noche)) 3) 
  		    (member$ noche ?franjas)
        ))
  
        (if ?c1 then
             (anadir-algo ?dia_elegido manana ?actividad)
         else
             (if ?c2 then
                   (anadir-algo ?dia_elegido tarde ?actividad)
              else
                   (if ?c3 then
                        (anadir-algo ?dia_elegido noche ?actividad)
                   )
              )
         )
         (send ?actividad put-hecho s'i)
     )
  ) 
  
  ;; La función para distribuir las actividades obligatorias es más 
  ;; sencilla porque no hace falta comprobar las condiciones. El 
  ;; usuario quiere hacerlo independiente de otras condiciones.
  
  (deffunction distribuir-obligatorias (?gusta)
  
     (bind ?actividad 
           (nth$ 1 (find-instance ((?inst ?gusta)) (eq ?inst:hecho no)))
     )
     (if (eq ?actividad nil) then (return))
     
     (bind ?franjas (send ?actividad get-franja))
  
     (bind ?dia_elegido (nth$ 1 (find-instance ((?horario Horario)) (or 
        (and (eq (nth$ 1 ?horario:manana) [nil]) (member$ manana ?franjas))
        (and (eq (nth$ 1 ?horario:tarde) [nil]) (member$ tarde ?franjas))
        (and (eq (nth$ 1 ?horario:noche) [nil]) (member$ noche ?franjas))))))
  
     (bind ?c1 
          (and 
  	   (eq (nth$ 1 (send ?dia_elegido get-manana)) [nil]) 
  	   (member$ manana ?franjas)
     ))
     (bind ?c2 
          (and 
  	   (eq (nth$ 1 (send ?dia_elegido get-tarde)) [nil]) 
  	   (member$ tarde ?franjas) 
     ))
     (bind ?c3 
          (and 
  	   (eq (nth$ 1 (send ?dia_elegido get-noche)) [nil]) 
  	   (member$ noche ?franjas)
     ))
  
  
     (if ?c1 then 
          (send ?dia_elegido put-manana ?actividad)
      else
          (if ?c2 then 
              (send ?dia_elegido put-tarde ?actividad)
           else
              (if ?c3 then
                  (send ?dia_elegido put-noche ?actividad)
              )
          )
     ) 
  )
  
  ;;; ########################################################################
  ;;; ### Funciones para distribuir las actividades de comida              ###
  ;;; ########################################################################
  
  ;; Las funciones de la distribución de las actividades de comida son muy
  ;; parecidos, hay sólo una diferencia: El atributo de la comida en el horario
  ;; sólo permite una actividad, por eso na hacen falta las bucles y las
  ;; funciones especiales para añadir una actividad. Pero el principio es lo
  ;; mismo.
  
  (deffunction distribuir-comida 
     (?actividad ?dia ?precio ?edad_m'axima ?edad_m'inima ?grupo 
                                                        ?duraci'on ?cond2 ?cond3)
  
     (if (eq ?cond2 true) then 
         (if (not (< (send ?actividad get-precio) ?precio)) then 
            (return)
         )
     )
     (if (eq ?cond3 true) then 
         (if (not (< (send ?actividad get-duraci'on) ?duraci'on)) then 
              (return)
         )
     )
     (if (eq ?cond3 true) then (if (not (grupo ?actividad ?grupo)) then 
         (return))
     )
     (if (not (>= (send ?actividad get-edad_m'axima) ?edad_m'axima)) then 
         (return)
     )
     (if (not (<= (send ?actividad get-edad_m'inima) ?edad_m'inima)) then 
         (return)
     )
  
     (loop-for-count (?j 1 ?dia)
         (bind ?dia_elegido (nth$ 1 (find-instance ((?horario Horario))
             (or
                (eq ?horario:comida_primera [nil])
                (eq ?horario:comida_segunda [nil])
             )
         )))
         (if (not (eq ?dia_elegido nil)) then (break))
     )
   
     (if (eq ?dia_elegido nil) then (return))   
     
     (bind ?c1 (eq (send ?dia_elegido get-comida_primera) [nil]) )
     (bind ?c2 (eq (send ?dia_elegido get-comida_segunda) [nil]) )
  
     (if ?c1 then
         (send ?dia_elegido put-comida_primera ?actividad)
      else
         (if ?c2 then
             (send ?dia_elegido put-comida_segunda ?actividad)
         )
     )
     (send ?actividad put-hecho s'i)
  )
  
  (deffunction distribuir-comida-gustas 
     (?gusta ?dia ?precio ?edad_m'axima ?edad_m'inima ?grupo 
                                                      ?duraci'on ?cond2 ?cond3)
     
     (bind ?limite (+ (* ?dia 0.3) 1))
     (loop-for-count (?i 1 ?limite)
        (bind ?actividad (nth$ 1 (find-instance ((?inst ?gusta))
           (and
              (eq ?inst:hecho no)
              (>= ?inst:edad_m'axima ?edad_m'axima)
              (<= ?inst:edad_m'inima ?edad_m'inima)
           )
        )))
     )
     (if (eq ?actividad nil) then (return))
  
     (if (eq ?cond2 true) then 
         (if (not (< (send ?actividad get-precio) ?precio)) then 
            (return)
         )
     )
     (if (eq ?cond3 true) then 
         (if (not (< (send ?actividad get-duraci'on) ?duraci'on)) then 
              (return)
         )
     )
     (if (eq ?cond3 true) then (if (not (grupo ?actividad ?grupo)) then 
  	(return))
     )
     (if (not (>= (send ?actividad get-edad_m'axima) ?edad_m'axima)) then 
          (return)
     )
     (if (not (<= (send ?actividad get-edad_m'inima) ?edad_m'inima)) then 
          (return)
     )
     
     (loop-for-count (?j 1 ?dia)
         (bind ?dia_elegido (nth$ 1 (find-instance ((?horario Horario))
             (or
                (eq ?horario:comida_primera [nil])
                (eq ?horario:comida_segunda [nil])
             )
         )))
         (if (not (eq ?dia_elegido nil)) then (break))
     )
  
     (if (eq ?dia_elegido nil) then (return))
  
     (bind ?c1 (eq (send ?dia_elegido get-comida_primera) [nil]))
     (bind ?c2 (eq (send ?dia_elegido get-comida_segunda) [nil]))
  
     (if ?c1 then
        (send ?dia_elegido put-comida_primera ?actividad)
      else
        (if ?c2 then
             (send ?dia_elegido put-comida_segunda ?actividad)
        )
      )
      (send ?actividad put-hecho s'i)
  ) 
  
  
  (deffunction distribuir-comida-obligatorias (?gusta)
  
     (bind ?actividad 
           (nth$ 1 (find-instance ((?inst ?gusta)) (eq ?inst:hecho no)))
     )
  
     (bind ?dia_elegido (nth$ 1 (find-instance ((?horario Horario)) 
        (or 
           (eq (nth$ 1 ?horario:comida_primera) [nil])
           (eq (nth$ 1 ?horario:comida_segunda) [nil])
        )
     )))
  
     (bind ?c1 (eq (nth$ 1 (send ?dia_elegido get-comida_primera)) [nil]))
     (bind ?c2 (eq (nth$ 1 (send ?dia_elegido get-comida_segunda)) [nil]))
  
     (if ?c1 then 
          (send ?dia_elegido put-comida_primera ?actividad)
      else
          (if ?c2 then 
              (send ?dia_elegido put-comida_segunda ?actividad)
          )
     ) 
     (send ?actividad put-hecho s'i)
  )
  
  ;;; ########################################################################
  ;;; ### Reglas para las actividades no comida                            ###
  ;;; ########################################################################
  
  ;; En el inicio todas las actividades prohibidas reciben el valor "sí" para 
  ;; el atributo "hecho". Después no son disponibles para la planificación
  
  (defrule prohibidas
     (declare (salience -1))
     (prohibido ?prohibido)
  => 
     (do-for-all-instances ((?inst ?prohibido)) TRUE
         (send ?inst put-hecho s'i)
     )
  )
  
  ;; Lss reglas son para cada combinación de las condiciones. Por ejemplo la
  ;; parte final del nombre de una regla ".2." significa que una actividad
  ;; debe cumplir la condición 2.
  
  (defrule distribuir-obligatorias 
     (declare (salience -1))
     (obligatorio ?nombre)
  =>
     (distribuir-obligatorias ?nombre)
  )
  
  
  (defrule distribuir-perfectas
     (declare (salience -2))
     (d'ia ?d)
     (gusta ?nombre)
     (edad_m'axima ?emax)
     (edad_m'inima ?emin)
     (duraci'on ?dur)
     (precio_m'aximo ?p)
     (grupo ?g)
  => 
     (distribuir-gustas ?nombre ?d ?p ?emax ?emin ?g ?dur true true)
  )
  
  (defrule distribuir-obligatorias 
     (declare (salience -3))
     (obligatorio ?nombre)
  =>
     (distribuir-obligatorias ?nombre)
  )
  
  (defrule distribuir-12.
     (declare (salience -4))
     (d'ia ?d)
     (gusta ?nombre)
     (edad_m'axima ?emax)
     (edad_m'inima ?emin)
     (duraci'on ?dur)
     (precio_m'aximo ?p)
     (grupo ?g)
     =>
     (distribuir-gustas ?nombre ?d ?p ?emax ?emin ?g ?dur true false)
  )  
  
  (defrule distribuir-1..
     (declare (salience -5))
     (d'ia ?d)
     (gusta ?nombre)
     (edad_m'axima ?emax)
     (edad_m'inima ?emin)
     (duraci'on ?dur)
     (precio_m'aximo ?p)
     (grupo ?g)
  =>
     (distribuir-gustas ?nombre ?d ?p ?emax ?emin ?g ?dur false false)
  )    
  
  (defrule distribuir-.23
     (declare (salience -6))
     (d'ia ?d)
     ?actividad <- (object (is-a Actividad_no_comida))
     (edad_m'axima ?emax)
     (edad_m'inima ?emin)
     (duraci'on ?dur)
     (precio_m'aximo ?p)
     (grupo ?g)
  =>
     (distribuir ?actividad ?d ?p ?emax ?emin ?g ?dur true true)
  )
   
  (defrule distribuir-.2.
     (declare (salience -7))
     (d'ia ?d)
     ?actividad <- (object (is-a Actividad_no_comida))
     (edad_m'axima ?emax)
     (edad_m'inima ?emin)
     (duraci'on ?dur)
     (precio_m'aximo ?p)
     (grupo ?g)
  =>
     (distribuir ?actividad ?d ?p ?emax ?emin ?g ?dur true false)
  )    
  
  (defrule distribuir-..3
     (declare (salience -8))
     (d'ia ?d)
     ?actividad <- (object (is-a Actividad_no_comida))
     (edad_m'axima ?emax)
     (edad_m'inima ?emin)
     (duraci'on ?dur)
     (precio_m'aximo ?p)
     (grupo ?g)
  =>
     (distribuir ?actividad ?d ?p ?emax ?emin ?g ?dur false true)
  )
  
  (defrule distribuir-...
     (declare (salience -9))
     (d'ia ?d)
     ?actividad <- (object (is-a Actividad_no_comida))
     (edad_m'axima ?emax)
     (edad_m'inima ?emin)
     (duraci'on ?dur)
     (precio_m'aximo ?p)
     (grupo ?g)
  =>
     (distribuir ?actividad ?d ?p ?emax ?emin ?g ?dur false false)
  )
  
  ;;; ########################################################################
  ;;; ### Reglas para las actividades de comida                            ###
  ;;; ########################################################################
  
  ;; Las reglas con del mismo orden como las actividades no comida y con el
  ;; mismo principio.
  
  (defrule comida-prohibidas
     (declare (salience -1))
     (prohibido comida ?prohibido)
  => 
     (do-for-all-instances ((?inst ?prohibido)) TRUE
         (send ?inst put-hecho s'i)
     )
  )
  
  
  (defrule distribuir-comida-obligatorias 
     (declare (salience -1))
     (obligatorio comida ?nombre)
  =>
     (distribuir-comida-obligatorias ?nombre)
  )
  
  
  (defrule distribuir-comida-perfectas
     (declare (salience -2))
     (d'ia ?d)
     (gusta comida ?nombre)
     (edad_m'axima ?emax)
     (edad_m'inima ?emin)
     (duraci'on ?dur)
     (precio_m'aximo ?p)
     (grupo ?g)
  => 
     (distribuir-comida-gustas ?nombre ?d ?p ?emax ?emin ?g ?dur true true)
  )
  
  (defrule distribuir-comida-obligatorias 
     (declare (salience -3))
     (obligatorio comida ?nombre)
  =>
     (distribuir-comida-obligatorias ?nombre)
  )
  
  (defrule distribuir-comida-12.
     (declare (salience -4))
     (d'ia ?d)
     (gusta comida ?nombre)
     (edad_m'axima ?emax)
     (edad_m'inima ?emin)
     (duraci'on ?dur)
     (precio_m'aximo ?p)
     (grupo ?g)
     =>
     (distribuir-comida-gustas ?nombre ?d ?p ?emax ?emin ?g ?dur true false)
  )  
  
  (defrule distribuir-comida-1..
     (declare (salience -5))
     (d'ia ?d)
     (gusta comida ?nombre)
     (edad_m'axima ?emax)
     (edad_m'inima ?emin)
     (duraci'on ?dur)
     (precio_m'aximo ?p)
     (grupo ?g)
  =>
     (distribuir-comida-gustas ?nombre ?d ?p ?emax ?emin ?g ?dur false false)
  )    
  
  (defrule distribuir-comida-.23
     (declare (salience -6))
     (d'ia ?d)
     ?actividad <- (object (is-a Actividad_Comida))
     (edad_m'axima ?emax)
     (edad_m'inima ?emin)
     (duraci'on ?dur)
     (precio_m'aximo ?p)
     (grupo ?g)
  =>
     (distribuir-comida ?actividad ?d ?p ?emax ?emin ?g ?dur true true)
  )
   
  (defrule distribuir-comida-.2.
     (declare (salience -7))
     (d'ia ?d)
     ?actividad <- (object (is-a Actividad_Comida))
     (edad_m'axima ?emax)
     (edad_m'inima ?emin)
     (duraci'on ?dur)
     (precio_m'aximo ?p)
     (grupo ?g)
  =>
     (distribuir-comida ?actividad ?d ?p ?emax ?emin ?g ?dur true false)
  )    
  
  (defrule distribuir-comida-..3
     (declare (salience -8))
     (d'ia ?d)
     ?actividad <- (object (is-a Actividad_Comida))
     (edad_m'axima ?emax)
     (edad_m'inima ?emin)
     (duraci'on ?dur)
     (precio_m'aximo ?p)
     (grupo ?g)
  =>
     (distribuir-comida ?actividad ?d ?p ?emax ?emin ?g ?dur false true)
  )
  
  (defrule distribuir-comida-...
     (declare (salience -9))
     (d'ia ?d)
     ?actividad <- (object (is-a Actividad_Comida))
     (edad_m'axima ?emax)
     (edad_m'inima ?emin)
     (duraci'on ?dur)
     (precio_m'aximo ?p)
     (grupo ?g)
  =>
     (distribuir-comida ?actividad ?d ?p ?emax ?emin ?g ?dur false false)
  )

Pruebas

  
  Viaje tranquilo: sí
  Lugares típicos: sí
  Museo: sí
   Museo de historia: obligatorio
   Museo de arte: sí
   Museo de Tecnología: prohibido
   Museo de personas célebres: no
  Monumentos: sí
   Edifícios: obligatorio
   Monumentos religiosos: no
   Esculturas: no
  Teatro: sí
   Teatro clásico: no
   Teatro de improvisación: no
   Teatro alternativo: sí
  Cine: sí
   Cine original:
   Cine famoso: sí
   Cine alternativo: sí
  Bares: sí
   español:
   irlandeses:
   para extranjeros:
   de cocktails:
  Comida: sí
   Bares para comer: sí
  Precio: no
  sólo: sí
  Edad mínima: 23
  Edad máxima: 23
  Duración: 2
  Días: 4
  
  Día 1:
  Manana:  Museu Frederic Mar'es ; Museu Etnol'ogic ; Casa-Museu Gaud'i ;
  Tarde :  Museu Nacional d'Art de Catalunya ; Fundaci'o Joan Mir'o ; Cine Verdi ;
  Noche :  Ginger ; Forty ; Blank ;
  Comida primera: Reno
  Comida segunda: Bestial
  
  Día 2:
  Manana:  The Quiet Man ; Ryan's ; Casa Mil'a ;
  Tarde :  'Acido 'Oxido ; Almirall ; Aribau ;
  Noche :  Bosque ; Teatre Grec ; Bodas ;
  Comida primera: Bun Sichi
  Comida segunda: Hard Rock Caf'e
  
  Día 3:
  Manana:  Museu Barbier-Mueller d'Art Precolombi ; Museo de Historia de la Ciudad ; Montserrat ;
  Tarde :  Museo del Futbol Club Barcelona ; AMC Cines Diagonal Mar ; Museu Maritim ;
  Noche :  Teatre T'ivoli ; Mercat de les Flors ; Dry Martini ;
  Comida primera: Mas Lluhi
  Comida segunda: Adelita Spring
  
  Día 4:
  Manana:  Museu de la Xocolata ; Museu d'Art Contemporani ; Museu Egipci ;
  Tarde :  Casa Batll'o ; La Vinya del Senyor ; Xampanyet ;
  Noche :  Cines Icaria Yelmo Cineplex ; Imax Port Vell ; East 47 ;
  Comida primera: Neichel
  Comida segunda: Al Passatore
  
  #########################################################################
  #########################################################################
  
  Viaje tranquilo: no
  Lugares típicos: no
  Museo: no
  Museo de historia: 
   Museo de arte: 
   Museo de Tecnología: 
   Museo de personas célebres: 
  Monumentos: no
   Edifícios: 
   Monumentos religiosos: 
   Esculturas: 
  Teatro: no
   Teatro clásico: 
   Teatro de improvisación: 
   Teatro alternativo: 
  Cine: no
   Cine original:
   Cine famoso: 
   Cine alternativo: 
  Bares: no
   español:
   irlandeses:
   para extranjeros:
   de cocktails:
  Comida: no
   Bares para comer: 
  Precio: no
  sólo: no
  Edad mínima: 6
  Edad máxima: 50
  Duración: 2
  Días: 3
  
  Día 1:
  Manana:  Museu Etnol'ogic ; Museu Egipci ; Museu Barbier-Mueller d'Art Precolombi ;
  Tarde :  Antic Teatre ; Ambar ; Almirall ;
  Noche :  'Acido 'Oxido ; Plaza Reial ; Teatre T'ivoli ;
  Comida primera: Bun Sichi
  Comida segunda: Hard Rock Caf'e
  
  Día 2:
  Manana:  Ryan's ; Museu Frederic Mar'es ; Casa-Museu Gaud'i ;
  Tarde :  Paddy Flaherty's ; Kennedy's ; Flann O'Brien ;
  Noche :  Arcoiris ; Antilla ; La Vinya del Senyor ;
  Comida primera: Mas Lluhi
  Comida segunda: Adelita Spring
  
  Día 3:
  Manana:  Museo de Historia de la Ciudad ; The Quiet Man ; Casa Mil'a ;
  Tarde :  Fundaci'o Joan Mir'o ; Museu Maritim ; Cine Verdi ;
  Noche :  The Fastnet ; Scobies ; Xampanyet ;
  Comida primera: Neichel
  Comida segunda: Al Passatore
  
  #########################################################################
  #########################################################################
  
  Viaje tranquilo: no
  Lugares típicos: sí
  Museo: sí
  Museo de historia: sí
   Museo de arte: sí
   Museo de Tecnología: sí
   Museo de personas célebres: sí
  Monumentos: sí
   Edifícios: sí
   Monumentos religiosos: sí
   Esculturas: sí
  Teatro: sí
   Teatro clásico: sí
   Teatro de improvisación: sí
   Teatro alternativo: sí
  Cine: sí
   Cine original: 
   Cine famoso: sí
   Cine alternativo: sí
  Bares: sí
   español: sí
   irlandeses: 
   para extranjeros: 
   de cocktails: 
  Comida: sí
   Bares para comer: sí
  Precio: sí
  sólo: sí
  Edad mínima: 56
  Edad máxima: 56
  Duración: 2
  Días: 3
  
  Día 1:
  Manana:  Ryan's ; Museu Frederic Mar'es ; Casa Mil'a ;
  Tarde :  Fundaci'o Joan Mir'o ; Museu Maritim ; Cine Verdi ;
  Noche :  Ginger ; Forty ; The Fastnet ;
  Comida primera: Almirall
  Comida segunda: 'Acido 'Oxido
  
  Día 2:
  Manana:  Museo de Historia de la Ciudad ; The Quiet Man ; Museu Egipci ;
  Tarde :  La Vinya del Senyor ; AMC Cines Diagonal Mar ; Bodas ;
  Noche :  Mirablau ; Rep'ublica ; Blank ;
  Comida primera: Antilla
  Comida segunda: Antic Teatre
  
  D'ia 3:
  Manana:  Tibidabo ; Montserrat ; Museu Etnol'ogic ;
  Tarde :  Cines Icaria Yelmo Cineplex ; Imax Port Vell ; East 47 ;
  Noche :  Bar Marsella ; Magic ; Dry Martini ;
  Comida primera: Neichel
  Comida segunda: Arcoiris
  
  #########################################################################
  #########################################################################
  
  Viaje tranquilo: no
  Lugares típicos: no
  Museo: no
  Museo de historia: 
   Museo de arte: 
   Museo de Tecnología: 
   Museo de personas célebres: 
  Monumentos: no
   Edifícios: 
   Monumentos religiosos: 
   Esculturas: 
  Teatro: no
   Teatro clásico: 
   Teatro de improvisación: 
   Teatro alternativo: 
  Cine: no
   Cine original: 
   Cine famoso: 
   Cine alternativo: 
  Bares: no
   español:
   irlandeses: 
   para extranjeros: 
   de cocktails: 
  Comida: no
   Bares para comer: 
  Precio: no
  sólo: sí
  Edad mínima: 45
  Edad máxima: 45
  Duración: 2
  Días: 4
  
  Día 1:
  Manana:  Montserrat ; Casa Mil'a ; Museu d'Art Contemporani ;
  Tarde :  Antilla ; Antic Teatre ; Ambar ;
  Noche :  Almirall ; 'Acido 'Oxido ; Palau de la M'usica Catalana ;
  Comida primera: Reno
  Comida segunda: Bestial
  
  Día 2:
  Manana:  Museu Etnol'ogic ; Museu Egipci ; Museu d'Arqueologia de Catalunya ;
  Tarde :  Scobies ; Paddy Flaherty's ; Kennedy's ;
  Noche :  Flann O'Brien ; Arcoiris ; Auditori ;
  Comida primera: Bun Sichi
  Comida segunda: Hard Rock Caf'e
  
  Día 3:
  Manana:  Ryan's ; Museu Frederic Mar'es ; Museu Barbier-Mueller d'Art Precolombi ;
  Tarde :  East 47 ; Dry Martini ; Bodas ;
  Noche :  Blank ; The Fastnet ; Teatre Grec ;
  Comida primera: Mas Lluhi
  Comida segunda: Adelita Spring
  
  Día 4:
  Manana:  Museo de Historia de la Ciudad ; The Quiet Man ; Casa-Museu Gaud'i ;
  Tarde :  Fundaci'o Joan Mir'o ; Museu Maritim ; Cine Verdi ;
  Noche :  Ginger ; Forty ; Teatre de Lliure ;
  Comida primera: Neichel
  Comida segunda: Al Passatore
  
  #########################################################################
  #########################################################################
  
  Viaje tranquilo: sí
  Lugares típicos: no
  Museo: 
  Museo de historia:
   Museo de arte:
   Museo de Tecnología:
   Museo de personas célebres:
  Monumentos: 
   Edifícios:
   Monumentos religiosos:
   Esculturas:
  Teatro: no
   Teatro clásico:
   Teatro de improvisación:
   Teatro alternativo:
  Cine: no
   Cine original:
   Cine famoso:
   Cine alternativo:
  Bares: sí
   español: sí
   irlandeses: sí
   para extranjeros: sí
   de cocktails: sí
  Comida: sí
   Bares para comer: sí
  Precio: no
  sólo: sí
  Edad mínima: 45
  Edad máxima: 45
  Duración: 2
  Días: 4
  
  Día 1:
  Manana:  Museu Etnol'ogic ; Museu Egipci ; Casa-Museu Gaud'i ;
  Tarde :  Fundaci'o Joan Mir'o ; Museu Maritim ; East 47 ;
  Noche :  Dry Martini ; Bodas ; Scobies ;
  Comida primera: Antic Teatre
  Comida segunda: Almirall
  
  Día 2:
  Manana:  Ryan's ; Museu Frederic Mar'es ; Casa Mil'a ;
  Tarde :  Kennedy's ; Xampanyet ; Cine Verdi ;
  Noche :  Ginger ; Forty ; The Fastnet ;
  Comida primera: Arcoiris
  Comida segunda: Antilla
  
  Día 3:
  Manana:  Museo de Historia de la Ciudad ; The Quiet Man ; Montserrat ;
  Tarde :  Blank ; Ambar ; Flann O'Brien ;
  Noche :  La Vinya del Senyor ; Bodas ; Blank ;
  Comida primera: Neichel
  Comida segunda: Reno
  
  #########################################################################
  #########################################################################
  
  Viaje tranquilo: sí
  Lugares típicos: no
  Museo:
  Museo de historia:
   Museo de arte:
   Museo de Tecnología:
   Museo de personas célebres:
  Monumentos:
   Edifícios:
   Monumentos religiosos:
   Esculturas:
  Teatro: no
   Teatro clásico:
   Teatro de improvisación:
   Teatro alternativo:
  Cine: no
   Cine original:
   Cine famoso:
   Cine alternativo:
  Bares: sí
   español: sí
   irlandeses: sí
   para extranjeros: sí
   de cocktails: sí
  Comida: sí
   Bares para comer: no
   rápida: sí
   turca: sí
   japonesa: sí
   mexicana: sí
   china: sí
   italiana: sí
   española: sí
  Precio: no
  sólo: sí
  Edad mínima: 34
  Edad máxima: 34
  Duración: 2
  Días: 2
  
  Día 1:
  Manana:  Ryan's ; Museu Frederic Mar'es ; Museu Egipci ;
  Tarde :  Kennedy's ; Xampanyet ; Fundaci'o Joan Mir'o ;
  Noche :  Cine Verdi ; Ginger ; East 47 ;
  Comida primera: Adelita Spring
  Comida segunda: Bun Sichi
  
  Día 2:
  Manana:  Museo de Historia de la Ciudad ; The Quiet Man ; Museu Etnol'ogic ;
  Tarde :  Blank ; Ambar ; Flann O'Brien ;
  Noche :  La Vinya del Senyor ; Bodas ; Forty ;
  Comida primera: Neichel
  Comida segunda: Mas Lluhi