Example #1
0
def preprocess(plot: bool = False):
    # TODO:
    #  - figure out what we really need
    #    - converters?
    #    - transformers?
    #  - figure out if lines and/or links can be extended
    #    - see prepare_network
    #  - Probably best to do all these steps once in a preprocess function and then just
    #   remove unwanted components at run time

    # Load main components
    # buses_df = load_buses_from_eg(countries, voltages)
    buses_df = load_buses_from_eg()
    # buses_df, links_df = load_links_from_eg(buses_df, config['links'], countries)
    buses_df, links_df = load_links_from_eg(buses_df)
    # converters_df = load_converters_from_eg(buses_df.index, config["links"])
    converters_df = load_converters_from_eg(buses_df.index)
    # lines_df = load_lines_from_eg(buses_df.index, config["lines"], voltages, net.line_types)
    lines_df = load_lines_from_eg(buses_df.index)
    # transformers_df = load_transformers_from_eg(buses_df.index, config["transformers"])
    transformers_df = load_transformers_from_eg(buses_df.index)

    # Add everything to the network
    net = Network()
    net.import_components_from_dataframe(buses_df, "Bus")
    net.import_components_from_dataframe(lines_df, "Line")
    net.import_components_from_dataframe(transformers_df, "Transformer")
    net.import_components_from_dataframe(links_df, "Link")
    net.import_components_from_dataframe(converters_df, "Link")

    # Update a bunch of parameters for given components according to parameters_correction.yaml
    apply_parameter_corrections(net)
    # Remove subnetworks with less than a given number of components
    net = remove_unconnected_components(net)
    # Determine to which country each bus (onshore or offshore) belong and do some stuff with substations
    # set_countries_and_substations(net, countries)
    set_countries_and_substations(net)
    # Set what portion of the link is under water
    # set_links_underwater_fraction(net, countries)
    set_links_underwater_fraction(net)
    # I have no clue what this is for...
    replace_b2b_converter_at_country_border_by_link(net)

    # Save base network
    net.export_to_csv_folder(
        f"{data_path}topologies/pypsa_entsoe_gridkit/generated/base_network/")

    if plot:
        import matplotlib.pyplot as plt
        net.plot(bus_sizes=0.001)
        plt.show()

    return net
Example #2
0
def load_topology(nuts_codes,
                  config,
                  voltages: List[float] = None,
                  plot: bool = False):

    net = Network()
    net.import_from_csv_folder(
        f"{data_path}topologies/pypsa_entsoe_gridkit/generated/base_network/")

    if 1:
        import matplotlib.pyplot as plt
        net.plot(bus_sizes=0.001)
        plt.show()

    exit()

    # Remove all buses outside desired regions
    region_shapes_ds = get_shapes(nuts_codes, save=True)["geometry"]
    buses_in_nuts_regions = \
        net.buses[['x', 'y']].apply(lambda p: any([shape.contains(Point(p)) for shape in region_shapes_ds]), axis=1)
    net.buses = net.buses[buses_in_nuts_regions]

    if 0:
        plt.figure()
        net.plot(bus_sizes=0.001)

    # Remove all buses which are not at the desired voltage
    buses_with_v_nom_to_keep_b = net.buses.v_nom.isnull()
    if voltages is not None:
        buses_with_v_nom_to_keep_b |= net.buses.v_nom.isin(voltages)
        logger.info(
            "Removing buses with voltages {}"
            "".format(
                pd.Index(
                    net.buses.v_nom.unique()).dropna().difference(voltages)))
    net.buses = net.buses[buses_with_v_nom_to_keep_b]

    if 1:
        plt.figure()
        net.plot(bus_sizes=0.001)
        plt.show()

    # Remove dangling branches
    net.lines = remove_dangling_branches(net.lines, net.buses.index)
    net.links = remove_dangling_branches(net.links, net.buses.index)
    net.transformers = remove_dangling_branches(net.transformers,
                                                net.buses.index)

    # Set electrical parameters
    set_electrical_parameters_lines(net, config['lines'],
                                    net.buses.v_nom.dropna().unique().tolist())
    set_electrical_parameters_links(net, config['links'])
    set_electrical_parameters_transformers(net, config['transformers'])

    # Allows to set under construction links and lines to 0 or remove them completely,
    # and remove some unconnected components that might appear as a result
    net = adjust_capacities_of_under_construction_branches(
        net, config['lines'], config['links'])

    # Remove unconnected components

    # TODO: allow to simplify the network (i.e. convert everything to 380) or not ?

    net = cluster_network(net, nuts_codes)

    if plot:
        from epippy.topologies.core.plot import plot_topology
        all_lines = pd.concat(
            (net.links[['bus0', 'bus1']], net.lines[['bus0', 'bus1']]))
        plot_topology(net.buses, all_lines)
        plt.show()

    return net
Example #3
0
def mv_grid_topology(pypsa_network,
                     configs,
                     timestep=None,
                     line_color=None,
                     node_color=None,
                     line_load=None,
                     grid_expansion_costs=None,
                     filename=None,
                     arrows=False,
                     grid_district_geom=True,
                     background_map=True,
                     voltage=None,
                     limits_cb_lines=None,
                     limits_cb_nodes=None,
                     xlim=None,
                     ylim=None,
                     lines_cmap='inferno_r',
                     title='',
                     scaling_factor_line_width=None):
    """
    Plot line loading as color on lines.

    Displays line loading relative to nominal capacity.

    Parameters
    ----------
    pypsa_network : :pypsa:`pypsa.Network<network>`
    configs : :obj:`dict`
        Dictionary with used configurations from config files. See
        :class:`~.grid.network.Config` for more information.
    timestep : :pandas:`pandas.Timestamp<timestamp>`
        Time step to plot analysis results for. If `timestep` is None maximum
        line load and if given, maximum voltage deviation, is used. In that
        case arrows cannot be drawn. Default: None.
    line_color : :obj:`str` or None
        Defines whereby to choose line colors (and implicitly size). Possible
        options are:

        * 'loading'
          Line color is set according to loading of the line. Loading of MV
          lines must be provided by parameter `line_load`.
        * 'expansion_costs'
          Line color is set according to investment costs of the line. This
          option also effects node colors and sizes by plotting investment in
          stations and setting `node_color` to 'storage_integration' in order
          to plot storage size of integrated storages. Grid expansion costs
          must be provided by parameter `grid_expansion_costs`.
        * None (default)
          Lines are plotted in black. Is also the fallback option in case of
          wrong input.

    node_color : :obj:`str` or None
        Defines whereby to choose node colors (and implicitly size). Possible
        options are:

        * 'technology'
          Node color as well as size is set according to type of node
          (generator, MV station, etc.).
        * 'voltage'
          Node color is set according to voltage deviation from 1 p.u..
          Voltages of nodes in MV grid must be provided by parameter `voltage`.
        * 'storage_integration'
          Only storages are plotted. Size of node corresponds to size of
          storage.
        * None (default)
          Nodes are not plotted. Is also the fallback option in case of wrong
          input.

    line_load : :pandas:`pandas.DataFrame<dataframe>` or None
        Dataframe with current results from power flow analysis in A. Index of
        the dataframe is a :pandas:`pandas.DatetimeIndex<datetimeindex>`,
        columns are the line representatives. Only needs to be provided when
        parameter `line_color` is set to 'loading'. Default: None.
    grid_expansion_costs : :pandas:`pandas.DataFrame<dataframe>` or None
        Dataframe with grid expansion costs in kEUR. See `grid_expansion_costs`
        in :class:`~.grid.network.Results` for more information. Only needs to
        be provided when parameter `line_color` is set to 'expansion_costs'.
        Default: None.
    filename : :obj:`str`
        Filename to save plot under. If not provided, figure is shown directly.
        Default: None.
    arrows : :obj:`Boolean`
        If True draws arrows on lines in the direction of the power flow. Does
        only work when `line_color` option 'loading' is used and a time step
        is given.
        Default: False.
    grid_district_geom : :obj:`Boolean`
        If True grid district polygon is plotted in the background. This also
        requires the geopandas package to be installed. Default: True.
    background_map : :obj:`Boolean`
        If True map is drawn in the background. This also requires the
        contextily package to be installed. Default: True.
    voltage : :pandas:`pandas.DataFrame<dataframe>`
        Dataframe with voltage results from power flow analysis in p.u.. Index
        of the dataframe is a :pandas:`pandas.DatetimeIndex<datetimeindex>`,
        columns are the bus representatives. Only needs to be provided when
        parameter `node_color` is set to 'voltage'. Default: None.
    limits_cb_lines : :obj:`tuple`
        Tuple with limits for colorbar of line color. First entry is the
        minimum and second entry the maximum value. Only needs to be provided
        when parameter `line_color` is not None. Default: None.
    limits_cb_nodes : :obj:`tuple`
        Tuple with limits for colorbar of nodes. First entry is the
        minimum and second entry the maximum value. Only needs to be provided
        when parameter `node_color` is not None. Default: None.
    xlim : :obj:`tuple`
        Limits of x-axis. Default: None.
    ylim : :obj:`tuple`
        Limits of y-axis. Default: None.
    lines_cmap : :obj:`str`
        Colormap to use for lines in case `line_color` is 'loading' or
        'expansion_costs'. Default: 'inferno_r'.
    title : :obj:`str`
        Title of the plot. Default: ''.
    scaling_factor_line_width : :obj:`float` or None
        If provided line width is set according to the nominal apparent power
        of the lines. If line width is None a default line width of 2 is used
        for each line. Default: None.

    """
    def get_color_and_size(name, colors_dict, sizes_dict):
        if 'BranchTee' in name:
            return colors_dict['BranchTee'], sizes_dict['BranchTee']
        elif 'LVStation' in name:
            return colors_dict['LVStation'], sizes_dict['LVStation']
        elif 'GeneratorFluctuating' in name:
            return (colors_dict['GeneratorFluctuating'],
                    sizes_dict['GeneratorFluctuating'])
        elif 'Generator' in name:
            return colors_dict['Generator'], sizes_dict['Generator']
        elif 'DisconnectingPoint' in name:
            return (colors_dict['DisconnectingPoint'],
                    sizes_dict['DisconnectingPoint'])
        elif 'MVStation' in name:
            return colors_dict['MVStation'], sizes_dict['MVStation']
        elif 'Storage' in name:
            return colors_dict['Storage'], sizes_dict['Storage']
        else:
            return colors_dict['else'], sizes_dict['else']

    def nodes_by_technology(buses):
        bus_sizes = {}
        bus_colors = {}
        colors_dict = {
            'BranchTee': 'b',
            'GeneratorFluctuating': 'g',
            'Generator': 'k',
            'LVStation': 'c',
            'MVStation': 'r',
            'Storage': 'y',
            'DisconnectingPoint': '0.75',
            'else': 'orange'
        }
        sizes_dict = {
            'BranchTee': 10,
            'GeneratorFluctuating': 100,
            'Generator': 100,
            'LVStation': 50,
            'MVStation': 120,
            'Storage': 100,
            'DisconnectingPoint': 50,
            'else': 200
        }
        for bus in buses:
            bus_colors[bus], bus_sizes[bus] = get_color_and_size(
                bus, colors_dict, sizes_dict)
        return bus_sizes, bus_colors

    def nodes_by_voltage(buses, voltage):
        bus_colors = {}
        bus_sizes = {}
        for bus in buses:
            if 'primary' in bus:
                bus_tmp = bus[12:]
            else:
                bus_tmp = bus[4:]
            if timestep is not None:
                bus_colors[bus] = 100 * abs(1 - voltage.loc[timestep,
                                                            ('mv', bus_tmp)])
            else:
                bus_colors[bus] = 100 * max(
                    abs(1 - voltage.loc[:, ('mv', bus_tmp)]))
            bus_sizes[bus] = 50
        return bus_sizes, bus_colors

    def nodes_storage_integration(buses):
        bus_sizes = {}
        for bus in buses:
            if not 'storage' in bus:
                bus_sizes[bus] = 0
            else:
                tmp = bus.split('_')
                storage_repr = '_'.join(tmp[1:])
                bus_sizes[bus] = pypsa_network.storage_units.loc[
                    storage_repr, 'p_nom'] * 1000 / 3
        return bus_sizes

    def nodes_by_costs(buses, grid_expansion_costs):
        # sum costs for each station
        costs_lv_stations = grid_expansion_costs[
            grid_expansion_costs.index.str.contains("LVStation")]
        costs_lv_stations['station'] = \
            costs_lv_stations.reset_index()['index'].apply(
                lambda _: '_'.join(_.split('_')[0:2])).values
        costs_lv_stations = costs_lv_stations.groupby('station').sum()
        costs_mv_station = grid_expansion_costs[
            grid_expansion_costs.index.str.contains("MVStation")]
        costs_mv_station['station'] = \
            costs_mv_station.reset_index()['index'].apply(
                lambda _: '_'.join(_.split('_')[0:2])).values
        costs_mv_station = costs_mv_station.groupby('station').sum()

        bus_sizes = {}
        bus_colors = {}
        for bus in buses:
            if 'LVStation' in bus:
                try:
                    tmp = bus.split('_')
                    lv_st = '_'.join(tmp[2:])
                    bus_colors[bus] = costs_lv_stations.loc[lv_st,
                                                            'total_costs']
                    bus_sizes[bus] = 100
                except:
                    bus_colors[bus] = 0
                    bus_sizes[bus] = 0
            elif 'MVStation' in bus:
                try:
                    tmp = bus.split('_')
                    mv_st = '_'.join(tmp[2:])
                    bus_colors[bus] = costs_mv_station.loc[mv_st,
                                                           'total_costs']
                    bus_sizes[bus] = 100
                except:
                    bus_colors[bus] = 0
                    bus_sizes[bus] = 0
            else:
                bus_colors[bus] = 0
                bus_sizes[bus] = 0

        return bus_sizes, bus_colors

    # set font and font size
    font = {'family': 'serif', 'size': 15}
    matplotlib.rc('font', **font)

    # create pypsa network only containing MV buses and lines
    pypsa_plot = PyPSANetwork()
    pypsa_plot.buses = pypsa_network.buses.loc[pypsa_network.buses.v_nom > 1]
    # filter buses of aggregated loads and generators
    pypsa_plot.buses = pypsa_plot.buses[~pypsa_plot.buses.index.str.
                                        contains("agg")]
    pypsa_plot.lines = pypsa_network.lines[pypsa_network.lines.bus0.isin(
        pypsa_plot.buses.index)][pypsa_network.lines.bus1.isin(
            pypsa_plot.buses.index)]

    # line colors
    if line_color == 'loading':
        line_colors = tools.calculate_relative_line_load(
            pypsa_network, configs, line_load, pypsa_network.lines.v_nom,
            pypsa_plot.lines.index, timestep).max()
    elif line_color == 'expansion_costs':
        node_color = 'expansion_costs'
        line_costs = pypsa_plot.lines.join(grid_expansion_costs,
                                           rsuffix='costs',
                                           how='left')
        line_colors = line_costs.total_costs.fillna(0)
    else:
        line_colors = pd.Series('black', index=pypsa_plot.lines.index)

    # bus colors and sizes
    if node_color == 'technology':
        bus_sizes, bus_colors = nodes_by_technology(pypsa_plot.buses.index)
        bus_cmap = None
    elif node_color == 'voltage':
        bus_sizes, bus_colors = nodes_by_voltage(pypsa_plot.buses.index,
                                                 voltage)
        bus_cmap = plt.cm.Blues
    elif node_color == 'storage_integration':
        bus_sizes = nodes_storage_integration(pypsa_plot.buses.index)
        bus_colors = 'orangered'
        bus_cmap = None
    elif node_color == 'expansion_costs':
        bus_sizes, bus_colors = nodes_by_costs(pypsa_plot.buses.index,
                                               grid_expansion_costs)
        bus_cmap = None
    elif node_color is None:
        bus_sizes = 0
        bus_colors = 'r'
        bus_cmap = None
    else:
        logging.warning('Choice for `node_color` is not valid. Default is '
                        'used instead.')
        bus_sizes = 0
        bus_colors = 'r'
        bus_cmap = None

    # convert bus coordinates to Mercator
    if contextily and background_map:
        inProj = Proj(init='epsg:4326')
        outProj = Proj(init='epsg:3857')
        x2, y2 = transform(inProj, outProj, list(pypsa_plot.buses.loc[:, 'x']),
                           list(pypsa_plot.buses.loc[:, 'y']))
        pypsa_plot.buses.loc[:, 'x'] = x2
        pypsa_plot.buses.loc[:, 'y'] = y2

    # plot
    plt.figure(figsize=(12, 8))
    ax = plt.gca()

    # plot grid district
    if grid_district_geom and geopandas:
        try:
            subst = pypsa_network.buses[pypsa_network.buses.index.str.contains(
                "MVStation")].index[0]
            subst_id = subst.split('_')[-1]
            projection = 3857 if contextily and background_map else 4326
            region = get_grid_district_polygon(configs,
                                               subst_id=subst_id,
                                               projection=projection)
            region.plot(ax=ax,
                        color='white',
                        alpha=0.2,
                        edgecolor='red',
                        linewidth=2)
        except Exception as e:
            logging.warning("Grid district geometry could not be plotted due "
                            "to the following error: {}".format(e))

    # if scaling factor is given s_nom is plotted as line width
    if scaling_factor_line_width is not None:
        line_width = pypsa_plot.lines.s_nom * scaling_factor_line_width
    else:
        line_width = 2
    cmap = plt.cm.get_cmap(lines_cmap)
    ll = pypsa_plot.plot(line_colors=line_colors,
                         line_cmap=cmap,
                         ax=ax,
                         title=title,
                         line_widths=line_width,
                         branch_components=['Line'],
                         basemap=True,
                         bus_sizes=bus_sizes,
                         bus_colors=bus_colors,
                         bus_cmap=bus_cmap)

    # color bar line loading
    if line_color == 'loading':
        if limits_cb_lines is None:
            limits_cb_lines = (min(line_colors), max(line_colors))
        v = np.linspace(limits_cb_lines[0], limits_cb_lines[1], 101)
        cb = plt.colorbar(ll[1], boundaries=v, ticks=v[0:101:10])
        cb.set_clim(vmin=limits_cb_lines[0], vmax=limits_cb_lines[1])
        cb.set_label('Line loading in p.u.')
    # color bar grid expansion costs
    elif line_color == 'expansion_costs':
        if limits_cb_lines is None:
            limits_cb_lines = (min(min(line_colors), min(bus_colors.values())),
                               max(max(line_colors), max(bus_colors.values())))
        v = np.linspace(limits_cb_lines[0], limits_cb_lines[1], 101)
        cb = plt.colorbar(ll[1], boundaries=v, ticks=v[0:101:10])
        cb.set_clim(vmin=limits_cb_lines[0], vmax=limits_cb_lines[1])
        cb.set_label('Grid expansion costs in kEUR')

    # color bar voltage
    if node_color == 'voltage':
        if limits_cb_nodes is None:
            limits_cb_nodes = (min(bus_colors.values()),
                               max(bus_colors.values()))
        v_voltage = np.linspace(limits_cb_nodes[0], limits_cb_nodes[1], 101)
        cb_voltage = plt.colorbar(ll[0],
                                  boundaries=v_voltage,
                                  ticks=v_voltage[0:101:10])
        cb_voltage.set_clim(vmin=limits_cb_nodes[0], vmax=limits_cb_nodes[1])
        cb_voltage.set_label('Voltage deviation in %')

    # storages
    if node_color == 'expansion_costs':
        ax.scatter(pypsa_plot.buses.loc[pypsa_network.storage_units.loc[:,
                                                                        'bus'],
                                        'x'],
                   pypsa_plot.buses.loc[pypsa_network.storage_units.loc[:,
                                                                        'bus'],
                                        'y'],
                   c='orangered',
                   s=pypsa_network.storage_units.loc[:, 'p_nom'] * 1000 / 3)
    # add legend for storage size and line capacity
    if (node_color == 'storage_integration' or
        node_color == 'expansion_costs') and \
            pypsa_network.storage_units.loc[:, 'p_nom'].any() > 0:
        scatter_handle = plt.scatter([], [],
                                     c='orangered',
                                     s=100,
                                     label='= 300 kW battery storage')
    else:
        scatter_handle = None
    if scaling_factor_line_width is not None:
        line_handle = plt.plot([], [],
                               c='black',
                               linewidth=scaling_factor_line_width * 10,
                               label='= 10 MVA')
    else:
        line_handle = None
    if scatter_handle and line_handle:
        plt.legend(handles=[scatter_handle, line_handle[0]],
                   labelspacing=1,
                   title='Storage size and line capacity',
                   borderpad=0.5,
                   loc=2,
                   framealpha=0.5,
                   fontsize='medium')
    elif scatter_handle:
        plt.legend(handles=[scatter_handle],
                   labelspacing=1,
                   title='Storage size',
                   borderpad=0.5,
                   loc=2,
                   framealpha=0.5,
                   fontsize='medium')
    elif line_handle:
        plt.legend(handles=[line_handle[0]],
                   labelspacing=1,
                   title='Line capacity',
                   borderpad=0.5,
                   loc=2,
                   framealpha=0.5,
                   fontsize='medium')

    # axes limits
    if xlim is not None:
        ax.set_xlim(xlim[0], xlim[1])
    if ylim is not None:
        ax.set_ylim(ylim[0], ylim[1])

    # hide axes labels
    ax.get_xaxis().set_visible(False)
    ax.get_yaxis().set_visible(False)

    # draw arrows on lines
    if arrows and timestep and line_color == 'loading':
        path = ll[1].get_segments()
        colors = cmap(ll[1].get_array() / 100)
        for i in range(len(path)):
            if pypsa_network.lines_t.p0.loc[timestep,
                                            line_colors.index[i]] > 0:
                arrowprops = dict(arrowstyle="->", color='b')  #colors[i])
            else:
                arrowprops = dict(arrowstyle="<-", color='b')  #colors[i])
            ax.annotate("",
                        xy=abs((path[i][0] - path[i][1]) * 0.51 - path[i][0]),
                        xytext=abs((path[i][0] - path[i][1]) * 0.49 -
                                   path[i][0]),
                        arrowprops=arrowprops,
                        size=10)

    # plot map data in background
    if contextily and background_map:
        try:
            add_basemap(ax, zoom=12)
        except Exception as e:
            logging.warning("Background map could not be plotted due to the "
                            "following error: {}".format(e))

    if filename is None:
        plt.show()
    else:
        plt.savefig(filename)
        plt.close()