ripple-down-rules 0.4.3__py3-none-any.whl → 0.4.4__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/experts.py +3 -2
- ripple_down_rules/rdr.py +59 -26
- ripple_down_rules/rules.py +9 -2
- ripple_down_rules/utils.py +2 -1
- {ripple_down_rules-0.4.3.dist-info → ripple_down_rules-0.4.4.dist-info}/METADATA +1 -1
- {ripple_down_rules-0.4.3.dist-info → ripple_down_rules-0.4.4.dist-info}/RECORD +9 -9
- {ripple_down_rules-0.4.3.dist-info → ripple_down_rules-0.4.4.dist-info}/WHEEL +0 -0
- {ripple_down_rules-0.4.3.dist-info → ripple_down_rules-0.4.4.dist-info}/licenses/LICENSE +0 -0
- {ripple_down_rules-0.4.3.dist-info → ripple_down_rules-0.4.4.dist-info}/top_level.txt +0 -0
ripple_down_rules/experts.py
CHANGED
@@ -103,7 +103,7 @@ class Human(Expert):
|
|
103
103
|
def ask_for_conditions(self, case_query: CaseQuery,
|
104
104
|
last_evaluated_rule: Optional[Rule] = None) \
|
105
105
|
-> CallableExpression:
|
106
|
-
if not self.use_loaded_answers:
|
106
|
+
if not self.use_loaded_answers and self.user_prompt.viewer is None:
|
107
107
|
show_current_and_corner_cases(case_query.case, {case_query.attribute_name: case_query.target_value},
|
108
108
|
last_evaluated_rule=last_evaluated_rule)
|
109
109
|
return self._get_conditions(case_query)
|
@@ -148,7 +148,8 @@ class Human(Expert):
|
|
148
148
|
scope=case_query.scope,
|
149
149
|
mutually_exclusive=case_query.mutually_exclusive)
|
150
150
|
else:
|
151
|
-
|
151
|
+
if self.user_prompt.viewer is None:
|
152
|
+
show_current_and_corner_cases(case_query.case)
|
152
153
|
expert_input, expression = self.user_prompt.prompt_user_for_expression(case_query, PromptFor.Conclusion)
|
153
154
|
self.all_expert_answers.append(expert_input)
|
154
155
|
case_query.target = expression
|
ripple_down_rules/rdr.py
CHANGED
@@ -22,7 +22,7 @@ from .rules import Rule, SingleClassRule, MultiClassTopRule, MultiClassStopRule
|
|
22
22
|
from .user_interface.gui import RDRCaseViewer
|
23
23
|
from .utils import draw_tree, make_set, copy_case, \
|
24
24
|
SubclassJSONSerializer, make_list, get_type_from_string, \
|
25
|
-
is_conflicting, update_case, get_imports_from_scope, extract_function_source
|
25
|
+
is_conflicting, update_case, get_imports_from_scope, extract_function_source, extract_imports
|
26
26
|
|
27
27
|
|
28
28
|
class RippleDownRules(SubclassJSONSerializer, ABC):
|
@@ -41,6 +41,10 @@ class RippleDownRules(SubclassJSONSerializer, ABC):
|
|
41
41
|
"""
|
42
42
|
The name of the generated python file.
|
43
43
|
"""
|
44
|
+
name: Optional[str] = None
|
45
|
+
"""
|
46
|
+
The name of the classifier.
|
47
|
+
"""
|
44
48
|
|
45
49
|
def __init__(self, start_rule: Optional[Rule] = None, viewer: Optional[RDRCaseViewer] = None):
|
46
50
|
"""
|
@@ -138,6 +142,7 @@ class RippleDownRules(SubclassJSONSerializer, ABC):
|
|
138
142
|
"""
|
139
143
|
if case_query is None:
|
140
144
|
raise ValueError("The case query cannot be None.")
|
145
|
+
self.name = case_query.attribute_name if self.name is None else self.name
|
141
146
|
if case_query.target is None:
|
142
147
|
case_query_cp = copy(case_query)
|
143
148
|
self.classify(case_query_cp.case, modify_case=True)
|
@@ -237,11 +242,15 @@ class RDRWithCodeWriter(RippleDownRules, ABC):
|
|
237
242
|
all_func_names = condition_func_names + conclusion_func_names
|
238
243
|
filepath = f"{package_dir}/{self.generated_python_defs_file_name}.py"
|
239
244
|
functions_source = extract_function_source(filepath, all_func_names, include_signature=False)
|
245
|
+
# get the scope from the imports in the file
|
246
|
+
scope = extract_imports(filepath)
|
240
247
|
for rule in [self.start_rule] + list(self.start_rule.descendants):
|
241
248
|
if rule.conditions is not None:
|
242
249
|
rule.conditions.user_input = functions_source[f"conditions_{rule.uid}"]
|
250
|
+
rule.conditions.scope = scope
|
243
251
|
if rule.conclusion is not None:
|
244
252
|
rule.conclusion.user_input = functions_source[f"conclusion_{rule.uid}"]
|
253
|
+
rule.conclusion.scope = scope
|
245
254
|
|
246
255
|
@abstractmethod
|
247
256
|
def write_rules_as_source_code_to_file(self, rule: Rule, file, parent_indent: str = "",
|
@@ -275,6 +284,7 @@ class RDRWithCodeWriter(RippleDownRules, ABC):
|
|
275
284
|
imports += f"from .{self.generated_python_defs_file_name} import *\n"
|
276
285
|
imports += f"from ripple_down_rules.rdr import {self.__class__.__name__}\n"
|
277
286
|
f.write(imports + "\n\n")
|
287
|
+
f.write(f"attribute_name = '{self.attribute_name}'\n")
|
278
288
|
f.write(f"conclusion_type = ({', '.join([ct.__name__ for ct in self.conclusion_type])},)\n")
|
279
289
|
f.write(f"type_ = {self.__class__.__name__}\n")
|
280
290
|
f.write(f"\n\n{func_def}")
|
@@ -384,6 +394,31 @@ class RDRWithCodeWriter(RippleDownRules, ABC):
|
|
384
394
|
"""
|
385
395
|
return self.start_rule.conclusion_name
|
386
396
|
|
397
|
+
def _to_json(self) -> Dict[str, Any]:
|
398
|
+
return {"start_rule": self.start_rule.to_json(), "generated_python_file_name": self.generated_python_file_name,
|
399
|
+
"name": self.name}
|
400
|
+
|
401
|
+
@classmethod
|
402
|
+
def _from_json(cls, data: Dict[str, Any]) -> Self:
|
403
|
+
"""
|
404
|
+
Create an instance of the class from a json
|
405
|
+
"""
|
406
|
+
start_rule = cls.start_rule_type().from_json(data["start_rule"])
|
407
|
+
new_rdr = cls(start_rule)
|
408
|
+
if "generated_python_file_name" in data:
|
409
|
+
new_rdr.generated_python_file_name = data["generated_python_file_name"]
|
410
|
+
if "name" in data:
|
411
|
+
new_rdr.name = data["name"]
|
412
|
+
return new_rdr
|
413
|
+
|
414
|
+
@staticmethod
|
415
|
+
@abstractmethod
|
416
|
+
def start_rule_type() -> Type[Rule]:
|
417
|
+
"""
|
418
|
+
:return: The type of the starting rule of the RDR classifier.
|
419
|
+
"""
|
420
|
+
pass
|
421
|
+
|
387
422
|
|
388
423
|
class SingleClassRDR(RDRWithCodeWriter):
|
389
424
|
|
@@ -478,16 +513,12 @@ class SingleClassRDR(RDRWithCodeWriter):
|
|
478
513
|
return (type(self.default_conclusion),)
|
479
514
|
return super().conclusion_type
|
480
515
|
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
@classmethod
|
485
|
-
def _from_json(cls, data: Dict[str, Any]) -> Self:
|
516
|
+
@staticmethod
|
517
|
+
def start_rule_type() -> Type[Rule]:
|
486
518
|
"""
|
487
|
-
|
519
|
+
:return: The type of the starting rule of the RDR classifier.
|
488
520
|
"""
|
489
|
-
|
490
|
-
return cls(start_rule)
|
521
|
+
return SingleClassRule
|
491
522
|
|
492
523
|
|
493
524
|
class MultiClassRDR(RDRWithCodeWriter):
|
@@ -693,16 +724,12 @@ class MultiClassRDR(RDRWithCodeWriter):
|
|
693
724
|
"""
|
694
725
|
self.start_rule.alternative = MultiClassTopRule(conditions, conclusion, corner_case=corner_case)
|
695
726
|
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
@classmethod
|
700
|
-
def _from_json(cls, data: Dict[str, Any]) -> Self:
|
727
|
+
@staticmethod
|
728
|
+
def start_rule_type() -> Type[Rule]:
|
701
729
|
"""
|
702
|
-
|
730
|
+
:return: The type of the starting rule of the RDR classifier.
|
703
731
|
"""
|
704
|
-
|
705
|
-
return cls(start_rule)
|
732
|
+
return MultiClassTopRule
|
706
733
|
|
707
734
|
|
708
735
|
class GeneralRDR(RippleDownRules):
|
@@ -733,15 +760,15 @@ class GeneralRDR(RippleDownRules):
|
|
733
760
|
super(GeneralRDR, self).__init__(**kwargs)
|
734
761
|
self.all_figs: List[plt.Figure] = [sr.fig for sr in self.start_rules_dict.values()]
|
735
762
|
|
736
|
-
def add_rdr(self, rdr: Union[SingleClassRDR, MultiClassRDR],
|
763
|
+
def add_rdr(self, rdr: Union[SingleClassRDR, MultiClassRDR], case_query: Optional[CaseQuery] = None):
|
737
764
|
"""
|
738
765
|
Add a ripple down rules classifier to the map of classifiers.
|
739
766
|
|
740
767
|
:param rdr: The ripple down rules classifier to add.
|
741
|
-
:param
|
768
|
+
:param case_query: The case query to add the classifier for.
|
742
769
|
"""
|
743
|
-
|
744
|
-
self.start_rules_dict[
|
770
|
+
name = case_query.attribute_name if case_query else rdr.name
|
771
|
+
self.start_rules_dict[name] = rdr
|
745
772
|
|
746
773
|
@property
|
747
774
|
def start_rule(self) -> Optional[Union[SingleClassRule, MultiClassTopRule]]:
|
@@ -825,7 +852,6 @@ class GeneralRDR(RippleDownRules):
|
|
825
852
|
:param expert: The expert to ask for differentiating features as new rule conditions.
|
826
853
|
:return: The categories that the case belongs to.
|
827
854
|
"""
|
828
|
-
|
829
855
|
case_query_cp = copy(case_query)
|
830
856
|
self.classify(case_query_cp.case, modify_case=True)
|
831
857
|
case_query_cp.update_target_value()
|
@@ -843,7 +869,7 @@ class GeneralRDR(RippleDownRules):
|
|
843
869
|
"""
|
844
870
|
if case_query.attribute_name not in self.start_rules_dict:
|
845
871
|
new_rdr = self.initialize_new_rdr_for_attribute(case_query)
|
846
|
-
self.add_rdr(new_rdr, case_query
|
872
|
+
self.add_rdr(new_rdr, case_query)
|
847
873
|
|
848
874
|
@staticmethod
|
849
875
|
def initialize_new_rdr_for_attribute(case_query: CaseQuery):
|
@@ -854,7 +880,9 @@ class GeneralRDR(RippleDownRules):
|
|
854
880
|
else MultiClassRDR()
|
855
881
|
|
856
882
|
def _to_json(self) -> Dict[str, Any]:
|
857
|
-
return {"start_rules": {
|
883
|
+
return {"start_rules": {name: rdr.to_json() for name, rdr in self.start_rules_dict.items()}
|
884
|
+
, "generated_python_file_name": self.generated_python_file_name,
|
885
|
+
"name": self.name}
|
858
886
|
|
859
887
|
@classmethod
|
860
888
|
def _from_json(cls, data: Dict[str, Any]) -> GeneralRDR:
|
@@ -864,7 +892,12 @@ class GeneralRDR(RippleDownRules):
|
|
864
892
|
start_rules_dict = {}
|
865
893
|
for k, v in data["start_rules"].items():
|
866
894
|
start_rules_dict[k] = get_type_from_string(v['_type']).from_json(v)
|
867
|
-
|
895
|
+
new_rdr = cls(start_rules_dict)
|
896
|
+
if "generated_python_file_name" in data:
|
897
|
+
new_rdr.generated_python_file_name = data["generated_python_file_name"]
|
898
|
+
if "name" in data:
|
899
|
+
new_rdr.name = data["name"]
|
900
|
+
return new_rdr
|
868
901
|
|
869
902
|
def update_from_python_file(self, package_dir: str) -> None:
|
870
903
|
"""
|
@@ -921,7 +954,7 @@ class GeneralRDR(RippleDownRules):
|
|
921
954
|
"""
|
922
955
|
:return: The default generated python file name.
|
923
956
|
"""
|
924
|
-
if self.start_rule is None or self.start_rule.
|
957
|
+
if self.start_rule is None or self.start_rule.conclusion is None:
|
925
958
|
return None
|
926
959
|
if isinstance(self.start_rule.corner_case, Case):
|
927
960
|
name = self.start_rule.corner_case._name
|
ripple_down_rules/rules.py
CHANGED
@@ -5,6 +5,7 @@ from abc import ABC, abstractmethod
|
|
5
5
|
from uuid import uuid4
|
6
6
|
|
7
7
|
from anytree import NodeMixin
|
8
|
+
from rospy import logwarn, logdebug
|
8
9
|
from sqlalchemy.orm import DeclarativeBase as SQLTable
|
9
10
|
from typing_extensions import List, Optional, Self, Union, Dict, Any, Tuple
|
10
11
|
|
@@ -151,7 +152,8 @@ class Rule(NodeMixin, SubclassJSONSerializer, ABC):
|
|
151
152
|
json_serialization = {"conditions": self.conditions.to_json(),
|
152
153
|
"conclusion": conclusion_to_json(self.conclusion),
|
153
154
|
"parent": self.parent.json_serialization if self.parent else None,
|
154
|
-
"corner_case": SubclassJSONSerializer.to_json_static(self.corner_case)
|
155
|
+
"corner_case": SubclassJSONSerializer.to_json_static(self.corner_case)
|
156
|
+
if self.corner_case else None,
|
155
157
|
"conclusion_name": self.conclusion_name,
|
156
158
|
"weight": self.weight,
|
157
159
|
"uid": self.uid}
|
@@ -159,10 +161,15 @@ class Rule(NodeMixin, SubclassJSONSerializer, ABC):
|
|
159
161
|
|
160
162
|
@classmethod
|
161
163
|
def _from_json(cls, data: Dict[str, Any]) -> Rule:
|
164
|
+
try:
|
165
|
+
corner_case = Case.from_json(data["corner_case"])
|
166
|
+
except Exception as e:
|
167
|
+
logdebug("Failed to load corner case from json, setting it to None.")
|
168
|
+
corner_case = None
|
162
169
|
loaded_rule = cls(conditions=CallableExpression.from_json(data["conditions"]),
|
163
170
|
conclusion=CallableExpression.from_json(data["conclusion"]),
|
164
171
|
parent=cls.from_json(data["parent"]),
|
165
|
-
corner_case=
|
172
|
+
corner_case=corner_case,
|
166
173
|
conclusion_name=data["conclusion_name"],
|
167
174
|
weight=data["weight"],
|
168
175
|
uid=data["uid"])
|
ripple_down_rules/utils.py
CHANGED
@@ -22,6 +22,7 @@ import requests
|
|
22
22
|
from anytree import Node, RenderTree
|
23
23
|
from anytree.exporter import DotExporter
|
24
24
|
from matplotlib import pyplot as plt
|
25
|
+
from rospy import logwarn
|
25
26
|
from sqlalchemy import MetaData, inspect
|
26
27
|
from sqlalchemy.orm import Mapped, registry, class_mapper, DeclarativeBase as SQLTable, Session
|
27
28
|
from tabulate import tabulate
|
@@ -129,7 +130,7 @@ def extract_imports(file_path):
|
|
129
130
|
module = importlib.import_module(module_name)
|
130
131
|
scope[asname] = getattr(module, name)
|
131
132
|
except (ImportError, AttributeError) as e:
|
132
|
-
|
133
|
+
logwarn(f"Could not import {module_name}: {e} while extracting imports from {file_path}")
|
133
134
|
|
134
135
|
return scope
|
135
136
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: ripple_down_rules
|
3
|
-
Version: 0.4.
|
3
|
+
Version: 0.4.4
|
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
|
@@ -1,12 +1,12 @@
|
|
1
1
|
ripple_down_rules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
2
|
ripple_down_rules/datasets.py,sha256=fJbZ7V-UUYTu5XVVpFinTbuzN3YePCnUB01L3AyZVM8,6837
|
3
|
-
ripple_down_rules/experts.py,sha256=
|
3
|
+
ripple_down_rules/experts.py,sha256=dEZKv58sdicDByj0T0bu0q6coIBMce6o4t9gJICQy1k,6556
|
4
4
|
ripple_down_rules/failures.py,sha256=E6ajDUsw3Blom8eVLbA7d_Qnov2conhtZ0UmpQ9ZtSE,302
|
5
5
|
ripple_down_rules/helpers.py,sha256=TvTJU0BA3dPcAyzvZFvAu7jZqsp8Lu0HAAwvuizlGjg,2018
|
6
|
-
ripple_down_rules/rdr.py,sha256=
|
6
|
+
ripple_down_rules/rdr.py,sha256=jxMvkyQ_2jdyxa5aKmvGRHvD-IJBKjsyOpxvvJbFLPE,45518
|
7
7
|
ripple_down_rules/rdr_decorators.py,sha256=VdmE0JrE8j89b6Af1R1tLZiKfy3h1VCvhAUefN_FLLQ,6753
|
8
|
-
ripple_down_rules/rules.py,sha256=
|
9
|
-
ripple_down_rules/utils.py,sha256=
|
8
|
+
ripple_down_rules/rules.py,sha256=PLNaRLn5tQbq7gNsAe-n9hTjKC80p_7MbfkZWgDxLBg,17474
|
9
|
+
ripple_down_rules/utils.py,sha256=AEV7hlnUIgMKN70gNax6q5l01oqtMM1lYBUH936XvMQ,49156
|
10
10
|
ripple_down_rules/datastructures/__init__.py,sha256=V2aNgf5C96Y5-IGghra3n9uiefpoIm_QdT7cc_C8cxQ,111
|
11
11
|
ripple_down_rules/datastructures/callable_expression.py,sha256=jA7424_mWPbOoPICW3eLMX0-ypxnsW6gOqxrJ7JpDbE,11610
|
12
12
|
ripple_down_rules/datastructures/case.py,sha256=oC8OSdhXvHE-Zx1IIQlad-fsKzQQqr6MZBW24c-dbeU,15191
|
@@ -18,8 +18,8 @@ ripple_down_rules/user_interface/ipython_custom_shell.py,sha256=tc7ms80iPNElm9AY
|
|
18
18
|
ripple_down_rules/user_interface/object_diagram.py,sha256=84JlEH0nQmtGmP8Su5iRX3ZfqByYHbVwd0BQYYPuckY,4436
|
19
19
|
ripple_down_rules/user_interface/prompt.py,sha256=tQyIrRno1YuS3K0c4p48FVTdDJGG0HCE63WHVKpSJ1I,7976
|
20
20
|
ripple_down_rules/user_interface/template_file_creator.py,sha256=PegwW_g8UScjEF2GGe1eV0VoWUzxPhA27L9ExbL7Y_E,12610
|
21
|
-
ripple_down_rules-0.4.
|
22
|
-
ripple_down_rules-0.4.
|
23
|
-
ripple_down_rules-0.4.
|
24
|
-
ripple_down_rules-0.4.
|
25
|
-
ripple_down_rules-0.4.
|
21
|
+
ripple_down_rules-0.4.4.dist-info/licenses/LICENSE,sha256=ixuiBLtpoK3iv89l7ylKkg9rs2GzF9ukPH7ynZYzK5s,35148
|
22
|
+
ripple_down_rules-0.4.4.dist-info/METADATA,sha256=I5dE8K62oQezDMIBgT91v7m93VbyvU84tsl7nckurXo,42875
|
23
|
+
ripple_down_rules-0.4.4.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
|
24
|
+
ripple_down_rules-0.4.4.dist-info/top_level.txt,sha256=VeoLhEhyK46M1OHwoPbCQLI1EifLjChqGzhQ6WEUqeM,18
|
25
|
+
ripple_down_rules-0.4.4.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|