plot_network: Add functionality to produce a network plot of each solution
This commit is contained in:
parent
540985164f
commit
0070b1c87c
12
Snakefile
12
Snakefile
@ -1,5 +1,7 @@
|
||||
configfile: "config.yaml"
|
||||
|
||||
COSTS="data/costs.csv"
|
||||
|
||||
wildcard_constraints:
|
||||
lv="[0-9\.]+|inf",
|
||||
simpl="[a-zA-Z0-9]*",
|
||||
@ -136,7 +138,7 @@ rule build_hydro_profile:
|
||||
rule add_electricity:
|
||||
input:
|
||||
base_network='networks/base.nc',
|
||||
tech_costs='data/costs.csv',
|
||||
tech_costs=COSTS,
|
||||
regions="resources/regions_onshore.geojson",
|
||||
powerplants='resources/powerplants.csv',
|
||||
hydro_capacities='data/bundle/hydro_capacities.csv',
|
||||
@ -195,7 +197,7 @@ rule cluster_network:
|
||||
# script: "scripts/add_sectors.py"
|
||||
|
||||
rule prepare_network:
|
||||
input: 'networks/{network}_s{simpl}_{clusters}.nc', tech_costs='data/costs.csv'
|
||||
input: 'networks/{network}_s{simpl}_{clusters}.nc', tech_costs=COSTS
|
||||
output: 'networks/{network}_s{simpl}_{clusters}_lv{lv}_{opts}.nc'
|
||||
threads: 1
|
||||
resources: mem=1000
|
||||
@ -248,10 +250,10 @@ rule solve_operations_network:
|
||||
rule plot_network:
|
||||
input:
|
||||
network="results/networks/{network}_s{simpl}_{clusters}_lv{lv}_{opts}.nc",
|
||||
costs='data/costs.csv'
|
||||
tech_costs=COSTS
|
||||
output:
|
||||
only_map="results/plots/{network}_s{simpl}_{clusters}_lv{lv}_{opts}_{attr}.pdf",
|
||||
ext="results/plots/{network}_s{simpl}_{clusters}_lv{lv}_{opts}_{attr}_ext.pdf"
|
||||
only_map="results/plots/{network}_s{simpl}_{clusters}_lv{lv}_{opts}_{attr}.{ext}",
|
||||
ext="results/plots/{network}_s{simpl}_{clusters}_lv{lv}_{opts}_{attr}_ext.{ext}"
|
||||
script: "scripts/plot_network.py"
|
||||
|
||||
# rule plot_costs:
|
||||
|
256
scripts/plot_network.py
Normal file
256
scripts/plot_network.py
Normal file
@ -0,0 +1,256 @@
|
||||
if 'snakemake' not in globals():
|
||||
from vresutils.snakemake import MockSnakemake, Dict
|
||||
from snakemake.rules import expand
|
||||
import yaml
|
||||
snakemake = Dict()
|
||||
snakemake = MockSnakemake(
|
||||
path='..',
|
||||
wildcards=dict(network='elec', simpl='', clusters='90', lv='1.25', opts='Co2L-3H', attr='p_nom', ext="pdf"),
|
||||
input=dict(network="results/networks/{network}_s{simpl}_{clusters}_lv{lv}_{opts}.nc",
|
||||
tech_costs="data/costs.csv"),
|
||||
output=dict(only_map="results/plots/{network}_s{simpl}_{clusters}_lv{lv}_{opts}_{attr}.{ext}",
|
||||
ext="results/plots/{network}_s{simpl}_{clusters}_lv{lv}_{opts}_{attr}_ext.{ext}")
|
||||
)
|
||||
|
||||
import pypsa
|
||||
|
||||
from _helpers import load_network, aggregate_p, aggregate_costs
|
||||
from vresutils import plot as vplot
|
||||
|
||||
import os
|
||||
import pypsa
|
||||
import pandas as pd
|
||||
import geopandas as gpd
|
||||
import numpy as np
|
||||
from itertools import product, chain
|
||||
from six.moves import map, zip
|
||||
from six import itervalues, iterkeys
|
||||
from collections import OrderedDict as odict
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib as mpl
|
||||
from matplotlib.patches import Circle, Ellipse
|
||||
from matplotlib.legend_handler import HandlerPatch
|
||||
import seaborn as sns
|
||||
to_rgba = mpl.colors.colorConverter.to_rgba
|
||||
|
||||
def make_handler_map_to_scale_circles_as_in(ax, dont_resize_actively=False):
|
||||
fig = ax.get_figure()
|
||||
def axes2pt():
|
||||
return np.diff(ax.transData.transform([(0,0), (1,1)]), axis=0)[0] * (72./fig.dpi)
|
||||
|
||||
ellipses = []
|
||||
if not dont_resize_actively:
|
||||
def update_width_height(event):
|
||||
dist = axes2pt()
|
||||
for e, radius in ellipses: e.width, e.height = 2. * radius * dist
|
||||
fig.canvas.mpl_connect('resize_event', update_width_height)
|
||||
ax.callbacks.connect('xlim_changed', update_width_height)
|
||||
ax.callbacks.connect('ylim_changed', update_width_height)
|
||||
|
||||
def legend_circle_handler(legend, orig_handle, xdescent, ydescent,
|
||||
width, height, fontsize):
|
||||
w, h = 2. * orig_handle.get_radius() * axes2pt()
|
||||
e = Ellipse(xy=(0.5*width-0.5*xdescent, 0.5*height-0.5*ydescent), width=w, height=w)
|
||||
ellipses.append((e, orig_handle.get_radius()))
|
||||
return e
|
||||
return {Circle: HandlerPatch(patch_func=legend_circle_handler)}
|
||||
|
||||
def make_legend_circles_for(sizes, scale=1.0, **kw):
|
||||
return [Circle((0,0), radius=(s/scale)**0.5, **kw) for s in sizes]
|
||||
|
||||
plt.style.use(['classic', 'seaborn-white',
|
||||
{'axes.grid': False, 'grid.linestyle': '--', 'grid.color': u'0.6',
|
||||
'hatch.color': 'white',
|
||||
'patch.linewidth': 0.5,
|
||||
'font.size': 12,
|
||||
'legend.fontsize': 'medium',
|
||||
'lines.linewidth': 1.5,
|
||||
'pdf.fonttype': 42,
|
||||
# 'font.family': 'Times New Roman'
|
||||
}])
|
||||
|
||||
opts = snakemake.config['plotting']
|
||||
map_figsize = opts['map']['figsize']
|
||||
map_boundaries = opts['map']['boundaries']
|
||||
|
||||
n = load_network(snakemake.input.network, snakemake.input.tech_costs, snakemake.config)
|
||||
|
||||
scenario_opts = snakemake.wildcards.opts.split('-')
|
||||
|
||||
## DATA
|
||||
line_colors = {'cur': "purple",
|
||||
'exp': to_rgba("red", 0.7)}
|
||||
tech_colors = opts['tech_colors']
|
||||
|
||||
if snakemake.wildcards.attr == 'p_nom':
|
||||
# bus_sizes = n.generators_t.p.sum().loc[n.generators.carrier == "load"].groupby(n.generators.bus).sum()
|
||||
bus_sizes = pd.concat((n.generators.query('carrier != "load"').groupby(['bus', 'carrier']).p_nom_opt.sum(),
|
||||
n.storage_units.groupby(['bus', 'carrier']).p_nom_opt.sum()))
|
||||
line_widths_exp = pd.concat(dict(Line=n.lines.s_nom_opt, Link=n.links.p_nom_opt))
|
||||
line_widths_cur = pd.concat(dict(Line=n.lines.s_nom_min, Link=n.links.p_nom_min))
|
||||
else:
|
||||
raise 'plotting of {} has not been implemented yet'.format(plot)
|
||||
|
||||
|
||||
line_colors_with_alpha = \
|
||||
pd.concat(dict(Line=(line_widths_cur['Line'] / n.lines.s_nom > 1e-3)
|
||||
.map({True: line_colors['cur'], False: to_rgba(line_colors['cur'], 0.)}),
|
||||
Link=(line_widths_cur['Link'] / n.links.p_nom > 1e-3)
|
||||
.map({True: line_colors['cur'], False: to_rgba(line_colors['cur'], 0.)})))
|
||||
|
||||
## FORMAT
|
||||
linewidth_factor = opts['map'][snakemake.wildcards.attr]['linewidth_factor']
|
||||
bus_size_factor = opts['map'][snakemake.wildcards.attr]['bus_size_factor']
|
||||
|
||||
## PLOT
|
||||
fig, ax = plt.subplots(figsize=map_figsize)
|
||||
n.plot(line_widths=line_widths_exp/linewidth_factor,
|
||||
line_colors=dict(Line=line_colors['exp'], Link=line_colors['exp']),
|
||||
bus_sizes=bus_sizes/bus_size_factor,
|
||||
bus_colors=tech_colors,
|
||||
boundaries=map_boundaries,
|
||||
basemap=True,
|
||||
ax=ax)
|
||||
n.plot(line_widths=line_widths_cur/linewidth_factor,
|
||||
line_colors=line_colors_with_alpha,
|
||||
bus_sizes=0,
|
||||
bus_colors=tech_colors,
|
||||
boundaries=map_boundaries,
|
||||
basemap=False,
|
||||
ax=ax)
|
||||
ax.set_aspect('equal')
|
||||
ax.axis('off')
|
||||
|
||||
# x1, y1, x2, y2 = map_boundaries
|
||||
# ax.set_xlim(x1, x2)
|
||||
# ax.set_ylim(y1, y2)
|
||||
|
||||
|
||||
# Rasterize basemap
|
||||
for c in ax.collections[:2]: c.set_rasterized(True)
|
||||
|
||||
# LEGEND
|
||||
handles = []
|
||||
labels = []
|
||||
|
||||
for s in (10, 1):
|
||||
handles.append(plt.Line2D([0],[0],color=line_colors['exp'],
|
||||
linewidth=s*1e3/linewidth_factor))
|
||||
labels.append("{} GW".format(s))
|
||||
l1 = l1_1 = ax.legend(handles, labels,
|
||||
loc="upper left", bbox_to_anchor=(0.24, 1.01),
|
||||
frameon=False,
|
||||
labelspacing=0.8, handletextpad=1.5,
|
||||
title='Transmission Exist./Exp. ')
|
||||
ax.add_artist(l1_1)
|
||||
|
||||
handles = []
|
||||
labels = []
|
||||
for s in (10, 5):
|
||||
handles.append(plt.Line2D([0],[0],color=line_colors['cur'],
|
||||
linewidth=s*1e3/linewidth_factor))
|
||||
labels.append("/")
|
||||
l1_2 = ax.legend(handles, labels,
|
||||
loc="upper left", bbox_to_anchor=(0.26, 1.01),
|
||||
frameon=False,
|
||||
labelspacing=0.8, handletextpad=0.5,
|
||||
title=' ')
|
||||
ax.add_artist(l1_2)
|
||||
|
||||
handles = make_legend_circles_for([10e3, 5e3, 1e3], scale=bus_size_factor, facecolor="w")
|
||||
labels = ["{} GW".format(s) for s in (10, 5, 3)]
|
||||
l2 = ax.legend(handles, labels,
|
||||
loc="upper left", bbox_to_anchor=(0.01, 1.01),
|
||||
frameon=False, labelspacing=1.0,
|
||||
title='Generation',
|
||||
handler_map=make_handler_map_to_scale_circles_as_in(ax))
|
||||
ax.add_artist(l2)
|
||||
|
||||
techs = (bus_sizes.index.levels[1]) & pd.Index(opts['vre_techs'] + opts['conv_techs'] + opts['storage_techs'])
|
||||
handles = []
|
||||
labels = []
|
||||
for t in techs:
|
||||
handles.append(plt.Line2D([0], [0], color=tech_colors[t], marker='o', markersize=8, linewidth=0))
|
||||
labels.append(opts['nice_names'].get(t, t))
|
||||
l3 = ax.legend(handles, labels, loc="upper center", bbox_to_anchor=(0.5, -0.), # bbox_to_anchor=(0.72, -0.05),
|
||||
handletextpad=0., columnspacing=0.5, ncol=4, title='Technology')
|
||||
|
||||
|
||||
fig.savefig(snakemake.output.only_map, dpi=150,
|
||||
bbox_inches='tight', bbox_extra_artists=[l1,l2,l3])
|
||||
|
||||
#n = load_network(snakemake.input.network, opts, combine_hydro_ps=False)
|
||||
|
||||
## Add total energy p
|
||||
|
||||
ax1 = ax = fig.add_axes([-0.115, 0.625, 0.2, 0.2])
|
||||
ax.set_title('Energy per technology', fontdict=dict(fontsize="medium"))
|
||||
|
||||
e_primary = aggregate_p(n).drop('load').loc[lambda s: s>0]
|
||||
|
||||
patches, texts, autotexts = ax.pie(e_primary,
|
||||
startangle=90,
|
||||
labels = e_primary.rename(opts['nice_names_n']).index,
|
||||
autopct='%.0f%%',
|
||||
shadow=False,
|
||||
colors = [tech_colors[tech] for tech in e_primary.index])
|
||||
for t1, t2, i in zip(texts, autotexts, e_primary.index):
|
||||
if e_primary.at[i] < 0.04 * e_primary.sum():
|
||||
t1.remove()
|
||||
t2.remove()
|
||||
|
||||
## Add average system cost bar plot
|
||||
# ax2 = ax = fig.add_axes([-0.1, 0.2, 0.1, 0.33])
|
||||
# ax2 = ax = fig.add_axes([-0.1, 0.15, 0.1, 0.37])
|
||||
ax2 = ax = fig.add_axes([-0.075, 0.1, 0.1, 0.45])
|
||||
total_load = (n.snapshot_weightings * n.loads_t.p.sum(axis=1)).sum()
|
||||
|
||||
def split_costs(n):
|
||||
costs = aggregate_costs(n).reset_index(level=0, drop=True)
|
||||
costs_ex = aggregate_costs(n, existing_only=True).reset_index(level=0, drop=True)
|
||||
return (costs['capital'].add(costs['marginal'], fill_value=0.),
|
||||
costs_ex['capital'], costs['capital'] - costs_ex['capital'], costs['marginal'])
|
||||
|
||||
costs, costs_cap_ex, costs_cap_new, costs_marg = split_costs(n)
|
||||
|
||||
costs_graph = pd.DataFrame(dict(a=costs.drop('load')),
|
||||
index=['AC-AC', 'AC line', 'onwind', 'offwind', 'solar', 'OCGT', 'battery', 'H2']).dropna()
|
||||
bottom = np.array([0., 0.])
|
||||
texts = []
|
||||
|
||||
for i,ind in enumerate(costs_graph.index):
|
||||
data = np.asarray(costs_graph.loc[ind])/total_load
|
||||
ax.bar([0.5], data, bottom=bottom, color=tech_colors[ind], width=0.7, zorder=-1)
|
||||
bottom_sub = bottom
|
||||
bottom = bottom+data
|
||||
|
||||
if ind in opts['conv_techs'] + ['AC line']:
|
||||
for c in [costs_cap_ex, costs_marg]:
|
||||
if ind in c:
|
||||
data_sub = np.asarray([c.loc[ind]])/total_load
|
||||
ax.bar([0.5], data_sub, linewidth=0,
|
||||
bottom=bottom_sub, color=tech_colors[ind],
|
||||
width=0.7, zorder=-1, alpha=0.8)
|
||||
bottom_sub += data_sub
|
||||
|
||||
if abs(data[-1]) < 5:
|
||||
continue
|
||||
|
||||
text = ax.text(1.1,(bottom-0.5*data)[-1]-3,opts['nice_names_n'].get(ind,ind))
|
||||
texts.append(text)
|
||||
|
||||
ax.set_ylabel("Average system cost [Eur/MWh]")
|
||||
ax.set_ylim([0,80]) # opts['costs_max']])
|
||||
ax.set_xlim([0,1])
|
||||
#ax.set_xticks([0.5])
|
||||
ax.set_xticklabels([]) #["w/o\nEp", "w/\nEp"])
|
||||
ax.grid(True, axis="y", color='k', linestyle='dotted')
|
||||
|
||||
#fig.tight_layout()
|
||||
|
||||
fig.suptitle('Expansion to {lv} x today\'s line volume at {clusters} clusters'
|
||||
.format(lv=snakemake.wildcards.lv, clusters=snakemake.wildcards.clusters))
|
||||
|
||||
fig.savefig(snakemake.output.ext, transparent=True,
|
||||
bbox_inches='tight', bbox_extra_artists=[l1, l2, l3, ax1, ax2])
|
Loading…
Reference in New Issue
Block a user