ripple-down-rules 0.0.11__tar.gz → 0.0.13__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 (28) hide show
  1. {ripple_down_rules-0.0.11 → ripple_down_rules-0.0.13}/PKG-INFO +1 -1
  2. {ripple_down_rules-0.0.11 → ripple_down_rules-0.0.13}/pyproject.toml +1 -1
  3. {ripple_down_rules-0.0.11 → ripple_down_rules-0.0.13}/src/ripple_down_rules/rdr.py +21 -16
  4. {ripple_down_rules-0.0.11 → ripple_down_rules-0.0.13}/src/ripple_down_rules/rules.py +11 -4
  5. {ripple_down_rules-0.0.11 → ripple_down_rules-0.0.13}/src/ripple_down_rules.egg-info/PKG-INFO +1 -1
  6. {ripple_down_rules-0.0.11 → ripple_down_rules-0.0.13}/test/test_json_serialization.py +11 -14
  7. {ripple_down_rules-0.0.11 → ripple_down_rules-0.0.13}/test/test_rdr.py +9 -29
  8. {ripple_down_rules-0.0.11 → ripple_down_rules-0.0.13}/LICENSE +0 -0
  9. {ripple_down_rules-0.0.11 → ripple_down_rules-0.0.13}/README.md +0 -0
  10. {ripple_down_rules-0.0.11 → ripple_down_rules-0.0.13}/setup.cfg +0 -0
  11. {ripple_down_rules-0.0.11 → ripple_down_rules-0.0.13}/src/ripple_down_rules/__init__.py +0 -0
  12. {ripple_down_rules-0.0.11 → ripple_down_rules-0.0.13}/src/ripple_down_rules/datasets.py +0 -0
  13. {ripple_down_rules-0.0.11 → ripple_down_rules-0.0.13}/src/ripple_down_rules/datastructures/__init__.py +0 -0
  14. {ripple_down_rules-0.0.11 → ripple_down_rules-0.0.13}/src/ripple_down_rules/datastructures/callable_expression.py +0 -0
  15. {ripple_down_rules-0.0.11 → ripple_down_rules-0.0.13}/src/ripple_down_rules/datastructures/case.py +0 -0
  16. {ripple_down_rules-0.0.11 → ripple_down_rules-0.0.13}/src/ripple_down_rules/datastructures/dataclasses.py +0 -0
  17. {ripple_down_rules-0.0.11 → ripple_down_rules-0.0.13}/src/ripple_down_rules/datastructures/enums.py +0 -0
  18. {ripple_down_rules-0.0.11 → ripple_down_rules-0.0.13}/src/ripple_down_rules/experts.py +0 -0
  19. {ripple_down_rules-0.0.11 → ripple_down_rules-0.0.13}/src/ripple_down_rules/failures.py +0 -0
  20. {ripple_down_rules-0.0.11 → ripple_down_rules-0.0.13}/src/ripple_down_rules/prompt.py +0 -0
  21. {ripple_down_rules-0.0.11 → ripple_down_rules-0.0.13}/src/ripple_down_rules/utils.py +0 -0
  22. {ripple_down_rules-0.0.11 → ripple_down_rules-0.0.13}/src/ripple_down_rules.egg-info/SOURCES.txt +0 -0
  23. {ripple_down_rules-0.0.11 → ripple_down_rules-0.0.13}/src/ripple_down_rules.egg-info/dependency_links.txt +0 -0
  24. {ripple_down_rules-0.0.11 → ripple_down_rules-0.0.13}/src/ripple_down_rules.egg-info/top_level.txt +0 -0
  25. {ripple_down_rules-0.0.11 → ripple_down_rules-0.0.13}/test/test_rdr_alchemy.py +0 -0
  26. {ripple_down_rules-0.0.11 → ripple_down_rules-0.0.13}/test/test_relational_rdr.py +0 -0
  27. {ripple_down_rules-0.0.11 → ripple_down_rules-0.0.13}/test/test_relational_rdr_alchemy.py +0 -0
  28. {ripple_down_rules-0.0.11 → ripple_down_rules-0.0.13}/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.0.11
3
+ Version: 0.0.13
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.0.11"
9
+ version = "0.0.13"
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" }]
@@ -17,7 +17,7 @@ from .utils import draw_tree, make_set, get_attribute_by_type, copy_case, \
17
17
  get_hint_for_attribute, SubclassJSONSerializer, is_iterable, make_list
18
18
 
19
19
 
20
- class RippleDownRules(ABC):
20
+ class RippleDownRules(SubclassJSONSerializer, ABC):
21
21
  """
22
22
  The abstract base class for the ripple down rules classifiers.
23
23
  """
@@ -175,10 +175,7 @@ class RippleDownRules(ABC):
175
175
  return conclusion_type in case
176
176
 
177
177
 
178
- RDR = RippleDownRules
179
-
180
-
181
- class RDRWithCodeWriter(RDR, ABC):
178
+ class RDRWithCodeWriter(RippleDownRules, ABC):
182
179
 
183
180
  @abstractmethod
184
181
  def write_rules_as_source_code_to_file(self, rule: Rule, file, parent_indent: str = ""):
@@ -199,7 +196,7 @@ class RDRWithCodeWriter(RDR, ABC):
199
196
  """
200
197
  func_def = f"def classify(case: {self.case_type.__name__}) -> {self._get_conclusion_type_hint()}:\n"
201
198
  with open(file_path + f"/{self.generated_python_file_name}.py", "w") as f:
202
- f.write(self._get_imports())
199
+ f.write(self._get_imports() + "\n\n")
203
200
  f.write(func_def)
204
201
  f.write(f"{' '*4}if not isinstance(case, Case):\n"
205
202
  f"{' '*4} case = create_case(case, recursion_idx=3)\n""")
@@ -222,7 +219,6 @@ class RDRWithCodeWriter(RDR, ABC):
222
219
  if self.conclusion_type.__module__ != "builtins":
223
220
  imports += f"from {self.conclusion_type.__module__} import {self.conclusion_type.__name__}\n"
224
221
  imports += "from ripple_down_rules.datastructures import Case, create_case\n"
225
- imports += "\n\n"
226
222
  return imports
227
223
 
228
224
  def get_rdr_classifier_from_python_file(self, package_name) -> Callable[[Any], Any]:
@@ -261,7 +257,7 @@ class RDRWithCodeWriter(RDR, ABC):
261
257
  return type(self.start_rule.conclusion)
262
258
 
263
259
 
264
- class SingleClassRDR(RDRWithCodeWriter, SubclassJSONSerializer):
260
+ class SingleClassRDR(RDRWithCodeWriter):
265
261
 
266
262
  def fit_case(self, case_query: CaseQuery, expert: Optional[Expert] = None, **kwargs) \
267
263
  -> Union[CaseAttribute, CallableExpression]:
@@ -354,17 +350,15 @@ class MultiClassRDR(RDRWithCodeWriter):
354
350
  The conditions of the stopping rule if needed.
355
351
  """
356
352
 
357
- def __init__(self, start_rules: Optional[List[Rule]] = None,
353
+ def __init__(self, start_rule: Optional[Rule] = None,
358
354
  mode: MCRDRMode = MCRDRMode.StopOnly, session: Optional[Session] = None):
359
355
  """
360
- :param start_rules: The starting rules for the classifier, these are the rules that are at the top of the tree
361
- and are always checked, in contrast to the refinement and alternative rules which are only checked if the
362
- starting rules fire or not.
356
+ :param start_rule: The starting rules for the classifier.
363
357
  :param mode: The mode of the classifier, either StopOnly or StopPlusRule, or StopPlusRuleCombined.
364
358
  :param session: The sqlalchemy orm session.
365
359
  """
366
- self.start_rules = [MultiClassTopRule()] if not start_rules else start_rules
367
- super(MultiClassRDR, self).__init__(self.start_rules[0], session=session)
360
+ start_rule = MultiClassTopRule() if not start_rule else start_rule
361
+ super(MultiClassRDR, self).__init__(start_rule, session=session)
368
362
  self.mode: MCRDRMode = mode
369
363
 
370
364
  def classify(self, case: Union[Case, SQLTable]) -> List[Any]:
@@ -452,8 +446,8 @@ class MultiClassRDR(RDRWithCodeWriter):
452
446
  return f"Set[{self.conclusion_type.__name__}]"
453
447
 
454
448
  def _get_imports(self) -> str:
455
- imports = super()._get_imports().strip('\n')
456
- imports += "\nfrom typing_extensions import Set\n\n"
449
+ imports = super()._get_imports()
450
+ imports += "from typing_extensions import Set\n"
457
451
  return imports
458
452
 
459
453
  def update_start_rule(self, case: Union[Case, SQLTable], target: Any, expert: Expert):
@@ -615,6 +609,17 @@ class MultiClassRDR(RDRWithCodeWriter):
615
609
  """
616
610
  self.start_rule.alternative = MultiClassTopRule(conditions, conclusion, corner_case=corner_case)
617
611
 
612
+ def _to_json(self) -> Dict[str, Any]:
613
+ return {"start_rule": self.start_rule.to_json()}
614
+
615
+ @classmethod
616
+ def _from_json(cls, data: Dict[str, Any]) -> Self:
617
+ """
618
+ Create an instance of the class from a json
619
+ """
620
+ start_rule = SingleClassRule.from_json(data["start_rule"])
621
+ return cls(start_rule)
622
+
618
623
 
619
624
  class GeneralRDR(RippleDownRules):
620
625
  """
@@ -265,14 +265,17 @@ class MultiClassStopRule(Rule, HasAlternativeRule):
265
265
 
266
266
  def _to_json(self) -> Dict[str, Any]:
267
267
  self.json_serialization = {**Rule._to_json(self),
268
- "top_rule": self.top_rule.to_json(),
269
268
  "alternative": self.alternative.to_json() if self.alternative is not None else None}
270
269
  return self.json_serialization
271
270
 
272
271
  @classmethod
273
272
  def _from_json(cls, data: Dict[str, Any]) -> MultiClassStopRule:
274
- loaded_rule = Rule._from_json(data)
275
- loaded_rule.top_rule = MultiClassTopRule.from_json(data["top_rule"])
273
+ loaded_rule = super(MultiClassStopRule, cls)._from_json(data)
274
+ # The following is done to prevent re-initialization of the top rule,
275
+ # so the top rule that is already initialized is passed in the data instead of its json serialization.
276
+ loaded_rule.top_rule = data['top_rule']
277
+ if data['alternative'] is not None:
278
+ data['alternative']['top_rule'] = data['top_rule']
276
279
  loaded_rule.alternative = MultiClassStopRule.from_json(data["alternative"])
277
280
  return loaded_rule
278
281
 
@@ -312,7 +315,11 @@ class MultiClassTopRule(Rule, HasRefinementRule, HasAlternativeRule):
312
315
 
313
316
  @classmethod
314
317
  def _from_json(cls, data: Dict[str, Any]) -> MultiClassTopRule:
315
- loaded_rule = Rule._from_json(data)
318
+ loaded_rule = super(MultiClassTopRule, cls)._from_json(data)
319
+ # The following is done to prevent re-initialization of the top rule,
320
+ # so the top rule that is already initialized is passed in the data instead of its json serialization.
321
+ if data['refinement'] is not None:
322
+ data['refinement']['top_rule'] = loaded_rule
316
323
  loaded_rule.refinement = MultiClassStopRule.from_json(data["refinement"])
317
324
  loaded_rule.alternative = MultiClassTopRule.from_json(data["alternative"])
318
325
  return loaded_rule
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ripple_down_rules
3
- Version: 0.0.11
3
+ Version: 0.0.13
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,9 @@ from typing_extensions import List
6
6
  from ripple_down_rules.datasets import load_zoo_dataset
7
7
  from ripple_down_rules.datastructures import CaseQuery, Case
8
8
  from ripple_down_rules.experts import Human
9
- from ripple_down_rules.rdr import SingleClassRDR
9
+ from ripple_down_rules.rdr import SingleClassRDR, MultiClassRDR
10
+ from ripple_down_rules.utils import make_set
11
+ from test_helpers.helpers import get_fit_mcrdr, get_fit_scrdr
10
12
 
11
13
 
12
14
  class TestJSONSerialization(TestCase):
@@ -20,7 +22,7 @@ class TestJSONSerialization(TestCase):
20
22
  cls.all_cases, cls.targets = load_zoo_dataset(cls.cache_dir + "/zoo_dataset.pkl")
21
23
 
22
24
  def test_scrdr_json_serialization(self):
23
- scrdr = self.get_fit_scrdr()
25
+ scrdr = get_fit_scrdr(self.all_cases, self.targets)
24
26
  filename = f"{self.cache_dir}/scrdr.json"
25
27
  scrdr.save(filename)
26
28
  scrdr = SingleClassRDR.load(filename)
@@ -28,16 +30,11 @@ class TestJSONSerialization(TestCase):
28
30
  cat = scrdr.classify(case)
29
31
  self.assertEqual(cat, target)
30
32
 
31
- def get_fit_scrdr(self, draw_tree=False) -> SingleClassRDR:
32
- filename = self.expert_answers_dir + "/scrdr_expert_answers_fit"
33
- expert = Human(use_loaded_answers=True)
34
- expert.load_answers(filename)
35
-
36
- scrdr = SingleClassRDR()
37
- case_queries = [CaseQuery(case, target=target) for case, target in zip(self.all_cases, self.targets)]
38
- scrdr.fit(case_queries, expert=expert,
39
- animate_tree=draw_tree)
33
+ def test_mcrdr_json_serialization(self):
34
+ mcrdr = get_fit_mcrdr(self.all_cases, self.targets)
35
+ filename = f"{self.cache_dir}/mcrdr.json"
36
+ mcrdr.save(filename)
37
+ mcrdr = MultiClassRDR.load(filename)
40
38
  for case, target in zip(self.all_cases, self.targets):
41
- cat = scrdr.classify(case)
42
- self.assertEqual(cat, target)
43
- return scrdr
39
+ cat = mcrdr.classify(case)
40
+ self.assertEqual(make_set(cat), make_set(target))
@@ -11,6 +11,7 @@ from ripple_down_rules.datastructures import Case, MCRDRMode, \
11
11
  from ripple_down_rules.experts import Human
12
12
  from ripple_down_rules.rdr import SingleClassRDR, MultiClassRDR, GeneralRDR
13
13
  from ripple_down_rules.utils import render_tree, get_all_subclasses, make_set
14
+ from test_helpers.helpers import get_fit_scrdr, get_fit_mcrdr
14
15
 
15
16
 
16
17
  class TestRDR(TestCase):
@@ -71,7 +72,7 @@ class TestRDR(TestCase):
71
72
  expert.save_answers(file)
72
73
 
73
74
  def test_write_scrdr_to_python_file(self):
74
- scrdr = self.get_fit_scrdr()
75
+ scrdr = get_fit_scrdr(self.all_cases,self.targets)
75
76
  scrdr.write_to_python_file(self.generated_rdrs_dir)
76
77
  classify_species_scrdr = scrdr.get_rdr_classifier_from_python_file(self.generated_rdrs_dir)
77
78
  for case, target in zip(self.all_cases, self.targets):
@@ -79,13 +80,12 @@ class TestRDR(TestCase):
79
80
  self.assertEqual(cat, target)
80
81
 
81
82
  def test_write_mcrdr_to_python_file(self):
82
- mcrdr = self.get_fit_mcrdr()
83
+ mcrdr = get_fit_mcrdr(self.all_cases, self.targets)
83
84
  mcrdr.write_to_python_file(self.generated_rdrs_dir)
84
85
  classify_species_mcrdr = mcrdr.get_rdr_classifier_from_python_file(self.generated_rdrs_dir)
85
- for case in self.all_cases:
86
- cat_1 = mcrdr.classify(case)
87
- cat_2 = classify_species_mcrdr(case)
88
- self.assertEqual(make_set(cat_1), make_set(cat_2))
86
+ for case, target in zip(self.all_cases, self.targets):
87
+ cat = classify_species_mcrdr(case)
88
+ self.assertEqual(make_set(cat), make_set(target))
89
89
 
90
90
  def test_classify_mcrdr(self):
91
91
  use_loaded_answers = True
@@ -115,7 +115,7 @@ class TestRDR(TestCase):
115
115
  expert.load_answers(filename)
116
116
  mcrdr = MultiClassRDR()
117
117
  case_queries = [CaseQuery(case, target=target) for case, target in zip(self.all_cases, self.targets)]
118
- mcrdr.fit(case_queries, expert=expert, animate_tree=draw_tree, n_iter=1)
118
+ mcrdr.fit(case_queries, expert=expert, animate_tree=draw_tree)
119
119
  render_tree(mcrdr.start_rule, use_dot_exporter=True,
120
120
  filename=self.test_results_dir + f"/mcrdr_stop_only")
121
121
  cats = mcrdr.classify(self.all_cases[50])
@@ -259,7 +259,7 @@ class TestRDR(TestCase):
259
259
  if use_loaded_answers:
260
260
  expert.load_answers(filename)
261
261
 
262
- fit_scrdr = self.get_fit_scrdr(draw_tree=False)
262
+ fit_scrdr = get_fit_scrdr(self.all_cases, self.targets, draw_tree=False)
263
263
 
264
264
  grdr = GeneralRDR({type(fit_scrdr.start_rule.conclusion): fit_scrdr})
265
265
 
@@ -310,7 +310,7 @@ class TestRDR(TestCase):
310
310
  if use_loaded_answers:
311
311
  expert.load_answers(filename)
312
312
 
313
- fit_scrdr = self.get_fit_scrdr(draw_tree=False)
313
+ fit_scrdr = get_fit_scrdr(self.all_cases, self.targets, draw_tree=False)
314
314
 
315
315
  grdr = GeneralRDR({type(fit_scrdr.start_rule.conclusion): fit_scrdr})
316
316
 
@@ -327,23 +327,3 @@ class TestRDR(TestCase):
327
327
  cwd = os.getcwd()
328
328
  file = os.path.join(cwd, filename)
329
329
  expert.save_answers(file)
330
-
331
- def get_fit_scrdr(self, draw_tree=False) -> SingleClassRDR:
332
- filename = self.expert_answers_dir + "/scrdr_expert_answers_fit"
333
- expert = Human(use_loaded_answers=True)
334
- expert.load_answers(filename)
335
-
336
- scrdr = SingleClassRDR()
337
- case_queries = [CaseQuery(case, target=target) for case, target in zip(self.all_cases, self.targets)]
338
- scrdr.fit(case_queries, expert=expert,
339
- animate_tree=draw_tree)
340
- return scrdr
341
-
342
- def get_fit_mcrdr(self, draw_tree: bool = False):
343
- filename = self.expert_answers_dir + "/mcrdr_expert_answers_stop_only_fit"
344
- expert = Human(use_loaded_answers=True)
345
- expert.load_answers(filename)
346
- mcrdr = MultiClassRDR()
347
- case_queries = [CaseQuery(case, target=target) for case, target in zip(self.all_cases, self.targets)]
348
- mcrdr.fit(case_queries, expert=expert, animate_tree=draw_tree, n_iter=1)
349
- return mcrdr