physics_engine
Dynamic Simulator
Physics engine for dynamic walking simulation.
Uses the 2D physics engine pymunk (chipmunk) for planar mechanism simulation. Manages the simulation space, road generation, and energy tracking.
- leggedsnake.physics_engine.DEFAULT_CONFIG = WorldConfig(gravity=(0, -9.80665), physics_period=0.02, torque=100.0, load_mass=10.0, ground_friction=0.7071067811865476, terrain=TerrainConfig(slope=0.17453292519943295, max_step=0.5, step_freq=0.1, noise=0.9, section_len=1.0, friction=0.7071067811865476, friction_range=None, seed=None, gap_freq=0.0, gap_width=1.0, obstacle_freq=0.0, obstacle_height=0.3, obstacle_width=0.4, slope_profile=<SlopeProfile.RANDOM: 'random'>, wave_period=10.0, wave_sweep_rate=0.0), payload_offset=(0.0, 0.0), wind_force=(0.0, 0.0), drag_coefficient=0.0)
Module-level default configuration, used when no config is passed.
- class leggedsnake.physics_engine.GroundParams
Bases:
TypedDict- friction: float
- max_step: float
- noise: float
- section_len: float
- slope: float
- step_freq: float
- class leggedsnake.physics_engine.LinkageParams
Bases:
TypedDict- crank_len: float
- load: float
- masses: float
- torque: float
- class leggedsnake.physics_engine.Params
Bases:
TypedDict- ground: GroundParams
- linkage: LinkageParams
- physics: PhysicsParams
- simul: SimulParams
- class leggedsnake.physics_engine.PhysicsParams
Bases:
TypedDict- gravity: tuple[float, float]
- max_force: float
- leggedsnake.physics_engine.SlopeGenerator
Registry mapping profile keys to generator callables. Signature:
(terrain, rng, step_counter) -> angle_in_radiansalias of
Callable[[TerrainConfig,Generator,int],float]
- class leggedsnake.physics_engine.SlopeProfile(*values)
Bases:
EnumNamed slope profiles for repeatable terrain generation.
Each value is a string key; the actual generator is looked up in
SLOPE_PROFILES.- CONSTANT = 'constant'
Constant uphill at the configured slope angle.
- FLAT = 'flat'
Perfectly flat ground (slope = 0).
- FREQUENCY_SWEEP = 'frequency_sweep'
Linear chirp — wave frequency grows with distance, controlled by
TerrainConfig.wave_sweep_rate. Useful for probing the speed response of a walker across a range of terrain frequencies in one run.
- RANDOM = 'random'
Default Gaussian-distributed random slopes.
- SAWTOOTH = 'sawtooth'
Repeating climb-then-drop pattern.
- SINUSOIDAL = 'sinusoidal'
Sinusoidal undulation with period
TerrainConfig.wave_period.
- VALLEY = 'valley'
V-shaped descent then ascent.
- class leggedsnake.physics_engine.TerrainConfig(slope: float = 0.17453292519943295, max_step: float = 0.5, step_freq: float = 0.1, noise: float = 0.9, section_len: float = 1.0, friction: float = 0.7071067811865476, friction_range: tuple[float, float] | None = None, seed: int | None = None, gap_freq: float = 0.0, gap_width: float = 1.0, obstacle_freq: float = 0.0, obstacle_height: float = 0.3, obstacle_width: float = 0.4, slope_profile: SlopeProfile | Callable[[TerrainConfig, Generator, int], float] | str = SlopeProfile.RANDOM, wave_period: float = 10.0, wave_sweep_rate: float = 0.0)
Bases:
objectTerrain generation parameters.
- __init__(slope: float = 0.17453292519943295, max_step: float = 0.5, step_freq: float = 0.1, noise: float = 0.9, section_len: float = 1.0, friction: float = 0.7071067811865476, friction_range: tuple[float, float] | None = None, seed: int | None = None, gap_freq: float = 0.0, gap_width: float = 1.0, obstacle_freq: float = 0.0, obstacle_height: float = 0.3, obstacle_width: float = 0.4, slope_profile: SlopeProfile | Callable[[TerrainConfig, Generator, int], float] | str = SlopeProfile.RANDOM, wave_period: float = 10.0, wave_sweep_rate: float = 0.0) None
- friction: float = 0.7071067811865476
Ground friction coefficient (square root of mu).
- friction_range: tuple[float, float] | None = None
If set, friction is randomized uniformly within this (min, max) range for each new road segment, overriding
friction.
- static from_preset(preset: TerrainPreset | str) TerrainConfig
Create a
TerrainConfigfrom a named preset.- Parameters:
preset (TerrainPreset or str) – One of the built-in terrain presets.
- gap_freq: float = 0.0
Probability of a gap (chasm) instead of a solid segment.
- gap_width: float = 1.0
Width of gaps in meters.
- max_step: float = 0.5
Maximum step height.
- noise: float = 0.9
Terrain variation factor (should be ≤ 1).
- obstacle_freq: float = 0.0
Probability of placing a rectangular obstacle on a segment.
- obstacle_height: float = 0.3
Maximum obstacle height in meters.
- obstacle_width: float = 0.4
Obstacle width in meters.
- section_len: float = 1.0
Length of each road section.
- seed: int | None = None
Random seed for reproducible terrain generation. None means non-deterministic.
- slope: float = 0.17453292519943295
Nominal slope in radians.
- slope_profile: SlopeProfile | Callable[[TerrainConfig, Generator, int], float] | str = 'random'
Slope generation strategy. Accepts a
SlopeProfileenum, a string key fromSLOPE_PROFILES, or a custom callable with signature(terrain, rng, step_counter) -> angle.
- step_freq: float = 0.1
Probability of a step vs. a slope segment.
- wave_period: float = 10.0
Spatial wavelength in metres for
SINUSOIDALandFREQUENCY_SWEEPslope profiles. Ignored by other profiles.
- wave_sweep_rate: float = 0.0
Frequency sweep rate (cycles per metre²) for
FREQUENCY_SWEEP. With0the chirp degenerates to a pure sinusoid.
- class leggedsnake.physics_engine.TerrainPreset(*values)
Bases:
EnumReady-made terrain configurations.
- FLAT = 'flat'
- HILLY = 'hilly'
- MIXED = 'mixed'
- ROUGH = 'rough'
- SINUSOIDAL = 'sinusoidal'
Smooth undulating ground for measuring speed-variance response.
- SLOPE_DOWN = 'slope_down'
Constant downhill grade — pairs with the Phase 9 terrain panel.
- SLOPE_UP = 'slope_up'
Constant uphill grade — pairs with the Phase 9 terrain panel.
- STAIRS = 'stairs'
- class leggedsnake.physics_engine.World(space: Space | None = None, road_y: float = -5, config: WorldConfig | None = None)
Bases:
objectSimulation world containing a pymunk space, linkages, and a road.
Not intended to be rendered visually per se, see VisualWorld for that.
- Parameters:
space (pm.Space | None) – Pymunk space. Created automatically if None.
road_y (float) – Initial road height.
config (WorldConfig | None) – Simulation parameters. Uses
DEFAULT_CONFIGwhen None.
- __init__(space: Space | None = None, road_y: float = -5, config: WorldConfig | None = None) None
- _apply_ground_filter() None
Tag all static-body shapes with the ground collision filter.
Called once when a linkage with foot-edge filtering is added. Also patches newly built road segments in the road-building methods via
_tag_ground_segment.
- _rng: Generator
- _segment_counter: int
- _segment_friction(terrain: TerrainConfig) float
Return friction for a new road segment.
- _slope_fn: Callable[[TerrainConfig, Generator, int], float]
- _tag_ground_segment(seg: Segment) None
Apply ground collision filter to seg when foot filtering is on.
- add_linkage(source: Any, load: float | None = None) None
Add a linkage to the simulation.
- Parameters:
source (Walker, DynamicLinkage, or legacy Linkage) – The mechanism to simulate.
load (float, optional) – Chassis mass override (only used when converting from Walker/Linkage). When None (the default) the value from
WorldConfig.load_massis used.
- build_road(positive: bool = False) None
Build a road part.
- Parameters:
positive (bool) – If False (default) the road is extended to the left.
- config: WorldConfig
- linkages: list[DynamicLinkage]
- road: list[tuple[float, float]]
- space: Space
- tune_solver() None
Auto-tune solver parameters for stability.
- update(dt: float | None = None) tuple[float, float] | None
Update simulation by one time step.
- Parameters:
dt (float | None) – Time step. Uses
config.physics_periodif None.
- class leggedsnake.physics_engine.WorldConfig(gravity: tuple[float, float] = (0, -9.80665), physics_period: float = 0.02, torque: float = 100.0, load_mass: float = 10.0, ground_friction: float = 0.7071067811865476, terrain: ~leggedsnake.physics_engine.TerrainConfig = <factory>, payload_offset: tuple[float, float] = (0.0, 0.0), wind_force: tuple[float, float] = (0.0, 0.0), drag_coefficient: float = 0.0)
Bases:
objectComplete simulation configuration.
Replaces the global
paramsdict with a structured, immutable-by-default configuration object. Pass toWorld(config=...)to parameterize a simulation; or omit to useDEFAULT_CONFIG.Examples
>>> cfg = WorldConfig(gravity=(0, -5.0), physics_period=0.01) >>> world = World(config=cfg)
- __init__(gravity: tuple[float, float] = (0, -9.80665), physics_period: float = 0.02, torque: float = 100.0, load_mass: float = 10.0, ground_friction: float = 0.7071067811865476, terrain: ~leggedsnake.physics_engine.TerrainConfig = <factory>, payload_offset: tuple[float, float] = (0.0, 0.0), wind_force: tuple[float, float] = (0.0, 0.0), drag_coefficient: float = 0.0) None
- drag_coefficient: float = 0.0
Linear drag coefficient (N·s/m). Each step, a force
-drag_coefficient * chassis_velocityis added to the chassis, modelling air / fluid drag.0disables drag.
- gravity: tuple[float, float] = (0, -9.80665)
Gravity vector (m/s²).
- ground_friction: float = 0.7071067811865476
Ground friction coefficient (square root of mu).
- load_mass: float = 10.0
Default chassis (frame) mass (kg). Applied automatically when a Walker is passed to
World.add_linkage()without an explicitloadoverride (before 0.5.0 the value was silently dropped — users had to passadd_linkage(walker, load=cfg.load_mass)to get it).
- payload_offset: tuple[float, float] = (0.0, 0.0)
Offset (metres) of the chassis centre of gravity relative to its reference position.
(0, 0)keeps the default behaviour; non-zero values simulate an uneven / off-centre payload.
- physics_period: float = 0.02
Time step for each physics computation (s).
- terrain: TerrainConfig
Terrain generation parameters.
- torque: float = 100.0
Maximum motor torque (N·m). Previously
1e3; lowered in 0.5.0 after confirming the old default overdrove typical Strandbeest/Klann walkers into pitch chaos before the stance phase could react.
- wind_force: tuple[float, float] = (0.0, 0.0)
Constant external force (Newtons) applied to each linkage’s chassis every physics step.
(+x, 0)models a steady headwind or tailwind. Applied viabody.apply_force_at_world_point— does not stack with user-applied forces.
- leggedsnake.physics_engine._preset_flat() TerrainConfig
- leggedsnake.physics_engine._preset_hilly() TerrainConfig
- leggedsnake.physics_engine._preset_mixed() TerrainConfig
- leggedsnake.physics_engine._preset_rough() TerrainConfig
- leggedsnake.physics_engine._preset_sinusoidal() TerrainConfig
- leggedsnake.physics_engine._preset_slope_down() TerrainConfig
- leggedsnake.physics_engine._preset_slope_up() TerrainConfig
- leggedsnake.physics_engine._preset_stairs() TerrainConfig
- leggedsnake.physics_engine._slope_constant(terrain: TerrainConfig, _rng: Generator, _step: int) float
- leggedsnake.physics_engine._slope_flat(_terrain: TerrainConfig, _rng: Generator, _step: int) float
- leggedsnake.physics_engine._slope_frequency_sweep(terrain: TerrainConfig, _rng: Generator, step: int) float
Linear chirp: frequency grows linearly with distance.
Phase
φ(x) = 2π (x / wave_period + sweep_rate · x² / 2)so the instantaneous wavelength shortens asxincreases.sweep_ratehas units of cycles per metre². Withsweep_rate = 0the profile reduces to_slope_sinusoidal().
- leggedsnake.physics_engine._slope_random(terrain: TerrainConfig, rng: Generator, _step: int) float
Gaussian-distributed slope (original behaviour).
- leggedsnake.physics_engine._slope_sawtooth(terrain: TerrainConfig, _rng: Generator, step: int) float
Climb for 10 segments, then a steep single-segment drop.
- leggedsnake.physics_engine._slope_sinusoidal(terrain: TerrainConfig, _rng: Generator, step: int) float
Smooth sinusoidal undulation in physical x-space.
Amplitude is
terrain.slope; spatial period isterrain.wave_periodmetres. Withwave_period <= 0the slope collapses to 0.
- leggedsnake.physics_engine._slope_valley(terrain: TerrainConfig, _rng: Generator, step: int) float
V-shape: descend for 20 segments, then ascend.
- leggedsnake.physics_engine.linkage_bb(linkage: Any) tuple[float, float, float, float]
Return the bounding box (min_y, max_x, max_y, min_x) for a linkage.
- leggedsnake.physics_engine.recalc_linkage(linkage: DynamicLinkage) None
Update all joint proxy coordinates from physics bodies.
- leggedsnake.physics_engine.set_space_constraints(space: Space) None
Auto-tune solver parameters based on constraint count.