ripple-down-rules 0.1.21__py3-none-any.whl → 0.1.62__py3-none-any.whl
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.
- ripple_down_rules/datasets.py +2 -1
- ripple_down_rules/datastructures/__init__.py +4 -4
- ripple_down_rules/datastructures/callable_expression.py +74 -129
- ripple_down_rules/datastructures/case.py +8 -6
- ripple_down_rules/datastructures/dataclasses.py +102 -48
- ripple_down_rules/datastructures/enums.py +5 -1
- ripple_down_rules/experts.py +61 -68
- ripple_down_rules/helpers.py +27 -3
- ripple_down_rules/prompt.py +87 -74
- ripple_down_rules/rdr.py +291 -206
- ripple_down_rules/rules.py +64 -32
- ripple_down_rules/utils.py +209 -4
- {ripple_down_rules-0.1.21.dist-info → ripple_down_rules-0.1.62.dist-info}/METADATA +5 -4
- ripple_down_rules-0.1.62.dist-info/RECORD +20 -0
- {ripple_down_rules-0.1.21.dist-info → ripple_down_rules-0.1.62.dist-info}/WHEEL +1 -1
- ripple_down_rules-0.1.21.dist-info/RECORD +0 -20
- {ripple_down_rules-0.1.21.dist-info → ripple_down_rules-0.1.62.dist-info}/licenses/LICENSE +0 -0
- {ripple_down_rules-0.1.21.dist-info → ripple_down_rules-0.1.62.dist-info}/top_level.txt +0 -0
ripple_down_rules/rules.py
CHANGED
@@ -1,14 +1,18 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
+
import re
|
3
4
|
from abc import ABC, abstractmethod
|
4
5
|
from enum import Enum
|
5
6
|
|
6
7
|
from anytree import NodeMixin
|
7
8
|
from typing_extensions import List, Optional, Self, Union, Dict, Any
|
8
9
|
|
9
|
-
from .datastructures import CallableExpression
|
10
|
+
from .datastructures.callable_expression import CallableExpression
|
11
|
+
from .datastructures.case import Case
|
12
|
+
from sqlalchemy.orm import DeclarativeBase as SQLTable
|
10
13
|
from .datastructures.enums import RDREdge, Stop
|
11
|
-
from .utils import SubclassJSONSerializer, is_iterable, get_full_class_name
|
14
|
+
from .utils import SubclassJSONSerializer, is_iterable, get_full_class_name, conclusion_to_json, \
|
15
|
+
get_rule_conclusion_as_source_code
|
12
16
|
|
13
17
|
|
14
18
|
class Rule(NodeMixin, SubclassJSONSerializer, ABC):
|
@@ -41,6 +45,7 @@ class Rule(NodeMixin, SubclassJSONSerializer, ABC):
|
|
41
45
|
self.conditions = conditions if conditions else None
|
42
46
|
self.conclusion_name: Optional[str] = conclusion_name
|
43
47
|
self.json_serialization: Optional[Dict[str, Any]] = None
|
48
|
+
self._name: Optional[str] = None
|
44
49
|
|
45
50
|
def _post_detach(self, parent):
|
46
51
|
"""
|
@@ -79,43 +84,48 @@ class Rule(NodeMixin, SubclassJSONSerializer, ABC):
|
|
79
84
|
|
80
85
|
:param parent_indent: The indentation of the parent rule.
|
81
86
|
"""
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
87
|
+
conclusion = self.conclusion
|
88
|
+
if isinstance(conclusion, CallableExpression):
|
89
|
+
if self.conclusion.user_input is not None:
|
90
|
+
conclusion = self.conclusion.user_input
|
91
|
+
else:
|
92
|
+
conclusion = self.conclusion.conclusion
|
93
|
+
if isinstance(conclusion, Enum):
|
94
|
+
conclusion = str(conclusion)
|
95
|
+
return self._conclusion_source_code(conclusion, parent_indent=parent_indent)
|
89
96
|
|
90
97
|
@abstractmethod
|
91
|
-
def
|
98
|
+
def _conclusion_source_code(self, conclusion: Any, parent_indent: str = "") -> str:
|
92
99
|
pass
|
93
100
|
|
94
|
-
def write_condition_as_source_code(self, parent_indent: str = "") -> str:
|
101
|
+
def write_condition_as_source_code(self, parent_indent: str = "", defs_file: Optional[str] = None) -> str:
|
95
102
|
"""
|
96
103
|
Get the source code representation of the conditions of the rule.
|
97
104
|
|
98
105
|
:param parent_indent: The indentation of the parent rule.
|
106
|
+
:param defs_file: The file to write the conditions to if they are a definition.
|
99
107
|
"""
|
100
108
|
if_clause = self._if_statement_source_code_clause()
|
101
|
-
|
109
|
+
if '\n' not in self.conditions.user_input:
|
110
|
+
return f"{parent_indent}{if_clause} {self.conditions.user_input}:\n"
|
111
|
+
elif "def " in self.conditions.user_input:
|
112
|
+
if defs_file is None:
|
113
|
+
raise ValueError("Cannot write conditions to source code as definitions python file was not given.")
|
114
|
+
# This means the conditions are a definition that should be written and then called
|
115
|
+
conditions_lines = self.conditions.user_input.split('\n')
|
116
|
+
# use regex to replace the function name
|
117
|
+
new_function_name = f"def conditions_{id(self)}"
|
118
|
+
conditions_lines[0] = re.sub(r"def (\w+)", new_function_name, conditions_lines[0])
|
119
|
+
def_code = "\n".join(conditions_lines)
|
120
|
+
with open(defs_file, 'a') as f:
|
121
|
+
f.write(def_code + "\n")
|
122
|
+
return f"\n{parent_indent}{if_clause} {new_function_name.replace('def ', '')}(case):\n"
|
102
123
|
|
103
124
|
@abstractmethod
|
104
125
|
def _if_statement_source_code_clause(self) -> str:
|
105
126
|
pass
|
106
127
|
|
107
128
|
def _to_json(self) -> Dict[str, Any]:
|
108
|
-
def conclusion_to_json(conclusion):
|
109
|
-
if is_iterable(conclusion):
|
110
|
-
conclusions = {'_type': get_full_class_name(type(conclusion)), 'value': []}
|
111
|
-
for c in conclusion:
|
112
|
-
conclusions['value'].append(conclusion_to_json(c))
|
113
|
-
elif hasattr(conclusion, 'to_json'):
|
114
|
-
conclusions = conclusion.to_json()
|
115
|
-
else:
|
116
|
-
conclusions = {'_type': get_full_class_name(type(conclusion)), 'value': conclusion}
|
117
|
-
return conclusions
|
118
|
-
|
119
129
|
json_serialization = {"conditions": self.conditions.to_json(),
|
120
130
|
"conclusion": conclusion_to_json(self.conclusion),
|
121
131
|
"parent": self.parent.json_serialization if self.parent else None,
|
@@ -137,7 +147,14 @@ class Rule(NodeMixin, SubclassJSONSerializer, ABC):
|
|
137
147
|
"""
|
138
148
|
Get the name of the rule, which is the conditions and the conclusion.
|
139
149
|
"""
|
140
|
-
return self.__str__()
|
150
|
+
return self._name if self._name is not None else self.__str__()
|
151
|
+
|
152
|
+
@name.setter
|
153
|
+
def name(self, new_name: str):
|
154
|
+
"""
|
155
|
+
Set the name of the rule.
|
156
|
+
"""
|
157
|
+
self._name = new_name
|
141
158
|
|
142
159
|
def __str__(self, sep="\n"):
|
143
160
|
"""
|
@@ -247,8 +264,13 @@ class SingleClassRule(Rule, HasAlternativeRule, HasRefinementRule):
|
|
247
264
|
loaded_rule.alternative = SingleClassRule.from_json(data["alternative"])
|
248
265
|
return loaded_rule
|
249
266
|
|
250
|
-
def
|
251
|
-
|
267
|
+
def _conclusion_source_code(self, conclusion: Any, parent_indent: str = "") -> str:
|
268
|
+
conclusion = str(conclusion)
|
269
|
+
indent = parent_indent + " " * 4
|
270
|
+
if '\n' not in conclusion:
|
271
|
+
return f"{indent}return {conclusion}\n"
|
272
|
+
else:
|
273
|
+
return get_rule_conclusion_as_source_code(self, conclusion, parent_indent=parent_indent)
|
252
274
|
|
253
275
|
def _if_statement_source_code_clause(self) -> str:
|
254
276
|
return "elif" if self.weight == RDREdge.Alternative.value else "if"
|
@@ -293,7 +315,7 @@ class MultiClassStopRule(Rule, HasAlternativeRule):
|
|
293
315
|
loaded_rule.alternative = MultiClassStopRule.from_json(data["alternative"])
|
294
316
|
return loaded_rule
|
295
317
|
|
296
|
-
def
|
318
|
+
def _conclusion_source_code(self, conclusion: Any, parent_indent: str = "") -> str:
|
297
319
|
return f"{parent_indent}{' ' * 4}pass\n"
|
298
320
|
|
299
321
|
def _if_statement_source_code_clause(self) -> str:
|
@@ -338,12 +360,22 @@ class MultiClassTopRule(Rule, HasRefinementRule, HasAlternativeRule):
|
|
338
360
|
loaded_rule.alternative = MultiClassTopRule.from_json(data["alternative"])
|
339
361
|
return loaded_rule
|
340
362
|
|
341
|
-
def
|
342
|
-
|
343
|
-
|
363
|
+
def _conclusion_source_code(self, conclusion: Any, parent_indent: str = "") -> str:
|
364
|
+
conclusion_str = str(conclusion)
|
365
|
+
indent = parent_indent + " " * 4
|
366
|
+
statement = ""
|
367
|
+
if '\n' not in conclusion_str:
|
368
|
+
if is_iterable(conclusion):
|
369
|
+
conclusion_str = "{" + ", ".join([str(c) for c in conclusion]) + "}"
|
370
|
+
else:
|
371
|
+
conclusion_str = "{" + str(conclusion) + "}"
|
344
372
|
else:
|
345
|
-
conclusion_str =
|
346
|
-
|
373
|
+
conclusion_str = get_rule_conclusion_as_source_code(self, conclusion_str, parent_indent=parent_indent)
|
374
|
+
lines = conclusion_str.split("\n")
|
375
|
+
conclusion_str = lines[-2].replace("return ", "").strip()
|
376
|
+
statement += "\n".join(lines[:-2]) + "\n"
|
377
|
+
|
378
|
+
statement += f"{indent}conclusions.update(make_set({conclusion_str}))\n"
|
347
379
|
if self.alternative is None:
|
348
380
|
statement += f"{parent_indent}return conclusions\n"
|
349
381
|
return statement
|
ripple_down_rules/utils.py
CHANGED
@@ -1,17 +1,20 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import ast
|
4
|
+
import builtins
|
4
5
|
import importlib
|
5
6
|
import json
|
6
7
|
import logging
|
7
8
|
import os
|
8
|
-
|
9
|
+
import re
|
9
10
|
from collections import UserDict
|
10
11
|
from copy import deepcopy
|
11
|
-
from dataclasses import
|
12
|
+
from dataclasses import is_dataclass, fields
|
13
|
+
from types import NoneType
|
12
14
|
|
13
15
|
import matplotlib
|
14
16
|
import networkx as nx
|
17
|
+
import requests
|
15
18
|
from anytree import Node, RenderTree
|
16
19
|
from anytree.exporter import DotExporter
|
17
20
|
from matplotlib import pyplot as plt
|
@@ -22,11 +25,207 @@ from typing_extensions import Callable, Set, Any, Type, Dict, TYPE_CHECKING, get
|
|
22
25
|
get_origin, get_args, Tuple, Optional, List, Union, Self
|
23
26
|
|
24
27
|
if TYPE_CHECKING:
|
25
|
-
from .datastructures import Case
|
28
|
+
from .datastructures.case import Case
|
29
|
+
from .rules import Rule
|
30
|
+
|
31
|
+
import ast
|
26
32
|
|
27
33
|
matplotlib.use("Qt5Agg") # or "Qt5Agg", depending on availability
|
28
34
|
|
29
35
|
|
36
|
+
def is_conflicting(conclusion: Any, target: Any) -> bool:
|
37
|
+
"""
|
38
|
+
:param conclusion: The conclusion to check.
|
39
|
+
:param target: The target to compare the conclusion with.
|
40
|
+
:return: Whether the conclusion is conflicting with the target by have different values for same type categories.
|
41
|
+
"""
|
42
|
+
return have_common_types(conclusion, target) and not make_set(conclusion).issubset(make_set(target))
|
43
|
+
|
44
|
+
|
45
|
+
def have_common_types(conclusion: Any, target: Any) -> bool:
|
46
|
+
"""
|
47
|
+
:param conclusion: The conclusion to check.
|
48
|
+
:param target: The target to compare the conclusion with.
|
49
|
+
:return: Whether the conclusion shares some types with the target.
|
50
|
+
"""
|
51
|
+
target_types = {type(t) for t in make_set(target)}
|
52
|
+
conclusion_types = {type(c) for c in make_set(conclusion)}
|
53
|
+
common_types = conclusion_types.intersection(target_types)
|
54
|
+
return len(common_types) > 0
|
55
|
+
|
56
|
+
|
57
|
+
def calculate_precision_and_recall(pred_cat: Dict[str, Any], target: Dict[str, Any]) -> Tuple[
|
58
|
+
List[bool], List[bool]]:
|
59
|
+
"""
|
60
|
+
:param pred_cat: The predicted category.
|
61
|
+
:param target: The target category.
|
62
|
+
:return: The precision and recall of the classifier.
|
63
|
+
"""
|
64
|
+
recall = []
|
65
|
+
precision = []
|
66
|
+
for pred_key, pred_value in pred_cat.items():
|
67
|
+
if pred_key not in target:
|
68
|
+
continue
|
69
|
+
precision.extend([v in make_set(target[pred_key]) for v in make_set(pred_value)])
|
70
|
+
for target_key, target_value in target.items():
|
71
|
+
if target_key not in pred_cat:
|
72
|
+
recall.append(False)
|
73
|
+
continue
|
74
|
+
recall.extend([v in make_set(pred_cat[target_key]) for v in make_set(target_value)])
|
75
|
+
return precision, recall
|
76
|
+
|
77
|
+
|
78
|
+
def get_rule_conclusion_as_source_code(rule: Rule, conclusion: str, parent_indent: str = "") -> str:
|
79
|
+
"""
|
80
|
+
Convert the conclusion of a rule to source code.
|
81
|
+
|
82
|
+
:param rule: The rule to get the conclusion from.
|
83
|
+
:param conclusion: The conclusion to convert to source code.
|
84
|
+
:param parent_indent: The indentation to use for the source code.
|
85
|
+
:return: The source code of the conclusion.
|
86
|
+
"""
|
87
|
+
indent = f"{parent_indent}{' ' * 4}"
|
88
|
+
if "def " in conclusion:
|
89
|
+
# This means the conclusion is a definition that should be written and then called
|
90
|
+
conclusion_lines = conclusion.split('\n')
|
91
|
+
# use regex to replace the function name
|
92
|
+
new_function_name = f"def conclusion_{id(rule)}"
|
93
|
+
conclusion_lines[0] = re.sub(r"def (\w+)", new_function_name, conclusion_lines[0])
|
94
|
+
conclusion_lines = [f"{indent}{line}" for line in conclusion_lines]
|
95
|
+
conclusion_lines.append(f"{indent}return {new_function_name.replace('def ', '')}(case)\n")
|
96
|
+
return "\n".join(conclusion_lines)
|
97
|
+
else:
|
98
|
+
raise ValueError(f"Conclusion is format is not valid, it should be a one line string or "
|
99
|
+
f"contain a function definition. Instead got:\n{conclusion}\n")
|
100
|
+
|
101
|
+
|
102
|
+
def ask_llm(prompt):
|
103
|
+
try:
|
104
|
+
response = requests.post("http://localhost:11434/api/generate", json={
|
105
|
+
"model": "codellama:7b-instruct", # or "phi"
|
106
|
+
"prompt": prompt,
|
107
|
+
"stream": False,
|
108
|
+
})
|
109
|
+
result = response.json()
|
110
|
+
return result.get("response", "").strip()
|
111
|
+
except Exception as e:
|
112
|
+
return f"❌ Local LLM error: {e}"
|
113
|
+
|
114
|
+
|
115
|
+
def get_case_attribute_type(original_case: Any, attribute_name: str,
|
116
|
+
known_value: Optional[Any] = None) -> Type:
|
117
|
+
"""
|
118
|
+
:param original_case: The case to get the attribute from.
|
119
|
+
:param attribute_name: The name of the attribute.
|
120
|
+
:param known_value: A known value of the attribute.
|
121
|
+
:return: The type of the attribute.
|
122
|
+
"""
|
123
|
+
if known_value is not None:
|
124
|
+
return type(known_value)
|
125
|
+
elif hasattr(original_case, attribute_name):
|
126
|
+
hint, origin, args = get_hint_for_attribute(attribute_name, original_case)
|
127
|
+
if origin is not None:
|
128
|
+
origin = typing_to_python_type(origin)
|
129
|
+
if origin == Union:
|
130
|
+
if len(args) == 2:
|
131
|
+
if args[1] is type(None):
|
132
|
+
return typing_to_python_type(args[0])
|
133
|
+
elif args[0] is type(None):
|
134
|
+
return typing_to_python_type(args[1])
|
135
|
+
elif len(args) == 1:
|
136
|
+
return typing_to_python_type(args[0])
|
137
|
+
else:
|
138
|
+
raise ValueError(f"Union with more than 2 types is not supported: {args}")
|
139
|
+
elif origin is not None:
|
140
|
+
return origin
|
141
|
+
if hint is not None:
|
142
|
+
return typing_to_python_type(hint)
|
143
|
+
|
144
|
+
|
145
|
+
def conclusion_to_json(conclusion):
|
146
|
+
if is_iterable(conclusion):
|
147
|
+
conclusions = {'_type': get_full_class_name(type(conclusion)), 'value': []}
|
148
|
+
for c in conclusion:
|
149
|
+
conclusions['value'].append(conclusion_to_json(c))
|
150
|
+
elif hasattr(conclusion, 'to_json'):
|
151
|
+
conclusions = conclusion.to_json()
|
152
|
+
else:
|
153
|
+
conclusions = {'_type': get_full_class_name(type(conclusion)), 'value': conclusion}
|
154
|
+
return conclusions
|
155
|
+
|
156
|
+
|
157
|
+
def contains_return_statement(source: str) -> bool:
|
158
|
+
"""
|
159
|
+
:param source: The source code to check.
|
160
|
+
:return: True if the source code contains a return statement, False otherwise.
|
161
|
+
"""
|
162
|
+
try:
|
163
|
+
tree = ast.parse(source)
|
164
|
+
for node in tree.body:
|
165
|
+
if isinstance(node, ast.Return):
|
166
|
+
return True
|
167
|
+
return False
|
168
|
+
except SyntaxError:
|
169
|
+
return False
|
170
|
+
|
171
|
+
|
172
|
+
def get_names_used(node):
|
173
|
+
return {n.id for n in ast.walk(node) if isinstance(n, ast.Name)}
|
174
|
+
|
175
|
+
|
176
|
+
def extract_dependencies(code_lines):
|
177
|
+
full_code = '\n'.join(code_lines)
|
178
|
+
tree = ast.parse(full_code)
|
179
|
+
final_stmt = tree.body[-1]
|
180
|
+
|
181
|
+
if not isinstance(final_stmt, ast.Return):
|
182
|
+
raise ValueError("Last line is not a return statement")
|
183
|
+
if final_stmt.value is None:
|
184
|
+
raise ValueError("Return statement has no value")
|
185
|
+
needed = get_names_used(final_stmt.value)
|
186
|
+
required_lines = []
|
187
|
+
line_map = {id(node): i for i, node in enumerate(tree.body)}
|
188
|
+
|
189
|
+
def handle_stmt(stmt, needed):
|
190
|
+
keep = False
|
191
|
+
if isinstance(stmt, ast.Assign):
|
192
|
+
targets = [t.id for t in stmt.targets if isinstance(t, ast.Name)]
|
193
|
+
if any(t in needed for t in targets):
|
194
|
+
needed.update(get_names_used(stmt.value))
|
195
|
+
keep = True
|
196
|
+
elif isinstance(stmt, ast.AugAssign):
|
197
|
+
if isinstance(stmt.target, ast.Name) and stmt.target.id in needed:
|
198
|
+
needed.update(get_names_used(stmt.value))
|
199
|
+
keep = True
|
200
|
+
elif isinstance(stmt, ast.FunctionDef):
|
201
|
+
if stmt.name in needed:
|
202
|
+
for n in ast.walk(stmt):
|
203
|
+
if isinstance(n, ast.Name):
|
204
|
+
needed.add(n.id)
|
205
|
+
keep = True
|
206
|
+
elif isinstance(stmt, (ast.For, ast.While, ast.If)):
|
207
|
+
# Check if any of the body statements interact with needed variables
|
208
|
+
for substmt in stmt.body + getattr(stmt, 'orelse', []):
|
209
|
+
if handle_stmt(substmt, needed):
|
210
|
+
keep = True
|
211
|
+
# Also check the condition (test or iter)
|
212
|
+
if isinstance(stmt, ast.For):
|
213
|
+
if isinstance(stmt.target, ast.Name) and stmt.target.id in needed:
|
214
|
+
keep = True
|
215
|
+
needed.update(get_names_used(stmt.iter))
|
216
|
+
elif isinstance(stmt, ast.If) or isinstance(stmt, ast.While):
|
217
|
+
needed.update(get_names_used(stmt.test))
|
218
|
+
|
219
|
+
return keep
|
220
|
+
|
221
|
+
for stmt in reversed(tree.body[:-1]):
|
222
|
+
if handle_stmt(stmt, needed):
|
223
|
+
required_lines.insert(0, code_lines[line_map[id(stmt)]])
|
224
|
+
|
225
|
+
required_lines.append(code_lines[-1]) # Always include return
|
226
|
+
return required_lines
|
227
|
+
|
228
|
+
|
30
229
|
def serialize_dataclass(obj: Any) -> Union[Dict, Any]:
|
31
230
|
"""
|
32
231
|
Recursively serialize a dataclass to a dictionary. If the dataclass contains any nested dataclasses, they will be
|
@@ -61,6 +260,7 @@ def deserialize_dataclass(data: dict) -> Any:
|
|
61
260
|
:param data: The dictionary to deserialize.
|
62
261
|
:return: The deserialized dataclass.
|
63
262
|
"""
|
263
|
+
|
64
264
|
def recursive_load(obj):
|
65
265
|
if isinstance(obj, dict) and "__dataclass__" in obj:
|
66
266
|
module_name, class_name = obj["__dataclass__"].rsplit(".", 1)
|
@@ -219,6 +419,8 @@ def get_type_from_string(type_path: str):
|
|
219
419
|
"""
|
220
420
|
module_path, class_name = type_path.rsplit(".", 1)
|
221
421
|
module = importlib.import_module(module_path)
|
422
|
+
if module == builtins and class_name == 'NoneType':
|
423
|
+
return type(None)
|
222
424
|
return getattr(module, class_name)
|
223
425
|
|
224
426
|
|
@@ -316,6 +518,8 @@ class SubclassJSONSerializer:
|
|
316
518
|
data_type = get_type_from_string(data["_type"])
|
317
519
|
if len(data) == 1:
|
318
520
|
return data_type
|
521
|
+
if data_type == NoneType:
|
522
|
+
return None
|
319
523
|
if data_type.__module__ == 'builtins':
|
320
524
|
if is_iterable(data['value']) and not isinstance(data['value'], dict):
|
321
525
|
return data_type([cls.from_json(d) for d in data['value']])
|
@@ -443,6 +647,7 @@ def table_rows_as_str(row_dict: Dict[str, Any], columns_per_row: int = 9):
|
|
443
647
|
values = [list(map(lambda i: i[1], row)) for row in all_items]
|
444
648
|
all_table_rows = []
|
445
649
|
for row_keys, row_values in zip(keys, values):
|
650
|
+
row_values = [str(v) if v is not None else "" for v in row_values]
|
446
651
|
table = tabulate([row_values], headers=row_keys, tablefmt='plain', maxcolwidths=[20] * len(row_keys))
|
447
652
|
all_table_rows.append(table)
|
448
653
|
return "\n".join(all_table_rows)
|
@@ -579,7 +784,7 @@ def get_all_subclasses(cls: Type) -> Dict[str, Type]:
|
|
579
784
|
return all_subclasses
|
580
785
|
|
581
786
|
|
582
|
-
def make_set(value: Any) -> Set:
|
787
|
+
def make_set(value: Any) -> Set[Any]:
|
583
788
|
"""
|
584
789
|
Make a set from a value.
|
585
790
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: ripple_down_rules
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.62
|
4
4
|
Summary: Implements the various versions of Ripple Down Rules (RDR) for knowledge representation and reasoning.
|
5
5
|
Author-email: Abdelrhman Bassiouny <abassiou@uni-bremen.de>
|
6
6
|
License: GNU GENERAL PUBLIC LICENSE
|
@@ -709,7 +709,7 @@ Fit the SCRDR to the data, then classify one of the data cases to check if its c
|
|
709
709
|
and render the tree to a file:
|
710
710
|
|
711
711
|
```Python
|
712
|
-
from ripple_down_rules.datastructures import CaseQuery
|
712
|
+
from ripple_down_rules.datastructures.dataclasses import CaseQuery
|
713
713
|
from ripple_down_rules.rdr import SingleClassRDR
|
714
714
|
from ripple_down_rules.datasets import load_zoo_dataset
|
715
715
|
from ripple_down_rules.utils import render_tree
|
@@ -719,12 +719,13 @@ all_cases, targets = load_zoo_dataset()
|
|
719
719
|
scrdr = SingleClassRDR()
|
720
720
|
|
721
721
|
# Fit the SCRDR to the data
|
722
|
-
case_queries = [CaseQuery(case, target
|
722
|
+
case_queries = [CaseQuery(case, 'species', type(target), True, _target=target)
|
723
|
+
for case, target in zip(all_cases[:10], targets[:10])]
|
723
724
|
scrdr.fit(case_queries, animate_tree=True)
|
724
725
|
|
725
726
|
# Render the tree to a file
|
726
727
|
render_tree(scrdr.start_rule, use_dot_exporter=True, filename="scrdr")
|
727
728
|
|
728
|
-
cat = scrdr.
|
729
|
+
cat = scrdr.classify(all_cases[50])
|
729
730
|
assert cat == targets[50]
|
730
731
|
```
|
@@ -0,0 +1,20 @@
|
|
1
|
+
ripple_down_rules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
+
ripple_down_rules/datasets.py,sha256=rCSpeFeu1gTuKESwjHUdQkPPvomI5OMRNGpbdKmHwMg,4639
|
3
|
+
ripple_down_rules/experts.py,sha256=TXU-VKhWlSE3aYrCEcXf9t9N4TBDW_T5tE4T6MdCibE,10342
|
4
|
+
ripple_down_rules/failures.py,sha256=E6ajDUsw3Blom8eVLbA7d_Qnov2conhtZ0UmpQ9ZtSE,302
|
5
|
+
ripple_down_rules/helpers.py,sha256=TvTJU0BA3dPcAyzvZFvAu7jZqsp8Lu0HAAwvuizlGjg,2018
|
6
|
+
ripple_down_rules/prompt.py,sha256=cHqhMJqubGhfGpOOY_uXv5L7PBNb64O0IBWSfiY0ui0,6682
|
7
|
+
ripple_down_rules/rdr.py,sha256=K-b1I1pBEN6rn3AZdTAxgOs6AFXXvqkGI4x8dV9nrWw,47793
|
8
|
+
ripple_down_rules/rdr_decorators.py,sha256=8SclpceI3EtrsbuukWJu8HGLh7Q1ZCgYGLX-RPlG-w0,2018
|
9
|
+
ripple_down_rules/rules.py,sha256=KTB7kPnyyU9GuZhVe9ba25-3ICdzl46r9MFduckk-_Y,16147
|
10
|
+
ripple_down_rules/utils.py,sha256=JIF99Knqzqjgny7unvEnib3sCmExqU-w9xYOSGIT86Q,32276
|
11
|
+
ripple_down_rules/datastructures/__init__.py,sha256=V2aNgf5C96Y5-IGghra3n9uiefpoIm_QdT7cc_C8cxQ,111
|
12
|
+
ripple_down_rules/datastructures/callable_expression.py,sha256=TW_u6CJfelW2CiJj9pWFpdOBNIxeEuhhsQEz_pLpFVE,9092
|
13
|
+
ripple_down_rules/datastructures/case.py,sha256=XJC6Sb67gzpEYYYYjvECJlJBRVphMScWhWMTc2kTtbc,13792
|
14
|
+
ripple_down_rules/datastructures/dataclasses.py,sha256=_aabVXsgdVUeAmgGA9K_LZpO2U5a6-htrg2Tka7qc30,5960
|
15
|
+
ripple_down_rules/datastructures/enums.py,sha256=RdyPUp9Ls1QuLmkcMMkBbCWrmXIZI4xWuM-cLPYZhR0,4666
|
16
|
+
ripple_down_rules-0.1.62.dist-info/licenses/LICENSE,sha256=ixuiBLtpoK3iv89l7ylKkg9rs2GzF9ukPH7ynZYzK5s,35148
|
17
|
+
ripple_down_rules-0.1.62.dist-info/METADATA,sha256=jPKNb7YM2Wcsy9p1otEVqyxv5apcfADU-sPin5Wu0qs,42576
|
18
|
+
ripple_down_rules-0.1.62.dist-info/WHEEL,sha256=ck4Vq1_RXyvS4Jt6SI0Vz6fyVs4GWg7AINwpsaGEgPE,91
|
19
|
+
ripple_down_rules-0.1.62.dist-info/top_level.txt,sha256=VeoLhEhyK46M1OHwoPbCQLI1EifLjChqGzhQ6WEUqeM,18
|
20
|
+
ripple_down_rules-0.1.62.dist-info/RECORD,,
|
@@ -1,20 +0,0 @@
|
|
1
|
-
ripple_down_rules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
ripple_down_rules/datasets.py,sha256=AzPtqUXuR1qLQNtRsWLsJ3gX2oIf8nIkFvmsmz7fHlw,4601
|
3
|
-
ripple_down_rules/experts.py,sha256=Xz1U1Tdq7jrFlcVuSusaMB241AG9TEs7q101i59Xijs,10683
|
4
|
-
ripple_down_rules/failures.py,sha256=E6ajDUsw3Blom8eVLbA7d_Qnov2conhtZ0UmpQ9ZtSE,302
|
5
|
-
ripple_down_rules/helpers.py,sha256=AhqerAQoCdSovJ7SdQrNtAI_hYagKpLsy2nJQGA0bl0,1062
|
6
|
-
ripple_down_rules/prompt.py,sha256=z6KddZOsNiStptgCRNh2OVHHuH6Ooa2f-nsrgJH1qJ8,6311
|
7
|
-
ripple_down_rules/rdr.py,sha256=NXVYIflUxcDzC5DDrK-l_ZT-sBmUV1ZgkznshSsJZYc,43508
|
8
|
-
ripple_down_rules/rdr_decorators.py,sha256=8SclpceI3EtrsbuukWJu8HGLh7Q1ZCgYGLX-RPlG-w0,2018
|
9
|
-
ripple_down_rules/rules.py,sha256=aM3Im4ePuFDlkuD2EKRtiVmYgoQ_sxlwcbzrDKqXAfs,14578
|
10
|
-
ripple_down_rules/utils.py,sha256=9gPnRWlLye7FettI2QRWJx8oU9z3ckwdO5jopXK8b-8,24290
|
11
|
-
ripple_down_rules/datastructures/__init__.py,sha256=zpmiYm4WkwNHaGdTIfacS7llN5d2xyU6U-saH_TpydI,103
|
12
|
-
ripple_down_rules/datastructures/callable_expression.py,sha256=ac2TaMr0hiRX928GMcr3oTQic8KXXO4syLw4KV-Iehs,10515
|
13
|
-
ripple_down_rules/datastructures/case.py,sha256=3Pl07jmYn94wdCVTaRZDmBPgyAsN1TjebvrE6-68MVU,13606
|
14
|
-
ripple_down_rules/datastructures/dataclasses.py,sha256=AI-wqNy8y9QPg6lov0P-c5b8JXemuM4X62tIRhW-Gqs,4231
|
15
|
-
ripple_down_rules/datastructures/enums.py,sha256=l0Eu-TeJ6qB2XHoJycXmUgLw-3yUebQ8SsEbW8bBZdM,4543
|
16
|
-
ripple_down_rules-0.1.21.dist-info/licenses/LICENSE,sha256=ixuiBLtpoK3iv89l7ylKkg9rs2GzF9ukPH7ynZYzK5s,35148
|
17
|
-
ripple_down_rules-0.1.21.dist-info/METADATA,sha256=Jg_qdgg-DkAqblqWTisVcv1K3H8Thr_CjdeIX3UyLgE,42519
|
18
|
-
ripple_down_rules-0.1.21.dist-info/WHEEL,sha256=pxyMxgL8-pra_rKaQ4drOZAegBVuX-G_4nRHjjgWbmo,91
|
19
|
-
ripple_down_rules-0.1.21.dist-info/top_level.txt,sha256=VeoLhEhyK46M1OHwoPbCQLI1EifLjChqGzhQ6WEUqeM,18
|
20
|
-
ripple_down_rules-0.1.21.dist-info/RECORD,,
|
File without changes
|
File without changes
|