plot_network: Split plotting into several functions to make sphinx happy

Also addresses part of issue #50: plot_map works again with cartopy.
This commit is contained in:
Jonas Hoersch 2019-08-12 12:42:33 +02:00
parent 89d89deec0
commit be8c9798c1

View File

@ -15,27 +15,6 @@ Description
""" """
# Dirty work-around so that sphinx can import this module and get the
# doc-string should be refactored in the style of the other scripts, ideally
# several functions for the different plots
if __name__ != "__main__":
import sys
sys.exit(0)
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 import pypsa
from _helpers import load_network, aggregate_p, aggregate_costs from _helpers import load_network, aggregate_p, aggregate_costs
@ -50,7 +29,9 @@ from itertools import product, chain
from six.moves import map, zip from six.moves import map, zip
from six import itervalues, iterkeys from six import itervalues, iterkeys
from collections import OrderedDict as odict from collections import OrderedDict as odict
import logging
import cartopy.crs as ccrs
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
import matplotlib as mpl import matplotlib as mpl
from matplotlib.patches import Circle, Ellipse from matplotlib.patches import Circle, Ellipse
@ -83,203 +64,242 @@ def make_handler_map_to_scale_circles_as_in(ax, dont_resize_actively=False):
def make_legend_circles_for(sizes, scale=1.0, **kw): def make_legend_circles_for(sizes, scale=1.0, **kw):
return [Circle((0,0), radius=(s/scale)**0.5, **kw) for s in sizes] return [Circle((0,0), radius=(s/scale)**0.5, **kw) for s in sizes]
plt.style.use(['classic', 'seaborn-white', def set_plot_style():
{'axes.grid': False, 'grid.linestyle': '--', 'grid.color': u'0.6', plt.style.use(['classic', 'seaborn-white',
'hatch.color': 'white', {'axes.grid': False, 'grid.linestyle': '--', 'grid.color': u'0.6',
'patch.linewidth': 0.5, 'hatch.color': 'white',
'font.size': 12, 'patch.linewidth': 0.5,
'legend.fontsize': 'medium', 'font.size': 12,
'lines.linewidth': 1.5, 'legend.fontsize': 'medium',
'pdf.fonttype': 42, 'lines.linewidth': 1.5,
# 'font.family': 'Times New Roman' 'pdf.fonttype': 42,
}]) # 'font.family': 'Times New Roman'
}])
opts = snakemake.config['plotting'] def plot_map(n, ax=None, attribute='p_nom', opts={}):
map_figsize = opts['map']['figsize'] if ax is None:
map_boundaries = opts['map']['boundaries'] ax = plt.gca()
n = load_network(snakemake.input.network, snakemake.input.tech_costs, snakemake.config) ## DATA
line_colors = {'cur': "purple",
'exp': to_rgba("red", 0.7)}
tech_colors = opts['tech_colors']
scenario_opts = snakemake.wildcards.opts.split('-') if attribute == 'p_nom':
# bus_sizes = n.generators_t.p.sum().loc[n.generators.carrier == "load"].groupby(n.generators.bus).sum()
## DATA bus_sizes = pd.concat((n.generators.query('carrier != "load"').groupby(['bus', 'carrier']).p_nom_opt.sum(),
line_colors = {'cur': "purple", n.storage_units.groupby(['bus', 'carrier']).p_nom_opt.sum()))
'exp': to_rgba("red", 0.7)} line_widths_exp = dict(Line=n.lines.s_nom_opt, Link=n.links.p_nom_opt)
tech_colors = opts['tech_colors'] line_widths_cur = dict(Line=n.lines.s_nom_min, Link=n.links.p_nom_min)
else:
if snakemake.wildcards.attr == 'p_nom': raise 'plotting of {} has not been implemented yet'.format(plot)
# 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 = dict(Line=n.lines.s_nom_opt, Link=n.links.p_nom_opt)
line_widths_cur = 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 = \ line_colors_with_alpha = \
dict(Line=(line_widths_cur['Line'] / n.lines.s_nom > 1e-3) dict(Line=(line_widths_cur['Line'] / n.lines.s_nom > 1e-3)
.map({True: line_colors['cur'], False: to_rgba(line_colors['cur'], 0.)}), .map({True: line_colors['cur'], False: to_rgba(line_colors['cur'], 0.)}),
Link=(line_widths_cur['Link'] / n.links.p_nom > 1e-3) Link=(line_widths_cur['Link'] / n.links.p_nom > 1e-3)
.map({True: line_colors['cur'], False: to_rgba(line_colors['cur'], 0.)})) .map({True: line_colors['cur'], False: to_rgba(line_colors['cur'], 0.)}))
## FORMAT ## FORMAT
linewidth_factor = opts['map'][snakemake.wildcards.attr]['linewidth_factor'] linewidth_factor = opts['map'][attribute]['linewidth_factor']
bus_size_factor = opts['map'][snakemake.wildcards.attr]['bus_size_factor'] bus_size_factor = opts['map'][attribute]['bus_size_factor']
## PLOT ## PLOT
fig, ax = plt.subplots(figsize=map_figsize) n.plot(line_widths=pd.concat(line_widths_exp)/linewidth_factor,
n.plot(line_widths=pd.concat(line_widths_exp)/linewidth_factor, line_colors=dict(Line=line_colors['exp'], Link=line_colors['exp']),
line_colors=dict(Line=line_colors['exp'], Link=line_colors['exp']), bus_sizes=bus_sizes/bus_size_factor,
bus_sizes=bus_sizes/bus_size_factor, bus_colors=tech_colors,
bus_colors=tech_colors, boundaries=map_boundaries,
boundaries=map_boundaries, geomap=True,
basemap=True, ax=ax)
ax=ax) n.plot(line_widths=pd.concat(line_widths_cur)/linewidth_factor,
n.plot(line_widths=pd.concat(line_widths_cur)/linewidth_factor, line_colors=pd.concat(line_colors_with_alpha),
line_colors=pd.concat(line_colors_with_alpha), bus_sizes=0,
bus_sizes=0, bus_colors=tech_colors,
bus_colors=tech_colors, boundaries=map_boundaries,
boundaries=map_boundaries, geomap=True, # TODO : Turn to False, after the release of PyPSA 0.14.2 (refer to https://github.com/PyPSA/PyPSA/issues/75)
basemap=False, ax=ax)
ax=ax) ax.set_aspect('equal')
ax.set_aspect('equal') ax.axis('off')
ax.axis('off')
# x1, y1, x2, y2 = map_boundaries # x1, y1, x2, y2 = map_boundaries
# ax.set_xlim(x1, x2) # ax.set_xlim(x1, x2)
# ax.set_ylim(y1, y2) # ax.set_ylim(y1, y2)
# Rasterize basemap # Rasterize basemap
for c in ax.collections[:2]: c.set_rasterized(True) # TODO : Check if this also works with cartopy
for c in ax.collections[:2]: c.set_rasterized(True)
# LEGEND # LEGEND
handles = [] handles = []
labels = [] labels = []
for s in (10, 1): for s in (10, 1):
handles.append(plt.Line2D([0],[0],color=line_colors['exp'], handles.append(plt.Line2D([0],[0],color=line_colors['exp'],
linewidth=s*1e3/linewidth_factor)) linewidth=s*1e3/linewidth_factor))
labels.append("{} GW".format(s)) labels.append("{} GW".format(s))
l1 = l1_1 = ax.legend(handles, labels, l1 = l1_1 = ax.legend(handles, labels,
loc="upper left", bbox_to_anchor=(0.24, 1.01), loc="upper left", bbox_to_anchor=(0.24, 1.01),
frameon=False, frameon=False,
labelspacing=0.8, handletextpad=1.5, labelspacing=0.8, handletextpad=1.5,
title='Transmission Exist./Exp. ') title='Transmission Exist./Exp. ')
ax.add_artist(l1_1) ax.add_artist(l1_1)
handles = [] handles = []
labels = [] labels = []
for s in (10, 5): for s in (10, 5):
handles.append(plt.Line2D([0],[0],color=line_colors['cur'], handles.append(plt.Line2D([0],[0],color=line_colors['cur'],
linewidth=s*1e3/linewidth_factor)) linewidth=s*1e3/linewidth_factor))
labels.append("/") labels.append("/")
l1_2 = ax.legend(handles, labels, l1_2 = ax.legend(handles, labels,
loc="upper left", bbox_to_anchor=(0.26, 1.01), loc="upper left", bbox_to_anchor=(0.26, 1.01),
frameon=False, frameon=False,
labelspacing=0.8, handletextpad=0.5, labelspacing=0.8, handletextpad=0.5,
title=' ') title=' ')
ax.add_artist(l1_2) ax.add_artist(l1_2)
handles = make_legend_circles_for([10e3, 5e3, 1e3], scale=bus_size_factor, facecolor="w") handles = make_legend_circles_for([10e3, 5e3, 1e3], scale=bus_size_factor, facecolor="w")
labels = ["{} GW".format(s) for s in (10, 5, 3)] labels = ["{} GW".format(s) for s in (10, 5, 3)]
l2 = ax.legend(handles, labels, l2 = ax.legend(handles, labels,
loc="upper left", bbox_to_anchor=(0.01, 1.01), loc="upper left", bbox_to_anchor=(0.01, 1.01),
frameon=False, labelspacing=1.0, frameon=False, labelspacing=1.0,
title='Generation', title='Generation',
handler_map=make_handler_map_to_scale_circles_as_in(ax)) handler_map=make_handler_map_to_scale_circles_as_in(ax))
ax.add_artist(l2) ax.add_artist(l2)
techs = (bus_sizes.index.levels[1]) & pd.Index(opts['vre_techs'] + opts['conv_techs'] + opts['storage_techs']) techs = (bus_sizes.index.levels[1]) & pd.Index(opts['vre_techs'] + opts['conv_techs'] + opts['storage_techs'])
handles = [] handles = []
labels = [] labels = []
for t in techs: for t in techs:
handles.append(plt.Line2D([0], [0], color=tech_colors[t], marker='o', markersize=8, linewidth=0)) handles.append(plt.Line2D([0], [0], color=tech_colors[t], marker='o', markersize=8, linewidth=0))
labels.append(opts['nice_names'].get(t, t)) 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), 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') handletextpad=0., columnspacing=0.5, ncol=4, title='Technology')
return fig
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) #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]) def plot_total_energy_pie(n, ax=None):
ax.set_title('Energy per technology', fontdict=dict(fontsize="medium")) """Add total energy pie plot"""
if ax is None:
ax = plt.gca()
e_primary = aggregate_p(n).drop('load', errors='ignore').loc[lambda s: s>0] ax.set_title('Energy per technology', fontdict=dict(fontsize="medium"))
patches, texts, autotexts = ax.pie(e_primary, e_primary = aggregate_p(n).drop('load', errors='ignore').loc[lambda s: s>0]
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 patches, texts, autotexts = ax.pie(e_primary,
# ax2 = ax = fig.add_axes([-0.1, 0.2, 0.1, 0.33]) startangle=90,
# ax2 = ax = fig.add_axes([-0.1, 0.15, 0.1, 0.37]) labels = e_primary.rename(opts['nice_names_n']).index,
ax2 = ax = fig.add_axes([-0.075, 0.1, 0.1, 0.45]) autopct='%.0f%%',
total_load = (n.snapshot_weightings * n.loads_t.p.sum(axis=1)).sum() 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()
def split_costs(n): def plot_total_cost_bar(n, ax=None):
costs = aggregate_costs(n).reset_index(level=0, drop=True) """Add average system cost bar plot"""
costs_ex = aggregate_costs(n, existing_only=True).reset_index(level=0, drop=True) if ax is None:
return (costs['capital'].add(costs['marginal'], fill_value=0.), ax = plt.gca()
costs_ex['capital'], costs['capital'] - costs_ex['capital'], costs['marginal'])
costs, costs_cap_ex, costs_cap_new, costs_marg = split_costs(n) total_load = (n.snapshot_weightings * n.loads_t.p.sum(axis=1)).sum()
costs_graph = pd.DataFrame(dict(a=costs.drop('load', errors='ignore')), def split_costs(n):
index=['AC-AC', 'AC line', 'onwind', 'offwind-ac', 'offwind-dc', 'solar', 'OCGT','CCGT', 'battery', 'H2']).dropna() costs = aggregate_costs(n).reset_index(level=0, drop=True)
bottom = np.array([0., 0.]) costs_ex = aggregate_costs(n, existing_only=True).reset_index(level=0, drop=True)
texts = [] return (costs['capital'].add(costs['marginal'], fill_value=0.),
costs_ex['capital'], costs['capital'] - costs_ex['capital'], costs['marginal'])
for i,ind in enumerate(costs_graph.index): costs, costs_cap_ex, costs_cap_new, costs_marg = split_costs(n)
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']: costs_graph = pd.DataFrame(dict(a=costs.drop('load', errors='ignore')),
for c in [costs_cap_ex, costs_marg]: index=['AC-AC', 'AC line', 'onwind', 'offwind-ac', 'offwind-dc', 'solar', 'OCGT','CCGT', 'battery', 'H2']).dropna()
if ind in c: bottom = np.array([0., 0.])
data_sub = np.asarray([c.loc[ind]])/total_load texts = []
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: for i,ind in enumerate(costs_graph.index):
continue 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
text = ax.text(1.1,(bottom-0.5*data)[-1]-3,opts['nice_names_n'].get(ind,ind)) if ind in opts['conv_techs'] + ['AC line']:
texts.append(text) 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
ax.set_ylabel("Average system cost [Eur/MWh]") if abs(data[-1]) < 5:
ax.set_ylim([0,80]) # opts['costs_max']]) continue
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() text = ax.text(1.1,(bottom-0.5*data)[-1]-3,opts['nice_names_n'].get(ind,ind))
texts.append(text)
ll = snakemake.wildcards.ll ax.set_ylabel("Average system cost [Eur/MWh]")
ll_type = ll[0] ax.set_ylim([0,80]) # opts['costs_max']])
ll_factor = ll[1:] ax.set_xlim([0,1])
lbl = dict(c='line cost', v='line volume')[ll_type] #ax.set_xticks([0.5])
amnt = '{ll} x today\'s'.format(ll=ll_factor) if ll_factor != 'opt' else 'optimal' ax.set_xticklabels([]) #["w/o\nEp", "w/\nEp"])
fig.suptitle('Expansion to {amount} {label} at {clusters} clusters' ax.grid(True, axis="y", color='k', linestyle='dotted')
.format(amount=amnt, label=lbl, clusters=snakemake.wildcards.clusters))
fig.savefig(snakemake.output.ext, transparent=True,
bbox_inches='tight', bbox_extra_artists=[l1, l2, l3, ax1, ax2]) if __name__ == "__main__":
if 'snakemake' not in globals():
from vresutils.snakemake import MockSnakemake, Dict
from snakemake.rules import expand
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}")
)
logging.basicConfig(level=snakemake.config['logging_level'])
set_plot_style()
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('-')
fig, ax = plt.subplots(figsize=map_figsize, subplot_kw={"projection": ccrs.PlateCarree()})
plot_map(n, ax, snakemake.wildcards.attr, opts)
fig.savefig(snakemake.output.only_map, dpi=150,
bbox_inches='tight', bbox_extra_artists=[l1,l2,l3])
ax1 = fig.add_axes([-0.115, 0.625, 0.2, 0.2])
plot_total_energy_pie(n, ax1)
ax2 = fig.add_axes([-0.075, 0.1, 0.1, 0.45])
plot_total_cost_bar(n, ax2)
#fig.tight_layout()
ll = snakemake.wildcards.ll
ll_type = ll[0]
ll_factor = ll[1:]
lbl = dict(c='line cost', v='line volume')[ll_type]
amnt = '{ll} x today\'s'.format(ll=ll_factor) if ll_factor != 'opt' else 'optimal'
fig.suptitle('Expansion to {amount} {label} at {clusters} clusters'
.format(amount=amnt, label=lbl, clusters=snakemake.wildcards.clusters))
fig.savefig(snakemake.output.ext, transparent=True,
bbox_inches='tight', bbox_extra_artists=[l1, l2, l3, ax1, ax2])