Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Examples: State estimation - Kalman filter #126

Merged
merged 8 commits into from
Dec 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
214 changes: 213 additions & 1 deletion examples/07_State_Estimation.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,210 @@
"source": [
"# State Estimation"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Prognostics involves two key steps: 1) state estimation and 2) prediction. [State estimation](https://nasa.github.io/progpy/prog_algs_guide.html#prog-algs-guide:~:text=to%20toe%20prediction.-,State%20Estimation,-%23) is the process of estimating the current state of the system using sensor data and a prognostics model. The result is an estimate of the current state of the system with uncertainty. This estimate can then used by the predictor to perform prediction of future states and events. In this section, we describe state estimation and the tools within ProgPy to implement it. \n",
"\n",
"State estimation is the process of estimating the internal model state (`x`) using the inputs, outputs, and parameters of the system. This is necessary for cases where the model state isn't directly measurable (i.e. *hidden states*), or where there is sensor noise in state measurements. \n",
"\n",
kjjarvis marked this conversation as resolved.
Show resolved Hide resolved
"The foundation of state estimators is the estimate method. The estimate method is called with a time, inputs, and outputs. Each time estimate is called, the internal state estimate is updated. For example: `state_estimator.estimate(time, inputs, outputs)`, then the resulting state can be accessed using the property x (i.e., `state_estimator.estimate .x`).\n",
"\n",
"ProgPy includes a number of [state estimators](https://nasa.github.io/progpy/api_ref/prog_algs/StateEstimator.html). The most common techniques include Kalman Filters and Particle Filters. Users can also define their own custom state estimators. Examples of each of these are presented below. "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Kalman Filter"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"One method for state estimation in ProgPy is using a [Kalman Filter (KF)](https://nasa.github.io/progpy/api_ref/prog_algs/StateEstimator.html#:~:text=Unscented%20Kalman%20Filter-,Kalman,-Filter). Kalman Filters are a simple, efficient state estimator for linear systems where state is represented by a mean and covariance matrix. The resulting state is represented by a [progpy.uncertain_data.MultivariateNormalDist](https://nasa.github.io/progpy/api_ref/progpy/UncertainData.html#progpy.uncertain_data.MultivariateNormalDist). Only works with Prognostic Models inheriting from [progpy.LinearModel](https://nasa.github.io/progpy/api_ref/progpy/LinearModel.html#progpy.LinearModel). "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To illustrate how to use a Kalman Filter for state estimation, we'll use a linear version of the ThrownObject model, and use the KF State estimator with fake data to estimate state.\n",
"\n",
"First, the necessary imports."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"from progpy.models.thrown_object import LinearThrownObject\n",
"from progpy.state_estimators import KalmanFilter"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's instantiate the model."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"m = LinearThrownObject()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To instantiate the Kalman filter, we need an initial (i.e. starting) state. We'll define this as slightly off of the actual state, so first we'll print the acutal state in the model for our information. "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"print('Initial thrower height:', m.parameters['thrower_height'])\n",
"print('Initial speed:', m.parameters['throwing_speed'])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Given this, let's define our starting state for estimation. "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"x_guess = m.StateContainer({'x': 1.75, 'v': 35}) # Slightly off of true x0"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now we can instantiate our Kalman filter. The Kalman filter requires two arguments, the prognostics model to be used in state estimation and an initial starting state. See [Kalman Filter Docs](https://nasa.github.io/progpy/api_ref/progpy/StateEstimator.html#progpy.state_estimators.KalmanFilter) for a full description of supported arguments."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"kf = KalmanFilter(m, x_guess)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"With this, we're ready to run the Kalman Filter state estimation. In the following, we'll use simulated data from the ThrownObject model. In a real application, we would be using sensor data from the system. "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"First, define the time step and pick a print frequency. "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"dt = 0.01 # time step (s)\n",
"print_freq = 50 # print every 50th iteration"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Next, initialize the state and input. Note that there is no input for this model, and thus it is defined as an empty InputContainer."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"x = m.initialize() # Initial state\n",
"u = m.InputContainer({}) # Initial input"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now let's run the Kalman filter. For each iteration, we'll first get the simulated output. (Note: In a real application, this would be a measured value.) Then we'll esimate the new state by calling the `estimate` method of the Kalman filter class, which takes input of the current timestamp, current input, and current output. The estimated state can then be accessed, and we print a comparison. Finally, we'll update the state, `x`. \n",
"\n",
"To visualize, we'll plot the error (i.e. the absolute value of the difference between the estimated state and the true state) throughout time. Notice that the error decreases as we progress through time. "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"fig, (ax1, ax2) = plt.subplots(2) # Initialize figure\n",
"\n",
"for i in range(500):\n",
"\n",
" # Get simulated output (would be measured values in a real application)\n",
" z = m.output(x)\n",
"\n",
" # Estimate new state\n",
" kf.estimate(i*dt, u, z)\n",
" x_est = kf.x.mean\n",
"\n",
" # Print results \n",
" if i%print_freq == 0: # Print every print_freq'th iteration\n",
" print(f\"t: {i*dt:.2f}\\n\\tEstimate: {x_est}\\n\\tTruth: {x}\")\n",
" diff = {key: abs(x_est[key] - x[key]) for key in x.keys()}\n",
" print(f\"\\t Diff: {diff}\")\n",
" \n",
" ax1.plot(i*dt, diff['x'], '-ob')\n",
" ax2.plot(i*dt, diff['v'], '-*r')\n",
" ax1.set(xlabel='Time', ylabel='Error in x')\n",
" ax2.set(xlabel='Time', ylabel='Error in y')\n",
"\n",
" # Update real state for next step\n",
" x = m.next_state(x, u, dt)\n",
"\n",
"plt.show()"
]
},
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about plotting the error in each state variable over time? A visual representation might help the user understand its function.

"cell_type": "markdown",
"metadata": {},
"source": [
"With this, we have illustrated how to use a built-in Kalman filter to perform state estimation. Next, we'll show how to create a new, custom state estimator. "
]
}
],
"metadata": {
Expand All @@ -15,8 +219,16 @@
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"version": "3.11.0"
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.6"
},
"orig_nbformat": 4,
"vscode": {
Expand Down
139 changes: 0 additions & 139 deletions examples/kalman_filter.py

This file was deleted.

Loading