Skip to content

Commit

Permalink
Merge pull request #256 from PyPSA/fix-bug-myopic-co2
Browse files Browse the repository at this point in the history
Fix bug myopic co2
  • Loading branch information
lisazeyen authored Aug 9, 2022
2 parents 8dd143f + b42e760 commit 3daff49
Show file tree
Hide file tree
Showing 6 changed files with 174 additions and 89 deletions.
10 changes: 7 additions & 3 deletions Snakefile
Original file line number Diff line number Diff line change
Expand Up @@ -442,14 +442,14 @@ rule build_population_weighted_energy_totals:


rule build_transport_demand:
input:
input:
clustered_pop_layout="resources/pop_layout_elec_s{simpl}_{clusters}.csv",
pop_weighted_energy_totals="resources/pop_weighted_energy_totals_s{simpl}_{clusters}.csv",
transport_data='resources/transport_data.csv',
traffic_data_KFZ="data/emobility/KFZ__count",
traffic_data_Pkw="data/emobility/Pkw__count",
temp_air_total="resources/temp_air_total_elec_s{simpl}_{clusters}.nc",
output:
output:
transport_demand="resources/transport_demand_s{simpl}_{clusters}.csv",
transport_data="resources/transport_data_s{simpl}_{clusters}.csv",
avail_profile="resources/avail_profile_s{simpl}_{clusters}.csv",
Expand All @@ -464,12 +464,14 @@ rule prepare_sector_network:
overrides="data/override_component_attrs",
network=pypsaeur('networks/elec_s{simpl}_{clusters}_ec_lv{lv}_{opts}.nc'),
energy_totals_name='resources/energy_totals.csv',
eurostat=input_eurostat,
pop_weighted_energy_totals="resources/pop_weighted_energy_totals_s{simpl}_{clusters}.csv",
transport_demand="resources/transport_demand_s{simpl}_{clusters}.csv",
transport_data="resources/transport_data_s{simpl}_{clusters}.csv",
avail_profile="resources/avail_profile_s{simpl}_{clusters}.csv",
dsm_profile="resources/dsm_profile_s{simpl}_{clusters}.csv",
co2_totals_name='resources/co2_totals.csv',
co2="data/eea/UNFCCC_v23.csv",
biomass_potentials='resources/biomass_potentials_s{simpl}_{clusters}.csv',
heat_profile="data/heat_load_profile_BDEW.csv",
costs=CDIR + "costs_{planning_horizons}.csv",
Expand Down Expand Up @@ -568,7 +570,9 @@ rule plot_summary:
input:
costs=SDIR + '/csvs/costs.csv',
energy=SDIR + '/csvs/energy.csv',
balances=SDIR + '/csvs/supply_energy.csv'
balances=SDIR + '/csvs/supply_energy.csv',
eurostat=input_eurostat,
country_codes='data/Country_codes.csv',
output:
costs=SDIR + '/graphs/costs.pdf',
energy=SDIR + '/graphs/energy.pdf',
Expand Down
10 changes: 8 additions & 2 deletions config.default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,12 @@ scenario:
# decay with initial growth rate 0
planning_horizons: # investment years for myopic and perfect; or costs year for overnight
- 2030
# for example, set to [2020, 2030, 2040, 2050] for myopic foresight
# for example, set to
# - 2020
# - 2030
# - 2040
# - 2050
# for myopic foresight

# CO2 budget as a fraction of 1990 emissions
# this is over-ridden if CO2Lx is set in sector_opts
Expand Down Expand Up @@ -134,7 +139,8 @@ solar_thermal:

# only relevant for foresight = myopic or perfect
existing_capacities:
grouping_years: [1980, 1985, 1990, 1995, 2000, 2005, 2010, 2015, 2020, 2025, 2030]
grouping_years_power: [1980, 1985, 1990, 1995, 2000, 2005, 2010, 2015, 2020, 2025, 2030]
grouping_years_heat: [1980, 1985, 1990, 1995, 2000, 2005, 2010, 2015, 2019] # these should not extend 2020
threshold_capacity: 10
conventional_carriers:
- lignite
Expand Down
170 changes: 119 additions & 51 deletions scripts/add_existing_baseyear.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,15 +131,15 @@ def add_power_capacities_installed_before_baseyear(n, grouping_years, costs, bas
'Oil': 'oil',
'OCGT': 'OCGT',
'CCGT': 'CCGT',
'Natural Gas': 'gas'
'Natural Gas': 'gas',
'Bioenergy': 'urban central solid biomass CHP',
}

fueltype_to_drop = [
'Hydro',
'Wind',
'Solar',
'Geothermal',
'Bioenergy',
'Waste',
'Other',
'CCGT, Thermal'
Expand All @@ -150,10 +150,29 @@ def add_power_capacities_installed_before_baseyear(n, grouping_years, costs, bas
'Storage Technologies'
]

# drop unused fueltyps and technologies
df_agg.drop(df_agg.index[df_agg.Fueltype.isin(fueltype_to_drop)], inplace=True)
df_agg.drop(df_agg.index[df_agg.Technology.isin(technology_to_drop)], inplace=True)
df_agg.Fueltype = df_agg.Fueltype.map(rename_fuel)

# Intermediate fix for DateIn & DateOut
# Fill missing DateIn
biomass_i = df_agg.loc[df_agg.Fueltype=='urban central solid biomass CHP'].index
mean = df_agg.loc[biomass_i, 'DateIn'].mean()
df_agg.loc[biomass_i, 'DateIn'] = df_agg.loc[biomass_i, 'DateIn'].fillna(int(mean))
# Fill missing DateOut
dateout = df_agg.loc[biomass_i, 'DateIn'] + snakemake.config['costs']['lifetime']
df_agg.loc[biomass_i, 'DateOut'] = df_agg.loc[biomass_i, 'DateOut'].fillna(dateout)


# drop assets which are already phased out / decomissioned
phased_out = df_agg[df_agg["DateOut"]<baseyear].index
df_agg.drop(phased_out, inplace=True)

# calculate remaining lifetime before phase-out (+1 because assumming
# phase out date at the end of the year)
df_agg["lifetime"] = df_agg.DateOut - df_agg.DateIn + 1

# assign clustered bus
busmap_s = pd.read_csv(snakemake.input.busmap_s, index_col=0).squeeze()
busmap = pd.read_csv(snakemake.input.busmap, index_col=0).squeeze()
Expand Down Expand Up @@ -182,35 +201,52 @@ def add_power_capacities_installed_before_baseyear(n, grouping_years, costs, bas
aggfunc='sum'
)

lifetime = df_agg.pivot_table(
index=["grouping_year", 'Fueltype'],
columns='cluster_bus',
values='lifetime',
aggfunc='mean' # currently taken mean for clustering lifetimes
)

carrier = {
"OCGT": "gas",
"CCGT": "gas",
"coal": "coal",
"oil": "oil",
"lignite": "lignite",
"nuclear": "uranium"
"nuclear": "uranium",
'urban central solid biomass CHP': "biomass",
}

for grouping_year, generator in df.index:


# capacity is the capacity in MW at each node for this
capacity = df.loc[grouping_year, generator]
capacity = capacity[~capacity.isna()]
capacity = capacity[capacity > snakemake.config['existing_capacities']['threshold_capacity']]

suffix = '-ac' if generator == 'offwind' else ''
name_suffix = f' {generator}{suffix}-{grouping_year}'
asset_i = capacity.index + name_suffix
if generator in ['solar', 'onwind', 'offwind']:

suffix = '-ac' if generator == 'offwind' else ''
name_suffix = f' {generator}{suffix}-{baseyear}'

# to consider electricity grid connection costs or a split between
# solar utility and rooftop as well, rather take cost assumptions
# from existing network than from the cost database
capital_cost = n.generators.loc[n.generators.carrier==generator+suffix, "capital_cost"].mean()

# check if assets are already in network (e.g. for 2020)
already_build = n.generators.index.intersection(asset_i)
new_build = asset_i.difference(n.generators.index)

# this is for the year 2020
if not already_build.empty:
n.generators.loc[already_build, "p_nom_min"] = capacity.loc[already_build.str.replace(name_suffix, "")].values
new_capacity = capacity.loc[new_build.str.replace(name_suffix, "")]

if 'm' in snakemake.wildcards.clusters:

for ind in capacity.index:
for ind in new_capacity.index:

# existing capacities are split evenly among regions in every country
inv_ind = [i for i in inv_busmap[ind]]
Expand All @@ -225,7 +261,7 @@ def add_power_capacities_installed_before_baseyear(n, grouping_years, costs, bas
[i + name_suffix for i in inv_ind],
bus=ind,
carrier=generator,
p_nom=capacity[ind] / len(inv_ind), # split among regions in a country
p_nom=new_capacity[ind] / len(inv_ind), # split among regions in a country
marginal_cost=costs.at[generator,'VOM'],
capital_cost=capital_cost,
efficiency=costs.at[generator, 'efficiency'],
Expand All @@ -236,42 +272,72 @@ def add_power_capacities_installed_before_baseyear(n, grouping_years, costs, bas

else:

p_max_pu = n.generators_t.p_max_pu[capacity.index + name_suffix]

n.madd("Generator",
capacity.index,
suffix=' ' + generator +"-"+ str(grouping_year),
bus=capacity.index,
carrier=generator,
p_nom=capacity,
marginal_cost=costs.at[generator, 'VOM'],
capital_cost=capital_cost,
efficiency=costs.at[generator, 'efficiency'],
p_max_pu=p_max_pu.rename(columns=n.generators.bus),
build_year=grouping_year,
lifetime=costs.at[generator, 'lifetime']
)
p_max_pu = n.generators_t.p_max_pu[capacity.index + f' {generator}{suffix}-{baseyear}']

if not new_build.empty:
n.madd("Generator",
new_capacity.index,
suffix=' ' + name_suffix,
bus=new_capacity.index,
carrier=generator,
p_nom=new_capacity,
marginal_cost=costs.at[generator, 'VOM'],
capital_cost=capital_cost,
efficiency=costs.at[generator, 'efficiency'],
p_max_pu=p_max_pu.rename(columns=n.generators.bus),
build_year=grouping_year,
lifetime=costs.at[generator, 'lifetime']
)

else:
bus0 = vars(spatial)[carrier[generator]].nodes
if "EU" not in vars(spatial)[carrier[generator]].locations:
bus0 = bus0.intersection(capacity.index + " gas")

n.madd("Link",
capacity.index,
suffix= " " + generator +"-" + str(grouping_year),
bus0=bus0,
bus1=capacity.index,
bus2="co2 atmosphere",
carrier=generator,
marginal_cost=costs.at[generator, 'efficiency'] * costs.at[generator, 'VOM'], #NB: VOM is per MWel
capital_cost=costs.at[generator, 'efficiency'] * costs.at[generator, 'fixed'], #NB: fixed cost is per MWel
p_nom=capacity / costs.at[generator, 'efficiency'],
efficiency=costs.at[generator, 'efficiency'],
efficiency2=costs.at[carrier[generator], 'CO2 intensity'],
build_year=grouping_year,
lifetime=costs.at[generator, 'lifetime']
)
already_build = n.links.index.intersection(asset_i)
new_build = asset_i.difference(n.links.index)
lifetime_assets = lifetime.loc[grouping_year,generator].dropna()

# this is for the year 2020
if not already_build.empty:
n.links.loc[already_build, "p_nom_min"] = capacity.loc[already_build.str.replace(name_suffix, "")].values

if not new_build.empty:
new_capacity = capacity.loc[new_build.str.replace(name_suffix, "")]

if generator!="urban central solid biomass CHP":
n.madd("Link",
new_capacity.index,
suffix= name_suffix,
bus0=bus0,
bus1=new_capacity.index,
bus2="co2 atmosphere",
carrier=generator,
marginal_cost=costs.at[generator, 'efficiency'] * costs.at[generator, 'VOM'], #NB: VOM is per MWel
capital_cost=costs.at[generator, 'efficiency'] * costs.at[generator, 'fixed'], #NB: fixed cost is per MWel
p_nom=new_capacity / costs.at[generator, 'efficiency'],
efficiency=costs.at[generator, 'efficiency'],
efficiency2=costs.at[carrier[generator], 'CO2 intensity'],
build_year=grouping_year,
lifetime=lifetime_assets.loc[new_capacity.index],
)
else:
key = 'central solid biomass CHP'
n.madd("Link",
new_capacity.index,
suffix= name_suffix,
bus0=spatial.biomass.df.loc[new_capacity.index]["nodes"].values,
bus1=new_capacity.index,
bus2=new_capacity.index + " urban central heat",
carrier=generator,
p_nom=new_capacity / costs.at[key, 'efficiency'],
capital_cost=costs.at[key, 'fixed'] * costs.at[key, 'efficiency'],
marginal_cost=costs.at[key, 'VOM'],
efficiency=costs.at[key, 'efficiency'],
build_year=grouping_year,
efficiency2=costs.at[key, 'efficiency-heat'],
lifetime=lifetime_assets.loc[new_capacity.index]
)


def add_heating_capacities_installed_before_baseyear(n, baseyear, grouping_years, ashp_cop, gshp_cop, time_dep_hp_cop, costs, default_lifetime):
Expand Down Expand Up @@ -376,10 +442,10 @@ def add_heating_capacities_installed_before_baseyear(n, baseyear, grouping_years
for i, grouping_year in enumerate(grouping_years):

if int(grouping_year) + default_lifetime <= int(baseyear):
ratio = 0
else:
# installation is assumed to be linear for the past 25 years (default lifetime)
ratio = (int(grouping_year) - int(grouping_years[i-1])) / default_lifetime
continue

# installation is assumed to be linear for the past 25 years (default lifetime)
ratio = (int(grouping_year) - int(grouping_years[i-1])) / default_lifetime

n.madd("Link",
nodes[name],
Expand Down Expand Up @@ -443,7 +509,7 @@ def add_heating_capacities_installed_before_baseyear(n, baseyear, grouping_years
# delete links with p_nom=nan corresponding to extra nodes in country
n.mremove("Link", [index for index in n.links.index.to_list() if str(grouping_year) in index and np.isnan(n.links.p_nom[index])])

# delete links if their lifetime is over and p_nom=0
# delete links with capacities below threshold
threshold = snakemake.config['existing_capacities']['threshold_capacity']
n.mremove("Link", [index for index in n.links.index.to_list() if str(grouping_year) in index and n.links.p_nom[index] < threshold])

Expand All @@ -454,11 +520,11 @@ def add_heating_capacities_installed_before_baseyear(n, baseyear, grouping_years
snakemake = mock_snakemake(
'add_existing_baseyear',
simpl='',
clusters="37",
clusters="45",
lv=1.0,
opts='',
sector_opts='168H-T-H-B-I-solar+p3-dist1',
planning_horizons=2020,
sector_opts='365H-T-H-B-I-A-solar+p3-dist1',
planning_horizons=2030,
)

logging.basicConfig(level=snakemake.config['logging_level'])
Expand All @@ -468,7 +534,7 @@ def add_heating_capacities_installed_before_baseyear(n, baseyear, grouping_years
options = snakemake.config["sector"]
opts = snakemake.wildcards.sector_opts.split('-')

baseyear= snakemake.config['scenario']["planning_horizons"][0]
baseyear = snakemake.config['scenario']["planning_horizons"][0]

overrides = override_component_attrs(snakemake.input.overrides)
n = pypsa.Network(snakemake.input.network, override_component_attrs=overrides)
Expand All @@ -485,15 +551,17 @@ def add_heating_capacities_installed_before_baseyear(n, baseyear, grouping_years
snakemake.config['costs']['lifetime']
)

grouping_years = snakemake.config['existing_capacities']['grouping_years']
add_power_capacities_installed_before_baseyear(n, grouping_years, costs, baseyear)
grouping_years_power = snakemake.config['existing_capacities']['grouping_years_power']
grouping_years_heat = snakemake.config['existing_capacities']['grouping_years_heat']
add_power_capacities_installed_before_baseyear(n, grouping_years_power, costs, baseyear)

if "H" in opts:
time_dep_hp_cop = options["time_dep_hp_cop"]
ashp_cop = xr.open_dataarray(snakemake.input.cop_air_total).to_pandas().reindex(index=n.snapshots)
gshp_cop = xr.open_dataarray(snakemake.input.cop_soil_total).to_pandas().reindex(index=n.snapshots)
default_lifetime = snakemake.config['costs']['lifetime']
add_heating_capacities_installed_before_baseyear(n, baseyear, grouping_years, ashp_cop, gshp_cop, time_dep_hp_cop, costs, default_lifetime)
add_heating_capacities_installed_before_baseyear(n, baseyear, grouping_years_heat,
ashp_cop, gshp_cop, time_dep_hp_cop, costs, default_lifetime)

n.meta = dict(snakemake.config, **dict(wildcards=dict(snakemake.wildcards)))
n.export_to_netcdf(snakemake.output[0])
Loading

0 comments on commit 3daff49

Please sign in to comment.