build ¶
build(scene: Scene | str | Path | dict, worlds: int | None = None, device: str | None = None, seed: int = 0, *, profile: str = 'standard', contacts: str = 'auto', plugins: Sequence[Any] = (), processes: int | str | None = None, mpm_sweeps: str = 'full', rerun_worlds: int = 0, mpm_coupling: str | None = None, contact_capacity: int | None = None, constraint_rows: int | None = None, welds: str | None = None) -> Any
Build scene (a Scene, a path / library scene name for alloy.load, or Scene.to_dict() data).
worlds: parallel copies (default: the scene'sworlds);device: "cuda:0", "cpu", or None for the GPU when there is one;seed: seeds randomized values (Scene.randomize); same seed, same worlds;profile: "standard" (default), "precision" (sub-mm fits; raises if a world goes INVALID), "fast" (throughput, lighter checks) or "debug" (no CUDA graphs);contacts: "auto" (default), "mujoco" or "newton". MuJoCo's own collision treats every mesh as its convex hull, so "auto" picks Newton's contact pipeline when the scene has a mesh with collision "sdf" or "mesh" (cups, bins, holes) and MuJoCo's otherwise;plugins: your ownalloy.pluginsobjects (forces, sensors), or functions of the built scene that return one; they run inside this simulation (sim.pluginsreads them). Heat set on the scene (Scene.set_temperature/add_heater) is added for you.processes: on the CPU, split the worlds over this many worker processes (an int, or "auto": one per core, at most one per world). Each process steps its own block of worlds (Warp's CPU kernels use one core), so many worlds run up to N-cores times faster; a world's trajectory is the same in any process (seeds and randomized values go by global world id). It returns analloy.Sim-like object (alloy.parallel.cpu_worlds.SplitSim) with the same calls;sim.close()stops the workers. Default None: one process.mpm_sweeps: "full" (default) or "partial": pastes (dough, mayonnaise, ...) and other MPM materials re-sweep only grid blocks still moving, same stop test (L4, 256 dough worlds: 1.29x faster, within 0.01 mm; 1 process).mpm_coupling: how a robot or a body coupled to sand, soil or paste (MPM) trades forces with it. "standard": two passes per exchange, 1.25 ms exchanges. "auto": one pass at the standard 1.25 ms exchange when the scene has a servo-driven robot and every body that starts near the material is that robot's link or welded; else standard. It is decided at build, so a free body that reaches the material later (a dropped block) also runs one pass, resting within 0.5 mm of standard. "fast": one pass and 2.5 ms exchanges for every body (a robot in sand runs faster still; a free body landing on the material rests about 3 mm deeper, one resting on it bobs about 15 µm before it settles). None (default): "auto".sim.routes()names the setting and why on the material. One process only (processes=refuses it;ALLOY_MPM_COUPLING, a run-wide override, reaches the workers).rerun_worlds: opt-in, 0 (default) is off. In a scene with several solvers (a robot or free body beside cloth or a soft body), the engine sometimes re-runs a control period for a few worlds (a contact's force jumped, or a world needed a finer exchange). Withrerun_worlds=nthose worlds re-run alone in a second simulation of n worlds, built with this one (memory for n more worlds), instead of every world re-running. The other worlds keep their first run. Results match a whole re-run to rounding (as another world count does). Take about worlds // 16. It helps on the GPU: on the CPU's default MuJoCo route, beside liquid or MPM, or when more worlds re-run than it holds, a re-run runs as before.sim.report()["fine_reruns"]counts the re-runs it took. Not withprocessesor userplugins.contact_capacity: MuJoCo's contact buffer,ncontacts per world on average (nconmax: MuJoCo Warp keeps one pool of n x worlds that the worlds share). Unlessconstraint_rowsis given it also sizes the rows per world from the model: each contact's rows (3 on the elliptic cone) x 2 n plus the scene's joint-limit, equality and friction-loss rows at their maximum (alloy.api._capacity); a world that takes more than 2 n of the pool runs out of rows first: ROW_OVERFLOW, same fix. None (default) sizes it from the scene: 8 per collision shape, at least 64, 6 rows per contact. Past the pool, MuJoCo Warp (the GPU's route) drops the last contacts found; the CPU's MuJoCo C route keeps them all in its physics, but health reads only this many, so the worlds are flagged the same way. Either way they are CONTACT_OVERFLOW and frozen, and the console says so once, at the first overflow (any step), with the n to pass;sim.health()names the worlds. One process only for now (processes=refuses it).constraint_rows: MuJoCo's constraint row buffer, rows per world (njmax), whatever the contact buffer is. None (default) keeps the sizing above; withoutcontact_capacitythat is 6 rows per contact, which for a robot of hundreds of collision shapes is thousands of rows where it uses tens. MuJoCo Warp (the GPU's route) runs its constraint and solver kernels over the whole buffer, so rows sized to the scene step faster, with less memory: on one L4 at 2,048 worlds a Panda holding 13 rows of 4,080 stepped 1.55x faster withcontact_capacity=64, constraint_rows=384, a LEAP hand 2.2x, a G1 (512 worlds, 256 and 768) 1.7x, with the same contacts, rows and solver iterations. A contact takes 3 rows (6 with torsional and rolling friction), and each joint limit in use, friction-loss joint and equality constraint its own: leave about twice what a world uses. With too few, the worlds are ROW_OVERFLOW and frozen (sim.health()), never stepped on missing rows without a flag. The CPU's MuJoCo C route sizes its own rows: the option changes nothing there. One process only for now.welds: "stiff" (the default: MJCF weld / connect rows at MuJoCo's default solref / solimp take the attachments' stiff rule for the rigid step,docs/known_gaps/weld-stretch-under-load.md) or "mujoco".
The first build of a new scene compiles GPU kernels (about a minute; cached afterwards).
Example: sim = alloy.build(scene, worlds=1024).