ripple-down-rules 0.1.65__tar.gz → 0.1.67__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.
Files changed (32) hide show
  1. {ripple_down_rules-0.1.65 → ripple_down_rules-0.1.67}/PKG-INFO +1 -1
  2. {ripple_down_rules-0.1.65 → ripple_down_rules-0.1.67}/pyproject.toml +1 -1
  3. {ripple_down_rules-0.1.65 → ripple_down_rules-0.1.67}/src/ripple_down_rules/datasets.py +1 -1
  4. {ripple_down_rules-0.1.65 → ripple_down_rules-0.1.67}/src/ripple_down_rules/datastructures/callable_expression.py +17 -9
  5. {ripple_down_rules-0.1.65 → ripple_down_rules-0.1.67}/src/ripple_down_rules/datastructures/dataclasses.py +1 -1
  6. {ripple_down_rules-0.1.65 → ripple_down_rules-0.1.67}/src/ripple_down_rules/prompt.py +3 -9
  7. {ripple_down_rules-0.1.65 → ripple_down_rules-0.1.67}/src/ripple_down_rules/rdr.py +14 -14
  8. {ripple_down_rules-0.1.65 → ripple_down_rules-0.1.67}/src/ripple_down_rules/rules.py +36 -36
  9. {ripple_down_rules-0.1.65 → ripple_down_rules-0.1.67}/src/ripple_down_rules/utils.py +57 -5
  10. {ripple_down_rules-0.1.65 → ripple_down_rules-0.1.67}/src/ripple_down_rules.egg-info/PKG-INFO +1 -1
  11. {ripple_down_rules-0.1.65 → ripple_down_rules-0.1.67}/test/test_relational_rdr.py +5 -4
  12. {ripple_down_rules-0.1.65 → ripple_down_rules-0.1.67}/test/test_relational_rdr_alchemy.py +5 -4
  13. {ripple_down_rules-0.1.65 → ripple_down_rules-0.1.67}/LICENSE +0 -0
  14. {ripple_down_rules-0.1.65 → ripple_down_rules-0.1.67}/README.md +0 -0
  15. {ripple_down_rules-0.1.65 → ripple_down_rules-0.1.67}/setup.cfg +0 -0
  16. {ripple_down_rules-0.1.65 → ripple_down_rules-0.1.67}/src/ripple_down_rules/__init__.py +0 -0
  17. {ripple_down_rules-0.1.65 → ripple_down_rules-0.1.67}/src/ripple_down_rules/datastructures/__init__.py +0 -0
  18. {ripple_down_rules-0.1.65 → ripple_down_rules-0.1.67}/src/ripple_down_rules/datastructures/case.py +0 -0
  19. {ripple_down_rules-0.1.65 → ripple_down_rules-0.1.67}/src/ripple_down_rules/datastructures/enums.py +0 -0
  20. {ripple_down_rules-0.1.65 → ripple_down_rules-0.1.67}/src/ripple_down_rules/experts.py +0 -0
  21. {ripple_down_rules-0.1.65 → ripple_down_rules-0.1.67}/src/ripple_down_rules/failures.py +0 -0
  22. {ripple_down_rules-0.1.65 → ripple_down_rules-0.1.67}/src/ripple_down_rules/helpers.py +0 -0
  23. {ripple_down_rules-0.1.65 → ripple_down_rules-0.1.67}/src/ripple_down_rules/rdr_decorators.py +0 -0
  24. {ripple_down_rules-0.1.65 → ripple_down_rules-0.1.67}/src/ripple_down_rules.egg-info/SOURCES.txt +0 -0
  25. {ripple_down_rules-0.1.65 → ripple_down_rules-0.1.67}/src/ripple_down_rules.egg-info/dependency_links.txt +0 -0
  26. {ripple_down_rules-0.1.65 → ripple_down_rules-0.1.67}/src/ripple_down_rules.egg-info/top_level.txt +0 -0
  27. {ripple_down_rules-0.1.65 → ripple_down_rules-0.1.67}/test/test_json_serialization.py +0 -0
  28. {ripple_down_rules-0.1.65 → ripple_down_rules-0.1.67}/test/test_on_mutagenic.py +0 -0
  29. {ripple_down_rules-0.1.65 → ripple_down_rules-0.1.67}/test/test_rdr.py +0 -0
  30. {ripple_down_rules-0.1.65 → ripple_down_rules-0.1.67}/test/test_rdr_alchemy.py +0 -0
  31. {ripple_down_rules-0.1.65 → ripple_down_rules-0.1.67}/test/test_rdr_world.py +0 -0
  32. {ripple_down_rules-0.1.65 → ripple_down_rules-0.1.67}/test/test_sql_model.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ripple_down_rules
3
- Version: 0.1.65
3
+ Version: 0.1.67
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
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
6
6
 
7
7
  [project]
8
8
  name = "ripple_down_rules"
9
- version = "0.1.65"
9
+ version = "0.1.67"
10
10
  description = "Implements the various versions of Ripple Down Rules (RDR) for knowledge representation and reasoning."
11
11
  readme = "README.md"
12
12
  authors = [{ name = "Abdelrhman Bassiouny", email = "abassiou@uni-bremen.de" }]
@@ -125,7 +125,7 @@ class HabitatTable(MappedAsDataclass, Base):
125
125
  return hash(self.habitat)
126
126
 
127
127
  def __str__(self):
128
- return self.habitat.value
128
+ return f"{HabitatTable.__name__}({Habitat.__name__}.{self.habitat.name})"
129
129
 
130
130
  def __repr__(self):
131
131
  return self.__str__()
@@ -3,11 +3,13 @@ from __future__ import annotations
3
3
  import ast
4
4
  import logging
5
5
  from _ast import AST
6
+ from enum import Enum
6
7
 
7
8
  from typing_extensions import Type, Optional, Any, List, Union, Tuple, Dict, Set
8
9
 
9
10
  from .case import create_case, Case
10
- from ..utils import SubclassJSONSerializer, get_full_class_name, get_type_from_string, conclusion_to_json, is_iterable
11
+ from ..utils import SubclassJSONSerializer, get_full_class_name, get_type_from_string, conclusion_to_json, is_iterable, \
12
+ build_user_input_from_conclusion, encapsulate_user_input
11
13
 
12
14
 
13
15
  class VariableVisitor(ast.NodeVisitor):
@@ -88,6 +90,7 @@ class CallableExpression(SubclassJSONSerializer):
88
90
  """
89
91
  A callable that is constructed from a string statement written by an expert.
90
92
  """
93
+ encapsulating_function: str = "def _get_value(case):"
91
94
 
92
95
  def __init__(self, user_input: Optional[str] = None, conclusion_type: Optional[Tuple[Type]] = None,
93
96
  expression_tree: Optional[AST] = None,
@@ -103,8 +106,10 @@ class CallableExpression(SubclassJSONSerializer):
103
106
  """
104
107
  if user_input is None and conclusion is None:
105
108
  raise ValueError("Either user_input or conclusion must be provided.")
109
+ if user_input is None:
110
+ user_input = build_user_input_from_conclusion(conclusion)
106
111
  self.conclusion: Optional[Any] = conclusion
107
- self.user_input: str = user_input
112
+ self.user_input: str = encapsulate_user_input(user_input, self.encapsulating_function)
108
113
  if conclusion_type is not None:
109
114
  if is_iterable(conclusion_type):
110
115
  conclusion_type = tuple(conclusion_type)
@@ -112,12 +117,11 @@ class CallableExpression(SubclassJSONSerializer):
112
117
  conclusion_type = (conclusion_type,)
113
118
  self.conclusion_type = conclusion_type
114
119
  self.scope: Optional[Dict[str, Any]] = scope if scope is not None else {}
115
- if conclusion is None:
116
- self.scope = get_used_scope(self.user_input, self.scope)
117
- self.expression_tree: AST = expression_tree if expression_tree else parse_string_to_expression(self.user_input)
118
- self.code = compile_expression_to_code(self.expression_tree)
119
- self.visitor = VariableVisitor()
120
- self.visitor.visit(self.expression_tree)
120
+ self.scope = get_used_scope(self.user_input, self.scope)
121
+ self.expression_tree: AST = expression_tree if expression_tree else parse_string_to_expression(self.user_input)
122
+ self.code = compile_expression_to_code(self.expression_tree)
123
+ self.visitor = VariableVisitor()
124
+ self.visitor.visit(self.expression_tree)
121
125
 
122
126
  def __call__(self, case: Any, **kwargs) -> Any:
123
127
  try:
@@ -145,7 +149,11 @@ class CallableExpression(SubclassJSONSerializer):
145
149
  """
146
150
  Combine this callable expression with another callable expression using the 'and' operator.
147
151
  """
148
- new_user_input = f"({self.user_input}) and ({other.user_input})"
152
+ cond1_user_input = self.user_input.replace(self.encapsulating_function, "def _cond1(case):")
153
+ cond2_user_input = other.user_input.replace(self.encapsulating_function, "def _cond2(case):")
154
+ new_user_input = (f"{cond1_user_input}\n"
155
+ f"{cond2_user_input}\n"
156
+ f"return _cond1(case) and _cond2(case)")
149
157
  return CallableExpression(new_user_input, conclusion_type=self.conclusion_type)
150
158
 
151
159
  def __eq__(self, other):
@@ -120,7 +120,7 @@ class CaseQuery:
120
120
  """
121
121
  :return: The target expression of the attribute.
122
122
  """
123
- if self._target is not None and not isinstance(self._target, CallableExpression):
123
+ if (self._target is not None) and (not isinstance(self._target, CallableExpression)):
124
124
  self._target = CallableExpression(conclusion=self._target, conclusion_type=self.attribute_type,
125
125
  scope=self.scope)
126
126
  return self._target
@@ -80,16 +80,10 @@ class IPythonShell:
80
80
  self.user_input = None
81
81
  else:
82
82
  self.all_code_lines = extract_dependencies(self.shell.all_lines)
83
- if len(self.all_code_lines) == 1:
84
- if self.all_code_lines[0].strip() == '':
85
- self.user_input = None
86
- else:
87
- self.user_input = self.all_code_lines[0].replace('return', '').strip()
83
+ if len(self.all_code_lines) == 1 and self.all_code_lines[0].strip() == '':
84
+ self.user_input = None
88
85
  else:
89
- self.user_input = f"def _get_value(case):\n "
90
- for cl in self.all_code_lines:
91
- sub_code_lines = cl.split('\n')
92
- self.user_input += '\n '.join(sub_code_lines) + '\n '
86
+ self.user_input = '\n'.join(self.all_code_lines)
93
87
 
94
88
 
95
89
  def prompt_user_for_expression(case_query: CaseQuery, prompt_for: PromptFor, prompt_str: Optional[str] = None)\
@@ -326,13 +326,10 @@ class RDRWithCodeWriter(RippleDownRules, ABC):
326
326
  """
327
327
  :return: The type of the conclusion of the RDR classifier.
328
328
  """
329
- if isinstance(self.start_rule.conclusion, CallableExpression):
330
- return self.start_rule.conclusion.conclusion_type
331
- else:
332
- conclusion = self.start_rule.conclusion
333
- if isinstance(conclusion, set):
334
- return type(list(conclusion)[0]), set
335
- return (type(conclusion),)
329
+ all_types = []
330
+ for rule in [self.start_rule] + list(self.start_rule.descendants):
331
+ all_types.extend(list(rule.conclusion.conclusion_type))
332
+ return tuple(set(all_types))
336
333
 
337
334
  @property
338
335
  def attribute_name(self) -> str:
@@ -419,7 +416,8 @@ class SingleClassRDR(RDRWithCodeWriter):
419
416
  self.write_rules_as_source_code_to_file(rule.refinement, file, parent_indent + " ",
420
417
  defs_file=defs_file)
421
418
 
422
- file.write(rule.write_conclusion_as_source_code(parent_indent))
419
+ conclusion_call = rule.write_conclusion_as_source_code(parent_indent, defs_file)
420
+ file.write(conclusion_call)
423
421
 
424
422
  if rule.alternative:
425
423
  self.write_rules_as_source_code_to_file(rule.alternative, file, parent_indent, defs_file=defs_file)
@@ -532,18 +530,20 @@ class MultiClassRDR(RDRWithCodeWriter):
532
530
  defs_file=defs_file)
533
531
  conclusion_indent = parent_indent + " " * 4
534
532
  file.write(f"{conclusion_indent}else:\n")
535
- file.write(rule.write_conclusion_as_source_code(conclusion_indent))
533
+
534
+ conclusion_call = rule.write_conclusion_as_source_code(conclusion_indent, defs_file)
535
+ file.write(conclusion_call)
536
536
 
537
537
  if rule.alternative:
538
538
  self.write_rules_as_source_code_to_file(rule.alternative, file, parent_indent, defs_file=defs_file)
539
539
 
540
540
  @property
541
541
  def conclusion_type_hint(self) -> str:
542
- return f"Set[{self.conclusion_type[0].__name__}]"
542
+ return f"Set[Union[{', '.join([ct.__name__ for ct in self.conclusion_type if ct not in [list, set]])}]]"
543
543
 
544
544
  def _get_imports(self) -> str:
545
545
  imports = super()._get_imports()
546
- imports += "from typing_extensions import Set\n"
546
+ imports += "from typing_extensions import Set, Union\n"
547
547
  imports += "from ripple_down_rules.utils import make_set\n"
548
548
  return imports
549
549
 
@@ -715,7 +715,7 @@ class GeneralRDR(RippleDownRules):
715
715
 
716
716
  @staticmethod
717
717
  def _classify(classifiers_dict: Dict[str, Union[ModuleType, RippleDownRules]],
718
- case: Any, modify_original_case: bool = True) -> Dict[str, Any]:
718
+ case: Any, modify_original_case: bool = False) -> Dict[str, Any]:
719
719
  """
720
720
  Classify a case by going through all classifiers and adding the categories that are classified,
721
721
  and then restarting the classification until no more categories can be added.
@@ -871,7 +871,7 @@ class GeneralRDR(RippleDownRules):
871
871
 
872
872
  @property
873
873
  def conclusion_type_hint(self) -> str:
874
- return f"List[Union[{', '.join([rdr.conclusion_type_hint for rdr in self.start_rules_dict.values()])}]]"
874
+ return f"Dict[str, Any]"
875
875
 
876
876
  def _get_imports(self, file_path: str) -> str:
877
877
  """
@@ -882,7 +882,7 @@ class GeneralRDR(RippleDownRules):
882
882
  """
883
883
  imports = ""
884
884
  # add type hints
885
- imports += f"from typing_extensions import List, Union, Set\n"
885
+ imports += f"from typing_extensions import Dict, Any, Union, Set\n"
886
886
  # import rdr type
887
887
  imports += f"from ripple_down_rules.rdr import GeneralRDR\n"
888
888
  # add case type
@@ -5,7 +5,7 @@ from abc import ABC, abstractmethod
5
5
  from enum import Enum
6
6
 
7
7
  from anytree import NodeMixin
8
- from typing_extensions import List, Optional, Self, Union, Dict, Any
8
+ from typing_extensions import List, Optional, Self, Union, Dict, Any, Tuple
9
9
 
10
10
  from .datastructures.callable_expression import CallableExpression
11
11
  from .datastructures.case import Case
@@ -78,24 +78,26 @@ class Rule(NodeMixin, SubclassJSONSerializer, ABC):
78
78
  """
79
79
  pass
80
80
 
81
- def write_conclusion_as_source_code(self, parent_indent: str = "") -> str:
81
+ def write_conclusion_as_source_code(self, parent_indent: str = "", defs_file: Optional[str] = None) -> str:
82
82
  """
83
83
  Get the source code representation of the conclusion of the rule.
84
84
 
85
85
  :param parent_indent: The indentation of the parent rule.
86
+ :param defs_file: The file to write the conclusion to if it is a definition.
87
+ :return: The source code representation of the conclusion of the rule.
86
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
+ if self.conclusion.user_input is not None:
90
+ conclusion = self.conclusion.user_input
91
+ else:
92
+ conclusion = self.conclusion.conclusion
93
+ conclusion_func, conclusion_func_call = self._conclusion_source_code(conclusion, parent_indent=parent_indent)
94
+ if conclusion_func is not None:
95
+ with open(defs_file, 'a') as f:
96
+ f.write(conclusion_func + "\n\n")
97
+ return conclusion_func_call
96
98
 
97
99
  @abstractmethod
98
- def _conclusion_source_code(self, conclusion: Any, parent_indent: str = "") -> str:
100
+ def _conclusion_source_code(self, conclusion: Any, parent_indent: str = "") -> Tuple[Optional[str], str]:
99
101
  pass
100
102
 
101
103
  def write_condition_as_source_code(self, parent_indent: str = "", defs_file: Optional[str] = None) -> str:
@@ -118,7 +120,7 @@ class Rule(NodeMixin, SubclassJSONSerializer, ABC):
118
120
  conditions_lines[0] = re.sub(r"def (\w+)", new_function_name, conditions_lines[0])
119
121
  def_code = "\n".join(conditions_lines)
120
122
  with open(defs_file, 'a') as f:
121
- f.write(def_code + "\n")
123
+ f.write(def_code + "\n\n")
122
124
  return f"\n{parent_indent}{if_clause} {new_function_name.replace('def ', '')}(case):\n"
123
125
 
124
126
  @abstractmethod
@@ -266,13 +268,13 @@ class SingleClassRule(Rule, HasAlternativeRule, HasRefinementRule):
266
268
  loaded_rule.alternative = SingleClassRule.from_json(data["alternative"])
267
269
  return loaded_rule
268
270
 
269
- def _conclusion_source_code(self, conclusion: Any, parent_indent: str = "") -> str:
271
+ def _conclusion_source_code(self, conclusion: Any, parent_indent: str = "") -> Tuple[Optional[str], str]:
270
272
  conclusion = str(conclusion)
271
- indent = parent_indent + " " * 4
272
- if '\n' not in conclusion:
273
- return f"{indent}return {conclusion}\n"
274
- else:
275
- return get_rule_conclusion_as_source_code(self, conclusion, parent_indent=parent_indent)
273
+ # indent = parent_indent + " " * 4
274
+ # if '\n' not in conclusion:
275
+ # return None, f"{indent}return {conclusion}\n"
276
+ # else:
277
+ return get_rule_conclusion_as_source_code(self, conclusion, parent_indent=parent_indent)
276
278
 
277
279
  def _if_statement_source_code_clause(self) -> str:
278
280
  return "elif" if self.weight == RDREdge.Alternative.value else "if"
@@ -317,8 +319,8 @@ class MultiClassStopRule(Rule, HasAlternativeRule):
317
319
  loaded_rule.alternative = MultiClassStopRule.from_json(data["alternative"])
318
320
  return loaded_rule
319
321
 
320
- def _conclusion_source_code(self, conclusion: Any, parent_indent: str = "") -> str:
321
- return f"{parent_indent}{' ' * 4}pass\n"
322
+ def _conclusion_source_code(self, conclusion: Any, parent_indent: str = "") -> Tuple[None, str]:
323
+ return None, f"{parent_indent}{' ' * 4}pass\n"
322
324
 
323
325
  def _if_statement_source_code_clause(self) -> str:
324
326
  return "elif" if self.weight == RDREdge.Alternative.value else "if"
@@ -362,25 +364,23 @@ class MultiClassTopRule(Rule, HasRefinementRule, HasAlternativeRule):
362
364
  loaded_rule.alternative = MultiClassTopRule.from_json(data["alternative"])
363
365
  return loaded_rule
364
366
 
365
- def _conclusion_source_code(self, conclusion: Any, parent_indent: str = "") -> str:
367
+ def _conclusion_source_code(self, conclusion: Any, parent_indent: str = "") -> Tuple[str, str]:
366
368
  conclusion_str = str(conclusion)
367
369
  indent = parent_indent + " " * 4
368
- statement = ""
369
- if '\n' not in conclusion_str:
370
- if is_iterable(conclusion):
371
- conclusion_str = "{" + ", ".join([str(c) for c in conclusion]) + "}"
372
- else:
373
- conclusion_str = "{" + str(conclusion) + "}"
374
- else:
375
- conclusion_str = get_rule_conclusion_as_source_code(self, conclusion_str, parent_indent=parent_indent)
376
- lines = conclusion_str.split("\n")
377
- conclusion_str = lines[-2].replace("return ", "").strip()
378
- statement += "\n".join(lines[:-2]) + "\n"
379
-
380
- statement += f"{indent}conclusions.update(make_set({conclusion_str}))\n"
370
+ # if '\n' not in conclusion_str:
371
+ # func = None
372
+ # if is_iterable(conclusion):
373
+ # conclusion_str = "{" + ", ".join([str(c) for c in conclusion]) + "}"
374
+ # else:
375
+ # conclusion_str = "{" + str(conclusion) + "}"
376
+ # else:
377
+ func, func_call = get_rule_conclusion_as_source_code(self, conclusion_str, parent_indent=parent_indent)
378
+ conclusion_str = func_call.replace("return ", "").strip()
379
+
380
+ statement = f"{indent}conclusions.update(make_set({conclusion_str}))\n"
381
381
  if self.alternative is None:
382
382
  statement += f"{parent_indent}return conclusions\n"
383
- return statement
383
+ return func, statement
384
384
 
385
385
  def _if_statement_source_code_clause(self) -> str:
386
386
  return "if"
@@ -10,6 +10,7 @@ import re
10
10
  from collections import UserDict
11
11
  from copy import deepcopy
12
12
  from dataclasses import is_dataclass, fields
13
+ from enum import Enum
13
14
  from types import NoneType
14
15
 
15
16
  import matplotlib
@@ -34,6 +35,58 @@ import ast
34
35
  matplotlib.use("Qt5Agg") # or "Qt5Agg", depending on availability
35
36
 
36
37
 
38
+ def encapsulate_user_input(user_input: str, func_signature: str) -> str:
39
+ """
40
+ Encapsulate the user input string with a function definition.
41
+
42
+ :param user_input: The user input string.
43
+ :param func_signature: The function signature to use for encapsulation.
44
+ :return: The encapsulated user input string.
45
+ """
46
+ if func_signature not in user_input:
47
+ new_user_input = func_signature + "\n "
48
+ if "return " not in user_input:
49
+ if '\n' not in user_input:
50
+ new_user_input += f"return {user_input}"
51
+ else:
52
+ raise ValueError("User input must contain a return statement or be a single line.")
53
+ else:
54
+ for cl in user_input.split('\n'):
55
+ sub_code_lines = cl.split('\n')
56
+ new_user_input += '\n '.join(sub_code_lines) + '\n '
57
+ else:
58
+ new_user_input = user_input
59
+ return new_user_input
60
+
61
+
62
+ def build_user_input_from_conclusion(conclusion: Any) -> str:
63
+ """
64
+ Build a user input string from the conclusion.
65
+
66
+ :param conclusion: The conclusion to use for the callable expression.
67
+ :return: The user input string.
68
+ """
69
+
70
+ # set user_input to the string representation of the conclusion
71
+ if isinstance(conclusion, set):
72
+ user_input = '{' + f"{', '.join([conclusion_to_str(t) for t in conclusion])}" + '}'
73
+ elif isinstance(conclusion, list):
74
+ user_input = '[' + f"{', '.join([conclusion_to_str(t) for t in conclusion])}" + ']'
75
+ elif isinstance(conclusion, tuple):
76
+ user_input = '(' + f"{', '.join([conclusion_to_str(t) for t in conclusion])}" + ')'
77
+ else:
78
+ user_input = conclusion_to_str(conclusion)
79
+
80
+ return user_input
81
+
82
+
83
+ def conclusion_to_str(conclusion_: Any) -> str:
84
+ if isinstance(conclusion_, Enum):
85
+ return type(conclusion_).__name__ + '.' + conclusion_.name
86
+ else:
87
+ return str(conclusion_)
88
+
89
+
37
90
  def update_case(case_query: CaseQuery, conclusions: Dict[str, Any]):
38
91
  """
39
92
  Update the case with the conclusions.
@@ -119,14 +172,14 @@ def calculate_precision_and_recall(pred_cat: Dict[str, Any], target: Dict[str, A
119
172
  return precision, recall
120
173
 
121
174
 
122
- def get_rule_conclusion_as_source_code(rule: Rule, conclusion: str, parent_indent: str = "") -> str:
175
+ def get_rule_conclusion_as_source_code(rule: Rule, conclusion: str, parent_indent: str = "") -> Tuple[str, str]:
123
176
  """
124
177
  Convert the conclusion of a rule to source code.
125
178
 
126
179
  :param rule: The rule to get the conclusion from.
127
180
  :param conclusion: The conclusion to convert to source code.
128
181
  :param parent_indent: The indentation to use for the source code.
129
- :return: The source code of the conclusion.
182
+ :return: The source code of the conclusion as a tuple of strings, one for the function and one for the call.
130
183
  """
131
184
  indent = f"{parent_indent}{' ' * 4}"
132
185
  if "def " in conclusion:
@@ -135,9 +188,8 @@ def get_rule_conclusion_as_source_code(rule: Rule, conclusion: str, parent_inden
135
188
  # use regex to replace the function name
136
189
  new_function_name = f"def conclusion_{id(rule)}"
137
190
  conclusion_lines[0] = re.sub(r"def (\w+)", new_function_name, conclusion_lines[0])
138
- conclusion_lines = [f"{indent}{line}" for line in conclusion_lines]
139
- conclusion_lines.append(f"{indent}return {new_function_name.replace('def ', '')}(case)\n")
140
- return "\n".join(conclusion_lines)
191
+ func_call = f"{indent}return {new_function_name.replace('def ', '')}(case)\n"
192
+ return "\n".join(conclusion_lines).strip(' '), func_call
141
193
  else:
142
194
  raise ValueError(f"Conclusion is format is not valid, it should be a one line string or "
143
195
  f"contain a function definition. Instead got:\n{conclusion}\n")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ripple_down_rules
3
- Version: 0.1.65
3
+ Version: 0.1.67
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
@@ -54,6 +54,7 @@ class RelationalRDRTestCase(TestCase):
54
54
  part_d: Part
55
55
  part_e: Part
56
56
  part_f: Part
57
+ target: List[PhysicalObject]
57
58
 
58
59
  @classmethod
59
60
  def setUpClass(cls):
@@ -71,8 +72,8 @@ class RelationalRDRTestCase(TestCase):
71
72
  cls.part_d.contained_objects = [cls.part_e]
72
73
  cls.part_e.contained_objects = [cls.part_f]
73
74
  cls.robot: Robot = robot
74
- cls.case_query = CaseQuery(robot, "contained_objects", (PhysicalObject,), False,
75
- _target=[cls.part_b, cls.part_c, cls.part_d, cls.part_e])
75
+ cls.case_query = CaseQuery(robot, "contained_objects", (PhysicalObject,), False)
76
+ cls.target = [cls.part_b, cls.part_c, cls.part_d, cls.part_e]
76
77
 
77
78
  def test_classify_scrdr(self):
78
79
  use_loaded_answers = True
@@ -86,7 +87,7 @@ class RelationalRDRTestCase(TestCase):
86
87
  cat = scrdr.fit_case(CaseQuery(self.robot, "contained_objects", (PhysicalObject,), False), expert=expert)
87
88
  render_tree(scrdr.start_rule, use_dot_exporter=True,
88
89
  filename=self.test_results_dir + "/relational_scrdr_classify")
89
- assert cat == self.case_query.target(self.case_query.case)
90
+ assert cat == self.target
90
91
 
91
92
  if save_answers:
92
93
  cwd = os.getcwd()
@@ -105,4 +106,4 @@ class RelationalRDRTestCase(TestCase):
105
106
  conclusion = CallableExpression(user_input, list)
106
107
  print(conclusion)
107
108
  print(conclusion(self.robot))
108
- assert conclusion(self.robot) == self.case_query.target(self.case_query.case)
109
+ assert conclusion(self.robot) == self.target
@@ -108,6 +108,7 @@ class RelationalRDRTestCase(TestCase):
108
108
  part_f: PhysicalObject
109
109
  rob_has_parts: List[HasPart]
110
110
  containments: List[ContainsObject]
111
+ target: List[PhysicalObject]
111
112
 
112
113
  @classmethod
113
114
  def setUpClass(cls):
@@ -129,8 +130,8 @@ class RelationalRDRTestCase(TestCase):
129
130
  cls.containments.append(ContainsObject(left=cls.part_d, right=cls.part_e))
130
131
  cls.containments.append(ContainsObject(left=cls.part_e, right=cls.part_f))
131
132
  cls.robot: PhysicalObject = robot
132
- cls.case_query = CaseQuery(robot, robot.contained_objects, (PhysicalObject,), False,
133
- _target=[cls.part_b, cls.part_c, cls.part_d, cls.part_e])
133
+ cls.case_query = CaseQuery(robot, robot.contained_objects, (PhysicalObject,), False)
134
+ cls.target = [cls.part_b, cls.part_c, cls.part_d, cls.part_e]
134
135
 
135
136
  def test_setup(self):
136
137
  assert self.robot.parts == [self.part_a, self.part_b, self.part_c, self.part_d]
@@ -153,7 +154,7 @@ class RelationalRDRTestCase(TestCase):
153
154
  cat = scrdr.fit_case(CaseQuery(self.robot, "contained_objects", (PhysicalObject,), False), expert=expert)
154
155
  render_tree(scrdr.start_rule, use_dot_exporter=True,
155
156
  filename=self.test_results_dir + "/relational_scrdr_classify")
156
- assert cat == self.case_query.target(self.case_query.case)
157
+ assert cat == self.target
157
158
 
158
159
  if save_answers:
159
160
  cwd = os.getcwd()
@@ -172,4 +173,4 @@ class RelationalRDRTestCase(TestCase):
172
173
  conclusion = CallableExpression(user_input, list)
173
174
  print(conclusion)
174
175
  print(conclusion(self.robot))
175
- assert conclusion(self.robot) == self.case_query.target(self.case_query.case)
176
+ assert conclusion(self.robot) == self.target