Merge pull request #84 from PyPSA/powerplants

Update powerplants according to new powerplantmatching version
This commit is contained in:
FabianHofmann 2019-11-04 16:26:54 +01:00 committed by GitHub
commit 3b83985436
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 236 additions and 6723 deletions

View File

@ -9,6 +9,7 @@ install:
- sudo apt-get install -yq --no-install-recommends curl bzip2 xz-utils git ca-certificates coinor-cbc
# download and extract data dependencies
- mkdir ./resources
- curl -L "https://zenodo.org/record/3517921/files/pypsa-eur-tutorial-data-bundle.tar.xz" -o "./bundle.tar.xz"
- curl -L "https://zenodo.org/record/3518020/files/pypsa-eur-tutorial-cutouts.tar.xz" -o "./cutouts.tar.xz"
- curl -L "https://zenodo.org/record/3518215/files/natura.tiff" -o "./resources/natura.tiff"

View File

@ -32,14 +32,13 @@ if config['enable']['prepare_links_p_nom']:
# group: 'nonfeedin_preparation'
script: 'scripts/prepare_links_p_nom.py'
if config['enable']['powerplantmatching']:
rule build_powerplants:
input: base_network="networks/base.nc"
output: "resources/powerplants.csv"
threads: 1
resources: mem=500
# group: 'nonfeedin_preparation'
script: "scripts/build_powerplants.py"
rule build_powerplants:
input: base_network="networks/base.nc"
output: "resources/powerplants.csv"
threads: 1
resources: mem=500
# group: 'nonfeedin_preparation'
script: "scripts/build_powerplants.py"
rule base_network:
input:

View File

@ -18,7 +18,6 @@ snapshots:
closed: 'left' # end is not inclusive
enable:
powerplantmatching: false
prepare_links_p_nom: false
electricity:
@ -34,7 +33,15 @@ electricity:
battery: 6
H2: 168
conventional_carriers: [] # [nuclear, oil, OCGT, CCGT, coal, lignite, geothermal, biomass]
powerplants_filter: false
custom_powerplants: false #replace or add
conventional_carriers: [] # nuclear, oil, OCGT, CCGT, coal, lignite, geothermal, biomass]
# estimate_renewable_capacities_from_capacity_stats:
# # Wind is the Fueltype in ppm.data.Capacity_stats, onwind, offwind-{ac,dc} the carrier in PyPSA-Eur
# Wind: [onwind, offwind-ac, offwind-dc]
# Solar: [solar]
atlite:
nprocesses: 4

File diff suppressed because it is too large Load Diff

View File

@ -36,8 +36,9 @@ Relevant Settings
lines:
length_factor:
.. seealso::
Documentation of the configuration file ``config.yaml`` at :ref:`costs_cf`, :ref:`electricity_cf`, :ref:`load_cf`, :ref:`renewable_cf`, :ref:`lines_cf`
.. seealso::
Documentation of the configuration file ``config.yaml`` at :ref:`costs_cf`,
:ref:`electricity_cf`, :ref:`load_cf`, :ref:`renewable_cf`, :ref:`lines_cf`
Inputs
------
@ -94,7 +95,6 @@ import pandas as pd
idx = pd.IndexSlice
import numpy as np
import scipy as sp
import xarray as xr
import geopandas as gpd
@ -104,14 +104,8 @@ from vresutils.load import timeseries_opsd
from vresutils import transfer as vtransfer
import pypsa
import powerplantmatching as ppm
try:
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()
@ -119,7 +113,8 @@ def _add_missing_carriers_from_costs(n, costs, carriers):
missing_carriers = pd.Index(carriers).difference(n.carriers.index)
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]
emissions = costs.loc[suptechs, emissions_cols].fillna(0.)
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("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,
"FOM" : 0,
@ -150,7 +146,8 @@ def load_costs(Nyears=1., tech_costs=None, config=None, elec_config=None):
"investment" : 0,
"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.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['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.):
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"],
max_hours=max_hours['battery'])
costs.loc["H2"] = \
costs_for_storage(costs.loc["hydrogen storage"], costs.loc["fuel cell"], costs.loc["electrolysis"],
max_hours=max_hours['H2'])
costs_for_storage(costs.loc["hydrogen storage"], costs.loc["fuel cell"],
costs.loc["electrolysis"], max_hours=max_hours['H2'])
for attr in ('marginal_cost', 'capital_cost'):
overwrites = config.get(attr)
@ -194,19 +192,27 @@ def load_costs(Nyears=1., tech_costs=None, config=None, elec_config=None):
return costs
def load_powerplants(n, ppl_fn=None):
def load_powerplants(ppl_fn=None):
if ppl_fn is None:
ppl_fn = snakemake.input.powerplants
ppl = pd.read_csv(ppl_fn, index_col=0, dtype={'bus': 'str'})
return ppl.loc[ppl.bus.isin(n.buses.index)]
carrier_dict = {'ocgt': 'OCGT', 'ccgt': 'CCGT', 'bioenergy':'biomass',
'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
def attach_load(n):
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)),
snakemake.input.opsd_load) *
snakemake.config.get('load', {}).get('scaling_factor', 1.0))
@ -224,17 +230,21 @@ def attach_load(n):
return pd.DataFrame({group.index[0]: l})
else:
nuts3_cntry = nuts3.loc[nuts3.country == cntry]
transfer = vtransfer.Shapes2Shapes(group, nuts3_cntry.geometry, normed=False).T.tocsr()
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)
transfer = vtransfer.Shapes2Shapes(group, nuts3_cntry.geometry,
normed=False).T.tocsr()
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
# 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))
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)
for cntry, group in regions.geometry.groupby(regions.country)], axis=1)
load = pd.concat([upsample(cntry, group) for cntry, group
in regions.geometry.groupby(regions.country)], axis=1)
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'
if simple_hvdc_costs:
n.links.loc[dc_b, 'capital_cost'] = (n.links.loc[dc_b, 'length'] * length_factor *
costs.at['HVDC overhead', 'capital_cost'])
costs = (n.links.loc[dc_b, 'length'] * length_factor *
costs.at['HVDC overhead', 'capital_cost'])
else:
n.links.loc[dc_b, 'capital_cost'] = (n.links.loc[dc_b, 'length'] * length_factor *
((1. - n.links.loc[dc_b, 'underwater_fraction']) *
costs.at['HVDC overhead', 'capital_cost'] +
n.links.loc[dc_b, 'underwater_fraction'] *
costs.at['HVDC submarine', 'capital_cost']) +
costs.at['HVDC inverter pair', 'capital_cost'])
costs = (n.links.loc[dc_b, 'length'] * length_factor *
((1. - n.links.loc[dc_b, 'underwater_fraction']) *
costs.at['HVDC overhead', 'capital_cost'] +
n.links.loc[dc_b, 'underwater_fraction'] *
costs.at['HVDC submarine', 'capital_cost']) +
costs.at['HVDC inverter pair', 'capital_cost'])
n.links.loc[dc_b, 'capital_cost'] = costs
# ### Generators
def attach_wind_and_solar(n, costs):
@ -271,13 +281,20 @@ def attach_wind_and_solar(n, costs):
suptech = tech.split('-', 2)[0]
if suptech == 'offwind':
underwater_fraction = ds['underwater_fraction'].to_pandas()
connection_cost = (snakemake.config['lines']['length_factor'] * ds['average_distance'].to_pandas() *
(underwater_fraction * costs.at[tech + '-connection-submarine', 'capital_cost'] +
(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.max(), tech))
connection_cost = (snakemake.config['lines']['length_factor'] *
ds['average_distance'].to_pandas() *
(underwater_fraction *
costs.at[tech + '-connection-submarine', 'capital_cost'] +
(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.max(), tech))
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:
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):
carriers = snakemake.config['electricity']['conventional_carriers']
_add_missing_carriers_from_costs(n, costs, carriers)
ppl = ppl.rename(columns={'Name': 'name', 'Capacity': 'p_nom'})
ppm_fuels = {'OCGT': 'OCGT', 'CCGT': 'CCGT',
'oil': 'Oil', 'nuclear': 'Nuclear',
'geothermal': 'Geothermal', 'biomass': 'Bioenergy',
'coal': 'Hard Coal', 'lignite': 'Lignite'}
ppl = (ppl.query('carrier in @carriers').join(costs, on='carrier')
.rename(index=lambda s: 'C' + str(s)))
for tech in carriers:
p = pd.DataFrame(ppl.loc[ppl['Fueltype'] == ppm_fuels[tech]])
p.index = 'C' + p.index.astype(str)
logger.info('Adding {} generators of type {} with capacity {}'
.format(len(p), tech, p.p_nom.sum()))
n.madd("Generator", p.index,
carrier=tech,
bus=p['bus'],
p_nom=p['p_nom'],
efficiency=costs.at[tech, 'efficiency'],
marginal_cost=costs.at[tech, 'marginal_cost'],
capital_cost=0)
logger.warn(f'Capital costs for conventional generators put to 0 EUR/MW.')
logger.info('Adding {} generators with capacities\n{}'
.format(len(ppl), ppl.groupby('carrier').p_nom.sum()))
n.madd("Generator", ppl.index,
carrier=ppl.carrier,
bus=ppl.bus,
p_nom=ppl.p_nom,
efficiency=ppl.efficiency,
marginal_cost=ppl.marginal_cost,
capital_cost=0)
logger.warning(f'Capital costs for conventional generators put to 0 EUR/MW.')
def attach_hydro(n, costs, ppl):
@ -327,100 +337,98 @@ def attach_hydro(n, costs, ppl):
_add_missing_carriers_from_costs(n, costs, carriers)
ppl = ppl.loc[ppl['Fueltype'] == 'Hydro']
ppl = ppl.set_index(pd.RangeIndex(len(ppl)).astype(str) + ' hydro', drop=False)
ppl = ppl.rename(columns={'Capacity':'p_nom', 'Technology': 'technology'})
ppl = ppl.loc[ppl.technology.notnull(), ['bus', 'p_nom', 'technology']]
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')
)
ppl = ppl.query('carrier == "hydro"').reset_index(drop=True)\
.rename(index=lambda s: str(s) + ' hydro')
ror = ppl.query('technology == "Run-Of-River"')
phs = ppl.query('technology == "Pumped Storage"')
hydro = ppl.query('technology == "Reservoir"')
country = ppl['bus'].map(n.buses.country).rename("country")
if ppl.has_inflow.any():
dist_key = ppl.loc[ppl.has_inflow, 'p_nom'].groupby(country).transform(normed)
inflow_idx = ror.index | hydro.index
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:
inflow_countries = pd.Index(country.loc[ppl.has_inflow].values)
assert len(inflow_countries.unique().difference(inflow.indexes['countries'])) == 0, (
"'{}' is missing inflow time-series for at least one country: {}"
.format(snakemake.input.profile_hydro, ", ".join(inflow_countries.unique().difference(inflow.indexes['countries'])))
)
inflow_countries = pd.Index(country[inflow_idx])
missing_c = (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.sel(countries=inflow_countries)
.rename({'countries': 'name'})
.assign_coords(name=ppl.index[ppl.has_inflow])
.transpose('time', 'name')
.to_pandas()
.multiply(dist_key, axis=1)
)
inflow_t = (inflow.sel(countries=inflow_countries)
.rename({'countries': 'name'})
.assign_coords(name=inflow_idx)
.transpose('time', 'name')
.to_pandas()
.multiply(dist_key, axis=1))
if 'ror' in carriers:
ror = ppl.loc[ppl.has_inflow & ~ ppl.has_store]
if not ror.empty:
n.madd("Generator", ror.index,
carrier='ror',
bus=ror['bus'],
p_nom=ror['p_nom'],
efficiency=costs.at['ror', 'efficiency'],
capital_cost=costs.at['ror', 'capital_cost'],
weight=ror['p_nom'],
p_max_pu=(inflow_t.loc[:, ror.index]
.divide(ror['p_nom'], axis=1)
.where(lambda df: df<=1., other=1.)))
if 'ror' in carriers and not ror.empty:
n.madd("Generator", ror.index,
carrier='ror',
bus=ror['bus'],
p_nom=ror['p_nom'],
efficiency=costs.at['ror', 'efficiency'],
capital_cost=costs.at['ror', 'capital_cost'],
weight=ror['p_nom'],
p_max_pu=(inflow_t[ror.index]
.divide(ror['p_nom'], axis=1)
.where(lambda df: df<=1., other=1.)))
if 'PHS' in carriers:
phs = ppl.loc[ppl.has_store & ppl.has_pump]
if not phs.empty:
n.madd('StorageUnit', phs.index,
carrier='PHS',
bus=phs['bus'],
p_nom=phs['p_nom'],
capital_cost=costs.at['PHS', 'capital_cost'],
max_hours=c['PHS_max_hours'],
efficiency_store=np.sqrt(costs.at['PHS','efficiency']),
efficiency_dispatch=np.sqrt(costs.at['PHS','efficiency']),
cyclic_state_of_charge=True,
inflow=inflow_t.loc[:, phs.index[phs.has_inflow]])
if 'PHS' in carriers and not phs.empty:
# fill missing max hours to config value and assume no natural inflow
# due to lack of data
phs = phs.replace({'max_hours': {0: c['PHS_max_hours']}})
n.madd('StorageUnit', phs.index,
carrier='PHS',
bus=phs['bus'],
p_nom=phs['p_nom'],
capital_cost=costs.at['PHS', 'capital_cost'],
max_hours=phs['max_hours'],
efficiency_store=np.sqrt(costs.at['PHS','efficiency']),
efficiency_dispatch=np.sqrt(costs.at['PHS','efficiency']),
cyclic_state_of_charge=True)
if 'hydro' in carriers:
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')
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
hydro_max_hours_country = hydro_e_country / hydro.groupby('country').p_nom.sum()
hydro_max_hours = hydro.country.map(hydro_e_country / hydro.groupby('country').p_nom.sum())
elif hydro_max_hours == 'estimate_by_large_installations':
hydro_capacities = pd.read_csv(snakemake.input.hydro_capacities, comment="#", na_values='-', index_col=0)
estim_hydro_max_hours = hydro_capacities.e_stor / hydro_capacities.p_nom_discharge
if 'hydro' in carriers and not hydro.empty:
hydro_max_hours = c.get('hydro_max_hours')
hydro_stats = pd.read_csv(snakemake.input.hydro_capacities,
comment="#", na_values='-', index_col=0)
e_target = hydro_stats["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
missing_countries = (pd.Index(hydro['country'].unique())
.difference(estim_hydro_max_hours.dropna().index))
if not missing_countries.empty:
logger.warning("Assuming max_hours=6 for hydro reservoirs in the countries: {}"
.format(", ".join(missing_countries)))
if hydro_max_hours == 'energy_capacity_totals_by_country':
# watch out some p_nom values like IE's are totally underrepresented
max_hours_country = e_missing / \
hydro.loc[missing_mh_i].groupby('country').p_nom.sum()
hydro_max_hours = hydro['country'].map(estim_hydro_max_hours).fillna(6)
elif hydro_max_hours == 'estimate_by_large_installations':
max_hours_country = hydro_stats['E_store[TWh]'] * 1e3 / \
hydro_stats['p_nom_discharge[GW]']
n.madd('StorageUnit', hydro.index, carrier='hydro',
bus=hydro['bus'],
p_nom=hydro['p_nom'],
max_hours=hydro_max_hours,
capital_cost=(costs.at['hydro', 'capital_cost']
if c.get('hydro_capital_cost') else 0.),
marginal_cost=costs.at['hydro', 'marginal_cost'],
p_max_pu=1., # dispatch
p_min_pu=0., # store
efficiency_dispatch=costs.at['hydro', 'efficiency'],
efficiency_store=0.,
cyclic_state_of_charge=True,
inflow=inflow_t.loc[:, hydro.index])
missing_countries = (pd.Index(hydro['country'].unique())
.difference(max_hours_country.dropna().index))
if not missing_countries.empty:
logger.warning("Assuming max_hours=6 for hydro reservoirs in the countries: {}"
.format(", ".join(missing_countries)))
hydro_max_hours = hydro.max_hours.where(hydro.max_hours > 0,
hydro.country.map(max_hours_country)).fillna(6)
n.madd('StorageUnit', hydro.index, carrier='hydro',
bus=hydro['bus'],
p_nom=hydro['p_nom'],
max_hours=hydro_max_hours,
capital_cost=(costs.at['hydro', 'capital_cost']
if c.get('hydro_capital_cost') else 0.),
marginal_cost=costs.at['hydro', 'marginal_cost'],
p_max_pu=1., # dispatch
p_min_pu=0., # store
efficiency_dispatch=costs.at['hydro', 'efficiency'],
efficiency_store=0.,
cyclic_state_of_charge=True,
inflow=inflow_t.loc[:, hydro.index])
def attach_extendable_generators(n, costs, ppl):
@ -433,7 +441,7 @@ def attach_extendable_generators(n, costs, ppl):
suptech = tech.split('-')[0]
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,
suffix=' OCGT',
bus=ocgt['bus'],
@ -445,7 +453,7 @@ def attach_extendable_generators(n, costs, ppl):
efficiency=costs.at['OCGT', 'efficiency'])
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,
suffix=' CCGT',
bus=ccgt['bus'],
@ -456,8 +464,9 @@ def attach_extendable_generators(n, costs, ppl):
marginal_cost=costs.at['CCGT', 'marginal_cost'],
efficiency=costs.at['CCGT', 'efficiency'])
else:
raise NotImplementedError(f"Adding extendable generators for carrier '{tech}' is not implemented, yet."
"Only OCGT and CCGT are allowed at the moment.")
raise NotImplementedError(f"Adding extendable generators for carrier "
"'{tech}' is not implemented, yet. "
"Only OCGT and CCGT are allowed at the moment.")
def attach_storage(n, costs):
@ -533,27 +542,27 @@ def attach_storage(n, costs):
def estimate_renewable_capacities(n, tech_map=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
assert has_ppm, "The estimation of renewable capacities needs the powerplantmatching package"
capacities = ppm.data.Capacity_stats()
capacities['alpha_2'] = capacities['Country'].map(country_alpha_2)
capacities = capacities.loc[capacities.Energy_Source_Level_2].set_index(['Fueltype', 'alpha_2']).sort_index()
capacities = (ppm.data.Capacity_stats().powerplant.convert_country_to_alpha2()
[lambda df: df.Energy_Source_Level_2]
.set_index(['Fueltype', 'Country']).sort_index())
countries = n.buses.country.unique()
for ppm_fueltype, techs in tech_map.items():
tech_capacities = capacities.loc[ppm_fueltype, 'Capacity'].reindex(countries, fill_value=0.)
tech_b = n.generators.carrier.isin(techs)
n.generators.loc[tech_b, 'p_nom'] = (
(n.generators_t.p_max_pu.mean().loc[tech_b] * n.generators.loc[tech_b, 'p_nom_max']) # maximal yearly generation
.groupby(n.generators.bus.map(n.buses.country)) # for each country
.transform(lambda s: normed(s) * tech_capacities.at[s.name])
.where(lambda s: s>0.1, 0.) # only capacities above 100kW
)
tech_capacities = capacities.loc[ppm_fueltype, 'Capacity']\
.reindex(countries, fill_value=0.)
tech_i = n.generators.query('carrier in @techs').index
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
.transform(lambda s: normed(s) * tech_capacities.at[s.name])
.where(lambda s: s>0.1, 0.)) # only capacities above 100kW
def add_co2limit(n, Nyears=1.):
n.add("GlobalConstraint", "CO2Limit",
@ -592,7 +601,7 @@ if __name__ == "__main__":
Nyears = n.snapshot_weightings.sum()/8760.
costs = load_costs(Nyears)
ppl = load_powerplants(n)
ppl = load_powerplants()
attach_load(n)

View File

@ -7,10 +7,11 @@ Relevant Settings
.. code:: yaml
enable:
powerplantmatching:
electricity:
powerplants_filter:
custom_powerplants:
.. seealso::
.. seealso::
Documentation of the configuration file ``config.yaml`` at
:ref:`toplevel_cf`
@ -35,71 +36,71 @@ Description
"""
import logging
import numpy as np
import pandas as pd
from scipy.spatial import cKDTree as KDTree
import pycountry as pyc
import pypsa
import powerplantmatching as ppm
import powerplantmatching as pm
import pandas as pd
logger = logging.getLogger(__name__)
def add_custom_powerplants(ppl):
custom_ppl_query = snakemake.config['electricity']['custom_powerplants']
if not custom_ppl_query:
return ppl
add_ppls = pd.read_csv(snakemake.input.custom_powerplants, index_col=0)
if isinstance(custom_ppl_query, str):
add_ppls.query(add_ppls, inplace=True)
return ppl.append(add_ppls, sort=False)
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 'snakemake' not in globals():
from vresutils.snakemake import MockSnakemake, Dict
snakemake = MockSnakemake(
input=Dict(base_network='networks/base.nc'),
input=Dict(base_network='networks/base.nc',
custom_powerplants='data/custom_powerplants.csv'),
output=['resources/powerplants.csv']
)
logging.basicConfig(level=snakemake.config['logging_level'])
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()
cntries_without_ppl = []
for cntry in countries:
substation_lv_i = n.buses.index[n.buses['substation_lv'] & (n.buses.country == cntry)]
ppl_b = ppl_country == cntry
if not ppl_b.any():
cntries_without_ppl.append(cntry)
continue
ppl = (pm.powerplants(from_url=True)
.powerplant.convert_country_to_alpha2()
.query('Fueltype not in ["Solar", "Wind"] and Country in @countries')
.replace({'Technology': {'Steam Turbine': 'OCGT'}})
.assign(Fueltype=lambda df: (
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)
ppl.loc[ppl_b, 'bus'] = substation_lv_i[kdtree.query(ppl.loc[ppl_b, ['lon','lat']].values)[1]]
ppl_query = snakemake.config['electricity']['powerplants_filter']
if isinstance(ppl_query, str):
ppl.query(ppl_query, inplace=True)
ppl = add_custom_powerplants(ppl) # add carriers from own powerplant files
cntries_without_ppl = [c for c in countries if c not in ppl.Country.unique()]
for c in countries:
substation_i = n.buses.query('substation_lv and country == @c').index
kdtree = KDTree(n.buses.loc[substation_i, ['x','y']].values)
ppl_i = ppl.query('Country == @c').index
ppl.loc[ppl_i, 'bus'] = substation_i[kdtree.query(ppl.loc[ppl_i,
['lon','lat']].values)[1]]
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()
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])

View File

@ -18,7 +18,6 @@ snapshots:
closed: 'left' # end is not inclusive
enable:
powerplantmatching: false
prepare_links_p_nom: false
electricity:
@ -33,6 +32,8 @@ electricity:
battery: 6
H2: 168
powerplants_filter: false
custom_powerplants: false #replace or add
conventional_carriers: [coal, CCGT] # [nuclear, oil, OCGT, CCGT, coal, lignite, geothermal, biomass]
atlite: