fitness
Physics-aware fitness protocol and built-in evaluators.
Physics-aware fitness protocol for walking mechanism evaluation.
Provides a standardized DynamicFitness protocol and FitnessResult
dataclass for evaluating walking mechanisms. Built-in implementations
cover common objectives (distance, efficiency, stride length). Adapter
functions bridge to pylinkage’s optimizer contracts.
Example:
from leggedsnake import (
DistanceFitness, EfficiencyFitness, StrideFitness,
as_eval_func, multi_objective_optimization,
)
# Use directly
fitness = DistanceFitness(duration=10.0, n_legs=2)
result = fitness(walker.topology, walker.dimensions)
print(result.score, result.metrics)
# Adapt to pylinkage optimizer contract
objectives = [
as_eval_func(DistanceFitness(duration=40.0, n_legs=4)),
as_eval_func(EfficiencyFitness(duration=40.0, n_legs=4)),
]
- class leggedsnake.fitness.CompositeFitness(duration: float = 40.0, n_legs: int = 4, motor_rates: float | dict[str, float] = -4.0, objectives: Sequence[str] = ('distance', 'efficiency', 'stability'), mirror: bool = False, contact_threshold: float = 0.1, loci_stride: int = 1)
Bases:
objectEvaluate multiple objectives in a single simulation run.
Runs physics once, then extracts distance, efficiency, stability, and gait metrics from the shared simulation data. This avoids redundant simulation when optimizing multiple objectives.
The primary
scoreis the distance; all requested objectives appear inFitnessResult.metrics.- Parameters:
duration (float) – Simulation duration in seconds.
n_legs (int) – Number of leg pairs.
motor_rates (float | dict[str, float]) – Motor angular velocity.
objectives (sequence of str) – Which metrics to compute. Supported:
"distance","efficiency","stability","gait".contact_threshold (float) – Y-coordinate below which a foot counts as in ground contact. Used when
"gait"is inobjectives.loci_stride (int) – Subsample loci recording — keep every
loci_stride-th step. At default1every physics step is recorded; raising it to e.g.10cuts loci memory 10× at the cost of trajectory resolution.analyze_gaitis automatically rescaled vialoci_dtso gait timing stays correct, but very large strides degrade event-detection accuracy.
- __call__(topology: HypergraphLinkage, dimensions: Dimensions, config: WorldConfig | None = None) FitnessResult
Call self as a function.
- __init__(duration: float = 40.0, n_legs: int = 4, motor_rates: float | dict[str, float] = -4.0, objectives: Sequence[str] = ('distance', 'efficiency', 'stability'), mirror: bool = False, contact_threshold: float = 0.1, loci_stride: int = 1) None
- class leggedsnake.fitness.DistanceFitness(duration: float = 40.0, n_legs: int = 4, motor_rates: float | dict[str, float] = -4.0, record_loci: bool = False, mirror: bool = False)
Bases:
objectEvaluate total walking distance via physics simulation.
- Parameters:
duration (float) – Simulation duration in seconds.
n_legs (int) – Total number of legs when
mirror=False(one-sided stack). Legs per side whenmirror=True— total is2 * n_legs.mirror (bool) – If True, call
add_opposite_leg()before phase-offset copies, producing a left/right-symmetric walker. The canonical Jansen Strandbeest isn_legs=4, mirror=True(4 per side = 8 total). Default False preserves the one-sided behavior that predates this flag.motor_rates (float | dict[str, float]) – Motor angular velocity passed to Walker.
record_loci (bool) – If True, record joint positions at each physics step.
- __call__(topology: HypergraphLinkage, dimensions: Dimensions, config: WorldConfig | None = None) FitnessResult
Call self as a function.
- __init__(duration: float = 40.0, n_legs: int = 4, motor_rates: float | dict[str, float] = -4.0, record_loci: bool = False, mirror: bool = False) None
- class leggedsnake.fitness.DynamicFitness(*args, **kwargs)
Bases:
ProtocolProtocol for physics-based fitness evaluation of walking mechanisms.
Implementations accept a topology and dimensions (the mechanism specification) plus an optional simulation configuration, and return a
FitnessResultwith a primary score and secondary metrics.The
topology+dimensionssignature means the same fitness works for any mechanism — not just a specific hand-coded linkage.- __call__(topology: HypergraphLinkage, dimensions: Dimensions, config: WorldConfig | None = None) FitnessResult
Call self as a function.
- __init__(*args, **kwargs)
- class leggedsnake.fitness.EfficiencyFitness(duration: float = 40.0, n_legs: int = 4, motor_rates: float | dict[str, float] = -4.0, min_distance: float = 5.0, record_loci: bool = False, mirror: bool = False)
Bases:
objectEvaluate energy efficiency via physics simulation.
Returns
total_efficiency / total_energyas the primary score. Returns 0 if the walker doesn’t travel at leastmin_distance.- Parameters:
duration (float) – Simulation duration in seconds.
n_legs (int) – Total legs (
mirror=False) or legs per side (mirror=True).mirror (bool) – If True, build a symmetric walker via
add_opposite_leg()before phase-offset copies. SeeDistanceFitness.motor_rates (float | dict[str, float]) – Motor angular velocity passed to Walker.
min_distance (float) – Minimum distance the walker must cover for a non-zero score.
record_loci (bool) – If True, record joint positions at each physics step.
- __call__(topology: HypergraphLinkage, dimensions: Dimensions, config: WorldConfig | None = None) FitnessResult
Call self as a function.
- __init__(duration: float = 40.0, n_legs: int = 4, motor_rates: float | dict[str, float] = -4.0, min_distance: float = 5.0, record_loci: bool = False, mirror: bool = False) None
- class leggedsnake.fitness.FitnessResult(score: float, metrics: dict[str, float] = <factory>, valid: bool = True, loci: dict[str, list[tuple[float, float]]] = <factory>)
Bases:
objectRich result from a dynamic fitness evaluation.
- Variables:
score (float) – Primary fitness value (higher is better by convention).
metrics (dict[str, float]) – Secondary metrics (e.g.,
{"distance": 12.3, "energy": 45.6}). These feed directly into multi-objective optimization.valid (bool) – Whether the simulation completed without collapse or unbuildable error.
loci (dict[str, list[Point]]) – Joint trajectories keyed by node ID. Empty when
record_loci=False.
- __init__(score: float, metrics: dict[str, float] = <factory>, valid: bool = True, loci: dict[str, list[tuple[float, float]]] = <factory>) None
- loci: dict[str, list[tuple[float, float]]]
- metrics: dict[str, float]
- score: float
- valid: bool = True
- class leggedsnake.fitness.GaitFitness(duration: float = 40.0, n_legs: int = 4, motor_rates: float | dict[str, float] = -4.0, mirror: bool = False, contact_threshold: float = 0.1, record_loci: bool = False, loci_stride: int = 1)
Bases:
objectEvaluate gait quality via physics simulation.
Runs the full dynamic sim, analyzes the foot trajectories into touchdown / liftoff events, and reports stride, duty-factor, asymmetry, and energy-per-cycle metrics. The primary score is
mean_stride_length(simulation-frame distance between consecutive touchdowns of the same foot), so higher is better.- Parameters:
duration (float) – Simulation duration in seconds.
n_legs (int) – Total legs (
mirror=False) or legs per side (mirror=True).mirror (bool) – Mirror the leg across the chassis via
add_opposite_leg().motor_rates (float | dict[str, float]) – Motor angular velocity.
contact_threshold (float) – Y-coordinate below which a foot counts as in ground contact.
record_loci (bool) – If True, return joint trajectories in
FitnessResult.loci. Gait analysis runs regardless — this only controls what’s returned.loci_stride (int) – Subsample loci recording — see
CompositeFitnessfor details. Lower values cost more memory; higher values reduce gait event-detection accuracy.
- __call__(topology: HypergraphLinkage, dimensions: Dimensions, config: WorldConfig | None = None) FitnessResult
Call self as a function.
- __init__(duration: float = 40.0, n_legs: int = 4, motor_rates: float | dict[str, float] = -4.0, mirror: bool = False, contact_threshold: float = 0.1, record_loci: bool = False, loci_stride: int = 1) None
- class leggedsnake.fitness.StabilityFitness(duration: float = 40.0, n_legs: int = 4, motor_rates: float | dict[str, float] = -4.0, min_distance: float = 2.0, record_loci: bool = False, mirror: bool = False)
Bases:
objectEvaluate walking stability via physics simulation.
Primary score is the mean tip-over margin. Stability metrics are included in
FitnessResult.metrics.- Parameters:
duration (float) – Simulation duration in seconds.
n_legs (int) – Number of leg pairs.
motor_rates (float | dict[str, float]) – Motor angular velocity.
min_distance (float) – Minimum travel distance for a non-zero score.
record_loci (bool) – If True, record joint trajectories.
- __call__(topology: HypergraphLinkage, dimensions: Dimensions, config: WorldConfig | None = None) FitnessResult
Call self as a function.
- __init__(duration: float = 40.0, n_legs: int = 4, motor_rates: float | dict[str, float] = -4.0, min_distance: float = 2.0, record_loci: bool = False, mirror: bool = False) None
- class leggedsnake.fitness.StrideFitness(lap_points: int = 12, step_height: float = 0.5, step_width: float = 0.2, stride_height: float = 0.2, foot_index: int = -2)
Bases:
objectEvaluate kinematic stride length (no physics simulation).
A fast, physics-free evaluation that measures horizontal travel of the foot locus. Suitable for initial exploration before expensive dynamic simulation.
- Parameters:
lap_points (int) – Points per crank revolution for kinematic simulation.
step_height (float) – Minimum height the foot must clear (obstacle clearance).
step_width (float) – Minimum width the foot must clear.
stride_height (float) – Height threshold for stride extraction.
foot_index (int) – Index of the foot joint in the locus output.
- __call__(topology: HypergraphLinkage, dimensions: Dimensions, config: WorldConfig | None = None) FitnessResult
Call self as a function.
- __init__(lap_points: int = 12, step_height: float = 0.5, step_width: float = 0.2, stride_height: float = 0.2, foot_index: int = -2) None
- leggedsnake.fitness.as_eval_func(fitness: DynamicFitness, config: WorldConfig | None = None, walker_factory: Callable[[], Any] | None = None, negate: bool = False) Callable[[...], float]
Adapt a
DynamicFitnessto pylinkage’s optimizer contract.Returns a function with signature
(linkage, dims, pos) -> floatcompatible withmulti_objective_optimization,chain_optimizers, and the walking objective factories.- Parameters:
fitness (DynamicFitness) – The fitness evaluator to adapt.
config (WorldConfig | None) – Simulation config override. If None, the fitness uses its own default.
walker_factory (callable, optional) – Zero-argument callable returning a fresh Walker. If provided, a new walker is built per evaluation (ignoring the
linkageargument) — required for thread/process safety in multi-worker NSGA evaluation. If None, the adapter mutates thelinkagepassed in (pylinkage’s default for sequential optimizers).negate (bool) – If True, return
-scoreso pylinkage’s minimization-based optimizers (multi_objective_optimization,minimize_linkage, etc.) treat walking scores as maximization. Default False.
- Returns:
eval_func(linkage, dims, pos) -> float- Return type:
callable
Example
Feed a walking fitness into pylinkage’s multi-objective optimizer:
from leggedsnake import DistanceFitness, as_eval_func from pylinkage.optimization import multi_objective_optimization eval_fn = as_eval_func( DistanceFitness(duration=20, n_legs=4), walker_factory=make_walker, negate=True, # pylinkage minimizes ) ensemble = multi_objective_optimization( objectives=[eval_fn], linkage=make_walker(), bounds=bounds, )
- leggedsnake.fitness.as_ga_fitness(fitness: DynamicFitness, walker_factory: Callable[[], Any], config: WorldConfig | None = None, minimize: bool = False) Callable[[...], tuple[float, list[Any]]]
Adapt a
DynamicFitnessto the GA optimizer’s DNA contract.Returns a function with signature
(dna) -> (score, positions)compatible withGeneticOptimization.- Parameters:
fitness (DynamicFitness) – The fitness evaluator to adapt.
walker_factory (callable) – Zero-argument callable that returns a fresh Walker instance. Called each evaluation to avoid mutation across generations.
config (WorldConfig | None) – Simulation config override.
minimize (bool) – If True, negate the score (GA maximizes by default).
- Returns:
ga_fitness(dna) -> (float, list[tuple[float, float]])- Return type:
callable
- leggedsnake.fitness.chain_walking_optimizers(fitness: ~leggedsnake.fitness.DynamicFitness, linkage: ~typing.Any, stages: ~collections.abc.Sequence[tuple[~typing.Callable[[...], ~typing.Any], dict[str, ~typing.Any]]], config: ~leggedsnake.physics_engine.WorldConfig | None = None, order_relation: ~typing.Callable[[float, float], float] = <built-in function max>, verbose: bool = True) Any
Chain optimizer stages on a walking
DynamicFitness.Thin wrapper around
pylinkage.optimization.chain_optimizers()that adapts a leggedsnakeDynamicFitnessviaas_eval_func()and forwards to pylinkage. Each stage receives the previous stage’s best solution as its starting point (centerfor population methods,x0for local ones) — same contract as pylinkage.- Parameters:
fitness (DynamicFitness) – Walking fitness evaluator (e.g.,
DistanceFitness).linkage (Walker) – Mechanism to optimize. Must implement
set_constraints/set_coords/get_coords(Walker does).stages (sequence of (optimizer, kwargs)) – Each optimizer is one of pylinkage’s (
particle_swarm_optimization,differential_evolution_optimization,dual_annealing_optimization,minimize_linkage, …) or leggedsnake’sgenetic_algorithm_optimization.kwargsare passed through, minuseval_func/linkage.config (WorldConfig | None) – Simulation config passed into the fitness.
order_relation (callable) –
maxfor maximize (default),minfor minimize.verbose (bool) – Print stage headers.
- Returns:
Result of the final stage — same type pylinkage returns.
- Return type:
Ensemble
Example
A global → local pipeline for a walker:
from leggedsnake import ( DistanceFitness, chain_walking_optimizers, differential_evolution_optimization, minimize_linkage, dual_annealing_optimization, ) result = chain_walking_optimizers( DistanceFitness(duration=20.0, n_legs=4), my_walker, stages=[ (differential_evolution_optimization, {"maxiter": 200}), (dual_annealing_optimization, {"maxiter": 100}), (minimize_linkage, {"method": "Nelder-Mead", "maxiter": 500}), ], ) best = result[0] # Member from the final stage
- leggedsnake.fitness.co_optimize_objective(fitness: DynamicFitness, config: WorldConfig | None = None, motor_rates: float | dict[str, float] = -4.0, kinematic_prefilter: DynamicFitness | None = None) Callable[[...], float]
Adapt a
DynamicFitnessto pylinkage’sco_optimize()contract.Returns
Callable[[Linkage], float]where the result is minimized (co_optimizeminimizes). Walking performance scores (higher = better) are negated so that better walkers have lower objective values.Supports an optional two-stage pipeline: a fast kinematic pre-filter rejects mechanisms with poor foot paths before the expensive physics simulation runs.
- Parameters:
fitness (DynamicFitness) – The physics-based fitness evaluator (e.g.,
DistanceFitness).config (WorldConfig | None) – Simulation config override.
motor_rates (float | dict[str, float]) – Motor angular velocity applied to each Walker. Default -4.0.
kinematic_prefilter (DynamicFitness | None) – Optional fast kinematic check (e.g.,
StrideFitness). If the pre-filter returnsscore <= 0orvalid=False, the mechanism is rejected (returnsinf) without running physics.
- Returns:
objective(linkage: Linkage) -> floatforco_optimize().- Return type:
callable