ThrustLab

Steady-state outputs

A single-point simulation and a sweep share one output schema. A completed result carries two siblings:

  • result — the computed outputs, keyed per-rotor and by aggregate.
  • display_labels — a {snake_key: human_string} map: the source of truth for a human-readable column header (thrust_n"Thrust (N)").

Every per-rotor and aggregate key is canonical snake_case. A sweep reuses the exact same per-point shape, so everything on this page applies to both.

Result shape

result is a dict keyed by:

KeyContents
"1", "2", …One entry per rotor group, in submit order. Per-rotor performance, aero, ESC, and thermal fields.
"All"Vehicle-level roll-ups summed or aggregated across every rotor.
"Battery"Pack-level state and per-cell arrays.
sim = client.simulations.retrieve("sim_2c5tQ...")
r = sim["result"]
labels = sim["display_labels"]

r["1"]["thrust_n"]          # 9.81  — per-rotor thrust, newtons
r["All"]["total_thrust_n"]  # 39.24 — vehicle total across 4 rotors
labels["thrust_n"]          # "Thrust (N)" — the display header for that key

Reading display_labels

display_labels is one map for the whole result. Look a key up to render it; fall back to the raw key when it is absent:

for key, value in r["1"].items():
    header = labels.get(key, key)
    print(f"{header}: {value}")

The map covers every per-rotor and "All" key. It does not cover the "Battery" entry — those keys are already human strings (see below).

Per-rotor fields (result["1"], result["2"], …)

Identification

KeyLabelMeaning
propellerPropellerPropeller name.
motorMotorMotor name (omitted when empty).

Performance

KeyLabelUnits
throttle_pctThrottle (%)%
rpmRPMrev/min
thrust_nThrust (N)N (thrust carries the same value)
static_torqueStatic TorqueN·m
dynamic_torqueDynamic TorqueN·m (0 in steady state)
total_torqueTotal TorqueN·m
powerPowerW (shaft mechanical)
motor_voltage_vMotor Voltage (V)V
battery_voltage_vBattery Voltage (V)V
current_aCurrent (A)A
shaft_power_wShaft Power (W)W
mechanical_power_wMechanical Power (W)W
electrical_power_wElectrical Power (W)W
g_per_wg/Wg/W (thrust per electrical watt)
efficiency_pctEfficiency (%)%
weight_gWeight (g)g (motor + propeller)

Aerodynamics

KeyLabelUnits
jJAdvance ratio (dimensionless)
ctCtThrust coefficient (dimensionless)
cpCpPower coefficient (dimensionless)
pePeIdeal (induced) power, W
machMachTip Mach number
reynReynBlade Reynolds number
thr_per_pwrTHR/PWRg/W
veVeExit velocity, m/s (exit_velocity is the same value)
h_force_nH-force (N)In-plane hub force, N (present only for an active oblique flight condition — nonzero edgewise inflow)
inflow_validityinflow_validity"ok" or "vrs_band", categorical (same presence rule as h_force_n)
f_vertical_nVert force (N)Ground-frame vertical force, N (ground mode only — inflow_mode="components" has no tilt angle to resolve a ground frame)
f_horizontal_nHoriz force (N)Ground-frame horizontal force, N (ground mode only)

ESC

KeyLabelUnits
esc_voltage_drop_vESC Voltage Drop (V)V
esc_power_loss_wESC Power Loss (W)W
esc_efficiencyESC EfficiencyFraction (0–1)
bus_current_aBus Current (A)A
motor_voltage_after_esc_vMotor Voltage After ESC (V)V

Thermal

Present when the coupled electromagnetic + thermal solve runs.

KeyLabelUnits
t_w_degcT_w (degC)Winding temperature, °C
t_mag_degcT_mag (degC)Magnet temperature, °C
t_ambient_degcT_ambient (degC)Ambient temperature, °C
p_motor_wP_motor (W)Motor dissipation, W
r_th_k_per_wR_th (K/W)Effective thermal resistance, K/W
motor_cooling_velocity_m_sMotor cooling velocity (m/s)Cooling-air velocity, m/s
thermal_convergedThermal convergedBoolean
thermal_iterationsThermal iterationsCount
irreversible_magnet_warningIrreversible magnet warningBoolean

Solver-quality flags

frac_converged, compressibility_warning, and frac_above_Mdd stay top-level on each rotor entry: they report per-rotor convergence and compressibility state. Per-rotor solver diagnostics and build metadata are relocated under a neutral result["1"]["diagnostics"] sub-object, off the physical surface.

Aggregate fields (result["All"])

KeyLabelUnits
total_thrust_nTotal Thrust (N)N
total_electrical_power_wTotal Electrical Power (W)W
total_shaft_power_wTotal Shaft Power (W)W
total_mechanical_power_wTotal Mechanical Power (W)W
total_current_aTotal Current (A)A
total_bus_current_aTotal Bus Current (A)A
total_g_per_wTotal g/Wg/W
total_efficiency_pctTotal Efficiency (%)%
total_weight_gTotal Weight (g)g
total_esc_power_loss_wTotal ESC Power Loss (W)W
f_vertical_total_nTotal F_vertical (N)N (count-weighted vehicle total; ground mode only)
f_horizontal_total_nTotal F_horizontal (N)N (count-weighted vehicle total; ground mode only)

Thermal roll-ups (max_t_w_degc, max_t_mag_degc, max_t_core_degc, max_t_case_degc, total_motor_dissipation_w, total_battery_dissipation_w, pack_thermal_coupling_warning, …) appear when the thermal solve runs. The "All" build metadata is relocated under result["All"]["diagnostics"].

Battery entry (result["Battery"])

The "Battery" entry keeps human-string keys and is not covered by display_labels — read these keys directly:

KeyUnits
Total Voltage (V)V
Current Draw (A)A
Power Draw (W)W
Cell voltages (V)List, V per cell
Charge levels (%)List, SOC % per cell
Internal Resistance (Ohm)Ω
Configuratione.g. "4S1P"
Remaining Capacity (mAh)mAh
Est. Time Remaining (s)s

Per-cell thermal arrays (T_core, T_case, R_cell, …) ride the same entry when the thermal solve runs.

Worked example

Runs a single point and a sweep, then prints each key next to its display_labels header:

"""Read a steady-state result: single-point AND sweep share one snake_case schema.

Run it:
    export THRUSTLAB_API_KEY=key_...        # never hard-code the key
    python examples/outputs/read_steady_state.py

A completed single-point simulation carries two siblings:
  * result           — a dict keyed per-rotor ("1", "2", ...) plus the "All"
                       and "Battery" aggregates. Every per-rotor and "All" key
                       is canonical snake_case (e.g. thrust_n, total_current_a).
  * display_labels    — {snake_key: human_string}, the single source of truth for
                       a human-readable column header (thrust_n -> "Thrust (N)").

A sweep reuses the EXACT same per-point shape: each point's `rotors` mirrors a
single-point `result`, and the same display_labels vocabulary applies. Swap in
your own component IDs, or resolve them by name with client.components.find(...).
"""

from thrustlab import Client

client = Client()  # reads $THRUSTLAB_API_KEY from the environment


def print_group(name: str, group: dict, labels: dict) -> None:
    """Print each key -> value alongside its human display label.

    Scalar keys print as `snake_key   Human Label   value`. Nested blocks
    (the relocated `diagnostics` sub-object) print their key set only.
    """
    print(f"\n[{name}]")
    for key, value in group.items():
        label = labels.get(key, key)  # "Battery" keys are already human strings
        if isinstance(value, (dict, list)):
            kind = "dict" if isinstance(value, dict) else "list"
            print(f"  {key:<28} {label:<28} <{kind}, {len(value)} entries>")
        else:
            print(f"  {key:<28} {label:<28} {value}")


project = client.projects.create(name="sdk read-steady-state example")

# Resolve by name, or paste explicit IDs: motor_id = "comp_motor_xxx"
motor = client.components.find(name="Spektrum Avian 4260 800Kv")
prop = client.components.find(name="10.5x4.5")
battery = client.components.find(name="Liperior 5000mAh 4S 35C")

# ---- single point ---------------------------------------------------------
sim = client.simulations.create(
    project_id=project["id"],
    battery_component_id=battery["id"],
    airspeed_m_s=0.0,
    density_kg_m3=1.225,
    battery_charge_pct=100,
    rotor_groups=[
        {
            "label": "main",
            "count": 4,
            "motor_component_id": motor["id"],
            "propeller_component_id": prop["id"],
            "throttle_pct": 70,
        }
    ],
)
result = client.simulations.wait(sim["id"], timeout=300)
print(f"single-point status: {result['status']}")

if result["status"] == "completed":
    blob = result["result"]
    labels = result["display_labels"]  # {snake_key: human_string}

    # Per-rotor group under its label index ("1", "2", ...).
    print_group("rotor 1", blob["1"], labels)
    # Aggregate roll-ups across all rotors.
    print_group("All", blob["All"], labels)
    # The "Battery" entry is passed through as-is: per-cell arrays keyed by human
    # strings ("Cell voltages (V)", "Charge levels (%)") that are NOT in
    # display_labels — read them directly.
    print_group("Battery", blob["Battery"], labels)

# ---- sweep (same schema, one point per grid cell) -------------------------
sweep = client.sweeps.create(
    project_id=project["id"],
    battery_component_id=battery["id"],
    airspeed_m_s=0.0,
    density_kg_m3=1.225,
    battery_charge_pct=100,
    rotor_groups=[
        {
            "label": "main",
            "count": 4,
            "motor_component_id": motor["id"],
            "propeller_component_id": prop["id"],
            "throttle_pct": 70,
        }
    ],
    sweep={
        "rotor_sweep_mask": [True],
        "throttle": {"mode": "range", "start": 10, "stop": 100, "steps": 10},
    },
)
sweep_result = client.sweeps.wait(sweep["id"], timeout=600)
print(f"\nsweep status: {sweep_result['status']}")

if sweep_result["status"] == "completed":
    # Each point's `rotors` is a single-point result dict; the same snake_case
    # keys (and the same display_labels vocabulary read above) apply per point.
    print("\npoint | throttle in | per-rotor thrust_n | total_thrust_n")
    for pt in client.sweeps.list_points(sweep["id"]):
        rotor = pt["rotors"]["1"]
        agg = pt["rotors"]["All"]
        print(
            f"  {pt['index']:>2} | {pt['inputs']} | "
            f"{rotor['thrust_n']:.2f} | {agg['total_thrust_n']:.2f}"
        )

Sweeps use the same schema

Each sweep point exposes a rotors dict with the identical per-rotor / "All" / "Battery" keys and the same display_labels vocabulary:

for pt in client.sweeps.list_points("sweep_2c5tQ..."):
    thrust = pt["rotors"]["1"]["thrust_n"]
    print(pt["index"], pt["inputs"], thrust)

See also

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

Steady-state outputs · ThrustLab API