Enero 2006.
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.
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.
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
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.
Al final, nuestro problema queda en:
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.
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.
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:
El problema consiste en tres partes:
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.
No todas las respuestas aportan la misma información. Por ejemplo, la pregunta "¿Te gustan los museos de arte?" tiene como respuesta sí 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)
.
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.
Ésta es la parte más importante del problema, y se explica en el apartado de Implementació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.
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.
En la ontología está especificado el tipo de cada atributo (por ejemplo, precio
es un Float), y algunos tienen restricciones especiales:
franja
es de tipo Símbolo, y sólo puede ser "mañana", "tarde" o "noche".
duración
(entero) vale como mínimo 1 y como máximo 3.
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:
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.
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:
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...
CLIPS nos ha dado bastantes problemas:
(gusta Bar_Irlandés)
, y hemos tenido que hacer cosas complicadas
como (gusta Bar_Irland'es)
(y entonces Protégé cambia la '
por %27
).
readline()
de Linux,
por lo que no se pueden usar los cursores si nos equivocamos al escribir.
De estas cosas y otras ya hemos informado al autor. Con Protégé no ha habido ningún problema y nos gusta mucho.
Actividad
. La jerarquía se explicará en el apartado de la ontología.
Horario
.
A continuación se explican los atributos que usamos en estas clases.
Datos sobre cada actividad; son los que se muestran en pantalla en el horario final.
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.
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.
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).
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.
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
):
(gusta nombre_de_clase)
(obligatorio nombre_de_clase)
(prohibido nombre_de_clase)
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.
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.
É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.
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:
A esto hay que añadir los "axiomas":
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):
12345
: Las actividades perfectas: las que cumplen todas las condiciones
1234.
: Las que cumplen las 4 primeras condiciones
123..
: Las que cumplen las 3 primeras
12...
: Las que cumplen las 2 primeras
1....
: Las que cumplen la primera
.2345
: Las que no cumplen la primera, pero sí las otras
.234.
.23..
.2...
..345
..34.
..3..
...45
...4.
....5
.....
: Las que no cumplen ninguna condición
(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:
deffunction
) para comparar ?a
y ?b
, actividades.
Aquí nos encontramos con el problema de que desde una función no podíamos
consultar hechos, como por ejemplo, (gusta Comida_China)
.
defrule
) que, para toda actividad ?a
y ?b
, añadieran
un hecho (mejor ?a que ?b)
o al revés.
Eso crearía muchísimos hechos (ya que comprobaría cada actvidad con todas las demás),
pero el trabajo de comparar todas con todas también iba a estar en un
algoritmo de ordenación.
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.
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):
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).
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:
123
)
12.
1..
.23
.2.
..3
...
)
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.
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.
Actividad_comida
y Actividad_no_comida
, pero todas son subclases de Actividad
Actividad_comida
es en un restaurante, y tiene como subclases los distintos tipos de comida: rápida, japonesa, mexicana, china, española, ...
Comida_Española
(como en los restaurantes).
Cultural
, Lúdica
y Comercial
.
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>
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 sí
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
(sí
/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>
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>
Pensamos en añadir más atributos a cada actividad:
es_caro
(booleano), que permita marcar a cada una de forma subjetiva.
Ahora tenemos el precio (que es objetivo), pero no es lo mismo 30 euros para
un cine que para un parque de atracciones.
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))
(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) )
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