ThrustLab

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 asynchronouscreate returns status: "queued" immediately.

Methods

MethodEndpointReturns
client.sweeps.create(project_id=..., **body)POST /v1/sweepsNew sweep in queued state
client.sweeps.list(project_id=...)GET /v1/sweepsCursorPager of sweeps
client.sweeps.retrieve(sweep_id)GET /v1/sweeps/{id}Single sweep (current status)
client.sweeps.cancel(sweep_id)POST /v1/sweeps/{id}/cancelSweep in canceled state
client.sweeps.list_points(sweep_id)GET /v1/sweeps/{id}/pointsCursorPager 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

We use cookies and analytics tools to understand how you use ThrustLab and improve the experience. Privacy Policy

Python SDK — Sweeps · ThrustLab API