fmdtools Tutorial
This tutorial notebook will show some of the basic commands needed to perform resilience analysis in fmdtools.
For some context, it may be helpful to look through the accompanying presentation. This notebook uses the model defined in ex_pump.py
. In this notebook, we will:
Load an environment and model
Simulate the system in nominal and faulty scenarios
Visualize and quantify the results
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.
1.) Loading the environment and model
To load the fmdtools
environment, we have to install and import it. This can be accomplished using pip
for both the stable and development versions:
[1]:
##Stable (PyPl) version
# pip install fmdtools
##Development version (downloaded from git)
# pip install -e /path/to/fmdtools ##Note that /path/to/fmdtools is the path to the fmdtools repo
There are a number of different syntaxes for importing modules. Because of the long names of the module trees, it is often helpful to load the modules individually and abbreviate (e.g. import fmdtools.sim.propagate as propagate
). Below, import the propagate fmdtools.sim.propagate
and fmdtools.analyze
modules, as well as sampling using the fmdtools.sim.sample
module.
[2]:
import fmdtools.sim.propagate as propagate
from fmdtools.sim.sample import FaultDomain, FaultSample
from fmdtools.analyze.phases import PhaseMap
import fmdtools.analyze as an
Now, import the Pump class defined in the ex_pump module.
[3]:
from ex_pump import Pump
We can then use that to instantiate a model object. See:
[4]:
mdl = Pump()
To get started, it can be helpful to view some of the aspects of the model. Try dir(mdl)
, mdl.fxns
, mdl.flows
, mdl.graph
, etc.
[5]:
mdl.fxns
[5]:
{'import_ee': import_ee ImportEE
- ImportEEState(effstate=1.0)
- ImportEEMode(mode=nominal, faults=set()),
'import_water': import_water ImportWater
- ImportWaterMode(mode=nominal, faults=set()),
'import_signal': import_signal ImportSig
- ImportSigMode(mode=nominal, faults=set()),
'move_water': move_water MoveWat
- MoveWatStates(eff=1.0)
- MoveWatMode(mode=nominal, faults=set()),
'export_water': export_water ExportWater
- ExportWaterMode(mode=nominal, faults=set())}
We can also view the run order to see how the model will be simulated. More details on run order are provided in Model Structure Visualization Tutorial, but in short static propagation functions simulate multiple times per time-step until the model converges, while dynamic propagation functions run once per timestep in a defined order.
[6]:
mg = mdl.as_modelgraph()
[7]:
mg.set_exec_order(mdl)
[8]:
mg.draw()
[8]:
(<Figure size 1200x1000 with 1 Axes>, <Axes: >)
As shown, because all of the methods were defined as generic behaviors, they are each run in the static propagation step. No order is shown in the static step because the static propagation step iterates between model functions until the values have converged. Nevertheless, one can view the initial static order using:
[9]:
mdl.staticfxns
[9]:
OrderedSet(['import_ee', 'import_water', 'import_signal', 'move_water', 'export_water'])
and the dynamic step order (if there was one):
[10]:
mdl.dynamicfxns
[10]:
OrderedSet()
We can also instantiate this model with different parameters. In Pump model, these are defined in PumpParam
[11]:
from ex_pump import PumpParam
p = PumpParam()
p
[11]:
PumpParam(cost=('repair', 'water'), delay=10)
We can pass non-default parameters to the model (as well as other non-defaults, such as simulation parameters SimParam
and random seeds) by passing them as dictionaries. In this case:
[12]:
mdl2 = Pump(p={'cost':('repair', 'water', 'ee'), 'delay':20})
mdl2
[12]:
pump Pump
FUNCTIONS:
import_ee ImportEE
- ImportEEState(effstate=1.0)
- ImportEEMode(mode=nominal, faults=set())
import_water ImportWater
- ImportWaterMode(mode=nominal, faults=set())
import_signal ImportSig
- ImportSigMode(mode=nominal, faults=set())
move_water MoveWat
- MoveWatStates(eff=1.0)
- MoveWatMode(mode=nominal, faults=set())
export_water ExportWater
- ExportWaterMode(mode=nominal, faults=set())
FLOWS:
ee_1 Electricity flow: EEStates(current=1.0, voltage=1.0)
sig_1 Signal flow: SignalStates(power=1.0)
wat_1 Water flow: WaterStates(flowrate=1.0, pressure=1.0, area=1.0, level=1.0)
wat_2 Water flow: WaterStates(flowrate=1.0, pressure=1.0, area=1.0, level=1.0)
Note that, for a model: - p
is the Parameter
defining non-mutable attributes (which do not change over a simulation) - sp
is the SimParam
defining the simulation start, end, and end conditions - r
is the Rand
defining random variables (e.g., for setting a seed)
2.), 3.), and 4.) Simulate and visualize the results!
Now, we will use the methods in propagate
and the visualization modules in rd
to simulate the model and visualize the results.
2a.) Simulate nominal
To simulate the model in the nominal scenario, use the propagate.nominal
method, which returns: endresults, which is a result (or a dict of multiple results) specified by the desired_result
option, e.g.: - ‘endclass’ (default), which provides the dict from mdl.find_classification
- ‘graph’ or fmdtools.define.graph.Graph
subclass for the model. - ‘fxnname.varname’/’flowname.varname’: which gets the values of these variables at a the end - a list of the above arguments (to return a
dict of multiple) - a dict of lists (for multiple over time), e.g. {time:[varnames,... 'endclass']}
and mdlhist, which is a history of model states specified by the track
option. Some of these options include: - fxns’ - ‘flows’ - ‘all’ - ‘none’ - ‘valparams’ (model states specified in mdl.valparams), - or a dict of form: {'functions':{'fxn1':'att1'}, 'flows':{'flow1':'att1'}}
[13]:
result_nominal, mdlhist_nominal=propagate.nominal(Pump(track="all"), desired_result="endclass")
What do the results look like? Explore results structures using the desired_results
and track
arguments.
[14]:
result_nominal
[14]:
endclass:
--rate: 1.0
--cost: 0.0
--expected_cost: 0.0
Note that an endclass
dictionary can take arbitrary form based on what is produced in find_classification
. By default, we often return: - rate
, which is the rate for the scenario calculated from the underlying fault probability model (defined in the Mode
for each Function
), - cost
, which is some cost calculated based on the consequences of the scenario - expected_cost
, which is the expected value of the cost of the scenario (i.e. rate*cost
).
[15]:
mdlhist_nominal
[15]:
i.finished: array(56)
i.on: array(56)
flows.ee_1.s.current: array(56)
flows.ee_1.s.voltage: array(56)
flows.sig_1.s.power: array(56)
flows.wat_1.s.flowrate: array(56)
flows.wat_1.s.pressure: array(56)
flows.wat_1.s.area: array(56)
flows.wat_1.s.level: array(56)
flows.wat_2.s.flowrate: array(56)
flows.wat_2.s.pressure: array(56)
flows.wat_2.s.area: array(56)
flows.wat_2.s.level: array(56)
fxns.import_ee.s.effstate: array(56)
fxns.import_ee.m.faults.no_v: array(56)
fxns.import_ee.m.faults.inf_v: array(56)
fxns.import_water.m.faults.no_wat: array(56)
fxns.import_water.m.faults.less_wat: array(56)
fxns.import_signal.m.faults.no_sig: array(56)
fxns.move_water.i.over_pressure: array(56)
fxns.move_water.s.eff: array(56)
fxns.move_water.m.faults.mech_break: array(56)
fxns.move_water.m.faults.short: array(56)
fxns.move_water.t.pressure_limit.time: array(56)
fxns.move_water.t.pressure_limit.mode: array(56)
fxns.export_water.m.faults.block: array(56)
time: array(56)
2b.) Visualize nominal model
First, we can show the model graph using Graph.draw
to see that it was set up correctly. We can do this both on the model graph itself (using ModelGraph
) and the results of the nominal run (by passing graph
as an argument in desired_result
) to verify both are fault-free.
Note that the underlying Graph
classes are very flexible and enables a wide range of display options: - Graph docs - Graph.draw
[16]:
mg = mdl.as_modelgraph()
mg.draw()
[16]:
(<Figure size 1200x1000 with 1 Axes>, <Axes: >)
We can also view the flow values of the model using History.plot_line
It may be helpful to only view flows of interest.
[17]:
fig = mdlhist_nominal.plot_line('flows.wat_1.s.flowrate', 'flows.wat_1.s.flowrate',
'flows.ee_1.s.voltage', 'flows.ee_1.s.current')
Note: for quick access to the syntax and options for these methods, type the ?method
or help(method)
in the terminal. For example ?History.plot_line
2b.) Simulate a fault mode
To simulate the model in a fault scenario, use the propagate.one_fault
method. The set of possible faults is defined in the function definitions in ex_pump.py
, and we can propagate a fault at any time in the operational interval (0-55 seconds).
For example, below we simulate a short in the MoveWater
function at time t=10:
[18]:
results_fault, mdlhist_fault=propagate.one_fault(Pump(track="all"), 'move_water', 'short', time=10,
desired_result=['endclass', 'graph'])
We can also view the results for from this. In this case mdlhist
gives a history of results for both the nominal and faulty runs.
[19]:
results_fault
[19]:
endclass.rate: 1e-05
endclass.cost: 29000.000000000007
endclass.expected_cost: 29000.000000000007
graph: <fmdtools.define.architecture.function.FunctionArchitectureGraph object at 0x000001C6BBA66C10>
[20]:
mdlhist_fault
[20]:
nominal.i.finished: array(56)
nominal.i.on: array(56)
nominal.flows.ee_1.s.current: array(56)
nominal.flows.ee_1.s.voltage: array(56)
nominal.flows.sig_1.s.power: array(56)
nominal.flows.wat_1.s.flowrate: array(56)
nominal.flows.wat_1.s.pressure: array(56)
nominal.flows.wat_1.s.area: array(56)
nominal.flows.wat_1.s.level: array(56)
nominal.flows.wat_2.s.flowrate: array(56)
nominal.flows.wat_2.s.pressure: array(56)
nominal.flows.wat_2.s.area: array(56)
nominal.flows.wat_2.s.level: array(56)
nominal.fxns.import_ee.s.effstate: array(56)
nominal.fxns.import_ee.m.faults.no_v: array(56)
nominal.fxns.import_ee.m.faults.inf_v: array(56)
nominal.fxns.import_ array(56)
nominal.fxns.import_ array(56)
nominal.fxns.import_ array(56)
nominal.fxns.move_wa array(56)
nominal.fxns.move_water.s.eff: array(56)
nominal.fxns.move_wa array(56)
nominal.fxns.move_water.m.faults.short: array(56)
nominal.fxns.move_wa array(56)
nominal.fxns.move_wa array(56)
nominal.fxns.export_ array(56)
nominal.time: array(56)
faulty.i.finished: array(56)
faulty.i.on: array(56)
faulty.flows.ee_1.s.current: array(56)
faulty.flows.ee_1.s.voltage: array(56)
faulty.flows.sig_1.s.power: array(56)
faulty.flows.wat_1.s.flowrate: array(56)
faulty.flows.wat_1.s.pressure: array(56)
faulty.flows.wat_1.s.area: array(56)
faulty.flows.wat_1.s.level: array(56)
faulty.flows.wat_2.s.flowrate: array(56)
faulty.flows.wat_2.s.pressure: array(56)
faulty.flows.wat_2.s.area: array(56)
faulty.flows.wat_2.s.level: array(56)
faulty.fxns.import_ee.s.effstate: array(56)
faulty.fxns.import_ee.m.faults.no_v: array(56)
faulty.fxns.import_ee.m.faults.inf_v: array(56)
faulty.fxns.import_w array(56)
faulty.fxns.import_w array(56)
faulty.fxns.import_s array(56)
faulty.fxns.move_water.i.over_pressure: array(56)
faulty.fxns.move_water.s.eff: array(56)
faulty.fxns.move_wat array(56)
faulty.fxns.move_water.m.faults.short: array(56)
faulty.fxns.move_wat array(56)
faulty.fxns.move_wat array(56)
faulty.fxns.export_w array(56)
faulty.time: array(56)
3b.) Visualize fault model states
History.plot_line
also works for a mdlhists given from propagate.one_fault
. We can view these results below. As shown, the function will give the nominal result in a blue dotted line and the faulty result in a red line.
[21]:
fig, axs = mdlhist_fault.plot_line('flows.wat_1.s.flowrate', 'flows.wat_1.s.flowrate',
'flows.ee_1.s.voltage', 'flows.ee_1.s.current',
title='Pump Response to Motor Short Fault',
time_slice=[10], legend_loc=False)
We can also view this result graph using Graph.draw
. In this case, it shows the state of the model at the final time-step of the model run. Thus, while the EE_1
flow is shown in orange (because it is off-nominal), the Water flows are not, because they have the same state at the final time-step.
[22]:
results_fault.graph.draw()
[22]:
(<Figure size 1200x1000 with 1 Axes>, <Axes: >)
If we want to view the graph at another time-step, we can use Graph.draw_from
, which will call History.get_degraded_hist
to first process the model states into results which can be visualized and then map that onto a graph view at a given time.
[23]:
fig, ax = mg.draw_from(20,mdlhist_fault)
Note that multiple graph representations are available for the graph if desired…
[24]:
from fmdtools.define.architecture.function import FunctionArchitectureFxnGraph
mg1 = FunctionArchitectureFxnGraph(mdl)
fig, ax = mg1.draw_from(20, mdlhist_fault, rem_ind=0)
4a.) Simulate set of fault modes
To simulate the set of fault modes, we first choose a FaultDomain
and FaultSample
. For simplicity, we can choose default parameters at first.
[25]:
fd = FaultDomain(mdl)
fd.add_all()
fd
[25]:
FaultDomain with faults:
-('import_ee', 'no_v')
-('import_ee', 'inf_v')
-('import_water', 'no_wat')
-('import_water', 'less_wat')
-('import_signal', 'no_sig')
-('move_water', 'mech_break')
-('move_water', 'short')
-('export_water', 'block')
[26]:
fs = FaultSample(fd, phasemap = PhaseMap(mdl.sp.phases))
fs.add_fault_phases()
fs
[26]:
FaultSample of scenarios:
- import_ee_no_v_t2p0
- import_ee_inf_v_t2p0
- import_water_no_wat_t2p0
- import_water_less_wat_t2p0
- import_signal_no_sig_t2p0
- move_water_mech_break_t2p0
- move_water_short_t2p0
- export_water_block_t2p0
- import_ee_no_v_t27p0
- import_ee_inf_v_t27p0
- ... (24 total)
[27]:
endclasses_samp, mdlhists_samp = propagate.fault_sample(mdl, fs)
SCENARIOS COMPLETE: 100%|██████████| 24/24 [00:01<00:00, 17.29it/s]
It can be helpful to view what these results look like–a Result
of faults injected at particular times with their correspnding Result
dictionaries.
[28]:
endclasses_samp
[28]:
import_ee_no_v_t2p0.endclass.rate: 0.0
import_ee_no_v_t2p0.endclass.cost: 20125.000000000007
import_ee_no_v_t2p0. 0.0
import_ee_inf_v_t2p0.endclass.rate: 0.0
import_ee_inf_v_t2p0.endclass.cost: 25125.000000000007
import_ee_inf_v_t2p0 0.0
import_water_no_wat_t2p0.endclass.rate: 1e-05
import_water_no_wat_t2p0.endclass.cost: 11125.000000000007
import_water_no_wat_ 11125.000000000007
import_water_less_wa 1e-05
import_water_less_wa 5062.500000000004
import_water_less_wa 5062.500000000004
import_signal_no_sig 1.5e-06
import_signal_no_sig 20125.000000000007
import_signal_no_sig 3018.750000000001
move_water_mech_brea6.000000000000001e-07
move_water_mech_brea 15125.000000000007
move_water_mech_brea 907.5000000000005
move_water_short_t2p0.endclass.rate: 1.5000000000000002e-05
move_water_short_t2p0.endclass.cost: 30125.000000000007
move_water_short_t2p 45187.500000000015
export_water_block_t2p0.endclass.rate: 1.5000000000000002e-05
export_water_block_t2p0.endclass.cost: 20102.500000000007
export_water_block_t 30153.750000000015
import_ee_no_v_t27p0.endclass.rate: 8.000000000000001e-06
import_ee_no_v_t27p0.endclass.cost: 15174.999999999998
import_ee_no_v_t27p0 12140.0
import_ee_inf_v_t27p0.endclass.rate: 2.0000000000000003e-06
import_ee_inf_v_t27p0.endclass.cost: 20175.0
import_ee_inf_v_t27p 4035.000000000001
import_water_no_wat_ 1e-05
import_water_no_wat_ 6174.999999999998
import_water_no_wat_ 6174.999999999998
import_water_less_wa 1e-05
import_water_less_wa 2587.499999999999
import_water_less_wa 2587.499999999999
import_signal_no_sig 1e-06
import_signal_no_sig 15174.999999999998
import_signal_no_sig 1517.4999999999998
move_water_mech_brea 7.2e-06
move_water_mech_brea 10174.999999999998
move_water_mech_brea 7325.999999999998
move_water_short_t27p0.endclass.rate: 1e-05
move_water_short_t27p0.endclass.cost: 25175.0
move_water_short_t27 25175.0
export_water_block_t27p0.endclass.rate: 1e-05
export_water_block_t27p0.endclass.cost: 15152.5
export_water_block_t 15152.5
import_ee_no_v_t52p0.endclass.rate: 0.0
import_ee_no_v_t52p0.endclass.cost: 10000.0
import_ee_no_v_t52p0 0.0
import_ee_inf_v_t52p0.endclass.rate: 0.0
import_ee_inf_v_t52p0.endclass.cost: 5000.0
import_ee_inf_v_t52p 0.0
import_water_no_wat_ 1e-05
import_water_no_wat_ 1000.0
import_water_no_wat_ 1000.0
import_water_less_wa 1e-05
import_water_less_wa 0.0
import_water_less_wa 0.0
import_signal_no_sig 1e-06
import_signal_no_sig 10000.0
import_signal_no_sig 999.9999999999999
move_water_mech_brea6.000000000000001e-07
move_water_mech_brea 5000.0
move_water_mech_brea 300.0
move_water_short_t52p0.endclass.rate: 1e-05
move_water_short_t52p0.endclass.cost: 10000.0
move_water_short_t52 10000.0
export_water_block_t52p0.endclass.rate: 1e-05
export_water_block_t52p0.endclass.cost: 5000.0
export_water_block_t 5000.0
nominal.endclass.rate: 1.0
nominal.endclass.cost: 0.0
nominal.endclass.expected_cost: 0.0
We can look at/use a more structured version using:
[29]:
ec_nest = endclasses_samp.nest()
ec_nest
[29]:
import_ee_no_v_t2p0:
--endclass:
----rate: 0.0
----cost: 20125.000000000007
----expected_cost: 0.0
import_ee_inf_v_t2p0:
--endclass:
----rate: 0.0
----cost: 25125.000000000007
----expected_cost: 0.0
import_water_no_wat_t2p0:
--endclass:
----rate: 1e-05
----cost: 11125.000000000007
----expected_cost: 11125.000000000007
import_water_less_wat_t2p0:
--endclass:
----rate: 1e-05
----cost: 5062.500000000004
----expected_cost: 5062.500000000004
import_signal_no_sig_t2p0:
--endclass:
----rate: 1.5e-06
----cost: 20125.000000000007
----expected_cost: 3018.750000000001
move_water_mech_break_t2p0:
--endclass:
----rate: 6.000000000000001e-07
----cost: 15125.000000000007
----expected_cost: 907.5000000000005
move_water_short_t2p0:
--endclass:
----rate: 1.5000000000000002e-05
----cost: 30125.000000000007
----expected_cost: 45187.500000000015
export_water_block_t2p0:
--endclass:
----rate: 1.5000000000000002e-05
----cost: 20102.500000000007
----expected_cost: 30153.750000000015
import_ee_no_v_t27p0:
--endclass:
----rate: 8.000000000000001e-06
----cost: 15174.999999999998
----expected_cost: 12140.0
import_ee_inf_v_t27p0:
--endclass:
----rate: 2.0000000000000003e-06
----cost: 20175.0
----expected_cost: 4035.000000000001
import_water_no_wat_t27p0:
--endclass:
----rate: 1e-05
----cost: 6174.999999999998
----expected_cost: 6174.999999999998
import_water_less_wat_t27p0:
--endclass:
----rate: 1e-05
----cost: 2587.499999999999
----expected_cost: 2587.499999999999
import_signal_no_sig_t27p0:
--endclass:
----rate: 1e-06
----cost: 15174.999999999998
----expected_cost: 1517.4999999999998
move_water_mech_break_t27p0:
--endclass:
----rate: 7.2e-06
----cost: 10174.999999999998
----expected_cost: 7325.999999999998
move_water_short_t27p0:
--endclass:
----rate: 1e-05
----cost: 25175.0
----expected_cost: 25175.0
export_water_block_t27p0:
--endclass:
----rate: 1e-05
----cost: 15152.5
----expected_cost: 15152.5
import_ee_no_v_t52p0:
--endclass:
----rate: 0.0
----cost: 10000.0
----expected_cost: 0.0
import_ee_inf_v_t52p0:
--endclass:
----rate: 0.0
----cost: 5000.0
----expected_cost: 0.0
import_water_no_wat_t52p0:
--endclass:
----rate: 1e-05
----cost: 1000.0
----expected_cost: 1000.0
import_water_less_wat_t52p0:
--endclass:
----rate: 1e-05
----cost: 0.0
----expected_cost: 0.0
import_signal_no_sig_t52p0:
--endclass:
----rate: 1e-06
----cost: 10000.0
----expected_cost: 999.9999999999999
move_water_mech_break_t52p0:
--endclass:
----rate: 6.000000000000001e-07
----cost: 5000.0
----expected_cost: 300.0
move_water_short_t52p0:
--endclass:
----rate: 1e-05
----cost: 10000.0
----expected_cost: 10000.0
export_water_block_t52p0:
--endclass:
----rate: 1e-05
----cost: 5000.0
----expected_cost: 5000.0
nominal:
--endclass:
----rate: 1.0
----cost: 0.0
----expected_cost: 0.0
We often want to adjust the FaultDomain
and SampleApproach
to answer specific questions about the model (e.g. focussing on single faults, different numbers of sample points, etc). Both have different methods with options which can be changed to enable this, e.g.:
[30]:
fd2 = FaultDomain(mdl)
fd2.add_fault('move_water', 'short')
fd2
[30]:
FaultDomain with faults:
-('move_water', 'short')
[31]:
fs2 = FaultSample(fd2, phasemap = PhaseMap(mdl.sp.phases))
fs2.add_fault_phases(args=(4,))
fs2
[31]:
FaultSample of scenarios:
- move_water_short_t0p0
- move_water_short_t1p0
- move_water_short_t2p0
- move_water_short_t3p0
- move_water_short_t4p0
- move_water_short_t14p0
- move_water_short_t23p0
- move_water_short_t31p0
- move_water_short_t40p0
- move_water_short_t51p0
- ... (13 total)
There are a number of different ways to sample the scenarios in the approach:
[32]:
fs2.times
[32]:
<bound method FaultSample.times of FaultSample of scenarios:
- move_water_short_t0p0
- move_water_short_t1p0
- move_water_short_t2p0
- move_water_short_t3p0
- move_water_short_t4p0
- move_water_short_t14p0
- move_water_short_t23p0
- move_water_short_t31p0
- move_water_short_t40p0
- move_water_short_t51p0
- ... (13 total)>
4b.) Visualize set of fault modes
Using this fault approach, we can now make an FMEA-like analyses of the different fault modes. The an.tabulate.FMEA
class organizes endclasses such that you can create a table (or plot) for each fault.
[33]:
summary_fmea = an.tabulate.FMEA(endclasses_samp, fs)
summary_fmea.as_table()
[33]:
rate | cost | expected_cost | ||
---|---|---|---|---|
move_water | short | 0.000035 | 24277.083333 | 0.803625 |
export_water | block | 0.000035 | 15093.541667 | 0.503063 |
import_water | no_wat | 0.000030 | 6100.000000 | 0.183000 |
import_ee | no_v | 0.000008 | 9573.333333 | 0.121400 |
move_water | mech_break | 0.000008 | 6472.250000 | 0.085335 |
import_water | less_wat | 0.000030 | 2550.000000 | 0.076500 |
import_signal | no_sig | 0.000003 | 8472.708333 | 0.055363 |
import_ee | inf_v | 0.000002 | 9055.833333 | 0.040350 |
We can also use an.tabulate.result_summary_fmea
with the processed results histories to get a better picture of which flows and functions degrade in each scenario.
[34]:
an.tabulate.result_summary_fmea(endclasses_samp, mdlhists_samp, *mdl.fxns, *mdl.flows)
[34]:
degraded | faulty | rate | cost | expected_cost | |
---|---|---|---|---|---|
import_ee_no_v_t2p0 | ['ee_1', 'wat_2'] | [] | 0.0 | 20125.0 | 0.0 |
import_ee_inf_v_t2p0 | ['ee_1', 'wat_2'] | [] | 0.0 | 25125.0 | 0.0 |
import_water_no_wat_t2p0 | ['ee_1', 'wat_2'] | [] | 0.00001 | 11125.0 | 11125.0 |
import_water_less_wat_t2p0 | ['ee_1', 'wat_2'] | [] | 0.00001 | 5062.5 | 5062.5 |
import_signal_no_sig_t2p0 | ['ee_1', 'wat_2'] | [] | 0.000002 | 20125.0 | 3018.75 |
move_water_mech_break_t2p0 | ['ee_1', 'wat_2'] | [] | 0.000001 | 15125.0 | 907.5 |
move_water_short_t2p0 | ['ee_1', 'wat_2'] | [] | 0.000015 | 30125.0 | 45187.5 |
export_water_block_t2p0 | ['ee_1', 'wat_2'] | [] | 0.000015 | 20102.5 | 30153.75 |
import_ee_no_v_t27p0 | ['ee_1', 'wat_2'] | [] | 0.000008 | 15175.0 | 12140.0 |
import_ee_inf_v_t27p0 | ['ee_1', 'wat_2'] | [] | 0.000002 | 20175.0 | 4035.0 |
import_water_no_wat_t27p0 | ['ee_1', 'wat_2'] | [] | 0.00001 | 6175.0 | 6175.0 |
import_water_less_wat_t27p0 | ['ee_1', 'wat_2'] | [] | 0.00001 | 2587.5 | 2587.5 |
import_signal_no_sig_t27p0 | ['ee_1', 'wat_2'] | [] | 0.000001 | 15175.0 | 1517.5 |
move_water_mech_break_t27p0 | ['ee_1', 'wat_2'] | [] | 0.000007 | 10175.0 | 7326.0 |
move_water_short_t27p0 | ['ee_1', 'wat_2'] | [] | 0.00001 | 25175.0 | 25175.0 |
export_water_block_t27p0 | ['ee_1', 'wat_2'] | [] | 0.00001 | 15152.5 | 15152.5 |
import_ee_no_v_t52p0 | ['ee_1', 'wat_2'] | [] | 0.0 | 10000.0 | 0.0 |
import_ee_inf_v_t52p0 | ['ee_1', 'wat_2'] | [] | 0.0 | 5000.0 | 0.0 |
import_water_no_wat_t52p0 | ['ee_1', 'wat_2'] | [] | 0.00001 | 1000.0 | 1000.0 |
import_water_less_wat_t52p0 | ['ee_1', 'wat_2'] | [] | 0.00001 | 0.0 | 0.0 |
import_signal_no_sig_t52p0 | ['ee_1', 'wat_2'] | [] | 0.000001 | 10000.0 | 1000.0 |
move_water_mech_break_t52p0 | ['ee_1', 'wat_2'] | [] | 0.000001 | 5000.0 | 300.0 |
move_water_short_t52p0 | ['ee_1', 'wat_2'] | [] | 0.00001 | 10000.0 | 10000.0 |
export_water_block_t52p0 | ['ee_1', 'wat_2'] | [] | 0.00001 | 5000.0 | 5000.0 |
nominal | ['ee_1', 'wat_2'] | [] | 1.0 | 0.0 | 0.0 |
Note that this is contingent on what we tracked in the history! In the above model, we used the default, which is only a few variables defined in the model.
If we tracked all, on the other hand, we would get the result below:
[35]:
endclasses_samp, mdlhists_samp = propagate.fault_sample(mdl, fs, track="all")
an.tabulate.result_summary_fmea(endclasses_samp, mdlhists_samp, *mdl.fxns, *mdl.flows)
SCENARIOS COMPLETE: 100%|██████████| 24/24 [00:01<00:00, 16.81it/s]
[35]:
degraded | faulty | rate | cost | expected_cost | |
---|---|---|---|---|---|
import_ee_no_v_t2p0 | ['ee_1', 'wat_2'] | [] | 0.0 | 20125.0 | 0.0 |
import_ee_inf_v_t2p0 | ['ee_1', 'wat_2'] | [] | 0.0 | 25125.0 | 0.0 |
import_water_no_wat_t2p0 | ['ee_1', 'wat_2'] | [] | 0.00001 | 11125.0 | 11125.0 |
import_water_less_wat_t2p0 | ['ee_1', 'wat_2'] | [] | 0.00001 | 5062.5 | 5062.5 |
import_signal_no_sig_t2p0 | ['ee_1', 'wat_2'] | [] | 0.000002 | 20125.0 | 3018.75 |
move_water_mech_break_t2p0 | ['ee_1', 'wat_2'] | [] | 0.000001 | 15125.0 | 907.5 |
move_water_short_t2p0 | ['ee_1', 'wat_2'] | [] | 0.000015 | 30125.0 | 45187.5 |
export_water_block_t2p0 | ['ee_1', 'wat_2'] | [] | 0.000015 | 20102.5 | 30153.75 |
import_ee_no_v_t27p0 | ['ee_1', 'wat_2'] | [] | 0.000008 | 15175.0 | 12140.0 |
import_ee_inf_v_t27p0 | ['ee_1', 'wat_2'] | [] | 0.000002 | 20175.0 | 4035.0 |
import_water_no_wat_t27p0 | ['ee_1', 'wat_2'] | [] | 0.00001 | 6175.0 | 6175.0 |
import_water_less_wat_t27p0 | ['ee_1', 'wat_2'] | [] | 0.00001 | 2587.5 | 2587.5 |
import_signal_no_sig_t27p0 | ['ee_1', 'wat_2'] | [] | 0.000001 | 15175.0 | 1517.5 |
move_water_mech_break_t27p0 | ['ee_1', 'wat_2'] | [] | 0.000007 | 10175.0 | 7326.0 |
move_water_short_t27p0 | ['ee_1', 'wat_2'] | [] | 0.00001 | 25175.0 | 25175.0 |
export_water_block_t27p0 | ['ee_1', 'wat_2'] | [] | 0.00001 | 15152.5 | 15152.5 |
import_ee_no_v_t52p0 | ['ee_1', 'wat_2'] | [] | 0.0 | 10000.0 | 0.0 |
import_ee_inf_v_t52p0 | ['ee_1', 'wat_2'] | [] | 0.0 | 5000.0 | 0.0 |
import_water_no_wat_t52p0 | ['ee_1', 'wat_2'] | [] | 0.00001 | 1000.0 | 1000.0 |
import_water_less_wat_t52p0 | ['ee_1', 'wat_2'] | [] | 0.00001 | 0.0 | 0.0 |
import_signal_no_sig_t52p0 | ['ee_1', 'wat_2'] | [] | 0.000001 | 10000.0 | 1000.0 |
move_water_mech_break_t52p0 | ['ee_1', 'wat_2'] | [] | 0.000001 | 5000.0 | 300.0 |
move_water_short_t52p0 | ['ee_1', 'wat_2'] | [] | 0.00001 | 10000.0 | 10000.0 |
export_water_block_t52p0 | ['ee_1', 'wat_2'] | [] | 0.00001 | 5000.0 | 5000.0 |
nominal | ['ee_1', 'wat_2'] | [] | 1.0 | 0.0 | 0.0 |
5.) Saving Work
In computationally expensive simulations, running a lot of computational simulations can take a considerable amount of time. As a result, it becomes impractical to run a new simulation every time one wishes to analyse its data. Results from fmdtools simulations (endclasses or mdlhists) can be saved as pickle, csv, or json files in this instance using either: - Result.save
/History.save
or - passing a save_args dictionary to the respective propagate functions (e.g.,
{'endclass':{'filename':'file.pkl','overwrite':True}
)
and then loaded using: - Result.load
/History.load
For example, for this variable:
[36]:
endclasses_samp
[36]:
import_ee_no_v_t2p0.endclass.rate: 0.0
import_ee_no_v_t2p0.endclass.cost: 20125.000000000007
import_ee_no_v_t2p0. 0.0
import_ee_inf_v_t2p0.endclass.rate: 0.0
import_ee_inf_v_t2p0.endclass.cost: 25125.000000000007
import_ee_inf_v_t2p0 0.0
import_water_no_wat_t2p0.endclass.rate: 1e-05
import_water_no_wat_t2p0.endclass.cost: 11125.000000000007
import_water_no_wat_ 11125.000000000007
import_water_less_wa 1e-05
import_water_less_wa 5062.500000000004
import_water_less_wa 5062.500000000004
import_signal_no_sig 1.5e-06
import_signal_no_sig 20125.000000000007
import_signal_no_sig 3018.750000000001
move_water_mech_brea6.000000000000001e-07
move_water_mech_brea 15125.000000000007
move_water_mech_brea 907.5000000000005
move_water_short_t2p0.endclass.rate: 1.5000000000000002e-05
move_water_short_t2p0.endclass.cost: 30125.000000000007
move_water_short_t2p 45187.500000000015
export_water_block_t2p0.endclass.rate: 1.5000000000000002e-05
export_water_block_t2p0.endclass.cost: 20102.500000000007
export_water_block_t 30153.750000000015
import_ee_no_v_t27p0.endclass.rate: 8.000000000000001e-06
import_ee_no_v_t27p0.endclass.cost: 15174.999999999998
import_ee_no_v_t27p0 12140.0
import_ee_inf_v_t27p0.endclass.rate: 2.0000000000000003e-06
import_ee_inf_v_t27p0.endclass.cost: 20175.0
import_ee_inf_v_t27p 4035.000000000001
import_water_no_wat_ 1e-05
import_water_no_wat_ 6174.999999999998
import_water_no_wat_ 6174.999999999998
import_water_less_wa 1e-05
import_water_less_wa 2587.499999999999
import_water_less_wa 2587.499999999999
import_signal_no_sig 1e-06
import_signal_no_sig 15174.999999999998
import_signal_no_sig 1517.4999999999998
move_water_mech_brea 7.2e-06
move_water_mech_brea 10174.999999999998
move_water_mech_brea 7325.999999999998
move_water_short_t27p0.endclass.rate: 1e-05
move_water_short_t27p0.endclass.cost: 25175.0
move_water_short_t27 25175.0
export_water_block_t27p0.endclass.rate: 1e-05
export_water_block_t27p0.endclass.cost: 15152.5
export_water_block_t 15152.5
import_ee_no_v_t52p0.endclass.rate: 0.0
import_ee_no_v_t52p0.endclass.cost: 10000.0
import_ee_no_v_t52p0 0.0
import_ee_inf_v_t52p0.endclass.rate: 0.0
import_ee_inf_v_t52p0.endclass.cost: 5000.0
import_ee_inf_v_t52p 0.0
import_water_no_wat_ 1e-05
import_water_no_wat_ 1000.0
import_water_no_wat_ 1000.0
import_water_less_wa 1e-05
import_water_less_wa 0.0
import_water_less_wa 0.0
import_signal_no_sig 1e-06
import_signal_no_sig 10000.0
import_signal_no_sig 999.9999999999999
move_water_mech_brea6.000000000000001e-07
move_water_mech_brea 5000.0
move_water_mech_brea 300.0
move_water_short_t52p0.endclass.rate: 1e-05
move_water_short_t52p0.endclass.cost: 10000.0
move_water_short_t52 10000.0
export_water_block_t52p0.endclass.rate: 1e-05
export_water_block_t52p0.endclass.cost: 5000.0
export_water_block_t 5000.0
nominal.endclass.rate: 1.0
nominal.endclass.cost: 0.0
nominal.endclass.expected_cost: 0.0
[37]:
endclasses_samp.save('tutorial_endclasses.csv', overwrite=True)
File already exists: tutorial_endclasses.csv, writing anyway...
[38]:
endclasses_saved = an.result.Result.load('tutorial_endclasses.csv')
[39]:
endclasses_saved
[39]:
import_ee_no_v_t2p0.endclass.rate: 0.0
import_ee_no_v_t2p0.endclass.cost: 20125.000000000007
import_ee_no_v_t2p0. 0.0
import_ee_inf_v_t2p0.endclass.rate: 0.0
import_ee_inf_v_t2p0.endclass.cost: 25125.000000000007
import_ee_inf_v_t2p0 0.0
import_water_no_wat_t2p0.endclass.rate: 1e-05
import_water_no_wat_t2p0.endclass.cost: 11125.000000000007
import_water_no_wat_ 11125.000000000007
import_water_less_wa 1e-05
import_water_less_wa 5062.500000000004
import_water_less_wa 5062.500000000004
import_signal_no_sig 1.5e-06
import_signal_no_sig 20125.000000000007
import_signal_no_sig 3018.750000000001
move_water_mech_brea6.000000000000001e-07
move_water_mech_brea 15125.000000000007
move_water_mech_brea 907.5000000000005
move_water_short_t2p0.endclass.rate: 1.5000000000000002e-05
move_water_short_t2p0.endclass.cost: 30125.000000000007
move_water_short_t2p 45187.500000000015
export_water_block_t2p0.endclass.rate: 1.5000000000000002e-05
export_water_block_t2p0.endclass.cost: 20102.500000000007
export_water_block_t 30153.75000000001
import_ee_no_v_t27p0.endclass.rate: 8.000000000000001e-06
import_ee_no_v_t27p0.endclass.cost: 15174.999999999998
import_ee_no_v_t27p0 12140.0
import_ee_inf_v_t27p0.endclass.rate: 2.0000000000000003e-06
import_ee_inf_v_t27p0.endclass.cost: 20175.0
import_ee_inf_v_t27p 4035.000000000001
import_water_no_wat_ 1e-05
import_water_no_wat_ 6174.999999999998
import_water_no_wat_ 6174.999999999998
import_water_less_wa 1e-05
import_water_less_wa 2587.499999999999
import_water_less_wa 2587.499999999999
import_signal_no_sig 1e-06
import_signal_no_sig 15174.999999999998
import_signal_no_sig 1517.4999999999998
move_water_mech_brea 7.2e-06
move_water_mech_brea 10174.999999999998
move_water_mech_brea 7325.999999999998
move_water_short_t27p0.endclass.rate: 1e-05
move_water_short_t27p0.endclass.cost: 25175.0
move_water_short_t27 25175.0
export_water_block_t27p0.endclass.rate: 1e-05
export_water_block_t27p0.endclass.cost: 15152.5
export_water_block_t 15152.5
import_ee_no_v_t52p0.endclass.rate: 0.0
import_ee_no_v_t52p0.endclass.cost: 10000.0
import_ee_no_v_t52p0 0.0
import_ee_inf_v_t52p0.endclass.rate: 0.0
import_ee_inf_v_t52p0.endclass.cost: 5000.0
import_ee_inf_v_t52p 0.0
import_water_no_wat_ 1e-05
import_water_no_wat_ 1000.0
import_water_no_wat_ 1000.0
import_water_less_wa 1e-05
import_water_less_wa 0.0
import_water_less_wa 0.0
import_signal_no_sig 1e-06
import_signal_no_sig 10000.0
import_signal_no_sig 1000.0
move_water_mech_brea6.000000000000001e-07
move_water_mech_brea 5000.0
move_water_mech_brea 300.0
move_water_short_t52p0.endclass.rate: 1e-05
move_water_short_t52p0.endclass.cost: 10000.0
move_water_short_t52 10000.0
export_water_block_t52p0.endclass.rate: 1e-05
export_water_block_t52p0.endclass.cost: 5000.0
export_water_block_t 5000.0
nominal.endclass.rate: 1.0
nominal.endclass.cost: 0.0
nominal.endclass.expected_cost: 0.0