mirror of
https://github.com/Brandon-Rozek/matmod.git
synced 2024-11-22 22:06:29 -05:00
Code cleanup and documentation
This commit is contained in:
parent
81a2d17965
commit
6b4d5828c8
3 changed files with 118 additions and 108 deletions
21
R.py
21
R.py
|
@ -2,17 +2,17 @@
|
||||||
Modeling the logic R
|
Modeling the logic R
|
||||||
"""
|
"""
|
||||||
from logic import (
|
from logic import (
|
||||||
|
Conjunction,
|
||||||
|
Disjunction,
|
||||||
|
Implication,
|
||||||
|
Logic,
|
||||||
|
Negation,
|
||||||
PropositionalVariable,
|
PropositionalVariable,
|
||||||
Rule,
|
Rule,
|
||||||
Logic,
|
|
||||||
Implication,
|
|
||||||
Conjunction,
|
|
||||||
Negation,
|
|
||||||
Disjunction,
|
|
||||||
Rule,
|
|
||||||
)
|
)
|
||||||
from model import Model, ModelFunction, ModelValue, has_vsp, satisfiable
|
from model import Model, ModelFunction, ModelValue, satisfiable
|
||||||
from generate_model import generate_model
|
from generate_model import generate_model
|
||||||
|
from vsp import has_vsp
|
||||||
|
|
||||||
|
|
||||||
# ===================================================
|
# ===================================================
|
||||||
|
@ -63,7 +63,7 @@ R_logic = Logic(operations, logic_rules)
|
||||||
|
|
||||||
# ===============================
|
# ===============================
|
||||||
|
|
||||||
# Example Model of R
|
# Example 2-element Model of R
|
||||||
|
|
||||||
|
|
||||||
a0 = ModelValue("a0")
|
a0 = ModelValue("a0")
|
||||||
|
@ -123,13 +123,14 @@ solutions = generate_model(R_logic, model_size, print_model=True)
|
||||||
print(f"There are {len(solutions)} satisfiable models of element length {model_size}")
|
print(f"There are {len(solutions)} satisfiable models of element length {model_size}")
|
||||||
|
|
||||||
for model, interpretation in solutions:
|
for model, interpretation in solutions:
|
||||||
print("Has VSP?", has_vsp(model, interpretation))
|
print(has_vsp(model, interpretation))
|
||||||
|
|
||||||
print("-" * 5)
|
print("-" * 5)
|
||||||
|
|
||||||
######
|
######
|
||||||
|
|
||||||
# Smallest model for R that has the variable sharing property
|
# Smallest model for R that has the variable sharing property
|
||||||
|
# This has 6 elements
|
||||||
|
|
||||||
a0 = ModelValue("a0")
|
a0 = ModelValue("a0")
|
||||||
a1 = ModelValue("a1")
|
a1 = ModelValue("a1")
|
||||||
|
@ -299,4 +300,4 @@ print(R_model_6)
|
||||||
|
|
||||||
print("Satisfiable", satisfiable(R_logic, R_model_6, interpretation))
|
print("Satisfiable", satisfiable(R_logic, R_model_6, interpretation))
|
||||||
|
|
||||||
print("Has VSP?", has_vsp(R_model_6, interpretation))
|
print(has_vsp(R_model_6, interpretation))
|
||||||
|
|
149
model.py
149
model.py
|
@ -1,21 +1,20 @@
|
||||||
"""
|
"""
|
||||||
Defining what it means to be a model
|
Matrix model semantics and satisfiability of
|
||||||
|
a given logic.
|
||||||
"""
|
"""
|
||||||
from common import set_to_str
|
from common import set_to_str
|
||||||
from logic import (
|
from logic import (
|
||||||
PropositionalVariable, get_propostional_variables, Logic, Term,
|
get_propostional_variables, Logic,
|
||||||
Operation, Conjunction, Disjunction, Implication
|
Operation, PropositionalVariable, Term
|
||||||
)
|
)
|
||||||
from typing import Set, Dict, Tuple, Optional
|
from collections import defaultdict
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from itertools import combinations, chain, product, permutations
|
from itertools import combinations_with_replacement, permutations, product
|
||||||
from copy import deepcopy
|
from typing import Dict, List, Set, Tuple
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['ModelValue', 'ModelFunction', 'Model']
|
__all__ = ['ModelValue', 'ModelFunction', 'Model']
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ModelValue:
|
class ModelValue:
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
self.name = name
|
self.name = name
|
||||||
|
@ -29,10 +28,7 @@ class ModelValue:
|
||||||
return self.hashed_value
|
return self.hashed_value
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
return isinstance(other, ModelValue) and self.name == other.name
|
return isinstance(other, ModelValue) and self.name == other.name
|
||||||
def __lt__(self, other):
|
def __deepcopy__(self, _):
|
||||||
assert isinstance(other, ModelValue)
|
|
||||||
return ModelOrderConstraint(self, other)
|
|
||||||
def __deepcopy__(self, memo):
|
|
||||||
return ModelValue(self.name)
|
return ModelValue(self.name)
|
||||||
|
|
||||||
|
|
||||||
|
@ -41,8 +37,9 @@ class ModelFunction:
|
||||||
self.operation_name = operation_name
|
self.operation_name = operation_name
|
||||||
self.arity = arity
|
self.arity = arity
|
||||||
|
|
||||||
# Correct input to always be a tuple
|
# Transform the mapping such that the
|
||||||
corrected_mapping = dict()
|
# key is always a tuple of model values
|
||||||
|
corrected_mapping: Dict[Tuple[ModelValue], ModelValue] = {}
|
||||||
for k, v in mapping.items():
|
for k, v in mapping.items():
|
||||||
if isinstance(k, tuple):
|
if isinstance(k, tuple):
|
||||||
assert len(k) == arity
|
assert len(k) == arity
|
||||||
|
@ -66,35 +63,17 @@ class ModelFunction:
|
||||||
def __call__(self, *args):
|
def __call__(self, *args):
|
||||||
return self.mapping[args]
|
return self.mapping[args]
|
||||||
|
|
||||||
# def __eq__(self, other):
|
|
||||||
# return isinstance(other, ModelFunction) and self.name == other.name and self.arity == other.arity
|
|
||||||
|
|
||||||
class ModelOrderConstraint:
|
|
||||||
# a < b
|
|
||||||
def __init__(self, a: ModelValue, b: ModelValue):
|
|
||||||
self.a = a
|
|
||||||
self.b = b
|
|
||||||
def __hash__(self):
|
|
||||||
return hash(self.a) * hash(self.b)
|
|
||||||
def __eq__(self, other):
|
|
||||||
return isinstance(other, ModelOrderConstraint) and \
|
|
||||||
self.a == other.a and self.b == other.b
|
|
||||||
|
|
||||||
class Model:
|
class Model:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
carrier_set: Set[ModelValue],
|
carrier_set: Set[ModelValue],
|
||||||
logical_operations: Set[ModelFunction],
|
logical_operations: Set[ModelFunction],
|
||||||
designated_values: Set[ModelValue],
|
designated_values: Set[ModelValue],
|
||||||
ordering: Optional[Set[ModelOrderConstraint]] = None
|
|
||||||
):
|
):
|
||||||
assert designated_values <= carrier_set
|
assert designated_values <= carrier_set
|
||||||
self.carrier_set = carrier_set
|
self.carrier_set = carrier_set
|
||||||
self.logical_operations = logical_operations
|
self.logical_operations = logical_operations
|
||||||
self.designated_values = designated_values
|
self.designated_values = designated_values
|
||||||
self.ordering = ordering if ordering is not None else set()
|
|
||||||
# TODO: Make sure ordering is "valid"
|
|
||||||
# That is: transitive, etc.
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
result = f"""Carrier Set: {set_to_str(self.carrier_set)}
|
result = f"""Carrier Set: {set_to_str(self.carrier_set)}
|
||||||
|
@ -106,12 +85,22 @@ Designated Values: {set_to_str(self.designated_values)}
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def evaluate_term(t: Term, f: Dict[PropositionalVariable, ModelValue], interpretation: Dict[Operation, ModelFunction]) -> ModelValue:
|
def evaluate_term(
|
||||||
|
t: Term, f: Dict[PropositionalVariable, ModelValue],
|
||||||
|
interpretation: Dict[Operation, ModelFunction]) -> ModelValue:
|
||||||
|
"""
|
||||||
|
Given a term in a logic, mapping
|
||||||
|
between terms and model values,
|
||||||
|
as well as an interpretation
|
||||||
|
of operations to model functions,
|
||||||
|
return the evaluated model value.
|
||||||
|
"""
|
||||||
|
|
||||||
if isinstance(t, PropositionalVariable):
|
if isinstance(t, PropositionalVariable):
|
||||||
return f[t]
|
return f[t]
|
||||||
|
|
||||||
model_function = interpretation[t.operation]
|
model_function = interpretation[t.operation]
|
||||||
model_arguments = []
|
model_arguments: List[ModelValue] = []
|
||||||
for logic_arg in t.arguments:
|
for logic_arg in t.arguments:
|
||||||
model_arg = evaluate_term(logic_arg, f, interpretation)
|
model_arg = evaluate_term(logic_arg, f, interpretation)
|
||||||
model_arguments.append(model_arg)
|
model_arguments.append(model_arg)
|
||||||
|
@ -121,11 +110,15 @@ def evaluate_term(t: Term, f: Dict[PropositionalVariable, ModelValue], interpret
|
||||||
def all_model_valuations(
|
def all_model_valuations(
|
||||||
pvars: Tuple[PropositionalVariable],
|
pvars: Tuple[PropositionalVariable],
|
||||||
mvalues: Tuple[ModelValue]):
|
mvalues: Tuple[ModelValue]):
|
||||||
|
"""
|
||||||
|
Given propositional variables and model values,
|
||||||
|
produce every possible mapping between the two.
|
||||||
|
"""
|
||||||
|
|
||||||
all_possible_values = product(mvalues, repeat=len(pvars))
|
all_possible_values = product(mvalues, repeat=len(pvars))
|
||||||
|
|
||||||
for valuation in all_possible_values:
|
for valuation in all_possible_values:
|
||||||
mapping: Dict[PropositionalVariable, ModelValue] = dict()
|
mapping: Dict[PropositionalVariable, ModelValue] = {}
|
||||||
assert len(pvars) == len(valuation)
|
assert len(pvars) == len(valuation)
|
||||||
for pvar, value in zip(pvars, valuation):
|
for pvar, value in zip(pvars, valuation):
|
||||||
mapping[pvar] = value
|
mapping[pvar] = value
|
||||||
|
@ -137,98 +130,92 @@ def all_model_valuations_cached(
|
||||||
mvalues: Tuple[ModelValue]):
|
mvalues: Tuple[ModelValue]):
|
||||||
return list(all_model_valuations(pvars, mvalues))
|
return list(all_model_valuations(pvars, mvalues))
|
||||||
|
|
||||||
def rule_ordering_satisfied(model: Model, interpretation: Dict[Operation, ModelFunction]) -> bool:
|
|
||||||
"""
|
|
||||||
Currently testing whether this function helps with runtime...
|
|
||||||
"""
|
|
||||||
if Conjunction in interpretation:
|
|
||||||
possible_inputs = ((a, b) for (a, b) in product(model.carrier_set, model.carrier_set))
|
|
||||||
for a, b in possible_inputs:
|
|
||||||
output = interpretation[Conjunction](a, b)
|
|
||||||
if a < b in model.ordering and output != a:
|
|
||||||
print("RETURNING FALSE")
|
|
||||||
return False
|
|
||||||
if b < a in model.ordering and output != b:
|
|
||||||
print("RETURNING FALSE")
|
|
||||||
return False
|
|
||||||
|
|
||||||
if Disjunction in interpretation:
|
|
||||||
possible_inputs = ((a, b) for (a, b) in product(model.carrier_set, model.carrier_set))
|
|
||||||
for a, b in possible_inputs:
|
|
||||||
output = interpretation[Disjunction](a, b)
|
|
||||||
if a < b in model.ordering and output != b:
|
|
||||||
print("RETURNING FALSE")
|
|
||||||
return False
|
|
||||||
if b < a in model.ordering and output != a:
|
|
||||||
print("RETURNING FALSE")
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def satisfiable(logic: Logic, model: Model, interpretation: Dict[Operation, ModelFunction]) -> bool:
|
def satisfiable(logic: Logic, model: Model, interpretation: Dict[Operation, ModelFunction]) -> bool:
|
||||||
|
"""
|
||||||
|
Determine whether a model satisfies a logic
|
||||||
|
given an interpretation.
|
||||||
|
"""
|
||||||
pvars = tuple(get_propostional_variables(tuple(logic.rules)))
|
pvars = tuple(get_propostional_variables(tuple(logic.rules)))
|
||||||
mappings = all_model_valuations_cached(pvars, tuple(model.carrier_set))
|
mappings = all_model_valuations_cached(pvars, tuple(model.carrier_set))
|
||||||
|
|
||||||
# NOTE: Does not look like rule ordering is helping for finding
|
|
||||||
# models of R...
|
|
||||||
if not rule_ordering_satisfied(model, interpretation):
|
|
||||||
return False
|
|
||||||
|
|
||||||
for mapping in mappings:
|
for mapping in mappings:
|
||||||
|
# Make sure that the model satisfies each of the rules
|
||||||
for rule in logic.rules:
|
for rule in logic.rules:
|
||||||
|
# The check only applies if the premises are designated
|
||||||
premise_met = True
|
premise_met = True
|
||||||
premise_ts = set()
|
premise_ts: Set[ModelValue] = set()
|
||||||
|
|
||||||
for premise in rule.premises:
|
for premise in rule.premises:
|
||||||
premise_t = evaluate_term(premise, mapping, interpretation)
|
premise_t = evaluate_term(premise, mapping, interpretation)
|
||||||
|
# As soon as one premise is not designated,
|
||||||
|
# move to the next rule.
|
||||||
if premise_t not in model.designated_values:
|
if premise_t not in model.designated_values:
|
||||||
premise_met = False
|
premise_met = False
|
||||||
break
|
break
|
||||||
|
# If designated, keep track of the evaluated term
|
||||||
premise_ts.add(premise_t)
|
premise_ts.add(premise_t)
|
||||||
|
|
||||||
if not premise_met:
|
if not premise_met:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# With the premises designated, make sure the consequent is designated
|
||||||
consequent_t = evaluate_term(rule.conclusion, mapping, interpretation)
|
consequent_t = evaluate_term(rule.conclusion, mapping, interpretation)
|
||||||
|
|
||||||
if consequent_t not in model.designated_values:
|
if consequent_t not in model.designated_values:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
from itertools import combinations_with_replacement
|
|
||||||
from collections import defaultdict
|
|
||||||
|
|
||||||
def model_closure(initial_set: Set[ModelValue], mfunctions: Set[ModelFunction]):
|
def model_closure(initial_set: Set[ModelValue], mfunctions: Set[ModelFunction]):
|
||||||
|
"""
|
||||||
|
Given an initial set of model values and a set of model functions,
|
||||||
|
compute the complete set of model values that are closed
|
||||||
|
under the operations.
|
||||||
|
"""
|
||||||
closure_set: Set[ModelValue] = initial_set
|
closure_set: Set[ModelValue] = initial_set
|
||||||
last_new = initial_set
|
last_new: Set[ModelValue] = initial_set
|
||||||
changed = True
|
changed: bool = True
|
||||||
|
|
||||||
while changed:
|
while changed:
|
||||||
changed = False
|
changed = False
|
||||||
new_elements = set()
|
new_elements: Set[ModelValue] = set()
|
||||||
old_closure = closure_set - last_new
|
old_closure: Set[ModelValue] = closure_set - last_new
|
||||||
|
|
||||||
# arity -> args
|
# arity -> args
|
||||||
cached_args = defaultdict(list)
|
cached_args = defaultdict(list)
|
||||||
|
|
||||||
|
# Pass elements into each model function
|
||||||
for mfun in mfunctions:
|
for mfun in mfunctions:
|
||||||
|
|
||||||
# Use cached args if this arity was looked at before
|
# If a previous function shared the same arity,
|
||||||
|
# we'll use the same set of computed arguments
|
||||||
|
# to pass into the model functions.
|
||||||
if mfun.arity in cached_args:
|
if mfun.arity in cached_args:
|
||||||
for args in cached_args[mfun.arity]:
|
for args in cached_args[mfun.arity]:
|
||||||
|
# Compute the new elements
|
||||||
|
# given the cached arguments.
|
||||||
element = mfun(*args)
|
element = mfun(*args)
|
||||||
if element not in closure_set:
|
if element not in closure_set:
|
||||||
new_elements.add(element)
|
new_elements.add(element)
|
||||||
# Move onto next function
|
|
||||||
|
# We don't need to compute the arguments
|
||||||
|
# thanks to the cache, so move onto the
|
||||||
|
# next function.
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Iterate over how many new elements would be within the arguments
|
# At this point, we don't have cached arguments, so we need
|
||||||
# NOTE: To not repeat work, there must be at least one new element
|
# to compute this set.
|
||||||
|
|
||||||
|
# Each argument must have at least one new element to not repeat
|
||||||
|
# work. We'll range over the number of new model values within our
|
||||||
|
# argument.
|
||||||
for num_new in range(1, mfun.arity + 1):
|
for num_new in range(1, mfun.arity + 1):
|
||||||
new_args = combinations_with_replacement(last_new, r=num_new)
|
new_args = combinations_with_replacement(last_new, r=num_new)
|
||||||
old_args = combinations_with_replacement(old_closure, r=mfun.arity - num_new)
|
old_args = combinations_with_replacement(old_closure, r=mfun.arity - num_new)
|
||||||
|
# Determine every possible ordering of the concatenated
|
||||||
|
# new and old model values.
|
||||||
for new_arg, old_arg in product(new_args, old_args):
|
for new_arg, old_arg in product(new_args, old_args):
|
||||||
for args in permutations(new_arg + old_arg):
|
for args in permutations(new_arg + old_arg):
|
||||||
cached_args[mfun.arity].append(args)
|
cached_args[mfun.arity].append(args)
|
||||||
|
|
52
vsp.py
52
vsp.py
|
@ -3,40 +3,62 @@ Check to see if the model has the variable
|
||||||
sharing property.
|
sharing property.
|
||||||
"""
|
"""
|
||||||
from itertools import chain, combinations, product
|
from itertools import chain, combinations, product
|
||||||
from typing import Dict, Set, Tuple, List
|
from typing import Dict, List, Optional, Set, Tuple
|
||||||
from model import (
|
from model import (
|
||||||
Model, ModelFunction, ModelValue, model_closure
|
Model, model_closure, ModelFunction, ModelValue
|
||||||
)
|
)
|
||||||
from logic import Implication, Operation
|
from logic import Implication, Operation
|
||||||
|
|
||||||
def preseed(initial_set: Set[ModelValue], cache:List[Tuple[Set[ModelValue], Set[ModelValue]]]):
|
def preseed(
|
||||||
|
initial_set: Set[ModelValue],
|
||||||
|
cache:List[Tuple[Set[ModelValue], Set[ModelValue]]]):
|
||||||
"""
|
"""
|
||||||
Cache contains caches of model closure calls:
|
Given a cache of previous model_closure calls,
|
||||||
Ex:
|
use this to compute an initial model closure
|
||||||
{1, 2, 3} -> {....}
|
set based on the initial set.
|
||||||
|
|
||||||
|
Basic Idea:
|
||||||
|
Let {1, 2, 3} -> X be in the cache.
|
||||||
If {1,2,3} is a subset of initial set,
|
If {1,2,3} is a subset of initial set,
|
||||||
then {....} is the subset of the output of model_closure.
|
then X is the subset of the output of model_closure.
|
||||||
|
|
||||||
We'll use the output to speed up the saturation procedure
|
This is used to speed up subsequent calls to model_closure
|
||||||
"""
|
"""
|
||||||
candidate_preseed: Tuple[Set[ModelValue], int] = (None, None)
|
candidate_preseed: Tuple[Set[ModelValue], int] = (None, None)
|
||||||
|
|
||||||
for i, o in cache:
|
for i, o in cache:
|
||||||
if i < initial_set:
|
if i < initial_set:
|
||||||
cost = len(initial_set - i)
|
cost = len(initial_set - i)
|
||||||
|
# If i is a subset with less missing elements than
|
||||||
|
# the previous candidate, then it's the new candidate.
|
||||||
if candidate_preseed[1] is None or cost < candidate_preseed[1]:
|
if candidate_preseed[1] is None or cost < candidate_preseed[1]:
|
||||||
candidate_preseed = o, cost
|
candidate_preseed = o, cost
|
||||||
|
|
||||||
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 has_vsp(model: Model, interpretation: Dict[Operation, ModelFunction]) -> bool:
|
class VSP_Result:
|
||||||
"""
|
def __init__(
|
||||||
Tells you whether a model has the
|
self, has_vsp: bool, subalgebra1: Optional[Set[ModelValue]] = None,
|
||||||
variable sharing property.
|
subalgebra2: Optional[Set[ModelValue]] = None, x: Optional[ModelValue] = None,
|
||||||
"""
|
y: Optional[ModelValue] = None):
|
||||||
|
self.has_vsp = has_vsp
|
||||||
|
self.subalgebra1 = subalgebra1
|
||||||
|
self.subalgebra2 = subalgebra2
|
||||||
|
self.x = x
|
||||||
|
self.y = y
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if self.has_vsp:
|
||||||
|
return "Model has the variable sharing property."
|
||||||
|
else:
|
||||||
|
return "Model does not have the variable sharing property."
|
||||||
|
|
||||||
|
def has_vsp(model: Model, interpretation: Dict[Operation, ModelFunction]) -> VSP_Result:
|
||||||
|
"""
|
||||||
|
Checks whether a model has the variable
|
||||||
|
sharing property.
|
||||||
|
"""
|
||||||
impfunction = interpretation[Implication]
|
impfunction = interpretation[Implication]
|
||||||
|
|
||||||
# Compute I the set of tuples (x, y) where
|
# Compute I the set of tuples (x, y) where
|
||||||
|
@ -109,6 +131,6 @@ def has_vsp(model: Model, interpretation: Dict[Operation, ModelFunction]) -> boo
|
||||||
break
|
break
|
||||||
|
|
||||||
if falsified:
|
if falsified:
|
||||||
return True
|
return VSP_Result(True, carrier_set_left, carrier_set_right, x2, y2)
|
||||||
|
|
||||||
return False
|
return VSP_Result(False)
|
||||||
|
|
Loading…
Reference in a new issue