{ "cells": [ { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "# 1. Simple Electrolyser Load Operation\n", "\n", "This example demonstrates a very simple application of the `NEMGLO` package; extracting historical NEM price data from AEMO (via NEMOSIS), defining load characteristics, then running the optimiser to find the operational behaviour of the load." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Install Packages\n", "For standard use of NEMGLO we can use from nemglo import * to import `nemglo` functionality. This example also uses plotly to generate charts, with the optional setting defining where to render charts." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "# NEMGLO Packages\n", "from nemglo import *\n", "\n", "# Generic Packages\n", "import pandas as pd\n", "import plotly.graph_objects as go\n", "from plotly.subplots import make_subplots" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "# Display plotly chart in a browser (optional)\n", "import plotly.io as pio\n", "pio.renderers.default = \"browser\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Load Historical AEMO data\n", "Create a nemosis_data object to retrieve historical data for the simulation. nemosis_data class requires a defined interval length and cache folder." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "inputdata = nemosis_data(intlength=30, local_cache=r'E:\\TEMPCACHE')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we define the simulation period by start and end dispatch intervals, as well as the region for which we are modelling the load in. These parameters are set by using functions set_ of the `nemosis_data` class." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "start = \"02/01/2020 00:00\"\n", "end = \"09/01/2020 00:00\"\n", "region = 'VIC1'" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "inputdata.set_dates(start, end)\n", "inputdata.set_region(region)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Price data can now be loaded from the data object by using the get_prices function.\n", "Notice the data has been aggregated to 30-minute intervals as initially defined (the original downloaded AEMO data is in 5-minute dispatch interval resolution)" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Compiling data for table DISPATCHPRICE.\n", "Returning DISPATCHPRICE.\n" ] }, { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
TimePrices
02020-01-02 00:30:0067.094337
12020-01-02 01:00:0065.526907
22020-01-02 01:30:0050.028502
32020-01-02 02:00:0040.664717
42020-01-02 02:30:0043.609028
.........
3312020-01-08 22:00:0044.572158
3322020-01-08 22:30:0051.387228
3332020-01-08 23:00:0044.822428
3342020-01-08 23:30:0044.136238
3352020-01-09 00:00:0049.485932
\n", "

336 rows × 2 columns

\n", "
" ], "text/plain": [ " Time Prices\n", "0 2020-01-02 00:30:00 67.094337\n", "1 2020-01-02 01:00:00 65.526907\n", "2 2020-01-02 01:30:00 50.028502\n", "3 2020-01-02 02:00:00 40.664717\n", "4 2020-01-02 02:30:00 43.609028\n", ".. ... ...\n", "331 2020-01-08 22:00:00 44.572158\n", "332 2020-01-08 22:30:00 51.387228\n", "333 2020-01-08 23:00:00 44.822428\n", "334 2020-01-08 23:30:00 44.136238\n", "335 2020-01-09 00:00:00 49.485932\n", "\n", "[336 rows x 2 columns]" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "prices = inputdata.get_prices()\n", "prices" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Create a planner object for this modelling session\n", "For every use case of NEMGLO, a `nemglo.planner.Plan` object is required. This must be created with a unique identifier, in this case called **\"H2_VIC\"**. The first step is then to load market prices for the simulation period for which the load will be optimised for. The function load_market_prices stores these required values.\n", "\n", "```{tip} Simplify your model by naming the python variable and the identifier parameter of the Plan class as the same.\n", "```" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "H2_VIC = Plan(identifier = \"H2_VIC\")\n", "H2_VIC.load_market_prices(prices)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This data is now stored as attributes in the `Plan` class. You can check these values, which are now lists, if you wish by _timeseries and _prices. For example, the first interval is:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Timestamp('2020-01-02 00:30:00')" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "H2_VIC._timeseries[0]" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "67.09433666666666" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "H2_VIC._prices[0]" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## Create an Electrolyser object + defining operating characteristics\n", "A load must be defined and linked to the `Plan` object in order to model it's behaviour. For creating components in `NEMGLO`, they must be defined as belonging to a `Plan` class. This is done by parsing the variable of the `Plan` object which we created in this python session, here **H@_VIC** (conveniently the same as the identifier name). Similarly to `Plan`, all components must have a unique identifer which has been called **H2E** here for the Electrolyser." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "h2e = Electrolyser(H2_VIC, identifier='H2E')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "There are numerous parameters we can now defined for the Electrolyser using load_h2_parameters_preset. At a minimum the following must be specified of the electrolyser:\n", "`capacity`: rated capacity [MW], \n", "`maxload`: maximum load [MW], \n", "`minload`: minimum stable loading (MSL) [MW],\n", "`offload`: off state [MW],\n", "`electrolyser_type`: either 'PEM' or 'AE',\n", "`sec_profile`: specific energy consumption as either 'fixed' or 'variable'.\n", "\n", "For this scenario we will also define `h2_price_kg`: the production benefit price of hydrogen [$/kg]. Although this is optional, if a price incentive is not defined, a production target must be set for the amount of hydrogen produced, otherwise the electrolyser will do nothing." ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "h2e.load_h2_parameters_preset(capacity = 100.0,\n", " maxload = 100.0,\n", " minload = 10.0,\n", " offload = 0.0,\n", " electrolyser_type = 'PEM',\n", " sec_profile = 'fixed',\n", " h2_price_kg = 6.0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The parameters are now stored in our `Electrolyser` object. We can check this by:" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'_system_plan': ,\n", " '_id': 'H2E',\n", " '_capacity': 100.0,\n", " '_maxload': 100.0,\n", " '_minload': 10.0,\n", " '_offload': 0.0,\n", " '_type': 'PEM',\n", " '_profile': 'fixed',\n", " '_sec_nominal': 66,\n", " '_sec_conversion': 1.0,\n", " '_sec_system': 66.0,\n", " '_sec_variable_points': h2e_load_pct nominal_sec_pct\n", " 0 0.0 0.8383\n", " 1 0.2 0.8383\n", " 2 0.4 0.8598\n", " 3 0.6 0.8982\n", " 4 0.8 0.9315\n", " 5 1.0 1.0101,\n", " '_h2_price_kg': 6.0,\n", " '_storage_max': None,\n", " '_storage_initial': None,\n", " '_storage_final': None,\n", " '_storage_drain': None}" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "h2e.__dict__" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To create necessary variables and constraints in the optimisation model we call the function add_electrolyser_operation. The specific variables and constraints created will depend on the input parameters as defined by the previous function." ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "h2e.add_electrolyser_operation()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can check the variables created using the view_variable function of the `Plan` object, and parse the name (or part of the name) for the variables we want to check. For example, specifying the variable **\"H2E-mw_load_sum\"** yields the following.\n", "\n", "Alternatively, if we parse the `identifier` of the component `Electrolyser` which is **\"H2E\"** we will see all optimisation variables belonging to that component." ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
variable_namevariable_idintervallower_boundupper_boundtype
0H2E-mw_load_sum336None0.0infcontinuous
\n", "
" ], "text/plain": [ " variable_name variable_id interval lower_bound upper_bound type\n", "0 H2E-mw_load_sum 336 None 0.0 inf continuous" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "H2_VIC.view_variable('H2E-mw_load_sum')" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
variable_namevariable_idintervallower_boundupper_boundtype
0H2E-mw_load000.0100.0continuous
1H2E-mw_load110.0100.0continuous
2H2E-mw_load220.0100.0continuous
3H2E-mw_load330.0100.0continuous
4H2E-mw_load440.0100.0continuous
.....................
1677H2E-msl_relieve16773310.01.0binary
1678H2E-msl_relieve16783320.01.0binary
1679H2E-msl_relieve16793330.01.0binary
1680H2E-msl_relieve16803340.01.0binary
1681H2E-msl_relieve16813350.01.0binary
\n", "

1682 rows × 6 columns

\n", "
" ], "text/plain": [ " variable_name variable_id interval lower_bound upper_bound \\\n", "0 H2E-mw_load 0 0 0.0 100.0 \n", "1 H2E-mw_load 1 1 0.0 100.0 \n", "2 H2E-mw_load 2 2 0.0 100.0 \n", "3 H2E-mw_load 3 3 0.0 100.0 \n", "4 H2E-mw_load 4 4 0.0 100.0 \n", "... ... ... ... ... ... \n", "1677 H2E-msl_relieve 1677 331 0.0 1.0 \n", "1678 H2E-msl_relieve 1678 332 0.0 1.0 \n", "1679 H2E-msl_relieve 1679 333 0.0 1.0 \n", "1680 H2E-msl_relieve 1680 334 0.0 1.0 \n", "1681 H2E-msl_relieve 1681 335 0.0 1.0 \n", "\n", " type \n", "0 continuous \n", "1 continuous \n", "2 continuous \n", "3 continuous \n", "4 continuous \n", "... ... \n", "1677 binary \n", "1678 binary \n", "1679 binary \n", "1680 binary \n", "1681 binary \n", "\n", "[1682 rows x 6 columns]" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "H2_VIC.view_variable('H2E')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Running the optimisation\n", "Since we have loaded a price trace into the model and added operating characteristics of the Electrolyser, we can now run the optimisation model via the `Plan` object function optimise. By default this is set to use the `CBC` solver. If you wish to save the optimisation results you can set `save_results` as True, likewise for debug files which are the low-level variable and constraints tables which formulate the optimisation problem, set `save_debug` as True. If you define these you can also specific the root filepath where you wish to save the results as `results_dir`. If this is left unchanged, a new results folder will be created in your current working directory. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "H2_VIC.optimise(solver_name=\"CBC\", save_debug=False, save_results=False, results_dir=None)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### View planner results\n", "The results can also be extracted within python through a number of functions.\n", "\n", "- The timeseries data for the load dispatch each interval is found with `get_load`.\n", "\n", "- The total energy consumption of the load in MW is found with `get_total_consumption`.\n", "\n", "- The load energy capacity factor is found with `get_load_capacity_factor`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "result_load = H2_VIC.get_load()\n", "result_load" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "H2_VIC.get_total_consumption()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "H2_VIC.get_load_capacity_factor()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Plotting results\n", "Constructing a chart in plotly of `result_load` and `price` produces the below." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# A visual representation of the results can be displayed using plotly to\n", "# compare the load result and input market price traces.\n", "fig = make_subplots(specs=[[{\"secondary_y\":True}]])\n", "fig.update_layout(title='NEMGLO Dispatch & Pricing Example 1
VIC: Jan-2020', titlefont=dict(size=24),\n", " xaxis=dict(title=\"Time\", showgrid=False, mirror=True, titlefont=dict(size=24), \\\n", " tickfont=dict(size=24), tickangle=-45, tickformat=\"%d-%b\"),\n", " yaxis=dict(title=\"Dispatch (MW)\", showgrid=False, range=[-10,140], mirror=True, titlefont=dict(size=24),\\\n", " tickfont=dict(size=24)),\n", " legend=dict(xanchor='center',x=0.5, y=-0.35, orientation='h', font=dict(size=20)),\n", " template=\"simple_white\",\n", " font_family=\"Times New Roman\",\n", " xaxis_showgrid=True,\n", " yaxis_showgrid=True,\n", " #width=800,\n", " height=600)\n", "fig.update_yaxes(title=\"Price ($/MWh)\", showgrid=False, gridcolor='slategrey', range=[-250,150], mirror=True, titlefont=dict(size=24),\\\n", " tickfont=dict(size=24), secondary_y=True)\n", "fig.add_trace(go.Scatter(x=prices['Time'], y=prices['Prices'],name=\"Price ($/MWh)\", \\\n", " line={'color':'#972f42'}),secondary_y=True)\n", "fig.add_trace(go.Scatter(x=result_load['time'], y=result_load['value'],name='Load (MW)', \\\n", " line={'color':'#7F22A6'}),secondary_y=False)\n", "\n", "\n", "fig.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```{include} example_1_plt_1.html \n", "```\n", "[
](example_1_plt_1.html)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3.9.12 ('nempy')", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.12" }, "vscode": { "interpreter": { "hash": "4aab49ac747d4948ee2428bd46f4ac833ef94a37ecb38233c747c75e4d05fe4b" } } }, "nbformat": 4, "nbformat_minor": 2 }