Aerodule
← Documentation

API Documentation

Describes how to use the Engineering Model API. API usage requires a paid plan — see plans.

API Overview

Aerodule’s API allows for scripted use of the engineering models.

Authentication

Create a key on your account page. It is shown only once, at creation. Send it as a bearer token.

Authorization: Bearer ak_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

One key per account. Creating a key revokes the previous one immediately.

Propulsion Model

Overview

Runs the propulsion system model. The API calls for propulsion data within a grid of user-set airspeeds and input voltages. It can also call for propulsion data at a single airspeed and input voltage. Solves take well under a second.

Inputs

FieldRequiredDescription
sim_params.input_unitsrequiredSets the units that the inputs in this request is written in. "system" is "imperial" or "metric".
sim_params.output_unitsrequiredSets the units that the outputs in this response is written in. "system" is "imperial" or "metric".
sim_params.atmosphererequiredSet either { altitude } and have atmospheric properties looked up from a STD ATM table or set { density } directly.
propulsion_params.motorrequiredMotor ID from the Motor & Propeller Catalog. Found here.
propulsion_params.propellerrequiredPropeller ID from the Motor & Propeller Catalog. Found here.
airspeedrequiredSets the airspeed to evaluate the propulsion system at. Input either a single airspeed or a range of airspeeds. Must be zero or greater.
voltagerequiredSets the voltage to evaluate the propulsion system at. Input either a single voltage or a range of voltages. Must be greater than 0.

Outputs

FieldDescription
model.nameThe name of the model.
model.versionThe model version that produced this result. See Model Version.
units.systemThe unit system that the outputs are written in.
motor.idThe ID of the motor.
motor.nameThe name of the motor.
motor.kvThe kV of the motor.
propeller.idThe ID of the propeller.
propeller.nameThe name of the propeller.
propeller.diameterThe diameter of the propeller.
propeller.pitchThe pitch of the propeller.
atmosphere.densityThe atmospheric density that the model was run at.
atmosphere.altitudeThe STD ATM altitude that the model was run at.
grid.voltagesThe grid of voltages that correlate with other grids of airspeed, thrust, etc.
grid.airspeedsThe grid of airspeeds that correlate with other grids of voltages, thrust, etc.
grid.thrustThe grid of thrust values that correlates with the other grids of airspeeds, voltages, etc.
grid.ampsThe grid of current (as in amps) values that correlates with the other grids of airspeeds, voltages, etc.
grid.rpmThe grid of RPMs that correlates with the other grids of airspeeds, voltages, etc.
grid.eta_motorThe grid of motor efficiency values that correlates with the other grids of airspeeds, voltages, etc.
grid.eta_propThe grid of propeller efficiency values that correlates with the other grids of airspeeds, voltages, etc.
grid.eta_totalThe grid of propulsion system efficiency values that correlates with the other grids of airspeeds, voltages, etc.
omitted.countThe amount of data points left out of the results.
omitted.pointsHolds each omitted data point and its reason for omission as { voltage, airspeed, reason }
usage.compute_secondsThe amount of API quota this request used.
usage.quota_remaining_secondsThe amount of API quota remaining in this period.
usage.reserve_remaining_secondsThe amount of reserve API quota remaining.

Examples

import os
import requests

API = "https://aerodule.com/api/v1/propulsion"
KEY = os.environ["AERODULE_API_KEY"]   # export AERODULE_API_KEY=ak_...

payload = {
    "sim_params": {
        "input_units":  {"system": "imperial"},
        "output_units": {"system": "imperial"},
        "atmosphere": {"altitude": 0},
    },
    "propulsion_params": {
        "motor": "scorpion-s-ii-2205-v2",
        "propeller": "apc-9-625x3-75n",
    },
    "airspeed": {"from": 0, "to": 120, "step": 40},
    "voltage":  {"from": 7.4, "to": 14.8, "step": 3.7},
}

r = requests.post(API, json=payload,
                  headers={"Authorization": f"Bearer {KEY}"}, timeout=30)
data = r.json()
if not r.ok:
    error = data["error"]
    raise SystemExit(f"{r.status_code} {error['code']}: {error['message']}")

if data["units"]["system"] == "imperial":
    speed, force, length, density = "ft/s", "lbf", "ft", "slug/ft^3"
else:
    speed, force, length, density = "m/s", "N", "m", "kg/m^3"

motor, prop, atm = data["motor"], data["propeller"], data["atmosphere"]
print(f"Model      : {data['model']['name']} {data['model']['version']}")
print(f"Units      : {data['units']['system']}")
print(f"Motor      : {motor['name']} ({motor['id']}), {motor['kv']} kV")
print(f"Propeller  : {prop['name']} ({prop['id']}), "
      f"diameter {prop['diameter']} {length}, pitch {prop['pitch']} {length}")
line = f"Atmosphere : density {atm['density']} {density}"
if "altitude" in atm:
    line += f", altitude {atm['altitude']} {length}"
print(line)

g = data["grid"]
tables = [
    ("thrust",    f"Thrust ({force})"),
    ("amps",      "Current (A)"),
    ("rpm",       "RPM"),
    ("eta_motor", "Motor efficiency"),
    ("eta_prop",  "Propeller efficiency"),
    ("eta_total", "Total efficiency"),
]
corner = f"V / {speed}"
for key, title in tables:
    # One row per voltage, one column per airspeed. None is an omitted point.
    print(f"\n{title}")
    print(f"{corner:>10}" + "".join(f"{a:>10g}" for a in g["airspeeds"]))
    for v, row in zip(g["voltages"], g[key]):
        cells = "".join(f"{'-':>10}" if x is None else f"{x:>10g}" for x in row)
        print(f"{v:>10g}" + cells)

print(f"\nOmitted points: {data['omitted']['count']}")
for o in data["omitted"]["points"]:
    print(f"  {o['voltage']:g} V, {o['airspeed']:g} {speed}: {o['reason']}")

u = data["usage"]
print(f"\nAPI quota used by this request : {u['compute_seconds']} s")
print(f"API quota left this period     : {u['quota_remaining_seconds']} s")
print(f"Reserve API quota left         : {u['reserve_remaining_seconds']} s")

Wing Model

Overview

Runs the wing aerodynamic model. Typical solves take 2–7 seconds with the heaviest around 12. Allow a generous client timeout to avoid timeout errors.

Inputs

FieldRequiredDescription
sim_params.input_unitsrequiredSets the units that the inputs in this request is written in. "system" is "imperial" or "metric". "angle" is "deg" or "rad".
sim_params.output_unitsrequiredSets the units that the outputs in this response is written in. "system" is "imperial" or "metric". "angle" is "deg" or "rad". "derivative" is "1/deg" or "1/rad".
sim_params.airspeedrequiredSets the freestream airspeed. Must be greater than zero.
sim_params.atmosphererequiredSet either { altitude } and have atmospheric properties looked up from a STD ATM table or set { density, viscosity } directly.
wing_params.airfoilrequiredSets the airfoil. It can be set using { catalog: "…" } with the filename of an airfoil in the Airfoil Catalog or { points: [[x, y], …] }, or { dat: "…" } as Selig-order text. Coordinates need at least four pairs of two columns, chord-normalised (0-1), running from the trailing edge forward over one surface and back along the other.
wing_params.spanrequiredSets the wingspan.
wing_params.root_chordrequiredSets the root chord.
wing_params.tip_chordrequiredSets the tip chord.
wing_params.sweeprequiredSets the sweep angle and reference location with { angle, reference }, where reference is the chord fraction the angle is measured at: 0 leading edge, 0.25 quarter chord, 1 trailing edge.
wing_params.dihedralrequiredSets wing dihedral. Must be at least 0 and less than 90 degrees (1.5708 radians).
wing_params.twistrequiredSets wingtip twist.
wing_params.tip_deviceoptionalOmit for a bare tip. { type: "endplate", height, chord } or { type: "winglet", height, taper, sweep: { angle, reference } } for endplates or winglets.
wing_params.tip_device.heightif endplate or wingletSets the vertical length of the wingtip device. Greater than 0.
wing_params.tip_device.chordif endplateSets the horizontal length of the endplate. Greater than 0.
wing_params.tip_device.taperif wingletSets the ratio of winglet tip chord to root chord, where the root chord is the wing tip chord. 0 or greater.
wing_params.tip_device.sweepif wingletSets the sweep angle of the winglet using { angle, reference }, measured the same way as wing_params.sweep.
parasite_dragoptionalOmit and parasite drag is not modelled. Include it and roughness, crud_factor and interference_factor are all required.
parasite_drag.roughnessif parasite_drag is presentCharacteristic roughness height of the wing and wingtip device surface. Greater than zero.
parasite_drag.crud_factorif parasite_drag is presentParasitic drag multiplier used to account for miscellaneous drag not modeled. 1 or greater.
parasite_drag.interference_factorif parasite_drag is presentParasite drag multiplier accounting for interference drag at the junctions with wingtip devices. 1 or greater.

Outputs

FieldDescription
model.nameThe name of the model.
model.versionThe model version that produced this result. See Model Version.
units.systemThe unit system that the outputs are written in.
units.angleThe units that the angles that the outputs are written in (deg or rad).
units.derivativeThe units that the derivatives are written in (1/deg or 1/rad).
geometry.srefPlanform area.
geometry.ARAspect ratio. Equal to the span squared divided by the planform area.
geometry.TRTaper ratio. The ratio of the tip chord to the root chord.
geometry.MACMean aerodynamic chord. The chord for a rectangular planform wing that would mirror the surface’s aerodynamic properties.
geometry.spanWingspan
geometry.root_chordRoot chord
geometry.tip_chordTip chord
geometry.sweep_refThe chordwise point that the sweep angle is measured from.
geometry.sweepSweep angle from the selected reference point.
geometry.dihedralDihedral
geometry.twistThe difference in incidence from tip chord to root chord. Negative values denote washout.
geometry.wingtip_typeDetermines the wingtip geometry. Options are none, endplate, or winglet.
geometry.wingtip_param_1Vertical length of the wingtip device. Returned for an endplate or a winglet.
geometry.wingtip_param_2Horizontal length of the endplate, or the sweep angle of the winglet about its quarter chord.
geometry.wingtip_param_3Ratio of winglet tip chord to root chord. Returned for a winglet.
geometry.airfoil_xA vector containing the x coordinates of the airfoil, in Selig order.
geometry.airfoil_yA vector containing the y coordinates of the airfoil, in Selig order.
aerodynamics.aoasA vector containing each AOA point in the output lift and drag polar.
aerodynamics.CLA vector containing the lift coefficient at each AOA.
aerodynamics.CDA vector containing the total drag coefficient at each AOA. Only returned if parasite_drag is included.
aerodynamics.CDiA vector containing the induced drag coefficient at each AOA.
aerodynamics.CD0A vector containing the parasite drag coefficient at each AOA. Only returned if parasite_drag is included.
aerodynamics.CD_frictionA vector containing the skin friction drag coefficient at each AOA. Constant across AOA. Only returned if parasite_drag is included.
aerodynamics.CD_formA vector containing the form drag coefficient at each AOA. Only returned if parasite_drag is included.
aerodynamics.CL_alphaThe lift-curve slope at 0 AOA.
aerodynamics.CY_betaThe derivative representing the change in sideforce with respect to sideslip angle at 0 AOA.
aerodynamics.CMl_betaThe derivative representing the change in rolling moment with respect to sideslip angle at 0 AOA.
aerodynamics.CMn_betaThe derivative representing the change in yawing moment with respect to sideslip angle at 0 AOA.
aerodynamics.CMm0Zero-lift pitching moment.
aerodynamics.interference_factorParasite drag multiplier accounting for interference drag at the junctions with wingtip devices.
aerodynamics.root_le_to_acLongitudinal location of the AC with respect to the root LE.
aerodynamics.stall.stall_alphaStall AOA.
aerodynamics.stall.stall_yThe spanwise location where stall occurs.
warningsPresent when there are warnings associated with the model inputs or outputs.
usage.compute_secondsThe amount of API quota this request used.
usage.quota_remaining_secondsThe amount of API quota remaining in this period.
usage.reserve_remaining_secondsThe amount of reserve API quota remaining.

Examples

Wing Model Example - Parasite Drag Omitted

import os
import requests

API = "https://aerodule.com/api/v1/wing"
KEY = os.environ["AERODULE_API_KEY"]   # export AERODULE_API_KEY=ak_...

payload = {
    "sim_params": {
        "input_units":  {"system": "imperial", "angle": "deg"},
        "output_units": {"system": "imperial", "angle": "deg", "derivative": "1/deg"},
        "airspeed": 60,
        "atmosphere": {"altitude": 0},
    },
    "wing_params": {
        "span": 8,
        "root_chord": 1.25,
        "tip_chord": 0.75,
        "sweep": {"angle": 0, "reference": 0.25},
        "dihedral": 3,
        "twist": -2,
        "airfoil": {"catalog": "naca2412.dat"},
    },
}

r = requests.post(API, json=payload,
                  headers={"Authorization": f"Bearer {KEY}"}, timeout=120)
data = r.json()
if not r.ok:
    error = data["error"]
    raise SystemExit(f"{r.status_code} {error['code']}: {error['message']}")

units = data["units"]
length, area = ("ft", "ft^2") if units["system"] == "imperial" else ("m", "m^2")
angle, per = units["angle"], units["derivative"]

print(f"Model  : {data['model']['name']} {data['model']['version']}")
print(f"Units  : {units['system']}, angles in {angle}, derivatives in {per}")

# Everything solved, then the planform, tip device and section echoed back.
geo = data["geometry"]
print("\nGeometry")
for name, unit in [("sref", area), ("AR", ""), ("TR", ""), ("MAC", length),
                   ("span", length), ("root_chord", length), ("tip_chord", length),
                   ("sweep_ref", ""), ("sweep", angle), ("dihedral", angle), ("twist", angle),
                   ("wingtip_type", "")]:
    print(f"  {name:<14} {geo[name]} {unit}".rstrip())
# One to three parameters, depending on the device: height and chord for an
# endplate; height, quarter-chord sweep and taper for a winglet; none at all
# when no device is fitted.
for i in (1, 2, 3):
    key = f"wingtip_param_{i}"
    if key in geo:
        print(f"  {key:<14} {geo[key]}")
print(f"  airfoil        {len(geo['airfoil_x'])} coordinate pairs, "
      f"first ({geo['airfoil_x'][0]}, {geo['airfoil_y'][0]})")

aero = data["aerodynamics"]
print("\nAerodynamics")
print(f"  CL_alpha       {aero['CL_alpha']} {per}")
print(f"  CY_beta        {aero['CY_beta']} {per}")
print(f"  CMl_beta       {aero['CMl_beta']} {per}")
print(f"  CMn_beta       {aero['CMn_beta']} {per}")
print(f"  CMm0           {aero['CMm0']}")
print(f"  interference_factor {aero['interference_factor']}")
print(f"  root_le_to_ac  {aero['root_le_to_ac']} {length}")
print(f"  stall_alpha    {aero['stall']['stall_alpha']} {angle}")
print(f"  stall_y        {aero['stall']['stall_y']} {length}")

# Parallel arrays on one AOA grid, from zero lift up to stall.
print(f"\n{'alpha (' + angle + ')':>12}{'CL':>12}{'CDi':>12}")
for alpha, cl, cdi in zip(aero["aoas"], aero["CL"], aero["CDi"]):
    print(f"{alpha:>12g}{cl:>12.6f}{cdi:>12.6f}")

# Present only when the solve has something to flag.
warnings = data.get("warnings", [])
print(f"\nWarnings: {len(warnings)}")
for w in warnings:
    print(f"  {w}")

u = data["usage"]
print(f"\nAPI quota used by this request : {u['compute_seconds']} s")
print(f"API quota left this period     : {u['quota_remaining_seconds']} s")
print(f"Reserve API quota left         : {u['reserve_remaining_seconds']} s")

Wing Model Example - Parasite Drag Included

import os
import requests

API = "https://aerodule.com/api/v1/wing"
KEY = os.environ["AERODULE_API_KEY"]   # export AERODULE_API_KEY=ak_...

payload = {
    "sim_params": {
        "input_units":  {"system": "imperial", "angle": "deg"},
        "output_units": {"system": "imperial", "angle": "deg", "derivative": "1/deg"},
        "airspeed": 60,
        "atmosphere": {"altitude": 0},
    },
    "wing_params": {
        "span": 8,
        "root_chord": 1.25,
        "tip_chord": 0.75,
        "sweep": {"angle": 0, "reference": 0.25},
        "dihedral": 3,
        "twist": -2,
        "airfoil": {"catalog": "naca2412.dat"},
    },
    "parasite_drag": {
        "roughness": 0.0000167,
        "crud_factor": 1.2,
        "interference_factor": 1.1,
    },
}

r = requests.post(API, json=payload,
                  headers={"Authorization": f"Bearer {KEY}"}, timeout=120)
data = r.json()
if not r.ok:
    error = data["error"]
    raise SystemExit(f"{r.status_code} {error['code']}: {error['message']}")

units = data["units"]
length, area = ("ft", "ft^2") if units["system"] == "imperial" else ("m", "m^2")
angle, per = units["angle"], units["derivative"]

print(f"Model  : {data['model']['name']} {data['model']['version']}")
print(f"Units  : {units['system']}, angles in {angle}, derivatives in {per}")

# Everything solved, then the planform, tip device and section echoed back.
geo = data["geometry"]
print("\nGeometry")
for name, unit in [("sref", area), ("AR", ""), ("TR", ""), ("MAC", length),
                   ("span", length), ("root_chord", length), ("tip_chord", length),
                   ("sweep_ref", ""), ("sweep", angle), ("dihedral", angle), ("twist", angle),
                   ("wingtip_type", "")]:
    print(f"  {name:<14} {geo[name]} {unit}".rstrip())
# One to three parameters, depending on the device: height and chord for an
# endplate; height, quarter-chord sweep and taper for a winglet; none at all
# when no device is fitted.
for i in (1, 2, 3):
    key = f"wingtip_param_{i}"
    if key in geo:
        print(f"  {key:<14} {geo[key]}")
print(f"  airfoil        {len(geo['airfoil_x'])} coordinate pairs, "
      f"first ({geo['airfoil_x'][0]}, {geo['airfoil_y'][0]})")

aero = data["aerodynamics"]
print("\nAerodynamics")
print(f"  CL_alpha       {aero['CL_alpha']} {per}")
print(f"  CY_beta        {aero['CY_beta']} {per}")
print(f"  CMl_beta       {aero['CMl_beta']} {per}")
print(f"  CMn_beta       {aero['CMn_beta']} {per}")
print(f"  CMm0           {aero['CMm0']}")
print(f"  interference_factor {aero['interference_factor']}")
print(f"  root_le_to_ac  {aero['root_le_to_ac']} {length}")
print(f"  stall_alpha    {aero['stall']['stall_alpha']} {angle}")
print(f"  stall_y        {aero['stall']['stall_y']} {length}")

# Parallel arrays on one AOA grid, from zero lift up to stall. With parasite_drag
# sent, the drag build-up is included: CD = CDi + CD0, CD0 = CD_friction + CD_form.
columns = ["CL", "CD", "CDi", "CD0", "CD_friction", "CD_form"]
print(f"\n{'alpha (' + angle + ')':>12}" + "".join(f"{name:>12}" for name in columns))
for i, alpha in enumerate(aero["aoas"]):
    print(f"{alpha:>12g}" + "".join(f"{aero[name][i]:>12.6f}" for name in columns))

# Present only when the solve has something to flag.
warnings = data.get("warnings", [])
print(f"\nWarnings: {len(warnings)}")
for w in warnings:
    print(f"  {w}")

u = data["usage"]
print(f"\nAPI quota used by this request : {u['compute_seconds']} s")
print(f"API quota left this period     : {u['quota_remaining_seconds']} s")
print(f"Reserve API quota left         : {u['reserve_remaining_seconds']} s")

Aircraft Model

Overview

Runs the aircraft aerodynamic model. Typical solves take 10–30 seconds. Allow a generous client timeout to avoid timeout errors. When modeling a v-tail or a-tail, the API expects many tail inputs to be under horizontal tail inputs. Reference the input/output indices for more detail.

Inputs

sim_params

FieldRequiredDescription
sim_params.input_unitsrequiredSets the units that the inputs in this request is written in. "system" is "imperial" or "metric". "angle" is "deg" or "rad". "derivative" is "1/deg" or "1/rad".
sim_params.output_unitsrequiredSets the units that the outputs in this response is written in. "system" is "imperial" or "metric". "angle" is "deg" or "rad". "derivative" is "1/deg" or "1/rad".
sim_params.parasite_dragrequiredSet as "true" to model parasitic drag on all parts. Set as "false" to ignore parasitic drag in the modeling process.
sim_params.airspeedrequiredSets the freestream airspeed. Must be greater than zero.
sim_params.max_trim_aoaoptionalSets the maximum AOA of the aircraft sweep. If the set AOA is above the wing stall AOA, then the input will be overridden to the wing stall AOA. If the field is ignored, then the maximum AOA of the sweep will be at wing stall.
sim_params.grow_tail_to_trimrequiredSet as "true" to grow the horizontal tail to allow the aircraft to trim at every modeled AOA. Set as "false" to ignore this process.
sim_params.atmosphererequiredSet either { altitude } and have atmospheric properties looked up from a STD ATM table or set { density, viscosity } directly.

wing_params

FieldRequiredDescription
wing_params.aerodynamicsrequiredSets the source of wing aerodynamics data. "simulate" simulates the wing aerodynamics and "input" allows wing aerodynamics to be input.
wing_params.airfoilif wing_params.aerodynamics=simulateSets the airfoil. It can be set using { catalog: "…" } with the filename of an airfoil in the Airfoil Catalog or { points: [[x, y], …] }, or { dat: "…" } as Selig-order text. Coordinates need at least four pairs of two columns, chord-normalised (0-1), running from the trailing edge forward over one surface and back along the other.
wing_params.spanrequiredSets the wingspan.
wing_params.root_chordrequiredSets the root chord.
wing_params.tip_chordrequiredSets the tip chord.
wing_params.sweeprequiredSets the sweep angle and reference location with { angle, reference }, where reference is the chord fraction the angle is measured at: 0 leading edge, 0.25 quarter chord, 1 trailing edge.
wing_params.dihedralrequiredSets wing dihedral. Must be at least 0 and less than 90 degrees (1.5708 radians).
wing_params.twistrequiredSets wingtip twist.
wing_params.tip_deviceoptional{ type: "endplate", height, chord } or { type: "winglet", height, taper, sweep: { angle, reference } }. Accepted only when aerodynamics is "simulate".
wing_params.tip_device.heightif endplate or wingletSets the vertical length of the wingtip device. Greater than 0.
wing_params.tip_device.chordif endplateSets the horizontal length of the endplate. Greater than 0.
wing_params.tip_device.taperif wingletSets the ratio of winglet tip chord to root chord, where the root chord is the wing tip chord. 0 or greater.
wing_params.tip_device.sweepif wingletSets the sweep angle of the winglet using { angle, reference }, measured the same way as wing_params.sweep.
wing_params.roughnessif sim_params.parasite_drag=true and wing_params.aerodynamics=simulateCharacteristic roughness height of the wing and wingtip device surface. Greater than zero.
wing_params.crud_factorif sim_params.parasite_drag=true and wing_params.aerodynamics=simulateParasitic drag multiplier used to account for miscellaneous drag not modeled. 1 or greater.
wing_params.polarif wing_params.aerodynamics=inputSets the wing aerodynamic data using { aoa, CL, CD } as three equal-length arrays. Each array must have at least 3 points, be ordered from low AOA to high AOA with all unique AOAs. If sim_params.parasite_drag is set to true, the polar can alternatively be input as { aoa, CL, CD, CDi, CD0 } with five equal-length arrays. This is not required, but allows wing_params.interference_factor to be applied to the parasitic drag coefficient, CD0. If sim_params.parasite_drag is enabled but wing_params.polar is formatted as only { aoa, CL, CD }, then wing_params.interference_factor will have no effect on the model.
wing_params.root_le_to_acif wing_params.aerodynamics=inputLongitudinal location of the wing AC with respect to the root LE.
wing_params.CMm0if wing_params.aerodynamics=inputWing pitching moment coefficient at zero lift.
wing_params.CMl_betaif wing_params.aerodynamics=inputThe derivative representing the change in rolling moment with respect to sideslip angle.
wing_params.CY_betaif wing_params.aerodynamics=inputThe derivative representing the change in sideforce with respect to sideslip angle.
wing_params.interference_factorif sim_params.parasite_drag=trueParasite drag multiplier accounting for interference drag at the junctions with wingtip devices and/or with the fuselage. 1 or greater.
wing_params.cg_to_acrequiredLongitudinal location of the wing AC with respect to the aircraft CG.
wing_params.ac_z_from_cgrequiredHeight of the wing AC above the CG, positive up. It is the moment arm the wing's drag acts on, so it is not cosmetic.
wing_params.incidencerequiredWing incidence to the fuselage reference line.

tail_params

FieldRequiredDescription
tail_params.configurationrequiredSets the tail configuration. Options are conventional/cruciform, t_tail, v_tail, a_tail, u_tail, inverted_u_tail or h_tail.
tail_params.elevator.chord_ratiorequiredRatio of the elevator (or ruddervator) chord to the MAC of the tail surface it is attached to. Must be greater than 0 and less than .33
tail_params.elevator.max_deflectionrequiredMaximum angle that the elevator (or ruddervator) can deflect in either direction. The total elevator range of motion is twice this value. Must be greater than 0 and less than 40 degrees (.698 radians)
tail_params.spanwise_mac_locationif tail_params.vertical.aerodynamics=input (tail_params.horizontal for a v_tail or a_tail), unless tail_params.configuration=h_tailThe spanwise distance from the root chord of the vertical tail to the MAC. For a v-tail or a-tail configuration, this value is the spanwise distance from the root chord of the entire tail to the MAC. For an h-tail configuration, this value is not accepted. Values must be positive.

tail_params.horizontal

FieldRequiredDescription
tail_params.horizontal.aerodynamicsrequiredSets the source of tail aerodynamics data. "simulate" simulates the tail aerodynamics and "input" allows tail aerodynamics to be input.
tail_params.horizontal.sizingrequiredSet the horizontal tail sizing parameter with { method: "stability", value }, { method: "volume_coefficient", value } or { method: "area", value }. For a v-tail or a-tail, this input is used with tail_params.vertical.sizing to size the tail.
tail_params.horizontal.sizing.methodrequiredSets how the horizontal tail is sized. Options are stability, volume_coefficient, or area. If "stability" is selected, the horizontal tail is sized based on an aircraft longitudinal static stability requirement. If "volume_coefficient" is selected, the horizontal tail is sized based on a horizontal tail volume coefficient. If "area" is selected, the horizontal tail is sized directly based on the input value. For a v-tail or a-tail, this input is used with tail_params.vertical.sizing.method to size the tail.
tail_params.horizontal.sizing.valuerequiredSets the value for the associated tail_params.horizontal.sizing.method. If tail_params.horizontal.sizing.method is set to "stability", then this value is the desired longitudinal static stability coefficient. If tail_params.horizontal.sizing.method is set to "volume_coefficient", then this value is the desired horizontal tail volume coefficient. If tail_params.horizontal.sizing.method is set to "area", then this value is the desired horizontal tail area.
tail_params.horizontal.ARrequiredSets the horizontal tail aspect ratio. Equal to the wingspan squared divided by the planform area. In the case of a v-tail or a-tail, this sets the aspect ratio of the entire tail.
tail_params.horizontal.TRrequiredSets the horizontal tail taper ratio. The ratio of the tip chord to the root chord. In the case of a v-tail or a-tail, this sets the taper ratio of the entire tail.
tail_params.horizontal.sweeprequiredSets the horizontal tail sweep angle and reference location with { angle, reference }, where reference is the chord fraction the angle is measured at: 0 leading edge, 0.25 quarter chord, 1 trailing edge. In the case of a v-tail or a-tail, this sets the sweep of the entire tail.
tail_params.horizontal.airfoilif tail_params.horizontal.aerodynamics=simulateSets the airfoil. It can be set using { catalog: "…" } with the filename of an airfoil in the Airfoil Catalog or { points: [[x, y], …] }, or { dat: "…" } as Selig-order text. Coordinates need at least four pairs of two columns, chord-normalised (0-1), running from the trailing edge forward over one surface and back along the other.
tail_params.horizontal.armrequiredSets the longitudinal location of the horizontal tail AC with respect to the aircraft CG. In the case of a v-tail or a-tail, this sets the longitudinal AC location of the entire tail.
tail_params.horizontal.incidencerequiredSets the horizontal tail incidence to the fuselage reference line. In the case of a v-tail or a-tail, this sets the incidence of the entire tail.
tail_params.horizontal.root_z_from_cgrequired unless tail_params.configuration=t_tailSets the vertical location of the horizontal tail root chord with respect to the aircraft CG. In the case of a v-tail or a-tail, this sets the vertical root location of the entire tail.
tail_params.horizontal.roughnessif sim_params.parasite_drag=true and tail_params.horizontal.aerodynamics=simulateCharacteristic roughness height of the tail surface. Greater than zero.
tail_params.horizontal.polarif tail_params.horizontal.aerodynamics=inputSets the tail aerodynamic data using { aoa, CL, CD } as three equal-length arrays. Each array must have at least 3 points, be ordered from low AOA to high AOA with all unique AOAs. If sim_params.parasite_drag is set to true, the polar can alternatively be input as { aoa, CL, CD, CDi, CD0 } with five equal-length arrays. This is not required, but allows tail_params.horizontal.interference_factor to be applied to the parasitic drag coefficient, CD0. If sim_params.parasite_drag is enabled but tail_params.horizontal.polar is formatted as only { aoa, CL, CD }, then tail_params.horizontal.interference_factor will have no effect on the model. In the case of a v-tail or a-tail, the uploaded polar should be for a flat surface. The model will add dihedral to the surface and properly scale the lift and drag coefficients based on dihedral.
tail_params.horizontal.root_le_to_acif tail_params.horizontal.aerodynamics=inputLongitudinal location of the tail AC with respect to its root LE.
tail_params.horizontal.CMm0if tail_params.horizontal.aerodynamics=inputTail pitching moment coefficient at zero lift.
tail_params.horizontal.crud_factorif sim_params.parasite_drag=true and tail_params.horizontal.aerodynamics=simulateParasitic drag multiplier used to account for miscellaneous drag not modeled. 1 or greater.
tail_params.horizontal.interference_factorif sim_params.parasite_drag=trueParasite drag multiplier accounting for interference drag at the junctions with other tail surfaces and/or with the fuselage. 1 or greater.

tail_params.vertical

FieldRequiredDescription
tail_params.vertical.aerodynamicsrequired unless tail_params.configuration=v_tail or a_tailSets the source of tail aerodynamics data. "simulate" simulates the tail aerodynamics and "input" allows tail aerodynamics to be input.
tail_params.vertical.sizingrequiredSet the vertical tail sizing parameter with { method: "stability", value }, { method: "volume_coefficient", value } or { method: "area", value }. For a v-tail or a-tail, the options are { method: "stability", value }, { method: "volume_coefficient", value } or { method: "dihedral", value } and the input is used with tail_params.horizontal.sizing to size the tail.
tail_params.vertical.sizing.methodrequiredSets how the vertical tail is sized. Options are stability, volume_coefficient, area, or dihedral. If "stability" is selected, the vertical tail is sized based on an aircraft directional static stability requirement. If "volume_coefficient" is selected, the vertical tail is sized based on a vertical tail volume coefficient. If "area" is selected, the vertical tail is sized directly based on the input value. "dihedral" must be selected as an option if tail_params.horizontal.sizing.method is set to "area" and a v-tail or a-tail is being used; it can only be selected under those conditions. For a v-tail or a-tail, this input is used with tail_params.horizontal.sizing.method to size the tail.
tail_params.vertical.sizing.valuerequiredSets the value for the associated tail_params.vertical.sizing.method. If tail_params.vertical.sizing.method is set to "stability", then this value is the desired directional static stability coefficient. If tail_params.vertical.sizing.method is set to "volume_coefficient", then this value is the desired vertical tail volume coefficient. If tail_params.vertical.sizing.method is set to "area", then this value is the desired vertical tail area. If tail_params.vertical.sizing.method is set to "dihedral", then this value is the v-tail or a-tail dihedral angle. Note that for an a-tail, positive dihedral values result in anhedral. Dihedral angle must be at least 0 and less than 90 degrees (1.5708 radians).
tail_params.vertical.ARrequired unless tail_params.configuration=v_tail or a_tailAspect ratio. Equal to the wingspan squared divided by the planform area.
tail_params.vertical.TRrequired unless tail_params.configuration=v_tail or a_tailTaper ratio. The ratio of the tip chord to the root chord.
tail_params.vertical.sweeprequired unless tail_params.configuration=v_tail or a_tailSets the sweep angle and reference location with { angle, reference }, where reference is the chord fraction the angle is measured at: 0 leading edge, 0.25 quarter chord, 1 trailing edge.
tail_params.vertical.airfoilif tail_params.vertical.aerodynamics=simulateSets the airfoil. It can be set using { catalog: "…" } with the filename of an airfoil in the Airfoil Catalog or { points: [[x, y], …] }, or { dat: "…" } as Selig-order text. Coordinates need at least four pairs of two columns, chord-normalised (0-1), running from the trailing edge forward over one surface and back along the other.
tail_params.vertical.armrequired unless tail_params.configuration=v_tail or a_tailLongitudinal location of the tail AC with respect to the aircraft CG.
tail_params.vertical.root_z_from_cgrequired unless tail_params.configuration=v_tail or a_tailSets the vertical location of the tail root chord with respect to the aircraft CG.
tail_params.vertical.roughnessif sim_params.parasite_drag=true and tail_params.vertical.aerodynamics=simulateCharacteristic roughness height of the tail surface. Greater than zero.
tail_params.vertical.CY_betaif tail_params.vertical.aerodynamics=inputThe derivative representing the change in sideforce with respect to sideslip angle. Could also be considered as the vertical tail’s lift-curve slope.
tail_params.vertical.CD0if tail_params.vertical.aerodynamics=inputSets the parasitic drag coefficient of the vertical tail.
tail_params.vertical.root_le_to_acif tail_params.vertical.aerodynamics=inputLongitudinal location of the tail AC with respect to its root LE.
tail_params.vertical.crud_factorif sim_params.parasite_drag=true and tail_params.vertical.aerodynamics=simulateParasitic drag multiplier used to account for miscellaneous drag not modeled. 1 or greater.
tail_params.vertical.interference_factorif sim_params.parasite_drag=trueParasite drag multiplier accounting for interference drag at the junctions with other tail surfaces and/or with the fuselage. 1 or greater.

fuselage_params

FieldRequiredDescription
fuselage_params.noseif fuselage_params is presentSets the { length, angle } of the fuselage nose section.
fuselage_params.centerif fuselage_params is presentSets the { length, angle } of the fuselage center section.
fuselage_params.tailif fuselage_params is presentSets the { length, angle } of the fuselage tail section.
fuselage_params.nose_to_cgif fuselage_params is presentSets the longitudinal location of the aircraft CG with respect to the fuselage nose.
fuselage_params.fwd_center_diameterif fuselage_params is presentSets the diameter at the forward end of the fuselage center section. Also equal to the diameter of the aft end of the fuselage nose section.
fuselage_params.aft_center_diameterif fuselage_params is presentSets the diameter at the aft end of the fuselage center section. Also equal to the diameter of the forward end of the fuselage tail section.
fuselage_params.roughnessif fuselage_params is present and sim_params.parasite_drag=trueCharacteristic roughness height of the fuselage surface. Greater than zero.
fuselage_params.crud_factorif fuselage_params is present and sim_params.parasite_drag=trueParasitic drag multiplier used to account for miscellaneous drag not modeled. 1 or greater.
fuselage_params.interference_factorif fuselage_params is present and sim_params.parasite_drag=trueParasite drag multiplier accounting for interference drag at the junctions with the wings, tail, etc. 1 or greater.

propeller_params

FieldRequiredDescription
propeller_params.diameterif propeller_params is presentSets the propeller diameter.
propeller_params.countif propeller_params is presentSets the number of each motor / propeller set on the aircraft.
propeller_params.bladesif propeller_params is presentSets the number of blades on each propeller. Options are 2, 3, 4, and 6.
propeller_params.thrustif propeller_params is presentSets the thrust per propeller at the reference airspeed.
propeller_params.hub_to_cgif propeller_params is presentSets the longitudinal location of the propeller hub with respect to the aircraft CG. Positive is aft of the CG.
propeller_params.hub_z_from_cgif propeller_params is presentSets the vertical location of the propeller hub with respect to the aircraft CG. Positive is up.
propeller_params.blanket_ratio.horizontalif propeller_params is presentSets the fraction of the horizontal tail area immersed in the propeller slipstream.
propeller_params.blanket_ratio.verticalif propeller_params is present, unless tail_params.configuration=v_tail or a_tailSets the fraction of the vertical tail area immersed in the propeller slipstream.

misc_drag_params

FieldRequiredDescription
misc_drag_params.sourcesoptional, if sim_params.parasite_drag=trueAdd an entry of { cd, area, z_from_cg } for each miscellaneous drag source. cd is its drag coefficient, area is its reference area, and z_from_cg is its vertical location relative to the cg, where positive is above the cg.
misc_drag_params.nacellesoptionalAdd an entry of { length, diameter } for each nacelle where diameter is the nacelle’s maximum diameter. If parasite_drag is set to "true", the entry must be { length, diameter, roughness, interference_factor } where roughness is the characteristic roughness height of the nacelle surface (greater than 0) and interference_factor is the parasite drag multiplier accounting for interference drag at the junctions with other surfaces (1 or greater).
misc_drag_params.nacelle_crud_factorif sim_params.parasite_drag=true and nacelles is presentParasitic drag multiplier used to account for miscellaneous drag not modeled. 1 or greater.

Outputs

FieldDescription
model.nameThe name of the model.
model.versionThe model version that produced this result, with the wing solver's version appended when the wing was simulated. See Model Version.
units.systemThe unit system that the outputs are written in.
units.angleThe units that the angles that the outputs are written in (deg or rad).
units.derivativeThe units that the derivatives are written in (1/deg or 1/rad).
geometry.aircraft.Swet_fusFuselage wetted area, also defined as the fuselage outer surface area.
geometry.aircraft.x_cg_to_wing_acLongitudinal location of the wing AC with respect to the aircraft CG.
geometry.aircraft.z_wing_ac_from_cgHeight of the wing AC above the CG, positive up.
geometry.aircraft.wing_incidenceWing incidence to the fuselage reference line.
geometry.aircraft.tail_configurationThe tail configuration. Options are conventional/cruciform, t_tail, v_tail, a_tail, u_tail, inverted_u_tail or h_tail.
geometry.wing.srefPlanform area of the wing.
geometry.wing.ARAspect ratio. Equal to the span squared divided by the planform area.
geometry.wing.TRTaper ratio. The ratio of the tip chord to the root chord.
geometry.wing.MACMean aerodynamic chord. The chord for a rectangular planform wing that would mirror the surface’s aerodynamic properties.
geometry.wing.spanTip-to-tip span of the wing.
geometry.wing.root_chordChord length at the wing root.
geometry.wing.tip_chordChord length at the wing tip.
geometry.wing.sweep_refThe chordwise point that the wing sweep angle is measured from.
geometry.wing.sweepWing sweep angle from the selected reference point.
geometry.wing.dihedralThe upward angle of the surface from the horizontal when viewed from the front.
geometry.wing.twistThe difference in wing incidence from tip chord to root chord. Negative values create washout.
geometry.wing.wingtip_typeDetermines the wingtip geometry. Options are none, endplate, or winglet.
geometry.wing.wingtip_param_1Vertical length of the wingtip device. Returned for an endplate or a winglet.
geometry.wing.wingtip_param_2Horizontal length of the endplate, or the sweep angle of the winglet about its quarter chord.
geometry.wing.wingtip_param_3Ratio of winglet tip chord to root chord. Returned for a winglet.
geometry.wing.airfoil_xA vector containing the x coordinates of the airfoil, in Selig order. Returned when the wing aerodynamics are simulated.
geometry.wing.airfoil_yA vector containing the y coordinates of the airfoil, in Selig order. Returned when the wing aerodynamics are simulated.
geometry.tail.tail_configurationThe tail configuration. Options are conventional/cruciform, t_tail, v_tail, a_tail, u_tail, inverted_u_tail or h_tail.
geometry.tail.dihedralThe v-tail or a-tail dihedral angle. For a v-tail dihedral angle behaves like wing dihedral. For an a-tail the sign of dihedral is reversed and positive values result in anhedral.
geometry.tail.horizontal.areaPlanform area of the horizontal tail. For a v-tail or a-tail, this is the planform area for the entire tail.
geometry.tail.horizontal.spanTip-to-tip span of the horizontal tail. For a v-tail or a-tail this is not the tip-to-tip span, it is twice the distance from the root chord to the tip chord.
geometry.tail.horizontal.root_chordChord length at the surface root. For a v-tail or a-tail, this is the root chord for the entire tail.
geometry.tail.horizontal.tip_chordChord length at the surface tip. For a v-tail or a-tail, this is the tip chord for the entire tail.
geometry.tail.horizontal.MACMean aerodynamic chord of the surface. For a v-tail or a-tail, this is the MAC for the entire tail.
geometry.tail.horizontal.x_ac_from_root_LELongitudinal location of the horizontal tail AC with respect to its root LE. For a v-tail or a-tail, this is the AC location for the entire tail.
geometry.tail.horizontal.root_z_from_cgThe vertical location of the horizontal tail root chord with respect to the aircraft CG. For a v-tail or a-tail this sets the vertical location of the entire tail root.
geometry.tail.horizontal.ARThe horizontal tail aspect ratio. Equal to the span squared divided by the planform area. For a v-tail or a-tail, this is the aspect ratio of the entire tail.
geometry.tail.horizontal.TRThe horizontal tail taper ratio. The ratio of the tip chord to the root chord. For a v-tail or a-tail, this is the taper ratio of the entire tail.
geometry.tail.horizontal.sweep_refThe chordwise point that the horizontal tail sweep angle is measured from.
geometry.tail.horizontal.sweepHorizontal tail sweep angle from the selected reference point. For a v-tail or a-tail, this is the sweep of the entire tail.
geometry.tail.horizontal.armLongitudinal location of the horizontal tail AC with respect to the aircraft CG. For a v-tail or a-tail, this is the longitudinal AC location of the entire tail.
geometry.tail.horizontal.incidenceHorizontal tail incidence to the fuselage reference line. For a v-tail or a-tail, this is the incidence of the entire tail.
geometry.tail.horizontal.airfoil_xA vector containing the x coordinates of the horizontal tail airfoil, in Selig order. Returned when the horizontal tail aerodynamics are simulated.
geometry.tail.horizontal.airfoil_yA vector containing the y coordinates of the horizontal tail airfoil, in Selig order. Returned when the horizontal tail aerodynamics are simulated.
geometry.tail.vertical.areaPlanform area of the vertical tail. For a v-tail or a-tail, this planform area is not returned.
geometry.tail.vertical.spanRoot-to-tip span of one vertical fin. For a v-tail or a-tail, this span is not returned. On an h-tail, this value represents the full height of each vertical tail.
geometry.tail.vertical.root_chordChord length at the surface root. For a v-tail or a-tail, this root chord is not returned.
geometry.tail.vertical.tip_chordChord length at the surface tip. For a v-tail or a-tail, this tip chord is not returned.
geometry.tail.vertical.MACMean aerodynamic chord of the surface. For a v-tail or a-tail, this MAC is not returned.
geometry.tail.vertical.x_ac_from_root_LELongitudinal location of the vertical tail AC with respect to its root LE. For a v-tail or a-tail, this AC location is not returned.
geometry.tail.vertical.root_z_from_cgThe vertical location of the vertical tail root chord with respect to the aircraft CG. For a v-tail or a-tail, this vertical location is not returned.
geometry.tail.vertical.ARThe vertical tail aspect ratio. Equal to the span squared divided by the planform area. For a v-tail or a-tail, this aspect ratio is not returned.
geometry.tail.vertical.TRThe vertical tail taper ratio. The ratio of the tip chord to the root chord. For a v-tail or a-tail, this taper ratio is not returned.
geometry.tail.vertical.sweep_refThe chordwise point that the vertical tail sweep angle is measured from. For a v-tail or a-tail, this is not returned.
geometry.tail.vertical.sweepVertical tail sweep angle from the selected reference point. For a v-tail or a-tail, this sweep is not returned.
geometry.tail.vertical.armLongitudinal location of the vertical tail AC with respect to the aircraft CG. For a v-tail or a-tail, this location is not returned.
geometry.tail.vertical.airfoil_xA vector containing the x coordinates of the vertical tail airfoil, in Selig order. Returned when the vertical tail aerodynamics are simulated, and not for a v-tail or a-tail.
geometry.tail.vertical.airfoil_yA vector containing the y coordinates of the vertical tail airfoil, in Selig order. Returned when the vertical tail aerodynamics are simulated, and not for a v-tail or a-tail.
aerodynamics.aircraft.aoasA vector containing each aircraft AOA point in the output aircraft lift and drag polar. Angles the aircraft cannot trim at are left out.
aerodynamics.aircraft.CLA vector containing the trimmed aircraft lift coefficient at each AOA.
aerodynamics.aircraft.CDA vector containing the aircraft total drag coefficient at each AOA.
aerodynamics.aircraft.CD_wingA vector containing the wing drag coefficient at each AOA.
aerodynamics.aircraft.CD_fusA vector containing the fuselage drag coefficient at each AOA.
aerodynamics.aircraft.CD_HTA vector containing the horizontal tail drag coefficient at each AOA, not including trim drag. For a v-tail or a-tail, this is the drag of the entire tail.
aerodynamics.aircraft.CD_VTA vector containing the vertical tail drag coefficient at each AOA. For a v-tail or a-tail, this value is null.
aerodynamics.aircraft.CD_trimA vector containing the drag coefficient added by elevator (or ruddervator) deflection at each AOA.
aerodynamics.aircraft.CD_nacA vector containing the nacelle drag coefficient at each AOA.
aerodynamics.aircraft.CD_miscA vector containing the drag coefficient of the miscellaneous drag sources at each AOA.
aerodynamics.aircraft.de_trimA vector containing elevator (or ruddervator) deflection needed to trim at each angle.
aerodynamics.aircraft.propwash_HTThe average dynamic pressure ratio of the horizontal tail due to being in the propeller slipstream. For a v-tail or a-tail, this is the dynamic pressure ratio of the entire tail.
aerodynamics.aircraft.propwash_VTThe average dynamic pressure ratio of the vertical tail due to being in the propeller slipstream.
aerodynamics.aircraft.CMm_aoaThe derivative representing the change in aircraft pitching moment with respect to AOA at 0 AOA. Also defined as the aircraft’s longitudinal static stability coefficient at 0 AOA.
aerodynamics.aircraft.CMn_betaThe derivative representing the change in aircraft yawing moment with respect to sideslip angle at 0 AOA. Also defined as the aircraft’s directional static stability coefficient at 0 AOA.
aerodynamics.aircraft.CMl_betaThe derivative representing the change in aircraft rolling moment with respect to sideslip angle at 0 AOA. Also defined as the aircraft’s lateral static stability coefficient at 0 AOA.
aerodynamics.aircraft.SMAircraft static margin. Normalized by wing MAC.
aerodynamics.aircraft.NPThe longitudinal point on the aircraft where the neutral point is located, measured from the aircraft CG.
aerodynamics.wing.aoasA vector containing each AOA point in the wing lift and drag polar. Returned when the wing aerodynamics are simulated.
aerodynamics.wing.CLA vector containing the wing lift coefficient at each AOA. Returned when the wing aerodynamics are simulated.
aerodynamics.wing.CDA vector containing the wing total drag coefficient at each AOA, the plain sum of its induced and parasite halves. Only returned if parasite_drag is included. Returned when the wing aerodynamics are simulated.
aerodynamics.wing.CDiA vector containing the wing induced drag coefficient at each AOA. Returned when the wing aerodynamics are simulated.
aerodynamics.wing.CD0A vector containing the wing parasite drag coefficient at each AOA. Only returned if parasite_drag is included. Returned when the wing aerodynamics are simulated.
aerodynamics.wing.CD_frictionA vector containing the wing skin friction drag coefficient at each AOA. Constant across AOA. Only returned if parasite_drag is included. Returned when the wing aerodynamics are simulated.
aerodynamics.wing.CD_formA vector containing the wing form drag coefficient at each AOA. Only returned if parasite_drag is included. Returned when the wing aerodynamics are simulated.
aerodynamics.wing.CL_alphaThe wing lift-curve slope at 0 AOA. Returned when the wing aerodynamics are simulated.
aerodynamics.wing.CY_betaThe derivative representing the change in wing sideforce with respect to sideslip angle at 0 AOA. Returned when the wing aerodynamics are simulated.
aerodynamics.wing.CMl_betaThe derivative representing the change in wing rolling moment with respect to sideslip angle at 0 AOA. Returned when the wing aerodynamics are simulated.
aerodynamics.wing.CMn_betaThe derivative representing the change in wing yawing moment with respect to sideslip angle at 0 AOA. Returned when the wing aerodynamics are simulated.
aerodynamics.wing.CMm0Wing zero-lift pitching moment. Returned when the wing aerodynamics are simulated.
aerodynamics.wing.interference_factorParasite drag multiplier accounting for interference drag at the junctions with wingtip devices. 1 when parasite drag is not modelled. Returned when the wing aerodynamics are simulated.
aerodynamics.wing.root_le_to_acLongitudinal location of the wing AC with respect to the root LE. Returned when the wing aerodynamics are simulated.
warningsPresent when there are warnings associated with the model inputs or outputs.
usage.compute_secondsThe amount of API quota this request used.
usage.quota_remaining_secondsThe amount of API quota remaining in this period.
usage.reserve_remaining_secondsThe amount of reserve API quota remaining.

Examples

Aircraft Model Example 1

  • Wing and tail only
  • Parasite drag off
  • Wing aerodynamics simulated
  • Wing airfoil read from the Airfoil Catalog
  • Endplates fitted to the wingtips
  • Conventional tail
  • Horizontal and vertical tails sized by static stability coefficient
import os
import requests

API = "https://aerodule.com/api/v1/aircraft"
KEY = os.environ["AERODULE_API_KEY"]   # export AERODULE_API_KEY=ak_...

units = {"system": "imperial", "angle": "deg", "derivative": "1/deg"}

payload = {
    "sim_params": {
        "input_units": units,
        "output_units": units,
        "airspeed": 60,
        "atmosphere": {"altitude": 0},
        "parasite_drag": False,
        "grow_tail_to_trim": False,
    },
    "wing_params": {
        "span": 8,
        "root_chord": 1.25,
        "tip_chord": 0.75,
        "sweep": {"angle": 0, "reference": 0.25},
        "dihedral": 3,
        "twist": -2,
        "incidence": 2,
        "cg_to_ac": 0.1,
        "ac_z_from_cg": 0,
        "aerodynamics": "simulate",
        "airfoil": {"catalog": "naca2412.dat"},
        "tip_device": {"type": "endplate", "height": 0.45, "chord": 0.5},
    },
    "tail_params": {
        "configuration": "conventional/cruciform",
        "elevator": {"chord_ratio": 0.33, "max_deflection": 30},
        "horizontal": {
            "AR": 4,
            "TR": 0.6,
            "sweep": {"angle": 0, "reference": 0.25},
            "arm": 3.5,
            "sizing": {"method": "stability", "value": -0.03},
            "incidence": 0,
            "root_z_from_cg": 0,
            "aerodynamics": "simulate",
            "airfoil": {"catalog": "n0012.dat"},
        },
        "vertical": {
            "AR": 1.5,
            "TR": 0.5,
            "sweep": {"angle": 15, "reference": 0},
            "arm": 3.5,
            "sizing": {"method": "stability", "value": 0.001},
            "root_z_from_cg": 0,
            "aerodynamics": "simulate",
            "airfoil": {"catalog": "n0012.dat"},
        },
    },
}

# The wing is simulated first, then the aircraft: allow a generous timeout.
r = requests.post(API, json=payload,
                  headers={"Authorization": f"Bearer {KEY}"}, timeout=300)
data = r.json()
if not r.ok:
    error = data["error"]
    raise SystemExit(f"{r.status_code} {error['code']}: {error['message']}")

u = data["units"]
length, area = ("ft", "ft^2") if u["system"] == "imperial" else ("m", "m^2")
angle, per = u["angle"], u["derivative"]

print(f"Model  : {data['model']['name']} {data['model']['version']}")
print(f"Units  : {u['system']}, angles in {angle}, derivatives in {per}")

# Geometry is grouped the way the exported workbooks are: the aircraft, the wing
# it was built on, then the tail.
geo = data["geometry"]
print("\nGeometry: aircraft")
for name, unit in [("Swet_fus", area), ("x_cg_to_wing_ac", length),
                   ("z_wing_ac_from_cg", length), ("wing_incidence", angle),
                   ("tail_configuration", "")]:
    print(f"  {name:<18} {geo['aircraft'][name]} {unit}".rstrip())

# The wing geometry always comes back: every figure in it follows from the
# planform that was sent, whether or not the wing was solved here.
w = geo["wing"]
print("\nGeometry: wing")
for name, unit in [("sref", area), ("AR", ""), ("TR", ""), ("MAC", length),
                   ("span", length), ("root_chord", length), ("tip_chord", length),
                   ("sweep_ref", ""), ("sweep", angle), ("dihedral", angle),
                   ("twist", angle), ("wingtip_type", "")]:
    print(f"  {name:<18} {w[name]} {unit}".rstrip())
# One to three parameters, depending on the device: height and chord for an
# endplate; height, quarter-chord sweep and taper for a winglet; none at all
# when no device is fitted.
for i in (1, 2, 3):
    key = f"wingtip_param_{i}"
    if key in w:
        print(f"  {key:<18} {w[key]}")
# The section is there only where one was given: a wing handed in as a polar
# names no airfoil.
if "airfoil_x" in w:
    print(f"  airfoil            {len(w['airfoil_x'])} coordinate pairs, "
          f"first ({w['airfoil_x'][0]}, {w['airfoil_y'][0]})")

tail = geo["tail"]
print(f"\nGeometry: tail ({tail['tail_configuration']})")
dims = [("area", area), ("span", length), ("root_chord", length), ("tip_chord", length),
        ("MAC", length), ("x_ac_from_root_LE", length), ("root_z_from_cg", length),
        ("AR", ""), ("TR", ""), ("sweep_ref", ""), ("sweep", angle), ("arm", length),
        ("incidence", angle)]
for surface in ("horizontal", "vertical"):
    if surface not in tail:            # no vertical block on a v-tail or a-tail
        continue
    s = tail[surface]
    print(f"\nGeometry: {surface} tail")
    for name, unit in dims:
        if name in s:                  # incidence is the horizontal tail's alone
            print(f"  {name:<18} {s[name]} {unit}".rstrip())
    if "airfoil_x" in s:               # only where a section was given
        print(f"  airfoil            {len(s['airfoil_x'])} coordinate pairs, "
              f"first ({s['airfoil_x'][0]}, {s['airfoil_y'][0]})")
if "dihedral" in tail:                 # v-tail or a-tail only
    print(f"\n  dihedral           {tail['dihedral']} {angle}")

aero = data["aerodynamics"]["aircraft"]
print("\nStability")
print(f"  CMm_aoa  {aero['CMm_aoa']} {per}")
print(f"  CMn_beta {aero['CMn_beta']} {per}")
print(f"  CMl_beta {aero['CMl_beta']} {per}")
print(f"  SM       {aero['SM']}")
print(f"  NP       {aero['NP']} {length}")
print("\nPropwash")
print(f"  propwash_HT {aero['propwash_HT']}")
if "propwash_VT" in aero:              # not returned for a v-tail or a-tail
    print(f"  propwash_VT {aero['propwash_VT']}")

# Parallel arrays on one AOA grid, from zero lift up to the maximum trim AOA.
# The CD_ components sum to CD. CD_VT is null throughout on a v-tail or a-tail.
columns = ["CL", "CD", "CD_wing", "CD_fus", "CD_HT", "CD_VT",
           "CD_trim", "CD_nac", "CD_misc", "de_trim"]
print(f"\n{'aoa (' + angle + ')':>11}" + "".join(f"{name:>11}" for name in columns))
for i, alpha in enumerate(aero["aoas"]):
    cells = "".join(f"{'-':>11}" if aero[name][i] is None else f"{aero[name][i]:>11.6f}"
                    for name in columns)
    print(f"{alpha:>11g}" + cells)

# The wing the aircraft was built on, on its own AOA grid: it runs below the
# aircraft's, because the aircraft reaches zero lift beneath the wing's own.
wing_aero = data["aerodynamics"].get("wing")
if wing_aero:
    print("\nWing aerodynamics")
    for name, unit in [("CL_alpha", per), ("CY_beta", per), ("CMl_beta", per),
                       ("CMn_beta", per), ("CMm0", ""), ("interference_factor", ""),
                       ("root_le_to_ac", length)]:
        print(f"  {name:<20} {wing_aero[name]} {unit}".rstrip())
    # The parasite columns are there only when parasite drag was modelled; CD is
    # the plain sum of CDi and CD0, with the interference factor left to apply.
    wcols = [c for c in ("CL", "CD", "CDi", "CD0", "CD_friction", "CD_form")
             if c in wing_aero]
    print(f"\n{'aoa (' + angle + ')':>11}" + "".join(f"{name:>13}" for name in wcols))
    for i, alpha in enumerate(wing_aero["aoas"]):
        print(f"{alpha:>11g}" + "".join(f"{wing_aero[name][i]:>13.6f}" for name in wcols))

# Present only when the solve has something to flag.
warnings = data.get("warnings", [])
print(f"\nWarnings: {len(warnings)}")
for w in warnings:
    print(f"  {w}")

usage = data["usage"]
print(f"\nAPI quota used by this request : {usage['compute_seconds']} s")
print(f"API quota left this period     : {usage['quota_remaining_seconds']} s")
print(f"Reserve API quota left         : {usage['reserve_remaining_seconds']} s")

Aircraft Model Example 2

  • Wing and tail aerodynamics simulated
  • Parasite drag on
  • Fuselage modelled
  • Propeller modelled
  • One miscellaneous drag source
  • One nacelle
  • Winglets fitted to the wingtips
  • Wing and tail airfoils read from the Airfoil Catalog
  • V-tail sized by horizontal and vertical tail volume coefficients
import os
import requests

API = "https://aerodule.com/api/v1/aircraft"
KEY = os.environ["AERODULE_API_KEY"]   # export AERODULE_API_KEY=ak_...

units = {"system": "imperial", "angle": "deg", "derivative": "1/deg"}

payload = {
    "sim_params": {
        "input_units": units,
        "output_units": units,
        "airspeed": 70,
        "atmosphere": {"altitude": 2000},
        "parasite_drag": True,
        "max_trim_aoa": 14,
        "grow_tail_to_trim": False,
    },
    "wing_params": {
        "span": 9,
        "root_chord": 1.3,
        "tip_chord": 0.8,
        "sweep": {"angle": 3, "reference": 0.25},
        "dihedral": 4,
        "twist": -2,
        "incidence": 2,
        "cg_to_ac": 0.12,
        "ac_z_from_cg": 0.15,
        "aerodynamics": "simulate",
        "airfoil": {"catalog": "naca2412.dat"},
        "tip_device": {
            "type": "winglet",
            "height": 0.5,
            "taper": 0.45,
            "sweep": {"angle": 28, "reference": 0.25},
        },
        "roughness": 1.67e-05,
        "crud_factor": 1.2,
        "interference_factor": 1.1,
    },
    "tail_params": {
        "configuration": "v_tail",
        "elevator": {"chord_ratio": 0.33, "max_deflection": 30},
        "horizontal": {
            "AR": 4,
            "TR": 0.6,
            "sweep": {"angle": 10, "reference": 0.25},
            "arm": 3.6,
            "sizing": {"method": "volume_coefficient", "value": 0.6},
            "incidence": 0,
            "root_z_from_cg": 0.3,
            "aerodynamics": "simulate",
            "airfoil": {"catalog": "n0012.dat"},
            "roughness": 1.67e-05,
            "crud_factor": 1.2,
            "interference_factor": 1.15,
        },
        "vertical": {
            "sizing": {"method": "volume_coefficient", "value": 0.04},
        },
    },
    "fuselage_params": {
        "nose": {"length": 1, "angle": 12},
        "center": {"length": 3, "angle": 0},
        "tail": {"length": 1.5, "angle": 8},
        "nose_to_cg": 1.8,
        "fwd_center_diameter": 0.55,
        "aft_center_diameter": 0.45,
        "roughness": 1.67e-05,
        "crud_factor": 1.2,
        "interference_factor": 1,
    },
    "propeller_params": {
        "diameter": 1.2,
        "count": 1,
        "blades": 2,
        "thrust": 6,
        "hub_to_cg": -2.2,
        "hub_z_from_cg": 0.1,
        "blanket_ratio": {"horizontal": 0.4},
    },
    "misc_drag_params": {
        "sources": [{"cd": 0.8, "area": 0.05, "z_from_cg": 0.3}],
        "nacelles": [
            {
                "length": 0.9,
                "diameter": 0.25,
                "roughness": 1.67e-05,
                "interference_factor": 1.1,
            },
        ],
        "nacelle_crud_factor": 1.2,
    },
}

# The wing is simulated first, then the aircraft: allow a generous timeout.
r = requests.post(API, json=payload,
                  headers={"Authorization": f"Bearer {KEY}"}, timeout=300)
data = r.json()
if not r.ok:
    error = data["error"]
    raise SystemExit(f"{r.status_code} {error['code']}: {error['message']}")

u = data["units"]
length, area = ("ft", "ft^2") if u["system"] == "imperial" else ("m", "m^2")
angle, per = u["angle"], u["derivative"]

print(f"Model  : {data['model']['name']} {data['model']['version']}")
print(f"Units  : {u['system']}, angles in {angle}, derivatives in {per}")

# Geometry is grouped the way the exported workbooks are: the aircraft, the wing
# it was built on, then the tail.
geo = data["geometry"]
print("\nGeometry: aircraft")
for name, unit in [("Swet_fus", area), ("x_cg_to_wing_ac", length),
                   ("z_wing_ac_from_cg", length), ("wing_incidence", angle),
                   ("tail_configuration", "")]:
    print(f"  {name:<18} {geo['aircraft'][name]} {unit}".rstrip())

# The wing geometry always comes back: every figure in it follows from the
# planform that was sent, whether or not the wing was solved here.
w = geo["wing"]
print("\nGeometry: wing")
for name, unit in [("sref", area), ("AR", ""), ("TR", ""), ("MAC", length),
                   ("span", length), ("root_chord", length), ("tip_chord", length),
                   ("sweep_ref", ""), ("sweep", angle), ("dihedral", angle),
                   ("twist", angle), ("wingtip_type", "")]:
    print(f"  {name:<18} {w[name]} {unit}".rstrip())
# One to three parameters, depending on the device: height and chord for an
# endplate; height, quarter-chord sweep and taper for a winglet; none at all
# when no device is fitted.
for i in (1, 2, 3):
    key = f"wingtip_param_{i}"
    if key in w:
        print(f"  {key:<18} {w[key]}")
# The section is there only where one was given: a wing handed in as a polar
# names no airfoil.
if "airfoil_x" in w:
    print(f"  airfoil            {len(w['airfoil_x'])} coordinate pairs, "
          f"first ({w['airfoil_x'][0]}, {w['airfoil_y'][0]})")

tail = geo["tail"]
print(f"\nGeometry: tail ({tail['tail_configuration']})")
dims = [("area", area), ("span", length), ("root_chord", length), ("tip_chord", length),
        ("MAC", length), ("x_ac_from_root_LE", length), ("root_z_from_cg", length),
        ("AR", ""), ("TR", ""), ("sweep_ref", ""), ("sweep", angle), ("arm", length),
        ("incidence", angle)]
for surface in ("horizontal", "vertical"):
    if surface not in tail:            # no vertical block on a v-tail or a-tail
        continue
    s = tail[surface]
    print(f"\nGeometry: {surface} tail")
    for name, unit in dims:
        if name in s:                  # incidence is the horizontal tail's alone
            print(f"  {name:<18} {s[name]} {unit}".rstrip())
    if "airfoil_x" in s:               # only where a section was given
        print(f"  airfoil            {len(s['airfoil_x'])} coordinate pairs, "
              f"first ({s['airfoil_x'][0]}, {s['airfoil_y'][0]})")
if "dihedral" in tail:                 # v-tail or a-tail only
    print(f"\n  dihedral           {tail['dihedral']} {angle}")

aero = data["aerodynamics"]["aircraft"]
print("\nStability")
print(f"  CMm_aoa  {aero['CMm_aoa']} {per}")
print(f"  CMn_beta {aero['CMn_beta']} {per}")
print(f"  CMl_beta {aero['CMl_beta']} {per}")
print(f"  SM       {aero['SM']}")
print(f"  NP       {aero['NP']} {length}")
print("\nPropwash")
print(f"  propwash_HT {aero['propwash_HT']}")
if "propwash_VT" in aero:              # not returned for a v-tail or a-tail
    print(f"  propwash_VT {aero['propwash_VT']}")

# Parallel arrays on one AOA grid, from zero lift up to the maximum trim AOA.
# The CD_ components sum to CD. CD_VT is null throughout on a v-tail or a-tail.
columns = ["CL", "CD", "CD_wing", "CD_fus", "CD_HT", "CD_VT",
           "CD_trim", "CD_nac", "CD_misc", "de_trim"]
print(f"\n{'aoa (' + angle + ')':>11}" + "".join(f"{name:>11}" for name in columns))
for i, alpha in enumerate(aero["aoas"]):
    cells = "".join(f"{'-':>11}" if aero[name][i] is None else f"{aero[name][i]:>11.6f}"
                    for name in columns)
    print(f"{alpha:>11g}" + cells)

# The wing the aircraft was built on, on its own AOA grid: it runs below the
# aircraft's, because the aircraft reaches zero lift beneath the wing's own.
wing_aero = data["aerodynamics"].get("wing")
if wing_aero:
    print("\nWing aerodynamics")
    for name, unit in [("CL_alpha", per), ("CY_beta", per), ("CMl_beta", per),
                       ("CMn_beta", per), ("CMm0", ""), ("interference_factor", ""),
                       ("root_le_to_ac", length)]:
        print(f"  {name:<20} {wing_aero[name]} {unit}".rstrip())
    # The parasite columns are there only when parasite drag was modelled; CD is
    # the plain sum of CDi and CD0, with the interference factor left to apply.
    wcols = [c for c in ("CL", "CD", "CDi", "CD0", "CD_friction", "CD_form")
             if c in wing_aero]
    print(f"\n{'aoa (' + angle + ')':>11}" + "".join(f"{name:>13}" for name in wcols))
    for i, alpha in enumerate(wing_aero["aoas"]):
        print(f"{alpha:>11g}" + "".join(f"{wing_aero[name][i]:>13.6f}" for name in wcols))

# Present only when the solve has something to flag.
warnings = data.get("warnings", [])
print(f"\nWarnings: {len(warnings)}")
for w in warnings:
    print(f"  {w}")

usage = data["usage"]
print(f"\nAPI quota used by this request : {usage['compute_seconds']} s")
print(f"API quota left this period     : {usage['quota_remaining_seconds']} s")
print(f"Reserve API quota left         : {usage['reserve_remaining_seconds']} s")

Aircraft Model Example 3

  • Wing and tail aerodynamic data supplied as input
  • Parasite drag on
  • A-tail sized by area and dihedral
import os
import requests

API = "https://aerodule.com/api/v1/aircraft"
KEY = os.environ["AERODULE_API_KEY"]   # export AERODULE_API_KEY=ak_...

units = {"system": "imperial", "angle": "deg", "derivative": "1/deg"}

payload = {
    "sim_params": {
        "input_units": units,
        "output_units": units,
        "airspeed": 65,
        "atmosphere": {"density": 0.0023769, "viscosity": 3.737e-07},
        "parasite_drag": True,
        "max_trim_aoa": 10,
        "grow_tail_to_trim": False,
    },
    "wing_params": {
        "span": 8.5,
        "root_chord": 1.2,
        "tip_chord": 0.8,
        "sweep": {"angle": 2, "reference": 0.25},
        "dihedral": 3,
        "twist": -1.5,
        "incidence": 1.5,
        "cg_to_ac": 0.15,
        "ac_z_from_cg": 0.1,
        "aerodynamics": "input",
        "polar": {
            "aoa": [-8, -6, -4, -2, 0, 2, 4, 6, 8, 10, 12, 14],
            "CL": [-0.54, -0.36, -0.18, 0, 0.18, 0.36, 0.54, 0.72, 0.9, 1.08, 1.26, 1.44],
            "CD": [0.02615, 0.018567, 0.014017, 0.0125, 0.014017, 0.018567, 0.02615, 0.036766, 0.050416, 0.0671, 0.086816, 0.109566],
            "CDi": [0.01365, 0.006067, 0.001517, 0, 0.001517, 0.006067, 0.01365, 0.024266, 0.037916, 0.0546, 0.074316, 0.097066],
            "CD0": [0.0125, 0.0125, 0.0125, 0.0125, 0.0125, 0.0125, 0.0125, 0.0125, 0.0125, 0.0125, 0.0125, 0.0125],
        },
        "root_le_to_ac": 0.26,
        "CMm0": -0.05,
        "CY_beta": -0.005,
        "CMl_beta": -0.0012,
        "interference_factor": 1.1,
    },
    "tail_params": {
        "configuration": "a_tail",
        "elevator": {"chord_ratio": 0.3, "max_deflection": 25},
        "spanwise_mac_location": 0.62,
        "horizontal": {
            "AR": 3.5,
            "TR": 0.55,
            "sweep": {"angle": 8, "reference": 0.25},
            "arm": 3.4,
            "sizing": {"method": "area", "value": 2.2},
            "incidence": -1,
            "root_z_from_cg": -0.2,
            "aerodynamics": "input",
            "polar": {
                "aoa": [-14, -12, -9, -6, -3, 0, 3, 6, 9, 12, 14],
                "CL": [-0.98, -0.84, -0.63, -0.42, -0.21, 0, 0.21, 0.42, 0.63, 0.84, 0.98],
                "CD": [0.093918, 0.071389, 0.044094, 0.024597, 0.012899, 0.009, 0.012899, 0.024597, 0.044094, 0.071389, 0.093918],
            },
            "CMm0": 0,
            "root_le_to_ac": 0.21,
            "interference_factor": 1.15,
        },
        "vertical": {
            "sizing": {"method": "dihedral", "value": 35},
        },
    },
}

# The wing is simulated first, then the aircraft: allow a generous timeout.
r = requests.post(API, json=payload,
                  headers={"Authorization": f"Bearer {KEY}"}, timeout=300)
data = r.json()
if not r.ok:
    error = data["error"]
    raise SystemExit(f"{r.status_code} {error['code']}: {error['message']}")

u = data["units"]
length, area = ("ft", "ft^2") if u["system"] == "imperial" else ("m", "m^2")
angle, per = u["angle"], u["derivative"]

print(f"Model  : {data['model']['name']} {data['model']['version']}")
print(f"Units  : {u['system']}, angles in {angle}, derivatives in {per}")

# Geometry is grouped the way the exported workbooks are: the aircraft, the wing
# it was built on, then the tail.
geo = data["geometry"]
print("\nGeometry: aircraft")
for name, unit in [("Swet_fus", area), ("x_cg_to_wing_ac", length),
                   ("z_wing_ac_from_cg", length), ("wing_incidence", angle),
                   ("tail_configuration", "")]:
    print(f"  {name:<18} {geo['aircraft'][name]} {unit}".rstrip())

# The wing geometry always comes back: every figure in it follows from the
# planform that was sent, whether or not the wing was solved here.
w = geo["wing"]
print("\nGeometry: wing")
for name, unit in [("sref", area), ("AR", ""), ("TR", ""), ("MAC", length),
                   ("span", length), ("root_chord", length), ("tip_chord", length),
                   ("sweep_ref", ""), ("sweep", angle), ("dihedral", angle),
                   ("twist", angle), ("wingtip_type", "")]:
    print(f"  {name:<18} {w[name]} {unit}".rstrip())
# One to three parameters, depending on the device: height and chord for an
# endplate; height, quarter-chord sweep and taper for a winglet; none at all
# when no device is fitted.
for i in (1, 2, 3):
    key = f"wingtip_param_{i}"
    if key in w:
        print(f"  {key:<18} {w[key]}")
# The section is there only where one was given: a wing handed in as a polar
# names no airfoil.
if "airfoil_x" in w:
    print(f"  airfoil            {len(w['airfoil_x'])} coordinate pairs, "
          f"first ({w['airfoil_x'][0]}, {w['airfoil_y'][0]})")

tail = geo["tail"]
print(f"\nGeometry: tail ({tail['tail_configuration']})")
dims = [("area", area), ("span", length), ("root_chord", length), ("tip_chord", length),
        ("MAC", length), ("x_ac_from_root_LE", length), ("root_z_from_cg", length),
        ("AR", ""), ("TR", ""), ("sweep_ref", ""), ("sweep", angle), ("arm", length),
        ("incidence", angle)]
for surface in ("horizontal", "vertical"):
    if surface not in tail:            # no vertical block on a v-tail or a-tail
        continue
    s = tail[surface]
    print(f"\nGeometry: {surface} tail")
    for name, unit in dims:
        if name in s:                  # incidence is the horizontal tail's alone
            print(f"  {name:<18} {s[name]} {unit}".rstrip())
    if "airfoil_x" in s:               # only where a section was given
        print(f"  airfoil            {len(s['airfoil_x'])} coordinate pairs, "
              f"first ({s['airfoil_x'][0]}, {s['airfoil_y'][0]})")
if "dihedral" in tail:                 # v-tail or a-tail only
    print(f"\n  dihedral           {tail['dihedral']} {angle}")

aero = data["aerodynamics"]["aircraft"]
print("\nStability")
print(f"  CMm_aoa  {aero['CMm_aoa']} {per}")
print(f"  CMn_beta {aero['CMn_beta']} {per}")
print(f"  CMl_beta {aero['CMl_beta']} {per}")
print(f"  SM       {aero['SM']}")
print(f"  NP       {aero['NP']} {length}")
print("\nPropwash")
print(f"  propwash_HT {aero['propwash_HT']}")
if "propwash_VT" in aero:              # not returned for a v-tail or a-tail
    print(f"  propwash_VT {aero['propwash_VT']}")

# Parallel arrays on one AOA grid, from zero lift up to the maximum trim AOA.
# The CD_ components sum to CD. CD_VT is null throughout on a v-tail or a-tail.
columns = ["CL", "CD", "CD_wing", "CD_fus", "CD_HT", "CD_VT",
           "CD_trim", "CD_nac", "CD_misc", "de_trim"]
print(f"\n{'aoa (' + angle + ')':>11}" + "".join(f"{name:>11}" for name in columns))
for i, alpha in enumerate(aero["aoas"]):
    cells = "".join(f"{'-':>11}" if aero[name][i] is None else f"{aero[name][i]:>11.6f}"
                    for name in columns)
    print(f"{alpha:>11g}" + cells)

# The wing the aircraft was built on, on its own AOA grid: it runs below the
# aircraft's, because the aircraft reaches zero lift beneath the wing's own.
wing_aero = data["aerodynamics"].get("wing")
if wing_aero:
    print("\nWing aerodynamics")
    for name, unit in [("CL_alpha", per), ("CY_beta", per), ("CMl_beta", per),
                       ("CMn_beta", per), ("CMm0", ""), ("interference_factor", ""),
                       ("root_le_to_ac", length)]:
        print(f"  {name:<20} {wing_aero[name]} {unit}".rstrip())
    # The parasite columns are there only when parasite drag was modelled; CD is
    # the plain sum of CDi and CD0, with the interference factor left to apply.
    wcols = [c for c in ("CL", "CD", "CDi", "CD0", "CD_friction", "CD_form")
             if c in wing_aero]
    print(f"\n{'aoa (' + angle + ')':>11}" + "".join(f"{name:>13}" for name in wcols))
    for i, alpha in enumerate(wing_aero["aoas"]):
        print(f"{alpha:>11g}" + "".join(f"{wing_aero[name][i]:>13.6f}" for name in wcols))

# Present only when the solve has something to flag.
warnings = data.get("warnings", [])
print(f"\nWarnings: {len(warnings)}")
for w in warnings:
    print(f"  {w}")

usage = data["usage"]
print(f"\nAPI quota used by this request : {usage['compute_seconds']} s")
print(f"API quota left this period     : {usage['quota_remaining_seconds']} s")
print(f"Reserve API quota left         : {usage['reserve_remaining_seconds']} s")

Aircraft Model Example 4

  • Wing aerodynamics simulated from custom airfoil geometry
  • Tail aerodynamic data supplied as input
  • Parasite drag on
  • Propeller modelled
  • T-tail, so the horizontal tail blanket ratio is 0: it sits clear of the slipstream
import os
import requests

API = "https://aerodule.com/api/v1/aircraft"
KEY = os.environ["AERODULE_API_KEY"]   # export AERODULE_API_KEY=ak_...

units = {"system": "imperial", "angle": "deg", "derivative": "1/deg"}

payload = {
    "sim_params": {
        "input_units": units,
        "output_units": units,
        "airspeed": 75,
        "atmosphere": {"altitude": 1000},
        "parasite_drag": True,
        "max_trim_aoa": 12,
        "grow_tail_to_trim": False,
    },
    "wing_params": {
        "span": 9.5,
        "root_chord": 1.35,
        "tip_chord": 0.85,
        "sweep": {"angle": 0, "reference": 0.25},
        "dihedral": 2,
        "twist": -2,
        "incidence": 1.5,
        "cg_to_ac": 0.14,
        "ac_z_from_cg": 0.12,
        "aerodynamics": "simulate",
        "airfoil": {
            "points": [
                [1, 0.0013],
                [0.95, 0.0114],
                [0.9, 0.0208],
                [0.8, 0.0375],
                [0.7, 0.0518],
                [0.6, 0.0636],
                [0.5, 0.0724],
                [0.4, 0.078],
                [0.3, 0.0788],
                [0.25, 0.0767],
                [0.2, 0.0726],
                [0.15, 0.0661],
                [0.1, 0.0563],
                [0.075, 0.0496],
                [0.05, 0.0413],
                [0.025, 0.0299],
                [0.0125, 0.0215],
                [0, 0],
                [0.0125, -0.0165],
                [0.025, -0.0227],
                [0.05, -0.0301],
                [0.075, -0.0346],
                [0.1, -0.0375],
                [0.15, -0.041],
                [0.2, -0.0423],
                [0.25, -0.0422],
                [0.3, -0.0412],
                [0.4, -0.038],
                [0.5, -0.0334],
                [0.6, -0.0276],
                [0.7, -0.0214],
                [0.8, -0.015],
                [0.9, -0.0082],
                [0.95, -0.0048],
                [1, -0.0013],
            ],
        },
        "roughness": 1.67e-05,
        "crud_factor": 1.2,
        "interference_factor": 1.1,
    },
    "tail_params": {
        "configuration": "t_tail",
        "elevator": {"chord_ratio": 0.32, "max_deflection": 30},
        "spanwise_mac_location": 0.58,
        "horizontal": {
            "AR": 4.2,
            "TR": 0.55,
            "sweep": {"angle": 6, "reference": 0.25},
            "arm": 3.8,
            "sizing": {"method": "stability", "value": -0.03},
            "incidence": -1,
            "aerodynamics": "input",
            "polar": {
                "aoa": [-14, -12, -9, -6, -3, 0, 3, 6, 9, 12, 14],
                "CL": [-0.952, -0.816, -0.612, -0.408, -0.204, 0, 0.204, 0.408, 0.612, 0.816, 0.952],
                "CD": [0.089635, 0.068375, 0.042617, 0.024219, 0.01318, 0.0095, 0.01318, 0.024219, 0.042617, 0.068375, 0.089635],
            },
            "CMm0": 0,
            "root_le_to_ac": 0.19,
            "interference_factor": 1.15,
        },
        "vertical": {
            "AR": 1.4,
            "TR": 0.5,
            "sweep": {"angle": 20, "reference": 0.25},
            "arm": 3.7,
            "sizing": {"method": "volume_coefficient", "value": 0.045},
            "root_z_from_cg": 0.25,
            "aerodynamics": "input",
            "CY_beta": 0.028,
            "CD0": 0.011,
            "root_le_to_ac": 0.24,
            "interference_factor": 1.15,
        },
    },
    "propeller_params": {
        "diameter": 1.4,
        "count": 2,
        "blades": 3,
        "thrust": 8,
        "hub_to_cg": -1.9,
        "hub_z_from_cg": 0.05,
        "blanket_ratio": {"horizontal": 0, "vertical": 0.35},
    },
}

# The wing is simulated first, then the aircraft: allow a generous timeout.
r = requests.post(API, json=payload,
                  headers={"Authorization": f"Bearer {KEY}"}, timeout=300)
data = r.json()
if not r.ok:
    error = data["error"]
    raise SystemExit(f"{r.status_code} {error['code']}: {error['message']}")

u = data["units"]
length, area = ("ft", "ft^2") if u["system"] == "imperial" else ("m", "m^2")
angle, per = u["angle"], u["derivative"]

print(f"Model  : {data['model']['name']} {data['model']['version']}")
print(f"Units  : {u['system']}, angles in {angle}, derivatives in {per}")

# Geometry is grouped the way the exported workbooks are: the aircraft, the wing
# it was built on, then the tail.
geo = data["geometry"]
print("\nGeometry: aircraft")
for name, unit in [("Swet_fus", area), ("x_cg_to_wing_ac", length),
                   ("z_wing_ac_from_cg", length), ("wing_incidence", angle),
                   ("tail_configuration", "")]:
    print(f"  {name:<18} {geo['aircraft'][name]} {unit}".rstrip())

# The wing geometry always comes back: every figure in it follows from the
# planform that was sent, whether or not the wing was solved here.
w = geo["wing"]
print("\nGeometry: wing")
for name, unit in [("sref", area), ("AR", ""), ("TR", ""), ("MAC", length),
                   ("span", length), ("root_chord", length), ("tip_chord", length),
                   ("sweep_ref", ""), ("sweep", angle), ("dihedral", angle),
                   ("twist", angle), ("wingtip_type", "")]:
    print(f"  {name:<18} {w[name]} {unit}".rstrip())
# One to three parameters, depending on the device: height and chord for an
# endplate; height, quarter-chord sweep and taper for a winglet; none at all
# when no device is fitted.
for i in (1, 2, 3):
    key = f"wingtip_param_{i}"
    if key in w:
        print(f"  {key:<18} {w[key]}")
# The section is there only where one was given: a wing handed in as a polar
# names no airfoil.
if "airfoil_x" in w:
    print(f"  airfoil            {len(w['airfoil_x'])} coordinate pairs, "
          f"first ({w['airfoil_x'][0]}, {w['airfoil_y'][0]})")

tail = geo["tail"]
print(f"\nGeometry: tail ({tail['tail_configuration']})")
dims = [("area", area), ("span", length), ("root_chord", length), ("tip_chord", length),
        ("MAC", length), ("x_ac_from_root_LE", length), ("root_z_from_cg", length),
        ("AR", ""), ("TR", ""), ("sweep_ref", ""), ("sweep", angle), ("arm", length),
        ("incidence", angle)]
for surface in ("horizontal", "vertical"):
    if surface not in tail:            # no vertical block on a v-tail or a-tail
        continue
    s = tail[surface]
    print(f"\nGeometry: {surface} tail")
    for name, unit in dims:
        if name in s:                  # incidence is the horizontal tail's alone
            print(f"  {name:<18} {s[name]} {unit}".rstrip())
    if "airfoil_x" in s:               # only where a section was given
        print(f"  airfoil            {len(s['airfoil_x'])} coordinate pairs, "
              f"first ({s['airfoil_x'][0]}, {s['airfoil_y'][0]})")
if "dihedral" in tail:                 # v-tail or a-tail only
    print(f"\n  dihedral           {tail['dihedral']} {angle}")

aero = data["aerodynamics"]["aircraft"]
print("\nStability")
print(f"  CMm_aoa  {aero['CMm_aoa']} {per}")
print(f"  CMn_beta {aero['CMn_beta']} {per}")
print(f"  CMl_beta {aero['CMl_beta']} {per}")
print(f"  SM       {aero['SM']}")
print(f"  NP       {aero['NP']} {length}")
print("\nPropwash")
print(f"  propwash_HT {aero['propwash_HT']}")
if "propwash_VT" in aero:              # not returned for a v-tail or a-tail
    print(f"  propwash_VT {aero['propwash_VT']}")

# Parallel arrays on one AOA grid, from zero lift up to the maximum trim AOA.
# The CD_ components sum to CD. CD_VT is null throughout on a v-tail or a-tail.
columns = ["CL", "CD", "CD_wing", "CD_fus", "CD_HT", "CD_VT",
           "CD_trim", "CD_nac", "CD_misc", "de_trim"]
print(f"\n{'aoa (' + angle + ')':>11}" + "".join(f"{name:>11}" for name in columns))
for i, alpha in enumerate(aero["aoas"]):
    cells = "".join(f"{'-':>11}" if aero[name][i] is None else f"{aero[name][i]:>11.6f}"
                    for name in columns)
    print(f"{alpha:>11g}" + cells)

# The wing the aircraft was built on, on its own AOA grid: it runs below the
# aircraft's, because the aircraft reaches zero lift beneath the wing's own.
wing_aero = data["aerodynamics"].get("wing")
if wing_aero:
    print("\nWing aerodynamics")
    for name, unit in [("CL_alpha", per), ("CY_beta", per), ("CMl_beta", per),
                       ("CMn_beta", per), ("CMm0", ""), ("interference_factor", ""),
                       ("root_le_to_ac", length)]:
        print(f"  {name:<20} {wing_aero[name]} {unit}".rstrip())
    # The parasite columns are there only when parasite drag was modelled; CD is
    # the plain sum of CDi and CD0, with the interference factor left to apply.
    wcols = [c for c in ("CL", "CD", "CDi", "CD0", "CD_friction", "CD_form")
             if c in wing_aero]
    print(f"\n{'aoa (' + angle + ')':>11}" + "".join(f"{name:>13}" for name in wcols))
    for i, alpha in enumerate(wing_aero["aoas"]):
        print(f"{alpha:>11g}" + "".join(f"{wing_aero[name][i]:>13.6f}" for name in wcols))

# Present only when the solve has something to flag.
warnings = data.get("warnings", [])
print(f"\nWarnings: {len(warnings)}")
for w in warnings:
    print(f"  {w}")

usage = data["usage"]
print(f"\nAPI quota used by this request : {usage['compute_seconds']} s")
print(f"API quota left this period     : {usage['quota_remaining_seconds']} s")
print(f"Reserve API quota left         : {usage['reserve_remaining_seconds']} s")

Errors

r = requests.post(url, json=payload, headers=auth, timeout=120)

# Lasts until the billing period resets. Retrying is pointless.
if r.status_code == 402:
    raise SystemExit("Quota spent; resets at " + r.headers["X-Quota-Reset"])

if not r.ok:
    err = r.json()["error"]
    # On a 400, details.field names the exact field that was wrong.
    field = err.get("details", {}).get("field")
    raise SystemExit(err["code"] + ": " + err["message"]
                     + (" [" + field + "]" if field else ""))

data = r.json()
CodeStatusDescription
unauthenticated401No key, or key was not a bearer token.
invalid_key401Unknown or revoked key.
not_entitled403No active plan, or a payment has failed.
invalid_request400Schema violation: a missing required field, a value of the wrong type or out of range, a field the endpoint does not recognise, or a field that does not apply to the mode chosen.
invalid_airfoil400Airfoil coordinates could not be read.
unknown_motor400No motor with that ID.
unknown_propeller400No propeller with that ID.
invalid_atmosphere400Invalid input for either altitude or density.
point_limit400There are more than 10,000 requested performance points.
out_of_operating_range422Every requested performance point is outside of the motor/propeller operating range.
quota_exhausted402API quota and reserve both spent.
billing_unavailable503Your billing period has ended and the renewal could not be confirmed with Stripe on this request. Retry after the Retry-After header.
solver_error422The geometry was understood but did not solve. On the aircraft endpoint this also covers a wing simulation that failed before the aircraft was reached.
upstream_unavailable503The model service was unreachable. Retry.
site_locked503The deployment is not open to the public yet and is behind a passcode.

Model Version

wing/1.0.0+solver.1.0.0          propulsion/1.0.0+data.1.0.0
     └──┬──┘        └──┬──┘             └──┬──┘       └─┬─┘
   conversion        the solver          conversion    the motor
   and geometry      itself              handling      and propeller
   handling                                            dataset

aircraft/1.0.0+solver.1.0.0+wing.1.0.0
        └──┬──┘        └──┬──┘   └──┬──┘
      conversion      the aircraft  the wing solver
      and geometry    solver
      handling

FAQ

What happens if I run out of computing mid sim?

The sim being run will complete.

When does my quota reset?

Quota resets at the start of each billing period, which follows the day of subscription. If the subscription was started on the 20th, the API quota will return on the 20th.

How are computing hours calculated?

Compute time is recorded as time the solver spends on your request. Parsing, validation, and network time are not counted.

What happens if a big sim fails or crashes?

A request that returns solver_error, out_of_operating_range or upstream_unavailable is not counted towards API usage quota.

Is there an API rate limit?

No.

Can I run simulations locally?

No. The models run on Aerodule’s servers and the API is the only programmatic access to them.