diff --git a/examples/03_Existing Models.ipynb b/examples/03_Existing Models.ipynb index 78ac0a8..d82e86e 100644 --- a/examples/03_Existing Models.ipynb +++ b/examples/03_Existing Models.ipynb @@ -4,9 +4,35 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Using Provided ProgPy Models\n", + "# 3. Using Included ProgPy Models" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "ProgPy is distributed with a few pre-constructed models that can be used in simulation or prognostics. These models for batteries, pumps, valves, among others, are included in the `progpy.models` package.\n", "\n", - "**A version of this notebook will be added in release v1.8, including:**" + "In this notebook, we will be exploring a generalized overview of each included model. For more in-depth descriptions of the included models, please refer to the [Included Models](https://nasa.github.io/progpy/api_ref/progpy/IncludedModels.html) documentation." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "jp-MarkdownHeadingCollapsed": true + }, + "source": [ + "## Table of Contents\n", + "* [Battery Models](#Battery-Models)\n", + " * [Battery Circuit](#BatteryCircuit)\n", + " * [BatteryElectroChemEOD](#BatteryElectroChemEOD)\n", + " * [BatteryElectroChemEOL](#BatteryElectroChemEOL)\n", + " * [Combined BatteryElectroChem (BatteryElectroChemEODEOL)](#Combined-BatteryElectroChem-(BatteryElectroChemEODEOL))\n", + " * [Simplified Battery](#Simplified-Battery)\n", + "* [Centrifugal Pump Model](#Centrifugal-Pump-Model)\n", + "* [Electric Powertrain Models](#Electric-Powertrain-Models)\n", + "* [Pneumatic Valve Model](#Pneumatic-Valve-Model)\n", + "* [Aircraft Flight Model](#Aircraft-Flight-Model)" ] }, { @@ -20,7 +46,546 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Electric Powertrain Models" + "We will start by introducing the battery models: `BatteryCircuit`, `BatteryElectroChemEOD`, `BatteryElectroChemEOL`, combined `BatteryElectroChem` (`BatteryElectroChemEODEOL`), and `SimplifiedBattery`.\n", + "\n", + "In the following battery models, with the exception of `SimplifiedBattery`, the default model parameters included are for Li-ion batteries, specifically 18650-type cells. Experimental discharge curves for these cells can be downloaded from the Prognostics Center of Excellence [Data Repository](https://ti.arc.nasa.gov/tech/dash/groups/pcoe/prognostic-data-repository)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### BatteryCircuit" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this first example, we will demonstrate how to set up, configure, and use the `BatteryCiruit` model. The `BatteryCircuit` model is a vectorized prognostics model for a battery, represented by an equivalent circuit model as described in [[Daigle Sankararaman 2013]](https://papers.phmsociety.org/index.php/phmconf/article/view/2253).\n", + "\n", + "We will start by importing the model and initializing a battery instance with default settings." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from progpy.models import BatteryCircuit\n", + "\n", + "batt = BatteryCircuit()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Information is passed to and from the model using containers that function like dictionaries. The keys of the containers are specific to the model. Let's look at the inputs (loading) and outputs (measurements) for the `BatteryCircuit` model." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print('inputs:', batt.inputs)\n", + "print('outputs:', batt.outputs)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If we refer to the [documentation](https://nasa.github.io/progpy/api_ref/progpy/IncludedModels.html), we can see that the input `i` refers to the current draw on the battery. The outputs `t` refers to the temperature in units Kelvin and `v` refers to voltage.\n", + "\n", + "We can also print out what events we're predicting and the internal states the model uses to represent the system." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print('event(s): ', batt.events)\n", + "print('states: ', batt.states)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can see that this particular model only predicts one event, called `EOD` (End of Discharge). The states listed include `tb`, the battery temperature in K; `qb`, the charge stored in Capacitor Cb of the equivalent circuit model; `qcp`, the charge stored in Capacitor Ccp of the equivalent circuit model; and `qcs`, the charge stored in Capacitor Ccs of the equivalent circuit model.\n", + "\n", + "Let's now look at the model's configuration parameters, which describe the specific system (in this case, the battery) that the model is simulating." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pprint import pprint\n", + "\n", + "print('Model configuration:')\n", + "pprint(batt.parameters)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's now use the model to do a simulation. To do this, we will first need to set a configuration and define a future load. For more details on future loading, refer to the related section in __[01 Simulation](01_Simulation.ipynb#Future-Loading)__." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "config = {\n", + " 'save_freq': 100,\n", + " 'dt': 2,\n", + " 't0': 700\n", + "}\n", + "\n", + "def future_loading(t, x=None):\n", + " if (t < 600):\n", + " i = 2\n", + " elif (t < 900):\n", + " i = 1\n", + " elif (t < 1800):\n", + " i = 4\n", + " elif (t < 3000):\n", + " i = 2 \n", + " else:\n", + " i = 3\n", + " return batt.InputContainer({'i': i})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's run the simulation and plot the inputs and outputs. We can do this using the built-in [plot method](https://nasa.github.io/progpy/api_ref/progpy/SimResult.html#progpy.sim_result.SimResult.plot) based on matplotlib or with other imported plotting libraries." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "simulated_results = batt.simulate_to_threshold(future_loading, **config)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the input plot, we can see the current drawn change based on the logic we defined in the future loading function." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig = simulated_results.inputs.plot(xlabel='time (s)', ylabel='current draw (amps)')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the output plots, we can observe how different input current draws affect the temperature and voltage curves. Generally, the graphs indicate that drawing a higher current leads to higher temperatures and lower voltage." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig = simulated_results.outputs.plot(keys=['t'], xlabel='time (s)', ylabel='temperature (K)', figsize=(10,4))\n", + "fig2 = simulated_results.outputs.plot(keys=['v'], xlabel='time (s)', ylabel='voltage (V)', figsize=(10,4))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### BatteryElectroChemEOD" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`BatteryElectroChemEOD` is a vectorized prognostics model for a battery, represented by an electrochemical equations as described in [[Daigle 2013]](https://papers.phmsociety.org/index.php/phmconf/article/view/2252). This model predicts the end of discharge event. Let's start by examining the model inputs, outputs, event(s), and states. We can refer to the [documentation](https://nasa.github.io/progpy/api_ref/progpy/IncludedModels.html) for more details." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from progpy.models import BatteryElectroChemEOD\n", + "\n", + "batt = BatteryElectroChemEOD()\n", + "\n", + "print('inputs:', batt.inputs)\n", + "print('outputs:', batt.outputs)\n", + "print('event(s): ', batt.events)\n", + "print('states:', batt.states)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's now run a simulation until `EOD`, or end of discharge. We wil use the same future loading function as the previous example and specify the configuration threshold event as `EOD`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "config = {\n", + " 'save_freq': 100,\n", + " 'dt': 2,\n", + " 'events': ['EOD']\n", + "}\n", + "\n", + "simulated_results = batt.simulate_to_threshold(future_loading, **config)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the input plot, we can see the current draw change based on the future loading function we defined." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig = simulated_results.inputs.plot(xlabel='time (s)', ylabel='current draw (amps)')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the output plots, we can see changes in voltage and temperature. We can also print parameters like `VEOD`, or the end of discharge voltage threshold. This value is the voltage at which a battery is considered fully discharged." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig = simulated_results.outputs.plot(keys=['v'], xlabel='time (s)', ylabel='voltage (V)', figsize=(10,4))\n", + "print(\"End of discharge voltage threshold:\", batt.parameters['VEOD'])\n", + "\n", + "fig2 = simulated_results.outputs.plot(keys=['t'], xlabel='time (s)', ylabel='temperature (°C)', figsize=(10,4))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the event state plot, we can see `EOD` decline until it reaches 0, or when the end of discharge event has occurred. This event occurence is when the simulation reached threshold and ended." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig = simulated_results.event_states.plot(xlabel='time (s)', ylabel='event state', labels={'EOD'})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### BatteryElectroChemEOL" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`BatteryElectroChemEOL` is a vectorized prognostics model for battery degradation, represented by an electrochemical model as described in [[Daigle 2016]](https://arc.aiaa.org/doi/pdf/10.2514/6.2016-2132). Let's go ahead and import the model, initialize a battery instance, and take a closer look at the details. We can also refer to the [documentation](https://nasa.github.io/progpy/api_ref/progpy/IncludedModels.html). Note that the model has no outputs. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from progpy.models import BatteryElectroChemEOL\n", + "\n", + "batt = BatteryElectroChemEOL()\n", + "\n", + "print('inputs:', batt.inputs)\n", + "print('outputs:', batt.outputs)\n", + "print('event(s): ', batt.events)\n", + "print('states:', batt.states)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's now run a simulation to predict when we will reach insufficient battery capacity. We will use the same future loading function as the previous examples and specify the configuration threhold event as `InsufficientCapacity`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "config = {\n", + " 'save_freq': 100,\n", + " 'dt': 2,\n", + " 'events': ['InsufficientCapacity']\n", + "}\n", + "\n", + "simulated_results = batt.simulate_to_threshold(future_loading, **config)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the input plot, we can once again see the current draw change based on the future loading function we defined." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig = simulated_results.inputs.plot(xlabel='time (s)', ylabel='current draw (amps)')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the event state plot, we can see `InsufficientCapacity` linearly decrease until it reaches 0, or when the event has occurred." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig = simulated_results.event_states.plot(xlabel='time (s)', ylabel='event state', labels={'InsufficientCapacity'})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Combined BatteryElectroChem (BatteryElectroChemEODEOL)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`BatteryElectroChemEODEOL` is a prognostics model for battery degradation and discharge, represented by an electrochemical model as described in [[Daigle 2013]](https://papers.phmsociety.org/index.php/phmconf/article/view/2252) and [[Daigle 2016]](https://arc.aiaa.org/doi/pdf/10.2514/6.2016-2132). This model combines both the `BatteryElectroChemEOL` and `BatteryElectroChemEOD` models.\n", + "\n", + "We will start by importing the model, initializing a battery instance, and examining the model details. We can once again refer to the [documentation](https://nasa.github.io/progpy/api_ref/progpy/IncludedModels.html) for more details." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from progpy.models import BatteryElectroChem\n", + "\n", + "batt = BatteryElectroChem()\n", + "\n", + "print('inputs:', batt.inputs)\n", + "print('outputs:', batt.outputs)\n", + "print('event(s): ', batt.events)\n", + "print('states:', batt.states)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this example, we will simulating a battery until `EOL` (End of Life). As battery capacity decreases with use, `EOL` is when the battery capacity falls below some acceptable threshold (i.e., what we define as useful capacity).\n", + "\n", + "We will now set the configuration and define a future loading function. As we want to simulate until `EOL`, we will set the configuration event to `InsufficientCapacity`. The future loading function is designed to charge the battery until `EOD` is 0.95 and then discharge until `EOD` is 0.05. Note that states represent the progress towards the event occurring. An event state of 0 indicates the event has occurred and 1 indicates no progress towards the event." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "config = {\n", + " 'save_freq': 1000,\n", + " 'dt': 2,\n", + " 'events': 'InsufficientCapacity',\n", + "}\n", + "\n", + "def future_loading(t, x=None):\n", + " load = 1\n", + " \n", + " if x is not None:\n", + " event_state = batt.event_state(x)\n", + " if event_state[\"EOD\"] > 0.95:\n", + " load = 1 # Discharge\n", + " elif event_state[\"EOD\"] < 0.05:\n", + " load = -1 # Charge\n", + " \n", + " return batt.InputContainer({'i': load})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will now simulate to the threshold and print the results." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "simulated_results = batt.simulate_to_threshold(future_loading, **config)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will now plot the inputs, outputs, and event states. In the input plot, we can see the current drawn fluctuate between -1 and 1 based on the current load we defined in the future loading function." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig = simulated_results.inputs.plot(xlabel='time (s)', ylabel='current drawn (amps)')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the output plots, we can see changes in the voltage and temperature." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig = simulated_results.outputs.plot(keys=['v'], xlabel='time (s)', ylabel='voltage (V)', figsize=(10,4))\n", + "fig2 = simulated_results.outputs.plot(keys=['t'], xlabel='time (s)', ylabel='temperature (°C)', figsize=(10,4))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the event states plot, we can see `EOD` incrementally spiking and `InsufficientCapacity` linearly declining until it reaches 0, or when the event has occurred." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "fig = simulated_results.event_states.plot(xlabel='time (s)', ylabel='event states', labels={'EOD', 'InsufficientCapacity'})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Simplified Battery" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`SimplifiedBattery` is a model from [[Sierra 2019]](https://www.sciencedirect.com/science/article/abs/pii/S0951832018301406). It was initially introduced in the __[2024 PHM Tutorial](2024PHMTutorial.ipynb)__. Unlike the previous models, the default parameters are for a Tattu battery. Let's start by importing the model, initializing an instance, and examining it." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from progpy.models import SimplifiedBattery\n", + "\n", + "batt = SimplifiedBattery()\n", + "\n", + "print('inputs:', batt.inputs)\n", + "print('outputs:', batt.outputs)\n", + "print('event(s): ', batt.events)\n", + "print('states:', batt.states)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will now define future loading based on a piecewise function and simulate to a set time." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "future_loading = Piecewise(\n", + " dict,\n", + " [600, 900, 1800, 3000, float('inf')],\n", + " {'P': [25, 12, 50, 25, 33]})\n", + "\n", + "result = batt.simulate_to(200, future_loading, {'v': 4.183})" ] }, { @@ -47,20 +612,15 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3.10.7 64-bit", + "display_name": ".venv", "language": "python", "name": "python3" }, "language_info": { "name": "python", - "version": "3.10.7" + "version": "3.12.9" }, - "orig_nbformat": 4, - "vscode": { - "interpreter": { - "hash": "610c699f0cd8c4f129acd9140687fff6866bed0eb8e82f249fc8848b827b628c" - } - } + "orig_nbformat": 4 }, "nbformat": 4, "nbformat_minor": 2