Split offshore wind turbines into AC and DC connections
This commit is contained in:
parent
bddea86b1e
commit
db073d8daa
16
config.yaml
16
config.yaml
@ -72,7 +72,7 @@ renewable:
|
|||||||
distance_grid_codes: [1, 2, 3, 4, 5, 6]
|
distance_grid_codes: [1, 2, 3, 4, 5, 6]
|
||||||
natura: true
|
natura: true
|
||||||
potential: conservative # or heuristic
|
potential: conservative # or heuristic
|
||||||
offwind:
|
offwind-ac:
|
||||||
cutout: europe-2013-era5
|
cutout: europe-2013-era5
|
||||||
resource:
|
resource:
|
||||||
method: wind
|
method: wind
|
||||||
@ -83,6 +83,20 @@ renewable:
|
|||||||
corine: [44, 255]
|
corine: [44, 255]
|
||||||
natura: true
|
natura: true
|
||||||
max_depth: 50
|
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
|
potential: conservative # or heuristic
|
||||||
solar:
|
solar:
|
||||||
cutout: europe-2013-sarah
|
cutout: europe-2013-sarah
|
||||||
|
@ -16,10 +16,15 @@ lignite,2030,lifetime,40,years,IEA2010
|
|||||||
geothermal,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
|
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
|
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,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-ac-station,2030,investment,250,EUR/kWel,DEA https://ens.dk/en/our-services/projections-and-models/technology-data
|
||||||
offwind-grid-perlength,2030,investment,0.97,EUR/kWel/km,Haertel 2017
|
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
|
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
|
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
|
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,investment,400,EUR/MW/km,Hagspiel
|
||||||
HVDC overhead,2030,lifetime,40,years,Hagspiel
|
HVDC overhead,2030,lifetime,40,years,Hagspiel
|
||||||
HVDC overhead,2030,FOM,2,%/year,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,lifetime,40,years,Hagspiel
|
||||||
HVDC submarine,2030,FOM,2,%/year,Hagspiel
|
HVDC submarine,2030,FOM,2,%/year,Hagspiel
|
||||||
HVDC inverter pair,2030,investment,150000,EUR/MW,Hagspiel
|
HVDC inverter pair,2030,investment,150000,EUR/MW,Hagspiel
|
||||||
|
|
@ -161,15 +161,18 @@ def attach_wind_and_solar(n, costs):
|
|||||||
|
|
||||||
n.add("Carrier", name=tech)
|
n.add("Carrier", name=tech)
|
||||||
with xr.open_dataset(getattr(snakemake.input, 'profile_' + tech)) as ds:
|
with xr.open_dataset(getattr(snakemake.input, 'profile_' + tech)) as ds:
|
||||||
capital_cost = costs.at[tech, 'capital_cost']
|
suptech = tech.split('-', 2)[0]
|
||||||
if tech + "-grid" in costs.index:
|
if suptech == 'offwind':
|
||||||
if tech + "-grid-perlength" in costs.index:
|
underwater_fraction = ds['underwater_fraction'].to_pandas()
|
||||||
grid_cost = costs.at[tech + "-grid", "capital_cost"] + costs.at[tech + "-grid-perlength", 'capital_cost'] * ds['average_distance'].to_pandas()
|
connection_cost = (snakemake.config['lines']['length_factor'] * 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))
|
(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:
|
else:
|
||||||
grid_cost = costs.at[tech + "-grid", "capital_cost"]
|
capital_cost = costs.at[tech, 'capital_cost']
|
||||||
logger.info("Added connection cost of {:0.0f} Eur/MW/a to {}".format(grid_cost, tech))
|
|
||||||
capital_cost = capital_cost + grid_cost
|
|
||||||
|
|
||||||
n.madd("Generator", ds.indexes['bus'], ' ' + tech,
|
n.madd("Generator", ds.indexes['bus'], ' ' + tech,
|
||||||
bus=ds.indexes['bus'],
|
bus=ds.indexes['bus'],
|
||||||
@ -177,9 +180,9 @@ def attach_wind_and_solar(n, costs):
|
|||||||
p_nom_extendable=True,
|
p_nom_extendable=True,
|
||||||
p_nom_max=ds['p_nom_max'].to_pandas(),
|
p_nom_max=ds['p_nom_max'].to_pandas(),
|
||||||
weight=ds['weight'].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,
|
capital_cost=capital_cost,
|
||||||
efficiency=costs.at[tech, 'efficiency'],
|
efficiency=costs.at[suptech, 'efficiency'],
|
||||||
p_max_pu=ds['profile'].transpose('time', 'bus').to_pandas())
|
p_max_pu=ds['profile'].transpose('time', 'bus').to_pandas())
|
||||||
|
|
||||||
|
|
||||||
|
@ -63,22 +63,51 @@ def simplify_network_to_380(n):
|
|||||||
|
|
||||||
return n, trafo_map
|
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,
|
costs = load_costs(n.snapshot_weightings.sum() / 8760, snakemake.input.tech_costs,
|
||||||
snakemake.config['costs'], snakemake.config['electricity'])
|
snakemake.config['costs'], snakemake.config['electricity'])
|
||||||
|
|
||||||
|
connection_costs_per_link = {}
|
||||||
|
|
||||||
for tech in snakemake.config['renewable']:
|
for tech in snakemake.config['renewable']:
|
||||||
if tech + "-grid-perlength" in costs.index:
|
if tech.startswith('offwind'):
|
||||||
cost_perlength = costs.at[tech + "-grid-perlength", "capital_cost"]
|
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
|
tech_b = n.generators.carrier == tech
|
||||||
generator_distance = n.generators.loc[tech_b, "bus"].map(distance).loc[lambda s: s>0]
|
costs = n.generators.loc[tech_b, "bus"].map(connection_costs_to_bus[tech]).loc[lambda s: s>0]
|
||||||
if not generator_distance.empty:
|
if not costs.empty:
|
||||||
n.generators.loc[generator_distance.index, "capital_cost"] += cost_perlength * generator_distance
|
n.generators.loc[costs.index, "capital_cost"] += costs
|
||||||
logger.info("Displacing generator(s) {}; capital_cost is adjusted accordingly"
|
logger.info("Displacing {} generator(s) and adding connection costs {} to capital_costs"
|
||||||
.format(", ".join("`{}` by {:.0f}km".format(b, d) for b, d in generator_distance.iteritems())))
|
.format(tech, ", ".join("of {:.0f} Eur/MW to `{}`".format(d, b) for b, d in costs.iteritems())))
|
||||||
|
|
||||||
|
def _aggregate_and_move_components(n, busmap, connection_costs_to_bus, aggregate_one_ports={"Load", "StorageUnit"}):
|
||||||
def _aggregate_and_move_components(n, busmap, distance, aggregate_one_ports={"Load", "StorageUnit"}):
|
|
||||||
def replace_components(n, c, df, pnl):
|
def replace_components(n, c, df, pnl):
|
||||||
n.mremove(c, n.df(c).index)
|
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:
|
if not df.empty:
|
||||||
import_series_from_dataframe(n, df, c, attr)
|
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)
|
generators, generators_pnl = aggregategenerators(n, busmap)
|
||||||
replace_components(n, "Generator", generators, generators_pnl)
|
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)
|
df = n.df(c)
|
||||||
n.mremove(c, df.index[df.bus0.isin(buses_to_del) | df.bus1.isin(buses_to_del)])
|
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):
|
def simplify_links(n):
|
||||||
## Complex multi-node links are folded into end-points
|
## Complex multi-node links are folded into end-points
|
||||||
logger.info("Simplifying connected link components")
|
logger.info("Simplifying connected link components")
|
||||||
@ -155,8 +174,9 @@ def simplify_links(n):
|
|||||||
seen.add(u)
|
seen.add(u)
|
||||||
|
|
||||||
busmap = n.buses.index.to_series()
|
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:
|
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']],
|
m = sp.spatial.distance_matrix(n.buses.loc[b, ['x', 'y']],
|
||||||
n.buses.loc[buses[1:-1], ['x', 'y']])
|
n.buses.loc[buses[1:-1], ['x', 'y']])
|
||||||
busmap.loc[buses] = b[np.r_[0, m.argmin(axis=0), 1]]
|
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, [])]
|
all_links = [i for _, i in sum(links, [])]
|
||||||
|
|
||||||
@ -200,7 +220,7 @@ def simplify_links(n):
|
|||||||
|
|
||||||
logger.debug("Collecting all components using the busmap")
|
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
|
return n, busmap
|
||||||
|
|
||||||
def remove_stubs(n):
|
def remove_stubs(n):
|
||||||
@ -208,9 +228,9 @@ def remove_stubs(n):
|
|||||||
|
|
||||||
busmap = busmap_by_stubs(n) # ['country'])
|
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
|
return n, busmap
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user