update build and add powerplant with new ppm version

This commit is contained in:
Fabian Hofmann 2019-10-30 23:09:41 +01:00
parent 1b3f52c2c2
commit 7a9842eba7
3 changed files with 6695 additions and 6709 deletions

File diff suppressed because it is too large Load Diff

View File

@ -37,7 +37,8 @@ Relevant Settings
length_factor: length_factor:
.. seealso:: .. seealso::
Documentation of the configuration file ``config.yaml`` at :ref:`costs_cf`, :ref:`electricity_cf`, :ref:`load_cf`, :ref:`renewable_cf`, :ref:`lines_cf` Documentation of the configuration file ``config.yaml`` at :ref:`costs_cf`,
:ref:`electricity_cf`, :ref:`load_cf`, :ref:`renewable_cf`, :ref:`lines_cf`
Inputs Inputs
------ ------
@ -94,7 +95,6 @@ import pandas as pd
idx = pd.IndexSlice idx = pd.IndexSlice
import numpy as np import numpy as np
import scipy as sp
import xarray as xr import xarray as xr
import geopandas as gpd import geopandas as gpd
@ -104,14 +104,8 @@ from vresutils.load import timeseries_opsd
from vresutils import transfer as vtransfer from vresutils import transfer as vtransfer
import pypsa import pypsa
try:
import powerplantmatching as ppm import powerplantmatching as ppm
from build_powerplants import country_alpha_2
has_ppm = True
except ImportError:
has_ppm = False
def normed(s): return s/s.sum() def normed(s): return s/s.sum()
@ -119,7 +113,8 @@ def _add_missing_carriers_from_costs(n, costs, carriers):
missing_carriers = pd.Index(carriers).difference(n.carriers.index) missing_carriers = pd.Index(carriers).difference(n.carriers.index)
if missing_carriers.empty: return if missing_carriers.empty: return
emissions_cols = costs.columns.to_series().loc[lambda s: s.str.endswith('_emissions')].values emissions_cols = costs.columns.to_series()\
.loc[lambda s: s.str.endswith('_emissions')].values
suptechs = missing_carriers.str.split('-').str[0] suptechs = missing_carriers.str.split('-').str[0]
emissions = costs.loc[suptechs, emissions_cols].fillna(0.) emissions = costs.loc[suptechs, emissions_cols].fillna(0.)
emissions.index = missing_carriers emissions.index = missing_carriers
@ -139,7 +134,8 @@ def load_costs(Nyears=1., tech_costs=None, config=None, elec_config=None):
costs.loc[costs.unit.str.contains("/kW"),"value"] *= 1e3 costs.loc[costs.unit.str.contains("/kW"),"value"] *= 1e3
costs.loc[costs.unit.str.contains("USD"),"value"] *= config['USD2013_to_EUR2013'] costs.loc[costs.unit.str.contains("USD"),"value"] *= config['USD2013_to_EUR2013']
costs = costs.loc[idx[:,config['year'],:], "value"].unstack(level=2).groupby("technology").sum(min_count=1) costs = (costs.loc[idx[:,config['year'],:], "value"]
.unstack(level=2).groupby("technology").sum(min_count=1))
costs = costs.fillna({"CO2 intensity" : 0, costs = costs.fillna({"CO2 intensity" : 0,
"FOM" : 0, "FOM" : 0,
@ -150,7 +146,8 @@ def load_costs(Nyears=1., tech_costs=None, config=None, elec_config=None):
"investment" : 0, "investment" : 0,
"lifetime" : 25}) "lifetime" : 25})
costs["capital_cost"] = ((annuity(costs["lifetime"], costs["discount rate"]) + costs["FOM"]/100.) * costs["capital_cost"] = ((annuity(costs["lifetime"], costs["discount rate"]) +
costs["FOM"]/100.) *
costs["investment"] * Nyears) costs["investment"] * Nyears)
costs.at['OCGT', 'fuel'] = costs.at['gas', 'fuel'] costs.at['OCGT', 'fuel'] = costs.at['gas', 'fuel']
@ -163,7 +160,8 @@ def load_costs(Nyears=1., tech_costs=None, config=None, elec_config=None):
costs.at['OCGT', 'co2_emissions'] = costs.at['gas', 'co2_emissions'] costs.at['OCGT', 'co2_emissions'] = costs.at['gas', 'co2_emissions']
costs.at['CCGT', 'co2_emissions'] = costs.at['gas', 'co2_emissions'] costs.at['CCGT', 'co2_emissions'] = costs.at['gas', 'co2_emissions']
costs.at['solar', 'capital_cost'] = 0.5*(costs.at['solar-rooftop', 'capital_cost'] + costs.at['solar-utility', 'capital_cost']) costs.at['solar', 'capital_cost'] = 0.5*(costs.at['solar-rooftop', 'capital_cost'] +
costs.at['solar-utility', 'capital_cost'])
def costs_for_storage(store, link1, link2=None, max_hours=1.): def costs_for_storage(store, link1, link2=None, max_hours=1.):
capital_cost = link1['capital_cost'] + max_hours * store['capital_cost'] capital_cost = link1['capital_cost'] + max_hours * store['capital_cost']
@ -183,8 +181,8 @@ def load_costs(Nyears=1., tech_costs=None, config=None, elec_config=None):
costs_for_storage(costs.loc["battery storage"], costs.loc["battery inverter"], costs_for_storage(costs.loc["battery storage"], costs.loc["battery inverter"],
max_hours=max_hours['battery']) max_hours=max_hours['battery'])
costs.loc["H2"] = \ costs.loc["H2"] = \
costs_for_storage(costs.loc["hydrogen storage"], costs.loc["fuel cell"], costs.loc["electrolysis"], costs_for_storage(costs.loc["hydrogen storage"], costs.loc["fuel cell"],
max_hours=max_hours['H2']) costs.loc["electrolysis"], max_hours=max_hours['H2'])
for attr in ('marginal_cost', 'capital_cost'): for attr in ('marginal_cost', 'capital_cost'):
overwrites = config.get(attr) overwrites = config.get(attr)
@ -194,19 +192,27 @@ def load_costs(Nyears=1., tech_costs=None, config=None, elec_config=None):
return costs return costs
def load_powerplants(n, ppl_fn=None): def load_powerplants(ppl_fn=None):
if ppl_fn is None: if ppl_fn is None:
ppl_fn = snakemake.input.powerplants ppl_fn = snakemake.input.powerplants
ppl = pd.read_csv(ppl_fn, index_col=0, dtype={'bus': 'str'}) carrier_dict = {'ocgt': 'OCGT', 'ccgt': 'CCGT', 'bioenergy':'biomass',
return ppl.loc[ppl.bus.isin(n.buses.index)] 'ccgt, thermal': 'CCGT', 'hard coal': 'coal'}
return (pd.read_csv(ppl_fn, index_col=0, dtype={'bus': 'str'})
.powerplant.to_pypsa_names()
.rename(columns=str.lower).drop(columns=['efficiency'])
.replace({'carrier': carrier_dict}))
# ## Attach components
# =============================================================================
# Attach components
# =============================================================================
# ### Load # ### Load
def attach_load(n): def attach_load(n):
substation_lv_i = n.buses.index[n.buses['substation_lv']] substation_lv_i = n.buses.index[n.buses['substation_lv']]
regions = gpd.read_file(snakemake.input.regions).set_index('name').reindex(substation_lv_i) regions = (gpd.read_file(snakemake.input.regions).set_index('name')
.reindex(substation_lv_i))
opsd_load = (timeseries_opsd(slice(*n.snapshots[[0,-1]].year.astype(str)), opsd_load = (timeseries_opsd(slice(*n.snapshots[[0,-1]].year.astype(str)),
snakemake.input.opsd_load) * snakemake.input.opsd_load) *
snakemake.config.get('load', {}).get('scaling_factor', 1.0)) snakemake.config.get('load', {}).get('scaling_factor', 1.0))
@ -224,17 +230,21 @@ def attach_load(n):
return pd.DataFrame({group.index[0]: l}) return pd.DataFrame({group.index[0]: l})
else: else:
nuts3_cntry = nuts3.loc[nuts3.country == cntry] nuts3_cntry = nuts3.loc[nuts3.country == cntry]
transfer = vtransfer.Shapes2Shapes(group, nuts3_cntry.geometry, normed=False).T.tocsr() transfer = vtransfer.Shapes2Shapes(group, nuts3_cntry.geometry,
gdp_n = pd.Series(transfer.dot(nuts3_cntry['gdp'].fillna(1.).values), index=group.index) normed=False).T.tocsr()
pop_n = pd.Series(transfer.dot(nuts3_cntry['pop'].fillna(1.).values), index=group.index) gdp_n = pd.Series(transfer.dot(nuts3_cntry['gdp'].fillna(1.).values),
index=group.index)
pop_n = pd.Series(transfer.dot(nuts3_cntry['pop'].fillna(1.).values),
index=group.index)
# relative factors 0.6 and 0.4 have been determined from a linear # relative factors 0.6 and 0.4 have been determined from a linear
# regression on the country to continent load data (refer to vresutils.load._upsampling_weights) # regression on the country to continent load data (refer to vresutils.load._upsampling_weights)
factors = normed(0.6 * normed(gdp_n) + 0.4 * normed(pop_n)) factors = normed(0.6 * normed(gdp_n) + 0.4 * normed(pop_n))
return pd.DataFrame(factors.values * l.values[:,np.newaxis], index=l.index, columns=factors.index) return pd.DataFrame(factors.values * l.values[:,np.newaxis],
index=l.index, columns=factors.index)
load = pd.concat([upsample(cntry, group) load = pd.concat([upsample(cntry, group) for cntry, group
for cntry, group in regions.geometry.groupby(regions.country)], axis=1) in regions.geometry.groupby(regions.country)], axis=1)
n.madd("Load", substation_lv_i, bus=substation_lv_i, p_set=load) n.madd("Load", substation_lv_i, bus=substation_lv_i, p_set=load)
@ -248,16 +258,16 @@ def update_transmission_costs(n, costs, length_factor=1.0, simple_hvdc_costs=Fal
dc_b = n.links.carrier == 'DC' dc_b = n.links.carrier == 'DC'
if simple_hvdc_costs: if simple_hvdc_costs:
n.links.loc[dc_b, 'capital_cost'] = (n.links.loc[dc_b, 'length'] * length_factor * costs = (n.links.loc[dc_b, 'length'] * length_factor *
costs.at['HVDC overhead', 'capital_cost']) costs.at['HVDC overhead', 'capital_cost'])
else: else:
n.links.loc[dc_b, 'capital_cost'] = (n.links.loc[dc_b, 'length'] * length_factor * costs = (n.links.loc[dc_b, 'length'] * length_factor *
((1. - n.links.loc[dc_b, 'underwater_fraction']) * ((1. - n.links.loc[dc_b, 'underwater_fraction']) *
costs.at['HVDC overhead', 'capital_cost'] + costs.at['HVDC overhead', 'capital_cost'] +
n.links.loc[dc_b, 'underwater_fraction'] * n.links.loc[dc_b, 'underwater_fraction'] *
costs.at['HVDC submarine', 'capital_cost']) + costs.at['HVDC submarine', 'capital_cost']) +
costs.at['HVDC inverter pair', 'capital_cost']) costs.at['HVDC inverter pair', 'capital_cost'])
n.links.loc[dc_b, 'capital_cost'] = costs
# ### Generators # ### Generators
def attach_wind_and_solar(n, costs): def attach_wind_and_solar(n, costs):
@ -271,13 +281,20 @@ def attach_wind_and_solar(n, costs):
suptech = tech.split('-', 2)[0] suptech = tech.split('-', 2)[0]
if suptech == 'offwind': if suptech == 'offwind':
underwater_fraction = ds['underwater_fraction'].to_pandas() underwater_fraction = ds['underwater_fraction'].to_pandas()
connection_cost = (snakemake.config['lines']['length_factor'] * ds['average_distance'].to_pandas() * connection_cost = (snakemake.config['lines']['length_factor'] *
(underwater_fraction * costs.at[tech + '-connection-submarine', 'capital_cost'] + ds['average_distance'].to_pandas() *
(1. - underwater_fraction) * costs.at[tech + '-connection-underground', 'capital_cost'])) (underwater_fraction *
capital_cost = costs.at['offwind', 'capital_cost'] + costs.at[tech + '-station', 'capital_cost'] + connection_cost costs.at[tech + '-connection-submarine', 'capital_cost'] +
logger.info("Added connection cost of {:0.0f}-{:0.0f} Eur/MW/a to {}".format(connection_cost.min(), connection_cost.max(), tech)) (1. - underwater_fraction) *
costs.at[tech + '-connection-underground', 'capital_cost']))
capital_cost = (costs.at['offwind', 'capital_cost'] +
costs.at[tech + '-station', 'capital_cost'] +
connection_cost)
logger.info("Added connection cost of {:0.0f}-{:0.0f} Eur/MW/a to {}"
.format(connection_cost.min(), connection_cost.mafx(), tech))
elif suptech == 'onwind': elif suptech == 'onwind':
capital_cost = costs.at['onwind', 'capital_cost'] + costs.at['onwind-landcosts', 'capital_cost'] capital_cost = (costs.at['onwind', 'capital_cost'] +
costs.at['onwind-landcosts', 'capital_cost'])
else: else:
capital_cost = costs.at[tech, 'capital_cost'] capital_cost = costs.at[tech, 'capital_cost']
@ -299,26 +316,19 @@ def attach_wind_and_solar(n, costs):
def attach_conventional_generators(n, costs, ppl): def attach_conventional_generators(n, costs, ppl):
carriers = snakemake.config['electricity']['conventional_carriers'] carriers = snakemake.config['electricity']['conventional_carriers']
_add_missing_carriers_from_costs(n, costs, carriers) _add_missing_carriers_from_costs(n, costs, carriers)
ppl = ppl.rename(columns={'Name': 'name', 'Capacity': 'p_nom'}) ppl = (ppl.query('carrier in @carriers').join(costs, on='carrier')
ppm_fuels = {'OCGT': 'OCGT', 'CCGT': 'CCGT', .rename(index=lambda s: 'C' + str(s)))
'oil': 'Oil', 'nuclear': 'Nuclear',
'geothermal': 'Geothermal', 'biomass': 'Bioenergy',
'coal': 'Hard Coal', 'lignite': 'Lignite'}
for tech in carriers: logger.info('Adding {} generators with capacities\n{}'
p = pd.DataFrame(ppl.loc[ppl['Fueltype'] == ppm_fuels[tech]]) .format(len(ppl), ppl.groupby('carrier').p_nom.sum()))
p.index = 'C' + p.index.astype(str) n.madd("Generator", ppl.index,
logger.info('Adding {} generators of type {} with capacity {}' carrier=ppl.carrier,
.format(len(p), tech, p.p_nom.sum())) bus=ppl.bus,
p_nom=ppl.p_nom,
n.madd("Generator", p.index, efficiency=ppl.efficiency,
carrier=tech, marginal_cost=ppl.marginal_cost,
bus=p['bus'],
p_nom=p['p_nom'],
efficiency=costs.at[tech, 'efficiency'],
marginal_cost=costs.at[tech, 'marginal_cost'],
capital_cost=0) capital_cost=0)
logger.warn(f'Capital costs for conventional generators put to 0 EUR/MW.') logger.warning(f'Capital costs for conventional generators put to 0 EUR/MW.')
def attach_hydro(n, costs, ppl): def attach_hydro(n, costs, ppl):
@ -327,42 +337,33 @@ def attach_hydro(n, costs, ppl):
_add_missing_carriers_from_costs(n, costs, carriers) _add_missing_carriers_from_costs(n, costs, carriers)
ppl = ppl.loc[ppl['Fueltype'] == 'Hydro'] ppl = ppl.query('carrier == "hydro"').reset_index(drop=True)\
ppl = ppl.set_index(pd.RangeIndex(len(ppl)).astype(str) + ' hydro', drop=False) .rename(index=lambda s: str(s) + ' hydro')
ror = ppl.query('technology == "Run-Of-River"')
ppl = ppl.rename(columns={'Capacity':'p_nom', 'Technology': 'technology'}) phs = ppl.query('technology == "Pumped Storage"')
ppl = ppl.loc[ppl.technology.notnull(), ['bus', 'p_nom', 'technology']] hydro = ppl.query('technology == "Reservoir"')
ppl = ppl.assign(
has_inflow=ppl.technology.str.contains('Reservoir|Run-Of-River|Natural Inflow'),
has_store=ppl.technology.str.contains('Reservoir|Pumped Storage'),
has_pump=ppl.technology.str.contains('Pumped Storage')
)
country = ppl['bus'].map(n.buses.country).rename("country") country = ppl['bus'].map(n.buses.country).rename("country")
if ppl.has_inflow.any(): inflow_idx = ror.index ^ hydro.index
dist_key = ppl.loc[ppl.has_inflow, 'p_nom'].groupby(country).transform(normed) if not inflow_idx.empty:
dist_key = ppl.loc[inflow_idx, 'p_nom'].groupby(country).transform(normed)
with xr.open_dataarray(snakemake.input.profile_hydro) as inflow: with xr.open_dataarray(snakemake.input.profile_hydro) as inflow:
inflow_countries = pd.Index(country.loc[ppl.has_inflow].values) inflow_countries = pd.Index(country[inflow_idx])
assert len(inflow_countries.unique().difference(inflow.indexes['countries'])) == 0, ( missing_c = (inflow_countries.unique()
"'{}' is missing inflow time-series for at least one country: {}" .difference(inflow.indexes['countries']))
.format(snakemake.input.profile_hydro, ", ".join(inflow_countries.unique().difference(inflow.indexes['countries']))) assert missing_c.empty, (f"'{snakemake.input.profile_hydro}' is missing "
) f"inflow time-series for at least one country: {', '.join(missing_c)}")
inflow_t = ( inflow_t = (inflow.sel(countries=inflow_countries)
inflow.sel(countries=inflow_countries)
.rename({'countries': 'name'}) .rename({'countries': 'name'})
.assign_coords(name=ppl.index[ppl.has_inflow]) .assign_coords(name=inflow_idx)
.transpose('time', 'name') .transpose('time', 'name')
.to_pandas() .to_pandas()
.multiply(dist_key, axis=1) .multiply(dist_key, axis=1))
)
if 'ror' in carriers: if 'ror' in carriers and not ror.empty:
ror = ppl.loc[ppl.has_inflow & ~ ppl.has_store]
if not ror.empty:
n.madd("Generator", ror.index, n.madd("Generator", ror.index,
carrier='ror', carrier='ror',
bus=ror['bus'], bus=ror['bus'],
@ -370,35 +371,43 @@ def attach_hydro(n, costs, ppl):
efficiency=costs.at['ror', 'efficiency'], efficiency=costs.at['ror', 'efficiency'],
capital_cost=costs.at['ror', 'capital_cost'], capital_cost=costs.at['ror', 'capital_cost'],
weight=ror['p_nom'], weight=ror['p_nom'],
p_max_pu=(inflow_t.loc[:, ror.index] p_max_pu=(inflow_t[ror.index]
.divide(ror['p_nom'], axis=1) .divide(ror['p_nom'], axis=1)
.where(lambda df: df<=1., other=1.))) .where(lambda df: df<=1., other=1.)))
if 'PHS' in carriers: if 'PHS' in carriers and not phs.empty:
phs = ppl.loc[ppl.has_store & ppl.has_pump] # fill missing max hours to config value and assume no natural inflow
if not phs.empty: # due to lack of data
phs = phs.replace({'max_hours': {0: c['PHS_max_hours']}})
n.madd('StorageUnit', phs.index, n.madd('StorageUnit', phs.index,
carrier='PHS', carrier='PHS',
bus=phs['bus'], bus=phs['bus'],
p_nom=phs['p_nom'], p_nom=phs['p_nom'],
capital_cost=costs.at['PHS', 'capital_cost'], capital_cost=costs.at['PHS', 'capital_cost'],
max_hours=c['PHS_max_hours'], max_hours=phs['max_hours'],
efficiency_store=np.sqrt(costs.at['PHS','efficiency']), efficiency_store=np.sqrt(costs.at['PHS','efficiency']),
efficiency_dispatch=np.sqrt(costs.at['PHS','efficiency']), efficiency_dispatch=np.sqrt(costs.at['PHS','efficiency']),
cyclic_state_of_charge=True, cyclic_state_of_charge=True)
inflow=inflow_t.loc[:, phs.index[phs.has_inflow]])
if 'hydro' in carriers: if 'hydro' in carriers and not hydro.empty:
hydro = ppl.loc[ppl.has_store & ~ ppl.has_pump & ppl.has_inflow].join(country)
if not hydro.empty:
hydro_max_hours = c.get('hydro_max_hours') hydro_max_hours = c.get('hydro_max_hours')
if hydro_max_hours == 'energy_capacity_totals_by_country': if hydro_max_hours == 'energy_capacity_totals_by_country':
hydro_e_country = pd.read_csv(snakemake.input.hydro_capacities, index_col=0)["E_store[TWh]"].clip(lower=0.2)*1e6 # TODO: check where the statistics come from, IE's p_nom is totally
hydro_max_hours_country = hydro_e_country / hydro.groupby('country').p_nom.sum() # underrepresented
hydro_max_hours = hydro.country.map(hydro_e_country / hydro.groupby('country').p_nom.sum()) e_target = (pd.read_csv(snakemake.input.hydro_capacities,
index_col=0)["E_store[TWh]"].clip(lower=0.2)*1e6)
e_installed = hydro.eval('p_nom * max_hours').groupby(hydro.country).sum()
e_missing = e_target - e_installed
missing_mh_i = hydro.query('max_hours == 0').index
max_hours_country = e_missing / hydro.loc[missing_mh_i].groupby('country').p_nom.sum()
hydro_max_hours = hydro.max_hours.where(hydro.max_hours > 0,
hydro.country.map(max_hours_country))
elif hydro_max_hours == 'estimate_by_large_installations': elif hydro_max_hours == 'estimate_by_large_installations':
hydro_capacities = pd.read_csv(snakemake.input.hydro_capacities, comment="#", na_values='-', index_col=0) hydro_capacities = pd.read_csv(snakemake.input.hydro_capacities,
estim_hydro_max_hours = hydro_capacities.e_stor / hydro_capacities.p_nom_discharge comment="#", na_values='-', index_col=0)
estim_hydro_max_hours = hydro_capacities['E_store[TWh]'] * 1e3 / \
hydro_capacities['p_nom_discharge[GW]']
missing_countries = (pd.Index(hydro['country'].unique()) missing_countries = (pd.Index(hydro['country'].unique())
.difference(estim_hydro_max_hours.dropna().index)) .difference(estim_hydro_max_hours.dropna().index))
@ -433,7 +442,7 @@ def attach_extendable_generators(n, costs, ppl):
suptech = tech.split('-')[0] suptech = tech.split('-')[0]
if suptech == 'OCGT': if suptech == 'OCGT':
ocgt = ppl.loc[ppl.Fueltype.isin(('OCGT', 'CCGT'))].groupby('bus', as_index=False).first() ocgt = ppl.query("carrier in ['OCGT', 'CCGT']").groupby('bus', as_index=False).first()
n.madd('Generator', ocgt.index, n.madd('Generator', ocgt.index,
suffix=' OCGT', suffix=' OCGT',
bus=ocgt['bus'], bus=ocgt['bus'],
@ -445,7 +454,7 @@ def attach_extendable_generators(n, costs, ppl):
efficiency=costs.at['OCGT', 'efficiency']) efficiency=costs.at['OCGT', 'efficiency'])
elif suptech == 'CCGT': elif suptech == 'CCGT':
ccgt = ppl.loc[ppl.Fueltype.isin(('OCGT', 'CCGT'))].groupby('bus', as_index=False).first() ccgt = ppl.query("carrier in ['OCGT', 'CCGT']").groupby('bus', as_index=False).first()
n.madd('Generator', ccgt.index, n.madd('Generator', ccgt.index,
suffix=' CCGT', suffix=' CCGT',
bus=ccgt['bus'], bus=ccgt['bus'],
@ -456,7 +465,8 @@ def attach_extendable_generators(n, costs, ppl):
marginal_cost=costs.at['CCGT', 'marginal_cost'], marginal_cost=costs.at['CCGT', 'marginal_cost'],
efficiency=costs.at['CCGT', 'efficiency']) efficiency=costs.at['CCGT', 'efficiency'])
else: else:
raise NotImplementedError(f"Adding extendable generators for carrier '{tech}' is not implemented, yet." raise NotImplementedError(f"Adding extendable generators for carrier "
"'{tech}' is not implemented, yet. "
"Only OCGT and CCGT are allowed at the moment.") "Only OCGT and CCGT are allowed at the moment.")
@ -533,27 +543,27 @@ def attach_storage(n, costs):
def estimate_renewable_capacities(n, tech_map=None): def estimate_renewable_capacities(n, tech_map=None):
if tech_map is None: if tech_map is None:
tech_map = snakemake.config['electricity'].get('estimate_renewable_capacities_from_capacity_stats', {}) tech_map = (snakemake.config['electricity']
.get('estimate_renewable_capacities_from_capacity_stats', {}))
if len(tech_map) == 0: return if len(tech_map) == 0: return
assert has_ppm, "The estimation of renewable capacities needs the powerplantmatching package" capacities = (ppm.data.Capacity_stats().powerplant.convert_country_to_alpha2()
[lambda df: df.Energy_Source_Level_2]
capacities = ppm.data.Capacity_stats() .set_index(['Fueltype', 'Country']).sort_index())
capacities['alpha_2'] = capacities['Country'].map(country_alpha_2)
capacities = capacities.loc[capacities.Energy_Source_Level_2].set_index(['Fueltype', 'alpha_2']).sort_index()
countries = n.buses.country.unique() countries = n.buses.country.unique()
for ppm_fueltype, techs in tech_map.items(): for ppm_fueltype, techs in tech_map.items():
tech_capacities = capacities.loc[ppm_fueltype, 'Capacity'].reindex(countries, fill_value=0.) tech_capacities = capacities.loc[ppm_fueltype, 'Capacity']\
tech_b = n.generators.carrier.isin(techs) .reindex(countries, fill_value=0.)
n.generators.loc[tech_b, 'p_nom'] = ( tech_i = n.generators.query('carrier in @techs').index
(n.generators_t.p_max_pu.mean().loc[tech_b] * n.generators.loc[tech_b, 'p_nom_max']) # maximal yearly generation n.generators.loc[tech_i, 'p_nom'] = (
(n.generators_t.p_max_pu[tech_i].mean() *
n.generators.loc[tech_i, 'p_nom_max']) # maximal yearly generation
.groupby(n.generators.bus.map(n.buses.country)) # for each country .groupby(n.generators.bus.map(n.buses.country)) # for each country
.transform(lambda s: normed(s) * tech_capacities.at[s.name]) .transform(lambda s: normed(s) * tech_capacities.at[s.name])
.where(lambda s: s>0.1, 0.) # only capacities above 100kW .where(lambda s: s>0.1, 0.)) # only capacities above 100kW
)
def add_co2limit(n, Nyears=1.): def add_co2limit(n, Nyears=1.):
n.add("GlobalConstraint", "CO2Limit", n.add("GlobalConstraint", "CO2Limit",
@ -592,7 +602,7 @@ if __name__ == "__main__":
Nyears = n.snapshot_weightings.sum()/8760. Nyears = n.snapshot_weightings.sum()/8760.
costs = load_costs(Nyears) costs = load_costs(Nyears)
ppl = load_powerplants(n) ppl = load_powerplants()
attach_load(n) attach_load(n)

View File

@ -35,22 +35,10 @@ Description
""" """
import logging import logging
import numpy as np
import pandas as pd
from scipy.spatial import cKDTree as KDTree from scipy.spatial import cKDTree as KDTree
import pycountry as pyc
import pypsa import pypsa
import powerplantmatching as ppm import powerplantmatching as pm
def country_alpha_2(name):
try:
cntry = pyc.countries.get(name=name)
except KeyError:
cntry = None
if cntry is None:
cntry = pyc.countries.get(official_name=name)
return cntry.alpha_2
if __name__ == "__main__": if __name__ == "__main__":
if 'snakemake' not in globals(): if 'snakemake' not in globals():
@ -64,42 +52,30 @@ if __name__ == "__main__":
logging.basicConfig(level=snakemake.config['logging_level']) logging.basicConfig(level=snakemake.config['logging_level'])
n = pypsa.Network(snakemake.input.base_network) n = pypsa.Network(snakemake.input.base_network)
ppm.powerplants(from_url=True)
ppl = (ppm.collection.matched_data()
[lambda df : ~df.Fueltype.isin(('Solar', 'Wind'))]
.pipe(ppm.cleaning.clean_technology)
.assign(Fueltype=lambda df: (
df.Fueltype.where(df.Fueltype != 'Natural Gas',
df.Technology.replace('Steam Turbine', 'OCGT').fillna('OCGT'))))
.pipe(ppm.utils.fill_geoposition))
# ppl.loc[(ppl.Fueltype == 'Other') & ppl.Technology.str.contains('CCGT'), 'Fueltype'] = 'CCGT'
# ppl.loc[(ppl.Fueltype == 'Other') & ppl.Technology.str.contains('Steam Turbine'), 'Fueltype'] = 'CCGT'
ppl = ppl.loc[ppl.lon.notnull() & ppl.lat.notnull()]
ppl = ppl.replace({"Country": {"Macedonia, Republic of": "North Macedonia"}})
ppl_country = ppl.Country.map(country_alpha_2)
countries = n.buses.country.unique() countries = n.buses.country.unique()
cntries_without_ppl = []
for cntry in countries: ppl = (pm.powerplants(from_url=True)
substation_lv_i = n.buses.index[n.buses['substation_lv'] & (n.buses.country == cntry)] .powerplant.convert_country_to_alpha2()
ppl_b = ppl_country == cntry .query('Fueltype not in ["Solar", "Wind"] and Country in @countries')
if not ppl_b.any(): .replace({'Technology': {'Steam Turbine': 'OCGT'}})
cntries_without_ppl.append(cntry) .assign(Fueltype=lambda df: (
continue df.Fueltype
.where(df.Fueltype != 'Natural Gas',
df.Technology.replace('Steam Turbine',
'OCGT').fillna('OCGT')))))
kdtree = KDTree(n.buses.loc[substation_lv_i, ['x','y']].values) cntries_without_ppl = [c for c in countries if c not in ppl.Country.unique()]
ppl.loc[ppl_b, 'bus'] = substation_lv_i[kdtree.query(ppl.loc[ppl_b, ['lon','lat']].values)[1]]
substation_i = n.buses.query('substation_lv').index
kdtree = KDTree(n.buses.loc[substation_i, ['x','y']].values)
ppl['bus'] = substation_i[kdtree.query(ppl[['lon','lat']].values)[1]]
if cntries_without_ppl: if cntries_without_ppl:
logging.warning("No powerplants known in: {}".format(", ".join(cntries_without_ppl))) logging.warning(f"No powerplants known in: {', '.join(cntries_without_ppl)}")
bus_null_b = ppl["bus"].isnull() bus_null_b = ppl["bus"].isnull()
if bus_null_b.any(): if bus_null_b.any():
logging.warning("Couldn't find close bus for {} powerplants".format(bus_null_b.sum())) logging.warning(f"Couldn't find close bus for {bus_null_b.sum()} powerplants")
ppl.to_csv(snakemake.output[0]) ppl.to_csv(snakemake.output[0])