{
"cells": [
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"# Welcome to ProgPy's Linear Model Example"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"The goal of this notebook is to instruct users on how to use ProgPy Model LinearModel.\n",
"\n",
"This example shows the use of the LinearModel class, a subclass of PrognosticsModel for models that can be described as a linear time series, which can be defined by the following equations:\n",
"\n",
"\n",
"\n",
"#### _**The State Equation****_:\n",
"$$\n",
"\\frac{dx}{dt} = Ax + Bu + E\n",
"$$\n",
"\n",
"#### _****The Output Equation****_:\n",
"$$\n",
"z = Cx + D\n",
"$$\n",
"\n",
"#### _****The Event State Equation****_:\n",
"$$\n",
"es = Fx + G\n",
"$$\n",
"\n",
"$x$ is `state`, $u$ is `input`, $z$ is `output`, and $es$ is `event state`"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"Linear Models are defined by creating a new model class that inherits from progpy's LinearModel class and defines the following properties:\n",
"* $A$: 2-D np.array[float], dimensions: n_states x n_states. The state transition matrix. It dictates how the current state affects the change in state dx/dt.\n",
"* $B$: 2-D np.array[float], optional (zeros by default), dimensions: n_states x n_inputs. The input matrix. It dictates how the input affects the change in state dx/dt.\n",
"* $C$: 2-D np.array[float], dimensions: n_outputs x n_states. The output matrix. It determines how the state variables contribute to the output.\n",
"* $D$: 1-D np.array[float], optional (zeros by default), dimensions: n_outputs x 1. A constant term that can represent any biases or offsets in the output.\n",
"* $E$: 1-D np.array[float], optional (zeros by default), dimensions: n_states x 1. A constant term, representing any external effects that are not captured by the state and input.\n",
"* $F$: 2-D np.array[float], dimensions: n_es x n_states. The event state matrix, dictating how state variables contribute to the event state.\n",
"* $G$: 1-D np.array[float], optional (zeros by default), dimensions: n_es x 1. A constant term that can represent any biases or offsets in the event state.\n",
"* __inputs__: list[str] - `input` keys\n",
"* __states__: list[str] - `state` keys\n",
"* __outputs__: list[str] - `output` keys\n",
"* __events__: list[str] - `event` keys"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"We will now utilize our LinearModel to model the classical physics problem throwing an object into the air! We can create a subclass of LinearModel which will be used to simulate an object thrown, which we will call the ThrownObject Class.\n",
"\n",
"\n",
"First, some definitions for our Model!\n",
"\n",
"#### __Events__: (2)\n",
"* `falling: The object is falling`\n",
"* `impact: The object has hit the ground`\n",
"\n",
"#### __Inputs/Loading__: (0)\n",
"* `None`\n",
"\n",
"#### __States__: (2)\n",
"* `x: Position in space (m)`\n",
"* `v: Velocity in space (m/s)`\n",
"\n",
"#### __Outputs/Measurements__: (1)\n",
"* `x: Position in space (m)`"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"Now, for our keyword arguments:\n",
"\n",
"* __thrower_height : Optional, float__\n",
" * Height of the thrower (m). Default is 1.83 m\n",
"* __throwing_speed : Optional, float__\n",
" * Speed at which the ball is thrown (m/s). Default is 40 m/s"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"With our definitions, we can now create the ThrownObject Model.\n",
"\n",
"First, we need to import the necessary packages."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"from progpy import LinearModel"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"Now we'll define some features of a ThrownObject LinearModel. Recall that all LinearModels follow a set of core equations and require some specific properties (see above). In the next step, we'll define our inputs, states, outputs, and events, along with the $A$, $C$, $E$, and $F$ values."
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"First, let's consider state transition. For an object thrown into the air without air resistance, velocity would decrease literally by __-9.81__ \n",
"$\\dfrac{m}{s^2}$ due to the effect of gravity, as described below:\n",
"\n",
" $$\\frac{dv}{dt} = -9.81$$\n",
"\n",
" Position change is defined by velocity (v), as described below:\n",
" \n",
" $$\\frac{dx}{dt} = v$$"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"Note: For the above equation x is position not state. Combining these equations to the model $\\frac{dx}{dt}$ equation defined earlier yields the A and E matrix defined below. Note that there is no B defined because this model does not have an inputs."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"class ThrownObject(LinearModel):\n",
" events = ['impact']\n",
" inputs = [] \n",
" states = ['x', 'v']\n",
" outputs = ['x']\n",
" \n",
" A = np.array([[0, 1], [0, 0]])\n",
" C = np.array([[1, 0]])\n",
" E = np.array([[0], [-9.81]])\n",
" F = None"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"Note that we defined our `A`, `C`, `E`, and `F` values to fit the dimensions that were stated at the beginning of the notebook! Since the parameter `F` is not optional, we have to explicitly set the value as __None__.\n",
"\n",
"Next, we'll define some default parameters for our ThrownObject model."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"class ThrownObject(ThrownObject): # Continue the ThrownObject class\n",
" default_parameters = {\n",
" 'thrower_height': 1.83,\n",
" 'throwing_speed': 40,\n",
" }"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"In the following cells, we'll define some class functions necessary to perform prognostics on the model."
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"The `initialize()` function sets the initial system state. Since we have defined the `x`and `v` values for our ThrownObject model to represent position and velocity in space, our initial values would be the thrower_height, and throwing_speed parameters, respectively."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"class ThrownObject(ThrownObject):\n",
" def initialize(self, u=None, z=None):\n",
" return self.StateContainer({\n",
" 'x': self.parameters['thrower_height'],\n",
" 'v': self.parameters['throwing_speed']\n",
" })"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"For our `threshold_met()`, we define the function to return True for event 'falling' when our thrown object model has a velocity value of less than 0 (object is 'falling') and for event 'impact' when our thrown object has a distance from of the ground of less than or equal to 0 (object is on the ground, or has made 'impact').\n",
"\n",
"`threshold_met()` returns a _dict_ of values, if each entry of the _dict_ is __True__, then our threshold has been met!"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"class ThrownObject(ThrownObject):\n",
" def threshold_met(self, x):\n",
" return {\n",
" 'falling': x['v'] < 0,\n",
" 'impact': x['x'] <= 0\n",
" }"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"Finally, for our `event_state()`, we will calculate the measurement of progress towards the events. We normalize our values such that they are in the range of 0 to 1, where 0 means the event has occurred."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"class ThrownObject(ThrownObject):\n",
" def event_state(self, x): \n",
" x_max = x['x'] + np.square(x['v'])/(9.81*2)\n",
" return {\n",
" 'falling': np.maximum(x['v']/self.parameters['throwing_speed'],0),\n",
" 'impact': np.maximum(x['x']/x_max,0) if x['v'] < 0 else 1\n",
" }"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"With these functions created, we can now run our ThrownObject Model!\n",
"\n",
"In this example, we will initialize our ThrownObject as `m`, and we'll use the `simulate_to_threshold()` function to simulate the movement of the thrown object in air. For more information, see the [Simulation](https://nasa.github.io/progpy/prog_models_guide.html#simulation) documentation."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"m = ThrownObject()\n",
"save = m.simulate_to_threshold(print = True, save_freq=1, threshold_keys='impact', dt=0.1)"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"__Note__: Because our model takes in no inputs, we have no need to actually define a future loading function! As a result, we are simply passing in an empty Input Container. However, for most models, there would be inputs, thus a need for a future loading function. For more information on future loading functions and when to use them, please refer to the ProgPy [Future Loading](https://nasa.github.io/progpy/prog_models_guide.html#future-loading) Documentation."
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"We'll also demonstrate how this looks plotted on a graph."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import matplotlib.pyplot as plt\n",
"save.outputs.plot(title='generated model')\n",
"plt.show()"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"Notice that that plot resembles a parabola, which represents the position of the ball through space as time progresses!"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Conclusion\n",
"\n",
"In this example, we will initialize our ThrownObject as `m` and use the `simulate_to_threshold()` function to simulate the movement of the thrown object in air. For more information, see the [Linear Model](https://nasa.github.io/progpy/api_ref/prog_models/LinearModel.html) Documentation."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3.11.0 64-bit",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.0"
},
"orig_nbformat": 4,
"vscode": {
"interpreter": {
"hash": "aee8b7b246df8f9039afb4144a1f6fd8d2ca17a180786b69acc140d282b71a49"
}
}
},
"nbformat": 4,
"nbformat_minor": 2
}
**