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:
[169]:
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.
[4]:
mdl.fxns
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[4], line 1
----> 1 mdl.fxns
NameError: name 'mdl' is not defined
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.
[171]:
[172]:
[173]:
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:
[174]:
mdl.staticfxns
and the dynamic step order (if there was one):
[175]:
mdl.dynamicfxns
We can also instantiate this model with different parameters. In Pump model, these are defined in PumpParam
[176]:
from ex_pump import PumpParam
p = PumpParam()
p
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:
[177]:
mdl2
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'}}
[178]:
What do the results look like? Explore results structures using the desired_results
and track
arguments.
[179]:
result_nominal
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
).
[180]:
mdlhist_nominal
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 underlyign Graph
class is very flexible and enables a wide range of display options: - Graph docs - Graph.draw
[181]:
We can also view the flow values of the model using an.plot.hist
(see docs)
It may be helpful to only view flows of interest.
[182]:
Note: for quick access to the syntax and options for these methods, type the ?method
or help(method)
in the terminal. For example ?an.plot.mdlhists
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:
[183]:
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.
[184]:
results_fault
[185]:
mdlhist_fault
3b.) Visualize fault model states
an.plot.mdlhistvals
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.
[186]:
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.
[187]:
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.
[188]:
Note that multiple graph representations are available for the graph if desired…
[189]:
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.
[190]:
[191]:
[192]:
It can be helpful to view what these results look like–a Result
of faults injected at particular times with their correspnding Result
dictionaries.
[193]:
endclasses_samp
We can look at/use a more structured version using:
[194]:
ec_nest = endclasses_samp.nest()
ec_nest
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.:
[195]:
[196]:
There are a number of different ways to sample the scenarios in the approach:
[197]:
fs2.times
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.
[198]:
We can also use an.result_summary_fmea
with the processed results histories to get a better picture of which flows and functions degrade in each scenario.
[199]:
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:
[200]:
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:
[201]:
endclasses_samp
[202]:
[203]:
[204]:
endclasses_saved