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