Skip to content

Commit

Permalink
Add implies to truth table
Browse files Browse the repository at this point in the history
  • Loading branch information
Maurits Rijk committed Dec 27, 2024
1 parent 67d2d8e commit d59507b
Show file tree
Hide file tree
Showing 7 changed files with 67 additions and 3 deletions.
3 changes: 3 additions & 0 deletions predicate/formatter/format_dot.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from predicate.formatter.helpers import set_to_str
from predicate.ge_predicate import GePredicate
from predicate.gt_predicate import GtPredicate
from predicate.implies import Implies
from predicate.is_falsy_predicate import IsFalsyPredicate
from predicate.is_instance_predicate import IsInstancePredicate
from predicate.is_none_predicate import IsNonePredicate
Expand Down Expand Up @@ -142,6 +143,8 @@ def to_value(predicate: Predicate):
dot.edge(kv, to_value(key), label="key")
dot.edge(kv, to_value(value), label="value")
return node
case Implies(left, right):
return add_node_left_right("implies", label="=>", left=left, right=right)
case IsInstancePredicate(klass):
name = klass[0].__name__ # type: ignore
return add_node("instance", label=f"is_{name}_p")
Expand Down
24 changes: 24 additions & 0 deletions predicate/implies.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from dataclasses import dataclass
from functools import singledispatch
from typing import override

from predicate.always_false_predicate import AlwaysFalsePredicate
from predicate.always_true_predicate import AlwaysTruePredicate
Expand Down Expand Up @@ -107,3 +109,25 @@ def _(predicate: InPredicate, other: Predicate) -> bool:
return predicate.v.issubset(v)
case _:
return False


@dataclass
class Implies[T](Predicate[T]):
"""A predicate class that models the 'implies' (=>) predicate."""

left: Predicate
right: Predicate

def __call__(self, x) -> bool:
return not self.left(x) or self.right(x)

def __repr__(self) -> str:
return f"{self.left} => {self.right}"

@override
def explain_failure(self, x) -> dict:
return {"reason": f"{self.left} doesn't imply {self.right}"}


def implies_p_p(left: Predicate, right: Predicate) -> Predicate:
return Implies(left=left, right=right)
9 changes: 8 additions & 1 deletion predicate/parser.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from lark import Lark, Transformer, UnexpectedEOF # type: ignore

from predicate import always_false_p, always_true_p
from predicate.implies import Implies
from predicate.named_predicate import NamedPredicate
from predicate.predicate import Predicate

Expand All @@ -9,7 +10,8 @@
predicate: expression | variable
variable: WORD
?expression: grouped_expression | or_expression | and_expression | xor_expression | not_expression | false | true
?expression: grouped_expression | or_expression | and_expression | xor_expression | not_expression
| implies_expression | false | true
false: "false"
true: "true"
Expand All @@ -18,6 +20,7 @@
and_expression: predicate "&" predicate
xor_expression: predicate "^" predicate
not_expression: "~" predicate
implies_expression: predicate "=>" predicate
%import common.WORD // imports from terminal library
%ignore " " // Disregard spaces in text
Expand All @@ -40,6 +43,10 @@ def false(self, _item) -> Predicate:
def grouped_expression(self, item):
return item[0]

def implies_expression(self, items: tuple[Predicate, Predicate]):
left, right = items
return Implies(left=left, right=right)

def not_expression(self, item: tuple[Predicate]) -> Predicate:
predicate = item[0]
return ~predicate
Expand Down
7 changes: 5 additions & 2 deletions predicate/truth_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from predicate.always_false_predicate import AlwaysFalsePredicate
from predicate.always_true_predicate import AlwaysTruePredicate
from predicate.implies import Implies
from predicate.named_predicate import NamedPredicate
from predicate.predicate import (
AndPredicate,
Expand Down Expand Up @@ -33,7 +34,9 @@ def get_named_predicates(predicate: Predicate) -> list[str]:

def get_names() -> Iterator[str]:
match predicate:
case AndPredicate(left, right) | OrPredicate(left, right) | XorPredicate(left, right):
case (
AndPredicate(left, right) | OrPredicate(left, right) | XorPredicate(left, right) | Implies(left, right)
):
yield from get_named_predicates(left)
yield from get_named_predicates(right)
case NotPredicate(not_predicate):
Expand All @@ -58,7 +61,7 @@ def execute_predicate(predicate: Predicate, values: dict) -> bool:
def set_named_values(predicate: Predicate, values: dict) -> None:
"""Set the named predicate values."""
match predicate:
case AndPredicate(left, right) | OrPredicate(left, right) | XorPredicate(left, right):
case AndPredicate(left, right) | OrPredicate(left, right) | XorPredicate(left, right) | Implies(left, right):
set_named_values(left, values)
set_named_values(right, values)
case NotPredicate(not_predicate):
Expand Down
9 changes: 9 additions & 0 deletions test/formatter/test_format_dot.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
not_in_p,
to_dot,
)
from predicate.implies import Implies
from predicate.named_predicate import NamedPredicate
from predicate.set_predicates import is_real_subset_p, is_real_superset_p, is_subset_p, is_superset_p
from predicate.standard_predicates import (
Expand Down Expand Up @@ -99,6 +100,14 @@ def test_format_dot_any():
assert dot


def test_format_dot_implies(p, q):
predicate = Implies(p, q)

dot = to_dot(predicate)

assert dot


def test_format_dot_not():
predicate = ~always_true_p

Expand Down
9 changes: 9 additions & 0 deletions test/test_parser.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import pytest

from predicate import always_false_p, always_true_p
from predicate.implies import Implies
from predicate.named_predicate import NamedPredicate
from predicate.parser import parse_expression

Expand Down Expand Up @@ -108,6 +109,14 @@ def test_parser_grouped(p, q, r):
assert predicate == p & (q | r)


def test_parser_implies(p, q):
expression = "p => q"

predicate = parse_expression(expression)

assert predicate == Implies(p, q)


def test_parser_failure(p, q):
expression = "p ^"

Expand Down
9 changes: 9 additions & 0 deletions test/test_truth_table.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import pytest

from predicate import always_false_p, always_true_p, le_p
from predicate.implies import implies_p_p
from predicate.named_predicate import NamedPredicate
from predicate.truth_table import get_named_predicates, set_named_values, truth_table

Expand Down Expand Up @@ -77,6 +78,14 @@ def test_truth_table_false(p):
assert result == [False, True]


def test_truth_table_implies(p, q):
predicate = implies_p_p(p, q)

result = [row[1] for row in truth_table(predicate)]

assert result == [True, True, False, True]


def test_truth_table_true(p):
predicate = p | always_true_p

Expand Down

0 comments on commit d59507b

Please sign in to comment.