diff --git a/MJ2383_Lab_1.ipynb b/MJ2383_Lab_1.ipynb index 16e0111..6a99ed2 100644 --- a/MJ2383_Lab_1.ipynb +++ b/MJ2383_Lab_1.ipynb @@ -6,8 +6,9 @@ "metadata": {}, "outputs": [], "source": [ + "# Make sure to run this cell.\n", "# here we import all the libraries we will use in this session\n", - "import numpy as np # numpy is a numerical library for Python\n", + "import numpy as np\n", "import itertools\n", "from plotly import express as px\n", "import pandas as pd\n", @@ -44,7 +45,7 @@ "\n", "- If you like, work together in pairs sharing a screen.\n", "\n", - "- The notebook has been designed to help support your learning by enabling you to explore, but also to challenge. However, we are not trying to trick you. In most cases, the answers are in front of you.\n", + "- The notebook has been designed to help support your learning by enabling you to explore, but also to challenge you. However, we are not trying to trick you. In most cases, the answers are in front of you.\n", "\n", "- If you have questions, please ask us!\n", "- If you have any suggestions for how this could be improved, or find a bug or problem, please submit an issue\n", @@ -231,15 +232,15 @@ "\n", "According to [OEE](http://www.open-electricity-economics.org/book/text/03.html) the formula to calculate LCOE is as follows:\n", "\n", - "\\\\(LCOE = \\frac{C_{fix} + \\sum_{y=1}^Y DF_y \\cdot C_y}{\\sum_{y=1}^Y DF_y \\cdot G_y}\\\\)\n", + "$LCOE = \\frac{C_{fix} + \\sum_{y=1}^Y DF_y \\cdot C_y}{\\sum_{y=1}^Y DF_y \\cdot G_y}$\n", "\n", - "where LCOE is the levelized cost in EUR per kWh, \\\\(C_{fix}\\\\) is the capital investment costs incurred for setting up the project, \\\\(C_y\\\\) are operational costs incurred in year \\\\(y\\\\), \\\\(Y\\\\) is the technical lifetime in years, and \\\\(G_y\\\\) is electricity generation in kWh. The costs are called levelized because they are “leveled” over all units of output. Levelized costs can be calculated for a specific power plant or for generic types of generation technologies.\n", + "where LCOE is the levelized cost in EUR per kWh, $C_{fix}$ is the capital investment costs incurred for setting up the project, $C_y$ are operational costs incurred in year $y$, $Y$ is the technical lifetime in years, and $G_y$ is electricity generation in kWh. The costs are called levelized because they are “leveled” over all units of output. Levelized costs can be calculated for a specific power plant or for generic types of generation technologies.\n", "\n", - "Discount factor (DF) is calculated for each year \\\\(y\\\\) of the plant's technical lifetime\n", + "Discount factor ($DF$) is calculated for each year ($y$) of the plant's technical lifetime\n", "\n", - "\\\\(DF_y = (1 + r)^{-y}\\\\)\n", + "$DF_y = (1 + r)^{-y}$\n", "\n", - "where r is the discount rate" + "where $r$ is the discount rate" ] }, { @@ -262,7 +263,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "You can access the value stored in the *variable* at anytime, by typing its name and press `shift` + `enter`" + "You can access the value stored in the *variable* at anytime, by typing its name and press `shift` + `enter`:" ] }, { @@ -280,7 +281,9 @@ "source": [ "We then create an *array* of years using the value stored in ``technical_lifetime``. \n", "\n", - "An *array* is like a list of values. The command ``np.arange()`` takes a number as an argument and creates a *array* of the same length containing the values from 0 to one less than the number." + "An *array* is like a list of values. The command ``np.arange()`` takes a number as an argument and creates an *array* of the same length containing the values from 0 to one less than the number. You can always check what are the parameters that a command (or function) accepts and what it returns as result. For this you can place the cursor over the function and hit `shift + tab` in your keyboard:\n", + "\n", + "![Documentation](img/docstring.gif \"Documentation\")" ] }, { @@ -289,16 +292,16 @@ "metadata": {}, "outputs": [], "source": [ - "year = np.arange(technical_lifetime)\n", - "year" + "years = np.arange(technical_lifetime)\n", + "years" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Now we calculate DF for each year in our plant's technical lifetime according to the formula we saw earlier:\n", - "\\\\(DF_y = (1 + r)^{-y}\\\\); \\\\(r\\\\) is the discount rate, and \\\\(y\\\\) is the year.\n", + "Now we calculate $DF$ for each year in our plant's technical lifetime according to the formula we saw earlier:\n", + "$DF_y = (1 + r)^{-y}$; $r$ is the discount rate, and $y$ is the year.\n", "\n", "**Implement the DF formula in the cell below. Remember that to exponentiate in Python we use** `**`.\n", "\n", @@ -312,7 +315,7 @@ "outputs": [], "source": [ "discount_rate = 0.05\n", - "df = \"replace me with the DF formula\"\n", + "df = \"replace with formula\"\n", "print(df) # we use the command print, to show the values stored inside the df variable" ] }, @@ -323,14 +326,16 @@ "outputs": [], "source": [ "# Run this cell to check your answer\n", - "checker_df(df, discount_rate, year)" + "checker_df(value=df, \n", + " discount_rate=discount_rate, \n", + " years=years)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Now we've calculated DF, let's get some data for the other parameters:" + "Now we've calculated $DF$, let's get some data for the other parameters:" ] }, { @@ -365,7 +370,7 @@ "metadata": {}, "outputs": [], "source": [ - "lcoe = \"replace me with the LCOE formula\"\n", + "lcoe = \"replace with formula\"\n", "print(lcoe) # €/kWh" ] }, @@ -375,7 +380,12 @@ "metadata": {}, "outputs": [], "source": [ - "checker_lcoe(lcoe, capital_costs, operational_costs, electricity_generation, discount_rate, technical_lifetime)" + "checker_lcoe(value=lcoe, \n", + " capital_costs=capital_costs, \n", + " operational_costs=operational_costs, \n", + " electricity_generation=electricity_generation, \n", + " discount_rate=discount_rate, \n", + " technical_lifetime=technical_lifetime)" ] }, { @@ -416,8 +426,8 @@ " # Use the input variables of the function: \n", " # (capital_costs, operational_costs, electricity_generation, discount_rate, technical_lifetime)\n", " \n", - " # <>\n", - " lcoe = 'replace me with some code'\n", + " # \n", + " lcoe = \"replace with formula\"\n", " return lcoe" ] }, @@ -434,9 +444,22 @@ "metadata": {}, "outputs": [], "source": [ - "actual = get_lcoe(5000, np.array([100, 100, 100, 100]), np.array([8760, 8760, 8760, 8760]), 0.1, 4)\n", + "# With the next line we make a call to the function you wrote passing some input values\n", + "actual = get_lcoe(capital_costs=5000, \n", + " operational_costs=np.array([100, 100, 100, 100]), \n", + " electricity_generation=np.array([8760, 8760, 8760, 8760]), \n", + " discount_rate=0.1, \n", + " technical_lifetime=4)\n", + "# We print the results\n", "print(actual)\n", - "checker_lcoe(actual, 5000, np.array([100, 100, 100, 100]), np.array([8760, 8760, 8760, 8760]), 0.1, 4)" + "\n", + "# We run the checker with your calculation and the same input data, to see if the results is correct\n", + "checker_lcoe(value=actual, \n", + " capital_costs=5000, \n", + " operational_costs=np.array([100, 100, 100, 100]), \n", + " electricity_generation=np.array([8760, 8760, 8760, 8760]), \n", + " discount_rate=0.1, \n", + " technical_lifetime=4)" ] }, { @@ -479,7 +502,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We'll now make use of an LCOE function to explore the effect of different parameter values for one technology - a combined cycle gas turbine. In the following implementation of LCOE, we make the assumption that the yearly values for operational costs and electricity generation are the same in each year of the technology's lifetime." + "We'll now make use of an LCOE function to explore the effect of different parameter values for one technology — a combined cycle gas turbine. In the following implementation of LCOE, we make the assumption that the yearly values for operational costs and electricity generation are the same in each year of the technology's lifetime." ] }, { @@ -536,14 +559,14 @@ "\n", "# 1. Populate some variables using the information from the table\n", "\n", - "lifetime = 25\n", - "discount_rate = 0.08\n", - "capacity = 750000 # kW\n", - "overnight_cost = 750.0 # €/kW\n", - "fixed_om_cost = 3 # €/kW\n", - "efficiency = 0.5\n", - "fuel_price = 0.03 # €/kWh\n", - "load_factor = 0.75\n", + "lifetime = \n", + "discount_rate = \n", + "capacity = # kW\n", + "overnight_cost = # €/kW the cost of a project if no interest was incurred during construction, as if the project was completed \"overnight.\"\n", + "fixed_om_cost = # €/kW\n", + "efficiency = \n", + "fuel_price = # €/kWh\n", + "load_factor = \n", "\n", "# 2. Calculate the intermediate values (you can use Python like a calculator)\n", "# Use *+-/ for multiply, add, substract and divide.\n", @@ -562,8 +585,18 @@ "metadata": {}, "outputs": [], "source": [ - "# run this cell to check your answers \n", - "checker_parameters(electricity_generation, operational_costs, capacity, load_factor, fixed_om_cost, fuel_price, efficiency)" + "# Run this cell to check your answers. \n", + "# Note the input parameters needed by the function (remember that you can use `Shift + Tab` \n", + "# to see the documentation of the function). \n", + "# If you named your parameters differently, provide the right names to the function\n", + "checker_parameters(el_gen=electricity_generation, \n", + " op_costs=operational_costs, \n", + " capacity=capacity, \n", + " load_factor=load_factor, \n", + " fixed_om_cost=fixed_om_cost, \n", + " fuel_price=fuel_price, \n", + " efficiency=efficiency, \n", + " lifetime=lifetime)" ] }, { @@ -572,8 +605,12 @@ "metadata": {}, "outputs": [], "source": [ - "# 3. Now use the earlier LCOE equation we developed to calculate LCOE\n", - "actual = get_lcoe(capital_costs, operational_costs, electricity_generation, discount_rate, lifetime)\n", + "# 3. Now use the earlier LCOE equation we developed to calculate the LCOE\n", + "actual = get_lcoe(capital_costs=capital_costs, \n", + " operational_costs=operational_costs, \n", + " electricity_generation=electricity_generation, \n", + " discount_rate=discount_rate, \n", + " technical_lifetime=lifetime)\n", "print(actual)" ] }, @@ -591,12 +628,12 @@ "metadata": {}, "outputs": [], "source": [ - "def get_lcoe_input(station_size, overnight_cost, fuel_efficiency, \n", + "def get_lcoe_input(station_size, overnight_cost, fuel_efficiency, technical_lifetime,\n", " fuel_price, fixed_om_cost, load_factor, variable_om_cost):\n", " \"\"\"Calculates the input parameters for the lcoe function\n", " \n", - " Arguments\n", - " ---------\n", + " Parameters\n", + " ----------\n", " station_size : float\n", " The capacity of the technology in kW\n", " overnight_cost : float\n", @@ -620,11 +657,11 @@ " The capital cost of the technology €\n", " total_fixed_om_cost : float\n", " Fixed costs €\n", - " annual_electricity_generation : float\n", + " annual_electricity_generation : np.ndarray\n", " annual electricity production kWh\n", " total_variable_om_cost : float\n", " variable cost due to fuel use €\n", - " annual_operational_cost : float\n", + " annual_operational_cost : np.ndarray\n", " annual operational cost €\n", " \n", " \"\"\"\n", @@ -632,7 +669,7 @@ " \n", " capital_cost = station_size * overnight_cost\n", " total_fixed_om_cost = station_size * fixed_om_cost\n", - " annual_electricity_generation = station_size * HOURS_IN_YEAR * load_factor\n", + " annual_electricity_generation = np.repeat(station_size * HOURS_IN_YEAR * load_factor, technical_lifetime)\n", "\n", " total_variable_om_cost = ((annual_electricity_generation / fuel_efficiency) * fuel_price) + \\\n", " (annual_electricity_generation * variable_om_cost)\n", @@ -649,12 +686,12 @@ "metadata": {}, "outputs": [], "source": [ - "def extended_lcoe(station_size, overnight_cost, fuel_efficiency, fuel_price, fixed_om_cost, load_factor, \n", - " discount_rate, technical_lifetime, var_om_cost):\n", + "def extended_lcoe(station_size, overnight_cost, fuel_efficiency, fuel_price, fixed_om_cost, var_om_cost, \n", + " load_factor, discount_rate, technical_lifetime):\n", " \"\"\"Calculates levelised cost of electricity as a function of useful parameters\n", " \n", - " Arguments\n", - " ---------\n", + " Parameters\n", + " ----------\n", " station_size : float\n", " The capacity of the technology in kW\n", " overnight_cost : float\n", @@ -679,12 +716,12 @@ " float\n", " The levelised cost of electricity in €/kWh\n", " \"\"\"\n", - " lcoe_params = get_lcoe_input(station_size, overnight_cost, fuel_efficiency, \n", + " lcoe_params = get_lcoe_input(station_size, overnight_cost, fuel_efficiency, technical_lifetime,\n", " fuel_price, fixed_om_cost, load_factor, var_om_cost)\n", "\n", " value = get_lcoe(lcoe_params['capital_cost'], lcoe_params['annual_operational_cost'], \n", - " lcoe_params['annual_electricity_generation'], discount_rate, \n", - " technical_lifetime)\n", + " lcoe_params['annual_electricity_generation'], discount_rate, \n", + " technical_lifetime)\n", " return value" ] }, @@ -701,8 +738,15 @@ "metadata": {}, "outputs": [], "source": [ - "ccgt_lcoe = extended_lcoe(capacity, overnight_cost, efficiency, fuel_price, fixed_om_cost, 0.75, \n", - " discount_rate, lifetime, 0.003)\n", + "ccgt_lcoe = extended_lcoe(station_size=capacity, \n", + " overnight_cost=overnight_cost, \n", + " fuel_efficiency=efficiency, \n", + " fuel_price=fuel_price, \n", + " fixed_om_cost=fixed_om_cost,\n", + " var_om_cost=0, \n", + " load_factor=load_factor, \n", + " discount_rate=discount_rate, \n", + " technical_lifetime=lifetime)\n", "ccgt_lcoe" ] }, @@ -727,8 +771,16 @@ "metadata": {}, "outputs": [], "source": [ - "interact(extended_lcoe, station_size=(100000,750000, 10000), overnight_cost=(300, 2000), fuel_efficiency=(0.3, 0.7, 0.01), fuel_price=(0.00 , 0.20, 0.01), fixed_om_cost=(1, 50, 0.001), load_factor=(0.01, 1.0, 0.01), \n", - " discount_rate=(0.01, 0.30, 0.01), technical_lifetime=(10, 60, 1), var_om_cost=(0.0, 0.011, 0.001))" + "interact(extended_lcoe, \n", + " station_size=(100000,750000, 10000), \n", + " overnight_cost=(300, 2000), \n", + " fuel_efficiency=(0.3, 0.7, 0.01), \n", + " fuel_price=(0.00 , 0.20, 0.01), \n", + " fixed_om_cost=(1, 50, 0.001), \n", + " load_factor=(0.01, 1.0, 0.01), \n", + " discount_rate=(0.01, 0.30, 0.01), \n", + " technical_lifetime=(10, 60, 1), \n", + " var_om_cost=(0.0, 0.011, 0.001))" ] }, { @@ -768,7 +820,7 @@ "outputs": [], "source": [ "def lcoe_params(station_sizes, overnight_costs, fixed_om_costs, load_factors, efficiencies, \n", - " fuel_prices, discount_rates, technical_lifetimes, technology, var_om_costs):\n", + " fuel_prices, discount_rates, technical_lifetimes, technology, var_om_costs):\n", " \"\"\"Calculate LCOE for cartesian product of all parameter lists\n", " \"\"\"\n", " results = [] # Create a list to hold the observations\n", @@ -781,11 +833,11 @@ "\n", " observation = {} # Create a dictionary to store the observation\n", " \n", - " lcoe_params = get_lcoe_input(capacity, capex, efficiency, \n", + " lcoe_params = get_lcoe_input(capacity, capex, efficiency, technical_lifetimes,\n", " fuel_price, fixed_om_cost, load_factor, variable_om_cost)\n", "\n", - " lcoe = extended_lcoe(capacity, capex, efficiency, fuel_price, fixed_om_cost, \n", - " load_factor, discount_rate, technical_lifetime, variable_om_cost)\n", + " lcoe = extended_lcoe(capacity, capex, efficiency, fuel_price, fixed_om_cost, variable_om_cost,\n", + " load_factor, discount_rate, technical_lifetime)\n", " \n", " observation['Technology'] = technology\n", " observation['StationSize'] = capacity\n", @@ -799,14 +851,14 @@ " observation['LCOE'] = lcoe\n", " \n", " year = np.arange(technical_lifetime)\n", - " total_df = sum((1 + discount_rate) ** - year)\n", + " df = (1 + discount_rate) ** - year\n", " \n", - " observation['Fixed OM Cost'] = (total_df * lcoe_params['total_fixed_om_cost']) / \\\n", - " (total_df * lcoe_params['annual_electricity_generation'])\n", - " observation['Variable OM Cost'] = (total_df * lcoe_params['total_variable_om_cost']) / \\\n", - " (total_df * lcoe_params['annual_electricity_generation'])\n", + " observation['Fixed OM Cost'] = sum(df * lcoe_params['total_fixed_om_cost']) / \\\n", + " sum(df * lcoe_params['annual_electricity_generation'])\n", + " observation['Variable OM Cost'] = sum(df * lcoe_params['total_variable_om_cost']) / \\\n", + " sum(df * lcoe_params['annual_electricity_generation'])\n", " observation['Capital Cost'] = lcoe_params['capital_cost'] / \\\n", - " (total_df * lcoe_params['annual_electricity_generation'])\n", + " sum(df * lcoe_params['annual_electricity_generation'])\n", "\n", " results.append(observation)\n", " \n", @@ -918,7 +970,8 @@ "metadata": {}, "outputs": [], "source": [ - "df = plotting.melt(id_vars=['Technology', 'LoadFactor', 'DiscountRate'], value_vars=['Fixed OM Cost', 'Variable OM Cost', 'Capital Cost'])\n", + "df = plotting.melt(id_vars=['Technology', 'LoadFactor', 'DiscountRate'], \n", + " value_vars=['Fixed OM Cost', 'Variable OM Cost', 'Capital Cost'])\n", "\n", "px.area(df, x='LoadFactor', y='value', color='variable', facet_col='Technology', facet_row='DiscountRate',\n", " labels={'LoadFactor': 'LoadFactor (%)', 'value': 'LCOE (€/kW)'})" @@ -947,7 +1000,7 @@ "hash": "af8ebb4e2d22b06da4313823d442847307ae4d45e401ef0669a9fed88d20d2aa" }, "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -961,7 +1014,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.8" + "version": "3.8.13" } }, "nbformat": 4, diff --git a/checkers.py b/checkers.py index cade3cc..2edc3be 100644 --- a/checkers.py +++ b/checkers.py @@ -2,10 +2,26 @@ import sys from IPython.display import display, Markdown, Latex -def checker_df(value, discount_rate, year): - df = (1 + discount_rate) ** -year +def checker_df(value, discount_rate, years): + """Checks if the values of the discount factor are correct based on the discount rate and years array as inputs. + + Parameters + ---------- + value: np.ndarray + Array of discount factors over the years. + discount_rate: float + Discount rate as fraction. + years: np.ndarray + Array of years over the analysis period. + + Returns + ------- + str + Displays wheter the calculated value is correct or not. + """ + df = (1 + discount_rate) ** -years if isinstance(value, np.ndarray): - if len(value) != len(year): + if len(value) != len(years): return print("The length of your array should be the same as the number of years.") elif sum(df == value) == len(df): return print("Your answer is correct!") @@ -23,15 +39,36 @@ def checker_lcoe( operational_costs, electricity_generation, discount_rate, - technical_lifetime, + technical_lifetime ): + """Checks if the calculated lcoe value is correct based on the capital costs, operational costs, electricity generation, discount rate and technical lifetime of the project. + + Parameters + ---------- + value: float + Calculated lcoe value to check. + capital_costs: float + Capital costs of the project. + operational_costs: np.ndarray + Array of operational costs over the analysis period. + electricity_generation: np.ndarray + Array of generated electricity over the analysis period. + discount_rate: float + Discount rate as fraction. + technical_lifetime: int + Years of the analysis period. + + Returns + ------- + str + Displays wheter the calculated value is correct or not. + """ try: if isinstance(value, float): year = np.arange(technical_lifetime) - df = (1 + discount_rate) ** -year - lcoe = (capital_costs + sum(df * operational_costs)) / sum( - df * electricity_generation - ) + df = (1 + discount_rate) ** - year + lcoe = (capital_costs + sum(df * operational_costs)) / \ + sum(df * electricity_generation) if value == lcoe: return print("Your answer is correct!") else: @@ -50,24 +87,51 @@ def checker_parameters( load_factor, fixed_om_cost, fuel_price, - efficiency + efficiency, + lifetime ): - electricity_generation = capacity * load_factor * 8760 # in kWh + """Checks if the electricity calculations and the operational costs are calculated correctly. + + Parameters + ---------- + el_gen: np.ndarray + Array of generated electricity over the analysis period. + op_costs: np.ndarray + Array of operational costs over the analysis period. + capacit: float + Installed capacity of the plant. + load_factor: float + The load factor describes the proportion of those 8760 hours the plant is operational. + fixed_om_cost: float + Fix costs of the power plant per kW of capacity. + fuel_price: float + Fuel price per kWh used. + efficiency: float + Energy convertion efficiency of the plant. + lifetime: int + Years of the analysis period. + + Returns + ------- + str + Displays wheter the calculated value is correct or not. + """ + electricity_generation = np.repeat(capacity * load_factor * 8760, lifetime) # in kWh annual_fixed_om_cost = capacity * fixed_om_cost # in € annual_variable_om_cost = fuel_price * electricity_generation / efficiency # in € operational_costs = annual_fixed_om_cost + annual_variable_om_cost # in € try: - if el_gen == electricity_generation: + if sum(el_gen) == sum(electricity_generation): str_out = 'Your electricity generation is correct!\n' else: str_out = f'Your electricity generation is not right... it should be {electricity_generation}\n' - if op_costs == operational_costs: + if sum(op_costs) == sum(operational_costs): str_out += 'Your operational costs are correct!' else: str_out += f'Your operational costs are not right... they should be {operational_costs}' return print(str_out) except: return print("Unexpected error: {}".format(sys.exc_info()[0]), - 'Make sure your electricity generation and operational values are of type float') + 'Make sure your electricity generation and operational values are of type np.ndarray') raise diff --git a/img/docstring.gif b/img/docstring.gif new file mode 100644 index 0000000..43d8be0 Binary files /dev/null and b/img/docstring.gif differ