client.sweeps
A sweep is a parameter study: pick a base configuration plus one or more parameter ranges, and the server computes a simulation at every combination. Use sweeps to plot thrust vs throttle curves, compare propellers across a fixed motor, or build hover-time vs payload tables.
Like simulations, sweeps are asynchronous — create returns
status: "queued" immediately.
Methods
| Method | Endpoint | Returns |
|---|---|---|
client.sweeps.create(project_id=..., **body) | POST /v1/sweeps | New sweep in queued state |
client.sweeps.list(project_id=...) | GET /v1/sweeps | CursorPager of sweeps |
client.sweeps.retrieve(sweep_id) | GET /v1/sweeps/{id} | Single sweep (current status) |
client.sweeps.cancel(sweep_id) | POST /v1/sweeps/{id}/cancel | Sweep in canceled state |
client.sweeps.list_points(sweep_id) | GET /v1/sweeps/{id}/points | CursorPager of computed points |
client.sweeps.wait(sweep_id, timeout=600) | (polls retrieve) | Final sweep when terminal |
Request body
sweeps.create takes the same base configuration as a simulation, plus a
sweep object naming the axes to vary:
sweep = client.sweeps.create(
project_id="proj_2c5tQ...",
battery_component_id="comp_batt_xxx",
airspeed_m_s=0.0,
density_kg_m3=1.225,
battery_charge_pct=100,
rotor_groups=[{
"label": "main", "count": 4,
"motor_component_id": "comp_motor_xxx",
"propeller_component_id": "comp_prop_xxx",
"throttle_pct": 70, # baseline; the swept axis overrides it
}],
sweep={
"rotor_sweep_mask": [True], # which rotor groups sweep the throttle axis
"throttle": {"mode": "range", "start": 10, "stop": 100, "steps": 10},
},
)
Numeric axes (throttle, airspeed, density, battery_charge,
esc_pwm_frequency_khz, vertical_speed, tilt) each take
{"mode": "range", "start": ..., "stop": ..., "steps": ...} or
{"mode": "custom", "values": [...]}. esc_timing_values is an explicit
list of timing presets, and component_axes sweeps categorical
motor/propeller/battery choices. Multiple axes form a Cartesian grid: 2 axes
of 5 values each = 25 simulation points.
sweep also requires rotor_sweep_mask — a bool per rotor group, same
length as rotor_groups — even when no axis needs it. At least one axis (or
a non-empty component_axes) must actually be armed, or the sweep 422s with
"At least one parameter must be swept".
The tilt axis needs an extra arming step: a tilt range alone does
nothing — sweep.tilt_sweep_mask (bool per rotor group, same length as
rotor_groups) selects which groups sweep it. Each masked group is an
independent grid dimension. vertical_speed and tilt are ground-mode
only (see Flight condition below) — under
inflow_mode="components" either one 422s.
See API reference for the full body schema.
Flight condition
sweeps.create accepts the same inflow_mode as a single-point simulation
("ground" default, or "components") — see
Simulations: Flight condition
for the field-level rules. vertical_speed and tilt sweep axes only make
sense under "ground" mode; a sweep with inflow_mode="components" that sets
either one 422s.
Run synchronously (wait + list_points)
The standard pattern: submit, wait for terminal, then iterate the points.
list_points returns a cursor pager — let it
auto-iterate so you don't have to manage the cursor by hand.
sweep = client.sweeps.create(project_id="proj_xxx", ...)
result = client.sweeps.wait(sweep["id"], timeout=600, poll_interval=5.0)
if result["status"] == "completed":
for pt in client.sweeps.list_points(sweep["id"]):
print(pt["inputs"]["throttle"], "→", pt["rotors"]["1"]["thrust_n"], "N")
poll_interval=5.0 is a sane setting for sweeps — points compute in batches
and the resource updates progressively.
Read points + plot
import matplotlib.pyplot as plt
xs, ys = [], []
for pt in client.sweeps.list_points("sweep_2c5tQ..."):
xs.append(pt["inputs"]["throttle"])
ys.append(pt["rotors"]["1"]["thrust_n"])
plt.plot(xs, ys, marker="o")
plt.xlabel("throttle (%)")
plt.ylabel("per-rotor thrust (N)")
plt.title("Quad-X hover thrust")
plt.show()
Each point carries index, inputs (the axis values for that point, e.g.
{"throttle": 40.0, "airspeed": 0.0, ...}), and rotors — the same
canonical snake_case shape as a single-point result: per-group blocks
under "1"/"2"/… plus "All"/"Battery" aggregates.
The human-label map lives on the points page envelope (GET /v1/sweeps/{id}/points), not on the sweep resource itself — and
list_points's CursorPager only exposes .data, not the raw envelope, so
display_labels isn't reachable through it. To read it, call the points
endpoint directly (or see
Steady-state outputs — a sweep point
uses the identical key vocabulary as a single-point result's display_labels).
Cancel an in-flight sweep
client.sweeps.cancel("sweep_2c5tQ...")
A queued sweep cancels immediately (status comes back canceled, compute
units refunded). A running sweep instead gets 202 Accepted with status still
"running" — the cancel signal is asynchronous (points already in flight
finish normally); poll retrieve/wait to observe it flip to canceled. A
draft sweep can't be canceled (409 — cancel only applies to a dispatched
run); delete it instead with DELETE /v1/sweeps/{id} (no SDK wrapper yet).
List sweeps for a project
for sweep in client.sweeps.list(project_id="proj_2c5tQ..."):
print(sweep["id"], sweep["status"])
See also
- Async resources — sweeps poll the same way as simulations
- Simulations — the request body shape matches one-to-one
- Pagination —
list_pointscursor contract