2018-01-29 21:28:33 +00:00
|
|
|
# coding: utf-8
|
|
|
|
|
|
|
|
import logging
|
|
|
|
logger = logging.getLogger(__name__)
|
2018-02-01 11:05:25 +00:00
|
|
|
|
2018-01-29 21:28:33 +00:00
|
|
|
import pandas as pd
|
|
|
|
idx = pd.IndexSlice
|
|
|
|
|
|
|
|
import numpy as np
|
|
|
|
import scipy as sp
|
|
|
|
import xarray as xr
|
|
|
|
|
|
|
|
import geopandas as gpd
|
|
|
|
|
|
|
|
from vresutils.costdata import annuity
|
2018-08-03 09:53:14 +00:00
|
|
|
from vresutils.load import timeseries_opsd
|
2018-08-06 19:38:13 +00:00
|
|
|
from vresutils import transfer as vtransfer
|
2018-01-29 21:28:33 +00:00
|
|
|
|
|
|
|
import pypsa
|
|
|
|
|
|
|
|
|
|
|
|
def normed(s): return s/s.sum()
|
|
|
|
|
|
|
|
def _add_missing_carriers_from_costs(n, costs, carriers):
|
|
|
|
missing_carriers = pd.Index(carriers).difference(n.carriers.index)
|
|
|
|
emissions_cols = costs.columns.to_series().loc[lambda s: s.str.endswith('_emissions')].values
|
|
|
|
n.import_components_from_dataframe(costs.loc[missing_carriers, emissions_cols].fillna(0.), 'Carrier')
|
|
|
|
|
2018-05-18 15:09:18 +00:00
|
|
|
def load_costs(Nyears=1., tech_costs=None, config=None, elec_config=None):
|
|
|
|
if tech_costs is None:
|
|
|
|
tech_costs = snakemake.input.tech_costs
|
|
|
|
|
|
|
|
if config is None:
|
|
|
|
config = snakemake.config['costs']
|
2018-01-29 21:28:33 +00:00
|
|
|
|
|
|
|
# set all asset costs and other parameters
|
2018-05-18 15:09:18 +00:00
|
|
|
costs = pd.read_csv(tech_costs, index_col=list(range(3))).sort_index()
|
2018-01-29 21:28:33 +00:00
|
|
|
|
|
|
|
# correct units to MW and EUR
|
|
|
|
costs.loc[costs.unit.str.contains("/kW"),"value"] *= 1e3
|
2018-05-18 15:09:18 +00:00
|
|
|
costs.loc[costs.unit.str.contains("USD"),"value"] *= config['USD2013_to_EUR2013']
|
2018-01-29 21:28:33 +00:00
|
|
|
|
2018-07-25 15:15:06 +00:00
|
|
|
costs = costs.loc[idx[:,config['year'],:], "value"].unstack(level=2).groupby("technology").sum(min_count=1)
|
2018-01-29 21:28:33 +00:00
|
|
|
|
|
|
|
costs = costs.fillna({"CO2 intensity" : 0,
|
|
|
|
"FOM" : 0,
|
|
|
|
"VOM" : 0,
|
2018-05-18 15:09:18 +00:00
|
|
|
"discount rate" : config['discountrate'],
|
2018-01-29 21:28:33 +00:00
|
|
|
"efficiency" : 1,
|
|
|
|
"fuel" : 0,
|
|
|
|
"investment" : 0,
|
|
|
|
"lifetime" : 25})
|
|
|
|
|
|
|
|
costs["capital_cost"] = ((annuity(costs["lifetime"], costs["discount rate"]) + costs["FOM"]/100.) *
|
|
|
|
costs["investment"] * Nyears)
|
2018-07-25 15:15:06 +00:00
|
|
|
|
|
|
|
costs.at['OCGT', 'fuel'] = costs.at['gas', 'fuel']
|
|
|
|
costs.at['CCGT', 'fuel'] = costs.at['gas', 'fuel']
|
|
|
|
|
2018-01-29 21:28:33 +00:00
|
|
|
costs['marginal_cost'] = costs['VOM'] + costs['fuel'] / costs['efficiency']
|
|
|
|
|
|
|
|
costs = costs.rename(columns={"CO2 intensity": "co2_emissions"})
|
|
|
|
|
2018-02-19 09:09:39 +00:00
|
|
|
costs.at['OCGT', 'co2_emissions'] = costs.at['gas', 'co2_emissions']
|
2018-03-13 09:50:28 +00:00
|
|
|
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'])
|
2018-02-19 09:09:39 +00:00
|
|
|
|
2018-01-29 21:28:33 +00:00
|
|
|
def costs_for_storage(store, link1, link2=None, max_hours=1.):
|
|
|
|
capital_cost = link1['capital_cost'] + max_hours * store['capital_cost']
|
|
|
|
efficiency = link1['efficiency']**0.5
|
|
|
|
if link2 is not None:
|
|
|
|
capital_cost += link2['capital_cost']
|
|
|
|
efficiency *= link2['efficiency']**0.5
|
|
|
|
return pd.Series(dict(capital_cost=capital_cost,
|
2018-01-30 22:12:36 +00:00
|
|
|
marginal_cost=0.,
|
2018-01-29 21:28:33 +00:00
|
|
|
efficiency=efficiency,
|
|
|
|
co2_emissions=0.))
|
|
|
|
|
2018-05-18 15:09:18 +00:00
|
|
|
if elec_config is None:
|
|
|
|
elec_config = snakemake.config['electricity']
|
|
|
|
max_hours = elec_config['max_hours']
|
2018-01-29 21:28:33 +00:00
|
|
|
costs.loc["battery"] = \
|
|
|
|
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'])
|
|
|
|
|
|
|
|
for attr in ('marginal_cost', 'capital_cost'):
|
2018-05-18 15:09:18 +00:00
|
|
|
overwrites = config.get(attr)
|
2018-01-29 21:28:33 +00:00
|
|
|
if overwrites is not None:
|
|
|
|
overwrites = pd.Series(overwrites)
|
|
|
|
costs.loc[overwrites.index, attr] = overwrites
|
|
|
|
|
|
|
|
return costs
|
|
|
|
|
2018-02-10 16:16:20 +00:00
|
|
|
def load_powerplants(n, ppl_fn=None):
|
|
|
|
if ppl_fn is None:
|
|
|
|
ppl_fn = snakemake.input.powerplants
|
2018-08-10 09:21:55 +00:00
|
|
|
ppl = pd.read_csv(ppl_fn, index_col=0, dtype={'bus': 'str'})
|
|
|
|
return ppl.loc[ppl.bus.isin(n.buses.index)]
|
2018-01-29 21:28:33 +00:00
|
|
|
|
|
|
|
# ## 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)
|
2018-09-03 21:05:33 +00:00
|
|
|
opsd_load = timeseries_opsd(slice(*n.snapshots[[0,-1]].year.astype(str)),
|
|
|
|
snakemake.input.opsd_load)
|
2018-08-03 09:53:14 +00:00
|
|
|
|
2018-08-06 19:38:13 +00:00
|
|
|
nuts3 = gpd.read_file(snakemake.input.nuts3_shapes).set_index('index')
|
2018-08-03 09:53:14 +00:00
|
|
|
|
|
|
|
def normed(x): return x.divide(x.sum())
|
|
|
|
|
|
|
|
def upsample(cntry, group):
|
|
|
|
l = opsd_load[cntry]
|
|
|
|
if len(group) == 1:
|
|
|
|
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()
|
2018-08-06 19:38:13 +00:00
|
|
|
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)
|
2018-08-03 09:53:14 +00:00
|
|
|
|
|
|
|
# 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)
|
|
|
|
|
|
|
|
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)
|
2018-01-29 21:28:33 +00:00
|
|
|
|
|
|
|
### Set line costs
|
|
|
|
|
2018-10-22 21:22:34 +00:00
|
|
|
def update_transmission_costs(n, costs, length_factor=1.0, simple_hvdc_costs=False):
|
2018-05-18 15:09:18 +00:00
|
|
|
n.lines['capital_cost'] = (n.lines['length'] * length_factor *
|
2018-01-29 21:28:33 +00:00
|
|
|
costs.at['HVAC overhead', 'capital_cost'])
|
|
|
|
|
|
|
|
dc_b = n.links.carrier == 'DC'
|
2018-10-22 21:22:34 +00:00
|
|
|
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'])
|
|
|
|
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'])
|
2018-01-29 21:28:33 +00:00
|
|
|
|
|
|
|
# ### Generators
|
|
|
|
|
|
|
|
def attach_wind_and_solar(n, costs):
|
|
|
|
for tech in snakemake.config['renewable']:
|
|
|
|
if tech == 'hydro': continue
|
|
|
|
|
|
|
|
n.add("Carrier", name=tech)
|
|
|
|
with xr.open_dataset(getattr(snakemake.input, 'profile_' + tech)) as ds:
|
2018-10-26 19:32:04 +00:00
|
|
|
capital_cost = costs.at[tech, 'capital_cost']
|
|
|
|
if tech + "-grid" in costs.index:
|
|
|
|
if tech + "-grid-perlength" in costs.index:
|
|
|
|
grid_cost = costs.at[tech + "-grid", "capital_cost"] + costs.at[tech + "-grid-perlength", 'capital_cost'] * ds['average_distance'].to_pandas()
|
|
|
|
logger.info("Added connection cost of {:0.0f}-{:0.0f} Eur/MW/a to {}".format(grid_cost.min(), grid_cost.max(), tech))
|
|
|
|
else:
|
|
|
|
grid_cost = costs.at[tech + "-grid", "capital_cost"]
|
|
|
|
logger.info("Added connection cost of {:0.0f} Eur/MW/a to {}".format(grid_cost, tech))
|
|
|
|
capital_cost = capital_cost + grid_cost
|
2018-01-29 21:28:33 +00:00
|
|
|
|
|
|
|
n.madd("Generator", ds.indexes['bus'], ' ' + tech,
|
|
|
|
bus=ds.indexes['bus'],
|
|
|
|
carrier=tech,
|
|
|
|
p_nom_extendable=True,
|
|
|
|
p_nom_max=ds['p_nom_max'].to_pandas(),
|
|
|
|
weight=ds['weight'].to_pandas(),
|
|
|
|
marginal_cost=costs.at[tech, 'marginal_cost'],
|
2018-10-26 19:32:04 +00:00
|
|
|
capital_cost=capital_cost,
|
2018-01-29 21:28:33 +00:00
|
|
|
efficiency=costs.at[tech, 'efficiency'],
|
|
|
|
p_max_pu=ds['profile'].transpose('time', 'bus').to_pandas())
|
|
|
|
|
|
|
|
|
|
|
|
# # Generators
|
|
|
|
|
|
|
|
|
2018-07-10 14:29:11 +00:00
|
|
|
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'}
|
|
|
|
|
|
|
|
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=costs.at[tech, 'capital_cost'])
|
2018-01-29 21:28:33 +00:00
|
|
|
|
|
|
|
|
|
|
|
def attach_hydro(n, costs, ppl):
|
|
|
|
c = snakemake.config['renewable']['hydro']
|
|
|
|
carriers = c.get('carriers', ['ror', 'PHS', 'hydro'])
|
|
|
|
|
|
|
|
_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')
|
|
|
|
)
|
|
|
|
|
2018-11-12 20:43:33 +00:00
|
|
|
country = ppl['bus'].map(n.buses.country)
|
2018-01-29 21:28:33 +00:00
|
|
|
# distribute by p_nom in each country
|
|
|
|
dist_key = ppl.loc[ppl.has_inflow, 'p_nom'].groupby(country).transform(normed)
|
|
|
|
|
|
|
|
with xr.open_dataarray(snakemake.input.profile_hydro) as inflow:
|
|
|
|
inflow_t = (
|
2018-11-12 20:43:33 +00:00
|
|
|
inflow.sel(countries=country.loc[ppl.has_inflow].values)
|
2018-01-29 21:28:33 +00:00
|
|
|
.rename({'countries': 'name'})
|
|
|
|
.assign_coords(name=ppl.index[ppl.has_inflow])
|
|
|
|
.transpose('time', 'name')
|
|
|
|
.to_pandas()
|
2018-07-25 21:57:58 +00:00
|
|
|
.multiply(dist_key, axis=1)
|
2018-01-29 21:28:33 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
if 'ror' in carriers:
|
|
|
|
ror = ppl.loc[ppl.has_inflow & ~ ppl.has_store]
|
|
|
|
n.madd("Generator", ror.index,
|
|
|
|
carrier='ror',
|
|
|
|
bus=ror['bus'],
|
2018-02-01 11:05:25 +00:00
|
|
|
p_nom=ror['p_nom'],
|
2018-01-29 21:28:33 +00:00
|
|
|
efficiency=costs.at['ror', 'efficiency'],
|
|
|
|
capital_cost=costs.at['ror', 'capital_cost'],
|
|
|
|
weight=ror['p_nom'],
|
|
|
|
p_max_pu=(inflow_t.loc[:, ror.index]
|
2018-07-25 21:57:58 +00:00
|
|
|
.divide(ror['p_nom'], axis=1)
|
2018-01-29 21:28:33 +00:00
|
|
|
.where(lambda df: df<=1., other=1.)))
|
|
|
|
|
|
|
|
if 'PHS' in carriers:
|
|
|
|
phs = ppl.loc[ppl.has_store & ppl.has_pump]
|
|
|
|
n.madd('StorageUnit', phs.index,
|
|
|
|
carrier='PHS',
|
|
|
|
bus=phs['bus'],
|
2018-02-01 11:05:25 +00:00
|
|
|
p_nom=phs['p_nom'],
|
2018-01-29 21:28:33 +00:00
|
|
|
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 'hydro' in carriers:
|
2018-11-12 20:43:33 +00:00
|
|
|
hydro = ppl.loc[ppl.has_store & ~ ppl.has_pump & ppl.has_inflow].join(country.rename('country'))
|
2018-01-29 21:28:33 +00:00
|
|
|
|
|
|
|
hydro_max_hours = c.get('hydro_max_hours')
|
2018-11-12 20:43:33 +00:00
|
|
|
if hydro_max_hours == 'energy_capacity_totals_by_country':
|
2018-08-06 19:38:13 +00:00
|
|
|
hydro_e_country = pd.read_csv(snakemake.input.hydro_capacities, index_col=0)["E_store[TWh]"].clip(lower=0.2)*1e6
|
2018-11-12 20:43:33 +00:00
|
|
|
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
|
|
|
|
|
|
|
|
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)))
|
|
|
|
|
|
|
|
hydro_max_hours = hydro['country'].map(estim_hydro_max_hours).fillna(6)
|
2018-01-29 21:28:33 +00:00
|
|
|
|
|
|
|
n.madd('StorageUnit', hydro.index, carrier='hydro',
|
|
|
|
bus=hydro['bus'],
|
2018-02-01 11:05:25 +00:00
|
|
|
p_nom=hydro['p_nom'],
|
2018-01-29 21:28:33 +00:00
|
|
|
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):
|
|
|
|
elec_opts = snakemake.config['electricity']
|
|
|
|
carriers = list(elec_opts['extendable_carriers']['Generator'])
|
|
|
|
assert carriers == ['OCGT'], "Only OCGT plants as extendable generators allowed for now"
|
|
|
|
|
|
|
|
_add_missing_carriers_from_costs(n, costs, carriers)
|
|
|
|
|
|
|
|
if 'OCGT' in carriers:
|
2018-10-09 13:44:46 +00:00
|
|
|
ocgt = ppl.loc[ppl.Fueltype.isin(('OCGT', 'CCGT'))].groupby('bus', as_index=False).first()
|
2018-01-29 21:28:33 +00:00
|
|
|
n.madd('Generator', ocgt.index,
|
|
|
|
bus=ocgt['bus'],
|
|
|
|
carrier='OCGT',
|
|
|
|
p_nom_extendable=True,
|
|
|
|
p_nom=0.,
|
|
|
|
capital_cost=costs.at['OCGT', 'capital_cost'],
|
|
|
|
marginal_cost=costs.at['OCGT', 'marginal_cost'],
|
|
|
|
efficiency=costs.at['OCGT', 'efficiency'])
|
|
|
|
|
|
|
|
|
|
|
|
def attach_storage(n, costs):
|
|
|
|
elec_opts = snakemake.config['electricity']
|
|
|
|
carriers = elec_opts['extendable_carriers']['StorageUnit']
|
|
|
|
max_hours = elec_opts['max_hours']
|
|
|
|
|
|
|
|
_add_missing_carriers_from_costs(n, costs, carriers)
|
|
|
|
|
|
|
|
buses_i = n.buses.index[n.buses.substation_lv]
|
|
|
|
|
|
|
|
for carrier in carriers:
|
|
|
|
n.madd("StorageUnit", buses_i, ' ' + carrier,
|
|
|
|
bus=buses_i,
|
|
|
|
carrier=carrier,
|
|
|
|
p_nom_extendable=True,
|
|
|
|
capital_cost=costs.at[carrier, 'capital_cost'],
|
|
|
|
marginal_cost=costs.at[carrier, 'marginal_cost'],
|
|
|
|
efficiency_store=costs.at[carrier, 'efficiency'],
|
|
|
|
efficiency_dispatch=costs.at[carrier, 'efficiency'],
|
|
|
|
max_hours=max_hours[carrier],
|
|
|
|
cyclic_state_of_charge=True)
|
|
|
|
|
|
|
|
## Implementing them separately will come later!
|
|
|
|
##
|
|
|
|
# if 'H2' in carriers:
|
|
|
|
# h2_buses = n.madd("Bus", buses + " H2", carrier="H2")
|
|
|
|
|
|
|
|
# n.madd("Link", h2_buses + " Electrolysis",
|
|
|
|
# bus1=h2_buses,
|
|
|
|
# bus0=buses,
|
|
|
|
# p_nom_extendable=True,
|
|
|
|
# efficiency=costs.at["electrolysis", "efficiency"],
|
|
|
|
# capital_cost=costs.at["electrolysis", "capital_cost"])
|
|
|
|
|
|
|
|
# n.madd("Link", h2_buses + " Fuel Cell",
|
|
|
|
# bus0=h2_buses,
|
|
|
|
# bus1=buses,
|
|
|
|
# p_nom_extendable=True,
|
|
|
|
# efficiency=costs.at["fuel cell", "efficiency"],
|
|
|
|
# #NB: fixed cost is per MWel
|
|
|
|
# capital_cost=costs.at["fuel cell", "capital_cost"] * costs.at["fuel cell", "efficiency"])
|
|
|
|
|
|
|
|
# n.madd("Store", h2_buses,
|
|
|
|
# bus=h2_buses,
|
|
|
|
# e_nom_extendable=True,
|
|
|
|
# e_cyclic=True,
|
|
|
|
# capital_cost=costs.at["hydrogen storage", "capital_cost"])
|
|
|
|
|
|
|
|
# if 'battery' in carriers:
|
|
|
|
# b_buses = n.madd("Bus", buses + " battery", carrier="battery")
|
|
|
|
|
|
|
|
# network.madd("Store", b_buses,
|
|
|
|
# bus=b_buses,
|
|
|
|
# e_cyclic=True,
|
|
|
|
# e_nom_extendable=True,
|
|
|
|
# capital_cost=costs.at['battery storage', 'capital_cost'])
|
|
|
|
|
|
|
|
# network.madd("Link", b_buses + " charger",
|
|
|
|
# bus0=buses,
|
|
|
|
# bus1=b_buses,
|
|
|
|
# efficiency=costs.at['battery inverter', 'efficiency']**0.5,
|
|
|
|
# capital_cost=costs.at['battery inverter', 'capital_cost'],
|
|
|
|
# p_nom_extendable=True)
|
|
|
|
|
|
|
|
# network.madd("Link",
|
|
|
|
# nodes + " battery discharger",
|
|
|
|
# bus0=nodes + " battery",
|
|
|
|
# bus1=nodes,
|
|
|
|
# efficiency=costs.at['battery inverter','efficiency']**0.5,
|
|
|
|
# marginal_cost=options['marginal_cost_storage'],
|
|
|
|
# p_nom_extendable=True)
|
|
|
|
|
|
|
|
|
|
|
|
def add_co2limit(n, Nyears=1.):
|
|
|
|
n.add("GlobalConstraint", "CO2Limit",
|
|
|
|
carrier_attribute="co2_emissions", sense="<=",
|
|
|
|
constant=snakemake.config['electricity']['co2limit'] * Nyears)
|
|
|
|
|
|
|
|
def add_emission_prices(n, emission_prices=None, exclude_co2=False):
|
|
|
|
if emission_prices is None:
|
|
|
|
emission_prices = snakemake.config['costs']['emission_prices']
|
|
|
|
if exclude_co2: emission_prices.pop('co2')
|
|
|
|
ep = (pd.Series(emission_prices).rename(lambda x: x+'_emissions') * n.carriers).sum(axis=1)
|
|
|
|
n.generators['marginal_cost'] += n.generators.carrier.map(ep)
|
|
|
|
n.storage_units['marginal_cost'] += n.storage_units.carrier.map(ep)
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
# Detect running outside of snakemake and mock snakemake for testing
|
|
|
|
if 'snakemake' not in globals():
|
2018-02-01 11:05:25 +00:00
|
|
|
from vresutils.snakemake import MockSnakemake, Dict
|
|
|
|
|
2018-02-19 09:09:39 +00:00
|
|
|
snakemake = MockSnakemake(output=['networks/elec.nc'])
|
2018-02-01 11:05:25 +00:00
|
|
|
snakemake.input = snakemake.expand(
|
|
|
|
Dict(base_network='networks/base.nc',
|
|
|
|
tech_costs='data/costs/costs.csv',
|
|
|
|
regions="resources/regions_onshore.geojson",
|
2018-02-19 09:09:39 +00:00
|
|
|
powerplants="resources/powerplants.csv",
|
2018-08-03 09:53:14 +00:00
|
|
|
hydro_capacities='data/hydro_capacities.csv',
|
|
|
|
opsd_load='data/time_series_60min_singleindex_filtered.csv',
|
2018-02-01 11:05:25 +00:00
|
|
|
**{'profile_' + t: "resources/profile_" + t + ".nc"
|
|
|
|
for t in snakemake.config['renewable']})
|
|
|
|
)
|
|
|
|
|
|
|
|
logging.basicConfig(level=snakemake.config['logging_level'])
|
2018-01-29 21:28:33 +00:00
|
|
|
|
|
|
|
n = pypsa.Network(snakemake.input.base_network)
|
|
|
|
Nyears = n.snapshot_weightings.sum()/8760.
|
|
|
|
|
|
|
|
costs = load_costs(Nyears)
|
|
|
|
ppl = load_powerplants(n)
|
|
|
|
|
|
|
|
attach_load(n)
|
|
|
|
|
|
|
|
update_transmission_costs(n, costs)
|
2018-07-10 14:29:11 +00:00
|
|
|
attach_conventional_generators(n, costs, ppl)
|
2018-01-29 21:28:33 +00:00
|
|
|
|
|
|
|
attach_wind_and_solar(n, costs)
|
|
|
|
attach_hydro(n, costs, ppl)
|
|
|
|
attach_extendable_generators(n, costs, ppl)
|
|
|
|
attach_storage(n, costs)
|
|
|
|
|
|
|
|
n.export_to_netcdf(snakemake.output[0])
|