Concepts

LeggedSnake’s API is small once you see the three pieces that compose into everything else: how a mechanism is described, how a candidate is scored, and how the optimizer landscape lays itself out. This page is the orientation; the per-module API pages have the exhaustive detail.

The mechanism model: topology + dimensions = walker

A walker is two orthogonal pieces of state, plus a thin object that holds them together.

Topology — what is connected to what.

HypergraphLinkage is a graph of Node (joints, with a NodeRole of GROUND, DRIVER, or DRIVEN) connected by Edge (binary links) and Hyperedge (rigid triangles / N-ary clusters). The topology describes the graph; it has no metric information.

Dimensions — how big and where.

Dimensions carries node positions, edge distances, and a DriverAngle per driver node (initial angle + angular velocity). Two walkers with the same topology but different dimensions are different designs; this is what an optimizer mutates.

Walker — the bag that holds them.

Walker wraps (topology, dimensions) plus a motor_rates override (float for all drivers, or dict[str, float] for per-driver rates — the multi-DOF path). It exposes add_legs(n) for phase-offset copies, add_opposite_leg() for mirroring, to_mechanism() for kinematic stepping, and the classical-mechanism factories (from_jansen, from_klann, from_chebyshev, from_strider, from_watt, from_catalog, …).

Why the split? Optimization perturbs dimensions on a fixed topology — separating the two means the optimizer never has to think about graph mutation. Topology co-design (see below) is the exception, and it picks topologies from a finite catalog rather than mutating edges.

The fitness protocol

All physics-aware evaluators share one signature, captured by the DynamicFitness Protocol:

def __call__(
    self,
    topology: HypergraphLinkage,
    dimensions: Dimensions,
    config: WorldConfig | None = None,
) -> FitnessResult: ...

A FitnessResult carries:

  • score — primary value, higher is better by convention.

  • metrics — dict of secondary numbers ("distance", "energy", "buildable_fraction", "froude_number", "cost_of_transport", …). Multi-objective optimizers consume these; you also read them post-hoc.

  • valid — did the run complete? Failed designs return valid=False with a meaningful buildable_fraction so the optimizer still has a gradient.

  • loci — per-joint trajectories when record_loci=True.

Built-in implementations

Class

Primary signal

Use when

DistanceFitness

Forward distance walked

You only care if it walks far.

EfficiencyFitness

Distance per unit motor energy

Energy-aware single-objective.

StabilityFitness

Mean tip-over margin

Robustness under disturbance.

GaitFitness

Mean stride length + gait metrics

You want gait shape, not just total distance.

CompositeFitness

Multiple objectives, one sim

NSGA front; avoids duplicate physics.

StrideFitness

Kinematic stride length, no physics

Inner loop of fast PSO; use for prefilter.

Adapters

Two glue functions bridge DynamicFitness to the older optimizer contracts:

  • as_eval_func() — wraps a fitness into pylinkage’s (linkage, dimensions, positions) float minimizer contract (use negate=True for any minimizer).

  • as_ga_fitness() — wraps a fitness into the GA’s (dna) (score, initial_positions) tuple contract.

The same fitness object can therefore drive particle_swarm_optimization(), differential_evolution_optimization(), minimize_linkage(), GeneticOptimization, and nsga_walking_optimization() without rewriting it.

The optimizer landscape

Pick the cheapest tool that answers your question. In rough cost order from fastest-per-eval to slowest:

Function / class

What it varies

Use when

particle_swarm_optimization(), kinematic_maximization()

Dimensions only (no physics)

Quick kinematic search; pair with StrideFitness for the inner loop.

chain_walking_optimizers()

Dimensions only

Stage global → local pipelines (DE → dual annealing → Nelder-Mead).

GeneticOptimization, genetic_algorithm_optimization()

Dimensions; JSON-checkpointed GA with multiprocessing

Single-objective dynamic GA; good for the final selection round.

nsga_walking_optimization()

Dimensions; multi-objective

Pareto front of distance vs. energy vs. stability vs. gait.

topology_walking_optimization()

Topology and dimensions (catalog index)

Pick a mechanism family + tune it jointly. Mixed chromosome.

optimize_walking_mechanism()

End-to-end pipeline

Topology discovery → kinematic prefilter → dynamic fitness in one call.

optimize_gait()

Phase offsets only

Tune trot / pace / canter on a finished walker.

sweep_leg_counts()

Number of legs

Post-hoc “how many legs?” study.

Where to go next