8. Algoritmo DPLL (practica)
El algoritmo DPLL es un algoritmo de descenso recursivo para determinar la satisfactibilidad de una expresión booleana. A continuación te presentamos código en lenguaje Python para que puedas reforzar las definiciones necesarias para que puedas implementar el algoritmo.
Literales
Una literal es una variable proposicional afirmada o negada. La siguiente es una clase de Python que representa una literal. El constructor recibe el nombre de una variable como primer argumento y si esta es afirmada (positive = True) o negada (positive = False).
Las literales negadas se representan como precedidas por una tilde (~).
import re
class Literal:
# regular expression for a valid variable name
ID_REGEXP = re.compile(r"^[^\d\W]\w*\Z", re.UNICODE)
"""
Propositional sentence tha is a variable or a negated variable
"""
def __init__(self,variable,positive=True):
"""
Creates the literal for the given variable
:param variable: the variable of the literal
:param positive: positive literal if True, negative literal if false
"""
if re.match(Literal.ID_REGEXP,variable):
self.variable = variable
else:
raise SyntaxError("Invalid variable name: '"+variable+"'")
self.positive = positive
@staticmethod
def parse(s):
s = s.replace(" ","")
positive = not s.startswith("~")
return Literal(s if positive else s[1:],positive)
def __repr__(self):
return self.__str__()
def __str__(self):
return ("" if self.positive else "~")+self.variable
def __hash__(self):
return hash(self.__str__())
def __eq__(self,other):
return self.__str__()==other.__str__()
def __invert__(self):
return Literal(self.variable,not self.positive)
Ejemplo para crear las literales L1=A y L2=¬B
l1 = Literal("A")
l2 = Literal("B",False)
l3 = Literal("C")
l4 = ~l3
print(l1,l2,l3,l4)
Cláusulas
Una cláusula es una disyunción de literales. La siguiente es una clase en Python para representar una cláusula. El constructor recibe como primer argumento las literales que forman parte de la cláusula. Un segundo argumento del constructor indica cuando la representación de dichas literales es usando un conjunto que admite hashing.
class Clause:
"""
A propositional clause
This class keeps a singleton pattern of the clause with frozen hash
so that any simplification can be propagated to sets and dictionaries
in a single operation
"""
def __init__(self, literals=None,frozen_hash=True):
"""
cretates the clause with the provided set of literals
:param literals: the set of literals
:param frozen_hash: if true the hash is not based on content
"""
self.frozen_hash = frozen_hash
if literals:
self.literals = frozenset(literals) if frozen_hash else literals
else:
self.literals = set()
def __hash__(self):
"""
the hash number is the memory location when the flag
frozen_hash is True, otherwise the hash is the same as the
hash of the internal frozenset containing the literals
:returns: the hash number of the clause
"""
return id(self) if self.frozen_hash else hash(self.literals)
def __eq__(self,other):
"""
Equality between different instances of the same clause is only checked
when frozen_hash is False. If frozen_hash is True, the method will
return True, only for the same instance of the class
"""
if self.__hash__()==hash(other):
if self.frozen_hash:
return True
else:
return self.literals == other.literals
else:
return False
def __iadd__(self,literal):
"""
adds the literal to the clause
:param literal: the literal to add
"""
if isinstance(literal,Literal):
self.literals = self.literals.union({literal})
return self
else:
raise TypeError("An argument of type Literal was expected")
def __add__(self,literal):
"""
adds the literal to the clause
:param literal: the literal to add
"""
if isinstance(literal,Literal):
return Clause(self.literals.union({literal}),self.frozen_hash)
else:
raise TypeError("An argument of type Literal was expected")
def __isub__(self,literal):
"""
deletes a literal from the clause
:param literal: the literal to substract
"""
if isinstance(literal, Literal):
self.literals = self.literals.difference({literal})
return self
else:
raise TypeError("A literal of type string was expected")
def __sub__(self,literal):
"""
deletes a literal from the clause
:param literal: the literal to substract
"""
if isinstance(literal, Literal):
return Clause(self.literals.difference({literal}),self.frozen_hash)
else:
raise TypeError("A literal of type string was expected")
def __str__(self):
return "( "+" | ".join(map(str,self.literals))+" )"
def __repr__(self):
return self.__str__()
def __iter__(self):
return (i for i in self.literals)
def __len__(self):
return len(self.literals)
def copy(self):
return Clause(self.literals.copy(),self.frozen_hash)
Ejemplo creamos una cláusula c1 formada por las literales l1 y l2
c1 = Clause({l1,l2})
print(c1)
( A | ~B )
Fórmula en Forma Normal Conjuntiva
Una formula en Forma Normal Conjuntiva (FNC) es una conjunción de literales. Toda fórmula proposicional admite llevarse a su FNC.
A continuación te proporcionamos una clase para representar fórmulas en FNC. En el constructor, el primer argumento recibe la fórmula en una cadena.
Para definir una fórmula: El símbolo de la disyunción es una barra vertical (|). El símbolo de la conjunción es el símbolo et (&).
import logging as log
class FormulaCNF:
"""
Formula in Conjunctive Normal Form
:param formula: CNF formula (string to parse)
example: '(A|!B)&(!A|B|C)'
"""
def __init__(self,formula=None,assignment=None):
#removes all white spaces
if formula:
formula = ''.join(formula.split())
(self.variables,self.clauses) = self.parse(formula)
self.build_dicts()
if assignment:
self.assignment = assignment.copy()
else:
self.assignment = set()
if log.getLogger().isEnabledFor(log.DEBUG):
log.debug(self.string_internals())
def __getitem__(self,literal):
"""
short notation for the simplify method
:returns: the simplified formula resulting from assuming the literal
is true
"""
return self.simplify(literal)
def empty_sentence(self):
"""
:returns: true if the formula has no clauses
"""
return not self.clauses
def empty_clause(self):
"""
:returns: true if there is an empty clause
"""
return 0 in self.n_to_c
def get_unit_clause_literal(self):
"""
Gets a unit clause if there is one, None otherwise
"""
clause = next(iter(self.n_to_c[1])) \
if 1 in self.n_to_c else {}
return next(iter(clause)) if clause else None
def get_pure_literal(self):
"""
Gest a pure literal
"""
return next(iter(self.P)) if self.P else None
def get_variable_literal(self):
"""
Obtiene una variable de la formula
"""
return Literal(next(iter(self.variables))) if self.variables else None
def simplify(self,literal):
"""
Simplifies the current sentence assming the provided literal
:param literal: the literal to assume as true
"""
log.debug("simplifying literal: "+str(literal))
if not literal:
raise ValueError(
"Invalid literal provided as argument: "+ str(literal))
# stores the assignment
self.assignment.add(literal)
# deletes the variable of the literal
self.variables = self.variables - {literal.variable}
# updates the list of literals
self.L = self.L -{literal}
# updates the list of pure literals
if self.isPureLiteral(literal):
self.P = self.P - {literal}
# deals with the clauses to delete
if literal in self.l_to_c:
for clause in self.l_to_c[literal].copy():
self.remove_clause(clause)
#TODO remover las cláusulas de otras entradas en l_to_c
# update data structures for other literals
for l in clause.copy():
self.decrease_literal_count(l)
self.del_from_dictionary_of_sets(
self.l_to_c,l,clause,True)
#removes the entry from dicionary literal to clauses
#del self.l_to_c[literal]
# deals with the literals to delete negated literal
neg_literal = ~literal
# updates list of literals
self.L = self.L - {neg_literal}
# deletes all negated literals from clauses
if neg_literal in self.l_to_c:
for clause in self.l_to_c[neg_literal]:
self.remove_literal_from_clause(neg_literal,clause)
# delete entry for negated literal from dictionary literal to clauses
if neg_literal in self.l_to_c:
del self.l_to_c[neg_literal]
# updates list of negated literals
if self.isPureLiteral(neg_literal):
self.P = self.P - {neg_literal}
log.debug("simplified formula:")
if log.getLogger().isEnabledFor(log.DEBUG):
log.debug(self.string_internals())
return self
def remove_clause(self,clause):
"""
Removes the clause from data structures
:parama clause: the clause to delete
"""
# deletes clause from inverse map counts
self.clauses.remove(clause)
n = len(clause)
self.del_from_dictionary_of_sets(self.n_to_c,n,clause,True)
def remove_literal_from_clause(self,literal,clause):
# frozen_hash allows for unique clause
# all clauses are updated (they are the same clause)
m = len(clause)
# remove clause from map of counts to clauses
self.del_from_dictionary_of_sets(self.n_to_c,m,clause,True)
# obtain simplified clause
clause -= literal
# update map of count to clauses
self.add_to_dictionary_of_sets(self.n_to_c,m-1,clause)
# update other counts related to the literal
self.decrease_literal_count(literal)
def decrease_literal_count(self,literal):
# decrease number to literal counts
# delete literal from previous count
n = self.l_to_n[literal]
self.del_from_dictionary_of_sets(self.n_to_l,n,literal,True)
# update the literal count if it is not zero
self.add_to_dictionary_of_sets(self.n_to_l,n-1,literal)
# update counts for the literal
if literal in self.l_to_n:
self.l_to_n[literal] -= 1
else:
raise ValueError("Inconsistent counts for literal: "+str(literal))
def isPureLiteral(self,literal):
"""
:returns: true if the literal is pure
"""
return literal in self.P
def parse(self,formula):
"""
Parses the CNF formula string
"""
variables = set()
clauses = set()
clstr = re.findall("[^&]+",formula)
for c in clstr:
literals = re.findall("[^\(\)\|]+",c)
clause = Clause()
for l in literals:
literal = Literal.parse(l)
variables.add(literal.variable)
clause += literal
clauses.add(clause)
return (variables,clauses)
def build_dicts(self):
"""
Builds dictionaries for efficient access
to clauses and literals
l_to_c: dictionary, keys are literals, values are clauses
n_to_c: dictionary, keys are sizes, values are clauses
l_to_n: dictionary, keys are literals, values are counts
n_to_c: dictionary, keys are counts, values are clauses
L: set, all literals in the formula
P: set, all pure literals in the formula
"""
# W gives fast access to clauses by literals
self.l_to_c = {}
# n gives fast access to clauses by size
self.n_to_c = {}
# literal to counts
self.l_to_n = {}
# counts to literals
self.n_to_l = {}
for c in self.clauses:
# build dictionary indexed by size
self.add_to_dictionary_of_sets(self.n_to_c,len(c),c)
for l in c:
#builds dictionary indexed by literal
self.add_to_dictionary_of_sets(self.l_to_c,l,c)
#compute frecuency of literals
self.l_to_n[l] = self.l_to_n[l] + 1 if l in self.l_to_n else 1
#reverse literal counts
self.L = set()
for k,v in self.l_to_n.items():
self.add_to_dictionary_of_sets(self.n_to_l,v,k)
self.L.add(k)
self.P = {v for v in self.L if ~v not in self.L}
def add_to_dictionary_of_sets(self,d,k,v):
"""
Adds a value to a dictionary of sets
:param d: the dictionary
:param k: the key
:param v: the value to add
:param by_len: True if the key is the length of the set to add
"""
singleton = {v}
d[k] = d[k].union(singleton) if k in d else singleton
def del_from_dictionary_of_sets(self,d,k,m,del_key):
"""
Deletes a set member from a dictionary of sets
:param d: the dictionary
:param k: the key
:parma m: the member of the set to delete
:del_key: if this flag is True the entry is removed when set is empty
"""
# removes with set difference
d[k] = d[k] - {m}
if del_key and not d[k]:
del d[k]
def copy(self):
"""
Creates a shallow copy of the CNF formula
"""
log.debug("Creating a copy of the current CNF formula")
formula = FormulaCNF(str(self),self.assignment)
return formula
def string_internals(self):
return ("clauses:"+str(self.clauses)+"\n"+
"variables:"+str(self.variables)+"\n"+
"assignment:"+str(self.assignment)+"\n"+
"l_to_c:"+str(self.l_to_c)+"\n"+
"n_to_c:"+str(self.n_to_c)+"\n"+
"l_to_n:"+str(self.l_to_n)+"\n"+
"n_to_l:"+str(self.n_to_l)+"\n"+
"L:"+str(self.L)+"\n"+
"P:"+str(self.P))
def __str__(self):
return " & ".join(map(str,self.clauses))
def __repr__(self):
return self.__str__()
Ejemplo de fórmula ϕ en FNC.
phi = FormulaCNF("(A|~B)&(~A|C)&(~B|~C)&(C)")
print(phi)
( A | ~B ) & ( ~A | C ) & ( ~B | ~C ) & ( C )
Como se puede apreciar, la clase incorpora varios métodos. A continuación te mostramos como pueden utilizarse aquellos que te servirán para implementar DPLL.
#verificar si la formula es vacía
phi.empty_sentence()
False
#¿tiene la formula una cláusula vacía?
phi.empty_clause()
False
#De existir una clúsula unitaria, obtiene la literal de la cláusula. Regresa None si no existe
l = phi.get_unit_clause_literal()
print(l)
C
#Obtiene una literal pura de existir, None si no existe
l = phi.get_pure_literal()
print(l)
~B
#Obtiene una literal con una variable arbitraria de la formula phi
v = phi.get_variable_literal()
print(v)
A
#Obtiene una copia de la formula
psi = phi.copy()
print(psi)
( A | ~B ) & ( C ) & ( ~B | ~C ) & ( ~A | C )
#Simplifica la formula asumiendo que la literal l es verdadera
psi = phi[l]
print(psi)
( ~A | C ) & ( C )
#Obtiene las asignaciones aplicadas a una fórmula
a = phi.assignment
print(a)
{~B}
relatedInteligencia Artificial