ContentsFoundations to FrontierBezhig

eng-lab/hwe/core/hover.py

All lab code. Download this file.

"""EL01 lab: why a drone hovers. YOU WRITE THIS FILE (EL01 lab, steps 1-3).

Each docstring is a contract, and labs/el01_hover/tests check exactly these
contracts. The @quantities line above each function is part of the contract too:
it rejects bare numbers and wrong units before your code runs, and it checks the
unit of whatever you return. Work through the lab steps in EL01, section 3, and
run `python labs/el01_hover/check.py N` after finishing step N.

Conventions for this whole file
- Up is positive. A thrust pushing up is positive; weight points down, so it
  enters every sum with a minus sign.
- Thrusts are forces. A thrust table's "grams" are gram-force (gf), and pint
  converts them for you: Q_(1500, "gf").to("N") is about 14.7 N.
- g defaults to standard gravity G0 = 9.80665 m/s^2 from hwe.units.
- Never strip units inside these functions (no .magnitude, no .m): let pint
  carry them to the end, so a slip shows up as an error instead of a wrong number.
"""
from hwe.units import G0, Q_, quantities


# ----------------------------------------------------------------------
# STEP 1: mass is not weight (worked example; type it from EL01, step 1).
# ----------------------------------------------------------------------
@quantities(mass="[mass]", g="[acceleration]", returns="[force]")
def weight(mass, g=G0):
    """The weight of a mass: W = m g, the force gravity exerts on it.

    mass   any mass unit (g, kg).
    g      gravitational acceleration; standard gravity unless told otherwise.
    Returns the weight in newtons.
    """
    raise NotImplementedError("Step 1: weight")


# ----------------------------------------------------------------------
# STEP 2: Newton's second law along the vertical (partly filled in EL01).
# ----------------------------------------------------------------------
@quantities(thrusts="[force]", mass="[mass]", g="[acceleration]", returns="[force]")
def net_force(thrusts, mass, g=G0):
    """The net vertical force on the drone: the sum of all thrusts minus its weight.

    thrusts  a list of thrust forces, one per motor, each pointing up.
    mass     the drone's total mass.
    Returns the net force in newtons: positive means it accelerates upward.
    Ignore drag (the drone is still or moving slowly).
    """
    # TODO(human): Step 2. Sum the thrusts from a start value that carries a unit, then subtract the weight.
    raise NotImplementedError("Step 2: net_force")


@quantities(thrusts="[force]", mass="[mass]", g="[acceleration]", returns="[acceleration]")
def acceleration(thrusts, mass, g=G0):
    """The drone's vertical acceleration from Newton's second law, a = F_net / m.

    Same inputs as net_force. Returns the acceleration in m/s^2 (up positive):
    zero in a hover, positive while speeding up upward (or slowing a descent).
    """
    raise NotImplementedError("Step 2: acceleration")


@quantities(accel="[acceleration]", g="[acceleration]", returns="[acceleration]")
def accelerometer_reading(accel, g=G0):
    """What an up-pointing accelerometer reads for a body with vertical acceleration accel.

    An accelerometer cannot feel gravity (gravity pulls its test mass and its
    case equally). It measures every OTHER force per unit mass, called the
    specific force: reading = accel + g, up positive (EL01, section 2.6).
    Examples: resting on a table, accel = 0, so it reads +g; in free fall,
    accel = -g, so it reads 0. Returns m/s^2.
    """
    raise NotImplementedError("Step 2: accelerometer_reading")


# ----------------------------------------------------------------------
# STEP 3: the thrust-to-weight family (on your own, from EL01, section 2.7).
# ----------------------------------------------------------------------
@quantities(max_thrust_per_motor="[force]", mass="[mass]", g="[acceleration]", returns="[]")
def thrust_to_weight(max_thrust_per_motor, motor_count, mass, g=G0):
    """T/W: the total maximum thrust of all motors divided by the drone's weight.

    max_thrust_per_motor  one motor's maximum thrust (for example from a thrust table).
    motor_count           how many motors (an int: 4 for a quad).
    Returns a pure number: 2.0 means the motors together can lift twice the weight.
    """
    raise NotImplementedError("Step 3: thrust_to_weight")


@quantities(mass="[mass]", g="[acceleration]", returns="[force]")
def hover_thrust_per_motor(mass, motor_count, g=G0):
    """The thrust each motor must make to hover, sharing the weight equally.

    Returns newtons. (EL02 shows what changes when the centre of mass is off-centre.)
    """
    raise NotImplementedError("Step 3: hover_thrust_per_motor")


@quantities(max_thrust_per_motor="[force]", mass="[mass]", g="[acceleration]", returns="[]")
def hover_thrust_fraction(max_thrust_per_motor, motor_count, mass, g=G0):
    """The fraction of maximum thrust spent just holding a hover: weight / total max thrust.

    Equal to 1 / (T/W). A value above 1 means the drone cannot hover at all;
    return it anyway, because the report uses it to say by how much.
    """
    raise NotImplementedError("Step 3: hover_thrust_fraction")


@quantities(max_thrust_per_motor="[force]", mass="[mass]", g="[acceleration]",
            returns="[acceleration]")
def max_climb_acceleration(max_thrust_per_motor, motor_count, mass, g=G0):
    """The largest upward acceleration at full thrust, ignoring drag: g (T/W - 1).

    Returns m/s^2. Negative means that even full thrust cannot lift the drone.
    """
    raise NotImplementedError("Step 3: max_climb_acceleration")