matmod/vsp.py
Brandon Rozek 2fa8aa9c15
Updates
- Parses multiple implication tables from magic
- Speed improvements to model closure
- Make use of prior model_closure computations
2024-05-12 13:03:28 -04:00

114 lines
3.5 KiB
Python

"""
Check to see if the model has the variable
sharing property.
"""
from itertools import chain, combinations, product
from typing import Dict, Set, Tuple, List
from model import (
Model, ModelFunction, ModelValue, model_closure
)
from logic import Implication, Operation
def preseed(initial_set: Set[ModelValue], cache:List[Tuple[Set[ModelValue], Set[ModelValue]]]):
"""
Cache contains caches of model closure calls:
Ex:
{1, 2, 3} -> {....}
If {1,2,3} is a subset of initial set,
then {....} is the subset of the output of model_closure.
We'll use the output to speed up the saturation procedure
"""
candidate_preseed: Tuple[Set[ModelValue], int] = (None, None)
for i, o in cache:
if i < initial_set:
cost = len(initial_set - i)
if candidate_preseed[1] is None or cost < candidate_preseed[1]:
candidate_preseed = o, cost
same_set = candidate_preseed[1] == 0
return candidate_preseed[0], same_set
def has_vsp(model: Model, interpretation: Dict[Operation, ModelFunction]) -> bool:
"""
Tells you whether a model has the
variable sharing property.
"""
impfunction = interpretation[Implication]
# Compute I the set of tuples (x, y) where
# x -> y does not take a designiated value
I: Set[Tuple[ModelValue, ModelValue]] = set()
for (x, y) in product(model.carrier_set, model.carrier_set):
if impfunction(x, y) not in model.designated_values:
I.add((x, y))
# Construct the powerset of I without the empty set
s = list(I)
I_power = chain.from_iterable(combinations(s, r) for r in range(1, len(s) + 1))
# ((x1, y1)), ((x1, y1), (x2, y2)), ...
# Closure cache
closure_cache: List[Tuple[Set[ModelValue], Set[ModelValue]]] = []
# Find the subalgebras which falsify implication
for xys in I_power:
xs = {xy[0] for xy in xys}
orig_xs = xs
cached_xs = preseed(xs, closure_cache)
if cached_xs[0] is not None:
xs |= cached_xs[0]
ys = {xy[1] for xy in xys}
orig_ys = ys
cached_ys = preseed(ys, closure_cache)
if cached_ys[0] is not None:
ys |= cached_ys[0]
# NOTE: Optimziation before model_closure
# If the carrier set intersects, then move on to the next
# subalgebra
if len(xs & ys) > 0:
continue
# Compute the closure of all operations
# with just the xs
carrier_set_left: Set[ModelValue] = model_closure(xs, model.logical_operations)
# Save to cache
if cached_xs[0] is not None and not cached_ys[1]:
closure_cache.append((orig_xs, carrier_set_left))
# Compute the closure of all operations
# with just the ys
carrier_set_right: Set[ModelValue] = model_closure(ys, model.logical_operations)
# Save to cache
if cached_ys[0] is not None and not cached_ys[1]:
closure_cache.append((orig_ys, carrier_set_right))
# If the carrier set intersects, then move on to the next
# subalgebra
if len(carrier_set_left & carrier_set_right) > 0:
continue
# See if for all pairs in the subalgebras, that
# implication is falsified
falsified = True
for (x2, y2) in product(carrier_set_left, carrier_set_right):
if impfunction(x2, y2) in model.designated_values:
falsified = False
break
if falsified:
return True
return False