Skip to content

Plot network

add_cost_pannel(df, fig, preferred_order, tech_colors, plot_additions, ax_loc=[-0.09, 0.28, 0.09, 0.45], **kwargs)

Add a cost pannel to the figure

Parameters:

Name Type Description Default
df DataFrame

the cost data to plot

required
fig Figure

the figure object to which the cost pannel will be added

required
preferred_order Index

index, the order in whiich to plot

required
tech_colors dict

the tech colors

required
plot_additions bool

plot the additions

required
ax_loc list

the location of the cost pannel. Defaults to [-0.09, 0.28, 0.09, 0.45].

[-0.09, 0.28, 0.09, 0.45]
Source code in workflow/scripts/plot_network.py
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
def add_cost_pannel(
    df: pd.DataFrame,
    fig: plt.Figure,
    preferred_order: pd.Index,
    tech_colors: dict,
    plot_additions: bool,
    ax_loc=[-0.09, 0.28, 0.09, 0.45],
    **kwargs: dict,
) -> None:
    """Add a cost pannel to the figure

    Args:
        df (pd.DataFrame): the cost data to plot
        fig (plt.Figure): the figure object to which the cost pannel will be added
        preferred_order (pd.Index): index, the order in whiich to plot
        tech_colors (dict): the tech colors
        plot_additions (bool): plot the additions
        ax_loc (list, optional): the location of the cost pannel.
            Defaults to [-0.09, 0.28, 0.09, 0.45].
    """
    ax3 = fig.add_axes(ax_loc)
    reordered = preferred_order.intersection(df.index).append(df.index.difference(preferred_order))
    colors = {k.lower(): v for k, v in tech_colors.items()}

    # Create color list with default color for missing carriers
    color_list = []
    for k in reordered:
        if k.lower() in colors:
            color_list.append(colors[k.lower()])
        else:
            color_list.append("lightgrey")  # Default color for missing carriers

    df.loc[reordered, df.columns].T.plot(
        kind="bar",
        ax=ax3,
        stacked=True,
        color=color_list,
        **kwargs,
    )
    ax3.legend().remove()
    ax3.set_ylabel("annualized system cost bEUR/a")
    ax3.set_xticklabels(ax3.get_xticklabels(), rotation="horizontal")
    ax3.grid(axis="y")
    ax3.set_ylim([0, df.sum().max() * 1.1])
    if plot_additions:
        # add label
        percent = np.round((df.sum()["added"] / df.sum()["total"]) * 100)
        ax3.text(0.85, (df.sum()["added"] + 15), str(percent) + "%", color="black")

    fig.tight_layout()
    return ax3

add_energy_pannel(df, fig, preferred_order, colors, ax_loc=[-0.09, 0.28, 0.09, 0.45])

Add a cost pannel to the figure

Parameters:

Name Type Description Default
df DataFrame

the statistics supply output by carrier (from plot_energy map)

required
fig Figure

the figure object to which the cost pannel will be added

required
preferred_order Index

index, the order in whiich to plot

required
colors Series

the colors for the techs, with the correct index and no extra techs

required
ax_loc list

the pannel location. Defaults to [-0.09, 0.28, 0.09, 0.45].

[-0.09, 0.28, 0.09, 0.45]
Source code in workflow/scripts/plot_network.py
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
def add_energy_pannel(
    df: pd.DataFrame,
    fig: plt.Figure,
    preferred_order: pd.Index,
    colors: pd.Series,
    ax_loc=[-0.09, 0.28, 0.09, 0.45],
) -> None:
    """Add a cost pannel to the figure

    Args:
        df (pd.DataFrame): the statistics supply output by carrier (from plot_energy map)
        fig (plt.Figure): the figure object to which the cost pannel will be added
        preferred_order (pd.Index): index, the order in whiich to plot
        colors (pd.Series): the colors for the techs, with the correct index and no extra techs
        ax_loc (list, optional): the pannel location. Defaults to [-0.09, 0.28, 0.09, 0.45].
    """
    ax3 = fig.add_axes(ax_loc)
    reordered = preferred_order.intersection(df.index).append(df.index.difference(preferred_order))
    df = df / PLOT_SUPPLY_UNITS
    # only works if colors has correct index
    df.loc[reordered, df.columns].T.plot(
        kind="bar",
        ax=ax3,
        stacked=True,
        color=colors[reordered],
    )

    ax3.legend().remove()
    ax3.set_ylabel("Electricity supply [TWh]")
    ax3.set_xticklabels(ax3.get_xticklabels(), rotation="horizontal")
    ax3.grid(axis="y")
    ax3.set_ylim([0, df.sum().max() * 1.1])

    fig.tight_layout()

plot_cost_map(network, opts, base_year=2020, plot_additions=True, capex_only=False, cost_pannel=True, save_path=None)

Plot the cost of each node on a map as well as the line capacities

Parameters:

Name Type Description Default
network Network

the network object

required
opts dict

the plotting config (snakemake.config["plotting"])

required
base_year int

the base year (for cost delta). Defaults to 2020.

2020
capex_only bool

do not plot VOM (FOM is in CAPEX). Defaults to False.

False
plot_additions bool

plot a map of investments (p_nom_opt vs p_nom). Defaults to True.

True
cost_pannel bool

add a bar graph with costs. Defaults to True.

True
save_path PathLike

save figure to path (or not if None). Defaults to None.

None

raises: ValueError: if plot_additions and not capex_only

Source code in workflow/scripts/plot_network.py
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
def plot_cost_map(
    network: pypsa.Network,
    opts: dict,
    base_year=2020,
    plot_additions=True,
    capex_only=False,
    cost_pannel=True,
    save_path: os.PathLike = None,
):
    """Plot the cost of each node on a map as well as the line capacities

    Args:
        network (pypsa.Network): the network object
        opts (dict): the plotting config (snakemake.config["plotting"])
        base_year (int, optional): the base year (for cost delta). Defaults to 2020.
        capex_only (bool, optional): do not plot VOM (FOM is in CAPEX). Defaults to False.
        plot_additions (bool, optional): plot a map of investments (p_nom_opt vs p_nom).
              Defaults to True.
        cost_pannel (bool, optional): add a bar graph with costs. Defaults to True.
        save_path (os.PathLike, optional): save figure to path (or not if None). Defaults to None.
    raises:
        ValueError: if plot_additions and not capex_only
    """

    if plot_additions and not capex_only:
        raise ValueError("Cannot plot additions without capex only")

    tech_colors = make_nice_tech_colors(opts["tech_colors"], opts["nice_names"])

    # TODO scale edges by cost from capex summary
    def calc_link_plot_width(row, carrier="AC", additions=False):
        if row.length == 0 or row.carrier != carrier or not row.plottable:
            return 0
        elif additions:
            return row.p_nom
        else:
            return row.p_nom_opt

    # ============ === Stats by bus ===
    # calc costs & sum over component types to keep bus & carrier (remove no loc)
    costs = network.statistics.capex(groupby=["location", "carrier"])
    costs = costs.groupby(level=[1, 2]).sum()
    if "" in costs.index:
        costs.drop("", inplace=True)
    # we miss some buses by grouping epr location, fill w 0s
    bus_idx = pd.MultiIndex.from_product([network.buses.index, ["AC"]])
    costs = costs.reindex(bus_idx.union(costs.index), fill_value=0)
    # add marginal (excluding quasi fixed) to costs if desired
    if not capex_only:
        opex = network.statistics.opex(groupby=["location", "carrier"])
        opex = opex.groupby(level=[1, 2]).sum()
        cost_pies = costs + opex.reindex(costs.index, fill_value=0)

    # === make map components: pies and edges
    cost_pies = costs.fillna(0)
    cost_pies.index.names = ["bus", "carrier"]
    carriers = cost_pies.index.get_level_values(1).unique()
    # map edges
    link_plot_w = network.links.apply(lambda row: calc_link_plot_width(row, carrier="AC"), axis=1)
    edges = pd.concat([network.lines.s_nom_opt, link_plot_w]).groupby(level=0).sum()
    line_lower_threshold = opts.get("min_edge_capacity", 0)
    edge_widths = edges.clip(line_lower_threshold, edges.max()).replace(line_lower_threshold, 0)

    # === Additions ===
    # for pathways sometimes interested in additions from last time step
    if plot_additions:
        installed = (
            network.statistics.installed_capex(groupby=["location", "carrier"])
            .groupby(level=[1, 2])
            .sum()
        )
        costs_additional = costs - installed.reindex(costs.index, fill_value=0)
        cost_pies_additional = costs_additional.fillna(0)
        cost_pies_additional.index.names = ["bus", "carrier"]

        link_additions = network.links.apply(
            lambda row: calc_link_plot_width(row, carrier="AC", additions=True), axis=1
        )
        added_links = link_plot_w - link_additions.reindex(link_plot_w.index, fill_value=0)
        added_lines = network.lines.s_nom_opt - network.lines.s_nom.reindex(
            network.lines.index, fill_value=0
        )
        edge_widths_added = pd.concat([added_links, added_lines]).groupby(level=0).sum()

        # add to carrier types
        carriers = carriers.union(cost_pies_additional.index.get_level_values(1).unique())

    preferred_order = pd.Index(opts["preferred_order"])
    carriers = carriers.tolist()

    # Make figure with right number of pannels
    if plot_additions:
        fig, (ax1, ax2) = plt.subplots(1, 2, subplot_kw={"projection": ccrs.PlateCarree()})
        fig.set_size_inches(opts["cost_map"]["figsize_w_additions"])
    else:
        fig, ax1 = plt.subplots(subplot_kw={"projection": ccrs.PlateCarree()})
        fig.set_size_inches(opts["cost_map"]["figsize"])

    # Add the total costs
    bus_size_factor = opts["cost_map"]["bus_size_factor"]
    linewidth_factor = opts["cost_map"]["linewidth_factor"]

    ax = plot_map(
        network,
        tech_colors=tech_colors,
        edge_widths=edge_widths / linewidth_factor,
        bus_colors=tech_colors,
        bus_sizes=cost_pies / bus_size_factor,
        edge_colors=opts["cost_map"]["edge_color"],
        ax=ax1,
        add_legend=not plot_additions,
        bus_ref_title=f"System costs{' (CAPEX)'if capex_only else ''}",
        **opts["cost_map"],
    )

    # TODO check edges is working
    # Add the added pathway costs
    if plot_additions:
        ax2 = plot_map(
            network,
            tech_colors=tech_colors,
            edge_widths=edge_widths_added / linewidth_factor,
            bus_colors=tech_colors,
            bus_sizes=cost_pies_additional / bus_size_factor,
            edge_colors="rosybrown",
            ax=ax2,
            bus_ref_title=f"Added costs{' (CAPEX)' if capex_only else ''}",
            add_legend=True,
            **opts["cost_map"],
        )

    # Add the optional cost pannel
    if cost_pannel:
        df = pd.DataFrame(columns=["total"])
        df["total"] = network.statistics.capex(nice_names=False).groupby(level=1).sum()
        if not capex_only:
            df["opex"] = network.statistics.opex(nice_names=False).groupby(level=1).sum()
            df.rename(columns={"total": "capex"}, inplace=True)
        elif plot_additions:
            df["added"] = (
                df["total"]
                - network.statistics.installed_capex(nice_names=False).groupby(level=1).sum()
            )

        df.fillna(0, inplace=True)
        df = df / PLOT_COST_UNITS
        # TODO decide discount
        # df = df / (1 + discount_rate) ** (int(planning_horizon) - base_year)
        ax3 = add_cost_pannel(
            df,
            fig,
            preferred_order,
            tech_colors,
            plot_additions,
            ax_loc=[-0.09, 0.28, 0.09, 0.45],
        )
        # Set x-label angle to 45 degrees for better readability
        for label in ax3.get_xticklabels():
            label.set_rotation(45)

    fig.set_size_inches(opts["cost_map"][f"figsize{'_w_additions' if plot_additions else ''}"])
    fig.tight_layout()
    if save_path:
        fig.savefig(save_path, transparent=opts["transparent"], bbox_inches="tight")

plot_energy_map(network, opts, energy_pannel=True, save_path=None, carrier='AC', plot_ac_imports=False, exclude_batteries=True, components=['Generator', 'Link'])

A map plot of energy, either AC or heat

Parameters:

Name Type Description Default
network Network

the pyPSA network object

required
opts dict

the plotting options (snakemake.config["plotting"])

required
energy_pannel bool

add an anergy pie to the left. Defaults to True.

True
save_path PathLike

Fig outp path. Defaults to None (no save).

None
carrier str

the energy carrier. Defaults to "AC".

'AC'
plot_ac_imports bool

plot electricity imports. Defaults to False.

False
exclude_batteries bool

exclude battery dischargers from the supply pie.

True
components list

the components to plot. Defaults to ["Generator", "Link"].

['Generator', 'Link']

raises: ValueError: if carrier is not AC or heat

Source code in workflow/scripts/plot_network.py
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
def plot_energy_map(
    network: pypsa.Network,
    opts: dict,
    energy_pannel=True,
    save_path: os.PathLike = None,
    carrier="AC",
    plot_ac_imports=False,
    exclude_batteries=True,
    components=["Generator", "Link"],
):
    """A map plot of energy, either AC or heat

    Args:
        network (pypsa.Network): the pyPSA network object
        opts (dict): the plotting options (snakemake.config["plotting"])
        energy_pannel (bool, optional): add an anergy pie to the left. Defaults to True.
        save_path (os.PathLike, optional): Fig outp path. Defaults to None (no save).
        carrier (str, optional): the energy carrier. Defaults to "AC".
        plot_ac_imports (bool, optional): plot electricity imports. Defaults to False.
        exclude_batteries (bool, optional): exclude battery dischargers from the supply pie.
        components (list, optional): the components to plot. Defaults to ["Generator", "Link"].
    raises:
        ValueError: if carrier is not AC or heat
    """
    if carrier not in ["AC", "heat"]:
        raise ValueError("Carrier must be either 'AC' or 'heat'")

    # make the statistics. Buses not assigned to a region will be included
    # if they are linked to a region (e.g. turbine link w carrier = hydroelectricity)
    energy_supply = network.statistics.supply(
        groupby=get_location_and_carrier,
        bus_carrier=carrier,
        comps=components,
    )
    # get rid of components
    supply_pies = energy_supply.groupby(level=[1, 2]).sum()

    # TODO fix  this for heat
    # # calc costs & sum over component types to keep bus & carrier (remove no loc)
    # energy_supply = network.statistics.capex(groupby=["location", "carrier"])
    # energy_supply = energy_supply.groupby(level=[1, 2]).sum().drop("")
    # # we miss some buses by grouping epr location, fill w 0s
    # bus_idx = pd.MultiIndex.from_product([network.buses.index, ["AC"]])
    # supply_pies = energy_supply.reindex(bus_idx.union(energy_supply.index), fill_value=0)

    # remove imports from supply pies
    if carrier == "AC" and not plot_ac_imports:
        supply_pies = supply_pies.loc[supply_pies.index.get_level_values(1) != "AC"]

    # TODO aggregate costs below threshold into "other" -> requires messing with network
    # network.add("Carrier", "Other")

    # get all carrier types
    carriers_list = supply_pies.index.get_level_values(1).unique()
    carriers_list = carriers_list.tolist()

    # TODO make line handling nicer
    line_lower_threshold = opts.get("min_edge_capacity", 500)
    # Make figur
    fig, ax = plt.subplots(subplot_kw={"projection": ccrs.PlateCarree()})
    fig.set_size_inches(opts["energy_map"]["figsize"])
    # get colors
    bus_colors = network.carriers.loc[network.carriers.nice_name.isin(carriers_list), "color"]
    bus_colors.rename(opts["nice_names"], inplace=True)

    preferred_order = pd.Index(opts["preferred_order"])
    reordered = preferred_order.intersection(bus_colors.index).append(
        bus_colors.index.difference(preferred_order)
    )
    # TODO there'sa  problem with network colors when using heat, pies aren't grouped by location
    colors = network.carriers.color.copy()
    colors.index = colors.index.map(opts["nice_names"])
    tech_colors = make_nice_tech_colors(opts["tech_colors"], opts["nice_names"])

    # make sure plot isnt overpopulated
    def calc_link_plot_width(row, carrier="AC"):
        if row.length == 0 or row.carrier != carrier or not row.plottable:
            return 0
        else:
            return row.p_nom_opt

    edge_carrier = "H2 pipeline" if carrier == "heat" else "AC"
    link_plot_w = network.links.apply(lambda row: calc_link_plot_width(row, edge_carrier), axis=1)
    edges = pd.concat([network.lines.s_nom_opt, link_plot_w])
    edge_widths = edges.clip(line_lower_threshold, edges.max()).replace(line_lower_threshold, 0)

    opts_plot = opts["energy_map"].copy()
    if carrier == "heat":
        opts_plot["ref_bus_sizes"] = opts_plot["ref_bus_sizes_heat"]
        opts_plot["ref_edge_sizes"] = opts_plot["ref_edge_sizes_heat"]
        opts_plot["linewidth_factor"] = opts_plot["linewidth_factor_heat"]
        opts_plot["bus_size_factor"] = opts_plot["bus_size_factor_heat"]
    # exclude battery dischargers from bus sizes
    if exclude_batteries:
        bus_sizes = (
            supply_pies.loc[~supply_pies.index.get_level_values(1).str.contains("battery")]
            / opts_plot["bus_size_factor"]
        )
    else:
        bus_sizes = supply_pies / opts_plot["bus_size_factor"]
    ax = plot_map(
        network,
        tech_colors=tech_colors,  # colors.to_dict(),
        edge_widths=edge_widths / opts_plot["linewidth_factor"],
        bus_colors=bus_colors.loc[reordered],
        bus_sizes=bus_sizes,
        edge_colors=opts_plot["edge_color"],
        ax=ax,
        edge_unit_conv=PLOT_CAP_UNITS,
        bus_unit_conv=PLOT_SUPPLY_UNITS,
        add_legend=True,
        **opts_plot,
    )
    # # Add the optional cost pannel
    if energy_pannel:
        df = supply_pies.groupby(level=1).sum().to_frame()
        df = df.fillna(0)
        df.rename(columns={0: ""}, inplace=True)
        add_energy_pannel(df, fig, preferred_order, bus_colors, ax_loc=[-0.09, 0.28, 0.09, 0.45])

    handles, labels = ax.get_legend_handles_labels()
    ax.legend(handles, labels, ncol=1, bbox_to_anchor=[1, 1], loc="upper left")
    fig.tight_layout()

    if save_path:
        fig.savefig(save_path, transparent=opts["transparent"], bbox_inches="tight")

plot_map(network, tech_colors, edge_widths, bus_colors, bus_sizes, edge_colors='black', add_ref_edge_sizes=True, add_ref_bus_sizes=True, add_legend=True, bus_unit_conv=PLOT_COST_UNITS, edge_unit_conv=PLOT_CAP_UNITS, ax=None, **kwargs)

Plot the network on a map

Parameters:

Name Type Description Default
network Network

the pypsa network (filtered to contain only relevant buses & links)

required
tech_colors dict

config mapping

required
edge_colors Series | str

the series of edge colors

'black'
edge_widths Series

the edge widths

required
bus_colors Series

the series of bus colors

required
bus_sizes Series

the series of bus sizes

required
add_ref_edge_sizes bool

add reference line sizes in legend (requires edge_colors=True). Defaults to True.

True
add_ref_bus_sizes bool

add reference bus sizes in legend. Defaults to True.

True
ax Axes

the plotting ax. Defaults to None (new figure).

None
Source code in workflow/scripts/plot_network.py
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
def plot_map(
    network: pypsa.Network,
    tech_colors: dict,
    edge_widths: pd.Series,
    bus_colors: dict | pd.Series,
    bus_sizes: pd.Series,
    edge_colors: pd.Series | str = "black",
    add_ref_edge_sizes=True,
    add_ref_bus_sizes=True,
    add_legend=True,
    bus_unit_conv=PLOT_COST_UNITS,
    edge_unit_conv=PLOT_CAP_UNITS,
    ax=None,
    **kwargs,
) -> plt.Axes:
    """Plot the network on a map

    Args:
        network (pypsa.Network): the pypsa network (filtered to contain only relevant buses & links)
        tech_colors (dict): config mapping
        edge_colors (pd.Series|str): the series of edge colors
        edge_widths (pd.Series): the edge widths
        bus_colors (pd.Series): the series of bus colors
        bus_sizes (pd.Series): the series of bus sizes
        add_ref_edge_sizes (bool, optional): add reference line sizes in legend
            (requires edge_colors=True). Defaults to True.
        add_ref_bus_sizes (bool, optional): add reference bus sizes in legend.
            Defaults to True.
        ax (plt.Axes, optional): the plotting ax. Defaults to None (new figure).
    """

    if not ax:
        fig, ax = plt.subplots()
    else:
        fig = ax.get_figure()

    network.plot(
        bus_sizes=bus_sizes,
        bus_colors=bus_colors,
        line_colors=edge_colors,
        link_colors=edge_colors,
        line_widths=edge_widths,
        link_widths=edge_widths,
        ax=ax,
        color_geomap=True,
        boundaries=kwargs.get("boundaries", None),
    )

    ax.add_feature(cfeature.BORDERS, linewidth=0.5, edgecolor="gray")
    states_provinces = cfeature.NaturalEarthFeature(
        category="cultural",
        name="admin_1_states_provinces_lines",
        scale="50m",
        facecolor="none",
    )
    # Add our states feature.
    ax.add_feature(states_provinces, edgecolor="lightgray", alpha=0.7)

    if add_legend:
        carriers = bus_sizes.index.get_level_values(1).unique()
        # Ensure all carriers have colors, use default color for missing ones
        colors = []
        for carrier in carriers:
            if carrier in tech_colors:
                colors.append(tech_colors[carrier])
            else:
                colors.append("lightgrey")  # Default color for missing carriers

        if isinstance(edge_colors, str):
            colors += [edge_colors]
            labels = carriers.to_list() + ["HVDC or HVAC link"]
        else:
            colors += edge_colors.values.to_list()
            labels = carriers.to_list() + edge_colors.index.to_list()
        leg_opt = {"bbox_to_anchor": (1.42, 1.04), "frameon": False}
        add_legend_patches(ax, colors, labels, legend_kw=leg_opt)

    if add_ref_edge_sizes & isinstance(edge_colors, str):
        ref_unit = kwargs.get("ref_edge_unit", "GW")
        size_factor = float(kwargs.get("linewidth_factor", 1e5))
        ref_sizes = kwargs.get("ref_edge_sizes", [1e5, 5e5])

        labels = [f"{float(s)/edge_unit_conv} {ref_unit}" for s in ref_sizes]
        ref_sizes = list(map(lambda x: float(x) / size_factor, ref_sizes))
        legend_kw = dict(
            loc="upper left",
            bbox_to_anchor=(0.26, 1.0),
            frameon=False,
            labelspacing=0.8,
            handletextpad=2,
            title=kwargs.get("edge_ref_title", "Grid cap."),
        )
        add_legend_lines(
            ax, ref_sizes, labels, patch_kw=dict(color=edge_colors), legend_kw=legend_kw
        )

    # add reference bus sizes ferom the units
    if add_ref_bus_sizes:
        ref_unit = kwargs.get("ref_bus_unit", "bEUR/a")
        size_factor = float(kwargs.get("bus_size_factor", 1e10))
        ref_sizes = kwargs.get("ref_bus_sizes", [2e10, 1e10, 5e10])
        labels = [f"{float(s)/bus_unit_conv:.0f} {ref_unit}" for s in ref_sizes]
        ref_sizes = list(map(lambda x: float(x) / size_factor, ref_sizes))

        legend_kw = {
            "loc": "upper left",
            "bbox_to_anchor": (0.0, 1.0),
            "labelspacing": 0.8,
            "frameon": False,
            "handletextpad": 0,
            "title": kwargs.get("bus_ref_title", "UNDEFINED TITLE"),
        }

        add_legend_circles(
            ax,
            ref_sizes,
            labels,
            srid=network.srid,
            patch_kw=dict(facecolor="lightgrey"),
            legend_kw=legend_kw,
        )

    fig.tight_layout()

    return ax

plot_nodal_prices(network, opts, carrier='AC', save_path=None)

A map plot of energy, either AC or heat

Parameters:

Name Type Description Default
network Network

the pyPSA network object

required
opts dict

the plotting options (snakemake.config["plotting"])

required
save_path PathLike

Fig outp path. Defaults to None (no save).

None
carrier str

the energy carrier. Defaults to "AC".

'AC'

raises: ValueError: if carrier is not AC or heat

Source code in workflow/scripts/plot_network.py
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
def plot_nodal_prices(
    network: pypsa.Network,
    opts: dict,
    carrier="AC",
    save_path: os.PathLike = None,
):
    """A map plot of energy, either AC or heat

    Args:
        network (pypsa.Network): the pyPSA network object
        opts (dict): the plotting options (snakemake.config["plotting"])
        save_path (os.PathLike, optional): Fig outp path. Defaults to None (no save).
        carrier (str, optional): the energy carrier. Defaults to "AC".
    raises:
        ValueError: if carrier is not AC or heat
    """
    if carrier not in ["AC", "heat"]:
        raise ValueError("Carrier must be either 'AC' or 'heat'")

    # demand weighed prices per node
    nodal_prices = (
        network.statistics.revenue(
            groupby=pypsa.statistics.get_bus_and_carrier_and_bus_carrier,
            comps="Load",
            bus_carrier=carrier,
        )
        / network.statistics.withdrawal(
            comps="Load",
            groupby=pypsa.statistics.get_bus_and_carrier_and_bus_carrier,
            bus_carrier=carrier,
        )
        * -1
    )
    # drop the carrier and bus_carrier, map to colors
    nodal_prices = nodal_prices.droplevel(1).droplevel(1)
    norm = plt.Normalize(vmin=nodal_prices.min(), vmax=nodal_prices.max())
    cmap = plt.get_cmap("plasma")
    bus_colors = nodal_prices.map(lambda x: cmap(norm(x)))

    energy_consum = network.statistics.withdrawal(
        groupby=pypsa.statistics.get_bus_and_carrier,
        bus_carrier=carrier,
        comps=["Load"],
    )
    consum_pies = energy_consum.groupby(level=1).sum()

    # Make figure
    fig, ax = plt.subplots(subplot_kw={"projection": ccrs.PlateCarree()})
    fig.set_size_inches(opts["price_map"]["figsize"])
    # get colors

    # TODO make line handling nicer
    # make sure plot isnt overpopulated
    def calc_plot_width(row, carrier="AC"):
        if row.length == 0:
            return 0
        elif row.carrier != carrier:
            return 0
        else:
            return row.p_nom_opt

    line_lower_threshold = opts.get("min_edge_capacity", 500)
    edge_carrier = "H2" if carrier == "heat" else "AC"
    link_plot_w = network.links.apply(lambda row: calc_plot_width(row, edge_carrier), axis=1)
    edges = pd.concat([network.lines.s_nom_opt, link_plot_w])
    edge_widths = edges.clip(line_lower_threshold, edges.max()).replace(line_lower_threshold, 0)

    bus_size_factor = opts["price_map"]["bus_size_factor"]
    linewidth_factor = opts["price_map"][f"linewidth_factor{"_heat" if carrier == 'heat' else ''}"]
    plot_map(
        network,
        tech_colors=None,
        edge_widths=edge_widths / linewidth_factor,
        bus_colors=bus_colors,
        bus_sizes=consum_pies / bus_size_factor,
        edge_colors=opts["price_map"]["edge_color"],
        ax=ax,
        edge_unit_conv=PLOT_CAP_UNITS,
        bus_unit_conv=PLOT_SUPPLY_UNITS,
        add_legend=False,
        **opts["price_map"],
    )

    # Add colorbar based on bus_colors
    # fig.tight_layout()
    fig.subplots_adjust(right=0.85)
    cax = fig.add_axes([0.87, ax.get_position().y0, 0.02, ax.get_position().height])
    sm = plt.cm.ScalarMappable(cmap=cmap, norm=norm)
    sm.set_array([])
    cbar = plt.colorbar(sm, cax=cax, orientation="vertical")
    cbar.set_label(f"Nodal Prices ${CURRENCY}/MWh")

    if save_path:
        fig.savefig(save_path, transparent=opts["transparent"], bbox_inches="tight")