Skip to content

Commit

Permalink
add fuel limits
Browse files Browse the repository at this point in the history
  • Loading branch information
trevorb1 committed Nov 18, 2024
1 parent 47f9cb3 commit 3bbd91a
Show file tree
Hide file tree
Showing 7 changed files with 136 additions and 70 deletions.
20 changes: 17 additions & 3 deletions workflow/rules/preprocess.smk
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,6 @@ max_capacity_files = [
'TotalAnnualMaxCapacity',
'TotalTechnologyAnnualActivityUpperLimit',
'AccumulatedAnnualDemand',
# 'TotalTechnologyModelPeriodActivityUpperLimit'
]

user_capacity_files = [
Expand Down Expand Up @@ -176,16 +175,31 @@ rule powerplant_var_costs:
output:
var_costs = 'results/data/powerplant/VariableCost.csv'
log:
log = 'results/logs/powerplant.log'
log = 'results/logs/powerplant_var_cost.log'
script:
"../scripts/osemosys_global/powerplant/variable_costs.py"

rule fuel_limits:
message:
"Generating mining fuel limits..."
input:
region_csv = "results/data/REGION.csv",
technology_csv = "results/data/powerplant/TECHNOLOGY.csv",
fuel_limit_csv = "resources/data/fuel_limits.csv",
output:
model_period_limit_csv = 'results/data/powerplant/ModelPeriodActivityUpperLimit.csv'
log:
log = 'results/logs/powerplant_fuel_limits.log'
script:
"../scripts/osemosys_global/powerplant/fuel_limits.py"

rule transmission:
message:
"Generating transmission data..."
input:
rules.powerplant.output.csv_files,
rules.powerplant_var_costs.output,
'results/data/powerplant/VariableCost.csv',
'results/data/powerplant/ModelPeriodActivityUpperLimit.csv',
default_op_life = 'resources/data/operational_life.csv',
gtd_existing = 'resources/data/GTD_existing.csv',
gtd_planned = 'resources/data/GTD_planned.csv',
Expand Down
19 changes: 0 additions & 19 deletions workflow/scripts/osemosys_global/max_capacity.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,25 +272,6 @@ def apply_fuel_limits(region, years, output_data_dir, input_dir, max_fuel):
index=None,
)

# Model Period Activity Upper Limit for 'MINCOA***01'
min_tech_df = pd.read_csv(os.path.join(output_data_dir, "TECHNOLOGY.csv"))
min_tech = [
x
for x in min_tech_df["VALUE"].unique()
if x.startswith("MINCOA")
if x.endswith("01")
]
min_tech_df_final = pd.DataFrame(columns=["REGION", "TECHNOLOGY", "VALUE"])
min_tech_df_final["TECHNOLOGY"] = min_tech
min_tech_df_final["REGION"] = region
min_tech_df_final["VALUE"] = 0
min_tech_df_final.to_csv(
os.path.join(
output_data_dir, "TotalTechnologyModelPeriodActivityUpperLimit.csv"
),
index=None,
)


def apply_calibration(region, years, output_data_dir, calibration):

Expand Down
97 changes: 97 additions & 0 deletions workflow/scripts/osemosys_global/powerplant/fuel_limits.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
"""Applies fuel limits to mining technologies"""

import pandas as pd
from typing import Optional


def import_fuel_limits(f: str) -> pd.DataFrame:
"""Imports contry level fuel limits.
fuel_limits.csv
"""
return pd.read_csv(f)


def import_set(f: str) -> pd.Series:
s = pd.read_csv(f).squeeze()
if isinstance(s, pd.Series):
return s
else:
# single element series
return pd.Series(s)


def get_template_fuel_limit(regions: pd.Series, techs: pd.Series) -> pd.Series:

r = regions.unique().tolist()
t = (
techs[(techs.str.startswith("MIN")) & ~(techs.str.endswith("INT"))]
.unique()
.tolist()
)

idx = pd.MultiIndex.from_product([r, t], names=["REGION", "TECHNOLOGY"])
s = pd.Series(index=idx).fillna(0)
s.name = "VALUE"

return s


def get_user_fuel_limits(
regions: pd.Series, fuel_limits: Optional[pd.DataFrame] = None
) -> pd.Series:

r = regions.unique().tolist()
assert len(r) == 1
r = r[0]

if fuel_limits.empty:
s = pd.Series(index=["REGION", "TECHNOLOGY"])
s.name = "VALUE"
return s

limits = fuel_limits.copy()
limits = limits[limits.VALUE > 0.001].copy() # assume these are zero

# not sure what the year column represents?
limits = limits.drop(columns="YEAR").drop_duplicates(
subset=["FUEL", "COUNTRY"], keep="last"
)

limits["TECHNOLOGY"] = "MIN" + limits.FUEL + limits.COUNTRY

s = limits[["TECHNOLOGY", "VALUE"]].copy()
s["REGION"] = r

return s.set_index(["REGION", "TECHNOLOGY"]).squeeze()


def merge_template_user_limits(
template: pd.Series, user_limits: pd.Series
) -> pd.Series:
return user_limits.combine_first(template)


if __name__ == "__main__":

if "snakemake" in globals():
technology_csv = snakemake.input.technology_csv
fuel_limit_csv = snakemake.input.fuel_limit_csv
region_csv = snakemake.input.region_csv
model_period_limit_csv = snakemake.output.model_period_limit_csv
else:
technology_csv = "results/India/data/TECHNOLOGY.csv"
fuel_limit_csv = "resources/data/fuel_limits.csv"
region_csv = "results/India/data/REGION.csv"
model_period_limit_csv = ""

regions = import_set(region_csv)
techs = import_set(technology_csv)
fuel_limits = import_fuel_limits(fuel_limit_csv)

template = get_template_fuel_limit(regions, techs)
user_limits = get_user_fuel_limits(regions, fuel_limits)

model_period_limit = merge_template_user_limits(template, user_limits)

model_period_limit.to_csv(model_period_limit_csv, index=True)
7 changes: 0 additions & 7 deletions workflow/scripts/osemosys_global/powerplant/read.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,6 @@ def import_fuel_prices(f: str) -> pd.DataFrame:
"""
return pd.read_csv(f)

def import_fuel_limits(f: str) -> pd.DataFrame:
"""Imports contry level fuel limits.
fuel_limits.csv
"""
return pd.read_csv(f)

def import_set(f: str) -> pd.Series:
s = pd.read_csv(f).squeeze()
if isinstance(s, pd.Series):
Expand Down
38 changes: 1 addition & 37 deletions workflow/scripts/osemosys_global/powerplant/variable_costs.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import_cmo_forecasts,
import_fuel_prices,
import_set,
import_fuel_limits,
)

# can not use constant file defintions due to waste being tagged incorrectly
Expand Down Expand Up @@ -126,7 +125,7 @@ def expand_cmo_data(
df_international = df_international.mul(international_cost_multiplier)

df_country = df_template.copy().droplevel("COUNTRY")
df_country["COUNTRY"] = countries * len(df_country)
df_country["COUNTRY"] = [countries] * len(df_country)
df_country = df_country.explode("COUNTRY")
df_country = df_country.reset_index().set_index(
["FUEL", "COUNTRY", "UNIT", "ENERGY_CONTENT"]
Expand Down Expand Up @@ -273,33 +272,6 @@ def filter_var_cost_technologies(
return df[df.index.get_level_values("TECHNOLOGY").isin(available_techs)].copy()


def assign_international_limits(
var_costs: pd.DataFrame, fuel_limits: pd.DataFrame
) -> pd.DataFrame:
"""Checks if a mining tech can contribute to international markets
If a country has a fuel limit, the country can contribute to international markets. If the
country does not have a fuel limit, the international variable cost is set to a very high value.
"""

df = var_costs.copy()

cost = 999999

# no international trading
if fuel_limits.empty:
df["VALUE"] = cost
return df

limits = fuel_limits.copy()
limits["name"] = "MIN" + limits.FUEL + limits.COUNTRY
limited_techs = limits.name.unique().tolist()

df.loc[df.TECHNOLOGY.isin(limited_techs), "VALUE"] = cost

return df


def get_mining_data(
costs: pd.DataFrame, mining_fuels: list[str], region: str
) -> pd.DataFrame:
Expand Down Expand Up @@ -380,7 +352,6 @@ def main(
nuclear_costs: int | float,
waste_costs: int | float,
international_cost_factor: Optional[int | float] = None,
fuel_limits: Optional[pd.DataFrame] = None,
) -> pd.DataFrame:
"""Creates variable cost data"""

Expand Down Expand Up @@ -422,9 +393,6 @@ def main(
mining_fuels = MINING_FUELS
renewable_fuels = RENEWABLE_FUELS

if not isinstance(fuel_limits, pd.DataFrame):
fuel_limits = pd.DataFrame() # no international trading

# process cost data
df = apply_energy_content(df)
df = expand_merged_data(df, years)
Expand All @@ -446,20 +414,17 @@ def main(
file_regions = snakemake.input.regions
file_years = snakemake.input.years
file_technologies = snakemake.input.technologies
file_fuel_limits = snakemake.input.fuel_limits
file_var_costs = snakemake.output.var_costs
else:
file_cmo_forecasts = "resources/data/CMO-October-2024-Forecasts.xlsx"
file_fuel_prices = "resources/data/fuel_prices.csv"
file_regions = "results/India/data/REGION.csv"
file_years = "results/India/data/YEAR.csv"
file_technologies = "results/India/data/TECHNOLOGY.csv"
file_fuel_limits = "resources/data/fuel_limits.csv"
file_var_costs = "results/India/data/VariableCosts.csv"

cmo_forecasts = import_cmo_forecasts(file_cmo_forecasts)
user_fuel_prices = import_fuel_prices(file_fuel_prices)
user_fuel_limits = import_fuel_limits(file_fuel_limits)
technologies = import_set(file_technologies)
years = import_set(file_years)
regions = import_set(file_regions)
Expand All @@ -475,7 +440,6 @@ def main(
"nuclear_costs": NUCLEAR_VAR_COSTS,
"waste_costs": WASTE_VAR_COSTS,
"international_cost_factor": INT_COST_FACTOR,
"fuel_limits": user_fuel_limits,
}

df = main(**input_data)
Expand Down
18 changes: 14 additions & 4 deletions workflow/scripts/osemosys_global/transmission/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
import_max_cap_invest_base,
import_min_cap_invest_base,
import_res_cap_base,
import_set_base
import_set_base,
import_period_limit
)

from constants import(
Expand Down Expand Up @@ -75,7 +76,8 @@ def main(
min_cap_invest_base: pd.DataFrame,
res_cap_base: pd.DataFrame,
tech_set_base: pd.DataFrame,
fuel_set_base: pd.DataFrame
fuel_set_base: pd.DataFrame,
model_period_limit: pd.DataFrame
):

# CALL FUNCTIONS
Expand Down Expand Up @@ -110,6 +112,10 @@ def main(

# Adjust activity limits if cross border trade is not allowed following user config.
activity_limit_trn = activity_transmission_limit(cross_border_trade, oar_trn)
if activity_limit_trn.empty:
activity_limit = model_period_limit.copy()
else:
activity_limit = pd.concat([activity_limit_trn, model_period_limit])

# Set operational life for transmission.
op_life_trn = set_op_life_transmission(oar_trn, default_op_life, op_life_base, region_name)
Expand Down Expand Up @@ -180,7 +186,7 @@ def main(

iar_trn.to_csv(os.path.join(transmission_data_dir, "InputActivityRatio.csv"), index=None)

activity_limit_trn.to_csv(os.path.join(output_data_dir,
activity_limit.to_csv(os.path.join(output_data_dir,
"TotalTechnologyModelPeriodActivityUpperLimit.csv"),
index = None)

Expand Down Expand Up @@ -246,6 +252,7 @@ def main(
file_res_cap_base = f'{powerplant_data_dir}/ResidualCapacity.csv'
file_tech_set = f'{powerplant_data_dir}/TECHNOLOGY.csv'
file_fuel_set = f'{powerplant_data_dir}/FUEL.csv'
file_model_period_activity = f'{powerplant_data_dir}/ModelPeriodActivityUpperLimit.csv'

# The below else statement defines variables if the 'transmission/main' script is to be run locally
# outside the snakemake workflow. This is relevant for testing purposes only! User inputs when running
Expand Down Expand Up @@ -290,6 +297,7 @@ def main(
file_res_cap_base = f'{powerplant_data_dir}/ResidualCapacity.csv'
file_tech_set = f'{powerplant_data_dir}/TECHNOLOGY.csv'
file_fuel_set = f'{powerplant_data_dir}/FUEL.csv'
file_model_period_activity = f'{powerplant_data_dir}/ModelPeriodActivityUpperLimit.csv'

# SET INPUT DATA
gtd_exist = format_gtd_existing(import_gtd_existing(file_gtd_existing))
Expand Down Expand Up @@ -319,7 +327,8 @@ def main(
min_cap_invest_base = import_min_cap_invest_base(file_min_cap_invest_base)
res_cap_base = import_res_cap_base(file_res_cap_base)
tech_set_base = import_set_base(file_tech_set)
fuel_set_base = import_set_base(file_fuel_set)
fuel_set_base = import_set_base(file_fuel_set)
model_period_limit = import_period_limit(file_model_period_activity)

input_data = {
"default_op_life": op_life_dict,
Expand All @@ -340,6 +349,7 @@ def main(
"res_cap_base" : res_cap_base,
"tech_set_base" : tech_set_base,
"fuel_set_base" : fuel_set_base,
"model_period_limit" : model_period_limit
}

# CALL MAIN
Expand Down
7 changes: 7 additions & 0 deletions workflow/scripts/osemosys_global/transmission/read.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,13 @@ def import_res_cap_base(f: str) -> pd.DataFrame:
"""
return pd.read_csv(f)

def import_period_limit(f: str) -> pd.DataFrame:
"""Imports ModelPeriodActivityUpperLimit.csv as output from the Powerplant rule.
ModelPeriodActivityUpperLimit.csv
"""
return pd.read_csv(f)

def import_set_base(f: str) -> pd.DataFrame:
"""Imports a set csv"""
return pd.read_csv(f)

0 comments on commit 3bbd91a

Please sign in to comment.