Simulation code for multi-quadrotor slung-load systems with variable-length tethers. This repo actually holds two separate, unrelated projects that happen to share a directory — see below.
A from-scratch, plain-NumPy, closed-loop simulation of the control law
derived in Automatica2026/support_document.tex ("Robust Anti-windup
Tracking Control For A Pair of Quadrotors Carrying A Rigid-body Payload With
Variable-Length Cable"): N=3 quadrotors (equilateral triangle, equal mass —
see CLAUDE.md for why not the paper's literal "pair") carrying a
rigid-body payload via variable-length cables, tracking circle and figure-8
trajectories.
Requires Python 3.11 (python3.11) — the system default
python3/python3.10 here has a broken NumPy/Matplotlib install unrelated
to this project.
Every run script lives under plots/<sim-name>/ together with the *.png
output(s) it produces (see Layout) — run them from the repo root
with python3.11 plots/<sim-name>/run_*.py; each writes its plot(s) next to
itself regardless of your current directory.
# nominal (disturbance-free) tracking
python3.11 plots/circle/run_circle.py
python3.11 plots/figure8/run_figure8.py
# with a disturbance + Uncertainty and Disturbance Estimator (UDE, from the paper)
python3.11 plots/ude_circle/run_ude_circle.py # time-varying disturbance, UDE on vs. off
python3.11 plots/ude_figure8/run_ude_figure8.py
python3.11 plots/ude_constant_circle/run_ude_constant_circle.py # constant disturbance -> zero steady-state error
# with structured (parametric) uncertainty + L1 adaptive control (NOT from the paper)
python3.11 plots/l1_circle/run_l1_circle.py # true payload mass/inertia/drag + disturbance, L1 on vs. off
python3.11 plots/l1_figure8/run_l1_figure8.py
# head-to-head: L1 (lumped) vs. L1 (structured) vs. UDE, mass + inertia uncertainty only
python3.11 plots/compare_mass_inertia/compare_l1_ude_mass_inertia.py
# same, plus a constant additive disturbance -- tests whether the structured
# estimator's advantage above survives uncertainty outside its parametric model
python3.11 plots/compare_mass_inertia_disturbance/compare_l1_ude_mass_inertia_disturbance.pyEach script prints a running tracking-error summary and writes one or more
*.png plots (3D path, tracking error, cable length/tension, and — for the
UDE/L1 scripts — estimated vs. true uncertainty) into its own plots/<sim-name>/
folder. These plots aren't checked in (see .gitignore) — regenerate them by
running the scripts.
Three things worth knowing before reading the code:
- Simulated at the control-affine level (support_document.tex
eq.
variable_length_control_affine), not physical per-quadrotor thrust — there's no quadrotor attitude inner loop here by design, not omission. - paper_sim is nominal (no disturbance); paper_sim_ude adds a true additive disturbance and the paper's own disturbance observer to reject it; paper_sim_l1 adds true parametric uncertainty (payload mass/inertia/drag) and a standard L1 adaptive controller instead — a new experiment, not derived from the paper, testing a different robustness mechanism against a different kind of uncertainty.
- Read
CLAUDE.md's gain-tuning section before touchingpaper_sim_l1's gains: this loop is not monotonic in adaptation speed the way the UDE is (parametric uncertainty feeds back into itself), and pushing it too fast causes real, sustained oscillation, not just a worse but stable result.
Full derivation-to-code mapping, the sign errors and missing terms found
in the paper while building this, and the architectural reasoning behind
the control-affine choice are all in CLAUDE.md — read that before making
changes here, it'll save you from re-discovering the same dead ends.
An unrelated, unfinished project: a 3-drone system model (PyTorch, batched)
intended for training/verifying a Control Contraction Metric (CCM) based
tracking controller, plus a NumPy RK4 simulation loop that expects an
externally-supplied learned controller. Not connected to paper_sim and not
implementing the same control law. Known broken/incomplete pieces
(controller/geometric_controller.py has syntax errors, numerical_solver.py
imports a planners/planner_MUAV module that isn't in this repo, plot.py
references a log directory that doesn't exist here) are listed in
CLAUDE.md. Editable install via pip install -e . if you need to work on
this part; paper_sim/paper_sim_ude don't need it installed.
automatica_simulation/
├── paper_sim/ outer-loop control law (nominal) -- see above
├── paper_sim_ude/ + disturbance + estimator -- see above
├── paper_sim_l1/ + structured uncertainty + L1 adaptive control -- see above
├── plots/ entry points + output for every sim run, one folder each:
│ ├── circle/ run_circle.py
│ ├── figure8/ run_figure8.py
│ ├── ude_circle/ run_ude_circle.py
│ ├── ude_figure8/ run_ude_figure8.py
│ ├── ude_constant_circle/ run_ude_constant_circle.py
│ ├── l1_circle/ run_l1_circle.py
│ ├── l1_figure8/ run_l1_figure8.py
│ ├── compare_mass_inertia/ compare_l1_ude_mass_inertia.py + mass_inertia_uncertainty.py
│ └── compare_mass_inertia_disturbance/ compare_l1_ude_mass_inertia_disturbance.py
│ + mass_inertia_disturbance_uncertainty.py
├── system_model/ CCM project (separate, WIP -- see above)
├── controller/ CCM project (separate, WIP)
├── utils/ CCM project (separate, WIP)
├── setup.py editable install for the CCM project
└── CLAUDE.md detailed notes for all projects