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: object

Evaluate 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 score is the distance; all requested objectives appear 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.

  • 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 in objectives.

  • loci_stride (int) – Subsample loci recording — keep every loci_stride-th step. At default 1 every physics step is recorded; raising it to e.g. 10 cuts loci memory 10× at the cost of trajectory resolution. analyze_gait is automatically rescaled via loci_dt so 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: object

Evaluate 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 when mirror=True — total is 2 * n_legs.

  • mirror (bool) – If True, call add_opposite_leg() before phase-offset copies, producing a left/right-symmetric walker. The canonical Jansen Strandbeest is n_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: Protocol

Protocol 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 FitnessResult with a primary score and secondary metrics.

The topology + dimensions signature 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: object

Evaluate energy efficiency via physics simulation.

Returns total_efficiency / total_energy as the primary score. Returns 0 if the walker doesn’t travel at least min_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. See DistanceFitness.

  • 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: object

Rich 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: object

Evaluate 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 CompositeFitness for 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: object

Evaluate 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: object

Evaluate 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 DynamicFitness to pylinkage’s optimizer contract.

Returns a function with signature (linkage, dims, pos) -> float compatible with multi_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 linkage argument) — required for thread/process safety in multi-worker NSGA evaluation. If None, the adapter mutates the linkage passed in (pylinkage’s default for sequential optimizers).

  • negate (bool) – If True, return -score so 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 DynamicFitness to the GA optimizer’s DNA contract.

Returns a function with signature (dna) -> (score, positions) compatible with GeneticOptimization.

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 leggedsnake DynamicFitness via as_eval_func() and forwards to pylinkage. Each stage receives the previous stage’s best solution as its starting point (center for population methods, x0 for 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’s genetic_algorithm_optimization. kwargs are passed through, minus eval_func / linkage.

  • config (WorldConfig | None) – Simulation config passed into the fitness.

  • order_relation (callable) – max for maximize (default), min for 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 DynamicFitness to pylinkage’s co_optimize() contract.

Returns Callable[[Linkage], float] where the result is minimized (co_optimize minimizes). 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 returns score <= 0 or valid=False, the mechanism is rejected (returns inf) without running physics.

Returns:

objective(linkage: Linkage) -> float for co_optimize().

Return type:

callable