py-predicate 0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,64 @@
1
+ Metadata-Version: 2.3
2
+ Name: py_predicate
3
+ Version: 0.1
4
+ Summary: Module to create composable predicates
5
+ Author-email: Maurits Rijk <maurits.rijk@gmail.com>
6
+ Requires-Python: >=3.12
7
+ Description-Content-Type: text/markdown
8
+ Classifier: Intended Audience :: Information Technology
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python
13
+ Classifier: Topic :: Internet
14
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
15
+ Classifier: Topic :: Software Development :: Libraries
16
+ Classifier: Topic :: Software Development
17
+ Classifier: Typing :: Typed
18
+ Classifier: Development Status :: 3 - Alpha
19
+ Classifier: Environment :: Web Environment
20
+ Classifier: License :: OSI Approved :: MIT License
21
+ Classifier: Programming Language :: Python :: 3 :: Only
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Programming Language :: Python :: 3.12
24
+ Requires-Dist: graphviz
25
+ Requires-Dist: more-itertools
26
+ Requires-Dist: bumpversion ; extra == "dev"
27
+ Requires-Dist: jsonschema ; extra == "dev"
28
+ Requires-Dist: pre-commit ; extra == "dev"
29
+ Requires-Dist: black ; extra == "test"
30
+ Requires-Dist: mypy ; extra == "test"
31
+ Requires-Dist: mypy_extensions ; extra == "test"
32
+ Requires-Dist: pytest ; extra == "test"
33
+ Requires-Dist: pytest-cov ; extra == "test"
34
+ Requires-Dist: ruff ; extra == "test"
35
+ Project-URL: Source, https://github.com/mrijk/py-predicate
36
+ Provides-Extra: dev
37
+ Provides-Extra: test
38
+
39
+ ![Documentation](https://github.com/mrijk/py-predicate/actions/workflows/pages.yaml/badge.svg)
40
+ ![Test](https://github.com/mrijk/py-predicate/actions/workflows/test.yaml/badge.svg)
41
+ [![codecov](https://codecov.io/gh/mrijk/py-predicate/graph/badge.svg?token=KMBDJNC3W9)](https://codecov.io/gh/mrijk/py-predicate)
42
+
43
+ # Introduction
44
+
45
+ py-predicate is a Python library to create composable predicates
46
+
47
+ # Example
48
+
49
+ ```python
50
+ filtered = [x for x in range(10) if x >= 2 and x <= 3]
51
+ ```
52
+
53
+ Version with predicates:
54
+
55
+ ```python
56
+ ge_2 = ge_p(2)
57
+ le_3 = le_p(3)
58
+
59
+ between_2_and_3 = ge_2 & le_3
60
+ filtered = [x for x in range(10) if between_2_and_3(x)]
61
+ ```
62
+
63
+ Of course this example looks way more complicated than the original version. The point here is that you can build
64
+ reusable predicates that can be used in multiple locations
@@ -0,0 +1,26 @@
1
+ ![Documentation](https://github.com/mrijk/py-predicate/actions/workflows/pages.yaml/badge.svg)
2
+ ![Test](https://github.com/mrijk/py-predicate/actions/workflows/test.yaml/badge.svg)
3
+ [![codecov](https://codecov.io/gh/mrijk/py-predicate/graph/badge.svg?token=KMBDJNC3W9)](https://codecov.io/gh/mrijk/py-predicate)
4
+
5
+ # Introduction
6
+
7
+ py-predicate is a Python library to create composable predicates
8
+
9
+ # Example
10
+
11
+ ```python
12
+ filtered = [x for x in range(10) if x >= 2 and x <= 3]
13
+ ```
14
+
15
+ Version with predicates:
16
+
17
+ ```python
18
+ ge_2 = ge_p(2)
19
+ le_3 = le_p(3)
20
+
21
+ between_2_and_3 = ge_2 & le_3
22
+ filtered = [x for x in range(10) if between_2_and_3(x)]
23
+ ```
24
+
25
+ Of course this example looks way more complicated than the original version. The point here is that you can build
26
+ reusable predicates that can be used in multiple locations
@@ -0,0 +1,63 @@
1
+ """The py-predicate module."""
2
+
3
+ __version__ = "0.0.1"
4
+
5
+ from predicate.optimizer.predicate_optimizer import can_optimize, optimize
6
+ from predicate.predicate import (
7
+ AllPredicate,
8
+ AlwaysFalsePredicate,
9
+ AlwaysTruePredicate,
10
+ AndPredicate,
11
+ AnyPredicate,
12
+ EqPredicate,
13
+ FnPredicate,
14
+ GePredicate,
15
+ GtPredicate,
16
+ InPredicate,
17
+ IsEmptyPredicate,
18
+ IsNonePredicate,
19
+ IsNotNonePredicate,
20
+ LePredicate,
21
+ LtPredicate,
22
+ NePredicate,
23
+ NotInPredicate,
24
+ NotPredicate,
25
+ OrPredicate,
26
+ Predicate,
27
+ XorPredicate,
28
+ always_false_p,
29
+ always_true_p,
30
+ )
31
+ from predicate.standard_predicates import ge_p, gt_p, le_p, lt_p
32
+
33
+ __all__ = [
34
+ "ge_p",
35
+ "gt_p",
36
+ "le_p",
37
+ "lt_p",
38
+ "can_optimize",
39
+ "optimize",
40
+ "always_true_p",
41
+ "AllPredicate",
42
+ "AndPredicate",
43
+ "AnyPredicate",
44
+ "InPredicate",
45
+ "FnPredicate",
46
+ "GePredicate",
47
+ "GtPredicate",
48
+ "IsEmptyPredicate",
49
+ "IsNonePredicate",
50
+ "IsNotNonePredicate",
51
+ "LePredicate",
52
+ "LtPredicate",
53
+ "NePredicate",
54
+ "NotInPredicate",
55
+ "XorPredicate",
56
+ "always_false_p",
57
+ "AlwaysTruePredicate",
58
+ "AlwaysFalsePredicate",
59
+ "EqPredicate",
60
+ "NotPredicate",
61
+ "OrPredicate",
62
+ "Predicate",
63
+ ]
File without changes
@@ -0,0 +1,138 @@
1
+ from itertools import count
2
+
3
+ import graphviz # type: ignore
4
+
5
+ from predicate import (
6
+ AllPredicate,
7
+ AlwaysFalsePredicate,
8
+ AlwaysTruePredicate,
9
+ AndPredicate,
10
+ AnyPredicate,
11
+ EqPredicate,
12
+ FnPredicate,
13
+ GePredicate,
14
+ GtPredicate,
15
+ InPredicate,
16
+ LePredicate,
17
+ LtPredicate,
18
+ NePredicate,
19
+ NotInPredicate,
20
+ NotPredicate,
21
+ OrPredicate,
22
+ Predicate,
23
+ XorPredicate,
24
+ )
25
+ from predicate.optimizer.predicate_optimizer import optimize
26
+
27
+
28
+ def to_dot(predicate: Predicate, predicate_string: str = "", show_optimized: bool = False):
29
+ graph_attr = {"label": predicate_string, "labelloc": "t"}
30
+
31
+ node_attr = {"shape": "rectangle", "style": "filled", "fillcolor": "#B7D7A8"}
32
+
33
+ edge_attr: dict = {}
34
+
35
+ dot = graphviz.Digraph(graph_attr=graph_attr, node_attr=node_attr, edge_attr=edge_attr)
36
+
37
+ node_nr = count()
38
+
39
+ render_original(dot, predicate, node_nr)
40
+
41
+ if show_optimized:
42
+ render_optimized(dot, predicate, node_nr)
43
+
44
+ return dot
45
+
46
+
47
+ def render(dot, predicate: Predicate, node_nr):
48
+ def add_node(name: str, *, label: str):
49
+ node = next(node_nr)
50
+ unique_name = f"{name}_{node}"
51
+ dot.node(unique_name, label=label)
52
+ return unique_name
53
+
54
+ def to_value(predicate: Predicate):
55
+ match predicate:
56
+ case AllPredicate(all_predicate):
57
+ node = add_node("all", label="∀")
58
+ child = to_value(all_predicate)
59
+ dot.edge(node, child)
60
+ return node
61
+ case AlwaysFalsePredicate():
62
+ return add_node("F", label="false")
63
+ case AlwaysTruePredicate():
64
+ return add_node("T", label="true")
65
+ case AndPredicate(left, right):
66
+ node = add_node("and", label="∧")
67
+ left_node = to_value(left)
68
+ right_node = to_value(right)
69
+ dot.edge(node, left_node)
70
+ dot.edge(node, right_node)
71
+ return node
72
+ case AnyPredicate(any_predicate):
73
+ node = add_node("any", label="∃")
74
+ child = to_value(any_predicate)
75
+ dot.edge(node, child)
76
+ return node
77
+ case EqPredicate(v):
78
+ return add_node("eq", label=f"x = {v}")
79
+ case FnPredicate(predicate_fn):
80
+ name = predicate_fn.__code__.co_name
81
+ # code = inspect.getsource(predicate_fn)
82
+ # m = re.match(r".*\(predicate_fn=(.*)\)", code)
83
+ return add_node("fn", label=f"fn: {name}")
84
+ case GePredicate(v):
85
+ return add_node("ge", label=f"x ≥ {v}")
86
+ case GtPredicate(v):
87
+ return add_node("gt", label=f"x > {v}")
88
+ case InPredicate(v):
89
+ items = ", ".join(str(item) for item in v)
90
+ return add_node("in", label=f"x ∈ {{{items}}}")
91
+ case LePredicate(v):
92
+ return add_node("le", label=f"x ≤ {v}")
93
+ case LtPredicate(v):
94
+ return add_node("lt", label=f"x < {v}")
95
+ case NotInPredicate(v):
96
+ items = ", ".join(str(item) for item in v)
97
+ return add_node("in", label=f"x ∉ {{{items}}}")
98
+ case NePredicate(v):
99
+ return add_node("ne", label=f"x ≠ {v}")
100
+ case NotPredicate(not_predicate):
101
+ child = to_value(not_predicate)
102
+ node = add_node("not", label="¬")
103
+ dot.edge(node, child)
104
+ return node
105
+ case OrPredicate(left, right):
106
+ node = add_node("or", label="∨")
107
+ left_node = to_value(left)
108
+ right_node = to_value(right)
109
+ dot.edge(node, left_node)
110
+ dot.edge(node, right_node)
111
+ return node
112
+ case XorPredicate(left, right):
113
+ node = add_node("xor", label="⊻")
114
+ left_node = to_value(left)
115
+ right_node = to_value(right)
116
+ dot.edge(node, left_node)
117
+ dot.edge(node, right_node)
118
+ return node
119
+ case _:
120
+ raise ValueError(f"Unknown predicate type {predicate}")
121
+
122
+ to_value(predicate)
123
+
124
+
125
+ def render_original(dot, predicate: Predicate, node_nr):
126
+ with dot.subgraph(name="cluster_original") as original:
127
+ original.attr(style="filled", color="lightgrey")
128
+ original.attr(label="Original predicate")
129
+ render(original, predicate, node_nr)
130
+
131
+
132
+ def render_optimized(dot, predicate: Predicate, node_nr):
133
+ optimized_predicate = optimize(predicate)
134
+
135
+ with dot.subgraph(name="cluster_optimized") as optimized:
136
+ optimized.attr(style="filled", color="lightgrey")
137
+ optimized.attr(label="Optimized predicate")
138
+ render(optimized, optimized_predicate, node_nr)
@@ -0,0 +1,45 @@
1
+ from typing import Any
2
+
3
+ from predicate import (
4
+ AllPredicate,
5
+ AlwaysFalsePredicate,
6
+ AlwaysTruePredicate,
7
+ AndPredicate,
8
+ AnyPredicate,
9
+ FnPredicate,
10
+ NePredicate,
11
+ NotPredicate,
12
+ OrPredicate,
13
+ Predicate,
14
+ XorPredicate,
15
+ )
16
+
17
+
18
+ def to_json(predicate: Predicate) -> dict[str, Any]:
19
+ def to_value(predicate) -> tuple[str, Any]:
20
+ match predicate:
21
+ case AllPredicate(all_predicate):
22
+ return "all", {"predicate": to_json(all_predicate)}
23
+ case AlwaysFalsePredicate():
24
+ return "false", False
25
+ case AlwaysTruePredicate():
26
+ return "true", True
27
+ case AndPredicate(left, right):
28
+ return "and", {"left": to_json(left), "right": to_json(right)}
29
+ case AnyPredicate(any_predicate):
30
+ return "any", {"predicate": to_json(any_predicate)}
31
+ case FnPredicate(predicate_fn):
32
+ name = predicate_fn.__code__.co_name
33
+ return "fn", {"name": name}
34
+ case NePredicate(v):
35
+ return "ne", {"v": v}
36
+ case NotPredicate(not_predicate):
37
+ return "not", {"predicate": to_json(not_predicate)}
38
+ case OrPredicate(left, right):
39
+ return "or", {"left": to_json(left), "right": to_json(right)}
40
+ case XorPredicate(left, right):
41
+ return "xor", {"left": to_json(left), "right": to_json(right)}
42
+ case _:
43
+ return "unknown", {}
44
+
45
+ return dict([to_value(predicate)])
@@ -0,0 +1,88 @@
1
+ from functools import singledispatch
2
+
3
+ from predicate import (
4
+ AlwaysFalsePredicate,
5
+ AlwaysTruePredicate,
6
+ EqPredicate,
7
+ GePredicate,
8
+ GtPredicate,
9
+ InPredicate,
10
+ IsNonePredicate,
11
+ IsNotNonePredicate,
12
+ LePredicate,
13
+ LtPredicate,
14
+ NePredicate,
15
+ NotInPredicate,
16
+ NotPredicate,
17
+ Predicate,
18
+ )
19
+
20
+
21
+ @singledispatch
22
+ def negate[T](predicate: Predicate[T]) -> Predicate[T]:
23
+ return NotPredicate(predicate=predicate)
24
+
25
+
26
+ @negate.register
27
+ def negate_is_not(predicate: NotPredicate) -> Predicate:
28
+ return predicate.predicate
29
+
30
+
31
+ @negate.register
32
+ def negate_is_false(_predicate: AlwaysFalsePredicate) -> Predicate:
33
+ return AlwaysTruePredicate()
34
+
35
+
36
+ @negate.register
37
+ def negate_is_true(_predicate: AlwaysTruePredicate) -> Predicate:
38
+ return AlwaysFalsePredicate()
39
+
40
+
41
+ @negate.register
42
+ def negate_eq(predicate: EqPredicate) -> Predicate:
43
+ return NePredicate(v=predicate.v)
44
+
45
+
46
+ @negate.register
47
+ def negate_ne(predicate: NePredicate) -> Predicate:
48
+ return EqPredicate(v=predicate.v)
49
+
50
+
51
+ @negate.register
52
+ def negate_gt(predicate: GtPredicate) -> Predicate:
53
+ return LePredicate(v=predicate.v)
54
+
55
+
56
+ @negate.register
57
+ def negate_ge(predicate: GePredicate) -> Predicate:
58
+ return LtPredicate(v=predicate.v)
59
+
60
+
61
+ @negate.register
62
+ def negate_in(predicate: InPredicate) -> Predicate:
63
+ return NotInPredicate(v=predicate.v)
64
+
65
+
66
+ @negate.register
67
+ def negate_not_in(predicate: NotInPredicate) -> Predicate:
68
+ return InPredicate(v=predicate.v)
69
+
70
+
71
+ @negate.register
72
+ def negate_lt(predicate: LtPredicate) -> Predicate:
73
+ return GePredicate(v=predicate.v)
74
+
75
+
76
+ @negate.register
77
+ def negate_le(predicate: LePredicate) -> Predicate:
78
+ return GtPredicate(v=predicate.v)
79
+
80
+
81
+ @negate.register
82
+ def negate_is_none(_predicate: IsNonePredicate) -> Predicate:
83
+ return IsNotNonePredicate()
84
+
85
+
86
+ @negate.register
87
+ def negate_is_not_none(_predicate: IsNotNonePredicate) -> Predicate:
88
+ return IsNonePredicate()
File without changes
@@ -0,0 +1,31 @@
1
+ from predicate.predicate import (
2
+ AllPredicate,
3
+ AlwaysFalsePredicate,
4
+ AlwaysTruePredicate,
5
+ AnyPredicate,
6
+ IsEmptyPredicate,
7
+ IsNonePredicate,
8
+ IsNotNonePredicate,
9
+ NotPredicate,
10
+ Predicate,
11
+ )
12
+
13
+
14
+ def optimize_all_predicate[T](predicate: AllPredicate[T]) -> Predicate[T]:
15
+ from predicate.optimizer.predicate_optimizer import optimize
16
+
17
+ optimized = optimize(predicate.predicate)
18
+
19
+ match optimized:
20
+ case AlwaysTruePredicate():
21
+ return AlwaysTruePredicate()
22
+ case AlwaysFalsePredicate():
23
+ return IsEmptyPredicate()
24
+ case NotPredicate(not_predicate):
25
+ return NotPredicate(predicate=AnyPredicate(predicate=not_predicate))
26
+ case IsNotNonePredicate():
27
+ return NotPredicate(predicate=AnyPredicate(predicate=IsNonePredicate()))
28
+ case _:
29
+ pass
30
+
31
+ return AllPredicate(predicate=optimized)
@@ -0,0 +1,121 @@
1
+ from predicate.predicate import (
2
+ AllPredicate,
3
+ AlwaysFalsePredicate,
4
+ AlwaysTruePredicate,
5
+ AndPredicate,
6
+ EqPredicate,
7
+ FnPredicate,
8
+ GePredicate,
9
+ InPredicate,
10
+ NePredicate,
11
+ NotInPredicate,
12
+ NotPredicate,
13
+ OrPredicate,
14
+ Predicate,
15
+ )
16
+
17
+
18
+ def optimize_and_predicate[T](predicate: AndPredicate[T]) -> Predicate[T]:
19
+ from predicate.negate import negate
20
+ from predicate.optimizer.predicate_optimizer import optimize
21
+
22
+ match left := predicate.left, right := predicate.right:
23
+ case OrPredicate(or_left, or_right), _:
24
+ match or_left, or_right:
25
+ case NotPredicate(not_predicate), _ if not_predicate == right: # (~p | q) & p == q & p
26
+ return AndPredicate(left=or_right, right=right)
27
+ case _, NotPredicate(not_predicate) if not_predicate == right: # (q | ~p) & p == q & p
28
+ return AndPredicate(left=or_left, right=right)
29
+
30
+ case _, OrPredicate():
31
+ return optimize_and_predicate(AndPredicate(left=right, right=left))
32
+
33
+ case _, _ if left == negate(right):
34
+ return AlwaysFalsePredicate() # p & ~p == False
35
+
36
+ match left := optimize(left), right := optimize(right):
37
+ case _, AlwaysFalsePredicate(): # p & False = False
38
+ return AlwaysFalsePredicate()
39
+ case AlwaysFalsePredicate(), _: # False & p = False
40
+ return AlwaysFalsePredicate()
41
+ case _, AlwaysTruePredicate(): # p & True == p
42
+ return optimize(left)
43
+ case AlwaysTruePredicate(), _: # True & p == p
44
+ return optimize(right)
45
+
46
+ case EqPredicate(v1), EqPredicate(v2) if v1 == v2:
47
+ # x = v1 & x = v2 & v1 == v2 => x = v1
48
+ return left
49
+ case EqPredicate(v1), EqPredicate(v2) if v1 != v2:
50
+ # x = v1 & x = v2 & v1 != v2 => False
51
+ return AlwaysFalsePredicate()
52
+ case EqPredicate(v1), GePredicate(v2) if v1 == v2:
53
+ # x = v1 & x >= v2 & v1 = v2 => x = v1
54
+ return left
55
+ case EqPredicate(v1), GePredicate(v2) if v1 < v2:
56
+ # x = v1 & x >= v2 & v1 < v2 => False
57
+ return AlwaysFalsePredicate()
58
+ case GePredicate(v1), GePredicate(v2):
59
+ # x >= v1 & x >= v2 => x >= max(v1, v2)
60
+ return GePredicate(v=max(v1, v2))
61
+
62
+ case FnPredicate(predicate_fn), EqPredicate(v):
63
+ return AlwaysTruePredicate() if predicate_fn(v) else AlwaysFalsePredicate()
64
+
65
+ case InPredicate(v1), InPredicate(v2):
66
+ v = v1 & v2
67
+ if not v:
68
+ return AlwaysFalsePredicate()
69
+ if len(v) == 1:
70
+ return EqPredicate(v=v.pop())
71
+ return InPredicate(v=v)
72
+
73
+ case InPredicate(v1), NotInPredicate(v2):
74
+ v = v1 - v2
75
+ if not v:
76
+ return AlwaysFalsePredicate()
77
+ if len(v) == 1:
78
+ return EqPredicate(v=v.pop())
79
+ return InPredicate(v=v)
80
+
81
+ case NotInPredicate(v1), NotInPredicate(v2):
82
+ v = v1 | v2
83
+ if not v:
84
+ return AlwaysTruePredicate()
85
+ if len(v) == 1:
86
+ return NePredicate(v=v.pop())
87
+ return NotInPredicate(v=v)
88
+
89
+ case AllPredicate(left_all), AllPredicate(right_all):
90
+ # All(p1) & All(p2) => All(p1 & p2)
91
+ return optimize(AllPredicate(predicate=optimize(AndPredicate(left=left_all, right=right_all))))
92
+
93
+ case _, _ if and_contains_negate(predicate, right):
94
+ return AlwaysFalsePredicate() # p & q & ... & ~p == False
95
+
96
+ case _, _ if and_contains_negate(predicate, left):
97
+ return AlwaysFalsePredicate() # q & p & ... & ~p == False
98
+
99
+ case _, _ if left == negate(right):
100
+ return AlwaysFalsePredicate() # p & ~p == False
101
+
102
+ case _, _ if left == right: # p & p == p
103
+ return left
104
+
105
+ case _:
106
+ # return AndPredicate(left=left, right=right)
107
+ return predicate
108
+
109
+
110
+ def and_contains_negate(predicate: AndPredicate, sub_predicate: Predicate) -> bool:
111
+ from predicate.negate import negate
112
+
113
+ match left := predicate.left, right := predicate.right:
114
+ case AndPredicate() as and_left, _:
115
+ return and_contains_negate(and_left, sub_predicate)
116
+ case _, AndPredicate() as and_right:
117
+ return and_contains_negate(and_right, sub_predicate)
118
+ case AndPredicate() as and_left, AndPredicate() as and_right:
119
+ return and_contains_negate(and_left, sub_predicate) or and_contains_negate(and_right, sub_predicate)
120
+ case _:
121
+ return negate(sub_predicate) in (left, right)
@@ -0,0 +1,30 @@
1
+ from predicate.predicate import (
2
+ AllPredicate,
3
+ AlwaysFalsePredicate,
4
+ AlwaysTruePredicate,
5
+ AnyPredicate,
6
+ EqPredicate,
7
+ NePredicate,
8
+ NotPredicate,
9
+ Predicate,
10
+ )
11
+
12
+
13
+ def optimize_any_predicate[T](predicate: AnyPredicate[T]) -> Predicate[T]:
14
+ from predicate.optimizer.predicate_optimizer import optimize
15
+
16
+ optimized = optimize(predicate.predicate)
17
+
18
+ match optimized:
19
+ case AlwaysTruePredicate():
20
+ return AlwaysTruePredicate()
21
+ case AlwaysFalsePredicate():
22
+ return AlwaysFalsePredicate()
23
+ case NePredicate(v):
24
+ return NotPredicate(predicate=AllPredicate(predicate=EqPredicate(v)))
25
+ case NotPredicate(not_predicate):
26
+ return NotPredicate(predicate=AllPredicate(predicate=optimize(not_predicate)))
27
+ case _:
28
+ pass
29
+
30
+ return AnyPredicate(predicate=optimized)
@@ -0,0 +1,57 @@
1
+ from predicate.predicate import (
2
+ AllPredicate,
3
+ AndPredicate,
4
+ AnyPredicate,
5
+ NotPredicate,
6
+ OrPredicate,
7
+ Predicate,
8
+ XorPredicate,
9
+ )
10
+
11
+
12
+ def optimize_not_predicate[T](predicate: NotPredicate[T]) -> Predicate[T]:
13
+ from predicate.negate import negate
14
+ from predicate.optimizer.predicate_optimizer import optimize
15
+
16
+ # ~~p == p
17
+ match predicate.predicate:
18
+ case NotPredicate(not_predicate):
19
+ return optimize(not_predicate)
20
+
21
+ optimized = optimize(predicate.predicate)
22
+
23
+ match optimized:
24
+ case AllPredicate(all_predicate):
25
+ match negate(all_predicate):
26
+ case _ as inverted:
27
+ return AnyPredicate(predicate=inverted)
28
+
29
+ case AndPredicate(left, right):
30
+ match left, right:
31
+ case _, NotPredicate(not_predicate):
32
+ return OrPredicate(left=negate(left), right=not_predicate) # ~(p & ~q) => ~p | q
33
+ case NotPredicate(not_predicate), _:
34
+ return OrPredicate(left=not_predicate, right=negate(right)) # ~(~p & q) => p | ~q
35
+
36
+ case AnyPredicate(any_predicate):
37
+ match negate(any_predicate):
38
+ case _ as inverted:
39
+ return AllPredicate(predicate=inverted)
40
+
41
+ case OrPredicate(left, right):
42
+ match left, right:
43
+ case _, NotPredicate(not_predicate):
44
+ return AndPredicate(left=negate(left), right=not_predicate) # ~(p | ~q) => ~p & q
45
+ case NotPredicate(not_predicate), _:
46
+ return AndPredicate(left=not_predicate, right=negate(right)) # ~(~p | q) => p & ~q
47
+
48
+ case XorPredicate(left, right):
49
+ match left, right:
50
+ case NotPredicate(not_predicate), _: # ~(~p ^ q) == p ^ q
51
+ return XorPredicate(left=not_predicate, right=right)
52
+ case _, NotPredicate(not_predicate): # ~(p ^ ~q) == p ^ q
53
+ return XorPredicate(left=left, right=not_predicate)
54
+ case _: # ~(p ^ q) == ~p ^ q
55
+ return XorPredicate(left=NotPredicate(predicate=left), right=right)
56
+
57
+ return negate(optimized)