Skip to content

oppenheimj/robo

Repository files navigation

Robo — Quadruped Robot Neuroevolution

A Rust application that evolves neural network controllers for a simulated quadruped robot. The robot model is created in Blender, physics are simulated with Rapier3D, and controllers are optimized using Age-Fitness Pareto Optimization (AFPO). Visualization uses the Bevy game engine.

Evolved quadruped robot walking

Background

The evolutionary algorithm is based on Age-Fitness Pareto Optimization (AFPO) from:

Schmidt, M. & Lipson, H. (2011). "Age-Fitness Pareto Optimization." Genetic Programming Theory and Practice VIII, pp. 129–146. doi:10.1007/978-1-4419-7747-2_8

AFPO uses multi-objective Pareto selection on two axes — maximize fitness and minimize age — to maintain population diversity without explicit diversity metrics. Each generation, one fresh random individual (age 0) is injected, offspring inherit age 0, and all Pareto-dominated individuals are culled. This prevents premature convergence while still selecting for high fitness.

Getting Started

Prerequisites

Install Rust via rustup:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Clone and Build

git clone https://github.com/your-username/robo.git
cd robo
cargo build --release

Running

All modes are selected via the --mode (-m) flag. Run in release mode for best performance:

# Default: random joint actuation (robot flops around)
cargo run --release -- --mode flop

# Suspended robot with joints driven to random positions (demonstrates full range of motion)
cargo run --release -- --mode flail

# Run a randomly-initialized neural network controller
cargo run --release -- --mode random-controller

# Headless AFPO evolutionary training (no window, parallel evaluation)
cargo run --release -- --mode evolve

# Replay the best evolved controller (requires best_controller.json)
cargo run --release -- --mode run-best

Camera Controls (visualized modes)

  • WASD — Move camera
  • Q/E — Move camera up/down
  • Arrow keys — Rotate camera

Robot Model

Quadruped robot in the simulator

The quadruped is modeled in Blender — the source file is glb/quadruped_2.blend — and exported to GLB format (glb/quadruped_2.glb).

Structure

  • Body — Central torso (cuboid collider)
  • 4 Legs — Front-left, front-right, back-left, back-right
    • Each leg: hip joint → upper leg → knee joint → lower leg

Joints (8 total)

Joint DOF Axis Limits
Hip (×4) 3 X: [-45°, 90°], Y: [-45°, 45°], Z: [-45°, 45°]
Knee (×4) 1 X: [0°, 135°]

Joint properties are stored as custom properties on Blender empty nodes and exported via the GLTF extras field:

{
  "joint_type": "revolute",
  "motor_enabled": true
}

Joint type (hip vs knee) is determined by node name convention (_knee / knee_ in the name).

GLB Parsing

At startup, quadruped.rs parses the GLB file using the gltf crate:

  1. Node traversal — All GLTF nodes are loaded into a map with their local transforms (translation, rotation, scale)
  2. World transforms — A BFS from the scene root accumulates quaternion rotations and positions to compute each node's world-space pose
  3. Rigid bodies — Nodes with meshes become PartDefinitions. Collider half-extents are derived from mesh bounding boxes scaled by the node's local scale
  4. Joints — Nodes whose extras contain a joint_type key become JointDefinitions. Parent and child parts are found by walking the node hierarchy. Anchor points and local rotations are computed by transforming the joint's world position into each part's local coordinate space

The result is a QuadrupedDefinition containing all parts and joints, which is then used to spawn physics bodies in both the Bevy visualizer and the headless simulator.

Neural Network

The controller is a direct linear mapping (no hidden layer):

$$\text{output} = \tanh(W \cdot x + b) \times 5.0$$

Count Description
Inputs 23 3 IMU (gravity in body-local frame) + 16 joint angles (4 hips × 3 axes + 4 knees × 1) + 4 foot contact sensors
Outputs 16 Motor velocity targets for each joint axis

The genome is a flat vector of 384 floats (23×16 weights + 16 biases), directly manipulated by the evolutionary algorithm.

Evolution

The AFPO training loop (--mode evolve) runs headless with parallel episode evaluation via Rayon:

Parameter Value
Population size 256
Mutation rate 0.2 (per gene)
Mutation strength (σ) 0.1
Episode duration 12 s
Selection Age-Fitness Pareto (canonical AFPO)

Fitness Function

$$\text{fitness} = \sqrt{(\Delta x)^2 + (\Delta z)^2} + \begin{cases} 1.0 & \text{if } y > 0.5 \ 0.0 & \text{otherwise} \end{cases}$$

Horizontal distance traveled from the origin, plus a small bonus for staying upright.

Output Files

File Description
best_controller.json Serialized weights/biases of the best-ever controller. Saved every 5 generations during evolution; loaded by --mode run-best
lineage_log.json Per-generation snapshots of the best individual in each lineage, for tracking diversity over time
plot_lineage.py Matplotlib script that reads lineage_log.json and plots fitness vs generation for each lineage. Run: python plot_lineage.py
  • Multiple robot configurations

About

Evolutionary robotics in 3D simulation

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors