Split offshore wind turbines into AC and DC connections

This commit is contained in:
Jonas Hoersch 2018-12-19 10:30:25 +01:00
parent bddea86b1e
commit db073d8daa
4 changed files with 85 additions and 43 deletions

View File

@ -72,7 +72,7 @@ renewable:
distance_grid_codes: [1, 2, 3, 4, 5, 6]
natura: true
potential: conservative # or heuristic
offwind:
offwind-ac:
cutout: europe-2013-era5
resource:
method: wind
@ -83,6 +83,20 @@ renewable:
corine: [44, 255]
natura: true
max_depth: 50
max_shore_distance: 80000
potential: conservative # or heuristic
offwind-dc:
cutout: europe-2013-era5
resource:
method: wind
turbine: NREL_ReferenceTurbine_5MW_offshore
# ScholzPhd Tab 4.3.1: 10MW/km^2
capacity_per_sqkm: 3
# correction_factor: 0.93
corine: [44, 255]
natura: true
max_depth: 50
min_shore_distance: 80000
potential: conservative # or heuristic
solar:
cutout: europe-2013-sarah

View File

@ -16,10 +16,15 @@ lignite,2030,lifetime,40,years,IEA2010
geothermal,2030,lifetime,40,years,IEA2010
biomass,2030,lifetime,30,years,ECF2010 in DIW DataDoc http://hdl.handle.net/10419/80348
oil,2030,lifetime,30,years,ECF2010 in DIW DataDoc http://hdl.handle.net/10419/80348
onwind,2030,investment,910,EUR/kWel,DEA https://ens.dk/en/our-services/projections-and-models/technology-data
onwind,2030,investment,1110,EUR/kWel,DEA https://ens.dk/en/our-services/projections-and-models/technology-data
onwind-landcosts,2030,investment,200,EUR/kWel,Land costs and compensation payments conservatively estimated based on DEA https://ens.dk/en/our-services/projections-and-models/technology-data
offwind,2030,investment,1640,EUR/kWel,DEA https://ens.dk/en/our-services/projections-and-models/technology-data
offwind-grid,2030,investment,255,EUR/kWel,Haertel 2017; assuming one onshore and one offshore node
offwind-grid-perlength,2030,investment,0.97,EUR/kWel/km,Haertel 2017
offwind-ac-station,2030,investment,250,EUR/kWel,DEA https://ens.dk/en/our-services/projections-and-models/technology-data
offwind-ac-connection-submarine,2030,investment,2685,EUR/MW/km,DEA https://ens.dk/en/our-services/projections-and-models/technology-data
offwind-ac-connection-underground,2030,investment,1342,EUR/MW/km,DEA https://ens.dk/en/our-services/projections-and-models/technology-data
offwind-dc-station,2030,investment,400,EUR/kWel,Haertel 2017; assuming one onshore and one offshore node + 13% learning reduction
offwind-dc-connection-submarine,2030,investment,2000,EUR/MW/km,DTU report based on Fig 34 of https://ec.europa.eu/energy/sites/ener/files/documents/2014_nsog_report.pdf
offwind-dc-connection-underground,2030,investment,1000,EUR/MW/km,Haertel 2017; average + 13% learning reduction
solar,2030,investment,600,EUR/kWel,DIW DataDoc http://hdl.handle.net/10419/80348
biomass,2030,investment,2209,EUR/kWel,DIW DataDoc http://hdl.handle.net/10419/80348
geothermal,2030,investment,3392,EUR/kWel,DIW DataDoc http://hdl.handle.net/10419/80348
@ -177,7 +182,7 @@ HVAC overhead,2030,FOM,2,%/year,Hagspiel
HVDC overhead,2030,investment,400,EUR/MW/km,Hagspiel
HVDC overhead,2030,lifetime,40,years,Hagspiel
HVDC overhead,2030,FOM,2,%/year,Hagspiel
HVDC submarine,2030,investment,2000,EUR/MW/km,Own analysis of European submarine HVDC projects since 2000
HVDC submarine,2030,investment,2000,EUR/MW/km,DTU report based on Fig 34 of https://ec.europa.eu/energy/sites/ener/files/documents/2014_nsog_report.pdf
HVDC submarine,2030,lifetime,40,years,Hagspiel
HVDC submarine,2030,FOM,2,%/year,Hagspiel
HVDC inverter pair,2030,investment,150000,EUR/MW,Hagspiel

1 technology year parameter value unit source
16 geothermal 2030 lifetime 40 years IEA2010
17 biomass 2030 lifetime 30 years ECF2010 in DIW DataDoc http://hdl.handle.net/10419/80348
18 oil 2030 lifetime 30 years ECF2010 in DIW DataDoc http://hdl.handle.net/10419/80348
19 onwind 2030 investment 910 1110 EUR/kWel DEA https://ens.dk/en/our-services/projections-and-models/technology-data
20 onwind-landcosts 2030 investment 200 EUR/kWel Land costs and compensation payments conservatively estimated based on DEA https://ens.dk/en/our-services/projections-and-models/technology-data
21 offwind 2030 investment 1640 EUR/kWel DEA https://ens.dk/en/our-services/projections-and-models/technology-data
22 offwind-grid offwind-ac-station 2030 investment 255 250 EUR/kWel Haertel 2017; assuming one onshore and one offshore node DEA https://ens.dk/en/our-services/projections-and-models/technology-data
23 offwind-grid-perlength offwind-ac-connection-submarine 2030 investment 0.97 2685 EUR/kWel/km EUR/MW/km Haertel 2017 DEA https://ens.dk/en/our-services/projections-and-models/technology-data
24 offwind-ac-connection-underground 2030 investment 1342 EUR/MW/km DEA https://ens.dk/en/our-services/projections-and-models/technology-data
25 offwind-dc-station 2030 investment 400 EUR/kWel Haertel 2017; assuming one onshore and one offshore node + 13% learning reduction
26 offwind-dc-connection-submarine 2030 investment 2000 EUR/MW/km DTU report based on Fig 34 of https://ec.europa.eu/energy/sites/ener/files/documents/2014_nsog_report.pdf
27 offwind-dc-connection-underground 2030 investment 1000 EUR/MW/km Haertel 2017; average + 13% learning reduction
28 solar 2030 investment 600 EUR/kWel DIW DataDoc http://hdl.handle.net/10419/80348
29 biomass 2030 investment 2209 EUR/kWel DIW DataDoc http://hdl.handle.net/10419/80348
30 geothermal 2030 investment 3392 EUR/kWel DIW DataDoc http://hdl.handle.net/10419/80348
182 HVDC overhead 2030 investment 400 EUR/MW/km Hagspiel
183 HVDC overhead 2030 lifetime 40 years Hagspiel
184 HVDC overhead 2030 FOM 2 %/year Hagspiel
185 HVDC submarine 2030 investment 2000 EUR/MW/km Own analysis of European submarine HVDC projects since 2000 DTU report based on Fig 34 of https://ec.europa.eu/energy/sites/ener/files/documents/2014_nsog_report.pdf
186 HVDC submarine 2030 lifetime 40 years Hagspiel
187 HVDC submarine 2030 FOM 2 %/year Hagspiel
188 HVDC inverter pair 2030 investment 150000 EUR/MW Hagspiel

View File

@ -161,15 +161,18 @@ def attach_wind_and_solar(n, costs):
n.add("Carrier", name=tech)
with xr.open_dataset(getattr(snakemake.input, 'profile_' + tech)) as ds:
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))
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))
elif suptech == 'onwind':
capital_cost = costs.at['onwind', 'capital_cost'] + costs.at['onwind-landcosts', 'capital_cost']
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
capital_cost = costs.at[tech, 'capital_cost']
n.madd("Generator", ds.indexes['bus'], ' ' + tech,
bus=ds.indexes['bus'],
@ -177,9 +180,9 @@ def attach_wind_and_solar(n, costs):
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'],
marginal_cost=costs.at[suptech, 'marginal_cost'],
capital_cost=capital_cost,
efficiency=costs.at[tech, 'efficiency'],
efficiency=costs.at[suptech, 'efficiency'],
p_max_pu=ds['profile'].transpose('time', 'bus').to_pandas())

View File

@ -63,22 +63,51 @@ def simplify_network_to_380(n):
return n, trafo_map
def _adjust_costs_using_distance(n, distance):
def _prepare_connection_costs_per_link(n):
costs = load_costs(n.snapshot_weightings.sum() / 8760, snakemake.input.tech_costs,
snakemake.config['costs'], snakemake.config['electricity'])
connection_costs_per_link = {}
for tech in snakemake.config['renewable']:
if tech + "-grid-perlength" in costs.index:
cost_perlength = costs.at[tech + "-grid-perlength", "capital_cost"]
if tech.startswith('offwind'):
connection_costs_per_link[tech] = (
n.links.length * snakemake.config['lines']['length_factor'] *
(n.links.underwater_fraction * costs.at[tech + '-connection-submarine', 'capital_cost'] +
(1. - n.links.underwater_fraction) * costs.at[tech + '-connection-underground', 'capital_cost'])
)
return connection_costs_per_link
def _compute_connection_costs_to_bus(n, busmap, connection_costs_per_link=None, buses=None):
if connection_costs_per_link is None:
connection_costs_per_link = _prepare_connection_costs_per_link(n)
if buses is None:
buses = busmap.index[busmap.index != busmap.values]
connection_costs_to_bus = pd.DataFrame(index=buses)
for tech in connection_costs_per_link:
adj = n.adjacency_matrix(weights=pd.concat(dict(Link=connection_costs_per_link[tech].reindex(n.links.index),
Line=pd.Series(0., n.lines.index))))
costs_between_buses = dijkstra(adj, directed=False, indices=n.buses.index.get_indexer(buses))
connection_costs_to_bus[tech] = costs_between_buses[np.arange(len(buses)),
n.buses.index.get_indexer(busmap.loc[buses])]
return connection_costs_to_bus
def _adjust_capital_costs_using_connection_costs(n, connection_costs_to_bus):
for tech in connection_costs_to_bus:
tech_b = n.generators.carrier == tech
generator_distance = n.generators.loc[tech_b, "bus"].map(distance).loc[lambda s: s>0]
if not generator_distance.empty:
n.generators.loc[generator_distance.index, "capital_cost"] += cost_perlength * generator_distance
logger.info("Displacing generator(s) {}; capital_cost is adjusted accordingly"
.format(", ".join("`{}` by {:.0f}km".format(b, d) for b, d in generator_distance.iteritems())))
costs = n.generators.loc[tech_b, "bus"].map(connection_costs_to_bus[tech]).loc[lambda s: s>0]
if not costs.empty:
n.generators.loc[costs.index, "capital_cost"] += costs
logger.info("Displacing {} generator(s) and adding connection costs {} to capital_costs"
.format(tech, ", ".join("of {:.0f} Eur/MW to `{}`".format(d, b) for b, d in costs.iteritems())))
def _aggregate_and_move_components(n, busmap, distance, aggregate_one_ports={"Load", "StorageUnit"}):
def _aggregate_and_move_components(n, busmap, connection_costs_to_bus, aggregate_one_ports={"Load", "StorageUnit"}):
def replace_components(n, c, df, pnl):
n.mremove(c, n.df(c).index)
@ -87,7 +116,7 @@ def _aggregate_and_move_components(n, busmap, distance, aggregate_one_ports={"Lo
if not df.empty:
import_series_from_dataframe(n, df, c, attr)
_adjust_costs_using_distance(n, distance)
_adjust_capital_costs_using_connection_costs(n, connection_costs_to_bus)
generators, generators_pnl = aggregategenerators(n, busmap)
replace_components(n, "Generator", generators, generators_pnl)
@ -102,16 +131,6 @@ def _aggregate_and_move_components(n, busmap, distance, aggregate_one_ports={"Lo
df = n.df(c)
n.mremove(c, df.index[df.bus0.isin(buses_to_del) | df.bus1.isin(buses_to_del)])
def _compute_distance(n, busmap, buses=None, adjacency_matrix=None):
if buses is None:
buses = busmap.index[busmap.index != busmap.values]
if adjacency_matrix is None:
adjacency_matrix = n.adjacency_matrix(weights=pd.concat(dict(Link=n.links.length, Line=pd.Series(0., n.lines.index))))
dist = dijkstra(adjacency_matrix, directed=False, indices=n.buses.index.get_indexer(buses))
return pd.Series(dist[np.arange(len(buses)), n.buses.index.get_indexer(busmap.loc[buses])], buses)
def simplify_links(n):
## Complex multi-node links are folded into end-points
logger.info("Simplifying connected link components")
@ -155,8 +174,9 @@ def simplify_links(n):
seen.add(u)
busmap = n.buses.index.to_series()
distance = pd.Series(0., n.buses.index)
adjacency_matrix = n.adjacency_matrix(weights=pd.concat(dict(Link=n.links.length, Line=pd.Series(0., n.lines.index))))
connection_costs_per_link = _prepare_connection_costs_per_link(n)
connection_costs_to_bus = pd.DataFrame(0., index=n.buses.index, columns=list(connection_costs_per_link))
for lbl in labels.value_counts().loc[lambda s: s > 2].index:
@ -169,7 +189,7 @@ def simplify_links(n):
m = sp.spatial.distance_matrix(n.buses.loc[b, ['x', 'y']],
n.buses.loc[buses[1:-1], ['x', 'y']])
busmap.loc[buses] = b[np.r_[0, m.argmin(axis=0), 1]]
distance.loc[buses] += _compute_distance(n, busmap, buses)
connection_costs_to_bus.loc[buses] += _compute_connection_costs_to_bus(n, busmap, connection_costs_per_link, buses)
all_links = [i for _, i in sum(links, [])]
@ -200,7 +220,7 @@ def simplify_links(n):
logger.debug("Collecting all components using the busmap")
_aggregate_and_move_components(n, busmap, distance)
_aggregate_and_move_components(n, busmap, connection_costs_to_bus)
return n, busmap
def remove_stubs(n):
@ -208,9 +228,9 @@ def remove_stubs(n):
busmap = busmap_by_stubs(n) # ['country'])
distance = _compute_distance(n, busmap)
connection_costs_to_bus = _compute_connection_costs_to_bus(n, busmap)
_aggregate_and_move_components(n, busmap, distance)
_aggregate_and_move_components(n, busmap, connection_costs_to_bus)
return n, busmap