From 112d2570b1d42fd5a84430bae0ae04ee19027415 Mon Sep 17 00:00:00 2001 From: Katy Date: Tue, 3 Oct 2023 16:00:46 -0700 Subject: [PATCH 1/3] Adding custom events example and final conclusion --- examples/04_New Models.ipynb | 231 +++++++++++++++++++++++++++++++++++ examples/events.py | 84 ------------- 2 files changed, 231 insertions(+), 84 deletions(-) delete mode 100644 examples/events.py diff --git a/examples/04_New Models.ipynb b/examples/04_New Models.ipynb index bacc58a..58d87a1 100644 --- a/examples/04_New Models.ipynb +++ b/examples/04_New Models.ipynb @@ -1200,12 +1200,243 @@ "## Custom 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`. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "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 beyond 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": [ + "There are three components of the model that must be specified in order to define events:\n", + "\n", + "1. The `events` property defines the expected events \n", + "\n", + "2. The `threshold_met` method defines the conditions under which an event occurs \n", + "\n", + "3. The `event_state` method returns an estimate of progress towards the threshold " + ] + }, + { + "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": [ + "To define the model, we'll start with the necessary imports." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "from progpy.loading import Piecewise\n", + "from progpy.models import BatteryElectroChemEOD" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, let's define our threshold values. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "YELLOW_THRESH = 0.15 # 15% SOC\n", + "RED_THRESH = 0.1 # 10% SOC\n", + "THRESHOLD = 0.05 # 5% SOC" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we'll create our model by subclassing from the `BatteryElectroChemEOD` model. First, we'll re-define `events` to include three new events for our two warnings and new threshold value, as well as the event `EOD` from the parent class." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "class Batt_newEvent(BatteryElectroChemEOD):\n", + " events = BatteryElectroChemEOD.events + ['EOD_warn_yellow', 'EOD_warn_red', 'EOD_requirement_threshold']\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we'll override the `event_state` method to additionally include calculations for progress towards each of our new events. We'll add yellow, red, and failure states by scaling the EOD state. We scale so that the threshold SOC is 0 at their associated events, while SOC of 1 is still 1. For example, for yellow, we want `EOD_warn_yellow` to be 1 when SOC is 1, and 0 when SOC is 15% or lower. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "class Batt_newEvent(Batt_newEvent):\n", + " \n", + " def event_state(self, state):\n", + " # Get event state from parent\n", + " event_state = super().event_state(state)\n", + "\n", + " # Add yellow, red, and failure states by scaling EOD state\n", + " event_state['EOD_warn_yellow'] = (event_state['EOD']-YELLOW_THRESH)/(1-YELLOW_THRESH) \n", + " 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" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, we'll override the `threshold_met` method to define when each event occurs. Based on the scaling in `event_state` each event is reached when the corresponding `event_state` value is less than or equal to 0. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "class Batt_newEvent(Batt_newEvent):\n", + " def threshold_met(self, x):\n", + " # Get threshold met from parent\n", + " t_met = super().threshold_met(x)\n", + "\n", + " # Add yell and red states from event_state\n", + " event_state = self.event_state(x)\n", + " t_met['EOD_warn_yellow'] = event_state['EOD_warn_yellow'] <= 0\n", + " t_met['EOD_warn_red'] = event_state['EOD_warn_red'] <= 0\n", + " t_met['EOD_requirement_threshold'] = event_state['EOD_requirement_threshold'] <= 0\n", + "\n", + " return t_met" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "With this, we have defined the three key model components for defining new events. \n", + "\n", + "Let's test out the model. First, create an instance of it. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "m = Batt_newEvent()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Recall that the battery model takes input of current. We will use a piecewise loading scheme (see 01. Simulation)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Variable (piecewise) future loading scheme\n", + "future_loading = Piecewise(\n", + " m.InputContainer,\n", + " [600, 900, 1800, 3000, float('inf')],\n", + " {'i': [2, 1, 4, 2, 3]})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we can simulate to threshold and plot the results. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "simulated_results = m.simulate_to_threshold(future_loading, threshold_keys=['EOD'], print = True)\n", + "\n", + "simulated_results.event_states.plot()\n", + "plt.show()" + ] + }, + { + "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": [ + "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. " + ] + }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Conclusions" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In these examples, we have described how to create new physics-based models. We have illustrated how to construct any general physics-based model, as well as highlighted some specific types of models including linear models, direct models, and matrix models. 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. " + ] } ], "metadata": { diff --git a/examples/events.py b/examples/events.py deleted file mode 100644 index f338d9c..0000000 --- a/examples/events.py +++ /dev/null @@ -1,84 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the -# National Aeronautics and Space Administration. All Rights Reserved. - -""" -Example further illustrating the concept of 'events' which generalizes EOL. - -.. dropdown:: More details - - :term:`Events` is the term 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. - - In progpy, events can be anything that needs to be predicted. Events can represent End of Life (EOL), End of Mission (EOM), warning thresholds, or any Event of Interest (EOI). - - This example demonstrates how events can be used in your applications. -""" -import matplotlib.pyplot as plt -from progpy.loading import Piecewise -from progpy.models import BatteryElectroChemEOD - -def run_example(): - # Example: Warning thresholds - # In this example we will use the battery model - # We of course are interested in end of discharge, but for this example we - # have a requirement that says the battery must not fall below 5% State of Charge (SOC) - # Note: SOC is the event state for the End of Discharge (EOD) event - # Event states, like SOC go between 0 and 1, where 1 is healthy and at 0 the event has occurred. - # So, 5% SOC corresponds to an 'EOD' event state of 0.05 - # Additionally, we have two warning thresholds (yellow and red) - - YELLOW_THRESH = 0.15 - RED_THRESH = 0.1 - THRESHOLD = 0.05 - - # Step 1: Extend the battery model to define the additional events - class MyBatt(BatteryElectroChemEOD): - events = BatteryElectroChemEOD.events + ['EOD_warn_yellow', 'EOD_warn_red', 'EOD_requirement_threshold'] - - def event_state(self, state): - # Get event state from parent - event_state = super().event_state(state) - - # Add yellow, red, and failure states by scaling EOD state - # Here we scale so the threshold SOC is 0 by their associated events, while SOC of 1 is still 1 - # For example, for yellow we want EOD_warn_yellow to be 1 when SOC is 1, and 0 when SOC is YELLOW_THRESH or lower - event_state['EOD_warn_yellow'] = (event_state['EOD']-YELLOW_THRESH)/(1-YELLOW_THRESH) - event_state['EOD_warn_red'] = (event_state['EOD']-RED_THRESH)/(1-RED_THRESH) - event_state['EOD_requirement_threshold'] = (event_state['EOD']-THRESHOLD)/(1-THRESHOLD) - - # Return - return event_state - - def threshold_met(self, x): - # Get threshold met from parent - t_met = super().threshold_met(x) - - # Add yell and red states from event_state - event_state = self.event_state(x) - t_met['EOD_warn_yellow'] = event_state['EOD_warn_yellow'] <= 0 - t_met['EOD_warn_red'] = event_state['EOD_warn_red'] <= 0 - t_met['EOD_requirement_threshold'] = event_state['EOD_requirement_threshold'] <= 0 - - return t_met - - # Step 2: Use it - m = MyBatt() - - # 2a: Setup model - - # Variable (piece-wise) future loading scheme - # For a battery, future loading is in term of current 'i' in amps. - future_loading = Piecewise( - m.InputContainer, - [600, 900, 1800, 3000, float('inf')], - {'i': [2, 1, 4, 2, 3]}) - - # 2b: Simulate to threshold - simulated_results = m.simulate_to_threshold(future_loading, threshold_keys=['EOD'], print = True) - - # 2c: Plot results - simulated_results.event_states.plot() - plt.show() - -# This allows the module to be executed directly -if __name__ == '__main__': - run_example() From c85215b39e138b0fba4a43a93463c46d171c3a45 Mon Sep 17 00:00:00 2001 From: Katy Date: Fri, 6 Oct 2023 09:41:26 -0700 Subject: [PATCH 2/3] Deleting conclusion; will add with serialization --- examples/04_New Models.ipynb | 9 --------- 1 file changed, 9 deletions(-) diff --git a/examples/04_New Models.ipynb b/examples/04_New Models.ipynb index 11cce93..4b5ee61 100644 --- a/examples/04_New Models.ipynb +++ b/examples/04_New Models.ipynb @@ -1397,15 +1397,6 @@ "source": [ "## Conclusions" ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In these examples, we have described how to create new physics-based models. We have illustrated how to construct any general physics-based model, as well as highlighted some specific types of models including linear models, direct models, and matrix models. 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. " - ] } ], "metadata": { From 3037071d5cb7d496ff9aeb90fe890f8d0ec47415 Mon Sep 17 00:00:00 2001 From: Katy Date: Wed, 11 Oct 2023 12:51:09 -0700 Subject: [PATCH 3/3] A few edits to custom_events --- examples/04_New Models.ipynb | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/examples/04_New Models.ipynb b/examples/04_New Models.ipynb index 4b5ee61..d275b85 100644 --- a/examples/04_New Models.ipynb +++ b/examples/04_New Models.ipynb @@ -1182,20 +1182,22 @@ "source": [ "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 beyond 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). " + "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": [ - "There are three components of the model that must be specified in order to define events:\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", "\n", "2. The `threshold_met` method defines the conditions under which an event occurs \n", "\n", - "3. The `event_state` method returns an estimate of progress towards the threshold " + "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. " ] }, { @@ -1261,7 +1263,7 @@ "metadata": {}, "outputs": [], "source": [ - "class Batt_newEvent(BatteryElectroChemEOD):\n", + "class BattNewEvent(BatteryElectroChemEOD):\n", " events = BatteryElectroChemEOD.events + ['EOD_warn_yellow', 'EOD_warn_red', 'EOD_requirement_threshold']\n" ] }, @@ -1269,7 +1271,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Next, we'll override the `event_state` method to additionally include calculations for progress towards each of our new events. We'll add yellow, red, and failure states by scaling the EOD state. We scale so that the threshold SOC is 0 at their associated events, while SOC of 1 is still 1. For example, for yellow, we want `EOD_warn_yellow` to be 1 when SOC is 1, and 0 when SOC is 15% or lower. " + "Next, we'll override the `event_state` method to additionally include calculations for progress towards each of our new events. We'll add yellow, red, and failure states by scaling the EOD state. We scale so that the threshold SOC is 0 at their associated events, while SOC of 1 is still 1. For example, for yellow, we want `EOD_warn_yellow` to be 1 when SOC is 1, and 0 when SOC is 0.15 or lower. " ] }, { @@ -1278,7 +1280,7 @@ "metadata": {}, "outputs": [], "source": [ - "class Batt_newEvent(Batt_newEvent):\n", + "class BattNewEvent(BattNewEvent):\n", " \n", " def event_state(self, state):\n", " # Get event state from parent\n", @@ -1306,7 +1308,7 @@ "metadata": {}, "outputs": [], "source": [ - "class Batt_newEvent(Batt_newEvent):\n", + "class BattNewEvent(BattNewEvent):\n", " def threshold_met(self, x):\n", " # Get threshold met from parent\n", " t_met = super().threshold_met(x)\n", @@ -1335,7 +1337,7 @@ "metadata": {}, "outputs": [], "source": [ - "m = Batt_newEvent()" + "m = BattNewEvent()" ] }, { @@ -1354,7 +1356,7 @@ "# Variable (piecewise) future loading scheme\n", "future_loading = Piecewise(\n", " m.InputContainer,\n", - " [600, 900, 1800, 3000, float('inf')],\n", + " [600, 900, 1800, 3000],\n", " {'i': [2, 1, 4, 2, 3]})" ] },