{ "cells": [ { "attachments": {}, "cell_type": "markdown", "id": "c3b31311-c4da-43d1-b074-6b0432da7ea3", "metadata": {}, "source": [ "# Optimizing the design and operation of a DAC system under varying ambient conditions\n", "This case study is based on a model developed here:\n", "[Wiegner, Jan F., Alexa Grimm, Lukas Weimann, and Matteo Gazzani. \"Optimal design and operation of solid sorbent direct air capture processes at varying ambient conditions.\" Industrial & Engineering Chemistry Research 61, no. 34 (2022): 12649-12667.\n", "](https://pubs.acs.org/doi/10.1021/acs.iecr.2c00681)\n", "\n", "We want to optimize the design and operation of a DAC with a target net negative emissions of 1000 t in the Netherlands. We therefore load climate data for a location in the Netherlands. We store the CO2 in a not further defined CO2 sink." ] }, { "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": 2, "id": "35fa511b-d815-43d0-bf9f-21ca339151cf", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Files already exist: caseStudies\\dac\\Topology.json caseStudies\\dac\\ConfigModel.json\n" ] } ], "source": [ "import adopt_net0 as adopt\n", "import json\n", "from pathlib import Path\n", "import pandas as pd\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/dac\")\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](https://adopt-net0.readthedocs.io/en/latest/case_studies/CaseStudy_Networks.html)), via some lines of code.\n", "For the topology, we need to change the following:\n", "- Change nodes: nl\n", "- Change carriers: electricity, heat and CO2captured\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\"] = [\"nl\"]\n", "# Carriers:\n", "topology[\"carriers\"] = [\"electricity\", \"heat\", \"CO2captured\"]\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)" ] }, { "cell_type": "markdown", "id": "ad6ca662-97ac-4899-a03c-7a9fa591d28c", "metadata": {}, "source": [ "## Adapt Model Configurations\n", "Now, we need to adapt the model configurations respectively. As the DAC model is rather complex, we also cluster the full resolution into 50 typical days (method 1, see [here](https://adopt-net0.readthedocs.io/en/latest/advanced_topics/time_aggregation.html#clustering-into-typical-days)).\n", "- Change objective to 'costs_emissionlimit' (this minimizes annualized costs at an emission limit)\n", "- Change emission limit to -1000 to account for the emission target\n", "- Change the number of typical days to 30 and select time aggregation method 1 " ] }, { "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_emissionlimit\"\n", "# Set emission limit:\n", "configuration[\"optimization\"][\"emission_limit\"][\"value\"] = -1000\n", "# Set time aggregation settings:\n", "configuration[\"optimization\"][\"typicaldays\"][\"N\"][\"value\"] = 30\n", "configuration[\"optimization\"][\"typicaldays\"][\"method\"][\"value\"] = 1\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 DAC technology as a new technology to the respective node.\n", "Additionally we:\n", "- copy over technology data to the input data folder\n", "- define climate data for a dutch location" ] }, { "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", "# Define node locations (here an exemplary location in the Netherlands)\n", "node_locations = pd.read_csv(input_data_path / \"NodeLocations.csv\", sep=\";\", index_col=0)\n", "node_locations.loc[\"nl\", \"lon\"] = 5.5\n", "node_locations.loc[\"nl\", \"lat\"] = 52.5\n", "node_locations.loc[\"nl\", \"alt\"] = 10\n", "node_locations.to_csv(input_data_path / \"NodeLocations.csv\", sep=\";\")\n", "\n", "# Add DAC as a new technology\n", "with open(input_data_path / \"period1\" / \"node_data\" / \"nl\" / \"Technologies.json\", \"r\") as json_file:\n", " technologies = json.load(json_file)\n", "technologies[\"new\"] = [\"DAC_Adsorption\"]\n", "technologies[\"existing\"] = {\"PermanentStorage_CO2_simple\": 10000}\n", "\n", "with open(input_data_path / \"period1\" / \"node_data\" / \"nl\" / \"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", "# Define climate data\n", "adopt.load_climate_data_from_api(input_data_path)\n" ] }, { "cell_type": "markdown", "id": "af3c3914-594d-4064-81d7-10534bafb223", "metadata": {}, "source": [ "## Run model - infeasibility\n", "Now, we have defined all required data to run the model. It will be infeasible though..." ] }, { "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()" ] }, { "cell_type": "markdown", "id": "1b881e00-47b9-4df8-8e4c-48e905d52375", "metadata": {}, "source": [ "## Allowing for heat and electricity import\n", "The model is infeasible, because we did not define where heat or electricity should come from. Here, we allow for electricity and heat import at a certain price at no additional emissions. As such we define:\n", "- An abitrary import limit on heat and electricity (1GW)\n", "- An import price on electricity of 60 EUR/MWh\n", "- An import price on heat of 20 EUR/MWh" ] }, { "cell_type": "code", "execution_count": null, "id": "6070c1ec-7419-4c92-9418-6027db67f0dd", "metadata": {}, "outputs": [], "source": [ "adopt.fill_carrier_data(input_data_path, value_or_data=1000, columns=['Import limit'], carriers=['electricity', 'heat'], nodes=['nl'])\n", "adopt.fill_carrier_data(input_data_path, value_or_data=60, columns=['Import price'], carriers=['electricity'], nodes=['nl'])\n", "adopt.fill_carrier_data(input_data_path, value_or_data=20, columns=['Import price'], carriers=['heat'], nodes=['nl'])" ] }, { "cell_type": "markdown", "id": "221513d9-c0b7-4f41-b4ff-d1ec2661c25f", "metadata": {}, "source": [ "## Run model again\n", "Now, the model should be feasible" ] }, { "cell_type": "code", "execution_count": null, "id": "1fd87d92-c79c-4b3d-a93f-5dca21e00043", "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. You can find the files (e.g. Summary.xlsx or optimization_results.h5) to drag into the visualization platform in the \"userData\" folder. The figures below are screenshots from the visualization platform.\n", "\n", "### Costs\n", "The objective is 318075, which are the total annual costs (from the log or the Summary.xlsx). As we captured 1000 t of CO2, the specific capturing costs are around 318 EUR.\n", "\n", "### Electricity and Heat requirements\n", "
\n", "\n", "
\n", "\n", "### DAC operation\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 }