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¶

  1. Imports

  2. Creating a simulation environment

  3. Generating a robot body with the NDE

  4. Spawning the robot

  5. Setting up the CPG controller

  6. 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.