{ "cells": [ { "attachments": {}, "cell_type": "markdown", "id": "c3b31311-c4da-43d1-b074-6b0432da7ea3", "metadata": {}, "source": [ "# Design and operations of energy technologies for a household (PV, heat pump, battery)\n", "\n", "\n", "In this case study we find the optimal sizes and operations of a heat pump, a set of solar panels and a battery to power the energy needs (heat and electricity) of a household over 1 year. The household, which has certain heat and electricity demand profiles, has an existing connection to the grid and an existing gas boiler. The emissions of CO2 are taxed. We show a schematic representation of the system in the figure below.\n", "\n", "
\n", "\n", "
" ] }, { "cell_type": "markdown", "id": "c76a7dea-3a71-4286-b28b-1259124a8df0", "metadata": {}, "source": [ "## Create templates\n", "We set the input data path and in this directory we can add input data templates for the model configuration and the topology with the function create_optimization_templates.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "35fa511b-d815-43d0-bf9f-21ca339151cf", "metadata": {}, "outputs": [], "source": [ "import adopt_net0 as adopt\n", "import json\n", "import pandas as pd\n", "from pathlib import Path\n", "import numpy as np\n", "\n", "# Create folder for results\n", "results_data_path = Path(\"./userData\")\n", "results_data_path.mkdir(parents=True, exist_ok=True)\n", "# Create input data path and optimization templates\n", "input_data_path = Path(\"./caseStudies/household\")\n", "input_data_path.mkdir(parents=True, exist_ok=True)\n", "adopt.create_optimization_templates(input_data_path)" ] }, { "cell_type": "markdown", "id": "f0d8ee0c-234f-4222-a2b0-700540c1292a", "metadata": {}, "source": [ "## Adapt Topology\n", "We need to adapt the topology as well as the model configuration file to our case study. This can be done either in the file itself (Topology.json) or, as we do it here, via some lines of code.\n", "For the topology, we need to change the following:\n", "- Change nodes: household\n", "- Change carriers: electricity, heat and gas\n", "- Change investment periods: period1\n", "- The options regarding the time frame we can leave at the default (one year with hourly operation)" ] }, { "cell_type": "code", "execution_count": null, "id": "5ad41f8f-43cc-4edd-a663-e66c8969339a", "metadata": {}, "outputs": [], "source": [ "# Load json template\n", "with open(input_data_path / \"Topology.json\", \"r\") as json_file:\n", " topology = json.load(json_file)\n", "# Nodes\n", "topology[\"nodes\"] = [\"household\"]\n", "# Carriers:\n", "topology[\"carriers\"] = [\"electricity\", \"heat\", \"gas\"]\n", "# Investment periods:\n", "topology[\"investment_periods\"] = [\"period1\"]\n", "# Save json template\n", "with open(input_data_path / \"Topology.json\", \"w\") as json_file:\n", " json.dump(topology, json_file, indent=4)\n" ] }, { "cell_type": "markdown", "id": "ad6ca662-97ac-4899-a03c-7a9fa591d28c", "metadata": {}, "source": [ "## Adapt Model Configurations\n", "Now, we need to adapt the model configurations respectively. In this case, we don't use any particular algorithm (clustering, time staging etc), so here we only specify what we want to minimize - costs in this example - and set the gap for the MILP solver to a desired value. \n" ] }, { "cell_type": "code", "execution_count": null, "id": "f459d0aa-b2b6-40a9-b63c-1f55bfbdd07f", "metadata": {}, "outputs": [], "source": [ "# Load json template\n", "with open(input_data_path / \"ConfigModel.json\", \"r\") as json_file:\n", " configuration = json.load(json_file)\n", "# Change objective\n", "configuration[\"optimization\"][\"objective\"][\"value\"] = \"costs\"\n", "# Set MILP gap\n", "configuration[\"solveroptions\"][\"mipgap\"][\"value\"] = 0.02\n", "# Save json template\n", "with open(input_data_path / \"ConfigModel.json\", \"w\") as json_file:\n", " json.dump(configuration, json_file, indent=4)" ] }, { "cell_type": "markdown", "id": "eeba77ce-2e8f-451e-9f58-408121561959", "metadata": {}, "source": [ "## Define input data\n", "We first create all required input data files based on the topology file and then add the existing technologies (gas boiler, 3kW capacity) and potential new technologies (heat pump, PV, battery). Since we have potentially a PV, we need to specify the location of the household.\n", "Additionally, we copy over technology data to the input data folder\n" ] }, { "cell_type": "code", "execution_count": null, "id": "090672de-cd62-46ed-af4b-86c70e69f659", "metadata": {}, "outputs": [], "source": [ "adopt.create_input_data_folder_template(input_data_path)\n", "\n", "# Add heat pump, PV and battery as new technologies\n", "with open(input_data_path / \"period1\" / \"node_data\" / \"household\" / \"Technologies.json\", \"r\") as json_file:\n", " technologies = json.load(json_file)\n", "technologies[\"new\"] = [\"Photovoltaic\",\"HeatPump_AirSourced\",\"Storage_Battery\"]\n", "technologies[\"existing\"] = {\"Boiler_Small_NG\": 0.003}\n", "\n", "with open(input_data_path / \"period1\" / \"node_data\" / \"household\" / \"Technologies.json\", \"w\") as json_file:\n", " json.dump(technologies, json_file, indent=4)\n", "\n", "# Copy over technology files\n", "adopt.copy_technology_data(input_data_path)\n", "\n", "# Specify location of the household\n", "node_location = pd.read_csv(input_data_path / \"NodeLocations.csv\", sep=';', index_col=0, header=0)\n", "node_location.at['household', 'lon'] = 4.9\n", "node_location.at['household', 'lat'] = 52\n", "node_location.at['household', 'alt'] = 10\n", "node_location = node_location.reset_index()\n", "node_location.to_csv(input_data_path / \"NodeLocations.csv\", sep=';', index=False)\n" ] }, { "cell_type": "markdown", "id": "a79ac7c3-249a-4795-b19e-5ddbaa11ca49", "metadata": {}, "source": [ "## Read demand data, import limits/price\n", "In this case study we want to model an hourly heat and electricity demand of the household, with variable electricity prices. In addition, we set a fixed value for gas and electricity import limits and gas prices. The examplary demand profiles and electricity profiles are provided with the package. As such we:\n", "- Read day-ahead electricity prices (hourly for the Netherlands in 2023)\n", "- Read hourly heat (total 7.3MWh) and electricity demand (total 4MWh)\n", "- Limit import of gas (5kW) and electricity (8kW)\n", "- Set an import price on gas of 120 EUR/MWh\n", "\n", "Finally, we set a carbon tax to 100 EUR/t.\n", "\n", "N.B all the data should be in MW, EUR/MWh, EUR/t, t/MWh" ] }, { "cell_type": "code", "execution_count": null, "id": "db1cc45c-6734-4a5b-b1ec-11af446ab43e", "metadata": {}, "outputs": [], "source": [ "# Read hourly data from Excel\n", "household_hourly_data = adopt.load_household_data()\n", "\n", "# Save the hourly data to the carrier's file in the case study folder\n", "# electricity demand and price\n", "el_price = household_hourly_data.iloc[:, 2]\n", "el_demand = household_hourly_data.iloc[:, 1]\n", "heat_demand = household_hourly_data.iloc[:, 0]\n", "adopt.fill_carrier_data(input_data_path, value_or_data=el_price, columns=['Import price'], carriers=['electricity'], nodes=['household'])\n", "adopt.fill_carrier_data(input_data_path, value_or_data=el_price, columns=['Export price'], carriers=['electricity'], nodes=['household'])\n", "adopt.fill_carrier_data(input_data_path, value_or_data=el_demand, columns=['Demand'], carriers=['electricity'], nodes=['household'])\n", "adopt.fill_carrier_data(input_data_path, value_or_data=heat_demand, columns=['Demand'], carriers=['heat'], nodes=['household'])\n", "\n", "# Set import limits/cost\n", "adopt.fill_carrier_data(input_data_path, value_or_data=0.005, columns=['Import limit'], carriers=['gas'], nodes=['household'])\n", "adopt.fill_carrier_data(input_data_path, value_or_data=0.008, columns=['Import limit'], carriers=['electricity'], nodes=['household'])\n", "adopt.fill_carrier_data(input_data_path, value_or_data=120, columns=['Import price'], carriers=['gas'], nodes=['household'])\n", "\n", "# Set carbon emission price\n", "carbon_price = np.ones(8760)*100\n", "carbon_cost_path = \"./caseStudies/household/period1/node_data/household/CarbonCost.csv\"\n", "carbon_cost_template = pd.read_csv(carbon_cost_path, sep=';', index_col=0, header=0)\n", "carbon_cost_template['price'] = carbon_price\n", "carbon_cost_template = carbon_cost_template.reset_index()\n", "carbon_cost_template.to_csv(carbon_cost_path, sep=';', index=False)" ] }, { "cell_type": "markdown", "id": "af3c3914-594d-4064-81d7-10534bafb223", "metadata": {}, "source": [ "## Run model\n", "Now, we have defined all required data to run the model" ] }, { "cell_type": "code", "execution_count": null, "id": "193d2a5c-6121-454a-bf1e-f06796add068", "metadata": {}, "outputs": [], "source": [ "m = adopt.ModelHub()\n", "m.read_data(input_data_path)\n", "m.quick_solve()" ] }, { "attachments": {}, "cell_type": "markdown", "id": "76619a94-fdfb-4e97-81bf-4baa251b18b4", "metadata": {}, "source": [ "## Visualization\n", "The results can be inspected using the provided [visualization platform](https://resultvisualization.streamlit.app/) for some basic plots. The figures below are screenshots from the visualization platform. The results data are saved in the userData folder.\n", "\n", "### Size of the new technologies\n", "The optimization shows that the only technology installed is the heat pump. This means that, in these conditions, it is not economically favourable to install a PV or a battery. The size of the heat pump is 0.6 kW (based on electricity input, not heat output), which complements the existing gas boiler in covering the household's heat demand.\n", "\n", "## Electricity and Heat requirements\n", "
\n", "\n", "
\n", "\n", "### Heat pump operation\n", "
\n", "\n", "
\n", "\n" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "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.12.3" } }, "nbformat": 4, "nbformat_minor": 5 }