File size: 3,314 Bytes
94f8868 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 | from enum import StrEnum
from typing import Self
from pydantic import Field, model_validator
from decision_lab.config import StrictModel
class Label(StrEnum):
TRUE = "TRUE"
FALSE = "FALSE"
UNKNOWN = "UNKNOWN"
class Atom(StrictModel):
predicate: str = Field(pattern=r"^[a-z][a-z_]*$")
arguments: tuple[str, ...]
negative: bool
@model_validator(mode="after")
def validate_arity(self):
if len(self.arguments) not in (1, 2) or any(not arg for arg in self.arguments):
raise ValueError("Atoms require one or two nonempty arguments")
return self
def opposite(self) -> Self:
return self.model_copy(update={"negative": not self.negative})
def ground(self, bindings: dict[str, str]) -> Self:
arguments = tuple(bindings[arg] if arg.startswith("?") else arg for arg in self.arguments)
return self.model_copy(update={"arguments": arguments})
class Rule(StrictModel):
conditions: tuple[Atom, ...]
conclusion: Atom
@model_validator(mode="after")
def validate_conditions(self):
if len(self.conditions) not in (1, 2):
raise ValueError("Rules require one or two conditions")
body_variables = {
term for atom in self.conditions for term in atom.arguments if term.startswith("?")
}
head_variables = {term for term in self.conclusion.arguments if term.startswith("?")}
if not head_variables <= body_variables:
raise ValueError("Conclusion variables must occur in the conditions")
return self
class Theory(StrictModel):
entities: tuple[str, ...]
facts: tuple[Atom, ...]
rules: tuple[Rule, ...]
@model_validator(mode="after")
def validate_theory(self):
if not self.entities or len(set(self.entities)) != len(self.entities):
raise ValueError("Theory entities must be nonempty and unique")
if any(term.startswith("?") for term in self.entities):
raise ValueError("Entity names cannot be variables")
if len(set(self.facts)) != len(self.facts) or len(set(self.rules)) != len(self.rules):
raise ValueError("Duplicate facts or rules are not allowed")
atoms = [*self.facts, *(a for r in self.rules for a in (*r.conditions, r.conclusion))]
arities: dict[str, int] = {}
for atom in atoms:
arity = len(atom.arguments)
if atom.predicate in arities and arities[atom.predicate] != arity:
raise ValueError("A predicate cannot have multiple arities")
arities[atom.predicate] = arity
for term in atom.arguments:
if not term.startswith("?") and term not in self.entities:
raise ValueError(f"Undeclared entity: {term}")
if any(term.startswith("?") for atom in self.facts for term in atom.arguments):
raise ValueError("Initial facts must be ground")
return self
class Example(StrictModel):
example_id: str
family_id: str
split: str
theory: Theory
query: Atom
label: Label
proof_depth: int | None
theory_depth: int
motif: str
renderer_id: str
theory_hash: str
structure_hash: str
facts: tuple[str, ...]
rules: tuple[str, ...]
question: str
prompt: str
|