Basic API Usage: NDE Body + CPG Controller¶
This tutorial walks through the minimum steps needed to generate a modular robot from a genotype and run it in simulation with a CPG controller.
Page Outline¶
Imports
Creating a simulation environment
Generating a robot body with the NDE
Spawning the robot
Setting up the CPG controller
Stepping the simulation
1. Imports¶
[1]:
import mujoco
import numpy as np
from ariel.body_phenotypes.robogen_lite.constructor import (
construct_mjspec_from_graph,
)
from ariel.body_phenotypes.robogen_lite.decoders.hi_prob_decoding import (
HighProbabilityDecoder,
)
from ariel.ec.genotypes.nde import NeuralDevelopmentalEncoding
from ariel.simulation.controllers.controller import Controller
from ariel.simulation.controllers.na_cpg import (
NaCPG,
create_fully_connected_adjacency,
)
from ariel.simulation.environments import SimpleFlatWorld
from ariel.utils.runners import simple_runner
from ariel.utils.tracker import Tracker
2. Creating a Simulation Environment¶
Create a SimpleFlatWorld — a flat chequerboard floor that provides the ground for the robot to walk on. Always reset the MuJoCo control callback before constructing a new simulation.
[2]:
mujoco.set_mjcb_control(None)
world = SimpleFlatWorld()
3. Generating a Robot Body with the NDE¶
A robot body is generated by passing a numeric genotype through the Neural Developmental Encoding (NDE) pipeline. The genotype is a list of three float arrays — one each for module types, connectivity, and joint rotations — which the NDE maps to probability matrices. The HighProbabilityDecoder then converts those matrices into a concrete robot graph.
For more detail on the NDE representation see the NDE Genotype documentation.
[3]:
RNG = np.random.default_rng(seed=42)
GENOTYPE_SIZE = 64
NUM_MODULES = 20
genotype = [
RNG.random(GENOTYPE_SIZE).astype(np.float32), # module-type chromosome
RNG.random(GENOTYPE_SIZE).astype(np.float32), # connectivity chromosome
RNG.random(GENOTYPE_SIZE).astype(np.float32), # rotation chromosome
]
nde = NeuralDevelopmentalEncoding(number_of_modules=NUM_MODULES)
p_matrices = nde.forward(genotype)
decoder = HighProbabilityDecoder(num_modules=NUM_MODULES)
robot_graph = decoder.probability_matrices_to_graph(*p_matrices)
robot = construct_mjspec_from_graph(robot_graph)
4. Spawning the Robot¶
Add the robot to the world at a given position, then compile the combined specification into a MuJoCo model.
[4]:
world.spawn(robot.spec, position=[0, 0, 0])
model = world.spec.compile()
data = mujoco.MjData(model)
5. Setting Up the CPG Controller¶
Create an NaCPG oscillator network with one node per actuator. Wrap it in a Controller, which handles control-frequency throttling, output smoothing, joint-limit clamping, and position tracking automatically. Register controller.set_control as the MuJoCo callback.
The CPG parameters (phase, w, amplitudes, ha, b) are initialised randomly — in an EA these become the controller genotype to evolve.
[5]:
tracker = Tracker(
mujoco_obj_to_find=mujoco.mjtObj.mjOBJ_GEOM,
name_to_bind="core",
observable_attributes=["xpos"],
)
tracker.setup(world.spec, data)
cpg = NaCPG(
adjacency_dict=create_fully_connected_adjacency(model.nu),
hard_bounds=(-np.pi / 2, np.pi / 2),
)
controller = Controller(
controller_callback_function=lambda m, d: cpg.forward(time=d.time),
tracker=tracker,
)
mujoco.set_mjcb_control(controller.set_control)
6. Stepping the Simulation¶
Run the simulation for a fixed duration. The controller and tracker update automatically at each step. After the run, read the final position of the robot’s core from the tracker history.
[6]:
simple_runner(model, data, duration=15)
start_pos = np.array(tracker.history["xpos"][0][0])
end_pos = np.array(tracker.history["xpos"][0][-1])
displacement = np.linalg.norm(end_pos[:2] - start_pos[:2])
Next Steps¶
Quick Start: Modular Robots — a broader guide covering prebuilt robots, manual assembly, all run modes, trajectory plotting, and fitness functions.
NDE Genotype — how to represent and evolve robot bodies.
Robot Evolution — a complete evolutionary algorithm that co-evolves body and controller.
MuJoCo Data Variables — all simulation state accessible during a run.