Skip to content

Commit 51304e3

Browse files
authored
Merge branch 'dev' into ipynb-benchmarking
2 parents bbe3e5b + 61ba4f6 commit 51304e3

File tree

10 files changed

+936
-184
lines changed

10 files changed

+936
-184
lines changed

examples/linear_model.ipynb

Lines changed: 364 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,364 @@
1+
{
2+
"cells": [
3+
{
4+
"attachments": {},
5+
"cell_type": "markdown",
6+
"metadata": {},
7+
"source": [
8+
"# Welcome to ProgPy's Linear Model Example"
9+
]
10+
},
11+
{
12+
"attachments": {},
13+
"cell_type": "markdown",
14+
"metadata": {},
15+
"source": [
16+
"The goal of this notebook is to instruct users on how to use ProgPy Model LinearModel.\n",
17+
"\n",
18+
"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",
19+
"\n",
20+
"\n",
21+
"\n",
22+
"#### _<b>The State Equation<b>_:\n",
23+
"$$\n",
24+
"\\frac{dx}{dt} = Ax + Bu + E\n",
25+
"$$\n",
26+
"\n",
27+
"#### _<b>The Output Equation<b>_:\n",
28+
"$$\n",
29+
"z = Cx + D\n",
30+
"$$\n",
31+
"\n",
32+
"#### _<b>The Event State Equation<b>_:\n",
33+
"$$\n",
34+
"es = Fx + G\n",
35+
"$$\n",
36+
"\n",
37+
"$x$ is `state`, $u$ is `input`, $z$ is `output`, and $es$ is `event state`"
38+
]
39+
},
40+
{
41+
"attachments": {},
42+
"cell_type": "markdown",
43+
"metadata": {},
44+
"source": [
45+
"Linear Models are defined by creating a new model class that inherits from progpy's LinearModel class and defines the following properties:\n",
46+
"* $A$: 2-D np.array[float], dimensions: n_states x n_states. <font color = 'teal'>The state transition matrix. It dictates how the current state affects the change in state dx/dt.</font>\n",
47+
"* $B$: 2-D np.array[float], optional (zeros by default), dimensions: n_states x n_inputs. <font color = 'teal'>The input matrix. It dictates how the input affects the change in state dx/dt.</font>\n",
48+
"* $C$: 2-D np.array[float], dimensions: n_outputs x n_states. The output matrix. <font color = 'teal'>It determines how the state variables contribute to the output.</font>\n",
49+
"* $D$: 1-D np.array[float], optional (zeros by default), dimensions: n_outputs x 1. <font color = 'teal'>A constant term that can represent any biases or offsets in the output.</font>\n",
50+
"* $E$: 1-D np.array[float], optional (zeros by default), dimensions: n_states x 1. <font color = 'teal'>A constant term, representing any external effects that are not captured by the state and input.</font>\n",
51+
"* $F$: 2-D np.array[float], dimensions: n_es x n_states. <font color = 'teal'>The event state matrix, dictating how state variables contribute to the event state.</font>\n",
52+
"* $G$: 1-D np.array[float], optional (zeros by default), dimensions: n_es x 1. <font color = 'teal'>A constant term that can represent any biases or offsets in the event state.</font>\n",
53+
"* __inputs__: list[str] - `input` keys\n",
54+
"* __states__: list[str] - `state` keys\n",
55+
"* __outputs__: list[str] - `output` keys\n",
56+
"* __events__: list[str] - `event` keys"
57+
]
58+
},
59+
{
60+
"attachments": {},
61+
"cell_type": "markdown",
62+
"metadata": {},
63+
"source": [
64+
"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",
65+
"\n",
66+
"\n",
67+
"First, some definitions for our Model!\n",
68+
"\n",
69+
"#### __Events__: (2)\n",
70+
"* `falling: The object is falling`\n",
71+
"* `impact: The object has hit the ground`\n",
72+
"\n",
73+
"#### __Inputs/Loading__: (0)\n",
74+
"* `None`\n",
75+
"\n",
76+
"#### __States__: (2)\n",
77+
"* `x: Position in space (m)`\n",
78+
"* `v: Velocity in space (m/s)`\n",
79+
"\n",
80+
"#### __Outputs/Measurements__: (1)\n",
81+
"* `x: Position in space (m)`"
82+
]
83+
},
84+
{
85+
"attachments": {},
86+
"cell_type": "markdown",
87+
"metadata": {},
88+
"source": [
89+
"Now, for our keyword arguments:\n",
90+
"\n",
91+
"* <font color = green>__thrower_height : Optional, float__</font>\n",
92+
" * Height of the thrower (m). Default is 1.83 m\n",
93+
"* <font color = green>__throwing_speed : Optional, float__</font>\n",
94+
" * Speed at which the ball is thrown (m/s). Default is 40 m/s"
95+
]
96+
},
97+
{
98+
"attachments": {},
99+
"cell_type": "markdown",
100+
"metadata": {},
101+
"source": [
102+
"With our definitions, we can now create the ThrownObject Model.\n",
103+
"\n",
104+
"First, we need to import the necessary packages."
105+
]
106+
},
107+
{
108+
"cell_type": "code",
109+
"execution_count": null,
110+
"metadata": {},
111+
"outputs": [],
112+
"source": [
113+
"import numpy as np\n",
114+
"from progpy import LinearModel"
115+
]
116+
},
117+
{
118+
"attachments": {},
119+
"cell_type": "markdown",
120+
"metadata": {},
121+
"source": [
122+
"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."
123+
]
124+
},
125+
{
126+
"attachments": {},
127+
"cell_type": "markdown",
128+
"metadata": {},
129+
"source": [
130+
"First, let's consider state transition. For an object thrown into the air without air resistance, velocity would decrease literally by __-9.81__ \n",
131+
"$\\dfrac{m}{s^2}$ due to the effect of gravity, as described below:\n",
132+
"\n",
133+
" $$\\frac{dv}{dt} = -9.81$$\n",
134+
"\n",
135+
" Position change is defined by velocity (v), as described below:\n",
136+
" \n",
137+
" $$\\frac{dx}{dt} = v$$"
138+
]
139+
},
140+
{
141+
"attachments": {},
142+
"cell_type": "markdown",
143+
"metadata": {},
144+
"source": [
145+
"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."
146+
]
147+
},
148+
{
149+
"cell_type": "code",
150+
"execution_count": null,
151+
"metadata": {},
152+
"outputs": [],
153+
"source": [
154+
"class ThrownObject(LinearModel):\n",
155+
" events = ['impact']\n",
156+
" inputs = [] \n",
157+
" states = ['x', 'v']\n",
158+
" outputs = ['x']\n",
159+
" \n",
160+
" A = np.array([[0, 1], [0, 0]])\n",
161+
" C = np.array([[1, 0]])\n",
162+
" E = np.array([[0], [-9.81]])\n",
163+
" F = None"
164+
]
165+
},
166+
{
167+
"attachments": {},
168+
"cell_type": "markdown",
169+
"metadata": {},
170+
"source": [
171+
"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",
172+
"\n",
173+
"Next, we'll define some default parameters for our ThrownObject model."
174+
]
175+
},
176+
{
177+
"cell_type": "code",
178+
"execution_count": null,
179+
"metadata": {},
180+
"outputs": [],
181+
"source": [
182+
"class ThrownObject(ThrownObject): # Continue the ThrownObject class\n",
183+
" default_parameters = {\n",
184+
" 'thrower_height': 1.83,\n",
185+
" 'throwing_speed': 40,\n",
186+
" }"
187+
]
188+
},
189+
{
190+
"attachments": {},
191+
"cell_type": "markdown",
192+
"metadata": {},
193+
"source": [
194+
"In the following cells, we'll define some class functions necessary to perform prognostics on the model."
195+
]
196+
},
197+
{
198+
"attachments": {},
199+
"cell_type": "markdown",
200+
"metadata": {},
201+
"source": [
202+
"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."
203+
]
204+
},
205+
{
206+
"cell_type": "code",
207+
"execution_count": null,
208+
"metadata": {},
209+
"outputs": [],
210+
"source": [
211+
"class ThrownObject(ThrownObject):\n",
212+
" def initialize(self, u=None, z=None):\n",
213+
" return self.StateContainer({\n",
214+
" 'x': self.parameters['thrower_height'],\n",
215+
" 'v': self.parameters['throwing_speed']\n",
216+
" })"
217+
]
218+
},
219+
{
220+
"attachments": {},
221+
"cell_type": "markdown",
222+
"metadata": {},
223+
"source": [
224+
"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",
225+
"\n",
226+
"`threshold_met()` returns a _dict_ of values, if each entry of the _dict_ is __True__, then our threshold has been met!"
227+
]
228+
},
229+
{
230+
"cell_type": "code",
231+
"execution_count": null,
232+
"metadata": {},
233+
"outputs": [],
234+
"source": [
235+
"class ThrownObject(ThrownObject):\n",
236+
" def threshold_met(self, x):\n",
237+
" return {\n",
238+
" 'falling': x['v'] < 0,\n",
239+
" 'impact': x['x'] <= 0\n",
240+
" }"
241+
]
242+
},
243+
{
244+
"attachments": {},
245+
"cell_type": "markdown",
246+
"metadata": {},
247+
"source": [
248+
"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."
249+
]
250+
},
251+
{
252+
"cell_type": "code",
253+
"execution_count": null,
254+
"metadata": {},
255+
"outputs": [],
256+
"source": [
257+
"class ThrownObject(ThrownObject):\n",
258+
" def event_state(self, x): \n",
259+
" x_max = x['x'] + np.square(x['v'])/(9.81*2)\n",
260+
" return {\n",
261+
" 'falling': np.maximum(x['v']/self.parameters['throwing_speed'],0),\n",
262+
" 'impact': np.maximum(x['x']/x_max,0) if x['v'] < 0 else 1\n",
263+
" }"
264+
]
265+
},
266+
{
267+
"attachments": {},
268+
"cell_type": "markdown",
269+
"metadata": {},
270+
"source": [
271+
"With these functions created, we can now run our ThrownObject Model!\n",
272+
"\n",
273+
"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."
274+
]
275+
},
276+
{
277+
"cell_type": "code",
278+
"execution_count": null,
279+
"metadata": {},
280+
"outputs": [],
281+
"source": [
282+
"m = ThrownObject()\n",
283+
"save = m.simulate_to_threshold(print = True, save_freq=1, threshold_keys='impact', dt=0.1)"
284+
]
285+
},
286+
{
287+
"attachments": {},
288+
"cell_type": "markdown",
289+
"metadata": {},
290+
"source": [
291+
"__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."
292+
]
293+
},
294+
{
295+
"attachments": {},
296+
"cell_type": "markdown",
297+
"metadata": {},
298+
"source": [
299+
"We'll also demonstrate how this looks plotted on a graph."
300+
]
301+
},
302+
{
303+
"cell_type": "code",
304+
"execution_count": null,
305+
"metadata": {},
306+
"outputs": [],
307+
"source": [
308+
"import matplotlib.pyplot as plt\n",
309+
"save.outputs.plot(title='generated model')\n",
310+
"plt.show()"
311+
]
312+
},
313+
{
314+
"attachments": {},
315+
"cell_type": "markdown",
316+
"metadata": {},
317+
"source": [
318+
"Notice that that plot resembles a parabola, which represents the position of the ball through space as time progresses!"
319+
]
320+
},
321+
{
322+
"attachments": {},
323+
"cell_type": "markdown",
324+
"metadata": {},
325+
"source": [
326+
"#### Conclusion\n",
327+
"\n",
328+
"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."
329+
]
330+
},
331+
{
332+
"cell_type": "markdown",
333+
"metadata": {},
334+
"source": []
335+
}
336+
],
337+
"metadata": {
338+
"kernelspec": {
339+
"display_name": "Python 3.11.0 64-bit",
340+
"language": "python",
341+
"name": "python3"
342+
},
343+
"language_info": {
344+
"codemirror_mode": {
345+
"name": "ipython",
346+
"version": 3
347+
},
348+
"file_extension": ".py",
349+
"mimetype": "text/x-python",
350+
"name": "python",
351+
"nbconvert_exporter": "python",
352+
"pygments_lexer": "ipython3",
353+
"version": "3.11.0"
354+
},
355+
"orig_nbformat": 4,
356+
"vscode": {
357+
"interpreter": {
358+
"hash": "aee8b7b246df8f9039afb4144a1f6fd8d2ca17a180786b69acc140d282b71a49"
359+
}
360+
}
361+
},
362+
"nbformat": 4,
363+
"nbformat_minor": 2
364+
}

0 commit comments

Comments
 (0)