2024-05-09 17:08:15 -04:00
|
|
|
|
"""
|
|
|
|
|
|
Parses the Magic Ugly Data File Format
|
|
|
|
|
|
|
|
|
|
|
|
Assumes the base logic is R with no extra connectives
|
|
|
|
|
|
"""
|
2024-10-28 12:54:13 -04:00
|
|
|
|
import re
|
2025-03-18 18:08:28 -04:00
|
|
|
|
from typing import TextIO, List, Iterator, Optional, Tuple, Set, Dict
|
2025-05-02 12:08:15 -04:00
|
|
|
|
from itertools import product
|
2024-05-09 17:08:15 -04:00
|
|
|
|
|
2025-01-31 17:16:25 -05:00
|
|
|
|
from model import Model, ModelValue, ModelFunction, OrderTable
|
2024-05-09 17:08:15 -04:00
|
|
|
|
from logic import (
|
|
|
|
|
|
Implication,
|
|
|
|
|
|
Conjunction,
|
|
|
|
|
|
Negation,
|
2024-10-04 15:51:05 -04:00
|
|
|
|
Necessitation,
|
2024-11-05 12:45:47 -05:00
|
|
|
|
Disjunction,
|
|
|
|
|
|
Operation
|
2024-05-09 17:08:15 -04:00
|
|
|
|
)
|
|
|
|
|
|
|
2024-06-23 20:02:53 -07:00
|
|
|
|
class SourceFile:
|
|
|
|
|
|
def __init__(self, fileobj: TextIO):
|
|
|
|
|
|
self.fileobj = fileobj
|
2025-05-02 12:08:15 -04:00
|
|
|
|
self.line_in_file = 0
|
2024-10-25 14:52:12 -04:00
|
|
|
|
self.reststr = ""
|
2024-06-23 20:02:53 -07:00
|
|
|
|
|
2024-10-28 11:14:33 -04:00
|
|
|
|
def next_line(self):
|
2024-11-05 12:45:47 -05:00
|
|
|
|
"""
|
|
|
|
|
|
Grabs the next line.
|
|
|
|
|
|
If reststr is populated return that, otherwise
|
|
|
|
|
|
consume generator
|
|
|
|
|
|
"""
|
2024-10-25 14:52:12 -04:00
|
|
|
|
if self.reststr != "":
|
2024-10-28 11:14:33 -04:00
|
|
|
|
reststr = self.reststr
|
|
|
|
|
|
self.reststr = ""
|
|
|
|
|
|
return reststr
|
2024-10-25 14:52:12 -04:00
|
|
|
|
|
2024-10-28 11:14:33 -04:00
|
|
|
|
contents = next(self.fileobj).strip()
|
2025-05-02 12:08:15 -04:00
|
|
|
|
self.line_in_file += 1
|
2024-06-23 20:02:53 -07:00
|
|
|
|
return contents
|
|
|
|
|
|
|
2024-10-28 11:14:33 -04:00
|
|
|
|
def __next__(self):
|
|
|
|
|
|
"""
|
|
|
|
|
|
Grabs the next word token from the stream
|
|
|
|
|
|
"""
|
|
|
|
|
|
if self.reststr == "":
|
|
|
|
|
|
self.reststr = next(self.fileobj).strip()
|
2025-05-02 12:08:15 -04:00
|
|
|
|
self.line_in_file += 1
|
2024-10-28 11:14:33 -04:00
|
|
|
|
|
2025-02-18 13:38:39 -05:00
|
|
|
|
next_token, _, self.reststr = self.reststr.partition(" ")
|
2024-10-28 11:14:33 -04:00
|
|
|
|
return next_token
|
|
|
|
|
|
|
2024-10-03 23:34:59 -04:00
|
|
|
|
class UglyHeader:
|
2024-10-24 21:18:56 -04:00
|
|
|
|
def __init__(self, negation: bool, necessitation: bool, custom_model_functions: List[Tuple[int, str]]):
|
2024-11-05 12:45:47 -05:00
|
|
|
|
# Booleans describing the logical fragment
|
2024-10-03 23:34:59 -04:00
|
|
|
|
self.negation = negation
|
|
|
|
|
|
self.necessitation = necessitation
|
2024-11-05 12:45:47 -05:00
|
|
|
|
# List of custom model functions described as
|
|
|
|
|
|
# a sequence of (adicity, symbol) pairs
|
2024-10-24 21:18:56 -04:00
|
|
|
|
self.custom_model_functions = custom_model_functions
|
2024-06-23 20:02:53 -07:00
|
|
|
|
|
2024-10-04 14:09:18 -04:00
|
|
|
|
class ModelBuilder:
|
|
|
|
|
|
def __init__(self):
|
|
|
|
|
|
self.size : int = 0
|
2025-05-02 12:08:15 -04:00
|
|
|
|
self.carrier_list : List[ModelValue] = []
|
2024-10-04 14:09:18 -04:00
|
|
|
|
self.mnegation: Optional[ModelFunction] = None
|
2025-01-31 17:16:25 -05:00
|
|
|
|
self.ordering: Optional[OrderTable] = None
|
2024-10-04 14:09:18 -04:00
|
|
|
|
self.mconjunction: Optional[ModelFunction] = None
|
|
|
|
|
|
self.mdisjunction: Optional[ModelFunction] = None
|
|
|
|
|
|
self.designated_values: Set[ModelValue] = set()
|
|
|
|
|
|
self.mimplication: Optional[ModelFunction] = None
|
2024-10-04 15:51:05 -04:00
|
|
|
|
self.mnecessitation: Optional[ModelFunction] = None
|
2024-11-05 12:45:47 -05:00
|
|
|
|
# Map symbol to model function
|
2024-10-28 12:54:13 -04:00
|
|
|
|
self.custom_model_functions: Dict[str, ModelFunction] = {}
|
2024-10-25 14:52:12 -04:00
|
|
|
|
|
2025-05-03 16:42:15 -04:00
|
|
|
|
def build(self, model_name: str) -> Tuple[Model, Dict[Operation, ModelFunction]]:
|
|
|
|
|
|
"""Create Model"""
|
|
|
|
|
|
assert self.size > 0
|
|
|
|
|
|
assert self.size + 1 == len(self.carrier_list)
|
|
|
|
|
|
assert len(self.designated_values) <= len(self.carrier_list)
|
|
|
|
|
|
assert self.mimplication is not None
|
|
|
|
|
|
|
|
|
|
|
|
# Implication is required to be present
|
|
|
|
|
|
logical_operations = { self.mimplication }
|
|
|
|
|
|
interpretation = {
|
|
|
|
|
|
Implication: self.mimplication
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# Other model functions and logical
|
|
|
|
|
|
# operations are optional
|
|
|
|
|
|
if self.mnegation is not None:
|
|
|
|
|
|
logical_operations.add(self.mnegation)
|
|
|
|
|
|
interpretation[Negation] = self.mnegation
|
|
|
|
|
|
if self.mconjunction is not None:
|
|
|
|
|
|
logical_operations.add(self.mconjunction)
|
|
|
|
|
|
interpretation[Conjunction] = self.mconjunction
|
|
|
|
|
|
if self.mdisjunction is not None:
|
|
|
|
|
|
logical_operations.add(self.mdisjunction)
|
|
|
|
|
|
interpretation[Disjunction] = self.mdisjunction
|
|
|
|
|
|
if self.mnecessitation is not None:
|
|
|
|
|
|
logical_operations.add(self.mnecessitation)
|
|
|
|
|
|
interpretation[Necessitation] = self.mnecessitation
|
|
|
|
|
|
|
|
|
|
|
|
# Custom model function definitions
|
|
|
|
|
|
for custom_mf in self.custom_model_functions.values():
|
|
|
|
|
|
if custom_mf is not None:
|
|
|
|
|
|
logical_operations.add(custom_mf)
|
|
|
|
|
|
op = Operation(custom_mf.operation_name, custom_mf.arity)
|
|
|
|
|
|
interpretation[op] = custom_mf
|
|
|
|
|
|
|
|
|
|
|
|
model = Model(set(self.carrier_list), logical_operations, self.designated_values, ordering=self.ordering, name=model_name)
|
|
|
|
|
|
return (model, interpretation)
|
|
|
|
|
|
|
|
|
|
|
|
|
2024-10-25 14:52:12 -04:00
|
|
|
|
class Stage:
|
|
|
|
|
|
def __init__(self, name: str):
|
|
|
|
|
|
self.name = name
|
|
|
|
|
|
self.next: Optional['Stage'] = None
|
|
|
|
|
|
self.previous: Optional['Stage'] = None
|
2024-11-05 12:45:47 -05:00
|
|
|
|
# This corresponds to a portion of the model name in MaGIC
|
2024-10-28 13:48:36 -04:00
|
|
|
|
self.num = 0
|
|
|
|
|
|
|
|
|
|
|
|
def increment(self):
|
|
|
|
|
|
self.num += 1
|
|
|
|
|
|
|
|
|
|
|
|
def reset(self):
|
|
|
|
|
|
self.num = 0
|
|
|
|
|
|
|
2024-10-25 14:52:12 -04:00
|
|
|
|
def __str__(self):
|
|
|
|
|
|
return self.name
|
|
|
|
|
|
|
|
|
|
|
|
class Stages:
|
|
|
|
|
|
def __init__(self):
|
2024-11-05 12:45:47 -05:00
|
|
|
|
end_stage = Stage("end")
|
|
|
|
|
|
self.stages: Dict[str, Stage] = {"end": end_stage}
|
|
|
|
|
|
self.last_added_stage: Stage = end_stage
|
2024-10-28 13:48:36 -04:00
|
|
|
|
self.first_stage: Optional[Stage] = None
|
|
|
|
|
|
|
2024-10-25 14:52:12 -04:00
|
|
|
|
def add(self, name: str):
|
|
|
|
|
|
stage = Stage(name)
|
2024-11-05 12:45:47 -05:00
|
|
|
|
stage.previous = self.last_added_stage
|
|
|
|
|
|
|
|
|
|
|
|
# End stage is a sink so don't
|
|
|
|
|
|
# mark any stages as next
|
|
|
|
|
|
if self.last_added_stage.name != "end":
|
2024-10-28 13:48:36 -04:00
|
|
|
|
self.last_added_stage.next = stage
|
2024-10-25 14:52:12 -04:00
|
|
|
|
else:
|
2024-11-05 12:45:47 -05:00
|
|
|
|
# If this is triggered, than this is the first
|
|
|
|
|
|
# stage added
|
2024-10-28 13:48:36 -04:00
|
|
|
|
self.first_stage = stage
|
2024-10-25 14:52:12 -04:00
|
|
|
|
|
|
|
|
|
|
self.stages[name] = stage
|
2024-10-28 13:48:36 -04:00
|
|
|
|
self.last_added_stage = stage
|
|
|
|
|
|
|
|
|
|
|
|
def reset_after(self, name):
|
2024-11-05 12:45:47 -05:00
|
|
|
|
"""
|
2025-05-02 12:08:15 -04:00
|
|
|
|
Resets the counters of every stage after
|
|
|
|
|
|
a given stage.
|
|
|
|
|
|
|
|
|
|
|
|
This is to accurately reflect how names are
|
|
|
|
|
|
generated within magic.
|
|
|
|
|
|
Example: 1.1, 1.2, (reset after 1), 2.1, 2.2
|
2024-11-05 12:45:47 -05:00
|
|
|
|
"""
|
2024-10-28 13:48:36 -04:00
|
|
|
|
stage = self.stages[name]
|
2025-05-02 12:08:15 -04:00
|
|
|
|
# NOTE: The process_model stage doesn't
|
|
|
|
|
|
# have a counter associated with it.
|
2024-11-05 12:45:47 -05:00
|
|
|
|
while stage.name != "process_model":
|
|
|
|
|
|
stage.reset()
|
|
|
|
|
|
stage = stage.next
|
2024-10-28 13:48:36 -04:00
|
|
|
|
|
2024-10-25 14:52:12 -04:00
|
|
|
|
def get(self, name):
|
|
|
|
|
|
return self.stages[name]
|
|
|
|
|
|
|
2024-10-28 13:48:36 -04:00
|
|
|
|
def name(self):
|
2025-05-02 12:08:15 -04:00
|
|
|
|
"""
|
|
|
|
|
|
Get the full name of where we are within
|
|
|
|
|
|
the parsing process. Takes into account
|
|
|
|
|
|
the stage number of all the stages seen
|
|
|
|
|
|
so far.
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
# Stages haven't been added yet
|
2024-10-28 13:48:36 -04:00
|
|
|
|
result = ""
|
|
|
|
|
|
stage = self.first_stage
|
|
|
|
|
|
if stage is None:
|
|
|
|
|
|
return ""
|
|
|
|
|
|
|
2025-05-02 12:08:15 -04:00
|
|
|
|
# There's only one stage
|
2024-10-28 13:48:36 -04:00
|
|
|
|
result = f"{stage.num}"
|
|
|
|
|
|
if stage.next == "process_model":
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
2025-05-02 12:08:15 -04:00
|
|
|
|
# Add every subsequent stage counter
|
|
|
|
|
|
# by appending .stage_num
|
2024-10-28 13:48:36 -04:00
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
2024-10-25 14:52:12 -04:00
|
|
|
|
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")
|
2024-10-28 12:54:13 -04:00
|
|
|
|
for (adicity, symbol) in header.custom_model_functions:
|
|
|
|
|
|
stages.add(f"custom--{adicity}--{symbol}")
|
2024-10-25 14:52:12 -04:00
|
|
|
|
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
|
|
|
|
|
|
|
2025-03-18 18:08:28 -04:00
|
|
|
|
def parse_matrices(infile: SourceFile) -> Iterator[Tuple[Model, Dict[Operation, ModelFunction]]]:
|
2024-10-03 23:34:59 -04:00
|
|
|
|
header = parse_header(infile)
|
2024-10-25 14:52:12 -04:00
|
|
|
|
stages = derive_stages(header)
|
|
|
|
|
|
first_run = True
|
2024-10-04 14:09:18 -04:00
|
|
|
|
current_model_parts = ModelBuilder()
|
2025-05-02 12:08:15 -04:00
|
|
|
|
|
2024-11-05 12:45:47 -05:00
|
|
|
|
stage = stages.first_stage
|
2024-10-25 14:52:12 -04:00
|
|
|
|
while True:
|
|
|
|
|
|
match stage.name:
|
|
|
|
|
|
case "end":
|
|
|
|
|
|
break
|
|
|
|
|
|
case "process_model":
|
2025-05-03 16:42:15 -04:00
|
|
|
|
yield current_model_parts.build(stages.name())
|
2024-10-25 14:52:12 -04:00
|
|
|
|
stage = stage.next
|
|
|
|
|
|
case "size":
|
|
|
|
|
|
processed = process_sizes(infile, current_model_parts, first_run)
|
|
|
|
|
|
first_run = False
|
2024-10-28 13:48:36 -04:00
|
|
|
|
if processed:
|
|
|
|
|
|
stage.num = current_model_parts.size + 1
|
|
|
|
|
|
stage = stage.next
|
|
|
|
|
|
else:
|
|
|
|
|
|
stages.reset_after(stage.name)
|
|
|
|
|
|
stage = stage.previous
|
2024-10-25 14:52:12 -04:00
|
|
|
|
case "negation":
|
|
|
|
|
|
processed = process_negations(infile, current_model_parts)
|
2024-10-28 13:48:36 -04:00
|
|
|
|
if processed:
|
|
|
|
|
|
stage.increment()
|
|
|
|
|
|
stage = stage.next
|
|
|
|
|
|
else:
|
|
|
|
|
|
stages.reset_after(stage.name)
|
|
|
|
|
|
stage = stage.previous
|
2024-10-25 14:52:12 -04:00
|
|
|
|
case "order":
|
|
|
|
|
|
processed = process_orders(infile, current_model_parts)
|
2024-10-28 13:48:36 -04:00
|
|
|
|
if processed:
|
|
|
|
|
|
stage.increment()
|
|
|
|
|
|
stage = stage.next
|
|
|
|
|
|
else:
|
|
|
|
|
|
stages.reset_after(stage.name)
|
|
|
|
|
|
stage = stage.previous
|
2024-10-25 14:52:12 -04:00
|
|
|
|
case "designated":
|
|
|
|
|
|
processed = process_designateds(infile, current_model_parts)
|
2024-10-28 13:48:36 -04:00
|
|
|
|
if processed:
|
|
|
|
|
|
stage.increment()
|
|
|
|
|
|
stage = stage.next
|
|
|
|
|
|
else:
|
|
|
|
|
|
stages.reset_after(stage.name)
|
|
|
|
|
|
stage = stage.previous
|
2024-10-25 14:52:12 -04:00
|
|
|
|
case "implication":
|
|
|
|
|
|
processed = process_implications(infile, current_model_parts)
|
2024-10-28 13:48:36 -04:00
|
|
|
|
if processed:
|
|
|
|
|
|
stage.increment()
|
|
|
|
|
|
stage = stage.next
|
|
|
|
|
|
else:
|
|
|
|
|
|
stages.reset_after(stage.name)
|
|
|
|
|
|
stage = stage.previous
|
2024-10-25 14:52:12 -04:00
|
|
|
|
case "necessitation":
|
|
|
|
|
|
processed = process_necessitations(infile, current_model_parts)
|
2024-10-28 13:48:36 -04:00
|
|
|
|
if processed:
|
|
|
|
|
|
stage.increment()
|
|
|
|
|
|
stage = stage.next
|
|
|
|
|
|
else:
|
|
|
|
|
|
stages.reset_after(stage.name)
|
|
|
|
|
|
stage = stage.previous
|
2024-10-25 14:52:12 -04:00
|
|
|
|
case _:
|
2024-10-28 12:54:13 -04:00
|
|
|
|
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)
|
2024-11-05 12:45:47 -05:00
|
|
|
|
processed = process_custom_connective(infile, symbol, adicity, current_model_parts)
|
2024-10-28 13:48:36 -04:00
|
|
|
|
|
|
|
|
|
|
if processed:
|
|
|
|
|
|
stage.increment()
|
|
|
|
|
|
stage = stage.next
|
|
|
|
|
|
else:
|
|
|
|
|
|
stages.reset_after(stage.name)
|
|
|
|
|
|
stage = stage.previous
|
2024-10-28 12:54:13 -04:00
|
|
|
|
|
2024-10-25 14:52:12 -04:00
|
|
|
|
def process_sizes(infile: SourceFile, current_model_parts: ModelBuilder, first_run: bool) -> bool:
|
2025-05-02 12:08:15 -04:00
|
|
|
|
size: Optional[int] = None
|
2024-10-25 14:52:12 -04:00
|
|
|
|
try:
|
|
|
|
|
|
size = parse_size(infile, first_run)
|
|
|
|
|
|
except StopIteration:
|
2025-05-02 12:08:15 -04:00
|
|
|
|
pass
|
|
|
|
|
|
|
2024-10-25 14:52:12 -04:00
|
|
|
|
if size is None:
|
|
|
|
|
|
return False
|
2024-10-04 15:51:05 -04:00
|
|
|
|
|
2025-05-02 12:08:15 -04:00
|
|
|
|
carrier_list = carrier_list_from_size(size)
|
|
|
|
|
|
current_model_parts.carrier_list = carrier_list
|
2024-10-28 13:48:36 -04:00
|
|
|
|
current_model_parts.size = size
|
2024-10-25 14:52:12 -04:00
|
|
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
def process_negations(infile: SourceFile, current_model_parts: ModelBuilder) -> bool:
|
2024-10-03 23:34:59 -04:00
|
|
|
|
"""Stage 2 (Optional)"""
|
2025-05-02 12:08:15 -04:00
|
|
|
|
mnegation = parse_single_monadic_connective(infile, "¬", current_model_parts.size, current_model_parts.carrier_list)
|
2024-10-25 14:52:12 -04:00
|
|
|
|
if mnegation is None:
|
|
|
|
|
|
return False
|
2024-05-09 17:08:15 -04:00
|
|
|
|
|
2024-10-25 14:52:12 -04:00
|
|
|
|
current_model_parts.mnegation = mnegation
|
|
|
|
|
|
return True
|
2024-10-03 23:34:59 -04:00
|
|
|
|
|
2024-10-25 14:52:12 -04:00
|
|
|
|
def process_orders(infile: SourceFile, current_model_parts: ModelBuilder) -> bool:
|
2024-10-03 23:34:59 -04:00
|
|
|
|
"""Stage 3"""
|
2025-05-02 12:08:15 -04:00
|
|
|
|
result = parse_single_order(infile, current_model_parts.size, current_model_parts.carrier_list)
|
2024-10-25 14:52:12 -04:00
|
|
|
|
if result is None:
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
2025-01-31 17:16:25 -05:00
|
|
|
|
ordering, mconjunction, mdisjunction = result
|
|
|
|
|
|
current_model_parts.ordering = ordering
|
2024-10-25 14:52:12 -04:00
|
|
|
|
current_model_parts.mconjunction = mconjunction
|
|
|
|
|
|
current_model_parts.mdisjunction = mdisjunction
|
|
|
|
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
def process_designateds(infile: SourceFile, current_model_parts: ModelBuilder) -> bool:
|
2024-10-03 23:34:59 -04:00
|
|
|
|
"""Stage 4"""
|
2025-05-03 16:42:15 -04:00
|
|
|
|
designated_values = parse_single_designated(infile, current_model_parts.size, current_model_parts.carrier_list)
|
2024-10-25 14:52:12 -04:00
|
|
|
|
if designated_values is None:
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
current_model_parts.designated_values = designated_values
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
def process_implications(infile: SourceFile, current_model_parts: ModelBuilder) -> bool:
|
2024-10-03 23:34:59 -04:00
|
|
|
|
"""Stage 5"""
|
2025-05-03 16:42:15 -04:00
|
|
|
|
mimplication = parse_single_dyadic_connective(infile, "→", current_model_parts.size, current_model_parts.carrier_list)
|
2024-10-25 14:52:12 -04:00
|
|
|
|
if mimplication is None:
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
current_model_parts.mimplication = mimplication
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
def process_necessitations(infile: SourceFile, current_model_parts: ModelBuilder) -> bool:
|
2025-05-02 12:08:15 -04:00
|
|
|
|
mnecessitation = parse_single_monadic_connective(infile, "!", current_model_parts.size, current_model_parts.carrier_list)
|
2024-10-25 14:52:12 -04:00
|
|
|
|
if mnecessitation is None:
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
current_model_parts.mnecessitation = mnecessitation
|
|
|
|
|
|
return True
|
2024-10-03 23:34:59 -04:00
|
|
|
|
|
2024-11-05 12:45:47 -05:00
|
|
|
|
def process_custom_connective(infile: SourceFile, symbol: str, adicity: int, current_model_parts: ModelBuilder) -> bool:
|
|
|
|
|
|
if adicity == 0:
|
|
|
|
|
|
mfunction = parse_single_nullary_connective(infile, symbol)
|
|
|
|
|
|
elif adicity == 1:
|
2025-05-02 12:08:15 -04:00
|
|
|
|
mfunction = parse_single_monadic_connective(infile, symbol, current_model_parts.size, current_model_parts.carrier_list)
|
2024-11-05 12:45:47 -05:00
|
|
|
|
elif adicity == 2:
|
2025-05-03 16:42:15 -04:00
|
|
|
|
mfunction = parse_single_dyadic_connective(infile, symbol, current_model_parts.size, current_model_parts.carrier_list)
|
2024-11-05 12:45:47 -05:00
|
|
|
|
else:
|
|
|
|
|
|
raise NotImplementedError("Unable to process connectives of adicity greater than 2")
|
2024-10-28 12:54:13 -04:00
|
|
|
|
|
|
|
|
|
|
if mfunction is None:
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
current_model_parts.custom_model_functions[symbol] = mfunction
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
2024-10-03 23:34:59 -04:00
|
|
|
|
|
|
|
|
|
|
def parse_header(infile: SourceFile) -> UglyHeader:
|
|
|
|
|
|
"""
|
|
|
|
|
|
Parse the header line from the ugly data format.
|
|
|
|
|
|
"""
|
2024-10-28 11:14:33 -04:00
|
|
|
|
header_line = infile.next_line()
|
2024-10-03 23:34:59 -04:00
|
|
|
|
header_tokens = header_line.split(" ")
|
|
|
|
|
|
assert header_tokens[0] in ["0", "1"]
|
|
|
|
|
|
assert header_tokens[6] in ["0", "1"]
|
2024-10-24 21:18:56 -04:00
|
|
|
|
assert len(header_tokens) >= 7
|
2024-10-03 23:34:59 -04:00
|
|
|
|
negation_defined = bool(int(header_tokens[0]))
|
|
|
|
|
|
necessitation_defined = bool(int(header_tokens[6]))
|
2024-10-24 21:18:56 -04:00
|
|
|
|
num_custom_connectives = int(header_tokens[7])
|
|
|
|
|
|
custom_model_functions: List[Tuple[int, str]] = []
|
|
|
|
|
|
for i in range(num_custom_connectives):
|
|
|
|
|
|
arity = int(header_tokens[7 + (2 * i) + 1])
|
|
|
|
|
|
symbol = header_tokens[7 + (2 * i) + 2]
|
|
|
|
|
|
custom_model_functions.append((arity, symbol))
|
|
|
|
|
|
return UglyHeader(negation_defined, necessitation_defined, custom_model_functions)
|
2024-05-09 17:08:15 -04:00
|
|
|
|
|
2025-05-02 12:08:15 -04:00
|
|
|
|
def carrier_list_from_size(size: int) -> List[ModelValue]:
|
2024-05-29 14:08:03 -04:00
|
|
|
|
"""
|
|
|
|
|
|
Construct a carrier set of model values
|
|
|
|
|
|
based on the desired size.
|
|
|
|
|
|
"""
|
2025-05-02 12:08:15 -04:00
|
|
|
|
return [
|
2024-05-09 17:08:15 -04:00
|
|
|
|
mvalue_from_index(i) for i in range(size + 1)
|
2025-05-02 12:08:15 -04:00
|
|
|
|
]
|
2024-05-09 17:08:15 -04:00
|
|
|
|
|
2024-10-15 10:28:16 -04:00
|
|
|
|
def parse_size(infile: SourceFile, first_run: bool) -> Optional[int]:
|
2024-05-29 14:08:03 -04:00
|
|
|
|
"""
|
|
|
|
|
|
Parse the line representing the matrix size.
|
|
|
|
|
|
"""
|
2024-10-28 11:14:33 -04:00
|
|
|
|
size = int(infile.next_line())
|
2024-11-05 12:45:47 -05:00
|
|
|
|
|
2024-10-24 21:21:39 -04:00
|
|
|
|
# HACK: When necessitation and custom connectives are enabled
|
|
|
|
|
|
# MaGIC may produce -1s at the beginning of the file
|
|
|
|
|
|
if first_run:
|
|
|
|
|
|
while size == -1:
|
2024-10-28 11:14:33 -04:00
|
|
|
|
size = int(infile.next_line())
|
2024-10-15 10:28:16 -04:00
|
|
|
|
|
2024-05-09 17:08:15 -04:00
|
|
|
|
if size == -1:
|
|
|
|
|
|
return None
|
2025-05-03 16:42:15 -04:00
|
|
|
|
|
2025-05-02 12:08:15 -04:00
|
|
|
|
assert size > 0, f"Unexpected size at line {infile.line_in_file}"
|
2025-05-03 16:42:15 -04:00
|
|
|
|
|
2024-05-09 17:08:15 -04:00
|
|
|
|
return size
|
2024-05-12 13:03:28 -04:00
|
|
|
|
|
2024-10-25 14:52:12 -04:00
|
|
|
|
def mvalue_from_index(i: int) -> ModelValue:
|
2024-05-29 14:08:03 -04:00
|
|
|
|
"""
|
2024-06-15 08:46:53 -07:00
|
|
|
|
Given an index, return the
|
2024-05-29 14:08:03 -04:00
|
|
|
|
representation of the model value.
|
|
|
|
|
|
"""
|
2024-11-26 15:52:20 -05:00
|
|
|
|
return ModelValue(f"{i}")
|
2024-05-09 17:08:15 -04:00
|
|
|
|
|
|
|
|
|
|
def parse_mvalue(x: str) -> ModelValue:
|
2024-05-29 14:08:03 -04:00
|
|
|
|
"""
|
|
|
|
|
|
Parse an element and return the model value.
|
|
|
|
|
|
"""
|
2024-06-15 08:46:53 -07:00
|
|
|
|
return mvalue_from_index(int(x))
|
2024-05-09 17:08:15 -04:00
|
|
|
|
|
2025-05-03 16:42:15 -04:00
|
|
|
|
|
|
|
|
|
|
def parse_single_order(infile: SourceFile, size: int, carrier_list: List[ModelValue]) -> Optional[
|
|
|
|
|
|
Tuple[OrderTable, Optional[ModelFunction], Optional[ModelFunction]]]:
|
2024-05-29 14:08:03 -04:00
|
|
|
|
"""
|
|
|
|
|
|
Parse the line representing the ordering table
|
|
|
|
|
|
"""
|
2024-10-28 11:14:33 -04:00
|
|
|
|
line = infile.next_line()
|
2024-05-09 17:08:15 -04:00
|
|
|
|
if line == '-1':
|
2024-05-12 13:03:28 -04:00
|
|
|
|
return None
|
|
|
|
|
|
|
2024-05-09 17:08:15 -04:00
|
|
|
|
table = line.split(" ")
|
|
|
|
|
|
|
2025-05-02 12:08:15 -04:00
|
|
|
|
assert len(table) == (size + 1)**2, f"Order table doesn't match expected size at line {infile.line_in_file}"
|
2024-05-09 17:08:15 -04:00
|
|
|
|
|
2025-01-31 17:16:25 -05:00
|
|
|
|
ordering = OrderTable()
|
2024-05-09 17:08:15 -04:00
|
|
|
|
omapping = {}
|
|
|
|
|
|
table_i = 0
|
|
|
|
|
|
|
2025-05-02 12:08:15 -04:00
|
|
|
|
for x, y in product(carrier_list, carrier_list):
|
|
|
|
|
|
omapping[(x, y)] = table[table_i] == '1'
|
|
|
|
|
|
if table[table_i] == '1':
|
|
|
|
|
|
ordering.add(x, y)
|
|
|
|
|
|
table_i += 1
|
2024-05-09 17:08:15 -04:00
|
|
|
|
|
|
|
|
|
|
cmapping = {}
|
|
|
|
|
|
dmapping = {}
|
|
|
|
|
|
|
2025-05-02 12:08:15 -04:00
|
|
|
|
for x, y in product(carrier_list, carrier_list):
|
2025-05-03 12:54:37 -04:00
|
|
|
|
cresult = ordering.meet(x, y)
|
2025-05-02 12:08:15 -04:00
|
|
|
|
if cresult is None:
|
2025-05-03 16:42:15 -04:00
|
|
|
|
return ordering, None, None
|
2025-05-02 12:08:15 -04:00
|
|
|
|
cmapping[(x, y)] = cresult
|
|
|
|
|
|
|
2025-05-03 12:54:37 -04:00
|
|
|
|
dresult = ordering.join(x, y)
|
2025-05-02 12:08:15 -04:00
|
|
|
|
if dresult is None:
|
2025-05-03 16:42:15 -04:00
|
|
|
|
return ordering, None, None
|
2025-05-02 12:08:15 -04:00
|
|
|
|
dmapping[(x, y)] = dresult
|
2024-05-09 17:08:15 -04:00
|
|
|
|
|
2024-05-29 14:08:03 -04:00
|
|
|
|
mconjunction = ModelFunction(2, cmapping, "∧")
|
|
|
|
|
|
mdisjunction = ModelFunction(2, dmapping, "∨")
|
2024-05-09 17:08:15 -04:00
|
|
|
|
|
2025-01-31 17:16:25 -05:00
|
|
|
|
return ordering, mconjunction, mdisjunction
|
2024-05-09 17:08:15 -04:00
|
|
|
|
|
2025-05-03 16:42:15 -04:00
|
|
|
|
def parse_single_designated(infile: SourceFile, size: int, carrier_list: List[ModelValue]) -> Optional[Set[ModelValue]]:
|
2024-05-29 14:08:03 -04:00
|
|
|
|
"""
|
|
|
|
|
|
Parse the line representing which model values are designated.
|
|
|
|
|
|
"""
|
2024-10-28 11:14:33 -04:00
|
|
|
|
line = infile.next_line()
|
2024-05-09 17:08:15 -04:00
|
|
|
|
if line == '-1':
|
|
|
|
|
|
return None
|
2024-05-12 13:03:28 -04:00
|
|
|
|
|
2024-05-09 17:08:15 -04:00
|
|
|
|
row = line.split(" ")
|
2025-05-02 12:08:15 -04:00
|
|
|
|
assert len(row) == size + 1, f"Designated table doesn't match expected size at line {infile.line_in_file}"
|
2024-05-09 17:08:15 -04:00
|
|
|
|
|
|
|
|
|
|
designated_values = set()
|
|
|
|
|
|
|
2025-05-03 16:42:15 -04:00
|
|
|
|
for x, j in zip(carrier_list, row):
|
2024-05-09 17:08:15 -04:00
|
|
|
|
if j == '1':
|
|
|
|
|
|
designated_values.add(x)
|
|
|
|
|
|
|
|
|
|
|
|
return designated_values
|
|
|
|
|
|
|
2024-10-28 12:54:13 -04:00
|
|
|
|
|
2024-11-05 12:45:47 -05:00
|
|
|
|
def parse_single_nullary_connective(infile: SourceFile, symbol: str) -> Optional[ModelFunction]:
|
|
|
|
|
|
line = infile.next_line()
|
|
|
|
|
|
if line == "-1":
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
row = line.split(" ")
|
2025-05-02 12:08:15 -04:00
|
|
|
|
assert len(row) == 1, f"More than one assignment for a nullary connective was provided at line {infile.line_in_file}"
|
2024-11-05 12:45:47 -05:00
|
|
|
|
|
|
|
|
|
|
mapping = {}
|
|
|
|
|
|
mapping[()] = parse_mvalue(row[0])
|
|
|
|
|
|
return ModelFunction(0, mapping, symbol)
|
2024-10-28 12:54:13 -04:00
|
|
|
|
|
2025-05-02 12:08:15 -04:00
|
|
|
|
def parse_single_monadic_connective(infile: SourceFile, symbol: str, size: int, carrier_list: List[ModelValue]) -> Optional[ModelFunction]:
|
2024-10-28 12:54:13 -04:00
|
|
|
|
line = infile.next_line()
|
|
|
|
|
|
if line == '-1':
|
|
|
|
|
|
return None
|
2024-10-04 15:51:05 -04:00
|
|
|
|
|
2024-10-28 12:54:13 -04:00
|
|
|
|
row = line.split(" ")
|
2025-05-02 12:08:15 -04:00
|
|
|
|
assert len(row) == size + 1, f"{symbol} table doesn't match size at line {infile.line_in_file}"
|
2024-10-28 12:54:13 -04:00
|
|
|
|
mapping = {}
|
|
|
|
|
|
|
2025-05-02 12:08:15 -04:00
|
|
|
|
for x, j in zip(carrier_list, row):
|
2024-10-28 12:54:13 -04:00
|
|
|
|
y = parse_mvalue(j)
|
|
|
|
|
|
mapping[(x, )] = y
|
|
|
|
|
|
|
|
|
|
|
|
return ModelFunction(1, mapping, symbol)
|
|
|
|
|
|
|
2025-05-03 16:42:15 -04:00
|
|
|
|
def parse_single_dyadic_connective(infile: SourceFile, symbol: str, size: int, carrier_list: List[ModelValue]) -> Optional[ModelFunction]:
|
2024-11-05 12:45:47 -05:00
|
|
|
|
first_token = next(infile)
|
|
|
|
|
|
if first_token == "-1":
|
2024-10-28 11:14:33 -04:00
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
table = []
|
|
|
|
|
|
try:
|
|
|
|
|
|
table = [first_token] + [next(infile) for _ in range((size + 1)**2 - 1)]
|
|
|
|
|
|
except StopIteration:
|
2025-05-03 16:42:15 -04:00
|
|
|
|
raise Exception(f"{symbol} table does not match expected size at line {infile.line_in_file}")
|
2024-10-04 15:51:05 -04:00
|
|
|
|
|
|
|
|
|
|
mapping = {}
|
|
|
|
|
|
table_i = 0
|
|
|
|
|
|
|
2025-05-03 16:42:15 -04:00
|
|
|
|
for x, y in product(carrier_list, carrier_list):
|
|
|
|
|
|
r = parse_mvalue(table[table_i])
|
|
|
|
|
|
table_i += 1
|
|
|
|
|
|
mapping[(x, y)] = r
|
2024-10-04 15:51:05 -04:00
|
|
|
|
|
2024-10-28 12:54:13 -04:00
|
|
|
|
return ModelFunction(2, mapping, symbol)
|