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.
Precomputed simulation results, publication figures, plots, and renders from the paper are included.
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-embeddingIf you already cloned without --recursive, initialize submodules with:
git submodule update --init --recursiveCreate and activate the conda environment:
conda env create -f environment.yml
conda activate codesign_embeddingBuild the project:
./install.shFor later rebuilds, use:
./compile.shRun 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_experimentsOptimization 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 underresults/. For example, the loss plots below can be regenerated directly from the archivedcmaes.logfiles 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.pyexpects newly generated material-matching and expressiveness inputs at fixed locations underoutput/;python/plot.pyand the preprocessing scripts accept input and output paths on the command line, so point them to the relevant directory underoutput/orresults/simulations/;- the Blender scripts keep their input/output paths in each script's
Settingsclass. These paths are relative to Blender's working directory and must be updated if your files are elsewhere.
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 -donutResults 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/plotsExpected cross result:
| 4×4 | 6×6 | 8×8 | Target |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
Expected donut result:
| 4×4 | 6×6 | 8×8 | Target |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
The original vector PDFs are in results/figures/.
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-expressivenessThe 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 200This 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 encoder | Neural-field encoder |
|---|---|
![]() |
![]() |
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-driftlossThe 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-cooptThis 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-simEach completed case produces:
cmaes.log: best loss and CMA-ES diagnostics at every generation;design.txtandbestcode.csv: the optimized design and parameter vector;- a
.pvdtime-series index plus numbered.vtusimulation 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/ |
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.pdfUse --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-optimized | Jumper: sequential vs co-optimized |
|---|---|
![]() |
![]() |
| Swimmer: neural field vs basis function (doubled loss) | Jumper: per voxel vs basis function |
![]() |
![]() |
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.
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/jumperbfpvThe 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.pyBlender 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 sequential | Swimmer: basis function vs neural field |
|---|---|
![]() |
![]() |
| Jumper: co-optimized vs sequential | Jumper: basis function vs per voxel |
![]() |
![]() |
- 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.
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}
}

















