Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Rapier trebuchet optimization

This standalone tutorial is part of the public fcmaes-rust repository. It combines the pure-Rust fcmaes-core optimizers with rapier2d-f64, the double-precision 2D build of the Rapier rigid-body physics engine.

Rapier is compiled with enhanced-determinism and without its optional parallel feature. Each objective evaluation is therefore a serial, deterministic physics run, while fcmaes evaluates independent candidates or retries on separate worker threads. This avoids nested thread pools.

Physical model

The compact model has a dynamic rigid throwing arm pinned to a fixed support, an attached counterweight, a projectile on a rope joint, joint limits, a ground collider, continuous collision detection, and an angle-triggered release. The rope joint is removed when the arm crosses the selected release angle. Contact and release make the objective discontinuous.

fcmaes controls:

IndexDecisionBoundsUnit
0Long arm length2–6m
1Counterweight mass20–220kg
2Projectile mass0.5–10kg
3Sling length1–6m
4Initial arm angle-1.25–-0.35rad
5Release angle-0.25–0.55rad
6Joint viscous damping0–12N·m·s
7Pivot Coulomb friction0–20N·m

The counterweight is rigidly attached to the short side of the arm. This makes the model a hybrid between a compact trebuchet and a robotic thrower, while the rope-constrained projectile retains the release dynamics.

The scalar BiteOpt-retry objective is minimized:

target error + 0.002 × input energy + 0.0002 × peak pivot load
             + invalid-release/landing penalty

The resource terms are positive penalties. A negative sign would reward wasting energy and increasing structural load.

MODE independently minimizes:

  1. absolute landing-position error;
  2. counterweight potential energy made available to the throw;
  3. peak pivot force, measured from Rapier’s hinge constraint impulse.

Invalid releases are penalized in every objective so that a motionless, zero-energy candidate cannot become a misleading Pareto solution.

MAP-Elites is an additional, not replacement, formulation. It uses:

  • trajectory apex and release time as behavior descriptors; and
  • the normalized target-error, energy and peak-load combination as the minimized quality inside each niche.

This produces mechanically different low/fast and high/slow throws while MODE continues to expose the original three-objective trade-off. Invalid releases return non-finite QD values and cannot occupy empty niches.

Run

Use 24 fcmaes workers for scalar, multi-objective and quality-diversity optimization:

cd tutorials/rapier-trebuchet
cargo run --release -- \
  --mode all --workers 24 --target 35 \
  --evaluations 20000 --retries 24 \
  --mo-evaluations 200000 --popsize 256 \
  --qd-evaluations 200000 --qd-capacity 400 \
  --qd-chunk-size 256 --seed 42

Quick smoke run:

cargo run --release -- \
  --mode all --workers 4 \
  --evaluations 200 --retries 2 \
  --mo-evaluations 512 --popsize 32 \
  --qd-evaluations 4096 --qd-capacity 100 --qd-chunk-size 64

Run only one formulation:

cargo run --release -- --mode single --workers 24
cargo run --release -- --mode multi --workers 24
cargo run --release -- --mode qd --workers 24

Replay a specified physical design without optimization:

cargo run --release -- --mode simulate \
  --x 3.5,80,4,2.5,-0.75,-0.05,2,2

--workers 0 uses the available logical CPU count. --evaluations is per BiteOpt retry. MODE rounds --mo-evaluations up to a complete population. MAP-Elites rounds --qd-evaluations up to a complete, even-sized QD chunk; --qd-capacity must be a perfect square for the documented two-dimensional grid. Run cargo run --release -- --help for all options.

Quick QD and visualization check

The descriptor pilot initially exposed releases beyond an early 4 s bound. The frozen tutorial bounds are now 0–60 m for apex and 0–8 s for release time. A 4,096-evaluation fixed-seed smoke run produced no clipped descriptors:

EvaluationsArchiveOccupiedCoverageInvalidClippedBest qualityWall time
4,0961003030.0%85500.2006505461.072107 s

This is a pipeline check, not the multi-seed publication campaign. Regenerate its figures from the native Rust artifacts:

PYTHONPATH=../python python -m fcmaes_tutorial_plots.cli \
  results/quick/qd/run.json --output-dir images/quick-qd

The corresponding quick MODE run remains independently available:

The publication QD campaign used 24 workers, a 400-cell archive, chunks of 256 and 200,192 actual evaluations for each of seeds 42, 43 and 44:

MetricMeanSample standard deviation
Wall time11.524671 s0.471233 s
Occupied niches162.3335.859
Coverage40.583%1.465 percentage points
QD score972.33066612.854853
Best balanced quality0.0412718900.003028160
Invalid evaluations52,173.3332,725.652
Clipped descriptors43.33312.014

The clipping rate was only 0.022% of evaluations, so the frozen bounds cover the practically searched behavior space without claiming every pathological trajectory is inside it. Raw per-seed statistics are in results/publication/qd-summary.csv. The figure below is the named seed-42 archive; the table prevents that one archive from standing in for run-to-run variability.

Recorded 24-worker optimization results

The following fixed-seed campaigns were executed on 2026-07-24 on an AMD Ryzen 9 9950X with 16 physical cores, 32 logical CPUs, and Rust 1.97.1. Both used exactly 24 fcmaes workers, a 35 m target, the release build, and the default simulation timestep of 1/180 s. Rapier remained single-threaded inside each evaluation.

The scalar campaign used 24 independent BiteOpt retries, 20,000 evaluations per retry, depth 6, and seed 42:

cargo run --release -- \
  --mode single --workers 24 --target 35 \
  --evaluations 20000 --retries 24 --depth 6 --seed 42 \
  --output results/publication/scalar-seed-42

The multi-objective campaign used MODE with population 256, 782 complete generations, and seed 42. Rounding to a complete population increased the requested 200,000 evaluations to 200,192:

cargo run --release -- \
  --mode multi --workers 24 --target 35 \
  --mo-evaluations 200000 --popsize 256 --seed 42 \
  --output results/publication/mo-seed-42
DesignEvaluationsWall timeEvaluations/sLandingTarget errorEnergyPeak pivot loadScalar score
Initial13.576227 m21.423773 m1478.120111 J340.157696 N24.448044362
BiteOpt retry best480,00028.968742 s16,57035.000096 m0.000096 m283.972483 J98.194144 N0.587679710
MODE representative200,1928.619674 s23,22534.999990 m0.000010 m297.884297 J106.682878 N0.617115116

The BiteOpt result reduced energy by 80.8% and peak pivot load by 71.1% relative to the initial design while matching the target. MODE retained 256 Pareto points. Its reported representative minimizes the example’s balanced selection score over the final front and has a printed Pareto quality of -0.040457002.

The optimized controls were:

DecisionBiteOpt retry bestMODE representative
Arm length2.3207972.275854
Counterweight mass20.00115321.394817
Projectile mass0.5016520.504059
Sling length5.9895215.612902
Initial arm angle-1.249383-1.249535
Release angle0.5495960.550000
Joint damping0.0001530.011078
Pivot friction0.0010280.000150

Selected extremes from the final MODE front illustrate the trade-off:

Selection criterionTarget errorEnergyPeak pivot load
Minimum target error0.000010 m297.884297 J106.682878 N
Minimum energy28.542249 m168.624992 J89.477070 N
Minimum peak load27.812185 m245.529436 J65.393401 N

Replaying both recorded vectors reproduced their reported objective values exactly. These are optimization results for the deterministic illustrative model, not repeated-seed benchmark statistics. In particular, sub-millimetre target errors describe numerical matching inside this model and must not be read as real-world mechanical accuracy.

Output and visual story

By default, the command creates results/:

  • replay.html: self-contained animation with initial/optimized trajectory comparison and convergence plot;
  • trajectory.csv: arm, counterweight, sling-tip, and projectile states;
  • convergence.csv: incumbent scalar or balanced MODE score;
  • pareto.csv: the final MODE front and its eight decision values.
  • qd_archive.csv: occupied niches, quality, descriptors, visit count and decision values for QD runs;
  • run.json: schema-v1 configuration, provenance and artifact references.

Open results/replay.html in a browser. No web server is needed.

The animation is a diagnostic replay, not a mechanical certification. Verify promising designs with finer timesteps, calibrated material limits, and a more detailed flexible-body model before drawing engineering conclusions.

Test

cargo test
cargo clippy --all-targets -- -D warnings
cargo run --release -- --mode simulate --no-output
PYTHONPATH=../python python -m pytest ../python/tests

The tests cover design validation, deterministic physics, finite loads, trajectory recording, objective adapters, option validation, and tiny parallel MODE and MAP-Elites runs. The Python tests validate schema loading, PyO3-array adapters, and deterministic SVG generation.