This repo is still under development. We are also actively looking for users and developers. If this sounds like you, get in touch!

Aviary#

Description#

The aviary is a handler for physics stepping, setpoint handling, collisions tracking, and much more. It provides a common endpoint from where users may control drones or define tasks. This page briefly goes through the key usage traits, although more experienced users may wish to consult the whole class description at the bottom of this page, or even (oh shit!) consult the source code.

Usage#

Core API Example#

"""Spawn a single drone on x=0, y=0, z=1, with 0 rpy."""
# Step 1: import things
import numpy as np
from PyFlyt.core import Aviary

# Step 2: define starting positions and orientations
start_pos = np.array([[0.0, 0.0, 1.0]])
start_orn = np.array([[0.0, 0.0, 0.0]])

# Step 3: instantiate aviary
env = Aviary(start_pos=start_pos, start_orn=start_orn, render=True, drone_type="quadx")

# Step 4: (Optional) define control mode to use for drone
env.set_mode(7)

# Step 5: (Optional) define a setpoint for the first drone (at index 0) in the aviary
setpoint = np.array([1.0, 0.0, 0.0, 1.0])
env.set_setpoint(0, setpoint)

# Step 6: step the physics
for i in range(1000):
    env.step()

# Gracefully close
env.close()

Controlling Drone Types#

The drone_type is a lowercase string that defines the type of drone(s) to spawn in the aviary. By default, PyFlyt ships with three different drones, listed under the drones section. These are:

  1. Fixedwing

  2. Rocket

  3. QuadX

So, for example, to spawn the Fixedwing drone instead of the QuadX, simply do:

...
env = Aviary(..., drone_type="fixedwing")
...

Spawning Custom Drones#

For custom drones not shipped in PyFlyt (such as the RocketBrick), you can call them into the aviary by setting custom drone_type_mappings, and then calling them using drone_type:

...
# import the drone class so it's callable
from ... import MyCustomDrone

# define a new drone so the aviary can call it
# note we do not instantiate the object, this is only a class pointer
drone_type_mappings = dict()
drone_type_mappings["mycustomdrone"] = MyCustomDrone

# pass the relevant arguments to the `aviary`
env = Aviary(..., drone_type="mycustomdrone", drone_type_mappings=drone_type_mappings)
...

Setting Drone Options#

Various drones can have different instantiation parameters, for example, the Fixedwing drone has a configurable starting velocity which the QuadX drone does not have.

To define these custom parameters, use the drone_options argument like so:

...
# define the parameters for the underlying drone
drone_options = dict()
drone_options["starting_velocity"] = np.array([3.0, 0.0, 3.0])

# pass the relevant arguments to the `aviary`
env = Aviary(..., drone_type="fixedwing", drone_options=drone_options)
...

Multi Drone Setup#

To spawn multiple drones with different types and parameters for each, lists of drone_type and drone_options can be used instead to give each drone a unique set of parameters. For example:

...
# the starting position and orientations
start_pos = np.array([[0.0, 5.0, 5.0], [3.0, 3.0, 1.0], [5.0, 0.0, 1.0]])
start_orn = np.zeros_like(start_pos)

# spawn different types of drones
drone_type = ["rocket", "quadx", "fixedwing"]

# individual spawn options for each drone
rocket_options = dict()
quadx_options = dict(use_camera=True, drone_model="primitive_drone")
fixedwing_options = dict(starting_velocity=np.array([0.0, 0.0, 0.0]))
drone_options = [rocket_options, quadx_options, fixedwing_options]

# environment setup
env = Aviary(
    start_pos=start_pos,
    start_orn=start_orn,
    render=True,
    drone_type=drone_type,
    drone_options=drone_options,
)

# set quadx to position control, rocket and fixedwing as nothing
env.set_mode([0, 7, 0])

# simulate for 1000 steps (1000/120 ~= 8 seconds)
for i in range(1000):
    env.step()

Here, we spawn three drones, a Rocket, a QuadX and a Fixedwing, at three different positions. Each drone has different spawn options.

Accessing Individual Drones#

All drones are stored within a (very creatively named) drones attribute. This allows raw access for any drone from outside the aviary.

...
# instantiate the aviary
env = Aviary(...)

# we can get the camera image of the last drone
rgbImg = env.drones[-1].rgbImg

Looprates#

By default, PyFlyt steps physics at 240 Hz - the default for a PyBullet environment. This can be changed via the physics_hz argument, although this is not recommended as it can lead to unstable simulation.

The various drones within the environment can also be configured to have a control looprate different to the physics looprate. This is configured through the drone_options argument, like so:

...
# define a control looprate
drone_options = dict()
drone_options["control_hz"] = 120

# pass the relevant arguments to the `aviary`
env = Aviary(..., drone_type="quadx", drone_options=drone_options)
...

All control looprates must be a common denominator of the physics looprate. For instance, for a physics looprate of 240 Hz, a control looprate of 60 Hz is valid, but 50 is not since 240 % 50 != 0.

Single Drone Physics Stepping#

Every call to step of the aviary steps the simulation enough times for one control loop to elapse. For example, if the physics looprate is 240 Hz and the control looprate is 120 Hz, each call to step steps the physics in the environment 2 times.

Multi Drone Physics Stepping#

In a multi drone setting, it is possible for various drones to have various looprates. The caveat here is that, when control looprates are arranged in ascending order, the i+1th looprate must be a round multiple of the ith looprate. For instance, this is a valid set of looprates:

...
drone_options = []
drone_options.append(dict(control_hz=60))
drone_options.append(dict(control_hz=30))
drone_options.append(dict(control_hz=120))
...

… but this is not:

...
drone_options = []
drone_options.append(dict(control_hz=40))
drone_options.append(dict(control_hz=30))
drone_options.append(dict(control_hz=120))
...

In the first sample, when arranged in ascending order, we get a list of looprates = [30, 60, 120]. When we do looprate[1:] / looprate[:-1], we get [2, 2], which is all integers. This is valid.

In the second sample, when arranged in ascending order, this list is looprates = [30, 40, 120]. Similarly, when we do looprate[1:] / looprate[:-1], we get [1.25, 3], which is not all integers. This is invalid.

Class Description#

class PyFlyt.core.Aviary(start_pos: ndarray, start_orn: ndarray, drone_type: str | Sequence[str], drone_type_mappings: None | dict[str, type[DroneClass]] = None, drone_options: dict[str, Any] | Sequence[dict[str, Any]] | None = None, wind_type: None | str | type[WindFieldClass] = None, wind_options: dict[str, Any] = {}, render: bool = False, physics_hz: int = 240, world_scale: float = 1.0, seed: None | int = None)#

Aviary class, the core of how PyFlyt handles UAVs in the PyBullet simulation environment.

The aviary is a handler for physics stepping, setpoint handling, collisions tracking, and much more. It provides a common endpoint from where users may control drones or define tasks.

Parameters:
  • start_pos (np.ndarray) – an (n, 3) array for the starting X, Y, Z positions for each drone.

  • start_orn (np.ndarray) – an (n, 3) array for the starting orientations for each drone, in terms of Euler angles.

  • drone_type (str | Sequence[str]) – a _lowercase_ string representing what type of drone to spawn.

  • drone_type_mappings (None | dict(str, Type[DroneClass])) – a dictionary mapping of {str: DroneClass} for spawning custom drones.

  • drone_options (dict[str, Any] | Sequence[dict[str, Any]] | None) – dictionary mapping of custom parameters for each drone.

  • wind_type (None | str | Type[WindField]) – a wind field model that will be used throughout the simulation.

  • wind_options (dict[str, Any] | Sequence[dict[str, Any]]) – dictionary mapping of custom parameters for the wind field.

  • render (bool) – a boolean whether to render the simulation.

  • physics_hz (int) – physics looprate (not recommended to be changed).

  • world_scale (float) – how big to spawn the floor.

  • seed (None | int) – optional int for seeding the simulation RNG.

Attributes#

property Aviary.all_states: list[ndarray]#

Returns a list of states for all drones in the environment.

This is a num_drones list of (4, 3) arrays, where each element in the list corresponds to the i-th drone state.

Similar to the state property, the states contain information corresponding to:
  • state[0, :] represents body frame angular velocity

  • state[1, :] represents ground frame angular position

  • state[2, :] represents body frame linear velocity

  • state[3, :] represents ground frame linear position

This function is not very optimized, if you want the state of a single drone, do state(i).

Returns:

list of states

Return type:

np.ndarray

property Aviary.all_aux_states: list[ndarray]#

Returns a list of auxiliary states for all drones in the environment.

This is a num_drones list of auxiliary states.

This function is not very optimized, if you want the aux state of a single drone, do aux_state(i).

Returns:

list of auxiliary states

Return type:

np.ndarray

property PyFlyt.core.Aviary.drones#

A list of all drones that the Aviary is currently handling.

property PyFlyt.core.Aviary.contact_array#

A numpy array of collisions between entities. Query whether an object with id n has contacted an object with id m via contact_array[n, m] or contact_array[m, n]. It is also possible to do np.any(contact_array) to check for all collisions.

property PyFlyt.core.Aviary.elapsed_ime#

A float representing the amount of time that has passed.

property PyFlyt.core.Aviary.physics_steps#

An integer representing the number of times the physics engine has been stepped.

property PyFlyt.core.Aviary.aviary_steps#

An integer representing the number of times step has been called.

Methods#

PyFlyt.core.Aviary.print_all_bodies(self) None#

Debugging function used to print out all bodies in the environment along with their IDs.

PyFlyt.core.Aviary.reset(self, seed: None | int = None) None#

Resets the simulation.

Parameters:

seed (None | int) – seed

PyFlyt.core.Aviary.register_all_new_bodies(self) None#

Registers all new bodies in the environment to be able to handle collisions later.

Call this when there is an update in the number of bodies in the environment.

PyFlyt.core.Aviary.state(self, index: int) ndarray#

Returns the state for the indexed drone.

This is a (4, 3) array, where:
  • state[0, :] represents body frame angular velocity

  • state[1, :] represents ground frame angular position

  • state[2, :] represents body frame linear velocity

  • state[3, :] represents ground frame linear position

Parameters:

index (DRONE_INDEX) – index

Returns:

state

Return type:

np.ndarray

PyFlyt.core.Aviary.aux_state(self, index: int) ndarray#

Returns the auxiliary state for the indexed drone.

This is typically an (n, ) vector, representing various attributes such as:
  • booster thrust settings

  • fuel remaining

  • control surfaces deflection magnitude

  • etc…

Parameters:

index (DRONE_INDEX) – index

Returns:

auxiliary state

Return type:

np.ndarray

PyFlyt.core.Aviary.set_armed(self, settings: int | bool | list[int] | list[bool]) None#

Sets the arming state of each drone in the environment. Unarmed drones won’t receive updates and will ragdoll.

Parameters:

settings (int | bool | list[int | bool]) – arm setting

PyFlyt.core.Aviary.set_mode(self, flight_modes: int | list[int]) None#

Sets the flight control mode of each drone in the environment.

When this is an int, sets the same flight mode for each drone. When this is a list of integers, sets a different flight mode for each drone depending on the list.

Parameters:

flight_modes (int | list[int]) – flight mode

PyFlyt.core.Aviary.set_setpoint(self, index: int, setpoint: ndarray) None#

Sets the setpoint of one drone in the environment.

Parameters:
  • index (DRONE_INDEX) – index

  • setpoint (np.ndarray) – setpoint

PyFlyt.core.Aviary.set_all_setpoints(self, setpoints: ndarray) None#

Sets the setpoints of each drone in the environment.

Parameters:

setpoints (np.ndarray) – list of setpoints

PyFlyt.core.Aviary.step(self) None#

Steps the environment, this automatically handles physics and control looprates, one step is equivalent to one control loop step.