├── .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 |
--------------------------------------------------------------------------------