From 711cbe941758c8072e51c4a53491dcbe29bf9fab Mon Sep 17 00:00:00 2001 From: Michelle Ly Date: Tue, 15 Apr 2025 13:11:01 -0700 Subject: [PATCH 01/10] feat: add table of contents --- examples/04_New Models.ipynb | 69 +++++++++++++++++++++++++----------- 1 file changed, 48 insertions(+), 21 deletions(-) diff --git a/examples/04_New Models.ipynb b/examples/04_New Models.ipynb index 2c5ca3fd..dd99e495 100644 --- a/examples/04_New Models.ipynb +++ b/examples/04_New Models.ipynb @@ -4,14 +4,34 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# 4. Defining new Physics-Based Prognostic Models" + "# 4. Defining New Physics-Based Prognostic Models" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "All of the past sections describe how to use an existing model. In this section we will describe how to create a new model. This section specifically describes creating a new physics-based model. For training and creating data-driven models see 5. Data-driven Models." + "All of the past sections describe how to use an existing model. In this section we will describe how to create a new model. This section specifically describes creating a new physics-based model. For training and creating data-driven models, see __[05 Data Driven](05_Data%20Driven.ipynb)__." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Table of Contents\n", + "* [Linear Models](#Linear-Models)\n", + "* [New State Transition Models](#New-State-Transition-Models)\n", + "* [Derived Parameters](#Derived-Parameters)\n", + "* [Direct Models](#Direct-Models)\n", + "* [Matrix Data Access Feature](#Matrix-Data-Access-Feature)\n", + "* [State Limits](#State-Limits)'\n", + "* [Custom Events](#Custom-Events)\n", + "* [Serialization](#Serialization)\n", + "* [Simplified Battery Model Example](#Simplified-Battery-Model-Example)\n", + " * [State Transition](#State-Transition)\n", + " * [Outputs](#Outputs)\n", + " * [Events](#Events)\n", + "* [Conclusion](#Conclusion)" ] }, { @@ -131,7 +151,7 @@ " \n", " $$\\frac{dx}{dt} = v$$\n", "\n", - " Note: For the above equation x is position not state. Combining these equations with the model $\\frac{dx}{dt}$ equation defined above yields the A and E matrix defined below. Note that there is no B defined because this model does not have any inputs." + "**Note:** For the above equation x is position not state. Combining these equations with the model $\\frac{dx}{dt}$ equation defined above yields the A and E matrix defined below. Note that there is no B defined because this model does not have any inputs." ] }, { @@ -716,7 +736,7 @@ "\\frac{dv}{dt} &= -g \n", "\\end{align*}\n", "\n", - "which can be solved explicity, given initial position $x_0$ and initial velocity $v_0$, to get:\n", + "This can be solved explicity given initial position $x_0$ and initial velocity $v_0$:\n", "\\begin{align*}\n", "x(t) &= -\\frac{1}{2} gt^2 + v_0 t + x_0 \\\\ \\\\ \n", "v(t) &= -gt + v_0\n", @@ -1618,14 +1638,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Example - Simplified Battery Model" + "## Simplified Battery Model Example" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "This is an example of a somewhat more complicated model, in this case a battery. We will be implementing the simplified battery model introduced by Gina Sierra, et. al. (https://www.sciencedirect.com/science/article/pii/S0951832018301406)." + "This is an example of a somewhat more complicated model, in this case a battery. We will be implementing the simplified battery model introduced by [Gina Sierra, et. al.](https://www.sciencedirect.com/science/article/pii/S0951832018301406)" ] }, { @@ -1648,7 +1668,13 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## State Transition\n", + "### State Transition" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ "The first step to creating a physics-based model is implementing state transition. From the paper we see one state (SOC) and one state transition equation:\n", "\n", "$SOC(k+1) = SOC(k) - P(k)*\\Delta t * E_{crit}^{-1} + w_2(k)$\n", @@ -1677,7 +1703,7 @@ "source": [ "Next we define parameters. In this case the parameters are the initial SOC state (1) and the E_crit (Internal Total Energy). We get the value for $E_{crit}$ from the paper.\n", "\n", - "**Note: wont actually subclass in practice, but it's used to break apart model definition into chunks**" + "**Note:** This won't actually subclass in practice, but it's used to break apart model definition into chunks." ] }, { @@ -1736,19 +1762,20 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Outputs\n", - "\n", + "### Outputs" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ "Now that state transition is defined, the next step is to define the outputs of the function. From the paper we have the following output equations:\n", "\n", - "$v(k) = v_{oc}(k) - i(k) * R_{int} + \\eta (k)$\n", - "\n", - "where\n", - "\n", - "$v_{oc}(k) = v_L - \\lambda ^ {\\gamma * SOC(k)} - \\mu * e ^ {-\\beta * \\sqrt{SOC(k)}}$\n", + "$$v(k) = v_{oc}(k) - i(k) * R_{int} + \\eta (k)$$\n", "\n", - "and\n", + "$$v_{oc}(k) = v_L - \\lambda ^ {\\gamma * SOC(k)} - \\mu * e ^ {-\\beta * \\sqrt{SOC(k)}}$$\n", "\n", - "$i(k) = \\frac{v_{oc}(k) - \\sqrt{v_{oc}(k)^2 - 4 * R_{int} * P(k)}}{2 * R_{int}(k)}$" + "$$i(k) = \\frac{v_{oc}(k) - \\sqrt{v_{oc}(k)^2 - 4 * R_{int} * P(k)}}{2 * R_{int}(k)}$$" ] }, { @@ -1794,7 +1821,7 @@ "source": [ "Note that the input ($P(k)$) is also used in the output equation, that means it's part of the state of the system. So we will update the states to include this.\n", "\n", - "**NOTE: WE CHANGE TO next_state.** Above, we define state transition with ProgPy's `dx` method because the model was continuous. Here, with the addition of power, the model becomes discrete, so we must now use ProgPy's `next_state` method to define state transition. " + "**Note:** We change to next_state. Above, we define state transition with ProgPy's `dx` method because the model was continuous. Here, with the addition of power, the model becomes discrete, so we must now use ProgPy's `next_state` method to define state transition. " ] }, { @@ -1870,7 +1897,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Events\n", + "### Events\n", "Finally we can define events. This is an easy case because our event state (SOC) is part of the model state. So we will simply define a single event (EOD: End of Discharge), where SOC is progress towards that event." ] }, @@ -1917,7 +1944,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Conclusions" + "## Conclusion" ] }, { @@ -1926,7 +1953,7 @@ "source": [ "In these examples, we have described how to create new physics-based models. We have illustrated how to construct a generic physics-based model, as well as highlighted some specific types of models including linear models and direct models. We highlighted the matrix data access feature for using matrix operations more efficiently. Additionally, we discussed a few important components of any prognostics model including derived parameters, state limits, and events. \n", "\n", - "With these tools, users are well-equipped to build their own prognostics models for their specific physics-based use-cases. In the next example, we'll discuss how to create data-driven models." + "With these tools, users are well-equipped to build their own prognostics models for their specific physics-based use-cases. In the next notebook __[05 Data Driven](05_Data%20Driven.ipynb)__, we'll discuss how to create data-driven models." ] } ], From 3c14d1e2875ca59c19f8addd02cfbee57a08328d Mon Sep 17 00:00:00 2001 From: Michelle Ly Date: Tue, 15 Apr 2025 13:31:08 -0700 Subject: [PATCH 02/10] fix: remove comma --- examples/04_New Models.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/04_New Models.ipynb b/examples/04_New Models.ipynb index dd99e495..f1d3ec25 100644 --- a/examples/04_New Models.ipynb +++ b/examples/04_New Models.ipynb @@ -24,7 +24,7 @@ "* [Derived Parameters](#Derived-Parameters)\n", "* [Direct Models](#Direct-Models)\n", "* [Matrix Data Access Feature](#Matrix-Data-Access-Feature)\n", - "* [State Limits](#State-Limits)'\n", + "* [State Limits](#State-Limits)\n", "* [Custom Events](#Custom-Events)\n", "* [Serialization](#Serialization)\n", "* [Simplified Battery Model Example](#Simplified-Battery-Model-Example)\n", From 5e0ce104adebbbb695e169916c93c44ad968ad92 Mon Sep 17 00:00:00 2001 From: Michelle Ly Date: Fri, 18 Apr 2025 17:02:06 -0700 Subject: [PATCH 03/10] fix: update descriptions and add plot labels --- examples/04_New Models.ipynb | 63 +++++++++++++++++------------------- 1 file changed, 29 insertions(+), 34 deletions(-) diff --git a/examples/04_New Models.ipynb b/examples/04_New Models.ipynb index f1d3ec25..76360d5b 100644 --- a/examples/04_New Models.ipynb +++ b/examples/04_New Models.ipynb @@ -11,7 +11,11 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "All of the past sections describe how to use an existing model. In this section we will describe how to create a new model. This section specifically describes creating a new physics-based model. For training and creating data-driven models, see __[05 Data Driven](05_Data%20Driven.ipynb)__." + "All of the previous sections describe how to use an existing model. In this section, we will explore how to create a new physics-based model. \n", + "\n", + "A physics-based model is a model where behavior is described by the physics of the system. Physics-based models are typically parameterized, so that exact behavior of the system can be configured or learned (through parameter estimation). \n", + "\n", + "For training and creating data-driven models, see __[05 Data Driven](05_Data%20Driven.ipynb)__." ] }, { @@ -66,7 +70,7 @@ "\n", "$x$ is `state`, $u$ is `input`, $z$ is `output`, and $es$ is `event state`\n", "\n", - "Linear Models are defined by creating a new model class that inherits from progpy's LinearModel class and defines the following properties:\n", + "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", @@ -84,25 +88,23 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We will now utilize our LinearModel to model the classical physics problem throwing an object into the air. This is a common example model, the non-linear version of which (`progpy.examples.ThrownObject`) has been used frequently throughout the examples. This version of ThrownObject will behave nearly identically to the non-linear ThrownObject, except it will not have the non-linear effects of air resistance.\n", - "\n", - "We can create a subclass of LinearModel which will be used to simulate an object thrown, which we will call the ThrownObject Class.\n", + "We will now utilize our `LinearModel` to model the classical physics problem throwing an object into the air. This is a common example model, the non-linear version of which (`progpy.examples.ThrownObject`) has been used frequently throughout the examples. This version of ThrownObject will behave almost identically to the non-linear `ThrownObject`, except it will not have the non-linear effects of air resistance.\n", "\n", - "First, some definitions for our Model:\n", + "We can create a subclass of `LinearModel` to simulate an object thrown, which we will call the `ThrownObject` class. Let's start with some definitions for our model:\n", "\n", "**Events**: (2)\n", - "* `falling: The object is falling`\n", - "* `impact: The object has hit the ground`\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", + "* `x`: Position in space (m)\n", + "* `v`: Velocity in space (m/s)\n", "\n", "**Outputs/Measurements**: (1)\n", - "* `x: Position in space (m)`" + "* `x`: Position in space (m)" ] }, { @@ -121,7 +123,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "With our definitions, we can now create the ThrownObject Model.\n", + "With our definitions, we can now create the `ThrownObject` model.\n", "\n", "First, we need to import the necessary packages." ] @@ -140,7 +142,7 @@ "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.\n", + "Now we'll define some features of a `ThrownObject` `LinearModel`. Recall that all `LinearModel` classes follow a set of core equations and require some specific properties, as noted earlier. In this next step, we'll define our inputs, states, outputs, and events, along with the $A$, $C$, $E$, and $F$ values.\n", "\n", "First, let's consider state transition. For an object thrown into the air without air resistance, velocity would decrease linearly by __-9.81__ \n", "$\\dfrac{m}{s^2}$ due to the effect of gravity, as described below:\n", @@ -151,7 +153,7 @@ " \n", " $$\\frac{dx}{dt} = v$$\n", "\n", - "**Note:** For the above equation x is position not state. Combining these equations with the model $\\frac{dx}{dt}$ equation defined above yields the A and E matrix defined below. Note that there is no B defined because this model does not have any inputs." + "*__Note:__* For the above equation x is position not state. Combining these equations with the model $\\frac{dx}{dt}$ equation defined above yields the $A$ and $E$ matrix defined below. There is no $B$ defined because this model does not have any inputs.*" ] }, { @@ -176,9 +178,9 @@ "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", + "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." + "Next, we'll define some default parameters for our `ThrownObject` model." ] }, { @@ -200,7 +202,7 @@ "source": [ "In the following cells, we'll define some class functions necessary to perform prognostics on the model.\n", "\n", - "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." + "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." ] }, { @@ -221,9 +223,9 @@ "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", + "For our `threshold_met()` equation, we will define the function to return `True` for event `falling` when our model has a velocity value less than 0 (object is 'falling') and for event `impact` when our thrown object has a distance from the ground 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!" + "`threshold_met()` returns a _dict_ of values, if each entry of the _dict_ is true, then our threshold has been met." ] }, { @@ -244,7 +246,7 @@ "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." + "Finally, for our `event_state()` equation, we will calculate the measurement of progress towards the events. We will normalize our values such that they are in the range of 0 to 1, where 0 means the event has occurred." ] }, { @@ -266,7 +268,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "With these functions created, we can now use the `simulate_to_threshold()` function to simulate the movement of the thrown object in air. For more information, see 1. Simulation." + "With these functions created, we can now use the `simulate_to_threshold()` function to simulate the movement of the thrown object in air. Let's run the simulation. For more information, see __[01 Simulation](01_Simulation.ipynb)__." ] }, { @@ -276,16 +278,16 @@ "outputs": [], "source": [ "m = ThrownObject()\n", - "save = m.simulate_to_threshold(print=True, save_freq=1, events='impact', dt=0.1)" + "simulated_results = m.simulate_to_threshold(print=True, save_freq=1, events='impact', dt=0.1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "__Note__: Because our model takes in no inputs, we have no need to define a future loading function. However, for most models, there would be inputs, and thus a need for a future loading function. For more information on future loading functions and when to use them, please refer to the future loading section in 1. Simulation.\n", + "*__Note__: Because our model takes in no inputs, we have no need to define a future loading function. However, for most models, there would be inputs, and thus a need for a future loading function. For more information on future loading functions and when to use them, please refer to the future loading section in 1. Simulation.*\n", "\n", - "Let's take a look at the outputs of this model" + "Let's take a look at the outputs of this model." ] }, { @@ -294,21 +296,14 @@ "metadata": {}, "outputs": [], "source": [ - "fig = save.outputs.plot(title='generated model')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Notice that that plot resembles a parabola, which represents the position of the ball through space as time progresses!" + "fig = simulated_results.outputs.plot(title='ThrownObject model simulation', xlabel='time (s)', ylabel='position (m)')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "For more information on Linear Models, see the [Linear Model](https://nasa.github.io/progpy/api_ref/prog_models/LinearModel.html) Documentation." + "Notice that that plot resembles a parabola, which represents the position of the ball through space as time progresses. For more information on `LinearModel`, see the [LinearModel](https://nasa.github.io/progpy/api_ref/prog_models/LinearModel.html) documentation." ] }, { @@ -322,7 +317,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "In the previous section, we defined a new prognostic model using the LinearModel class. This can be a powerful tool for defining models that can be described as a linear time series. Physics-based state transition models that cannot be described linearly are constructed by subclassing [progpy.PrognosticsModel](https://nasa.github.io/progpy/api_ref/prog_models/PrognosticModel.html#prog_models.PrognosticsModel). To demonstrate this, we'll create a new model class that inherits from this class. Once constructed in this way, the analysis and simulation tools for PrognosticsModels will work on the new model.\n", + "In the previous section, we defined a new prognostic model using the `LinearModel` class. This can be a powerful tool for defining models that can be described as a linear time series. Physics-based state transition models that cannot be described linearly are constructed by subclassing [progpy.PrognosticsModel](https://nasa.github.io/progpy/api_ref/prog_models/PrognosticModel.html#prog_models.PrognosticsModel). To demonstrate this, we'll create a new model class that inherits from this class. Once constructed in this way, the analysis and simulation tools for PrognosticsModels will work on the new model.\n", "\n", "For this example, we'll create a simple state-transition model of an object thrown upward into the air without air resistance. Note that this is the same dynamic system as the linear model example above, but formulated in a different way. " ] From df033077389f21be60b06c1f44776d3a34ab91a1 Mon Sep 17 00:00:00 2001 From: Michelle Ly Date: Mon, 21 Apr 2025 09:00:26 -0700 Subject: [PATCH 04/10] fix: ensure consistency --- examples/04_New Models.ipynb | 293 ++++++++++++----------------------- 1 file changed, 95 insertions(+), 198 deletions(-) diff --git a/examples/04_New Models.ipynb b/examples/04_New Models.ipynb index 76360d5b..502cb603 100644 --- a/examples/04_New Models.ipynb +++ b/examples/04_New Models.ipynb @@ -296,7 +296,7 @@ "metadata": {}, "outputs": [], "source": [ - "fig = simulated_results.outputs.plot(title='ThrownObject model simulation', xlabel='time (s)', ylabel='position (m)')" + "fig = simulated_results.outputs.plot(title='ThrownObject model simulation output', xlabel='time (s)', ylabel='position (m)')" ] }, { @@ -317,16 +317,15 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "In the previous section, we defined a new prognostic model using the `LinearModel` class. This can be a powerful tool for defining models that can be described as a linear time series. Physics-based state transition models that cannot be described linearly are constructed by subclassing [progpy.PrognosticsModel](https://nasa.github.io/progpy/api_ref/prog_models/PrognosticModel.html#prog_models.PrognosticsModel). To demonstrate this, we'll create a new model class that inherits from this class. Once constructed in this way, the analysis and simulation tools for PrognosticsModels will work on the new model.\n", + "In the previous section, we defined a new prognostic model using the `LinearModel` class. This can be a powerful tool for defining models that can be described as a linear time series. \n", "\n", - "For this example, we'll create a simple state-transition model of an object thrown upward into the air without air resistance. Note that this is the same dynamic system as the linear model example above, but formulated in a different way. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "First, we'll import the necessary packages to create a general prognostics model." + "Physics-based state transition models that cannot be described linearly are constructed by subclassing `PrognosticsModel`. For more information, refer to the [progpy.PrognosticsModel](https://nasa.github.io/progpy/api_ref/prog_models/PrognosticModel.html#prog_models.PrognosticsModel) documentation. \n", + "\n", + "To demonstrate this, we'll create a new model class that inherits from `PrognosticsModel`. This inheritance allows the new model to use the analysis and simulation tools from `PrognosticsModel`.\n", + "\n", + "Let's create a simple state-transition model of an object thrown upwards in the air without air resistance. Note that this is the same dynamic system as the linear model example above, but formulated differently.\n", + "\n", + "First, we'll import the necessary packages and classes." ] }, { @@ -345,7 +344,7 @@ "source": [ "Next, we'll define our model class. PrognosticsModels require defining [inputs](https://nasa.github.io/progpy/glossary.html#term-input), [states](https://nasa.github.io/progpy/glossary.html#term-state), [outputs](https://nasa.github.io/progpy/glossary.html#term-output), and [event](https://nasa.github.io/progpy/glossary.html#term-event) keys. As in the above example, the states include position (`x`) and velocity(`v`) of the object, the output is position (`x`), and the events are `falling` and `impact`. \n", "\n", - "Note that we define this class as `ThrownObject_ST` to distinguish it as a state-transition model compared to the previous linear model class. " + "We will define this new class as `ThrownObject_ST` to distinguish it as a state-transition model compared to the previous linear model class. " ] }, { @@ -358,7 +357,6 @@ " \"\"\"\n", " Model that simulates an object thrown into the air without air resistance\n", " \"\"\"\n", - "\n", " inputs = [] # no inputs, no way to control\n", " states = [\n", " 'x', # Position (m) \n", @@ -387,7 +385,6 @@ "outputs": [], "source": [ "class ThrownObject_ST(ThrownObject_ST):\n", - "\n", " default_parameters = {\n", " 'thrower_height': 1.83, # default height \n", " 'throwing_speed': 40, # default speed\n", @@ -399,25 +396,18 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "All prognostics models require some specific class functions. We'll define those next. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "First, we'll need to add the functionality to set the initial state of the system. There are two ways to provide the logic to initialize model state. \n", + "All prognostics models require some specific class functions. We'll define those next.\n", "\n", - "1. Provide the initial state in `parameters['x0']`, or \n", - "2. Provide an `initialize` function \n", + "We'll first add the functionality to set the initial state of the system. There are two ways to provide the logic to initialize model state. \n", "\n", - "The first method here is preferred. If `parameters['x0']` are defined, there is no need to explicitly define an initialize method, and these parameter values will be used as the initial state. \n", + "1. Provide the initial state in `parameters['x0']`\n", + "2. Provide an `initialize` function \n", "\n", - "However, there are some cases where the initial state is a function of the input (`u`) or output (`z`) (e.g. a use-case where the input is also a state). In this case, an explicitly defined `initialize` method is required. \n", + "The first method is preferred since defining `parameters['x0']` means we don't need to explicitly define an `initialize` method as these parameter values will already be used as the initial state.\n", "\n", - "Here, we'll set our initial state by defining an `initialize` function. In the code below, note that the function can take arguments for both input `u` and output `z`, though these are optional. \n", + "However, there are some cases where the initial state is a function of the input (`u`) or output (`z`) (e.g. a use-case where the input is also a state), so an explicitly defined `initialize` method is required. \n", "\n", - "Note that for this example, defining initialize in this way is not necessary. We could have simply defined `parameters['x0']`. However, we choose to use this method for ease when using the `derived_params` feature, discussed in the next section. " + "In this example, we could set our initial state by simply defining `parameters['x0']`. However, we will use an `initialize` function for ease when using the `derived_params` feature, which will be discussed in the next section. In the code below, note that the function can take arguments for both input `u` and output `z`, though these are optional." ] }, { @@ -427,11 +417,10 @@ "outputs": [], "source": [ "class ThrownObject_ST(ThrownObject_ST):\n", - "\n", " def initialize(self, u=None, z=None):\n", " return self.StateContainer({\n", - " 'x': self['thrower_height'], # Thrown, so initial altitude is height of thrower\n", - " 'v': self['throwing_speed'] # Velocity at which the ball is thrown - this guy is a professional baseball pitcher\n", + " 'x': self['thrower_height'], # Initial height from which the ball is thrown\n", + " 'v': self['throwing_speed'] # Velocity at which the ball is thrown\n", " })" ] }, @@ -439,9 +428,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Next, the PrognosticsModel class requires that we define how the state transitions throughout time. For continuous models, this is defined with the method `dx`, which calculates the first derivative of the state at a specific time. For discrete systems, this is defined with the method `next_state`, using the state transition equation for the system. When possible, it is recommended to use the continuous (`dx`) form, as some algorithms will only work on continuous models.\n", + "The `PrognosticsModel` class requires that we define how the state transitions throughout time. For continuous models, this is defined with the method `dx`, which calculates the first derivative of the state at a specific time. For discrete systems, this is defined with the method `next_state`, using the state transition equation for the system. When possible, it is recommended to use the continuous (`dx`) form, as some algorithms will only work on continuous models.\n", "\n", - "Here, we use the equations for the derivatives of our system (i.e., the continuous form)." + "Here, we will use the equations for the derivatives of our system (i.e., the continuous form)." ] }, { @@ -451,7 +440,6 @@ "outputs": [], "source": [ "class ThrownObject_ST(ThrownObject_ST):\n", - "\n", " def dx(self, x, u):\n", " return self.StateContainer({\n", " 'x': x['v'], \n", @@ -472,7 +460,6 @@ "outputs": [], "source": [ "class ThrownObject_ST(ThrownObject_ST):\n", - " \n", " def output(self, x):\n", " return self.OutputContainer({'x': x['x']})" ] @@ -482,7 +469,7 @@ "metadata": {}, "source": [ "The next method we define is [`event_state`](https://nasa.github.io/progpy/glossary.html#term-event-state). As before, \n", - "`event_state` calculates the progress towards the events. Normalized to be between 0 and 1, 1 means there is no progress towards the event and 0 means the event has occurred. " + "`event_state` calculates the progress towards the events. This is normalized to be between 0 and 1, where 1 means there is no progress towards the event and 0 means the event has occurred. " ] }, { @@ -492,7 +479,6 @@ "outputs": [], "source": [ "class ThrownObject_ST(ThrownObject_ST):\n", - " \n", " def event_state(self, x): \n", " # Use speed and position to estimate maximum height\n", " x_max = x['x'] + np.square(x['v'])/(-self['g']*2)\n", @@ -508,16 +494,11 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "At this point, we have defined all necessary information for the PrognosticsModel to be complete. There are other methods that can additionally be defined (see the [PrognosticsModel](https://nasa.github.io/progpy/api_ref/prog_models/PrognosticModel.html) documentation for more information) to provide further configuration for new models. We'll highlight some of these in the following sections. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As an example of one of these, we additionally define a `threshold_met` equation. Note that this is optional. Leaving `threshold_met` empty will use the event state to define thresholds (threshold = event state == 0). However, this implementation is more efficient, so we include it. \n", + "At this point, we have defined all necessary information for our new model to be complete. There are other methods that can be defined to provide additional configuration, and we'll highlight some of them in the following sections. We can also refer to the [PrognosticsModel](https://nasa.github.io/progpy/api_ref/prog_models/PrognosticModel.html) documentation for more information.\n", + "\n", + "For example, we can optionally include a `threshold_met` equation. Without an explicit definition, `threshold_met` will use the event state to define thresholds (threshold = event state == 0). However, this implementation is more efficient, so we will include it.\n", "\n", - "Here, we define `threshold_met` in the same way as our linear model example. `threshold_met` will return a _dict_ of values, one for each event. Threshold is met when all dictionary entries are __True__. " + "We will define `threshold_met` in the same way as our linear model example. `threshold_met` will return a _dict_ of values, one for each event. The threshold will be met when all dictionary entries are `True`. " ] }, { @@ -527,7 +508,6 @@ "outputs": [], "source": [ "class ThrownObject_ST(ThrownObject_ST):\n", - "\n", " def threshold_met(self, x):\n", " return {\n", " 'falling': x['v'] < 0, # Falling occurs when velocity becomes negative\n", @@ -539,14 +519,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "With that, we have created a new ThrownObject state-transition model. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now let's can test our model through simulation. First, we'll create an instance of the model." + "We have now created a new `ThrownObject_ST` model. Let's now test our model through simulation. \n", + "\n", + "First, we'll create an instance of the model." ] }, { @@ -562,7 +537,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We'll start by simulating to impact. We'll specify the `events` to specifically indicate we are interested in impact. For more information on simulation, see 1. Simulation. " + "We'll then simulate to impact, as specified in `events`. For more information on how simulation work, refer to __[01 Simulation](01_Simulation.ipynb)__. " ] }, { @@ -583,33 +558,46 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "To summarize this section, we have illustrated how to construct new physics-based models by subclassing from [progpy.PrognosticsModel](https://nasa.github.io/progpy/api_ref/prog_models/PrognosticModel.html#prog_models.PrognosticsModel). Some elements (e.g. inputs, states, outputs, events keys; methods for initialization, dx/next_state, output, and event_state) are required. Models can be additionally configured with additional methods and parameters.\n", - "\n", - "Note that in this example, we defined each part one piece at a time, recursively subclassing the partially defined class. This was done to illustrate the parts of the model. In reality, all methods and properties would be defined together in a single class definition. " + "Let's now plot the results." ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ - "## Derived Parameters" + "fig = simulated_results.outputs.plot(title='ThrownObject_ST model simulation output', xlabel='time (s)', ylabel='position (m)')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "In the previous section, we constructed a new model from scratch by subclassing from [progpy.PrognosticsModel](https://nasa.github.io/progpy/api_ref/prog_models/PrognosticModel.html#prog_models.PrognosticsModel) and specifying all of the necessary model components. An additional optional feature of PrognosticsModels is derived parameters, illustrated below. \n", + "We can see the parabolic shape of the object being thrown in the air.\n", "\n", - "A derived parameter is a parameter (see parameter section in 1. Simulation) that is a function of another parameter. For example, in the case of a thrown object, one could assume that throwing speed is a function of thrower height, with taller throwing height resulting in faster throwing speeds. In the electrochemistry battery model (see 3. Included Models), there are parameters for the maximum and minimum charge at the surface and bulk, and these are dependent on the capacity of the battery (i.e. another parameter, qMax). When such derived parameters exist, they must be updated whenever the parameters they depend on are updated. In PrognosticsModels, this is achieved with the `derived_params` feature. \n", + "We have so far illustrated how to construct new physics-based models by subclassing from [progpy.PrognosticsModel](https://nasa.github.io/progpy/api_ref/prog_models/PrognosticModel.html#prog_models.PrognosticsModel). Some elements (e.g. inputs, states, outputs, events keys; methods for initialization, dx/next_state, output, and event_state) are required. Models can be additionally configured with additional methods and parameters.\n", "\n", - "This feature can also be used to cache combinations of parameters that are used frequently in state transition or other model methods. Creating lumped parameters using `derived_params` causes them to be calculated once when configuring, instead of each time step in simulation or prediction. " + "*__Note__: in the previous example, we defined each part one piece at a time, recursively subclassing the partially defined class. This was done to illustrate the parts of the model. In actual usage, all methods and properties should be defined together in a single class definition.* " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Derived Parameters" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ + "In the previous section, we constructed a new model from scratch by subclassing from [progpy.PrognosticsModel](https://nasa.github.io/progpy/api_ref/prog_models/PrognosticModel.html#prog_models.PrognosticsModel) and specifying all of the necessary model components. An additional optional feature of PrognosticsModels is derived parameters, illustrated below. \n", + "\n", + "A derived parameter is a parameter that is a function of another parameter. For example, in the case of a thrown object, one could assume that throwing speed is a function of thrower height, with taller throwing height resulting in faster throwing speeds. In the electrochemistry battery model (see __[03 Included Models](03_Existing%20Models.ipynb)__), there are parameters for the maximum and minimum charge at the surface and bulk, and these are dependent on the capacity of the battery (i.e. another parameter, `qMax`). When such derived parameters exist, they must be updated whenever the parameters they depend on are updated. In `PrognosticsModels`, this is achieved with the `derived_params` feature. \n", + "\n", + "This feature can also be used to cache combinations of parameters that are used frequently in state transition or other model methods. Creating lumped parameters using `derived_params` causes them to be calculated once when configuring, instead of each time step in simulation or prediction. \n", + "\n", "For this example, we will use the `ThrownObject_ST` model created in the previous section. We will extend this model to include a derived parameter, namely `throwing_speed` will be dependent on `thrower_height`.\n", "\n", "To implement this, we must first define a function for the relationship between the two parameters. We'll assume that `throwing_speed` is a linear function of `thrower_height`. " @@ -652,13 +640,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "You can also have more than one function be called when a single parameter is changed. You would do this by adding the additional callbacks to the list (e.g., 'thrower_height': [update_thrown_speed, other_fcn])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ + "You can also have more than one function be called when a single parameter is changed. You would do this by adding the additional callbacks to the list (e.g., 'thrower_height': [update_thrown_speed, other_fcn])\n", + "\n", "We have now added the capability for `throwing_speed` to be a derived parameter. Let's try it out. First, we'll create an instance of our class and print out the default parameters. " ] }, @@ -709,20 +692,10 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "In the previous sections, we illustrated how to create and use state-transition models, or models that use state transition differential equations to propagate the state forward. In this example, we'll explore another type of model implemented within ProgPy - Direct Models. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Direct models estimate the time of event directly from the system state and future load, rather than through state transitions. This approach is particularly useful for physics-based models where the differential equations of state transitions can be explicitly solved, or for data-driven models that map sensor data directly to the time of an event. When applicable, using a direct model approach provides a more efficient way to estimate the time of an event, especially for events that occur later in the simulation. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ + "In the previous sections, we illustrated how to create and use state-transition models, or models that use state transition differential equations to propagate the state forward. In this example, we'll explore another type of model implemented within ProgPy: direct models. \n", + "\n", + "Direct models estimate the time of event directly from the system state and future load, rather than through state transitions. This approach is particularly useful for physics-based models where the differential equations of state transitions can be explicitly solved, or for data-driven models that map sensor data directly to the time of an event. When applicable, using a direct model approach provides a more efficient way to estimate the time of an event, especially for events that occur later in the simulation.\n", + "\n", "To illustrate this concept, we will extend the state-transition model, `ThrownObject_ST`, defined above, to create a new model class, `DirectThrownObject`. The dynamics of a thrown object lend easily to a direct model, since we can solve the differential equations explicitly to estimate the time at which the events occur. \n", "\n", "Recall that our physical system is described by the following differential equations: \n", @@ -754,7 +727,6 @@ "outputs": [], "source": [ "class DirectThrownObject(ThrownObject_ST):\n", - " \n", " def time_of_event(self, x, *args, **kwargs):\n", " # calculate time when object hits ground given x['x'] and x['v']\n", " # 0 = x0 + v0*t - 0.5*g*t^2\n", @@ -764,7 +736,7 @@ " # 0 = v0 - g*t\n", " t_falling = -x['v']/g\n", " \n", - " return {'falling': t_falling, 'impact': t_impact}\n" + " return {'falling': t_falling, 'impact': t_impact}" ] }, { @@ -829,15 +801,10 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Notice that execution is significantly faster for the direct model. Furthermore, the result is actually more accurate, since it's not limited by the timestep (see dt section in 1. Simulation). These observations will be even more pronounced for events that occur later in the simulation. \n", + "Notice that execution is significantly faster for the direct model. Furthermore, the result is actually more accurate, since it's not limited by the timestep (see dt section in __[01 Simulation](01_Simulation.ipynb)__). These observations will be even more pronounced for events that occur later in the simulation. \n", + "\n", + "It's important to note that this is a very simple example, as there are no inputs. For models with inputs, future loading must be provided to `time_of_event` (see the future loading section in __[01 Simulation](01_Simulation.ipynb)__). In these cases, most direct models will encode or discretize the future loading profile to use it in a direct estimation of time of event.\n", "\n", - "It's important to note that this is a very simple example, as there are no inputs. For models with inputs, future loading must be provided to `time_of_event` (see the Future Loading section in 1. Simulation). In these cases, most direct models will encode or discretize the future loading profile to use it in a direct estimation of time of event." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ "In the example provided, we have illustrated how to use a direct model. Direct models are a powerful tool for estimating the time of an event directly from the system state. By avoiding the process of state transitions, direct models can provide more efficient event time estimates. Additionally, the direct model approach is not limited to physics-based models. It can also be applied to data-driven models that can map sensor data directly to the time of an event. \n", "\n", "In conclusion, direct models offer an efficient and versatile approach for prognostics modeling, enabling faster and more direct estimations of event times. " @@ -856,20 +823,10 @@ "source": [ "In the above models, we have used dictionaries to represent the states. For example, in the implementation of `ThrownObject_ST` above, see how `dx` is defined with a StateContainer dictionary. While all models can be constructed using dictionaries in this way, some dynamical systems allow for the state of the system to be represented with a matrix. For such use-cases, ProgPy has an advanced *matrix data access feature* that provides a more efficient way to define these models.\n", "\n", - "In ProgPy's implementation, the provided model.StateContainer, InputContainer, and OutputContainers can be treated as dictionaries but use an underlying matrix. This is important for some applications like surrogate and machine-learned models where the state is represented by a tensor. ProgPy's *matrix data access feature* allows the matrices to be used directly. Simulation functions propagate the state using the matrix form, preventing the inefficiency of having to convert to and from dictionaries. Additionally, this implementation is faster than recreating the StateContainer each time, especially when updating inplace." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In this example, we'll illustrate how to use the matrix data access feature. We'll continue with our ThrownObject system, and create a model to simulate this using matrix notation (instead of dictionary notation as in the standard model, seen above in `ThrownObject_ST`). The implementation of the model is comparable to a standard model, except that it uses matrix operations within each function, as seen below. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ + "In ProgPy's implementation, the provided model.StateContainer, InputContainer, and OutputContainers can be treated as dictionaries but use an underlying matrix. This is important for some applications like surrogate and machine-learned models where the state is represented by a tensor. ProgPy's *matrix data access feature* allows the matrices to be used directly. Simulation functions propagate the state using the matrix form, preventing the inefficiency of having to convert to and from dictionaries. Additionally, this implementation is faster than recreating the StateContainer each time, especially when updating inplace.\n", + "\n", + "In this example, we'll illustrate how to use the matrix data access feature. We'll continue with our ThrownObject system, and create a model to simulate this using matrix notation (instead of dictionary notation as in the standard model, seen above in `ThrownObject_ST`). The implementation of the model is comparable to a standard model, except that it uses matrix operations within each function, as seen below. \n", + "\n", "First, the necessary imports." ] }, @@ -915,14 +872,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Our model is now specified. Let's try simulating with it." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "First, we'll create an instance of the model." + "Our model is now specified. Let's try simulating with it.\n", + "\n", + "Let's create an instance of the model." ] }, { @@ -991,13 +943,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "As we can see, for this system, using the matrix data access feature is computationally faster than a standard state-transition matrix that uses dictionaries. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ + "As we can see, for this system, using the matrix data access feature is computationally faster than a standard state-transition matrix that uses dictionaries.\n", + "\n", "As illustrated here, the matrix data access feature is an advanced capability that represents the state of a system using matrices. This can provide efficiency for use-cases where the state is easily represented by a tensor and operations are defined by matrices." ] }, @@ -1012,13 +959,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "In real-world physical systems, there are often constraints on what values the states can take. For example, in the case of a thrown object, if we define our reference frame with the ground at a position of $x=0$, then the position of the object should only be greater than or equal to 0, and should never take on negative values. In ProgPy, we can enforce constraints on the range of each state for a state-transition model using the [state limits](https://nasa.github.io/progpy/prog_models_guide.html#state-limits) attribute. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ + "In real-world physical systems, there are often constraints on what values the states can take. For example, in the case of a thrown object, if we define our reference frame with the ground at a position of $x=0$, then the position of the object should only be greater than or equal to 0, and should never take on negative values. In ProgPy, we can enforce constraints on the range of each state for a state-transition model using the [state limits](https://nasa.github.io/progpy/prog_models_guide.html#state-limits) attribute. \n", + "\n", "To illustrate the use of `state_limits`, we'll use our thrown object model `ThrownObject_ST`, created in an above section. " ] }, @@ -1057,13 +999,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Notice that at the end of the simulation, the object's position (`x`) is negative. This doesn't make sense physically, since the object cannot fall below ground level (at $x=0$)." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ + "Notice that at the end of the simulation, the object's position (`x`) is negative. This doesn't make sense physically, since the object cannot fall below ground level (at $x=0$).\n", + "\n", "To avoid this, and keep the state in a realistic range, we can change the `state_limits` attribute of the model. The `state_limits` attribute is a dictionary that contains the state limits for each state. The keys of the dictionary are the state names, and the values are tuples that contain the lower and upper limits of the state. \n", "\n", "In our Thrown Object model, our states are position, which can range from 0 to infinity, and velocity, which we'll limit to not exceed the speed of light." @@ -1188,22 +1125,12 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "In the examples above, we have focused on the simple event of a thrown object hitting the ground or reaching `impact`. In this section, we highlight additional uses of ProgPy's generalizable concept of `events`. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ + "In the examples above, we have focused on the simple event of a thrown object hitting the ground or reaching `impact`. In this section, we highlight additional uses of ProgPy's generalizable concept of `events`. \n", + "\n", "The term [events](https://nasa.github.io/progpy/prog_models_guide.html#events) is used to describe something to be predicted. Generally in the PHM community, these are referred to as End of Life (EOL). However, they can be much more. \n", "\n", - "In ProgPy, events can be anything that needs to be predicted. Systems will often have multiple failure modes, and each of these modes can be represented by a separate event. Additionally, events can also be used to predict other events of interest other than failure, such as special system states or warning thresholds. Thus, `events` in ProgPy can represent End of Life (EOL), End of Mission (EOM), warning thresholds, or any Event of Interest (EOI). " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ + "In ProgPy, events can be anything that needs to be predicted. Systems will often have multiple failure modes, and each of these modes can be represented by a separate event. Additionally, events can also be used to predict other events of interest other than failure, such as special system states or warning thresholds. Thus, `events` in ProgPy can represent End of Life (EOL), End of Mission (EOM), warning thresholds, or any Event of Interest (EOI). \n", + "\n", "There are a few components of the model that must be specified in order to define events:\n", "\n", "1. The `events` property defines the expected events \n", @@ -1212,27 +1139,12 @@ "\n", "3. The `event_state` method returns an estimate of progress towards the threshold \n", "\n", - "Note that because of the interconnected relationship between `threshold_met` and `event_state`, it is only required to define one of these. However, it is generally beneficial to specify both. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To illustrate this concept, we will use the `BatteryElectroChemEOD` model (see section 03. Included Models). In the standard implementation of this model, the defined event is `EOD` or End of Discharge. This occurs when the voltage drops below a pre-defined threshold value. The State-of-Charge (SOC) of the battery is the event state for the EOD event. Recall that event states (and therefore SOC) vary between 0 and 1, where 1 is healthy and 0 signifies the event has occurred. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Suppose we have the requirement that our battery must not fall below 5% State-of-Charge. This would correspond to an `EOD` event state of 0.05. Additionally, let's add events for two warning thresholds, a $\\text{\\textcolor{yellow}{yellow}}$ threshold at 15% SOC and a $\\text{\\textcolor{red}{red}}$ threshold at 10% SOC. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ + "Note that because of the interconnected relationship between `threshold_met` and `event_state`, it is only required to define one of these. However, it is generally beneficial to specify both. \n", + "\n", + "To illustrate this concept, we will use the `BatteryElectroChemEOD` model (see section 03. Included Models). In the standard implementation of this model, the defined event is `EOD` or End of Discharge. This occurs when the voltage drops below a pre-defined threshold value. The State-of-Charge (SOC) of the battery is the event state for the EOD event. Recall that event states (and therefore SOC) vary between 0 and 1, where 1 is healthy and 0 signifies the event has occurred. \n", + "\n", + "Suppose we have the requirement that our battery must not fall below 5% State-of-Charge. This would correspond to an `EOD` event state of 0.05. Additionally, let's add events for two warning thresholds, a $\\text{\\textcolor{yellow}{yellow}}$ threshold at 15% SOC and a $\\text{\\textcolor{red}{red}}$ threshold at 10% SOC. \n", + "\n", "To define the model, we'll start with the necessary imports." ] }, @@ -1398,13 +1310,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Here, we can see the SOC plotted for the different events throughout time. The yellow warning (15% SOC) reaches threshold first, followed by the red warning (10% SOC), new EOD threshold (5% SOC), and finally the original EOD value. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ + "Here, we can see the SOC plotted for the different events throughout time. The yellow warning (15% SOC) reaches threshold first, followed by the red warning (10% SOC), new EOD threshold (5% SOC), and finally the original EOD value. \n", + "\n", "In this section, we have illustrated how to define custom [events](https://nasa.github.io/progpy/prog_models_guide.html#events) for prognostics models. Events can be used to define anything that a user is interested in predicting, including common values like Remaining Useful Life (RUL) and End of Discharge (EOD), as well as other values like special intermediate states or warning thresholds. " ] }, @@ -1423,13 +1330,8 @@ "\n", "Model serialization has a variety of purposes. For example, serialization allows us to save a specific model or model configuration to a file to be loaded later, or can aid us in sending a model to another machine over a network connection. Some users maintain a directory or repository of configured models representing specific systems in their stock.\n", "\n", - "In this section, we'll show how to serialize and deserialize model objects using `pickle` and `JSON` methods. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ + "In this section, we'll show how to serialize and deserialize model objects using `pickle` and `JSON` methods. \n", + "\n", "First, we'll import the necessary modules." ] }, @@ -1466,13 +1368,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "First, we'll serialize the model in two different ways using 1) `pickle` and 2) `JSON`. Then, we'll plot the results from simulating the deserialized models to show equivalence of the methods. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ + "First, we'll serialize the model in two different ways using 1) `pickle` and 2) `JSON`. Then, we'll plot the results from simulating the deserialized models to show equivalence of the methods. \n", + "\n", "To save using the `pickle` package, we'll serialize the model using the `dump` method. Once saved, we can then deserialize using the `load` method. In practice, deserializing will likely occur in a different file or in a later use-case, but here we deserialize to show equivalence of the saved model. " ] }, @@ -1698,7 +1595,7 @@ "source": [ "Next we define parameters. In this case the parameters are the initial SOC state (1) and the E_crit (Internal Total Energy). We get the value for $E_{crit}$ from the paper.\n", "\n", - "**Note:** This won't actually subclass in practice, but it's used to break apart model definition into chunks." + "***Note:** This won't actually subclass in practice, but it's used to break apart model definition into chunks.*" ] }, { @@ -1779,9 +1676,9 @@ "source": [ "There is one output here (v, voltage), the same one input (P, Power), and a few lumped parameters: $v_L$, $\\lambda$, $\\gamma$, $\\mu$, $\\beta$, and $R_{int}$. The default parameters are found in the paper.\n", "\n", - "Note that $\\eta$ is the measurement noise, which progpy handles, so that's omitted from the equation below.\n", + "$\\eta$ is the measurement noise, which ProgPy handles, so that's omitted from the equation below.\n", "\n", - "Note 2: There is a typo in the paper where the sign of the second term in the $v_{oc}$ term. It should be negative (like above), but is reported as positive in the paper.\n", + "***Note**: There is a typo in the paper where the sign of the second term in the $v_{oc}$ term. It should be negative (like above), but is reported as positive in the paper.*\n", "\n", "We can update the definition of the model to include this output and parameters." ] @@ -1814,9 +1711,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Note that the input ($P(k)$) is also used in the output equation, that means it's part of the state of the system. So we will update the states to include this.\n", + "The input ($P(k)$) is also used in the output equation, which means it's part of the state of the system. So we will update the states to include this.\n", "\n", - "**Note:** We change to next_state. Above, we define state transition with ProgPy's `dx` method because the model was continuous. Here, with the addition of power, the model becomes discrete, so we must now use ProgPy's `next_state` method to define state transition. " + "***Note:** We change to next_state. Above, we define state transition with ProgPy's `dx` method because the model was continuous. Here, with the addition of power, the model becomes discrete, so we must now use ProgPy's `next_state` method to define state transition.*" ] }, { From bba90ab0dceb2c6649d26ed3f15bc53e33bb07cc Mon Sep 17 00:00:00 2001 From: Michelle Ly <59234861+lymichelle21@users.noreply.github.com> Date: Wed, 23 Apr 2025 12:49:56 -0700 Subject: [PATCH 05/10] Apply suggestions from code review Co-authored-by: Christopher Teubert Co-authored-by: Katy Jarvis Griffith <55932920+kjjarvis@users.noreply.github.com> --- examples/04_New Models.ipynb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/04_New Models.ipynb b/examples/04_New Models.ipynb index 502cb603..394fab27 100644 --- a/examples/04_New Models.ipynb +++ b/examples/04_New Models.ipynb @@ -153,7 +153,7 @@ " \n", " $$\\frac{dx}{dt} = v$$\n", "\n", - "*__Note:__* For the above equation x is position not state. Combining these equations with the model $\\frac{dx}{dt}$ equation defined above yields the $A$ and $E$ matrix defined below. There is no $B$ defined because this model does not have any inputs.*" + "For the above equation x is position not state. Combining these equations with the model $\\frac{dx}{dt}$ equation defined above yields the $A$ and $E$ matrix defined below. There is no $B$ defined because this model does not have any inputs." ] }, { @@ -537,7 +537,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We'll then simulate to impact, as specified in `events`. For more information on how simulation work, refer to __[01 Simulation](01_Simulation.ipynb)__. " + "We'll then simulate to impact, as specified in `events`. For more information on how simulation works, refer to __[01 Simulation](01_Simulation.ipynb)__. " ] }, { @@ -1139,7 +1139,7 @@ "\n", "3. The `event_state` method returns an estimate of progress towards the threshold \n", "\n", - "Note that because of the interconnected relationship between `threshold_met` and `event_state`, it is only required to define one of these. However, it is generally beneficial to specify both. \n", + "Note that because of the interconnected relationship between `threshold_met` and `event_state`, it is only required to define one of these. However, there are frequently computational advantages to specifying both. \n", "\n", "To illustrate this concept, we will use the `BatteryElectroChemEOD` model (see section 03. Included Models). In the standard implementation of this model, the defined event is `EOD` or End of Discharge. This occurs when the voltage drops below a pre-defined threshold value. The State-of-Charge (SOC) of the battery is the event state for the EOD event. Recall that event states (and therefore SOC) vary between 0 and 1, where 1 is healthy and 0 signifies the event has occurred. \n", "\n", From c29768b324fa848c70acbb7bf3086190872bcae6 Mon Sep 17 00:00:00 2001 From: Christopher Teubert Date: Mon, 21 Apr 2025 16:31:09 -0700 Subject: [PATCH 06/10] Updating supported python versions --- sphinx-config/troubleshooting.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx-config/troubleshooting.rst b/sphinx-config/troubleshooting.rst index a71b1e2c..073eabae 100644 --- a/sphinx-config/troubleshooting.rst +++ b/sphinx-config/troubleshooting.rst @@ -30,4 +30,4 @@ If you are using data-driven tools (e.g., LSTM model), make sure the datadriven Installing ProgPy Data-Driven Tools with Python 3.13 ------------------------ -Tensorflow does not support Python3.13 as of the writing of this (April 2025). Until this is fixed, ProgPy data-driven features may not work correctly. If you are having trouble running data-driven features with Python3.13, try with an earlier version of Python. +Tensorflow does not support python3.13 as of the writing of this. Until this is fixed, ProgPy data-driven features may not work correctly. If you are having trouble running data-driven features with Python3.13, try with an earlier version of Python. From d07ab09cf5efcf2d6c371811ed47cbfbd17b1d39 Mon Sep 17 00:00:00 2001 From: Christopher Teubert Date: Tue, 22 Apr 2025 08:44:52 -0700 Subject: [PATCH 07/10] Fix troubleshooting page --- sphinx-config/troubleshooting.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx-config/troubleshooting.rst b/sphinx-config/troubleshooting.rst index 073eabae..a71b1e2c 100644 --- a/sphinx-config/troubleshooting.rst +++ b/sphinx-config/troubleshooting.rst @@ -30,4 +30,4 @@ If you are using data-driven tools (e.g., LSTM model), make sure the datadriven Installing ProgPy Data-Driven Tools with Python 3.13 ------------------------ -Tensorflow does not support python3.13 as of the writing of this. Until this is fixed, ProgPy data-driven features may not work correctly. If you are having trouble running data-driven features with Python3.13, try with an earlier version of Python. +Tensorflow does not support Python3.13 as of the writing of this (April 2025). Until this is fixed, ProgPy data-driven features may not work correctly. If you are having trouble running data-driven features with Python3.13, try with an earlier version of Python. From d9845703d70720affd561cf9b91282591ddf0414 Mon Sep 17 00:00:00 2001 From: Michelle Ly Date: Wed, 23 Apr 2025 13:59:14 -0700 Subject: [PATCH 08/10] fix: add labels and address comments --- examples/04_New Models.ipynb | 332 ++++++++++++++++++----------------- 1 file changed, 169 insertions(+), 163 deletions(-) diff --git a/examples/04_New Models.ipynb b/examples/04_New Models.ipynb index 394fab27..f31cd61b 100644 --- a/examples/04_New Models.ipynb +++ b/examples/04_New Models.ipynb @@ -24,13 +24,14 @@ "source": [ "## Table of Contents\n", "* [Linear Models](#Linear-Models)\n", - "* [New State Transition Models](#New-State-Transition-Models)\n", - "* [Derived Parameters](#Derived-Parameters)\n", + "* [State Transition Models](#State-Transition-Models)\n", "* [Direct Models](#Direct-Models)\n", - "* [Matrix Data Access Feature](#Matrix-Data-Access-Feature)\n", - "* [State Limits](#State-Limits)\n", - "* [Custom Events](#Custom-Events)\n", - "* [Serialization](#Serialization)\n", + "* [Advanced Features](#Advanced-Features)\n", + " * [Derived Parameters](#Derived-Parameters)\n", + " * [Matrix Data Access](#Matrix-Data-Access)\n", + " * [State Limits](#State-Limits)\n", + " * [Custom Events](#Custom-Events)\n", + " * [Serialization](#Serialization)\n", "* [Simplified Battery Model Example](#Simplified-Battery-Model-Example)\n", " * [State Transition](#State-Transition)\n", " * [Outputs](#Outputs)\n", @@ -144,7 +145,7 @@ "source": [ "Now we'll define some features of a `ThrownObject` `LinearModel`. Recall that all `LinearModel` classes follow a set of core equations and require some specific properties, as noted earlier. In this next step, we'll define our inputs, states, outputs, and events, along with the $A$, $C$, $E$, and $F$ values.\n", "\n", - "First, let's consider state transition. For an object thrown into the air without air resistance, velocity would decrease linearly by __-9.81__ \n", + "First, let's consider state transition. For an object thrown into the air without air resistance, velocity would decrease linearly 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", @@ -285,7 +286,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "*__Note__: Because our model takes in no inputs, we have no need to define a future loading function. However, for most models, there would be inputs, and thus a need for a future loading function. For more information on future loading functions and when to use them, please refer to the future loading section in 1. Simulation.*\n", + "Since our model takes in no inputs, we have no need to define a future loading function. However, for most models, there would be inputs, and thus a need for a future loading function. For more information on future loading functions and when to use them, please refer to the future loading section in __[01 Simulation](01_Simulation.ipynb)__.\n", "\n", "Let's take a look at the outputs of this model." ] @@ -310,7 +311,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## New State Transition Models" + "## State Transition Models" ] }, { @@ -576,51 +577,48 @@ "source": [ "We can see the parabolic shape of the object being thrown in the air.\n", "\n", - "We have so far illustrated how to construct new physics-based models by subclassing from [progpy.PrognosticsModel](https://nasa.github.io/progpy/api_ref/prog_models/PrognosticModel.html#prog_models.PrognosticsModel). Some elements (e.g. inputs, states, outputs, events keys; methods for initialization, dx/next_state, output, and event_state) are required. Models can be additionally configured with additional methods and parameters.\n", + "We have so far illustrated how to construct new physics-based models by subclassing from [progpy.PrognosticsModel](https://nasa.github.io/progpy/api_ref/prog_models/PrognosticModel.html#prog_models.PrognosticsModel). Some elements (e.g. `inputs`, `states`, `outputs`, events keys, methods for initialization, `dx` or `next_state`, `output`, and `event_state`) are required. Models can be additionally configured with additional methods and parameters.\n", "\n", - "*__Note__: in the previous example, we defined each part one piece at a time, recursively subclassing the partially defined class. This was done to illustrate the parts of the model. In actual usage, all methods and properties should be defined together in a single class definition.* " + "In the previous example, we defined each part one piece at a time, recursively subclassing the partially defined class. This was done to illustrate the parts of the model. In actual usage, all methods and properties should be defined together in a single class definition." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Derived Parameters" + "## Direct Models" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "In the previous section, we constructed a new model from scratch by subclassing from [progpy.PrognosticsModel](https://nasa.github.io/progpy/api_ref/prog_models/PrognosticModel.html#prog_models.PrognosticsModel) and specifying all of the necessary model components. An additional optional feature of PrognosticsModels is derived parameters, illustrated below. \n", + "In the previous sections, we illustrated how to create and use state-transition models, or models that use state transition differential equations to propagate the state forward. In this example, we'll explore another type of model implemented within ProgPy: direct models. \n", "\n", - "A derived parameter is a parameter that is a function of another parameter. For example, in the case of a thrown object, one could assume that throwing speed is a function of thrower height, with taller throwing height resulting in faster throwing speeds. In the electrochemistry battery model (see __[03 Included Models](03_Existing%20Models.ipynb)__), there are parameters for the maximum and minimum charge at the surface and bulk, and these are dependent on the capacity of the battery (i.e. another parameter, `qMax`). When such derived parameters exist, they must be updated whenever the parameters they depend on are updated. In `PrognosticsModels`, this is achieved with the `derived_params` feature. \n", + "Direct models estimate the time of event directly from the system state and future load, rather than through state transitions. This approach is particularly useful for physics-based models where the differential equations of state transitions can be explicitly solved, or for data-driven models that map sensor data directly to the time of an event. When applicable, using a direct model approach provides a more efficient way to estimate the time of an event, especially for events that occur later in the simulation.\n", "\n", - "This feature can also be used to cache combinations of parameters that are used frequently in state transition or other model methods. Creating lumped parameters using `derived_params` causes them to be calculated once when configuring, instead of each time step in simulation or prediction. \n", + "To illustrate this concept, we will extend the state-transition model, `ThrownObject_ST`, defined above, to create a new model class, `DirectThrownObject`. The dynamics of a thrown object lend easily to a direct model, since we can solve the differential equations explicitly to estimate the time at which the events occur. \n", "\n", - "For this example, we will use the `ThrownObject_ST` model created in the previous section. We will extend this model to include a derived parameter, namely `throwing_speed` will be dependent on `thrower_height`.\n", + "Recall that our physical system is described by the following differential equations: \n", + "\\begin{align*}\n", + "\\frac{dx}{dt} &= v \\\\ \\\\\n", + "\\frac{dv}{dt} &= -g \n", + "\\end{align*}\n", "\n", - "To implement this, we must first define a function for the relationship between the two parameters. We'll assume that `throwing_speed` is a linear function of `thrower_height`. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def update_thrown_speed(params):\n", - " return {\n", - " 'throwing_speed': params['thrower_height'] * 21.85\n", - " } \n", - " # Note: one or more parameters can be changed in these functions, whatever parameters are changed are returned in the dictionary" + "This can be solved explicity given initial position $x_0$ and initial velocity $v_0$:\n", + "\\begin{align*}\n", + "x(t) &= -\\frac{1}{2} gt^2 + v_0 t + x_0 \\\\ \\\\ \n", + "v(t) &= -gt + v_0\n", + "\\end{align*}\n", + "\n", + "Setting these equations to 0 and solving for time, we get the time at which the object hits the ground and begins falling, respectively. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Next, we'll define the parameter callbacks, so that `throwing_speed` is updated appropriately any time that `thrower_height` changes. The following effectively tells the derived callbacks feature to call the `update_thrown_speed` function whenever the `thrower_height` changes. " + "To construct our direct model, we'll extend the `ThrownObject_ST` model to additionally include the method [time_to_event](https://nasa.github.io/progpy/api_ref/prog_models/PrognosticModel.html#prog_models.PrognosticsModel.time_of_event). This method will calculate the time at which each event occurs (i.e., time when the event threshold is met), based on the equations above. `time_of_event` must be implemented by any direct model. " ] }, { @@ -629,20 +627,33 @@ "metadata": {}, "outputs": [], "source": [ - "class ThrownObject_ST(ThrownObject_ST):\n", + "class DirectThrownObject(ThrownObject_ST):\n", + " def time_of_event(self, x, *args, **kwargs):\n", + " # calculate time when object hits ground given x['x'] and x['v']\n", + " # 0 = x0 + v0*t - 0.5*g*t^2\n", + " g = self['g']\n", + " t_impact = -(x['v'] + np.sqrt(x['v']*x['v'] - 2*g*x['x']))/g\n", "\n", - " param_callbacks = {\n", - " 'thrower_height': [update_thrown_speed]\n", - " }" + " # 0 = v0 - g*t\n", + " t_falling = -x['v']/g\n", + " \n", + " return {'falling': t_falling, 'impact': t_impact}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "You can also have more than one function be called when a single parameter is changed. You would do this by adding the additional callbacks to the list (e.g., 'thrower_height': [update_thrown_speed, other_fcn])\n", + "With this, our direct model is created. Note that adding `*args` and `**kwargs` is optional. Having these arguments makes the function interchangeable with other models which may have arguments or keyword arguments. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now let's test out this capability. To do so, we'll use the `time` package to compare the direct model to our original timeseries model. \n", "\n", - "We have now added the capability for `throwing_speed` to be a derived parameter. Let's try it out. First, we'll create an instance of our class and print out the default parameters. " + "Let's start by creating an instance of our timeseries model, calculating the time of event, and timing this computation. Note that for a state transition model, `time_of_event` still returns the time at which `threshold_met` returns true for each event, but this is calculated by simulating to threshold." ] }, { @@ -651,15 +662,24 @@ "metadata": {}, "outputs": [], "source": [ - "obj = ThrownObject_ST()\n", - "print(\"Default Settings:\\n\\tthrower_height: {}\\n\\tthrowing_speed: {}\".format(obj['thrower_height'], obj['throwing_speed']))" + "import time \n", + "\n", + "m_timeseries = ThrownObject_ST()\n", + "x = m_timeseries.initialize()\n", + "print(m_timeseries.__class__.__name__, \"(Direct Model)\" if m_timeseries.is_direct else \"(Timeseries Model)\")\n", + "tic = time.perf_counter()\n", + "print('Time of event: ', m_timeseries.time_of_event(x, dt = 0.05))\n", + "toc = time.perf_counter()\n", + "print(f'execution: {(toc-tic)*1000:0.4f} milliseconds')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Now, let's change the thrower height. If our derived parameters work correctly, the thrower speed should change accordingly. " + "Now let's do the same using our direct model implementation. In this case, when `time_to_event` is called, the event time will be estimated directly from the state, instead of through simulation to threshold. \n", + "\n", + "Note that a limitation of a direct model is that you cannot get intermediate states (i.e., `save_pts` or `save_freq`) since the time of event is calculated directly. " ] }, { @@ -668,56 +688,56 @@ "metadata": {}, "outputs": [], "source": [ - "obj['thrower_height'] = 1.75 # Our thrower is 1.75 m tall\n", - "print(\"\\nUpdated Settings:\\n\\tthrower_height: {}\\n\\tthowing_speed: {}\".format(obj['thrower_height'], obj['throwing_speed']))" + "m_direct = DirectThrownObject()\n", + "x = m_direct.initialize() # Using Initial state\n", + "# Now instead of simulating to threshold, we can estimate it directly from the state, like so\n", + "print('\\n', m_direct.__class__.__name__, \"(Direct Model)\" if m_direct.is_direct else \"(Timeseries Model)\")\n", + "tic = time.perf_counter()\n", + "print('Time of event: ', m_direct.time_of_event(x))\n", + "toc = time.perf_counter()\n", + "print(f'execution: {(toc-tic)*1000:0.4f} milliseconds')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "As we can see, when the thrower height was changed, the throwing speed was re-calculated too. \n", + "Notice that execution is significantly faster for the direct model. Furthermore, the result is actually more accurate, since it's not limited by the timestep (see dt section in __[01 Simulation](01_Simulation.ipynb)__). These observations will be even more pronounced for events that occur later in the simulation. \n", "\n", - "In this example, we illustrated how to use the `derived_params` feature, which allows a parameter to be a function of another parameter. " + "It's important to note that this is a very simple example, as there are no inputs. For models with inputs, future loading must be provided to `time_of_event` (see the future loading section in __[01 Simulation](01_Simulation.ipynb)__). In these cases, most direct models will encode or discretize the future loading profile to use it in a direct estimation of time of event.\n", + "\n", + "In the example provided, we have illustrated how to use a direct model. Direct models are a powerful tool for estimating the time of an event directly from the system state. By avoiding the process of state transitions, direct models can provide more efficient event time estimates. Additionally, the direct model approach is not limited to physics-based models. It can also be applied to data-driven models that can map sensor data directly to the time of an event. \n", + "\n", + "In conclusion, direct models offer an efficient and versatile approach for prognostics modeling, enabling faster and more direct estimations of event times. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Direct Models" + "## Advanced Features" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "In the previous sections, we illustrated how to create and use state-transition models, or models that use state transition differential equations to propagate the state forward. In this example, we'll explore another type of model implemented within ProgPy: direct models. \n", - "\n", - "Direct models estimate the time of event directly from the system state and future load, rather than through state transitions. This approach is particularly useful for physics-based models where the differential equations of state transitions can be explicitly solved, or for data-driven models that map sensor data directly to the time of an event. When applicable, using a direct model approach provides a more efficient way to estimate the time of an event, especially for events that occur later in the simulation.\n", - "\n", - "To illustrate this concept, we will extend the state-transition model, `ThrownObject_ST`, defined above, to create a new model class, `DirectThrownObject`. The dynamics of a thrown object lend easily to a direct model, since we can solve the differential equations explicitly to estimate the time at which the events occur. \n", - "\n", - "Recall that our physical system is described by the following differential equations: \n", - "\\begin{align*}\n", - "\\frac{dx}{dt} &= v \\\\ \\\\\n", - "\\frac{dv}{dt} &= -g \n", - "\\end{align*}\n", - "\n", - "This can be solved explicity given initial position $x_0$ and initial velocity $v_0$:\n", - "\\begin{align*}\n", - "x(t) &= -\\frac{1}{2} gt^2 + v_0 t + x_0 \\\\ \\\\ \n", - "v(t) &= -gt + v_0\n", - "\\end{align*}\n", - "\n", - "Setting these equations to 0 and solving for time, we get the time at which the object hits the ground and begins falling, respectively. " + "### Derived Parameters" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "To construct our direct model, we'll extend the `ThrownObject_ST` model to additionally include the method [time_to_event](https://nasa.github.io/progpy/api_ref/prog_models/PrognosticModel.html#prog_models.PrognosticsModel.time_of_event). This method will calculate the time at which each event occurs (i.e., time when the event threshold is met), based on the equations above. `time_of_event` must be implemented by any direct model. " + "In the previous section, we constructed a new model from scratch by subclassing from [progpy.PrognosticsModel](https://nasa.github.io/progpy/api_ref/prog_models/PrognosticModel.html#prog_models.PrognosticsModel) and specifying all of the necessary model components. An additional optional feature of `PrognosticsModel` is derived parameters, illustrated below. \n", + "\n", + "A derived parameter is a parameter that is a function of another parameter. For example, in the case of a thrown object, one could assume that throwing speed is a function of thrower height, with taller throwing height resulting in faster throwing speeds. In the electrochemistry battery model (see __[03 Included Models](03_Existing%20Models.ipynb)__), there are parameters for the maximum and minimum charge at the surface and bulk, and these are dependent on the capacity of the battery (i.e. another parameter, `qMax`). When such derived parameters exist, they must be updated whenever the parameters they depend on are updated. In `PrognosticsModels`, this is achieved with the `derived_params` feature. \n", + "\n", + "This feature can also be used to cache combinations of parameters that are used frequently in state transition or other model methods. Creating lumped parameters using `derived_params` causes them to be calculated once when configuring, instead of each time step in simulation or prediction. \n", + "\n", + "For this example, we will use the `ThrownObject_ST` model created in a previous section. We will extend this model to include a derived parameter, namely `throwing_speed` to be dependent on `thrower_height`.\n", + "\n", + "To implement this, we must first define a function for the relationship between the two parameters. We'll assume that `throwing_speed` is a linear function of `thrower_height`. " ] }, { @@ -726,33 +746,40 @@ "metadata": {}, "outputs": [], "source": [ - "class DirectThrownObject(ThrownObject_ST):\n", - " def time_of_event(self, x, *args, **kwargs):\n", - " # calculate time when object hits ground given x['x'] and x['v']\n", - " # 0 = x0 + v0*t - 0.5*g*t^2\n", - " g = self['g']\n", - " t_impact = -(x['v'] + np.sqrt(x['v']*x['v'] - 2*g*x['x']))/g\n", - "\n", - " # 0 = v0 - g*t\n", - " t_falling = -x['v']/g\n", - " \n", - " return {'falling': t_falling, 'impact': t_impact}" + "def update_thrown_speed(params):\n", + " return {\n", + " 'throwing_speed': params['thrower_height'] * 21.85\n", + " } \n", + " # One or more parameters can be changed in these functions, and parameters that are changed are returned in the dictionary" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "With this, our direct model is created. Note that adding `*args` and `**kwargs` is optional. Having these arguments makes the function interchangeable with other models which may have arguments or keyword arguments. " + "Next, we'll define the parameter callbacks, so that `throwing_speed` is updated appropriately any time that `thrower_height` changes. The following effectively tells the derived callbacks feature to call the `update_thrown_speed` function whenever the `thrower_height` changes. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "class ThrownObject_ST(ThrownObject_ST):\n", + "\n", + " param_callbacks = {\n", + " 'thrower_height': [update_thrown_speed]\n", + " }" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Now let's test out this capability. To do so, we'll use the `time` package to compare the direct model to our original timeseries model. \n", + "We can also have more than one function be called when a single parameter is changed. We could do this by adding the additional callbacks to the list (e.g., `thrower_height`: [`update_thrown_speed`, `other_fcn`])\n", "\n", - "Let's start by creating an instance of our timeseries model, calculating the time of event, and timing this computation. Note that for a state transition model, `time_of_event` still returns the time at which `threshold_met` returns true for each event, but this is calculated by simulating to threshold." + "We have now added the capability for `throwing_speed` to be a derived parameter. Let's try it out. First, we'll create an instance of our class and print out the default parameters. " ] }, { @@ -761,24 +788,15 @@ "metadata": {}, "outputs": [], "source": [ - "import time \n", - "\n", - "m_timeseries = ThrownObject_ST()\n", - "x = m_timeseries.initialize()\n", - "print(m_timeseries.__class__.__name__, \"(Direct Model)\" if m_timeseries.is_direct else \"(Timeseries Model)\")\n", - "tic = time.perf_counter()\n", - "print('Time of event: ', m_timeseries.time_of_event(x, dt = 0.05))\n", - "toc = time.perf_counter()\n", - "print(f'execution: {(toc-tic)*1000:0.4f} milliseconds')" + "obj = ThrownObject_ST()\n", + "print(\"Default Settings:\\n\\tthrower_height: {}\\n\\tthrowing_speed: {}\".format(obj['thrower_height'], obj['throwing_speed']))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Now let's do the same using our direct model implementation. In this case, when `time_to_event` is called, the event time will be estimated directly from the state, instead of through simulation to threshold. \n", - "\n", - "Note that a limitation of a direct model is that you cannot get intermediate states (i.e., save_pts or save_freq) since the time of event is calculated directly. " + "Now, let's change the thrower height. If our derived parameters work correctly, the thrower speed should change accordingly. " ] }, { @@ -787,45 +805,35 @@ "metadata": {}, "outputs": [], "source": [ - "m_direct = DirectThrownObject()\n", - "x = m_direct.initialize() # Using Initial state\n", - "# Now instead of simulating to threshold, we can estimate it directly from the state, like so\n", - "print('\\n', m_direct.__class__.__name__, \"(Direct Model)\" if m_direct.is_direct else \"(Timeseries Model)\")\n", - "tic = time.perf_counter()\n", - "print('Time of event: ', m_direct.time_of_event(x))\n", - "toc = time.perf_counter()\n", - "print(f'execution: {(toc-tic)*1000:0.4f} milliseconds')" + "obj['thrower_height'] = 1.75 # Our thrower is 1.75 m tall\n", + "print(\"\\nUpdated Settings:\\n\\tthrower_height: {}\\n\\tthowing_speed: {}\".format(obj['thrower_height'], obj['throwing_speed']))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Notice that execution is significantly faster for the direct model. Furthermore, the result is actually more accurate, since it's not limited by the timestep (see dt section in __[01 Simulation](01_Simulation.ipynb)__). These observations will be even more pronounced for events that occur later in the simulation. \n", - "\n", - "It's important to note that this is a very simple example, as there are no inputs. For models with inputs, future loading must be provided to `time_of_event` (see the future loading section in __[01 Simulation](01_Simulation.ipynb)__). In these cases, most direct models will encode or discretize the future loading profile to use it in a direct estimation of time of event.\n", - "\n", - "In the example provided, we have illustrated how to use a direct model. Direct models are a powerful tool for estimating the time of an event directly from the system state. By avoiding the process of state transitions, direct models can provide more efficient event time estimates. Additionally, the direct model approach is not limited to physics-based models. It can also be applied to data-driven models that can map sensor data directly to the time of an event. \n", + "As we can see, when the thrower height was changed, the throwing speed was re-calculated too. \n", "\n", - "In conclusion, direct models offer an efficient and versatile approach for prognostics modeling, enabling faster and more direct estimations of event times. " + "In this example, we illustrated how to use the `derived_params` feature, which allows a parameter to be a function of another parameter. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Matrix Data Access Feature" + "### Matrix Data Access" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "In the above models, we have used dictionaries to represent the states. For example, in the implementation of `ThrownObject_ST` above, see how `dx` is defined with a StateContainer dictionary. While all models can be constructed using dictionaries in this way, some dynamical systems allow for the state of the system to be represented with a matrix. For such use-cases, ProgPy has an advanced *matrix data access feature* that provides a more efficient way to define these models.\n", + "In the above models, we have used dictionaries to represent the states. For example, in the implementation of `ThrownObject_ST` above, see how `dx` is defined with a `StateContainer` dictionary. While all models can be constructed using dictionaries in this way, some dynamic systems allow for the state of the system to be represented with a matrix. For such use-cases, ProgPy has an advanced matrix data access feature that provides a more efficient way to define these models.\n", "\n", - "In ProgPy's implementation, the provided model.StateContainer, InputContainer, and OutputContainers can be treated as dictionaries but use an underlying matrix. This is important for some applications like surrogate and machine-learned models where the state is represented by a tensor. ProgPy's *matrix data access feature* allows the matrices to be used directly. Simulation functions propagate the state using the matrix form, preventing the inefficiency of having to convert to and from dictionaries. Additionally, this implementation is faster than recreating the StateContainer each time, especially when updating inplace.\n", + "In ProgPy's implementation, the provided model. `StateContainer`, `InputContainer`, and `OutputContainer` can be treated as dictionaries but use an underlying matrix. This is important for some applications like surrogate and machine-learned models where the state is represented by a tensor. ProgPy's matrix data access feature allows the matrices to be used directly. Simulation functions propagate the state using the matrix form, preventing the inefficiency of having to convert to and from dictionaries. Additionally, this implementation is faster than recreating the `StateContainer` each time, especially when updating in place.\n", "\n", - "In this example, we'll illustrate how to use the matrix data access feature. We'll continue with our ThrownObject system, and create a model to simulate this using matrix notation (instead of dictionary notation as in the standard model, seen above in `ThrownObject_ST`). The implementation of the model is comparable to a standard model, except that it uses matrix operations within each function, as seen below. \n", + "In this example, we'll illustrate how to use the matrix data access feature. We'll continue with our `ThrownObject` system, and create a model to simulate this using matrix notation (instead of dictionary notation as in the standard model, seen above in `ThrownObject_ST`). The implementation of the model is comparable to a standard model, except that it uses matrix operations within each function, as seen below. \n", "\n", "First, the necessary imports." ] @@ -837,7 +845,7 @@ "outputs": [], "source": [ "import numpy as np\n", - "from progpy import PrognosticsModel" + "import time" ] }, { @@ -848,7 +856,7 @@ "\n", "To use the matrix data access feature, we'll use matrices to define how the state transitions. Since we are working with a discrete version of the system now, we'll define the `next_state` method, and this will override the `dx` method in the parent class. \n", "\n", - "In the following, we will use the matrix version for each variable, accessed with `.matrix`. We implement this within `next_state`, but this feature can also be used in other functions. Here, both `x.matrix` and `u.matrix` are column vectors, and `u.matrix` is in the same order as model.inputs." + "In the following, we will use the matrix version for each variable, accessed with `.matrix`. We implement this within `next_state`, but this feature can also be used in other functions. Here, both `x.matrix` and `u.matrix` are column vectors, and `u.matrix` is in the same order as `model.inputs`." ] }, { @@ -858,9 +866,7 @@ "outputs": [], "source": [ "class ThrownObject_MM(ThrownObject_ST):\n", - "\n", " def next_state(self, x, u, dt):\n", - "\n", " A = np.array([[0, 1], [0, 0]]) # State transition matrix\n", " B = np.array([[0], [self['g']]]) # Acceleration due to gravity\n", " x.matrix += (np.matmul(A, x.matrix) + B) * dt\n", @@ -899,15 +905,15 @@ "metadata": {}, "outputs": [], "source": [ - "import time \n", - "\n", "tic_matrix = time.perf_counter()\n", + "\n", "# Simulate to threshold \n", "m_matrix.simulate_to_threshold(\n", " print = True, \n", " events = 'impact', \n", " dt = 0.1, \n", " save_freq = 1)\n", + "\n", "toc_matrix = time.perf_counter()" ] }, @@ -952,7 +958,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## State Limits" + "### State Limits" ] }, { @@ -1111,14 +1117,14 @@ "source": [ "In conclusion, setting appropriate [state limits](https://nasa.github.io/progpy/prog_models_guide.html#state-limits) is crucial in creating realistic and accurate state-transition models. It ensures that the model's behavior stays within the constraints of the physical system. The limits should be set based on the physical or practical constraints of the system being modeled. \n", "\n", - "As a final note, state limits are especially important for state estimation (to be discussed in the State Estimation section), as it will force the state estimator to only consider states that are possible or feasible. State estimation will be described in more detail in section 08. State Estimation. " + "As a final note, state limits are especially important for state estimation (to be discussed in the State Estimation section), as it will force the state estimator to only consider states that are possible or feasible. State estimation will be described in more detail in section __[07 State Estimation](07_State%20Estimation.ipynb)__. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Custom Events" + "### Custom Events" ] }, { @@ -1127,9 +1133,9 @@ "source": [ "In the examples above, we have focused on the simple event of a thrown object hitting the ground or reaching `impact`. In this section, we highlight additional uses of ProgPy's generalizable concept of `events`. \n", "\n", - "The term [events](https://nasa.github.io/progpy/prog_models_guide.html#events) is used to describe something to be predicted. Generally in the PHM community, these are referred to as End of Life (EOL). However, they can be much more. \n", + "The term [events](https://nasa.github.io/progpy/prog_models_guide.html#events) is used to describe something to be predicted. Generally in the PHM community, these are referred to as End of Life (`EOL`). However, they can be much more. \n", "\n", - "In ProgPy, events can be anything that needs to be predicted. Systems will often have multiple failure modes, and each of these modes can be represented by a separate event. Additionally, events can also be used to predict other events of interest other than failure, such as special system states or warning thresholds. Thus, `events` in ProgPy can represent End of Life (EOL), End of Mission (EOM), warning thresholds, or any Event of Interest (EOI). \n", + "In ProgPy, events can be anything that needs to be predicted. Systems will often have multiple failure modes, and each of these modes can be represented by a separate event. Additionally, events can also be used to predict other events of interest other than failure, such as special system states or warning thresholds. Thus, `events` in ProgPy can represent End of Life (`EOL`), End of Mission (`EOM`), warning thresholds, or any Event of Interest (`EOI`). \n", "\n", "There are a few components of the model that must be specified in order to define events:\n", "\n", @@ -1208,7 +1214,6 @@ "outputs": [], "source": [ "class BattNewEvent(BattNewEvent):\n", - " \n", " def event_state(self, state):\n", " # Get event state from parent\n", " event_state = super().event_state(state)\n", @@ -1218,7 +1223,6 @@ " event_state['EOD_warn_red'] = (event_state['EOD']-RED_THRESH)/(1-RED_THRESH)\n", " event_state['EOD_requirement_threshold'] = (event_state['EOD']-THRESHOLD)/(1-THRESHOLD)\n", "\n", - " # Return\n", " return event_state" ] }, @@ -1294,15 +1298,24 @@ "Now we can simulate to threshold and plot the results. " ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "simulated_results = m.simulate_to_threshold(future_loading, events='EOD', print = True)" + ] + }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "simulated_results = m.simulate_to_threshold(future_loading, events='EOD', print = True)\n", - "\n", - "simulated_results.event_states.plot()\n", + "simulated_results.event_states.plot(xlabel='time (s)', title='BattNewEvent model simulation EOD event state')\n", "plt.show()" ] }, @@ -1319,7 +1332,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Serialization " + "### Serialization " ] }, { @@ -1352,7 +1365,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "For this example, we'll use the BatteryElectroChemEOD model. We'll start by creating a model object. " + "For this example, we'll use the `BatteryElectroChemEOD` model. We'll start by creating a model object. " ] }, { @@ -1490,8 +1503,9 @@ "plt.plot(results_json_1.times,voltage_json_1,'-.g',label='First JSON serialized surrogate') \n", "plt.plot(results_json_2.times, voltage_json_2, '--y', label='Second JSON serialized surrogate')\n", "plt.legend()\n", - "plt.xlabel('Time (sec)')\n", - "plt.ylabel('Voltage (volts)')" + "plt.title('Serialized model simulation outputs')\n", + "plt.xlabel('time (s)')\n", + "plt.ylabel('voltage (V)')" ] }, { @@ -1509,8 +1523,6 @@ "metadata": {}, "outputs": [], "source": [ - "import numpy as np\n", - "\n", "# Check if the arrays are the same\n", "are_arrays_same = np.array_equal(voltage_orig, voltage_pkl) and \\\n", " np.array_equal(voltage_orig, voltage_json_1) and \\\n", @@ -1537,14 +1549,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "This is an example of a somewhat more complicated model, in this case a battery. We will be implementing the simplified battery model introduced by [Gina Sierra, et. al.](https://www.sciencedirect.com/science/article/pii/S0951832018301406)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "First, we import the PrognosticsModel class. This is the parent class for all ProgPy Models" + "This is an example of a somewhat more complicated model, in this case a battery. We will be implementing the simplified battery model introduced by [Gina Sierra, et. al.](https://www.sciencedirect.com/science/article/pii/S0951832018301406)\n", + "\n", + "First, we will import `PrognosticsModel`, which the parent class for all ProgPy models." ] }, { @@ -1569,13 +1576,11 @@ "source": [ "The first step to creating a physics-based model is implementing state transition. From the paper we see one state (SOC) and one state transition equation:\n", "\n", - "$SOC(k+1) = SOC(k) - P(k)*\\Delta t * E_{crit}^{-1} + w_2(k)$\n", + "$$SOC(k+1) = SOC(k) - P(k)*\\Delta t * E_{crit}^{-1} + w_2(k)$$\n", "\n", "where $k$ is discrete time. The $w$ term is process noise. This can be omitted, since it's handled by ProgPy. \n", "\n", - "In this equation we see one input ($P$, power). Note that the previous battery model uses current, where this uses power.\n", - "\n", - "Armed with this information we can start defining our model. First, we start by declaring our inputs and states:" + "In this equation we see one input ($P$, power). Note that the previous battery model uses current, where this uses power. With this information, we can start defining our model. First, we start by declaring our inputs and states:" ] }, { @@ -1584,7 +1589,7 @@ "metadata": {}, "outputs": [], "source": [ - "class SimplifiedEquivilantCircuit(PrognosticsModel):\n", + "class SimplifiedEquivalentCircuit(PrognosticsModel):\n", " inputs = ['P']\n", " states = ['SOC']" ] @@ -1593,7 +1598,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Next we define parameters. In this case the parameters are the initial SOC state (1) and the E_crit (Internal Total Energy). We get the value for $E_{crit}$ from the paper.\n", + "Next we define parameters. In this case the parameters are the initial `SOC` state (1) and the `E_crit` (Internal Total Energy). We get the value for $E_{crit}$ from the paper.\n", "\n", "***Note:** This won't actually subclass in practice, but it's used to break apart model definition into chunks.*" ] @@ -1604,7 +1609,7 @@ "metadata": {}, "outputs": [], "source": [ - "class SimplifiedEquivilantCircuit(SimplifiedEquivilantCircuit):\n", + "class SimplifiedEquivalentCircuit(SimplifiedEquivalentCircuit):\n", " default_parameters = {\n", " 'E_crit': 202426.858, # Internal Total Energy\n", " 'x0': {\n", @@ -1626,7 +1631,7 @@ "metadata": {}, "outputs": [], "source": [ - "class SimplifiedEquivilantCircuit(SimplifiedEquivilantCircuit):\n", + "class SimplifiedEquivalentCircuit(SimplifiedEquivalentCircuit):\n", " state_limits = {\n", " 'SOC': (0.0, 1.0),\n", " }" @@ -1636,7 +1641,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Next, we define the state transition equation. There are two methods for doing this: *dx* (for continuous) and *next_state* (for discrete). Today we're using the $dx$ function. This was selected because the model is continuous." + "Next, we define the state transition equation. There are two methods for doing this: `dx` (for continuous) and `next_state` (for discrete). We will use the `dx` function since the model is continuous." ] }, { @@ -1645,7 +1650,7 @@ "metadata": {}, "outputs": [], "source": [ - "class SimplifiedEquivilantCircuit(SimplifiedEquivilantCircuit):\n", + "class SimplifiedEquivalentCircuit(SimplifiedEquivalentCircuit):\n", " def dx(self, x, u):\n", " return self.StateContainer({'SOC': -u['P']/self['E_crit']})" ] @@ -1689,7 +1694,7 @@ "metadata": {}, "outputs": [], "source": [ - "class SimplifiedEquivilantCircuit(SimplifiedEquivilantCircuit):\n", + "class SimplifiedEquivalentCircuit(SimplifiedEquivalentCircuit):\n", " outputs = ['v']\n", "\n", " default_parameters = {\n", @@ -1711,9 +1716,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The input ($P(k)$) is also used in the output equation, which means it's part of the state of the system. So we will update the states to include this.\n", + "The input ($P(k)$) is also used in the output equation, which means it's part of the state of the system. We will update the states in `next_state`. \n", "\n", - "***Note:** We change to next_state. Above, we define state transition with ProgPy's `dx` method because the model was continuous. Here, with the addition of power, the model becomes discrete, so we must now use ProgPy's `next_state` method to define state transition.*" + "Remember that in the earlier example, we defined the state transition with ProgPy's `dx` method because the model was continuous. Here, with the addition of power, the model becomes discrete, so we must now use ProgPy's `next_state` method to define state transition." ] }, { @@ -1722,7 +1727,7 @@ "metadata": {}, "outputs": [], "source": [ - "class SimplifiedEquivilantCircuit(SimplifiedEquivilantCircuit):\n", + "class SimplifiedEquivalentCircuit(SimplifiedEquivalentCircuit):\n", " states = ['SOC', 'P']\n", "\n", " def next_state(self, x, u, dt):\n", @@ -1736,7 +1741,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Adding a default P state as well:" + "We will also add a default `P` state." ] }, { @@ -1745,7 +1750,7 @@ "metadata": {}, "outputs": [], "source": [ - "class SimplifiedEquivilantCircuit(SimplifiedEquivilantCircuit):\n", + "class SimplifiedEquivalentCircuit(SimplifiedEquivalentCircuit):\n", " default_parameters = {\n", " 'E_crit': 202426.858,\n", " 'v_L': 11.148,\n", @@ -1766,7 +1771,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Finally, we're ready to define the output equations (defined above)." + "Finally, we're ready to define the output equations." ] }, { @@ -1776,7 +1781,8 @@ "outputs": [], "source": [ "import math\n", - "class SimplifiedEquivilantCircuit(SimplifiedEquivilantCircuit):\n", + "\n", + "class SimplifiedEquivalentCircuit(SimplifiedEquivalentCircuit):\n", " def output(self, x):\n", " v_oc = self['v_L'] - self['lambda']**(self['gamma']*x['SOC']) - self['mu'] * math.exp(-self['beta']* math.sqrt(x['SOC']))\n", " i = (v_oc - math.sqrt(v_oc**2 - 4 * self['R_int'] * x['P']))/(2 * self['R_int'])\n", @@ -1790,7 +1796,7 @@ "metadata": {}, "source": [ "### Events\n", - "Finally we can define events. This is an easy case because our event state (SOC) is part of the model state. So we will simply define a single event (EOD: End of Discharge), where SOC is progress towards that event." + "Finally we can define events. This is an easy case because our event state (`SOC`) is part of the model state. So we will simply define a single event (`EOD`: End of Discharge), where `SOC` is progress towards that event." ] }, { @@ -1799,7 +1805,7 @@ "metadata": {}, "outputs": [], "source": [ - "class SimplifiedEquivilantCircuit(SimplifiedEquivilantCircuit):\n", + "class SimplifiedEquivalentCircuit(SimplifiedEquivalentCircuit):\n", " events = ['EOD']" ] }, @@ -1816,7 +1822,7 @@ "metadata": {}, "outputs": [], "source": [ - "class SimplifiedEquivilantCircuit(SimplifiedEquivilantCircuit):\n", + "class SimplifiedEquivalentCircuit(SimplifiedEquivalentCircuit):\n", " def event_state(self, x):\n", " return {'EOD': x['SOC']}" ] @@ -1825,11 +1831,11 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The threshold of the event is defined as the state where the event state (EOD) is 0.\n", + "The threshold of the event is defined as the state where the event state (`EOD`) is 0.\n", "\n", - "That's it. We've now defined a complete model. Now it's ready to be used for state estimation or prognostics, like any model distributed with ProgPy\n", + "We've now defined a complete model. Now it's ready to be used for state estimation or prognostics, like any model distributed with ProgPy.\n", "\n", - "Note that this model can be extended by changing the parameters ecrit and r to steady states. This will help the model account for the effects of aging, since they will be estimated with each state estimation step." + "Note that this model can be extended by changing the parameters `ecrit` and `r` to steady states. This will help the model account for the effects of aging, since they will be estimated with each state estimation step." ] }, { From 81365ef60b7f1015d8767caf97413cdd90998f29 Mon Sep 17 00:00:00 2001 From: Michelle Ly Date: Wed, 23 Apr 2025 14:13:06 -0700 Subject: [PATCH 09/10] fix: update --- examples/04_New Models.ipynb | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/examples/04_New Models.ipynb b/examples/04_New Models.ipynb index f31cd61b..4e1149f3 100644 --- a/examples/04_New Models.ipynb +++ b/examples/04_New Models.ipynb @@ -1147,7 +1147,7 @@ "\n", "Note that because of the interconnected relationship between `threshold_met` and `event_state`, it is only required to define one of these. However, there are frequently computational advantages to specifying both. \n", "\n", - "To illustrate this concept, we will use the `BatteryElectroChemEOD` model (see section 03. Included Models). In the standard implementation of this model, the defined event is `EOD` or End of Discharge. This occurs when the voltage drops below a pre-defined threshold value. The State-of-Charge (SOC) of the battery is the event state for the EOD event. Recall that event states (and therefore SOC) vary between 0 and 1, where 1 is healthy and 0 signifies the event has occurred. \n", + "To illustrate this concept, we will use the `BatteryElectroChemEOD` model (see __[03 Included Models](03_Existing%20Models.ipynb)__). In the standard implementation of this model, the defined event is `EOD` or End of Discharge. This occurs when the voltage drops below a pre-defined threshold value. The State-of-Charge (SOC) of the battery is the event state for the EOD event. Recall that event states (and therefore SOC) vary between 0 and 1, where 1 is healthy and 0 signifies the event has occurred. \n", "\n", "Suppose we have the requirement that our battery must not fall below 5% State-of-Charge. This would correspond to an `EOD` event state of 0.05. Additionally, let's add events for two warning thresholds, a $\\text{\\textcolor{yellow}{yellow}}$ threshold at 15% SOC and a $\\text{\\textcolor{red}{red}}$ threshold at 10% SOC. \n", "\n", @@ -1275,7 +1275,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Recall that the battery model takes input of current. We will use a piecewise loading scheme (see 01. Simulation)." + "Recall that the battery model takes input of current. We will use a piecewise loading scheme." ] }, { @@ -1857,7 +1857,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3.11.0 64-bit", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -1871,9 +1871,8 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.13.0" + "version": "3.10.5" }, - "orig_nbformat": 4, "vscode": { "interpreter": { "hash": "aee8b7b246df8f9039afb4144a1f6fd8d2ca17a180786b69acc140d282b71a49" @@ -1881,5 +1880,5 @@ } }, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 4 } From 105c0bbf0ec2b6b23cbc9b1cab1f91f5c1788b4e Mon Sep 17 00:00:00 2001 From: Michelle Ly Date: Wed, 23 Apr 2025 14:15:05 -0700 Subject: [PATCH 10/10] fix: undo metadata change --- examples/04_New Models.ipynb | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/examples/04_New Models.ipynb b/examples/04_New Models.ipynb index 4e1149f3..f31cd61b 100644 --- a/examples/04_New Models.ipynb +++ b/examples/04_New Models.ipynb @@ -1147,7 +1147,7 @@ "\n", "Note that because of the interconnected relationship between `threshold_met` and `event_state`, it is only required to define one of these. However, there are frequently computational advantages to specifying both. \n", "\n", - "To illustrate this concept, we will use the `BatteryElectroChemEOD` model (see __[03 Included Models](03_Existing%20Models.ipynb)__). In the standard implementation of this model, the defined event is `EOD` or End of Discharge. This occurs when the voltage drops below a pre-defined threshold value. The State-of-Charge (SOC) of the battery is the event state for the EOD event. Recall that event states (and therefore SOC) vary between 0 and 1, where 1 is healthy and 0 signifies the event has occurred. \n", + "To illustrate this concept, we will use the `BatteryElectroChemEOD` model (see section 03. Included Models). In the standard implementation of this model, the defined event is `EOD` or End of Discharge. This occurs when the voltage drops below a pre-defined threshold value. The State-of-Charge (SOC) of the battery is the event state for the EOD event. Recall that event states (and therefore SOC) vary between 0 and 1, where 1 is healthy and 0 signifies the event has occurred. \n", "\n", "Suppose we have the requirement that our battery must not fall below 5% State-of-Charge. This would correspond to an `EOD` event state of 0.05. Additionally, let's add events for two warning thresholds, a $\\text{\\textcolor{yellow}{yellow}}$ threshold at 15% SOC and a $\\text{\\textcolor{red}{red}}$ threshold at 10% SOC. \n", "\n", @@ -1275,7 +1275,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Recall that the battery model takes input of current. We will use a piecewise loading scheme." + "Recall that the battery model takes input of current. We will use a piecewise loading scheme (see 01. Simulation)." ] }, { @@ -1857,7 +1857,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "Python 3.11.0 64-bit", "language": "python", "name": "python3" }, @@ -1871,8 +1871,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.5" + "version": "3.13.0" }, + "orig_nbformat": 4, "vscode": { "interpreter": { "hash": "aee8b7b246df8f9039afb4144a1f6fd8d2ca17a180786b69acc140d282b71a49" @@ -1880,5 +1881,5 @@ } }, "nbformat": 4, - "nbformat_minor": 4 + "nbformat_minor": 2 }