fmdtools Paper Demonstration
This notebook uses a high-level model of a multirotor drone to illustrate the different levels of model fidelity that can be defined and analyzed in fmdtools. The intent of the fmdtools model represenation is to be able to iteratively make a model more detailed to support the design process. Specifically, we’d like to be able to: - Start with a Network representation of the system to see the network properties of the high-level system structure (Netowrk Model) - Be able to simulate the system prior to understanding the full system dynamics (Static Propagation Model) - Be able to add system dynamics (Dynamic Propagation Model) - Be able to add component architectures as needed (Hierarchical Model)
A version of this notebook was presented in the paper:
At that time, fmdtools was not developed with inheritence in mind, and thus the iterative update of models meant either over-writing an existing model structure (losing high-level design information) or copy-pasting model definition (duplicating system definition).
Since then, we have been evolving fmdtools classes to be more modular in support inheritance. The following demo was thus adapted with this in mind, to show not only the progression of analysis types, but also to show how more detailed models can inherit from early model structures to maintain a consistent system representation.
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.
Initial Network Model
In our initial model, all we have is the flows, functions, and connections between them. These are set up in a model class as shown:
[58]:
from fmdtools.define.architecture.function import FunctionArchitecture
from fmdtools.define.block.function import GenericFxn
class Drone(FunctionArchitecture):
__slots__=()
default_sp={}
def init_architecture(self, **kwargs):
#add flows to the model
self.add_flow("force_st")
self.add_flow("force_lin")
self.add_flow("ee_1")
self.add_flow("ee_mot")
self.add_flow("ee_ctl")
self.add_flow("ctl")
self.add_flow("dofs")
self.add_flow("des_traj")
# add functions to the model
self.add_fxn("store_ee", GenericFxn, "ee_1", "force_st")
self.add_fxn("dist_ee", GenericFxn, "ee_1", "ee_mot", "ee_ctl", "force_st")
self.add_fxn("affect_dof", GenericFxn, "ee_mot", "ctl", "dofs", "force_lin")
self.add_fxn("ctl_dof", GenericFxn, "ee_ctl", "des_traj", "ctl", "dofs", "force_st")
self.add_fxn("plan_path", GenericFxn, "ee_ctl", "des_traj", "force_st", "dofs")
self.add_fxn("hold_payload", GenericFxn, "dofs", "force_lin", "force_st")
self.add_fxn("view_env", GenericFxn, "dofs")
mdl = Drone()
Setting Node Positions
As shown below, it can be difficult to make sense of a model structure using the default graph layout. We might instead want to see something that more closely approximates a flow chart of the system.
[59]:
from fmdtools.define.architecture.function import FunctionArchitectureGraph, FunctionArchitectureFxnGraph
mdl = Drone()
mg = FunctionArchitectureGraph(mdl)
dot = mg.draw_graphviz()
[60]:
mdl.fxns
[60]:
{'store_ee': store_ee GenericFxn,
'dist_ee': dist_ee GenericFxn,
'affect_dof': affect_dof GenericFxn,
'ctl_dof': ctl_dof GenericFxn,
'plan_path': plan_path GenericFxn,
'hold_payload': hold_payload GenericFxn,
'view_env': view_env GenericFxn}
[61]:
mg.g.nodes
[61]:
NodeView(('drone.flows.force_st', 'drone.flows.force_lin', 'drone.flows.ee_1', 'drone.flows.ee_mot', 'drone.flows.ee_ctl', 'drone.flows.ctl', 'drone.flows.dofs', 'drone.flows.des_traj', 'drone.fxns.store_ee', 'drone.fxns.dist_ee', 'drone.fxns.affect_dof', 'drone.fxns.ctl_dof', 'drone.fxns.plan_path', 'drone.fxns.hold_payload', 'drone.fxns.view_env'))
To set node positions, we can use Graph.move_nodes
, which lets one drag the nodes to their desired locations. Node that while these node positions become attributes of the object (for future display) it is good practice to save node locations when one is done in the script so you don’t have to move them every time.
[62]:
# %matplotlib qt5
# gi = mg.move_nodes()
[63]:
# %matplotlib inline
[64]:
mg.pos={'drone.fxns.store_ee': [-0.85, -0.63],
'drone.fxns.dist_ee': [-0.79, 0.09],
'drone.fxns.affect_dof': [0.69, 0.34],
'drone.fxns.ctl_dof': [-0.01, 0.45],
'drone.fxns.plan_path': [-0.55, 0.8],
'drone.fxns.hold_payload': [0.22, -0.72],
'drone.fxns.view_env': [0.77, -0.63],
'drone.flows.force_st': [-0.29, -0.66],
'drone.flows.force_lin': [0.24, -0.13],
'drone.flows.ee_1': [-0.82, -0.26],
'drone.flows.ee_mot': [-0.1, 0.14],
'drone.flows.ee_ctl': [-0.83, 0.45],
'drone.flows.ctl': [0.35, 0.61],
'drone.flows.dofs': [0.72, -0.06],
'drone.flows.des_traj': [0.09, 0.78]}
[65]:
fig, ax = mg.draw()
Note that a variety of views of the model graph can be used for analysis/visualization. Below we use the ModelFxnGraph
[66]:
fg = FunctionArchitectureFxnGraph(mdl)
fg.pos={'drone.fxns.store_ee': [-0.85, -0.63],
'drone.fxns.dist_ee': [-0.79, 0.09],
'drone.fxns.affect_dof': [0.69, 0.34],
'drone.fxns.ctl_dof': [-0.01, 0.45],
'drone.fxns.plan_path': [-0.55, 0.8],
'drone.fxns.hold_payload': [0.22, -0.72],
'drone.fxns.view_env': [0.77, -0.63]}
fg.set_edge_labels(title='', subtext='')
fig, ax = fg.draw(figsize=(6,4), withlegend=False)
Network Analysis
A network model can be used to compute network metrics and visualize network vulnerabilities.
We can calculate network metrics using calc_aspl, calc_modularity, and calc_robustness_coefficient in the networks
module.
[67]:
aspl = fg.calc_aspl()
q = fg.calc_modularity()
rc = fg.calc_robustness_coefficient()
print("ASPL: %.2f" % round(aspl, 2))
print("Modularity: %.2f" % round(q,2))
print("Robustness Coefficient: %.2f" % round(rc,2))
ASPL: 1.14
Modularity: 0.00
Robustness Coefficient: 99.47
Next, we visualize network vulnerabilities using find_bridging_nodes and find_high_degree_nodes.
[68]:
fig_bridging_nodes, ax_bridging_nodes = fg.plot_bridging_nodes(figsize=(6,4), withlegend=False)
fig_bridging_nodes.savefig('bridgingnodes.pdf', format="pdf", bbox_inches = 'tight', pad_inches = 0)
As shown, no bridging notes are found. This is somewhat expected, since functiongraph functions are for the most part all connected to the same flows.
[69]:
fig_high_degree_nodes, ax_high_degree_nodes = fg.plot_high_degree_nodes(figsize=(6,4), withlegend=False)
fig_bridging_nodes.savefig('highdegreenodes.pdf', format="pdf", bbox_inches = 'tight', pad_inches = 0)
However, some functions do have more connected to them than others. In this case-, path planning and control, along with the structure.
High degree nodes (along with their degrees) and bridging nodes are also obtainable as lists.
[70]:
print('Bridging Nodes:', fg.find_bridging_nodes())
print('High Degree Nodes:',fg.find_high_degree_nodes())
Bridging Nodes: []
High Degree Nodes: [('drone.fxns.ctl_dof', 6), ('drone.fxns.plan_path', 6), ('drone.fxns.hold_payload', 6)]
Finally, we can plot the degree distribution of the network using degree_dist.
[71]:
fig = fg.plot_degree_dist()
fig.savefig('degreedist.pdf', format="pdf", bbox_inches = 'tight', pad_inches = 0.0)
The above analysis includes only function nodes. It is also possible to treat the entire model graph (containing both functions and flows) as a unipartite-like graph and perform similar analysis on both function and flow nodes.
[72]:
aspl = mg.calc_aspl()
q = mg.calc_modularity()
rc = mg.calc_robustness_coefficient()
print("ASPL, functions and flows: %.2f" % round(aspl, 2))
print("Modularity, functions and flows: %.2f" % round(q,2))
print("Robustness Coefficient, functions and flows: %.2f" % round(rc,2))
ASPL, functions and flows: 2.30
Modularity, functions and flows: 0.32
Robustness Coefficient, functions and flows: 87.12
[73]:
fig_bridging_nodes, ax_bridging_nodes = mg.plot_bridging_nodes(figsize=(6,4), withlegend=False)
fig_high_degree_nodes, ax_high_degree_nodes = mg.plot_high_degree_nodes(figsize=(6,4), withlegend=False)
[74]:
print('Bridging Nodes:',mg.find_bridging_nodes())
print('High Degree Nodes:',mg.find_high_degree_nodes())
Bridging Nodes: ['drone.fxns.ctl_dof', 'drone.fxns.dist_ee', 'drone.fxns.hold_payload', 'drone.fxns.plan_path']
High Degree Nodes: [('drone.flows.force_st', 5), ('drone.flows.dofs', 5), ('drone.fxns.ctl_dof', 5)]
[75]:
fig = mg.plot_degree_dist()
The SFF model can be simulated with options for simulation time, infection (failure) rate, and recovery (fix) rate. The start node can be selected or chosen randomly. Plotting includes an option for error bars. This models the system’s response to a failure using an analogy of an epidemic spreading through a network.
[76]:
fig = mg.sff_model(endtime=15,pi=.3,pr=.1,start_node='drone.fxns.affect_dof',error_bar_option='on')
fig.savefig('sff_model.pdf', format="pdf", bbox_inches = 'tight', pad_inches = 0)
Static Model
In this demonstration, we will use a static representation of the system model to displaygraph views of fault scenarios and produce a static FMEA
The static model is located in drone_mdl_static.py
.
[77]:
from drone_mdl_static import Drone as Drone_Static
This model has function definitions for single-timestep behavior. Thus, we can simulate it with methods in sim.propagate
. In this case, we run all single-fault scenarios.
[78]:
from fmdtools.sim import propagate
static_mdl = Drone_Static()
endclasses, mdlhists = propagate.single_faults(static_mdl)
SCENARIOS COMPLETE: 100%|██████████| 21/21 [00:00<00:00, 169.33it/s]
Here we produce a scenario-based FMEA to show which scenarios are most important in the model:
[79]:
from fmdtools.analyze import tabulate
static_fmea = tabulate.result_summary_fmea(endclasses, mdlhists, *static_mdl.fxns, *static_mdl.flows)
static_fmea.sort_values('expected_cost', ascending=False)
[79]:
degraded | faulty | rate | cost | expected_cost | |
---|---|---|---|---|---|
store_ee_nocharge_t0p0 | ['view_env'] | ['store_ee', 'dist_ee', 'affect_dof', 'ctl_dof... | 0.00001 | 32800.0 | 32800.0 |
plan_path_degloc_t0p0 | ['store_ee', 'dist_ee', 'affect_dof', 'ctl_dof... | ['plan_path'] | 0.000008 | 20000.0 | 16000.0 |
dist_ee_short_t0p0 | ['store_ee', 'view_env', 'ee_1'] | ['dist_ee', 'affect_dof', 'ctl_dof', 'plan_pat... | 0.000003 | 35500.0 | 10650.0 |
ctl_dof_degctl_t0p0 | ['store_ee', 'dist_ee', 'affect_dof', 'plan_pa... | ['ctl_dof'] | 0.000008 | 10000.0 | 8000.0 |
affect_dof_ctlbreak_t0p0 | ['store_ee', 'view_env'] | ['dist_ee', 'affect_dof', 'ctl_dof', 'plan_pat... | 0.000002 | 33500.0 | 6700.0 |
affect_dof_ctlup_t0p0 | ['store_ee', 'view_env'] | ['dist_ee', 'affect_dof', 'ctl_dof', 'plan_pat... | 0.000002 | 33000.0 | 6600.0 |
ctl_dof_noctl_t0p0 | ['store_ee', 'view_env'] | ['dist_ee', 'affect_dof', 'ctl_dof', 'plan_pat... | 0.000002 | 32500.0 | 6500.0 |
dist_ee_break_t0p0 | ['store_ee', 'view_env'] | ['dist_ee', 'affect_dof', 'ctl_dof', 'plan_pat... | 0.000002 | 32500.0 | 6500.0 |
affect_dof_short_t0p0 | ['store_ee', 'view_env'] | ['dist_ee', 'affect_dof', 'ctl_dof', 'plan_pat... | 0.000001 | 32700.0 | 3270.0 |
affect_dof_openc_t0p0 | ['store_ee', 'view_env'] | ['dist_ee', 'affect_dof', 'ctl_dof', 'plan_pat... | 0.000001 | 32700.0 | 3270.0 |
affect_dof_mechbreak_t0p0 | ['store_ee', 'view_env'] | ['dist_ee', 'affect_dof', 'ctl_dof', 'plan_pat... | 0.000001 | 32500.0 | 3250.0 |
plan_path_noloc_t0p0 | ['store_ee', 'dist_ee', 'affect_dof', 'ctl_dof... | ['plan_path'] | 0.000002 | 10000.0 | 2000.0 |
view_env_poorview_t0p0 | ['store_ee', 'dist_ee', 'affect_dof', 'ctl_dof... | ['view_env'] | 0.000002 | 10000.0 | 2000.0 |
affect_dof_propbreak_t0p0 | ['store_ee', 'view_env'] | ['dist_ee', 'affect_dof', 'ctl_dof', 'plan_pat... | 0.0 | 32700.0 | 981.0 |
hold_payload_deform_t0p0 | ['store_ee', 'dist_ee', 'affect_dof', 'ctl_dof... | ['hold_payload'] | 0.000001 | 10000.0 | 800.0 |
affect_dof_propstuck_t0p0 | ['store_ee', 'view_env'] | ['dist_ee', 'affect_dof', 'ctl_dof', 'plan_pat... | 0.0 | 32700.0 | 654.0 |
hold_payload_break_t0p0 | ['store_ee', 'view_env'] | ['dist_ee', 'affect_dof', 'ctl_dof', 'plan_pat... | 0.0 | 32500.0 | 650.0 |
dist_ee_degr_t0p0 | ['store_ee', 'affect_dof', 'plan_path', 'hold_... | ['dist_ee'] | 0.000005 | 1000.0 | 500.0 |
affect_dof_ctldn_t0p0 | ['store_ee', 'dist_ee', 'ctl_dof', 'plan_path'... | ['affect_dof'] | 0.000002 | 500.0 | 100.0 |
affect_dof_mechfriction_t0p0 | ['store_ee', 'dist_ee', 'ctl_dof', 'plan_path'... | ['affect_dof'] | 0.000001 | 500.0 | 25.0 |
affect_dof_propwarp_t0p0 | ['store_ee', 'dist_ee', 'ctl_dof', 'plan_path'... | ['affect_dof'] | 0.0 | 200.0 | 2.0 |
nominal | ['store_ee', 'dist_ee', 'affect_dof', 'ctl_dof... | [] | 1.0 | 0.0 | 0.0 |
[80]:
print(static_fmea.sort_values('expected_cost', ascending=False).to_latex())
\begin{tabular}{llllll}
\toprule
& degraded & faulty & rate & cost & expected_cost \\
\midrule
store_ee_nocharge_t0p0 & ['view_env'] & ['store_ee', 'dist_ee', 'affect_dof', 'ctl_dof', 'plan_path', 'hold_payload'] & 0.000010 & 32800.000000 & 32800.000000 \\
plan_path_degloc_t0p0 & ['store_ee', 'dist_ee', 'affect_dof', 'ctl_dof', 'hold_payload', 'view_env', 'force_st', 'force_lin', 'ee_1', 'ee_mot', 'ee_ctl'] & ['plan_path'] & 0.000008 & 20000.000000 & 16000.000000 \\
dist_ee_short_t0p0 & ['store_ee', 'view_env', 'ee_1'] & ['dist_ee', 'affect_dof', 'ctl_dof', 'plan_path', 'hold_payload'] & 0.000003 & 35500.000000 & 10650.000000 \\
ctl_dof_degctl_t0p0 & ['store_ee', 'dist_ee', 'affect_dof', 'plan_path', 'hold_payload', 'view_env', 'force_st', 'force_lin', 'ee_1', 'ee_mot', 'ee_ctl', 'des_traj'] & ['ctl_dof'] & 0.000008 & 10000.000000 & 8000.000000 \\
affect_dof_ctlbreak_t0p0 & ['store_ee', 'view_env'] & ['dist_ee', 'affect_dof', 'ctl_dof', 'plan_path', 'hold_payload'] & 0.000002 & 33500.000000 & 6700.000000 \\
affect_dof_ctlup_t0p0 & ['store_ee', 'view_env'] & ['dist_ee', 'affect_dof', 'ctl_dof', 'plan_path', 'hold_payload'] & 0.000002 & 33000.000000 & 6600.000000 \\
ctl_dof_noctl_t0p0 & ['store_ee', 'view_env'] & ['dist_ee', 'affect_dof', 'ctl_dof', 'plan_path', 'hold_payload'] & 0.000002 & 32500.000000 & 6500.000000 \\
dist_ee_break_t0p0 & ['store_ee', 'view_env'] & ['dist_ee', 'affect_dof', 'ctl_dof', 'plan_path', 'hold_payload'] & 0.000002 & 32500.000000 & 6500.000000 \\
affect_dof_short_t0p0 & ['store_ee', 'view_env'] & ['dist_ee', 'affect_dof', 'ctl_dof', 'plan_path', 'hold_payload'] & 0.000001 & 32700.000000 & 3270.000000 \\
affect_dof_openc_t0p0 & ['store_ee', 'view_env'] & ['dist_ee', 'affect_dof', 'ctl_dof', 'plan_path', 'hold_payload'] & 0.000001 & 32700.000000 & 3270.000000 \\
affect_dof_mechbreak_t0p0 & ['store_ee', 'view_env'] & ['dist_ee', 'affect_dof', 'ctl_dof', 'plan_path', 'hold_payload'] & 0.000001 & 32500.000000 & 3250.000000 \\
plan_path_noloc_t0p0 & ['store_ee', 'dist_ee', 'affect_dof', 'ctl_dof', 'hold_payload', 'view_env', 'force_st', 'force_lin', 'ee_1', 'ee_mot', 'ee_ctl'] & ['plan_path'] & 0.000002 & 10000.000000 & 2000.000000 \\
view_env_poorview_t0p0 & ['store_ee', 'dist_ee', 'affect_dof', 'ctl_dof', 'plan_path', 'hold_payload', 'force_st', 'force_lin', 'ee_1', 'ee_mot', 'ee_ctl', 'ctl', 'dofs', 'des_traj'] & ['view_env'] & 0.000002 & 10000.000000 & 2000.000000 \\
affect_dof_propbreak_t0p0 & ['store_ee', 'view_env'] & ['dist_ee', 'affect_dof', 'ctl_dof', 'plan_path', 'hold_payload'] & 0.000000 & 32700.000000 & 981.000000 \\
hold_payload_deform_t0p0 & ['store_ee', 'dist_ee', 'affect_dof', 'ctl_dof', 'plan_path', 'view_env', 'ee_1', 'ee_mot', 'ee_ctl', 'ctl', 'dofs', 'des_traj'] & ['hold_payload'] & 0.000001 & 10000.000000 & 800.000000 \\
affect_dof_propstuck_t0p0 & ['store_ee', 'view_env'] & ['dist_ee', 'affect_dof', 'ctl_dof', 'plan_path', 'hold_payload'] & 0.000000 & 32700.000000 & 654.000000 \\
hold_payload_break_t0p0 & ['store_ee', 'view_env'] & ['dist_ee', 'affect_dof', 'ctl_dof', 'plan_path', 'hold_payload'] & 0.000000 & 32500.000000 & 650.000000 \\
dist_ee_degr_t0p0 & ['store_ee', 'affect_dof', 'plan_path', 'hold_payload', 'view_env', 'force_st', 'force_lin', 'ee_1', 'des_traj'] & ['dist_ee'] & 0.000005 & 1000.000000 & 500.000000 \\
affect_dof_ctldn_t0p0 & ['store_ee', 'dist_ee', 'ctl_dof', 'plan_path', 'hold_payload', 'view_env', 'force_st', 'force_lin', 'ee_ctl', 'des_traj'] & ['affect_dof'] & 0.000002 & 500.000000 & 100.000000 \\
affect_dof_mechfriction_t0p0 & ['store_ee', 'dist_ee', 'ctl_dof', 'plan_path', 'hold_payload', 'view_env', 'force_st', 'force_lin', 'ee_1', 'ee_mot', 'ee_ctl', 'ctl', 'dofs', 'des_traj'] & ['affect_dof'] & 0.000001 & 500.000000 & 25.000000 \\
affect_dof_propwarp_t0p0 & ['store_ee', 'dist_ee', 'ctl_dof', 'plan_path', 'hold_payload', 'view_env', 'force_st', 'force_lin', 'ee_ctl', 'ctl', 'des_traj'] & ['affect_dof'] & 0.000000 & 200.000000 & 2.000000 \\
nominal & ['store_ee', 'dist_ee', 'affect_dof', 'ctl_dof', 'plan_path', 'hold_payload', 'view_env', 'force_st', 'force_lin', 'ee_1', 'ee_mot', 'ee_ctl', 'ctl', 'dofs', 'des_traj'] & [] & 1.000000 & 0.000000 & 0.000000 \\
\bottomrule
\end{tabular}
We can in turn visualize these faults on the graph representation of the system. Here we will focus on the break of one of the rotors in the AffectDOF function, the effects of which are shown below:
[81]:
static_mdl = Drone_Static()
result, mdlhist = propagate.one_fault(static_mdl,'affect_dof', 'mechbreak', t=0, desired_result='graph')
result.graph.set_edge_labels(title='')
fig, ax = result.graph.draw(title = 'AffectDOF: Mechbreak at final sim time')
#fig.savefig('static_propagation.pdf', format="pdf", bbox_inches = 'tight', pad_inches = 0)
Dynamic Model
In the dynamic model, we add time ranges and dynamic behaviors to generate behavior-over-time graphs and dynamic/phase-based FMEAs.
This model is located in drone_mdl_dynamic.py
.
[82]:
from examples.multirotor.drone_mdl_dynamic import Drone as Drone_Dynamic
Here we can see how the system operates over time in the nominal case:
[83]:
# Note: because of the complicated functions, the model must be re-instantiated for each function in order to work in this case
dynamic_mdl = Drone_Dynamic()
endclass, mdlhist = propagate.nominal(dynamic_mdl)
[84]:
fig, ax = mdlhist.plot_line('fxns.store_ee.s.soc',
'flows.dofs.s.x',
'flows.dofs.s.y',
'flows.dofs.s.z',
'flows.dofs.s.planvel',
'flows.dofs.s.vertvel',
'fxns.plan_path.m.mode')
As shown below, in the case of the break in the AffectDOF function, the system crashes:
[85]:
dynamic_mdl = Drone_Dynamic()
endclass, mdlhist = propagate.one_fault(dynamic_mdl,'affect_dof', 'mechbreak', time=10)
[86]:
fig, axs = mdlhist.plot_line('flows.dofs.s.x','flows.dofs.s.y','flows.dofs.s.z', 'fxns.store_ee.s.soc',
title ='Drone Response to affect_dof mechbreak', time_slice=10,
ylabels={'dofs.s.x':'m','dofs.s.y':'m','dofs.s.z':'m','fxns.store_ee.s.soc':'%'},\
h_padding=0.3, legend_loc=2, xlabel='time (s)')
fig.savefig("fault_behavior.pdf", format="pdf", bbox_inches = 'tight', pad_inches = 0)
Finally, we can see how the cost function of this scenario changes over time. As shown, when the fault is injected early, it has a lower cost because it crashes at the landing pad and not in a dangerous area. When it is injected at the end, the cost is minimal because the drone has already landed.
[87]:
from fmdtools.sim.sample import FaultDomain, FaultSample
from fmdtools.analyze.phases import PhaseMap
mdl_quad_comp = Drone_Dynamic()
fd = FaultDomain(mdl_quad_comp)
fd.add_fault('affect_dof', 'mechbreak')
fs = FaultSample(fd, phasemap = PhaseMap(mdl_quad_comp.sp.phases))
fs.add_fault_phases(args=(5,))
[88]:
quad_comp_endclasses, quad_comp_mdlhists = propagate.fault_sample(mdl_quad_comp, fs, staged=True)
SCENARIOS COMPLETE: 100%|██████████| 13/13 [00:00<00:00, 39.14it/s]
[89]:
quad_comp_mdlhists.affect_dof_mechbreak_t17p0.flows.dofs.s.x[-1]
[89]:
0.0
[90]:
from fmdtools.analyze import phases
fig = phases.samplemetric(fs, quad_comp_endclasses)
fig.savefig("cost_over_time.pdf", format="pdf", bbox_inches = 'tight', pad_inches = 0)
[91]:
quad_comp_endclasses
[91]:
affect_dof_mechbreak5.000000000000001e-07
affect_dof_mechbreak 500
affect_dof_mechbreak 25.000000000000007
affect_dof_mechbreak5.000000000000001e-07
affect_dof_mechbreak 132500
affect_dof_mechbreak 6625.000000000002
affect_dof_mechbreak2.0000000000000004e-07
affect_dof_mechbreak 182500
affect_dof_mechbreak 3650.0000000000005
affect_dof_mechbreak2.0000000000000004e-07
affect_dof_mechbreak 182500
affect_dof_mechbreak 3650.0000000000005
affect_dof_mechbreak2.0000000000000004e-07
affect_dof_mechbreak 182500
affect_dof_mechbreak 3650.0000000000005
affect_dof_mechbreak2.0000000000000004e-07
affect_dof_mechbreak 182500
affect_dof_mechbreak 3650.0000000000005
affect_dof_mechbreak2.0000000000000004e-07
affect_dof_mechbreak 182500
affect_dof_mechbreak 3650.0000000000005
affect_dof_mechbreak2.5000000000000004e-07
affect_dof_mechbreak 132500
affect_dof_mechbreak 3312.500000000001
affect_dof_mechbreak2.5000000000000004e-07
affect_dof_mechbreak 500
affect_dof_mechbreak 12.500000000000004
affect_dof_mechbreak2.5000000000000004e-07
affect_dof_mechbreak 500
affect_dof_mechbreak 12.500000000000004
affect_dof_mechbreak2.5000000000000004e-07
affect_dof_mechbreak 500
affect_dof_mechbreak 12.500000000000004
affect_dof_mechbreak5.000000000000001e-07
affect_dof_mechbreak 500
affect_dof_mechbreak 25.000000000000007
affect_dof_mechbreak5.000000000000001e-07
affect_dof_mechbreak 500
affect_dof_mechbreak 25.000000000000007
nominal.endclass.rate: 1.0
nominal.endclass.cost: 0
nominal.endclass.expected_cost: 0.0
Hierarchical Model
In the hierarchical model, we can use the simulation to compare system architectures. First by seeing how faults effect the behaviors in each architechture, then by seing how it affects the overall system resilience.
This model is located in drone_mdl_hierarchical.py
.
[92]:
from examples.multirotor.drone_mdl_hierarchical import Drone as Drone_Hierarchical
First, we can model how the quadrotor architecture behaves under faults–in this case, identically to the monolythic model:
[93]:
hierarchical_model = Drone_Hierarchical(p={'arch':'quad'})
endclass, mdlhist = propagate.one_fault(hierarchical_model,'affect_dof', 'rf_mechbreak', time=10)
[94]:
fig, axs = mdlhist.plot_line('flows.dofs.s.x', 'flows.dofs.s.y', 'flows.dofs.s.z', 'fxns.store_ee.s.soc',
title='Response of Drone to AffectDOF mechbreak', time_slice=10, legend_loc=False)
Then we can see how the octorotor architecture performs in the same case:
[95]:
hierarchical_model = Drone_Hierarchical(p={'arch':'oct'})
endclass, mdlhist = propagate.one_fault(hierarchical_model,'affect_dof', 'rf_mechbreak', time=10)
[96]:
fig, axs = mdlhist.plot_line('flows.dofs.s.x', 'flows.dofs.s.y', 'flows.dofs.s.z', 'fxns.store_ee.s.soc',
title='Response of Drone to AffectDOF: RFmechbreak',
ylabels={'flows.dofs.s.x':'m','flows.dofs.s.y':'m','flows.dofs.s.z':'m','fxns.store_ee.s.soc':'%'},\
time_slice=10, h_padding=0.3, legend_loc=2)
fig.savefig("red_fault_behavior.pdf", format="pdf", bbox_inches = 'tight', pad_inches = 0)
As shown, the octorotor architecture enables the quadrotor to recover from the fault and land.
Next, we can compare how each architecture mitigates the set of faults that originiate in each function: ### Quadcopter Resilience
Here we quantify the expected costs of faults originiating in the quadcopter architecture:
[97]:
mdl_quad = Drone_Hierarchical(p={'arch':'quad'})
mdl_quad.fxns['affect_dof'].m.faultmodes
quad_faults = [('affect_dof', fault) for fault in list(mdl_quad.fxns['affect_dof'].m.faultmodes.keys())]
[98]:
fd_quad = FaultDomain(mdl_quad)
fd_quad.add_all_fxn_modes("affect_dof")
fd_quad
[98]:
FaultDomain with faults:
-('affect_dof', 'short')
-('affect_dof', 'openc')
-('affect_dof', 'ctlup')
-('affect_dof', 'ctldn')
-('affect_dof', 'ctlbreak')
-('affect_dof', 'mechbreak')
-('affect_dof', 'mechfriction')
-('affect_dof', 'propwarp')
-('affect_dof', 'propstuck')
-('affect_dof', 'propbreak')
-...more
[99]:
fs_quad = FaultSample(fd_quad, phasemap = PhaseMap(mdl_quad.sp.phases))
fs_quad.add_fault_phases("forward")
fs_quad
[99]:
FaultSample of scenarios:
- affect_dof_short_t8p0
- affect_dof_openc_t8p0
- affect_dof_ctlup_t8p0
- affect_dof_ctldn_t8p0
- affect_dof_ctlbreak_t8p0
- affect_dof_mechbreak_t8p0
- affect_dof_mechfriction_t8p0
- affect_dof_propwarp_t8p0
- affect_dof_propstuck_t8p0
- affect_dof_propbreak_t8p0
- ... (50 total)
[100]:
quad_endclasses, quad_mdlhists = propagate.fault_sample(mdl_quad, fs_quad, staged=True)
SCENARIOS COMPLETE: 100%|██████████| 50/50 [00:02<00:00, 23.55it/s]
[101]:
quad_tab=quad_endclasses.create_simple_fmea()
quad_tab.sort_values('expected_cost', ascending=False)
[101]:
rate | cost | expected_cost | |
---|---|---|---|
affect_dof_rr_ctlbreak_t8p0 | 2.000000e-06 | 185800.0 | 37160.0 |
affect_dof_rf_ctlbreak_t8p0 | 2.000000e-06 | 185800.0 | 37160.0 |
affect_dof_lr_ctlbreak_t8p0 | 2.000000e-06 | 185800.0 | 37160.0 |
affect_dof_lf_ctlbreak_t8p0 | 2.000000e-06 | 185800.0 | 37160.0 |
affect_dof_lf_ctlup_t8p0 | 2.000000e-06 | 185300.0 | 37060.0 |
affect_dof_rr_ctldn_t8p0 | 2.000000e-06 | 185300.0 | 37060.0 |
affect_dof_rr_ctlup_t8p0 | 2.000000e-06 | 185300.0 | 37060.0 |
affect_dof_rf_ctldn_t8p0 | 2.000000e-06 | 185300.0 | 37060.0 |
affect_dof_rf_ctlup_t8p0 | 2.000000e-06 | 185300.0 | 37060.0 |
affect_dof_lr_ctldn_t8p0 | 2.000000e-06 | 185300.0 | 37060.0 |
affect_dof_lf_ctldn_t8p0 | 2.000000e-06 | 185300.0 | 37060.0 |
affect_dof_lr_ctlup_t8p0 | 2.000000e-06 | 185300.0 | 37060.0 |
affect_dof_lf_openc_t8p0 | 1.000000e-06 | 185000.0 | 18500.0 |
affect_dof_rr_openc_t8p0 | 1.000000e-06 | 185000.0 | 18500.0 |
affect_dof_rr_short_t8p0 | 1.000000e-06 | 185000.0 | 18500.0 |
affect_dof_rf_openc_t8p0 | 1.000000e-06 | 185000.0 | 18500.0 |
affect_dof_rf_short_t8p0 | 1.000000e-06 | 185000.0 | 18500.0 |
affect_dof_lf_short_t8p0 | 1.000000e-06 | 185000.0 | 18500.0 |
affect_dof_lr_short_t8p0 | 1.000000e-06 | 185000.0 | 18500.0 |
affect_dof_lr_openc_t8p0 | 1.000000e-06 | 185000.0 | 18500.0 |
affect_dof_rr_mechbreak_t8p0 | 1.000000e-06 | 184800.0 | 18480.0 |
affect_dof_rf_mechbreak_t8p0 | 1.000000e-06 | 184800.0 | 18480.0 |
affect_dof_lr_mechbreak_t8p0 | 1.000000e-06 | 184800.0 | 18480.0 |
affect_dof_lf_mechbreak_t8p0 | 1.000000e-06 | 184800.0 | 18480.0 |
affect_dof_rr_mechfriction_t8p0 | 5.000000e-07 | 185300.0 | 9265.0 |
affect_dof_lr_mechfriction_t8p0 | 5.000000e-07 | 185300.0 | 9265.0 |
affect_dof_rf_mechfriction_t8p0 | 5.000000e-07 | 185300.0 | 9265.0 |
affect_dof_lf_mechfriction_t8p0 | 5.000000e-07 | 185300.0 | 9265.0 |
affect_dof_rr_propbreak_t8p0 | 3.000000e-07 | 184800.0 | 5544.0 |
affect_dof_lf_propbreak_t8p0 | 3.000000e-07 | 184800.0 | 5544.0 |
affect_dof_lr_propbreak_t8p0 | 3.000000e-07 | 184800.0 | 5544.0 |
affect_dof_rf_propbreak_t8p0 | 3.000000e-07 | 184800.0 | 5544.0 |
affect_dof_rr_propstuck_t8p0 | 2.000000e-07 | 185000.0 | 3700.0 |
affect_dof_rf_propstuck_t8p0 | 2.000000e-07 | 185000.0 | 3700.0 |
affect_dof_lr_propstuck_t8p0 | 2.000000e-07 | 185000.0 | 3700.0 |
affect_dof_lf_propstuck_t8p0 | 2.000000e-07 | 185000.0 | 3700.0 |
affect_dof_rf_propwarp_t8p0 | 1.000000e-07 | 185000.0 | 1850.0 |
affect_dof_lf_propwarp_t8p0 | 1.000000e-07 | 185000.0 | 1850.0 |
affect_dof_lr_propwarp_t8p0 | 1.000000e-07 | 185000.0 | 1850.0 |
affect_dof_rr_propwarp_t8p0 | 1.000000e-07 | 185000.0 | 1850.0 |
affect_dof_ctlbreak_t8p0 | 2.000000e-06 | 1000.0 | 200.0 |
affect_dof_ctldn_t8p0 | 2.000000e-06 | 500.0 | 100.0 |
affect_dof_ctlup_t8p0 | 2.000000e-06 | 500.0 | 100.0 |
affect_dof_mechbreak_t8p0 | 1.000000e-06 | 500.0 | 50.0 |
affect_dof_mechfriction_t8p0 | 5.000000e-07 | 500.0 | 25.0 |
affect_dof_short_t8p0 | 1.000000e-06 | 200.0 | 20.0 |
affect_dof_openc_t8p0 | 1.000000e-06 | 200.0 | 20.0 |
affect_dof_propbreak_t8p0 | 3.000000e-07 | 200.0 | 6.0 |
affect_dof_propstuck_t8p0 | 2.000000e-07 | 200.0 | 4.0 |
affect_dof_propwarp_t8p0 | 1.000000e-07 | 200.0 | 2.0 |
nominal | 1.000000e+00 | 0.0 | 0.0 |
Based on this model, we can calculate some metrics that quantify how resilient the system was to the set of faults, such as the cost of resilience:
[102]:
quad_res = sum(quad_tab['expected_cost'])
quad_res
[102]:
749003.0000000001
The overall rate of crashes:
[103]:
quad_crashes = quad_tab[quad_tab['cost']>100000]
quad_rate = sum(quad_crashes['rate'])
quad_rate
[103]:
4.0400000000000006e-05
The number of crashes:
[104]:
quad_num_crashes = len(quad_crashes['rate'])
quad_num_crashes
[104]:
40
The percentage of crashes:
[105]:
quad_perc_crashes = len(quad_crashes['rate'])/len(quad_tab['rate'])
quad_perc_crashes
[105]:
0.7843137254901961
Octocopter Resilience
Here we quantify the expected costs of faults originiating in the octocopter architecture:
[106]:
mdl_oct = Drone_Hierarchical(p={'arch':'oct'})
mdl_oct.fxns['affect_dof'].m.faultmodes
[106]:
{'short': Fault(prob=1.0000000000000002e-06, cost=200, phases={}, units='sim'),
'openc': Fault(prob=1.0000000000000002e-06, cost=200, phases={}, units='sim'),
'ctlup': Fault(prob=2.0000000000000003e-06, cost=500, phases={}, units='sim'),
'ctldn': Fault(prob=2.0000000000000003e-06, cost=500, phases={}, units='sim'),
'ctlbreak': Fault(prob=2.0000000000000003e-06, cost=1000, phases={}, units='sim'),
'mechbreak': Fault(prob=1.0000000000000002e-06, cost=500, phases={}, units='sim'),
'mechfriction': Fault(prob=5.000000000000001e-07, cost=500, phases={}, units='sim'),
'propwarp': Fault(prob=1.0000000000000001e-07, cost=200, phases={}, units='sim'),
'propstuck': Fault(prob=2.0000000000000002e-07, cost=200, phases={}, units='sim'),
'propbreak': Fault(prob=3.0000000000000004e-07, cost=200, phases={}, units='sim'),
'lf_short': Fault(prob=1.0000000000000002e-06, cost=200, phases={}, units='sim'),
'lf_openc': Fault(prob=1.0000000000000002e-06, cost=200, phases={}, units='sim'),
'lf_ctlup': Fault(prob=2.0000000000000003e-06, cost=500, phases={}, units='sim'),
'lf_ctldn': Fault(prob=2.0000000000000003e-06, cost=500, phases={}, units='sim'),
'lf_ctlbreak': Fault(prob=2.0000000000000003e-06, cost=1000, phases={}, units='sim'),
'lf_mechbreak': Fault(prob=1.0000000000000002e-06, cost=500, phases={}, units='sim'),
'lf_mechfriction': Fault(prob=5.000000000000001e-07, cost=500, phases={}, units='sim'),
'lf_propwarp': Fault(prob=1.0000000000000001e-07, cost=200, phases={}, units='sim'),
'lf_propstuck': Fault(prob=2.0000000000000002e-07, cost=200, phases={}, units='sim'),
'lf_propbreak': Fault(prob=3.0000000000000004e-07, cost=200, phases={}, units='sim'),
'lr_short': Fault(prob=1.0000000000000002e-06, cost=200, phases={}, units='sim'),
'lr_openc': Fault(prob=1.0000000000000002e-06, cost=200, phases={}, units='sim'),
'lr_ctlup': Fault(prob=2.0000000000000003e-06, cost=500, phases={}, units='sim'),
'lr_ctldn': Fault(prob=2.0000000000000003e-06, cost=500, phases={}, units='sim'),
'lr_ctlbreak': Fault(prob=2.0000000000000003e-06, cost=1000, phases={}, units='sim'),
'lr_mechbreak': Fault(prob=1.0000000000000002e-06, cost=500, phases={}, units='sim'),
'lr_mechfriction': Fault(prob=5.000000000000001e-07, cost=500, phases={}, units='sim'),
'lr_propwarp': Fault(prob=1.0000000000000001e-07, cost=200, phases={}, units='sim'),
'lr_propstuck': Fault(prob=2.0000000000000002e-07, cost=200, phases={}, units='sim'),
'lr_propbreak': Fault(prob=3.0000000000000004e-07, cost=200, phases={}, units='sim'),
'rf_short': Fault(prob=1.0000000000000002e-06, cost=200, phases={}, units='sim'),
'rf_openc': Fault(prob=1.0000000000000002e-06, cost=200, phases={}, units='sim'),
'rf_ctlup': Fault(prob=2.0000000000000003e-06, cost=500, phases={}, units='sim'),
'rf_ctldn': Fault(prob=2.0000000000000003e-06, cost=500, phases={}, units='sim'),
'rf_ctlbreak': Fault(prob=2.0000000000000003e-06, cost=1000, phases={}, units='sim'),
'rf_mechbreak': Fault(prob=1.0000000000000002e-06, cost=500, phases={}, units='sim'),
'rf_mechfriction': Fault(prob=5.000000000000001e-07, cost=500, phases={}, units='sim'),
'rf_propwarp': Fault(prob=1.0000000000000001e-07, cost=200, phases={}, units='sim'),
'rf_propstuck': Fault(prob=2.0000000000000002e-07, cost=200, phases={}, units='sim'),
'rf_propbreak': Fault(prob=3.0000000000000004e-07, cost=200, phases={}, units='sim'),
'rr_short': Fault(prob=1.0000000000000002e-06, cost=200, phases={}, units='sim'),
'rr_openc': Fault(prob=1.0000000000000002e-06, cost=200, phases={}, units='sim'),
'rr_ctlup': Fault(prob=2.0000000000000003e-06, cost=500, phases={}, units='sim'),
'rr_ctldn': Fault(prob=2.0000000000000003e-06, cost=500, phases={}, units='sim'),
'rr_ctlbreak': Fault(prob=2.0000000000000003e-06, cost=1000, phases={}, units='sim'),
'rr_mechbreak': Fault(prob=1.0000000000000002e-06, cost=500, phases={}, units='sim'),
'rr_mechfriction': Fault(prob=5.000000000000001e-07, cost=500, phases={}, units='sim'),
'rr_propwarp': Fault(prob=1.0000000000000001e-07, cost=200, phases={}, units='sim'),
'rr_propstuck': Fault(prob=2.0000000000000002e-07, cost=200, phases={}, units='sim'),
'rr_propbreak': Fault(prob=3.0000000000000004e-07, cost=200, phases={}, units='sim'),
'lf2_short': Fault(prob=1.0000000000000002e-06, cost=200, phases={}, units='sim'),
'lf2_openc': Fault(prob=1.0000000000000002e-06, cost=200, phases={}, units='sim'),
'lf2_ctlup': Fault(prob=2.0000000000000003e-06, cost=500, phases={}, units='sim'),
'lf2_ctldn': Fault(prob=2.0000000000000003e-06, cost=500, phases={}, units='sim'),
'lf2_ctlbreak': Fault(prob=2.0000000000000003e-06, cost=1000, phases={}, units='sim'),
'lf2_mechbreak': Fault(prob=1.0000000000000002e-06, cost=500, phases={}, units='sim'),
'lf2_mechfriction': Fault(prob=5.000000000000001e-07, cost=500, phases={}, units='sim'),
'lf2_propwarp': Fault(prob=1.0000000000000001e-07, cost=200, phases={}, units='sim'),
'lf2_propstuck': Fault(prob=2.0000000000000002e-07, cost=200, phases={}, units='sim'),
'lf2_propbreak': Fault(prob=3.0000000000000004e-07, cost=200, phases={}, units='sim'),
'lr2_short': Fault(prob=1.0000000000000002e-06, cost=200, phases={}, units='sim'),
'lr2_openc': Fault(prob=1.0000000000000002e-06, cost=200, phases={}, units='sim'),
'lr2_ctlup': Fault(prob=2.0000000000000003e-06, cost=500, phases={}, units='sim'),
'lr2_ctldn': Fault(prob=2.0000000000000003e-06, cost=500, phases={}, units='sim'),
'lr2_ctlbreak': Fault(prob=2.0000000000000003e-06, cost=1000, phases={}, units='sim'),
'lr2_mechbreak': Fault(prob=1.0000000000000002e-06, cost=500, phases={}, units='sim'),
'lr2_mechfriction': Fault(prob=5.000000000000001e-07, cost=500, phases={}, units='sim'),
'lr2_propwarp': Fault(prob=1.0000000000000001e-07, cost=200, phases={}, units='sim'),
'lr2_propstuck': Fault(prob=2.0000000000000002e-07, cost=200, phases={}, units='sim'),
'lr2_propbreak': Fault(prob=3.0000000000000004e-07, cost=200, phases={}, units='sim'),
'rf2_short': Fault(prob=1.0000000000000002e-06, cost=200, phases={}, units='sim'),
'rf2_openc': Fault(prob=1.0000000000000002e-06, cost=200, phases={}, units='sim'),
'rf2_ctlup': Fault(prob=2.0000000000000003e-06, cost=500, phases={}, units='sim'),
'rf2_ctldn': Fault(prob=2.0000000000000003e-06, cost=500, phases={}, units='sim'),
'rf2_ctlbreak': Fault(prob=2.0000000000000003e-06, cost=1000, phases={}, units='sim'),
'rf2_mechbreak': Fault(prob=1.0000000000000002e-06, cost=500, phases={}, units='sim'),
'rf2_mechfriction': Fault(prob=5.000000000000001e-07, cost=500, phases={}, units='sim'),
'rf2_propwarp': Fault(prob=1.0000000000000001e-07, cost=200, phases={}, units='sim'),
'rf2_propstuck': Fault(prob=2.0000000000000002e-07, cost=200, phases={}, units='sim'),
'rf2_propbreak': Fault(prob=3.0000000000000004e-07, cost=200, phases={}, units='sim'),
'rr2_short': Fault(prob=1.0000000000000002e-06, cost=200, phases={}, units='sim'),
'rr2_openc': Fault(prob=1.0000000000000002e-06, cost=200, phases={}, units='sim'),
'rr2_ctlup': Fault(prob=2.0000000000000003e-06, cost=500, phases={}, units='sim'),
'rr2_ctldn': Fault(prob=2.0000000000000003e-06, cost=500, phases={}, units='sim'),
'rr2_ctlbreak': Fault(prob=2.0000000000000003e-06, cost=1000, phases={}, units='sim'),
'rr2_mechbreak': Fault(prob=1.0000000000000002e-06, cost=500, phases={}, units='sim'),
'rr2_mechfriction': Fault(prob=5.000000000000001e-07, cost=500, phases={}, units='sim'),
'rr2_propwarp': Fault(prob=1.0000000000000001e-07, cost=200, phases={}, units='sim'),
'rr2_propstuck': Fault(prob=2.0000000000000002e-07, cost=200, phases={}, units='sim'),
'rr2_propbreak': Fault(prob=3.0000000000000004e-07, cost=200, phases={}, units='sim')}
[107]:
fd_oct = FaultDomain(mdl_oct)
fd_oct.add_all_fxn_modes("affect_dof")
fd_oct
[107]:
FaultDomain with faults:
-('affect_dof', 'short')
-('affect_dof', 'openc')
-('affect_dof', 'ctlup')
-('affect_dof', 'ctldn')
-('affect_dof', 'ctlbreak')
-('affect_dof', 'mechbreak')
-('affect_dof', 'mechfriction')
-('affect_dof', 'propwarp')
-('affect_dof', 'propstuck')
-('affect_dof', 'propbreak')
-...more
[108]:
fs_oct = FaultSample(fd_oct, phasemap = PhaseMap(mdl_oct.sp.phases))
fs_oct.add_fault_phases("forward")
fs_oct
[108]:
FaultSample of scenarios:
- affect_dof_short_t8p0
- affect_dof_openc_t8p0
- affect_dof_ctlup_t8p0
- affect_dof_ctldn_t8p0
- affect_dof_ctlbreak_t8p0
- affect_dof_mechbreak_t8p0
- affect_dof_mechfriction_t8p0
- affect_dof_propwarp_t8p0
- affect_dof_propstuck_t8p0
- affect_dof_propbreak_t8p0
- ... (90 total)
[109]:
oct_endclasses, oct_mdlhists = propagate.fault_sample(mdl_oct, fs_oct, staged=True)
SCENARIOS COMPLETE: 100%|██████████| 90/90 [00:05<00:00, 17.15it/s]
[110]:
oct_tab = oct_endclasses.create_simple_fmea()
oct_tab.sort_values('expected_cost', ascending=False)
[110]:
rate | cost | expected_cost | |
---|---|---|---|
affect_dof_ctlbreak_t8p0 | 2.000000e-06 | 1000.0 | 200.0 |
affect_dof_lf2_ctlbreak_t8p0 | 2.000000e-06 | 1000.0 | 200.0 |
affect_dof_rf_ctlbreak_t8p0 | 2.000000e-06 | 1000.0 | 200.0 |
affect_dof_rr_ctlbreak_t8p0 | 2.000000e-06 | 1000.0 | 200.0 |
affect_dof_rr2_ctlbreak_t8p0 | 2.000000e-06 | 1000.0 | 200.0 |
... | ... | ... | ... |
affect_dof_lr_propwarp_t8p0 | 1.000000e-07 | 200.0 | 2.0 |
affect_dof_propwarp_t8p0 | 1.000000e-07 | 200.0 | 2.0 |
affect_dof_rr2_propwarp_t8p0 | 1.000000e-07 | 200.0 | 2.0 |
affect_dof_rf_propwarp_t8p0 | 1.000000e-07 | 200.0 | 2.0 |
nominal | 1.000000e+00 | 0.0 | 0.0 |
91 rows × 3 columns
Based on this model, we can calculate some metrics that quantify how resilient the system was to the set of faults, such as the cost of resilience:
[111]:
oct_res = sum(oct_tab['expected_cost'])
oct_res
[111]:
4743.000000000001
The overall rate of crashes:
[112]:
oct_crashes = oct_tab[oct_tab['cost']>100000]
oct_rate = sum(oct_crashes['rate'])
oct_rate
[112]:
0
Number of crashes:
[113]:
oct_num_crashes = len(oct_crashes['rate'])
oct_num_crashes
[113]:
0
Percent of crashes:
[114]:
oct_perc_crashes = len(oct_crashes['rate'])/len(oct_tab['rate'])
oct_perc_crashes
[114]:
0.0