ripple-down-rules 0.4.88__tar.gz → 0.5.0__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.
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/PKG-INFO +1 -1
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/src/ripple_down_rules/__init__.py +1 -1
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/src/ripple_down_rules/datastructures/callable_expression.py +20 -1
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/src/ripple_down_rules/datastructures/dataclasses.py +9 -1
- ripple_down_rules-0.5.0/src/ripple_down_rules/experts.py +317 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/src/ripple_down_rules/rdr.py +162 -76
- ripple_down_rules-0.5.0/src/ripple_down_rules/rdr_decorators.py +160 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/src/ripple_down_rules/rules.py +5 -4
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/src/ripple_down_rules/user_interface/template_file_creator.py +6 -6
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/src/ripple_down_rules/utils.py +3 -6
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/src/ripple_down_rules.egg-info/PKG-INFO +1 -1
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/src/ripple_down_rules.egg-info/SOURCES.txt +10 -2
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/test/test_json_serialization.py +6 -6
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/test/test_on_mutagenic.py +5 -5
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/test/test_rdr.py +80 -32
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/test/test_rdr_world.py +9 -7
- ripple_down_rules-0.5.0/test/test_results/datasets_physical_object_is_a_robot/__init__.py +1 -0
- ripple_down_rules-0.5.0/test/test_results/datasets_physical_object_is_a_robot/physical_object_is_a_robot_output__scrdr.py +16 -0
- ripple_down_rules-0.5.0/test/test_results/datasets_physical_object_is_a_robot/physical_object_is_a_robot_output__scrdr_defs.py +19 -0
- ripple_down_rules-0.5.0/test/test_results/datasets_physical_object_is_a_robot/physical_object_is_a_robot_rdr.py +15 -0
- {ripple_down_rules-0.4.88/test/test_results → ripple_down_rules-0.5.0/test/test_results/datasets_physical_object_is_a_robot/rdr_metadata}/datasets_physical_object_is_a_robot.json +16 -11
- ripple_down_rules-0.5.0/test/test_results/datasets_physical_object_select_objects_that_are_parts_of_robot/__init__.py +1 -0
- ripple_down_rules-0.5.0/test/test_results/datasets_physical_object_select_objects_that_are_parts_of_robot/physical_object_select_objects_that_are_parts_of_robot_output__mcrdr.py +20 -0
- ripple_down_rules-0.5.0/test/test_results/datasets_physical_object_select_objects_that_are_parts_of_robot/physical_object_select_objects_that_are_parts_of_robot_output__mcrdr_defs.py +23 -0
- ripple_down_rules-0.5.0/test/test_results/datasets_physical_object_select_objects_that_are_parts_of_robot/physical_object_select_objects_that_are_parts_of_robot_rdr.py +15 -0
- {ripple_down_rules-0.4.88/test/test_results → ripple_down_rules-0.5.0/test/test_results/datasets_physical_object_select_objects_that_are_parts_of_robot/rdr_metadata}/datasets_physical_object_select_objects_that_are_parts_of_robot.json +20 -14
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/test/test_template_file_creator.py +29 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/test/test_user_interface/test_qt_gui_inline.py +2 -1
- ripple_down_rules-0.4.88/src/ripple_down_rules/experts.py +0 -160
- ripple_down_rules-0.4.88/src/ripple_down_rules/rdr_decorators.py +0 -139
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/.github/workflows/publish-to-test-pypi.yml +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/.idea/shelf/Uncommitted_changes_before_Checkout_at_2_4_25,_6_32_PM_[Changes]/shelved.patch +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/.idea/shelf/Uncommitted_changes_before_Checkout_at_2_4_25,_6_32_PM_[Changes]1/shelved.patch +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/LICENSE +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/README.md +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/examples/readme_example.py +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/images/thinking_pr2.jpg +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/pyproject.toml +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/pytest.ini +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/requirements-dev.txt +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/requirements-gui.txt +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/requirements-viz.txt +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/requirements.txt +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/results/complete_mcrdr_extra.dot +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/results/complete_mcrdr_extra.png +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/results/complete_mcrdr_stop_only.dot +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/results/complete_mcrdr_stop_only.png +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/results/complete_mcrdr_stop_plus_rule.dot +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/results/complete_mcrdr_stop_plus_rule.png +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/results/complete_scrdr.dot +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/results/complete_scrdr.png +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/results/complete_scrdr_2.dot +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/results/complete_scrdr_2.png +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/results/complete_scrdr_3.dot +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/results/complete_scrdr_3.png +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/results/grdr_Habitat.dot +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/results/grdr_Habitat.png +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/results/grdr_Species.dot +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/results/grdr_Species.png +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/results/mcrdr_extra.dot +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/results/mcrdr_extra.png +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/results/mcrdr_extra_classify.dot +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/results/mcrdr_extra_classify.png +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/results/mcrdr_stop_plus_rule_combined.dot +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/results/mcrdr_stop_plus_rule_combined.png +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/results/partial_mcrdr_extra.dot +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/results/partial_mcrdr_extra.png +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/results/relational_scrdr_classify.dot +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/results/relational_scrdr_classify.png +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/setup.cfg +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/src/ripple_down_rules/datasets.py +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/src/ripple_down_rules/datastructures/__init__.py +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/src/ripple_down_rules/datastructures/case.py +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/src/ripple_down_rules/datastructures/enums.py +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/src/ripple_down_rules/failures.py +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/src/ripple_down_rules/helpers.py +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/src/ripple_down_rules/start-code-server.sh +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/src/ripple_down_rules/user_interface/__init__.py +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/src/ripple_down_rules/user_interface/gui.py +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/src/ripple_down_rules/user_interface/ipython_custom_shell.py +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/src/ripple_down_rules/user_interface/object_diagram.py +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/src/ripple_down_rules/user_interface/prompt.py +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/src/ripple_down_rules.egg-info/dependency_links.txt +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/src/ripple_down_rules.egg-info/requires.txt +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/src/ripple_down_rules.egg-info/top_level.txt +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/test/test_expert_answers/correct_drawer_rdr_expert_answers_fit.json +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/test/test_expert_answers/grdr_expert_answers_classify.json +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/test/test_expert_answers/grdr_expert_answers_fit.json +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/test/test_expert_answers/grdr_expert_answers_fit_extra.json +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/test/test_expert_answers/grdr_expert_answers_fit_no_targets.json +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/test/test_expert_answers/mcrdr_expert_answers_classify.json +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/test/test_expert_answers/mcrdr_expert_answers_fit_no_targets.json +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/test/test_expert_answers/mcrdr_expert_answers_stop_only_fit.json +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/test/test_expert_answers/mcrdr_extra_expert_answers_classify.json +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/test/test_expert_answers/mcrdr_extra_expert_answers_fit.json +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/test/test_expert_answers/mcrdr_multi_line_expert_answers_fit.json +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/test/test_expert_answers/mcrdr_stop_only_answers_fit.json +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/test/test_expert_answers/mcrdr_stop_plus_rule_answers_fit.json +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/test/test_expert_answers/mcrdr_stop_plus_rule_combined_expert_answers_fit.json +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/test/test_expert_answers/mcrdr_stop_plus_rule_expert_answers_fit.json +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/test/test_expert_answers/mutagenic_expert_answers.json +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/test/test_expert_answers/relational_scrdr_expert_answers_classify.json +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/test/test_expert_answers/scrdr_expert_answers_classify.json +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/test/test_expert_answers/scrdr_expert_answers_fit.json +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/test/test_expert_answers/scrdr_expert_answers_fit_no_targets.json +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/test/test_expert_answers/scrdr_multi_line_expert_answers_fit.json +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/test/test_expert_answers/scrdr_world_expert_answers_fit.json +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/test/test_expert_answers/view_rdr_expert_answers_fit.json +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/test/test_generated_rdrs/__init__.py +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/test/test_helpers/__init__.py +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/test/test_helpers/helpers.py +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/test/test_helpers/object_diagram_case_query.png +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/test/test_helpers/object_diagram_person.png +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/test/test_object_diagram.py +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/test/test_rdr_alchemy.py +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/test/test_rdr_decorators.py +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/test/test_relational_rdr.py +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/test/test_relational_rdr_alchemy.py +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/test/test_sql_model.py +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/test/test_user_interface/__init__.py +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/test/test_user_interface/test_ipython.py +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/test/test_user_interface/test_ipython_copilot.py +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/test/test_user_interface/test_pdb.py +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/test/test_user_interface/test_qt_gui.py +0 -0
- {ripple_down_rules-0.4.88 → ripple_down_rules-0.5.0}/test/test_utils.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: ripple_down_rules
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.5.0
|
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
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
import ast
|
4
4
|
import logging
|
5
|
+
import os
|
5
6
|
from _ast import AST
|
6
7
|
from enum import Enum
|
7
8
|
|
@@ -10,7 +11,7 @@ from typing_extensions import Type, Optional, Any, List, Union, Tuple, Dict, Set
|
|
10
11
|
from .case import create_case, Case
|
11
12
|
from ..utils import SubclassJSONSerializer, get_full_class_name, get_type_from_string, conclusion_to_json, is_iterable, \
|
12
13
|
build_user_input_from_conclusion, encapsulate_user_input, extract_function_source, are_results_subclass_of_types, \
|
13
|
-
make_list
|
14
|
+
make_list, get_imports_from_scope
|
14
15
|
|
15
16
|
|
16
17
|
class VariableVisitor(ast.NodeVisitor):
|
@@ -175,6 +176,24 @@ class CallableExpression(SubclassJSONSerializer):
|
|
175
176
|
return
|
176
177
|
self.user_input = self.encapsulating_function + '\n' + new_function_body
|
177
178
|
|
179
|
+
def write_to_python_file(self, file_path: str, append: bool = False):
|
180
|
+
"""
|
181
|
+
Write the callable expression to a python file.
|
182
|
+
|
183
|
+
:param file_path: The path to the file where the callable expression will be written.
|
184
|
+
:param append: If True, the callable expression will be appended to the file. If False,
|
185
|
+
the file will be overwritten.
|
186
|
+
"""
|
187
|
+
imports = '\n'.join(get_imports_from_scope(self.scope))
|
188
|
+
if append and os.path.exists(file_path):
|
189
|
+
with open(file_path, 'a') as f:
|
190
|
+
f.write('\n\n\n' + imports + '\n\n\n')
|
191
|
+
f.write(self.user_input)
|
192
|
+
else:
|
193
|
+
with open(file_path, 'w') as f:
|
194
|
+
f.write(imports + '\n\n\n')
|
195
|
+
f.write(self.user_input)
|
196
|
+
|
178
197
|
@property
|
179
198
|
def user_input(self):
|
180
199
|
"""
|
@@ -78,7 +78,15 @@ class CaseQuery:
|
|
78
78
|
"""
|
79
79
|
:return: The type of the case that the attribute belongs to.
|
80
80
|
"""
|
81
|
-
|
81
|
+
if self.is_function:
|
82
|
+
if self.function_args_type_hints is not None:
|
83
|
+
func_args = [arg for name, arg in self.function_args_type_hints.items() if name != 'return']
|
84
|
+
case_type_args = Union[tuple(func_args)]
|
85
|
+
else:
|
86
|
+
case_type_args = Any
|
87
|
+
return Dict[str, case_type_args]
|
88
|
+
else:
|
89
|
+
return self.original_case._obj_type if isinstance(self.original_case, Case) else type(self.original_case)
|
82
90
|
|
83
91
|
@property
|
84
92
|
def case(self) -> Any:
|
@@ -0,0 +1,317 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import ast
|
4
|
+
import json
|
5
|
+
import logging
|
6
|
+
import os
|
7
|
+
from abc import ABC, abstractmethod
|
8
|
+
|
9
|
+
from typing_extensions import Optional, TYPE_CHECKING, List
|
10
|
+
|
11
|
+
from .datastructures.callable_expression import CallableExpression
|
12
|
+
from .datastructures.enums import PromptFor
|
13
|
+
from .datastructures.dataclasses import CaseQuery
|
14
|
+
from .datastructures.case import show_current_and_corner_cases
|
15
|
+
from .utils import extract_imports, extract_function_source, get_imports_from_scope, encapsulate_user_input
|
16
|
+
|
17
|
+
try:
|
18
|
+
from .user_interface.gui import RDRCaseViewer
|
19
|
+
except ImportError as e:
|
20
|
+
RDRCaseViewer = None
|
21
|
+
from .user_interface.prompt import UserPrompt
|
22
|
+
|
23
|
+
if TYPE_CHECKING:
|
24
|
+
from .rdr import Rule
|
25
|
+
|
26
|
+
|
27
|
+
class Expert(ABC):
|
28
|
+
"""
|
29
|
+
The Abstract Expert class, all experts should inherit from this class.
|
30
|
+
An expert is a class that can provide differentiating features and conclusions for a case when asked.
|
31
|
+
The expert can compare a case with a corner case and provide the differentiating features and can also
|
32
|
+
provide one or multiple conclusions for a case.
|
33
|
+
"""
|
34
|
+
all_expert_answers: Optional[List] = None
|
35
|
+
"""
|
36
|
+
A list of all expert answers, used for testing purposes.
|
37
|
+
"""
|
38
|
+
use_loaded_answers: bool = False
|
39
|
+
"""
|
40
|
+
A flag to indicate if the expert should use loaded answers or not.
|
41
|
+
"""
|
42
|
+
|
43
|
+
def __init__(self, use_loaded_answers: bool = True,
|
44
|
+
append: bool = False,
|
45
|
+
answers_save_path: Optional[str] = None):
|
46
|
+
self.all_expert_answers = []
|
47
|
+
self.use_loaded_answers = use_loaded_answers
|
48
|
+
self.append = append
|
49
|
+
self.answers_save_path = answers_save_path
|
50
|
+
if answers_save_path is not None:
|
51
|
+
if use_loaded_answers:
|
52
|
+
self.load_answers(answers_save_path)
|
53
|
+
else:
|
54
|
+
os.remove(answers_save_path + '.py')
|
55
|
+
self.append = True
|
56
|
+
|
57
|
+
@abstractmethod
|
58
|
+
def ask_for_conditions(self, case_query: CaseQuery, last_evaluated_rule: Optional[Rule] = None) \
|
59
|
+
-> CallableExpression:
|
60
|
+
"""
|
61
|
+
Ask the expert to provide the differentiating features between two cases or unique features for a case
|
62
|
+
that doesn't have a corner case to compare to.
|
63
|
+
|
64
|
+
:param case_query: The case query containing the case to classify and the required target.
|
65
|
+
:param last_evaluated_rule: The last evaluated rule.
|
66
|
+
:return: The differentiating features as new rule conditions.
|
67
|
+
"""
|
68
|
+
pass
|
69
|
+
|
70
|
+
@abstractmethod
|
71
|
+
def ask_for_conclusion(self, case_query: CaseQuery) -> Optional[CallableExpression]:
|
72
|
+
"""
|
73
|
+
Ask the expert to provide a relational conclusion for the case.
|
74
|
+
|
75
|
+
:param case_query: The case query containing the case to find a conclusion for.
|
76
|
+
:return: A callable expression that can be called with a new case as an argument.
|
77
|
+
"""
|
78
|
+
|
79
|
+
def clear_answers(self, path: Optional[str] = None):
|
80
|
+
"""
|
81
|
+
Clear the expert answers.
|
82
|
+
|
83
|
+
:param path: The path to clear the answers from. If None, the answers will be cleared from the
|
84
|
+
answers_save_path attribute.
|
85
|
+
"""
|
86
|
+
if path is None and self.answers_save_path is None:
|
87
|
+
raise ValueError("No path provided to clear expert answers, either provide a path or set the "
|
88
|
+
"answers_save_path attribute.")
|
89
|
+
if path is None:
|
90
|
+
path = self.answers_save_path
|
91
|
+
if os.path.exists(path + '.json'):
|
92
|
+
os.remove(path + '.json')
|
93
|
+
if os.path.exists(path + '.py'):
|
94
|
+
os.remove(path + '.py')
|
95
|
+
self.all_expert_answers = []
|
96
|
+
|
97
|
+
def save_answers(self, path: Optional[str] = None):
|
98
|
+
"""
|
99
|
+
Save the expert answers to a file.
|
100
|
+
|
101
|
+
:param path: The path to save the answers to.
|
102
|
+
"""
|
103
|
+
if path is None and self.answers_save_path is None:
|
104
|
+
raise ValueError("No path provided to save expert answers, either provide a path or set the "
|
105
|
+
"answers_save_path attribute.")
|
106
|
+
if path is None:
|
107
|
+
path = self.answers_save_path
|
108
|
+
is_json = os.path.exists(path + '.json')
|
109
|
+
if is_json:
|
110
|
+
self._save_to_json(path)
|
111
|
+
else:
|
112
|
+
self._save_to_python(path)
|
113
|
+
|
114
|
+
def _save_to_json(self, path: str):
|
115
|
+
"""
|
116
|
+
Save the expert answers to a JSON file.
|
117
|
+
|
118
|
+
:param path: The path to save the answers to.
|
119
|
+
"""
|
120
|
+
all_answers = self.all_expert_answers
|
121
|
+
if self.append and os.path.exists(path + '.json'):
|
122
|
+
# read the file and append the new answers
|
123
|
+
with open(path + '.json', "r") as f:
|
124
|
+
old_answers = json.load(f)
|
125
|
+
all_answers = old_answers + all_answers
|
126
|
+
with open(path + '.json', "w") as f:
|
127
|
+
json.dump(all_answers, f)
|
128
|
+
|
129
|
+
def _save_to_python(self, path: str):
|
130
|
+
"""
|
131
|
+
Save the expert answers to a Python file.
|
132
|
+
|
133
|
+
:param path: The path to save the answers to.
|
134
|
+
"""
|
135
|
+
dir_name = os.path.dirname(path)
|
136
|
+
if not os.path.exists(dir_name + '/__init__.py'):
|
137
|
+
os.makedirs(dir_name, exist_ok=True)
|
138
|
+
with open(dir_name + '/__init__.py', 'w') as f:
|
139
|
+
f.write('# This is an empty init file to make the directory a package.\n')
|
140
|
+
action = 'w' if not self.append else 'a'
|
141
|
+
with open(path + '.py', action) as f:
|
142
|
+
for scope, func_source in self.all_expert_answers:
|
143
|
+
if len(scope) > 0:
|
144
|
+
imports = '\n'.join(get_imports_from_scope(scope)) + '\n\n\n'
|
145
|
+
else:
|
146
|
+
imports = ''
|
147
|
+
if func_source is not None:
|
148
|
+
func_source = encapsulate_user_input(func_source, CallableExpression.encapsulating_function)
|
149
|
+
else:
|
150
|
+
func_source = 'pass # No user input provided for this case.\n'
|
151
|
+
f.write(imports + func_source + '\n' + '\n\n\n\'===New Answer===\'\n\n\n')
|
152
|
+
|
153
|
+
def load_answers(self, path: Optional[str] = None):
|
154
|
+
"""
|
155
|
+
Load the expert answers from a file.
|
156
|
+
|
157
|
+
:param path: The path to load the answers from.
|
158
|
+
"""
|
159
|
+
if path is None and self.answers_save_path is None:
|
160
|
+
raise ValueError("No path provided to load expert answers from, either provide a path or set the "
|
161
|
+
"answers_save_path attribute.")
|
162
|
+
if path is None:
|
163
|
+
path = self.answers_save_path
|
164
|
+
is_json = os.path.exists(path + '.json')
|
165
|
+
if is_json:
|
166
|
+
self._load_answers_from_json(path)
|
167
|
+
elif os.path.exists(path + '.py'):
|
168
|
+
self._load_answers_from_python(path)
|
169
|
+
|
170
|
+
def _load_answers_from_json(self, path: str):
|
171
|
+
"""
|
172
|
+
Load the expert answers from a JSON file.
|
173
|
+
|
174
|
+
:param path: The path to load the answers from.
|
175
|
+
"""
|
176
|
+
with open(path + '.json', "r") as f:
|
177
|
+
all_answers = json.load(f)
|
178
|
+
self.all_expert_answers = [({}, answer) for answer in all_answers]
|
179
|
+
|
180
|
+
def _load_answers_from_python(self, path: str):
|
181
|
+
"""
|
182
|
+
Load the expert answers from a Python file.
|
183
|
+
|
184
|
+
:param path: The path to load the answers from.
|
185
|
+
"""
|
186
|
+
file_path = path + '.py'
|
187
|
+
with open(file_path, "r") as f:
|
188
|
+
all_answers = f.read().split('\n\n\n\'===New Answer===\'\n\n\n')
|
189
|
+
for answer in all_answers:
|
190
|
+
answer = answer.strip('\n').strip()
|
191
|
+
if 'def ' not in answer and 'pass' in answer:
|
192
|
+
self.all_expert_answers.append(({}, None))
|
193
|
+
scope = extract_imports(tree=ast.parse(answer))
|
194
|
+
func_source = list(extract_function_source(file_path, []).values())[0]
|
195
|
+
self.all_expert_answers.append((scope, func_source))
|
196
|
+
|
197
|
+
|
198
|
+
class Human(Expert):
|
199
|
+
"""
|
200
|
+
The Human Expert class, an expert that asks the human to provide differentiating features and conclusions.
|
201
|
+
"""
|
202
|
+
|
203
|
+
def __init__(self, viewer: Optional[RDRCaseViewer] = None, **kwargs):
|
204
|
+
"""
|
205
|
+
Initialize the Human expert.
|
206
|
+
|
207
|
+
:param viewer: The RDRCaseViewer instance to use for prompting the user.
|
208
|
+
"""
|
209
|
+
super().__init__(**kwargs)
|
210
|
+
self.user_prompt = UserPrompt(viewer)
|
211
|
+
|
212
|
+
def ask_for_conditions(self, case_query: CaseQuery,
|
213
|
+
last_evaluated_rule: Optional[Rule] = None) \
|
214
|
+
-> CallableExpression:
|
215
|
+
if not self.use_loaded_answers and self.user_prompt.viewer is None:
|
216
|
+
show_current_and_corner_cases(case_query.case, {case_query.attribute_name: case_query.target_value},
|
217
|
+
last_evaluated_rule=last_evaluated_rule)
|
218
|
+
return self._get_conditions(case_query)
|
219
|
+
|
220
|
+
def _get_conditions(self, case_query: CaseQuery) \
|
221
|
+
-> CallableExpression:
|
222
|
+
"""
|
223
|
+
Ask the expert to provide the differentiating features between two cases or unique features for a case
|
224
|
+
that doesn't have a corner case to compare to.
|
225
|
+
|
226
|
+
:param case_query: The case query containing the case to classify.
|
227
|
+
:return: The differentiating features as new rule conditions.
|
228
|
+
"""
|
229
|
+
user_input = None
|
230
|
+
if self.use_loaded_answers and len(self.all_expert_answers) == 0 and self.append:
|
231
|
+
self.use_loaded_answers = False
|
232
|
+
if self.use_loaded_answers:
|
233
|
+
try:
|
234
|
+
loaded_scope, user_input = self.all_expert_answers.pop(0)
|
235
|
+
except IndexError:
|
236
|
+
self.use_loaded_answers = False
|
237
|
+
if user_input is not None:
|
238
|
+
condition = CallableExpression(user_input, bool, scope=case_query.scope)
|
239
|
+
else:
|
240
|
+
user_input, condition = self.user_prompt.prompt_user_for_expression(case_query, PromptFor.Conditions)
|
241
|
+
if not self.use_loaded_answers:
|
242
|
+
self.all_expert_answers.append((condition.scope, user_input))
|
243
|
+
if self.answers_save_path is not None:
|
244
|
+
self.save_answers()
|
245
|
+
case_query.conditions = condition
|
246
|
+
return condition
|
247
|
+
|
248
|
+
def ask_for_conclusion(self, case_query: CaseQuery) -> Optional[CallableExpression]:
|
249
|
+
"""
|
250
|
+
Ask the expert to provide a conclusion for the case.
|
251
|
+
|
252
|
+
:param case_query: The case query containing the case to find a conclusion for.
|
253
|
+
:return: The conclusion for the case as a callable expression.
|
254
|
+
"""
|
255
|
+
expression: Optional[CallableExpression] = None
|
256
|
+
expert_input: Optional[str] = None
|
257
|
+
if self.use_loaded_answers and len(self.all_expert_answers) == 0 and self.append:
|
258
|
+
self.use_loaded_answers = False
|
259
|
+
if self.use_loaded_answers:
|
260
|
+
try:
|
261
|
+
loaded_scope, expert_input = self.all_expert_answers.pop(0)
|
262
|
+
if expert_input is not None:
|
263
|
+
expression = CallableExpression(expert_input, case_query.attribute_type,
|
264
|
+
scope=case_query.scope,
|
265
|
+
mutually_exclusive=case_query.mutually_exclusive)
|
266
|
+
except IndexError:
|
267
|
+
self.use_loaded_answers = False
|
268
|
+
if not self.use_loaded_answers:
|
269
|
+
if self.user_prompt.viewer is None:
|
270
|
+
show_current_and_corner_cases(case_query.case)
|
271
|
+
expert_input, expression = self.user_prompt.prompt_user_for_expression(case_query, PromptFor.Conclusion)
|
272
|
+
if expression is None:
|
273
|
+
self.all_expert_answers.append(({}, None))
|
274
|
+
else:
|
275
|
+
self.all_expert_answers.append((expression.scope, expert_input))
|
276
|
+
if self.answers_save_path is not None:
|
277
|
+
self.save_answers()
|
278
|
+
case_query.target = expression
|
279
|
+
return expression
|
280
|
+
|
281
|
+
|
282
|
+
class File(Expert):
|
283
|
+
"""
|
284
|
+
The File Expert class, an expert that reads the answers from a file.
|
285
|
+
This is used for testing purposes.
|
286
|
+
"""
|
287
|
+
|
288
|
+
def __init__(self, filename: str, **kwargs):
|
289
|
+
"""
|
290
|
+
Initialize the File expert.
|
291
|
+
|
292
|
+
:param filename: The path to the file containing the expert answers.
|
293
|
+
"""
|
294
|
+
super().__init__(**kwargs)
|
295
|
+
self.filename = filename
|
296
|
+
self.load_answers(filename)
|
297
|
+
|
298
|
+
def ask_for_conditions(self, case_query: CaseQuery,
|
299
|
+
last_evaluated_rule: Optional[Rule] = None) -> CallableExpression:
|
300
|
+
loaded_scope, user_input = self.all_expert_answers.pop(0)
|
301
|
+
if user_input:
|
302
|
+
condition = CallableExpression(user_input, bool, scope=case_query.scope)
|
303
|
+
else:
|
304
|
+
raise ValueError("No user input found in the expert answers file.")
|
305
|
+
case_query.conditions = condition
|
306
|
+
return condition
|
307
|
+
|
308
|
+
def ask_for_conclusion(self, case_query: CaseQuery) -> Optional[CallableExpression]:
|
309
|
+
loaded_scope, expert_input = self.all_expert_answers.pop(0)
|
310
|
+
if expert_input is not None:
|
311
|
+
expression = CallableExpression(expert_input, case_query.attribute_type,
|
312
|
+
scope=case_query.scope,
|
313
|
+
mutually_exclusive=case_query.mutually_exclusive)
|
314
|
+
else:
|
315
|
+
raise ValueError("No expert input found in the expert answers file.")
|
316
|
+
case_query.target = expression
|
317
|
+
return expression
|