Changelog¶
Version 2.1.1¶
Additions¶
- Added
NeuroMechFlyWithSeparateTrochanterFemur, a NeuroMechFly variant in which each leg's trochanter and femur are separate bodies (with separate meshes) rather than a single fused trochanterfemur body. This enables valid segmentation maps for these segments. We tentatively plan to make this the only supported NeuroMechFly body in FlyGym v3 (#313). - Added
GPUSimulation.set_state(),get_base_linear_velocity(), andget_base_angular_velocity()(#305). - Added
forward()to bothSimulationandGPUSimulationto recompute derived quantities (e.g. body positions) without stepping physics. - Lazy getters in
GPUSimulationnow accept an optionaldstarray to write into an existing buffer instead of allocating a new one (#301). - Added
World.flyandSimulation.flyshorthands for single-fly setups,Simulation.fly_lookup, andBaseFly.n_bodysegs,n_jointdofs, andget_n_actuated_jointdofs()(#301). - Added
Retina.fisheye_image_to_hex_pxls(), which fuses fisheye correction and ommatidia pooling into a single pass. CPU vision now uses it, making this step about 2× faster (#308, #315; thanks @Cr0me1ve). - Added
scripts/export_model_for_flexik.pyto export the fly body plan for FlexIK. - Added trajectory recording, which decouples the number of simulated worlds from the number of rendered ones.
Simulation.set_trajectory_recorder()andGPUSimulation.set_trajectory_recorder()attach a recorder that stores only the kinematic state (qposand mocap poses) at the video frame rate. The resultingRecordedTrajectoryobjects can be saved, sub-selected, and rendered to video afterwards withflygym.recording.render_trajectories()(CPU) orflygym.warp.recording.render_trajectories_gpu()(GPU batch rendering). A recorder can be used together with a renderer, and rendering a recorded trajectory gives exactly the same frames as rendering live. See the new tutorial 8. Recording and rendering trajectories (#296, #319).
Changes¶
- Renderers now draw a private copy of the simulation state in which body, camera, light, and tendon positions are recomputed from the current
qpos. Previously, they drew the positions computed at the start of the last physics step, so frames lagged by one timestep. The simulation state itself is not modified (#319). WarpGPUBatchRenderernow renders only the selectedworldsinstead of all simulated worlds, so rendering a few out of many worlds is much faster and uses less GPU memory (#319).GPUSimulation.reset()now resetsmjw_datain place using MuJoCo Warp'sreset_data_keyframe(), instead of reallocating the GPU-side model and data on every reset (#302). Note that, as withSimulation.reset(), derived kinematic quantities (xpos,xquat, …) are not recomputed on reset; callforward()if you need them.- Remote assets are now stored as tarballs with SHA-256 checksums. FlyGym verifies the checksum after download and moves the extracted files into the cache atomically, which also makes concurrent downloads safe (#303).
GPUSimulationnow uses a constraint solver tolerance of 1e-5 by default, configurable via the newsolver_toleranceargument. Since MuJoCo Warp 3.10, which fixed a float32 precision issue in its convergence check, MuJoCo Warp's default tolerance (1e-6) causes a small fraction of worlds to stall until they hit the solver iteration limit, printing "solver iterations limit reached" warnings. Because all worlds wait for the slowest one, this slowed down GPU simulation by ~25% with 1000 worlds. With 1e-5, these stalls are essentially gone, while joint-angle tracking is unchanged within GPU run-to-run variability.GPUSimulationnow disables MuJoCo Warp's solver and line search iteration limit warnings, which are printed once per world per step and could flood the output. This follows MuJoCo Playground. Warnings about contact and constraint buffer overflows are still shown.- The WASM NeuroMechFly Live game is easier to play: stronger leg adhesion, faster turning, gamepad leg buttons matching the physical left/right layout, and correct colors for split trochanter/femur geoms (#314).
Bug fixes¶
- Fixed body-segment position/orientation getters returning wrong values when MuJoCo's
fusestaticcompiler option merges bodies and invalidates body IDs (#312). - Fixed the ground texture not showing in GPU batch rendering after the MuJoCo Warp 3.16 upgrade. MuJoCo Warp now maps textures on finite planes like the CPU renderer does, so FlyGym's workaround of scaling
texrepeatdown by 1000× was removed. - Fixed
GPUSimulationdropping thetimestepoverride when GPU batch rendering is enabled, which recompiles the model. - Fixed GPU benchmark and tutorial timings, which stopped the timer before queued GPU work finished and therefore overstated throughput by up to ~50% (#311; thanks @AdeliyaLeleytner).
Housekeeping¶
- Bumped MuJoCo and MuJoCo Warp versions to 3.16.
- FlyGym now supports Python 3.12 – 3.15. The
rlextra is not yet installable on Python 3.15 because PyTorch does not ship wheels for it. - Updated Warp kernel type hints to the PEP 484-compliant
wp.array[dtype]syntax and replaced string array dimensions withTypeVars defined inflygym.utils.typing(#304). - Unit tests can now run in parallel via
pytest-xdist(pytest -n auto).
Contributors¶
@sibocw, @stimpfli, @AdeliyaLeleytner, @Cr0me1ve
Version 2.1.0¶
API-breaking changes
FlyGym 2.1.0 drops the dm-control PyMJCF backend in favour of MuJoCo's native MjSpec API
(#282, #285).
Model elements (cameras, materials, bodies, joints, …) are now mujoco._specs.Mjs* objects rather than dm_control.mjcf wrappers. The following changes may require updates:
-
element.full_identifier→element.name: PyMJCF exposedfull_identifierto return the fully-scoped compiled name (e.g."fly/trackcam"). MjSpec mutates.namein place when a child spec is attached with a prefix, so.namealready returns the prefixed name afterworld.add_fly(). Replace every occurrence of.full_identifierwith.name.Migration: If you stored references to spec elements (e.g. the
MjsCamerareturned byfly.add_tracking_camera()) and later usedelement.full_identifieras a dict key or to look up the compiled ID, replace it withelement.name:# Before (PyMJCF) cam = fly.add_tracking_camera(name="body_cam") world.add_fly(fly, ...) frames = renderer.frames[cam.full_identifier] # AttributeError under MjSpec # After (MjSpec) cam = fly.add_tracking_camera(name="body_cam") world.add_fly(fly, ...) frames = renderer.frames[cam.name] # "fly_name/body_cam" after attachmentNo other change is needed:
cam.namealready returns the same prefixed string thatcam.full_identifierused to return, because MjSpec mutates held element references in place when the child spec is attached. -
spec.asset.find_all("material")→spec.materials: The PyMJCF tree-traversal helper is gone. MjSpec exposes typed collection properties (spec.materials,spec.bodies,spec.joints, etc.) directly on theMjSpecobject. -
add_tracking_cameraplacement is now relative to the fly's root segment:Fly.add_tracking_camera()now adds the camera inside the fly's root segment (thorax) body so that MuJoCo'strackmode actually follows the fly as it moves. As a result,pos_offset(androtation) are interpreted in the root segment's body frame rather than in world coordinates as before, and the defaultpos_offsetchanged from(0, -7.5, 6)to(-0.5, -7.5, 5). The samepos_offsetnow yields the same camera position relative to the fly in every context (FlatGroundWorld,TetheredWorld, or a fly compiled on its own forpreview_model).Migration: Any hard-coded
pos_offsetvalues must be re-tuned. A value that previously positioned the camera in the world frame now positions it relative to the root segment, which in the neutral pose sits roughly(0.5, 0, 1.3)mm from the fly's attachment point plus the spawn height. The camera will therefore appear higher and shifted toward the head unless you adjust the offset (to reproduce the old framing, subtract the root segment's rest position from your previous world-frame offset). -
World.world_dof_neutral_statesis now aset, not adict: Under MjSpec, the neutral pose of world-level DoFs (e.g. the free joints by which flies are attached) is read from the compiled model'sqpos0rest configuration. The explicit per-DoF state values thatWorld.world_dof_neutral_statesused to map to are therefore no longer needed, so the attribute is now aset[str]of DoF (joint) names rather than adict[str, list[float]]. Relatedly,World._attach_fly_mjcfnow returns aset[str]of the DoF names it created instead of a name → state mapping.Migration: If you read this attribute, iterate it as a set of names (iterating a
dictalready yielded its keys, so plainfor name in world.world_dof_neutral_statesis unaffected); subscripting it (world.world_dof_neutral_states[name]) no longer works. If you implement a customWorldsubclass, return aset[str]from_attach_fly_mjcf.
Additions¶
- [Experimental feature] Integrated FlyMimic (Özdil et al., 2026): an imitation-learning task and a muscle-actuated musculoskeletal fly model (#270). A tutorial is also added.
- [Experimental feature] Integrated the FlyBody model (#272). Two tutorials are also added.
- Added an in-browser interactive viewer using WebAssembly (#273).
- Moved the NeuroMechFly Live game into FlyGym and reimplemented it to run in-browser using WebAssembly (#277, #278).
- Added standalone kinematic-replay scripts for end-to-end testing and profiling (#283). A tutorial on profiling is also added.
- Added Docker installation instructions.
- Tutorial are now runnable in Google Colab (#284).
- Bumped MuJoCo and MuJoCo Warp versions to 3.9 (#281, #291).
- FlyGym now supports Python 3.12 – 3.14.
- Moved large asset files to an S3 bucket and made FlyGym download them lazily (#280, #292).
- Added unit tests that execute the tutorial notebooks and check for errors (#286).
Bug fixes¶
- Fixed geom fitting that forced claw (tarsus5) geoms to capsules even when
UNMODIFIED(#274). - Fixed packaging so the
flygympackage's bundled assets (simplified meshes, poses, configs, and the musculoskeletal MJCF) are reliably shipped in the built wheel/sdist. They are now declared explicitly under[tool.setuptools.package-data]rather than relying oninclude-package-data, which silently included nothing on a clean build (no MANIFEST.in / setuptools-scm).
Housekeeping¶
- Switch from MkDocs to ProperDocs, a fork of MkDocs 1.x.x that will continue to support for plugins like Material for MkDocs (see this note).
- Added packaging tests (
tests/core/test_packaging.py) that build a wheel and assert every bundled asset is shipped (large lazily-downloaded meshes excluded), withflygym_demoassets checked separately.
Version 2.0.2¶
API-breaking changes
- Adhesion control range:
Fly.add_leg_adhesion()andSimulation.set_leg_adhesion_states()now use a normalised[0, 1]control range (0 = fully released, 1 = full gain) instead of the previous[1, 100]range. Boolean0/1adhesion commands that previously fell outside the valid range will now work as expected. get_ground_contact_info()first return value: renamed fromcontact_activetocontact_foundand now returns the raw MuJoCo contact-sensorfoundchannel (a floating-point value) rather than a booleanized flag. Code that comparedcontact_active == 1should now checkcontact_found > 0.ContactParams.get_solimp_tuple(): now returns a 5-element tuple (was 4). The previously missingsolver_impedance_min2max_widthfield is now included as the third element ([dmin, dmax, width, midpoint, power]), matching MuJoCo'ssolimpspecification. Any code that destructured or indexed the old 4-tuple needs to be updated.
Bug fixes¶
ContactParams.get_solimp_tuple()previously returned a 4-element tuple, causing MuJoCo to silently use a default value for the transition-width parameter. It now returns the correct 5-element tuple.
Additions¶
- Added vision to CPU-based simulation. Addition of vision to GPU-based simulation will be deferred for now.
- Added
GappedTerrainWorld,BlocksTerrainWorld, andMixedTerrainWorldtoflygym.compose. - Added
Simulation.get_bodysegment_contact_forces()for querying ground-contact forces on arbitrary body segments. - Added locomotion controller examples (
CPGController,RuleBasedController,HybridController,HybridTurningController) inflygym_demo.complex_terrain. Added tutorials accordingly.
Housekeeping¶
- Switched formatter from Black to Ruff
Version 2.0.1 (2026–04–16)¶
Bug fixes¶
- Include sample Spotlight data in shipped
flygym_demobuild (#259). - Fix dimension check for 3D rotations specified in
axisangleformat. - Fix bugs with creating
AnatomicalJointandJointDOFobjects from strings.
Additions¶
- New API to add sites to fly model and read out their positions
- Additional tutorial: 1bis. Advanced model composition
Housekeeping¶
- Add changelog and correct info in LICENSE
Contributors¶
@sibocw, @djsamseng
Version 2.0.0 (2026–04–02)¶
Full rewrite — not backward-compatible
This version is a full rewrite of the codebase and it is not backward-compatible. See this page for more info.
Version 1.2.1¶
- Improve vision readout computing time by avoiding redundant memory copies
- Bump NumPy version to 2.* and end official support for macOS 13
Version 1.2.0¶
API-breaking changes
- Enhance camera logic. See the Camera API reference for details.
- Use the FlyVis package as published on PyPI. Users should now use
flyvisinstead offlyvisionas module name.
Other changes¶
- Add NeuroMechFly game for outreach (used at EPFL Scientastic! Days, etc.).
- Transition from
setup.pytopyproject.toml, specifically using Poetry as the build backend. Users can now install FlyGym with the exact versions of dependencies used by the developers by runningpoetry installfrom the root directory. This will create a virtual environment with the correct dependencies as specified in the includedpoetry.lockfile, which is version-tracked as a part of the FlyGym Github repository. - Add VS Code devcontainer support.
- Remove support for Python 3.9.
Version 1.1.0¶
- Added cardinal direction sensing (vectors describing +x, +y, +z of the fly) to the observation space.
- Removed legacy spawn orientation preprocessing: Previously, pi/2 was subtracted from the user-specified spawn orientation on the x-y plane. This was to make the behavior consistent with a legacy version of NeuroMechFly. This behavior is no longer desired; from this version onwards, the spawn orientation is used as is.
- Strictly fixed the required MuJoCo version to 3.2.3, and dm_control version to 1.0.23. This is to prevent API-breaking changes in future versions of these libraries from affecting FlyGym. FlyGym maintainers will periodically check for compatibility with newer versions of these libraries.
- Changed flip detection method: Previously, flips are reported when all legs reliably lose contact with the ground. Now, we simply check if the z component of the "up" cardinal vector is negative. Additionally, the
detect_flipparameter ofFlyis now deprecated; flips are always detect and reported. - Allowed different sets of DoFs to be monitored vs. actuated. Previously, the two sets are always the same.
- From this version onwards, we will use EffVer as the versioning policy. The version number will communicate how much effort we expect a user will need to spend to adopt the new version. While we previously tried to adhere to the stricter SemVer, we found that it was not effective because many core dependencies of FlyGym (e.g., MuJoCo, NumPy, and Python itself) do not use SemVer.
Version 1.0.1¶
Fixed minor bugs related to the set of DoFs in the predefined poses, and to rendering at extremely high frequencies. Fixed outdated class names and links in the docs. In addition, contact sensor placements used by the hybrid turning controller are now added to the preprogrammed module.
Version 1.0.0¶
In spring 2024, NeuroMechFly was used, for the second time, in a course titled Controlling behavior in animals and robots at EPFL. At the same time, we revised the NeuroMechFly v2 manuscript. In these processes, we significantly improved the FlyGym package, added new functionalities, and incorporated changes as we received feedback from the students. These enhancements are released as FlyGym version 1.0.0. This release is not backward compatible; please refer to the tutorials and API references for more information. The main changes are:
Major API changes
- The
NeuroMechFlyclass is split intoFly, a class that represents the fly, andSimulation, a class that represents the simulation, which can potentially contain multiple flies. - The
Parametersclass is deprecated. Parameters related to the fly (such as joint parameters, actuated DoFs, etc.) should be set directly on theFlyobject. Parameters related to the simulation (such as the time step, the render cameras, etc.) should be set directly on theSimulationobject. - A new
Cameraclass is introduced. A simulation can contain multiple cameras.
New examples¶
See examples:
- Path integration based on ascending mechanosensory feedback.
- Head stabilization based on ascending mechanosensory feedback.
- Navigating a complex plume, simulated separately in a fluid mechanics simulator.
- Following another fly using a realistic, connectome-constrained neural network that processes visual inputs.
Version 0.2.5¶
Modify model file to make it compatible with MuJoCo 3.1.1. Disable Python 3.7 support accordingly.
Version 0.2.4¶
Set MuJoCo version to 2.3.7. Documentation updates.
Version 0.2.3¶
Various bug fixes. Improved placement of the spherical treadmill in the tethered environment.
Version 0.2.2¶
Changed default joint kp and adhesion forces to those used in the controller comparison task. Various minor bug fixes. Documentation updates.
Version 0.2.1¶
Simplified class names: NeuroMechFlyMuJoCo → NeuroMechFly, MuJoCoParameters → Parameters. Minor documentation updates.
Version 0.2.0¶
The current base version — major API change from 0.1.x.
Version 0.1.x¶
Versions used during the initial development of NeuroMechFly v2.
Unversioned¶
Versions used for the Spring 2023 offering of BIOENG-456 Controlling Behavior in Animals and Robots course at EPFL.