Generalidades
SAT es el ejemplo can贸nico de problema NP-completo. Consiste en determinar, dada una f贸rmula booleana en forma de conjunci贸n de disyunciones (donde cada disyunci贸n se llama cla煤sula), si existe una asignaci贸n de valores a sus variables que hagan verdadera la f贸rmula (esto se denomina “satisfacer” la f贸rmula y de ah铆 viene el nombre SATisfiability asignado al problema). Por ejemplo,
,
ya que la asignaci贸n
hace verdadera a la expresi贸n. Mientras que
,
ya que no existe forma de asignarle valores de verdad a
y
sin hacer falsa a la f贸rmula.
Un algoritmo simple para resolverlo es probar distintas combinaciones de valores para las variables hasta encontrar alguna que haga verdadera a la f贸rmula o agotar las posibles combinaciones de valores. En el peor caso (cuando no se pueda encontrar una asignaci贸n), el algoritmo examinar谩
posibles asignaciones (siendo
el n煤mero de variables). Por lo tanto, este algoritmo tiene una complejidad temporal
.
Pero, aunque no se cree posible encontrar un algoritmo de complejidad sub-exponencial para este problema, si se conocen algoritmos que mejoran significativamente al algoritmo anteriormente descrito. A continuaci贸n estudiaremos uno de los m谩s simples, conocido como DPLL.
DPLL
El algoritmo DPLL, llamado as铆 por las iniciales de sus creadores, mejora al algoritmo anteriormente descrito mediante el uso de dos t茅cnicas:
- Backtracking: la asignaci贸n de variables para satisfacer la f贸rmula se construye variable por variable, volvi茅ndose atr谩s apenas se detecta que la asignaci贸n de valor efectuada a una variable hace imposible satisfacer la f贸rmula.
- Unit propagation: cuando una de las cl谩usulas contiene una sola variable (negada o no), la conjunci贸n obliga a que esta variable tome un valor espec铆fico. El proceso de unit propagation consiste en propagar el efecto de esta asignaci贸n de valor sobre el resto de las cl谩usulas.
Si bien la referencia original utilizaba una t茅cnica adicional de eliminaci贸n de literales puros, esta no proporciona ventajas significativas en performance por lo que la omitiremos del an谩lisis.
Implementaci贸n de DPLL en Javascript
A continuaci贸n analizaremos una implementaci贸n de este algoritmo en Javascript. La implementaci贸n enlazada toma una cla煤sula por l铆nea, utilizando - como operador de negaci贸n y sin requerir operadores expl铆citos para la disyunci贸n y la conjunci贸n. Por ejemplo, la f贸rmula
se representar铆a como
x1 x2 -x3
-x2 x3
-x1
Un script incluido en el archivo HTML se encarga de transformar el problema a un formato can贸nico, en el que las variables son reemplazadas por n煤meros enteros mayores que uno y la negaci贸n l贸gica se transforma en una negaci贸r aritm茅tica. Las cl谩usulas se convierten en arrays Javascript, con lo que la f贸rmula anterior quedar铆a transformada en [[1, 2, -3], [-2, 3], [-1]]
.
La primera funci贸n que analizaremos es simplifyClause()
:
function simplifyClause(c, a) {
var nc, i;
nc = [];
for (i = 0; i < c.length; i++) {
if (c[i] in a) {
return null;
} else if (!(-c[i] in a)) {
nc.push(c[i]);
}
}
return nc;
}
Toma dos par谩metros, c
y a
, que representan una cla煤sula dada como array de valores num茅ricos y una asignaci贸n de valores a las variables dada como un diccionario, respectivamente. El diccionario se utiliza como conjunto, empleando el n煤mero correspondiente a la variable como clave y true
como valor; la clave num茅rica se niega para indicar la asignaci贸n de un valor falso a la variable. Por ejemplo, la asignaci贸n
y
se representar铆a con {1: true, -2: true}
.
La funci贸n simplemente copia la cla煤sula c
a una nueva cl谩usula nc
, examinando las apariciones de variables a la luz de la asignaci贸n de valores que se utiliza en ese momento. Si encuentra una aparici贸n de variable que se corresponda con una asignaci贸n incluida en a
, significa que la cl谩usula es verdadera; por lo tanto devuelve null
indicando la “desaparici贸n” de la misma. Por el contrario, si encuentra la aparici贸n negada en a
, simplemente no la incluye en la nueva cla煤sula (ya que es falsa, el valor neutro de la disyunci贸n).
Continuando con las funciones, podemos examinar la funci贸n applyAssignment()
:
function applyAssignment(f, a) {
var nf, i, nc;
nf = [];
for (i = 0; i < f.length; i++) {
nc = simplifyClause(f[i], a);
if (nc !== null) {
nf.push(nc);
}
}
return nf;
}
Esta funci贸n hace lo mismo que la anteriormente descrita pero sobre toda una f贸rmula. Como la simplificaci贸n se efect煤a cl谩usula por cl谩usula, el 煤nico procesamiento no delegado a simplifyClause()
es la supresi贸n de las cla煤sulas que son devueltas como null
(que son las que simplifyClause()
determin贸 como tautol贸gicas dada la asignaci贸n a
).
Dejando a un lado funciones auxiliares, como cloneAssignment()
, solo resta analizar la funci贸n principal: recDPLL()
.
function recDPLL(f, a) {
var i, na, v, ret;
f = applyAssignment(f, a);
if (f.length === 0) {
return [true, a];
}
for (i = 0; i < f.length; i++) {
if (f[i].length === 0) {
return [false, {}];
} else if (f[i].length === 1) {
na = cloneAssignment(a);
na[f[i][0]] = true;
return recDPLL(f, na);
}
}
na = cloneAssignment(a);
na[f[0][0]] = true;
ret = recDPLL(f, na);
if (ret[0]) {
return ret;
}
delete na[f[0][0]];
na[-f[0][0]] = true;
return recDPLL(f, na);
}
En primer lugar la funci贸n simplifica la f贸rmula f
con la asignaci贸n de valores a
, empleando las t茅cnicas anteriormente descritas. Si esta simplificaci贸n conduzca a una f贸rmula vac铆a, dado que una conjunci贸n vac铆a es verdadera, devuelve true
y la asignaci贸n de valores que llev贸 a este valor de verdad.
Si no se lleg贸 a una f贸rmula vac铆a es obvio que restan cl谩usulas no tautol贸gicas dentro de la misma (todo esto tomando como “dada” la asignaci贸n a
de variables). Estas pueden ser de dos tipos: cl谩usulas contradictorias o contingencias. Como las cl谩usulas restantes no pueden contener variables con valores asignados, dado el anterior paso de simplificaci贸n, una cl谩usula contradictoria solo puede ser vac铆a.
El procesamiento restante de la f贸rmula consiste en recorrer las cl谩usulas buscando cl谩usulas vac铆as o con una sola aparici贸n de variable. Si se encuentra un cl谩usula vac铆a, estaremos en presencia de una contradicci贸n que imposibilita aumentar a
hasta convertirla en una asignaci贸n que satisfaga a la f贸rmula en cuesti贸n. Por lo tanto se realiza backtracking devolviendo [false, {}]
.
La aparici贸n de una cl谩usula con una sola variable es m谩s interesante, ya que puede verse como “forzando” una asignaci贸n. En este caso es cuando se realiza el proceso de unit propagation, que consiste simplemente en incorporar la 煤nica asignaci贸n compatible con la cl谩usula unitaria dentro de a
y continuar la b煤squeda recursiva. No es necesario simplificar la f贸rmula, ya que esto se realizar谩 apenas comience la ejecuci贸n de la nueva instancia de recDPLL()
.
Por 煤ltimo, si la f贸rmula no cae en ninguno de estos dos casos se realiza una exploraci贸n recursiva normal.
Utilizando SAT para resolver otros problemas
Para mostrar como SAT puede utilizarse para resolver problemas l贸gicos en general, veremos como aplicarlo a la resoluci贸n de un problema de l贸gica conocido como “el problema de Einstein” (aunque es bastante poco probable que Einstein efectivamente lo haya creado). La formulaci贸n que utilizaremos es la siguiente:
1. There are 5 houses (along the street) in 5 different colors: blue, green, red, white and yellow.
2. In each house lives a person of a different nationality: Brit, Dane, German, Norwegian and Swede.
3. These 5 owners drink a certain beverage: beer, coffee, milk, tea and water,
smoke a certain brand of cigar: Blue Master, Dunhill, Pall Mall, Prince and blend,
and keep a certain pet: cat, bird, dog, fish and horse.
4. No owners have the same pet, smoke the same brand of cigar, or drink the same beverage.
Hints
1. The Brit lives in a red house.
2. The Swede keeps dogs as pets.
3. The Dane drinks tea.
4. The green house is on the left of the white house (next to it).
5. The green house owner drinks coffee.
6. The person who smokes Pall Mall rears birds.
7. The owner of the yellow house smokes Dunhill.
8. The man living in the house right in the center drinks milk.
9. The Norwegian lives in the first house.
10. The man who smokes blend lives next to the one who keeps cats.
11. The man who keeps horses lives next to the man who smokes Dunhill.
12. The owner who smokes Blue Master drinks beer.
13. The German smokes Prince.
14. The Norwegian lives next to the blue house.
15. The man who smokes blend has a neighbor who drinks water.
The question is: Who keeps fish?
(Puede verse tambi茅n una formulaci贸n equivalente en espa帽ol.)
Si bien probablemente la t茅cnica m谩s simple de aplicar en este caso sea constraint-based programming, podemos aplicar SAT sin problemas. Para ello debemos decidir primero que variables utilizaremos, siendo una elecci贸n razonable numerar las casas, colores, nacionalidades, bebidas, cigarros y mascotas e introducir variables booleanas asociando las casas con los distintos atributos de la persona que vive en ellas. Eso nos da un total de
variables.
La traducci贸n de estas relaciones a cl谩usulas SAT puede ser algo tediosa, por lo que nos conviene emplear un script simple para este proceso. En caso de no disponer de Python, puede consultarse la salida del script en ep_sat.txt
. La nomenclatura de las variables es simple: hijk
, donde i
indica el n煤emro de casa, j
indica el atributo en cuesti贸n y k
indica un posible valor de este atributo. En el c贸digo Python pueden verse los detalles de como se traducen las implicaciones y restricciones a disyunciones.
Introduciendo las cl谩usulas SAT en la p谩gina de prueba de DPLL, podemos encontrar el siguiente resultado:
SATISFIABLE h0c4 h1c0 h2c2 h3c1 h4c3 h0n3 h0b4 h0s1 h0p0 h1n1 h1b3 h1s4 h1p4 h2n0 h2b2 h2s2 h2p1 h3n2 h3b1 h3s3 h3p3 h4n4 h4b0 h4s0 h4p2 -h0c3 -h2b0 -h2b1 -h2b3 -h2b4 -h0b2 -h1b2 -h3b2 -h4b2 -h2n1 -h2c1 -h3c3 -h0n0 -h0n1 -h0n2 -h0n4 -h1n3 -h2n3 -h3n3 -h4n3 -h2s0 -h1c1 -h1c2 -h1c3 -h1c4 -h0c0 -h2c0 -h3c0 -h4c0 -h1n0 -h2c3 -h0c1 -h0c2 -h2c4 -h3c2 -h4c2 -h3c4 -h4c1 -h4c4 -h3n0 -h4n0 -h5c3 -h3b0 -h3b3 -h3b4 -h0b1 -h1b1 -h4b1 -h3n1 -h0s0 -h0s2 -h0s3 -h0s4 -h1s1 -h2s1 -h3s1 -h4s1 -h1p0 -h1p1 -h1p2 -h1p3 -h0p4 -h2p4 -h3p4 -h4p4 -h1n4 -h1s2 -h3s0 -h4s4 -h0b0 -h0b3 -h1b4 -h4b4 -h2s4 -h3s4 -h0p1 -h0p2 -h0p3 -h2p0 -h3p0 -h4p0 -h1n2 -h4n1 -h1b0 -h4b3 -h1s0 -h1s3 -h2n2 -h2n4 -h2s3 -h3s2 -h4s2 -h4s3 -h2p2 -h2p3 -h3p1 -h4p1 -h4n2 -h3n4 -h4p3 -h3p2
Como el pez ser铆a la mascota n煤mero 3, buscamos apariciones de p3
en una variable no negada y encontramos h3p3
. Buscando la nacionalidad del habitante de la casa n煤mero 3 encontramos h3n2
, por lo que podemos concluir que el alem谩n es quien tiene un pez como mascota (coincidiendo con la soluci贸n dada a este problema).