├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── documentation_request.md │ └── feature_request.md └── workflows │ ├── install_display.sh │ ├── main.yml │ └── mypy.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── codetools ├── black │ ├── check.sh │ ├── fix.sh │ └── requirements.txt ├── darglint │ ├── check.sh │ └── requirements.txt ├── fix_all.sh ├── isort │ ├── check.sh │ ├── fix.sh │ └── requirements.txt ├── mypy │ ├── check.sh │ ├── check_all.sh │ ├── mypy.ini │ └── requirements.txt ├── pydocstyle │ ├── check.sh │ └── requirements.txt ├── pyflakes │ ├── check.sh │ └── requirements.txt ├── read_project_parts.sh ├── requirements.txt └── sort_all │ ├── fix.sh │ └── requirements.txt ├── docs ├── Makefile ├── requirements.txt └── source │ ├── _templates │ └── autoapi │ │ └── python │ │ └── module.rst │ ├── active_hinge.png │ ├── brick.png │ ├── conf.py │ ├── contributing_guide │ └── index.rst │ ├── core.png │ ├── core_serial_port.png │ ├── creating_a_physical_robot │ ├── active_hinge_to_core.png │ ├── assemble_servo_1.png │ ├── assemble_servo_2.png │ ├── assemble_servo_3.png │ ├── assemble_servo_4.png │ ├── assemble_servo_5.png │ ├── bolt_together_1.png │ ├── bolt_together_2.png │ ├── bolts.png │ └── index.rst │ ├── favicon.png │ ├── getting_started │ └── index.rst │ ├── index.rst │ ├── installation │ └── index.rst │ ├── introduction_to_modular_robots │ └── index.rst │ ├── isotropic_brick.png │ ├── logo.png │ ├── logo_light.png │ ├── modular_robot.png │ ├── package_diagram.png │ └── physical_robot_core_setup │ └── index.rst ├── examples ├── 1_simulator_basics │ ├── 1a_simulate_single_robot │ │ ├── README.md │ │ ├── main.py │ │ └── requirements.txt │ ├── 1b_custom_terrain │ │ ├── README.md │ │ ├── main.py │ │ └── requirements.txt │ └── README.md ├── 2_modular_robot_basics │ ├── 2a_custom_brain │ │ ├── README.md │ │ ├── main.py │ │ └── requirements.txt │ ├── 2b_brain_with_feedback │ │ ├── README.md │ │ ├── main.py │ │ └── requirements.txt │ ├── 2c_custom_parts │ │ ├── README.md │ │ ├── main.py │ │ └── requirements.txt │ └── README.md ├── 3_experiment_foundations │ ├── 3a_experiment_setup │ │ ├── README.md │ │ ├── config.py │ │ ├── main.py │ │ └── requirements.txt │ ├── 3b_evaluate_single_robot │ │ ├── README.md │ │ ├── main.py │ │ └── requirements.txt │ ├── 3c_evaluate_multiple_isolated_robots │ │ ├── README.md │ │ ├── main.py │ │ └── requirements.txt │ ├── 3d_evaluate_multiple_interacting_robots │ │ ├── README.md │ │ ├── main.py │ │ └── requirements.txt │ └── README.md ├── 4_example_experiment_setups │ ├── 4a_simple_ea_xor │ │ ├── README.md │ │ ├── config.py │ │ ├── evaluate.py │ │ ├── genotype.py │ │ ├── individual.py │ │ ├── main.py │ │ └── requirements.txt │ ├── 4b_simple_ea_xor_database │ │ ├── README.md │ │ ├── config.py │ │ ├── database_components │ │ │ ├── __init__.py │ │ │ ├── _base.py │ │ │ ├── _experiment.py │ │ │ ├── _generation.py │ │ │ ├── _genotype.py │ │ │ ├── _individual.py │ │ │ └── _population.py │ │ ├── evaluate.py │ │ ├── main.py │ │ ├── plot.py │ │ └── requirements.txt │ ├── 4c_robot_bodybrain_ea │ │ ├── README.md │ │ ├── config.py │ │ ├── evaluator.py │ │ ├── genotype.py │ │ ├── individual.py │ │ ├── main.py │ │ ├── requirements.txt │ │ └── rerun.py │ ├── 4d_robot_bodybrain_ea_database │ │ ├── README.md │ │ ├── config.py │ │ ├── database_components │ │ │ ├── __init__.py │ │ │ ├── _base.py │ │ │ ├── _experiment.py │ │ │ ├── _generation.py │ │ │ ├── _genotype.py │ │ │ ├── _individual.py │ │ │ └── _population.py │ │ ├── evaluator.py │ │ ├── main.py │ │ ├── plot.py │ │ ├── requirements.txt │ │ └── rerun.py │ ├── 4e_robot_brain_cmaes │ │ ├── README.md │ │ ├── config.py │ │ ├── evaluator.py │ │ ├── main.py │ │ ├── requirements.txt │ │ └── rerun.py │ ├── 4f_robot_brain_cmaes_database │ │ ├── README.md │ │ ├── config.py │ │ ├── database_components │ │ │ ├── __init__.py │ │ │ ├── _base.py │ │ │ ├── _experiment.py │ │ │ ├── _generation.py │ │ │ ├── _genotype.py │ │ │ ├── _individual.py │ │ │ └── _population.py │ │ ├── evaluator.py │ │ ├── main.py │ │ ├── plot.py │ │ ├── requirements.txt │ │ └── rerun.py │ ├── 4g_explore_initial_population │ │ ├── README.md │ │ ├── config.py │ │ ├── evaluator.py │ │ ├── genotype.py │ │ ├── individual.py │ │ ├── main.py │ │ └── requirements.txt │ └── README.md └── 5_physical_modular_robots │ ├── 5a_physical_robot_remote │ ├── README.md │ ├── main.py │ └── requirements.txt │ ├── 5b_compare_simulated_and_physical_robot │ ├── README.md │ ├── main_physical.py │ ├── main_simulation.py │ └── requirements.txt │ └── README.md ├── experimentation ├── pyproject.toml └── revolve2 │ └── experimentation │ ├── __init__.py │ ├── _util │ ├── __init__.py │ └── init_subclass_get_generic_args.py │ ├── database │ ├── __init__.py │ ├── _has_id.py │ ├── _open_method.py │ └── _sqlite.py │ ├── evolution │ ├── __init__.py │ ├── _modular_robot_evolution.py │ └── abstract_elements │ │ ├── __init__.py │ │ ├── _evaluator.py │ │ ├── _evolution.py │ │ ├── _learner.py │ │ ├── _reproducer.py │ │ └── _selector.py │ ├── logging.py │ ├── optimization │ ├── __init__.py │ └── ea │ │ ├── __init__.py │ │ ├── _generation.py │ │ ├── _individual.py │ │ ├── _parameters.py │ │ ├── _population.py │ │ ├── population_management │ │ ├── __init__.py │ │ ├── _generational.py │ │ └── _steady_state.py │ │ └── selection │ │ ├── __init__.py │ │ ├── _argsort.py │ │ ├── _multiple_unique.py │ │ ├── _pareto_frontier.py │ │ ├── _supports_lt.py │ │ ├── _topn.py │ │ └── _tournament.py │ ├── py.typed │ └── rng.py ├── modular_robot ├── pyproject.toml └── revolve2 │ └── modular_robot │ ├── __init__.py │ ├── _modular_robot.py │ ├── _modular_robot_control_interface.py │ ├── body │ ├── __init__.py │ ├── _attachment_point.py │ ├── _color.py │ ├── _module.py │ ├── _right_angles.py │ ├── base │ │ ├── __init__.py │ │ ├── _active_hinge.py │ │ ├── _attachment_face.py │ │ ├── _body.py │ │ ├── _brick.py │ │ └── _core.py │ ├── sensors │ │ ├── __init__.py │ │ ├── _active_hinge_sensor.py │ │ ├── _camera_sensor.py │ │ ├── _imu_sensor.py │ │ └── _sensor.py │ ├── v1 │ │ ├── __init__.py │ │ ├── _active_hinge_v1.py │ │ ├── _body_v1.py │ │ ├── _brick_v1.py │ │ └── _core_v1.py │ └── v2 │ │ ├── __init__.py │ │ ├── _active_hinge_v2.py │ │ ├── _attachment_face_core_v2.py │ │ ├── _body_v2.py │ │ ├── _brick_v2.py │ │ └── _core_v2.py │ ├── brain │ ├── __init__.py │ ├── _brain.py │ ├── _brain_instance.py │ ├── cpg │ │ ├── __init__.py │ │ ├── _brain_cpg_instance.py │ │ ├── _brain_cpg_network_neighbor.py │ │ ├── _brain_cpg_network_neighbor_random.py │ │ ├── _brain_cpg_network_static.py │ │ ├── _cpg_network_structure.py │ │ └── _make_cpg_network_structure_neighbor.py │ └── dummy │ │ ├── __init__.py │ │ ├── _brain_dummy.py │ │ └── _brain_dummy_instance.py │ ├── py.typed │ └── sensor_state │ ├── __init__.py │ ├── _active_hinge_sensor_state.py │ ├── _camera_sensor_state.py │ ├── _imu_sensor_state.py │ └── _modular_robot_sensor_state.py ├── modular_robot_physical ├── pyproject.toml └── revolve2 │ ├── modular_robot_physical │ ├── __init__.py │ ├── _bin │ │ ├── __init__.py │ │ └── robot_daemon.py │ ├── _config.py │ ├── _hardware_type.py │ ├── _protocol_version.py │ ├── _standard_port.py │ ├── _uuid_key.py │ ├── physical_interfaces │ │ ├── __init__.py │ │ ├── _get_interface.py │ │ ├── _physical_interface.py │ │ ├── v1 │ │ │ ├── __init__.py │ │ │ └── _v1_physical_interface.py │ │ └── v2 │ │ │ ├── __init__.py │ │ │ └── _v2_physical_interface.py │ ├── remote │ │ ├── __init__.py │ │ ├── _active_hinge_sensor_state_impl.py │ │ ├── _camera_sensor_state_impl.py │ │ ├── _imu_sensor_state_impl.py │ │ ├── _modular_robot_control_interface_impl.py │ │ ├── _modular_robot_sensor_state_impl_v1.py │ │ ├── _modular_robot_sensor_state_impl_v2.py │ │ ├── _remote.py │ │ └── _test_physical_robot.py │ ├── robot_daemon │ │ ├── __init__.py │ │ ├── _robo_server_impl.py │ │ └── _robot_daemon.py │ └── robot_daemon_api │ │ ├── __init__.py │ │ ├── _generate_stubs.sh │ │ ├── robot_daemon_protocol.capnp │ │ ├── robot_daemon_protocol_capnp.py │ │ └── robot_daemon_protocol_capnp.pyi │ └── py.typed ├── modular_robot_simulation ├── pyproject.toml └── revolve2 │ └── modular_robot_simulation │ ├── __init__.py │ ├── _build_multi_body_systems │ ├── __init__.py │ ├── _body_to_multi_body_system_converter.py │ ├── _body_to_multi_body_system_mapping.py │ ├── _builders │ │ ├── __init__.py │ │ ├── _active_hinge_builder.py │ │ ├── _active_hinge_sensor_builder.py │ │ ├── _attachment_face_builder.py │ │ ├── _brick_builder.py │ │ ├── _builder.py │ │ ├── _camera_sensor_builder.py │ │ ├── _core_builder.py │ │ └── _imu_sensor_builder.py │ ├── _convert_color.py │ ├── _get_builder.py │ └── _unbuilt_child.py │ ├── _convert_terrain.py │ ├── _modular_robot_control_interface_impl.py │ ├── _modular_robot_scene.py │ ├── _modular_robot_simulation_handler.py │ ├── _modular_robot_simulation_state.py │ ├── _scene_simulation_state.py │ ├── _sensor_state_impl │ ├── __init__.py │ ├── _active_hinge_sensor_state_impl.py │ ├── _camera_sensor_state_impl.py │ ├── _imu_sensor_state_impl.py │ └── _modular_robot_sensor_state_impl.py │ ├── _simulate_scenes.py │ ├── _terrain.py │ ├── _test_robot.py │ ├── _to_batch.py │ └── py.typed ├── project.yml ├── requirements_dev.txt ├── requirements_editable.txt ├── simulation ├── pyproject.toml └── revolve2 │ └── simulation │ ├── __init__.py │ ├── py.typed │ ├── scene │ ├── __init__.py │ ├── _aabb.py │ ├── _color.py │ ├── _control_interface.py │ ├── _joint.py │ ├── _joint_fixed.py │ ├── _joint_hinge.py │ ├── _multi_body_system.py │ ├── _pose.py │ ├── _rigid_body.py │ ├── _scene.py │ ├── _simulation_handler.py │ ├── _simulation_state.py │ ├── _uuid_key.py │ ├── conversion │ │ ├── __init__.py │ │ └── _multi_body_system_to_urdf.py │ ├── geometry │ │ ├── __init__.py │ │ ├── _geometry.py │ │ ├── _geometry_box.py │ │ ├── _geometry_heightmap.py │ │ ├── _geometry_plane.py │ │ ├── _geometry_sphere.py │ │ └── textures │ │ │ ├── __init__.py │ │ │ ├── _map_type.py │ │ │ ├── _texture.py │ │ │ └── _texture_reference.py │ ├── sensors │ │ ├── __init__.py │ │ ├── _camera_sensor.py │ │ ├── _imu_sensor.py │ │ └── _sensor.py │ └── vector2 │ │ ├── __init__.py │ │ ├── vector2.py │ │ └── vector2aux.py │ └── simulator │ ├── __init__.py │ ├── _batch.py │ ├── _batch_parameters.py │ ├── _record_settings.py │ ├── _simulator.py │ └── _viewer.py ├── simulators └── mujoco_simulator │ ├── pyproject.toml │ └── revolve2 │ └── simulators │ └── mujoco_simulator │ ├── __init__.py │ ├── _abstraction_to_mujoco_mapping.py │ ├── _control_interface_impl.py │ ├── _local_simulator.py │ ├── _open_gl_vision.py │ ├── _render_backend.py │ ├── _scene_to_model.py │ ├── _simulate_manual_scene.py │ ├── _simulate_scene.py │ ├── _simulation_state_impl.py │ ├── py.typed │ ├── textures │ ├── __init__.py │ ├── _checker.py │ ├── _flat.py │ └── _gradient.py │ └── viewers │ ├── __init__.py │ ├── _custom_mujoco_viewer.py │ ├── _native_mujoco_viewer.py │ └── _viewer_type.py ├── standards ├── pyproject.toml └── revolve2 │ └── standards │ ├── __init__.py │ ├── ci_lab_utilities │ ├── __init__.py │ ├── _calibrate_camera.py │ └── _ip_camera.py │ ├── fitness_functions.py │ ├── genotypes │ ├── __init__.py │ └── cppnwin │ │ ├── __init__.py │ │ ├── _multineat_genotype_pickle_wrapper.py │ │ ├── _multineat_rng_from_random.py │ │ ├── _random_multineat_genotype.py │ │ └── modular_robot │ │ ├── __init__.py │ │ ├── _brain_cpg_network_neighbor.py │ │ ├── _brain_genotype_cpg.py │ │ ├── _brain_genotype_cpg_orm.py │ │ ├── _multineat_params.py │ │ ├── v1 │ │ ├── __init__.py │ │ ├── _body_develop.py │ │ ├── _body_genotype_orm_v1.py │ │ └── _body_genotype_v1.py │ │ └── v2 │ │ ├── __init__.py │ │ ├── _body_develop.py │ │ ├── _body_genotype_orm_v2.py │ │ └── _body_genotype_v2.py │ ├── interactive_objects │ ├── __init__.py │ └── _ball.py │ ├── modular_robots_v1.py │ ├── modular_robots_v2.py │ ├── morphological_measures.py │ ├── morphological_novelty_metric │ ├── __init__.py │ ├── _build_cmodule.py │ ├── _calculate_novelty.pyx │ ├── _coordinate_operations.py │ ├── _morphological_novelty_metric.py │ └── calculate_novelty.pyi │ ├── planar_robot_representation.py │ ├── py.typed │ ├── simulation_parameters.py │ └── terrains.py ├── student_install.sh ├── tests ├── README.md ├── __init__.py ├── conftest.py ├── examples │ ├── __init__.py │ ├── _clear_example_modules_from_cache.py │ ├── _patched_batch_parameters.py │ ├── test_1a_simulate_single_robot.py │ ├── test_2b_brain_with_feedback.py │ ├── test_3a_experiment_setup.py │ ├── test_3b_evaluate_single_robot.py │ ├── test_3c_evaluate_multiple_isolated_robots.py │ ├── test_4a_simple_ea_xor.py │ ├── test_4d_robot_bodybrain_ea_database.py │ └── test_4e_robot_brain_cmaes.py └── requirements.txt └── uninstall.sh /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve Revolve2 4 | title: "[BUG]" 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Platform:** 27 | - OS: [e.g. Windows, Linux, MacOS] 28 | - Python Version 29 | 30 | **Additional context** 31 | Add any other context about the problem here. 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Documentation request 3 | about: Suggest improvement or addition to the Revolve2 documentation. 4 | title: "[DOCS]" 5 | labels: documentation 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Please describe what part of Revolve 2 is unclear to you.** 11 | A clear and concise description of what part in Revolve 2 is not well documented. 12 | 13 | **Describe what you would need to understand.** 14 | A clear and concise description of what you want to happen, in order for the code to be more understandable. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for Revolve2 4 | title: "[FEATURE]" 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen, and in which package this solution should be. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | Also mention your current workarounds if applicable. 19 | 20 | **Additional context** 21 | Add any other context or screenshots about the feature request here. 22 | -------------------------------------------------------------------------------- /.github/workflows/install_display.sh: -------------------------------------------------------------------------------- 1 | # install a virtual display for rendering in the CI environment. 2 | # based on https://github.com/Farama-Foundation/Gymnasium/blob/9d2b8310ada39b86bb83cbe0cb55f5b61084e27f/bin/docker_entrypoint 3 | 4 | set -ex 5 | 6 | SCRIPT_DIR="$(realpath "$(dirname "$0")")" 7 | ROOT_DIR="$(realpath "$SCRIPT_DIR/../..")" 8 | 9 | function main() { 10 | echo "installing xvfb..." 11 | sudo apt update && sudo apt install -y xvfb 12 | # Set up display; otherwise rendering will fail 13 | Xvfb -screen 0 1024x768x24 & 14 | export DISPLAY=:0 15 | 16 | # Wait for the file to come up 17 | display=0 18 | file="/tmp/.X11-unix/X$display" 19 | for i in $(seq 1 10); do 20 | if [ -e "$file" ]; then 21 | break 22 | fi 23 | 24 | echo "Waiting for $file to be created (try $i/10)" 25 | sleep "$i" 26 | done 27 | if ! [ -e "$file" ]; then 28 | echo "Timing out: $file was not created" 29 | exit 1 30 | fi 31 | 32 | echo -e "display ready! DISPLAY='$DISPLAY'\n" 33 | } 34 | 35 | main "$@" 36 | -------------------------------------------------------------------------------- /.github/workflows/mypy.yml: -------------------------------------------------------------------------------- 1 | name: mypy 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | directory: 7 | required: true 8 | type: string 9 | 10 | jobs: 11 | mypy: 12 | runs-on: ubuntu-20.04 13 | strategy: 14 | matrix: 15 | python-version: [ "3.10", "3.11" ] 16 | steps: 17 | - uses: actions/checkout@v4 18 | - uses: actions/setup-python@v5.1.0 19 | with: 20 | python-version: ${{ matrix.python-version }} 21 | - name: Install dev_requirements.txt 22 | run: pip install -r ./requirements_dev.txt 23 | - name: Run mypy 24 | run: ./codetools/mypy/check.sh ./${{ inputs.directory }} 25 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### Contributing guide 4 | 5 | If you intend to contribute you may find this guide helpful: [Revolve2 Contributing Guide](https://ci-group.github.io/revolve2/contributing_guide/index.html).
6 | Contributions are highly appreciated. -------------------------------------------------------------------------------- /codetools/black/check.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cd "$(dirname "$0")" 4 | 5 | packages=$(../read_project_parts.sh) 6 | 7 | cd ../.. 8 | 9 | black --diff --check $packages 10 | -------------------------------------------------------------------------------- /codetools/black/fix.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cd "$(dirname "$0")" 4 | 5 | packages=$(../read_project_parts.sh) 6 | 7 | cd ../.. 8 | 9 | black $packages 10 | -------------------------------------------------------------------------------- /codetools/black/requirements.txt: -------------------------------------------------------------------------------- 1 | black==24.3.0 2 | -------------------------------------------------------------------------------- /codetools/darglint/check.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cd "$(dirname "$0")" 4 | 5 | packages=$(../read_project_parts.sh) 6 | 7 | cd ../.. 8 | 9 | darglint -s sphinx $packages 10 | -------------------------------------------------------------------------------- /codetools/darglint/requirements.txt: -------------------------------------------------------------------------------- 1 | darglint==1.8.1 2 | -------------------------------------------------------------------------------- /codetools/fix_all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | cd "$(dirname "$0")" 6 | 7 | echo "--------------" 8 | echo "mypy" 9 | echo "--------------" 10 | ./mypy/check_all.sh 11 | 12 | echo "--------------" 13 | echo "pyflakes" 14 | echo "--------------" 15 | ./pyflakes/check.sh 16 | 17 | echo "--------------" 18 | echo "sort-all" 19 | echo "--------------" 20 | ./sort_all/fix.sh 21 | 22 | echo "--------------" 23 | echo "black" 24 | echo "--------------" 25 | ./black/fix.sh 26 | 27 | echo "--------------" 28 | echo "isort" 29 | echo "--------------" 30 | ./isort/fix.sh 31 | 32 | echo "--------------" 33 | echo "pydocstyle" 34 | echo "--------------" 35 | ./pydocstyle/check.sh 36 | 37 | echo "--------------" 38 | echo "darglint" 39 | echo "--------------" 40 | ./darglint/check.sh 41 | -------------------------------------------------------------------------------- /codetools/isort/check.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cd "$(dirname "$0")" 4 | 5 | packages=$(../read_project_parts.sh) 6 | 7 | cd ../.. 8 | 9 | isort --check-only --diff --profile black $packages 10 | -------------------------------------------------------------------------------- /codetools/isort/fix.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cd "$(dirname "$0")" 4 | 5 | packages=$(../read_project_parts.sh) 6 | 7 | cd ../.. 8 | 9 | isort --profile black $packages 10 | -------------------------------------------------------------------------------- /codetools/isort/requirements.txt: -------------------------------------------------------------------------------- 1 | isort==5.10.1 2 | -------------------------------------------------------------------------------- /codetools/mypy/check.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | cd "$(dirname "$0")" 6 | 7 | echo "$1:" 8 | mypy --config-file ./mypy.ini "../../$1" 9 | -------------------------------------------------------------------------------- /codetools/mypy/check_all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | cd "$(dirname "$0")" 6 | 7 | while read -r package; do 8 | echo "$package:" 9 | mypy --config-file ./mypy.ini "../../$package" 10 | done < <(../read_project_parts.sh) 11 | -------------------------------------------------------------------------------- /codetools/mypy/mypy.ini: -------------------------------------------------------------------------------- 1 | [mypy] 2 | strict = True 3 | 4 | [mypy-noise.*] 5 | ignore_missing_imports = True 6 | 7 | [mypy-multineat.*] 8 | ignore_missing_imports = True 9 | 10 | [mypy-pyrr.*] 11 | ignore_missing_imports = True 12 | 13 | [mypy-mujoco.*] 14 | ignore_missing_imports = True 15 | 16 | [mypy-mujoco_viewer.*] 17 | ignore_missing_imports = True 18 | 19 | [mypy-dm_control.*] 20 | ignore_missing_imports = True 21 | 22 | [mypy-matplotlib.pyplot] 23 | follow_imports = skip 24 | # The current matplotlib version 3.8.0 introduced typing, but these are not always proper. 25 | # For now we ignore the emerged errors. 26 | 27 | [mypy-pandas.*] 28 | ignore_missing_imports = True 29 | 30 | [mypy-cma.*] 31 | ignore_missing_imports = True 32 | 33 | [mypy-pigpio.*] 34 | ignore_missing_imports = True 35 | 36 | [mypy-Cython.*] 37 | ignore_missing_imports = True 38 | 39 | [mypy-setuptools.*] 40 | ignore_missing_imports = True 41 | 42 | [mypy-capnp.*] 43 | ignore_missing_imports = True 44 | 45 | [mypy-scipy.*] 46 | ignore_missing_imports = True 47 | 48 | [mypy-sklearn.*] 49 | ignore_missing_imports = True 50 | 51 | [mypy-cairo.*] 52 | ignore_missing_imports = True 53 | 54 | [mypy-glfw.*] 55 | ignore_missing_imports = True 56 | -------------------------------------------------------------------------------- /codetools/mypy/requirements.txt: -------------------------------------------------------------------------------- 1 | mypy==1.5.1 2 | -------------------------------------------------------------------------------- /codetools/pydocstyle/check.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cd "$(dirname "$0")" 4 | 5 | packages=$(../read_project_parts.sh) 6 | 7 | cd ../.. 8 | 9 | pydocstyle $packages 10 | -------------------------------------------------------------------------------- /codetools/pydocstyle/requirements.txt: -------------------------------------------------------------------------------- 1 | pydocstyle==6.1.1 2 | toml==0.10.2 3 | -------------------------------------------------------------------------------- /codetools/pyflakes/check.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cd "$(dirname "$0")" 4 | 5 | packages=$(../read_project_parts.sh) 6 | 7 | cd ../.. 8 | 9 | pyflakes $packages 10 | -------------------------------------------------------------------------------- /codetools/pyflakes/requirements.txt: -------------------------------------------------------------------------------- 1 | pyflakes==2.4.0 2 | -------------------------------------------------------------------------------- /codetools/read_project_parts.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | cd "$(dirname "$0")" 6 | 7 | python3 -c " 8 | import yaml 9 | 10 | with open('../project.yml') as file: 11 | data = yaml.safe_load(file) 12 | 13 | namespace = data['revolve2-namespace'] 14 | examples_dir = data['examples-dir'] 15 | tests_dir = data['tests-dir'] 16 | 17 | platform_dependent = [f'{pkg}/{namespace}' for pkg in data['platform_dependent_packages']] 18 | platform_independent = [f'{pkg}/{namespace}' for pkg in data['platform_independent_packages']] 19 | examples = [f'{examples_dir}/{example}' for example in data['examples']] 20 | 21 | all_packages = platform_dependent + platform_independent + examples + [tests_dir] 22 | print('\n'.join(all_packages)) 23 | " 24 | -------------------------------------------------------------------------------- /codetools/requirements.txt: -------------------------------------------------------------------------------- 1 | pyyaml 2 | -r pyflakes/requirements.txt 3 | -r mypy/requirements.txt 4 | -r sort_all/requirements.txt 5 | -r black/requirements.txt 6 | -r isort/requirements.txt 7 | -r pydocstyle/requirements.txt 8 | -r darglint/requirements.txt 9 | -------------------------------------------------------------------------------- /codetools/sort_all/fix.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cd "$(dirname "$0")" 4 | 5 | packages=$(../read_project_parts.sh) 6 | 7 | cd ../.. 8 | 9 | find $packages -type f -name '__init__.py' -print0 | xargs -0 sort-all 10 | -------------------------------------------------------------------------------- /codetools/sort_all/requirements.txt: -------------------------------------------------------------------------------- 1 | sort-all==1.2.0 2 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | SPHINXOPTS = -W --keep-going 2 | SPHINXBUILD = sphinx-build 3 | SOURCEDIR = source 4 | BUILDDIR = build 5 | 6 | help: 7 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 8 | 9 | %: Makefile 10 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 11 | 12 | .PHONY: help Makefile -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx==7.2.6 2 | sphinx-rtd-theme==1.3.0 3 | sphinx-autoapi==3.0.0 4 | -------------------------------------------------------------------------------- /docs/source/active_hinge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ci-group/revolve2/8156a66624f96c43cdfcec05bbb366cc492b87e0/docs/source/active_hinge.png -------------------------------------------------------------------------------- /docs/source/brick.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ci-group/revolve2/8156a66624f96c43cdfcec05bbb366cc492b87e0/docs/source/brick.png -------------------------------------------------------------------------------- /docs/source/core.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ci-group/revolve2/8156a66624f96c43cdfcec05bbb366cc492b87e0/docs/source/core.png -------------------------------------------------------------------------------- /docs/source/core_serial_port.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ci-group/revolve2/8156a66624f96c43cdfcec05bbb366cc492b87e0/docs/source/core_serial_port.png -------------------------------------------------------------------------------- /docs/source/creating_a_physical_robot/active_hinge_to_core.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ci-group/revolve2/8156a66624f96c43cdfcec05bbb366cc492b87e0/docs/source/creating_a_physical_robot/active_hinge_to_core.png -------------------------------------------------------------------------------- /docs/source/creating_a_physical_robot/assemble_servo_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ci-group/revolve2/8156a66624f96c43cdfcec05bbb366cc492b87e0/docs/source/creating_a_physical_robot/assemble_servo_1.png -------------------------------------------------------------------------------- /docs/source/creating_a_physical_robot/assemble_servo_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ci-group/revolve2/8156a66624f96c43cdfcec05bbb366cc492b87e0/docs/source/creating_a_physical_robot/assemble_servo_2.png -------------------------------------------------------------------------------- /docs/source/creating_a_physical_robot/assemble_servo_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ci-group/revolve2/8156a66624f96c43cdfcec05bbb366cc492b87e0/docs/source/creating_a_physical_robot/assemble_servo_3.png -------------------------------------------------------------------------------- /docs/source/creating_a_physical_robot/assemble_servo_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ci-group/revolve2/8156a66624f96c43cdfcec05bbb366cc492b87e0/docs/source/creating_a_physical_robot/assemble_servo_4.png -------------------------------------------------------------------------------- /docs/source/creating_a_physical_robot/assemble_servo_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ci-group/revolve2/8156a66624f96c43cdfcec05bbb366cc492b87e0/docs/source/creating_a_physical_robot/assemble_servo_5.png -------------------------------------------------------------------------------- /docs/source/creating_a_physical_robot/bolt_together_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ci-group/revolve2/8156a66624f96c43cdfcec05bbb366cc492b87e0/docs/source/creating_a_physical_robot/bolt_together_1.png -------------------------------------------------------------------------------- /docs/source/creating_a_physical_robot/bolt_together_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ci-group/revolve2/8156a66624f96c43cdfcec05bbb366cc492b87e0/docs/source/creating_a_physical_robot/bolt_together_2.png -------------------------------------------------------------------------------- /docs/source/creating_a_physical_robot/bolts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ci-group/revolve2/8156a66624f96c43cdfcec05bbb366cc492b87e0/docs/source/creating_a_physical_robot/bolts.png -------------------------------------------------------------------------------- /docs/source/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ci-group/revolve2/8156a66624f96c43cdfcec05bbb366cc492b87e0/docs/source/favicon.png -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | ====================== 2 | Revolve2 documentation 3 | ====================== 4 | 5 | Revolve2 is a collection of Python packages used for researching evolutionary algorithms and modular robotics. 6 | Its primary features are a modular robot framework, an abstraction layer around physics simulators, and evolutionary algorithms. 7 | The structure of the packages in Revolve2 is as follows: 8 | 9 | .. image:: package_diagram.png 10 | :width: 700px 11 | :height: 500px 12 | :align: center 13 | 14 | 15 | Revolve2's code resides at ``_. 16 | 17 | .. toctree:: 18 | :maxdepth: 2 19 | 20 | Getting Started 21 | Installation 22 | Introduction to modular robots 23 | Creating a physical robot 24 | Physical Robot Core Setup 25 | Contributing guide -------------------------------------------------------------------------------- /docs/source/introduction_to_modular_robots/index.rst: -------------------------------------------------------------------------------- 1 | ================================= 2 | An introduction to modular robots 3 | ================================= 4 | This introduction applies to V2 robots. V1 robots are only used internally in CI Group. 5 | 6 | .. image:: ../modular_robot.png 7 | :width: 400 8 | :alt: A V2 modular robot 9 | 10 | Modular robots are robots built from a set of modules and sensors. 11 | A central brain controls the robot, which can read the sensors. 12 | The modular robots system in Revolve2 is based on the RoboGen system (*RoboGen: Robot Generation through Artificial Evolution, ser. Artificial Life Conference Proceedings, vol. ALIFE 14: The Fourteenth International Conference on the Synthesis and Simulation of Living Systems, 07 2014*). 13 | It is possible to adjust Revolve2 to include modules and sensors not readily available in the standard set. 14 | 15 | ------- 16 | Modules 17 | ------- 18 | At the center of the robot lies the core module, which contains the brain. 19 | 20 | .. image:: ../core.png 21 | :width: 400 22 | :alt: A core module 23 | 24 | Other modules are attached to the core in its sides. 25 | Brick are passive blocks to which again modules can be attached. 26 | 27 | .. image:: ../brick.png 28 | :width: 400 29 | :alt: A brick module 30 | 31 | Active hinges are revolute joints that are controlled by the robot's brain. 32 | 33 | .. image:: ../active_hinge.png 34 | :width: 400 35 | :alt: An active hinge module 36 | 37 | ------- 38 | Sensors 39 | ------- 40 | Currently the brain can read only the current position of each servo. This works both in simulation and real hardware. 41 | -------------------------------------------------------------------------------- /docs/source/isotropic_brick.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ci-group/revolve2/8156a66624f96c43cdfcec05bbb366cc492b87e0/docs/source/isotropic_brick.png -------------------------------------------------------------------------------- /docs/source/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ci-group/revolve2/8156a66624f96c43cdfcec05bbb366cc492b87e0/docs/source/logo.png -------------------------------------------------------------------------------- /docs/source/logo_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ci-group/revolve2/8156a66624f96c43cdfcec05bbb366cc492b87e0/docs/source/logo_light.png -------------------------------------------------------------------------------- /docs/source/modular_robot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ci-group/revolve2/8156a66624f96c43cdfcec05bbb366cc492b87e0/docs/source/modular_robot.png -------------------------------------------------------------------------------- /docs/source/package_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ci-group/revolve2/8156a66624f96c43cdfcec05bbb366cc492b87e0/docs/source/package_diagram.png -------------------------------------------------------------------------------- /examples/1_simulator_basics/1a_simulate_single_robot/README.md: -------------------------------------------------------------------------------- 1 | This example shows you how to simulate and visualize a single modular robot. 2 | 3 | You learn: 4 | - How to create a robot body with a basic controller. 5 | - How to simulate and see a robot. 6 | -------------------------------------------------------------------------------- /examples/1_simulator_basics/1a_simulate_single_robot/requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ci-group/revolve2/8156a66624f96c43cdfcec05bbb366cc492b87e0/examples/1_simulator_basics/1a_simulate_single_robot/requirements.txt -------------------------------------------------------------------------------- /examples/1_simulator_basics/1b_custom_terrain/README.md: -------------------------------------------------------------------------------- 1 | In this example you create a custom terrain to put a modular robot in. 2 | Other examples for possible terrains can be found in `standards.terrains` 3 | 4 | Make sure you understand simulation, for example through the example `1a_simulate_single_robot`. 5 | 6 | You learn: 7 | - How to create a custom terrain. 8 | -------------------------------------------------------------------------------- /examples/1_simulator_basics/1b_custom_terrain/requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ci-group/revolve2/8156a66624f96c43cdfcec05bbb366cc492b87e0/examples/1_simulator_basics/1b_custom_terrain/requirements.txt -------------------------------------------------------------------------------- /examples/1_simulator_basics/README.md: -------------------------------------------------------------------------------- 1 | ## 1) Start with the basics. 2 | 3 | In this collection of examples you will learn the basics of the simulator used for Revolve2. 4 | 5 | - In `1a_simulate_single_robot` you will learn how to simulate robots using Revolve2. 6 | - In `1b_custom_terrain` you learn how to make a terrain that you can use for your experiments. 7 | -------------------------------------------------------------------------------- /examples/2_modular_robot_basics/2a_custom_brain/README.md: -------------------------------------------------------------------------------- 1 | In this example you create a custom brain that controls a modular robot. 2 | 3 | Make sure you understand simulation, for example through the example `1a_simulate_single_robot`. 4 | 5 | You learn: 6 | - How to create a custom brain. 7 | -------------------------------------------------------------------------------- /examples/2_modular_robot_basics/2a_custom_brain/requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ci-group/revolve2/8156a66624f96c43cdfcec05bbb366cc492b87e0/examples/2_modular_robot_basics/2a_custom_brain/requirements.txt -------------------------------------------------------------------------------- /examples/2_modular_robot_basics/2b_brain_with_feedback/README.md: -------------------------------------------------------------------------------- 1 | This example shows you how to get feedback in the brain from the current active hinge positions. 2 | 3 | You learn: 4 | - How to create and read sensors 5 | - How to control read sensors in the brain. 6 | -------------------------------------------------------------------------------- /examples/2_modular_robot_basics/2b_brain_with_feedback/requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ci-group/revolve2/8156a66624f96c43cdfcec05bbb366cc492b87e0/examples/2_modular_robot_basics/2b_brain_with_feedback/requirements.txt -------------------------------------------------------------------------------- /examples/2_modular_robot_basics/2c_custom_parts/README.md: -------------------------------------------------------------------------------- 1 | This example shows you how to create vustom versions of the modular robot elements. 2 | 3 | You learn: 4 | - Where are parts defined and what parameters can be changed. 5 | - How to implement custom versions of the parts to analyze the effect of different parameters. 6 | - Using the test_robot function to test robots with manual/dummy brains. 7 | -------------------------------------------------------------------------------- /examples/2_modular_robot_basics/2c_custom_parts/requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ci-group/revolve2/8156a66624f96c43cdfcec05bbb366cc492b87e0/examples/2_modular_robot_basics/2c_custom_parts/requirements.txt -------------------------------------------------------------------------------- /examples/2_modular_robot_basics/README.md: -------------------------------------------------------------------------------- 1 | ## 2) Learn about the Modular Robots 2 | 3 | Revolve2 is a framework, which is designed towards modular robot evolution, meaning robots are a collection of arbitrary modules. 4 | The Morphology (Body) of these robots can be evolved aswell as their Controller (Brain). 5 | 6 | In this collection of examples you will learn the following: 7 | 8 | - In `2a_custom_brain` you will walk through the process of making a custom brain to use for your robots. 9 | - In `2b_brain_with_feedback` you will learn how a brain can use sensors on the robots for some advanced control. 10 | -------------------------------------------------------------------------------- /examples/3_experiment_foundations/3a_experiment_setup/README.md: -------------------------------------------------------------------------------- 1 | In this example you set up a trivial experiment with muliple repetitions. 2 | We will sample from a distribution given a few parameters and determine the ratio of success 3 | To change the experiments parameters, adjust the values in `config.py`. 4 | 5 | You learn: 6 | - Setting up an experiment. 7 | - Basic use of logging. 8 | - Experiment repetitions. 9 | - About the random number generator and reproducible experiments. 10 | - In particular, you will NOT learn how to save your experiment results. 11 | Either use your own preferred method, or look at the SQLAlchemy database abstraction commonly used in Revolve2. 12 | 13 | You can change the configuration of the experiment in `config.py`. 14 | -------------------------------------------------------------------------------- /examples/3_experiment_foundations/3a_experiment_setup/config.py: -------------------------------------------------------------------------------- 1 | """Configuration parameters for this example.""" 2 | 3 | PROBABILITIES = [0.5, 0.75, 0.9] 4 | NUM_SAMPLES = [5, 10, 50] 5 | NUM_REPETITIONS = 5 6 | -------------------------------------------------------------------------------- /examples/3_experiment_foundations/3a_experiment_setup/requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ci-group/revolve2/8156a66624f96c43cdfcec05bbb366cc492b87e0/examples/3_experiment_foundations/3a_experiment_setup/requirements.txt -------------------------------------------------------------------------------- /examples/3_experiment_foundations/3b_evaluate_single_robot/README.md: -------------------------------------------------------------------------------- 1 | No experiment is useful without some way to evaluate your results. 2 | In this example you simulate a single modular robot, and then calculate its xy displacement. 3 | 4 | To understand simulation, first see the `1a_simulate_single_robot` example. 5 | 6 | You learn: 7 | - How to process simulation results. 8 | -------------------------------------------------------------------------------- /examples/3_experiment_foundations/3b_evaluate_single_robot/requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ci-group/revolve2/8156a66624f96c43cdfcec05bbb366cc492b87e0/examples/3_experiment_foundations/3b_evaluate_single_robot/requirements.txt -------------------------------------------------------------------------------- /examples/3_experiment_foundations/3c_evaluate_multiple_isolated_robots/README.md: -------------------------------------------------------------------------------- 1 | This example simulates modular robots that do not interact with each other, and then calculates their xy displacement. 2 | 3 | To understand better, first see the '3b_evaluate_single_robot' example. 4 | 5 | You learn: 6 | - How to simulate multiple isolated robots. 7 | - How to process their simulation results. 8 | -------------------------------------------------------------------------------- /examples/3_experiment_foundations/3c_evaluate_multiple_isolated_robots/requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ci-group/revolve2/8156a66624f96c43cdfcec05bbb366cc492b87e0/examples/3_experiment_foundations/3c_evaluate_multiple_isolated_robots/requirements.txt -------------------------------------------------------------------------------- /examples/3_experiment_foundations/3d_evaluate_multiple_interacting_robots/README.md: -------------------------------------------------------------------------------- 1 | In this example you simulate a multiple robots in the same scene, and then calculate their x-y displacement. 2 | Since the robots are within one scene, they can interact with one another. 3 | 4 | First see the `3c_evaluate_multiple_isolated_robots` example. 5 | This example is extremely similar, but will ommit some of the commentary. 6 | 7 | You learn: 8 | - How to put multiple robots in the same scene. 9 | - How to process the simulation results. 10 | -------------------------------------------------------------------------------- /examples/3_experiment_foundations/3d_evaluate_multiple_interacting_robots/requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ci-group/revolve2/8156a66624f96c43cdfcec05bbb366cc492b87e0/examples/3_experiment_foundations/3d_evaluate_multiple_interacting_robots/requirements.txt -------------------------------------------------------------------------------- /examples/3_experiment_foundations/README.md: -------------------------------------------------------------------------------- 1 | ## 3) Learn how to properly setup experiments 2 | Simulating robots on its own is not that useful, so we actually want to make experiments that show us cool new things we did not know before. 3 | Here you will learn about a few things that are important in almost all experimental setups. 4 | 5 | - In `3a_experiment_setup` you will learn a way to structure a basic experiment in revolve2. Make sure to understand this example well. 6 | - In `3b_evaluate_single_robot` we will see how you can evaluate robots in ane experiment. 7 | - Since evaluating one robot is not very interesting in `3c_evaluate_multiple_isolated_robots` you will see how this evaluation can be done for a population of robots. 8 | - Alternatively if you want multiple robots interacting with each other look into example `3d_evaluate_multiple_interacting_robots`. 9 | 10 | 11 | -------------------------------------------------------------------------------- /examples/4_example_experiment_setups/4a_simple_ea_xor/README.md: -------------------------------------------------------------------------------- 1 | Here you will optimize a neural network to calculate XOR, using an evolutionary algorithm. 2 | 3 | To better understand, first look at the `3a_experiment_setup` example. 4 | 5 | You learn: 6 | - How to create a simple evolutionary loop. 7 | 8 | The evaluation of the neural network is done in `evaluate.py`.
9 | In `config.py` you can adjust parameter.
10 | `genotype.py` houses all representations and functionality behind a robots genotypes.
11 | In `individual.py` you find a class that stores individual robots. 12 | -------------------------------------------------------------------------------- /examples/4_example_experiment_setups/4a_simple_ea_xor/config.py: -------------------------------------------------------------------------------- 1 | """Configuration parameters for this example.""" 2 | 3 | POPULATION_SIZE = 50 4 | OFFSPRING_SIZE = 25 5 | NUM_GENERATIONS = 200 6 | NUM_PARAMETERS = 9 7 | MUTATE_STD = 0.05 8 | -------------------------------------------------------------------------------- /examples/4_example_experiment_setups/4a_simple_ea_xor/individual.py: -------------------------------------------------------------------------------- 1 | """Individual class.""" 2 | 3 | from dataclasses import dataclass 4 | 5 | from genotype import Genotype 6 | 7 | 8 | @dataclass 9 | class Individual: 10 | """An individual in a population.""" 11 | 12 | genotype: Genotype 13 | fitness: float 14 | -------------------------------------------------------------------------------- /examples/4_example_experiment_setups/4a_simple_ea_xor/requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ci-group/revolve2/8156a66624f96c43cdfcec05bbb366cc492b87e0/examples/4_example_experiment_setups/4a_simple_ea_xor/requirements.txt -------------------------------------------------------------------------------- /examples/4_example_experiment_setups/4b_simple_ea_xor_database/config.py: -------------------------------------------------------------------------------- 1 | """Configuration parameters for this example.""" 2 | 3 | DATABASE_FILE = "database.sqlite" 4 | NUM_REPETITIONS = 5 5 | POPULATION_SIZE = 50 6 | OFFSPRING_SIZE = 25 7 | NUM_GENERATIONS = 200 8 | NUM_PARAMETERS = 9 9 | MUTATE_STD = 0.05 10 | -------------------------------------------------------------------------------- /examples/4_example_experiment_setups/4b_simple_ea_xor_database/database_components/__init__.py: -------------------------------------------------------------------------------- 1 | """A collection of components used in the Database.""" 2 | 3 | from ._base import Base 4 | from ._experiment import Experiment 5 | from ._generation import Generation 6 | from ._genotype import Genotype 7 | from ._individual import Individual 8 | from ._population import Population 9 | 10 | __all__ = ["Base", "Experiment", "Generation", "Genotype", "Individual", "Population"] 11 | -------------------------------------------------------------------------------- /examples/4_example_experiment_setups/4b_simple_ea_xor_database/database_components/_base.py: -------------------------------------------------------------------------------- 1 | """Base class.""" 2 | 3 | import sqlalchemy.orm as orm 4 | 5 | 6 | class Base(orm.MappedAsDataclass, orm.DeclarativeBase): 7 | """ 8 | Base class for all SQLAlchemy models in this example. 9 | 10 | All ORM classes should inherit from this. 11 | This is simply how SQLAlchemy is designed. 12 | We can then use this base class to create the structure of our new (empty) database, 13 | automatically knowing all classes that inherited from the base. 14 | """ 15 | 16 | pass 17 | -------------------------------------------------------------------------------- /examples/4_example_experiment_setups/4b_simple_ea_xor_database/database_components/_experiment.py: -------------------------------------------------------------------------------- 1 | """Experiment class.""" 2 | 3 | import sqlalchemy.orm as orm 4 | 5 | from revolve2.experimentation.database import HasId 6 | 7 | from ._base import Base 8 | 9 | 10 | class Experiment(Base, HasId): 11 | """ 12 | This is the ORM class that describes an experiment. 13 | 14 | Every experiment run will create an instance of this class and save it to the database. 15 | All other object in the database will directly or indirectly refer to an instance of this class, 16 | so you always know which experiment they are part of. 17 | Additionally it is a great place to store experiment parameters, as well your generated rng seed. 18 | 19 | Next to defining our own or 'rng_seed' field, 20 | we inherit from the Revolve2 'HasId' class which defines an 'id' variable for us. 21 | """ 22 | 23 | # We have to defined the name of this classes table in the database. 24 | __tablename__ = "experiment" 25 | 26 | # The seed for the rng. 27 | # Very important to save (for reproducibility) as it is generated at the start of the program based on the current time. 28 | rng_seed: orm.Mapped[int] = orm.mapped_column(nullable=False) 29 | -------------------------------------------------------------------------------- /examples/4_example_experiment_setups/4b_simple_ea_xor_database/database_components/_generation.py: -------------------------------------------------------------------------------- 1 | """Generation class.""" 2 | 3 | import sqlalchemy 4 | import sqlalchemy.orm as orm 5 | 6 | from revolve2.experimentation.database import HasId 7 | 8 | from ._base import Base 9 | from ._experiment import Experiment 10 | from ._population import Population 11 | 12 | 13 | class Generation(Base, HasId): 14 | """ 15 | A single finished iteration of the EA. 16 | 17 | We reference the experiment so we know which experiment this generation is part of. 18 | In addition every generation has a corresponding population. 19 | This is all done in a similar way to the genotype, individual, and population classes. 20 | 21 | Finally, we save the number of the current generation. 22 | """ 23 | 24 | __tablename__ = "generation" 25 | 26 | experiment_id: orm.Mapped[int] = orm.mapped_column( 27 | sqlalchemy.ForeignKey("experiment.id"), nullable=False, init=False 28 | ) 29 | experiment: orm.Mapped[Experiment] = orm.relationship() 30 | generation_index: orm.Mapped[int] = orm.mapped_column(nullable=False) 31 | population_id: orm.Mapped[int] = orm.mapped_column( 32 | sqlalchemy.ForeignKey("population.id"), nullable=False, init=False 33 | ) 34 | population: orm.Mapped[Population] = orm.relationship() 35 | -------------------------------------------------------------------------------- /examples/4_example_experiment_setups/4b_simple_ea_xor_database/database_components/_population.py: -------------------------------------------------------------------------------- 1 | """Population class.""" 2 | 3 | import sqlalchemy.ext.orderinglist 4 | import sqlalchemy.orm as orm 5 | 6 | from revolve2.experimentation.database import HasId 7 | 8 | from ._base import Base 9 | from ._individual import Individual 10 | 11 | 12 | class Population(Base, HasId, kw_only=True): 13 | """ 14 | A population of individuals. 15 | 16 | Contain a list of individuals. 17 | First take a look at the Individual class. 18 | SQLAlchemy automatically uses the individuals 'population_id' and 'population_index' to 19 | create a table of individuals referring to populations, while retaining the individuals original order in the population. 20 | """ 21 | 22 | __tablename__ = "population" 23 | 24 | individuals: orm.Mapped[list[Individual]] = orm.relationship( 25 | order_by=Individual.population_index, 26 | collection_class=sqlalchemy.ext.orderinglist.ordering_list("population_index"), 27 | ) 28 | -------------------------------------------------------------------------------- /examples/4_example_experiment_setups/4b_simple_ea_xor_database/requirements.txt: -------------------------------------------------------------------------------- 1 | pandas>=2.1.0 2 | matplotlib>=3.8.0 3 | -------------------------------------------------------------------------------- /examples/4_example_experiment_setups/4c_robot_bodybrain_ea/README.md: -------------------------------------------------------------------------------- 1 | In this example you see an experiment that optimizes the bodies and brains of a robots using a simple evolutionary algorithm. 2 | The genotypes for both body and brain are CPPNWIN. 3 | 4 | Before starting this tutorial, it is useful to look at the 5 | `3c_evaluate_multiple_isolated_robots`, and `4a_simple_ea_xor` examples. 6 | It is also nice to understand the concept of a cpg brain and CPPN, although not really needed. 7 | 8 | You learn: 9 | - How to optimize the body and brain of a robot using an EA. 10 | - How to use the ModularRobotEvolution object for easy evolutionary control. 11 | 12 | To change the parameters of the experiment use `config.py`.
13 | The evaluation of individual robots is done in `evaluator.py`.
14 | `genotype.py` houses all representations and functionality behind a robots genotypes.
15 | In `individual.py` you find a class that stores individual robots. 16 | 17 | To visualize the evolved robots, use `rerun.py` with the pickled genotype you got from evolution. 18 | -------------------------------------------------------------------------------- /examples/4_example_experiment_setups/4c_robot_bodybrain_ea/config.py: -------------------------------------------------------------------------------- 1 | """Configuration parameters for this example.""" 2 | 3 | NUM_SIMULATORS = 8 4 | POPULATION_SIZE = 100 5 | OFFSPRING_SIZE = 50 6 | NUM_GENERATIONS = 100 7 | -------------------------------------------------------------------------------- /examples/4_example_experiment_setups/4c_robot_bodybrain_ea/individual.py: -------------------------------------------------------------------------------- 1 | """Individual class.""" 2 | 3 | from dataclasses import dataclass 4 | 5 | from genotype import Genotype 6 | 7 | 8 | @dataclass 9 | class Individual: 10 | """An individual in a population.""" 11 | 12 | genotype: Genotype 13 | fitness: float 14 | -------------------------------------------------------------------------------- /examples/4_example_experiment_setups/4c_robot_bodybrain_ea/requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ci-group/revolve2/8156a66624f96c43cdfcec05bbb366cc492b87e0/examples/4_example_experiment_setups/4c_robot_bodybrain_ea/requirements.txt -------------------------------------------------------------------------------- /examples/4_example_experiment_setups/4d_robot_bodybrain_ea_database/README.md: -------------------------------------------------------------------------------- 1 | This is the `robot_bodybrain_ea` example, but with added saving of results to a database. 2 | 3 | Definitely first look at the `4c_robot_bodybrain_ea` and `4b_simple_ea_xor_database` examples. 4 | Many explanation comments are omitted here. 5 | 6 | To visualize the evolved robots, use `rerun.py` with the pickled genotype you got from evolution. 7 | Running `plot.py` allows you to plot the robots fitness metrics over each generation. 8 | -------------------------------------------------------------------------------- /examples/4_example_experiment_setups/4d_robot_bodybrain_ea_database/config.py: -------------------------------------------------------------------------------- 1 | """Configuration parameters for this example.""" 2 | 3 | DATABASE_FILE = "database.sqlite" 4 | NUM_REPETITIONS = 5 5 | NUM_SIMULATORS = 8 6 | POPULATION_SIZE = 100 7 | OFFSPRING_SIZE = 50 8 | NUM_GENERATIONS = 100 9 | -------------------------------------------------------------------------------- /examples/4_example_experiment_setups/4d_robot_bodybrain_ea_database/database_components/__init__.py: -------------------------------------------------------------------------------- 1 | """A collection of components used in the Database.""" 2 | 3 | from ._base import Base 4 | from ._experiment import Experiment 5 | from ._generation import Generation 6 | from ._genotype import Genotype 7 | from ._individual import Individual 8 | from ._population import Population 9 | 10 | __all__ = ["Base", "Experiment", "Generation", "Genotype", "Individual", "Population"] 11 | -------------------------------------------------------------------------------- /examples/4_example_experiment_setups/4d_robot_bodybrain_ea_database/database_components/_base.py: -------------------------------------------------------------------------------- 1 | """Base class.""" 2 | 3 | import sqlalchemy.orm as orm 4 | 5 | 6 | class Base(orm.MappedAsDataclass, orm.DeclarativeBase): 7 | """Base class for all SQLAlchemy models in this example.""" 8 | 9 | pass 10 | -------------------------------------------------------------------------------- /examples/4_example_experiment_setups/4d_robot_bodybrain_ea_database/database_components/_experiment.py: -------------------------------------------------------------------------------- 1 | """Experiment class.""" 2 | 3 | import sqlalchemy.orm as orm 4 | 5 | from revolve2.experimentation.database import HasId 6 | 7 | from ._base import Base 8 | 9 | 10 | class Experiment(Base, HasId): 11 | """Experiment description.""" 12 | 13 | __tablename__ = "experiment" 14 | 15 | # The seed for the rng. 16 | rng_seed: orm.Mapped[int] = orm.mapped_column(nullable=False) 17 | -------------------------------------------------------------------------------- /examples/4_example_experiment_setups/4d_robot_bodybrain_ea_database/database_components/_generation.py: -------------------------------------------------------------------------------- 1 | """Generation class.""" 2 | 3 | import sqlalchemy 4 | import sqlalchemy.orm as orm 5 | 6 | from revolve2.experimentation.database import HasId 7 | 8 | from ._base import Base 9 | from ._experiment import Experiment 10 | from ._population import Population 11 | 12 | 13 | class Generation(Base, HasId): 14 | """A single finished iteration of CMA-ES.""" 15 | 16 | __tablename__ = "generation" 17 | 18 | experiment_id: orm.Mapped[int] = orm.mapped_column( 19 | sqlalchemy.ForeignKey("experiment.id"), nullable=False, init=False 20 | ) 21 | experiment: orm.Mapped[Experiment] = orm.relationship() 22 | generation_index: orm.Mapped[int] = orm.mapped_column(nullable=False) 23 | population_id: orm.Mapped[int] = orm.mapped_column( 24 | sqlalchemy.ForeignKey("population.id"), nullable=False, init=False 25 | ) 26 | population: orm.Mapped[Population] = orm.relationship() 27 | -------------------------------------------------------------------------------- /examples/4_example_experiment_setups/4d_robot_bodybrain_ea_database/database_components/_individual.py: -------------------------------------------------------------------------------- 1 | """Individual class.""" 2 | 3 | from dataclasses import dataclass 4 | 5 | from revolve2.experimentation.optimization.ea import Individual as GenericIndividual 6 | 7 | from ._base import Base 8 | from ._genotype import Genotype 9 | 10 | 11 | @dataclass 12 | class Individual( 13 | Base, GenericIndividual[Genotype], population_table="population", kw_only=True 14 | ): 15 | """An individual in a population.""" 16 | 17 | __tablename__ = "individual" 18 | -------------------------------------------------------------------------------- /examples/4_example_experiment_setups/4d_robot_bodybrain_ea_database/database_components/_population.py: -------------------------------------------------------------------------------- 1 | """Population class.""" 2 | 3 | from revolve2.experimentation.optimization.ea import Population as GenericPopulation 4 | 5 | from ._base import Base 6 | from ._individual import Individual 7 | 8 | 9 | class Population(Base, GenericPopulation[Individual], kw_only=True): 10 | """A population of individuals.""" 11 | 12 | __tablename__ = "population" 13 | -------------------------------------------------------------------------------- /examples/4_example_experiment_setups/4d_robot_bodybrain_ea_database/requirements.txt: -------------------------------------------------------------------------------- 1 | pandas>=2.1.0 2 | matplotlib>=3.8.0 3 | -------------------------------------------------------------------------------- /examples/4_example_experiment_setups/4d_robot_bodybrain_ea_database/rerun.py: -------------------------------------------------------------------------------- 1 | """Rerun the best robot between all experiments.""" 2 | 3 | import logging 4 | 5 | import config 6 | from database_components import Genotype, Individual 7 | from evaluator import Evaluator 8 | from sqlalchemy import select 9 | from sqlalchemy.orm import Session 10 | 11 | from revolve2.experimentation.database import OpenMethod, open_database_sqlite 12 | from revolve2.experimentation.logging import setup_logging 13 | 14 | 15 | def main() -> None: 16 | """Perform the rerun.""" 17 | setup_logging() 18 | 19 | # Load the best individual from the database. 20 | dbengine = open_database_sqlite( 21 | config.DATABASE_FILE, open_method=OpenMethod.OPEN_IF_EXISTS 22 | ) 23 | 24 | with Session(dbengine) as ses: 25 | row = ses.execute( 26 | select(Genotype, Individual.fitness) 27 | .join_from(Genotype, Individual, Genotype.id == Individual.genotype_id) 28 | .order_by(Individual.fitness.desc()) 29 | .limit(1) 30 | ).one() 31 | assert row is not None 32 | 33 | genotype = row[0] 34 | fitness = row[1] 35 | 36 | logging.info(f"Best fitness: {fitness}") 37 | 38 | # Create the evaluator. 39 | evaluator = Evaluator(headless=False, num_simulators=1) 40 | 41 | # Show the robot. 42 | evaluator.evaluate([genotype]) 43 | 44 | 45 | if __name__ == "__main__": 46 | main() 47 | -------------------------------------------------------------------------------- /examples/4_example_experiment_setups/4e_robot_brain_cmaes/README.md: -------------------------------------------------------------------------------- 1 | Here you will set up an experiment that optimizes the brain of a given robot body using CMA-ES. 2 | As the body is static, the genotype of the brain will be a fixed length real-valued vector. 3 | 4 | Before starting this tutorial, it is useful to look at the `3a_experiment_setup` and `3c_evaluate_multiple_isolated_robots` examples. 5 | It is also nice to understand the concept of a cpg brain, although not really needed. 6 | 7 | You learn: 8 | - How to optimize the brain of a robot using CMA-ES. 9 | 10 | To change the parameters of the experiment use `config.py`.
11 | The evaluation of individual robots is done in `evaluator.py`.
12 | 13 | To visualize the evolved robots, use `rerun.py` with the *PARAMS* you got from evolution. 14 | -------------------------------------------------------------------------------- /examples/4_example_experiment_setups/4e_robot_brain_cmaes/config.py: -------------------------------------------------------------------------------- 1 | """Configuration parameters for this example.""" 2 | 3 | from revolve2.standards.modular_robots_v1 import gecko_v1 4 | 5 | NUM_SIMULATORS = 8 6 | INITIAL_STD = 0.5 7 | NUM_GENERATIONS = 100 8 | BODY = gecko_v1() 9 | -------------------------------------------------------------------------------- /examples/4_example_experiment_setups/4e_robot_brain_cmaes/requirements.txt: -------------------------------------------------------------------------------- 1 | cma>=3.3.0 2 | -------------------------------------------------------------------------------- /examples/4_example_experiment_setups/4f_robot_brain_cmaes_database/README.md: -------------------------------------------------------------------------------- 1 | This is the `robot_brain_cmaes` example, but with added saving of results to a database. 2 | 3 | Definitely first look at the `4e_robot_brain_cmaes` and `4b_simple_ea_xor_database` examples. 4 | Many explanation comments are omitted here. 5 | 6 | To visualize the evolved robots, use `rerun.py` with the parameters you got from evolution. 7 | Running `plot.py` allows you to plot the robots fitness metrics over each generation. 8 | -------------------------------------------------------------------------------- /examples/4_example_experiment_setups/4f_robot_brain_cmaes_database/config.py: -------------------------------------------------------------------------------- 1 | """Configuration parameters for this example.""" 2 | 3 | from revolve2.standards.modular_robots_v2 import gecko_v2 4 | 5 | DATABASE_FILE = "database.sqlite" 6 | NUM_REPETITIONS = 5 7 | NUM_SIMULATORS = 8 8 | INITIAL_STD = 0.5 9 | NUM_GENERATIONS = 100 10 | BODY = gecko_v2() 11 | -------------------------------------------------------------------------------- /examples/4_example_experiment_setups/4f_robot_brain_cmaes_database/database_components/__init__.py: -------------------------------------------------------------------------------- 1 | """A collection of components used to record the experiments in the Database.""" 2 | 3 | from ._base import Base 4 | from ._experiment import Experiment 5 | from ._generation import Generation 6 | from ._genotype import Genotype 7 | from ._individual import Individual 8 | from ._population import Population 9 | 10 | __all__ = ["Base", "Experiment", "Generation", "Genotype", "Individual", "Population"] 11 | -------------------------------------------------------------------------------- /examples/4_example_experiment_setups/4f_robot_brain_cmaes_database/database_components/_base.py: -------------------------------------------------------------------------------- 1 | """Base class.""" 2 | 3 | import sqlalchemy.orm as orm 4 | 5 | 6 | class Base(orm.MappedAsDataclass, orm.DeclarativeBase): 7 | """Base class for all SQLAlchemy models in this example.""" 8 | 9 | pass 10 | -------------------------------------------------------------------------------- /examples/4_example_experiment_setups/4f_robot_brain_cmaes_database/database_components/_experiment.py: -------------------------------------------------------------------------------- 1 | """Experiment class.""" 2 | 3 | import sqlalchemy.orm as orm 4 | 5 | from revolve2.experimentation.database import HasId 6 | 7 | from ._base import Base 8 | 9 | 10 | class Experiment(Base, HasId): 11 | """Experiment description.""" 12 | 13 | __tablename__ = "experiment" 14 | 15 | # The seed for the rng. 16 | rng_seed: orm.Mapped[int] = orm.mapped_column(nullable=False) 17 | -------------------------------------------------------------------------------- /examples/4_example_experiment_setups/4f_robot_brain_cmaes_database/database_components/_generation.py: -------------------------------------------------------------------------------- 1 | """Generation class.""" 2 | 3 | import sqlalchemy 4 | import sqlalchemy.orm as orm 5 | 6 | from revolve2.experimentation.database import HasId 7 | 8 | from ._base import Base 9 | from ._experiment import Experiment 10 | from ._population import Population 11 | 12 | 13 | class Generation(Base, HasId): 14 | """A single finished iteration of CMA-ES.""" 15 | 16 | __tablename__ = "generation" 17 | 18 | experiment_id: orm.Mapped[int] = orm.mapped_column( 19 | sqlalchemy.ForeignKey("experiment.id"), nullable=False, init=False 20 | ) 21 | experiment: orm.Mapped[Experiment] = orm.relationship() 22 | generation_index: orm.Mapped[int] = orm.mapped_column(nullable=False) 23 | population_id: orm.Mapped[int] = orm.mapped_column( 24 | sqlalchemy.ForeignKey("population.id"), nullable=False, init=False 25 | ) 26 | population: orm.Mapped[Population] = orm.relationship() 27 | -------------------------------------------------------------------------------- /examples/4_example_experiment_setups/4f_robot_brain_cmaes_database/database_components/_genotype.py: -------------------------------------------------------------------------------- 1 | """Genotype class.""" 2 | 3 | from __future__ import annotations 4 | 5 | from revolve2.experimentation.database import HasId 6 | from revolve2.experimentation.optimization.ea import Parameters as GenericParameters 7 | 8 | from ._base import Base 9 | 10 | 11 | class Genotype(Base, HasId, GenericParameters): 12 | """A genotype that is an array of parameters.""" 13 | 14 | __tablename__ = "genotype" 15 | -------------------------------------------------------------------------------- /examples/4_example_experiment_setups/4f_robot_brain_cmaes_database/database_components/_individual.py: -------------------------------------------------------------------------------- 1 | """Individual class.""" 2 | 3 | from dataclasses import dataclass 4 | 5 | from revolve2.experimentation.optimization.ea import Individual as GenericIndividual 6 | 7 | from ._base import Base 8 | from ._genotype import Genotype 9 | 10 | 11 | @dataclass 12 | class Individual( 13 | Base, GenericIndividual[Genotype], population_table="population", kw_only=True 14 | ): 15 | """An individual in a population.""" 16 | 17 | __tablename__ = "individual" 18 | -------------------------------------------------------------------------------- /examples/4_example_experiment_setups/4f_robot_brain_cmaes_database/database_components/_population.py: -------------------------------------------------------------------------------- 1 | """Population class.""" 2 | 3 | from revolve2.experimentation.optimization.ea import Population as GenericPopulation 4 | 5 | from ._base import Base 6 | from ._individual import Individual 7 | 8 | 9 | class Population(Base, GenericPopulation[Individual], kw_only=True): 10 | """A population of individuals.""" 11 | 12 | __tablename__ = "population" 13 | -------------------------------------------------------------------------------- /examples/4_example_experiment_setups/4f_robot_brain_cmaes_database/requirements.txt: -------------------------------------------------------------------------------- 1 | cma>=3.3.0 2 | pandas>=2.1.0 3 | matplotlib>=3.8.0 4 | -------------------------------------------------------------------------------- /examples/4_example_experiment_setups/4g_explore_initial_population/README.md: -------------------------------------------------------------------------------- 1 | In this example you will learn how to create N random robots, starting from their genotypes and the process of mapping them into 2 | phenotypes and finally robots in order to asses the "quality" of your initial population and individual morphological traits. 3 | 4 | You learn: 5 | - How to generate N random genotypes (where N is set in config.py). 6 | - How to develop the genotypes into phenotypes to form robots. 7 | - How to visualize such robots to asses their morphologies 8 | - How to use the "MorphologicalMeasures" class to calculate different morpholoical traits of individuals. 9 | - How to use such measures to compute population metrics, such as diversity. 10 | -------------------------------------------------------------------------------- /examples/4_example_experiment_setups/4g_explore_initial_population/config.py: -------------------------------------------------------------------------------- 1 | """Configuration parameters for this example.""" 2 | 3 | DATABASE_FILE = "database.sqlite" 4 | NUM_SIMULATORS = 8 5 | POPULATION_SIZE = 2 # Setting this to 1 will result in warnings and faulty diversity measures, as you need more than 1 individual for that. 6 | EVALUATE = True 7 | VISUALIZE_MAP = True # Be careful when setting this to true when POPULATION_size > 1, as you will get plots for each individual. 8 | -------------------------------------------------------------------------------- /examples/4_example_experiment_setups/4g_explore_initial_population/individual.py: -------------------------------------------------------------------------------- 1 | """Individual class.""" 2 | 3 | from dataclasses import dataclass 4 | 5 | from genotype import Genotype 6 | 7 | 8 | @dataclass 9 | class Individual: 10 | """An individual in a population.""" 11 | 12 | genotype: Genotype 13 | fitness: float 14 | -------------------------------------------------------------------------------- /examples/4_example_experiment_setups/4g_explore_initial_population/requirements.txt: -------------------------------------------------------------------------------- 1 | scikit-learn>=1.5.2 2 | -------------------------------------------------------------------------------- /examples/4_example_experiment_setups/README.md: -------------------------------------------------------------------------------- 1 | ## 4) Lets look into more complex experiment setups 2 | Most projects require some more complex setups to make them interesting research. 3 | In this collection of examples we will show how such setups can be made and how you can incorporate databases for easy data collection. 4 | Additionally you will learn hwo to use the evolution abstraction layer in Revolve2. 5 | 6 | - `4a_simlpe_ea_xor` gives you a quick introduction on how you can setup a simple evolutionary algorithm for optimization. 7 | - In `4b_simple_ea_xor_database` we use the same logic as in the example before, but add a database to collect the information. 8 | - `4c_robot_bodybrain_ea` shows you how to use more abstraction objects and specifies how to optimize the brain and body of modular robots. 9 | - `4d_robot_bodybrain_ea_database` is the same example, with the addition of databases to allow for data storage. 10 | - If you want to add learning into your experiments look at `4e_robot_brain_cmaes`, in which we add learning to a **single** robot. 11 | - `4f_robot_brain_cmaes_database` does the same thing as the previous example with the addition of a database. 12 | - Finally you can learn about the exploration of the initial population and their morphological features in `4g_explore_initial_population` 13 | 14 | -------------------------------------------------------------------------------- /examples/5_physical_modular_robots/5a_physical_robot_remote/README.md: -------------------------------------------------------------------------------- 1 | In this example, you will control a physical modular robot by running its brain locally on your computer and streaming movement instructions to the physical modular robot. 2 | 3 | You learn: 4 | - How to create a configuration describing how a physical modular robot should be controlled. 5 | - How to remote control a physical modular robot using this configuration. 6 | 7 | Requirements: 8 | - A physical modular robot. Note whether you are using V1 or V2 hardware; If you do not know, it is probably V2. TODO in the next version of Revolve2 a guide will be added on how to build a physical robot. For now, ask the CI Group lab. 9 | - The modular robot body and brain defined in a Revolve2 ModularRobot object. It is recommended that you test it in simulation first. The code in this example shows you how to create a ModularRobot as well. 10 | - Understanding of simulation and other examples related to simulation will greatly help with your understanding of this example. 11 | 12 | The rest of this example is contained in the `main.py` file. 13 | -------------------------------------------------------------------------------- /examples/5_physical_modular_robots/5a_physical_robot_remote/requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ci-group/revolve2/8156a66624f96c43cdfcec05bbb366cc492b87e0/examples/5_physical_modular_robots/5a_physical_robot_remote/requirements.txt -------------------------------------------------------------------------------- /examples/5_physical_modular_robots/5b_compare_simulated_and_physical_robot/README.md: -------------------------------------------------------------------------------- 1 | In this example you create a physical robot with a simulated twin. 2 | You will use two separate scripts, one for the simulated robot and one for the physical robot. 3 | With this duplicate you can check whether you have built the physical robot correctly. 4 | 5 | If hinges in the simulation behave exactly the opposite as on the physical robot you can adjust them without rebuilding using the inverting function, as shown in the example. 6 | 7 | You learn: 8 | - How to validate the physical robot with simulations. 9 | - How to use inverting functionality for quick fixes. 10 | -------------------------------------------------------------------------------- /examples/5_physical_modular_robots/5b_compare_simulated_and_physical_robot/main_simulation.py: -------------------------------------------------------------------------------- 1 | """Simulate the modular robot for comparison with the physical counterpart.""" 2 | 3 | from revolve2.modular_robot_simulation import test_robot 4 | from revolve2.simulators.mujoco_simulator import LocalSimulator 5 | from revolve2.standards import terrains 6 | from revolve2.standards.modular_robots_v2 import gecko_v2 7 | from revolve2.standards.simulation_parameters import make_standard_batch_parameters 8 | 9 | 10 | def main() -> None: 11 | """Run the simulation part of the example.""" 12 | # Here we define the Modular Robot we want to test. 13 | # You can use either a ModularRobot instance or a Body instance. 14 | body = gecko_v2() 15 | 16 | # Now we test our simulated robot, to validate our physical robot. 17 | simulator = LocalSimulator(manual_control=True) 18 | batch_parameters = make_standard_batch_parameters() 19 | test_robot( 20 | robot=body, 21 | terrain=terrains.flat(), 22 | simulator=simulator, 23 | batch_parameters=batch_parameters, 24 | ) 25 | 26 | 27 | if __name__ == "__main__": 28 | main() 29 | -------------------------------------------------------------------------------- /examples/5_physical_modular_robots/5b_compare_simulated_and_physical_robot/requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ci-group/revolve2/8156a66624f96c43cdfcec05bbb366cc492b87e0/examples/5_physical_modular_robots/5b_compare_simulated_and_physical_robot/requirements.txt -------------------------------------------------------------------------------- /examples/5_physical_modular_robots/README.md: -------------------------------------------------------------------------------- 1 | ## 5) Put your evolved robots into the real world. 2 | Simply simulating all your experiments might not be enough. 3 | To validate if the experiments can reproduce on actual physical robots, you first need to build the robot. 4 | If you dont know how to start with this visit the [Revolve2 Documentation](https://ci-group.github.io/revolve2/). 5 | Specifically look at *Creating a physical robot* and *Physical Robot Core Setup*. 6 | 7 | - In `5a_physical_robot_remote` you learn how to control your newly built robot remotely. 8 | - In `5b_compare_simulated_and_physical_robot` you will be able to check whether your built robot has been built correctly, by comparing it to the simulation counterpart. 9 | -------------------------------------------------------------------------------- /experimentation/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["poetry-core>=1.6.0"] 3 | build-backend = "poetry.core.masonry.api" 4 | 5 | [tool.poetry] 6 | name = "revolve2-experimentation" 7 | version = "1.2.4" 8 | description = "Revolve2: Tools for experimentation." 9 | readme = "../README.md" 10 | authors = [ 11 | "Aart Stuurman ", 12 | "Oliver Weissl ", 13 | ] 14 | repository = "https://github.com/ci-group/revolve2" 15 | classifiers = [ 16 | "Development Status :: 5 - Production/Stable", 17 | "Typing :: Typed", 18 | "Topic :: Scientific/Engineering", 19 | "Programming Language :: Python :: 3", 20 | "Programming Language :: Python :: 3.10", 21 | "Programming Language :: Python :: 3.11", 22 | "Operating System :: OS Independent", 23 | "Intended Audience :: Science/Research", 24 | "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)", 25 | ] 26 | packages = [{ include = "revolve2" }] 27 | 28 | [tool.poetry.dependencies] 29 | python = "^3.10,<3.12" 30 | numpy = "^1.21.2" 31 | sqlalchemy = "^2.0.0" 32 | 33 | [tool.poetry.extras] 34 | dev = [] 35 | -------------------------------------------------------------------------------- /experimentation/revolve2/experimentation/__init__.py: -------------------------------------------------------------------------------- 1 | """Revolve2 experimentation tools.""" 2 | -------------------------------------------------------------------------------- /experimentation/revolve2/experimentation/_util/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ci-group/revolve2/8156a66624f96c43cdfcec05bbb366cc492b87e0/experimentation/revolve2/experimentation/_util/__init__.py -------------------------------------------------------------------------------- /experimentation/revolve2/experimentation/_util/init_subclass_get_generic_args.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Type, TypeVar, get_args, get_origin 2 | 3 | TChild = TypeVar("TChild") 4 | TParent = TypeVar("TParent") 5 | 6 | 7 | def init_subclass_get_generic_args( 8 | child: Type[TChild], parent: Type[TParent] 9 | ) -> tuple[Any, ...]: 10 | """ 11 | Get the generic arguments from a class within the __init_subclass__ function. 12 | 13 | :param child: The type passed to the __init_subclass__ function. 14 | :param parent: The type of the parent class, which is the class __init_subclass__ is implemented for. 15 | :returns: The types. Keep in mind these can be `ForwardRef`. 16 | """ 17 | # find parent and its type annotations in the list of base classes of child 18 | orig_bases: list[Type[TParent]] = [orig_base for orig_base in child.__orig_bases__ if get_origin(orig_base) is parent] # type: ignore[attr-defined] 19 | assert ( 20 | len(orig_bases) == 1 21 | ), "Implementer thinks this should be impossible. Expected that user can only inherit from parent class once." 22 | orig_base = orig_bases[0] 23 | # get the generic types from the type annotations 24 | return get_args(orig_base) 25 | -------------------------------------------------------------------------------- /experimentation/revolve2/experimentation/database/__init__.py: -------------------------------------------------------------------------------- 1 | """Standard SQLAlchemy models and different ways to open databases.""" 2 | 3 | from ._has_id import HasId 4 | from ._open_method import OpenMethod 5 | from ._sqlite import open_async_database_sqlite, open_database_sqlite 6 | 7 | __all__ = ["HasId", "OpenMethod", "open_async_database_sqlite", "open_database_sqlite"] 8 | -------------------------------------------------------------------------------- /experimentation/revolve2/experimentation/database/_has_id.py: -------------------------------------------------------------------------------- 1 | import sqlalchemy.orm as orm 2 | 3 | 4 | class HasId(orm.MappedAsDataclass): 5 | """An SQLAlchemy mixin that provides an id column.""" 6 | 7 | id: orm.Mapped[int] = orm.mapped_column( 8 | primary_key=True, 9 | unique=True, 10 | autoincrement=True, 11 | nullable=False, 12 | init=False, 13 | ) 14 | -------------------------------------------------------------------------------- /experimentation/revolve2/experimentation/database/_open_method.py: -------------------------------------------------------------------------------- 1 | from enum import Enum, auto 2 | 3 | 4 | class OpenMethod(Enum): 5 | """Describes the way a database should be opened.""" 6 | 7 | OPEN_IF_EXISTS = auto() 8 | OPEN_OR_CREATE = auto() 9 | NOT_EXISTS_AND_CREATE = auto() 10 | OVERWITE_IF_EXISTS = auto() 11 | -------------------------------------------------------------------------------- /experimentation/revolve2/experimentation/evolution/__init__.py: -------------------------------------------------------------------------------- 1 | """Concrete Elements for Evolutionary Processes.""" 2 | 3 | from ._modular_robot_evolution import ModularRobotEvolution 4 | 5 | __all__ = ["ModularRobotEvolution"] 6 | -------------------------------------------------------------------------------- /experimentation/revolve2/experimentation/evolution/abstract_elements/__init__.py: -------------------------------------------------------------------------------- 1 | """An Abstraction Layer for Elements in an Evolutionary Process.""" 2 | 3 | from ._evaluator import Evaluator 4 | from ._evolution import Evolution 5 | from ._learner import Learner 6 | from ._reproducer import Reproducer 7 | from ._selector import Selector 8 | 9 | __all__ = ["Evaluator", "Evolution", "Learner", "Reproducer", "Selector"] 10 | -------------------------------------------------------------------------------- /experimentation/revolve2/experimentation/evolution/abstract_elements/_evaluator.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import Any 3 | 4 | TPopulation = ( 5 | Any # An alias for Any signifying that a population can vary depending on use-case. 6 | ) 7 | 8 | 9 | class Evaluator(ABC): 10 | """An Evaluator object that enables evaluation of individuals in an evolutionary process.""" 11 | 12 | @abstractmethod 13 | def evaluate(self, population: TPopulation) -> list[float]: 14 | """ 15 | Evaluate individuals from a population. 16 | 17 | :param population: The population for evaluation. 18 | :return: The results of the evaluation. 19 | """ 20 | -------------------------------------------------------------------------------- /experimentation/revolve2/experimentation/evolution/abstract_elements/_evolution.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import Any 3 | 4 | TPopulation = ( 5 | Any # An alias for Any signifying that a population can vary depending on use-case. 6 | ) 7 | 8 | 9 | class Evolution(ABC): 10 | """An abstract object to encapsulate an evolutionary process.""" 11 | 12 | @abstractmethod 13 | def step(self, population: TPopulation, **kwargs: Any) -> TPopulation: 14 | """ 15 | Step the current evolution by one iteration.. 16 | 17 | :param population: The current population. 18 | :param kwargs: Additional keyword arguments to use in the step. 19 | :return: The population resulting from the step 20 | """ 21 | -------------------------------------------------------------------------------- /experimentation/revolve2/experimentation/evolution/abstract_elements/_learner.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import Any 3 | 4 | from ._evaluator import Evaluator 5 | 6 | TPopulation = ( 7 | Any # An alias for Any signifying that a population can vary depending on use-case. 8 | ) 9 | 10 | 11 | class Learner(ABC): 12 | """ 13 | A Learner object that enables learning for individuals in an evolutionary process. 14 | 15 | The learner is dependent on its reward function, which is: a measure that drives learning. 16 | Depending on the learning method used, the reward can simply equal task performance. 17 | 18 | Task performance on the other hand is a measure that reflects how well a task is performed. 19 | In a robot system with multiple tasks, there are multiple definitions of task performance. 20 | Task performance can be used to define fitness and/or reward functions. 21 | 22 | For more information wait for Prof. Dr. A.E. Eiben`s book on evolutionary robotics, or ask him directly. 23 | """ 24 | 25 | _reward_function: Evaluator 26 | 27 | @abstractmethod 28 | def learn(self, population: TPopulation) -> TPopulation: 29 | """ 30 | Make Individuals from a population learn. 31 | 32 | :param population: The population. 33 | :return: The learned population. 34 | """ 35 | -------------------------------------------------------------------------------- /experimentation/revolve2/experimentation/evolution/abstract_elements/_reproducer.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import Any 3 | 4 | TPopulation = ( 5 | Any # An alias for Any signifying that a population can vary depending on use-case. 6 | ) 7 | 8 | 9 | class Reproducer(ABC): 10 | """A Reproducer object that enables the reproduction of individuals in an evolutionary process.""" 11 | 12 | @abstractmethod 13 | def reproduce(self, population: TPopulation, **kwargs: Any) -> TPopulation: 14 | """ 15 | Make Individuals Reproduce. 16 | 17 | :param population: The population. 18 | :param kwargs: Additional arguments. 19 | :return: The children. 20 | """ 21 | -------------------------------------------------------------------------------- /experimentation/revolve2/experimentation/evolution/abstract_elements/_selector.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import Any 3 | 4 | TPopulation = ( 5 | Any # An alias for Any signifying that a population can vary depending on use-case. 6 | ) 7 | KWArgs = dict[str, Any] 8 | 9 | 10 | class Selector(ABC): 11 | """A Selector object that enables selection of individuals in an evolutionary process.""" 12 | 13 | @abstractmethod 14 | def select( 15 | self, population: TPopulation, **kwargs: Any 16 | ) -> tuple[TPopulation, KWArgs]: 17 | """ 18 | Select individuals from a population. 19 | 20 | :param population: The population for selection. 21 | :param kwargs: Possible metrics for selection. 22 | :return: The selected subset of the population and additional kwargs. 23 | """ 24 | -------------------------------------------------------------------------------- /experimentation/revolve2/experimentation/logging.py: -------------------------------------------------------------------------------- 1 | """Functions to work with logging in a standardized way.""" 2 | 3 | import logging 4 | 5 | 6 | def setup_logging(level: int = logging.INFO, file_name: str | None = None) -> None: 7 | """ 8 | Set up logging for experiments. 9 | 10 | :param level: The log level to use. 11 | :param file_name: If not None, also writes to this file. 12 | """ 13 | """ 14 | Each message has an associated 'level'. 15 | By default, we are interested in messages of level 'info' and the more severe 'warning', 'error', and 'critical', and we exclude the less severe 'debug'. 16 | Furthermore, we specify the format in which we want the messages to be printed. 17 | """ 18 | logging.basicConfig( 19 | level=level, 20 | format="[%(asctime)s] [%(levelname)s] [%(module)s] %(message)s", 21 | ) 22 | if file_name is not None: 23 | logging.root.handlers.append(logging.FileHandler(file_name)) 24 | logging.info("=======================================") 25 | logging.info("=======================================") 26 | logging.info("=======================================") 27 | logging.info(f"{'New log starts here.':^39}") 28 | logging.info("=======================================") 29 | logging.info("=======================================") 30 | logging.info("=======================================") 31 | -------------------------------------------------------------------------------- /experimentation/revolve2/experimentation/optimization/__init__.py: -------------------------------------------------------------------------------- 1 | """Standard algorithms, tools, and SQLAlchemy models and mixing for optimization.""" 2 | -------------------------------------------------------------------------------- /experimentation/revolve2/experimentation/optimization/ea/__init__.py: -------------------------------------------------------------------------------- 1 | """Standardized building blocks related to Evolutionary Algorithms.""" 2 | 3 | from ._generation import Generation 4 | from ._individual import Individual 5 | from ._parameters import Parameters 6 | from ._population import Population 7 | 8 | __all__ = ["Generation", "Individual", "Parameters", "Population"] 9 | -------------------------------------------------------------------------------- /experimentation/revolve2/experimentation/optimization/ea/_parameters.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import numpy.typing as npt 3 | import sqlalchemy.orm as orm 4 | from sqlalchemy import event 5 | from sqlalchemy.engine import Connection 6 | 7 | 8 | class Parameters(orm.MappedAsDataclass): 9 | """ 10 | An SQLAlchemy mixing that provides a parameters column that is a tuple of floats. 11 | 12 | The parameters are saved in the database as string of semicolon seperated floats. 13 | """ 14 | 15 | parameters: npt.NDArray[np.float_] 16 | 17 | _serialized_parameters: orm.Mapped[str] = orm.mapped_column( 18 | "serialized_parameters", init=False, nullable=False 19 | ) 20 | 21 | 22 | @event.listens_for(Parameters, "before_update", propagate=True) 23 | @event.listens_for(Parameters, "before_insert", propagate=True) 24 | def _update_serialized_parameters( 25 | mapper: orm.Mapper[Parameters], 26 | connection: Connection, 27 | target: Parameters, 28 | ) -> None: 29 | target._serialized_parameters = ";".join((str(p) for p in target.parameters)) 30 | 31 | 32 | @event.listens_for(Parameters, "load", propagate=True) 33 | def _deserialize_parameters(target: Parameters, context: orm.QueryContext) -> None: 34 | target.parameters = np.array( 35 | [float(p) for p in target._serialized_parameters.split(";")] 36 | ) 37 | -------------------------------------------------------------------------------- /experimentation/revolve2/experimentation/optimization/ea/population_management/__init__.py: -------------------------------------------------------------------------------- 1 | """Functions for combining populations in EA algorithms.""" 2 | 3 | from ._generational import generational 4 | from ._steady_state import steady_state 5 | 6 | __all__ = [ 7 | "generational", 8 | "steady_state", 9 | ] 10 | -------------------------------------------------------------------------------- /experimentation/revolve2/experimentation/optimization/ea/population_management/_generational.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, TypeVar 2 | 3 | Genotype = TypeVar("Genotype") 4 | Fitness = TypeVar("Fitness") 5 | 6 | 7 | def generational( 8 | old_genotypes: list[Genotype], 9 | old_fitnesses: list[Fitness], 10 | new_genotypes: list[Genotype], 11 | new_fitnesses: list[Fitness], 12 | selection_function: Callable[[int, list[Genotype], list[Fitness]], list[int]], 13 | ) -> tuple[list[int], list[int]]: 14 | """ 15 | Select `len(old_genotypes)` individuals using the provided selection function from only the offspring(`new_genotypes`). 16 | 17 | :param old_genotypes: Genotypes of the individuals in the parent population. Ignored and only here for function signature compatibility with `steady_state`. 18 | :param old_fitnesses: Fitnesses of the individuals in the parent population. Ignored and only here for function signature compatibility with `steady_state`. 19 | :param new_genotypes: Genotypes of the individuals from the offspring. 20 | :param new_fitnesses: Fitnesses of the individuals from the offspring. 21 | :param selection_function: Function that selects n individuals from a population based on their genotype and fitness. (n, genotypes, fitnesses) -> indices 22 | :returns: (always empty list of indices of selected old individuals, indices of selected individuals from offspring). 23 | """ 24 | population_size = len(old_genotypes) 25 | assert len(new_genotypes) == len(new_fitnesses) 26 | assert len(new_fitnesses) >= population_size 27 | 28 | return [], selection_function(population_size, new_genotypes, new_fitnesses) 29 | -------------------------------------------------------------------------------- /experimentation/revolve2/experimentation/optimization/ea/selection/__init__.py: -------------------------------------------------------------------------------- 1 | """Functions for selecting individuals from populations in EA algorithms.""" 2 | 3 | from ._multiple_unique import multiple_unique 4 | from ._pareto_frontier import pareto_frontier 5 | from ._topn import topn 6 | from ._tournament import tournament 7 | 8 | __all__ = ["multiple_unique", "pareto_frontier", "topn", "tournament"] 9 | -------------------------------------------------------------------------------- /experimentation/revolve2/experimentation/optimization/ea/selection/_argsort.py: -------------------------------------------------------------------------------- 1 | from typing import Sequence, TypeVar 2 | 3 | from ._supports_lt import SupportsLt 4 | 5 | Item = TypeVar("Item", bound="SupportsLt") 6 | 7 | 8 | def argsort(seq: Sequence[Item]) -> list[int]: 9 | """ 10 | Get the indices of the sequence sorted by value. 11 | 12 | :param seq: The sequence. 13 | :returns: The indices. 14 | """ 15 | # http://stackoverflow.com/questions/3071415/efficient-method-to-calculate-the-rank-vector-of-a-list-in-python 16 | return sorted(range(len(seq)), key=seq.__getitem__) 17 | -------------------------------------------------------------------------------- /experimentation/revolve2/experimentation/optimization/ea/selection/_multiple_unique.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, TypeVar 2 | 3 | import numpy as np 4 | import numpy.typing as npt 5 | 6 | TIndividual = TypeVar("TIndividual") 7 | TFitness = TypeVar("TFitness") 8 | 9 | 10 | def multiple_unique( 11 | selection_size: int, 12 | population: list[TIndividual], 13 | fitnesses: list[TFitness], 14 | selection_function: Callable[[list[TIndividual], list[TFitness]], int], 15 | ) -> npt.NDArray[np.float_]: 16 | """ 17 | Select multiple distinct individuals from a population using the provided selection function. 18 | 19 | :param selection_size: Amount of of individuals to select. 20 | :param population: List of individuals to select from. 21 | :param fitnesses: Fitnesses of the population. 22 | :param selection_function: Function that select a single individual from a population. ([TIndividual], [TFitness]) -> index. 23 | :returns: Indices of the selected individuals. 24 | """ 25 | assert len(population) == len(fitnesses) 26 | assert selection_size <= len(population) 27 | 28 | selected_individuals = [] 29 | for _ in range(selection_size): 30 | new_individual = False 31 | while new_individual is False: 32 | selected_individual = selection_function(population, fitnesses) 33 | if selected_individual not in selected_individuals: 34 | selected_individuals.append(selected_individual) 35 | new_individual = True 36 | return np.array(selected_individuals) 37 | -------------------------------------------------------------------------------- /experimentation/revolve2/experimentation/optimization/ea/selection/_supports_lt.py: -------------------------------------------------------------------------------- 1 | from typing import Protocol, TypeVar 2 | 3 | TSupportsLt = TypeVar("TSupportsLt", bound="SupportsLt") 4 | 5 | 6 | class SupportsLt(Protocol): 7 | """Interface for types supporting the < operator.""" 8 | 9 | def __lt__(self: TSupportsLt, other: TSupportsLt) -> bool: 10 | """ 11 | Compare two objects using the < operator. 12 | 13 | :param other: The object to compare this with. 14 | """ 15 | pass 16 | -------------------------------------------------------------------------------- /experimentation/revolve2/experimentation/optimization/ea/selection/_topn.py: -------------------------------------------------------------------------------- 1 | from typing import TypeVar 2 | 3 | from ._argsort import argsort 4 | from ._supports_lt import SupportsLt 5 | 6 | Genotype = TypeVar("Genotype") 7 | Fitness = TypeVar("Fitness", bound=SupportsLt) 8 | 9 | 10 | def topn(n: int, genotypes: list[Genotype], fitnesses: list[Fitness]) -> list[int]: 11 | """ 12 | Get indices of the top n genotypes sorted by their fitness. 13 | 14 | :param n: The number of genotypes to select. 15 | :param genotypes: The genotypes. Ignored, but argument kept for function signature compatibility with other selection functions/ 16 | :param fitnesses: Fitnesses of the genotypes. 17 | :returns: Indices of the selected genotypes. 18 | """ 19 | assert len(fitnesses) >= n 20 | 21 | return argsort(fitnesses)[::-1][:n] 22 | -------------------------------------------------------------------------------- /experimentation/revolve2/experimentation/optimization/ea/selection/_tournament.py: -------------------------------------------------------------------------------- 1 | from typing import TypeVar 2 | 3 | import numpy as np 4 | 5 | Fitness = TypeVar("Fitness") 6 | 7 | 8 | def tournament(rng: np.random.Generator, fitnesses: list[Fitness], k: int) -> int: 9 | """ 10 | Perform tournament selection and return the index of the best individual. 11 | 12 | :param rng: Random number generator. 13 | :param fitnesses: List of finesses of individuals that joint the tournamente. 14 | :param k: Amount of individuals to participate in tournament. 15 | :returns: The index of te individual that won the tournament. 16 | """ 17 | assert len(fitnesses) >= k 18 | 19 | participant_indices = rng.choice(range(len(fitnesses)), size=k) 20 | return max(participant_indices, key=lambda i: fitnesses[i]) # type: ignore[no-any-return] 21 | -------------------------------------------------------------------------------- /experimentation/revolve2/experimentation/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ci-group/revolve2/8156a66624f96c43cdfcec05bbb366cc492b87e0/experimentation/revolve2/experimentation/py.typed -------------------------------------------------------------------------------- /experimentation/revolve2/experimentation/rng.py: -------------------------------------------------------------------------------- 1 | """Functions for standardized number generation.""" 2 | 3 | import hashlib 4 | import logging 5 | from datetime import datetime 6 | 7 | import numpy as np 8 | 9 | 10 | def seed_from_time(log_seed: bool = True) -> int: 11 | """ 12 | Create a seed from the current time in microseconds. 13 | 14 | :param log_seed: If the seed should be logged. It probably should. 15 | :returns: The created seed. 16 | """ 17 | seed = int(datetime.now().timestamp() * 1e6) 18 | if log_seed: 19 | logging.info(f"Rng seed: {seed}") 20 | return seed 21 | 22 | 23 | def seed_from_string(text: str) -> int: 24 | """ 25 | Convert a string seed to an integer seed. 26 | 27 | :param text: The seed as string. 28 | :returns: The seed as integer. 29 | """ 30 | return int( 31 | hashlib.sha256(text.encode()).hexdigest(), 32 | 16, 33 | ) 34 | 35 | 36 | def make_rng(seed: int) -> np.random.Generator: 37 | """ 38 | Create a numpy random number generator from a seed. 39 | 40 | :param seed: The seed to use. 41 | :returns: The random number generator. 42 | """ 43 | return np.random.Generator(np.random.PCG64(seed)) 44 | 45 | 46 | def make_rng_time_seed(log_seed: bool = True) -> np.random.Generator: 47 | """ 48 | Create a numpy random number generator from a seed. 49 | 50 | :param log_seed: If the automatically created seed should be logged. It probably should. 51 | :returns: The random number generator. 52 | """ 53 | return np.random.Generator(np.random.PCG64(seed_from_time(log_seed))) 54 | -------------------------------------------------------------------------------- /modular_robot/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["poetry-core>=1.6.0"] 3 | build-backend = "poetry.core.masonry.api" 4 | 5 | [tool.poetry] 6 | name = "revolve2-modular-robot" 7 | version = "1.2.4" 8 | description = "Revolve2: Everything for defining modular robots." 9 | readme = "../README.md" 10 | authors = [ 11 | "Aart Stuurman ", 12 | "Oliver Weissl ", 13 | "Andres Garcia " 14 | ] 15 | repository = "https://github.com/ci-group/revolve2" 16 | classifiers = [ 17 | "Development Status :: 5 - Production/Stable", 18 | "Typing :: Typed", 19 | "Topic :: Scientific/Engineering", 20 | "Programming Language :: Python :: 3", 21 | "Programming Language :: Python :: 3.10", 22 | "Programming Language :: Python :: 3.11", 23 | "Operating System :: OS Independent", 24 | "Intended Audience :: Science/Research", 25 | "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)", 26 | ] 27 | packages = [{ include = "revolve2" }] 28 | 29 | [tool.poetry.dependencies] 30 | python = "^3.10,<3.12" 31 | numpy = "^1.21.2" 32 | pyrr = "^0.10.3" 33 | 34 | [tool.poetry.extras] 35 | dev = [] 36 | -------------------------------------------------------------------------------- /modular_robot/revolve2/modular_robot/__init__.py: -------------------------------------------------------------------------------- 1 | """Classes and functions to describe and work with modular robots as used in the CI Group at VU Amsterdam.""" 2 | 3 | from ._modular_robot import ModularRobot 4 | from ._modular_robot_control_interface import ModularRobotControlInterface 5 | 6 | __all__ = [ 7 | "ModularRobot", 8 | "ModularRobotControlInterface", 9 | ] 10 | -------------------------------------------------------------------------------- /modular_robot/revolve2/modular_robot/_modular_robot.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | 3 | from .body.base import Body 4 | from .brain import Brain 5 | 6 | 7 | class ModularRobot: 8 | """A module robot consisting of a body and brain.""" 9 | 10 | _uuid: uuid.UUID 11 | 12 | body: Body 13 | brain: Brain 14 | 15 | def __init__(self, body: Body, brain: Brain): 16 | """ 17 | Initialize the ModularRobot. 18 | 19 | :param body: The body of the modular robot. 20 | :param brain: The brain of the modular robot. 21 | """ 22 | self._uuid = uuid.uuid1() 23 | self.body = body 24 | self.brain = brain 25 | 26 | @property 27 | def uuid(self) -> uuid.UUID: 28 | """ 29 | Get the uuid, used for identification. 30 | 31 | :returns: The uuid. 32 | """ 33 | return self._uuid 34 | -------------------------------------------------------------------------------- /modular_robot/revolve2/modular_robot/_modular_robot_control_interface.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | from .body.base import ActiveHinge 4 | 5 | 6 | class ModularRobotControlInterface(ABC): 7 | """Interface for controlling modular robots.""" 8 | 9 | @abstractmethod 10 | def set_active_hinge_target(self, active_hinge: ActiveHinge, target: float) -> None: 11 | """ 12 | Set the position target for an active hinge on the modular robot. 13 | 14 | :param active_hinge: The active hinge object to set the target for. 15 | :param target: The target value to set. 16 | """ 17 | -------------------------------------------------------------------------------- /modular_robot/revolve2/modular_robot/body/__init__.py: -------------------------------------------------------------------------------- 1 | """Objects used for modular robot bodies.""" 2 | 3 | from ._attachment_point import AttachmentPoint 4 | from ._color import Color 5 | from ._module import Module 6 | from ._right_angles import RightAngles 7 | 8 | __all__ = [ 9 | "AttachmentPoint", 10 | "Color", 11 | "Module", 12 | "RightAngles", 13 | ] 14 | -------------------------------------------------------------------------------- /modular_robot/revolve2/modular_robot/body/_attachment_point.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from pyrr import Quaternion, Vector3 4 | 5 | 6 | @dataclass 7 | class AttachmentPoint: 8 | """ 9 | An attachment point is a theoretical location on the parent module for the child to be attached to. 10 | 11 | The attachment of a module to its parent is not considered to be a separate AttachmentPoint. 12 | This class simply is used for potential children to be placed on the correct positions of the current module. 13 | """ 14 | 15 | orientation: Quaternion 16 | """The orientation of the attachment point on the module.""" 17 | offset: Vector3 18 | """The offset for the attachment point, with respect to the center of the module.""" 19 | -------------------------------------------------------------------------------- /modular_robot/revolve2/modular_robot/body/_color.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | @dataclass 5 | class Color: 6 | """ 7 | Represents a color in RGBA format. 8 | 9 | All values should from 0 to 255. 10 | """ 11 | 12 | red: int 13 | green: int 14 | blue: int 15 | alpha: int 16 | 17 | def to_normalized_rgba_list(self) -> list[float]: 18 | """ 19 | Convert to rgba list where each value is between 0 and 1. 20 | 21 | :returns: The list. 22 | """ 23 | return [self.red / 255, self.green / 255, self.blue / 255, self.alpha / 255] 24 | -------------------------------------------------------------------------------- /modular_robot/revolve2/modular_robot/body/_right_angles.py: -------------------------------------------------------------------------------- 1 | import math 2 | from enum import Enum 3 | 4 | 5 | class RightAngles(Enum): 6 | """Standard angles at which some modular robot modules can be attached.""" 7 | 8 | DEG_0 = 0 9 | DEG_90 = math.pi / 2.0 10 | DEG_180 = math.pi 11 | DEG_270 = math.pi / 2.0 * 3 12 | RAD_0 = 0 13 | RAD_HALFPI = math.pi / 2.0 14 | RAD_PI = math.pi 15 | RAD_ONEANDAHALFPI = math.pi / 2.0 * 3 16 | -------------------------------------------------------------------------------- /modular_robot/revolve2/modular_robot/body/base/__init__.py: -------------------------------------------------------------------------------- 1 | """Abstract Base Modules for Robots.""" 2 | 3 | from ._active_hinge import ActiveHinge 4 | from ._attachment_face import AttachmentFace 5 | from ._body import Body 6 | from ._brick import Brick 7 | from ._core import Core 8 | 9 | __all__ = [ 10 | "ActiveHinge", 11 | "AttachmentFace", 12 | "Body", 13 | "Brick", 14 | "Core", 15 | ] 16 | -------------------------------------------------------------------------------- /modular_robot/revolve2/modular_robot/body/base/_attachment_face.py: -------------------------------------------------------------------------------- 1 | from pyrr import Quaternion 2 | 3 | from .._attachment_point import AttachmentPoint 4 | from .._color import Color 5 | from .._module import Module 6 | from .._right_angles import RightAngles 7 | 8 | 9 | class AttachmentFace(Module): 10 | """ 11 | Collect AttachmentPoints on a modules face. 12 | 13 | This face can be thought of as a pseudo-module which usually does not have a body on its own. 14 | """ 15 | 16 | def __init__( 17 | self, 18 | rotation: float | RightAngles, 19 | attachment_points: dict[int, AttachmentPoint], 20 | ) -> None: 21 | """ 22 | Initialize this object. 23 | 24 | :param rotation: Orientation of this model relative to its parent. 25 | :param attachment_points: The attachment points available on a module. 26 | """ 27 | """ 28 | The base module only has orientation as its parameter since not all modules are square. 29 | 30 | Here we covert the angle of the module to its orientation in space. 31 | """ 32 | orientation = Quaternion.from_eulers( 33 | [rotation if isinstance(rotation, float) else rotation.value, 0, 0] 34 | ) 35 | super().__init__( 36 | orientation=orientation, 37 | attachment_points=attachment_points, 38 | color=Color(255, 255, 255, 255), 39 | sensors=[], 40 | ) 41 | -------------------------------------------------------------------------------- /modular_robot/revolve2/modular_robot/body/sensors/__init__.py: -------------------------------------------------------------------------------- 1 | """Sensors for Modular Robots.""" 2 | 3 | from ._active_hinge_sensor import ActiveHingeSensor 4 | from ._camera_sensor import CameraSensor 5 | from ._imu_sensor import IMUSensor 6 | from ._sensor import Sensor 7 | 8 | __all__ = ["ActiveHingeSensor", "CameraSensor", "IMUSensor", "Sensor"] 9 | -------------------------------------------------------------------------------- /modular_robot/revolve2/modular_robot/body/sensors/_active_hinge_sensor.py: -------------------------------------------------------------------------------- 1 | from pyrr import Quaternion, Vector3 2 | 3 | from ._sensor import Sensor 4 | 5 | 6 | class ActiveHingeSensor(Sensor): 7 | """A sensors for an active hinge that measures its angle.""" 8 | 9 | def __init__(self) -> None: 10 | """Initialize the ActiveHinge sensor.""" 11 | super().__init__(Quaternion(), Vector3()) 12 | -------------------------------------------------------------------------------- /modular_robot/revolve2/modular_robot/body/sensors/_camera_sensor.py: -------------------------------------------------------------------------------- 1 | from pyrr import Quaternion, Vector3 2 | 3 | from ._sensor import Sensor 4 | 5 | 6 | class CameraSensor(Sensor): 7 | """A camera for the Modular Robot.""" 8 | 9 | _camera_size: tuple[int, int] 10 | 11 | def __init__( 12 | self, 13 | position: Vector3, 14 | orientation: Quaternion = Quaternion(), 15 | camera_size: tuple[int, int] = (50, 50), 16 | ) -> None: 17 | """ 18 | Initialize the Camera Sensor. 19 | 20 | Note that the camera_size can have a significant impact on performance. 21 | For evolution related work stick to 10x10 for fast results. 22 | 23 | :param position: The position of the camera. 24 | :param orientation: The rotation of the camera. 25 | :param camera_size: The size of the camera image. 26 | """ 27 | super().__init__(orientation, position) 28 | self._camera_size = camera_size 29 | 30 | @property 31 | def camera_size(self) -> tuple[int, int]: 32 | """ 33 | Get the size of the camera. 34 | 35 | :return: The camera size. 36 | """ 37 | return self._camera_size 38 | -------------------------------------------------------------------------------- /modular_robot/revolve2/modular_robot/body/sensors/_imu_sensor.py: -------------------------------------------------------------------------------- 1 | from pyrr import Quaternion, Vector3 2 | 3 | from ._sensor import Sensor 4 | 5 | 6 | class IMUSensor(Sensor): 7 | """ 8 | An inertial measurement unit (IMU). 9 | 10 | Reports specific force(closely related to acceleration), angular rate(closely related to angular velocity), and orientation. 11 | """ 12 | 13 | def __init__( 14 | self, position: Vector3, orientation: Quaternion = Quaternion() 15 | ) -> None: 16 | """ 17 | Initialize the IMU sensor. 18 | 19 | :param orientation: The rotation of the IMU. 20 | :param position: The position of the IMU. 21 | """ 22 | super().__init__(orientation, position) 23 | -------------------------------------------------------------------------------- /modular_robot/revolve2/modular_robot/body/sensors/_sensor.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | from abc import ABC 3 | 4 | from pyrr import Quaternion, Vector3 5 | 6 | 7 | class Sensor(ABC): 8 | """An abstract Sensor Class.""" 9 | 10 | _uuid: uuid.UUID 11 | _orientation: Quaternion 12 | _position: Vector3 13 | 14 | def __init__(self, orientation: Quaternion, position: Vector3) -> None: 15 | """ 16 | Initialize the sensor. 17 | 18 | :param orientation: The rotation of the sensor. 19 | :param position: The position of the sensor. 20 | """ 21 | self._orientation = orientation 22 | self._uuid = uuid.uuid1() 23 | self._position = position 24 | 25 | @property 26 | def uuid(self) -> uuid.UUID: 27 | """ 28 | Get the uuid of the sensor. 29 | 30 | :return: The uuid. 31 | """ 32 | return self._uuid 33 | 34 | @property 35 | def orientation(self) -> Quaternion: 36 | """ 37 | Return the orientation of the sensor. 38 | 39 | :return: The orientation. 40 | """ 41 | return self._orientation 42 | 43 | @property 44 | def position(self) -> Vector3: 45 | """ 46 | Get the relative position of the sensor on a module. 47 | 48 | :return: The position. 49 | """ 50 | return self._position 51 | -------------------------------------------------------------------------------- /modular_robot/revolve2/modular_robot/body/v1/__init__.py: -------------------------------------------------------------------------------- 1 | """Explicit modules of V1 Robot.""" 2 | 3 | from ._active_hinge_v1 import ActiveHingeV1 4 | from ._body_v1 import BodyV1 5 | from ._brick_v1 import BrickV1 6 | from ._core_v1 import CoreV1 7 | 8 | __all__ = ["ActiveHingeV1", "BodyV1", "BrickV1", "CoreV1"] 9 | -------------------------------------------------------------------------------- /modular_robot/revolve2/modular_robot/body/v1/_active_hinge_v1.py: -------------------------------------------------------------------------------- 1 | from pyrr import Vector3 2 | 3 | from .._right_angles import RightAngles 4 | from ..base import ActiveHinge 5 | 6 | 7 | class ActiveHingeV1(ActiveHinge): 8 | """ 9 | An active hinge module for a modular robot. 10 | 11 | This is a rotary joint. 12 | """ 13 | 14 | def __init__(self, rotation: float | RightAngles): 15 | """ 16 | Initialize this object. 17 | 18 | :param rotation: The Modules rotation. 19 | """ 20 | super().__init__( 21 | rotation=rotation, 22 | range=1.047197551, # 60 degrees 23 | effort=0.948013269, # motor specs: 9.4 kgfcm at 4.8V or 11 kgfcm at 6.0V -> at 5.0V: 9.6667 * 9.807 / 100 24 | velocity=6.338968228, # motor specs: 0.17 at 4.8V or 0.14 s/60deg at 6.0V -> at 5.0V: 1 / 0.1652 * 60 / 360 * 2pi 25 | frame_bounding_box=Vector3([0.018, 0.053, 0.0165891]), 26 | frame_offset=0.04525, 27 | servo1_bounding_box=Vector3([0.0583, 0.0512, 0.020]), 28 | servo2_bounding_box=Vector3([0.002, 0.053, 0.053]), 29 | frame_mass=0.011, 30 | servo1_mass=0.058, 31 | servo2_mass=0.02, 32 | servo_offset=0.0299, 33 | joint_offset=0.0119, 34 | static_friction=1.0, 35 | dynamic_friction=1.0, 36 | armature=0.002, 37 | pid_gain_p=5.0, 38 | pid_gain_d=0.05, 39 | child_offset=0.0583 / 2 + 0.002, 40 | sensors=[], 41 | ) 42 | -------------------------------------------------------------------------------- /modular_robot/revolve2/modular_robot/body/v1/_body_v1.py: -------------------------------------------------------------------------------- 1 | from ..base import Body 2 | from ._core_v1 import CoreV1 3 | 4 | 5 | class BodyV1(Body): 6 | """Body of a V1 modular robot.""" 7 | 8 | _core: CoreV1 9 | 10 | def __init__(self) -> None: 11 | """Initialize this object.""" 12 | super().__init__(CoreV1(0.0)) 13 | 14 | @property 15 | def core_v1(self) -> CoreV1: 16 | """ 17 | Get the specific v1 core of the body. 18 | 19 | :return: The v1 core. 20 | """ 21 | return self._core 22 | -------------------------------------------------------------------------------- /modular_robot/revolve2/modular_robot/body/v1/_brick_v1.py: -------------------------------------------------------------------------------- 1 | from pyrr import Vector3 2 | 3 | from .._right_angles import RightAngles 4 | from ..base import Brick 5 | 6 | 7 | class BrickV1(Brick): 8 | """A brick module for a v1 modular robot.""" 9 | 10 | def __init__(self, rotation: float | RightAngles): 11 | """ 12 | Initialize this object. 13 | 14 | :param rotation: The modules' rotation. 15 | """ 16 | super().__init__( 17 | rotation=rotation, 18 | bounding_box=Vector3([0.06288625, 0.06288625, 0.0603]), 19 | mass=0.030, 20 | child_offset=0.06288625 / 2.0, 21 | sensors=[], 22 | ) 23 | -------------------------------------------------------------------------------- /modular_robot/revolve2/modular_robot/body/v1/_core_v1.py: -------------------------------------------------------------------------------- 1 | from pyrr import Vector3 2 | 3 | from .._right_angles import RightAngles 4 | from ..base import Core 5 | 6 | 7 | class CoreV1(Core): 8 | """The core module of a v1 modular robot.""" 9 | 10 | def __init__(self, rotation: float | RightAngles): 11 | """ 12 | Initialize this object. 13 | 14 | :param rotation: The modules' rotation. 15 | """ 16 | super().__init__( 17 | rotation=rotation, 18 | bounding_box=Vector3([0.089, 0.089, 0.0603]), 19 | mass=0.250, 20 | child_offset=0.089 / 2.0, 21 | sensors=[], 22 | ) 23 | -------------------------------------------------------------------------------- /modular_robot/revolve2/modular_robot/body/v2/__init__.py: -------------------------------------------------------------------------------- 1 | """Explicit modules of V2 Robots.""" 2 | 3 | from ._active_hinge_v2 import ActiveHingeV2 4 | from ._body_v2 import BodyV2 5 | from ._brick_v2 import BrickV2 6 | from ._core_v2 import CoreV2 7 | 8 | __all__ = ["ActiveHingeV2", "BodyV2", "BrickV2", "CoreV2"] 9 | -------------------------------------------------------------------------------- /modular_robot/revolve2/modular_robot/body/v2/_active_hinge_v2.py: -------------------------------------------------------------------------------- 1 | from pyrr import Vector3 2 | 3 | from .._right_angles import RightAngles 4 | from ..base import ActiveHinge 5 | from ..sensors import ActiveHingeSensor 6 | 7 | 8 | class ActiveHingeV2(ActiveHinge): 9 | """ 10 | An active hinge v2 module for a modular robot. 11 | 12 | This is a rotary joint. 13 | """ 14 | 15 | def __init__(self, rotation: float | RightAngles): 16 | """ 17 | Initialize this object. 18 | 19 | :param rotation: The Modules rotation. 20 | """ 21 | super().__init__( 22 | rotation=rotation, 23 | range=1.047197551, 24 | effort=0.948013269, 25 | velocity=6.338968228, 26 | frame_bounding_box=Vector3([0.018, 0.052, 0.0165891]), 27 | frame_offset=0.04495, 28 | servo1_bounding_box=Vector3([0.05125, 0.0512, 0.020]), 29 | servo2_bounding_box=Vector3([0.002, 0.052, 0.052]), 30 | frame_mass=0.01144, 31 | servo1_mass=0.058, 32 | servo2_mass=0.025, 33 | servo_offset=0.0239, 34 | joint_offset=0.0119, 35 | static_friction=1.0, 36 | dynamic_friction=1.0, 37 | armature=0.002, 38 | pid_gain_p=5.0, 39 | pid_gain_d=0.05, 40 | child_offset=0.05125 / 2 + 0.002 + 0.01, 41 | sensors=[ 42 | ActiveHingeSensor() 43 | ], # By default, V2 robots have ActiveHinge sensors, since the hardware also supports them natively. 44 | ) 45 | -------------------------------------------------------------------------------- /modular_robot/revolve2/modular_robot/body/v2/_body_v2.py: -------------------------------------------------------------------------------- 1 | from ..base import Body 2 | from ._core_v2 import CoreV2 3 | 4 | 5 | class BodyV2(Body): 6 | """Body of a V2 modular robot.""" 7 | 8 | _core: CoreV2 9 | 10 | def __init__(self) -> None: 11 | """Initialize the Body.""" 12 | super().__init__(CoreV2(0.0)) 13 | 14 | @property 15 | def core_v2(self) -> CoreV2: 16 | """ 17 | Get the specific v2 core of the body. 18 | 19 | This function is usd since the base core has fewer attributes than a V2 core. 20 | Using this, allows us to specify the return type without overwriting functions of the base module. 21 | 22 | :return: The v2 core. 23 | """ 24 | return self._core 25 | -------------------------------------------------------------------------------- /modular_robot/revolve2/modular_robot/body/v2/_brick_v2.py: -------------------------------------------------------------------------------- 1 | from pyrr import Vector3 2 | 3 | from .._right_angles import RightAngles 4 | from ..base import Brick 5 | 6 | 7 | class BrickV2(Brick): 8 | """A brick module for a modular robot.""" 9 | 10 | def __init__(self, rotation: float | RightAngles): 11 | """ 12 | Initialize this object. 13 | 14 | :param rotation: The modules' rotation. 15 | """ 16 | w, h, d = 0.075, 0.075, 0.075 17 | super().__init__( 18 | rotation=rotation, 19 | bounding_box=Vector3([w, h, d]), 20 | mass=0.05864, 21 | child_offset=d / 2.0, 22 | sensors=[], 23 | ) 24 | -------------------------------------------------------------------------------- /modular_robot/revolve2/modular_robot/brain/__init__.py: -------------------------------------------------------------------------------- 1 | """Modular robot brain interface and implementations.""" 2 | 3 | from ._brain import Brain 4 | from ._brain_instance import BrainInstance 5 | 6 | __all__ = ["Brain", "BrainInstance"] 7 | -------------------------------------------------------------------------------- /modular_robot/revolve2/modular_robot/brain/_brain.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | from ._brain_instance import BrainInstance 4 | 5 | 6 | class Brain(ABC): 7 | """ 8 | The brain of a modular robot. 9 | 10 | Inherit from this to implement your own brain. 11 | Each brain implements the `make_instance` function, 12 | which create the actual brain instance that control the robot. 13 | The instance contains all the state associated with the control strategy; 14 | this class must be stateless. 15 | """ 16 | 17 | @abstractmethod 18 | def make_instance(self) -> BrainInstance: 19 | """ 20 | Create an instance of this brain. 21 | 22 | :returns: The created instance. 23 | """ 24 | -------------------------------------------------------------------------------- /modular_robot/revolve2/modular_robot/brain/_brain_instance.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | from .._modular_robot_control_interface import ModularRobotControlInterface 4 | from ..sensor_state import ModularRobotSensorState 5 | 6 | 7 | class BrainInstance(ABC): 8 | """ 9 | An instance of a brain that perform the control of a robot. 10 | 11 | Instances of this class can be stateful. 12 | """ 13 | 14 | @abstractmethod 15 | def control( 16 | self, 17 | dt: float, 18 | sensor_state: ModularRobotSensorState, 19 | control_interface: ModularRobotControlInterface, 20 | ) -> None: 21 | """ 22 | Control the modular robot. 23 | 24 | :param dt: Elapsed seconds since last call to this function. 25 | :param sensor_state: Interface for reading the current sensor state. 26 | :param control_interface: Interface for controlling the robot. 27 | """ 28 | -------------------------------------------------------------------------------- /modular_robot/revolve2/modular_robot/brain/cpg/__init__.py: -------------------------------------------------------------------------------- 1 | """CPG brains for modular robots.""" 2 | 3 | from ._brain_cpg_instance import BrainCpgInstance 4 | from ._brain_cpg_network_neighbor import BrainCpgNetworkNeighbor 5 | from ._brain_cpg_network_neighbor_random import BrainCpgNetworkNeighborRandom 6 | from ._brain_cpg_network_static import BrainCpgNetworkStatic 7 | from ._cpg_network_structure import CpgNetworkStructure 8 | from ._make_cpg_network_structure_neighbor import ( 9 | active_hinges_to_cpg_network_structure_neighbor, 10 | ) 11 | 12 | __all__ = [ 13 | "BrainCpgInstance", 14 | "BrainCpgNetworkNeighbor", 15 | "BrainCpgNetworkNeighborRandom", 16 | "BrainCpgNetworkStatic", 17 | "CpgNetworkStructure", 18 | "active_hinges_to_cpg_network_structure_neighbor", 19 | ] 20 | -------------------------------------------------------------------------------- /modular_robot/revolve2/modular_robot/brain/cpg/_brain_cpg_network_neighbor_random.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from ...body.base import ActiveHinge, Body 4 | from ._brain_cpg_network_neighbor import BrainCpgNetworkNeighbor 5 | 6 | 7 | class BrainCpgNetworkNeighborRandom(BrainCpgNetworkNeighbor): 8 | """ 9 | A cpg brain with random weights between neurons. 10 | 11 | The weights are randomly generated when this object is created, 12 | so they will be the same for every controller instance. 13 | """ 14 | 15 | _rng: np.random.Generator 16 | 17 | def __init__(self, body: Body, rng: np.random.Generator) -> None: 18 | """ 19 | Initialize this object. 20 | 21 | :param body: The body to create the cpg network and brain for. 22 | :param rng: Random number generator used for generating the weights. 23 | """ 24 | self._rng = rng 25 | super().__init__(body) 26 | 27 | def _make_weights( 28 | self, 29 | active_hinges: list[ActiveHinge], 30 | connections: list[tuple[ActiveHinge, ActiveHinge]], 31 | body: Body, 32 | ) -> tuple[list[float], list[float]]: 33 | return ( 34 | (self._rng.random(size=len(active_hinges)) * 2.0 - 1).tolist(), 35 | (self._rng.random(size=len(connections)) * 2.0 - 1).tolist(), 36 | ) 37 | -------------------------------------------------------------------------------- /modular_robot/revolve2/modular_robot/brain/cpg/_make_cpg_network_structure_neighbor.py: -------------------------------------------------------------------------------- 1 | from ...body.base import ActiveHinge 2 | from ._cpg_network_structure import CpgNetworkStructure, CpgPair 3 | 4 | 5 | def active_hinges_to_cpg_network_structure_neighbor( 6 | active_hinges: list[ActiveHinge], 7 | ) -> tuple[CpgNetworkStructure, list[tuple[int, ActiveHinge]]]: 8 | """ 9 | Create the structure of a CPG network based on a list of active hinges. 10 | 11 | The order of the active hinges matches the order of the CPGs. 12 | I.e. every active hinges has a corresponding CPG, 13 | and these are stored in the order the hinges are provided in. 14 | 15 | :param active_hinges: The active hinges to base the structure on. 16 | :returns: The created structure and a mapping between state indices and active hinges. 17 | """ 18 | cpgs = CpgNetworkStructure.make_cpgs(len(active_hinges)) 19 | connections: set[CpgPair] = set() 20 | 21 | active_hinge_to_cpg = { 22 | active_hinge: cpg for active_hinge, cpg in zip(active_hinges, cpgs) 23 | } 24 | 25 | for active_hinge, cpg in zip(active_hinges, cpgs): 26 | neighbours = [ 27 | n 28 | for n in active_hinge.neighbours(within_range=2) 29 | if isinstance(n, ActiveHinge) 30 | ] 31 | connections = connections.union( 32 | [CpgPair(cpg, active_hinge_to_cpg[neighbour]) for neighbour in neighbours] 33 | ) 34 | 35 | cpg_network_structure = CpgNetworkStructure(cpgs, connections) 36 | 37 | return cpg_network_structure, [ 38 | mapping for mapping in zip(cpg_network_structure.output_indices, active_hinges) 39 | ] 40 | -------------------------------------------------------------------------------- /modular_robot/revolve2/modular_robot/brain/dummy/__init__.py: -------------------------------------------------------------------------------- 1 | """A brain that does nothing.""" 2 | 3 | from ._brain_dummy import BrainDummy 4 | from ._brain_dummy_instance import BrainDummyInstance 5 | 6 | __all__ = ["BrainDummy", "BrainDummyInstance"] 7 | -------------------------------------------------------------------------------- /modular_robot/revolve2/modular_robot/brain/dummy/_brain_dummy.py: -------------------------------------------------------------------------------- 1 | from .._brain import Brain 2 | from .._brain_instance import BrainInstance 3 | from ._brain_dummy_instance import BrainDummyInstance 4 | 5 | 6 | class BrainDummy(Brain): 7 | """A brain that does nothing.""" 8 | 9 | def make_instance(self) -> BrainInstance: 10 | """ 11 | Create an instance of this brain. 12 | 13 | :returns: The created instance. 14 | """ 15 | return BrainDummyInstance() 16 | -------------------------------------------------------------------------------- /modular_robot/revolve2/modular_robot/brain/dummy/_brain_dummy_instance.py: -------------------------------------------------------------------------------- 1 | from ..._modular_robot_control_interface import ModularRobotControlInterface 2 | from ...sensor_state import ModularRobotSensorState 3 | from .._brain_instance import BrainInstance 4 | 5 | 6 | class BrainDummyInstance(BrainInstance): 7 | """A brain that does nothing.""" 8 | 9 | def control( 10 | self, 11 | dt: float, 12 | sensor_state: ModularRobotSensorState, 13 | control_interface: ModularRobotControlInterface, 14 | ) -> None: 15 | """ 16 | Control nothing. 17 | 18 | This brain does not do anything for control, as it is an empty box. 19 | 20 | :param dt: Elapsed seconds since last call to this function. 21 | :param sensor_state: Interface for reading the current sensor state. 22 | :param control_interface: Interface for controlling the robot. 23 | """ 24 | -------------------------------------------------------------------------------- /modular_robot/revolve2/modular_robot/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ci-group/revolve2/8156a66624f96c43cdfcec05bbb366cc492b87e0/modular_robot/revolve2/modular_robot/py.typed -------------------------------------------------------------------------------- /modular_robot/revolve2/modular_robot/sensor_state/__init__.py: -------------------------------------------------------------------------------- 1 | """Sensor states from modular robots.""" 2 | 3 | from ._active_hinge_sensor_state import ActiveHingeSensorState 4 | from ._camera_sensor_state import CameraSensorState 5 | from ._imu_sensor_state import IMUSensorState 6 | from ._modular_robot_sensor_state import ModularRobotSensorState 7 | 8 | __all__ = [ 9 | "ActiveHingeSensorState", 10 | "CameraSensorState", 11 | "IMUSensorState", 12 | "ModularRobotSensorState", 13 | ] 14 | -------------------------------------------------------------------------------- /modular_robot/revolve2/modular_robot/sensor_state/_active_hinge_sensor_state.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | 4 | class ActiveHingeSensorState(ABC): 5 | """The state of an active hinge sensor.""" 6 | 7 | @property 8 | @abstractmethod 9 | def position(self) -> float: 10 | """ 11 | Get the measured position of the active hinge. 12 | 13 | :returns: The measured position. 14 | """ 15 | -------------------------------------------------------------------------------- /modular_robot/revolve2/modular_robot/sensor_state/_camera_sensor_state.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | import numpy as np 4 | from numpy.typing import NDArray 5 | 6 | 7 | class CameraSensorState(ABC): 8 | """The state of a camera sensor.""" 9 | 10 | @property 11 | @abstractmethod 12 | def image(self) -> NDArray[np.uint8]: 13 | """ 14 | Get the current image. 15 | 16 | :returns: The image. 17 | """ 18 | pass 19 | -------------------------------------------------------------------------------- /modular_robot/revolve2/modular_robot/sensor_state/_imu_sensor_state.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | from pyrr import Vector3 4 | 5 | 6 | class IMUSensorState(ABC): 7 | """The state of an IMU sensor.""" 8 | 9 | @property 10 | @abstractmethod 11 | def specific_force(self) -> Vector3: 12 | """ 13 | Get the measured specific force. 14 | 15 | :returns: The measured specific force. 16 | """ 17 | 18 | @property 19 | @abstractmethod 20 | def angular_rate(self) -> Vector3: 21 | """ 22 | Get the measured angular rate. 23 | 24 | :returns: The measured angular rate. 25 | """ 26 | 27 | @property 28 | @abstractmethod 29 | def orientation(self) -> Vector3: 30 | """ 31 | Get the measured orientation. 32 | 33 | :returns: The measured orientation. 34 | """ 35 | -------------------------------------------------------------------------------- /modular_robot/revolve2/modular_robot/sensor_state/_modular_robot_sensor_state.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | from ..body.sensors import ActiveHingeSensor, CameraSensor, IMUSensor 4 | from ._active_hinge_sensor_state import ActiveHingeSensorState 5 | from ._camera_sensor_state import CameraSensorState 6 | from ._imu_sensor_state import IMUSensorState 7 | 8 | 9 | class ModularRobotSensorState(ABC): 10 | """The state of modular robot's sensors.""" 11 | 12 | @abstractmethod 13 | def get_active_hinge_sensor_state( 14 | self, sensor: ActiveHingeSensor 15 | ) -> ActiveHingeSensorState: 16 | """ 17 | Get the state of the provided active hinge sensor. 18 | 19 | :param sensor: The sensor. 20 | :returns: The state. 21 | """ 22 | 23 | @abstractmethod 24 | def get_imu_sensor_state(self, sensor: IMUSensor) -> IMUSensorState: 25 | """ 26 | Get the state of the provided IMU sensor. 27 | 28 | :param sensor: The sensor. 29 | :returns: The state. 30 | """ 31 | 32 | @abstractmethod 33 | def get_camera_sensor_state(self, sensor: CameraSensor) -> CameraSensorState: 34 | """ 35 | Get the state of the provided camera sensor. 36 | 37 | :param sensor: The sensor. 38 | :returns: The state. 39 | """ 40 | -------------------------------------------------------------------------------- /modular_robot_physical/revolve2/modular_robot_physical/__init__.py: -------------------------------------------------------------------------------- 1 | """Physical Robot Control and Utils.""" 2 | 3 | from ._config import Config 4 | from ._hardware_type import HardwareType 5 | from ._protocol_version import PROTOCOL_VERSION 6 | from ._standard_port import STANDARD_PORT 7 | from ._uuid_key import UUIDKey 8 | 9 | __all__ = ["Config", "HardwareType", "PROTOCOL_VERSION", "STANDARD_PORT", "UUIDKey"] 10 | -------------------------------------------------------------------------------- /modular_robot_physical/revolve2/modular_robot_physical/_bin/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Executable scripts shipped with this library. 3 | 4 | These are not meant to be imported and are simply a module because of packaging reasons. 5 | """ 6 | -------------------------------------------------------------------------------- /modular_robot_physical/revolve2/modular_robot_physical/_bin/robot_daemon.py: -------------------------------------------------------------------------------- 1 | """Physical modular robot daemon that provides a network interface to use the robot.""" 2 | 3 | import typed_argparse as tap 4 | 5 | from .._hardware_type import HardwareType 6 | from ..robot_daemon import run_robot_daemon 7 | 8 | 9 | class Args(tap.TypedArgs): 10 | """Arguments for the program.""" 11 | 12 | debug: bool = tap.arg(help="Print debug messages.", default=False) 13 | hardware: HardwareType = tap.arg(help="The type of hardware this brain runs on.") 14 | dry: bool = tap.arg( 15 | help="Run in dry mode, not doing anything with the robot hardware.", 16 | default=False, 17 | ) 18 | 19 | 20 | def main_with_args(args: Args) -> None: 21 | """ 22 | Run the program from the point where arguments were parsed. 23 | 24 | :param args: The parsed program arguments. 25 | """ 26 | run_robot_daemon(debug=args.debug, dry=args.dry, hardware_type=args.hardware) 27 | 28 | 29 | def main() -> None: 30 | """Run the script.""" 31 | tap.Parser(Args).bind(main_with_args).run() 32 | -------------------------------------------------------------------------------- /modular_robot_physical/revolve2/modular_robot_physical/_config.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from dataclasses import dataclass, field 4 | 5 | from revolve2.modular_robot import ModularRobot 6 | from revolve2.modular_robot.body.base import ActiveHinge 7 | 8 | from ._uuid_key import UUIDKey 9 | 10 | 11 | @dataclass 12 | class Config: 13 | """The configuration for running a physical robot.""" 14 | 15 | modular_robot: ModularRobot 16 | """The Modular Robot Object.""" 17 | hinge_mapping: dict[UUIDKey[ActiveHinge], int] 18 | """Hinge mapping: map each active hinge object to a specific Servo with its ID (int).""" 19 | initial_hinge_positions: dict[UUIDKey[ActiveHinge], float] 20 | """Initial positions of the active hinges.""" 21 | run_duration: int 22 | """Duration to run the brain for in seconds.""" 23 | control_frequency: int 24 | """Frequency at which to call the brain control functions in seconds. There currently is a bug where if you set the control frequency to (around) 10 or smaller the program might hang. This is most likely a big in pycapnp and once pycapnp v2 is released this is probably resolved.""" 25 | inverse_servos: dict[int, bool] = field(default_factory=dict) 26 | """ 27 | If a servo is mounted in the wrong direction on the body one can fix it by inverting the action. 28 | inverse_servos allows you to inverse specific servos with their gpio number as key. 29 | """ 30 | -------------------------------------------------------------------------------- /modular_robot_physical/revolve2/modular_robot_physical/_hardware_type.py: -------------------------------------------------------------------------------- 1 | from enum import Enum, auto 2 | 3 | 4 | class HardwareType(Enum): 5 | """The types of hardware.""" 6 | 7 | v1 = auto() 8 | v2 = auto() 9 | -------------------------------------------------------------------------------- /modular_robot_physical/revolve2/modular_robot_physical/_protocol_version.py: -------------------------------------------------------------------------------- 1 | """The version of the communication protocol used.""" 2 | 3 | PROTOCOL_VERSION = "1.0.2" 4 | -------------------------------------------------------------------------------- /modular_robot_physical/revolve2/modular_robot_physical/_standard_port.py: -------------------------------------------------------------------------------- 1 | """The standard port for the communication of physical modular robot with the remote.""" 2 | 3 | STANDARD_PORT = 20812 4 | -------------------------------------------------------------------------------- /modular_robot_physical/revolve2/modular_robot_physical/physical_interfaces/__init__.py: -------------------------------------------------------------------------------- 1 | """Interfaces to the hardware.""" 2 | 3 | from ._get_interface import get_interface 4 | from ._physical_interface import PhysicalInterface 5 | 6 | __all__ = ["PhysicalInterface", "get_interface"] 7 | -------------------------------------------------------------------------------- /modular_robot_physical/revolve2/modular_robot_physical/physical_interfaces/_get_interface.py: -------------------------------------------------------------------------------- 1 | from .._hardware_type import HardwareType 2 | from ._physical_interface import PhysicalInterface 3 | 4 | 5 | def get_interface( 6 | hardware_type: HardwareType, debug: bool, dry: bool 7 | ) -> PhysicalInterface: 8 | """ 9 | Get the interface for the given hardware type. 10 | 11 | :param hardware_type: The type of hardware. 12 | :param debug: If debugging messages are activated. 13 | :param dry: If servo outputs are not propagated to the physical servos.: 14 | :returns: The interface. 15 | :raises NotImplementedError: If the hardware type is not supported or if careful is enabled and not supported for the hardware type. 16 | :raises ModuleNotFoundError: If some required package are not installed. 17 | """ 18 | try: 19 | match hardware_type: 20 | case HardwareType.v1: 21 | from ..physical_interfaces.v1 import V1PhysicalInterface 22 | 23 | return V1PhysicalInterface(debug=debug, dry=dry) 24 | case HardwareType.v2: 25 | from ..physical_interfaces.v2 import V2PhysicalInterface 26 | 27 | return V2PhysicalInterface(debug=debug, dry=dry) 28 | case _: 29 | raise NotImplementedError("Hardware type not supported.") 30 | except ModuleNotFoundError as e: 31 | raise ModuleNotFoundError( 32 | f"Could not import physical interface, did you install the required extras? Error: {e}" 33 | ) from None 34 | -------------------------------------------------------------------------------- /modular_robot_physical/revolve2/modular_robot_physical/physical_interfaces/v1/__init__.py: -------------------------------------------------------------------------------- 1 | """V1 physical interface implementation.""" 2 | 3 | from ._v1_physical_interface import V1PhysicalInterface 4 | 5 | __all__ = ["V1PhysicalInterface"] 6 | -------------------------------------------------------------------------------- /modular_robot_physical/revolve2/modular_robot_physical/physical_interfaces/v2/__init__.py: -------------------------------------------------------------------------------- 1 | """V2 physical interface implementation.""" 2 | 3 | from ._v2_physical_interface import V2PhysicalInterface 4 | 5 | __all__ = ["V2PhysicalInterface"] 6 | -------------------------------------------------------------------------------- /modular_robot_physical/revolve2/modular_robot_physical/remote/__init__.py: -------------------------------------------------------------------------------- 1 | """Physical modular robot remote control.""" 2 | 3 | from ._remote import run_remote 4 | from ._test_physical_robot import test_physical_robot 5 | 6 | __all__ = ["run_remote", "test_physical_robot"] 7 | -------------------------------------------------------------------------------- /modular_robot_physical/revolve2/modular_robot_physical/remote/_active_hinge_sensor_state_impl.py: -------------------------------------------------------------------------------- 1 | from revolve2.modular_robot.sensor_state import ActiveHingeSensorState 2 | 3 | 4 | class ActiveHingeSensorStateImpl(ActiveHingeSensorState): 5 | """ActiveHingeSensorState implementation for physical robots.""" 6 | 7 | _position: float 8 | 9 | def __init__(self, position: float) -> None: 10 | """ 11 | Initialize this object. 12 | 13 | :param position: The position of the active hinge. 14 | """ 15 | self._position = position 16 | 17 | @property 18 | def position(self) -> float: 19 | """ 20 | Get the measured position of the active hinge. 21 | 22 | :returns: The measured position. 23 | """ 24 | return self._position 25 | -------------------------------------------------------------------------------- /modular_robot_physical/revolve2/modular_robot_physical/remote/_camera_sensor_state_impl.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from numpy.typing import NDArray 3 | 4 | from revolve2.modular_robot.sensor_state import CameraSensorState 5 | 6 | 7 | class CameraSensorStateImpl(CameraSensorState): 8 | """CameraSensorState implementation for physical robots.""" 9 | 10 | _image: NDArray[np.uint8] 11 | 12 | def __init__(self, image: NDArray[np.uint8]) -> None: 13 | """ 14 | Initialize this object. 15 | 16 | :param image: The current image. 17 | """ 18 | self._image = image 19 | 20 | @property 21 | def image(self) -> NDArray[np.uint8]: 22 | """ 23 | Get the current image. 24 | 25 | :returns: The image. 26 | """ 27 | return self._image 28 | -------------------------------------------------------------------------------- /modular_robot_physical/revolve2/modular_robot_physical/remote/_imu_sensor_state_impl.py: -------------------------------------------------------------------------------- 1 | from pyrr import Vector3 2 | 3 | from revolve2.modular_robot.sensor_state import IMUSensorState 4 | 5 | 6 | class IMUSensorStateImpl(IMUSensorState): 7 | """The state of an IMU sensor.""" 8 | 9 | _specific_force: Vector3 10 | _angular_rate: Vector3 11 | _orientation: Vector3 12 | 13 | def __init__( 14 | self, specific_force: Vector3, angular_rate: Vector3, orientation: Vector3 15 | ) -> None: 16 | """ 17 | Initialize this object. 18 | 19 | :param specific_force: Speicfic force. 20 | :param angular_rate: Angular rate. 21 | :param orientation: Orientation. 22 | """ 23 | self._specific_force = specific_force 24 | self._angular_rate = angular_rate 25 | self._orientation = orientation 26 | 27 | @property 28 | def specific_force(self) -> Vector3: 29 | """ 30 | Get the measured specific force. 31 | 32 | :returns: The measured specific force. 33 | """ 34 | return self._specific_force 35 | 36 | @property 37 | def angular_rate(self) -> Vector3: 38 | """ 39 | Get the measured angular rate. 40 | 41 | :returns: The measured angular rate. 42 | """ 43 | return self._angular_rate 44 | 45 | @property 46 | def orientation(self) -> Vector3: 47 | """ 48 | Get the measured orientation. 49 | 50 | :returns: The measured orientation. 51 | """ 52 | return self._orientation 53 | -------------------------------------------------------------------------------- /modular_robot_physical/revolve2/modular_robot_physical/remote/_modular_robot_control_interface_impl.py: -------------------------------------------------------------------------------- 1 | from revolve2.modular_robot import ModularRobotControlInterface 2 | from revolve2.modular_robot.body.base import ActiveHinge 3 | 4 | from .._uuid_key import UUIDKey 5 | 6 | 7 | class ModularRobotControlInterfaceImpl(ModularRobotControlInterface): 8 | """Implementation of ModularRobotControlInterface.""" 9 | 10 | _set_active_hinges: list[tuple[UUIDKey[ActiveHinge], float]] 11 | 12 | def __init__(self) -> None: 13 | """Initialize this object.""" 14 | self._set_active_hinges = [] 15 | 16 | def set_active_hinge_target(self, active_hinge: ActiveHinge, target: float) -> None: 17 | """ 18 | Set the position target for an active hinge. 19 | 20 | Target is clamped within the active hinges range. 21 | 22 | :param active_hinge: The active hinge to set the target for. 23 | :param target: The target to set. 24 | """ 25 | self._set_active_hinges.append((UUIDKey(active_hinge), target)) 26 | -------------------------------------------------------------------------------- /modular_robot_physical/revolve2/modular_robot_physical/remote/_modular_robot_sensor_state_impl_v1.py: -------------------------------------------------------------------------------- 1 | from revolve2.modular_robot.body.sensors import ( 2 | ActiveHingeSensor, 3 | CameraSensor, 4 | IMUSensor, 5 | ) 6 | from revolve2.modular_robot.sensor_state import ( 7 | ActiveHingeSensorState, 8 | CameraSensorState, 9 | IMUSensorState, 10 | ModularRobotSensorState, 11 | ) 12 | 13 | 14 | class ModularRobotSensorStateImplV1(ModularRobotSensorState): 15 | """Implementation of ModularRobotSensorState for v1 robots.""" 16 | 17 | def get_active_hinge_sensor_state( 18 | self, sensor: ActiveHingeSensor 19 | ) -> ActiveHingeSensorState: 20 | """ 21 | Get sensor states for Hinges. 22 | 23 | :param sensor: The sensor to query. 24 | :raises NotImplementedError: Always. 25 | """ 26 | raise NotImplementedError("V1 hardware does not support sensor reading.") 27 | 28 | def get_imu_sensor_state(self, sensor: IMUSensor) -> IMUSensorState: 29 | """ 30 | Get the state of the provided IMU sensor. 31 | 32 | :param sensor: The sensor. 33 | :raises NotImplementedError: Always. 34 | """ 35 | raise NotImplementedError() 36 | 37 | def get_camera_sensor_state(self, sensor: CameraSensor) -> CameraSensorState: 38 | """ 39 | Get the state of the provided camera sensor. 40 | 41 | :param sensor: The sensor. 42 | :raises NotImplementedError: Always. 43 | """ 44 | raise NotImplementedError() 45 | -------------------------------------------------------------------------------- /modular_robot_physical/revolve2/modular_robot_physical/remote/_test_physical_robot.py: -------------------------------------------------------------------------------- 1 | from revolve2.modular_robot import ModularRobot 2 | from revolve2.modular_robot.body.base import ActiveHinge, Body 3 | from revolve2.modular_robot.brain.dummy import BrainDummy 4 | 5 | from .._config import Config 6 | from .._uuid_key import UUIDKey 7 | from ._remote import run_remote 8 | 9 | 10 | def test_physical_robot( 11 | robot: ModularRobot | Body, 12 | hostname: str, 13 | hinge_mapping: dict[UUIDKey[ActiveHinge], int], 14 | inverse_servos: dict[int, bool], 15 | ) -> None: 16 | """ 17 | Remotely connect to a physical robot and provide manual controls to test it. 18 | 19 | :param robot: Body of the robot. 20 | :param hostname: Hostname of the robot. 21 | :param hinge_mapping: map each active hinge object to a specific Servo with its ID (int). 22 | :param inverse_servos: If a servo is mounted in the wrong direction on the body one can fix it by inversing the action. inverse_servos allows you to inverse specific servos with their gpio number as key. 23 | """ 24 | if isinstance(robot, Body): 25 | body = robot 26 | brain = BrainDummy() 27 | robot = ModularRobot(body=body, brain=brain) 28 | config = Config( 29 | modular_robot=robot, 30 | hinge_mapping=hinge_mapping, 31 | run_duration=99999, 32 | control_frequency=20, 33 | initial_hinge_positions={ 34 | UUIDKey(active_hinge.value): 0.0 for active_hinge in hinge_mapping.keys() 35 | }, 36 | inverse_servos=inverse_servos, 37 | ) 38 | 39 | run_remote( 40 | config=config, hostname=hostname, on_prepared=lambda: None, manual_mode=True 41 | ) 42 | -------------------------------------------------------------------------------- /modular_robot_physical/revolve2/modular_robot_physical/robot_daemon/__init__.py: -------------------------------------------------------------------------------- 1 | """Daemon with TCP API to control the robot.""" 2 | 3 | from ._robot_daemon import run_robot_daemon 4 | 5 | __all__ = ["run_robot_daemon"] 6 | -------------------------------------------------------------------------------- /modular_robot_physical/revolve2/modular_robot_physical/robot_daemon_api/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | API for the physical modular robot daemon. 3 | 4 | For developers: 5 | This API uses Cap'n Proto. Take a look at its documentation 6 | The main file to change is `robot_daemon_api.capnp`. 7 | After you changed the file, run `_generate_stubs.sh` to generate the python files matching your protocol definition. 8 | Those files are edited by hand slightly, see the files themselves 9 | """ 10 | -------------------------------------------------------------------------------- /modular_robot_physical/revolve2/modular_robot_physical/robot_daemon_api/_generate_stubs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # To use this tool you have to manually install the capnp-stub-generator package. 4 | # See pyproject.toml. 5 | 6 | cd "$(dirname "$0")" 7 | 8 | capnp-stub-generator -p "./robot_daemon_protocol.capnp" -c "./robot_daemon_protocol.pyi" 9 | -------------------------------------------------------------------------------- /modular_robot_physical/revolve2/modular_robot_physical/robot_daemon_api/robot_daemon_protocol.capnp: -------------------------------------------------------------------------------- 1 | @0xd4d97e2cea03a846; # unique file ID, generated by `capnp id` 2 | 3 | struct SetupArgs { 4 | version @0 :Text; 5 | activePins @1 :List(Int32); 6 | } 7 | 8 | enum HardwareType { 9 | v1 @0; 10 | v2 @1; 11 | } 12 | 13 | struct SetupResponse { 14 | versionOk @0 :Bool; 15 | hardwareType @1 :HardwareType; 16 | } 17 | 18 | struct PinControl { 19 | pin @0 :UInt8; 20 | target @1 :Float32; 21 | } 22 | 23 | struct ControlArgs { 24 | setPins @0 :List(PinControl); 25 | } 26 | 27 | struct ReadSensorsArgs { 28 | readPins @0 :List(Int32); 29 | } 30 | 31 | struct ControlAndReadSensorsArgs { 32 | setPins @0 :List(PinControl); 33 | readPins @1 :List(Int32); 34 | } 35 | 36 | struct Vector3 { 37 | x @0 :Float32; 38 | y @1 :Float32; 39 | z @2 :Float32; 40 | } 41 | 42 | struct Image { 43 | r @0 :List(Int32); 44 | g @1 :List(Int32); 45 | b @2 :List(Int32); 46 | } 47 | 48 | struct SensorReadings { 49 | pins @0 :List(Float32); 50 | battery @1 :Float32; 51 | imuOrientation @2 :Vector3; 52 | imuSpecificForce @3 :Vector3; 53 | imuAngularRate @4 :Vector3; 54 | cameraView @5 :Image; 55 | } 56 | 57 | interface RoboServer { 58 | setup @0 (args :SetupArgs) -> (response :SetupResponse); 59 | control @1 (args :ControlArgs); 60 | readSensors @2 (args :ReadSensorsArgs) -> (response :SensorReadings); 61 | controlAndReadSensors @3 (args :ControlAndReadSensorsArgs) -> (response :SensorReadings); 62 | } 63 | -------------------------------------------------------------------------------- /modular_robot_physical/revolve2/modular_robot_physical/robot_daemon_api/robot_daemon_protocol_capnp.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is an automatically generated stub for `robot_daemon_protocol.capnp`. 3 | 4 | This file was manually edited to contain the RoboServer interface. 5 | """ 6 | 7 | import os 8 | 9 | import capnp # type: ignore 10 | 11 | capnp.remove_import_hook() 12 | here = os.path.dirname(os.path.abspath(__file__)) 13 | module_file = os.path.abspath(os.path.join(here, "robot_daemon_protocol.capnp")) 14 | SetupArgs = capnp.load(module_file).SetupArgs 15 | SetupArgsBuilder = SetupArgs 16 | SetupArgsReader = SetupArgs 17 | SetupResponse = capnp.load(module_file).SetupResponse 18 | SetupResponseBuilder = SetupResponse 19 | SetupResponseReader = SetupResponse 20 | PinControl = capnp.load(module_file).PinControl 21 | PinControlBuilder = PinControl 22 | PinControlReader = PinControl 23 | ControlArgs = capnp.load(module_file).ControlArgs 24 | ControlArgsBuilder = ControlArgs 25 | ControlArgsReader = ControlArgs 26 | ReadSensorsArgs = capnp.load(module_file).ReadSensorsArgs 27 | ReadSensorsArgsBuilder = ReadSensorsArgs 28 | ReadSensorsArgsReader = ReadSensorsArgs 29 | ControlAndReadSensorsArgs = capnp.load(module_file).ControlAndReadSensorsArgs 30 | ControlAndReadSensorsArgsBuilder = ControlAndReadSensorsArgs 31 | ControlAndReadSensorsArgsReader = ControlAndReadSensorsArgs 32 | Vector3 = capnp.load(module_file).Vector3 33 | Vector3Builder = Vector3 34 | Vector3Reader = Vector3 35 | Image = capnp.load(module_file).Image 36 | ImageBuilder = Image 37 | ImageReader = Image 38 | SensorReadings = capnp.load(module_file).SensorReadings 39 | SensorReadingsBuilder = SensorReadings 40 | SensorReadingsReader = SensorReadings 41 | RoboServer = capnp.load(module_file).RoboServer 42 | -------------------------------------------------------------------------------- /modular_robot_physical/revolve2/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ci-group/revolve2/8156a66624f96c43cdfcec05bbb366cc492b87e0/modular_robot_physical/revolve2/py.typed -------------------------------------------------------------------------------- /modular_robot_simulation/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["poetry-core>=1.6.0"] 3 | build-backend = "poetry.core.masonry.api" 4 | 5 | [tool.poetry] 6 | name = "revolve2-modular-robot-simulation" 7 | version = "1.2.4" 8 | description = "Revolve2: Functionality to define scenes with modular robots in a terrain and simulate them." 9 | readme = "../README.md" 10 | authors = [ 11 | "Aart Stuurman ", 12 | "Oliver Weissl " 13 | ] 14 | repository = "https://github.com/ci-group/revolve2" 15 | classifiers = [ 16 | "Development Status :: 5 - Production/Stable", 17 | "Typing :: Typed", 18 | "Topic :: Scientific/Engineering", 19 | "Programming Language :: Python :: 3", 20 | "Programming Language :: Python :: 3.10", 21 | "Programming Language :: Python :: 3.11", 22 | "Operating System :: OS Independent", 23 | "Intended Audience :: Science/Research", 24 | "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)", 25 | ] 26 | packages = [{ include = "revolve2" }] 27 | 28 | [tool.poetry.dependencies] 29 | python = "^3.10,<3.12" 30 | revolve2-modular-robot = "1.2.4" 31 | revolve2-simulation = "1.2.4" 32 | 33 | [tool.poetry.extras] 34 | dev = [] 35 | -------------------------------------------------------------------------------- /modular_robot_simulation/revolve2/modular_robot_simulation/__init__.py: -------------------------------------------------------------------------------- 1 | """Everything for the simulation of modular robots.""" 2 | 3 | from ._modular_robot_scene import ModularRobotScene 4 | from ._modular_robot_simulation_state import ModularRobotSimulationState 5 | from ._scene_simulation_state import SceneSimulationState 6 | from ._simulate_scenes import simulate_scenes 7 | from ._terrain import Terrain 8 | from ._test_robot import test_robot 9 | 10 | __all__ = [ 11 | "ModularRobotScene", 12 | "ModularRobotSimulationState", 13 | "SceneSimulationState", 14 | "Terrain", 15 | "simulate_scenes", 16 | "test_robot", 17 | ] 18 | -------------------------------------------------------------------------------- /modular_robot_simulation/revolve2/modular_robot_simulation/_build_multi_body_systems/__init__.py: -------------------------------------------------------------------------------- 1 | """Builders for simulating modular robots.""" 2 | 3 | from ._body_to_multi_body_system_converter import BodyToMultiBodySystemConverter 4 | from ._body_to_multi_body_system_mapping import BodyToMultiBodySystemMapping 5 | 6 | __all__ = ["BodyToMultiBodySystemConverter", "BodyToMultiBodySystemMapping"] 7 | -------------------------------------------------------------------------------- /modular_robot_simulation/revolve2/modular_robot_simulation/_build_multi_body_systems/_body_to_multi_body_system_mapping.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field 2 | 3 | from revolve2.modular_robot.body.base import ActiveHinge 4 | from revolve2.modular_robot.body.sensors import ( 5 | ActiveHingeSensor, 6 | CameraSensor, 7 | IMUSensor, 8 | ) 9 | from revolve2.simulation.scene import JointHinge, MultiBodySystem, UUIDKey 10 | from revolve2.simulation.scene.sensors import CameraSensor as CameraSim 11 | from revolve2.simulation.scene.sensors import IMUSensor as IMUSim 12 | 13 | 14 | @dataclass(eq=False) 15 | class BodyToMultiBodySystemMapping: 16 | """Mappings from bodies to multi-body system objects.""" 17 | 18 | multi_body_system: MultiBodySystem 19 | active_hinge_to_joint_hinge: dict[UUIDKey[ActiveHinge], JointHinge] = field( 20 | init=False, default_factory=dict 21 | ) 22 | active_hinge_sensor_to_joint_hinge: dict[UUIDKey[ActiveHingeSensor], JointHinge] = ( 23 | field(init=False, default_factory=dict) 24 | ) 25 | imu_to_sim_imu: dict[UUIDKey[IMUSensor], IMUSim] = field( 26 | init=False, default_factory=dict 27 | ) 28 | camera_to_sim_camera: dict[UUIDKey[CameraSensor], CameraSim] = field( 29 | init=False, default_factory=dict 30 | ) 31 | -------------------------------------------------------------------------------- /modular_robot_simulation/revolve2/modular_robot_simulation/_build_multi_body_systems/_builders/__init__.py: -------------------------------------------------------------------------------- 1 | """Builders for specific modules or the modular robots.""" 2 | 3 | from ._active_hinge_builder import ActiveHingeBuilder 4 | from ._active_hinge_sensor_builder import ActiveHingeSensorBuilder 5 | from ._attachment_face_builder import AttachmentFaceBuilder 6 | from ._brick_builder import BrickBuilder 7 | from ._builder import Builder 8 | from ._camera_sensor_builder import CameraSensorBuilder 9 | from ._core_builder import CoreBuilder 10 | from ._imu_sensor_builder import IMUSensorBuilder 11 | 12 | __all__ = [ 13 | "ActiveHingeBuilder", 14 | "ActiveHingeSensorBuilder", 15 | "AttachmentFaceBuilder", 16 | "BrickBuilder", 17 | "Builder", 18 | "CameraSensorBuilder", 19 | "CoreBuilder", 20 | "IMUSensorBuilder", 21 | ] 22 | -------------------------------------------------------------------------------- /modular_robot_simulation/revolve2/modular_robot_simulation/_build_multi_body_systems/_builders/_active_hinge_sensor_builder.py: -------------------------------------------------------------------------------- 1 | from revolve2.modular_robot.body.sensors import ActiveHingeSensor 2 | from revolve2.simulation.scene import JointHinge, MultiBodySystem, RigidBody, UUIDKey 3 | 4 | from .._body_to_multi_body_system_mapping import BodyToMultiBodySystemMapping 5 | from .._unbuilt_child import UnbuiltChild 6 | from ._builder import Builder 7 | 8 | 9 | class ActiveHingeSensorBuilder(Builder): 10 | """A Builder for Cores.""" 11 | 12 | _sensor: ActiveHingeSensor 13 | 14 | def __init__(self, sensor: ActiveHingeSensor, rigid_body: RigidBody) -> None: 15 | """ 16 | Initialize the Active Hinge Sensor Builder. 17 | 18 | :param sensor: The sensor to be built. 19 | :param rigid_body: The rigid body for the module to be built on. 20 | """ 21 | self._sensor = sensor 22 | self._rigid_body = rigid_body 23 | 24 | def build( 25 | self, 26 | multi_body_system: MultiBodySystem, 27 | body_to_multi_body_system_mapping: BodyToMultiBodySystemMapping, 28 | ) -> list[UnbuiltChild]: 29 | """ 30 | Build a module onto the Robot. 31 | 32 | :param multi_body_system: The multi body system of the robot. 33 | :param body_to_multi_body_system_mapping: A mapping from body to multi-body system 34 | :return: The next children to be built. 35 | """ 36 | joint: JointHinge = multi_body_system.get_joints_for_rigid_body(self._rigid_body)[0] # type: ignore 37 | body_to_multi_body_system_mapping.active_hinge_sensor_to_joint_hinge[ 38 | UUIDKey(self._sensor) 39 | ] = joint 40 | return [] 41 | -------------------------------------------------------------------------------- /modular_robot_simulation/revolve2/modular_robot_simulation/_build_multi_body_systems/_builders/_builder.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | from revolve2.simulation.scene import MultiBodySystem 4 | 5 | from .._body_to_multi_body_system_mapping import BodyToMultiBodySystemMapping 6 | from .._unbuilt_child import UnbuiltChild 7 | 8 | 9 | class Builder(ABC): 10 | """An abstract builder class.""" 11 | 12 | @abstractmethod 13 | def build( 14 | self, 15 | multi_body_system: MultiBodySystem, 16 | body_to_multi_body_system_mapping: BodyToMultiBodySystemMapping, 17 | ) -> list[UnbuiltChild]: 18 | """ 19 | Build a module onto the Robot. 20 | 21 | :param multi_body_system: The multi body system of the robot. 22 | :param body_to_multi_body_system_mapping: A mapping from body to multi-body system 23 | :return: The next children to be built. 24 | """ 25 | -------------------------------------------------------------------------------- /modular_robot_simulation/revolve2/modular_robot_simulation/_build_multi_body_systems/_convert_color.py: -------------------------------------------------------------------------------- 1 | from revolve2.modular_robot.body import Color 2 | from revolve2.simulation.scene import Color as SimulationColor 3 | 4 | 5 | def convert_color(color: Color) -> SimulationColor: 6 | """ 7 | Convert ModularRobot Color to Simulator Color. 8 | 9 | :param color: The ModularRobot color. 10 | :return: The Simulator color. 11 | """ 12 | return SimulationColor(color.red, color.green, color.blue, color.alpha) 13 | -------------------------------------------------------------------------------- /modular_robot_simulation/revolve2/modular_robot_simulation/_build_multi_body_systems/_unbuilt_child.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field 2 | 3 | from pyrr import Quaternion, Vector3 4 | 5 | from revolve2.modular_robot.body import Module 6 | from revolve2.modular_robot.body.sensors import Sensor 7 | from revolve2.simulation.scene import Pose, RigidBody 8 | 9 | 10 | @dataclass 11 | class UnbuiltChild: 12 | """A dataclass to store unbuilt children for the builders.""" 13 | 14 | child_object: Module | Sensor 15 | rigid_body: RigidBody 16 | pose: Pose = field(init=False) 17 | 18 | def make_pose( 19 | self, position: Vector3, orientation: Quaternion = Quaternion() 20 | ) -> None: 21 | """ 22 | Make the pose of the unbuilt child. 23 | 24 | :param position: The position argument from the parent. 25 | :param orientation: The orientation of the attachment on the parent. 26 | """ 27 | self.pose = Pose( 28 | position, 29 | orientation * self.child_object.orientation, 30 | ) 31 | -------------------------------------------------------------------------------- /modular_robot_simulation/revolve2/modular_robot_simulation/_convert_terrain.py: -------------------------------------------------------------------------------- 1 | from revolve2.simulation.scene import MultiBodySystem, Pose, RigidBody 2 | 3 | from ._terrain import Terrain 4 | 5 | 6 | def convert_terrain(terrain: Terrain) -> MultiBodySystem: 7 | """ 8 | Convert a terrain to a multi-body system. 9 | 10 | :param terrain: The terrain to convert. 11 | :returns: The created multi-body system. 12 | """ 13 | multi_body_system = MultiBodySystem(pose=Pose(), is_static=True) 14 | rigid_body = RigidBody( 15 | initial_pose=Pose(), 16 | static_friction=1.0, 17 | dynamic_friction=1.0, 18 | geometries=[], 19 | ) # We use these friction values but in the future they should be set through the terrain description. 20 | multi_body_system.add_rigid_body(rigid_body) 21 | 22 | for geometry in terrain.static_geometry: 23 | rigid_body.geometries.append(geometry) 24 | return multi_body_system 25 | -------------------------------------------------------------------------------- /modular_robot_simulation/revolve2/modular_robot_simulation/_sensor_state_impl/__init__.py: -------------------------------------------------------------------------------- 1 | """Sensor state implementations for the simulations.""" 2 | 3 | from ._active_hinge_sensor_state_impl import ActiveHingeSensorStateImpl 4 | from ._camera_sensor_state_impl import CameraSensorStateImpl 5 | from ._imu_sensor_state_impl import IMUSensorStateImpl 6 | from ._modular_robot_sensor_state_impl import ModularRobotSensorStateImpl 7 | 8 | __all__ = [ 9 | "ActiveHingeSensorStateImpl", 10 | "CameraSensorStateImpl", 11 | "IMUSensorStateImpl", 12 | "ModularRobotSensorStateImpl", 13 | ] 14 | -------------------------------------------------------------------------------- /modular_robot_simulation/revolve2/modular_robot_simulation/_sensor_state_impl/_active_hinge_sensor_state_impl.py: -------------------------------------------------------------------------------- 1 | from revolve2.modular_robot.sensor_state import ActiveHingeSensorState 2 | from revolve2.simulation.scene import JointHinge, SimulationState 3 | 4 | 5 | class ActiveHingeSensorStateImpl(ActiveHingeSensorState): 6 | """Implements the active hinge sensor state.""" 7 | 8 | _simulation_state: SimulationState 9 | _hinge_joint: JointHinge 10 | 11 | def __init__( 12 | self, simulation_state: SimulationState, hinge_joint: JointHinge 13 | ) -> None: 14 | """ 15 | Initialize this object. 16 | 17 | :param simulation_state: The state of the simulation. 18 | :param hinge_joint: The hinge joint this state is for. 19 | """ 20 | self._simulation_state = simulation_state 21 | self._hinge_joint = hinge_joint 22 | 23 | @property 24 | def position(self) -> float: 25 | """ 26 | Get the measured position of the active hinge. 27 | 28 | :returns: The measured position. 29 | """ 30 | return self._simulation_state.get_hinge_joint_position(self._hinge_joint) 31 | -------------------------------------------------------------------------------- /modular_robot_simulation/revolve2/modular_robot_simulation/_sensor_state_impl/_camera_sensor_state_impl.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from numpy.typing import NDArray 3 | 4 | from revolve2.modular_robot.sensor_state import CameraSensorState 5 | from revolve2.simulation.scene import MultiBodySystem, SimulationState 6 | from revolve2.simulation.scene.sensors import CameraSensor 7 | 8 | 9 | class CameraSensorStateImpl(CameraSensorState): 10 | """The simulation implementation of the camera sensor state.""" 11 | 12 | _simulation_state: SimulationState 13 | _multi_body_system: MultiBodySystem 14 | _camera: CameraSensor 15 | 16 | def __init__( 17 | self, 18 | simulation_state: SimulationState, 19 | multi_body_system: MultiBodySystem, 20 | camera: CameraSensor, 21 | ) -> None: 22 | """ 23 | Initialize this object. 24 | 25 | :param simulation_state: The state of the simulation. 26 | :param multi_body_system: The multi body system this imu is attached to. 27 | :param camera: The camera sensor. 28 | """ 29 | self._simulation_state = simulation_state 30 | self._multi_body_system = multi_body_system 31 | self._camera = camera 32 | 33 | @property 34 | def image(self) -> NDArray[np.uint8]: 35 | """ 36 | Get the current image. 37 | 38 | :returns: The image. 39 | """ 40 | return self._simulation_state.get_camera_view(self._camera) 41 | -------------------------------------------------------------------------------- /modular_robot_simulation/revolve2/modular_robot_simulation/_terrain.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from revolve2.simulation.scene.geometry import Geometry 4 | 5 | 6 | @dataclass 7 | class Terrain: 8 | """Terrain consising of only static geometry.""" 9 | 10 | static_geometry: list[Geometry] 11 | """The static geometry that defines the terrain.""" 12 | -------------------------------------------------------------------------------- /modular_robot_simulation/revolve2/modular_robot_simulation/_test_robot.py: -------------------------------------------------------------------------------- 1 | from revolve2.modular_robot import ModularRobot 2 | from revolve2.modular_robot.body.base import Body 3 | from revolve2.modular_robot.brain.dummy import BrainDummy 4 | from revolve2.simulation.simulator import BatchParameters, Simulator 5 | 6 | from ._modular_robot_scene import ModularRobotScene 7 | from ._simulate_scenes import simulate_scenes 8 | from ._terrain import Terrain 9 | 10 | 11 | def test_robot( 12 | robot: ModularRobot | Body, 13 | terrain: Terrain, 14 | simulator: Simulator, 15 | batch_parameters: BatchParameters, 16 | ) -> None: 17 | """ 18 | Test a robot with a manual brain. 19 | 20 | :param robot: The ModularRobot or Body instance. 21 | :param terrain: The terrain to test on. 22 | :param simulator: The simulator. 23 | :param batch_parameters: The batch parameters. 24 | """ 25 | if isinstance(robot, Body): 26 | body = robot 27 | brain = BrainDummy() 28 | robot = ModularRobot(body=body, brain=brain) 29 | 30 | scene = ModularRobotScene(terrain=terrain) 31 | scene.add_robot(robot) 32 | 33 | simulate_scenes( 34 | simulator=simulator, batch_parameters=batch_parameters, scenes=scene 35 | ) 36 | -------------------------------------------------------------------------------- /modular_robot_simulation/revolve2/modular_robot_simulation/_to_batch.py: -------------------------------------------------------------------------------- 1 | from revolve2.modular_robot import ModularRobot 2 | from revolve2.simulation.scene import MultiBodySystem, UUIDKey 3 | from revolve2.simulation.simulator import Batch, BatchParameters, RecordSettings 4 | 5 | from ._modular_robot_scene import ModularRobotScene 6 | 7 | 8 | def to_batch( 9 | scenes: ModularRobotScene | list[ModularRobotScene], 10 | batch_parameters: BatchParameters, 11 | record_settings: RecordSettings | None = None, 12 | ) -> tuple[Batch, list[dict[UUIDKey[ModularRobot], MultiBodySystem]]]: 13 | """ 14 | Convert one or more modular robot scenes to a batch of simulation scenes. 15 | 16 | :param scenes: The modular robot scene(s) to make the batch from. 17 | :param batch_parameters: Parameters for the batch that are not contained in the modular robot scenes. 18 | :param record_settings: Setting for recording the simulations. 19 | :returns: The created batch and a mapping from modular robots to multi-body systems for each scene. 20 | """ 21 | if isinstance(scenes, ModularRobotScene): 22 | scenes = [scenes] 23 | 24 | converted = [ 25 | modular_robot_scene.to_simulation_scene() for modular_robot_scene in scenes 26 | ] 27 | 28 | batch = Batch(parameters=batch_parameters, record_settings=record_settings) 29 | batch.scenes.extend(simulation_scene for simulation_scene, _ in converted) 30 | 31 | return batch, [mapping for _, mapping in converted] 32 | -------------------------------------------------------------------------------- /modular_robot_simulation/revolve2/modular_robot_simulation/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ci-group/revolve2/8156a66624f96c43cdfcec05bbb366cc492b87e0/modular_robot_simulation/revolve2/modular_robot_simulation/py.typed -------------------------------------------------------------------------------- /project.yml: -------------------------------------------------------------------------------- 1 | # This is custom config file that defines some project wide things such as the names of packages. 2 | # Primarily used by the CI. 3 | platform_independent_packages: 4 | - experimentation 5 | - modular_robot 6 | - modular_robot_physical 7 | - modular_robot_simulation 8 | - simulation 9 | - simulators/mujoco_simulator 10 | platform_dependent_packages: 11 | - standards 12 | examples: 13 | - 1_simulator_basics/1a_simulate_single_robot 14 | - 1_simulator_basics/1b_custom_terrain 15 | - 2_modular_robot_basics/2a_custom_brain 16 | - 2_modular_robot_basics/2b_brain_with_feedback 17 | - 2_modular_robot_basics/2c_custom_parts 18 | - 3_experiment_foundations/3a_experiment_setup 19 | - 3_experiment_foundations/3b_evaluate_single_robot 20 | - 3_experiment_foundations/3c_evaluate_multiple_isolated_robots 21 | - 3_experiment_foundations/3d_evaluate_multiple_interacting_robots 22 | - 4_example_experiment_setups/4a_simple_ea_xor 23 | - 4_example_experiment_setups/4b_simple_ea_xor_database 24 | - 4_example_experiment_setups/4c_robot_bodybrain_ea 25 | - 4_example_experiment_setups/4d_robot_bodybrain_ea_database 26 | - 4_example_experiment_setups/4e_robot_brain_cmaes 27 | - 4_example_experiment_setups/4f_robot_brain_cmaes_database 28 | - 4_example_experiment_setups/4g_explore_initial_population 29 | - 5_physical_modular_robots/5a_physical_robot_remote 30 | - 5_physical_modular_robots/5b_compare_simulated_and_physical_robot 31 | tests-dir: tests 32 | examples-dir: examples 33 | revolve2-namespace: revolve2 34 | -------------------------------------------------------------------------------- /requirements_dev.txt: -------------------------------------------------------------------------------- 1 | # Installs all Revolve2 packages in editable mode as well as development tools and their requirements. 2 | 3 | -e standards[dev] 4 | -e simulators/mujoco_simulator[dev] 5 | -e experimentation[dev] 6 | -e modular_robot_simulation[dev] 7 | -e modular_robot_physical[dev] 8 | -e modular_robot[dev] 9 | -e simulation[dev] 10 | -r ./codetools/requirements.txt 11 | -r ./docs/requirements.txt 12 | -r ./examples/4_example_experiment_setups/4d_robot_bodybrain_ea_database/requirements.txt 13 | -r ./examples/4_example_experiment_setups/4f_robot_brain_cmaes_database/requirements.txt 14 | -r ./examples/4_example_experiment_setups/4b_simple_ea_xor_database/requirements.txt 15 | -r ./examples/4_example_experiment_setups/4g_explore_initial_population/requirements.txt 16 | -r ./tests/requirements.txt 17 | -------------------------------------------------------------------------------- /requirements_editable.txt: -------------------------------------------------------------------------------- 1 | # Installs all Revolve2 packages in editable mode as well as all example requirements. 2 | 3 | -e standards[dev] 4 | -e simulators/mujoco_simulator[dev] 5 | -e experimentation[dev] 6 | -e modular_robot_simulation[dev] 7 | -e modular_robot_physical[remote] 8 | -e modular_robot[dev] 9 | -e simulation[dev] 10 | -r ./examples/4_example_experiment_setups/4d_robot_bodybrain_ea_database/requirements.txt 11 | -r ./examples/4_example_experiment_setups/4f_robot_brain_cmaes_database/requirements.txt 12 | -r ./examples/4_example_experiment_setups/4b_simple_ea_xor_database/requirements.txt 13 | -r ./examples/4_example_experiment_setups/4g_explore_initial_population/requirements.txt 14 | 15 | -------------------------------------------------------------------------------- /simulation/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["poetry-core>=1.6.0"] 3 | build-backend = "poetry.core.masonry.api" 4 | 5 | [tool.poetry] 6 | name = "revolve2-simulation" 7 | version = "1.2.4" 8 | description = "Revolve2: Physics simulation abstraction layer." 9 | readme = "../README.md" 10 | authors = [ 11 | "Aart Stuurman ", 12 | "Oliver Weissl ", 13 | "Kevin Godin-Dubois ", 14 | ] 15 | repository = "https://github.com/ci-group/revolve2" 16 | classifiers = [ 17 | "Development Status :: 5 - Production/Stable", 18 | "Typing :: Typed", 19 | "Topic :: Scientific/Engineering", 20 | "Programming Language :: Python :: 3", 21 | "Programming Language :: Python :: 3.10", 22 | "Programming Language :: Python :: 3.11", 23 | "Operating System :: OS Independent", 24 | "Intended Audience :: Science/Research", 25 | "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)", 26 | ] 27 | packages = [{ include = "revolve2" }] 28 | 29 | [tool.poetry.dependencies] 30 | python = "^3.10,<3.12" 31 | pyrr = "^0.10.3" 32 | scipy = "^1.7.1" 33 | 34 | [tool.poetry.extras] 35 | dev = [] 36 | -------------------------------------------------------------------------------- /simulation/revolve2/simulation/__init__.py: -------------------------------------------------------------------------------- 1 | """Abstraction layer for physics simulators.""" 2 | -------------------------------------------------------------------------------- /simulation/revolve2/simulation/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ci-group/revolve2/8156a66624f96c43cdfcec05bbb366cc492b87e0/simulation/revolve2/simulation/py.typed -------------------------------------------------------------------------------- /simulation/revolve2/simulation/scene/__init__.py: -------------------------------------------------------------------------------- 1 | """Everything to describe scenes to be simulated.""" 2 | 3 | from ._aabb import AABB 4 | from ._color import Color 5 | from ._control_interface import ControlInterface 6 | from ._joint import Joint 7 | from ._joint_fixed import JointFixed 8 | from ._joint_hinge import JointHinge 9 | from ._multi_body_system import MultiBodySystem 10 | from ._pose import Pose 11 | from ._rigid_body import RigidBody 12 | from ._scene import Scene 13 | from ._simulation_handler import SimulationHandler 14 | from ._simulation_state import SimulationState 15 | from ._uuid_key import UUIDKey 16 | 17 | __all__ = [ 18 | "AABB", 19 | "Color", 20 | "ControlInterface", 21 | "Joint", 22 | "JointFixed", 23 | "JointHinge", 24 | "MultiBodySystem", 25 | "Pose", 26 | "RigidBody", 27 | "Scene", 28 | "SimulationHandler", 29 | "SimulationState", 30 | "UUIDKey", 31 | ] 32 | -------------------------------------------------------------------------------- /simulation/revolve2/simulation/scene/_aabb.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from pyrr import Vector3 4 | 5 | 6 | @dataclass 7 | class AABB: 8 | """An axis aligned bounding box.""" 9 | 10 | size: Vector3 11 | """ 12 | Sizes of the length of the bounding box. 13 | 14 | Not half of the box. 15 | """ 16 | -------------------------------------------------------------------------------- /simulation/revolve2/simulation/scene/_color.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | @dataclass 5 | class Color: 6 | """ 7 | Represents a color in RGBA format. 8 | 9 | All values should from 0 to 255. 10 | """ 11 | 12 | red: int 13 | green: int 14 | blue: int 15 | alpha: int 16 | 17 | def to_normalized_rgba_list(self) -> list[float]: 18 | """ 19 | Convert to rgba list where each value is between 0 and 1. 20 | 21 | :returns: The list. 22 | """ 23 | return [self.red / 255, self.green / 255, self.blue / 255, self.alpha / 255] 24 | 25 | def to_normalized_rgb_list(self) -> list[float]: 26 | """ 27 | Convert to rgb list where each value is between 0 and 1. 28 | 29 | :returns: The list. 30 | """ 31 | return [self.red / 255, self.green / 255, self.blue / 255] 32 | -------------------------------------------------------------------------------- /simulation/revolve2/simulation/scene/_control_interface.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | from ._joint_hinge import JointHinge 4 | 5 | 6 | class ControlInterface(ABC): 7 | """Interface for controlling a scene during simulation.""" 8 | 9 | @abstractmethod 10 | def set_joint_hinge_position_target( 11 | self, joint_hinge: JointHinge, position: float 12 | ) -> None: 13 | """ 14 | Set the position target of a hinge joint. 15 | 16 | :param joint_hinge: The hinge to set the position target for. 17 | :param position: The position target. 18 | """ 19 | pass 20 | -------------------------------------------------------------------------------- /simulation/revolve2/simulation/scene/_joint.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | from dataclasses import dataclass, field 3 | 4 | from ._pose import Pose 5 | from ._rigid_body import RigidBody 6 | 7 | 8 | @dataclass(kw_only=True) 9 | class Joint: 10 | """Base class for all joints.""" 11 | 12 | _uuid: uuid.UUID = field(init=False, default_factory=uuid.uuid1) 13 | 14 | @property 15 | def uuid(self) -> uuid.UUID: 16 | """ 17 | Get the uuid. 18 | 19 | :returns: The uuid. 20 | """ 21 | return self._uuid 22 | 23 | pose: Pose 24 | """Pose of the joint.""" 25 | 26 | rigid_body1: RigidBody 27 | """The first attached rigid body.""" 28 | 29 | rigid_body2: RigidBody 30 | """The second attached rigid body.""" 31 | -------------------------------------------------------------------------------- /simulation/revolve2/simulation/scene/_joint_fixed.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from ._joint import Joint 4 | 5 | 6 | @dataclass 7 | class JointFixed(Joint): 8 | """ 9 | A joint fixing the rigid bodies together rigidly. 10 | 11 | This makes them effectively a single rigid body. 12 | """ 13 | -------------------------------------------------------------------------------- /simulation/revolve2/simulation/scene/_joint_hinge.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from pyrr import Vector3 4 | 5 | from ._joint import Joint 6 | 7 | 8 | @dataclass 9 | class JointHinge(Joint): 10 | """ 11 | A hinge joint, also known as revolute joint. 12 | 13 | Rotates around a single axis. 14 | """ 15 | 16 | axis: Vector3 17 | """Directional vector the joint rotates about.""" 18 | 19 | range: float 20 | """ 21 | Rotation range of the joint in radians. 22 | 23 | How much it can rotate to each side, in radians. 24 | So double this is the complete range of motion. 25 | """ 26 | 27 | effort: float 28 | """Maximum effort the joint can exert, in newton-meter.""" 29 | 30 | velocity: float 31 | """Maximum velocity of the joint, in radian per second.""" 32 | 33 | armature: float 34 | """ 35 | Armature of the joint. 36 | 37 | This represents the inertia of the motor itself when nothing is attached. 38 | """ 39 | 40 | pid_gain_p: float 41 | """Proportional gain of the pid position controller.""" 42 | 43 | pid_gain_d: float 44 | """Derivative gain of the pid position controller.""" 45 | -------------------------------------------------------------------------------- /simulation/revolve2/simulation/scene/_pose.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field 2 | 3 | from pyrr import Quaternion, Vector3 4 | 5 | 6 | @dataclass 7 | class Pose: 8 | """A position and orientation.""" 9 | 10 | position: Vector3 = field(default_factory=Vector3) 11 | """Position of the object.""" 12 | 13 | orientation: Quaternion = field(default_factory=Quaternion) 14 | """Orientation of the object.""" 15 | -------------------------------------------------------------------------------- /simulation/revolve2/simulation/scene/_scene.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field 2 | 3 | from ._multi_body_system import MultiBodySystem 4 | from ._simulation_handler import SimulationHandler 5 | 6 | 7 | @dataclass(kw_only=True) 8 | class Scene: 9 | """Description of a scene that can be simulated.""" 10 | 11 | handler: SimulationHandler 12 | _multi_body_systems: list[MultiBodySystem] = field(default_factory=list, init=False) 13 | """ 14 | Multi-body system in this scene. 15 | 16 | Don't add to this directly, but use `add_multi_body_system` instead. 17 | """ 18 | 19 | def add_multi_body_system(self, multi_body_system: MultiBodySystem) -> None: 20 | """ 21 | Add a multi-body system to the scene. 22 | 23 | :param multi_body_system: The multi-body system to add. 24 | """ 25 | self._multi_body_systems.append(multi_body_system) 26 | 27 | @property 28 | def multi_body_systems(self) -> list[MultiBodySystem]: 29 | """ 30 | Get the multi-body systems in scene. 31 | 32 | Do not make changes to this list. 33 | 34 | :returns: The multi-body systems in the scene. 35 | """ 36 | return self._multi_body_systems[:] 37 | -------------------------------------------------------------------------------- /simulation/revolve2/simulation/scene/_simulation_handler.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | from ._control_interface import ControlInterface 4 | from ._simulation_state import SimulationState 5 | 6 | 7 | class SimulationHandler(ABC): 8 | """Base class for handling a simulation, which includes, for example, controlling robots.""" 9 | 10 | @abstractmethod 11 | def handle( 12 | self, state: SimulationState, control: ControlInterface, dt: float 13 | ) -> None: 14 | """ 15 | Handle a simulation frame. 16 | 17 | :param state: The current state of the simulation. 18 | :param control: Interface for setting control targets. 19 | :param dt: The time since the last call to this function. 20 | """ 21 | pass 22 | -------------------------------------------------------------------------------- /simulation/revolve2/simulation/scene/conversion/__init__.py: -------------------------------------------------------------------------------- 1 | """Conversion from scene related things to other formats.""" 2 | 3 | from ._multi_body_system_to_urdf import multi_body_system_to_urdf 4 | 5 | __all__ = ["multi_body_system_to_urdf"] 6 | -------------------------------------------------------------------------------- /simulation/revolve2/simulation/scene/geometry/__init__.py: -------------------------------------------------------------------------------- 1 | """Interface and implementation of geometries.""" 2 | 3 | from ._geometry import Geometry 4 | from ._geometry_box import GeometryBox 5 | from ._geometry_heightmap import GeometryHeightmap 6 | from ._geometry_plane import GeometryPlane 7 | from ._geometry_sphere import GeometrySphere 8 | 9 | __all__ = [ 10 | "Geometry", 11 | "GeometryBox", 12 | "GeometryHeightmap", 13 | "GeometryPlane", 14 | "GeometrySphere", 15 | ] 16 | -------------------------------------------------------------------------------- /simulation/revolve2/simulation/scene/geometry/_geometry.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from .._pose import Pose 4 | from .textures import Texture 5 | 6 | 7 | @dataclass(kw_only=True) 8 | class Geometry: 9 | """Geometry describing part of a rigid body shape.""" 10 | 11 | pose: Pose 12 | """Pose of the geometry.""" 13 | 14 | mass: float 15 | """ 16 | Mass of the geometry. 17 | 18 | This the absolute mass, irrespective of the size of the bounding box. 19 | """ 20 | 21 | texture: Texture 22 | """Texture when rendering this geometry.""" 23 | -------------------------------------------------------------------------------- /simulation/revolve2/simulation/scene/geometry/_geometry_box.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from .._aabb import AABB 4 | from ._geometry import Geometry 5 | 6 | 7 | @dataclass(kw_only=True) 8 | class GeometryBox(Geometry): 9 | """Box geometry.""" 10 | 11 | aabb: AABB 12 | """AABB describing the box's bounding box.""" 13 | -------------------------------------------------------------------------------- /simulation/revolve2/simulation/scene/geometry/_geometry_heightmap.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field 2 | 3 | import numpy as np 4 | import numpy.typing as npt 5 | from pyrr import Vector3 6 | 7 | from .._color import Color 8 | from ._geometry import Geometry 9 | from .textures import MapType, Texture 10 | 11 | 12 | @dataclass(kw_only=True) 13 | class GeometryHeightmap(Geometry): 14 | """ 15 | A heightmap geometry. 16 | 17 | Similarly to the `Plane` geometry, x and y of `size` define the space the heighmap encompasses. 18 | The z-coordinate defines the height of a heightmap edge when it's value is maximum. 19 | `heights` defines the edge of the heighmap. Values much lie between 0.0 and 1.0, inclusive. 20 | `base_thickness` defines the thickness of the box below the heighmap, which is requires for proper collision detection in some simulators. 21 | """ 22 | 23 | size: Vector3 24 | base_thickness: float 25 | heights: npt.NDArray[np.float_] # MxN matrix. outer list is x, inner list is y 26 | texture: Texture = field( 27 | default_factory=lambda: Texture( 28 | base_color=Color(100, 100, 100, 255), map_type=MapType.MAP2D 29 | ) 30 | ) 31 | -------------------------------------------------------------------------------- /simulation/revolve2/simulation/scene/geometry/_geometry_plane.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field 2 | 3 | from .._color import Color 4 | from ..vector2 import Vector2 5 | from ._geometry import Geometry 6 | from .textures import MapType, Texture 7 | 8 | 9 | @dataclass(kw_only=True) 10 | class GeometryPlane(Geometry): 11 | """A flat plane geometry.""" 12 | 13 | size: Vector2 14 | texture: Texture = field( 15 | default_factory=lambda: Texture( 16 | base_color=Color(100, 100, 100, 255), 17 | map_type=MapType.MAP2D, 18 | ) 19 | ) 20 | -------------------------------------------------------------------------------- /simulation/revolve2/simulation/scene/geometry/_geometry_sphere.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from ._geometry import Geometry 4 | 5 | 6 | @dataclass(kw_only=True) 7 | class GeometrySphere(Geometry): 8 | """Box geometry.""" 9 | 10 | radius: float 11 | """The radius of the sphere.""" 12 | -------------------------------------------------------------------------------- /simulation/revolve2/simulation/scene/geometry/textures/__init__.py: -------------------------------------------------------------------------------- 1 | """Standard resources for textures in simulation.""" 2 | 3 | from ._map_type import MapType 4 | from ._texture import Texture 5 | from ._texture_reference import TextureReference 6 | 7 | __all__ = ["MapType", "Texture", "TextureReference"] 8 | -------------------------------------------------------------------------------- /simulation/revolve2/simulation/scene/geometry/textures/_map_type.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class MapType(Enum): 5 | """ 6 | Enumerate different map types for textures. 7 | 8 | - "MAP2D": Maps the texture to a 2d surface. 9 | - "CUBE": Wraps the texture around a cube object. 10 | - "SKYBOX": Like "cube" but maps onto the inside of an object. 11 | """ 12 | 13 | MAP2D = "2d" 14 | CUBE = "cube" 15 | SKYBOX = "skybox" 16 | -------------------------------------------------------------------------------- /simulation/revolve2/simulation/scene/geometry/textures/_texture_reference.py: -------------------------------------------------------------------------------- 1 | from abc import ABC 2 | from dataclasses import dataclass, field 3 | 4 | 5 | @dataclass(frozen=True, kw_only=True) 6 | class TextureReference(ABC): 7 | """Reference texture information for the simulators.""" 8 | 9 | builtin: str | None = field(default=None) 10 | """Allows to reference builtin textures of the targeted simulator.""" 11 | file: str | None = field(default=None) 12 | """The source file of the specified texture, if custom.""" 13 | content_type: str | None = field(default=None) 14 | """The type of the source file. The supported types depend on the simulator used. Please check the simulators documentation for supported filetypes.""" 15 | gridlayout: str | None = field(default=None) 16 | """Specify the layout of the texture to be mapped onto an object. This depends on the simulator used. Please check the simulators documentation for supported grid layouts.""" 17 | 18 | def __post_init__(self) -> None: 19 | """ 20 | Check for potential conflicts due to missing arguments. 21 | 22 | :raises NotImplementedError: If not sufficient arguments are given. 23 | """ 24 | if self.builtin is None and self.file is None: 25 | raise NotImplementedError( 26 | "No texture reference given in form of a file or builtin texture." 27 | ) 28 | if self.file is not None and self.content_type is None: 29 | raise NotImplementedError( 30 | "Please indicate the content type of your texture source file." 31 | ) 32 | -------------------------------------------------------------------------------- /simulation/revolve2/simulation/scene/sensors/__init__.py: -------------------------------------------------------------------------------- 1 | """Sensor classes for the simulators.""" 2 | 3 | from ._camera_sensor import CameraSensor 4 | from ._imu_sensor import IMUSensor 5 | from ._sensor import Sensor 6 | 7 | __all__ = ["CameraSensor", "IMUSensor", "Sensor"] 8 | -------------------------------------------------------------------------------- /simulation/revolve2/simulation/scene/sensors/_camera_sensor.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field 2 | 3 | from .._pose import Pose 4 | from ._sensor import Sensor 5 | 6 | 7 | @dataclass 8 | class CameraSensor(Sensor): 9 | """Camera sensor.""" 10 | 11 | pose: Pose 12 | camera_size: tuple[int, int] 13 | """Pose of the geometry, relative to its parent rigid body.""" 14 | type: str = field( 15 | default="camera" 16 | ) # The type attribute is used for the translation into XML formats. 17 | -------------------------------------------------------------------------------- /simulation/revolve2/simulation/scene/sensors/_imu_sensor.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field 2 | 3 | from .._pose import Pose 4 | from ._sensor import Sensor 5 | 6 | 7 | @dataclass 8 | class IMUSensor(Sensor): 9 | """ 10 | An inertial measurement unit. 11 | 12 | Reports specific force(closely related to acceleration), angular rate(closely related to angularvelocity), and orientation. 13 | """ 14 | 15 | pose: Pose 16 | type: str = field( 17 | default="imu" 18 | ) # The type attribute is used for the translation into XML formats. 19 | -------------------------------------------------------------------------------- /simulation/revolve2/simulation/scene/sensors/_sensor.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | from abc import ABC 3 | from dataclasses import dataclass, field 4 | 5 | 6 | @dataclass 7 | class Sensor(ABC): 8 | """ 9 | An inertial measurement unit. 10 | 11 | Reports specific force(closely related to acceleration), angular rate(closely related to angularvelocity), and orientation. 12 | """ 13 | 14 | _uuid: uuid.UUID = field(init=False, default_factory=uuid.uuid1) 15 | 16 | @property 17 | def uuid(self) -> uuid.UUID: 18 | """ 19 | Get the uuid. 20 | 21 | :returns: The uuid. 22 | """ 23 | return self._uuid 24 | -------------------------------------------------------------------------------- /simulation/revolve2/simulation/scene/vector2/__init__.py: -------------------------------------------------------------------------------- 1 | """A custom Vector2 Class for revolve2.""" 2 | 3 | from .vector2 import Vector2 4 | from .vector2aux import unit 5 | 6 | __all__ = ["Vector2", "unit"] 7 | -------------------------------------------------------------------------------- /simulation/revolve2/simulation/simulator/__init__.py: -------------------------------------------------------------------------------- 1 | """Interface for simulators and everything to tell them what to do.""" 2 | 3 | from ._batch import Batch 4 | from ._batch_parameters import BatchParameters 5 | from ._record_settings import RecordSettings 6 | from ._simulator import Simulator 7 | from ._viewer import Viewer 8 | 9 | __all__ = ["Batch", "BatchParameters", "RecordSettings", "Simulator", "Viewer"] 10 | -------------------------------------------------------------------------------- /simulation/revolve2/simulation/simulator/_batch.py: -------------------------------------------------------------------------------- 1 | """Batch class.""" 2 | 3 | from dataclasses import dataclass, field 4 | 5 | from ..scene import Scene 6 | from ._batch_parameters import BatchParameters 7 | from ._record_settings import RecordSettings 8 | 9 | 10 | @dataclass 11 | class Batch: 12 | """A set of scenes and shared parameters for simulation.""" 13 | 14 | parameters: BatchParameters 15 | 16 | scenes: list[Scene] = field(default_factory=list, init=False) 17 | """The scenes to simulate.""" 18 | 19 | record_settings: RecordSettings | None = None 20 | -------------------------------------------------------------------------------- /simulation/revolve2/simulation/simulator/_batch_parameters.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | @dataclass(kw_only=True) 5 | class BatchParameters: 6 | """Parameters for a simulation batch.""" 7 | 8 | simulation_time: int | None 9 | """ 10 | Seconds. The duration for which each robot should be simulated. 11 | If set to 'None', the simulation will run indefinitely. 12 | """ 13 | 14 | sampling_frequency: float | None 15 | """ 16 | Hz. Frequency for state sampling during the simulation. 17 | The simulator will attempt to follow this as closely as possible, 18 | but is dependent on the actual step frequency of the simulator. 19 | If set to 'None', no sampling will be performed. 20 | """ 21 | 22 | simulation_timestep: float 23 | """ 24 | Simulation time step in seconds. This is an important parameter that affects the trade-off between speed and accuracy, 25 | as well as the stability of the robot model. 26 | Smaller values of this parameter generally result in better accuracy and stability. 27 | However, the specific value of this parameter may vary depending on the scenes being simulated and other batch parameters. 28 | """ 29 | 30 | control_frequency: float 31 | """Similar to `sampling_frequency` but for how often the control function is called.""" 32 | -------------------------------------------------------------------------------- /simulation/revolve2/simulation/simulator/_record_settings.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | @dataclass 5 | class RecordSettings: 6 | """Settings for recording a simulation.""" 7 | 8 | video_directory: str 9 | overwrite: bool = False 10 | 11 | fps: int = 24 12 | 13 | width: int | None = None 14 | height: int | None = None 15 | -------------------------------------------------------------------------------- /simulation/revolve2/simulation/simulator/_simulator.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | from ..scene import SimulationState 4 | from ._batch import Batch 5 | 6 | 7 | class Simulator(ABC): 8 | """Interface for a simulator.""" 9 | 10 | @abstractmethod 11 | def simulate_batch(self, batch: Batch) -> list[list[SimulationState]]: 12 | """ 13 | Simulate the provided batch by simulating each contained scene. 14 | 15 | :param batch: The batch to run. 16 | :returns: List of simulation states in ascending order of time. 17 | """ 18 | pass 19 | -------------------------------------------------------------------------------- /simulation/revolve2/simulation/simulator/_viewer.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import Any 3 | 4 | 5 | class Viewer(ABC): 6 | """An abstract viewer class, enabling the rendering of simulations.""" 7 | 8 | @abstractmethod 9 | def close_viewer(self) -> None: 10 | """Close the viewer.""" 11 | 12 | @abstractmethod 13 | def render(self) -> Any | None: 14 | """ 15 | Render the scene on the viewer. 16 | 17 | :returns: Nothing or feedback. 18 | """ 19 | 20 | @abstractmethod 21 | def current_viewport_size(self) -> tuple[int, int]: 22 | """ 23 | Get the current viewport size. 24 | 25 | :returns: The size as a tuple. 26 | """ 27 | 28 | @property 29 | @abstractmethod 30 | def view_port(self) -> Any: 31 | """ 32 | Get the viewport. 33 | 34 | :returns: The viewport object. 35 | """ 36 | 37 | @property 38 | @abstractmethod 39 | def context(self) -> Any: 40 | """ 41 | Return the viewer context. 42 | 43 | :returns: The context object. 44 | """ 45 | 46 | @property 47 | @abstractmethod 48 | def can_record(self) -> bool: 49 | """ 50 | Check if this viewer can record. 51 | 52 | :returns: The truth. 53 | """ 54 | -------------------------------------------------------------------------------- /simulators/mujoco_simulator/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["poetry-core>=1.6.0"] 3 | build-backend = "poetry.core.masonry.api" 4 | 5 | [tool.poetry] 6 | name = "revolve2-mujoco-simulator" 7 | version = "1.2.4" 8 | description = "Revolve2: MuJoCo simulator." 9 | readme = "../../README.md" 10 | authors = [ 11 | "Aart Stuurman ", 12 | "Oliver Weissl ", 13 | "Kevin Godin-Dubois ", 14 | "Andres Garcia " 15 | ] 16 | repository = "https://github.com/ci-group/revolve2" 17 | classifiers = [ 18 | "Development Status :: 5 - Production/Stable", 19 | "Typing :: Typed", 20 | "Topic :: Scientific/Engineering", 21 | "Programming Language :: Python :: 3", 22 | "Programming Language :: Python :: 3.10", 23 | "Programming Language :: Python :: 3.11", 24 | "Operating System :: OS Independent", 25 | "Intended Audience :: Science/Research", 26 | "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)", 27 | ] 28 | packages = [{ include = "revolve2" }] 29 | 30 | [tool.poetry.dependencies] 31 | python = "^3.10,<3.12" 32 | revolve2-simulation = "1.2.4" 33 | mujoco-python-viewer = "^0.1.3" 34 | mujoco = "^2.2.0" 35 | dm-control = "^1.0.3" 36 | opencv-python = "^4.6.0.66" 37 | 38 | [tool.poetry.extras] 39 | dev = [] 40 | -------------------------------------------------------------------------------- /simulators/mujoco_simulator/revolve2/simulators/mujoco_simulator/__init__.py: -------------------------------------------------------------------------------- 1 | """Physics simulator using the MuJoCo.""" 2 | 3 | from ._local_simulator import LocalSimulator 4 | 5 | __all__ = ["LocalSimulator"] 6 | -------------------------------------------------------------------------------- /simulators/mujoco_simulator/revolve2/simulators/mujoco_simulator/_abstraction_to_mujoco_mapping.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field 2 | 3 | from revolve2.simulation.scene import JointHinge, MultiBodySystem, UUIDKey 4 | from revolve2.simulation.scene.sensors import CameraSensor, IMUSensor 5 | 6 | 7 | @dataclass 8 | class JointHingeMujoco: 9 | """Information about a MuJoCo hinge joint.""" 10 | 11 | id: int 12 | ctrl_index_position: int 13 | ctrl_index_velocity: int 14 | 15 | 16 | @dataclass 17 | class MultiBodySystemMujoco: 18 | """Information about a MuJoCo body.""" 19 | 20 | id: int 21 | 22 | 23 | @dataclass 24 | class IMUSensorMujoco: 25 | """Information about a MuJoCo IMU sensor.""" 26 | 27 | gyro_id: int 28 | accelerometer_id: int 29 | 30 | 31 | @dataclass 32 | class CameraSensorMujoco: 33 | """Information about a MuJoCo camera sensor.""" 34 | 35 | camera_id: int 36 | camera_size: tuple[int, int] 37 | 38 | 39 | @dataclass(eq=False) 40 | class AbstractionToMujocoMapping: 41 | """Data to interpret a MuJoCo model using the simulation abstraction.""" 42 | 43 | hinge_joint: dict[UUIDKey[JointHinge], JointHingeMujoco] = field( 44 | init=False, default_factory=dict 45 | ) 46 | 47 | multi_body_system: dict[UUIDKey[MultiBodySystem], MultiBodySystemMujoco] = field( 48 | init=False, default_factory=dict 49 | ) 50 | 51 | imu_sensor: dict[UUIDKey[IMUSensor], IMUSensorMujoco] = field( 52 | init=False, default_factory=dict 53 | ) 54 | camera_sensor: dict[UUIDKey[CameraSensor], CameraSensorMujoco] = field( 55 | init=False, default_factory=dict 56 | ) 57 | -------------------------------------------------------------------------------- /simulators/mujoco_simulator/revolve2/simulators/mujoco_simulator/_render_backend.py: -------------------------------------------------------------------------------- 1 | from enum import Enum, auto 2 | 3 | 4 | class RenderBackend(Enum): 5 | """Enumerator for rendering backend libraries.""" 6 | 7 | GLFW = auto() 8 | EGL = auto() 9 | OSMESA = auto() 10 | -------------------------------------------------------------------------------- /simulators/mujoco_simulator/revolve2/simulators/mujoco_simulator/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ci-group/revolve2/8156a66624f96c43cdfcec05bbb366cc492b87e0/simulators/mujoco_simulator/revolve2/simulators/mujoco_simulator/py.typed -------------------------------------------------------------------------------- /simulators/mujoco_simulator/revolve2/simulators/mujoco_simulator/textures/__init__.py: -------------------------------------------------------------------------------- 1 | """Mujoco specific Textures.""" 2 | 3 | from ._checker import Checker 4 | from ._flat import Flat 5 | from ._gradient import Gradient 6 | 7 | __all__ = ["Checker", "Flat", "Gradient"] 8 | -------------------------------------------------------------------------------- /simulators/mujoco_simulator/revolve2/simulators/mujoco_simulator/textures/_checker.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field 2 | 3 | from revolve2.simulation.scene import Color 4 | from revolve2.simulation.scene.geometry.textures import Texture, TextureReference 5 | 6 | 7 | @dataclass(kw_only=True, frozen=True) 8 | class Checker(Texture): 9 | """A checker texture for geometric models.""" 10 | 11 | reference: TextureReference = field( 12 | default_factory=lambda: TextureReference(builtin="checker") 13 | ) 14 | repeat: tuple[int, int] = field(default=(100, 100)) 15 | 16 | primary_color: Color 17 | secondary_color: Color 18 | -------------------------------------------------------------------------------- /simulators/mujoco_simulator/revolve2/simulators/mujoco_simulator/textures/_flat.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field 2 | 3 | from revolve2.simulation.scene import Color 4 | from revolve2.simulation.scene.geometry.textures import Texture, TextureReference 5 | 6 | 7 | @dataclass(kw_only=True, frozen=True) 8 | class Flat(Texture): 9 | """A flat texture for geometric models.""" 10 | 11 | reference: TextureReference = field( 12 | default_factory=lambda: TextureReference(builtin="flat") 13 | ) 14 | primary_color: Color 15 | -------------------------------------------------------------------------------- /simulators/mujoco_simulator/revolve2/simulators/mujoco_simulator/textures/_gradient.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field 2 | 3 | from revolve2.simulation.scene import Color 4 | from revolve2.simulation.scene.geometry.textures import Texture, TextureReference 5 | 6 | 7 | @dataclass(kw_only=True, frozen=True) 8 | class Gradient(Texture): 9 | """A color gradient spanning over geometric models.""" 10 | 11 | reference: TextureReference = field( 12 | default_factory=lambda: TextureReference(builtin="gradient") 13 | ) 14 | 15 | primary_color: Color 16 | secondary_color: Color 17 | -------------------------------------------------------------------------------- /simulators/mujoco_simulator/revolve2/simulators/mujoco_simulator/viewers/__init__.py: -------------------------------------------------------------------------------- 1 | """Different viewer implementations for mujoco.""" 2 | 3 | from ._custom_mujoco_viewer import CustomMujocoViewer, CustomMujocoViewerMode 4 | from ._native_mujoco_viewer import NativeMujocoViewer 5 | from ._viewer_type import ViewerType 6 | 7 | __all__ = [ 8 | "CustomMujocoViewer", 9 | "CustomMujocoViewerMode", 10 | "NativeMujocoViewer", 11 | "ViewerType", 12 | ] 13 | -------------------------------------------------------------------------------- /simulators/mujoco_simulator/revolve2/simulators/mujoco_simulator/viewers/_viewer_type.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from enum import Enum, auto 4 | 5 | 6 | class ViewerType(Enum): 7 | """Viewer types available for mujoco.""" 8 | 9 | NATIVE = auto() 10 | CUSTOM = auto() 11 | 12 | @staticmethod 13 | def from_string(value: str) -> ViewerType: 14 | """ 15 | Get viewer type from string. 16 | 17 | :param value: The value. 18 | :returns: The viewer type. 19 | :raises ValueError: If the passed value has no viewer type defined. 20 | """ 21 | match value.lower(): 22 | case "native": 23 | return ViewerType.NATIVE 24 | case "custom": 25 | return ViewerType.CUSTOM 26 | case _: 27 | raise ValueError(f"No viewer type {value} defined.") 28 | -------------------------------------------------------------------------------- /standards/revolve2/standards/__init__.py: -------------------------------------------------------------------------------- 1 | """Standard resources such as terrains and robots that can be used for testing and research.""" 2 | -------------------------------------------------------------------------------- /standards/revolve2/standards/ci_lab_utilities/__init__.py: -------------------------------------------------------------------------------- 1 | """Utility functions for the CI-group lab.""" 2 | 3 | from ._calibrate_camera import calibrate_camera 4 | from ._ip_camera import IPCamera 5 | 6 | __all__ = ["IPCamera", "calibrate_camera"] 7 | -------------------------------------------------------------------------------- /standards/revolve2/standards/fitness_functions.py: -------------------------------------------------------------------------------- 1 | """Standard fitness functions for modular robots.""" 2 | 3 | import math 4 | 5 | from revolve2.modular_robot_simulation import ModularRobotSimulationState 6 | 7 | 8 | def xy_displacement( 9 | begin_state: ModularRobotSimulationState, end_state: ModularRobotSimulationState 10 | ) -> float: 11 | """ 12 | Calculate the distance traveled on the xy-plane by a single modular robot. 13 | 14 | :param begin_state: Begin state of the robot. 15 | :param end_state: End state of the robot. 16 | :returns: The calculated fitness. 17 | """ 18 | begin_position = begin_state.get_pose().position 19 | end_position = end_state.get_pose().position 20 | return math.sqrt( 21 | (begin_position.x - end_position.x) ** 2 22 | + (begin_position.y - end_position.y) ** 2 23 | ) 24 | -------------------------------------------------------------------------------- /standards/revolve2/standards/genotypes/__init__.py: -------------------------------------------------------------------------------- 1 | """Genotypes for evolutionary algorithms.""" 2 | -------------------------------------------------------------------------------- /standards/revolve2/standards/genotypes/cppnwin/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | CPPNWIN genotype. 3 | 4 | That is, Compositional Pattern-Producing Network With Innovation Numbers. 5 | """ 6 | -------------------------------------------------------------------------------- /standards/revolve2/standards/genotypes/cppnwin/_multineat_genotype_pickle_wrapper.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import cast 3 | 4 | import multineat 5 | 6 | 7 | @dataclass 8 | class MultineatGenotypePickleWrapper: 9 | """A wrapper about multineat.Genome that provides pickling.""" 10 | 11 | genotype: multineat.Genome 12 | 13 | def __getstate__(self) -> str: 14 | """ 15 | Convert the genotype to a string, serializing it. 16 | 17 | :returns: The string. 18 | """ 19 | return cast(str, self.genotype.Serialize()).replace(" ", "") 20 | 21 | def __setstate__(self, serialized_genotype: str) -> None: 22 | """ 23 | Convert a string obtained through __getstate__ to a genotype and set it as the genotype. 24 | 25 | :param serialized_genotype: The string to convert. 26 | """ 27 | genotype = multineat.Genome() 28 | genotype.Deserialize(serialized_genotype) 29 | self.genotype = genotype 30 | -------------------------------------------------------------------------------- /standards/revolve2/standards/genotypes/cppnwin/_multineat_rng_from_random.py: -------------------------------------------------------------------------------- 1 | import multineat 2 | import numpy as np 3 | 4 | 5 | def multineat_rng_from_random(rng: np.random.Generator) -> multineat.RNG: 6 | """ 7 | Create a multineat rng object from a numpy rng state. 8 | 9 | :param rng: The numpy rng. 10 | :returns: The multineat rng. 11 | """ 12 | multineat_rng = multineat.RNG() 13 | multineat_rng.Seed(rng.integers(0, 2**31)) 14 | return multineat_rng 15 | -------------------------------------------------------------------------------- /standards/revolve2/standards/genotypes/cppnwin/modular_robot/__init__.py: -------------------------------------------------------------------------------- 1 | """CPPNWIN genotypes for modular robots.""" 2 | 3 | from ._brain_cpg_network_neighbor import BrainCpgNetworkNeighbor 4 | from ._brain_genotype_cpg import BrainGenotypeCpg 5 | from ._brain_genotype_cpg_orm import BrainGenotypeCpgOrm 6 | 7 | __all__ = [ 8 | "BrainCpgNetworkNeighbor", 9 | "BrainGenotypeCpg", 10 | "BrainGenotypeCpgOrm", 11 | ] 12 | -------------------------------------------------------------------------------- /standards/revolve2/standards/genotypes/cppnwin/modular_robot/v1/__init__.py: -------------------------------------------------------------------------------- 1 | """Body Genotype Mapping for V1 Robot.""" 2 | 3 | from ._body_genotype_orm_v1 import BodyGenotypeOrmV1 4 | from ._body_genotype_v1 import BodyGenotypeV1 5 | 6 | __all__ = ["BodyGenotypeOrmV1", "BodyGenotypeV1"] 7 | -------------------------------------------------------------------------------- /standards/revolve2/standards/genotypes/cppnwin/modular_robot/v2/__init__.py: -------------------------------------------------------------------------------- 1 | """Body Genotype Mapping for V1 Robot.""" 2 | 3 | from ._body_genotype_orm_v2 import BodyGenotypeOrmV2 4 | from ._body_genotype_v2 import BodyGenotypeV2 5 | 6 | __all__ = ["BodyGenotypeOrmV2", "BodyGenotypeV2"] 7 | -------------------------------------------------------------------------------- /standards/revolve2/standards/interactive_objects/__init__.py: -------------------------------------------------------------------------------- 1 | """Interactive objects for the moduler robots to play with.""" 2 | 3 | from ._ball import Ball 4 | 5 | __all__ = ["Ball"] 6 | -------------------------------------------------------------------------------- /standards/revolve2/standards/interactive_objects/_ball.py: -------------------------------------------------------------------------------- 1 | from revolve2.simulation.scene import Color, MultiBodySystem, Pose, RigidBody 2 | from revolve2.simulation.scene.geometry import GeometrySphere 3 | from revolve2.simulation.scene.geometry.textures import Texture 4 | 5 | 6 | class Ball(MultiBodySystem): 7 | """An interactive ball.""" 8 | 9 | def __init__( 10 | self, 11 | radius: float, 12 | mass: float, 13 | pose: Pose = Pose(), 14 | is_static: bool = False, 15 | texture: Texture | None = None, 16 | ) -> None: 17 | """ 18 | Initialize the ball. 19 | 20 | :param radius: The radius of the ball. 21 | :param mass: The mass of the ball. 22 | :param pose: The pose of the ball in the simulation. 23 | :param is_static: Whether the ball can be interacted with. 24 | :param texture: The texture of the ball. 25 | """ 26 | super().__init__(pose=pose, is_static=is_static) 27 | 28 | if texture is None: 29 | texture = Texture(base_color=Color(255, 255, 255, 255)) 30 | 31 | ball = GeometrySphere(pose=Pose(), mass=mass, radius=radius, texture=texture) 32 | rigid_body = RigidBody( 33 | initial_pose=Pose(), 34 | static_friction=1.0, 35 | dynamic_friction=1.0, 36 | geometries=[ball], 37 | ) 38 | self.add_rigid_body(rigid_body) 39 | -------------------------------------------------------------------------------- /standards/revolve2/standards/morphological_novelty_metric/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Calculate Morphological Novelty across a Population. 3 | 4 | Using the MorphologicalNoveltyMetric for evaluating a population of robots can be done as shown in the code below. 5 | 6 | >>> robots: list[revolve2.modular_robot.ModularRobot] 7 | >>> novelties = get_novelty_from_population(robots) 8 | """ 9 | 10 | from ._morphological_novelty_metric import get_novelty_from_population 11 | 12 | __all__ = ["get_novelty_from_population"] 13 | -------------------------------------------------------------------------------- /standards/revolve2/standards/morphological_novelty_metric/_build_cmodule.py: -------------------------------------------------------------------------------- 1 | import os 2 | from os.path import join 3 | 4 | import numpy 5 | from Cython.Build import cythonize 6 | from setuptools import Extension, setup 7 | 8 | 9 | def build() -> None: 10 | """ 11 | Build the morphological novelty shared object. 12 | 13 | :raises OSError: If the users OS is not Windows or UNIX-based. 14 | """ 15 | directory_path = os.path.dirname(os.path.abspath(__file__)) 16 | 17 | source = join(directory_path, "_calculate_novelty.pyx") 18 | 19 | include = numpy.get_include() 20 | 21 | match os.name: 22 | case "nt": # Windows 23 | extra_compile_args = [ 24 | "/O2", 25 | "-UNDEBUG", 26 | ] 27 | case "posix": # UNIX-based systems 28 | extra_compile_args = [ 29 | "-O3", 30 | "-ffast-math", 31 | "-UNDEBUG", 32 | ] 33 | case _: 34 | raise OSError( 35 | f"No build parameter set for operating systems of type {os.name}" 36 | ) 37 | 38 | ext = Extension( 39 | name="calculate_novelty", 40 | sources=[source], 41 | include_dirs=[include], 42 | define_macros=[("NPY_NO_DEPRECATED_API", "NPY_1_7_API_VERSION")], 43 | extra_compile_args=extra_compile_args, 44 | ) 45 | 46 | ext_modules = cythonize( 47 | ext, 48 | include_path=[include], 49 | compiler_directives={"binding": True, "language_level": 3}, 50 | ) 51 | 52 | setup( 53 | ext_modules=ext_modules, 54 | script_args=["build_ext", f"--build-lib={directory_path}"], 55 | ) 56 | 57 | 58 | if __name__ == "__main__": 59 | build() 60 | -------------------------------------------------------------------------------- /standards/revolve2/standards/morphological_novelty_metric/calculate_novelty.pyi: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from numpy.typing import NDArray 3 | 4 | """Allow mypy and sphinx to resolve the compiled cython module.""" 5 | 6 | def calculate_novelty( 7 | histograms: NDArray[np.int64], amount_instances: int, histogram_size: int 8 | ) -> NDArray[np.float64]: ... 9 | -------------------------------------------------------------------------------- /standards/revolve2/standards/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ci-group/revolve2/8156a66624f96c43cdfcec05bbb366cc492b87e0/standards/revolve2/standards/py.typed -------------------------------------------------------------------------------- /standards/revolve2/standards/simulation_parameters.py: -------------------------------------------------------------------------------- 1 | """Standard simulation functions and parameters.""" 2 | 3 | from revolve2.simulation.simulator import BatchParameters 4 | 5 | STANDARD_SIMULATION_TIME = 30 6 | STANDARD_SAMPLING_FREQUENCY = 5 7 | STANDARD_SIMULATION_TIMESTEP = 0.001 8 | STANDARD_CONTROL_FREQUENCY = 20 9 | 10 | 11 | def make_standard_batch_parameters( 12 | simulation_time: int = STANDARD_SIMULATION_TIME, 13 | sampling_frequency: float | None = STANDARD_SAMPLING_FREQUENCY, 14 | simulation_timestep: float = STANDARD_SIMULATION_TIMESTEP, 15 | control_frequency: float = STANDARD_CONTROL_FREQUENCY, 16 | ) -> BatchParameters: 17 | """ 18 | Create batch parameters as standardized within the CI Group. 19 | 20 | :param simulation_time: As defined in the `BatchParameters` class. 21 | :param sampling_frequency: As defined in the `BatchParameters` class. 22 | :param simulation_timestep: As defined in the `BatchParameters` class. 23 | :param control_frequency: As defined in the `BatchParameters` class. 24 | :returns: The create batch parameters. 25 | """ 26 | return BatchParameters( 27 | simulation_time=simulation_time, 28 | sampling_frequency=sampling_frequency, 29 | simulation_timestep=simulation_timestep, 30 | control_frequency=control_frequency, 31 | ) 32 | -------------------------------------------------------------------------------- /student_install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Installs all Revolve2 packages in editable mode as well as all example requirements. 4 | # This is the same as installing `requirements_editable.txt`. 5 | 6 | cd "$(dirname "$0")" 7 | 8 | pip install -r ./requirements_editable.txt 9 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | # Running Unit Tests 2 | 3 | ```bash 4 | # (in sourced environment) 5 | pip install pytest 6 | 7 | # run tests (printing names of all tests as it goes) 8 | pytest -v 9 | 10 | # run tests with all output visible (e.g. print statements): 11 | pytest -s 12 | ``` 13 | 14 | For more info, see the [pytest docs](https://docs.pytest.org/). 15 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | """Unit tests for Revolve2.""" 2 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file configures pytest. 3 | 4 | The name `conftest.py` is pytest's default name and should not be changed. 5 | """ 6 | 7 | import os 8 | import subprocess 9 | 10 | TEST_DIR = os.path.abspath(os.path.dirname(__file__)) 11 | ROOT_DIR = os.path.abspath(os.path.dirname(TEST_DIR)) 12 | 13 | EXAMPLES_DIR = os.path.join(ROOT_DIR, "examples") 14 | 15 | 16 | def assert_command_succeeds(cmd: list[str]) -> None: 17 | """ 18 | Assert if a given command succeeds. 19 | 20 | :param cmd: Slices of the command. 21 | """ 22 | print("running command:\n", " ".join(cmd)) 23 | res = subprocess.run(cmd, stdout=subprocess.PIPE) 24 | print(res.stdout) 25 | assert res.returncode == 0, f"expected return code 0, got {res.returncode}" 26 | -------------------------------------------------------------------------------- /tests/examples/__init__.py: -------------------------------------------------------------------------------- 1 | """Unit tests for the examples provided.""" 2 | -------------------------------------------------------------------------------- /tests/examples/_clear_example_modules_from_cache.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from ..conftest import EXAMPLES_DIR 4 | 5 | 6 | def clear_exp_modules_from_cache() -> None: 7 | """Clear any module from the sys.modules cache that is part of the temporary directory.""" 8 | modules_to_delete = [ 9 | name 10 | for name, mod in sys.modules.items() 11 | if mod is not None 12 | and hasattr(mod, "__file__") 13 | and getattr(mod, "__file__") is not None 14 | and getattr(mod, "__file__", "").startswith(EXAMPLES_DIR) 15 | ] 16 | for mod in modules_to_delete: 17 | del sys.modules[mod] 18 | -------------------------------------------------------------------------------- /tests/examples/_patched_batch_parameters.py: -------------------------------------------------------------------------------- 1 | """Standard simulation functions and parameters patched for unit test.""" 2 | 3 | from revolve2.simulation.simulator import BatchParameters 4 | 5 | 6 | def make_patched_batch_parameters() -> BatchParameters: 7 | """ 8 | Create batch parameters for unit-tests. 9 | 10 | :returns: The create batch parameters. 11 | """ 12 | return BatchParameters( 13 | simulation_time=1, 14 | sampling_frequency=5, 15 | simulation_timestep=0.001, 16 | control_frequency=20, 17 | ) 18 | -------------------------------------------------------------------------------- /tests/examples/test_1a_simulate_single_robot.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from unittest.mock import Mock 4 | 5 | from ..conftest import EXAMPLES_DIR 6 | from ._clear_example_modules_from_cache import clear_exp_modules_from_cache 7 | from ._patched_batch_parameters import make_patched_batch_parameters 8 | 9 | 10 | def test_1a_simulate_single_robot(mocker: Mock) -> None: 11 | """ 12 | Test 1a_simulate_single_robot example can complete. 13 | 14 | :param mocker: The mock object. 15 | """ 16 | exp_dir = os.path.join(EXAMPLES_DIR, "1_simulator_basics/1a_simulate_single_robot") 17 | # Clear any previously imported modules from examples directory from cache 18 | clear_exp_modules_from_cache() 19 | # Add examples directory to path, so we can import them without the examples being packages. 20 | sys.path.insert(0, exp_dir) 21 | # Override import for patching batch parameters. 22 | mocker.patch("main.make_standard_batch_parameters", make_patched_batch_parameters) 23 | 24 | try: 25 | # This type ignore is required since mypy cant resolve this import. 26 | import main # type: ignore 27 | 28 | main.main() 29 | finally: 30 | # Remove the example directory from path, even if the test fails. 31 | sys.path.pop(0) 32 | -------------------------------------------------------------------------------- /tests/examples/test_2b_brain_with_feedback.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from unittest.mock import Mock 4 | 5 | from ..conftest import EXAMPLES_DIR 6 | from ._clear_example_modules_from_cache import clear_exp_modules_from_cache 7 | from ._patched_batch_parameters import make_patched_batch_parameters 8 | 9 | 10 | def test_2b_brain_with_feedback(mocker: Mock) -> None: 11 | """ 12 | Test 2b_brain_with_feedback example can complete. 13 | :param mocker: The mock object. 14 | """ 15 | exp_dir = os.path.join( 16 | EXAMPLES_DIR, "2_modular_robot_basics/2b_brain_with_feedback" 17 | ) 18 | # Clear any previously imported modules from examples directory from cache 19 | clear_exp_modules_from_cache() 20 | # Add examples directory to path, so we can import them without the examples being packages. 21 | sys.path.insert(0, exp_dir) 22 | # Override import for patching batch parameters. 23 | mocker.patch("main.make_standard_batch_parameters", make_patched_batch_parameters) 24 | 25 | try: 26 | # This type ignore is required since mypy cant resolve this import. 27 | import main # type: ignore 28 | 29 | main.main() 30 | finally: 31 | # Remove the example directory from path, even if the test fails. 32 | sys.path.pop(0) 33 | -------------------------------------------------------------------------------- /tests/examples/test_3a_experiment_setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from ..conftest import EXAMPLES_DIR, assert_command_succeeds 4 | 5 | 6 | def test_3a_experiment_setup() -> None: 7 | """Test 3a_experiment_setup example can complete.""" 8 | exp_dir = os.path.join(EXAMPLES_DIR, "3_experiment_foundations/3a_experiment_setup") 9 | assert_command_succeeds(["python", os.path.join(exp_dir, "main.py")]) 10 | -------------------------------------------------------------------------------- /tests/examples/test_3b_evaluate_single_robot.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from unittest.mock import Mock 4 | 5 | from ..conftest import EXAMPLES_DIR 6 | from ._clear_example_modules_from_cache import clear_exp_modules_from_cache 7 | from ._patched_batch_parameters import make_patched_batch_parameters 8 | 9 | 10 | def test_3b_evaluate_single_robot(mocker: Mock) -> None: 11 | """ 12 | Test 3b_evaluate_single_robot example can complete. 13 | 14 | :param mocker: The mock object. 15 | """ 16 | exp_dir = os.path.join( 17 | EXAMPLES_DIR, "3_experiment_foundations/3b_evaluate_single_robot" 18 | ) 19 | # Clear any previously imported modules from examples directory from cache 20 | clear_exp_modules_from_cache() 21 | # Add examples directory to path, so we can import them without the examples being packages. 22 | sys.path.insert(0, exp_dir) 23 | # Override import for patching batch parameters. 24 | mocker.patch("main.make_standard_batch_parameters", make_patched_batch_parameters) 25 | 26 | try: 27 | # This type ignore is required since mypy cant resolve this import. 28 | import main # type: ignore 29 | 30 | main.main() 31 | finally: 32 | # Remove the example directory from path, even if the test fails. 33 | sys.path.pop(0) 34 | -------------------------------------------------------------------------------- /tests/examples/test_3c_evaluate_multiple_isolated_robots.py: -------------------------------------------------------------------------------- 1 | """Tests some of the examples that can just be easily ran as a subprocess.""" 2 | 3 | import os 4 | import sys 5 | from unittest.mock import Mock 6 | 7 | from ..conftest import EXAMPLES_DIR 8 | from ._clear_example_modules_from_cache import clear_exp_modules_from_cache 9 | from ._patched_batch_parameters import make_patched_batch_parameters 10 | 11 | 12 | def test_3c_evaluate_multiple_isolated_robots(mocker: Mock) -> None: 13 | """ 14 | Test 3c_evaluate_multiple_isolated_robots example can complete. 15 | 16 | :param mocker: The mock object. 17 | """ 18 | exp_dir = os.path.join( 19 | EXAMPLES_DIR, "3_experiment_foundations/3c_evaluate_multiple_isolated_robots" 20 | ) 21 | # Clear any previously imported modules from examples directory from cache 22 | clear_exp_modules_from_cache() 23 | # Add examples directory to path, so we can import them without the examples being packages. 24 | sys.path.insert(0, exp_dir) 25 | # Override import for patching batch parameters. 26 | mocker.patch("main.make_standard_batch_parameters", make_patched_batch_parameters) 27 | 28 | try: 29 | # This type ignore is required since mypy cant resolve this import. 30 | import main # type: ignore 31 | 32 | main.main() 33 | finally: 34 | # Remove the example directory from path, even if the test fails. 35 | sys.path.pop(0) 36 | -------------------------------------------------------------------------------- /tests/examples/test_4a_simple_ea_xor.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from ..conftest import EXAMPLES_DIR, assert_command_succeeds 4 | 5 | 6 | def test_4a_simple_ea_xor() -> None: 7 | """Test 4a_simple_ea_xor example can complete.""" 8 | exp_dir = os.path.join(EXAMPLES_DIR, "4_example_experiment_setups/4a_simple_ea_xor") 9 | assert_command_succeeds(["python", os.path.join(exp_dir, "main.py")]) 10 | -------------------------------------------------------------------------------- /tests/examples/test_4e_robot_brain_cmaes.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from unittest.mock import Mock 4 | 5 | from ..conftest import EXAMPLES_DIR 6 | from ._clear_example_modules_from_cache import clear_exp_modules_from_cache 7 | 8 | 9 | def test_4e_robot_brain_cmaes(mocker: Mock) -> None: 10 | """ 11 | Test 4e_robot_brain_cmaes example can complete. 12 | 13 | :param mocker: The mock object. 14 | """ 15 | exp_dir = os.path.join( 16 | EXAMPLES_DIR, "4_example_experiment_setups/4e_robot_brain_cmaes" 17 | ) 18 | 19 | # Clear any previously imported modules from examples directory from cache 20 | clear_exp_modules_from_cache() 21 | 22 | # Add examples directory to path, so we can import them without the examples being packages. 23 | sys.path.insert(0, exp_dir) # Add the example directory to sys.path 24 | 25 | # Override default config to reduce number of generations. 26 | mocker.patch("config.NUM_GENERATIONS", 2) 27 | 28 | # Import the example main and run it. 29 | try: 30 | # This type ignore is required since mypy cant resolve this import. 31 | import main # type: ignore 32 | 33 | main.main() 34 | finally: 35 | # Remove the example directory from path, even if the test fails. 36 | sys.path.pop(0) 37 | -------------------------------------------------------------------------------- /tests/requirements.txt: -------------------------------------------------------------------------------- 1 | pytest~=7.4.2 2 | pytest-mock==3.11.1 3 | -------------------------------------------------------------------------------- /uninstall.sh: -------------------------------------------------------------------------------- 1 | # !/bin/bash 2 | # pip uninstall for every package which name contains 'revolve2' 3 | packages=$(pip list | grep revolve2 | awk '{print $1}') 4 | if [ -n "$packages" ]; then 5 | echo "The following packages will be uninstalled:" 6 | echo "$packages" 7 | read -p "Are you sure? (y/N) " -n 1 -r 8 | echo 9 | if [[ $REPLY =~ ^[Yy]$ ]]; then 10 | pip uninstall $packages -y 11 | fi 12 | else 13 | echo "No packages to uninstall." 14 | fi 15 | --------------------------------------------------------------------------------