Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Natural Gas Updates #440

Open
wants to merge 12 commits into
base: develop
Choose a base branch
from
9 changes: 5 additions & 4 deletions workflow/repo_data/config/config.sector.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ sector:
sequestration_potential: 0
policy: "config/policy_constraints/sector_co2_limits.csv"
natural_gas:
allow_imports_exports: true # false to be implemented
cyclic_storage: false
force_imports_exports: true
cyclic_storage: true
standing_loss: 0
methane:
leakage_rate: 2 # percent # to be implemented
gwp: 18 # to be implemented
leakage_rate: 0.02 # per unit
gwp: 18
heating:
heat_pump_sink_T: 55.
service_sector:
Expand Down
13 changes: 12 additions & 1 deletion workflow/scripts/add_sectors.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

from _helpers import configure_logging, get_snapshots, load_costs
from add_electricity import sanitize_carriers
from build_co2_tracking import build_co2_tracking
from build_emission_tracking import build_ch4_tracking, build_co2_tracking
from build_heat import build_heat
from build_natural_gas import StateGeometry, build_natural_gas
from build_stock_data import (
Expand Down Expand Up @@ -385,6 +385,8 @@ def get_pwr_co2_intensity(carrier: str, costs: pd.DataFrame) -> float:
co2_intensity = get_pwr_co2_intensity(carrier, costs)
convert_generators_2_links(n, carrier, f" gas", co2_intensity)

ng_options = snakemake.params.sector["natural_gas"]

# add natural gas infrastructure and data
build_natural_gas(
n=n,
Expand All @@ -394,8 +396,17 @@ def get_pwr_co2_intensity(carrier: str, costs: pd.DataFrame) -> float:
county_path=snakemake.input.county,
pipelines_path=snakemake.input.pipeline_capacity,
pipeline_shape_path=snakemake.input.pipeline_shape,
options=ng_options,
)

# add methane tracking - if leakage rate is included
# this must happen after natural gas system is built
methane_options = snakemake.params.sector["methane"]
leakage_rate = methane_options.get("leakage_rate", 0)
if leakage_rate > 0.000001:
gwp = methane_options.get("gwp", 1)
build_ch4_tracking(n, gwp, leakage_rate)

pop_layout_path = snakemake.input.clustered_pop_layout
cop_ashp_path = snakemake.input.cop_air_total
cop_gshp_path = snakemake.input.cop_soil_total
Expand Down
81 changes: 0 additions & 81 deletions workflow/scripts/build_co2_tracking.py

This file was deleted.

179 changes: 179 additions & 0 deletions workflow/scripts/build_emission_tracking.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
"""
Module for building state and sector level co2 tracking.
"""

import itertools
import logging
from typing import Any, Optional

import numpy as np
import pandas as pd
import pypsa

logger = logging.getLogger(__name__)


def build_co2_tracking(
n: pypsa.Network,
config: Optional[dict[str, Any]] = None,
) -> None:
"""
Main funtion to interface with.
"""

states = n.buses.STATE.unique()

sectors = ["pwr", "trn", "res", "com", "ind"]

if not config:
config = {}

if "co2" not in n.carriers:
_add_co2_carrier(n, config)

_build_co2_bus(n, states, sectors)
_build_co2_store(n, states, sectors)


def build_ch4_tracking(
n: pypsa.Network,
gwp: float,
leakage_rate: float,
config: Optional[dict[str, Any]] = None,
) -> None:
"""
Builds CH4 tracking.

Natural gas network must already be constructed
"""

states = [x for x in n.buses.STATE.dropna().unique() if x != np.nan]

if not config:
config = {}

if "ch4" not in n.carriers:
_add_ch4_carrier(n, config)

_build_ch4_bus(n, states)
_build_ch4_store(n, states)
_build_ch4_links(n, states, gwp, leakage_rate)


def _add_co2_carrier(n, config: dict[Any]):
try:
nice_name = config["plotting"]["nice_names"]["co2"]
except KeyError:
nice_name = "CO2"
try:
color = config["plotting"]["tech_colors"]["co2"]
except KeyError:
color = "#000000" # black

n.add("Carrier", "co2", nice_name=nice_name, color=color)


def _build_co2_bus(n: pypsa.Network, states: list[str], sectors: list[str]):
"""
Builds state level co2 bus per sector.
"""

df = pd.DataFrame(itertools.product(states, sectors), columns=["state", "sector"])
df.index = df.state + " " + df.sector

n.madd("Bus", df.index, suffix="-co2", carrier="co2", STATE=df.state)


def _build_co2_store(n: pypsa.Network, states: list[str], sectors: list[str]):
"""
Builds state level co2 stores per sector.
"""

df = pd.DataFrame(itertools.product(states, sectors), columns=["state", "sector"])
df.index = df.state + " " + df.sector

n.madd(
"Store",
df.index,
suffix="-co2",
bus=df.index + "-co2",
e_nom_extendable=False,
marginal_cost=0,
e_nom=np.inf,
e_initial=0,
e_cyclic=False,
e_cyclic_per_period=False,
standing_loss=0,
e_min_pu=0,
e_max_pu=1,
carrier="co2",
)


def _add_ch4_carrier(n, config: dict[Any]):
try:
nice_name = config["plotting"]["nice_names"]["ch4"]
except KeyError:
nice_name = "CH4"
try:
color = config["plotting"]["tech_colors"]["co2"]
except KeyError:
color = "#000000" # black

n.add("Carrier", "ch4", nice_name=nice_name, color=color)


def _build_ch4_bus(n: pypsa.Network, states: list[str]):
"""
Builds state level co2 bus per sector.
"""

df = pd.DataFrame(states, columns=["state"])
df.index = df.state

n.madd("Bus", df.index, suffix=" gas-ch4", carrier="ch4", STATE=df.state)


def _build_ch4_store(n: pypsa.Network, states: list[str]):
"""
Builds state level co2 stores per sector.
"""

df = pd.DataFrame(states, columns=["state"])
df.index = df.state

n.madd(
"Store",
df.index,
suffix=" gas-ch4",
bus=df.index + " gas-ch4",
e_nom_extendable=False,
marginal_cost=0,
e_nom=np.inf,
e_initial=0,
e_cyclic=False,
e_cyclic_per_period=False,
standing_loss=0,
e_min_pu=0,
e_max_pu=1,
carrier="ch4",
)


def _build_ch4_links(n, states: list[str], gwp: float, leakage_rate: float):
"""
Modifies existing gas production links.
"""

# first extract out exising gas production links

gas_production = [f"{x} gas production" for x in states]
links = n.links[n.links.index.isin(gas_production)].index

# calculate co2e value per unit injected to the ng system
emissions = gwp * leakage_rate

# append the connection to methane stores

n.links.loc[links, "bus2"] = n.links.loc[links,].bus1 + "-ch4" # 'CA gas-ch4'
n.links.loc[links, "efficency2"] = emissions
Loading