Skip to content

Repository files navigation

Unified Design Embedding for Soft Robots

This repository contains the optimization framework for the paper A Unified Low-Dimensional Design Embedding for Joint Optimization of Shape, Material, and Actuation in Soft Robots.

The framework jointly parameterizes shape, material distribution, and actuation in a smooth, low-dimensional space. It uses SORS as its finite element backend through a Git submodule and CMA-ES for black-box optimization.

Overview of the unified low-dimensional design embedding

Precomputed simulation results, publication figures, plots, and renders from the paper are included.

Installation

The codebase is tested on Linux (Ubuntu 22.04). Use Conda 4.9 or newer; older releases are not supported by conda-forge and may fail to resolve its metadata and dependencies. A current Miniforge installation is recommended.

Clone the repository with submodules:

git clone --recursive git@github.com:srl-ethz/codesign-embedding.git
cd codesign-embedding

If you already cloned without --recursive, initialize submodules with:

git submodule update --init --recursive

Create and activate the conda environment:

conda env create -f environment.yml
conda activate codesign_embedding

Build the project:

./install.sh

For later rebuilds, use:

./compile.sh

Reproducing the paper results

Run the commands below from the repository root, after activating the environment and building the project. The C++ executable lists every available experiment when called without arguments:

./build/optimization/codesign_experiments

Optimization is optional for plotting and visualization. The dynamic paper runs are archived under results/simulations/ with their optimization logs, designs, actuation signals, and PVD/VTU time series. The publication figures and renders are also included under results/. For example, the loss plots below can be regenerated directly from the archived cmaes.log files without rerunning CMA-ES.

Pay attention to paths when switching between a new run and an archived paper run:

  • the experiment executable writes new results to output/, relative to the directory from which it is launched;
  • python/main.py expects newly generated material-matching and expressiveness inputs at fixed locations under output/;
  • python/plot.py and the preprocessing scripts accept input and output paths on the command line, so point them to the relevant directory under output/ or results/simulations/;
  • the Blender scripts keep their input/output paths in each script's Settings class. These paths are relative to Blender's working directory and must be updated if your files are elsewhere.

1. Material distribution matching (Fig. 3)

These experiments fit two materials to a fixed cross or donut (the 2D torus in the paper). Each command runs the 4×4, 6×6, and 8×8 RBF resolutions:

./build/optimization/codesign_experiments material-matching -cross
./build/optimization/codesign_experiments material-matching -donut

Results are written below output/material_matching/<target>/<resolution>/. Each resolution contains the optimized design.txt, bestcode.csv, and cmaes.log. Generate the four paper-style plots for each target with:

python python/main.py matching -cross -o output/material_matching/plots
python python/main.py matching -donut -o output/material_matching/plots

Expected cross result:

4×46×68×8Target
Cross, 4 by 4 RBFs Cross, 6 by 6 RBFs Cross, 8 by 8 RBFs Cross target

Expected donut result:

4×46×68×8Target
Donut, 4 by 4 RBFs Donut, 6 by 6 RBFs Donut, 8 by 8 RBFs Donut target

The original vector PDFs are in results/figures/.

2. Shape expressiveness (Table I and Fig. 4)

The following command samples both the basis-function and neural-field encoders at three sizes, then computes their pairwise Chamfer-distance matrices:

./build/optimization/codesign_experiments shape-expressiveness

The paper uses the default population of 2000. Since the pairwise calculation grows quadratically with the population, it may take a while. It is parallelized and is reasonably fast when enough CPU cores are available. For a quicker run with the same qualitative trend, use a smaller population, for example:

./build/optimization/codesign_experiments shape-expressiveness --population-size 200

This writes six distance matrices to output/. Analyze them and produce the summary tables, ECDFs, eigenspectra, MDS plots, and novelty plots with:

python python/main.py expressiveness basis -o output/expressiveness/basis --formats pdf png
python python/main.py expressiveness neural -o output/expressiveness/neural --formats pdf png
Basis-function encoderNeural-field encoder
Basis-function novelty distributions Neural-field novelty distributions

3. Dynamic optimization experiments (Figs. 5–7)

Runtime: these are full CMA-ES optimizations with 200 generations and 50 simulations per generation (10,000 simulator evaluations). Expect several hours per case. The paper runs used 50 CPU cores so that one full generation could be evaluated in parallel.

Run the co-optimization, sequential, and baseline cases with:

# Co-optimization versus sequential optimization (Fig. 5)
./build/optimization/codesign_experiments swimmer-coopt
./build/optimization/codesign_experiments swimmer-sequential
./build/optimization/codesign_experiments jumper-coopt
./build/optimization/codesign_experiments jumper-sequential

# Alternative encodings (Fig. 7)
./build/optimization/codesign_experiments swimmer-neural
./build/optimization/codesign_experiments jumper-voxel

# Basis-function swimmer under the doubled forward-loss objective used
# for the fair neural-field comparison in Fig. 7
./build/optimization/codesign_experiments swimmer-coopt --double-driftloss

The neural swimmer is optimized with twice the forward-displacement contribution. Therefore, compare it with the basis-function run produced using --double-driftloss, not with the standard swimmer-coopt log. This doubles the relevant objective term during optimization; the plotted log should not be multiplied afterward.

All these commands write to output/. If you ran the first two sections, archive their output/ directory before beginning the long cases. Then run one optimization at a time and archive its directory before starting the next case, for example:

./build/optimization/codesign_experiments swimmer-coopt
mv output output-swimmer-coopt

This also avoids appending a new run to an existing output/cmaes.log. The next experiment recreates output/ automatically. Use --single-sim with any dynamic case for a quick random-design check before committing to the full run:

./build/optimization/codesign_experiments swimmer-coopt --single-sim

Each completed case produces:

  • cmaes.log: best loss and CMA-ES diagnostics at every generation;
  • design.txt and bestcode.csv: the optimized design and parameter vector;
  • a .pvd time-series index plus numbered .vtu simulation frames;
  • one or more actuation CSV files.

The exact paper runs are already available:

Experiment Precomputed result
Swimmer, co-optimized results/simulations/swimmer_co/
Swimmer, sequential results/simulations/swimmer_seq/
Swimmer, neural field results/simulations/swimmer_neural/
Swimmer, basis function with doubled forward loss results/simulations/swimmer_co2x/
Jumper, co-optimized results/simulations/jumper_co/
Jumper, sequential results/simulations/jumper_seq/
Jumper, per voxel results/simulations/jumper_vx/

Plotting two optimization losses

python/plot.py accepts any two cmaes.log files and recreates the paper's loss-plot style. It also detects and joins the two phases of sequential runs automatically:

python python/plot.py \
  results/simulations/swimmer_seq/cmaes.log \
  results/simulations/swimmer_co/cmaes.log \
  --labels "Sequential" "Co-opt." --title "Swimmer" \
  -o output/swimmer_loss.pdf

Use --colors COLOR_A COLOR_B to override the paper-blue/paper-red defaults, and --show to open an interactive window. The four comparisons from the supplied logs are:

Swimmer: sequential vs co-optimizedJumper: sequential vs co-optimized
Swimmer sequential and co-optimized losses Jumper sequential and co-optimized losses
Swimmer: neural field vs basis function (doubled loss)Jumper: per voxel vs basis function
Neural-field and basis-function swimmer losses Per-voxel and basis-function jumper losses

Inspecting simulations in ParaView

Open the case's .pvd file in ParaView, click Apply, select elementColor to display the material assignment, and use the time controls to play the simulation. Opening the .pvd file is preferable to opening individual .vtu frames because it preserves the complete time series.

Paper-quality Blender renders

The .pvd/.vtu output must first be converted into compact surface meshes. Use rendering/preprocess.py for tetrahedral swimmers and rendering/preprocess_cubes_hex.py for hexahedral jumpers. Each invocation takes two simulations and creates fish1/fish2 or cube1/cube2 streams:

# Swimmer: co-optimized versus sequential
python rendering/preprocess.py \
  --pvd1 results/simulations/swimmer_co/ellipsoid.pvd \
  --pvd2 results/simulations/swimmer_seq/swimmerGeomAct.pvd \
  --out rendering/preprocessed/swimmercoseq

# Swimmer: doubled-loss basis function versus neural field
python rendering/preprocess.py \
  --pvd1 results/simulations/swimmer_co2x/ellipsoid.pvd \
  --pvd2 results/simulations/swimmer_neural/swimmerneural.pvd \
  --out rendering/preprocessed/swimmerbfnn2

# Three-lane paper still: also prepare the standard basis-function/neural pair
python rendering/preprocess.py \
  --pvd1 results/simulations/swimmer_co/ellipsoid.pvd \
  --pvd2 results/simulations/swimmer_neural/swimmerneural.pvd \
  --out rendering/preprocessed/swimmerbfnn

# Jumper: co-optimized versus sequential
python rendering/preprocess_cubes_hex.py \
  --pvd1 results/simulations/jumper_co/jumper.pvd \
  --pvd2 results/simulations/jumper_seq/jumperGeomAct.pvd \
  --out rendering/preprocessed/jumpercoseq

# Jumper: basis function versus per voxel
python rendering/preprocess_cubes_hex.py \
  --pvd1 results/simulations/jumper_co/jumper.pvd \
  --pvd2 results/simulations/jumper_vx/jumperpervoxel.pvd \
  --out rendering/preprocessed/jumperbfpv

The renderer paths, output names, frame selections, resolution, and render quality are hardcoded in each script's Settings class (and at the bottom of paper_render_swimmer_frames.py). Edit those values if your directories differ. Paths are relative to the directory from which Blender is launched, so run the supplied configuration from rendering/:

cd rendering

# Videos; PNG frames are rendered first and then joined with ffmpeg
blender --background --python video_swimmer1.py  # co-optimized / sequential
blender --background --python video_swimmer2.py  # basis function / neural field
blender --background --python video_cube1.py     # co-optimized / sequential
blender --background --python video_cube2.py     # basis function / per voxel

# Selected still frames used by the paper figures
blender --background --python paper_render_swimmer_frames.py
blender --background --python paper_render_cubes.py

Blender is required separately from the conda environment; video assembly also requires ffmpeg (or an updated ffmpeg_bin in the video scripts). If ffmpeg is unavailable, the scripts keep the rendered PNG frames so they can be assembled later.

Each video script renders PNG frames and then invokes ffmpeg automatically. With the commands above, the finished videos are written to these paths relative to rendering/:

Script Generated video
video_swimmer1.py renders/swimmers_video_coseq/animationswimmer_coseq.mp4
video_swimmer2.py renders/swimmers_video_bfnn2/animationswimmer_bfnn2.mp4
video_cube1.py renders/jumperscoseq/jumperscoseq.mp4
video_cube2.py renders/cubes_video_bfpv/animationcube_bfpv.mp4

The precomputed looping GIFs below start automatically on GitHub. Higher-resolution MP4 copies remain available under results/renders/.

Swimmer: co-optimized vs sequentialSwimmer: basis function vs neural field
Co-optimized versus sequential swimmer animation Basis-function versus neural-field swimmer animation
Jumper: co-optimized vs sequentialJumper: basis function vs per voxel
Co-optimized versus sequential jumper animation Basis-function versus per-voxel jumper animation

Known limitations

  • Full CMA-ES runs are computationally expensive: each dynamic case uses 10,000 CPU-based simulator evaluations in the default configuration.
  • Multi-objective task losses require manual weighting of their constituent terms.
  • Actuation is open loop; state-dependent feedback control is not addressed.
  • Fabrication constraints are not explicitly enforced, and the co-designed robots have not yet been validated experimentally.
  • Basis functions are placed on fixed, uniform grids rather than being positioned or refined adaptively.

Citation

If you use this work, please cite:

@article{candiello2026designembedding,
  author  = {Candiello, Vittorio and Mekkattu, Manuel and Michelis, Mike Y. and Katzschmann, Robert K.},
  title   = {A Unified Low-Dimensional Design Embedding for Joint Optimization of Shape, Material, and Actuation in Soft Robots},
  journal = {arXiv preprint arXiv:2603.06497},
  year    = {2026},
  url     = {https://arxiv.org/abs/2603.06497}
}

About

Codebase for the paper: A Unified Low-Dimensional Design Embedding for Joint Optimization of Shape, Material, and Actuation in Soft Robots

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages