The goal of this notebook is to instruct the user on how to use and extend the NASA Python Prognostics Algorithms Package.
First some background. The Prognostics Algorithms Package (prog_algs
) contains tools for performing prognostics (event prediction) using the Prognostics Models Package. prog_algs
also includes tools for analyzing the performance of prognostics algorithms.
A few definitions:
The prog_algs
package has the following primary subpackages
prog_algs.state_estimators
- Tools for performing state estimationprog_algs.predictors
- Tools for performing predictionprog_algs.uncertain_data
- Tools for representing data with uncertaintyIn addition to the prog_algs
package, this repo includes examples showing how to use the package (see examples/
), a template for implementing a new state estimator (state_estimator_template
), a template for implementing a new predictor (predictor_template
), documentation (https://nasa.github.io/prog_algs), and this tutorial (tutorial.ipynb
).
Before you start, install prog_algs
using pip:
`pip install prog_algs`
or, to use the pre-release, close from GitHub and checkout the dev branch. Then run the following command:
pip install -e .
Now let's get started with some examples
Uncertainty is sometimes present in data used for performing state estimations or making predictions.
In prog_algs
, data with uncertainty is represented using classes inheriting from UncertainData
:
prog_algs.uncertain_data.MultivariateNormalDist
- Data represented by a multivariate normal distribution with mean and covariance matrixprog_algs.uncertain_data.ScalarData
- Data without uncertainty, a single valueprog_algs.uncertain_data.UnweightedSamples
- Data represented by a set of unweighted samples. Objects of this class can be treated like a list where samples[n] returns the nth sample (Dict)To begin using UncertainData
, import the type that best portrays the data. In this simple demonstration, we import the UnweightedSamples
data type. See https://nasa.github.io/prog_algs for full details on the available UncertainData
types.
from prog_algs.uncertain_data import UnweightedSamples
With UnweightedSamples
imported, construct an object with samples. This object can be initialized using either a dictionary, list, or model.*Container type from prog_models (e.g., StateContainer). Let's try creating an object using a dictionary.
samples = UnweightedSamples([{'x': 1, 'v':2}, {'x': 3, 'v':-2}])
print(samples)
Given an integer value, addition and subtraction can be performed on the UncertainData
classes to adjust the distribution by a scalar amount.
samples = samples + 5
print(samples)
samples -= 3
print(samples)
We can also sample from any UncertainData
distribution using the sample
method. In this case it resamples from the existing samples
print(samples.sample()) # A single sample
print(samples.sample(10)) # 10 samples
We can see the keys present using the .keys()
method:
print(samples.keys())
and the data corresponding to a specific key can be retrieved using .key()
print(samples.key('x'))
Various properties are available to quantify the UncertainData
distribution
print('mean', samples.mean)
print('median', samples.median)
print('covariance', samples.cov)
print('size', samples.size)
These UncertainData
classes are used throughout the prog_algs package to represent data with uncertainty, as described in the following sections.
State estimation is the process of estimating the system state given sensor data and a model. Typically, this is done repeatedly as new sensor data is available.
In prog_algs
a State Estimator is used to estimate the system state.
To start, import the needed packages. Here we will import the BatteryCircuit
model and the UnscentedKalmanFilter
state estimator. See https://nasa.github.io/prog_algs for more details on the available state estimators.
from prog_models.models import BatteryCircuit
from prog_algs.state_estimators import UnscentedKalmanFilter
Next we construct and initialize the model.
We use the resulting model and initial state to construct the state estimator.
m = BatteryCircuit()
x0 = m.initialize()
# Turn into a distribution - this represents uncertainty in the initial state
from prog_algs.uncertain_data import MultivariateNormalDist
from numpy import diag
INITIAL_UNCERT = 0.05 # Uncertainty in initial state (%)
# Construct covariance matrix (making sure each value is positive)
cov = diag([max(abs(INITIAL_UNCERT * value), 1e-9) for value in x0.values()])
x0 = MultivariateNormalDist(x0.keys(), x0.values(), cov)
# Construct State estimator
est = UnscentedKalmanFilter(m, x0)
Now we can use the estimator to estimate the system state.
print("Prior State:", est.x.mean)
print('\tSOC: ', m.event_state(est.x.mean)['EOD'])
fig = est.x.plot_scatter(label='prior')
t = 0.1
u = m.InputContainer({'i': 2})
example_measurements = m.OutputContainer({'t': 32.2, 'v': 3.915})
est.estimate(t, u, example_measurements)
print("Posterior State:", est.x.mean)
print('\tSOC: ', m.event_state(est.x.mean)['EOD'])
est.x.plot_scatter(fig= fig, label='posterior')
As mentioned previously, this step is typically repeated when there's new data. filt.x may not be accessed every time the estimate is updated, only when it's needed.
Prediction is the practice of using a state estimation, a model, and estimates of future loading to predict future states and when an event will occur.
First we will import a predictor. In this case, we will use the MonteCarlo Predictor, but see documentation https://nasa.github.io/prog_algs for a full list of predictors and their configuration parameters.
from prog_algs.predictors import MonteCarlo
Next we initialize it using the model from the above example
mc = MonteCarlo(m)
Next, let's define future loading and the first state. The first state is the output of the state estimator, and the future loading scheme is a simple piecewise function
x = est.x # The state estimate
def future_loading(t, x={}):
# Variable (piece-wise) future loading scheme
if (t < 600):
i = 2
elif (t < 900):
i = 1
elif (t < 1800):
i = 4
elif (t < 3000):
i = 2
else:
i = 3
return m.InputContainer({'i': i})
Now let's use the constructed mc predictor to perform a single prediction. Here we're setting dt to 0.25. Note this may take up to a minute
mc_results = mc.predict(x, future_loading, dt=0.25, n_samples=20)
The predict function returns predictions of future inputs, states, outputs, and event_states at each save point. For sample-based predictors like the monte carlo, these can be accessed like an array with the format [sample #][time]
so that mc_results.states[m][n]
corresponds to the state for sample m
at time mc_results.times[m][n]
. Alternately, use the method snapshot
to get a single point in time. e.g.,
`state = mc_results.states.snapshot(3)`
In this case the state snapshot corresponds to time mc_results.times[3]
. The snapshot method returns type UncertainData.
The predict
method also returns Time of Event (ToE) as a type UncertainData, representing the predicted time of event (for each event predicted), with uncertainty.
Next, let's use the metrics package to analyze the ToE
print("\nEOD Predictions (s):")
print('\tPortion between 3005.2 and 3005.6: ', mc_results.time_of_event.percentage_in_bounds([3005.2, 3005.6]))
print('\tAssuming ground truth 3005.25: ', mc_results.time_of_event.metrics(ground_truth = 3005.25))
from prog_algs.metrics import prob_success
print('\tP(Success) if mission ends at 3005.25: ', prob_success(mc_results.time_of_event, 3005.25))
These analysis methods applied to ToE can also be applied to anything of type UncertainData (e.g., state snapshot).
You can also visualize the results in a variety of different ways. For example, state transition
fig = mc_results.states.snapshot(0).plot_scatter(label = "t={:.0f}".format(int(mc_results.times[0])))
for i in range(1, 4):
index = int(len(mc_results.times)/4*i)
mc_results.states.snapshot(index).plot_scatter(fig=fig, label = "t={:.0f}".format(mc_results.times[index]))
mc_results.states.snapshot(-1).plot_scatter(fig = fig, label = "t={:.0f}".format(int(mc_results.times[-1])))
Or time of event (ToE)
fig = mc_results.time_of_event.plot_hist()
Note, for this event, there is only one event (End of Discharge). Many models have multiple events that can be predicted. For these models, ToE for each event is returned and can be analyzed.
Alternately, a specific event (or events) can be specified for prediction. See examples.predict_specific_event
for more details.
Frequently the prediction step is run periodically, less often than the state estimator step
New state estimators can be created by extending the state_estimator interface. As an example lets use a really dumb state estimator that adds random noise each step - and accepts the state that is closest.
First thing we need to do is import the StateEstimator parent class
from prog_algs.state_estimators.state_estimator import StateEstimator
Next we select how state will be represented. In this case there's no uncertainty- it's just one state, so we represent it as a scaler. Import the appropriate class
from prog_algs.uncertain_data import ScalarData
Now we construct the class, implementing the functions of the state estimator template (state_estimator_template.py
)
import random
class BlindlyStumbleEstimator(StateEstimator):
def __init__(self, model, x0):
self.m = model
self.state = x0
def estimate(self, t, u, z):
# Generate new candidate state
x2 = {key : float(value) + 10*(random.random()-0.5) for (key,value) in self.state.items()}
# Calculate outputs
z_est = self.m.output(self.state)
z_est2 = self.m.output(x2)
# Now score them each by how close they are to the measured z
z_est_score = sum([abs(z_est[key] - z[key]) for key in self.m.outputs])
z_est2_score = sum([abs(z_est2[key] - z[key]) for key in self.m.outputs])
# Now choose the closer one
if z_est2_score < z_est_score:
self.state = x2
@property
def x(self):
return ScalarData(self.state)
Great, now let's try it out using the model from earlier. with an initial state of all 0s. It should slowly converge towards the correct state
x0 = {key: 0 for key in m.states}
se = BlindlyStumbleEstimator(m, x0)
for i in range(25):
u = m.InputContainer({'i': 0})
z = m.OutputContainer({'t': 18.95, 'v': 4.183})
se.estimate(i, u, z)
print(se.x.mean)
print("\tcorrect: {'tb': 18.95, 'qb': 7856.3254, 'qcp': 0, 'qcs': 0}")
Like the example above with StateEstimators, Predictors can be extended by subclassing the Predictor class. Copy predictor_template.py
as a starting point.
This is just the basics, there's much more to learn. Please see the documentation at https://nasa.github.io/prog_algs and the examples in the examples/
folder for more details on how to use the package, including:
examples.basic_example
: A basic Example using prog_algs for Prognosticsexamples.benchmarking_example
: An example benchmarking the performance of prognostics algorithmsexamples.eol_event
: An example where a model has multiple events, but the user is only interested in predicting the time when the first event occurs (whatever it is).examples.measurement_eqn_example
: An example where not every output is measured or measurements are not in the same format as outputs, so a measurement equation is defined to translate between outputs and what's measured.examples.new_state_estimator_example
: An example of extending StateEstimator to create a new state estimator classexamples.playback
: A full example performing prognostics using playback data.examples.predict_specific_event
: An example where the model has multiple events, but the user is only interested in predicting a specific event (or events).examples.thrown_object_example
: An example performing prognostics with the simplified ThrownObject modelexamples.utpredictor
: An example using the Unscented Transform Predictor for prediction.Thank you for trying out this tutorial. Open an issue on github (https://github.com/nasa/prog_algs/issues) or email Chris Teubert (christopher.a.teubert@nasa.gov) with any questions or issues.
Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved.