| name | crazyflow |
|---|---|
| description | Non-obvious traps and conventions in the crazyflow drone simulator. Use when working in the crazyflow codebase, adding a dynamics model or drone platform, reusing the dynamics or controllers outside Sim for estimation or MPC, or writing code under jax.jit, grad or vmap. |
Settings are in pyproject.toml, layout in the tree, API reference in docs/.
- Ruff settings live in
pyproject.tomland must pass before committing. - Dynamics and controllers follow the array API standard. Take the namespace from the inputs with
array_namespace(...), do all math through it, coerce bound parameters withto_xp, and never importnumpyinto a computation path. crazyflow/control/mellinger/params.tomldeliberately holds values that differ from the true physical constants incrazyflow/drones/params.toml, reproducing the real firmware. Do not unify them.
pixi run -e tests tests # what CI runs
pixi run -e tests test-docs # every example in docstrings and docsUse pixi, not uv. pixi run <cmd> resolves task names only, so arbitrary commands need -e tests.
addopts = "-m 'not render'"deselects every render test, andtests/unit/test_visualizations.pyis@skip_if_headlesswithout the marker, so it is skipped silently withoutDISPLAY. Rendering regressions pass CI green. Run-m renderlocally with a display.- Docs and docstrings are executable. The markdown runner catches exceptions but never compares
output, so a
printwith the result in a trailing comment proves nothing. Output that must be checked goes in apyconfence with>>>prompts, which doctest picks up. tests/integration/test_examples.pyruns every script underexamples/, so a new example is a new test.tests/conftest.pyforces JAX's persistent cache on at/tmp/jax_cache, shared across branches. Delete it when failures make no sense.- Request the
devicefixture rather than a GPU marker. It falls back to CPU silently, sogpu-testsasserts nothing about placement on a machine without CUDA.
Grepping the name of an existing model or drone finds every registration site, except when matching
against all models in the simulation's build_control_fns.
Define the function in dynamics.py and never in the package __init__.py, because load_params
derives the model name from fn.__module__.split(".")[-2]. parametrize binds exactly the
keyword-only parameters after the bare *, so anything before it is never bound. Every drone in
available_drones needs a section in every crazyflow/dynamics/*/params.toml, even an empty one.
Registration alone produces roughly 40 parametrized tests. These do not include derivatives tests.
Pure, batched, array-API functions with no dependency on Sim.
- Import crazyflow before scipy.
crazyflow/__init__.pysetsSCIPY_ARRAY_API=1and imports scipy immediately, and scipy cannot be reconfigured once loaded. Transitive imports through acados or sklearn trigger this too. - Three different
load_paramsexist. The two in.corefilter to the target signature and silently drop the rest, so hardware constants likethrust_maxneedcrazyflow.drones.load_params. parametrizereturns afunctools.partialwhosekeywordsdict is shared by every reference to it. Callparametrizeagain for an independent copy.- Leading batch dimensions, trailing feature axis.
quatis scalar-last xyzw,forceis(..., 1)rather than a scalar,ang_velis body frame. ctrl_freqscales only the integral and derivative terms, so it must match your real loop rate. Under jit, initialize integral states to zeros rather thanNone, since the pytree structure change forces a recompile.symbolic_dynamicsmonkey-patches module-global symbols while building, so it is not reentrant. Do not build two concurrently.
Sim rebinds sim.data as a Python side effect that JAX cannot trace, so anything inside jit,
grad, vmap or scan must use crazyflow.sim.functional with Sim only as the builder. See
functional-api.md.
- After editing a pipeline, call
build_step_fn()orbuild_reset_fn()again. The builders snapshot the stages, so a later edit is silently ignored and the stage never runs. n_stepsis static, so each distinct value compiles separately. Build once and reuse.states.forceandstates.torqueare inputs, not outputs, and nothing clears them, so writing them applies a persistent disturbance.- Mutating state does not invalidate
core.mjx_synced, socontacts()can query stale geometry. Set the flag false yourself. - Gradients vanish at
clip_floor_pos(jnp.whereon floor contact) and at any saturation bound.
attach_splats performs no calibration. Both .ply files must already sit in the simulator's
frames, the scene in the MuJoCo world frame at metric scale and the drone in its body frame. An
uncalibrated splat looks correct alone but does not match the simulated geometry. See
splats.md.