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.
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/PKG-INFO +1 -1
- ripple_down_rules-0.6.49/doc/_toc.yml +12 -0
- ripple_down_rules-0.6.49/doc/intro.md +150 -0
- ripple_down_rules-0.6.49/doc/propositional_example_tutorial.md +114 -0
- ripple_down_rules-0.6.49/doc/relational_example_tutorial.md +120 -0
- ripple_down_rules-0.6.49/doc/relational_example_with_decorator_tutorial.md +123 -0
- ripple_down_rules-0.6.49/doc/test_driven_rdr_fitting_tutorial.md +133 -0
- ripple_down_rules-0.6.49/examples/decorator_example.py +24 -0
- ripple_down_rules-0.6.49/examples/decorator_model.py +32 -0
- ripple_down_rules-0.6.49/examples/relational_example.py +25 -0
- ripple_down_rules-0.6.49/examples/relational_model.py +36 -0
- ripple_down_rules-0.6.49/examples/test_relational_example.py +28 -0
- ripple_down_rules-0.6.49/src/ripple_down_rules/__init__.py +17 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/src/ripple_down_rules/helpers.py +12 -1
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/src/ripple_down_rules/rdr.py +69 -55
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/src/ripple_down_rules/rdr_decorators.py +12 -8
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/src/ripple_down_rules/user_interface/ipython_custom_shell.py +1 -1
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/src/ripple_down_rules.egg-info/PKG-INFO +1 -1
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/src/ripple_down_rules.egg-info/SOURCES.txt +9 -6
- ripple_down_rules-0.6.49/test/test_results/__init__.py +1 -0
- 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
- 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
- ripple_down_rules-0.6.47/doc/_toc.yml +0 -8
- ripple_down_rules-0.6.47/doc/intro.md +0 -296
- ripple_down_rules-0.6.47/examples/part_containment_rdr/__init__.py +0 -1
- ripple_down_rules-0.6.47/examples/part_containment_rdr/rdr_metadata/part_containment_rdr.json +0 -95
- ripple_down_rules-0.6.47/examples/part_containment_rdr/robot_contained_objects_mcrdr.py +0 -20
- ripple_down_rules-0.6.47/examples/part_containment_rdr/robot_contained_objects_mcrdr_defs.py +0 -23
- ripple_down_rules-0.6.47/examples/part_containment_rdr/robot_rdr.py +0 -16
- ripple_down_rules-0.6.47/examples/relational_example.py +0 -48
- ripple_down_rules-0.6.47/src/ripple_down_rules/__init__.py +0 -5
- 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
- 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
- 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
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/.github/workflows/build_and_deploy_doc.yml +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/.github/workflows/ci.yml +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/.github/workflows/publish-to-test-pypi.yml +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/.gitignore +0 -0
- {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
- {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
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/LICENSE +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/README.md +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/doc/_config.yml +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/doc/bibliography.md +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/doc/references.bib +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/doc/requirements.txt +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/examples/__init__.py +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/examples/animal_species.py +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/images/scrdr.dot +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/images/scrdr.png +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/images/thinking_pr2.jpg +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/pyproject.toml +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/pytest.ini +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/requirements-dev-ci.txt +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/requirements-dev.txt +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/requirements-gui.txt +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/requirements-viz.txt +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/requirements.txt +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/results/complete_mcrdr_extra.dot +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/results/complete_mcrdr_extra.png +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/results/complete_mcrdr_stop_only.dot +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/results/complete_mcrdr_stop_only.png +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/results/complete_mcrdr_stop_plus_rule.dot +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/results/complete_mcrdr_stop_plus_rule.png +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/results/complete_scrdr.dot +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/results/complete_scrdr.png +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/results/complete_scrdr_2.dot +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/results/complete_scrdr_2.png +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/results/complete_scrdr_3.dot +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/results/complete_scrdr_3.png +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/results/grdr_Habitat.dot +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/results/grdr_Habitat.png +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/results/grdr_Species.dot +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/results/grdr_Species.png +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/results/mcrdr_extra.dot +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/results/mcrdr_extra.png +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/results/mcrdr_extra_classify.dot +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/results/mcrdr_extra_classify.png +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/results/mcrdr_stop_plus_rule_combined.dot +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/results/mcrdr_stop_plus_rule_combined.png +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/results/partial_mcrdr_extra.dot +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/results/partial_mcrdr_extra.png +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/results/relational_scrdr_classify.dot +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/results/relational_scrdr_classify.png +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/scripts/live_dot_server_client.py +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/setup.cfg +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/src/ripple_down_rules/datastructures/__init__.py +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/src/ripple_down_rules/datastructures/callable_expression.py +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/src/ripple_down_rules/datastructures/case.py +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/src/ripple_down_rules/datastructures/dataclasses.py +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/src/ripple_down_rules/datastructures/enums.py +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/src/ripple_down_rules/experts.py +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/src/ripple_down_rules/failures.py +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/src/ripple_down_rules/rules.py +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/src/ripple_down_rules/start-code-server.sh +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/src/ripple_down_rules/user_interface/__init__.py +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/src/ripple_down_rules/user_interface/gui.py +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/src/ripple_down_rules/user_interface/object_diagram.py +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/src/ripple_down_rules/user_interface/prompt.py +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/src/ripple_down_rules/user_interface/template_file_creator.py +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/src/ripple_down_rules/utils.py +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/src/ripple_down_rules.egg-info/dependency_links.txt +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/src/ripple_down_rules.egg-info/requires.txt +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/src/ripple_down_rules.egg-info/top_level.txt +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/__init__.py +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/conf/__init__.py +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/conf/world/__init__.py +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/conf/world/base_config.py +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/conf/world/handles_and_containers.py +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/conftest.py +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/datasets.py +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/factories/__init__.py +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/factories/world/__init__.py +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/factories/world/handles_and_containers.py +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_expert_answers/__init__.py +0 -0
- {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
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_expert_answers/drawer_cabinet_expert_answers_fit.json +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_expert_answers/grdr_expert_answers_classify.json +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_expert_answers/grdr_expert_answers_fit.json +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_expert_answers/grdr_expert_answers_fit.py +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_expert_answers/grdr_expert_answers_fit_extra.json +0 -0
- {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
- {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
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_expert_answers/mcrdr_expert_answers_classify.json +0 -0
- {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
- {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
- {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
- {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
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_expert_answers/mcrdr_extra_expert_answers_classify.json +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_expert_answers/mcrdr_extra_expert_answers_fit.json +0 -0
- {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
- {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
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_expert_answers/mcrdr_stop_only_answers_fit.json +0 -0
- {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
- {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
- {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
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_expert_answers/mutagenic_expert_answers.json +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_expert_answers/relational_scrdr_expert_answers_classify.json +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_expert_answers/scrdr_expert_answers_classify.json +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_expert_answers/scrdr_expert_answers_fit.json +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_expert_answers/scrdr_expert_answers_fit.py +0 -0
- {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
- {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
- {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
- {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
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_expert_answers/scrdr_world_expert_answers_fit.json +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_generated_rdrs/__init__.py +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_helpers/__init__.py +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_helpers/helpers.py +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_helpers/object_diagram_case_query.png +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_helpers/object_diagram_person.png +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_json_serialization.py +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_object_diagram.py +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_on_mutagenic.py +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_rdr.py +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_rdr_alchemy.py +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_rdr_decorators.py +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_rdr_helpers_rdrs.py +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_rdr_world/__init__.py +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_rdr_world/conftest.py +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_rdr_world/test_rdr_world.py +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_relational_rdr.py +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_relational_rdr_alchemy.py +0 -0
- {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
- {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
- {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
- {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
- {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
- {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
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_sql_model.py +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_template_file_creator.py +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_user_interface/__init__.py +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_user_interface/test_ipython.py +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_user_interface/test_ipython_copilot.py +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_user_interface/test_prompt.py +0 -0
- {ripple_down_rules-0.6.47 → ripple_down_rules-0.6.49}/test/test_user_interface/test_qt_gui_inline.py +0 -0
- {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.
|
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
|
+

|
@@ -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]
|