Skip to content

Repository files navigation

Airloom — evolving quadcopter frames for Wh/km

(heirloom + air: every frame inherits from its ancestors, and the gallery is the family archive. Formerly framevo; the old CLI name still works as an alias.)

A fully automated, headless research loop that evolves quadcopter frame geometry to minimize energy per distance (Wh/km) flown through a portfolio of adverse-weather scenarios. Everything except the frame — battery, motors, propellers, flight controller — is fixed. Runs unattended, is resumable after a crash, and produces a ranked, visual archive of every frame it ever evaluated.

make demo        # 3 generations x population 8, ~2-4 min on 8 cores
make test        # pytest suite
.venv/bin/airloom run --generations 100            # starts, or RESUMES the
                                                   #  latest run by default
.venv/bin/airloom run --generations 100 --fresh    # snapshot + CLEAR results,
                                                   #  then a brand-new run
.venv/bin/airloom lineage <genome_hash>            # ancestor chain w/ fitness
.venv/bin/airloom gallery                          # rebuild artifacts from db
.venv/bin/airloom robustness                       # is the RANKING an artifact
                                                   #  of the model knobs?
.venv/bin/airloom verify-champions                 # refined structural check of
                                                   #  the top frames + print-
                                                   #  and-test protocol

While a run is live, type quit + enter (or ctrl-c) to stop gracefully — the tasks in flight finish and the run stays resumable. If the latest run's genomes predate the current genome spec, airloom run starts a fresh run automatically; a run.db written by an older schema is archived (never deleted) and recreated.

Local-first: plain pip install -e . on macOS (Apple Silicon) or Linux x86, CPU-only, no Docker/GPU/EGL. Thumbnails render through headless matplotlib. Worker count auto-scales to the core count.

Every airloom run first commits a snapshot of results/ into its own nested repo (results/.git — separate from the project repo, so big run artifacts never bloat it). --fresh then actually clears the folder. Restore any past state with git -C results log + git -C results checkout <hash> -- .

What it produces (in results/)

  • index.html — the gallery: static, no frameworks, no server, open with file:// (auto-reloads every 30 s while a run is live — locally only); also mirrored into docs/, the GitHub Pages root, on every refresh. Tufte-styled (cream paper, ink, one rust accent, hairlines). At the top: a progress chart of every candidate in evaluation order — gray dots, a best-so-far step line with labeled improvements, invalid candidates as rust ×, generation ticks, and two horizontal benchmark lines (≈5.0 Wh/km aggregate = current 7-inch long-range practice, ≈4.0 = record-class stretch; both set in scenarios.yaml) — so you can see what moved the needle and where the search plateaued. Below: one row per generation sorted by fitness, then detail blocks showing a from-below still of each candidate — click it to open a full-screen overlay with two tabs: the interactive 3D model (depth-buffered WebGL, drag/scroll/double-click), and a side-by-side comparison with the oldest ancestor of its lineage whose rotation and zoom stay in sync. A metric/value table, the genome table, parent thumbnails and the candidate's lab-notebook notes (hypothesis / method / result — see the narrator below) sit beside each still. Parts are colored by role: rust arms and near-black deck plates are the evolved geometry; the blue Li-Ion pack, gray motor cans and pale translucent prop disks are the fixed kit.
  • leaderboard.md — top 10 with all metrics and per-scenario columns.
  • lineage.html (+ raw lineage.svg, lineage.dot) — a dedicated family tree page: nodes shaded by fitness (dark = better), hollow nodes = invalid, edges colored by operator, elite carry-overs as dotted pass-throughs.
  • convergence.png — fitness vs. generation.
  • frames/gen_XXXX/<hash>.stl + .pngevery candidate, including invalid ones (_INVALID suffix; the failures are instructive).
  • gen_XXXX_best.stl — the generation's champion (fused), plus gen_XXXX_best_parts/ — the same frame as separate flat, printable pieces.
  • designer_log.md — the designer rounds' proposals with their one-line rationales (see below).
  • robustness.md (on demand, airloom robustness) — the top candidates re-flown under perturbed model knobs (±30 % handbook Cds, ±10 % rotor tables, rain knobs, wash term): rank correlation, top-5 overlap and champion identity vs the baseline ordering, with a STABLE / MODERATE / FRAGILE verdict. Answers "is the leaderboard a frame property or a model artifact?" and names the knob where Phase B fidelity would matter first.
  • champion_check.md (on demand, airloom verify-champions) — the top frames re-analyzed with the same station-by-station variable-section bending model the in-loop constraint now runs (net-section stress concentration at holes/cutouts via Peterson Kt, and the taper gene), adding the one thing the fast in-loop gate deliberately doesn't apply: as-built strength knockdowns for printed materials — plus a bench print-and-test protocol with hold and predicted-failure loads.
  • run.db — SQLite: genomes, lineage (self-referencing, WITH RECURSIVE ancestry), per-scenario metrics, populations, lab-notebook notes, config snapshot, git hash. Written by an older schema? It gets archived automatically (never deleted) and recreated.
  • glossary.html (copied from docs/glossary.html, linked from the gallery) — definitions of both the evolutionary-algorithm terms and the domain terms (genes, Wh/km, constraints, physics vocabulary).

The fixed platform (never evolved)

Matched to the official DroneAid Collective (drone-aid.de) workshop kit, a 7-inch long-range build in the most popular open-source plate-frame archetype — the TBS Source One family (open-source: 2 mm deck plates, M3 standoffs, sandwiched plate arms; the upstream repo carries no license file, so terms are unconfirmed). Kit components as modeled: 4× 2806-class ~1300KV motors with 7×4-class 3-blade props (ground truth: UIUC Master Airscrew GF 7×4 measurements), a self-built 6S1P 21700 Li-Ion pack (4.2 Ah, 470 g, strapped on top of the deck), 30.5 mm Betaflight FC + 4-in-1 ESC stack between the plates, micro FPV camera, VTX + antenna, ELRS receiver. Fixed non-frame mass: 0.78 kg; baseline AUW ≈ 1.0 kg.

The generation-0 baseline genome is measured from the official Source One V6 7in DC plate drawing (cached with provenance notes in data/source_one/): 106.6×48.5 mm bottom plate, 2 mm plates, 6 mm arms, M3×30 standoffs, ~160 mm arms. Assembly is modeled the way the real frame bolts together: arm root tongues rest on the bottom plate inside the sandwich (tongues may not collide — hard constraint), the FC/ESC boards sit in the gap (and count as a bluff body for drag), the battery wedge hinges on its front bottom edge so it never sinks into the plate, and the XT60/lead are modeled visually. The best candidate of each generation is also exported as individual print/cut-ready pieces in gen_XXXX_best_parts/ (bottom_plate, top_plate, arm ×4).

The genome: morphs of the REAL parts (14 genes)

The genome no longer describes primitives — it deforms the official Source One V6 7in DC plate drawings (data/source_one/, parsed from the DXF with arcs, cutouts and every bolt hole) under zone constraints that keep each candidate a printable/cuttable derivative of the real design:

  • arm_length_scale, arm_width_scale, arm_waist_scale, arm_thickness — stretch/reshape the real arm outlines; the bolt tongue and the 16×19 mm motor-mount end stay rigid.
  • tip_thickness_scale — mid-shaft thickness dip as a fraction of root thickness (a bump profile: full thickness stays at the tongue and the motor mount, where it's needed for the plate-sandwich fit and screw engagement; 1.0 = uniform, no dip).
  • arm_cutout_scale — three lightening holes drilled along each arm's shaft centerline, radius scaled off the local width there (0 = none).
  • front_sweep_deg, rear_sweep_deg — rotate the arms about their drawing-registered plate anchors (front anchors are exact bolt-pattern registrations; DC arms keep their true left/right mirrored chirality).
  • plate_length_scale, plate_width_scale, plate_thickness_scale, deck_gap — stretch the real main/mid/top plates (the 30.5 mm stack pattern stays pinned exactly), set the standoff length.
  • battery_wedge_deg — tilt of the pack on the top plate.
  • material — carbon plate / PA12-CF / PET-CF / PLA+ / PETG / ASA library.

Gene value 1.0 on every scale gene reproduces the real V6 exactly (the generation-0 seed; its frame mass comes out at 144 g — the real frame weighs ~145 g). Every candidate assembles the full kit: 6×21700 cell pack, FC/ESC stack, 2806 motors, 3-blade props, camera, VTX + ELRS antennas, GPS, XT60 and routed wire looms — dimension-accurate meshes (components.py).

Hard constraints (fitness = ∞, no simulation): the FC/ESC stack must fit the deck gap, placed arm outlines must not overlap (the real notch interlock is not redesigned), each tongue's bolt pair must land on main-plate material, rotors need ≥ 5 mm from each other, and prop disks are checked against the deck/battery in 3D (the real V6's rear props sweep below the top-plate corners; deck_gap trades that margin).

Scenario portfolio

Six scenarios (config/scenarios.yaml): calm_warm, cold_headwind, storm (25 mm/h rain), crosswind, gusty_light (severe Dryden gusts), hot_thin (1500 m density altitude). Every candidate flies all of them over the identical mission: 2 km north + 2 km south at 12 m/s, 30 m AGL. Each scenario has a fixed turbulence seed, so all candidates fly identical gust histories — fitness differences are frame differences, never gust luck.

Designer rounds (headless Claude): every 6 generations (config: designer in evolution.yaml), a headless claude -p call receives the elite genomes with per-scenario results and the invalid-design histogram, and proposes new genome vectors as design hypotheses — injected as operator designer (violet in the family tree), rationales appended to results/designer_log.md. The brief includes the known constraint couplings (deck gap vs. FC stack, sweep vs. tongue collisions and bolt registration). Fail-soft: if the CLI is unavailable the round is skipped.

Lab-notebook narrator (headless Claude): after each generation, ONE claude -p call writes three short notes for every new candidate — hypothesis (the pre-flight "why": what the search had observed and what bet this design makes), method (what concretely changed vs. the parents), result (outcome vs. parents and best-so-far, or the constraint that killed it, plus one idea worth testing next). Rule-based fallback notes are written instantly; the Claude enrichment runs on a background thread off the loop's critical path and upgrades them in place. The notes appear in the gallery detail blocks and the family-tree hover cards. Config: narrator in evolution.yaml; fail-soft like the designer.

Patience: if the best-so-far stalls for 6 generations (no ≥0.5 % improvement), the loop pivots — it crosses tournament winners with far parents (the most genetically distant still-decent candidates in the run's history) under boosted mutation, escalating to random parents if the plateau persists. Derived from persisted history, so it survives --resume; configured under ga.patience in config/evolution.yaml.

Fitness = mean Wh/km + λ·worst Wh/km (λ = 0.5), or pure worst-case with aggregation: minimax. Thrust limiting (rotor RPM ceiling incl. battery sag, per-motor power, pack deliverable power) clamps rather than instantly failing — a gust transient costs tracking and energy, like real flight. A scenario fails on sustained infeasibility: thrust-limited beyond 10 % of the nominal mission time (blamed on the dominant limiter), altitude/track divergence, mission not completed within 1.6× nominal time, or the structural check. Any scenario failure ⇒ invalid. An optional early-reject screen (early_reject.enabled) flies calm_warm first and gives clearly-losing candidates a finite penalized fitness without flying the other five.

Physics (Phase A — deliberately boring and verifiable)

piece model source / check
rotor CT(J), CP(J) interpolated from measured tables; T = ρn²D⁴CT, P = ρn³D⁵CP / 0.85 (motor+ESC) UIUC Propeller DB, Master Airscrew GF 7×4 (static + 4 RPM sweeps), cached in data/uiuc/; unit-tested against tabulated points
frame drag component buildup: projected areas from rasterizing the STL along the flow at 0–60° tilt; handbook per-class Cds corrected by CFD-fitted tilt-dependent ratios and an assembly interference factor (wake shielding + silhouette double-count, −6 %/−27 %/−37 % at 0°/20°/40°); rotor-wash download on arm planform under the disks calibrated against 12 OpenFOAM v2512 k-ω SST cases (cfd/calibration.md); round-trip unit-tested to ±3 % of the measured CdAs
turbulence Dryden, MIL-F-8785C low-altitude forms, spectral synthesis, fixed seeds variance unit-tested against σ² spec
rain (a) water-film added mass ∝ top area, (b) momentum drag via equivalent suspended-water density + vertical impact force, (c) 15 % thrust-coefficient penalty empirical knobs, see NASA TP-2671 (Dunham et al.) heavy-rain research; all in config
flight 100 Hz point-mass 3-DOF sim; quasi-static attitude (the quad tilts into the relative wind), P velocity loop with accel limits; rotor speeds solved each step from the CT tables; electrical energy integrated zero-wind mission unit-tested against a quasi-static analytic power balance (±6 %)
battery quasi-static pack model: V = V0 − I·R, current solved from demanded motor power each step; energy integrated at the pack terminals (incl. I²R loss); RPM ceiling scales with sagged voltage; deliverable-power (V0²/4R ≈ 820 W) and cell-current (45 A) limits feed the saturation clock P42A-class DC IR + discharge-curve sag; unit-tested (quadratic solve, IR-loss share, storm clamp behavior)
structure station-by-station variable-section Euler–Bernoulli bending along the real morphed arm outline (net width minus any hole/cutout, Peterson Kt where one pierces a station, genome-selected thickness taper): worst-case per-rotor thrust across all scenarios × 1.5 safety factor; max stress ≤ material strength, tip deflection ≤ 5 % L, first bending mode outside ±15 % of hover 1P stress/deflection unit-tested against hand calculations on a plain (untapered, unpierced) section, which reduces to the classic constant-section formulas

Sanity anchors (unit-tested): the original spec anchor — a 5-inch quad at 1.1 kg AUW hovers at ~195 W (180–280 W band), checked against the cached GWS 5×4.3 dataset — plus the shipped platform: the baseline 7-inch deck (~0.34 m wheelbase, ~1.0 kg AUW) hovers in a plausible 90–230 W band on the MA GF 7×4 data.

Known limitations (please read before trusting numbers)

  • Phase A drag is approximate. Component buildup with handbook Cd values and a cos²-blend between body-x and body-y flow azimuths; no interference drag beyond the rotor-wash term, no Reynolds corrections.
  • Rotor model is axial-flow data stretched to edgewise flight. UIUC wind tunnel sweeps measure propellers in axial advance; a quadcopter in cruise sees mostly edgewise inflow. J is computed from the axial inflow component only, and there is no rotor–rotor interaction.
  • The rain model is empirical. Film mass, momentum drag and the 15 % thrust penalty are literature-inspired config knobs, not physics.
  • No rotational dynamics. Attitude is quasi-static; control effort in gusts appears as thrust modulation, not motor differential torques. Gust series are synthesized at the commanded cruise speed rather than the instantaneous airspeed.
  • UIUC data was measured at 2.5k–7k RPM and on a 2-blade prop; the kit flies 3-blade 7×4s and hovers near 9k RPM. CT/CP are treated as Re-independent and blade-count effects are absorbed into the tables.
  • Print-material properties are XY/datasheet values. FDM parts are weaker across layer lines (often 40–60 % in Z); in the LOOP the 1.5 safety factor is the only allowance for anisotropy, print quality, or temperature — datasheet strength, not as-built. airloom verify-champions re-checks the top frames with per-material as-built strength knockdowns on top of the same station-by-station model, and emits a bench print-and-test protocol.
  • Structural model checks the arms only (station-by-station stress along the shaft, tip deflection, resonance off the root station's stiffness); the deck is assumed rigid and the standoffs ideal.
  • The battery model is quasi-static. Constant open-circuit voltage at the nominal 3.7 V/cell (no state-of-charge curve — conservative for a short mission that only uses ~15 % of the pack), constant DC internal resistance (no temperature or transient effects), no thermal limits for pack or motors. It exists to catch physically undeliverable power demands, not to predict voltage traces.
  • Printability is enforced by real-outline geometry constraints, not full DFM. Parts are the actual (morphed) V6 outlines with their holes and cutouts, exported as separate flat pieces; but tongue bolt holes are assumed re-cut with the plates when sweep deviates from stock, the rear anchor is a best-fit (98 % clamp coverage) rather than a hole registration, and tolerances/print-orientation strength are not modeled.
  • CMA-ES mode (--optimizer cmaes) has no discrete parents — it samples from an adapted Gaussian — so the family tree is skipped and distribution-level provenance (mean, σ per generation) is stored in cma_state instead.

Phase B (CFD) — milestone 1 complete: calibrated, verdict STABLE

airloom cfd-calibrate generated 12 OpenFOAM cases (arms alone, deck/kit alone, full assembly, and a contrasting genome's full assembly — each at 0°/20°/40° tilt, freestream BCs, one snappyHexMesh per geometry) and the overnight solve produced cfd/calibration.md. Findings: the handbook buildup overestimated drag ~2× at cruise tilt; assembly interference (wake shielding + silhouette double-count) is −27 % at 20°; and the contrast genome's ratios matched the baseline's within ~2 points at every angle — the error is systematic, not gene-dependent. The corrections now live in aero.py (CAL_* tables, applied in drag_table_from_areas), and the post-calibration airloom robustness verdict is STABLE (min Spearman 0.958, no champion flips): the ranking is a frame property, not a model artifact. Consequence: the originally-planned per-candidate CFD pipeline (top-N per generation, cached by genome hash) is not needed; the seam (DragTable from AreaTable) remains if a future genome expansion moves outside the calibrated shape envelope. Note: absolute Wh/km dropped with the calibration, so the gallery's 5.0/4.0 benchmark lines predate it and need re-derivation.

Layout

config/          platform.yaml (fixed hardware), scenarios.yaml (mission,
                 portfolio, aggregation + benchmark lines), evolution.yaml
                 (GA, patience, designer, narrator, execution)
data/uiuc/       cached UIUC propeller measurements (scripts/fetch_uiuc.py refreshes)
data/source_one/ official Source One V6 7in DC plate DXF + provenance notes
docs/            glossary.html (copied into results/ next to the gallery)
src/airloom/     config, genome, realgeo (DXF outline parsing), frame_gen,
                 components, meshutil, rotor_model, aero, dryden, simulator,
                 structures, evaluate, parallel, evolution, designer,
                 narrator, loop, dbstore, lineage, gallery, render, cli
tests/           frame validity (baseline reproduces the real V6), kit
                 components, materials, designer parsing, patience/pivot,
                 UIUC points, beam hand-calc, Dryden variance, zero-wind
                 energy, hover sanity anchors
results/         everything a run produces (see above)

License

airloom is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. See LICENSE for the full text.

About

Spiral AI engineering for quadcopter resilience design

Resources

Stars

Watchers

Forks

Packages

Contributors

Languages