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

Implements Backstop Technologies #242

Merged
merged 9 commits into from
Dec 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions workflow/scripts/osemosys_global/check_backstop.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""Prints warning if backstop techs are used"""

import pandas as pd
import sys
from pathlib import Path

if __name__ == "__main__":

if len(sys.argv) != 2:
raise ValueError("Usage: python create_checkbackstop.py <sceanrio_name>")
else:
scenario = sys.argv[1]

new_capacity_csv = Path("results", scenario, "results", "NewCapacity.csv")
try:
new_capacity = pd.read_csv(new_capacity_csv)
except FileNotFoundError:
sys.exit()

backstop = new_capacity[new_capacity.TECHNOLOGY.str.startswith("PWRBCK")]

if not backstop.empty:
techs = backstop.TECHNOLOGY.unique().tolist()
print("\n*****\n")
print("The following Backstop Technologies are being used:\n")
print(techs)
print("\n*****\n")
66 changes: 66 additions & 0 deletions workflow/scripts/osemosys_global/powerplant/backstop.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
"""Sets backstop parameters"""

import pandas as pd


def get_backstop_data(
tech_set: pd.DataFrame, year_set: pd.DataFrame, region: str
) -> tuple[pd.DataFrame]:
"""Gets the following backstop data

- Technologies
- OAR
- Capital Cost
- Fixed Cost
- CapacityToActivity
"""

df = tech_set.copy()

# technologies
techs = df[df.VALUE.str.startswith("PWR")].copy() # pwrtrn not added yet
techs["VALUE"] = "PWRBCK" + df.VALUE.str[-7:-2]
techs = techs.drop_duplicates()
bck_techs = techs.VALUE.to_list()

# activity ratios
years = year_set.VALUE.to_list()
oar = pd.DataFrame(
index=pd.MultiIndex.from_product(
[bck_techs, years], names=["TECHNOLOGY", "YEAR"]
)
).reset_index()
oar["REGION"] = region
oar["FUEL"] = oar.TECHNOLOGY.str.replace("PWRBCK", "ELC")
oar["FUEL"] = oar.FUEL + "01"
oar["MODE_OF_OPERATION"] = 1
oar["VALUE"] = 1
oar = oar[["REGION", "TECHNOLOGY", "FUEL", "MODE_OF_OPERATION", "YEAR", "VALUE"]]

# capital cost
capex = pd.DataFrame(
index=pd.MultiIndex.from_product(
[bck_techs, years], names=["TECHNOLOGY", "YEAR"]
)
).reset_index()
capex["REGION"] = region
capex["VALUE"] = 999999
capex = capex[["REGION", "TECHNOLOGY", "YEAR", "VALUE"]]

# fixed costs
opex = pd.DataFrame(
index=pd.MultiIndex.from_product(
[bck_techs, years], names=["TECHNOLOGY", "YEAR"]
)
).reset_index()
opex["REGION"] = region
opex["VALUE"] = 999999
opex = opex[["REGION", "TECHNOLOGY", "YEAR", "VALUE"]]

# capacity to activity
capact = pd.DataFrame(index=pd.Index(bck_techs, name="TECHNOLOGY")).reset_index()
capact["REGION"] = region
capact["VALUE"] = 31.536
capact = capact[["REGION", "TECHNOLOGY", "VALUE"]]

return techs, oar, capex, opex, capact
33 changes: 14 additions & 19 deletions workflow/scripts/osemosys_global/powerplant/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@

from calibration import apply_calibration

from backstop import get_backstop_data

def main(
plexos_prop: pd.DataFrame,
plexos_memb: pd.DataFrame,
Expand Down Expand Up @@ -139,22 +141,15 @@ def main(
region_name)
# Calculate residual capacity.
df_res_cap = res_capacity(gen_table, DUPLICATE_TECHS, start_year, end_year, region_name)

# if custom_nodes:


# Adds residual capacity for custom entries.
df_res_cap, custom_techs = add_custom_res_cap(df_res_cap, custom_res_cap,
tech_list, start_year,
end_year, region_name)

# Creates sets for TECHNOLOGIES including custom entries.
tech_set = create_sets('TECHNOLOGY', df_oar_final, powerplant_data_dir, custom_techs)
# else:
# custom_techs = []

# Creates sets for TECHNOLOGIES absent custom entries.
# tech_set = create_sets('TECHNOLOGY', df_oar_final, powerplant_data_dir, [])


# Creates sets for FUEL.
fuel_set = create_sets('FUEL', df_oar_final, powerplant_data_dir, [])

Expand Down Expand Up @@ -237,6 +232,14 @@ def main(
df_res_cap,
region_name)

# add backstop technologies
bck_techs, bck_oar, bck_capex, bck_opex, bck_capact = get_backstop_data(tech_set, years_set, region_name)
tech_set = pd.concat([tech_set, bck_techs])
df_oar_final = pd.concat([df_oar_final, bck_oar])
df_cap_cost_final = pd.concat([df_cap_cost_final, bck_capex])
df_fix_cost_final = pd.concat([df_fix_cost_final, bck_opex])
df_capact_final = pd.concat([df_capact_final, bck_capact])

# OUTPUT CSV's USED AS INPUT FOR TRANSMISSION RULE

df_res_cap.to_csv(os.path.join(powerplant_data_dir, "ResidualCapacity.csv"), index=None)
Expand Down Expand Up @@ -312,8 +315,6 @@ def main(
input_data_dir = snakemake.params.input_data_dir
powerplant_data_dir = snakemake.params.powerplant_data_dir
file_specified_annual_demand = f'{output_data_dir}/SpecifiedAnnualDemand.csv'

# if custom_nodes:
file_custom_res_cap = snakemake.input.custom_res_cap
file_custom_res_potentials = snakemake.input.custom_res_potentials

Expand Down Expand Up @@ -351,13 +352,11 @@ def main(
fossil_capacity_targets = [["BTNXX", 'COA', 2030, 2050, 'ABS', 1],
["INDNE", 'CCG', 2040, 2050, 'MIN', 10],
["INDSO", 'OCG', 2025, 2050, 'MAX', 25]]
min_generation_factors = {'OCG1': [50, "IND", 2021]}
calibration = {'OCG1': [50, "IND", 2021]}
output_data_dir = 'results/data'
input_data_dir = 'resources/data'
powerplant_data_dir = 'results/data/powerplant'
file_specified_annual_demand = f'{output_data_dir}/SpecifiedAnnualDemand.csv'

#if custom_nodes:
file_custom_res_cap = 'resources/data/custom_nodes/residual_capacity.csv'
file_custom_res_potentials = 'resources/data/custom_nodes/RE_potentials.csv'

Expand Down Expand Up @@ -385,13 +384,9 @@ def main(

availability = import_afs(file_default_af_factors)
specified_annual_demand = import_specified_annual_demand(file_specified_annual_demand)

#if custom_nodes:

custom_res_cap = import_custom_res_cap(file_custom_res_cap)
custom_res_potentials = import_custom_res_potentials(file_custom_res_potentials)
#else:
# custom_res_cap = []
# custom_res_potentials = []

input_data = {
"plexos_prop": plexos_prop,
Expand Down
33 changes: 29 additions & 4 deletions workflow/scripts/osemosys_global/powerplant/variable_costs.py
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,26 @@ def _get_country_nodes_mapper(nodes: list[str]) -> dict[str, list[str]]:
return df.set_index(["REGION", "TECHNOLOGY", "MODE_OF_OPERATION", "YEAR"])


def get_backstop_var_costs(
techs: pd.Series, years: pd.Series, regions: pd.Series
) -> pd.Series:
"""Gets backstop variable costs"""

t = techs[techs.str.startswith("PWRBCK")].unique().tolist()
y = years.unique().tolist()
r = regions.unique().tolist()[0] # only one region

df = pd.DataFrame(
index=pd.MultiIndex.from_product(
[[r], t, y], names=["REGION", "TECHNOLOGY", "YEAR"]
)
).reset_index()
df["MODE_OF_OPERATION"] = 1
df["VALUE"] = 999999

return df.set_index(["REGION", "TECHNOLOGY", "MODE_OF_OPERATION", "YEAR"])


def main(
cmo_forecasts: pd.DataFrame,
cmo_data_year: int,
Expand Down Expand Up @@ -403,6 +423,11 @@ def main(
var_costs = pd.concat([mining_data, renewable_data])
var_costs = var_costs[var_costs.index.get_level_values("YEAR").isin(y)]
var_costs = filter_var_cost_technologies(var_costs, technologies)

bck_var_costs = get_backstop_var_costs(technologies, years, regions)

var_costs = pd.concat([var_costs, bck_var_costs])

return var_costs.round(3)


Expand All @@ -418,10 +443,10 @@ def main(
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_var_costs = "results/India/data/VariableCosts.csv"
file_regions = "results/data/REGION.csv"
file_years = "results/data/YEAR.csv"
file_technologies = "results/data/powerplant/TECHNOLOGY.csv"
file_var_costs = "results/data/powerplant/VariableCosts.csv"

cmo_forecasts = import_cmo_forecasts(file_cmo_forecasts)
user_fuel_prices = import_fuel_prices(file_fuel_prices)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
def set_reserve_margin_technologies(margins_technologies,
tech_set, start_year,
end_year, region_name):

years = get_years(start_year, end_year)
data = pd.DataFrame()

'''Check if transmission is added to the config to contribute to
reserves and develop outputs.'''
for tech, val in margins_technologies.items():
Expand All @@ -20,21 +20,45 @@ def set_reserve_margin_technologies(margins_technologies,
for x in tech_set["VALUE"].unique()
if x.startswith("TRN")
]

else:
rm_techs = [
x
for x in tech_set["VALUE"].unique()
if x.startswith("PWR")
if x[3:6] == tech
]

tech_data = pd.DataFrame(
list(itertools.product([region_name], rm_techs, years)),
columns=["REGION", "TECHNOLOGY", "YEAR"],
)

tech_data['VALUE'] = val / 100
data = pd.concat([data, tech_data])

return data
backstop = get_backstop_rm(tech_set, region_name, start_year, end_year)

return pd.concat([data, backstop])


def get_backstop_rm(
tech_set: pd.DataFrame, region: str, start_year: int, end_year: int
) -> pd.DataFrame:
"""Backstop does not contribute to reserve margin"""

bck_techs = tech_set[tech_set.VALUE.str.startswith("PWRBCK")]

if bck_techs.empty:
return pd.DataFrame(columns=["REGION", "TECHNOLOGY", "YEAR", "VALUE"])

t = bck_techs.VALUE.unique().tolist()
y = get_years(start_year, end_year)

df = pd.DataFrame(
index=pd.MultiIndex.from_product([t, y], names=["TECHNOLOGY", "YEAR"])
).reset_index()
df["REGION"] = region
df["VALUE"] = 0

return df[["REGION", "TECHNOLOGY", "YEAR", "VALUE"]]
4 changes: 2 additions & 2 deletions workflow/scripts/osemosys_global/visualise.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ def plot_generation_hourly(
else:
fig_file = os.path.join(save_dir, 'GenerationHourly.html')
return fig.write_html(fig_file)

def midpoint(x1, y1, x2, y2):
return ((x1 + x2)/2, (y1 + y2)/2)

Expand Down Expand Up @@ -445,4 +445,4 @@ def plot_transmission_flow(

except FileNotFoundError:
print(f"Usage: python {sys.argv[0]} <input_data.csv> <result_data.csv> <scenario_figs_dir> <countries> <results_by_country> <years> <custom_nodes>")
sys.exit(1)
sys.exit(1)
1 change: 1 addition & 0 deletions workflow/snakefile
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ include: "rules/validate.smk"
# handlers

onsuccess:
shell(f"python workflow/scripts/osemosys_global/check_backstop.py {config['scenario']}")
print('Workflow finished successfully!')

# Will fix this in next update so that preprocessing steps dont always need to be rerun
Expand Down
Loading