Discard subalgebras with bottom/top

This commit is contained in:
Brandon Rozek 2024-11-05 13:01:45 -05:00 committed by GitHub
commit 8628107704
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 95 additions and 6 deletions

View file

@ -219,15 +219,18 @@ def satisfiable(logic: Logic, model: Model, interpretation: Dict[Operation, Mode
def model_closure(initial_set: Set[ModelValue], mfunctions: Set[ModelFunction]): def model_closure(initial_set: Set[ModelValue], mfunctions: Set[ModelFunction], top: Optional[ModelValue], bottom: Optional[ModelValue]) -> Set[ModelValue]:
""" """
Given an initial set of model values and a set of model functions, Given an initial set of model values and a set of model functions,
compute the complete set of model values that are closed compute the complete set of model values that are closed
under the operations. under the operations.
If top or bottom is encountered, then we end the saturation procedure early.
""" """
closure_set: Set[ModelValue] = initial_set closure_set: Set[ModelValue] = initial_set
last_new: Set[ModelValue] = initial_set last_new: Set[ModelValue] = initial_set
changed: bool = True changed: bool = True
topbottom_found = False
while changed: while changed:
changed = False changed = False
@ -251,6 +254,18 @@ def model_closure(initial_set: Set[ModelValue], mfunctions: Set[ModelFunction]):
if element not in closure_set: if element not in closure_set:
new_elements.add(element) new_elements.add(element)
# Optimization: Break out of computation
# early when top or bottom element is foun
if top is not None and element == top:
topbottom_found = True
if bottom is not None and element == bottom:
topbottom_found = True
if topbottom_found:
break
if topbottom_found:
break
# We don't need to compute the arguments # We don't need to compute the arguments
# thanks to the cache, so move onto the # thanks to the cache, so move onto the
# next function. # next function.
@ -274,8 +289,27 @@ def model_closure(initial_set: Set[ModelValue], mfunctions: Set[ModelFunction]):
if element not in closure_set: if element not in closure_set:
new_elements.add(element) new_elements.add(element)
# Optimization: Break out of computation
# early when top or bottom element is foun
if top is not None and element == top:
topbottom_found = True
if bottom is not None and element == bottom:
topbottom_found = True
if topbottom_found:
break
if topbottom_found:
break
if topbottom_found:
break
closure_set.update(new_elements) closure_set.update(new_elements)
changed = len(new_elements) > 0 changed = len(new_elements) > 0
last_new = new_elements last_new = new_elements
if topbottom_found:
break
return closure_set return closure_set

65
vsp.py
View file

@ -8,7 +8,7 @@ from common import set_to_str
from model import ( from model import (
Model, model_closure, ModelFunction, ModelValue Model, model_closure, ModelFunction, ModelValue
) )
from logic import Implication, Operation from logic import Conjunction, Disjunction, Implication, Operation
def preseed( def preseed(
initial_set: Set[ModelValue], initial_set: Set[ModelValue],
@ -38,6 +38,40 @@ def preseed(
same_set = candidate_preseed[1] == 0 same_set = candidate_preseed[1] == 0
return candidate_preseed[0], same_set return candidate_preseed[0], same_set
def find_top(algebra: Set[ModelValue], mconjunction: Optional[ModelFunction], mdisjunction: Optional[ModelFunction]) -> Optional[ModelValue]:
"""
Find the top of the order lattice.
T || a = T, T && a = a for all a in the carrier set
"""
if mconjunction is None or mdisjunction is None:
return None
for x in algebra:
for y in algebra:
if mdisjunction(x, y) == x and mconjunction(x, y) == y:
return x
print("[Warning] Failed to find the top of the lattice")
return None
def find_bottom(algebra: Set[ModelValue], mconjunction: Optional[ModelFunction], mdisjunction: Optional[ModelFunction]) -> Optional[ModelValue]:
"""
Find the bottom of the order lattice
F || a = a, F && a = F for all a in the carrier set
"""
if mconjunction is None or mdisjunction is None:
return None
for x in algebra:
for y in algebra:
if mdisjunction(x, y) == y and mconjunction(x, y) == x:
return x
print("[Warning] Failed to find the bottom of the lattice")
return None
class VSP_Result: class VSP_Result:
def __init__( def __init__(
self, has_vsp: bool, model_name: Optional[str] = None, self, has_vsp: bool, model_name: Optional[str] = None,
@ -62,6 +96,10 @@ def has_vsp(model: Model, interpretation: Dict[Operation, ModelFunction]) -> VSP
sharing property. sharing property.
""" """
impfunction = interpretation[Implication] impfunction = interpretation[Implication]
mconjunction = interpretation.get(Conjunction)
mdisjunction = interpretation.get(Disjunction)
top = find_top(model.carrier_set, mconjunction, mdisjunction)
bottom = find_bottom(model.carrier_set, mconjunction, mdisjunction)
# NOTE: No models with only one designated # NOTE: No models with only one designated
# value satisfies VSP # value satisfies VSP
@ -101,28 +139,45 @@ def has_vsp(model: Model, interpretation: Dict[Operation, ModelFunction]) -> VSP
# NOTE: Optimziation before model_closure # NOTE: Optimziation before model_closure
# If the carrier set intersects, then move on to the next # If the two subalgebras intersect, move
# subalgebra # onto the next pair
if len(xs & ys) > 0: if len(xs & ys) > 0:
continue continue
# NOTE: Optimization
# if either subalgebra contains top or bottom, move
# onto the next pair
if top is not None and (top in xs or top in ys):
continue
if bottom is not None and (bottom in xs or bottom in ys):
continue
# Compute the closure of all operations # Compute the closure of all operations
# with just the xs # with just the xs
carrier_set_left: Set[ModelValue] = model_closure(xs, model.logical_operations) carrier_set_left: Set[ModelValue] = model_closure(xs, model.logical_operations, top, bottom)
# Save to cache # Save to cache
if cached_xs[0] is not None and not cached_ys[1]: if cached_xs[0] is not None and not cached_ys[1]:
closure_cache.append((orig_xs, carrier_set_left)) closure_cache.append((orig_xs, carrier_set_left))
if top is not None and top in carrier_set_left:
continue
if bottom is not None and bottom in carrier_set_left:
continue
# Compute the closure of all operations # Compute the closure of all operations
# with just the ys # with just the ys
carrier_set_right: Set[ModelValue] = model_closure(ys, model.logical_operations) carrier_set_right: Set[ModelValue] = model_closure(ys, model.logical_operations, top, bottom)
# Save to cache # Save to cache
if cached_ys[0] is not None and not cached_ys[1]: if cached_ys[0] is not None and not cached_ys[1]:
closure_cache.append((orig_ys, carrier_set_right)) closure_cache.append((orig_ys, carrier_set_right))
if top is not None and top in carrier_set_right:
continue
if bottom is not None and bottom in carrier_set_right:
continue
# If the carrier set intersects, then move on to the next # If the carrier set intersects, then move on to the next
# subalgebra # subalgebra