diff --git a/workflow/rules/postprocess.smk b/workflow/rules/postprocess.smk index d288785..6cece80 100644 --- a/workflow/rules/postprocess.smk +++ b/workflow/rules/postprocess.smk @@ -19,16 +19,20 @@ RESULT_SUMMARIES = [ "AnnualTotalTradeFlowsNode", "AnnualTotalTradeFlowsCountry", "AnnualEmissionIntensity", + "AnnualEmissionIntensityGlobal", "PowerCapacityNode", "TransmissionCapacityNode", "PowerCapacityCountry", "TransmissionCapacityCountry", "GenerationSharesNode", "GenerationSharesCountry", + "GenerationSharesGlobal", "PowerCostNode", "TotalCostNode", "PowerCostCountry", "TotalCostCountry", + "PowerCostGlobal", + "TotalCostGlobal", "Metrics" ] @@ -113,6 +117,7 @@ rule calculate_carbon_intensity: annual_emissions = "results/{scenario}/results/AnnualEmissions.csv", output: emission_intensity = "results/{scenario}/result_summaries/AnnualEmissionIntensity.csv", + emission_intensity_global = "results/{scenario}/result_summaries/AnnualEmissionIntensityGlobal.csv", log: log = 'results/{scenario}/logs/carbon_intensity.log' script: @@ -127,8 +132,10 @@ rule calculate_costs: output: node_pwr_cost = "results/{scenario}/result_summaries/PowerCostNode.csv", country_pwr_cost = "results/{scenario}/result_summaries/PowerCostCountry.csv", + global_pwr_cost = "results/{scenario}/result_summaries/PowerCostGlobal.csv", node_cost = "results/{scenario}/result_summaries/TotalCostNode.csv", country_cost = "results/{scenario}/result_summaries/TotalCostCountry.csv", + global_cost = "results/{scenario}/result_summaries/TotalCostGlobal.csv", log: log = 'results/{scenario}/logs/node_cost.log' script: @@ -144,6 +151,7 @@ rule calculate_generation_shares: output: generation_shares_node = "results/{scenario}/result_summaries/GenerationSharesNode.csv", generation_shares_country = "results/{scenario}/result_summaries/GenerationSharesCountry.csv", + generation_shares_global = "results/{scenario}/result_summaries/GenerationSharesGlobal.csv", log: log = 'results/{scenario}/logs/generation_shares.log' script: diff --git a/workflow/scripts/osemosys_global/summary/carbon_intensity.py b/workflow/scripts/osemosys_global/summary/carbon_intensity.py index cb9cbf0..d9d2cc4 100644 --- a/workflow/scripts/osemosys_global/summary/carbon_intensity.py +++ b/workflow/scripts/osemosys_global/summary/carbon_intensity.py @@ -14,6 +14,7 @@ def format_production( ) -> pd.DataFrame: df = production.copy() + df = df.loc[df.index.get_level_values("FUEL").str.startswith('ELC')] df = df[ (df.index.get_level_values("TECHNOLOGY").str.startswith("PWR")) @@ -40,16 +41,27 @@ def format_emissions(annual_emissions: pd.DataFrame) -> pd.DataFrame: df["COUNTRY"] = df.EMISSION.str[3:6] return df.groupby(["REGION", "EMISSION", "COUNTRY", "YEAR"]).sum() +def format_global_values(production: pd.DataFrame, emissions: pd.DataFrame) -> tuple[pd.DataFrame, pd.DataFrame]: + p_global = production.reset_index()[["YEAR", "VALUE"] + ].groupby(["YEAR"]).sum() + + e_global = emissions.reset_index()[["YEAR", "VALUE"] + ].groupby(["YEAR"]).sum() + + return p_global, e_global def calculate_emission_intensity( - production: pd.DataFrame, emissions: pd.DataFrame + production: pd.DataFrame, + emissions: pd.DataFrame, + country: bool ) -> pd.DataFrame: p = production.copy().rename(columns={"VALUE": "PROD_PJ"}) e = emissions.copy().rename(columns={"VALUE": "EMISSIONS_MT"}) df = p.join(e).fillna(0) - df = df.droplevel("COUNTRY") # emission retains country + if country: + df = df.droplevel("COUNTRY") # emission retains country # 1 PJ (1000 TJ / PJ) (1000 GJ / TJ) (1000 MJ / GJ) (1000 kJ / MJ) (hr / 3600sec) df["PROD_kwh"] = df.PROD_PJ.mul(1000).mul(1000).mul(1000).mul(1000).div(3600) @@ -57,7 +69,7 @@ def calculate_emission_intensity( # 1 MT (1,000,000 T / MT) (1000 kg / T) (1000g / kg) df["EMISSIONS_g"] = df.EMISSIONS_MT.mul(1000000).mul(1000).mul(1000) - # intensity in g/kwh + # intensity in g/kwh - kg/MWh df["VALUE"] = df.EMISSIONS_g.div(df.PROD_kwh) return df["VALUE"].to_frame() @@ -68,11 +80,13 @@ def calculate_emission_intensity( production_csv = snakemake.input.production_by_technology annual_emissions_csv = snakemake.input.annual_emissions save = snakemake.output.emission_intensity + save_global = snakemake.output.emission_intensity_global storage = snakemake.params.storage else: production_csv = "results/India/results/ProductionByTechnology.csv" annual_emissions_csv = "results/India/results/AnnualEmissions.csv" save = "results/India/results/AnnualEmissionIntensity.csv" + save_global = "results/India/results/AnnualEmissionIntensityGlobal.csv" storage = {"SDS": [], "LDS": []} if storage: @@ -85,7 +99,14 @@ def calculate_emission_intensity( production = format_production(production, exclusions) emissions = format_emissions(annual_emissions) + + production_global, emissions_global = format_global_values(production, emissions) - emission_intensity = calculate_emission_intensity(production, emissions).round(2) + emission_intensity = calculate_emission_intensity( + production, emissions, country = True).round(2) + + emission_intensity_global = calculate_emission_intensity( + production_global, emissions_global, country = False).round(2) emission_intensity.to_csv(save, index=True) + emission_intensity_global.to_csv(save_global, index=True) diff --git a/workflow/scripts/osemosys_global/summary/constants.py b/workflow/scripts/osemosys_global/summary/constants.py index 9f4b1f1..4865efa 100644 --- a/workflow/scripts/osemosys_global/summary/constants.py +++ b/workflow/scripts/osemosys_global/summary/constants.py @@ -1,7 +1,7 @@ """Constants for summary statistics""" RENEWABLES = ["BIO", "GEO", "HYD", "SPV", "CSP", "WAS", "WAV", "WON", "WOF"] -CLEAN = ["BIO", "GEO", "HYD", "SPV", "CSP", "WAS", "WAV", "WON", "WOF", "URN"] +CLEAN = ["BIO", "CCS", "GEO", "HYD", "SPV", "CSP", "WAS", "WAV", "WON", "WOF", "URN"] FOSSIL = ["CCG", "COA", "COG", "OCG", "OIL", "OTH", "PET"] MONTH_NAMES = { diff --git a/workflow/scripts/osemosys_global/summary/costs.py b/workflow/scripts/osemosys_global/summary/costs.py index 1273a5c..2bc3362 100644 --- a/workflow/scripts/osemosys_global/summary/costs.py +++ b/workflow/scripts/osemosys_global/summary/costs.py @@ -47,6 +47,42 @@ def get_storage_cost( .sum() ) +def get_transmission_cost(discounted_cost_tech: pd.DataFrame, country: bool) -> pd.DataFrame: + + df = discounted_cost_tech.copy() + + df1 = df[ + (df.index.get_level_values("TECHNOLOGY").str.startswith("TRN")) + ].copy() + + if country: + r = "COUNTRY" + df1[r] = df1.index.get_level_values("TECHNOLOGY").str[3:6] + else: + r = "NODE" + df1[r] = df1.index.get_level_values("TECHNOLOGY").str[3:8] + + df2 = df.copy()[ + (df.index.get_level_values("TECHNOLOGY").str.startswith("TRN")) + ] + + if country: + r = "COUNTRY" + df2[r] = df2.index.get_level_values("TECHNOLOGY").str[8:11] + else: + r = "NODE" + df2[r] = df2.index.get_level_values("TECHNOLOGY").str[8:13] + + for df in [df1, df2]: + df['VALUE'] = df['VALUE'] / 2 + + df = pd.concat([df1, df2]) + + return ( + df.reset_index()[["REGION", r, "YEAR", "VALUE"]] + .groupby(["REGION", r, "YEAR"]) + .sum() + ) def get_demand(demand: pd.DataFrame, country: bool) -> pd.DataFrame: @@ -84,17 +120,22 @@ def get_pwr_cost(demand: pd.DataFrame, cost: pd.DataFrame) -> pd.DataFrame: demand_csv = snakemake.input.demand power_cost_node_csv = snakemake.output.node_pwr_cost power_cost_country_csv = snakemake.output.country_pwr_cost + power_cost_global_csv = snakemake.output.global_pwr_cost total_cost_node_csv = snakemake.output.node_cost total_cost_country_csv = snakemake.output.country_cost + total_cost_global_csv = snakemake.output.global_cost + else: discounted_cost_by_technology_csv = ( "results/India/results/DiscountedCostByTechnology.csv" ) demand_csv = "results/India/results/Demand.csv" - power_cost_node_csv = "results/India/result_summaries/NodePowerCost.csv" - power_cost_country_csv = "results/India/result_summaries/CountryPowerCost.csv" - total_cost_node_csv = "results/India/result_summaries/NodeCost.csv" - total_cost_country_csv = "results/India/result_summaries/CountryCost.csv" + power_cost_node_csv = "results/India/result_summaries/PowerCostNode.csv" + power_cost_country_csv = "results/India/result_summaries/PowerCostCountry.csv" + power_cost_global_csv = "results/India/result_summaries/PowerCostGlobal.csv" + total_cost_node_csv = "results/India/result_summaries/TotalCostNode.csv" + total_cost_country_csv = "results/India/result_summaries/TotalCostCountry.csv" + total_cost_global_csv = "results/India/result_summaries/TotalCostGlobal.csv" discounted_cost_by_technology = pd.read_csv( discounted_cost_by_technology_csv, index_col=[0, 1, 2] @@ -119,7 +160,11 @@ def get_pwr_cost(demand: pd.DataFrame, cost: pd.DataFrame) -> pd.DataFrame: tech_cost = get_tech_cost(discounted_cost_by_technology, country=False) storage_cost = get_storage_cost(discounted_cost_by_storage, country=False) - cost = tech_cost.add(storage_cost, fill_value=0) + transmission_cost = get_transmission_cost(discounted_cost_by_technology, country=False) + + cost = tech_cost.add(storage_cost, fill_value = 0 + ).add(transmission_cost, fill_value = 0) + demand = get_demand(demand_raw, country=False) pwr_cost = get_pwr_cost(demand, cost) @@ -128,11 +173,31 @@ def get_pwr_cost(demand: pd.DataFrame, cost: pd.DataFrame) -> pd.DataFrame: # country level metrics - tech_cost = get_tech_cost(discounted_cost_by_technology, country=True) - storage_cost = get_storage_cost(discounted_cost_by_storage, country=True) - cost = tech_cost.add(storage_cost, fill_value=0) - demand = get_demand(demand_raw, country=True) - pwr_cost = get_pwr_cost(demand, cost) - - pwr_cost.to_csv(power_cost_country_csv, index=True) - cost.to_csv(total_cost_country_csv, index=True) + tech_cost_country = get_tech_cost(discounted_cost_by_technology, country=True) + storage_cost_country = get_storage_cost(discounted_cost_by_storage, country=True) + transmission_cost_country = get_transmission_cost(discounted_cost_by_technology, country=True) + + cost_country = tech_cost_country.add(storage_cost_country, fill_value = 0 + ).add(transmission_cost_country, fill_value = 0) + + demand_country = get_demand(demand_raw, country=True) + pwr_cost_country = get_pwr_cost(demand_country, cost_country) + + pwr_cost_country.to_csv(power_cost_country_csv, index=True) + cost_country.to_csv(total_cost_country_csv, index=True) + + # global level metrics + + cost_global = tech_cost_country.add(storage_cost_country, + fill_value = 0 + ).add(transmission_cost_country, fill_value = 0 + ).groupby(["YEAR"]).sum() + + demand_global = get_demand(demand_raw, country=True) + demand_global = demand_global.groupby([ + demand_global.index.get_level_values("YEAR")]).sum() + + pwr_cost_global = get_pwr_cost(demand_global, cost_global) + + pwr_cost_global.to_csv(power_cost_global_csv, index=True) + cost_global.to_csv(total_cost_global_csv, index=True) \ No newline at end of file diff --git a/workflow/scripts/osemosys_global/summary/gen_shares.py b/workflow/scripts/osemosys_global/summary/gen_shares.py index 1b8e9a0..a06490c 100644 --- a/workflow/scripts/osemosys_global/summary/gen_shares.py +++ b/workflow/scripts/osemosys_global/summary/gen_shares.py @@ -12,6 +12,7 @@ def _get_gen_by_node( ) -> pd.DataFrame: df = production.copy() + df = df.loc[df.index.get_level_values("FUEL").str.startswith('ELC')] assert "TECHNOLOGY" in df.index.names @@ -38,6 +39,7 @@ def _get_gen_by_country( ) -> pd.DataFrame: df = production.copy() + df = df.loc[df.index.get_level_values("FUEL").str.startswith('ELC')] assert "TECHNOLOGY" in df.index.names @@ -56,6 +58,31 @@ def _get_gen_by_country( .sum() ) +def _get_gen_global( + production: pd.DataFrame, + include: Optional[list[str]] = None, + exclude: Optional[list[str]] = None, +) -> pd.DataFrame: + + df = production.copy() + df = df.loc[df.index.get_level_values("FUEL").str.startswith('ELC')] + + assert "TECHNOLOGY" in df.index.names + + df["TECH"] = df.index.get_level_values("TECHNOLOGY").str[3:6] + #df["COUNTRY"] = df.index.get_level_values("TECHNOLOGY").str[6:9] + + if exclude: + df = df[~df.TECH.isin(exclude)].copy() + + if include: + df = df[df.TECH.isin(include)].copy() + + return ( + df.reset_index()[["REGION", "YEAR", "VALUE"]] + .groupby(["REGION", "YEAR"]) + .sum() + ) def calc_generation_shares_node( production_by_technology: pd.DataFrame, exclusions: Optional[list[str]] = None @@ -72,10 +99,13 @@ def calc_generation_shares_node( ] total = _get_gen_by_node(df, exclude=exclusions).rename(columns={"VALUE": "TOTAL"}) + clean = _get_gen_by_node(df, include=CLEAN).rename(columns={"VALUE": "CLEAN"}) + renewable = _get_gen_by_node(df, include=RENEWABLES).rename( columns={"VALUE": "RENEWABLE"} ) + fossil = _get_gen_by_node(df, include=FOSSIL).rename(columns={"VALUE": "FOSSIL"}) shares = ( @@ -128,6 +158,42 @@ def calc_generation_shares_country( return shares[["CLEAN", "RENEWABLE", "FOSSIL"]].round(1) +def calc_generation_shares_global( + production_by_technology: pd.DataFrame, exclusions: Optional[list[str]] = None +) -> pd.DataFrame: + + if not exclusions: + exclusions = [] + + df = production_by_technology.copy() + + df = df[ + (df.index.get_level_values("TECHNOLOGY").str.startswith("PWR")) + & ~(df.index.get_level_values("TECHNOLOGY").str.contains("TRN")) + ] + + total = _get_gen_global(df, exclude=exclusions).rename( + columns={"VALUE": "TOTAL"} + ) + clean = _get_gen_global(df, include=CLEAN).rename(columns={"VALUE": "CLEAN"}) + renewable = _get_gen_global(df, include=RENEWABLES).rename( + columns={"VALUE": "RENEWABLE"} + ) + fossil = _get_gen_global(df, include=FOSSIL).rename(columns={"VALUE": "FOSSIL"}) + + shares = ( + total.join(clean, how="outer") + .join(renewable, how="outer") + .join(fossil, how="outer") + .fillna(0) + ) + + shares["CLEAN"] = shares.CLEAN.div(shares.TOTAL).mul(100) + shares["RENEWABLE"] = shares.RENEWABLE.div(shares.TOTAL).mul(100) + shares["FOSSIL"] = shares.FOSSIL.div(shares.TOTAL).mul(100) + + return shares[["CLEAN", "RENEWABLE", "FOSSIL"]].round(1) + if __name__ == "__main__": if "snakemake" in globals(): @@ -135,6 +201,7 @@ def calc_generation_shares_country( storage = snakemake.params.storage gen_shares_node = snakemake.output.generation_shares_node gen_shares_country = snakemake.output.generation_shares_country + gen_shares_global = snakemake.output.generation_shares_global else: production_by_technology_annual_csv = ( "results/India/results/ProductionByTechnologyAnnual.csv" @@ -144,6 +211,10 @@ def calc_generation_shares_country( gen_shares_country = ( "results/India/result_summaries/GenerationSharesCountry.csv" ) + gen_shares_global = ( + "results/India/result_summaries/GenerationSharesGlobal.csv" + ) + production_by_technology_annual = pd.read_csv( production_by_technology_annual_csv, index_col=[0, 1, 2, 3] @@ -155,9 +226,9 @@ def calc_generation_shares_country( exclusions = [] nodes = calc_generation_shares_node(production_by_technology_annual, exclusions) - country = calc_generation_shares_country( - production_by_technology_annual, exclusions - ) + country = calc_generation_shares_country(production_by_technology_annual, exclusions) + glob = calc_generation_shares_global(production_by_technology_annual, exclusions) nodes.to_csv(gen_shares_node, index=True) country.to_csv(gen_shares_country, index=True) + glob.to_csv(gen_shares_global, index=True) diff --git a/workflow/scripts/osemosys_global/summary/headline.py b/workflow/scripts/osemosys_global/summary/headline.py index e4bb810..039cca7 100644 --- a/workflow/scripts/osemosys_global/summary/headline.py +++ b/workflow/scripts/osemosys_global/summary/headline.py @@ -52,6 +52,7 @@ def _filter_pwr_techs( """ df = production_by_technology.copy() + df = df.loc[df.index.get_level_values("FUEL").str.startswith('ELC')] df = df[ (df.index.get_level_values("TECHNOLOGY").str.startswith("PWR")) @@ -71,6 +72,7 @@ def _filter_techs( ) -> pd.DataFrame: df = production_by_technology.copy() + df = df.loc[df.index.get_level_values("FUEL").str.startswith('ELC')] df["tech"] = df.index.get_level_values("TECHNOLOGY").str[0:6] techs = [f"PWR{x}" for x in carriers] @@ -83,15 +85,18 @@ def _filter_techs( def get_gen_shares( production_by_technology: pd.DataFrame, exclusions: Optional[list[str]] = None ) -> pd.DataFrame: + + df = production_by_technology.copy() + df = df.loc[df.index.get_level_values("FUEL").str.startswith('ELC')] - gen_total = _filter_pwr_techs(production_by_technology, exclusions).VALUE.sum() - rnw_total = _filter_techs(production_by_technology, RENEWABLES).VALUE.sum() - fsl_total = _filter_techs(production_by_technology, FOSSIL).VALUE.sum() - cln_total = _filter_techs(production_by_technology, CLEAN).VALUE.sum() + gen_total = _filter_pwr_techs(df, exclusions).VALUE.sum() + rnw_total = _filter_techs(df, RENEWABLES).VALUE.sum() + fsl_total = _filter_techs(df, FOSSIL).VALUE.sum() + cln_total = _filter_techs(df, CLEAN).VALUE.sum() - rnw_share = round((rnw_total / gen_total) * 100, 0) - fsl_share = round((fsl_total / gen_total) * 100, 0) - cln_share = round((cln_total / gen_total) * 100, 0) + rnw_share = round((rnw_total / gen_total) * 100, 2) + fsl_share = round((fsl_total / gen_total) * 100, 2) + cln_share = round((cln_total / gen_total) * 100, 2) data = [ ["Fossil energy share", "%", fsl_share],