ripple-down-rules 0.6.47__tar.gz → 0.6.49__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 (177) hide show
  1. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/PKG-INFO +1 -1
  2. ripple_down_rules-0.6.49/doc/_toc.yml +12 -0
  3. ripple_down_rules-0.6.49/doc/intro.md +150 -0
  4. ripple_down_rules-0.6.49/doc/propositional_example_tutorial.md +114 -0
  5. ripple_down_rules-0.6.49/doc/relational_example_tutorial.md +120 -0
  6. ripple_down_rules-0.6.49/doc/relational_example_with_decorator_tutorial.md +123 -0
  7. ripple_down_rules-0.6.49/doc/test_driven_rdr_fitting_tutorial.md +133 -0
  8. ripple_down_rules-0.6.49/examples/decorator_example.py +24 -0
  9. ripple_down_rules-0.6.49/examples/decorator_model.py +32 -0
  10. ripple_down_rules-0.6.49/examples/relational_example.py +25 -0
  11. ripple_down_rules-0.6.49/examples/relational_model.py +36 -0
  12. ripple_down_rules-0.6.49/examples/test_relational_example.py +28 -0
  13. ripple_down_rules-0.6.49/src/ripple_down_rules/__init__.py +17 -0
  14. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/src/ripple_down_rules/helpers.py +12 -1
  15. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/src/ripple_down_rules/rdr.py +69 -55
  16. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/src/ripple_down_rules/rdr_decorators.py +12 -8
  17. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/src/ripple_down_rules/user_interface/ipython_custom_shell.py +1 -1
  18. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/src/ripple_down_rules.egg-info/PKG-INFO +1 -1
  19. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/src/ripple_down_rules.egg-info/SOURCES.txt +9 -6
  20. ripple_down_rules-0.6.49/test/test_results/__init__.py +1 -0
  21. ripple_down_rules-0.6.49/test/test_results/datasets_physical_object_is_a_robot/physical_object_is_a_robot_output__scrdr_defs.py +19 -0
  22. ripple_down_rules-0.6.49/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 +19 -0
  23. ripple_down_rules-0.6.47/doc/_toc.yml +0 -8
  24. ripple_down_rules-0.6.47/doc/intro.md +0 -296
  25. ripple_down_rules-0.6.47/examples/part_containment_rdr/__init__.py +0 -1
  26. ripple_down_rules-0.6.47/examples/part_containment_rdr/rdr_metadata/part_containment_rdr.json +0 -95
  27. ripple_down_rules-0.6.47/examples/part_containment_rdr/robot_contained_objects_mcrdr.py +0 -20
  28. ripple_down_rules-0.6.47/examples/part_containment_rdr/robot_contained_objects_mcrdr_defs.py +0 -23
  29. ripple_down_rules-0.6.47/examples/part_containment_rdr/robot_rdr.py +0 -16
  30. ripple_down_rules-0.6.47/examples/relational_example.py +0 -48
  31. ripple_down_rules-0.6.47/src/ripple_down_rules/__init__.py +0 -5
  32. ripple_down_rules-0.6.47/test/test_results/datasets_physical_object_is_a_robot/physical_object_is_a_robot_output__scrdr_defs.py +0 -19
  33. ripple_down_rules-0.6.47/test/test_results/datasets_physical_object_is_a_robot/rdr_metadata/datasets_physical_object_is_a_robot.json +0 -56
  34. ripple_down_rules-0.6.47/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 +0 -19
  35. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/.github/workflows/build_and_deploy_doc.yml +0 -0
  36. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/.github/workflows/ci.yml +0 -0
  37. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/.github/workflows/publish-to-test-pypi.yml +0 -0
  38. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/.gitignore +0 -0
  39. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/.idea/shelf/Uncommitted_changes_before_Checkout_at_2_4_25,_6_32_PM_[Changes]/shelved.patch +0 -0
  40. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/.idea/shelf/Uncommitted_changes_before_Checkout_at_2_4_25,_6_32_PM_[Changes]1/shelved.patch +0 -0
  41. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/LICENSE +0 -0
  42. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/README.md +0 -0
  43. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/doc/_config.yml +0 -0
  44. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/doc/bibliography.md +0 -0
  45. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/doc/references.bib +0 -0
  46. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/doc/requirements.txt +0 -0
  47. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/examples/__init__.py +0 -0
  48. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/examples/animal_species.py +0 -0
  49. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/images/scrdr.dot +0 -0
  50. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/images/scrdr.png +0 -0
  51. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/images/thinking_pr2.jpg +0 -0
  52. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/pyproject.toml +0 -0
  53. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/pytest.ini +0 -0
  54. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/requirements-dev-ci.txt +0 -0
  55. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/requirements-dev.txt +0 -0
  56. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/requirements-gui.txt +0 -0
  57. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/requirements-viz.txt +0 -0
  58. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/requirements.txt +0 -0
  59. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/results/complete_mcrdr_extra.dot +0 -0
  60. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/results/complete_mcrdr_extra.png +0 -0
  61. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/results/complete_mcrdr_stop_only.dot +0 -0
  62. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/results/complete_mcrdr_stop_only.png +0 -0
  63. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/results/complete_mcrdr_stop_plus_rule.dot +0 -0
  64. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/results/complete_mcrdr_stop_plus_rule.png +0 -0
  65. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/results/complete_scrdr.dot +0 -0
  66. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/results/complete_scrdr.png +0 -0
  67. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/results/complete_scrdr_2.dot +0 -0
  68. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/results/complete_scrdr_2.png +0 -0
  69. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/results/complete_scrdr_3.dot +0 -0
  70. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/results/complete_scrdr_3.png +0 -0
  71. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/results/grdr_Habitat.dot +0 -0
  72. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/results/grdr_Habitat.png +0 -0
  73. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/results/grdr_Species.dot +0 -0
  74. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/results/grdr_Species.png +0 -0
  75. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/results/mcrdr_extra.dot +0 -0
  76. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/results/mcrdr_extra.png +0 -0
  77. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/results/mcrdr_extra_classify.dot +0 -0
  78. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/results/mcrdr_extra_classify.png +0 -0
  79. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/results/mcrdr_stop_plus_rule_combined.dot +0 -0
  80. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/results/mcrdr_stop_plus_rule_combined.png +0 -0
  81. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/results/partial_mcrdr_extra.dot +0 -0
  82. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/results/partial_mcrdr_extra.png +0 -0
  83. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/results/relational_scrdr_classify.dot +0 -0
  84. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/results/relational_scrdr_classify.png +0 -0
  85. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/scripts/live_dot_server_client.py +0 -0
  86. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/setup.cfg +0 -0
  87. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/src/ripple_down_rules/datastructures/__init__.py +0 -0
  88. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/src/ripple_down_rules/datastructures/callable_expression.py +0 -0
  89. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/src/ripple_down_rules/datastructures/case.py +0 -0
  90. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/src/ripple_down_rules/datastructures/dataclasses.py +0 -0
  91. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/src/ripple_down_rules/datastructures/enums.py +0 -0
  92. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/src/ripple_down_rules/experts.py +0 -0
  93. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/src/ripple_down_rules/failures.py +0 -0
  94. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/src/ripple_down_rules/rules.py +0 -0
  95. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/src/ripple_down_rules/start-code-server.sh +0 -0
  96. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/src/ripple_down_rules/user_interface/__init__.py +0 -0
  97. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/src/ripple_down_rules/user_interface/gui.py +0 -0
  98. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/src/ripple_down_rules/user_interface/object_diagram.py +0 -0
  99. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/src/ripple_down_rules/user_interface/prompt.py +0 -0
  100. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/src/ripple_down_rules/user_interface/template_file_creator.py +0 -0
  101. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/src/ripple_down_rules/utils.py +0 -0
  102. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/src/ripple_down_rules.egg-info/dependency_links.txt +0 -0
  103. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/src/ripple_down_rules.egg-info/requires.txt +0 -0
  104. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/src/ripple_down_rules.egg-info/top_level.txt +0 -0
  105. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/__init__.py +0 -0
  106. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/conf/__init__.py +0 -0
  107. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/conf/world/__init__.py +0 -0
  108. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/conf/world/base_config.py +0 -0
  109. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/conf/world/handles_and_containers.py +0 -0
  110. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/conftest.py +0 -0
  111. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/datasets.py +0 -0
  112. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/factories/__init__.py +0 -0
  113. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/factories/world/__init__.py +0 -0
  114. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/factories/world/handles_and_containers.py +0 -0
  115. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_expert_answers/__init__.py +0 -0
  116. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_expert_answers/correct_drawer_rdr_expert_answers_fit.json +0 -0
  117. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_expert_answers/drawer_cabinet_expert_answers_fit.json +0 -0
  118. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_expert_answers/grdr_expert_answers_classify.json +0 -0
  119. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_expert_answers/grdr_expert_answers_fit.json +0 -0
  120. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_expert_answers/grdr_expert_answers_fit.py +0 -0
  121. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_expert_answers/grdr_expert_answers_fit_extra.json +0 -0
  122. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_expert_answers/grdr_expert_answers_fit_no_targets.json +0 -0
  123. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_expert_answers/grdr_expert_answers_fit_no_targets.py +0 -0
  124. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_expert_answers/mcrdr_expert_answers_classify.json +0 -0
  125. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_expert_answers/mcrdr_expert_answers_fit_no_targets.json +0 -0
  126. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_expert_answers/mcrdr_expert_answers_fit_no_targets.py +0 -0
  127. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_expert_answers/mcrdr_expert_answers_stop_only_fit.json +0 -0
  128. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_expert_answers/mcrdr_expert_answers_stop_only_fit.py +0 -0
  129. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_expert_answers/mcrdr_extra_expert_answers_classify.json +0 -0
  130. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_expert_answers/mcrdr_extra_expert_answers_fit.json +0 -0
  131. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_expert_answers/mcrdr_multi_line_expert_answers_fit.json +0 -0
  132. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_expert_answers/mcrdr_multi_line_expert_answers_fit.py +0 -0
  133. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_expert_answers/mcrdr_stop_only_answers_fit.json +0 -0
  134. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_expert_answers/mcrdr_stop_plus_rule_answers_fit.json +0 -0
  135. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_expert_answers/mcrdr_stop_plus_rule_combined_expert_answers_fit.json +0 -0
  136. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_expert_answers/mcrdr_stop_plus_rule_expert_answers_fit.json +0 -0
  137. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_expert_answers/mutagenic_expert_answers.json +0 -0
  138. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_expert_answers/relational_scrdr_expert_answers_classify.json +0 -0
  139. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_expert_answers/scrdr_expert_answers_classify.json +0 -0
  140. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_expert_answers/scrdr_expert_answers_fit.json +0 -0
  141. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_expert_answers/scrdr_expert_answers_fit.py +0 -0
  142. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_expert_answers/scrdr_expert_answers_fit_no_targets.json +0 -0
  143. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_expert_answers/scrdr_expert_answers_fit_no_targets.py +0 -0
  144. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_expert_answers/scrdr_multi_line_expert_answers_fit.json +0 -0
  145. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_expert_answers/scrdr_multi_line_expert_answers_fit.py +0 -0
  146. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_expert_answers/scrdr_world_expert_answers_fit.json +0 -0
  147. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_generated_rdrs/__init__.py +0 -0
  148. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_helpers/__init__.py +0 -0
  149. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_helpers/helpers.py +0 -0
  150. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_helpers/object_diagram_case_query.png +0 -0
  151. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_helpers/object_diagram_person.png +0 -0
  152. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_json_serialization.py +0 -0
  153. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_object_diagram.py +0 -0
  154. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_on_mutagenic.py +0 -0
  155. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_rdr.py +0 -0
  156. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_rdr_alchemy.py +0 -0
  157. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_rdr_decorators.py +0 -0
  158. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_rdr_helpers_rdrs.py +0 -0
  159. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_rdr_world/__init__.py +0 -0
  160. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_rdr_world/conftest.py +0 -0
  161. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_rdr_world/test_rdr_world.py +0 -0
  162. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_relational_rdr.py +0 -0
  163. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_relational_rdr_alchemy.py +0 -0
  164. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_results/datasets_physical_object_is_a_robot/__init__.py +0 -0
  165. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_results/datasets_physical_object_is_a_robot/physical_object_is_a_robot_output__scrdr.py +1 -1
  166. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_results/datasets_physical_object_is_a_robot/physical_object_is_a_robot_rdr.py +2 -2
  167. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_results/datasets_physical_object_select_objects_that_are_parts_of_robot/__init__.py +0 -0
  168. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/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 +1 -1
  169. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_results/datasets_physical_object_select_objects_that_are_parts_of_robot/physical_object_select_objects_that_are_parts_of_robot_rdr.py +2 -2
  170. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_sql_model.py +0 -0
  171. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_template_file_creator.py +0 -0
  172. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_user_interface/__init__.py +0 -0
  173. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_user_interface/test_ipython.py +0 -0
  174. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_user_interface/test_ipython_copilot.py +0 -0
  175. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_user_interface/test_prompt.py +0 -0
  176. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_user_interface/test_qt_gui_inline.py +0 -0
  177. {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/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.6.47
3
+ Version: 0.6.49
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
@@ -0,0 +1,12 @@
1
+ # Table of contents
2
+ # Learn more at https://jupyterbook.org/customize/toc.html
3
+
4
+ format: jb-book
5
+ root: intro
6
+ chapters:
7
+ - file: relational_example_tutorial
8
+ - file: relational_example_with_decorator_tutorial
9
+ - file: test_driven_rdr_fitting_tutorial
10
+ - file: propositional_example_tutorial
11
+ - file: bibliography
12
+ - file: autoapi/index
@@ -0,0 +1,150 @@
1
+ # Ripple Down Rules
2
+
3
+
4
+ Welcome to the Ripple Down Rules package!
5
+ The ripple_down_rules is a python package that implements the various ripple down rules versions, including
6
+ Single Classification (SCRDR), Multi Classification (MCRDR), and Generalised Ripple Down Rules (GRDR).
7
+
8
+ SCRDR, MCRDR, and GRDR are rule-based classifiers that are built incrementally, and can be used to classify data cases.
9
+ The rules are refined as new data cases are classified, this is done by prompting the user to add a new rule when a case
10
+ is misclassified or not classified at all. This allows the system to adapt to new data without the need for retraining.
11
+
12
+ SCRDR, MCRDR, and GRDR logic were inspired from the book:
13
+ ["Ripple Down Rules: An Alternative to Machine Learning"](https://www.taylorfrancis.com/books/mono/10.1201/9781003126157/ripple-rules-paul-compton-byeong-ho-kang) by Paul Compton, Byeong Ho Kang.
14
+
15
+
16
+ ## 🚀 Enhanced Ripple-Down Rules Engine – Key Features
17
+
18
+ ### 🧠 Data Model (Ontology) + Rule Base as One Entity
19
+ - 🧬 Unified data structure: Data Model (Ontology) and rules use the same Python data structures.
20
+ - 🔄 Automatic sync: Updates to the data model instantly reflect in the rule base.
21
+ - 📦 Version controlled: The rule base is a Python module, versioned with your project.
22
+
23
+ ### 🔁 Supports First, Second & Higher-Order Logic
24
+ - 🧩 Unlimited expressiveness: Rule conditions and conclusions are plain Python functions — anything Python can do, your rules can too!
25
+
26
+ ### 🛡️ Automatic Rule Base Maintenance
27
+ - ⚠️ Contradiction detection: New rules are auto-checked for logical conflicts.
28
+ - 🔧 Prompted refinements: If a contradiction arises, you're guided to add a refinement rule.
29
+
30
+ ### 📝 Transparent & Editable Rule Base
31
+ - 📖 Readable rules: Rules are clean, understandable Python code.
32
+ - 🔄 Reload-friendly: Easily edit and reload rules manually as needed.
33
+
34
+ ### 💻 Developer-Centric Interface
35
+ - 👨‍💻 Feels like home: Seamless integration with your favorite IDE.
36
+ - ✨ Modern coding experience: Auto-completion and suggestions supported via IDE plugins.
37
+
38
+ ### 🤖 LLM-Powered Rule Writing
39
+ - 💡 AI-assisted authoring: Ask AI for help or suggestions directly within the IDE.
40
+ - ⚡ Smart completion: Context-aware completions streamline rule writing.
41
+
42
+ ### 🎯 Flexible Rule Specificity
43
+ - 🧪 Instance-level precision: Write rules for highly specific object scenarios.
44
+ - 🏛️ Generalization-ready: Create broad rules for superclass relationships.
45
+
46
+ ### 🖼️ GUI for Rule Exploration
47
+ - 🧭 Object Explorer Panel: Navigate and inspect objects easily.
48
+ - 🧯 Interactive Diagram: Expandable/collapsible object diagram to guide rule creation visually.
49
+
50
+ This work aims to provide a flexible and powerful rule-based system that can be used in various applications,
51
+ from simple classification tasks to complex decision-making systems. Furthermore, one of the main goals is to
52
+ provide an easy-to-use interface that allows users to write rules in a natural way, without the need for
53
+ complex configurations or setups, and without the need to learn old or deprecated programming languages.
54
+
55
+ Future (and current) work will focus on improving the user experience, adding more features, and enhancing the
56
+ performance, so stay tuned for updates! and feel free to contribute, give feedback, or report issues on the
57
+ [GitHub repository](https://github.com/AbdelrhmanBassiouny/ripple_down_rules/issues)
58
+
59
+ ## To Cite:
60
+
61
+ ```bib
62
+ @software{bassiouny2025rdr,
63
+ author = {Bassiouny, Abdelrhman},
64
+ title = {Ripple-Down-Rules},
65
+ url = {https://github.com/AbdelrhmanBassiouny/ripple_down_rules},
66
+ version = {0.6.47},
67
+ }
68
+ ```
69
+
70
+ ## Installation
71
+ ```bash
72
+ sudo apt-get install graphviz graphviz-dev
73
+ pip install ripple_down_rules
74
+ ```
75
+ For GUI support, also install:
76
+
77
+ ```bash
78
+ sudo apt-get install libxcb-cursor-dev
79
+ ```
80
+
81
+ ## Technical Overview
82
+
83
+ ### CaseQuery
84
+
85
+ The {py:class}`ripple_down_rules.datastructures.dataclasses.CaseQuery` class is a data structure that represents a query for a case in the ripple down rules system.
86
+ It mainly requires the queried object which is referred to as `case`, the target attribute that is being queried, the type(s) of the target, and a boolean indicating whether the target is mutually exclusive or not.
87
+
88
+ ### RippleDownRules
89
+
90
+ {py:class}`ripple_down_rules.rdr.RippleDownRules` This is the main abstract class for the ripple down rules. From this class, the different versions of
91
+ ripple down rules are derived. So the {py:class}`ripple_down_rules.rdr.SingleClassRDR`, {py:class}`ripple_down_rules.rdr.MultiClassRDR`, and {py:class}`ripple_down_rules.rdr.GeneralRDR` classes
92
+ are all derived from this class.
93
+
94
+ For most cases, you will use the `GeneralRDR` class, which is the most general version of the ripple down rules,
95
+ and it internally uses the `SingleClassRDR` and `MultiClassRDR` classes to handle the classification tasks depending
96
+ on the case query.
97
+
98
+ This class has four main methods that you will mostly use:
99
+
100
+ - {py:func}`ripple_down_rules.rdr.RippleDownRules.fit`: This method is used to fit the rules to the data. It takes a list of `CaseQuery` objects that contain the
101
+ data cases and their targets, and it will prompt the user to add rules when a case is misclassified or not classified at all.
102
+ - {py:func}`ripple_down_rules.rdr.RippleDownRules.fit_case`: This method is used to fit a single case to the rules.
103
+ - {py:func}`ripple_down_rules.rdr.RippleDownRules.classify`: This method is used to classify a data case. It takes a data case (any python object) and returns the
104
+ predicted target.
105
+ - {py:func}`ripple_down_rules.rdr.RippleDownRules.save`: This method is used to save the rules to a file. It will save the rules as a python module that can be imported
106
+ in your project. In addition, it will save the rules as a JSON file with some metadata about the rules.
107
+ - {py:func}`ripple_down_rules.rdr.RippleDownRules.load`: This method is used to load the rules from a file. It will load the rules from a JSON file and then update the
108
+ rules from the python module (which is important in case the user manually edited the rules in the python module).
109
+
110
+ ### SingleClassRDR
111
+ This is the single classification version of the ripple down rules. It is used to classify data cases into a single
112
+ target class. This is used when your classification is mutually exclusive, meaning that a data case can only belong
113
+ to one class. For example, an animal can be either a "cat" or a "dog", but not both at the same time.
114
+
115
+ ### MultiClassRDR
116
+ This is the multi classification version of the ripple down rules. It is used to classify data cases into multiple
117
+ target classes. This is used when your classification is not mutually exclusive, meaning that a data case can belong
118
+ to multiple classes at the same time. For example, an animal's habitat can be both "land" and "water" at the same time.
119
+
120
+ ### GeneralRDR
121
+ This is the general version of the ripple down rules. It has the following features:
122
+ - It can handle both single and multi classification tasks by creating instances of `SingleClassRDR` and `MultiClassRDR`
123
+ internally depending on the case query.
124
+ - It performs multiple passes over the rules and uses conclusions from previous passes to refine the classification,
125
+ which allows it to handle complex classification tasks.
126
+
127
+ ## Expert
128
+
129
+ The {py:class}`ripple_down_rules.experts.Expert` is an interface between the ripple down rules and the rule writer. Currently, only a {py:class}`ripple_down_rules.experts.Human` expert is
130
+ implemented, but it is designed to be easily extendable to other types of experts, such as LLMs or other AI systems.
131
+
132
+ The main APIs are:
133
+
134
+ - {py:func}`ripple_down_rules.experts.Expert.ask_for_conclusion`: This method is used to ask the expert for a conclusion or a target for a data case.
135
+ - {py:func}`ripple_down_rules.experts.Expert.ask_for_conditions`: This method is used to ask the expert for the conditions that should be met for a rule to be
136
+ applied or evaluated.
137
+
138
+ ## RDRDecorator
139
+
140
+ The {py:class}`ripple_down_rules.rdr_decorators.RDRDecorator` is a decorator that can be used to create rules in a more convenient way. It allows you to write
141
+ functions normally and then decorate them with the `@RDRDecorator().decorator`. This will allow the function to be
142
+ to use ripple down rules to provide its output. This also allows you to write your own initial function logic, and then
143
+ this will be used input or feature to the ripple down rules.
144
+
145
+
146
+ ## Example Usage
147
+
148
+ - [Relational Example](relational_example_tutorial.md): This example shows how to use the Ripple Down Rules to classify objects in a relational model.
149
+ - [Relational Example with Decorator](relational_example_with_decorator_tutorial.md): This example shows how to use the Ripple Down Rules with a decorator to overload methods.
150
+ - [Propositional Example](propositional_example_tutorial.md): This example shows generic usage of the Ripple Down Rules to classify objects in a propositional setting.
@@ -0,0 +1,114 @@
1
+ ### Propositional Example Tutorial
2
+
3
+ By propositional, I mean that each rule conclusion is a propositional logic statement with a constant value.
4
+
5
+ For this example, we will use the [UCI Zoo dataset](https://archive.ics.uci.edu/ml/datasets/zoo) to classify animals
6
+ into their species based on their features. The dataset contains 101 animals with 16 features, and the target is th
7
+ e species of the animal.
8
+
9
+ To install the dataset:
10
+ ```bash
11
+ pip install ucimlrepo
12
+ ```
13
+
14
+ ### Prepare the Data
15
+
16
+ Put this in a file named `propositional_data.py`:
17
+
18
+ ```python
19
+ from __future__ import annotations
20
+ from ripple_down_rules.datastructures.case import create_cases_from_dataframe
21
+ from ucimlrepo import fetch_ucirepo
22
+ from enum import Enum
23
+
24
+ class Species(str, Enum):
25
+ """Enum for the species of the animals in the UCI Zoo dataset."""
26
+ mammal = "mammal"
27
+ bird = "bird"
28
+ reptile = "reptile"
29
+ fish = "fish"
30
+ amphibian = "amphibian"
31
+ insect = "insect"
32
+ molusc = "molusc"
33
+
34
+ @classmethod
35
+ def from_str(cls, value: str) -> Species:
36
+ return getattr(cls, value)
37
+
38
+ # fetch dataset
39
+ zoo = fetch_ucirepo(id=111)
40
+
41
+ # data (as pandas dataframes)
42
+ X = zoo.data.features
43
+ y = zoo.data.targets
44
+
45
+ # This is a utility that allows each row to be a Case instance,
46
+ # which simplifies access to column values using dot notation.
47
+ all_cases = create_cases_from_dataframe(X, name="Animal")
48
+
49
+ # The targets are the species of the animals
50
+ category_names = ["mammal", "bird", "reptile", "fish", "amphibian", "insect", "molusc"]
51
+ category_id_to_name = {i + 1: name for i, name in enumerate(category_names)}
52
+ targets = [Species.from_str(category_id_to_name[i]) for i in y.values.flatten()]
53
+ ```
54
+
55
+ ### Define the Case Queries
56
+ Create a new python script to define the case queries and the Ripple Down Rules classifier.
57
+ For every target, we create a `CaseQuery` that specifies the case, the target attribute, the type of the target,
58
+ and whether the classification is mutually exclusive or not. In this case, we set `mutually_exclusive` to `True` since
59
+ each animal belongs to only one species.
60
+ ```python
61
+ from ripple_down_rules import CaseQuery
62
+ from propositional_data import all_cases, targets, Species
63
+
64
+
65
+ case_queries = [CaseQuery(case, 'species', type(target), True, _target=target)
66
+ for case, target in zip(all_cases[:10], targets[:10])]
67
+ ```
68
+
69
+ ### Create and Use the Ripple Down Rules Classifier
70
+
71
+ ```python
72
+ # Optionally Enable GUI if available
73
+ from ripple_down_rules.helpers import enable_gui
74
+ enable_gui()
75
+
76
+
77
+ from ripple_down_rules import GeneralRDR
78
+ from ripple_down_rules.utils import render_tree
79
+
80
+
81
+ # Now that we are done with the data preparation, we can create and use the Ripple Down Rules classifier.
82
+ grdr = GeneralRDR(save_dir="./", model_name="species_rdr")
83
+
84
+ # Fit the GRDR to the data
85
+ grdr.fit(case_queries, animate_tree=True)
86
+
87
+ # Render the tree to a file
88
+ render_tree(grdr.start_rules[0], use_dot_exporter=True, filename="species_rdr")
89
+
90
+ # Classify a case
91
+ cat = grdr.classify(all_cases[50])
92
+ assert cat['species'] == targets[50]
93
+ ```
94
+
95
+ When prompted to write conditions for the given target, I press the "Edit" buttond (%edit in the Ipython interface) and I wrote the following
96
+ inside the template function that the Ripple Down Rules created:
97
+ ```python
98
+ return case.milk == 1
99
+ ```
100
+ Then, I press the "Load" button (%load in Ipython), this loads the function I just wrote such that I can test it inside
101
+ the Ipython interface.
102
+ After that, I press the "Accept" button (return func_name(case) in Ipython), this will save the rule permanently.
103
+
104
+ When prompted for conditions for the next target, I wrote the following inside the template function that the
105
+ Ripple Down Rules created:
106
+ ```python
107
+ return case.aquatic == 1
108
+ ```
109
+
110
+ I keep doing this for all the prompts until I have fitted the rules such that the classifier correctly classifies the
111
+ animals in the dataset. This took around 10 prompts in total, and the rules correctly classified 101 animals in the dataset.
112
+
113
+ The rule tree generated from fitting all the dataset will look like this:
114
+ ![species_rdr](https://raw.githubusercontent.com/AbdelrhmanBassiouny/ripple_down_rules/main/images/scrdr.png)
@@ -0,0 +1,120 @@
1
+ # Relational Example Tutorial
2
+
3
+ In this tutorial, we will walk through the process of fitting a Ripple Down Rules (RDR) model to a relational data model.
4
+ Where there are multiple objects that are related to each other, and we want to query them using Ripple Down Rules.
5
+
6
+ <iframe width="560" height="315" src="https://www.youtube.com/embed/Dgcj7Y7qNyI" frameborder="0" allowfullscreen></iframe>
7
+
8
+ ### Define your Data Model
9
+
10
+ Here we define a simple data model of a robot with parts both of which are physical objects and can contain other physical objects.
11
+ Put this in a file called `relational_model.py`:
12
+ ```python
13
+ from __future__ import annotations
14
+ from dataclasses import dataclass, field
15
+ from typing_extensions import List
16
+
17
+
18
+ @dataclass(unsafe_hash=True)
19
+ class PhysicalObject:
20
+ """
21
+ A physical object is an object that can be contained in a container.
22
+ """
23
+ name: str
24
+ contained_objects: List[PhysicalObject] = field(default_factory=list, hash=False)
25
+
26
+ @dataclass(unsafe_hash=True)
27
+ class Part(PhysicalObject):
28
+ ...
29
+
30
+ @dataclass(unsafe_hash=True)
31
+ class Robot(PhysicalObject):
32
+ parts: List[Part] = field(default_factory=list, hash=False)
33
+ ```
34
+
35
+ ### Create your Case Object (Object to be Queried):
36
+ In a new python script, create instances of the `Part` and `Robot` classes to represent your robot and its parts.
37
+ This will be the object that you will query with Ripple Down Rules.
38
+ ```python
39
+ from relational_model import Part, Robot, PhysicalObject
40
+
41
+
42
+ part_a = Part(name="A")
43
+ part_b = Part(name="B")
44
+ part_c = Part(name="C")
45
+ robot = Robot("pr2", parts=[part_a])
46
+ part_a.contained_objects = [part_b]
47
+ part_b.contained_objects = [part_c]
48
+ ```
49
+
50
+ ### (Optional) Enable Ripple Down Rules GUI
51
+
52
+ If you want to use the GUI for Ripple Down Rules, ensure you have PyQt6 installed:
53
+ ```bash
54
+ pip install pyqt6
55
+ sudo apt-get install libxcb-cursor-dev
56
+ ```
57
+
58
+ Then, you can enable the GUI in your script as follows:
59
+ ```python
60
+ # Optionally Enable GUI if available
61
+ from ripple_down_rules.helpers import enable_gui
62
+ enable_gui()
63
+ ```
64
+
65
+ ### Define the RDR Model and the Case Query
66
+ Here create/load our RDR model, then we define a query on the `robot` object to find out which objects are contained within it.
67
+ The output type is specified as `PhysicalObject`, and there can be multiple contained objects so we set `mutually_exclusive` to `False`.
68
+
69
+ Optionally enable the GUI.
70
+
71
+ ```python
72
+ from ripple_down_rules import CaseQuery, GeneralRDR
73
+
74
+ grdr = GeneralRDR(save_dir='./', model_name='part_containment_rdr')
75
+
76
+ case_query = CaseQuery(robot, "contained_objects", (PhysicalObject,), False)
77
+ ```
78
+
79
+ ### Fit the Model to the Case Query by Answering the prompts.
80
+ ```python
81
+ grdr.fit_case(case_query)
82
+ ```
83
+
84
+ When prompted to write a rule, I press edit in GUI (or type %edit in the Ipython interface if not using GUI),
85
+ I wrote the following inside the template function that the Ripple Down Rules created for me, this function takes a
86
+ `case` object as input:
87
+
88
+ ```python
89
+ contained_objects = []
90
+ for part in case.parts:
91
+ contained_objects.extend(part.contained_objects)
92
+ return contained_objects
93
+ ```
94
+
95
+ I press the "Load" button (%load in Ipython), this loads the function I just wrote such that I can test it inside the
96
+ Ipython interface.
97
+
98
+ If I like the result, I press the "Accept" button (return func_name(case) in Ipython), this will save the rule
99
+ permanently.
100
+
101
+ And then when asked for conditions, I wrote the following inside the template function that the Ripple Down Rules
102
+ created:
103
+
104
+ ```python
105
+ return len(case.parts) > 0
106
+ ```
107
+
108
+ This means that the rule will only be applied if the robot has parts.
109
+
110
+ ### Finally, Classify the Object and Verify the Result
111
+
112
+ ```python
113
+ result = grdr.classify(robot)
114
+ assert result['contained_objects'] == {part_b}
115
+ ```
116
+
117
+ If you notice, the result only contains part B, while one could say that part C is also contained in the robot, but,
118
+ the rule we wrote only returns the contained objects of the parts of the robot. To get part C, we would have to
119
+ add another rule that says that the contained objects of my contained objects are also contained in me, you can
120
+ try that yourself and see if it works!
@@ -0,0 +1,123 @@
1
+ # Relational Example With Decorator Tutorial
2
+
3
+ Similar to the [Relational Example Tutorial](relational_example_tutorial.md), but using the `RDRDecorator` to enable
4
+ Ripple Down Rules classification on a method of a class (or any method).
5
+
6
+ <iframe width="560" height="315" src="https://www.youtube.com/embed/iapEdQRZTKo" frameborder="0" allowfullscreen></iframe>
7
+
8
+ ### Define your Data Model With RDRDecorator
9
+
10
+ Here we define a simple data model of a robot with parts both of which are physical objects and can contain other physical objects.
11
+ We also add an RDRDecorator to the Robot class on the get_contained_objects method to enable Ripple Down Rules classification on this method.
12
+
13
+ Put this in a file called `relational_model.py`:
14
+ ```python
15
+ from __future__ import annotations
16
+ from dataclasses import dataclass, field
17
+ from typing_extensions import List
18
+ from ripple_down_rules.rdr_decorators import RDRDecorator
19
+
20
+
21
+ @dataclass(unsafe_hash=True)
22
+ class PhysicalObject:
23
+ """
24
+ A physical object is an object that can be contained in a container.
25
+ """
26
+ name: str
27
+ contained_objects: List[PhysicalObject] = field(default_factory=list, hash=False)
28
+
29
+ @dataclass(unsafe_hash=True)
30
+ class Part(PhysicalObject):
31
+ ...
32
+
33
+ @dataclass(unsafe_hash=True)
34
+ class Robot(PhysicalObject):
35
+ parts: List[Part] = field(default_factory=list, hash=False)
36
+ containment_rdr: RDRDecorator = RDRDecorator("./", (PhysicalObject,), False,
37
+ fit=False)
38
+
39
+ @containment_rdr.decorator
40
+ def get_contained_objects(self) -> List[PhysicalObject]:
41
+ """
42
+ Returns the contained objects of the robot.
43
+ """
44
+ ...
45
+ ```
46
+
47
+ ### Create your Case Object (Object to be Queried):
48
+ In a new python script, create instances of the `Part` and `Robot` classes to represent your robot and its parts.
49
+ This will be the object that you will query with Ripple Down Rules.
50
+ ```python
51
+ from relational_model import Part, Robot, PhysicalObject
52
+
53
+
54
+ part_a = Part(name="A")
55
+ part_b = Part(name="B")
56
+ part_c = Part(name="C")
57
+ robot = Robot("pr2", parts=[part_a])
58
+ part_a.contained_objects = [part_b]
59
+ part_b.contained_objects = [part_c]
60
+ ```
61
+
62
+ ### (Optional) Enable Ripple Down Rules GUI
63
+
64
+ If you want to use the GUI for Ripple Down Rules, ensure you have PyQt6 installed:
65
+ ```bash
66
+ pip install pyqt6
67
+ sudo apt-get install libxcb-cursor-dev
68
+ ```
69
+
70
+ Then, you can enable the GUI in your script as follows:
71
+ ```python
72
+ # Optionally Enable GUI if available
73
+ from ripple_down_rules.helpers import enable_gui
74
+ enable_gui()
75
+ ```
76
+
77
+ ### Directly Use the decorated method to Fit/Classify the Object
78
+ ```python
79
+ robot.containment_rdr.fit = True
80
+ robot.get_contained_objects()
81
+
82
+ robot.containment_rdr.fit = False
83
+ contained_objects = robot.get_contained_objects()
84
+ assert contained_objects == [part_b]
85
+ ```
86
+
87
+ When prompted to write a rule, I press edit in GUI (or type %edit in the Ipython interface if not using GUI),
88
+ I wrote the following inside the template function that the Ripple Down Rules created for me, this function takes a
89
+ `case` object as input:
90
+
91
+ ```python
92
+ contained_objects = []
93
+ for part in self_.parts:
94
+ contained_objects.extend(part.contained_objects)
95
+ return contained_objects
96
+ ```
97
+
98
+ I press the "Load" button (%load in Ipython), this loads the function I just wrote such that I can test it inside the
99
+ Ipython interface.
100
+
101
+ If I like the result, I press the "Accept" button (return func_name(**case) in Ipython), this will save the rule
102
+ permanently.
103
+
104
+ And then when asked for conditions, I wrote the following inside the template function that the Ripple Down Rules
105
+ created:
106
+
107
+ ```python
108
+ return len(case.parts) > 0
109
+ ```
110
+
111
+ This means that the rule will only be applied if the robot has parts.
112
+
113
+ ### Additional Tip for RDRDecorator Usage:
114
+ We can let the fit mode be `True`, but give the rdr a function that tells it when to prompt for an answer.
115
+ For example, we can ask for an answer only when the robot's name is "tracy", which will result in the rdr not asking
116
+ for an answer because the robot name is "pr2" for the current case.
117
+ The input to the `ask_now` function is a dictionary with the original function arguments, while arguments like
118
+ `self` and `cls` are passed as a special key `'self_'` or `'cls_'` respectively.
119
+ ```python
120
+ robot.containment_rdr.fit = True
121
+ robot.containment_rdr.ask_now = lambda case: case['self_'].name == "tracy"
122
+ robot.get_contained_objects()
123
+ ```
@@ -0,0 +1,133 @@
1
+ # Test Driven RDR Fitting Tutorial
2
+
3
+ In this tutorial, we will walk through the process of fitting a Ripple Down Rules (RDR) model to a case object using a test-driven approach.
4
+ Similar to the [RDR Fitting Tutorial](rdr_fitting_tutorial.md), but with a focus on writing tests first and then
5
+ implementing the rules, this will enable maintenance of the rules and ensure that they work as expected with changes over time,
6
+ and it will enable collaboration with other developers who can write tests for their rules and merge them into the main rule tree.
7
+
8
+ <iframe width="560" height="315" src="https://www.youtube.com/embed/g5lpQIHYIG0" frameborder="0" allowfullscreen></iframe>
9
+
10
+ ### Define your Data Model
11
+
12
+ Here we define a simple data model of a robot with parts both of which are physical objects and can contain other physical objects.
13
+ Put this in a file called `relational_model.py`:
14
+ ```python
15
+ from __future__ import annotations
16
+ from dataclasses import dataclass, field
17
+ from typing_extensions import List
18
+
19
+
20
+ @dataclass(unsafe_hash=True)
21
+ class PhysicalObject:
22
+ """
23
+ A physical object is an object that can be contained in a container.
24
+ """
25
+ name: str
26
+ contained_objects: List[PhysicalObject] = field(default_factory=list, hash=False)
27
+
28
+ @dataclass(unsafe_hash=True)
29
+ class Part(PhysicalObject):
30
+ ...
31
+
32
+ @dataclass(unsafe_hash=True)
33
+ class Robot(PhysicalObject):
34
+ parts: List[Part] = field(default_factory=list, hash=False)
35
+ ```
36
+
37
+ ### Create your Case Object (Object to be Queried) In a Factory Method:
38
+ In a new python test script, create instances of the `Part` and `Robot` classes to represent your robot and its parts.
39
+ This will be the object that you will query with Ripple Down Rules. Put this inside a factory method such that
40
+ the case can be recreated easily and consistently over the lifetime of the project.
41
+
42
+ ```python
43
+ from relational_model import Part, Robot, PhysicalObject
44
+ from ripple_down_rules import CaseQuery
45
+
46
+ def robot_factory() -> Robot:
47
+ """
48
+ Factory method to create a robot with parts.
49
+ """
50
+ part_a = Part(name="A")
51
+ part_b = Part(name="B")
52
+ part_c = Part(name="C")
53
+
54
+ robot = Robot("pr2", parts=[part_a])
55
+
56
+ # Establish containment relationships
57
+ part_a.contained_objects = [part_b]
58
+ part_b.contained_objects = [part_c]
59
+
60
+ return robot
61
+ ```
62
+
63
+ ### Put The RDR model in a pytest fixture (or a normal method):
64
+ Here since this RDR model will most likely be used in multiple tests, we put it in a pytest fixture.
65
+
66
+ ```python
67
+ import pytest
68
+ from ripple_down_rules import GeneralRDR
69
+
70
+
71
+ @pytest.fixture
72
+ def robot_rdr():
73
+ """
74
+ Fixture to create a GeneralRDR instance for testing.
75
+ """
76
+ return GeneralRDR(save_dir='./', model_name='robot_rdr')
77
+ ```
78
+
79
+ ### Write a Test for fitting contained_objects of the robot:
80
+ Here we write a test that will check if the `contained_objects` of the robot are correctly identified, and this will also
81
+ serve as the fitting process for the Ripple Down Rules model.
82
+
83
+ Notice that we added two extra inputs to the `CaseQuery`:
84
+ - `scenario`: This is a reference to the test function itself, which can be useful for debugging and understanding the
85
+ context of the case, and recreating the scenario when verifying the rules or comparing with other rules.
86
+ - `case_factory`: This is a reference to the factory method that creates the case object,
87
+ allowing the RDR model to recreate the case object when needed.
88
+
89
+ ```python
90
+ def test_fit_robot_contained_objects(robot_rdr):
91
+ """
92
+ Test to fit the Ripple Down Rules model to the robot's contained objects.
93
+ """
94
+ robot = robot_factory()
95
+
96
+ # Define the case query for contained objects
97
+ case_query = CaseQuery(robot, "contained_objects", (PhysicalObject,), False,
98
+ scenario=test_fit_robot_contained_objects,
99
+ case_factory=robot_factory)
100
+
101
+ # Fit the RDR model to the case query
102
+ robot_rdr.fit_case(case_query, update_existing_rules=False)
103
+
104
+ # Classify the object and verify the result
105
+ result = robot_rdr.classify(robot)
106
+
107
+ # Assert that the result contains part B as the only contained object
108
+ assert result['contained_objects'] == {robot.parts[0].contained_objects[0]}
109
+ ```
110
+
111
+ ### (Optional) Enable Ripple Down Rules GUI
112
+
113
+ If you want to use the GUI for Ripple Down Rules, ensure you have PyQt6 installed:
114
+ ```bash
115
+ pip install pyqt6
116
+ sudo apt-get install libxcb-cursor-dev
117
+ ```
118
+
119
+ Then, you can enable the GUI in your script as follows:
120
+ ```python
121
+ # Optionally Enable GUI if available
122
+ from ripple_down_rules.helpers import enable_gui
123
+ enable_gui()
124
+ ```
125
+
126
+ ### Run the Test
127
+
128
+ You can run the test using pytest:
129
+ ```bash
130
+ pytest -s test_rdr_fitting.py
131
+ ```
132
+ This will execute the test, prompting you to fit the RDR model to the case object or if there is rules already fitted,
133
+ it will classify the object based on the existing rules.
@@ -0,0 +1,24 @@
1
+ from decorator_model import Robot
2
+ from relational_model import Part, PhysicalObject
3
+ from ripple_down_rules.helpers import enable_gui
4
+
5
+
6
+ # Define a simple robot with parts and containment relationships
7
+ part_a = Part(name="A")
8
+ part_b = Part(name="B")
9
+ part_c = Part(name="C")
10
+ robot = Robot("pr2", parts=[part_a])
11
+ part_a.contained_objects = [part_b]
12
+ part_b.contained_objects = [part_c]
13
+
14
+ # Optional: Use the GUI.r
15
+ enable_gui()
16
+
17
+ # Create a GeneralRDR instance and fit it to the case query
18
+ robot.containment_rdr.fit = True
19
+ robot.get_contained_objects()
20
+
21
+ # Classify the robot to check if it contains part_b
22
+ robot.containment_rdr.fit = False
23
+ result = robot.get_contained_objects()
24
+ assert result == [part_b]