Urban Drone Demo
Copyright © 2024, United States Government, as represented by the Administrator of the National Aeronautics and Space Administration. All rights reserved.
The “"Fault Model Design tools - fmdtools version 2"” software is licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
[15]:
import fmdtools.sim.propagate as propagate
from fmdtools.analyze import tabulate
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import pandas as pd
import numpy as np
Model Overview
The drone model is defined in drone_mdl_urban.py
, along with some visualization functions.
[16]:
from drone_mdl_urban import Drone
[17]:
mdl = Drone()
This is the model structure:
[18]:
mg = mdl.as_modelgraph()
fig, ax = mg.draw()

We can also view the grid environment using its show
methods:
[19]:
collections={"all_occupied": {"color": "red"}, "all_allowed": {"color": "blue"}, "start": {"color": "blue"}, "end": {"color": "blue"}}
fig, ax = mdl.flows['environment'].c.show({"height": {}}, collections=collections)

Which shows the Start, End, and allowed/unsafe locations in the 1000x1000-m grid. In this display, line thickness corresponds to building height, and hatching corresponds to whether or not the space is occupied. We can also display this using show.coord3d
:
[20]:
fig, ax = mdl.flows['environment'].c.show_z("height", collections=collections)

Nominal Simulation
Below we show how this drone performs in the nominal scenario.
[21]:
results_nom, hist_nom =propagate.nominal(mdl)
fig, axs = hist_nom.plot_line("fxns.store_ee.s.soc",
'flows.ee_1.s.rate',
'flows.dofs.s.z',
'fxns.plan_path.m.mode',
'fxns.plan_path.s.pt',
'flows.des_traj.s.power')

As shown, the flight ends fairly quickly (in 10 minutes), with the drone successively proceeding through points in the flight plan.
We can also view this flightpath in 3-d space using History.plot_trajectories
:
[22]:
fig, ax = hist_nom.plot_trajectories('dofs.s.x', 'dofs.s.y', 'dofs.s.z')

Trajectory plots can be overlaid on top of environment plots. In this case, we defined the method plot_env_with_traj
and plot_env_with_traj_z
for this case.
[23]:
from drone_mdl_urban import plot_env_with_traj, plot_env_with_traj_z
[24]:
plot_env_with_traj_z(hist_nom, mdl)
[24]:
(<Figure size 400x400 with 1 Axes>,
<Axes3D: title={'center': 'trajectory'}, xlabel='x', ylabel='y', zlabel='z'>)

[25]:
plot_env_with_traj(hist_nom, mdl)
[25]:
(<Figure size 500x500 with 2 Axes>,
<Axes: title={'center': 'trajectory'}, xlabel='x', ylabel='y'>)

As shown, this is a rather simple straight-line path. If we wanted a more complex scenario, we could make the path more complex by adding multiple destinations or planning the path based on allowed flight/landing locations.
As it is, we may also want to adjust the timestep/speed to get more resolution, since the drone only has a few discrete timesteps in the air.
The results for the simulation are:
[26]:
results_nom
[26]:
endclass:
--rate: 1.0
--cost: 0.0
--expected_cost: 0.0
--repcost: 0
--unsafe_flight_time: 0
--body_strikes: 0.0
--head_strikes: 0.0
--property_restrictions: 0
--safecost: 0.0
--landcost: 0
--p_safety: 0.0
--severities: {'hazardous': 0.0, 'minor': 1.0}
Resilience model
A number of different faults have been implemented in the system.
For example, here we inject a mechanical fault in the left-rear rotor during flight:
[27]:
mdl.fxns['affect_dof'].get_faults(only_present=False)
[27]:
{'drone.fxns.affect_dof': {},
'drone.fxns.affect_dof.ca': {},
'drone.fxns.affect_dof.ca.comps.lf': {'ctlbreak': Fault(prob=2.0000000000000003e-06, cost=1000, phases=(), disturbances=(), units='sim'),
'ctldn': Fault(prob=2.0000000000000003e-06, cost=500, phases=(), disturbances=(), units='sim'),
'ctlup': Fault(prob=2.0000000000000003e-06, cost=500, phases=(), disturbances=(), units='sim'),
'mechbreak': Fault(prob=1.0000000000000002e-06, cost=500, phases=(), disturbances=(), units='sim'),
'mechfriction': Fault(prob=5.000000000000001e-07, cost=500, phases=(), disturbances=(), units='sim'),
'openc': Fault(prob=1.0000000000000002e-06, cost=200, phases=(), disturbances=(), units='sim'),
'propbreak': Fault(prob=3.0000000000000004e-07, cost=200, phases=(), disturbances=(), units='sim'),
'propstuck': Fault(prob=2.0000000000000002e-07, cost=200, phases=(), disturbances=(), units='sim'),
'propwarp': Fault(prob=1.0000000000000001e-07, cost=200, phases=(), disturbances=(), units='sim'),
'short': Fault(prob=1.0000000000000002e-06, cost=200, phases=(), disturbances=(), units='sim')},
'drone.fxns.affect_dof.ca.comps.lr': {'ctlbreak': Fault(prob=2.0000000000000003e-06, cost=1000, phases=(), disturbances=(), units='sim'),
'ctldn': Fault(prob=2.0000000000000003e-06, cost=500, phases=(), disturbances=(), units='sim'),
'ctlup': Fault(prob=2.0000000000000003e-06, cost=500, phases=(), disturbances=(), units='sim'),
'mechbreak': Fault(prob=1.0000000000000002e-06, cost=500, phases=(), disturbances=(), units='sim'),
'mechfriction': Fault(prob=5.000000000000001e-07, cost=500, phases=(), disturbances=(), units='sim'),
'openc': Fault(prob=1.0000000000000002e-06, cost=200, phases=(), disturbances=(), units='sim'),
'propbreak': Fault(prob=3.0000000000000004e-07, cost=200, phases=(), disturbances=(), units='sim'),
'propstuck': Fault(prob=2.0000000000000002e-07, cost=200, phases=(), disturbances=(), units='sim'),
'propwarp': Fault(prob=1.0000000000000001e-07, cost=200, phases=(), disturbances=(), units='sim'),
'short': Fault(prob=1.0000000000000002e-06, cost=200, phases=(), disturbances=(), units='sim')},
'drone.fxns.affect_dof.ca.comps.rf': {'ctlbreak': Fault(prob=2.0000000000000003e-06, cost=1000, phases=(), disturbances=(), units='sim'),
'ctldn': Fault(prob=2.0000000000000003e-06, cost=500, phases=(), disturbances=(), units='sim'),
'ctlup': Fault(prob=2.0000000000000003e-06, cost=500, phases=(), disturbances=(), units='sim'),
'mechbreak': Fault(prob=1.0000000000000002e-06, cost=500, phases=(), disturbances=(), units='sim'),
'mechfriction': Fault(prob=5.000000000000001e-07, cost=500, phases=(), disturbances=(), units='sim'),
'openc': Fault(prob=1.0000000000000002e-06, cost=200, phases=(), disturbances=(), units='sim'),
'propbreak': Fault(prob=3.0000000000000004e-07, cost=200, phases=(), disturbances=(), units='sim'),
'propstuck': Fault(prob=2.0000000000000002e-07, cost=200, phases=(), disturbances=(), units='sim'),
'propwarp': Fault(prob=1.0000000000000001e-07, cost=200, phases=(), disturbances=(), units='sim'),
'short': Fault(prob=1.0000000000000002e-06, cost=200, phases=(), disturbances=(), units='sim')},
'drone.fxns.affect_dof.ca.comps.rr': {'ctlbreak': Fault(prob=2.0000000000000003e-06, cost=1000, phases=(), disturbances=(), units='sim'),
'ctldn': Fault(prob=2.0000000000000003e-06, cost=500, phases=(), disturbances=(), units='sim'),
'ctlup': Fault(prob=2.0000000000000003e-06, cost=500, phases=(), disturbances=(), units='sim'),
'mechbreak': Fault(prob=1.0000000000000002e-06, cost=500, phases=(), disturbances=(), units='sim'),
'mechfriction': Fault(prob=5.000000000000001e-07, cost=500, phases=(), disturbances=(), units='sim'),
'openc': Fault(prob=1.0000000000000002e-06, cost=200, phases=(), disturbances=(), units='sim'),
'propbreak': Fault(prob=3.0000000000000004e-07, cost=200, phases=(), disturbances=(), units='sim'),
'propstuck': Fault(prob=2.0000000000000002e-07, cost=200, phases=(), disturbances=(), units='sim'),
'propwarp': Fault(prob=1.0000000000000001e-07, cost=200, phases=(), disturbances=(), units='sim'),
'short': Fault(prob=1.0000000000000002e-06, cost=200, phases=(), disturbances=(), units='sim')}}
[30]:
results_fault, hist_fault =propagate.one_fault(mdl, "affect_dof.ca.comps.lr", "mechbreak", time=2.0)
[31]:
fig, axs = hist_fault.plot_line("fxns.store_ee.s.soc",
'flows.ee_1.s.rate',
'flows.dofs.s.z',
'fxns.plan_path.m.mode',
'fxns.plan_path.s.pt',
'flows.des_traj.s.power')

[32]:
plot_env_with_traj_z(hist_fault, mdl)
[32]:
(<Figure size 400x400 with 1 Axes>,
<Axes3D: title={'center': 'trajectory'}, xlabel='x', ylabel='y', zlabel='z'>)

[33]:
plot_env_with_traj(hist_fault, mdl)
[33]:
(<Figure size 500x500 with 2 Axes>,
<Axes: title={'center': 'trajectory'}, xlabel='x', ylabel='y'>)

[34]:
results_fault
[34]:
endclass.rate: 1.0000000000000002e-06
endclass.cost: 28600.0
endclass.expected_cost: 2860.0000000000005
endclass.repcost: 500
endclass.unsafe_flight_time: 281
endclass.body_strikes: 0.0
endclass.head_strikes: 0.0
endclass.property_restrictions: 0
endclass.safecost: 28100.0
endclass.landcost: 0
endclass.p_safety: 0.0
endclass.severities: {'hazardous': 0.0, 'minor': 1.0000000000000002e-06}
Here we inject a large list of faults in the system and evaluate their relative consequences in terms of metrics calculated in find_classification:
[35]:
from fmdtools.analyze.phases import PhaseMap, from_hist
phasemaps = from_hist(hist_nom, fxn_modephases=[])
phasemaps
[35]:
{'ctl_dof': PhaseMap({'nominal': [0.0, 30.0]}, {}),
'plan_path': PhaseMap({'taxi': [0.0, 1.0], 'move': [1.1, 1.6], 'land': [1.7, 2.4], 'taxi1': [2.5, 30.0]}, {})}
[36]:
from drone_mdl_urban import make_move_quad
move_quad=make_move_quad(hist_nom, phasemaps['plan_path'].phases['move'])
move_quad
[36]:
{'samp': 'quadrature',
'quad': {'nodes': [-0.20000000000000018, 1.0], 'weights': [1.0, 0.92]}}
[37]:
from fmdtools.sim.sample import FaultDomain, FaultSample
fd = FaultDomain(mdl)
fd.add_all()
fs = FaultSample(fd, phasemap = phasemaps['plan_path'])
fs.add_fault_phases("move", method = "quad",
args=(move_quad['quad']['nodes'], move_quad['quad']['weights']))
fs
[37]:
FaultSample of scenarios:
- drone_fxns_manage_health_lostfunction_t2
- drone_fxns_manage_health_lostfunction_t2
- drone_fxns_store_ee_lowcharge_t2
- drone_fxns_store_ee_lowcharge_t2
- drone_fxns_store_ee_nocharge_t2
- drone_fxns_store_ee_nocharge_t2
- drone_fxns_store_ee_ca_comps_s1p1_break_t2
- drone_fxns_store_ee_ca_comps_s1p1_break_t2
- drone_fxns_store_ee_ca_comps_s1p1_degr_t2
- drone_fxns_store_ee_ca_comps_s1p1_degr_t2
- ... (118 total)
[38]:
endresults, hists = propagate.fault_sample(mdl, fs, staged=False)
SCENARIOS COMPLETE: 100%|██████████| 118/118 [01:32<00:00, 1.27it/s]
[39]:
plot_env_with_traj_z(hists , mdl)
[39]:
(<Figure size 400x400 with 1 Axes>,
<Axes3D: title={'center': 'trajectory'}, xlabel='x', ylabel='y', zlabel='z'>)

[ ]:
statsfmea = tabulate.FMEA(endresults, fs,
average_metric=['rate', 'unsafe_flight_time', 'cost', 'repcost',
'landcost', 'body_strikes',
'head_strikes', 'property_restrictions'],
rates='rate')
fmeatab = statsfmea.as_table(sort_by="cost")
fmeatab
average_rate | average_unsafe_flight_time | average_cost | average_repcost | average_landcost | average_body_strikes | average_head_strikes | average_property_restrictions | ||
---|---|---|---|---|---|---|---|---|---|
drone.fxns.plan_path.ca.comps.vision | undesired_detection | 5.989583e-03 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
lack_of_detection | 5.989583e-03 | 281.0 | 28100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | |
drone.fxns.store_ee | lowcharge | 6.708333e-06 | 281.0 | 28200.0 | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 |
drone.fxns.dist_ee | degr | 2.395833e-06 | 281.0 | 29100.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 |
drone.fxns.store_ee | nocharge | 1.916667e-06 | 281.0 | 28200.0 | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 |
drone.fxns.dist_ee | short | 1.437500e-06 | 281.0 | 29600.0 | 1500.0 | 0.0 | 0.0 | 0.0 | 0.0 |
drone.fxns.plan_path | degloc | 1.150000e-06 | 281.0 | 29100.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 |
drone.fxns.ctl_dof | degctl | 1.150000e-06 | 281.0 | 29100.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 |
drone.fxns.affect_dof.ca.comps.rr | ctlup | 9.583333e-07 | 281.0 | 28600.0 | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 |
drone.fxns.affect_dof.ca.comps.rf | ctldn | 9.583333e-07 | 281.0 | 28600.0 | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 |
drone.fxns.affect_dof.ca.comps.lr | ctlbreak | 9.583333e-07 | 281.0 | 29100.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 |
drone.fxns.affect_dof.ca.comps.rr | ctlbreak | 9.583333e-07 | 281.0 | 29100.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 |
drone.fxns.affect_dof.ca.comps.rf | ctlbreak | 9.583333e-07 | 281.0 | 29100.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 |
drone.fxns.affect_dof.ca.comps.rr | ctldn | 9.583333e-07 | 281.0 | 28600.0 | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 |
drone.fxns.affect_dof.ca.comps.lr | ctldn | 9.583333e-07 | 281.0 | 28600.0 | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 |
drone.fxns.affect_dof.ca.comps.lf | ctlup | 9.583333e-07 | 281.0 | 28600.0 | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 |
drone.fxns.affect_dof.ca.comps.rf | ctlup | 9.583333e-07 | 281.0 | 28600.0 | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 |
drone.fxns.affect_dof.ca.comps.lf | ctlbreak | 9.583333e-07 | 281.0 | 29100.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 |
drone.fxns.dist_ee | break | 9.583333e-07 | 281.0 | 29600.0 | 1500.0 | 0.0 | 0.0 | 0.0 | 0.0 |
drone.fxns.affect_dof.ca.comps.lr | ctlup | 9.583333e-07 | 281.0 | 28600.0 | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 |
drone.fxns.affect_dof.ca.comps.lf | ctldn | 9.583333e-07 | 281.0 | 28600.0 | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 |
drone.fxns.affect_dof.ca.comps.rf | mechbreak | 4.791667e-07 | 281.0 | 28600.0 | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 |
drone.fxns.affect_dof.ca.comps.rr | openc | 4.791667e-07 | 281.0 | 28300.0 | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 |
drone.fxns.affect_dof.ca.comps.rf | short | 4.791667e-07 | 281.0 | 28300.0 | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 |
drone.fxns.affect_dof.ca.comps.rr | mechbreak | 4.791667e-07 | 281.0 | 28600.0 | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 |
drone.fxns.affect_dof.ca.comps.lr | mechbreak | 4.791667e-07 | 281.0 | 28600.0 | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 |
drone.fxns.affect_dof.ca.comps.lf | mechbreak | 4.791667e-07 | 281.0 | 28600.0 | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 |
openc | 4.791667e-07 | 281.0 | 28300.0 | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 | |
short | 4.791667e-07 | 281.0 | 28300.0 | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 | |
drone.fxns.affect_dof.ca.comps.lr | openc | 4.791667e-07 | 281.0 | 28300.0 | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 |
drone.fxns.affect_dof.ca.comps.rr | short | 4.791667e-07 | 281.0 | 28300.0 | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 |
drone.fxns.affect_dof.ca.comps.rf | openc | 4.791667e-07 | 281.0 | 28300.0 | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 |
drone.fxns.affect_dof.ca.comps.lr | short | 4.791667e-07 | 281.0 | 28300.0 | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 |
drone.fxns.ctl_dof | noctl | 2.875000e-07 | 281.0 | 29100.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 |
drone.fxns.plan_path | noloc | 2.875000e-07 | 281.0 | 29100.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 |
drone.fxns.affect_dof.ca.comps.rf | mechfriction | 2.395833e-07 | 281.0 | 28600.0 | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 |
drone.fxns.affect_dof.ca.comps.lr | mechfriction | 2.395833e-07 | 281.0 | 28600.0 | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 |
drone.fxns.affect_dof.ca.comps.rr | mechfriction | 2.395833e-07 | 281.0 | 28600.0 | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 |
drone.fxns.affect_dof.ca.comps.lf | mechfriction | 2.395833e-07 | 281.0 | 28600.0 | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 |
drone.fxns.store_ee.ca.comps.s1p1 | nocharge | 1.437500e-07 | 281.0 | 28200.0 | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 |
drone.fxns.affect_dof.ca.comps.rf | propbreak | 1.437500e-07 | 281.0 | 28300.0 | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 |
drone.fxns.affect_dof.ca.comps.lr | propbreak | 1.437500e-07 | 281.0 | 28300.0 | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 |
drone.fxns.affect_dof.ca.comps.rr | propbreak | 1.437500e-07 | 281.0 | 28300.0 | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 |
drone.fxns.affect_dof.ca.comps.lf | propbreak | 1.437500e-07 | 281.0 | 28300.0 | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 |
drone.fxns.store_ee.ca.comps.s1p1 | lowcharge | 9.583333e-08 | 281.0 | 28200.0 | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 |
drone.fxns.affect_dof.ca.comps.rr | propstuck | 9.583333e-08 | 281.0 | 28300.0 | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 |
drone.fxns.affect_dof.ca.comps.rf | propstuck | 9.583333e-08 | 281.0 | 28300.0 | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 |
drone.fxns.affect_dof.ca.comps.lf | propstuck | 9.583333e-08 | 281.0 | 28300.0 | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 |
drone.fxns.affect_dof.ca.comps.lr | propstuck | 9.583333e-08 | 281.0 | 28300.0 | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 |
drone.fxns.store_ee.ca.comps.s1p1 | short | 7.187500e-08 | 281.0 | 28200.0 | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 |
break | 7.187500e-08 | 281.0 | 28200.0 | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | |
degr | 7.187500e-08 | 281.0 | 28200.0 | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | |
drone.fxns.affect_dof.ca.comps.rr | propwarp | 4.791667e-08 | 281.0 | 28300.0 | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 |
drone.fxns.affect_dof.ca.comps.rf | propwarp | 4.791667e-08 | 281.0 | 28300.0 | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 |
drone.fxns.affect_dof.ca.comps.lr | propwarp | 4.791667e-08 | 281.0 | 28300.0 | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 |
drone.fxns.affect_dof.ca.comps.lf | propwarp | 4.791667e-08 | 281.0 | 28300.0 | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 |
drone.fxns.manage_health | lostfunction | 7.187500e-09 | 281.0 | 29100.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 |
drone.fxns.hold_payload | deform | 2.875000e-09 | 281.0 | 29600.0 | 1500.0 | 0.0 | 0.0 | 0.0 | 0.0 |
break | 7.187500e-10 | 281.0 | 29600.0 | 1500.0 | 0.0 | 0.0 | 0.0 | 0.0 |
[ ]: