ripple-down-rules 0.0.14__py3-none-any.whl → 0.0.15__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/rdr.py +97 -13
- ripple_down_rules/rules.py +5 -1
- {ripple_down_rules-0.0.14.dist-info → ripple_down_rules-0.0.15.dist-info}/METADATA +1 -1
- {ripple_down_rules-0.0.14.dist-info → ripple_down_rules-0.0.15.dist-info}/RECORD +7 -7
- {ripple_down_rules-0.0.14.dist-info → ripple_down_rules-0.0.15.dist-info}/WHEEL +0 -0
- {ripple_down_rules-0.0.14.dist-info → ripple_down_rules-0.0.15.dist-info}/licenses/LICENSE +0 -0
- {ripple_down_rules-0.0.14.dist-info → ripple_down_rules-0.0.15.dist-info}/top_level.txt +0 -0
ripple_down_rules/rdr.py
CHANGED
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
import importlib
|
4
4
|
from abc import ABC, abstractmethod
|
5
|
-
from copy import copy
|
5
|
+
from copy import copy
|
6
6
|
from types import ModuleType
|
7
7
|
|
8
8
|
from matplotlib import pyplot as plt
|
@@ -120,7 +120,8 @@ class RippleDownRules(SubclassJSONSerializer, ABC):
|
|
120
120
|
plt.show()
|
121
121
|
|
122
122
|
@staticmethod
|
123
|
-
def calculate_precision_and_recall(pred_cat: List[CaseAttribute], target: List[CaseAttribute]) -> Tuple[
|
123
|
+
def calculate_precision_and_recall(pred_cat: List[CaseAttribute], target: List[CaseAttribute]) -> Tuple[
|
124
|
+
List[bool], List[bool]]:
|
124
125
|
"""
|
125
126
|
:param pred_cat: The predicted category.
|
126
127
|
:param target: The target category.
|
@@ -194,16 +195,17 @@ class RDRWithCodeWriter(RippleDownRules, ABC):
|
|
194
195
|
|
195
196
|
:param file_path: The path to the file to write the source code to.
|
196
197
|
"""
|
197
|
-
func_def = f"def classify(case: {self.case_type.__name__}) -> {self.
|
198
|
+
func_def = f"def classify(case: {self.case_type.__name__}) -> {self.conclusion_type_hint}:\n"
|
198
199
|
with open(file_path + f"/{self.generated_python_file_name}.py", "w") as f:
|
199
200
|
f.write(self._get_imports() + "\n\n")
|
200
201
|
f.write(func_def)
|
201
|
-
f.write(f"{' '*4}if not isinstance(case, Case):\n"
|
202
|
-
f"{' '*4} case = create_case(case, recursion_idx=3)\n""")
|
202
|
+
f.write(f"{' ' * 4}if not isinstance(case, Case):\n"
|
203
|
+
f"{' ' * 4} case = create_case(case, recursion_idx=3)\n""")
|
203
204
|
self.write_rules_as_source_code_to_file(self.start_rule, f, " " * 4)
|
204
205
|
|
206
|
+
@property
|
205
207
|
@abstractmethod
|
206
|
-
def
|
208
|
+
def conclusion_type_hint(self) -> str:
|
207
209
|
"""
|
208
210
|
:return: The type hint of the conclusion of the rdr as a string.
|
209
211
|
"""
|
@@ -254,6 +256,8 @@ class RDRWithCodeWriter(RippleDownRules, ABC):
|
|
254
256
|
if isinstance(self.start_rule.conclusion, CallableExpression):
|
255
257
|
return self.start_rule.conclusion.conclusion_type
|
256
258
|
else:
|
259
|
+
if isinstance(self.start_rule.conclusion, set):
|
260
|
+
return type(list(self.start_rule.conclusion)[0])
|
257
261
|
return type(self.start_rule.conclusion)
|
258
262
|
|
259
263
|
|
@@ -316,7 +320,8 @@ class SingleClassRDR(RDRWithCodeWriter):
|
|
316
320
|
if rule.alternative:
|
317
321
|
self.write_rules_as_source_code_to_file(rule.alternative, file, parent_indent)
|
318
322
|
|
319
|
-
|
323
|
+
@property
|
324
|
+
def conclusion_type_hint(self) -> str:
|
320
325
|
return self.conclusion_type.__name__
|
321
326
|
|
322
327
|
def _to_json(self) -> Dict[str, Any]:
|
@@ -427,6 +432,8 @@ class MultiClassRDR(RDRWithCodeWriter):
|
|
427
432
|
file, parent_indent: str = ""):
|
428
433
|
"""
|
429
434
|
Write the rules as source code to a file.
|
435
|
+
|
436
|
+
:
|
430
437
|
"""
|
431
438
|
if rule == self.start_rule:
|
432
439
|
file.write(f"{parent_indent}conclusions = set()\n")
|
@@ -435,14 +442,15 @@ class MultiClassRDR(RDRWithCodeWriter):
|
|
435
442
|
conclusion_indent = parent_indent
|
436
443
|
if hasattr(rule, "refinement") and rule.refinement:
|
437
444
|
self.write_rules_as_source_code_to_file(rule.refinement, file, parent_indent + " ")
|
438
|
-
conclusion_indent = parent_indent + " "*4
|
445
|
+
conclusion_indent = parent_indent + " " * 4
|
439
446
|
file.write(f"{conclusion_indent}else:\n")
|
440
447
|
file.write(rule.write_conclusion_as_source_code(conclusion_indent))
|
441
448
|
|
442
449
|
if rule.alternative:
|
443
450
|
self.write_rules_as_source_code_to_file(rule.alternative, file, parent_indent)
|
444
451
|
|
445
|
-
|
452
|
+
@property
|
453
|
+
def conclusion_type_hint(self) -> str:
|
446
454
|
return f"Set[{self.conclusion_type.__name__}]"
|
447
455
|
|
448
456
|
def _get_imports(self) -> str:
|
@@ -654,7 +662,7 @@ class GeneralRDR(RippleDownRules):
|
|
654
662
|
@start_rule.setter
|
655
663
|
def start_rule(self, value: Union[SingleClassRDR, MultiClassRDR]):
|
656
664
|
if value:
|
657
|
-
self.start_rules_dict[
|
665
|
+
self.start_rules_dict[value.conclusion_type] = value
|
658
666
|
|
659
667
|
@property
|
660
668
|
def start_rules(self) -> List[Union[SingleClassRule, MultiClassTopRule]]:
|
@@ -665,6 +673,19 @@ class GeneralRDR(RippleDownRules):
|
|
665
673
|
Classify a case by going through all RDRs and adding the categories that are classified, and then restarting
|
666
674
|
the classification until no more categories can be added.
|
667
675
|
|
676
|
+
:param case: The case to classify.
|
677
|
+
:return: The categories that the case belongs to.
|
678
|
+
"""
|
679
|
+
return self._classify(self.start_rules_dict, case)
|
680
|
+
|
681
|
+
@staticmethod
|
682
|
+
def _classify(classifiers_dict: Dict[Type, Union[ModuleType, RippleDownRules]],
|
683
|
+
case: Union[Case, SQLTable]) -> Optional[List[Any]]:
|
684
|
+
"""
|
685
|
+
Classify a case by going through all classifiers and adding the categories that are classified,
|
686
|
+
and then restarting the classification until no more categories can be added.
|
687
|
+
|
688
|
+
:param classifiers_dict: A dictionary mapping conclusion types to the classifiers that produce them.
|
668
689
|
:param case: The case to classify.
|
669
690
|
:return: The categories that the case belongs to.
|
670
691
|
"""
|
@@ -672,8 +693,8 @@ class GeneralRDR(RippleDownRules):
|
|
672
693
|
case_cp = copy_case(case)
|
673
694
|
while True:
|
674
695
|
added_attributes = False
|
675
|
-
for cat_type, rdr in
|
676
|
-
if
|
696
|
+
for cat_type, rdr in classifiers_dict.items():
|
697
|
+
if GeneralRDR.case_has_conclusion(case_cp, cat_type):
|
677
698
|
continue
|
678
699
|
pred_atts = rdr.classify(case_cp)
|
679
700
|
if pred_atts:
|
@@ -733,7 +754,7 @@ class GeneralRDR(RippleDownRules):
|
|
733
754
|
conclusions = rdr.classify(case_cp)
|
734
755
|
else:
|
735
756
|
conclusions = self.start_rules_dict[target_type].fit_case(case_query_cp,
|
736
|
-
|
757
|
+
expert, **kwargs)
|
737
758
|
self.update_case(case_cp, conclusions, rdr_type)
|
738
759
|
|
739
760
|
return self.classify(case)
|
@@ -815,3 +836,66 @@ class GeneralRDR(RippleDownRules):
|
|
815
836
|
k = get_type_from_string(k)
|
816
837
|
start_rules_dict[k] = get_type_from_string(v['_type']).from_json(v)
|
817
838
|
return cls(start_rules_dict)
|
839
|
+
|
840
|
+
def write_to_python_file(self, file_path: str):
|
841
|
+
"""
|
842
|
+
Write the tree of rules as source code to a file.
|
843
|
+
|
844
|
+
:param file_path: The path to the file to write the source code to.
|
845
|
+
"""
|
846
|
+
for rdr in self.start_rules_dict.values():
|
847
|
+
rdr.write_to_python_file(file_path)
|
848
|
+
func_def = f"def classify(case: {self.case_type.__name__}) -> {self.conclusion_type_hint}:\n"
|
849
|
+
with open(file_path + f"/{self.generated_python_file_name}.py", "w") as f:
|
850
|
+
f.write(self._get_imports(file_path) + "\n\n")
|
851
|
+
f.write("classifiers_dict = dict()\n")
|
852
|
+
for t, rdr in self.start_rules_dict.items():
|
853
|
+
f.write(f"classifiers_dict[{t.__name__}] = {t.__name__.lower()}_classifier\n")
|
854
|
+
f.write("\n\n")
|
855
|
+
f.write(func_def)
|
856
|
+
f.write(f"{' ' * 4}if not isinstance(case, Case):\n"
|
857
|
+
f"{' ' * 4} case = create_case(case, recursion_idx=3)\n""")
|
858
|
+
f.write(f"{' ' * 4}return GeneralRDR._classify(classifiers_dict, case)\n")
|
859
|
+
|
860
|
+
@property
|
861
|
+
def case_type(self) -> Type:
|
862
|
+
"""
|
863
|
+
:return: The type of the case (input) to the RDR classifier.
|
864
|
+
"""
|
865
|
+
if isinstance(self.start_rule.corner_case, Case):
|
866
|
+
return self.start_rule.corner_case._type
|
867
|
+
else:
|
868
|
+
return type(self.start_rule.corner_case)
|
869
|
+
|
870
|
+
def get_rdr_classifier_from_python_file(self, file_path: str):
|
871
|
+
"""
|
872
|
+
:param file_path: The path to the file that contains the RDR classifier function.
|
873
|
+
:return: The module that contains the rdr classifier function.
|
874
|
+
"""
|
875
|
+
return importlib.import_module(f"{file_path.strip('./')}.{self.generated_python_file_name}").classify
|
876
|
+
|
877
|
+
@property
|
878
|
+
def generated_python_file_name(self) -> str:
|
879
|
+
return f"{self.case_type.__name__.lower()}_grdr"
|
880
|
+
|
881
|
+
@property
|
882
|
+
def conclusion_type_hint(self) -> str:
|
883
|
+
return f"List[Union[{', '.join([rdr.conclusion_type_hint for rdr in self.start_rules_dict.values()])}]]"
|
884
|
+
|
885
|
+
def _get_imports(self, file_path: str) -> str:
|
886
|
+
imports = ""
|
887
|
+
# add type hints
|
888
|
+
imports += f"from typing_extensions import List, Union, Set\n"
|
889
|
+
# import rdr type
|
890
|
+
imports += f"from ripple_down_rules.rdr import GeneralRDR\n"
|
891
|
+
# add case type
|
892
|
+
imports += f"from ripple_down_rules.datastructures import Case, create_case\n"
|
893
|
+
imports += f"from {self.case_type.__module__} import {self.case_type.__name__}\n"
|
894
|
+
# add conclusion type imports
|
895
|
+
for conclusion_type in self.start_rules_dict.keys():
|
896
|
+
imports += f"from {conclusion_type.__module__} import {conclusion_type.__name__}\n"
|
897
|
+
# add rdr python generated functions.
|
898
|
+
for conclusion_type, rdr in self.start_rules_dict.items():
|
899
|
+
imports += (f"from {file_path.strip('./')}"
|
900
|
+
f" import {rdr.generated_python_file_name} as {conclusion_type.__name__.lower()}_classifier\n")
|
901
|
+
return imports
|
ripple_down_rules/rules.py
CHANGED
@@ -334,7 +334,11 @@ class MultiClassTopRule(Rule, HasRefinementRule, HasAlternativeRule):
|
|
334
334
|
return loaded_rule
|
335
335
|
|
336
336
|
def _conclusion_source_code_clause(self, conclusion: Any, parent_indent: str = "") -> str:
|
337
|
-
|
337
|
+
if is_iterable(conclusion):
|
338
|
+
conclusion_str = "{" + ", ".join([str(c) for c in conclusion]) + "}"
|
339
|
+
else:
|
340
|
+
conclusion_str = "{" + str(conclusion) + "}"
|
341
|
+
statement = f"{parent_indent}{' ' * 4}conclusions.update({conclusion_str})\n"
|
338
342
|
if self.alternative is None:
|
339
343
|
statement += f"{parent_indent}return conclusions\n"
|
340
344
|
return statement
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: ripple_down_rules
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.15
|
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
|
@@ -3,16 +3,16 @@ ripple_down_rules/datasets.py,sha256=qYX7IF7ACm0VRbaKfEgQ32j0YbUUyt2GfGU5Lo42CqI
|
|
3
3
|
ripple_down_rules/experts.py,sha256=wg1uY0ox9dUMR4s1RdGjzpX1_WUqnCa060r1U9lrKYI,11214
|
4
4
|
ripple_down_rules/failures.py,sha256=E6ajDUsw3Blom8eVLbA7d_Qnov2conhtZ0UmpQ9ZtSE,302
|
5
5
|
ripple_down_rules/prompt.py,sha256=QAmxg4ssrGUAlK7lbyKw2nuRczTZColpjc9uMC1ts3I,4210
|
6
|
-
ripple_down_rules/rdr.py,sha256=
|
7
|
-
ripple_down_rules/rules.py,sha256=
|
6
|
+
ripple_down_rules/rdr.py,sha256=am57_cmN2ge_eKGQLIAyd5iH4iSC15QrFHmdmwwM_uY,41947
|
7
|
+
ripple_down_rules/rules.py,sha256=MUZv42WPOB73rzw6Um2F_q0woRFG4db4yYB6R-qMpKA,14239
|
8
8
|
ripple_down_rules/utils.py,sha256=32gU9NHIcxQpfS4zPSAtn63np5SdVvS9VuyoYiyKZbc,18664
|
9
9
|
ripple_down_rules/datastructures/__init__.py,sha256=zpmiYm4WkwNHaGdTIfacS7llN5d2xyU6U-saH_TpydI,103
|
10
10
|
ripple_down_rules/datastructures/callable_expression.py,sha256=TN6bi4VYjyLlSLTEA3dRo5ENfEdQYc8Fjj5nbnsz-C0,9058
|
11
11
|
ripple_down_rules/datastructures/case.py,sha256=0EV69VBzsBTHQ3Ots7fkP09g1tyJ20xW9kSm0nZGy40,14071
|
12
12
|
ripple_down_rules/datastructures/dataclasses.py,sha256=EVQ1jBKW7K7q7_JNgikHX9fm3EmQQKA74sNjEQ4rXn8,2449
|
13
13
|
ripple_down_rules/datastructures/enums.py,sha256=l0Eu-TeJ6qB2XHoJycXmUgLw-3yUebQ8SsEbW8bBZdM,4543
|
14
|
-
ripple_down_rules-0.0.
|
15
|
-
ripple_down_rules-0.0.
|
16
|
-
ripple_down_rules-0.0.
|
17
|
-
ripple_down_rules-0.0.
|
18
|
-
ripple_down_rules-0.0.
|
14
|
+
ripple_down_rules-0.0.15.dist-info/licenses/LICENSE,sha256=ixuiBLtpoK3iv89l7ylKkg9rs2GzF9ukPH7ynZYzK5s,35148
|
15
|
+
ripple_down_rules-0.0.15.dist-info/METADATA,sha256=JOCvhnY9h7adkNGnR9uX0j_d7oQcIIBTY8kCpZ2VsD4,42519
|
16
|
+
ripple_down_rules-0.0.15.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
17
|
+
ripple_down_rules-0.0.15.dist-info/top_level.txt,sha256=VeoLhEhyK46M1OHwoPbCQLI1EifLjChqGzhQ6WEUqeM,18
|
18
|
+
ripple_down_rules-0.0.15.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|