Optimizing the design and operation of a DAC system under varying ambient conditions

This case study is based on a model developed here: 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.

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.

Create templates

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.

import adopt_net0 as adopt
import json
from pathlib import Path
import pandas as pd

# Create folder for results
results_data_path = Path("./userData")
results_data_path.mkdir(parents=True, exist_ok=True)
# Create input data path and optimization templates
input_data_path = Path("./caseStudies/dac")
input_data_path.mkdir(parents=True, exist_ok=True)
adopt.create_optimization_templates(input_data_path)
Files already exist: caseStudies\dac\Topology.json caseStudies\dac\ConfigModel.json

Adapt Topology

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. For the topology, we need to change the following:

  • Change nodes: nl

  • Change carriers: electricity, heat and CO2captured

  • Change investment periods: period1

  • The options regarding the time frame we can leave at the default (one year with hourly operation)

# Load json template
with open(input_data_path / "Topology.json", "r") as json_file:
    topology = json.load(json_file)
# Nodes
topology["nodes"] = ["nl"]
# Carriers:
topology["carriers"] = ["electricity", "heat", "CO2captured"]
# Investment periods:
topology["investment_periods"] = ["period1"]
# Save json template
with open(input_data_path / "Topology.json", "w") as json_file:
    json.dump(topology, json_file, indent=4)

Adapt Model Configurations

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).

  • Change objective to ‘costs_emissionlimit’ (this minimizes annualized costs at an emission limit)

  • Change emission limit to -1000 to account for the emission target

  • Change the number of typical days to 30 and select time aggregation method 1

# Load json template
with open(input_data_path / "ConfigModel.json", "r") as json_file:
    configuration = json.load(json_file)
# Change objective
configuration["optimization"]["objective"]["value"] = "costs_emissionlimit"
# Set emission limit:
configuration["optimization"]["emission_limit"]["value"] = -1000
# Set time aggregation settings:
configuration["optimization"]["typicaldays"]["N"]["value"] = 30
configuration["optimization"]["typicaldays"]["method"]["value"] = 1
# Set MILP gap
configuration["solveroptions"]["mipgap"]["value"] = 0.02
# Save json template
with open(input_data_path / "ConfigModel.json", "w") as json_file:
    json.dump(configuration, json_file, indent=4)

Define input data

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. Additionally we:

  • copy over technology data to the input data folder

  • define climate data for a dutch location

adopt.create_input_data_folder_template(input_data_path)

# Define node locations (here an exemplary location in the Netherlands)
node_locations = pd.read_csv(input_data_path / "NodeLocations.csv", sep=";", index_col=0)
node_locations.loc["nl", "lon"] = 5.5
node_locations.loc["nl", "lat"] = 52.5
node_locations.loc["nl", "alt"] = 10
node_locations.to_csv(input_data_path / "NodeLocations.csv", sep=";")

# Add DAC as a new technology
with open(input_data_path / "period1" / "node_data" / "nl" / "Technologies.json", "r") as json_file:
    technologies = json.load(json_file)
technologies["new"] = ["DAC_Adsorption"]
technologies["existing"] = {"PermanentStorage_CO2_simple": 10000}

with open(input_data_path / "period1" / "node_data" / "nl" / "Technologies.json", "w") as json_file:
    json.dump(technologies, json_file, indent=4)

# Copy over technology files
adopt.copy_technology_data(input_data_path)

# Define climate data
adopt.load_climate_data_from_api(input_data_path)

Run model - infeasibility

Now, we have defined all required data to run the model. It will be infeasible though…

m = adopt.ModelHub()
m.read_data(input_data_path)
m.quick_solve()

Allowing for heat and electricity import

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:

  • An abitrary import limit on heat and electricity (1GW)

  • An import price on electricity of 60 EUR/MWh

  • An import price on heat of 20 EUR/MWh

adopt.fill_carrier_data(input_data_path, value_or_data=1000, columns=['Import limit'], carriers=['electricity', 'heat'], nodes=['nl'])
adopt.fill_carrier_data(input_data_path, value_or_data=60, columns=['Import price'], carriers=['electricity'], nodes=['nl'])
adopt.fill_carrier_data(input_data_path, value_or_data=20, columns=['Import price'], carriers=['heat'], nodes=['nl'])

Run model again

Now, the model should be feasible

m = adopt.ModelHub()
m.read_data(input_data_path)
m.quick_solve()

Visualization

The results can be inspected using the provided visualization platform 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.

Costs

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.

Electricity and Heat requirements

DAC operation