Compare commits

...

5 commits

Author SHA1 Message Date
a02043ba25
Merge 9744f976dd into 84e4d3d1e1 2024-10-30 15:49:32 -04:00
9744f976dd Fixed numbering scheme 2024-10-28 13:48:36 -04:00
17fb542bd0 Progress on handling custom connectives
TODO: Fix matrix numbering
2024-10-28 12:54:13 -04:00
e293e5ac3e Finished parse refactor 2024-10-28 11:14:33 -04:00
4e4e19e117 Clarifying the parsing transition system 2024-10-25 14:52:12 -04:00

View file

@ -4,6 +4,7 @@ Parses the Magic Ugly Data File Format
Assumes the base logic is R with no extra connectives Assumes the base logic is R with no extra connectives
""" """
import argparse import argparse
import re
import sys import sys
from typing import TextIO, List, Optional, Tuple, Set, Dict from typing import TextIO, List, Optional, Tuple, Set, Dict
@ -21,12 +22,36 @@ class SourceFile:
def __init__(self, fileobj: TextIO): def __init__(self, fileobj: TextIO):
self.fileobj = fileobj self.fileobj = fileobj
self.current_line = 0 self.current_line = 0
self.reststr = ""
def __next__(self): def next_line(self):
contents = next(self.fileobj) if self.reststr != "":
reststr = self.reststr
self.reststr = ""
return reststr
contents = next(self.fileobj).strip()
self.current_line += 1 self.current_line += 1
return contents return contents
def __next__(self):
"""
Grabs the next word token from the stream
"""
if self.reststr == "":
self.reststr = next(self.fileobj).strip()
self.current_line += 1
tokens = self.reststr.split(" ")
next_token = tokens[0]
self.reststr = " ".join(tokens[1:])
return next_token
def set_reststr(self, reststr: str):
self.reststr = reststr
class UglyHeader: class UglyHeader:
def __init__(self, negation: bool, necessitation: bool, custom_model_functions: List[Tuple[int, str]]): def __init__(self, negation: bool, necessitation: bool, custom_model_functions: List[Tuple[int, str]]):
self.negation = negation self.negation = negation
@ -37,146 +62,287 @@ class ModelBuilder:
def __init__(self): def __init__(self):
self.size : int = 0 self.size : int = 0
self.carrier_set : Set[ModelValue] = set() self.carrier_set : Set[ModelValue] = set()
self.num_negation: int = 0
self.mnegation: Optional[ModelFunction] = None self.mnegation: Optional[ModelFunction] = None
self.num_order: int = 0
self.mconjunction: Optional[ModelFunction] = None self.mconjunction: Optional[ModelFunction] = None
self.mdisjunction: Optional[ModelFunction] = None self.mdisjunction: Optional[ModelFunction] = None
self.num_designated: int = 0
self.designated_values: Set[ModelValue] = set() self.designated_values: Set[ModelValue] = set()
self.num_implication: int = 0
self.mimplication: Optional[ModelFunction] = None self.mimplication: Optional[ModelFunction] = None
self.num_necessitation: int = 0
self.mnecessitation: Optional[ModelFunction] = None self.mnecessitation: Optional[ModelFunction] = None
self.custom_model_functions: Dict[str, ModelFunction] = {}
class Stage:
def __init__(self, name: str):
self.name = name
self.next: Optional['Stage'] = None
self.previous: Optional['Stage'] = None
self.num = 0
def increment(self):
self.num += 1
def reset(self):
self.num = 0
def __str__(self):
return self.name
class Stages:
def __init__(self):
self.stages: Dict[str, Stage] = {}
self.last_added_stage: Optional[Stage] = None
self.first_stage: Optional[Stage] = None
def add(self, name: str):
stage = Stage(name)
stage.next = stage
if self.last_added_stage is not None:
stage.previous = self.last_added_stage
self.last_added_stage.next = stage
else:
# The previous of the first stage
# is the end
self.first_stage = stage
stage.previous = Stage("end")
self.stages[name] = stage
self.last_added_stage = stage
def next_stage(self, name):
return self.stages[name].next
def previous_stage(self, name):
return self.stages[name].previous
def reset_after(self, name):
stage = self.stages[name]
stage.reset()
if stage.next.name == "process_model":
return
next_stage = stage.next
while next_stage is not None:
next_stage.reset()
if next_stage.next.name == "process_model":
next_stage = None
else:
next_stage = next_stage.next
def get(self, name):
return self.stages[name]
def name(self):
result = ""
stage = self.first_stage
if stage is None:
return ""
result = f"{stage.num}"
if stage.next == "process_model":
return result
stage = stage.next
while stage is not None:
result += f".{stage.num}"
if stage.next.name != "process_model":
stage = stage.next
else:
stage = None
return result
def derive_stages(header: UglyHeader) -> Stages:
stages = Stages()
stages.add("size")
if header.negation:
stages.add("negation")
stages.add("order")
stages.add("designated")
stages.add("implication")
if header.necessitation:
stages.add("necessitation")
for (adicity, symbol) in header.custom_model_functions:
stages.add(f"custom--{adicity}--{symbol}")
stages.add("process_model")
# After processing the model, go to the previous stage
stages.get("process_model").next = stages.get("process_model").previous
return stages
def parse_matrices(infile: SourceFile) -> List[Tuple[Model, Dict]]: def parse_matrices(infile: SourceFile) -> List[Tuple[Model, Dict]]:
solutions = [] # Reset solutions = [] # Reset
header = parse_header(infile) header = parse_header(infile)
stages = derive_stages(header)
first_run = True
current_model_parts = ModelBuilder() current_model_parts = ModelBuilder()
process_sizes(infile, header, current_model_parts, solutions) stage = stages.get("size")
while True:
match stage.name:
case "end":
break
case "process_model":
process_model(stages.name(), current_model_parts, solutions)
stage = stage.next
case "size":
processed = process_sizes(infile, current_model_parts, first_run)
first_run = False
if processed:
stage.num = current_model_parts.size + 1
stage = stage.next
else:
stages.reset_after(stage.name)
stage = stage.previous
case "negation":
processed = process_negations(infile, current_model_parts)
if processed:
stage.increment()
stage = stage.next
else:
stages.reset_after(stage.name)
stage = stage.previous
case "order":
processed = process_orders(infile, current_model_parts)
if processed:
stage.increment()
stage = stage.next
else:
stages.reset_after(stage.name)
stage = stage.previous
case "designated":
processed = process_designateds(infile, current_model_parts)
if processed:
stage.increment()
stage = stage.next
else:
stages.reset_after(stage.name)
stage = stage.previous
case "implication":
processed = process_implications(infile, current_model_parts)
if processed:
stage.increment()
stage = stage.next
else:
stages.reset_after(stage.name)
stage = stage.previous
case "necessitation":
processed = process_necessitations(infile, current_model_parts)
if processed:
stage.increment()
stage = stage.next
else:
stages.reset_after(stage.name)
stage = stage.previous
case _:
custom_stage = re.search(r"custom--(\d+)--(\S+)", stage.name)
if custom_stage is None or len(custom_stage.groups()) != 2:
raise NotImplementedError(f"Unrecognized Stage: {stage.name}")
adicity, symbol = custom_stage.groups()
adicity = int(adicity)
processed = True
if adicity == 0:
# We don't need to do anything here
stage = stage.next
elif adicity == 1:
processed = process_custom_monadic_connective(infile, symbol, current_model_parts)
elif adicity == 2:
processed = process_custom_dyadic_connective(infile, symbol, current_model_parts)
else:
raise NotImplementedError("Unable to process connectives of adicity greater than c2")
if processed:
stage.increment()
stage = stage.next
else:
stages.reset_after(stage.name)
stage = stage.previous
return solutions return solutions
def process_sizes(infile: SourceFile, header: UglyHeader, current_model_parts: ModelBuilder, solutions: List[Tuple[Model, Dict]]): def process_sizes(infile: SourceFile, current_model_parts: ModelBuilder, first_run: bool) -> bool:
"""Stage 1"""
first_run = True
while True:
print("Processing next size")
try: try:
size = parse_size(infile, first_run) size = parse_size(infile, first_run)
first_run = False
except StopIteration: except StopIteration:
# For some reason, when necessitation is enabled this doesn't return False
# have a -1 on the last line
break
if size is None: if size is None:
break return False
carrier_set = carrier_set_from_size(size) carrier_set = carrier_set_from_size(size)
current_model_parts.size = size
current_model_parts.carrier_set = carrier_set current_model_parts.carrier_set = carrier_set
process_negations(infile, header, current_model_parts, solutions) current_model_parts.size = size
def process_negations(infile: SourceFile, header: UglyHeader, current_model_parts: ModelBuilder, solutions: List[Tuple[Model, Dict]]): return True
def process_negations(infile: SourceFile, current_model_parts: ModelBuilder) -> bool:
"""Stage 2 (Optional)""" """Stage 2 (Optional)"""
num_negation = 0
while True:
print("Processing next negation")
mnegation = None
if header.negation:
mnegation = parse_single_negation(infile, current_model_parts.size) mnegation = parse_single_negation(infile, current_model_parts.size)
if mnegation is None: if mnegation is None:
break return False
num_negation += 1
current_model_parts.num_negation = num_negation
current_model_parts.mnegation = mnegation current_model_parts.mnegation = mnegation
return True
process_orders(infile, header, current_model_parts, solutions) def process_orders(infile: SourceFile, current_model_parts: ModelBuilder) -> bool:
if not header.negation:
break
def process_orders(infile: SourceFile, header: UglyHeader, current_model_parts: ModelBuilder, solutions: List[Tuple[Model, Dict]]):
"""Stage 3""" """Stage 3"""
num_order = 0
while True:
print("Processing next order")
result = parse_single_order(infile, current_model_parts.size) result = parse_single_order(infile, current_model_parts.size)
if result is None: if result is None:
break return False
num_order += 1
mconjunction, mdisjunction = result mconjunction, mdisjunction = result
current_model_parts.num_order = num_order
current_model_parts.mconjunction = mconjunction current_model_parts.mconjunction = mconjunction
current_model_parts.mdisjunction = mdisjunction current_model_parts.mdisjunction = mdisjunction
process_designateds(infile, header, current_model_parts, solutions)
def process_designateds(infile: SourceFile, header: UglyHeader, current_model_parts: ModelBuilder, solutions: List[Tuple[Model, Dict]]): return True
def process_designateds(infile: SourceFile, current_model_parts: ModelBuilder) -> bool:
"""Stage 4""" """Stage 4"""
num_designated = 0
while True:
print("Processing next designated")
designated_values = parse_single_designated(infile, current_model_parts.size) designated_values = parse_single_designated(infile, current_model_parts.size)
if designated_values is None: if designated_values is None:
break return False
num_designated += 1
current_model_parts.num_designated = num_designated
current_model_parts.designated_values = designated_values current_model_parts.designated_values = designated_values
process_implications(infile, header, current_model_parts, solutions) return True
def process_implications( def process_implications(infile: SourceFile, current_model_parts: ModelBuilder) -> bool:
infile: SourceFile, header: UglyHeader, current_model_parts: ModelBuilder, solutions: List[Tuple[Model, Dict]]):
"""Stage 5""" """Stage 5"""
if header.necessitation: mimplication = parse_single_implication(infile, current_model_parts.size)
num_implication = 0
while True:
print("Processing next implication")
instr = next(infile).strip()
mimplication, reststr = parse_single_implication(instr, infile.current_line, current_model_parts.size)
if mimplication is None: if mimplication is None:
break return False
num_implication += 1
current_model_parts.num_implication = num_implication
current_model_parts.mimplication = mimplication current_model_parts.mimplication = mimplication
process_necessitations(infile, reststr, header, current_model_parts, solutions) return True
else:
results = parse_implications(infile, current_model_parts.size)
for num_implication, mimplication in enumerate(results, 1):
current_model_parts.num_implication = num_implication
current_model_parts.mimplication = mimplication
process_model(current_model_parts, solutions)
def process_necessitations(infile: SourceFile, instr: str, header: UglyHeader, current_model_parts: ModelBuilder, solutions: List[Tuple[Model, Dict]]): def process_necessitations(infile: SourceFile, current_model_parts: ModelBuilder) -> bool:
# NOTE: For some reason, one necessitation table will be on the same line as the implication table
mnecessitation = parse_single_necessitation_from_str(instr, infile.current_line, current_model_parts.size)
assert mnecessitation is not None, f"Expected Necessitation Table at line {infile.current_line}"
num_necessitation = 1
current_model_parts.num_necessitation = num_necessitation
current_model_parts.mnecessitation = mnecessitation
process_model(current_model_parts, solutions)
while True:
print("Processing next necessitation")
mnecessitation = parse_single_necessitation(infile, current_model_parts.size) mnecessitation = parse_single_necessitation(infile, current_model_parts.size)
if mnecessitation is None: if mnecessitation is None:
break return False
num_necessitation += 1
current_model_parts.num_necessitation = num_necessitation
current_model_parts.mnecessitation = mnecessitation current_model_parts.mnecessitation = mnecessitation
process_model(current_model_parts, solutions) return True
def process_model(mp: ModelBuilder, solutions: List[Tuple[Model, Dict]]): def process_custom_monadic_connective(infile: SourceFile, symbol: str, current_model_parts: ModelBuilder) -> bool:
mfunction = parse_single_monadic_connective(infile, symbol, current_model_parts.size)
if mfunction is None:
return False
current_model_parts.custom_model_functions[symbol] = mfunction
return True
def process_custom_dyadic_connective(infile: SourceFile, symbol: str, current_model_parts: ModelBuilder) -> bool:
mfunction = parse_single_dyadic_connective(infile, symbol, current_model_parts.size)
if mfunction is None:
return False
current_model_parts.custom_model_functions[symbol] = mfunction
return True
def process_model(model_name: str, mp: ModelBuilder, solutions: List[Tuple[Model, Dict]]):
"""Create Model""" """Create Model"""
assert mp.mimplication is not None assert mp.mimplication is not None
assert len(mp.carrier_set) > 0 assert mp.size + 1 == len(mp.carrier_set)
logical_operations = { mp.mimplication } logical_operations = { mp.mimplication }
model_name = f"{mp.size}{'.' + str(mp.num_negation) if mp.num_negation != 0 else ''}.{mp.num_order}.{mp.num_designated}.{mp.num_implication}{'.' + str(mp.num_necessitation) if mp.num_necessitation != 0 else ''}"
model = Model(mp.carrier_set, logical_operations, mp.designated_values, name=model_name) model = Model(mp.carrier_set, logical_operations, mp.designated_values, name=model_name)
interpretation = { interpretation = {
Implication: mp.mimplication Implication: mp.mimplication
@ -194,6 +360,12 @@ def process_model(mp: ModelBuilder, solutions: List[Tuple[Model, Dict]]):
logical_operations.add(mp.mnecessitation) logical_operations.add(mp.mnecessitation)
interpretation[Necessitation] = mp.mnecessitation interpretation[Necessitation] = mp.mnecessitation
for custom_mf in mp.custom_model_functions.values():
if custom_mf is not None:
logical_operations.add(custom_mf)
# NOTE: No need to assign interpretation
# for VSP check
solutions.append((model, interpretation)) solutions.append((model, interpretation))
print(f"Parsed Matrix {model.name}") print(f"Parsed Matrix {model.name}")
@ -201,7 +373,7 @@ def parse_header(infile: SourceFile) -> UglyHeader:
""" """
Parse the header line from the ugly data format. Parse the header line from the ugly data format.
""" """
header_line = next(infile).strip() header_line = infile.next_line()
header_tokens = header_line.split(" ") header_tokens = header_line.split(" ")
assert header_tokens[0] in ["0", "1"] assert header_tokens[0] in ["0", "1"]
assert header_tokens[6] in ["0", "1"] assert header_tokens[6] in ["0", "1"]
@ -216,7 +388,7 @@ def parse_header(infile: SourceFile) -> UglyHeader:
custom_model_functions.append((arity, symbol)) custom_model_functions.append((arity, symbol))
return UglyHeader(negation_defined, necessitation_defined, custom_model_functions) return UglyHeader(negation_defined, necessitation_defined, custom_model_functions)
def carrier_set_from_size(size: int): def carrier_set_from_size(size: int) -> Set[ModelValue]:
""" """
Construct a carrier set of model values Construct a carrier set of model values
based on the desired size. based on the desired size.
@ -230,12 +402,12 @@ def parse_size(infile: SourceFile, first_run: bool) -> Optional[int]:
Parse the line representing the matrix size. Parse the line representing the matrix size.
""" """
size = int(next(infile)) size = int(infile.next_line())
# HACK: When necessitation and custom connectives are enabled # HACK: When necessitation and custom connectives are enabled
# MaGIC may produce -1s at the beginning of the file # MaGIC may produce -1s at the beginning of the file
if first_run: if first_run:
while size == -1: while size == -1:
size = int(next(infile)) size = int(infile.next_line())
if size == -1: if size == -1:
return None return None
@ -246,23 +418,9 @@ def parse_single_negation(infile: SourceFile, size: int) -> Optional[ModelFuncti
""" """
Parse the line representing the negation table. Parse the line representing the negation table.
""" """
line = next(infile).strip() return parse_single_monadic_connective(infile, "¬", size)
if line == '-1':
return None
row = line.split(" ") def mvalue_from_index(i: int) -> ModelValue:
assert len(row) == size + 1, f"Negation table doesn't match size at line {infile.current_line}"
mapping = {}
for i, j in zip(range(size + 1), row):
x = mvalue_from_index(i)
y = parse_mvalue(j)
mapping[(x, )] = y
return ModelFunction(1, mapping, "¬")
def mvalue_from_index(i: int):
""" """
Given an index, return the Given an index, return the
representation of the model value. representation of the model value.
@ -327,7 +485,7 @@ def parse_single_order(infile: SourceFile, size: int) -> Optional[Tuple[ModelFun
""" """
Parse the line representing the ordering table Parse the line representing the ordering table
""" """
line = next(infile).strip() line = infile.next_line()
if line == '-1': if line == '-1':
return None return None
@ -378,7 +536,7 @@ def parse_single_designated(infile: SourceFile, size: int) -> Optional[Set[Model
""" """
Parse the line representing which model values are designated. Parse the line representing which model values are designated.
""" """
line = next(infile).strip() line = infile.next_line()
if line == '-1': if line == '-1':
return None return None
@ -394,78 +552,25 @@ def parse_single_designated(infile: SourceFile, size: int) -> Optional[Set[Model
return designated_values return designated_values
def parse_single_implication(infile: SourceFile, size: int) -> Tuple[ModelFunction]:
def parse_single_implication(instr: str, line: int, size: int) -> Tuple[ModelFunction, str]:
""" """
Take the current string, parse an implication table from it, Take the current string, parse an implication table from it.
and return along with it the remainder of the string
""" """
if instr == "-1": return parse_single_dyadic_connective(infile, "", size)
return None, ""
table = instr.split(" ") def parse_single_necessitation(infile: SourceFile, size: int) -> Optional[ModelFunction]:
assert len(table) >= (size + 1)**2, f"Implication table does not match expected size at line {line}"
mapping = {}
table_i = 0
for i in range(size + 1):
x = mvalue_from_index(i)
for j in range(size + 1):
y = mvalue_from_index(j)
r = parse_mvalue(table[table_i])
table_i += 1
mapping[(x, y)] = r
mimplication = ModelFunction(2, mapping, "")
reststr = " ".join(table[(size + 1)**2:])
return mimplication, reststr
def parse_implications(infile: SourceFile, size: int) -> List[ModelFunction]:
"""
Parse the line representing the list of implication
tables.
"""
line = next(infile).strip()
# Split and remove the last '-1' character
table = line.split(" ")[:-1]
assert len(table) % (size + 1)**2 == 0, f"Implication table does not match expected size at line {infile.current_line}"
table_i = 0
mimplications: List[ModelFunction] = []
for _ in range(len(table) // (size + 1)**2):
mapping = {}
for i in range(size + 1):
x = mvalue_from_index(i)
for j in range(size + 1):
y = mvalue_from_index(j)
r = parse_mvalue(table[table_i])
table_i += 1
mapping[(x, y)] = r
mimplication = ModelFunction(2, mapping, "")
mimplications.append(mimplication)
return mimplications
def parse_single_necessitation_from_str(instr: str, line: int, size: int) -> Optional[ModelFunction]:
""" """
Parse the line representing the necessitation table. Parse the line representing the necessitation table.
""" """
if instr == "-1": return parse_single_monadic_connective(infile, "!", size)
def parse_single_monadic_connective(infile: SourceFile, symbol: str, size: int) -> Optional[ModelFunction]:
line = infile.next_line()
if line == '-1':
return None return None
row = instr.split(" ") row = line.split(" ")
assert len(row) == size + 1, f"Necessitation table doesn't match size at line {line}" assert len(row) == size + 1, f"{symbol} table doesn't match size at line {infile.current_line}"
mapping = {} mapping = {}
for i, j in zip(range(size + 1), row): for i, j in zip(range(size + 1), row):
@ -473,11 +578,37 @@ def parse_single_necessitation_from_str(instr: str, line: int, size: int) -> Opt
y = parse_mvalue(j) y = parse_mvalue(j)
mapping[(x, )] = y mapping[(x, )] = y
return ModelFunction(1, mapping, "!") return ModelFunction(1, mapping, symbol)
def parse_single_necessitation(infile: SourceFile, size: int) -> Optional[ModelFunction]: def parse_single_dyadic_connective(infile: SourceFile, symbol: str, size: int) -> Optional[ModelFunction]:
line = next(infile).strip() try:
return parse_single_necessitation_from_str(line, infile.current_line, size) first_token = next(infile)
if first_token == "-1":
return None
except StopIteration:
return None
table = []
try:
table = [first_token] + [next(infile) for _ in range((size + 1)**2 - 1)]
except StopIteration:
pass
assert len(table) == (size + 1)**2, f"{symbol} table does not match expected size at line {infile.current_line}"
mapping = {}
table_i = 0
for i in range(size + 1):
x = mvalue_from_index(i)
for j in range(size + 1):
y = mvalue_from_index(j)
r = parse_mvalue(table[table_i])
table_i += 1
mapping[(x, y)] = r
return ModelFunction(2, mapping, symbol)
if __name__ == "__main__": if __name__ == "__main__":