mirror of
https://github.com/Brandon-Rozek/matmod.git
synced 2025-12-19 05:10:25 +00:00
Code cleanup and documentation
This commit is contained in:
parent
81a2d17965
commit
6b4d5828c8
3 changed files with 118 additions and 108 deletions
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 logic import (
|
||||
PropositionalVariable, get_propostional_variables, Logic, Term,
|
||||
Operation, Conjunction, Disjunction, Implication
|
||||
get_propostional_variables, Logic,
|
||||
Operation, PropositionalVariable, Term
|
||||
)
|
||||
from typing import Set, Dict, Tuple, Optional
|
||||
from collections import defaultdict
|
||||
from functools import lru_cache
|
||||
from itertools import combinations, chain, product, permutations
|
||||
from copy import deepcopy
|
||||
from itertools import combinations_with_replacement, permutations, product
|
||||
from typing import Dict, List, Set, Tuple
|
||||
|
||||
|
||||
__all__ = ['ModelValue', 'ModelFunction', 'Model']
|
||||
|
||||
|
||||
|
||||
class ModelValue:
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
|
@ -29,10 +28,7 @@ class ModelValue:
|
|||
return self.hashed_value
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, ModelValue) and self.name == other.name
|
||||
def __lt__(self, other):
|
||||
assert isinstance(other, ModelValue)
|
||||
return ModelOrderConstraint(self, other)
|
||||
def __deepcopy__(self, memo):
|
||||
def __deepcopy__(self, _):
|
||||
return ModelValue(self.name)
|
||||
|
||||
|
||||
|
|
@ -41,8 +37,9 @@ class ModelFunction:
|
|||
self.operation_name = operation_name
|
||||
self.arity = arity
|
||||
|
||||
# Correct input to always be a tuple
|
||||
corrected_mapping = dict()
|
||||
# Transform the mapping such that the
|
||||
# key is always a tuple of model values
|
||||
corrected_mapping: Dict[Tuple[ModelValue], ModelValue] = {}
|
||||
for k, v in mapping.items():
|
||||
if isinstance(k, tuple):
|
||||
assert len(k) == arity
|
||||
|
|
@ -66,35 +63,17 @@ class ModelFunction:
|
|||
def __call__(self, *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:
|
||||
def __init__(
|
||||
self,
|
||||
carrier_set: Set[ModelValue],
|
||||
logical_operations: Set[ModelFunction],
|
||||
designated_values: Set[ModelValue],
|
||||
ordering: Optional[Set[ModelOrderConstraint]] = None
|
||||
):
|
||||
assert designated_values <= carrier_set
|
||||
self.carrier_set = carrier_set
|
||||
self.logical_operations = logical_operations
|
||||
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):
|
||||
result = f"""Carrier Set: {set_to_str(self.carrier_set)}
|
||||
|
|
@ -106,12 +85,22 @@ Designated Values: {set_to_str(self.designated_values)}
|
|||
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):
|
||||
return f[t]
|
||||
|
||||
model_function = interpretation[t.operation]
|
||||
model_arguments = []
|
||||
model_arguments: List[ModelValue] = []
|
||||
for logic_arg in t.arguments:
|
||||
model_arg = evaluate_term(logic_arg, f, interpretation)
|
||||
model_arguments.append(model_arg)
|
||||
|
|
@ -121,11 +110,15 @@ def evaluate_term(t: Term, f: Dict[PropositionalVariable, ModelValue], interpret
|
|||
def all_model_valuations(
|
||||
pvars: Tuple[PropositionalVariable],
|
||||
mvalues: Tuple[ModelValue]):
|
||||
"""
|
||||
Given propositional variables and model values,
|
||||
produce every possible mapping between the two.
|
||||
"""
|
||||
|
||||
all_possible_values = product(mvalues, repeat=len(pvars))
|
||||
|
||||
for valuation in all_possible_values:
|
||||
mapping: Dict[PropositionalVariable, ModelValue] = dict()
|
||||
mapping: Dict[PropositionalVariable, ModelValue] = {}
|
||||
assert len(pvars) == len(valuation)
|
||||
for pvar, value in zip(pvars, valuation):
|
||||
mapping[pvar] = value
|
||||
|
|
@ -137,98 +130,92 @@ def all_model_valuations_cached(
|
|||
mvalues: Tuple[ModelValue]):
|
||||
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:
|
||||
"""
|
||||
Determine whether a model satisfies a logic
|
||||
given an interpretation.
|
||||
"""
|
||||
pvars = tuple(get_propostional_variables(tuple(logic.rules)))
|
||||
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:
|
||||
# Make sure that the model satisfies each of the rules
|
||||
for rule in logic.rules:
|
||||
# The check only applies if the premises are designated
|
||||
premise_met = True
|
||||
premise_ts = set()
|
||||
premise_ts: Set[ModelValue] = set()
|
||||
|
||||
for premise in rule.premises:
|
||||
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:
|
||||
premise_met = False
|
||||
break
|
||||
# If designated, keep track of the evaluated term
|
||||
premise_ts.add(premise_t)
|
||||
|
||||
if not premise_met:
|
||||
continue
|
||||
|
||||
# With the premises designated, make sure the consequent is designated
|
||||
consequent_t = evaluate_term(rule.conclusion, mapping, interpretation)
|
||||
|
||||
if consequent_t not in model.designated_values:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
from itertools import combinations_with_replacement
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
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
|
||||
last_new = initial_set
|
||||
changed = True
|
||||
last_new: Set[ModelValue] = initial_set
|
||||
changed: bool = True
|
||||
|
||||
while changed:
|
||||
changed = False
|
||||
new_elements = set()
|
||||
old_closure = closure_set - last_new
|
||||
new_elements: Set[ModelValue] = set()
|
||||
old_closure: Set[ModelValue] = closure_set - last_new
|
||||
|
||||
# arity -> args
|
||||
cached_args = defaultdict(list)
|
||||
|
||||
# Pass elements into each model function
|
||||
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:
|
||||
for args in cached_args[mfun.arity]:
|
||||
# Compute the new elements
|
||||
# given the cached arguments.
|
||||
element = mfun(*args)
|
||||
if element not in closure_set:
|
||||
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
|
||||
|
||||
# Iterate over how many new elements would be within the arguments
|
||||
# NOTE: To not repeat work, there must be at least one new element
|
||||
# At this point, we don't have cached arguments, so we need
|
||||
# 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):
|
||||
new_args = combinations_with_replacement(last_new, r=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 args in permutations(new_arg + old_arg):
|
||||
cached_args[mfun.arity].append(args)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue