diff --git a/MJ2383_Lab_3.ipynb b/MJ2383_Lab_3.ipynb index d3beaf1..ca2dc8c 100644 --- a/MJ2383_Lab_3.ipynb +++ b/MJ2383_Lab_3.ipynb @@ -3,17 +3,18 @@ { "cell_type": "code", "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "import pandas as pd\n", "from plotly import express as ex\n", "from tqdm import trange\n", "import os" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "# MJ2383 Computer Lab 3\n", "\n", @@ -67,11 +68,11 @@ "- [Stage 2](#Stage-2:-Adding-Resources) - We add complexity, by increasing the number of steps in our supply curve by adding resources. We explore what difference this makes to the electricity price under different conditions.\n", "- [Stage 3](#Stage-3:-A-tax-on-pollution) - We add an emissions penalty, imposing a tax upon CO2\n", "- [Stage 4](#Stage-4:-Renewable-electricity-%22there's-no-such-thing-as-a-free-lunch%22) - We add renewable technologies to the electricity sector, whose marginal cost of generation is 0. However, this creates a demand for backup capacity. How does this affect the marginal price of electricity?" - ], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "## Stage 1: A super simple supply curve\n", "\n", @@ -90,12 +91,13 @@ "Plotted, this looks rather uninspiring, but at least gives an impression of a supply curve for natural gas.\n", "\n", "__Run the next cell to view the graph__\n" - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "gas_extraction = [[\"GasExtraction\", x, 8] for x in range(7)]\n", "gas_import = [[\"GasImport\", x, 12] for x in range(6, 21)]\n", @@ -105,21 +107,20 @@ "fig.update_traces(mode=\"markers+lines\")\n", "fig.update_xaxes(showspikes=True)\n", "fig.show()" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "In OSeMOSYS, supply of energy must equal demand for energy. As the quantity of energy demanded increases, energy supply increases accordingly. Given that our demand curve is a straight line (imagine a vertical line moving along the x-axis) the equilibrium point (where the lines cross) denotes the marginal cost of production and in this case is equal to the price.\n", "\n", "Within most energy systems, there are multiple markets for different, related, energy commodities. Later, we will use our simple OSeMOSYS model to begin to understand the interactions between these different energy markets." - ], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "### Exercise 1: Manually computing the marginal cost of electricity\n", "\n", @@ -128,11 +129,11 @@ "In our simple example, we demand a specific quantity of electricity. The electricity is produced by the `NGCC` technology which requires almost 2 units of gas to produce 1 unit of electricity.\n", "\n", "**Now answer the quiz question 1 on Canvas.**" - ], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "### Marginal Prices\n", "\n", @@ -145,41 +146,42 @@ "```\n", "\n", "We can extract some useful information from this equation when we solve the optimisation problem to minimise costs. These data are stored in the file `results/ProductionDual.csv`." - ], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "### Exercise 2: Understanding production\n", "\n", "We'll now run OSeMOSYS from this Jupyter Notebook to compute the equilibrium price:" - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "def write_demand(model, value):\n", " \"\"\"Write a ``value`` into the SpecifiedAnnualDemand file\n", " \"\"\"\n", " demand = pd.DataFrame([['SIMPLICITY', 'FEL', 2014, value]], columns=[\"REGION\",\"FUEL\",\"YEAR\",\"VALUE\"])\n", " demand.to_csv(os.path.join(model, \"data/SpecifiedAnnualDemand.csv\"), index=False)" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "In the plot below, we can see the total production by fuel over the year. " - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "demand = 7\n", "write_demand(\"model/gas\", demand)\n", @@ -188,12 +190,11 @@ "!glpsol -d temp.txt -m osemosys.txt > osemosys.log\n", "production = pd.read_csv('results/ProductionByTechnologyAnnual.csv').groupby(by=['TECHNOLOGY', 'FUEL']).sum()\n", "ex.bar(production.reset_index(), x='FUEL', y='VALUE', color='TECHNOLOGY')" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "However, production differs across the year - in OSeMOSYS we use `TIMESLICES` to represent the fractions of the year in which different levels of demand exist. This approximates the average demand profile for electricity and other fuels across days and seasons. In our example model, we have defined 6 time-slices:\n", "\n", @@ -205,70 +206,71 @@ "SN | Summer night\n", "WD | Winter day\n", "WN | Winter night" - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "production = pd.read_csv('results/ProductionByTechnology.csv').groupby(by=['TIMESLICE', 'FUEL']).sum()\n", "fig = ex.bar(production.reset_index(), x='FUEL', y='VALUE', color='TIMESLICE')\n", "fig.update_layout(barmode='group')" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "**Now answer quiz question 2 on Canvas**" - ], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "### Exercise 3: Using OSeMOSYS to compute the marginal price\n", "\n", "Below, we use a Python library called `pandas` to read the `ProductionDual` comma-separated values file. We extract the data point for `GAS` during the winter day (`WD`) timeslice." - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "cost = pd.read_csv('results/ProductionDual.csv').set_index(['TIMESLICE', 'FUEL'])\n", "cost['DUAL'] = cost['DUAL'] / (1.05**-0.5)\n", "cost.loc[('WD', 'GAS'),'DUAL']" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "By running the cell, we see that the marginal cost of producing gas, is equal to the cost of importing natural gas: about €12/PJ. Interesting. That must mean that the model is importing gas, rather than extracting it (which is cheaper).\n", "\n", "NB: The division of `(1.05**-0.5)` compounds the marginal value back to the base year. In OSeMOSYS, fixed and variable operating costs are discounted to the middle of the year.\n", "\n", "**Now answer quiz question 3 on Canvas. Hint: Consider tweaking and re-running selected parts of above code.**" - ], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "Plotted onto the supply curve we plotted earlier, we see that the results from the OSeMOSYS model make sense. The marginal cost of gas production from the OSeMOSYS model sits on the supply cost curve of the two gas resources.\n", "\n", "However, we only have one point for electricity. In the next stages, we will use the model to compute the marginal cost of electricity production." - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "fig = ex.line(gas_supply_curve, x='supply', y='marginal_cost', \n", " range_x=[0, 20], range_y=[0, 30], line_shape='vh')\n", @@ -295,32 +297,32 @@ " x0=elec_demand, x1=elec_demand, xref=\"x\", y0=0, y1=cost.loc[('WD', 'FEL'),'DUAL'])\n", "\n", "fig.show()" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "### Notes\n", "\n", "- The x-axis shows the supply quantity of energy\n", "- The marginal cost of electricity is plotted for reference against the quantity of electricity supplied, and marginal cost of gas against the quantity of gas supplied" - ], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "### Running the model to extract the marginal cost of production of electricity\n", "\n", "We can automate the running of the OSeMOSYS model over multiple levels of demand for electricity. The following code loops over demands ranging from 1 to *n* in increments of *x*. We write the demand into the CSV file `SpecifiedAnnualDemand.csv`, create the modelfile and then solve the model. We then extract the results from the `results/ProductionDual.csv` file and store them in a list. Finally, the function returns the list of results for further processing." - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "def extract_result(param: str, production, cost):\n", " \"\"\"Read the marginal cost values from the results\n", @@ -363,41 +365,40 @@ " results.append(extract_result('COA', production, cost))\n", "\n", " return results\n" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": null, - "source": [ - "gas_results = run_model(\"model/gas\", 1, 20, 1)" - ], - "outputs": [], "metadata": { "tags": [] - } + }, + "outputs": [], + "source": [ + "gas_results = run_model(\"model/gas\", 1, 20, 1)" + ] }, { "cell_type": "code", "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "gas_data = pd.DataFrame(gas_results)\n", "ex.line(gas_data, x='quantity', y='value', facet_col='param', range_x=[0, 20], range_y=[0, 40], \n", " line_shape='vh')" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "In the plot above, we now see the supply cost curves derived from the OSeMOSYS model. Given the simple supply cost curve for gas, with two stages and a breakpoint between 4.19 and 6.29 (we know that the cost of gas production changes at 6), you can see that the marginal cost of producing 2 units of electricity is 16.77 and the marginal cost of producing 3 or more units is 25.16." - ], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "## Summary of Stage 1\n", "\n", @@ -407,11 +408,11 @@ "- The OSeMOSYS code can be extended to write out marginal costs for all commodities in the model. This is particularly useful to show the marginal cost of derived commodities, such as electricity, which are produced from a variety of different energy chains.\n", "- By running our model at different levels of demand, we can use the model to compute the derived supply cost curve for each commodity.\n", "- We can use OSeMOSYS to explore the relationship between the structure of the energy system, supply cost curves for primary resources, and supply cost curves for secondary and tertiary energy commodities." - ], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "# Stage 2: Adding Resources\n", "\n", @@ -430,7 +431,7 @@ "GasExtraction | 10 | 8.0\n", "GasExtraction2 | 15 | 10.0\n", "GasExtraction3 | 30 | 11.0\n", - "GasImport | - | 20.0\n", + "GasImport | - | 12.0\n", "CoalExtraction | 10 | 4.0\n", "CoalExtraction2 | 15 | 5.0\n", "CoalExtraction3 | 30 | 5.5\n", @@ -452,85 +453,83 @@ "SCC | OutputActivityRatio(SEC_EL) | 1.0\n", "NGCC | ResidualCapacity | 30\n", "SCC| ResidualCapacity |30" - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": null, - "source": [ - "more_results = run_model(\"model/gas_more\", 1, 60, 2)" - ], - "outputs": [], "metadata": { "tags": [] - } + }, + "outputs": [], + "source": [ + "more_results = run_model(\"model/gas_more\", 1, 60, 2)" + ] }, { "cell_type": "code", "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "more_data = pd.DataFrame(more_results)\n", "ex.line(more_data, x='quantity', y='value', facet_col='param', \n", " range_x=[0, 130], range_y=[0, 45], line_shape='hv')" - ], - "outputs": [], - "metadata": { - "scrolled": false - } + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "We now have a much more complicated supply cost curve for all commodities. Four distinct steps are evident in the supply cost curve for gas (`GAS`), three/four for coal (`COA`), and seven in the the plot for final electricity (`FEL`). Each of these steps in the gas and coal plots correspond to one of the resources that we defined in this more complicated model. However, the supply cost curve for final electricity (`FEL`) is a function of the combination of the gas and coal cost curves, and depends on the blend of coal and gas used to supply electricity at each level of demand." - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "production = pd.read_csv('results/ProductionByTechnologyAnnual.csv').groupby(by=['TECHNOLOGY', 'FUEL']).sum()\n", "ex.bar(production.reset_index(), x='FUEL', y='VALUE', color='TECHNOLOGY')" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "### Exercise 4\n", "\n", "We can confirm the outputs from the model run with maximum demand by viewing the production of the different technologies. In the plot above you can view the quantity of coal and gas extracted or imported, and the quantity of secondary electricity produced by the power plants and final electricity transmitted to meet the demands.\n", "\n", "**Now, answer quiz question 4 on Canvas**" - ], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "### Exercise 5\n", "\n", "To answer this question, you will need to edit the OSeMOSYS parameter file which defines the price of resources. These is [`VariableCost.csv`](../edit/model/gas_more/data/VariableCost.csv). Don't forget saving the changes after editing.\n", "\n", "**Now, answer quiz question 5 on Canvas**" - ], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "### Exercise 6\n", "\n", "To answer this question, you will need to edit the OSeMOSYS parameter files which defines the efficiency of a technology. These are [`InputActivityRatio.csv`](../edit/model/gas_more/data/InputActivityRatio.csv) and [`OutputActivityRatio.csv`](../edit/model/gas_more/data/OutputActivityRatio.csv)\n", "\n", "**Now, answer quiz question 6 on Canvas**" - ], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "## Summary of Stage 2\n", "\n", @@ -538,11 +537,11 @@ "\n", "- Our computation of marginal costs using OSeMOSYS holds for a more complicated energy system structure\n", "- The short-run marginal cost of electricity is a function of the cost of resources and efficiency of the conversion plants" - ], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "# Stage 3: A tax on pollution\n", "\n", @@ -551,11 +550,11 @@ "### Exercise 7\n", "\n", "Before proceeding, please **answer quiz question 7 on Canvas**." - ], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "### Adding an emissions penalty to OSeMOSYS\n", "To impose a financial penalty in OSeMOSYS we can edit the [EmissionsPenalty](../edit/model/gas_more/data/EmissionsPenalty.csv) parameter file. Units are \\$/tCO2.\n", @@ -565,21 +564,22 @@ " SIMPLICITY,CO2,2014,50\n", " \n", "First try a value of $50/kCO2. You may use the plot below to compare your results with a zero emission price case (from the previous exercise)." - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "emission_results = run_model(\"model/gas_more\", 1, 60, 2)" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "emissions_data = pd.DataFrame(emission_results)\n", "emit_more_data = more_data.rename(columns={'value': 'noCO2price'})\n", @@ -587,45 +587,44 @@ " emit_more_data.set_index(['param', 'quantity']), left_index=True, right_index=True)\n", "ex.line(all_data.reset_index(), x='quantity', y=['value','noCO2price'], facet_col='param', \n", " range_x=[0, 65], range_y=[0, 80], line_shape='vh')" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "### Exercise 8\n", "Now please go to **quiz question 8 in Canvas**." - ], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "### Exercise 9\n", "Please answer **quiz question 9 on Canvas**." - ], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "### Summary of Stage 3\n", "In this stage, we have explored the effects of a carbon tax on the supply curves of gas, coal and final electricity. In the exercises on Canvas we have thought about the differences between carbon taxes and emission trading systems. Furthermore, we have analysed how a carbon price affects different fuels and how we can see these effects in the marginal cost curve of electricity." - ], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "# Stage 4: Renewable electricity\n", "In this stage we are adding a solar PV technology and a wind power technology to our model. We are now working with the model in `model/gas_solar_wind`. In the following we want to explore and think about the effects that the addition of solar PV and Wind have on the modelled system.\n", "![](img/gassolarwind.svg)" - ], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "The solar PV technology (`SOLPV`) and wind turbine technology (`WIND`) have the following characteristics:\n", "\n", @@ -650,68 +649,66 @@ "SN | 0.0 | 0.25\n", "WD | 0.25 | 0.25\n", "WN | 0.0 | 0.25" - ], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "### Exercise 10\n", "**Before** running our new model, please think about the effect that solar PV and wind power will have on the marginal cost curves that we looked at in the previous stages. And please **answer quiz question 10 on Canvas**." - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "results = run_model(\"model/gas_solar_wind\", 1, 60, 2)" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "renewable_data = pd.DataFrame(results)\n", "ex.line(renewable_data, x='quantity', y='value', facet_col='param', range_x=[0,65], range_y=[0,45], line_shape='vh')" - ], - "outputs": [], - "metadata": { - "scrolled": false - } + ] }, { "cell_type": "code", "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "production = pd.read_csv('results/ProductionByTechnologyAnnual.csv').groupby(by=['TECHNOLOGY', 'FUEL']).sum()\n", "ex.bar(production.reset_index(), x='FUEL', y='VALUE', color='TECHNOLOGY')" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "### Exercise 11\n", "Now please go to **quiz question 11 on Canvas**." - ], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "### Summary of Stage 4\n", "In this stage we explored the effects that renewable power generation technologies have on the marginal cost curve of electricity. We discovered that the introduction of renewable power sources:\n", "- does not necessarily push the technologies with the highest emissions out of the market\n", "- from a operational point of view a market does not necessarily create a well functionoing technology mix, e.g. the combination of coal power with renewable technologies creates operational challegnes due to the inertia of coal fired power plants" - ], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "# Extension Tasks\n", "\n", @@ -731,18 +728,18 @@ " \n", " \n", "- Return to stage 4 and add an `EmissionsPenalty` to the model, observe what happens to the supply cost curve depending on the hight of penalty you implement." - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": null, - "source": [], + "metadata": {}, "outputs": [], - "metadata": {} + "source": [] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "# Summary of lab 3\n", "In this lab we have explored the following:\n", @@ -750,8 +747,7 @@ "- the functioning of electricity markets and their sensitivity to price changes\n", "- the possibility to extract marginal costs from OSeMOSYS models\n", "- the use of a range of parameters to define a power system in OSeMOSYS" - ], - "metadata": {} + ] } ], "metadata": { @@ -771,9 +767,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.9" + "version": "3.8.8" } }, "nbformat": 4, - "nbformat_minor": 2 -} \ No newline at end of file + "nbformat_minor": 4 +}