Files
rotor_py_control/examples/multimav_multiprocessing_demo.py

177 lines
6.5 KiB
Python
Raw Normal View History

"""
Imports
"""
from rotorpy.vehicles.multirotor import Multirotor
from rotorpy.vehicles.crazyflie_params import quad_params
from rotorpy.controllers.quadrotor_control import SE3Control
from rotorpy.trajectories.hover_traj import HoverTraj
from rotorpy.trajectories.circular_traj import CircularTraj, ThreeDCircularTraj
from rotorpy.trajectories.lissajous_traj import TwoDLissajous
from rotorpy.trajectories.speed_traj import ConstantSpeed
from rotorpy.trajectories.minsnap import MinSnap
from rotorpy.world import World
from rotorpy.utils.animate import animate
from rotorpy.simulate import merge_dicts
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from scipy.spatial.transform import Rotation
import os
import yaml
import multiprocessing
####################### Helper functions
def run_sim(trajectory, t_offset, t_final=10, t_step=1/100):
"""
Runs an instance of the simulation environment which creates a vehicle object and tracking controller on
an individual cpu process using Python's multiprocessing.
Inputs:
trajectory: the trajectory object for this mav to track.
t_offset: time offset (useful for offsetting multiple mavs on the same trajectory).
t_final: duration of the sim for this object.
t_step: timestep for the simulation.
Outputs:
time: time array.
states: array of quadrotor states.
controls: array of quadrotor control variables.
flats: array of flat outputs describing the trajectory to track.
"""
mav = Multirotor(quad_params)
controller = SE3Control(quad_params)
# Init mav at the first waypoint for the trajectory.
x0 = {'x': trajectory.update(t_offset)['x'],
'v': np.zeros(3,),
'q': np.array([0, 0, 0, 1]), # [i,j,k,w]
'w': np.zeros(3,),
'wind': np.array([0,0,0]), # Since wind is handled elsewhere, this value is overwritten
'rotor_speeds': np.array([1788.53, 1788.53, 1788.53, 1788.53])}
time = [0]
states = [x0]
flats = [trajectory.update(time[-1] + t_offset)]
controls = [controller.update(time[-1], states[-1], flats[-1])]
while True:
if time[-1] >= t_final:
break
time.append(time[-1] + t_step)
states.append(mav.step(states[-1], controls[-1], t_step))
flats.append(trajectory.update(time[-1] + t_offset))
controls.append(controller.update(time[-1], states[-1], flats[-1]))
time = np.array(time, dtype=float)
states = merge_dicts(states)
controls = merge_dicts(controls)
flats = merge_dicts(flats)
return time, states, controls, flats
def worker_fn(cfg):
"""
Enumerates over the configurations for each process in multiprocessing.
"""
return run_sim(*cfg)
def find_collisions(all_positions, epsilon=1e-1):
"""
Checks if any two agents get within epsilon meters of any other agent.
Inputs:
all_positions: the position vs time for each agent concatenated into one array.
epsilon: the distance threshold constituting a collision.
Outputs:
collisions: a list of dictionaries where each dict describes the time of a collision, agents involved, and the location.
"""
N, M, _ = all_positions.shape
collisions = []
for t in range(N):
# Get positions.
pos_t = all_positions[t]
dist_sq = np.sum((pos_t[:, np.newaxis, :] - pos_t[np.newaxis, :, :])**2, axis=-1)
# Set diagonal to a large value to avoid false positives.
np.fill_diagonal(dist_sq, np.inf)
close_pairs = np.where(dist_sq < epsilon**2)
for i, j in zip(*close_pairs):
if i < j: # avoid duplicate pairs.
collision_info = {
"timestep": t,
"agents": (i, j),
"location": pos_t[i]
}
collisions.append(collision_info)
return collisions
####################### Start of user code
# Construct the world.
world = World.empty([-3, 3, -3, 3, -3, 3])
# Generate a list of configurations to run in parallel. Each config has a trajectory, time offset, sim duration, and sim time discretization.
dt = 1/100
tf = 10
# Hard coded list of Lissajous maneuvers.
config_list = [(TwoDLissajous(A=1, B=1, a=2, b=1, x_offset=-0.5, y_offset=0, height=2.0), 0, tf, dt),
(TwoDLissajous(A=1, B=1, a=2, b=1, x_offset=-0.25, y_offset=0, height=2.0), 0.5, tf, dt),
(TwoDLissajous(A=1, B=1, a=2, b=1, x_offset=0.0, y_offset=0, height=2.0), 1.0, tf, dt),
(TwoDLissajous(A=1, B=1, a=2, b=1, x_offset=0.25, y_offset=0, height=2.0), 1.5, tf, dt),
(TwoDLissajous(A=1, B=1, a=2, b=1, x_offset=0.50, y_offset=0, height=2.0), 2.0, tf, dt)]
# Programmatic construction of a swarm of MAVs following a MinSnap trajectory.
Nc = 7
R = 0.5
for i in range(Nc):
x0 = np.array([-2 + R*np.cos(i*2*np.pi/Nc), R*np.sin(i*2*np.pi/Nc), 0])
xf = np.array([ 2 + R*np.cos(i*2*np.pi/Nc), R*np.sin(i*2*np.pi/Nc), 0])
config_list.append((MinSnap(points=np.row_stack((x0, xf)), v_avg=1.0, verbose=False), 0, tf, dt))
# Run RotorPy in parallel.
with multiprocessing.Pool() as pool:
results = pool.map(worker_fn, config_list)
# Concatentate all the relevant states/inputs for animation.
all_pos = []
all_rot = []
all_wind = []
all_time = results[0][0]
for r in results:
all_pos.append(r[1]['x'])
all_wind.append(r[1]['wind'])
all_rot.append(Rotation.from_quat(r[1]['q']).as_matrix())
all_pos = np.stack(all_pos, axis=1)
all_wind = np.stack(all_wind, axis=1)
all_rot = np.stack(all_rot, axis=1)
# Check for collisions.
collisions = find_collisions(all_pos, epsilon=2e-1)
# Animate.
ani = animate(all_time, all_pos, all_rot, all_wind, animate_wind=False, world=world, filename=None)
# Plot the positions of each agent in 3D, alongside collision events (when applicable)
fig = plt.figure()
ax = fig.add_subplot(projection='3d')
colors = plt.cm.tab10(range(all_pos.shape[1]))
for mav in range(all_pos.shape[1]):
ax.plot(all_pos[:, mav, 0], all_pos[:, mav, 1], all_pos[:, mav, 2], color=colors[mav])
ax.plot([all_pos[-1, mav, 0]], [all_pos[-1, mav, 1]], [all_pos[-1, mav, 2]], '*', markersize=10, markerfacecolor=colors[mav], markeredgecolor='k')
world.draw(ax)
for event in collisions:
ax.plot([all_pos[event['timestep'], event['agents'][0], 0]], [all_pos[event['timestep'], event['agents'][0], 1]], [all_pos[event['timestep'], event['agents'][0], 2]], 'rx', markersize=10)
ax.set_xlabel("x, m")
ax.set_ylabel("y, m")
ax.set_zlabel("z, m")
plt.show()