ripple-down-rules 0.4.2__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 +78 -30
- ripple_down_rules/rules.py +9 -2
- ripple_down_rules/user_interface/gui.py +55 -16
- ripple_down_rules/utils.py +2 -1
- {ripple_down_rules-0.4.2.dist-info → ripple_down_rules-0.4.4.dist-info}/METADATA +14 -2
- {ripple_down_rules-0.4.2.dist-info → ripple_down_rules-0.4.4.dist-info}/RECORD +10 -10
- {ripple_down_rules-0.4.2.dist-info → ripple_down_rules-0.4.4.dist-info}/WHEEL +1 -1
- {ripple_down_rules-0.4.2.dist-info → ripple_down_rules-0.4.4.dist-info}/licenses/LICENSE +0 -0
- {ripple_down_rules-0.4.2.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
@@ -16,12 +16,13 @@ from .datastructures.callable_expression import CallableExpression
|
|
16
16
|
from .datastructures.case import Case, CaseAttribute, create_case
|
17
17
|
from .datastructures.dataclasses import CaseQuery
|
18
18
|
from .datastructures.enums import MCRDRMode
|
19
|
-
from .experts import Expert
|
19
|
+
from .experts import Expert, Human
|
20
20
|
from .helpers import is_matching
|
21
21
|
from .rules import Rule, SingleClassRule, MultiClassTopRule, MultiClassStopRule
|
22
|
+
from .user_interface.gui import RDRCaseViewer
|
22
23
|
from .utils import draw_tree, make_set, copy_case, \
|
23
24
|
SubclassJSONSerializer, make_list, get_type_from_string, \
|
24
|
-
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
|
25
26
|
|
26
27
|
|
27
28
|
class RippleDownRules(SubclassJSONSerializer, ABC):
|
@@ -40,13 +41,30 @@ class RippleDownRules(SubclassJSONSerializer, ABC):
|
|
40
41
|
"""
|
41
42
|
The name of the generated python file.
|
42
43
|
"""
|
44
|
+
name: Optional[str] = None
|
45
|
+
"""
|
46
|
+
The name of the classifier.
|
47
|
+
"""
|
43
48
|
|
44
|
-
def __init__(self, start_rule: Optional[Rule] = None):
|
49
|
+
def __init__(self, start_rule: Optional[Rule] = None, viewer: Optional[RDRCaseViewer] = None):
|
45
50
|
"""
|
46
51
|
:param start_rule: The starting rule for the classifier.
|
47
52
|
"""
|
48
53
|
self.start_rule = start_rule
|
49
54
|
self.fig: Optional[plt.Figure] = None
|
55
|
+
self.viewer: Optional[RDRCaseViewer] = viewer
|
56
|
+
if self.viewer is not None:
|
57
|
+
self.viewer.set_save_function(self.save)
|
58
|
+
|
59
|
+
def set_viewer(self, viewer: RDRCaseViewer):
|
60
|
+
"""
|
61
|
+
Set the viewer for the classifier.
|
62
|
+
|
63
|
+
:param viewer: The viewer to set.
|
64
|
+
"""
|
65
|
+
self.viewer = viewer
|
66
|
+
if self.viewer is not None:
|
67
|
+
self.viewer.set_save_function(self.save)
|
50
68
|
|
51
69
|
def fit(self, case_queries: List[CaseQuery],
|
52
70
|
expert: Optional[Expert] = None,
|
@@ -124,6 +142,7 @@ class RippleDownRules(SubclassJSONSerializer, ABC):
|
|
124
142
|
"""
|
125
143
|
if case_query is None:
|
126
144
|
raise ValueError("The case query cannot be None.")
|
145
|
+
self.name = case_query.attribute_name if self.name is None else self.name
|
127
146
|
if case_query.target is None:
|
128
147
|
case_query_cp = copy(case_query)
|
129
148
|
self.classify(case_query_cp.case, modify_case=True)
|
@@ -223,11 +242,15 @@ class RDRWithCodeWriter(RippleDownRules, ABC):
|
|
223
242
|
all_func_names = condition_func_names + conclusion_func_names
|
224
243
|
filepath = f"{package_dir}/{self.generated_python_defs_file_name}.py"
|
225
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)
|
226
247
|
for rule in [self.start_rule] + list(self.start_rule.descendants):
|
227
248
|
if rule.conditions is not None:
|
228
249
|
rule.conditions.user_input = functions_source[f"conditions_{rule.uid}"]
|
250
|
+
rule.conditions.scope = scope
|
229
251
|
if rule.conclusion is not None:
|
230
252
|
rule.conclusion.user_input = functions_source[f"conclusion_{rule.uid}"]
|
253
|
+
rule.conclusion.scope = scope
|
231
254
|
|
232
255
|
@abstractmethod
|
233
256
|
def write_rules_as_source_code_to_file(self, rule: Rule, file, parent_indent: str = "",
|
@@ -261,6 +284,7 @@ class RDRWithCodeWriter(RippleDownRules, ABC):
|
|
261
284
|
imports += f"from .{self.generated_python_defs_file_name} import *\n"
|
262
285
|
imports += f"from ripple_down_rules.rdr import {self.__class__.__name__}\n"
|
263
286
|
f.write(imports + "\n\n")
|
287
|
+
f.write(f"attribute_name = '{self.attribute_name}'\n")
|
264
288
|
f.write(f"conclusion_type = ({', '.join([ct.__name__ for ct in self.conclusion_type])},)\n")
|
265
289
|
f.write(f"type_ = {self.__class__.__name__}\n")
|
266
290
|
f.write(f"\n\n{func_def}")
|
@@ -370,6 +394,31 @@ class RDRWithCodeWriter(RippleDownRules, ABC):
|
|
370
394
|
"""
|
371
395
|
return self.start_rule.conclusion_name
|
372
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
|
+
|
373
422
|
|
374
423
|
class SingleClassRDR(RDRWithCodeWriter):
|
375
424
|
|
@@ -464,16 +513,12 @@ class SingleClassRDR(RDRWithCodeWriter):
|
|
464
513
|
return (type(self.default_conclusion),)
|
465
514
|
return super().conclusion_type
|
466
515
|
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
@classmethod
|
471
|
-
def _from_json(cls, data: Dict[str, Any]) -> Self:
|
516
|
+
@staticmethod
|
517
|
+
def start_rule_type() -> Type[Rule]:
|
472
518
|
"""
|
473
|
-
|
519
|
+
:return: The type of the starting rule of the RDR classifier.
|
474
520
|
"""
|
475
|
-
|
476
|
-
return cls(start_rule)
|
521
|
+
return SingleClassRule
|
477
522
|
|
478
523
|
|
479
524
|
class MultiClassRDR(RDRWithCodeWriter):
|
@@ -679,16 +724,12 @@ class MultiClassRDR(RDRWithCodeWriter):
|
|
679
724
|
"""
|
680
725
|
self.start_rule.alternative = MultiClassTopRule(conditions, conclusion, corner_case=corner_case)
|
681
726
|
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
@classmethod
|
686
|
-
def _from_json(cls, data: Dict[str, Any]) -> Self:
|
727
|
+
@staticmethod
|
728
|
+
def start_rule_type() -> Type[Rule]:
|
687
729
|
"""
|
688
|
-
|
730
|
+
:return: The type of the starting rule of the RDR classifier.
|
689
731
|
"""
|
690
|
-
|
691
|
-
return cls(start_rule)
|
732
|
+
return MultiClassTopRule
|
692
733
|
|
693
734
|
|
694
735
|
class GeneralRDR(RippleDownRules):
|
@@ -703,7 +744,8 @@ class GeneralRDR(RippleDownRules):
|
|
703
744
|
gets called when the final rule fires.
|
704
745
|
"""
|
705
746
|
|
706
|
-
def __init__(self, category_rdr_map: Optional[Dict[str, Union[SingleClassRDR, MultiClassRDR]]] = None
|
747
|
+
def __init__(self, category_rdr_map: Optional[Dict[str, Union[SingleClassRDR, MultiClassRDR]]] = None,
|
748
|
+
**kwargs):
|
707
749
|
"""
|
708
750
|
:param category_rdr_map: A map of case attribute names to ripple down rules classifiers,
|
709
751
|
where each category is a parent category that has a set of mutually exclusive (in case of SCRDR) child
|
@@ -715,18 +757,18 @@ class GeneralRDR(RippleDownRules):
|
|
715
757
|
"""
|
716
758
|
self.start_rules_dict: Dict[str, Union[SingleClassRDR, MultiClassRDR]] \
|
717
759
|
= category_rdr_map if category_rdr_map else {}
|
718
|
-
super(GeneralRDR, self).__init__()
|
760
|
+
super(GeneralRDR, self).__init__(**kwargs)
|
719
761
|
self.all_figs: List[plt.Figure] = [sr.fig for sr in self.start_rules_dict.values()]
|
720
762
|
|
721
|
-
def add_rdr(self, rdr: Union[SingleClassRDR, MultiClassRDR],
|
763
|
+
def add_rdr(self, rdr: Union[SingleClassRDR, MultiClassRDR], case_query: Optional[CaseQuery] = None):
|
722
764
|
"""
|
723
765
|
Add a ripple down rules classifier to the map of classifiers.
|
724
766
|
|
725
767
|
:param rdr: The ripple down rules classifier to add.
|
726
|
-
:param
|
768
|
+
:param case_query: The case query to add the classifier for.
|
727
769
|
"""
|
728
|
-
|
729
|
-
self.start_rules_dict[
|
770
|
+
name = case_query.attribute_name if case_query else rdr.name
|
771
|
+
self.start_rules_dict[name] = rdr
|
730
772
|
|
731
773
|
@property
|
732
774
|
def start_rule(self) -> Optional[Union[SingleClassRule, MultiClassTopRule]]:
|
@@ -810,7 +852,6 @@ class GeneralRDR(RippleDownRules):
|
|
810
852
|
:param expert: The expert to ask for differentiating features as new rule conditions.
|
811
853
|
:return: The categories that the case belongs to.
|
812
854
|
"""
|
813
|
-
|
814
855
|
case_query_cp = copy(case_query)
|
815
856
|
self.classify(case_query_cp.case, modify_case=True)
|
816
857
|
case_query_cp.update_target_value()
|
@@ -828,7 +869,7 @@ class GeneralRDR(RippleDownRules):
|
|
828
869
|
"""
|
829
870
|
if case_query.attribute_name not in self.start_rules_dict:
|
830
871
|
new_rdr = self.initialize_new_rdr_for_attribute(case_query)
|
831
|
-
self.add_rdr(new_rdr, case_query
|
872
|
+
self.add_rdr(new_rdr, case_query)
|
832
873
|
|
833
874
|
@staticmethod
|
834
875
|
def initialize_new_rdr_for_attribute(case_query: CaseQuery):
|
@@ -839,7 +880,9 @@ class GeneralRDR(RippleDownRules):
|
|
839
880
|
else MultiClassRDR()
|
840
881
|
|
841
882
|
def _to_json(self) -> Dict[str, Any]:
|
842
|
-
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}
|
843
886
|
|
844
887
|
@classmethod
|
845
888
|
def _from_json(cls, data: Dict[str, Any]) -> GeneralRDR:
|
@@ -849,7 +892,12 @@ class GeneralRDR(RippleDownRules):
|
|
849
892
|
start_rules_dict = {}
|
850
893
|
for k, v in data["start_rules"].items():
|
851
894
|
start_rules_dict[k] = get_type_from_string(v['_type']).from_json(v)
|
852
|
-
|
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
|
853
901
|
|
854
902
|
def update_from_python_file(self, package_dir: str) -> None:
|
855
903
|
"""
|
@@ -906,7 +954,7 @@ class GeneralRDR(RippleDownRules):
|
|
906
954
|
"""
|
907
955
|
:return: The default generated python file name.
|
908
956
|
"""
|
909
|
-
if self.start_rule is None or self.start_rule.
|
957
|
+
if self.start_rule is None or self.start_rule.conclusion is None:
|
910
958
|
return None
|
911
959
|
if isinstance(self.start_rule.corner_case, Case):
|
912
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"])
|
@@ -12,7 +12,7 @@ from PyQt6.QtWidgets import (
|
|
12
12
|
)
|
13
13
|
from qtconsole.inprocess import QtInProcessKernelManager
|
14
14
|
from qtconsole.rich_jupyter_widget import RichJupyterWidget
|
15
|
-
from typing_extensions import Optional, Any, List, Dict
|
15
|
+
from typing_extensions import Optional, Any, List, Dict, Callable
|
16
16
|
|
17
17
|
from ..datastructures.dataclasses import CaseQuery
|
18
18
|
from ..datastructures.enums import PromptFor
|
@@ -275,9 +275,13 @@ class RDRCaseViewer(QMainWindow):
|
|
275
275
|
main_obj: Optional[Dict[str, Any]] = None
|
276
276
|
user_input: Optional[str] = None
|
277
277
|
attributes_widget: Optional[QWidget] = None
|
278
|
+
save_function: Optional[Callable[str], None] = None
|
278
279
|
|
279
|
-
|
280
|
+
|
281
|
+
def __init__(self, parent=None, save_file: Optional[str] = None):
|
280
282
|
super().__init__(parent)
|
283
|
+
self.save_file = save_file
|
284
|
+
|
281
285
|
self.setWindowTitle("RDR Case Viewer")
|
282
286
|
|
283
287
|
self.setBaseSize(1600, 600) # or your preferred initial size
|
@@ -316,6 +320,15 @@ class RDRCaseViewer(QMainWindow):
|
|
316
320
|
main_layout.addWidget(middle_widget, stretch=2)
|
317
321
|
main_layout.addWidget(self.obj_diagram_viewer, stretch=2)
|
318
322
|
|
323
|
+
def set_save_function(self, save_function: Callable[[str], None]) -> None:
|
324
|
+
"""
|
325
|
+
Set the function to save the file.
|
326
|
+
|
327
|
+
:param save_function: The function to save the file.
|
328
|
+
"""
|
329
|
+
self.save_function = save_function
|
330
|
+
self.save_btn.clicked.connect(lambda: self.save_function(self.save_file))
|
331
|
+
|
319
332
|
def print(self, msg):
|
320
333
|
"""
|
321
334
|
Print a message to the console.
|
@@ -406,9 +419,9 @@ class RDRCaseViewer(QMainWindow):
|
|
406
419
|
self.expand_collapse_all(item.widget(), expand=False)
|
407
420
|
|
408
421
|
def expand_collapse_all(self, widget, expand=True, curr_depth=0, max_depth=2):
|
409
|
-
widget.toggle_button.setChecked(expand)
|
410
|
-
widget.toggle()
|
411
422
|
if expand and curr_depth < max_depth:
|
423
|
+
widget.toggle_button.setChecked(expand)
|
424
|
+
widget.toggle()
|
412
425
|
# do it for recursive children
|
413
426
|
for i in range(widget.content_layout.count()):
|
414
427
|
item = widget.content_layout.itemAt(i)
|
@@ -419,7 +432,14 @@ class RDRCaseViewer(QMainWindow):
|
|
419
432
|
|
420
433
|
def create_buttons_widget(self):
|
421
434
|
button_widget = QWidget()
|
422
|
-
button_widget_layout =
|
435
|
+
button_widget_layout = QVBoxLayout(button_widget)
|
436
|
+
|
437
|
+
row_1_button_widget = QWidget()
|
438
|
+
row_1_button_widget_layout = QHBoxLayout(row_1_button_widget)
|
439
|
+
row_2_button_widget = QWidget()
|
440
|
+
row_2_button_widget_layout = QHBoxLayout(row_2_button_widget)
|
441
|
+
button_widget_layout.addWidget(row_1_button_widget)
|
442
|
+
button_widget_layout.addWidget(row_2_button_widget)
|
423
443
|
|
424
444
|
accept_btn = QPushButton("Accept")
|
425
445
|
accept_btn.clicked.connect(self._accept)
|
@@ -433,9 +453,13 @@ class RDRCaseViewer(QMainWindow):
|
|
433
453
|
load_btn.clicked.connect(self._load)
|
434
454
|
load_btn.setStyleSheet(f"background-color: {color_name_to_html('b')}; color: white;") # Blue button
|
435
455
|
|
436
|
-
|
437
|
-
|
438
|
-
|
456
|
+
self.save_btn = QPushButton("Save")
|
457
|
+
self.save_btn.setStyleSheet(f"background-color: {color_name_to_html('b')}; color: white;") # Blue button
|
458
|
+
|
459
|
+
row_1_button_widget_layout.addWidget(edit_btn)
|
460
|
+
row_1_button_widget_layout.addWidget(load_btn)
|
461
|
+
row_1_button_widget_layout.addWidget(accept_btn)
|
462
|
+
row_2_button_widget_layout.addWidget(self.save_btn)
|
439
463
|
return button_widget
|
440
464
|
|
441
465
|
def _accept(self):
|
@@ -595,16 +619,31 @@ class IPythonConsole(RichJupyterWidget):
|
|
595
619
|
self._control.setPalette(palette)
|
596
620
|
|
597
621
|
# Override the stylesheet to force background and text colors
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
622
|
+
self.style_sheet = '''\
|
623
|
+
QPlainTextEdit, QTextEdit {
|
624
|
+
background-color: %(bgcolor)s;
|
625
|
+
background-clip: padding;
|
626
|
+
color: %(fgcolor)s;
|
627
|
+
selection-background-color: %(select)s;
|
628
|
+
}
|
629
|
+
.inverted {
|
630
|
+
background-color: %(fgcolor)s;
|
631
|
+
color: %(bgcolor)s;
|
632
|
+
}
|
633
|
+
.error { color: red; }
|
634
|
+
.in-prompt-number { font-weight: bold; color: %(in_prompt_number_color)s} }
|
635
|
+
.in-prompt { font-weight: bold; color: %(in_prompt_color)s }
|
636
|
+
.out-prompt-number { font-weight: bold; color: %(out_prompt_number_color)s }
|
637
|
+
.out-prompt { font-weight: bold; color: %(out_prompt_color)s }
|
638
|
+
'''%dict(
|
639
|
+
bgcolor='#0b0d0b', fgcolor='#47d9cc', select="#555",
|
640
|
+
in_prompt_number_color='lime', in_prompt_color='lime',
|
641
|
+
out_prompt_number_color='red', out_prompt_color='red'
|
642
|
+
)
|
604
643
|
|
605
644
|
# Use a dark syntax style like monokai
|
606
|
-
|
607
|
-
self.set_default_style(colors='
|
645
|
+
self.syntax_style = 'monokai'
|
646
|
+
# self.set_default_style(colors='lightbg')
|
608
647
|
|
609
648
|
self.exit_requested.connect(self.stop)
|
610
649
|
|
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
|
@@ -714,8 +714,9 @@ sudo apt-get install libxcb-cursor-dev
|
|
714
714
|
|
715
715
|
Fit the SCRDR to the data, then classify one of the data cases to check if its correct,
|
716
716
|
and render the tree to a file:
|
717
|
+
```
|
717
718
|
|
718
|
-
```
|
719
|
+
```python
|
719
720
|
from ripple_down_rules.datastructures.dataclasses import CaseQuery
|
720
721
|
from ripple_down_rules.rdr import SingleClassRDR
|
721
722
|
from ripple_down_rules.datasets import load_zoo_dataset
|
@@ -736,3 +737,14 @@ render_tree(scrdr.start_rule, use_dot_exporter=True, filename="scrdr")
|
|
736
737
|
cat = scrdr.classify(all_cases[50])
|
737
738
|
assert cat == targets[50]
|
738
739
|
```
|
740
|
+
|
741
|
+
## To Cite:
|
742
|
+
|
743
|
+
```bib
|
744
|
+
@software{bassiouny2025rdr,
|
745
|
+
author = {Bassiouny, Abdelrhman},
|
746
|
+
title = {Ripple-Down-Rules},
|
747
|
+
url = {https://github.com/AbdelrhmanBassiouny/ripple_down_rules},
|
748
|
+
version = {0.4.1},
|
749
|
+
}
|
750
|
+
```
|
@@ -1,25 +1,25 @@
|
|
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
|
13
13
|
ripple_down_rules/datastructures/dataclasses.py,sha256=GWnUF4h4zfNHSsyBIz3L9y8sLkrXRv0FK_OxzzLc8L8,8183
|
14
14
|
ripple_down_rules/datastructures/enums.py,sha256=ce7tqS0otfSTNAOwsnXlhsvIn4iW_Y_N3TNebF3YoZs,5700
|
15
15
|
ripple_down_rules/user_interface/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
16
|
-
ripple_down_rules/user_interface/gui.py,sha256=
|
16
|
+
ripple_down_rules/user_interface/gui.py,sha256=GlVUYGpMRikrd-FjYCAamdKFAuTAeiq21ZzQvjVKJ4g,27588
|
17
17
|
ripple_down_rules/user_interface/ipython_custom_shell.py,sha256=tc7ms80iPNElm9AYC6i1FlfMKkqHLT8wmOEXg_k9yAU,6275
|
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
|