apuntes uned clips

33
Una introducción a la programación en Clips 1 Una introducción a la programación en Clips Dpto. Inteligencia Artificial. UNIVERSIDAD NACIONAL DE EDUCACIÓN A DISTANCIA

Upload: josemary

Post on 27-Jun-2015

360 views

Category:

Documents


3 download

TRANSCRIPT

Page 1: Apuntes Uned Clips

Una introducción a la programación en Clips 1

Una introducción a la programación en Clips

Dpto. Inteligencia Artificial.

UNIVERSIDAD NACIONAL DE EDUCACIÓN A DISTANCIA

Page 2: Apuntes Uned Clips

2

Page 3: Apuntes Uned Clips

Una introducción a la programación en Clips 1

1 Introducción

Paradigmas de programación: orientación a reglas, orientación a objetos, programación procedimental. Distingue entre mayúsculas y minúsculas.

Entorno de trabajo: La ayuda del programa permite tener en línea la descripción completa de cada comando y cada elemento del lenguaje, por lo que en este documento no se necesita ser exhaustivo en estas particularidades.

• Comandos del sistema: Se introducen entre paréntesis, a continuación del prompt de comandos y se ejecuta con la introducción de [enter]. Ejem:

> (+ 5 9)

> 14

>

Pueden introducirse directamente en la línea de comandos del sistema o agruparse en un fichero batch.

En particular el comando watch permite visualizar las modificaciones de uno o unos elementos determinados del lenguaje o del programa. El argumento del comando es la referencia al elemento o elementos que se desean observar. Puede ser específico, una instancia o una regla (en este caso se observarían sus activaciones) determinadas, por ejemplo, o genérico, todos los hechos, también por ejemplo. Para esto último, el argumento debería ser la denominación de los elementos genéricos que se irán referenciando en este apartado: facts, rules, instances, activations, etc. El comando inverso es unwatch.

2 Componentes del lenguaje

• Construcciones: Hablaremos de construcciones, como elementos que soportan las clases, instancias (los hechos) y las reglas. Pueden introducirse directamente en la línea de comandos o a través de un fichero texto que se carga desde el entorno.

• Ejecución: Si en la base de hechos hay hechos que cumplen las restricciones de la parte izquierda de una regla, ésta entra en la agenda. Cuando se lanza (activation en terminología Clips) una regla, se ejecutan las acciones de su parte derecha. Al ejecutar un programa, se lanza primero la regla contenida en la agenda que tiene una mayor prioridad.

Con (run limite) se ejecutan tantas reglas como indica límite.

Page 4: Apuntes Uned Clips

2

El comando (agenda) muestra las reglas contenidas en la agenda, su prioridad y los hechos que han cumplido su parte izquierda.

Clips tiene la propiedad denominada refracción, esto es, ante un conjunto de hechos determinado, las reglas solamente se disparan una vez. Sino fuera así, se caería inmediatamente en una repetición constante de las mismas reglas. Clips “recuerda” los identificadores de los hechos que desencadenaron una regla en el disparo y no activará nuevamente la regla con la misma combinación exacta de identificadores. Hay que tener en cuenta que cuando se retira y se vuelve a introducir un hecho, su identificador ha cambiado y por lo tanto puede volver a disparar la misma regla. El comando (refresh nom-regla) permite que la regla referenciada se vuelva a disparar aún sin haber cambiado los hechos participantes.

Con (set-break nombre-regla) se para la ejecución al llegar el control a la regla referenciada por este comando. Introduce un punto de parada en la ejecución. El comando (show-breaks) muestra los puntos de interrupción activos y (remove-break nombre-regla) elimina el que afecta a la regla.

• Hechos: fragmentos de información con el siguiente formato entre paréntesis: nombre de relación seguido por cero o más slots o ranuras y sus valores asociados.

Hechos compuestos: indican diferentes hechos particulares sobre una misma relación.

Templates o plantillas: Definen la estructura de hechos compuestos, lo que en orientación a objetos sería la definición de clases.

(deftemplate vehiculo (slot matricula) (slot color) (slot propietario) (slot antiguedad) (slot cilindrada) (slot kilometraje))

Slots múltiples (multislot): pueden contener varios valores (campo multivaluado).

En el ejemplo anterior de template no se ha definido el tipo de datos de slot y por lo tanto esos slots admiten cualquier tipo de datos, pero esto se puede restringir a través del operador type. En el ejemplo:

(deftemplate vehiculo (slot matricula (type SYMBOL)) (slot color (type SYMBOL)) (slot propietario (type STRING)) (slot antiguedad (type INTEGER)) (slot cilindrada (type INTEGER))

Page 5: Apuntes Uned Clips

Una introducción a la programación en Clips 3

(slot kilometraje(type INTEGER)))

Los identificadores del tipo de datos son: SYMBOL, STRING, LEXEME (válido para símbolos y strings), INTEGER, FLOAT, NUMBER (válido para enteros y reales) y ?VARIABLE, este último permite cualquier valor y es la restricción por defecto, como hemos visto.

Hay otras posibilidades para la restricción de datos en los slots. Por ejemplo los operadores que definen por enumeración los valores permitidos, que se indica con el operador allowed-tipo-datos, donde tipo-datos pude ser symbols, strings, lexems, integers, floats, numbers o values. Por ejemplo:

(deftemplate vehiculo ... (slot color (type SYMBOL)(allowed-symbols verde rojo azul)) ...

Una nota importante, si elimináramos del ejemplo anterior la opción type: (deftemplate vehiculo ... (slot color (allowed-symbols verde rojo azul)) ...

el slot color admitiría cualquier valor de otros tipos, pero si es un símbolo, éste debería ser uno de los enumerados. Para restringir específicamente a los enumerados, utilizaríamos la variante con values:

(deftemplate vehiculo ... (slot color (allowed-values verde rojo azul)) ...

Para restringir valores numéricos es más habitual definir un rango. Para ellos se dispone, lógicamente, de range:

(deftemplate vehiculo ... (slot antiguedad (type INTEGER)(range 0 ?VARIABLE))) ...

El ejemplo restringe los valores de antigüedad a valores superiores o iguales a 0. En 0 es el valor mínimo del rango y el valor máximo no queda definido, para esto se ha utilizado ?VARIABLE.

Para los slots multivaluados, se dispone de la posibilidad de definir el número mínimo y máximo de valores admisibles, a través de cardinality. Si queremos determinar la cardinalidad del multislot, no a un rango, sino a un único valor, indicaríamos ese valor como límite mínimo y máximo a la vez. En el siguiente ejemplo indicamos que la matricula de un vehículo se compone exactamente de tres strings.

Page 6: Apuntes Uned Clips

4

(deftemplate vehiculo ... (multislot matricula (type string)(cardinality 3 3)) ...

Por supuesto también se dispone de la posibilidad de dar un valor por defecto al slot mediante el atributo default. Si no se incluye este atributo o se incluye con argumento ?DERIVE, al afirmar ese slot sin darle un valor, el sistema obtiene un valor a partir de su tipo de datos y de su rango y cardinalidad. Por ejemplo:

(deftemplate conduccion (slot conductor-principal (type STRING)) (slot tipo-permiso (allowed-values A-1 A-2 A-3)) (slot permiso-valido (default TRUE)) (slot edad (type INTEGER)(range 18 ?VARIABLE)) (multislot otros-conductores (type STRING) (cardinality 2

3)) ...

Si afirmamos sin más: (assert (conduccion))

Obtendríamos en la base de hechos el siguiente hecho: (conduccion (conductor-principal "") (tipo-permiso A-1) (edad 18) (otros-conductores "" "") ...

Ya que obtiene el primer valor de un allowed-values, el mínimo de un range, el 0 de un INTEGER, "" si es STRING y el valor nil si no hay especificación de ningún tipo. En un multislot, se obtendrían tantos valores como el valor mínimo de su cardinalidad.

Como hemos visto en el ejemplo, la referencia a un hecho se hace con el siguiente formato: (nombre-template (valor valor)).

Para visualizar hechos en un instante desde la línea de comandos (facts), cuyo resultado es una lista de hechos y su número asignado. Para incluir un hecho se utiliza assert: (assert hecho-1 hecho-2 ... hecho-n). Para suprimirlo, retract: (retract num-hecho). Para modificarlo, se utiliza (modify num-hecho (slot valor)+), que suprime el antiguo hecho y añade el nuevo. La función duplicate añade también el mismo hecho pero no elimina el antiguo.

Para definir hechos de forma automática a través de un programa, se puede utilizar deffacts que se construyen con esta sintaxis (deffacts nombre comentario lista-de hechos). En orientación a objetos hablaríamos de instancias.

(deffacts vehiculos-vendidos

Page 7: Apuntes Uned Clips

Una introducción a la programación en Clips 5

(vehiculo (matricula MIB-3456-ERC) (color rojo) (propietario "Luis Gomez")) (vehiculo (matricula BBB-6765-IXC) (color verde) (propietario "Jose Perez")))

Se entiende por hecho toda la instancia de la construcción template, de tal forma que el siguiente hecho es imposible

(vehiculo (matricula MIB-3456-ERC) (color rojo) (propietario "Luis Gomez"))

y en todo caso: (vehiculo (matricula MIB-3456-ERC) (color rojo) (propietario nil) (antiguedad nil) (cilindrada nil) (kilometraje nil))

Reset elimina todos los hechos y afirma todos los hechos especificados por los deffacts.

El comando (assert hecho) añade el hecho a la base de hechos. Desde luego si ese hecho ya estaba en la base, no tiene efecto la adición. Pero hecho debe ser la descripción completa del hecho, puesto que en caso de no describirse unos determinados slots, estos se consideran como nil. Así si afirmamos:

(assert (vehiculo (matricula MIB-3456-ERC)))

El resultado en la base de hechos es la inclusión de: (vehiculo (matricula MIB-3456-ERC) (color nil) (propietario nil) (antiguedad nil) (cilindrada nil) (kilometraje nil))

3 Reglas y comparación de patrones

• Reglas: tienen la siguiente sintaxis: (defrule nombre-reg comentario patrones => acciones).

Page 8: Apuntes Uned Clips

6

Evidentemente “patrones” define las restricciones exigidas para que se lleven a cabo las “acciones”. Si no existe un solo patrón en la regla, ésta se lanzará cada vez que se ejecuta el comando reset.

Las variables para la comparación de patrones guardan semejanza en funcionalidad y sintaxis con las de prolog. En el siguiente ejemplo , la regla buscará los vehículos de color verde:

(defrule encontrar-vehiculo-de-color (vehiculo (matricula ?matricula) (color verde)) => (printout t "El vehiculo " ?matricula " tiene el color

verde" crlf))

En la siguiente variante de esta regla, se utiliza un slot (encontrar color) que contiene el color que se busca.

(deftemplate encontrar (slot color)) ... (defrule encontrar-vehiculo-de-color (encontrar (color ?color)) (vehiculo (matricula ?matricula) (color ?color)) => (printout t "El vehículo " ?matricula " tiene el color "

?color crlf))

Así, con los siguientes dos comandos nos listarían las matrículas de vehículos de color verde:

> (assert (encontrar (color verde))) > (run)

En el ejemplo anterior se buscaban los vehículos de color verde.

Como contenido de una variable también podemos incluir la dirección o identificativo de un hecho. El símbolo <- indica la transferencia de la dirección del hecho que se indica a la derecha sobre la variable de su izquierda:

(deftemplate modificacion-color (slot matricula) (slot color)) (defrule realizar-modificacion-de-color) ?f1 <- (modificacion-color (matricula ?matricula) (color

?color)) ?f2 <- (vehiculo (matricula ?matricula)) => (retract ?f1) (modify ?f2 (color ?color)))

Gracias a retract la regla no entra en un ciclo infinito. El ejemplo muestra la regla que, al ocurrir un hecho como anotación de un cambio (modificacion-color), modifica el hecho que había registrado el color del vehículo correspondiente.

El comando bind permite cargar una variable con el resultado de una expresión:

Page 9: Apuntes Uned Clips

Una introducción a la programación en Clips 7

(bind ?area (*?base ?altura))

Cuando un slot es multivaluado, denominaremos cada posible valor como campo. Así, en la parte izquierda de una reglas es posible referenciar a cada campo de forma independiente, a través de la posición que ocupa, referenciando previamente todas las posiciones (campos) anteriores. Por ejemplo el slot matricula podíamos haberlo definido como multivaluado (multislot) de tal forma que se pueda hacer referencia a cada uno de sus tres componentes por diferenciado (letras, número, letras). La regla del siguiente ejemplo imprimir las matrículas de los vehículos de color verde, separando sus componentes con un guión. En la parte izquierda de la regla, las variables ?letras-ini, ?numero y ?letras-fin toman los valores del primer, segundo y tercer componente del multislot matrícula.

(defrule imprime-matriculas-verdes (vehiculo (color verde)) (matricula ?letras-ini ?numero ?letras-fin)) => (printout t ?letras-ini "-" ?numero "-" ?letras-fin

crlf))

Pero otras veces no hace falta nombrar las variables puesto que posteriormente no se va a hacer uso de ellas, porque interesa únicamente un componente del multislot. Por ejemplo si queremos imprimir únicamente las letras finales de la matrícula de cada coche, cambiaríamos los nombres de variables por ? sin más:

(defrule imprime-matriculas-verdes (vehiculo (color verde)) (matricula ? ? ?letras-fin)) => (printout t ?letras-fin crlf))

Para los casos en que interesa comparar un valor con todos los campos de un multislot, por ejemplo buscar las letras IX en la matrícula, que pueden aparecer en el primer campo o en el último, se dispone del símbolo $?, que representa a cero o más campos. En el ejemplo se imprimirían los nombres de los propietarios cuyos vehículos tengan matriculas que contengan esos dos caracteres.

(defrule imprime-matriculas-IX (vehiculo (matricula $? IX) (propietario ?propietario)) => (printout t ?propietario crlf))

El caracter $ tiene también la función de designar a una variable como multivaluada. En el siguiente ejemplo se imprimen los nombres de los propietarios y las matrículas de sus vehículos. Sin este símbolo, la variable ?matricula solo haría referencia a su primer campo. Obsérvese como en la parte derecha de la regla el carácter $ ya no precede al nombre de la variable multivaluada, porque ya capturó todo su contenido en la parte izquierda.

(defrule imprime-propietario-matriculas (vehiculo (matricula $?matricula) (propietario ?propietario))

Page 10: Apuntes Uned Clips

8

=> (printout t ?propietario " es propietario de " ?matricula crlf))

Como ejemplo final de este apartado mostramos las reglas que permiten manejar una pila. Se parte de un multislot pila y de un slot nuevo-valor que contiene el valor a insertar.

(defrule apilar ?apilar <- (nuevo-valor ?valor) ?pila <- (pila ?ant-contenido) => (retract ?apilar ?pila) (assert (pila ?valor ?ant-contenido)) (printout t "Apilado "?valor crlf)) (defrule desapilar-correcto ?desapilar <- (desapilar) ?pila <- (pila ?cima ?resto) => (retract ?desapilar ?pila) (assert (pila ?resto)) (printout t "Desapilado el valor "?cima crlf)) (defrule desapilar-incorrecto ?desapilar <- (desapilar) (pila) => (retract ?desapilar) (printout t "Error, desapilando de pila vacia"

crlf))

Las restricciones de la parte izquierda de una regla admiten una serie de operadores lógicos como not, and y or. Por ejemplo, la siguiente regla imprime los nombres de los propietarios de vehículos cuyo color no es verde.

(defrule imprime-propietarios-vehiculos-no-verdes (vehiculo (color ~verde) (propietario ?propietario)) => (printout t ?propietario crlf))

La siguiente imprimiría los propietarios de vehículos de color verde o rojo: (defrule imprime-propietarios-vehiculos-rojos-verdes (vehiculo (color verde | rojo) (propietario ?propietario)) => (printout t ?propietario crlf))

El operador and se suele utilizar conjuntamente con otras restricciones. Por ejemplo, si a la regla anterior deseamos incluirle la impresión del color de cada vehículo seleccionado:

(defrule imprime-propietarios-vehiculos-rojos-verdes (vehiculo (color ?color&verde | rojo) (propietario ?propietario)) => (printout t ?propietario "tiene un vehiculo de color "

?color crlf))

?color&verde | rojo es la expresión para ligar la variable al mismo tiempo que restringe el valor que puede tomar.

Page 11: Apuntes Uned Clips

Una introducción a la programación en Clips 9

La regla opuesta, es decir, que imprima para los vehículos de color diferente al rojo y verde sería:

(defrule imprime-propietarios-vehiculos-rojos-verdes (vehiculo (color ?color&~verde & ~rojo) (propietario ?propietario)) => (printout t ?propietario " tiene un vehiculo de color "

?color crlf))

Pero en la definición de patrones como restricciones no solamente pueden intervenir constantes (verde, rojo) sino el valor booleano resultante de una expresión, a través del carácter “:” antes de la expresión. En el siguiente ejemplo se imprimen los propietarios de aquellos vehículos con más de 12 meses de antigüedad.

(defrule imprime-propietarios-vehiculos-media-antiguedad (vehiculo (antiguedad ?antiguedad&: (> ?antiguedad 12) &: (<

?antiguedad 24)) (propietario ?propietario)) => (printout t ?propietario " tiene un vehiculo de uno a

dos años de antiguedad" crlf))

Claro que también se podría haber utilizado la función test, que devuelve el valor booleano de una expresión:

(defrule imprime-propietarios-vehiculos-antiguos (vehiculo (antiguedad ?antiguedad) (propietario ?propietario)) (test (> ?antiguedad 12)) => (printout t ?propietario " tiene un vehiculo con más de

12 meses" crlf))

Vamos a ver un ejemplo de utilización de las restricciones, como la anteriores, pero para seleccionar dos instancias (hechos) de la misma clase y en la misma regla, que imprime los nombres de dos propietarios de sendos vehículos del mismo color.

(defrule imprime-propietarios-vehiculos-mismo-color (vehiculo (propietario ?propietario1) (color ?color1)) (vehiculo (propietario ?propietario2&~?propietario1) (color

?color2&?color1)) => (printout t ?propietario1 " y " ?propietario2 " tienen

un vehículo del mismo color " ?color1 crlf))

En este otro ejemplo, la expresión de la restricción se hace algo más compleja: (defrule imprime-propietarios-vehiculos-requieren-ITV (vehiculo (antiguedad ?antiguedad&:(= ?antiguedad 5) |:(>

?antiguedad 5)&:(= (mod (- ?antiguedad 5) 2) 0)) (matricula ?matricula)) => (printout t "El vehiculo " ?matricula " requiere ITV"

crlf))

Una comparación de patrones pueden incluir la evaluación de funciones, siempre y cuando, el resultado sea un único valor, que permita, al evaluar la expresión, determinar si se cumple

Page 12: Apuntes Uned Clips

10

la premisa, o no, para una instancia o hecho determinado, sin necesidad de cargar el valor sobre una variable. En el siguiente ejemplo, ficticio por supuesto, el tipo de vehículo coincide con el múltiplo de 500 que es su cilindrada, y este tipo esta relacionado con el impuesto de circulación a través de una tabla-impuestos: (deftemplate impuesto-circulacion (slot tipo) (slot impuesto)) (defrule imprime-vehiculos-impuestos-circulacion (vehiculo (cilindrada ?cilindrada) (matricula ?matricula)) (impuesto-circulacion (tipo =(div ?cilindrada 500)) (impuesto ?impuesto))

=> (printout t "El vehiculo " ?matricula " tiene que pagar " ?impuesto crlf))

(deffacts tabla-impuestos (impuesto-circulacion (tipo 3)(impuesto 150)) (impuesto-circulacion (tipo 4)(impuesto 225)) (impuesto-circulacion (tipo 5)(impuesto 250)))

Los anteriores ejemplos se han basado en restricciones sobre slots o variables pero que no se combinaban en una misma expresión de restricción. Pero esto también es posible mediante las funciones or, and y not. Entre las premisas de una misma regla evidentemente hay ya un and implícito, el uso explícito de esta función tendrá sentido al combinarse con las otras dos. En el caso del or hay que tener en cuenta en su utilización, que puede generar varias concordancias del patrón de las premisas, por ejemplo:

(defrule imprime-vehiculos-seguro-TipoB (or (vehiculo (antiguedad ?antiguedad&:(> ?antiguedad

120))) (vehiculo (kilometraje ?kilometraje&:(> ?kilometraje

150000)))) (vehiculo (matricula ?matricula)) => (printout t "El vehiculo " ?matricula " paga seguro

tipo B " crlf))

Ante estos hechos: (deffacts vehiculos-vendidos (vehiculo (matricula MIB-3456-ERC) (antiguedad 124) (kilometraje 200000)) (vehiculo (matricula BBB-6765-IXC) (antiguedad 11) (kilometraje 100000)))

La respuesta del lanzamiento de la regla serían cuatro líneas en vez una, que sería lo correcto. Para que funcionar adecuadamente la regla debería haber sido la siguiente:

(defrule imprime-vehiculos-seguro-TipoB (vehiculo (antiguedad ?antiguedad)(kilometraje ?kilometraje)

Page 13: Apuntes Uned Clips

Una introducción a la programación en Clips 11

(matricula ?matricula)) (test (or (> ?antiguedad 120)(> ?kilometraje 150000))) => (printout t "El vehiculo " ?matricula " paga seguro

tipo B " crlf))

En este otro ejemplo, la ejecución de la regla obtendrá la máxima antigüedad de entre todos los vehículos:

(defrule imprime-mayor-antiguedad (vehiculo (antiguedad ?antiguedad1)) (not (vehiculo (antiguedad ?antiguedad2&:(> ?antiguedad2

?antiguedad1 )))) =>(printout t "La máxima antiguedad de un vehiculo es "

?antiguedad1 crlf))

Ahora que, si queremos obtener la matrícula del coche de mayor antigüedad, la regla quedaría así:

(defrule imprime-vehiculo-mayor-antiguedad (vehiculo (antiguedad ?antiguedad1)(matricula ?matricula)) (not (vehiculo (antiguedad ?antiguedad2&:(> ?antiguedad2

?antiguedad1 )))) =>(printout t "El vehiculo de maxima antiguedad es "

?matricula crlf))

Sabemos que la siguiente regla: (defrule imprime-si-hay-vehiculos (vehiculo) =>(printout t "existe un vehiculo" crlf))

imprimiría tantas líneas como vehículos tengamos afirmados, para comprobar si al menos hay un vehículo y no tener que listarlos todos, podría utilizarse el operador exists, no entra en el patrón un vehículo en particular, de la siguiente forma:

(defrule imprime-si-hay-vehiculos (exists (vehiculo)) =>(printout t "existe un vehiculo" crlf))

El operador de funcionalidad inversa es forall, en el siguiente ejemplo, la regla imprimiría un mesaje unicamente si todos los vehículos que tienen más de 1500cc tienen también más de 100 caballos:

(defrule comprueba-cilindrada-CV (forall (vehiculo (cilindrada ?cilindrada&:(> ?cilindrada

1500))(matricula ?matricula)) (vehiculo(CV ?cv&:(> ?cv 100))(matricula ?matricula))) => (printout t "Todo vehiculo con mas de 1500 cc tiene mas de 100

cv" crlf))

Page 14: Apuntes Uned Clips

12

En este punto es aconsejable estudiar el ejemplo sticks.clp (curso virtual). Es muy importante estudiar detenidamente la utilización de los identificativos de hechos y de la función retract para el control de la ejecución de las reglas.

4 Entrada/Salida de datos

Hemos estado utilizando la función printout (t es el nombre de referencia del terminal estándar, normalmente pantalla para salida y teclado para entrada) para visualizar datos por el terminal. La función read puede utilizarse para el proceso complementario de captura de datos desde el teclado. Devuelve el valor y el control al introducirse el enter:

(defrule introducir-propietario => (printout t "Nombre del propietario: ") (bind ?propietario (read)) (assert nombre-propietario ?propietario))

La función bind almacena en la variable, que se indica como primer argumento en el comando, el valor resultante de la expresión del segundo argumento.

Las funciones para lectura escritura en ficheros son también estas mismas read y printout, pero se requiere que los correspondientes ficheros hayan sido previamente abiertos con (open nom-archivo identificador tipo-acceso). Por ejemplo: (open “camiones.dat” camiones “r”) abrirá el archivo camiones.dat para lectura y le asignará el identificador interno camiones. Los tipos pueden ser “r” para solo lectura, “w” para solo escritura, “r+” lectura y escritura y “a” para poder únicamente añadir.

Existe la posibilidad de en vez de utilizar read para leer desde un fichero de texto, utilizar (readline identificador) para leer caracteres hasta el cambio de línea. Esta línea se introduce como un campo simple, si se desea descomponer, se puede utilizar la función explode$, veamos el siguiente ejemplo:

(defrule obtener-propietario => (printout t "Nombre del propietario: ") (bind ?propietario (explode$(readline))) (assert (nombre-propietario ?propietario))

Si a la pregunta de esta regla respondemos con tres palabras (nombre y apellidos), la regla producirá 3 nuevos hechos. Si no hubiéramos utilizado explode$, se hubiese producido un único hecho.

Similar a la función printout, la función format permite trasladar a la salida los datos especificados en un formato determinado, habitualmente con el objetivo de estructurar estos datos en tablas (ver documentación).

Page 15: Apuntes Uned Clips

Una introducción a la programación en Clips 13

5 Mantenimiento de la verdad

En Clips, cuando una regla da lugar a un nuevo hecho, éste no desaparece cuando, ante nuevos eventos, las condiciones de las premisas de la regla ya no se cumplen. Para algunas situaciones o problemas esto está bien así, es coherente con el conocimiento que se desea representar, pero en otras ocasiones no. Ante el incumplimiento de una premisa de una regla, deberían desaparecer los hechos a los que previamente dio lugar. Para Clips dispone del operador logical, que crea dependencias entre los hechos que coinciden con los patrones contenidos por el operador, en el lado izquierdo de la regla, y los hechos afirmados en la parte derecha.

Dado el siguiente hecho (sea f-4) y al lanzar la siguiente regla, se afirmaría un nuevo hecho (sea f-5):

(deffacts incautaciones (incautado (matricula BBB-6765-IXC))) (defrule determina-incautados (logical (incautado (matricula ?matricula))) (vehiculo (matricula ?matricula)(inhabilitado false)) => (assert (vehiculo (matricula ?matricula) (inhabilitado true))))

De tal forma que si retiramos este hecho, con (retract 4), desaparecen de la base de hechos tanto f-4 como f-5.

6 Prioridad y control

Por defecto, el orden de ejecución de las reglas depende de su orden de llegada a la pila de la agenda, por tanto y como tal pila, se activan primero las últimas reglas recibidas. El orden de llegada a la agenda depende a su vez del orden en la afirmación de los hechos que las disparan. Pero este orden de ejecución puede ser alterado gracias a la posibilidad de establecer prioridades para cada regla particular. Es posible definir un nivel de prioridad desde –10.000 a +10.000, siendo el valor por defecto 0. Se indica con (declare(salience n)) como premisa de la parte izquierda de la regla. En definitiva, en la agenda las reglas se ordenan por su valor salience y, con igual valor, por su orden inverso de llegada.

En la mayoría de los sistemas expertos que se desarrollen, en cuanto tienen una cierta complejidad, se pueden identificar fases o secuencias de aplicación de reglas, esto es, admiten una cierta visión procedimental: Reglas que solucionan el subproblema-1, las que solucionan el subproblema-2, etc. y hay que secuenciarlas adecuadamente. Las reglas desde luego se activan cada vez que se cumplen sus premisas, que son aplicables, esto es lo esencial, pero también, como decimos, hay que establecer una cierta secuenciación o flujo de control. Por ejemplo, en las Fig. 1 y Fig. 2 se describe la secuencia de control en el

Page 16: Apuntes Uned Clips

14

lanzamiento las reglas en las que se apoyan los pasos de inferencia recubre, predice y compara en el que se ha descompuesto la tarea de diagnóstico.

Fig. 1 Ejemplo de esquema inferencial que supone una secuenciación en la ejecución de los distintos tipos de reglas

Fig. 2 Ejemplo en KML en el que se describe el método utilizado para la resolución de la tarea, especificando las subtareas y la estructura de control sobre ellas.

Para establecer una estructura de control de este tipo mediante reglas como las que hemos visto, podemos utilizar uno de los siguientes métodos. En los ejemplos se supondrá que la subtareas se suceden sin interrupción.

Incluir en cada regla una premisa que determine la fase (predice, obtiene, etc.) del proceso en que podrá ser activada y la afirmación del hecho que sitúa al sistema en la siguiente fase (si ha lugar):

Page 17: Apuntes Uned Clips

Una introducción a la programación en Clips 15

(defrule predice-... (fase predice) ... => ... (retract (fase predice)) (assert (fase obtiene)))

Otra opción es secuenciar a través de los niveles de prioridad, pero esto tiene el problema de que se pueden mezclar las fases, en tanto que, si en la fase obtiene entra a la agenda una regla de las de apoyo al predice, esta se activará antes que restantes correspondientes a obtiene. El ejemplo auto.clp (en el sitio web de Clips) utiliza este sistema.

La tercera opción, podríamos decir que híbrida de las anteriores, es definir unas reglas específicamente para secuenciar las fases, que tienen una prioridad más baja que las reglas que soportan el conocimiento experto propiamente dicho, y que cambian el hecho de define las subtarea activa.

(defrule predecir-obtener (declare (salience –100)) ?subtarea <- (subtarea predecir) => (retract ?subtarea) (assert (subtarea obtener))) (defrule obtener-comparar (declare (salience –100)) ?subtarea <- (subtarea obtener) => (retract ?subtarea) (assert (subtarea comparar))) (defrule comparar-predecir (declare (salience –100)) ?subtarea <- (subtarea comparar) => (retract ?subtarea) (assert (subtarea predecir)))

Cada regla “experta” comienza con una premisa que la identifica como perteneciente al conocimiento de apoyo de una subtarea:

(defrule predice-... (subtarea predecir) ; premisas que determinan la hipótesis de una avería ... => ... )

Alternativamente a esta opción, podría escribirse una regla única de control que fuera lanzando las sucesivas subtareas sin tener que aparecer explícitamente en la regla, de esta forma:

(defrule lanza-subtarea (declare (salience –100)) ?subtarea <- (subtarea ?subtarea-actual) (subtarea-siguiente-a ?subtarea-actual ?subtarea-siguiente)

Page 18: Apuntes Uned Clips

16

=> (retract ?subtarea) (assert (subtarea ?subtarea-siguiente)))

Siempre y cuando se hayan definido estas fases como hechos sucesivos de un slot fase-posterior:

(deffacts secuencia-subtareas (subtarea predecir) (subtarea-siguiente-a predecir obtener) (subtarea-siguiente-a obtener comparar) (subtarea-siguiente-a comparar predecir))

Algo totalmente equivalente sería utilizar una lista de subtareas y una regla que las secuenciara, es decir:

(defrule lanza-subtarea (declare (salience –100)) ?subt <- (subtarea ?subt-actual) ?sec-subts <- (secuencia-subts ?subt-siguiente $?otras-

subts) => (retract ?subt ?sec-subts) (assert (subtarea ?subt-siguiente)) (assert (secuencia-subts ?otras-subts ?subt-

siguiente)))

Con (deffacts estructura-subtareas (subtarea predecir) (secuencia-subts obtener comparar predecir))

Según cualquiera de estas opciones anteriores, tendríamos dos niveles de prioridad, uno asignado a las reglas que hemos denominado expertas y otro a las reglas de control. Aún podrían introducirse dos niveles más, también para el correcto control en la evaluación de las reglas. El mayor nivel de prioridad deberían tenerlo aquellas reglas que detectan situaciones que se han definido como ilegales del programa. Las reglas especificas para plantear preguntas al usuario deber deberían tener un nivel intermedio de prioridad, entre las reglas de control y las reglas expertas, de tal forma que solo se pregunte al usuario cuando no haya conocimiento experto para inferir el valor que se cuestiona.

Pero no deberían utilizarse muchos más niveles de prioridad, desde luego no dentro de las reglas “expertas”, estas deben contener en sus premisas la determinación exacta y explícita de la situación que las hacen activarse. Así, en un sistema algo grande, es preferible utilizar módulos contenedores compactos de reglas, y por tanto más sistemas de reglas en sí más reducidos, que se activan según una estructura de control procedimental.

Page 19: Apuntes Uned Clips

Una introducción a la programación en Clips 17

7 Programación procedimental

Clips dispone de algunas funciones para implementar las estructuras de control propias de la programación procedimental. En conjunción con las reglas, se pueden utilizar estas estructuras principalmente expresadas en la parte derecha de la regla, de tal forma, que la veracidad de las premisas suponga la ejecución de un pequeño procedimiento expresado, como decimos en la parte derecha. Principalmente son if, while y halt. La primera obviamente soporta la estructura if..the..else, por todos conocida. La segunda soporta la ejecución del típico bucle while y la función halt, sin argumentos, permite parar la ejecución de las reglas de la agenda. La siguiente regla nos muestra la utilización de estas tres funciones. En la secuencialidad de las subtareas podemos dar la opción al usuario de parar momentáneamente o definitivamente la ejecución.

(defrule plantear-revision ?subtarea <- (subtarea plantear-revision) => (retract ?subtarea) (printout t "¿continuamos?(S/N) ") (bind ?respuesta (read)) (while (and (neq ?respuesta S) (neq ?respuesta N)) do (printout t "¿continuamos?(S/N) ") (bind ?respuesta (read))) (assert (subtarea continuar)) (if (neq ?respuesta S) then (halt)))

La función while nos permite seguir planteando la pregunta al usuario hasta que éste responda una de los dos caracteres reconocidos. La función if permite parar la ejecución, con halt, si la respuesta es afirmativa.

8 Ejemplo de tratamiento de la incertidumbre en Clips: factores Mycin

Clips no incluye ningún mecanismo para el tratamiento de la incertidumbre, por tanto, si queremos emplear alguna técnica para ello, hay que codificar el soporte y los mecanismos asociados. Como ejemplo vamos a solucionar el tratamiento de la incertidumbre con factores de certeza Mycin.

La representación en Mycin se apoya en el habitual par slot-valor, o dicho de otra forma, en la terna objeto-atributo-valor, más un valor entre –1 y +1 que indica el nivel de certeza o de evidencia de que el valor del slot sea el especificado, es decir del hecho. Vamos a definir un template que soporte los cuatro valores:

(deftemplate oav-fc (multislot objeto (type SYMBOL)) (multislot atributo (type SYMBOL))

Page 20: Apuntes Uned Clips

18

(multislot valor) (slot fc (type FLOAT) (range –1.0 +1.0)))

Por ejemplo: (oav-fc (objeto microorganismo) (atributo morfologia) (valor bacilo) (fc 0.7))

Una característica fundamental en Mycin es que pueden llegarse a los mismos hechos desde diferente reglas o inferencias y esto afectará a su certeza, de tal forma que es preciso combinar los diferentes factores de certeza obtenidos. Como es posible que se obtengan valores idénticos para los factores desde, como decimos, diferentes reglas, tenemos que decirle a Clips que admita hechos duplicados con la instrucción:

(set-fact-duplication true)

Evidentemente, la misma instrucción con FALSE desactiva la posibilidad de hechos duplicados.

Para combinar los hechos duplicados se precisa una regla que combine los factores de certeza de dos hechos y que genere un nuevo hecho con el nuevo factor combinado, anulando los hechos previos. Con la siguiente regla se consigue esto para hechos con una factor de certeza positivo o cero.

(defrule combina-oav-fc-positivas (declare (auto-focus true)) ?hecho1 <- (oav-fc (objeto $?obj) (atributo $?atri) (valor $?val) (FC $?fc1&: (>= ?fc1 0))) ?hecho2 <- (oav-fc (objeto $?obj) (atributo $?atri) (valor $?val) (FC $?fc2&: (>= ?fc2 0))) (test (neq hecho1 hecho2)) => (retract ?hecho1) (bind ?fc3 (- (+?fc1 ?fc2) (* ?Fc1 ?fc2))) (modify ?hecho2 (FC ?fc3)))

La instrucción (declare (auto-focus true)) impide que se disparen antes otras reglas, asegura que sea ésta la primera que se dispare al cumplirse las premisas. La premisa con test garantiza que la regla se aplica sobre diferentes hechos. Como podemos observar, la regla realmente no genera un tercer hecho y suprime los anteriores, sino que suprime uno y modifica el otro.

Page 21: Apuntes Uned Clips

Una introducción a la programación en Clips 19

Por otro lado, las reglas de la base de conocimientos tienen que tener en cuenta un mecanismo de propagación de la certeza de la premisa sobre la certeza de la conclusión. Sirva la siguiente regla Clips como ejemplo de estructura de regla mycin que contiene el mecanismo de propagación de la evidencia. En este caso, solamente se afirmará el hecho conclusión es decir, se ha limitado la posibilidad de transferir evidencia a la conclusión, cuando existan unos determinados hechos y con factores superiores a 0.2:

(defrule combina-oav-FC-positivas (oav-fc (objeto microorganismo) (atributo coloracion) (valor gramnegativo) (fc ?fc1)) (oav-fc (objeto microorganismo) (atributo morfologia) (valor bacilo) (fc ?fc2)) (oav-fc (objeto paciente) (atributo es-un) (valor huesped-afectado) (fc ?fc3)) (test (> (min (?fc1 ?fc2 ?fc3) 0.2)) => (bind ?fc4 (* (min (?fc1 ?fc2 ?fc3) 0.6))) (assert (oav-fc (objeto microorganismo) (atributo identidad) (valor seudomonas) (fc ?fc4))))

Como vemos, el factor de certeza de nuevo hecho generado en la conclusión es el resultado de multiplicar el valor de certeza de la propia afirmación de la regla (0.6) por el valor de certeza combinado de la premisa, el mínimo de los valores de certeza de los hechos que intervienen en las premisas enlazadas por un y-lógico.

9 Árboles de decisión.

Recordemos el problema de diagnóstico de una hemorragia a través de la siguiente estructura:

Page 22: Apuntes Uned Clips

20

Fig. 3 Árbol de decisión binario

Es la estructura de lo que se denomina árbol de decisión binario, esto es, un árbol compuesto por nodos intermedios, que representan afirmaciones (“El tiempo de tromboplastina parcial activado es normal”), de los que salen enlaces, según la afirmación sea cierta o no, hacia dos nodos descendientes. Los nodos hoja representan afirmaciones finales que ya no se evalúan (“Secuestro leucocitario”). También pueden verse los nodos intermedios como preguntas que solo tienen dos posibles respuestas Si y No (“¿Es normal el tiempo de tromboplastina parcial activado?”) y los nodos hoja como afirmaciones concluyentes.

El algoritmo de recorrido de un árbol de decisión de este tipo es muy sencillo, Comienzo nodo-actual := nodo-raiz; Mientras not(nodo-hoja(nodo-actual)) hacer respuesta:= preguntar(nodo-actual); Si respuesta =cierto entonces nodo-actual := descendiente-cierto(nodo-actual); Si respuesta =falso entonces nodo-actual := descendiente-falso(nodo-actual); Fin Visualizar-afirmacion(nodo-actual) Fin

En el seudocódigo empleado se ha supuesto una función preguntar(nodo-actual) que pregunta al usuario si es cierta o falsa la afirmación contenida en el nodo-actual y devuelve

Page 23: Apuntes Uned Clips

Una introducción a la programación en Clips 21

la respuesta. También sendas funciones que proporcionan los dos descendientes del nodo-actual. Por último la función visualizar-afirmacion muestra en la interfaz de salida la afirmación del nodo hoja en que queda la variable nodo-actual tras el recorrido del árbol. Por supuesto esta función sería sustituible por cualquier otro proceso sobre el nodo hoja localizado, por ejemplo, la simple devolución como parámetro de vuelta del procedimiento.

Veamos ahora el código necesario en Clips para llevar a cabo lo expresado en este seudocódigo. Podemos comenzar por definir las templates que soporten nodos con esta estructura:

Fig. 4 Estructura de los nodos

(deftemplate nodo (slot nombre) (slot tipo (allowed-values intermedio hoja)) (slot afirmacion) (slot sig-h-cierto) (slot sig-h-falso))

Por tanto, los nodos se definen como hechos con este formato:

(deffacts arbol-diagnostico

(nodo (nombre PRLP) (tipo intermedio) (afirmación "Prueba de Rumpell-Leede positiva") (sig-h-cierto VAS) (sig-h-falso RPB))

… (nodo (nombre TNB) (tipo intermedio)

Page 24: Apuntes Uned Clips

22

(afirmación "Tamaño normal del bazo") (sig-h-cierto SL) (sig-h-falso CI))

Para comenzar a recorrer el árbol se termina el def-facts con la definición de un hecho definiendo el nodo inicial o raíz (en nuestro caso PRLP):

(nodo-actual PRLP))

Lo cual ya prepararía el sistema para una regla como esta: (defrule afirmacion ?nodo <- (nodo-actual ?nombre)) (nodo (nombre ?nombre) (tipo intermedio) (afirmacion ?afirmacion)) (not (respuesta ?)) => (printout t "¿Es cierto lo siguiente (S/N)? "

?afirmacion) (assert (respuesta (read))))

Que tiene su continuación en la regla que restringe los valores de la respuesta a la anterior pregunta:

(defrule respuesta-incorrecta ?respuesta <- (respuesta ~S&~N)) => (retract ?respuesta))

Al eliminar el hecho basado en respuesta, se activa inmediatamente la regla afirmación. Por el contrario, si la respuesta fue correcta, o bien se activa:

(defrule respuesta-afirmativa ?nodo <- (nodo-actual ?nombre)) (nodo (nombre ?nombre) (tipo intermedio) (sig-h-cierto ?descendiente-cierto)) (?respuesta <- (respuesta S)) => (retract ?nodo ?respuesta) (assert (nodo-actual ?descendiente-cierto))

O bien se activa: (defrule respuesta-negativa ?nodo <- (nodo-actual ?nombre)) (nodo (nombre ?nombre) (tipo intermedio) (sig-h-falso ?descendiente-falso)) (?respuesta <- (respuesta N)) => (retract ?nodo ?respuesta) (assert (nodo-actual ?descendiente-falso))

Para los nodos hoja:

Page 25: Apuntes Uned Clips

Una introducción a la programación en Clips 23

(defrule nodos-hoja ?nodo <- (nodo-actual ?nombre)) (nodo (nombre ?nombre) (tipo hoja) (afirmacion ?afirmacion)) (not (respuesta ?)) => (retract ?nodo ?respuesta) (printout t "Se deduce que... " ?afirmacion crlf) (assert (nuevo-caso))

Se afirma el hecho nuevo-caso para determinar el lanzamiento de la siguiente regla que pregunta al usuario si desea continuar con otro caso y esto requiere además otras dos reglas:

(defrule preguntar-por-nuevo-caso (nuevo-caso) (not (respuesta ?)) => (printout t "¿Desea comenzar un nuevo caso (S/N)? ") (assert (respuesta (read)))) (defrule nuevo-caso ?nuevo-caso <- (nuevo-caso) ?respuesta <- (respuesta S) => (retract ?nuevo-caso ?respuesta) (assert (nodo-actual PRLP))) (defrule terminar ?nuevo-caso <- (nuevo-caso) ?respuesta <- (respuesta N) => (retract ?nuevo-caso ?respuesta) (printout t "FIN DE LA EJECUCION" crlf))

Si se consideran los nodos intermedios como preguntas sobre el valor de un slot y se admiten más de dos valores posibles (más allá de un “si” y un “no”), el algoritmo necesario es algo más complejo, pero también evidente. En la figura se muestra la trasformación de una parte del ejemplo anterior en un nodo de respuesta múltiple:

Fig. 5 Ejemplo de árbol de decisión de respuesta múltiple

Comienzo nodo-actual := nodo-raiz; Mientras not(nodo-hoja(nodo-actual))

Page 26: Apuntes Uned Clips

24

Hacer respuesta:= preguntar(nodo-actual); i:=1; Mientras existe-descendiente(nodo-actual, i) y respuesta <> valor-descendiente(nodo-actual, i) Hacer i:= i+1; Fin Si existe-descendiente(nodo-actual, i) entonces nodo-actual := descendiente(nodo-actual, i); Fin Visualizar-afirmacion(nodo-actual)

Fin

En el seudocódigo se ha incluido un índice i para referenciar a los enlaces a los correspondientes nodos descendientes (evidentemente es un procedimiento sustituible por cualquier otro) en una función de existencia de los dichos enlaces (existe-descendiente), en una función para obtener la respuesta correspondiente al enlace i-ésimo (valor-descendiente) y en una función que proporciona justamente el nodo de ese enlace (descendiente). El algoritmo expuesto asume que la respuesta del usuario debe coincidir con el valor correspondiente de alguno de los nodos descendientes. Si se considera la posibilidad contraria, habría que incluir un procedimiento de vuelta a la pregunta al usuario, para que éste pueda seleccionar una respuesta para la que sí exista un descendiente.

EJERCICIO 1: Escribir la variante del código anterior de acuerdo a lo expresado en este seudocódigo.

Una posibilidad muy importante de los árboles de decisión es la posibilidad de “aprender”, entendiéndose este concepto aquí como la posibilidad de que en la sesión de trabajo se incluyan nuevos nodos para corregir, completar o para ampliar el árbol, de tal forma que el árbol quede representado un conocimiento más completo. Por ejemplo, supongamos el siguiente árbol, previo al de la figura 3, en el que la afirmación de la hipótesis: “No hay reducción o ausencia de megacariocito” lleva a la conclusión de “Causas inmunológicas”.

Page 27: Apuntes Uned Clips

Una introducción a la programación en Clips 25

Fig. 6 Árbol de decisión incompleto

El usuario experto, durante la sesión de evaluación de un caso, puede encontrarse ante la secuencia:

> Es cierto que “No hay reducción o ausencia de megacariocitos”?

> Entonces, el origen es de “Causas inmunológicas”

Y, por tanto, desee cambiar la estructura de árbol para que responde adecuadamente, tal y como muestra la figura 3. Para ello la sesión debería continuar, ante la exposición de una conclusión:

> Entonces, el origen es de “Causas inmunológicas” ¿Es correcta la conclusión?

No

>¿Cuál es la conclusión correcta?

Secuestro leucocitario

> Entonces, ¿cuál debería ser la hipótesis cuya afirmación conduce a esta conclusión?

Page 28: Apuntes Uned Clips

26

Tamaño normal del bazo

Con esto, el subárbol que estamos corrigiendo debería quedar así:

Fig. 7 Corrección del árbol de partida

EJERCICIO 2: Escribir la variación del seudocódigo para que incluya esta posibilidad de aprendizaje.

EJERCICIO 3: Escribir la variante de códigos anteriores para que incluir esta posibilidad de aprendizaje (nota: habrá que utilizar los operadores load-facts y save-facts para cargar y guardar las modificaciones en el árbol, en la base de hechos puesto que los nodos se definen como hechos a partir del template básico).

10 El problema del encadenamiento hacia atrás

El motor de inferencia de Clips, como ya se ha nombrado, solo encadena reglas hacia delante (forward chaining). Esto supone que si tenemos la exigencia de un encadenamiento hacia atrás (backward chainning) tenemos dos opciones, o utilizar otra herramienta cuyo motor de inferencia sí pueda hacer este tipo de encadenamiento, o emular el encadenamiento de alguna forma, sin salir de Clips. En este apartado vamos a ver esta última opción de un modo simplificado, con restricciones que iremos detallando. La emulación es tal, como veremos, que no sirve la construcción def-rule para definir las reglas que van a encadenarse hacia atrás. Estas se construirán como hechos sobre los que los procedimientos de encadenamiento realizarán la inferencia.

La estructura de las reglas va a ser muy simple: (deftemplate rule (multislot if) (multislot then))

Por supuesto el slot if sostendrá las premisas mientras que then lo hará con las conclusiones. En if (o parte izquierda, LHS) determinaremos que las premisas sencillas

Page 29: Apuntes Uned Clips

Una introducción a la programación en Clips 27

tengan la estructura: <atributo> es <valor>, con es como palabra reservada. Admitiremos esta misma estructura pero entendida como una única asignación de un valor simple o inclusión de un hecho, como conclusión de la regla (then o parte derecha, RHS). En la LHS admitiremos la composición a través de la conectiva y.

<atributo-1> es <valor-1> y ... <atributo-n> es <valor-n>

Pero el formato Clips de la regla nos interesa especialmente para codificar el motor de inferencia, pero el seudocódigo, independientemente de este formato, que expresa las funcionalidad del motor debería ser: Procedimiento Obtener-objetivo(objetivo)

Si valor(objetivo)<> nil entonces retornar(valor(objetivo)); Para cada regla ∈ Base-Conocimientos / contiene-rhs(propiedad,

regla) hacer Si regla-con-exito(regla)=TRUE entonces valor(objetivo):= valor-rhs(regla); retornar(valor(objetivo) Fin Fin valor(objetivo):= pedir-valor-usuario; retornar(valor(objetivo);

Fin

El proceso central de este procedimiento es el de búsqueda de las reglas cuya RHS consistan en la asignación de un valor al atributo objetivo. Como vemos, si no existe ninguna de tales reglas, el procedimiento pide al usuario que introduzca un valor para dicho atributo a través de una función pedir-valor-usuario. En el seudocódigo se incluye una estructura de control que recorre todas las reglas, las incluidas en la base de conocimientos que, recordemos, se ha construido como un conjunto de hechos. Se asume también la existencia de un función que identifica la existencia del atributo objetivo en la parte derecha de la regla RHS (contiene-rhs), así como de una función de devuelve un TRUE si la regla cumple todas las premisas de su LHS (regla-con-éxito). Y para esto último, para la función regla-con-exito, el siguiente seudocódigo puede ser descriptivo: Procedimiento Regla-con-exito(regla)

Para cada condición ∈ LHS(regla) hacer Si Obtener-objetivo(propiedad(condicion))<> valor(condicion)

entonces retornar(FALSE); Fin retornar(TRUE);

Fin

Este procedimiento, claro, hace uso recursivamente del procedimiento invocante obtener-objetivo esta vez para buscar un valor que permite evaluar el cumplimiento de la condición de la parte izquierda de la regla.

Page 30: Apuntes Uned Clips

28

Para implementar este mecanismo de encadenamiento hacia atrás en Clips, a partir del formato de regla simplificado que ya hemos definido es preciso definir un template para describir los hechos que juegan el papel de objetivos, a través de la definición de una propiedad, en la búsqueda y otro para describir los hechos de los valores de esas propiedades realmente encontrados (propiedad-valor).

(deftemplate objetivo (slot propiedad)) (deftemplate propiedad-valor (slot propiedad) (slot valor))

Además es preciso definir un hecho que soporte el objetivo inicial de la búsqueda. El objetivo es el atributo cuyo valor se busca en la inferencia:

(deffacts objetivo-inicial (objetivo (propiedad propiedad-objetivo-inicial))

Como problema ejemplo, vamos a utilizar una versión simplificada del diagnóstico de hemorragias:

Fig. 8 Árbol de decisión del ejemplo simplificado

La reglas que soportan este árbol serían las siguientes: (deffacts reglas-diagnostico (rule (if rumpell-leede es positivo) (then diagnostico es vasculitis)) (rule (if rumpell-leede es negativo y

Page 31: Apuntes Uned Clips

Una introducción a la programación en Clips 29

recuento-plaquetas es normal) (then diagnostico es deficiencia-factores-

coagulacion)) (rule (if rumpell-leede es negativo y recuento-plaquetas es bajo y reduccion-megacariocitos es falso) (then diagnostico es aplasia-medula-osea)) (rule (if rumpell-leede es negativo y recuento-plaquetas es bajo y reduccion-megacariocitos es cierto y dimension-bazo es normal) (then diagnostico es secuestro-leucocitario)) (rule (if rumpell-leede es negativo y recuento-plaquetas es bajo y reduccion-megacariocitos es cierto y dimension-bazo es anormal) (then diagnostico es causas-inmunologicas)))

Junto con los hechos iniciales: (propiedad-valor (propiedad rumpell-leede)) (propiedad-valor (propiedad recuento-plaquetas)) (propiedad-valor (propiedad reduccion-megacariocitos)) (propiedad-valor (propiedad dimension-bazo))

Por tanto, el objetivo inicial es encontrar el valor del diagnóstico: (deffacts objetivo-inicial (objetivo (propiedad diagnostico))

Para ello, debemos incluir dos reglas clips, una que busque las rules cuya conclusión (then) incluya la asignación de un valor a diagnóstico y que afirme su antecedente (if) como nuevo objetivo, siempre y cuando no tenga ya un valor previo o ya sea un objetivo. (defrule evaluar-regla

(objetivo (propiedad ?objetivo)) (rule (if ?propiedad $?) (then ?objetivo $?)) (not (propiedad-valor (propiedad ?propiedad))) (not (objetivo (propiedad ?propiedad))) => (assert (objetivo (propiedad ?propiedad))))

Y la otra que pregunte al usuario por el valor del antecedente cuando no haya rules que lo incluyan en su consecuente. (defrule pedir-valor-propiedad

?hecho-objetivo <- (objetivo (propiedad ?objetivo)) (not (propiedad-valor (propiedad ?objetivo))) (not (rule (then ?objetivo $?))) => (retract ?hecho-objetivo) (printout t "Introduzca el valor de " ?objetivo ": ") (assert (propiedad-valor (propiedad ?objetivo)

Page 32: Apuntes Uned Clips

30

(valor (read)))))

Complementando a estas dos reglas, debe haber otra regla que elimine como objetivo aquél que ya se ha logrado y esto, es evidente, debe tener prioridad sobre la búsqueda de objetivos (las anteriores reglas): (defrule retractar-objetivo-logrado

(declare (salience 10)) ?hecho-objetivo <- (objetivo (propiedad ?objetivo)) (propiedad-valor (propiedad ?objetivo)) => (retract ?hecho-objetivo))

También tiene que tener prevalencia la afirmación del consecuente de una rule cuando su antecedente, como anterior objetivo, se haya logrado. Esto tiene que llevar asociado la eliminación de esa rule. (defrule afirmar-consecuente

(declare (salience 10)) (objetivo (propiedad ?objetivo)) (propiedad-valor (propiedad ?propiedad) (valor ?valor)) ?rule <- (rule (if ?propiedad es ?valor) (then ?objetivo es ?valor-objetivo)) => (retract ?rule) (assert (propiedad-valor (propiedad ?objetivo) (valor ?valor-objetivo))))

Cuando se cumple la primera premisa del antecedente de una rule, esta misma premisa se elimina de la rule, para no volverla a tener en cuenta: (defrule eliminar-premisa-cumplida

(declare (salience 10)) (objetivo (propiedad ?objetivo)) (propiedad-valor (propiedad ?propiedad) (valor ?valor)) ?rule <- (rule (if ?propiedad es ?valor y $?resto-if) (then ?objetivo es ?valor-objetivo))) => (retract ?rule) (modify ?rule (if $?resto-if)))

Pero si no se cumple la premisa, y por tanto, todo el antecedente, toda la rule debe ser eliminada: (defrule retractar-rule-fallida

(declare (salience 10)) (objetivo (propiedad ?objetivo)) (propiedad-valor (propiedad ?propiedad) (valor ?valor)) ?rule <- (rule (if ?propiedad es ~?valor) (then ?objetivo es ?valor-objetivo)))

Page 33: Apuntes Uned Clips

Una introducción a la programación en Clips 31

=> (retract ?rule))

Si deseamos que en la pregunta al usuario se especifiquen los posibles valores aceptables, una forma muy sencilla de resolverlo sería modificar la regla pedir-valor-propiedad para que quede:

(defrule pedir-valor-propiedad ?hecho-objetivo <- (objetivo (propiedad ?objetivo)) (not (propiedad-valor (propiedad ?objetivo))) (not (rule (then ?objetivo $?))) (valores-posibles (propiedad ?objetivo) (valor-1 ?valor-1)

(valor-2 ?valor-2)) => (retract ?hecho-objetivo) (printout t "Introduzca el valor de " ?objetivo " ("

?valor-1 "/" ?valor-2 "): ") (assert (propiedad-valor (propiedad ?objetivo) (valor (read)))))

Para lo que es preciso definir un nuevo template: (deftemplate valores-posibles (slot propiedad) (slot valor-1) (slot valor-2))

Y los siguientes hechos complementarios: (deffacts pruebas

(valores-posibles (propiedad rumpell-leede) (valor-1 positivo) (valor-2 negativo))

(valores-posibles (propiedad recuento-plaquetas) (valor-1 normal) (valor-2 bajo))

(valores-posibles (propiedad reduccion-megacariocitos) (valor-1 cierto) (valor-2 falso))

(valores-posibles (propiedad dimension-bazo) (valor-1 normal) (valor-2 anormal)))