Take care of CHPs when doing p_nom < threshold & extra_functionality

- add_brownfield.py: Have to make sure that for each CHP there is both
  a heat and electric link, but they have different p_nom for each
  CHP, so have to make sure we don't remove one without the other.

- solve_network.py: Make sure extra_functionality constraints for CHP
  power-heat feasibility graph also work for non-extendable CHPs.
This commit is contained in:
Tom Brown 2020-08-14 09:11:19 +02:00
parent 7e6c7b3dd3
commit 8c196a7a21
5 changed files with 64 additions and 27 deletions

0
Snakefile Executable file → Normal file
View File

0
config.default.yaml Executable file → Normal file
View File

View File

@ -58,8 +58,13 @@ def add_brownfield(n, n_p, year):
c.df.index[c.df.build_year + c.df.lifetime < year])
#remove assets if their optimized nominal capacity is lower than a threshold
#since CHP heat Link is proportional to CHP electric Link, make sure threshold is compatible
chp_heat = c.df.index[c.df[attr + "_nom_extendable"] & c.df.index.str.contains("urban central") & c.df.index.str.contains("CHP") & c.df.index.str.contains("heat")]
if not chp_heat.empty:
n_p.mremove(c.name,
chp_heat[c.df.loc[chp_heat, attr + "_nom_opt"] < snakemake.config['existing_capacities']['threshold_capacity']*c.df.efficiency[chp_heat.str.replace("heat","electric")].values*c.df.p_nom_ratio[chp_heat.str.replace("heat","electric")].values/c.df.efficiency[chp_heat].values])
n_p.mremove(c.name,
c.df.index[c.df[attr + "_nom_opt"] < snakemake.config['existing_capacities']['threshold_capacity']])
c.df.index[c.df[attr + "_nom_extendable"] & ~c.df.index.isin(chp_heat) & (c.df[attr + "_nom_opt"] < snakemake.config['existing_capacities']['threshold_capacity'])])
#copy over assets but fix their capacity
c.df[attr + "_nom"] = c.df[attr + "_nom_opt"]

26
scripts/prepare_sector_network.py Executable file → Normal file
View File

@ -52,11 +52,11 @@ def add_lifetime_wind_solar(n):
if carrier in index], 'lifetime']=costs.at[carrier_name,'lifetime']
def update_wind_solar_costs(n,costs):
"""
Update costs for wind and solar generators added with pypsa-eur to those
Update costs for wind and solar generators added with pypsa-eur to those
cost in the planning year
"""
#assign clustered bus
#map initial network -> simplified network
busmap_s = pd.read_hdf(snakemake.input.clustermaps,
@ -72,13 +72,13 @@ def update_wind_solar_costs(n,costs):
profile = snakemake.input.profile_offwind_dc if tech=='offwind-dc' else snakemake.input.profile_offwind_ac
with xr.open_dataset(profile) as ds:
underwater_fraction = ds['underwater_fraction'].to_pandas().to_frame()
underwater_fraction["cluster_bus"] = underwater_fraction.index.map(clustermaps)
underwater_fraction["cluster_bus"] = underwater_fraction.index.map(clustermaps)
u_f = underwater_fraction.groupby("cluster_bus").mean()
average_distance = ds['average_distance'].to_pandas().to_frame()
average_distance["cluster_bus"] = average_distance.index.map(clustermaps)
average_distance["cluster_bus"] = average_distance.index.map(clustermaps)
a_d = average_distance.groupby("cluster_bus").mean()
connection_cost = (snakemake.config['costs']['lines']['length_factor'] *
a_d*
(u_f *
@ -90,16 +90,16 @@ def update_wind_solar_costs(n,costs):
connection_cost)
logger.info("Added connection cost of {:0.0f}-{:0.0f} Eur/MW/a to {}"
.format(connection_cost[0].min(), connection_cost[0].max(), tech))
capital_cost.rename(index=lambda node: node + ' ' + tech, inplace=True)
n.generators.loc[n.generators.index[n.generators.carrier==tech],'capital_cost']=capital_cost[0]
capital_cost.rename(index=lambda node: node + ' ' + tech, inplace=True)
n.generators.loc[n.generators.index[n.generators.carrier==tech],'capital_cost']=capital_cost[0]
elif suptech == 'solar':
capital_cost = costs.at['solar-utility', 'fixed']
capital_cost = costs.at['solar-utility', 'fixed']
n.generators.loc[n.generators.index[n.generators.carrier==tech],'capital_cost']=capital_cost
else:
capital_cost = costs.at['onwind', 'fixed']
capital_cost = costs.at['onwind', 'fixed']
n.generators.loc[n.generators.index[n.generators.carrier==tech],'capital_cost']=capital_cost
def add_carrier_buses(n, carriers):
"""
Add buses to connect e.g. coal, nuclear and oil plants
@ -1683,7 +1683,7 @@ if __name__ == "__main__":
clustered_pop_layout='pypsa-eur-sec/resources/pop_layout_{network}_s{simpl}_{clusters}.csv',
costs='pypsa-eur-sec/data/costs/costs_{planning_horizons}.csv',
profile_offwind_ac='pypsa-eur/resources/profile_offwind-ac.nc',
profile_offwind_dc='pypsa-eur/resources/profile_offwind-dc.nc',
profile_offwind_dc='pypsa-eur/resources/profile_offwind-dc.nc',
clustermaps="pypsa-eur/resources/clustermaps_{network}_s{simpl}_{clusters}.h5",
cop_air_total='pypsa-eur-sec/resources/cop_air_total_{network}_s{simpl}_{clusters}.nc',
cop_soil_total='pypsa-eur-sec/resources/cop_soil_total_{network}_s{simpl}_{clusters}.nc',

View File

@ -123,21 +123,36 @@ def add_battery_constraints(n):
def add_chp_constraints(n):
electric = n.links.index[n.links.index.str.contains("urban central") & n.links.index.str.contains("CHP") & n.links.index.str.contains("electric")]
heat = n.links.index[n.links.index.str.contains("urban central") & n.links.index.str.contains("CHP") & n.links.index.str.contains("heat")]
electric_bool = (n.links.index.str.contains("urban central")
& n.links.index.str.contains("CHP")
& n.links.index.str.contains("electric"))
heat_bool = (n.links.index.str.contains("urban central")
& n.links.index.str.contains("CHP")
& n.links.index.str.contains("heat"))
if not electric.empty:
electric = n.links.index[electric_bool]
heat = n.links.index[heat_bool]
electric_ext = n.links.index[electric_bool & n.links.p_nom_extendable]
heat_ext = n.links.index[heat_bool & n.links.p_nom_extendable]
electric_fix = n.links.index[electric_bool & ~n.links.p_nom_extendable]
heat_fix = n.links.index[heat_bool & ~n.links.p_nom_extendable]
if not electric_ext.empty:
link_p_nom = get_var(n, "Link", "p_nom")
#ratio of output heat to electricity set by p_nom_ratio
lhs = linexpr((n.links.loc[electric,"efficiency"]
*n.links.loc[electric,'p_nom_ratio'],
link_p_nom[electric]),
(-n.links.loc[heat,"efficiency"].values,
link_p_nom[heat].values))
lhs = linexpr((n.links.loc[electric_ext,"efficiency"]
*n.links.loc[electric_ext,'p_nom_ratio'],
link_p_nom[electric_ext]),
(-n.links.loc[heat_ext,"efficiency"].values,
link_p_nom[heat_ext].values))
define_constraints(n, lhs, "=", 0, 'chplink', 'fix_p_nom_ratio')
if not electric.empty:
link_p = get_var(n, "Link", "p")
#backpressure
@ -149,12 +164,29 @@ def add_chp_constraints(n):
define_constraints(n, lhs, "<=", 0, 'chplink', 'backpressure')
#top_iso_fuel_line
lhs = linexpr((1,link_p[heat]),
(1,link_p[electric].values),
(-1,link_p_nom[electric].values))
define_constraints(n, lhs, "<=", 0, 'chplink', 'top_iso_fuel_line')
if not electric_ext.empty:
link_p_nom = get_var(n, "Link", "p_nom")
link_p = get_var(n, "Link", "p")
#top_iso_fuel_line for extendable
lhs = linexpr((1,link_p[heat_ext]),
(1,link_p[electric_ext].values),
(-1,link_p_nom[electric_ext].values))
define_constraints(n, lhs, "<=", 0, 'chplink', 'top_iso_fuel_line_ext')
if not electric_fix.empty:
link_p = get_var(n, "Link", "p")
#top_iso_fuel_line for fixed
lhs = linexpr((1,link_p[heat_fix]),
(1,link_p[electric_fix].values))
define_constraints(n, lhs, "<=", n.links.loc[electric_fix,"p_nom"].values, 'chplink', 'top_iso_fuel_line_fix')
def add_land_use_constraint(n):
for carrier in ['solar', 'onwind', 'offwind-ac', 'offwind-dc']: