{
"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", " | Time | \n", "Prices | \n", "
|---|---|---|
| 0 | \n", "2020-01-02 00:30:00 | \n", "67.094337 | \n", "
| 1 | \n", "2020-01-02 01:00:00 | \n", "65.526907 | \n", "
| 2 | \n", "2020-01-02 01:30:00 | \n", "50.028502 | \n", "
| 3 | \n", "2020-01-02 02:00:00 | \n", "40.664717 | \n", "
| 4 | \n", "2020-01-02 02:30:00 | \n", "43.609028 | \n", "
| ... | \n", "... | \n", "... | \n", "
| 331 | \n", "2020-01-08 22:00:00 | \n", "44.572158 | \n", "
| 332 | \n", "2020-01-08 22:30:00 | \n", "51.387228 | \n", "
| 333 | \n", "2020-01-08 23:00:00 | \n", "44.822428 | \n", "
| 334 | \n", "2020-01-08 23:30:00 | \n", "44.136238 | \n", "
| 335 | \n", "2020-01-09 00:00:00 | \n", "49.485932 | \n", "
336 rows × 2 columns
\n", "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': 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", " | variable_name | \n", "variable_id | \n", "interval | \n", "lower_bound | \n", "upper_bound | \n", "type | \n", "
|---|---|---|---|---|---|---|
| 0 | \n", "H2E-mw_load_sum | \n", "336 | \n", "None | \n", "0.0 | \n", "inf | \n", "continuous | \n", "
| \n", " | variable_name | \n", "variable_id | \n", "interval | \n", "lower_bound | \n", "upper_bound | \n", "type | \n", "
|---|---|---|---|---|---|---|
| 0 | \n", "H2E-mw_load | \n", "0 | \n", "0 | \n", "0.0 | \n", "100.0 | \n", "continuous | \n", "
| 1 | \n", "H2E-mw_load | \n", "1 | \n", "1 | \n", "0.0 | \n", "100.0 | \n", "continuous | \n", "
| 2 | \n", "H2E-mw_load | \n", "2 | \n", "2 | \n", "0.0 | \n", "100.0 | \n", "continuous | \n", "
| 3 | \n", "H2E-mw_load | \n", "3 | \n", "3 | \n", "0.0 | \n", "100.0 | \n", "continuous | \n", "
| 4 | \n", "H2E-mw_load | \n", "4 | \n", "4 | \n", "0.0 | \n", "100.0 | \n", "continuous | \n", "
| ... | \n", "... | \n", "... | \n", "... | \n", "... | \n", "... | \n", "... | \n", "
| 1677 | \n", "H2E-msl_relieve | \n", "1677 | \n", "331 | \n", "0.0 | \n", "1.0 | \n", "binary | \n", "
| 1678 | \n", "H2E-msl_relieve | \n", "1678 | \n", "332 | \n", "0.0 | \n", "1.0 | \n", "binary | \n", "
| 1679 | \n", "H2E-msl_relieve | \n", "1679 | \n", "333 | \n", "0.0 | \n", "1.0 | \n", "binary | \n", "
| 1680 | \n", "H2E-msl_relieve | \n", "1680 | \n", "334 | \n", "0.0 | \n", "1.0 | \n", "binary | \n", "
| 1681 | \n", "H2E-msl_relieve | \n", "1681 | \n", "335 | \n", "0.0 | \n", "1.0 | \n", "binary | \n", "
1682 rows × 6 columns
\n", "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