plot_network: Add functionality to produce a network plot of each solution

This commit is contained in:
Jonas Hörsch 2018-10-25 16:43:24 +02:00
parent 540985164f
commit 0070b1c87c
2 changed files with 263 additions and 5 deletions

View File

@ -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
View 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])