topology_optimization
Joint topology + dimensions co-optimization over the catalog.
Topology co-optimization for walking mechanisms.
Jointly optimizes the discrete mechanism topology (four-bar, six-bar,
eight-bar variants) alongside continuous link dimensions — and, when
evolve_offsets=True, the per-leg phase offsets that determine
gait, and when evolve_motor_rates=True, the crank angular velocity
per DRIVER node — using NSGA-II/III. Each candidate is a mixed
chromosome:
[topology_index, [n_legs,] dim_1, ..., dim_N, [off_1, ..., off_M],
[motor_1, ..., motor_K]].
Leverages pylinkage’s topology catalog and co-optimization infrastructure while evaluating candidates through leggedsnake’s physics-based fitness functions.
Example:
from leggedsnake import (
DistanceFitness,
StabilityFitness,
NsgaWalkingConfig,
)
from leggedsnake.topology_optimization import (
TopologyCoOptConfig,
topology_walking_optimization,
)
result = topology_walking_optimization(
objectives=[DistanceFitness(duration=10), StabilityFitness(duration=10)],
objective_names=["distance", "stability"],
config=TopologyCoOptConfig(
max_links=6,
n_generations=50,
pop_size=40,
),
)
for sol in result.pareto_front.solutions:
print(sol.scores, sol.metadata.get("topology_name"))
- class leggedsnake.topology_optimization.TopologyCoOptConfig(max_links: int = 8, n_generations: int = 100, pop_size: int = 100, algorithm: Literal['nsga2', 'nsga3'] = 'nsga2', seed: int | None = None, verbose: bool = True, n_legs: int = 2, n_legs_min: int | None = None, n_legs_max: int | None = None, motor_rates: float | dict[str, float] = -4.0, dimension_lower: float = 0.3, dimension_upper: float = 5.0, topology_mutation_rate: float = 0.1, evolve_offsets: bool = False, evolve_motor_rates: bool = False, motor_rate_lower: float = -8.0, motor_rate_upper: float = 8.0, allow_passive: bool = False, wind_force: tuple[float, float] = (0.0, 0.0), n_workers: int = 1)
Bases:
objectConfiguration for topology co-optimization.
- Variables:
max_links (int) – Maximum number of links to consider from catalog.
n_generations (int) – Number of evolutionary generations.
pop_size (int) – Population size per generation.
algorithm ({"nsga2", "nsga3"}) – Multi-objective algorithm.
seed (int | None) – Random seed for reproducibility.
verbose (bool) – Print progress.
n_legs (int) – Fixed number of legs per walker candidate. Applied when
n_legs_minandn_legs_maxare both unset (the default).n_legs_max (n_legs_min,) – If both are set and differ, leg count joins the chromosome as an integer gene sampled from
[n_legs_min, n_legs_max]. When equal (or either is None), the fixedn_legsis used.motor_rates (float | dict[str, float]) – Motor angular velocities.
dimension_lower (float) – Lower bound for link lengths.
dimension_upper (float) – Upper bound for link lengths.
topology_mutation_rate (float) – Probability of mutating the topology gene.
evolve_offsets (bool) – If True, append per-leg phase-offset genes (radians, bounded
[0, tau)) to the chromosome so the optimizer co-evolves gait pattern alongside topology + dimensions. The offset region is sized formax(leg_bounds) - 1so the chromosome length stays fixed under a variablen_legs; surplus offset genes are ignored when a candidate uses fewer legs (mirroring the existing dimension-padding scheme). Requiresmax(leg_bounds) >= 2. DefaultFalsekeeps the classical evenly-spaced rotating-stack gait ofWalker.add_legs(n).evolve_motor_rates (bool) – If True, append per-DRIVER motor-rate genes (rad/s, bounded
[motor_rate_lower, motor_rate_upper]) to the chromosome so the optimizer co-evolves crank angular velocity alongside structure and geometry. The motor region is sized for the maximum DRIVER count across all candidate topologies; topologies with fewer drivers consume the leading slots and the rest are ignored (mirroring the dimension-padding scheme). DefaultFalsekeeps the fixedmotor_ratesvalue. The evolved rates override themotor_ratesattribute on each fitness objective (which defaults to-4.0and would otherwise always win againstDimensions.driver_angles); fitness objects without a settablemotor_ratesattribute are not affected.motor_rate_upper (motor_rate_lower,) – Bounds (rad/s) for the motor-rate genes when
evolve_motor_rates=True. Default[-8.0, +8.0]covers the practical walking-speed range; the sign sets crank direction. Using0as a bound endpoint allows the optimizer to converge on a stationary driver — useful for prototyping wind- or slope-driven passive walkers.allow_passive (bool) – If True, drivers whose evolved rate is within
1e-6 rad/sof zero are snapped to exactly0.0, which the physics layer treats as passive: noSimpleMotorconstraint is created and the crank stays a free-pivoting rigid body driven only by gravity, wind, inertia, and ground contact. Requiresevolve_motor_rates=Trueand bounds spanning zero (motor_rate_lower <= 0 <= motor_rate_upper); the optimizer otherwise has no way to reach the passive region. DefaultFalsekeeps every driver powered. Pair with a non-zerowind_force(or a slopedworld_configterrain) when looking for purely passive walkers.wind_force (tuple[float, float]) – Constant external force (Newtons,
(fx, fy)) applied to each candidate’s chassis every physics step during evaluation. Convenience knob: whenworld_configis left atNoneintopology_walking_optimization(), a defaultWorldConfigis built with this wind. Set both an explicitworld_configand a non-zerowind_forceonly if they agree;topology_walking_optimization()raisesValueErrorotherwise. Default(0.0, 0.0)keeps the existing windless behaviour.
- __init__(max_links: int = 8, n_generations: int = 100, pop_size: int = 100, algorithm: Literal['nsga2', 'nsga3'] = 'nsga2', seed: int | None = None, verbose: bool = True, n_legs: int = 2, n_legs_min: int | None = None, n_legs_max: int | None = None, motor_rates: float | dict[str, float] = -4.0, dimension_lower: float = 0.3, dimension_upper: float = 5.0, topology_mutation_rate: float = 0.1, evolve_offsets: bool = False, evolve_motor_rates: bool = False, motor_rate_lower: float = -8.0, motor_rate_upper: float = 8.0, allow_passive: bool = False, wind_force: tuple[float, float] = (0.0, 0.0), n_workers: int = 1) None
- algorithm: Literal['nsga2', 'nsga3'] = 'nsga2'
- allow_passive: bool = False
- dimension_lower: float = 0.3
- dimension_upper: float = 5.0
- evolve_motor_rates: bool = False
- evolve_offsets: bool = False
- property leg_bounds: tuple[int, int]
Effective (low, high) leg-count bounds. Inclusive.
- property leg_gene_active: bool
True when leg count joins the chromosome as a variable gene.
- max_links: int = 8
- motor_rate_lower: float = -8.0
- motor_rate_upper: float = 8.0
- motor_rates: float | dict[str, float] = -4.0
- n_generations: int = 100
- n_legs: int = 2
- n_legs_max: int | None = None
- n_legs_min: int | None = None
- property n_offset_genes: int
Number of phase-offset genes appended to the chromosome.
Zero when
evolve_offsetsis False. Otherwise sized for the upper bound on leg count (max(leg_bounds) - 1) so the chromosome remains fixed-length under a variablen_legs; only the firstcurrent_n_legs - 1offsets are read per evaluation and the rest are ignored.
- n_workers: int = 1
Number of parallel workers. 1 = sequential. >1 uses process pool.
- pop_size: int = 100
- seed: int | None = None
- topology_mutation_rate: float = 0.1
- verbose: bool = True
- wind_force: tuple[float, float] = (0.0, 0.0)
- class leggedsnake.topology_optimization.TopologySolutionInfo(topology_name: str, topology_id: str, topology_idx: int, num_links: int, n_legs: int = 1, phase_offsets: list[float] | None = None, motor_rates: dict[str, float] | None = None)
Bases:
objectTopology metadata for a single Pareto solution.
- Variables:
topology_name (str) – Human-readable name from catalog (e.g., “Four-bar linkage”).
topology_id (str) – Catalog ID (e.g., “four-bar”).
topology_idx (int) – Index in the catalog used during optimization.
num_links (int) – Number of links in the topology.
n_legs (int) – Number of legs in the evaluated walker. Equal to
config.n_legswhen the leg-count range is fixed, or the value chosen by the evolutionary search whenn_legs_min != n_legs_max.phase_offsets (list[float] | None) – Evolved per-leg phase offsets (radians) when
TopologyCoOptConfig.evolve_offsetsis set.Nonewhen offsets are not part of the chromosome — the walker was built with the classic evenly-spaced rotating-stack gait. Length equalsn_legs - 1.motor_rates (dict[str, float] | None) – Evolved motor angular velocities (rad/s) keyed by DRIVER node ID, when
TopologyCoOptConfig.evolve_motor_ratesis set.Nonewhen the chromosome did not carry motor genes — the walker used the fixedTopologyCoOptConfig.motor_rates.
- __init__(topology_name: str, topology_id: str, topology_idx: int, num_links: int, n_legs: int = 1, phase_offsets: list[float] | None = None, motor_rates: dict[str, float] | None = None) None
- motor_rates: dict[str, float] | None = None
- n_legs: int = 1
- num_links: int
- phase_offsets: list[float] | None = None
- topology_id: str
- topology_idx: int
- topology_name: str
- class leggedsnake.topology_optimization.TopologyWalkingResult(pareto_front: ~pylinkage.optimization.collections.pareto.ParetoFront, topology_info: dict[int, ~leggedsnake.topology_optimization.TopologySolutionInfo] = <factory>, gait_analyses: dict[int, ~leggedsnake.gait_analysis.GaitAnalysisResult] | None = None, stability_series: dict[int, ~leggedsnake.stability.StabilityTimeSeries] | None = None, config: ~leggedsnake.nsga_optimizer.NsgaWalkingConfig = <factory>, co_opt_config: ~leggedsnake.topology_optimization.TopologyCoOptConfig = <factory>)
Bases:
objectResult of topology co-optimization.
Extends
NsgaWalkingResultwith per-solution topology metadata.- Variables:
pareto_front (ParetoFront) – Non-dominated solutions.
topology_info (dict[int, TopologySolutionInfo]) – Topology metadata for each Pareto solution (index -> info).
gait_analyses (dict[int, GaitAnalysisResult] | None) – Gait analysis per solution.
stability_series (dict[int, StabilityTimeSeries] | None) – Stability per solution.
config (NsgaWalkingConfig) – The NSGA configuration used.
co_opt_config (TopologyCoOptConfig) – The topology co-opt configuration used.
- __init__(pareto_front: ~pylinkage.optimization.collections.pareto.ParetoFront, topology_info: dict[int, ~leggedsnake.topology_optimization.TopologySolutionInfo] = <factory>, gait_analyses: dict[int, ~leggedsnake.gait_analysis.GaitAnalysisResult] | None = None, stability_series: dict[int, ~leggedsnake.stability.StabilityTimeSeries] | None = None, config: ~leggedsnake.nsga_optimizer.NsgaWalkingConfig = <factory>, co_opt_config: ~leggedsnake.topology_optimization.TopologyCoOptConfig = <factory>) None
- best_compromise(weights: Sequence[float] | None = None) ParetoSolution
Return the best compromise solution.
- best_for_objective(objective_index: int) ParetoSolution
Return best Pareto solution for a single objective.
- co_opt_config: TopologyCoOptConfig
- config: NsgaWalkingConfig
- gait_analyses: dict[int, GaitAnalysisResult] | None = None
- pareto_front: ParetoFront
- solutions_by_topology() dict[str, list[int]]
Group solution indices by topology ID.
- stability_series: dict[int, StabilityTimeSeries] | None = None
- topology_info: dict[int, TopologySolutionInfo]
- leggedsnake.topology_optimization.topology_walking_optimization(objectives: Sequence[DynamicFitness], objective_names: Sequence[str] | None = None, config: TopologyCoOptConfig | None = None, world_config: Any | None = None, catalog: Any | None = None, include_gait: bool = False, include_stability: bool = False) TopologyWalkingResult
Topology co-optimization for walking mechanisms.
Jointly optimizes the mechanism topology (from pylinkage’s catalog) and link dimensions using NSGA-II/III with physics-based fitness evaluation.
Note
This function reimplements the NSGA-II + mixed-chromosome plumbing that now ships in
pylinkage.optimization.co_optimize. The pylinkage-backed path —leggedsnake.optimize_walking_mechanism()— delegates the optimizer loop to pylinkage and wraps walking fitness throughleggedsnake.fitness.co_optimize_objective(). Prefer it for new code. This standalone implementation is kept for backwards compatibility and will be deprecated once the pylinkage-backed path reaches feature parity (multi-process evaluation, gait / stability post-analysis).- Parameters:
objectives (sequence of DynamicFitness) – Fitness evaluators (e.g.,
DistanceFitness,StabilityFitness).objective_names (sequence of str, optional) – Human-readable names for each objective.
config (TopologyCoOptConfig, optional) – Optimization configuration. Uses defaults if None.
world_config (WorldConfig, optional) – Simulation config passed to fitness evaluators.
catalog (TopologyCatalog, optional) – Topology catalog. Uses built-in catalog if None.
include_gait (bool) – If True, run gait analysis on Pareto-front solutions.
include_stability (bool) – If True, collect stability time series for Pareto-front solutions.
- Returns:
Pareto front with optional gait/stability analysis. Each
ParetoSolution.metadatacontainstopology_nameandtopology_ididentifying the mechanism type.- Return type: