Skip to main content

Bio-Inspired Robotics Testbed (BRT)

The Bio-Inspired Robotics Testbed (BRT) is a collection of MuJoCo simulation environments of animals. It serves two communities:

  • Roboticists get benchmarks for the design and control of bio-inspired robots: parameterised morphologies, tasks with rewards, and fast, vectorised simulation on CPU or GPU.
  • Biologists get robots as a controlled comparative method: change one trait of an in-silico animal (arm count, leg strength, a dragline, the span of a tail muscle, ...) while keeping everything else fixed, and study its effect on behaviour, ecology and evolution.

Live demos of every animal run in your browser at https://co-evolve.github.io/brt/.

Environments

Animal Tasks (biorobot.make name) Docs
Brittle star brittle_star/undirected_locomotion, brittle_star/directed_locomotion, brittle_star/light_escape README
Jumping spider jumping_spider/long_jump, jumping_spider/platform_jump, jumping_spider/directed_jump README
Seahorse tail seahorse/tail (no task: a muscle-architecture playground for open-loop control) README

Each animal's README covers its biology, the in-silico morphology, the observations, rewards and configuration of each task, and runnable examples. The seahorse tail (Marzougui et al., 2025) has no task: its reward is always 0, and you study muscle architectures by configuring its morphology and driving it open-loop. To get a feel for it first, try the seahorse muscle playground.

Installation

pip install biorobot              # MuJoCo (CPU) and MJX (JAX: CPU, GPU, TPU)
pip install "biorobot[cuda]"      # + JAX with CUDA 12, to run MJX on NVIDIA GPUs
pip install "biorobot[warp]"      # + NVIDIA Warp, for the "warp" backend

BRT needs Python 3.11 or newer. To work on BRT itself: pip install -e ".[dev]" (adds pytest and black).

Quickstart

import numpy as np

import biorobot

print(biorobot.available())  # all registered environments

env = biorobot.make("brittle_star/undirected_locomotion", backend="mjc", simulation_time=5)
rng = np.random.default_rng(0)

state = env.reset(rng)
while not (state.terminated or state.truncated):
    action = rng.uniform(env.action_low, env.action_high)
    state = env.step(state, action)
print(f"t = {state.info['time']:.2f} s, last reward = {state.reward:.4f}")

An environment is a pure reset(rng) -> state / step(state, action) -> state interface. The state holds the MuJoCo data, a dictionary of observations, the reward, the terminated and truncated flags and task info. Keyword arguments of biorobot.make override fields of the task's configuration dataclass (simulation_time, render_mode, camera_ids, ...) or replace its morphology_specification / arena_configuration.

Set render_mode="human" to watch in an OpenCV window, or render_mode="rgb_array" to get RGB frames from env.render(state) and save them with biorobot.utils.save_video(frames, "episode.mp4", fps=1 / env.config.control_timestep).

Backends

The same task implementation runs on three simulation backends; pick one with backend=...:

Backend Simulator Use it for
"mjc" MuJoCo (C), numpy, CPU Debugging, visualisation, single environments, evolutionary/morphology search on many CPU cores, Gymnasium-based RL libraries.
"mjx" MJX with its JAX implementation Thousands of parallel environments on a GPU/TPU with jax.jit and jax.vmap; end-to-end JAX training loops.
"warp" MJX with the MuJoCo Warp implementation Same JAX interface as "mjx", but faster on NVIDIA GPUs, especially for contact-rich models. Needs biorobot[warp]; pass num_envs (see below).

With "mjx" and "warp", the state is a JAX pytree, so reset and step can be jitted and vmapped. MuJoCo Warp allocates a single contact buffer for all vmapped environments, so with "warp" pass the largest batch size you will use as num_envs when creating the environment (it is ignored by the other backends):

import jax

import biorobot

num_envs = 16
env = biorobot.make("jumping_spider/long_jump", backend="mjx", num_envs=num_envs)  # or "warp"
reset = jax.jit(jax.vmap(env.reset))
step = jax.jit(jax.vmap(env.step))

state = reset(jax.random.split(jax.random.PRNGKey(0), num_envs))
actions = jax.random.uniform(
    jax.random.PRNGKey(1), (num_envs, env.mj_model.nu), minval=env.action_low, maxval=env.action_high
)
state = step(state, actions)
print(state.reward.shape)  # (16,)

A batched state can be rendered too: env.render(state) returns the frames of every environment (rendered one after the other with a single renderer).

Gymnasium

GymnasiumEnv wraps an "mjc" environment as a gymnasium.Env for standard RL libraries. Observations are a dictionary; wrap with gymnasium.wrappers.FlattenObservation for a flat vector.

import gymnasium

import biorobot
from biorobot.core.gym import GymnasiumEnv

env = gymnasium.wrappers.FlattenObservation(GymnasiumEnv(biorobot.make("brittle_star/light_escape")))
observation, info = env.reset(seed=0)
observation, reward, terminated, truncated, info = env.step(env.action_space.sample())

Design

  • Models are built in Python with mujoco.MjSpec. Every morphology is generated from a specification: plain, nested Python dataclasses (e.g. number of arms and segments, joint ranges, actuator strength). Change a field and you get a new, valid robot, which is what morphology optimisation and comparative studies need. Arenas are built the same way from configuration dataclasses. Both expose the raw MjSpec as .spec, so you can still edit anything by hand before the environment compiles it, or export() it to MJCF XML.
  • One task implementation, three backends. Tasks are written once against env.xp (numpy or jax.numpy) and a small backend interface (biorobot/core/backend.py). Everything that varies per episode (initial poses, targets, light maps) lives in the simulation data or the state's info, never in the model, so vmapped environments can share one model.
  • A registry (biorobot.make, biorobot.available) builds each task with a sensible default morphology and arena.

For example, a brittle star with three arms of eight segments and position control:

import biorobot
from biorobot.brittle_star import default_brittle_star_morphology_specification

specification = default_brittle_star_morphology_specification(
    num_arms=3, num_segments_per_arm=8, use_p_control=True
)
env = biorobot.make("brittle_star/directed_locomotion", morphology_specification=specification)
print(env.mj_model.nu)  # 48 actuators: 3 arms x 8 segments x 2 joints

Adding a new animal

  1. Read the core in biorobot/core: builder.py (Morphology, Arena, compose), env.py (BRTEnv, EnvConfig and the task hooks), observables.py and backend.py. tests/test_core.py contains a complete toy environment in about 60 lines; start from it.
  2. Create biorobot/<animal>/ with specification.py (dataclasses and a default_..._specification), morphology.py and arena.py (MjSpec builders), and environment/ with one module per task that registers a factory with @biorobot.register("<animal>/<task>"). Add the package to _ANIMAL_PACKAGES in biorobot/registry.py. The brittle star and jumping spider packages are complete examples. Put mesh or other data files under biorobot/<animal>/assets/ and add them to [tool.setuptools.package-data] in pyproject.toml and to MANIFEST.in (like the seahorse's meshes), otherwise they are missing from the wheel.
  3. Two rules keep a task correct on every backend: write it with self.xp and without Python branching on simulated values (use xp.where), and compute rewards from previous_state.observations or previous_state.info, never from previous_state.data (with "mjc" the data is updated in place).
  4. Add tests/test_<animal>.py (an "mjc" episode, MJX matching "mjc", a render smoke test), an examples/ folder and a README.

Migrating from 0.4.x

Version 1.0 is a rewrite and is not backwards compatible.

  • BRT no longer depends on moojoco, fprs or dm_control. Models are built with mujoco.MjSpec, specifications are plain dataclasses instead of fprs specifications, and morphologies and arenas subclass biorobot.core.Morphology / biorobot.core.Arena instead of moojoco's MJCFMorphology / MJCFArena.
  • Module paths changed: e.g. biorobot.brittle_star.mjcf.morphology.morphology is now biorobot.brittle_star.morphology, and the per-backend mjc_env.py / mjx_env.py / dual.py modules are replaced by one class per task in biorobot.<animal>.environment. The simplest way to create an environment is now biorobot.make("<animal>/<task>", backend=...).
  • The environment API is functional on every backend: state = env.reset(rng) and state = env.step(state, action). Use biorobot.core.gym.GymnasiumEnv for the Gymnasium API.
  • Rewards of "mjc" environments were always 0 in 0.4.x; they are correct now, so results are not comparable.

To keep running code written for 0.4.x, pin the old version: pip install biorobot==0.4.2.

Examples

Every animal has runnable examples, e.g.

python -m biorobot.brittle_star.examples.light_escape --backend mjx --render human
python -m biorobot.jumping_spider.examples.scripted_jump
python -m biorobot.seahorse.examples.open_loop

The Jupyter notebooks of the SEL3 course use BRT 0.4.x (pip install biorobot==0.4.2).

Contributing

Interested in having a model developed? Please fill in this form. Bug reports and pull requests are welcome on GitHub. Run pytest -m "not slow" and black biorobot tests scripts before opening a pull request.

Publications

Papers that use BRT's animals (the animal READMEs have the BibTeX):

  • Brittle star:
    • Decentralized neural dynamics and sensory constraints shape brittle star locomotion, Marzougui, Adriaens and wyffels, Journal of the Royal Society Interface (2026)
    • Stability-Driven CPG Design for More Effective Quality-Diversity in Evolutionary Robotics, Pex, Marzougui and wyffels, GECCO (2026)
  • Seahorse tail:
    • Exploring the evolutionary adaptations of the unique seahorse tail's muscle architecture through in silico modelling and robotic prototyping, Marzougui, Das, Mazzolai, Adriaens and wyffels, Journal of the Royal Society Interface (2025); try its muscle playground

Using BRT in a paper? Open a pull request to add it here.

Citation

If you use BRT in your research, please cite this repository:

@software{marzougui_brt,
  author = {Marzougui, Dries},
  title = {{BRT}: The Bio-Inspired Robotics Testbed},
  url = {https://github.com/Co-Evolve/brt},
  version = {1.0.0},
  year = {2026}
}

Metadata

Release files for biorobot 1.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for biorobot 1.0.0
File Size Uploaded Provenance Actions
biorobot-1.0.0.tar.gz 2.7 MB Without provenance Details for biorobot-1.0.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for biorobot 1.0.0
File Interpreter ABI Platform Actions
biorobot-1.0.0-py3-none-any.whl Python 3 none any Details for biorobot-1.0.0-py3-none-any.whl

Total release size: 5.3 MB

Release files / biorobot-1.0.0.tar.gz

Download URL biorobot-1.0.0.tar.gz
Size 2.7 MB
Tags Source
SHA-256 checksum
How to use checksums
c5df686783a24edbc8181ddf734b921260173a407e0fb960364983b127c76c33
BLAKE2b-256 checksum
How to use checksums
98e5c1025423faa4106d638ca74f18f1f0a90a562a2e15250131968d087e20f0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.16

Release files / biorobot-1.0.0-py3-none-any.whl

Download URL biorobot-1.0.0-py3-none-any.whl
Size 2.7 MB
Tags Python 3
SHA-256 checksum
How to use checksums
20941b2aa9dbd23b267129b73d3d220faa8a7c288d1f9bee685192f0aa2315d1
BLAKE2b-256 checksum
How to use checksums
d60d8043930b7c64456f6522269c001b1f49869fd83e302c95917398cb8ccd03
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.16

Security for biorobot 1.0.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page