Exemple #1
0
def average_every_nhours(net: pypsa.Network, offset: str, precision: int = 3) -> pypsa.Network:
    """
    Average all components snapshots to a given offset and update network snapshot weights

    Parameters
    ----------
    net: pypsa.Network
        PyPSA network
    offset: str
        Offset over which the mean is applied (e.g. 3H, 1D, etc.)
    precision: int (default: 3)
        Indicates at which decimal time series should be rounded
    """
    logger.info(f"Resampling the network to {offset}")
    net_agg = net.copy(with_time=False)

    snapshot_weightings = net.snapshot_weightings['objective'].resample(offset).sum()
    net_agg.set_snapshots(snapshot_weightings.index)
    net_agg.snapshot_weightings = snapshot_weightings

    for c in net.iterate_components():
        pnl = getattr(net_agg, c.list_name+"_t")
        for k, df in c.pnl.items():
            if not df.empty:
                pnl[k] = df.resample(offset).mean().round(precision)

    return net_agg
Exemple #2
0
 def update_network(n_clicks, value1, value2):
     print(value2)
     self.net = Network()
     self.net.import_from_csv_folder(f"{self.output_dir}{value1}/")
     self.current_link_id = self.net.links.index[0]
     self.current_bus_id = self.net.buses.index[0]
     self.selected_types = value2
     return get_map(), get_costs_table(), get_capacities(), get_total_generation(), get_load_gen()
Exemple #3
0
def remove_unconnected_components(net: pypsa.Network) -> pypsa.Network:
    """
    TODO: complete
    Parameters
    ----------
    net

    Returns
    -------

    Notes
    -----
    This function is originally copied from PyPSA-Eur script base_network.py.

    """
    _, labels = csgraph.connected_components(net.adjacency_matrix(),
                                             directed=False)
    component = pd.Series(labels, index=net.buses.index)

    component_sizes = component.value_counts()
    components_to_remove = component_sizes.iloc[1:]

    logger.info(
        "Removing {} unconnected network components with less than {} buses. In total {} buses."
        .format(len(components_to_remove), components_to_remove.max(),
                components_to_remove.sum()))

    return net[component == component_sizes.index[0]]
Exemple #4
0
def add_generators(network: pypsa.Network, tech: str) -> pypsa.Network:
    """
    Add conventional generators to a Network instance.

    Parameters
    ----------
    network: pypsa.Network
        A PyPSA Network instance with nodes associated to regions.
    tech: str
        Type of conventional generator (ccgt or ocgt)

    Returns
    -------
    network: pypsa.Network
        Updated network
    """
    logger.info(f"Adding {tech} generation.")

    assert hasattr(network.buses, "onshore_region"), "Some buses must be associated to an onshore region to add" \
                                                     "conventional generators."

    # Filter to keep only onshore buses
    # buses = network.buses[network.buses.onshore]
    buses = network.buses.dropna(subset=["onshore_region"], axis=0)

    capital_cost, marginal_cost = get_costs(
        tech, sum(network.snapshot_weightings['objective']))

    # Get fuel type and efficiency
    fuel, efficiency = get_tech_info(tech, ["fuel", "efficiency_ds"])

    network.madd("Generator",
                 buses.index,
                 suffix=f" Gen {tech}",
                 bus=buses.index,
                 p_nom_extendable=True,
                 type=tech,
                 carrier=fuel,
                 efficiency=efficiency,
                 marginal_cost=marginal_cost,
                 capital_cost=capital_cost,
                 x=buses.x.values,
                 y=buses.y.values)

    return network
Exemple #5
0
def init_pypsa_network(time_range_lim):
    """
    Instantiate PyPSA network
    Parameters
    ----------
    time_range_lim:
    Returns
    -------
    network: PyPSA network object
        Contains powerflow problem
    snapshots: iterable
        Contains snapshots to be analyzed by powerplow calculation
    """
    network = Network()
    network.set_snapshots(time_range_lim)
    snapshots = network.snapshots

    return network, snapshots
Exemple #6
0
def add_load_shedding(net: pypsa.Network,
                      load_df: pd.DataFrame) -> pypsa.Network:
    """
    Adding dummy-generators for load shedding.

    Parameters
    ----------
    net: pypsa.Network
        A Network instance with regions
    load_df: pd.DataFrame
        Frame containing load data.

    Returns
    -------
    network: pypsa.Network
        Updated network
    """

    tech_dir = f"{data_path}technologies/"
    fuel_info = pd.read_excel(join(tech_dir, 'fuel_info.xlsx'),
                              sheet_name='values',
                              index_col=0)

    onshore_buses = net.buses.dropna(subset=["onshore_region"], axis=0)

    # Get peak load and normalized load profile
    loads_max = load_df.max(axis=0)
    loads_pu = load_df.apply(lambda x: x / x.max(), axis=0)
    # Add generators for load shedding (prevents the model from being infeasible)

    net.madd("Generator",
             "Load shed " + onshore_buses.index,
             bus=onshore_buses.index,
             type="load",
             p_nom=loads_max.values,
             p_max_pu=loads_pu.values,
             x=net.buses.loc[onshore_buses.index].x.values,
             y=net.buses.loc[onshore_buses.index].y.values,
             marginal_cost=fuel_info.loc["load", "cost"])

    return net
Exemple #7
0
    def __init__(self, output_dir, test_number=None):

        # Load css
        css_folder = os.path.join(os.path.dirname(os.path.abspath(__file__)), "assets/")
        self.app = dash.Dash(__name__, assets_url_path=css_folder)

        # Load net
        # If no test_number is specified, take the last run
        self.current_test_number = test_number
        if self.current_test_number is None:
            self.current_test_number = sorted(os.listdir(output_dir))[-1]
        self.output_dir = output_dir
        self.net = Network()
        self.net.import_from_csv_folder(f"{self.output_dir}{self.current_test_number}/")
        if len(self.net.lines) != 0:
            self.current_line_id = self.net.lines.index[0]
        if len(self.net.links) != 0:
            self.current_link_id = self.net.links.index[0]
        self.current_bus_id = self.net.buses.index[0]

        self.selected_types = sorted(list(set(self.net.generators.type.values)))
Exemple #8
0
def cluster_on_extra_high_voltage(network, busmap, with_time=True):

    network_c = Network()

    buses = aggregatebuses(network, busmap, {
        'x': _leading(busmap, network.buses),
        'y': _leading(busmap, network.buses)
    })

    # keep attached lines
    lines = network.lines.copy()
    mask = lines.bus0.isin(buses.index)
    lines = lines.loc[mask, :]

    # keep attached transformer
    transformers = network.transformers.copy()
    mask = transformers.bus0.isin(buses.index)
    transformers = transformers.loc[mask, :]

    io.import_components_from_dataframe(network_c, buses, "Bus")
    io.import_components_from_dataframe(network_c, lines, "Line")
    io.import_components_from_dataframe(network_c, transformers, "Transformer")

    if with_time:
        network_c.now = network.now
        network_c.set_snapshots(network.snapshots)

    # dealing with generators
    network.generators['weight'] = 1
    new_df, new_pnl = aggregategenerators(network, busmap, with_time)
    io.import_components_from_dataframe(network_c, new_df, 'Generator')
    for attr, df in iteritems(new_pnl):
        io.import_series_from_dataframe(network_c, df, 'Generator', attr)

    # dealing with all other components
    aggregate_one_ports = components.one_port_components.copy()
    aggregate_one_ports.discard('Generator')

    for one_port in aggregate_one_ports:
        new_df, new_pnl = aggregateoneport(network,
                                           busmap,
                                           component=one_port,
                                           with_time=with_time)
        io.import_components_from_dataframe(network_c, new_df, one_port)
        for attr, df in iteritems(new_pnl):
            io.import_series_from_dataframe(network_c, df, one_port, attr)

    network_c.determine_network_topology()

    return network_c
Exemple #9
0
def simplify_network_to_380(net: pypsa.Network) -> (pypsa.Network, pd.Series):
    """
    TODO: complete

    Parameters
    ----------
    net

    Returns
    -------

    """

    logger.info("Mapping all network lines onto a single 380kV layer")

    net.buses['v_nom'] = 380.

    # Reset num_parallel, v_nom, type and s_nom for non 380kV lines
    linetype_380, = net.lines.loc[net.lines.v_nom == 380., 'type'].unique()
    non380_lines_b = net.lines.v_nom != 380.
    net.lines.loc[non380_lines_b, 'num_parallel'] *= (net.lines.loc[non380_lines_b, 'v_nom'] / 380.)**2
    net.lines.loc[non380_lines_b, 'v_nom'] = 380.
    net.lines.loc[non380_lines_b, 'type'] = linetype_380
    net.lines.loc[non380_lines_b, 's_nom'] = (
        np.sqrt(3) * net.lines['type'].map(net.line_types.i_nom) *
        net.lines.bus0.map(net.buses.v_nom) * net.lines.num_parallel
    )

    # Remove all transformers and merge buses at different voltage levels
    # Create a series associating the starting bus of each transformer to its end bus
    trafo_map = pd.Series(net.transformers.bus1.values, index=net.transformers.bus0.values)
    # Remove duplicate elements that have the same starting bus
    trafo_map = trafo_map[~trafo_map.index.duplicated(keep='first')]
    # Update ending bus it two transfos follow each other
    several_trafo_b = trafo_map.isin(trafo_map.index)
    trafo_map.loc[several_trafo_b] = trafo_map.loc[several_trafo_b].map(trafo_map)
    # Find buses without transfos starting on them and a transfo starting and finishing at those buses
    missing_buses_i = net.buses.index.difference(trafo_map.index)
    trafo_map = trafo_map.append(pd.Series(missing_buses_i, missing_buses_i))

    # Set containing {'Load', 'Generator', 'Store', 'StorageUnit', ShuntImpedance', 'Link', 'Line', 'Transformer'}
    # Update bus information in all components DataFrame
    for c in net.one_port_components | net.branch_components:
        df = net.df(c)
        for col in df.columns:
            if col.startswith('bus'):
                df[col] = df[col].map(trafo_map)

    # Remove all transformers
    net.mremove("Transformer", net.transformers.index)
    net.mremove("Bus", net.buses.index.difference(trafo_map))

    return net, trafo_map
Exemple #10
0
def get_topology(network: pypsa.Network,
                 countries: List[str] = None,
                 p_nom_extendable: bool = True,
                 extension_multiplier: float = None,
                 extension_base: str = 'GCA',
                 use_ex_line_cap: bool = True,
                 p_max_pu: float = 1.0,
                 plot: bool = False) -> pypsa.Network:
    """
    Load the e-highway network topology (buses and links) using PyPSA.

    Parameters
    ----------
    network: pypsa.Network
        Network instance
    countries: List[str] (default: None)
        List of ISO codes of countries for which we want the tyndp topology.
    p_nom_extendable: bool (default: True)
        Whether line capacity is allowed to be expanded
    extension_multiplier: float (default: None)
        By how much the capacity can be extended if extendable. If None, no limit on expansion.
    extension_base: str (default: GCA)
        TYNDP 2040 scenario to use as the basis for computing the max potential of links, can be one of:
        - ST (Sustainable Transition): targets reached by national regulation, emission trading schemes and subsidies,
                                       maximising the use of existing infrastructure
                                       ~ 180 GW and 94 TWkm
        - DG (Distributed Generation): prosumers at the centre - small-scale generation, batteries and fuel-switching
                                       society engaged and empowered
                                       ~ 190 GW and 99 TWkm
        - GCA (Global Climate Action): full-speed global decarbonisation, large-scale renewables
                                       ~ 200 GW and 103 TWkm
        The three scenarios are quite similar in terms of NTCs with GCA being the most generous.
    use_ex_line_cap: bool (default True)
        Whether to use existing line capacity
    p_max_pu: float (default: 1.0)
        Maximal dispatch per unit of p_nom
    plot: bool (default: False)
        Whether to show loaded topology or not

    Returns
    -------
    network: pypsa.Network
        Updated network
    """

    assert countries is None or len(countries) != 0, "Error: Countries list must not be empty. If you want to " \
                                                     "obtain, the full topology, don't pass anything as argument."
    assert extension_base in ['ST', 'DG', 'GCA'], f"Error: extension_base must be one of ST, DG or GCA, " \
                                                     f"received {extension_base}."

    topology_dir = f"{data_path}topologies/tyndp2018/generated/"
    buses_fn = f"{topology_dir}buses.csv"
    assert isfile(
        buses_fn), f"Error: Buses are undefined. Please run 'preprocess'."
    buses = pd.read_csv(buses_fn, index_col='id')
    links_fn = f"{topology_dir}links.csv"
    assert isfile(
        links_fn), f"Error: Links are undefined. Please run 'preprocess'."
    links = pd.read_csv(links_fn, index_col='id')

    if countries is not None:
        # Check if there is a bus for each country considered
        missing_countries = set(countries) - set(buses.index)
        assert not missing_countries, f"Error: No buses exist for the following countries: {missing_countries}"
        # Remove buses that are not associated with the considered countries
        buses = buses.loc[buses.index.isin(countries)]
    countries = buses.index

    # Converting polygons strings to Polygon object
    for region_type in ["onshore_region", "offshore_region"]:
        regions = buses[region_type].values
        # Convert strings
        for i, region in enumerate(regions):
            if isinstance(region, str):
                regions[i] = shapely.wkt.loads(region)

    # If we have only one bus, add it to the network and return
    if len(buses) == 1:
        network.import_components_from_dataframe(buses, "Bus")
        return network

    # Remove links for which one of the two end buses has been removed
    links = pd.DataFrame(links.loc[links.bus0.isin(buses.index)
                                   & links.bus1.isin(buses.index)])

    # Removing offshore buses that are not connected anymore
    connected_buses = sorted(list(
        set(links["bus0"]).union(set(links["bus1"]))))
    buses = buses.loc[connected_buses]

    disconnected_onshore_bus = set(countries) - set(buses.index)
    assert not disconnected_onshore_bus, f"Error: Buses {disconnected_onshore_bus} were disconnected."

    if not use_ex_line_cap:
        links['p_nom'] = 0
    links['p_nom_min'] = links['p_nom']
    links['p_max_pu'] = p_max_pu
    links['p_min_pu'] = -p_max_pu  # Making the link bi-directional
    links['p_nom_extendable'] = p_nom_extendable
    if p_nom_extendable:
        # Choose p_nom_max based on some TYNDP 2040 scenario
        if extension_base == 'ST':
            links['p_nom_max'] = links['p_nom_st']
        elif extension_base == 'DG':
            links['p_nom_max'] = links['p_nom_dg']
        else:
            links['p_nom_max'] = links['p_nom_gca']
        links = links.drop(['p_nom_st', 'p_nom_dg', 'p_nom_gca'], axis=1)
        if extension_multiplier is not None:
            links['p_nom_max'] = (links['p_nom_max'] *
                                  extension_multiplier).round(3)
            links['p_nom_max'] = links[['p_nom_max', 'p_nom_min']].max(axis=1)
        else:
            links['p_nom_max'] = "inf"
    links['capital_cost'] = pd.Series(index=links.index)
    for idx in links.index:
        carrier = links.loc[idx].carrier
        cap_cost, _ = get_costs(carrier,
                                sum(network.snapshot_weightings['objective']))
        links.loc[idx, ('capital_cost', )] = cap_cost * links.length.loc[idx]

    network.import_components_from_dataframe(buses, "Bus")
    network.import_components_from_dataframe(links, "Link")

    if plot:
        from epippy.topologies.core.plot import plot_topology
        plot_topology(buses, links)
        plt.show()

    return network
Exemple #11
0
def add_generators_in_grid_cells(net: pypsa.Network, technologies: List[str],
                                 region: str, spatial_resolution: float,
                                 use_ex_cap: bool = True, limit_max_cap: bool = True,
                                 min_cap_pot: List[float] = None) -> pypsa.Network:
    """
    Create VRES generators in every grid cells obtained from dividing a certain number of regions.

    Parameters
    ----------
    net: pypsa.Network
        A PyPSA Network instance with buses associated to regions
    technologies: List[str]
        Which technologies to add.
    region: str
        Region code defined in 'data_path'/geographics/region_definition.csv over which the network is defined.
    spatial_resolution: float
        Spatial resolution at which to define grid cells.
    use_ex_cap: bool (default: True)
        Whether to take into account existing capacity.
    limit_max_cap: bool (default: True)
        Whether to limit capacity expansion at each grid cell to a certain capacity potential.
    min_cap_pot: List[float] (default: None)
        List of thresholds per technology. Points with capacity potential under this threshold will be removed.


    Returns
    -------
    net: pypsa.Network
        Updated network

    Notes
    -----
    net.buses must have a 'region_onshore' if adding onshore technologies and a 'region_offshore' attribute
    if adding offshore technologies.
    """

    from resite.resite import Resite

    # Generate deployment sites using resite
    resite = Resite([region], technologies, [net.snapshots[0], net.snapshots[-1]], spatial_resolution)
    resite.build_data(use_ex_cap, min_cap_pot)

    for tech in technologies:

        points = resite.tech_points_dict[tech]
        onshore_tech = get_config_values(tech, ['onshore'])

        # Associate sites to buses (using the associated shapes)
        buses = net.buses.copy()
        region_type = 'onshore_region' if onshore_tech else 'offshore_region'
        buses = buses.dropna(subset=[region_type])
        associated_buses = match_points_to_regions(points, buses[region_type]).dropna()
        points = list(associated_buses.index)

        p_nom_max = 'inf'
        if limit_max_cap:
            p_nom_max = resite.data_dict["cap_potential_ds"][tech][points].values
        p_nom = resite.data_dict["existing_cap_ds"][tech][points].values
        p_max_pu = resite.data_dict["cap_factor_df"][tech][points].values

        capital_cost, marginal_cost = get_costs(tech, len(net.snapshots))

        net.madd("Generator",
                 pd.Index([f"Gen {tech} {x}-{y}" for x, y in points]),
                 bus=associated_buses.values,
                 p_nom_extendable=True,
                 p_nom_max=p_nom_max,
                 p_nom=p_nom,
                 p_nom_min=p_nom,
                 p_min_pu=0.,
                 p_max_pu=p_max_pu,
                 type=tech,
                 x=[x for x, _ in points],
                 y=[y for _, y in points],
                 marginal_cost=marginal_cost,
                 capital_cost=capital_cost)

    return net
Exemple #12
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
Exemple #13
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
Exemple #14
0
def add_res_at_sites(
    net: pypsa.Network,
    config,
    output_dir,
    eu_countries,
):

    eu_technologies = config['res']['techs']

    logger.info(f"Adding RES {eu_technologies} generation.")

    spatial_res = config["res"]["spatial_resolution"]
    use_ex_cap = config["res"]["use_ex_cap"]
    min_cap_pot = config["res"]["min_cap_pot"]
    min_cap_if_sel = config["res"]["min_cap_if_selected"]

    # Build sites for EU
    r_europe = Resite(eu_countries, eu_technologies,
                      [net.snapshots[0], net.snapshots[-1]], spatial_res,
                      min_cap_if_sel)
    regions_shapes = net.buses.loc[eu_countries,
                                   ["onshore_region", 'offshore_region']]
    regions_shapes.columns = ['onshore', 'offshore']
    r_europe.build_data(use_ex_cap, min_cap_pot, regions_shapes=regions_shapes)
    net.cc_ds = r_europe.data_dict["capacity_credit_ds"]

    # Build sites for other regions
    non_eu_res = config["non_eu"]
    all_remote_countries = []
    if non_eu_res is not None:
        for region in non_eu_res.keys():
            if region in ["na", "me"]:
                remote_countries = get_subregions(region)
            else:
                remote_countries = [region]
            all_remote_countries += remote_countries
            remote_techs = non_eu_res[region]
            r_remote = Resite(remote_countries, remote_techs,
                              [net.snapshots[0], net.snapshots[-1]],
                              spatial_res)
            regions_shapes = net.buses.loc[
                remote_countries, ["onshore_region", 'offshore_region']]
            regions_shapes.columns = ['onshore', 'offshore']
            r_remote.build_data(False,
                                compute_load=False,
                                regions_shapes=regions_shapes)

            # Add sites to European ones
            r_europe.regions += r_remote.regions
            r_europe.technologies = list(
                set(r_europe.technologies).union(r_remote.technologies))
            r_europe.min_cap_pot_dict = {
                **r_europe.min_cap_pot_dict,
                **r_remote.min_cap_pot_dict
            }
            r_europe.tech_points_tuples = np.concatenate(
                (r_europe.tech_points_tuples, r_remote.tech_points_tuples))
            r_europe.initial_sites_ds = r_europe.initial_sites_ds.append(
                r_remote.initial_sites_ds)
            r_europe.tech_points_regions_ds = \
                r_europe.tech_points_regions_ds.append(r_remote.tech_points_regions_ds)
            r_europe.data_dict["load"] = pd.concat(
                [r_europe.data_dict["load"], r_remote.data_dict["load"]],
                axis=1)
            r_europe.data_dict["cap_potential_ds"] = \
                r_europe.data_dict["cap_potential_ds"].append(r_remote.data_dict["cap_potential_ds"])
            r_europe.data_dict["existing_cap_ds"] = \
                r_europe.data_dict["existing_cap_ds"].append(r_remote.data_dict["existing_cap_ds"])
            r_europe.data_dict["cap_factor_df"] = \
                pd.concat([r_europe.data_dict["cap_factor_df"], r_remote.data_dict["cap_factor_df"]], axis=1)

    # Update dictionary
    tech_points_dict = {}
    techs = set(r_europe.initial_sites_ds.index.get_level_values(0))
    for tech in techs:
        tech_points_dict[tech] = list(r_europe.initial_sites_ds[tech].index)
    r_europe.tech_points_dict = tech_points_dict

    # Do siting if required
    if config["res"]["strategy"] == "siting":
        logger.info('resite model being built.')
        siting_params = config['res']
        # if siting_params['formulation'] == "min_cost_global":
        #    siting_params['formulation_params']['perc_per_region'] = \
        #        siting_params['formulation_params']['perc_per_region'] + [0.] * len(all_remote_countries)
        r_europe.build_model(siting_params["modelling"],
                             siting_params['formulation'],
                             siting_params['formulation_params'],
                             siting_params['write_lp'], f"{output_dir}resite/")

        logger.info('Sending resite to solver.')
        r_europe.init_output_folder(f"{output_dir}resite/")
        r_europe.solve_model(f"{output_dir}resite/",
                             solver=config['solver'],
                             solver_options=config['solver_options'])

        logger.info("Saving resite results")
        r_europe.retrieve_selected_sites_data()
        r_europe.save(f"{output_dir}resite/")

        # Add solution to network
        logger.info('Retrieving resite results.')
        tech_location_dict = r_europe.sel_tech_points_dict
        existing_cap_ds = r_europe.sel_data_dict["existing_cap_ds"]
        cap_potential_ds = r_europe.sel_data_dict["cap_potential_ds"]
        cap_factor_df = r_europe.sel_data_dict["cap_factor_df"]

        if not r_europe.timestamps.equals(net.snapshots):
            # If network snapshots is a subset of resite snapshots just crop the data
            missing_timestamps = set(net.snapshots) - set(r_europe.timestamps)
            if not missing_timestamps:
                cap_factor_df = cap_factor_df.loc[net.snapshots]
            else:
                # In other case, need to recompute capacity factors
                raise NotImplementedError(
                    "Error: Network snapshots must currently be a subset of resite snapshots."
                )

    else:  # no siting
        tech_location_dict = r_europe.tech_points_dict
        existing_cap_ds = r_europe.data_dict["existing_cap_ds"]
        cap_potential_ds = r_europe.data_dict["cap_potential_ds"]
        cap_factor_df = r_europe.data_dict["cap_factor_df"]

    for tech, points in tech_location_dict.items():

        onshore_tech = get_config_values(tech, ['onshore'])

        # Associate sites to buses (using the associated shapes)
        buses = net.buses.copy()
        region_type = 'onshore_region' if onshore_tech else 'offshore_region'
        buses = buses.dropna(subset=[region_type])
        associated_buses = match_points_to_regions(
            points, buses[region_type]).dropna()
        points = list(associated_buses.index)

        p_nom_max = 'inf'
        if config['res']['limit_max_cap']:
            p_nom_max = cap_potential_ds[tech][points].values
        p_nom = existing_cap_ds[tech][points].values
        p_max_pu = cap_factor_df[tech][points].values

        capital_cost, marginal_cost = get_costs(
            tech, sum(net.snapshot_weightings['objective']))

        net.madd("Generator",
                 pd.Index([f"Gen {tech} {x}-{y}" for x, y in points]),
                 bus=associated_buses.values,
                 p_nom_extendable=True,
                 p_nom_max=p_nom_max,
                 p_nom=p_nom,
                 p_nom_min=p_nom,
                 p_min_pu=0.,
                 p_max_pu=p_max_pu,
                 type=tech,
                 x=[x for x, _ in points],
                 y=[y for _, y in points],
                 marginal_cost=marginal_cost,
                 capital_cost=capital_cost)

    return net
Exemple #15
0
def replace_su_closed_loop(network: pypsa.Network, su_to_replace: str) -> None:
    """
    Convert a PyPSA Storage Unit to a Store with additional components
     so that power and energy can be sized independently.

    Parameters
    ----------
    network: pypsa.Network
        PyPSA network
    su_to_replace: str
        Name of the storage unit to be replaced

    """

    su = network.storage_units.loc[su_to_replace]

    su_short_name = su_to_replace.split(' ')[-1]
    bus_name = f"{su['bus']} {su_short_name}"
    link_1_name = f"{su['bus']} link {su_short_name} to AC"
    link_2_name = f"{su['bus']} link AC to {su_short_name}"
    store_name = f"{bus_name} store"

    # add bus
    network.add("Bus", bus_name)

    # add discharge link
    network.add("Link",
                link_1_name,
                bus0=bus_name,
                bus1=su["bus"],
                capital_cost=su["capital_cost"],
                marginal_cost=su["marginal_cost"],
                p_nom_extendable=su["p_nom_extendable"],
                p_nom=su["p_nom"] / su["efficiency_dispatch"],
                p_nom_min=su["p_nom_min"] / su["efficiency_dispatch"],
                p_nom_max=su["p_nom_max"] / su["efficiency_dispatch"],
                p_max_pu=su["p_max_pu"],
                efficiency=su["efficiency_dispatch"])

    # add charge link
    network.add("Link",
                link_2_name,
                bus1=bus_name,
                bus0=su["bus"],
                p_nom=su["p_nom"],
                p_nom_extendable=su["p_nom_extendable"],
                p_nom_min=su["p_nom_min"],
                p_nom_max=su["p_nom_max"],
                p_max_pu=-su["p_min_pu"],
                efficiency=su["efficiency_store"])

    # add store
    network.add("Store",
                store_name,
                bus=bus_name,
                capital_cost=su["capital_cost_e"],
                marginal_cost=su["marginal_cost_e"],
                e_nom=su["p_nom"] * su["max_hours"],
                e_nom_min=su["p_nom_min"] / su["efficiency_dispatch"] *
                su["max_hours"],
                e_nom_max=su["p_nom_max"] / su["efficiency_dispatch"] *
                su["max_hours"],
                e_nom_extendable=su["p_nom_extendable"],
                e_max_pu=1.,
                e_min_pu=0.,
                standing_loss=su["standing_loss"],
                e_cyclic=su['cyclic_state_of_charge'])

    network.remove("StorageUnit", su_to_replace)
Exemple #16
0
def add_generators_using_siting(net: pypsa.Network, technologies: List[str],
                                region: str, siting_params: Dict[str, Any],
                                use_ex_cap: bool = True, limit_max_cap: bool = True,
                                output_dir: str = None) -> pypsa.Network:
    """
    Add generators for different technologies at a series of location selected via an optimization mechanism.

    Parameters
    ----------
    net: pypsa.Network
        A network with defined buses.
    technologies: List[str]
        Which technologies to add using this methodology
    siting_params: Dict[str, Any]
        Set of parameters necessary for siting.
    region: str
        Region over which the network is defined
    use_ex_cap: bool (default: True)
        Whether to take into account existing capacity.
    limit_max_cap: bool (default: True)
        Whether to limit capacity expansion at each grid cell to a certain capacity potential.
    output_dir: str
        Absolute path to directory where resite output should be stored

    Returns
    -------
    net: pypsa.Network
        Updated network

    Notes
    -----
    net.buses must have a 'region_onshore' if adding onshore technologies and a 'region_offshore' attribute
    if adding offshore technologies.
    """

    for param in ["timeslice", "spatial_resolution", "modelling", "formulation", "formulation_params", "write_lp"]:
        assert param in siting_params, f"Error: Missing parameter {param} for siting."

    from resite.resite import Resite

    logger.info('Setting up resite.')
    resite = Resite([region], technologies, siting_params["timeslice"], siting_params["spatial_resolution"],
                    siting_params["min_cap_if_selected"])
    resite.build_data(use_ex_cap)

    logger.info('resite model being built.')
    resite.build_model(siting_params["modelling"], siting_params['formulation'], siting_params['formulation_params'],
                       siting_params['write_lp'], output_dir)

    logger.info('Sending resite to solver.')
    resite.solve_model(solver_options=siting_params['solver_options'], solver=siting_params['solver'])

    logger.info('Retrieving resite results.')
    resite.retrieve_selected_sites_data()
    tech_location_dict = resite.sel_tech_points_dict
    existing_cap_ds = resite.sel_data_dict["existing_cap_ds"]
    cap_potential_ds = resite.sel_data_dict["cap_potential_ds"]
    cap_factor_df = resite.sel_data_dict["cap_factor_df"]

    logger.info("Saving resite results")
    resite.save(output_dir)

    if not resite.timestamps.equals(net.snapshots):
        # If network snapshots is a subset of resite snapshots just crop the data
        missing_timestamps = set(net.snapshots) - set(resite.timestamps)
        if not missing_timestamps:
            cap_factor_df = cap_factor_df.loc[net.snapshots]
        else:
            # In other case, need to recompute capacity factors
            raise NotImplementedError("Error: Network snapshots must currently be a subset of resite snapshots.")

    for tech, points in tech_location_dict.items():

        onshore_tech = get_config_values(tech, ['onshore'])

        # Associate sites to buses (using the associated shapes)
        buses = net.buses.copy()
        region_type = 'onshore_region' if onshore_tech else 'offshore_region'
        buses = buses.dropna(subset=[region_type])
        associated_buses = match_points_to_regions(points, buses[region_type]).dropna()
        points = list(associated_buses.index)

        p_nom_max = 'inf'
        if limit_max_cap:
            p_nom_max = cap_potential_ds[tech][points].values
        p_nom = existing_cap_ds[tech][points].values
        p_max_pu = cap_factor_df[tech][points].values

        capital_cost, marginal_cost = get_costs(tech, len(net.snapshots))

        net.madd("Generator",
                 pd.Index([f"Gen {tech} {x}-{y}" for x, y in points]),
                 bus=associated_buses.values,
                 p_nom_extendable=True,
                 p_nom_max=p_nom_max,
                 p_nom=p_nom,
                 p_nom_min=p_nom,
                 p_min_pu=0.,
                 p_max_pu=p_max_pu,
                 type=tech,
                 x=[x for x, _ in points],
                 y=[y for _, y in points],
                 marginal_cost=marginal_cost,
                 capital_cost=capital_cost)

    return net
Exemple #17
0
        output_dir = f'../output/{topology}/{run_id}/'
        config_fn = join(output_dir, 'config.yaml')

        config = yaml.load(open(config_fn, 'r'), Loader=yaml.FullLoader)
        run_name = config["res"]["sites_dir"] + "_" + \
                   config["res"]["sites_fn"].split("_")[0].upper()

        name = run_name.split("_")[-3:]
        if "MAX" in name:
            name = "PROD"
        else:
            name = "COMP_c = " + name[1][1:] + "_" + name[0]

        run_names.append(name)

        net = Network()
        net.import_from_csv_folder(output_dir)
        nets += [net]

        with open(join(output_dir, "solver.log"), 'r') as f:
            for line in f:
                if "objective" in line:
                    objectives[name] = float(line.split(' ')[-1]) * 1e-5

    caption = ", ".join(run_name.split('_')[:-2])

    table = generate_costs_table(nets, run_names, [
        "ccgt", "wind_offshore", "wind_onshore", "pv_utility",
        "pv_residential", "AC", "DC", "Li-ion"
    ])
    text = convert_cost_table_to_latex(table, objectives, caption)
Exemple #18
0
def add_phs_plants(net: pypsa.Network,
                   topology_type: str = "countries",
                   extendable: bool = False,
                   cyclic_sof: bool = True) -> pypsa.Network:
    """
    Add pumped-hydro storage units to a PyPSA Network instance.

    Parameters
    ----------
    net: pypsa.Network
        A Network instance.
    topology_type: str
        Can currently be countries (for one node per country topologies) or ehighway (for topologies based on ehighway)
    extendable: bool (default: False)
        Whether generators are extendable
    cyclic_sof: bool (default: True)
        Whether to set to True the cyclic_state_of_charge for the storage_unit component

    Returns
    -------
    net: pypsa.Network
        Updated network
    """
    check_assertions(net, topology_type)

    # Hydro generators can only be added onshore
    buses_onshore = net.buses.dropna(subset=["onshore_region"], axis=0)

    # Load capacities
    aggr_level = "countries" if topology_type == "countries" else "NUTS3"
    pow_cap, en_cap = get_phs_capacities(aggr_level)

    if topology_type == 'countries':
        # Extract only countries for which data is available
        countries_with_capacity = sorted(
            list(set(buses_onshore.country) & set(pow_cap.index)))
        buses_with_capacity_indexes = net.buses[net.buses.country.isin(
            countries_with_capacity)].index
        bus_pow_cap = pow_cap.loc[countries_with_capacity]
        bus_pow_cap.index = buses_with_capacity_indexes
        bus_en_cap = en_cap.loc[countries_with_capacity]
        bus_en_cap.index = buses_with_capacity_indexes
    else:  # topology_type == 'ehighway
        bus_pow_cap, bus_en_cap = phs_inputs_nuts_to_ehighway(
            buses_onshore.index, pow_cap, en_cap)
        countries_with_capacity = set(bus_pow_cap.index.str[2:])

    logger.info(
        f"Adding {bus_pow_cap.sum():.3f} GW of PHS hydro "
        f"with {bus_en_cap.sum():.3f} GWh of storage in {countries_with_capacity}."
    )

    max_hours = bus_en_cap / bus_pow_cap

    # Get cost and efficiencies
    capital_cost, marginal_cost = get_costs(
        'phs', sum(net.snapshot_weightings['objective']))
    efficiency_dispatch, efficiency_store, self_discharge = \
        get_tech_info('phs', ["efficiency_ds", "efficiency_ch", "efficiency_sd"])
    self_discharge = round(1 - self_discharge, 4)

    net.madd("StorageUnit",
             bus_pow_cap.index,
             suffix=" Storage PHS",
             bus=bus_pow_cap.index,
             type='phs',
             p_nom=bus_pow_cap,
             p_nom_min=bus_pow_cap,
             p_nom_extendable=extendable,
             max_hours=max_hours.values,
             capital_cost=capital_cost,
             marginal_cost=marginal_cost,
             efficiency_store=efficiency_store,
             efficiency_dispatch=efficiency_dispatch,
             self_discharge=self_discharge,
             cyclic_state_of_charge=cyclic_sof,
             x=buses_onshore.loc[bus_pow_cap.index].x,
             y=buses_onshore.loc[bus_pow_cap.index].y)

    return net
Exemple #19
0
    def construct_partial_network(self, cluster, scenario):
        """
        Compute the partial network that has been merged into a single cluster.
        The resulting network retains the external cluster buses that
        share some line with the cluster identified by `cluster`.
        These external buses will be prefixed by self.id_prefix in order to
        prevent name clashes with buses in the disaggregation

        :param cluster: Index of the cluster to disaggregate
        :return: Tuple of (partial_network, external_buses) where
        `partial_network` is the result of the partial decomposition
        and `external_buses` represent clusters adjacent to `cluster` that may
        be influenced by calculations done on the partial network.
        """

        #Create an empty network
        partial_network = Network()

        # find all lines that have at least one bus inside the cluster
        busflags = (self.buses['cluster'] == cluster)

        def is_bus_in_cluster(conn):
            return busflags[conn]

        # Copy configurations to new network
        partial_network.snapshots = self.original_network.snapshots
        partial_network.snapshot_weightings = (
            self.original_network.snapshot_weightings)
        partial_network.carriers = self.original_network.carriers

        # Collect all connectors that have some node inside the cluster

        external_buses = pd.DataFrame()

        line_types = ['lines', 'links', 'transformers']
        for line_type in line_types:
            # Copy all lines that reside entirely inside the cluster ...
            setattr(
                partial_network, line_type,
                filter_internal_connector(
                    getattr(self.original_network, line_type),
                    is_bus_in_cluster))

            # ... and their time series
            # TODO: These are all time series, not just the ones from lines
            #       residing entirely in side the cluster.
            #       Is this a problem?
            setattr(partial_network, line_type + '_t',
                    getattr(self.original_network, line_type + '_t'))

            # Copy all lines whose `bus0` lies within the cluster
            left_external_connectors = filter_left_external_connector(
                getattr(self.original_network, line_type), is_bus_in_cluster)

            if not left_external_connectors.empty:
                f = lambda x: self.idx_prefix + self.clustering.busmap.loc[x]
                ca_option = pd.get_option('mode.chained_assignment')
                pd.set_option('mode.chained_assignment', None)
                left_external_connectors.loc[:, 'bus0'] = (
                    left_external_connectors.loc[:, 'bus0'].apply(f))
                pd.set_option('mode.chained_assignment', ca_option)
                external_buses = pd.concat(
                    (external_buses, left_external_connectors.bus0))

            # Copy all lines whose `bus1` lies within the cluster
            right_external_connectors = filter_right_external_connector(
                getattr(self.original_network, line_type), is_bus_in_cluster)
            if not right_external_connectors.empty:
                f = lambda x: self.idx_prefix + self.clustering.busmap.loc[x]
                ca_option = pd.get_option('mode.chained_assignment')
                pd.set_option('mode.chained_assignment', None)
                right_external_connectors.loc[:, 'bus1'] = (
                    right_external_connectors.loc[:, 'bus1'].apply(f))
                pd.set_option('mode.chained_assignment', ca_option)
                external_buses = pd.concat(
                    (external_buses, right_external_connectors.bus1))

        # Collect all buses that are contained in or somehow connected to the
        # cluster

        buses_in_lines = self.buses[busflags].index

        bus_types = [
            'loads', 'generators', 'stores', 'storage_units',
            'shunt_impedances'
        ]

        # Copy all values that are part of the cluster
        partial_network.buses = self.original_network.buses[
            self.original_network.buses.index.isin(buses_in_lines)]

        # Collect all buses that are external, but connected to the cluster ...
        externals_to_insert = self.clustered_network.buses[
            self.clustered_network.buses.index.isin(
                map(lambda x: x[0][len(self.idx_prefix):],
                    external_buses.values))]

        # ... prefix them to avoid name clashes with buses from the original
        # network ...
        self.reindex_with_prefix(externals_to_insert)

        # .. and insert them as well as their time series
        partial_network.buses = (
            partial_network.buses.append(externals_to_insert))
        partial_network.buses_t = self.original_network.buses_t

        # TODO: Rename `bustype` to on_bus_type
        for bustype in bus_types:
            # Copy loads, generators, ... from original network to network copy
            setattr(
                partial_network, bustype,
                filter_buses(getattr(self.original_network, bustype),
                             buses_in_lines))

            # Collect on-bus components from external, connected clusters
            buses_to_insert = filter_buses(
                getattr(self.clustered_network, bustype),
                map(lambda x: x[0][len(self.idx_prefix):],
                    external_buses.values))

            # Prefix their external bindings
            buses_to_insert.loc[:, 'bus'] = (self.idx_prefix +
                                             buses_to_insert.loc[:, 'bus'])

            setattr(partial_network, bustype,
                    getattr(partial_network, bustype).append(buses_to_insert))

            # Also copy their time series
            setattr(partial_network, bustype + '_t',
                    getattr(self.original_network, bustype + '_t'))
            # Note: The code above copies more than necessary, because it
            #       copies every time series for `bustype` from the original
            #       network and not only the subset belonging to the partial
            #       network. The commented code below tries to filter the time
            #       series accordingly, but there must be bug somewhere because
            #       using it, the time series in the clusters and sums of the
            #       time series after disaggregation don't match up.
            """
            series = getattr(self.original_network, bustype + '_t')
            partial_series = type(series)()
            for s in series:
                partial_series[s] = series[s].loc[
                        :,
                        getattr(partial_network, bustype)
                        .index.intersection(series[s].columns)]
            setattr(partial_network, bustype + '_t', partial_series)
            """

        # Just a simple sanity check
        # TODO: Remove when sure that disaggregation will not go insane anymore
        for line_type in line_types:
            assert (getattr(partial_network, line_type).bus0.isin(
                partial_network.buses.index).all())
            assert (getattr(partial_network, line_type).bus1.isin(
                partial_network.buses.index).all())

        return partial_network, external_buses
Exemple #20
0
def add_sto_plants(net: pypsa.Network,
                   topology_type: str = "countries",
                   extendable: bool = False,
                   cyclic_sof: bool = True) -> pypsa.Network:
    """
    Add run-of-river generators to a Network instance

    Parameters
    ----------
    net: pypsa.Network
        A Network instance.
    topology_type: str
        Can currently be countries (for one node per country topologies) or ehighway (for topologies based on ehighway)
    extendable: bool (default: False)
        Whether generators are extendable
    cyclic_sof: bool (default: True)
        Whether to set to True the cyclic_state_of_charge for the storage_unit component

    Returns
    -------
    net: pypsa.Network
        Updated network
    """
    check_assertions(net, topology_type)

    # Hydro generators can only be added onshore
    buses_onshore = net.buses.dropna(subset=["onshore_region"], axis=0)

    # Load capacities and inflows
    aggr_level = "countries" if topology_type == "countries" else "NUTS3"
    pow_cap, en_cap = get_sto_capacities(aggr_level)
    inflows = get_sto_inflows(aggr_level, net.snapshots)

    if topology_type == 'countries':
        # Extract only countries for which data is available
        countries_with_capacity = sorted(
            list(set(buses_onshore.country) & set(pow_cap.index)))
        buses_with_capacity_indexes = net.buses[net.buses.country.isin(
            countries_with_capacity)].index
        bus_pow_cap = pow_cap.loc[countries_with_capacity]
        bus_pow_cap.index = buses_with_capacity_indexes
        bus_en_cap = en_cap.loc[countries_with_capacity]
        bus_en_cap.index = buses_with_capacity_indexes
        bus_inflows = inflows[countries_with_capacity]
        bus_inflows.columns = buses_with_capacity_indexes
    else:  # topology_type == 'ehighway'
        bus_pow_cap, bus_en_cap, bus_inflows = \
            sto_inputs_nuts_to_ehighway(buses_onshore.index, pow_cap, en_cap, inflows)
        countries_with_capacity = set(bus_pow_cap.index.str[2:])

    logger.info(
        f"Adding {bus_pow_cap.sum():.2f} GW of STO hydro "
        f"with {bus_en_cap.sum() * 1e-3:.2f} TWh of storage in {countries_with_capacity}."
    )
    bus_inflows = bus_inflows.round(3)

    max_hours = bus_en_cap / bus_pow_cap

    capital_cost, marginal_cost = get_costs(
        'sto', sum(net.snapshot_weightings['objective']))

    # Get efficiencies
    efficiency_dispatch = get_tech_info('sto',
                                        ['efficiency_ds'])["efficiency_ds"]

    net.madd("StorageUnit",
             bus_pow_cap.index,
             suffix=" Storage reservoir",
             bus=bus_pow_cap.index,
             type='sto',
             p_nom=bus_pow_cap,
             p_nom_min=bus_pow_cap,
             p_min_pu=0.,
             p_nom_extendable=extendable,
             capital_cost=capital_cost,
             marginal_cost=marginal_cost,
             efficiency_store=0.,
             efficiency_dispatch=efficiency_dispatch,
             cyclic_state_of_charge=cyclic_sof,
             max_hours=max_hours,
             inflow=bus_inflows,
             x=buses_onshore.loc[bus_pow_cap.index.values].x,
             y=buses_onshore.loc[bus_pow_cap.index.values].y)

    return net
Exemple #21
0
def add_ror_plants(net: pypsa.Network,
                   topology_type: str = "countries",
                   extendable: bool = False) -> pypsa.Network:
    """
    Add run-of-river generators to a Network instance.

    Parameters
    ----------
    net: pypsa.Network
        A Network instance.
    topology_type: str
        Can currently be countries (for one node per country topologies) or ehighway (for topologies based on ehighway)
    extendable: bool (default: False)
        Whether generators are extendable

    Returns
    -------
    net: pypsa.Network
        Updated network
    """
    check_assertions(net, topology_type)

    # Hydro generators can only be added onshore
    buses_onshore = net.buses.dropna(subset=["onshore_region"], axis=0)

    # Load capacities and inflows
    aggr_level = "countries" if topology_type == "countries" else "NUTS3"
    pow_cap = get_ror_capacities(aggr_level)
    inflows = get_ror_inflows(aggr_level, net.snapshots)

    if topology_type == 'countries':
        # Extract only countries for which data is available
        countries_with_capacity = sorted(
            list(set(buses_onshore.country) & set(pow_cap.index)))
        buses_with_capacity_indexes = net.buses[net.buses.country.isin(
            countries_with_capacity)].index
        bus_pow_cap = pow_cap.loc[countries_with_capacity]
        bus_pow_cap.index = buses_with_capacity_indexes
        bus_inflows = inflows[countries_with_capacity]
        bus_inflows.columns = buses_with_capacity_indexes
    else:  # topology_type == 'ehighway'
        bus_pow_cap, bus_inflows = \
            ror_inputs_nuts_to_ehighway(buses_onshore.index, pow_cap, inflows)
        countries_with_capacity = set(bus_pow_cap.index.str[2:])

    logger.info(
        f"Adding {bus_pow_cap.sum():.2f} GW of ROR hydro in {countries_with_capacity}."
    )

    bus_inflows = bus_inflows.dropna().round(3)

    # Get cost and efficiencies
    capital_cost, marginal_cost = get_costs(
        'ror', sum(net.snapshot_weightings['objective']))
    efficiency = get_tech_info('ror', ["efficiency_ds"])["efficiency_ds"]

    net.madd("Generator",
             bus_pow_cap.index,
             suffix=" Generator ror",
             bus=bus_pow_cap.index,
             type='ror',
             p_nom=bus_pow_cap,
             p_nom_min=bus_pow_cap,
             p_nom_extendable=extendable,
             capital_cost=capital_cost,
             marginal_cost=marginal_cost,
             efficiency=efficiency,
             p_min_pu=0.,
             p_max_pu=bus_inflows,
             x=buses_onshore.loc[bus_pow_cap.index].x,
             y=buses_onshore.loc[bus_pow_cap.index].y)

    return net
Exemple #22
0
def add_batteries(network: pypsa.Network,
                  battery_type: str,
                  buses_ids: List[str] = None,
                  fixed_duration: bool = False) -> pypsa.Network:
    """
    Add a battery at each node of the network.

    Parameters
    ----------
    network: pypsa.Network
        PyPSA network
    battery_type: str
        Type of battery to add
    buses_ids: List[str]
        IDs of the buses at which we want to add batteries.
    fixed_duration: bool
        Whether the battery storage is modelled with fixed duration.

    Returns
    -------
    network: pypsa.Network
        Updated network

    """
    logger.info(f"Adding {battery_type} storage.")

    buses = network.buses
    if buses_ids is not None:
        buses = buses.loc[buses_ids]

    # buses = network.buses[network.buses.onshore]
    # onshore_bus_indexes = pd.Index([bus_id for bus_id in buses.index if buses.loc[bus_id].onshore])
    onshore_buses = buses.dropna(subset=["onshore_region"], axis=0)

    # Add batteries with fixed energy-power ratio
    if fixed_duration:

        capital_cost, marginal_cost = get_costs(
            battery_type, sum(network.snapshot_weightings['objective']))
        efficiency_dispatch, efficiency_store, self_discharge = \
            get_tech_info(battery_type, ["efficiency_ds", "efficiency_ch", "efficiency_sd"])
        self_discharge = round(1 - self_discharge, 4)

        # Get max number of hours of storage
        max_hours = get_config_values(battery_type, ["max_hours"])

        network.madd("StorageUnit",
                     onshore_buses.index,
                     suffix=f" StorageUnit {battery_type}",
                     type=battery_type,
                     bus=onshore_buses.index,
                     p_nom_extendable=True,
                     max_hours=max_hours,
                     capital_cost=capital_cost,
                     marginal_cost=marginal_cost,
                     efficiency_dispatch=efficiency_dispatch,
                     efficiency_store=efficiency_store,
                     standing_loss=self_discharge)

    # Add batteries where energy and power are sized independently
    else:

        battery_type_power = battery_type + '_p'
        battery_type_energy = battery_type + '_e'

        capital_cost, marginal_cost = get_costs(
            battery_type_power, sum(network.snapshot_weightings['objective']))
        capital_cost_e, marginal_cost_e = get_costs(
            battery_type_energy, sum(network.snapshot_weightings['objective']))
        efficiency_dispatch, efficiency_store = get_tech_info(
            battery_type_power, ["efficiency_ds", "efficiency_ch"])
        self_discharge = get_tech_info(battery_type_energy,
                                       ["efficiency_sd"]).astype(float)
        self_discharge = round(1 - self_discharge.values[0], 4)
        ctd_ratio = get_config_values(battery_type_power, ["ctd_ratio"])

        network.madd("StorageUnit",
                     onshore_buses.index,
                     suffix=f" StorageUnit {battery_type}",
                     type=battery_type,
                     bus=onshore_buses.index,
                     p_nom_extendable=True,
                     capital_cost=capital_cost,
                     marginal_cost=marginal_cost,
                     capital_cost_e=capital_cost_e,
                     marginal_cost_e=marginal_cost_e,
                     efficiency_dispatch=efficiency_dispatch,
                     efficiency_store=efficiency_store,
                     standing_loss=self_discharge,
                     ctd_ratio=ctd_ratio)

        storages = network.storage_units.index[network.storage_units.type ==
                                               battery_type]
        for storage_to_replace in storages:
            replace_su_closed_loop(network, storage_to_replace)

    return network
Exemple #23
0
    topology = 'tyndp2018'
    main_output_dir = f'../output/{topology}/'

    results = 'compare'  # 'compare', 'single'

    resolution = 'H'
    start = datetime(2015, 12, 13, 0, 0, 0)
    end = datetime(2015, 12, 19, 23, 0, 0)

    if results == 'single':

        run_id = '20200429_123543'

        output_dir = f"{main_output_dir}{run_id}/"

        net = Network()
        net.import_from_csv_folder(output_dir)

        pprp = SizingResultsSingleNet(net, start, end, resolution)

        pprp.make_plots_one_net()

    else:

        # first_run_id = '20200429_123543'
        # second_run_id = '20200429_132039'
        first_run_id = '20200429_123543'
        second_run_id = '20200429_154728'

        first_output_dir = f"{main_output_dir}{first_run_id}/"
        second_output_dir = f"{main_output_dir}{second_run_id}/"
Exemple #24
0
def add_generators(net: pypsa.Network,
                   countries: List[str],
                   use_ex_cap: bool = True,
                   extendable: bool = False) -> pypsa.Network:
    """
    Add nuclear generators to a PyPsa Network instance.

    Parameters
    ----------
    net: pypsa.Network
        A Network instance with nodes associated to parameters: 'onshore' and 'region'.
    countries: List[str]
        Codes of countries over which the network is built
    use_ex_cap: bool (default: True)
        Whether to consider existing capacity or not
    extendable: bool (default: False)
        Whether generators are extendable

    Returns
    -------
    network: pypsa.Network
        Updated network
    """

    for attr in ["onshore_region"]:
        assert hasattr(
            net.buses,
            attr), f"Error: Buses must contain a '{attr}' attribute."

    # Nuclear plants can only be added onshore
    # onshore_buses = net.buses[net.buses.onshore]
    onshore_buses = net.buses.dropna(subset=["onshore_region"], axis=0)
    if len(onshore_buses) == 0:
        warn(
            "Warning: Trying to add nuclear to network without onshore buses.")
        return net

    gens = get_powerplants('nuclear', countries)
    buses_countries = list(onshore_buses.country) if hasattr(
        onshore_buses, 'country') else None
    gens["bus_id"] = match_powerplants_to_regions(
        gens,
        onshore_buses.onshore_region,
        shapes_countries=buses_countries,
        dist_threshold=50.0)

    # If no plants in the chosen countries, return directly the network
    if len(gens) == 0:
        return net

    logger.info(
        f"Adding {gens['Capacity'].sum() * 1e-3:.2f} GW of nuclear capacity "
        f"in {sorted(gens['ISO2'].unique())}.")

    if not use_ex_cap:
        gens.Capacity = 0.
    gens.Capacity /= 1000.  # Convert MW to GW

    capital_cost, marginal_cost = get_costs(
        'nuclear', sum(net.snapshot_weightings['objective']))

    # Get fuel type, efficiency and ramp rates
    fuel, efficiency, ramp_rate, base_level = \
        get_tech_info('nuclear', ["fuel", "efficiency_ds", "ramp_rate", "base_level"])

    net.madd("Generator",
             "Gen nuclear " + gens.Name + " " + gens.bus_id,
             bus=gens.bus_id.values,
             p_nom=gens.Capacity.values,
             p_nom_min=gens.Capacity.values,
             p_nom_extendable=extendable,
             type='nuclear',
             carrier=fuel,
             efficiency=efficiency,
             marginal_cost=marginal_cost,
             capital_cost=capital_cost,
             ramp_limit_up=ramp_rate,
             ramp_limit_down=ramp_rate,
             p_min_pu=base_level,
             x=gens.lon.values,
             y=gens.lat.values)

    return net
Exemple #25
0
def upgrade_topology(net: pypsa.Network, regions: List[str], plot: bool = False,
                     ac_carrier: str = "HVAC_OHL", dc_carrier: str = "HVDC_GLIS") -> pypsa.Network:

    buses = pd.DataFrame(columns=["x", "y", "country", "onshore_region", "offshore_region"])
    links = pd.DataFrame(columns=["bus0", "bus1", "carrier", "length"])

    if "IS" in regions:
        buses.loc["IS", "onshore_region"] = get_shapes(["IS"], "onshore")["geometry"][0]
        buses.loc["IS", ["x", "y"]] = buses.loc["IS", "onshore_region"].centroid
        buses.loc["IS", "country"] = "IS"
        # Adding link to GB
        links.loc["IS-GB", ["bus0", "bus1", "carrier"]] = ["IS", "GB", dc_carrier]

    if "GL" in regions:
        assert 'IS' in regions, "Error: Cannot add a node in Greenland without adding a node in Iceland."
        full_gl_shape = get_shapes(["GL"], "onshore")["geometry"][0]
        trunc_gl_shape = full_gl_shape.intersection(Polygon([(-44.6, 59.5), (-44.6, 60.6), (-42, 60.6), (-42, 59.5)]))
        buses.loc["GL", "onshore_region"] = trunc_gl_shape
        buses.loc["GL", ["x", "y"]] = (-44., 60.)
        # buses.loc["GL", "country"] = "GL"
        # Adding link to IS
        links.loc["GL-IS", ["bus0", "bus1", "carrier"]] = ["GL", "IS", dc_carrier]

    if "na" in regions:
        countries = get_subregions("na")
        shapes = get_shapes(countries, "onshore")["geometry"]
        trunc_shape = Polygon([(-14, 27.7), (-14, 40), (40, 40), (40, 27.7)])
        for c in countries:
            buses.loc[c, "onshore_region"] = shapes.loc[c].intersection(trunc_shape)
            # buses.loc[c, "country"] = c
        buses.loc["DZ", ["x", "y"]] = (3, 36.5)  # Algeria, Alger
        buses.loc["EG", ["x", "y"]] = (31., 30.)  # Egypt, Cairo
        buses.loc["LY", ["x", "y"]] = (22, 32) #(13., 32.5)  # Libya, Tripoli
        buses.loc["MA", ["x", "y"]] = (-6., 35.)  # Morocco, Rabat
        buses.loc["TN", ["x", "y"]] = (10., 36.5)  # Tunisia, Tunis
        # Adding links
        links.loc["DZ-MA", ["bus0", "bus1", "carrier"]] = ["DZ", "MA", ac_carrier]
        links.loc["DZ-TN", ["bus0", "bus1", "carrier"]] = ["DZ", "TN", ac_carrier]
        links.loc["LY-TN", ["bus0", "bus1", "carrier", "length"]] = ["LY", "TN", ac_carrier, 2000]
        links.loc["EG-LY", ["bus0", "bus1", "carrier", "length"]] = ["EG", "LY", ac_carrier, 700]
        if "GR" in net.buses.index:
            links.loc["LY-GR", ["bus0", "bus1", "carrier", "length"]] = ["LY", "GR", dc_carrier, 900]
        if "ES" in net.buses.index:
            links.loc["MA-ES", ["bus0", "bus1", "carrier"]] = ["MA", "ES", dc_carrier]
        if "IT" in net.buses.index:
            links.loc["TN-IT", ["bus0", "bus1", "carrier", "length"]] = ["TN", "IT", dc_carrier, 600]

    if "me" in regions:
        # countries = ["AE", "BH", "CY", "IL", "IQ", "IR", "JO", "KW", "LB", "OM", "QA", "SA", "SY"]  # , "YE"]
        countries = get_subregions("me")
        shapes = get_shapes(countries, "onshore")["geometry"]
        trunc_shape = Polygon([(25, 27.7), (25, 60), (60, 60), (60, 27.7)])
        for c in countries:
            buses.loc[c, "onshore_region"] = shapes.loc[c].intersection(trunc_shape)
            # buses.loc[c, "country"] = c
        # buses.loc["AE", ["x", "y"]] = (54.5, 24.5)  # UAE, Abu Dhabi
        # buses.loc["BH", ["x", "y"]] = (50.35, 26.13)  # Bahrain, Manama
        buses.loc["TR", ["x", "y"]] = buses.loc["TR", "onshore_region"].centroid
        buses.loc["CY", ["x", "y"]] = (33.21, 35.1)  # Cyprus, Nicosia
        buses.loc["IL", ["x", "y"]] = (34.76, 32.09)  # Tel-Aviv, Jerusalem
        # if 'TR' in net.buses.index:
        #     buses.loc["IQ", ["x", "y"]] = (44.23, 33.2)  # Iraq, Baghdad
        #     buses.loc["IR", ["x", "y"]] = (51.23, 35.41)  # Iran, Tehran
        # else:
        #    buses = buses.drop(["IQ", "IR"])
        buses.loc["JO", ["x", "y"]] = (35.55, 31.56)  # Jordan, Amman
        # buses.loc["KW", ["x", "y"]] = (47.58, 29.22)  # Kuwait, Kuwait City
        # buses.loc["LB", ["x", "y"]] = (35.3, 33.53)  # Lebanon, Beirut
        # buses.loc["OM", ["x", "y"]] = (58.24, 23.35)  # Oman, Muscat
        # buses.loc["QA", ["x", "y"]] = (51.32, 25.17)  # Qatar, Doha
        buses.loc["SA", ["x", "y"]] = buses.loc["SA", "onshore_region"].centroid #(46.43, 24.38)  # Saudi Arabia, Riyadh
        buses.loc["SY", ["x", "y"]] = (36.64, 34.63)  # Syria, Homs
        # buses.loc["YE", ["x", "y"]] = (44.12, 15.20)  # Yemen, Sana
        # Adding links
        links.loc["IL-JO", ["bus0", "bus1", "carrier"]] = ["IL", "JO", ac_carrier]
        # links.loc["IL-LI", ["bus0", "bus1", "carrier"]] = ["IL", "LB", ac_carrier]
        # links.loc["SY-LI", ["bus0", "bus1", "carrier"]] = ["SY", "LB", ac_carrier]
        links.loc["SY-JO", ["bus0", "bus1", "carrier"]] = ["SY", "JO", ac_carrier]
        links.loc["IL-CY", ["bus0", "bus1", "carrier"]] = ["IL", "CY", "DC"]
        # This links comes from nowhere
        links.loc["SA-JO", ["bus0", "bus1", "carrier"]] = ["SA", "JO", ac_carrier]
        # links.loc["CY-SY", ["bus0", "bus1", "carrier"]] = ["CY", "SY", "DC"]
        # links.loc["OM-AE", ["bus0", "bus1", "carrier"]] = ["OM", "AE", ac_carrier]
        # links.loc["QA-AE", ["bus0", "bus1", "carrier"]] = ["QA", "AE", ac_carrier]
        # links.loc["QA-SA", ["bus0", "bus1", "carrier"]] = ["QA", "SA", ac_carrier]
        # links.loc["BH-QA", ["bus0", "bus1", "carrier"]] = ["BH", "QA", ac_carrier]
        # links.loc["BH-KW", ["bus0", "bus1", "carrier"]] = ["BH", "KW", ac_carrier]
        # links.loc["BH-SA", ["bus0", "bus1", "carrier"]] = ["BH", "SA", ac_carrier]
        # links.loc["YE-SA", ["bus0", "bus1", "carrier"]] = ["YE", "SA", ac_carrier]
        if "EG" in buses.index:
            links.loc["EG-IL", ["bus0", "bus1", "carrier"]] = ["EG", "IL", ac_carrier]
            links.loc["SA-EG", ["bus0", "bus1", "carrier"]] = ["SA", "EG", ac_carrier]
        #if "TR" in net.buses.index:
        links.loc["SY-TR", ["bus0", "bus1", "carrier"]] = ["SY", "TR", ac_carrier]
            # links.loc["IQ-TR", ["bus0", "bus1", "carrier"]] = ["IQ", "TR", ac_carrier]
            # links.loc["IR-TR", ["bus0", "bus1", "carrier"]] = ["IR", "TR", ac_carrier]
            # links.loc["IR-IQ", ["bus0", "bus1", "carrier"]] = ["IR", "IQ", ac_carrier]
        if "GR" in net.buses.index:
            links.loc["CY-GR", ["bus0", "bus1", "carrier", "length"]] = ["CY", "GR", dc_carrier, 850]
            # From TYNDP
            links.loc["TR-GR", ["bus0", "bus1", "carrier", "length"]] = ["TR", "GR", dc_carrier, 1173.53]  # p_nom = 0.66
        if "BG" in net.buses.index:
            links.loc["TR-BG", ["bus0", "bus1", "carrier", "length"]] = ["TR", "BG", ac_carrier, 932.16]  # p_nom = 1.2

    buses = buses.infer_objects()
    net.madd("Bus", buses.index,
             x=buses.x, y=buses.y, country=buses.country,
             onshore_region=buses.onshore_region, offshore_region=buses.offshore_region,)

    # Adding length to the lines for which we did not fix it manually
    for idx in links[links.length.isnull()].index:
        bus0_id = links.loc[idx]["bus0"]
        bus1_id = links.loc[idx]["bus1"]
        bus0_x = net.buses.loc[bus0_id]["x"]
        bus0_y = net.buses.loc[bus0_id]["y"]
        bus1_x = net.buses.loc[bus1_id]["x"]
        bus1_y = net.buses.loc[bus1_id]["y"]
        links.loc[idx, "length"] = geopy.distance.geodesic((bus0_y, bus0_x), (bus1_y, bus1_x)).km

    links['capital_cost'] = pd.Series(index=links.index)
    for idx in links.index:
        carrier = links.loc[idx].carrier
        cap_cost, _ = get_costs(carrier, sum(net.snapshot_weightings['objective']))
        links.loc[idx, ('capital_cost', )] = cap_cost * links.length.loc[idx]
    net.madd("Link", links.index, bus0=links.bus0, bus1=links.bus1, carrier=links.carrier, p_nom_extendable=True,
             length=links.length, capital_cost=links.capital_cost)

    # from tyndp
    if "TR" in net.buses.index:
        net.links.loc[["TR-BG", "TR-GR"], "p_nom"] = [1.2, 0.66]

    if plot:
        plot_topology(net.buses, net.links)
        plt.show()

    return net
Exemple #26
0
def get_topology(network: pypsa.Network,
                 countries: List[str] = None,
                 add_offshore: bool = True,
                 extend_line_cap: bool = True,
                 use_ex_line_cap: bool = True,
                 plot: bool = False) -> pypsa.Network:
    """
    Load the e-highway network topology (buses and links) using PyPSA.

    Parameters
    ----------
    network: pypsa.Network
        Network instance
    countries: List[str] (default: None)
        List of ISO codes of countries for which we want the e-highway topology
    add_offshore: bool (default: True)
        Whether to include offshore nodes
    extend_line_cap: bool (default True)
        Whether line capacity is allowed to be expanded
    use_ex_line_cap: bool (default True)
        Whether to use existing line capacity
    plot: bool (default: False)
        Whether to show loaded topology or not

    Returns
    -------
    network: pypsa.Network
        Updated network
    """

    assert countries is None or len(countries) != 0, "Error: Countries list must not be empty. If you want to " \
                                                     "obtain, the full topology, don't pass anything as argument."

    topology_dir = f"{data_path}topologies/e-highways/generated/"
    buses_fn = f"{topology_dir}buses.csv"
    assert isfile(
        buses_fn), f"Error: Buses are undefined. Please run 'preprocess'."
    buses = pd.read_csv(buses_fn, index_col='id')
    lines_fn = f"{topology_dir}lines.csv"
    assert isfile(
        lines_fn), f"Error: Lines are undefined. Please run 'preprocess'."
    lines = pd.read_csv(lines_fn, index_col='id')

    # Remove offshore buses if not considered
    if not add_offshore:
        buses = buses.dropna(subset=["onshore_region"])

    if countries is not None:
        # In e-highway, GB is referenced as UK
        iso_to_ehighway = {"GB": "UK"}
        ehighway_countries = [
            iso_to_ehighway[c] if c in iso_to_ehighway else c
            for c in countries
        ]

        # Remove onshore buses that are not in the considered region,
        # keep also buses that are offshore (i.e. with a country name that is not a string)
        def filter_buses(bus):
            return (not isinstance(
                bus.country, str)) or (bus.name[2:] in ehighway_countries)

        buses = buses.loc[buses.apply(filter_buses, axis=1)]
    else:
        countries = replace_iso2_codes(
            list(
                set([
                    idx[2:]
                    for idx in buses.dropna(subset=["onshore_region"]).index
                ])))

    # Converting polygons strings to Polygon object
    for region_type in ["onshore_region", "offshore_region"]:
        regions = buses[region_type].values
        # Convert strings
        for i, region in enumerate(regions):
            if isinstance(region, str):
                regions[i] = shapely.wkt.loads(region)

    # Remove lines for which one of the two end buses has been removed
    lines = pd.DataFrame(lines.loc[lines.bus0.isin(buses.index)
                                   & lines.bus1.isin(buses.index)])

    # Removing offshore buses that are not connected anymore
    connected_buses = sorted(list(
        set(lines["bus0"]).union(set(lines["bus1"]))))
    buses = buses.loc[connected_buses]
    assert len(
        buses
    ) != 0, "Error: No buses are located in the given list of countries."

    # Add offshore polygons to remaining offshore buses
    if add_offshore:
        offshore_shapes = get_shapes(countries, which='offshore',
                                     save=True)["geometry"]
        if len(offshore_shapes) != 0:
            offshore_zones_shape = unary_union(offshore_shapes.values)
            offshore_bus_indexes = buses[
                buses["onshore_region"].isnull()].index
            offshore_buses = buses.loc[offshore_bus_indexes]
            # Use a home-made 'voronoi' partition to assign a region to each offshore bus
            buses.loc[offshore_bus_indexes,
                      "offshore_region"] = voronoi_special(
                          offshore_zones_shape, offshore_buses[["x", "y"]])

    # Setting line parameters
    """ For DC-opf
    lines['s_nom'] *= 1000.0  # PyPSA uses MW
    lines['s_nom_min'] = lines['s_nom']
    # Define reactance   # TODO: do sth more clever
    lines['x'] = pd.Series(0.00001, index=lines.index)
    lines['s_nom_extendable'] = pd.Series(True, index=lines.index) # TODO: parametrize
    lines['capital_cost'] = pd.Series(index=lines.index)
    for idx in lines.index:
        carrier = lines.loc[idx].carrier
        cap_cost, _ = get_costs(carrier, sum(network.snapshot_weightings['objective']))
        lines.loc[idx, ('capital_cost', )] = cap_cost * lines.length.loc[idx]
    """

    lines['p_nom'] = lines["s_nom"]
    if not use_ex_line_cap:
        lines['p_nom'] = 0
    lines['p_nom_min'] = lines['p_nom']
    lines['p_min_pu'] = -1.  # Making the link bi-directional
    lines = lines.drop('s_nom', axis=1)
    lines['p_nom_extendable'] = extend_line_cap
    lines['capital_cost'] = pd.Series(index=lines.index)
    for idx in lines.index:
        carrier = lines.loc[idx].carrier
        cap_cost, _ = get_costs(carrier,
                                sum(network.snapshot_weightings['objective']))
        lines.loc[idx, ('capital_cost', )] = cap_cost * lines.length.loc[idx]

    network.import_components_from_dataframe(buses, "Bus")
    network.import_components_from_dataframe(lines, "Link")
    # network.import_components_from_dataframe(lines, "Line") for dc-opf

    if plot:
        from epippy.topologies.core.plot import plot_topology
        plot_topology(buses, lines)
        plt.show()

    return network
Exemple #27
0
class SizingDash:
    
    def __init__(self, output_dir, test_number=None):

        # Load css
        css_folder = os.path.join(os.path.dirname(os.path.abspath(__file__)), "assets/")
        self.app = dash.Dash(__name__, assets_url_path=css_folder)

        # Load net
        # If no test_number is specified, take the last run
        self.current_test_number = test_number
        if self.current_test_number is None:
            self.current_test_number = sorted(os.listdir(output_dir))[-1]
        self.output_dir = output_dir
        self.net = Network()
        self.net.import_from_csv_folder(f"{self.output_dir}{self.current_test_number}/")
        if len(self.net.lines) != 0:
            self.current_line_id = self.net.lines.index[0]
        if len(self.net.links) != 0:
            self.current_link_id = self.net.links.index[0]
        self.current_bus_id = self.net.buses.index[0]

        self.selected_types = sorted(list(set(self.net.generators.type.values)))

    def built_app(self):
    
        def get_map():

            map_coords = [min(self.net.buses["x"].values) - 5,
                          max(self.net.buses["x"].values) + 5,
                          min(self.net.buses["y"].values) - 2,
                          max(self.net.buses["y"].values) + 2]
    
            fig = go.Figure(layout=go.Layout(
                                showlegend=False,
                                geo=dict(
                                    showcountries=True,
                                    scope='world',
                                    lonaxis=dict(
                                        showgrid=True,
                                        gridwidth=1,
                                        range=[map_coords[0], map_coords[1]],
                                        dtick=5
                                    ),
                                    lataxis=dict(
                                        showgrid=True,
                                        gridwidth=1,
                                        range=[map_coords[2], map_coords[3]],
                                        dtick=5
                                    )
                                )
                            ))

            # Adding lines to map
            if len(self.net.lines) != 0:
                # Get minimum s_nom_opt
                s_nom_opt_min = min(self.net.lines.s_nom_opt[self.net.lines.s_nom_opt > 0].values)
                for i, idx in enumerate(self.net.lines.index):
                    bus0_id = self.net.lines.loc[idx, ("bus0",)]
                    bus1_id = self.net.lines.loc[idx, ("bus1",)]
                    bus0_x = self.net.buses.loc[bus0_id, ("x",)]
                    bus0_y = self.net.buses.loc[bus0_id, ("y",)]
                    bus1_x = self.net.buses.loc[bus1_id, ("x",)]
                    bus1_y = self.net.buses.loc[bus1_id, ("y",)]
                    color = 'rgba(0,0,255,0.8)'
                    name = 'AC'
                    s_nom_mul = self.net.lines.loc[idx, ('s_nom_opt',)] / s_nom_opt_min
                    if self.net.lines.loc[idx, ("carrier",)] == "DC":
                        color = 'rgba(255,0,0,0.8)'
                        name = 'DC'

                    fig.add_trace(go.Scattergeo(
                        mode='lines',
                        lon=[bus0_x, (bus0_x + bus1_x) / 2, bus1_x],
                        lat=[bus0_y, (bus0_y + bus1_y) / 2, bus1_y],
                        line=dict(
                            width=np.log(1 + s_nom_mul),
                            color=color),
                        text=[idx, idx, idx],
                        hoverinfo='text',
                        name=name
                    ))

            # Adding links to map
            if len(self.net.links) != 0:
                # Get minimum p_nom_opt
                p_nom_opt_min = min(self.net.links.p_nom_opt[self.net.links.p_nom_opt > 0].values)
                for i, idx in enumerate(self.net.links.index):
                    bus0_id = self.net.links.loc[idx, ("bus0", )]
                    bus1_id = self.net.links.loc[idx, ("bus1", )]
                    bus0_x = self.net.buses.loc[bus0_id, ("x", )]
                    bus0_y = self.net.buses.loc[bus0_id, ("y", )]
                    bus1_x = self.net.buses.loc[bus1_id, ("x", )]
                    bus1_y = self.net.buses.loc[bus1_id, ("y", )]
                    color = 'rgba(0,0,255,0.8)'
                    name = 'AC'
                    p_nom_mul = self.net.links.loc[idx, 'p_nom_opt']/p_nom_opt_min
                    if self.net.links.loc[idx, ("carrier", )] == "DC":
                        color = 'rgba(255,0,0,0.8)'
                        name = 'DC'

                    fig.add_trace(go.Scattergeo(
                            mode='lines',
                            lon=[bus0_x, (bus0_x+bus1_x)/2, bus1_x],
                            lat=[bus0_y, (bus0_y+bus1_y)/2, bus1_y],
                            line=dict(
                                width=np.log(1+p_nom_mul)/4,
                                color=color),
                            text=["", f"Init Capacity: {self.net.links.loc[idx, 'p_nom']}<br>"
                                      f"Opt Capacity: {self.net.links.loc[idx, 'p_nom_opt']}", ""],
                            hoverinfo='text',
                            name=name
                        ))

            # Add points to map
            p_noms = np.zeros((len(self.net.buses.index, )))
            color = tech_colors['All']
            if len(self.selected_types) == 1:
                color = tech_colors[self.selected_types[0]]
            colors = [color]*len(self.net.buses.index)
            for i, bus_id in enumerate(self.net.buses.index):
                total_gens = 0
                generators = self.net.generators[self.net.generators.bus == bus_id]

                # Keep only the reactors of the type we want to display
                for t in self.selected_types:
                    generators_filter = generators[generators.type == t]
                    p_noms[i] += np.sum(generators_filter["p_nom_opt"].values)
                    total_gens += len(generators_filter["p_nom"].values)

                if total_gens == 0:
                    # No allowed generation building
                    colors[i] = 'grey'
                elif p_noms[i] == 0:
                    colors[i] = 'black'

            p_nom_max = np.max(p_noms)
            if p_nom_max == 0:
                p_nom_max = 1  # Prevents cases where there is no installed capacity at all

            fig.add_trace(go.Scattergeo(
                                mode="markers",
                                lat=self.net.buses['y'].values,
                                lon=self.net.buses['x'].values,
                                text=self.net.buses.index,
                                hoverinfo='text',
                                marker=dict(
                                    size=10+40*np.log(1+p_noms/p_nom_max),
                                    color=colors
                                ),
                                name='bus'
                            ))

            return fig
    
        def get_line_info():

            fig = go.Figure(data=go.Scatter(
                                x=[i for i in range(len(self.net.snapshots))],
                                y=self.net.links_t.p0[self.current_link_id],
                                marker=dict(color='blue'),
                                name='Power flow'),
                            layout=go.Layout(
                                title=f"Power flow for {self.current_line_id}",
                                xaxis={'title': 'Time stamps'},
                                yaxis={'title': 'GWh (or GW)'}))
    
            # Original capacity
            capacity = self.net.links.loc[self.current_link_id, 'p_nom']
            fig.add_trace(go.Scatter(
                x=[i for i in range(len(self.net.snapshots))],
                y=[capacity]*len(self.net.snapshots),
                marker=dict(color='red'),
                name='Original capacity'
            ))
    
            fig.add_trace(go.Scatter(
                x=[i for i in range(len(self.net.snapshots))],
                y=[-capacity]*len(self.net.snapshots),
                marker=dict(color='red', opacity=0.5),
                name='Original capacity'
            ))
    
            # New capacity
            capacity = self.net.links.loc[self.current_link_id, 'p_nom_opt']
            fig.add_trace(go.Scatter(
                x=[i for i in range(len(self.net.snapshots))],
                y=[capacity] * len(self.net.snapshots),
                marker=dict(color='green'),
                name='Updated capacity'
            ))
    
            fig.add_trace(go.Scatter(
                x=[i for i in range(len(self.net.snapshots))],
                y=[-capacity] * len(self.net.snapshots),
                marker=dict(color='green'),
                name='Updated capacity'
            ))
    
            return fig
    
        # Application layout
        def get_generation():

            gens = self.net.generators

            fig = go.Figure(
                layout=go.Layout(
                    title=f"Generation in {self.current_bus_id}",
                    xaxis={'title': 'Time stamps'},
                    yaxis={'title': 'GWh (or GW)'}
                ))

            types = list(set(gens.type.values))
            # Put nuclear first if present
            if 'nuclear' in types:
                types.remove("nuclear")
                types.insert(0, "nuclear")
            for t in types:
                total_generation = [0] * len(self.net.snapshots)
                types_gens = gens[gens.type == t]
                generations_by_type = self.net.generators_t.p[types_gens.index].values
                if len(generations_by_type) != 0:
                    total_generation = np.sum(generations_by_type, axis=1)
                fig.add_trace(go.Scatter(
                    x=[i for i in range(len(self.net.snapshots))],
                    y=total_generation,
                    opacity=0.5,
                    stackgroup='one',
                    mode='none',
                    fillcolor=tech_colors[t],
                    marker=dict(color=tech_colors[t],
                                opacity=0.5),
                    name=t))

            return fig

        def get_generation_per_node():


            gens = self.net.generators[self.net.generators.bus == self.current_bus_id]

            """installed_cap = gens['p_nom_opt'].sum()
            available_power_per_gen = gens['p_nom_opt'].values
            gens_p_max_pu = np.zeros((len(gens.index), len(self.net.snapshots)))
            for i, idx in enumerate(gens.index):
                if idx in self.net.generators_t.p_max_pu.keys():
                    gens_p_max_pu[i] = self.net.generators_t.p_max_pu[idx].values
            available_power_per_bus = available_power_per_gen @ gens_p_max_pu
            """

            fig = go.Figure(
                layout=go.Layout(
                    title=f"Generation in {self.current_bus_id}",
                    xaxis={'title': 'Time stamps'},
                    yaxis={'title': 'GWh (or GW)'}
                ))

            """
                fig.add_trace(go.Scatter(
                    x=[i for i in range(len(self.net.snapshots))],
                    y=[installed_cap]*len(self.net.snapshots),
                    name='Gen Capacity'))

                fig.add_trace(go.Scatter(
                    x=[i for i in range(len(self.net.snapshots))],
                    y=available_power_per_bus,
                    name='Available Cap'))
            """

            types = list(set(gens.type.values))
            # Put nuclear first if present
            if 'nuclear' in types:
                types.remove("nuclear")
                types.insert(0, "nuclear")
            for t in types:
                total_generation = [0] * len(self.net.snapshots)
                types_gens = gens[gens.type == t]
                generations_by_type = self.net.generators_t.p[types_gens.index].values
                if len(generations_by_type) != 0:
                    total_generation = np.sum(generations_by_type, axis=1)
                fig.add_trace(go.Scatter(
                        x=[i for i in range(len(self.net.snapshots))],
                        y=total_generation,
                        opacity=0.5,
                        stackgroup='one',
                        mode='none',
                        fillcolor=tech_colors[t],
                        marker=dict(color=tech_colors[t],
                                    opacity=0.5),
                        name=t))

            return fig

        def get_demand_balancing():

            # Compute total load, first line is useful in case there is no load at the selected bus
            load = np.zeros(len(self.net.snapshots))
            loads = self.net.loads[self.net.loads.bus == self.current_bus_id]
            if len(loads) != 0:
                load += self.net.loads_t.p_set[loads.index].sum(axis=1)

            demand_balancing = np.zeros((len(self.net.snapshots)))

            # Generation
            gens = self.net.generators[self.net.generators.bus == self.current_bus_id]
            demand_balancing += self.net.generators_t.p[gens.index].sum(axis=1)

            # Add imports and remove exports
            links_out = self.net.links[self.net.links.bus0 == self.current_bus_id]
            links_in = self.net.links[self.net.links.bus1 == self.current_bus_id]
            inflow = self.net.links_t.p1[links_out.index].sum(axis=1) + self.net.links_t.p0[links_in.index].sum(axis=1)
            demand_balancing += inflow

            # Add discharge of battery and remove store
            storages = self.net.storage_units[self.net.storage_units.bus == self.current_bus_id]
            demand_balancing += self.net.storage_units_t.p[storages.index].sum(axis=1)

            fig = go.Figure(
                data=go.Scatter(
                    x=[i for i in range(len(self.net.snapshots))],
                    y=load,
                    name='Load',
                    marker=dict(color='red',
                                opacity=0.5)
                ),
                layout=go.Layout(
                    title=f"Demand balancing in {self.current_bus_id}",
                    xaxis={'title': 'Time stamps'},
                    yaxis={'title': 'GWh (or GW)'}
                ))

            fig.add_trace(go.Scatter(
                x=[i for i in range(len(self.net.snapshots))],
                y=demand_balancing,
                opacity=0.5,
                stackgroup='one',
                mode='none',
                name='Demand balancing',
                fillcolor='blue',
                marker=dict(color='blue',
                            opacity=0.5)))

            return fig

        def get_state_of_charge():

            # Get storages at the current node
            storages = self.net.storages\
                .where(self.net.storages.bus == self.current_bus_id, drop=True)

            # State of charge
            fig = go.Figure(
                data=go.Scatter(
                    x=[i for i in range(len(self.net.snapshots))],
                    y=np.sum(storages.state_of_charge.values, axis=0),
                    name='SOF'),
                layout=go.Layout(
                    title=f"State of charge in {self.current_bus_id}",
                    xaxis={'title': 'Time stamps'},
                    yaxis={'title': 'GWh (or GW)'},
                ))

            # Maximum level of charge
            fig.add_trace(go.Scatter(
                x=[i for i in range(len(self.net.snapshots))],
                y=[storages.p_nom_opt.values.item()*storages.max_hours.values.item()]*len(self.net.snapshots),
                name='Max Storage'
            ))

            return fig

        def get_charge_discharge():

            # Get storages at the current node
            storages = self.net.storages\
                .where(self.net.storages.bus == self.current_bus_id, drop=True)

            # State of charge
            fig = go.Figure(
                data=go.Scatter(
                    x=[i for i in range(len(self.net.snapshots))],
                    y=np.sum(storages.charge.values, axis=0),
                    name='Charge-Discharge'),
                layout=go.Layout(
                    title=f"Charge in {self.current_bus_id}",
                    xaxis={'title': 'Time stamps'},
                    yaxis={'title': 'GWh (or GW)'},
                ))

            # Maximum level of charge
            fig.add_trace(go.Scatter(
                x=[i for i in range(len(self.net.snapshots))],
                y=[storages.p_nom_opt.values.item()]*len(self.net.snapshots),
                name='Max in power'
            ))

            # Maximum level of discharge
            fig.add_trace(go.Scatter(
                x=[i for i in range(len(self.net.snapshots))],
                y=[-storages.p_nom_opt.values.item()]*len(self.net.snapshots),
                name='Max out power'
            ))

            return fig

        def get_costs_table():

            #costs_fn = f"{self.output_dir}{self.current_test_number}/costs.yaml"
            #costs = yaml.load(open(costs_fn, "r"), Loader=yaml.FullLoader)

            # Generation
            gen_types = set(self.net.generators.type.values)
            total_new_gen_cap = dict.fromkeys(gen_types, 0)
            gen_invest_cost = dict.fromkeys(gen_types, 0)
            gen_op_cost = dict.fromkeys(gen_types, 0)
            for idx in self.net.generators.index:
                gen = self.net.generators.loc[idx]
                t = gen.type
                invest_cost = gen.capital_cost
                new_gen_cap = gen.p_nom_opt - gen.p_nom
                total_new_gen_cap[t] += new_gen_cap
                gen_invest_cost[t] += new_gen_cap * invest_cost
                gen_op_cost[t] += np.sum(self.net.generators_t.p[idx]) * gen.marginal_cost

            # Transmission
            trans_invest_cost = 0
            total_new_trans_cap = 0
            for idx in self.net.lines.index:
                line = self.net.lines.loc[idx]
                new_trans_cap = line.s_nom_opt - line.s_nom
                total_new_trans_cap += new_trans_cap
                trans_invest_cost += new_trans_cap * line.capital_cost

            # Storage
            store_invest_cost = 0
            total_new_store_cap = 0
            for idx in self.net.storage_units.index:
                storage = self.net.storage_units.loc[idx]
                new_store_cap = storage.p_nom_opt - storage.p_nom
                total_new_store_cap += new_store_cap
                store_invest_cost += new_store_cap * storage.capital_cost

            # Lost load
            """
            lost_load_cost = 0
            lost_load_cost_per_unit = costs["lost_load"]
            for idx in self.net.buses.index:
                load = self.net.loads_t.where(self.net.loads.bus == idx).p_set.values[0]
                generation = np.sum(self.net.generators_t
                                    .where(self.net.generators.bus == idx).p.values, axis=0)
                inflows = np.sum(self.net.lines_t.where(self.net.lines.bus1 == idx).s.values, axis=0)
                outflows = np.sum(self.net.lines_t.where(self.net.lines.bus0 == idx).s.values, axis=0)
                battery_charge = np.sum(self.net.storages_t.where(self.net.storages.bus == idx).charge.values,
                                        axis=0)
                lost_load = np.sum(load - generation - inflows + outflows + battery_charge)
                print(lost_load)
                lost_load_cost += lost_load * lost_load_cost_per_unit
            """
            table = []

            diviser = 1000.0
            for t in gen_types:
                gen_invest_cost[t] /= diviser
                gen_op_cost[t] /= diviser
            total_gen_invest_cost = sum([gen_invest_cost[t] for t in gen_types])
            total_gen_op_cost = sum([gen_op_cost[t] for t in gen_types])
            total_gen_cost = total_gen_invest_cost + total_gen_op_cost
            trans_invest_cost /= diviser
            store_invest_cost /= diviser
            #lost_load_cost /= diviser

            for t in gen_types:
                table.append({"Tech": t, "Investment": f"{gen_invest_cost[t]:.4f}",
                              "Operation": f"{gen_op_cost[t]:.4f}",
                              "Total": f"{gen_invest_cost[t] + gen_op_cost[t]:.4f}"})
            table.append({"Tech": "Gen Total", "Investment": f"{total_gen_invest_cost:.4f}",
                          "Operation": f"{total_gen_op_cost:.4f}",
                          "Total": f"{total_gen_cost:.4f}"})
            table.append({"Tech": "Trans Total", "Investment": f"{trans_invest_cost:.4f}",
                          "Operation": 0,
                          "Total": f"{trans_invest_cost:.4f}"})
            table.append({"Tech": "Store Total", "Investment": f"{store_invest_cost:.4f}",
                          "Operation": 0,
                          "Total": f"{store_invest_cost:.4f}"})
            #table.append({"Tech": "Lost load", "Investment": "N/A", "Operation": "N/A",
            #              "Total": f"{lost_load_cost:.4f}"})
            table.append({"Tech": "Total", "Investment": f"{trans_invest_cost+total_gen_invest_cost:.4f}",
                          "Operation": f"{total_gen_op_cost:.4f}",
                          "Total": f"{trans_invest_cost+total_gen_cost+store_invest_cost:.4f}"})

            return table

        def get_output_folders():
            return [{'label': dir_name, 'value': dir_name} for dir_name in sorted(os.listdir(self.output_dir))]
    
        def get_parameters_list():
            attrs = yaml.load(open(f"{self.output_dir}{self.current_test_number}/config.yaml", "rb"),
                              Loader=yaml.FullLoader)
            return [{'name': key, 'value': str(attrs[key])} for key in attrs]

        def get_tech_type_list():
            return [{'label': t, 'value': t} for t in self.selected_types]

        def get_capacities():

            all_cap = dict.fromkeys(sorted(set(self.net.generators.type)))
            for key in all_cap:
                all_cap[key] = np.sum(self.net.generators[self.net.generators.type == key].p_nom_opt.values)/1000.0

            fig = go.Figure(
                data=go.Pie(
                    title="Capacities (GW)",
                    values=list(all_cap.values()),
                    labels=list(all_cap.keys()),
                    marker=dict(colors=[tech_colors[key] for key in all_cap.keys()])))
            return fig

        def get_capacities_for_node():

            gens_at_bus = self.net.generators[self.net.generators.bus == self.current_bus_id]
            all_types = sorted(set(gens_at_bus.type.values))
            data = []
            for tech in all_types:
                data.append(go.Bar(name=tech,
                                   x=["Capacity"],
                                   y=[np.sum(gens_at_bus[self.net.generators.type == tech].p_nom_opt.values)/1000.0],
                                   marker=dict(color=tech_colors[tech])))

            fig = go.Figure(data=data)
            fig.update_layout(barmode='stack')

            return fig

        def get_total_generation():

            all_cap = dict.fromkeys(sorted(set(self.net.generators.type)))
            for key in all_cap:
                all_cap[key] = np.sum(self.net.generators_t.p[
                                          self.net.generators[self.net.generators.type == key].index].values)/1000.0

            fig = go.Figure(
                data=go.Pie(
                    title="Generation (GWh)",
                    values=list(all_cap.values()),
                    labels=list(all_cap.keys()),
                    marker=dict(colors=[tech_colors[key] for key in all_cap.keys()])))
            return fig

        def get_total_generation_for_node():

            gens_at_bus = self.net.generators[self.net.generators.bus == self.current_bus_id]
            all_types = sorted(set(gens_at_bus.type.values))
            data = []
            for tech in all_types:
                data.append(go.Bar(name=tech,
                                   x=["Generation"],
                                   y=[np.sum(self.net.generators_t.p[gens_at_bus[gens_at_bus.type == tech].index].values)/1000.0],
                                   marker=dict(color=tech_colors[tech])))

            fig = go.Figure(data=data)
            fig.update_layout(barmode='stack')

            return fig

        def get_load_gen():

            # Get the total load for every time stamp
            total_load = np.sum(self.net.loads_t.p_set.values, axis=1)

            # Get the total gen for every time stamp
            total_gen = np.zeros(len(total_load))
            techs = self.selected_types
            for tech in techs:
                total_gen += np.sum(self.net.generators_t.p[
                                        self.net.generators[self.net.generators.type == tech].index].values,
                                    axis=1)

            fig = go.Figure(
                data=go.Scatter(
                    x=[i for i in range(len(self.net.snapshots))],
                    y=total_load,
                    name='Total Load'),
                layout=go.Layout(
                    title='Total Load vs Total Gen',
                    xaxis={'title': 'Time stamps'},
                    yaxis={'title': 'MWh (or MW)'},
                ))

            # Maximum level of charge
            fig.add_trace(go.Scatter(
                x=[i for i in range(len(self.net.snapshots))],
                y=total_gen,
                name='Generation'
            ))

            return fig

        self.app.layout = html.Div([
    
            html.H1('Interactive network representation'),

            html.Div([

                # Parameters and costs
                html.Div([
                    dcc.Dropdown(
                        id='output-selector',
                        options=get_output_folders(),
                        value=self.current_test_number
                    ),

                    html.Div([
                        dash_table.DataTable(
                            id='parameters-table',
                            columns=[{"name": "name", "id": "name"}, {"name": "value", "id": "value"}],
                            data=get_parameters_list()),
                        dcc.Checklist(
                            id="tech-types",
                            options=get_tech_type_list(),
                            value=self.selected_types
                        ),
                        html.Button(id='submit-button', n_clicks=0, children='Submit')
                    ]),

                    html.Div([
                        dash_table.DataTable(
                            id='cost-table',
                            columns=[{"name": "Tech", "id": "Tech"},
                                     {"name": "Investment (G€)", "id": "Investment"},
                                     {"name": "Operation (G€)", "id": "Operation"},
                                     {"name": "Total (G€)", "id": "Total"}],
                            data=get_costs_table()),
                    ])
                ], style={"width": 1000}),
                
                # Graphs
                html.Div([
                    # Map and general info
                    html.Div([
                        # Map
                        html.Div([
                            dcc.Graph(
                                id='map',
                                figure=get_map(),
                                style={'height': 700}
                            )
                        ]),

                        html.Div([
                            dcc.Graph(
                                id='tot-cap',
                                figure=get_capacities(),
                            ),
                            dcc.Graph(
                                id='tot-gen',
                                figure=get_total_generation(),
                            )
                        ]),

                        dcc.Graph(
                            id='load-gen',
                            figure=get_load_gen(),
                        )
                    ], style={'display': 'flex'}),

                    # Per-node info
                    html.Div([
                        html.Div([
                            # Line power flow
                            dcc.Graph(
                                id='line-power-flow',
                                figure=get_line_info(),
                            ),

                            # Demand balancing graph
                            dcc.Graph(
                                id='demand-balancing',
                                figure=get_demand_balancing(),
                            ),

                            dcc.Graph(
                                id='gen-per-bus',
                                figure=get_generation_per_node(),
                            ),

                            dcc.Graph(
                                id='gen',
                                figure=get_generation(),
                            )
                        ]),

                        """
                        html.Div([
                            dcc.Graph(
                                id='state-of-charge',
                                figure=get_state_of_charge()
                            ),

                            dcc.Graph(
                                id='charge-discharge',
                                figure=get_charge_discharge()
                            )
                        ])""",

                        html.Div([
                            dcc.Graph(
                                id='cap-per-bus',
                                figure=get_capacities_for_node(),
                            ),

                            dcc.Graph(
                                id='tot-gen-per-bus',
                                figure=get_total_generation_for_node(),
                            )
                        ])

                    ], style={'display': 'flex'})
                ], style={'display': 'flex', 'flex-wrap': 'wrap'})
            ])
        ])

        @self.app.callback(
            [Output('demand-balancing', 'figure'),
             # Output('state-of-charge', 'figure'),
             # Output('charge-discharge', 'figure'),
             Output('cap-per-bus', 'figure'),
             Output('tot-gen-per-bus', 'figure'),
             Output('gen-per-bus', 'figure'),
             Output('gen', 'figure')],
            [Input('map', 'clickData')])
        def update_demand_balancing(clickData):
            if clickData is not None and 'points' in clickData:
                points = clickData['points'][0]
                if 'text' in points and '-' not in points['text']:
                    self.current_bus_id = points['text']
                    return get_demand_balancing(), get_capacities_for_node(), get_total_generation_for_node(), \
                           get_generation_per_node(), get_generation() # , get_state_of_charge(), get_charge_discharge(), \
            raise PreventUpdate

        """
        @self.app.callback(
            Output('line-power-flow', 'figure'),
            [Input('map', 'clickData')])
        def update_line_power_flow(clickData):
            if clickData is not None and 'points' in clickData:
                points = clickData['points'][0]
                if 'text' in points and '-' in points['text']:
                    self.current_line_id = points['text']
                    return get_line_info()
            raise PreventUpdate
        """

        @self.app.callback(
            Output('parameters-table', 'data'),
            [Input('output-selector', 'value')])
        def update_param_table(value):
            self.current_test_number = value
            return get_parameters_list()

        @self.app.callback(
            [Output('map', 'figure'),
             Output('cost-table', 'data'),
             Output('tot-cap', 'figure'),
             Output('tot-gen', 'figure'),
             Output('load-gen', 'figure')],
            [Input('submit-button', 'n_clicks')],
            [State('output-selector', 'value'),
             State('tech-types', 'value')])
        def update_network(n_clicks, value1, value2):
            print(value2)
            self.net = Network()
            self.net.import_from_csv_folder(f"{self.output_dir}{value1}/")
            self.current_link_id = self.net.links.index[0]
            self.current_bus_id = self.net.buses.index[0]
            self.selected_types = value2
            return get_map(), get_costs_table(), get_capacities(), get_total_generation(), get_load_gen()

        return self.app
Exemple #28
0
def add_generators_per_bus(net: pypsa.Network, technologies: List[str],
                           use_ex_cap: bool = True, bus_ids: List[str] = None,
                           precision: int = 3) -> pypsa.Network:
    """
    Add VRES generators to each bus of a PyPSA Network, each bus being associated to a geographical region.

    Parameters
    ----------
    net: pypsa.Network
        A PyPSA Network instance with buses associated to regions.
    technologies: List[str]
        Names of VRES technologies to be added.
    use_ex_cap: bool (default: True)
        Whether to take into account existing capacity.
    bus_ids: List[str]
        Subset of buses to which the generators must be added.
    precision: int (default: 3)
        Indicates at which decimal values should be rounded

    Returns
    -------
    net: pypsa.Network
        Updated network

    Notes
    -----
    Each bus must contain 'x', 'y' attributes.
    In addition, each bus must have a 'region_onshore' and/or 'region_offshore' attributes.
    Finally, if the topology has one bus per country (and no offshore buses), all buses can be associated
    to an ISO code under the attribute 'country' to fasten some computations.

    """

    # Filter out buses
    all_buses = net.buses.copy()
    all_buses = all_buses[all_buses['country'].notna()]
    if bus_ids is not None:
        all_buses = all_buses.loc[bus_ids]

    for attr in ["x", "y"]:
        assert hasattr(all_buses, attr), f"Error: Buses must contain a '{attr}' attribute."
    assert all([len(bus[["onshore_region", "offshore_region"]].dropna()) != 0 for idx, bus in all_buses.iterrows()]), \
        "Error: Each bus must be associated to an 'onshore_region' and/or 'offshore_region' attribute."

    one_bus_per_country = False
    if hasattr(all_buses, 'country'):
        # Check every bus has a value for this attribute
        complete = len(all_buses["country"].dropna()) == len(all_buses)
        # Check the values are unique
        unique = len(all_buses["country"].unique()) == len(all_buses)
        one_bus_per_country = complete & unique

    tech_config_dict = get_config_dict(technologies, ["filters", "power_density", "onshore"])
    for tech in technologies:

        # Detect if technology is onshore(/offshore) based
        onshore_tech = tech_config_dict[tech]["onshore"]

        # Get buses which are associated to an onshore/offshore region
        region_type = "onshore_region" if onshore_tech else 'offshore_region'
        buses = all_buses.dropna(subset=[region_type], axis=0)
        countries = list(buses["country"].unique())

        # Get the shapes of regions associated to each bus
        buses_regions_shapes_ds = buses[region_type]

        # Compute capacity potential at each bus
        # TODO: WARNING: first part of if-else to be removed
        enspreso = False
        if enspreso:
            logger.warning("Capacity potentials computed using ENSPRESO data.")
            if one_bus_per_country:
                cap_pot_country_ds = get_capacity_potential_for_countries(tech, countries)
                cap_pot_ds = pd.Series(index=buses.index, dtype=float)
                cap_pot_ds[:] = cap_pot_country_ds.loc[buses.country]
            else:  # topology_type == "regions"
                cap_pot_ds = get_capacity_potential_for_regions({tech: buses_regions_shapes_ds.values})[tech]
                cap_pot_ds.index = buses.index
        else:
            # Using GLAES
            filters = tech_config_dict[tech]["filters"]
            power_density = tech_config_dict[tech]["power_density"]
            cap_pot_ds = pd.Series(index=buses.index, dtype=float)
            cap_pot_ds[:] = get_capacity_potential_for_shapes(buses_regions_shapes_ds.values, filters,
                                                              power_density, precision=precision)

        # Get one capacity factor time series per bus
        if one_bus_per_country:
            # For country-based topologies, use aggregated series obtained from Renewables.ninja
            cap_factor_countries_df = get_cap_factor_for_countries(tech, countries, net.snapshots, precision, False)
            cap_factor_df = pd.DataFrame(index=net.snapshots, columns=buses.index, dtype=float)
            cap_factor_df[:] = cap_factor_countries_df[buses.country]
        else:
            # For region-based topology, compute capacity factors at (rounded) buses position
            spatial_res = 0.5
            points = [(round(shape.centroid.x/spatial_res) * spatial_res,
                       round(shape.centroid.y/spatial_res) * spatial_res)
                      for shape in buses_regions_shapes_ds.values]
            cap_factor_df = compute_capacity_factors({tech: points}, spatial_res, net.snapshots, precision)[tech]
            cap_factor_df.columns = buses.index

        # Compute legacy capacity (not available for wind_floating)
        legacy_cap_ds = pd.Series(0., index=buses.index, dtype=float)
        if use_ex_cap and tech != "wind_floating":
            if one_bus_per_country and len(countries) != 0:
                legacy_cap_countries = get_legacy_capacity_in_countries(tech, countries)
                legacy_cap_ds[:] = legacy_cap_countries.loc[buses.country]
            else:
                legacy_cap_ds = get_legacy_capacity_in_regions(tech, buses_regions_shapes_ds, countries)

        # Update capacity potentials if legacy capacity is bigger
        for bus in buses.index:
            if cap_pot_ds.loc[bus] < legacy_cap_ds.loc[bus]:
                cap_pot_ds.loc[bus] = legacy_cap_ds.loc[bus]

        # Remove generators if capacity potential is 0
        non_zero_potential_gens_index = cap_pot_ds[cap_pot_ds > 0].index
        cap_pot_ds = cap_pot_ds.loc[non_zero_potential_gens_index]
        legacy_cap_ds = legacy_cap_ds.loc[non_zero_potential_gens_index]
        cap_factor_df = cap_factor_df[non_zero_potential_gens_index]
        buses = buses.loc[non_zero_potential_gens_index]

        # Get costs
        capital_cost, marginal_cost = get_costs(tech, len(net.snapshots))

        # Adding to the network
        net.madd("Generator",
                 buses.index,
                 suffix=f" Gen {tech}",
                 bus=buses.index,
                 p_nom_extendable=True,
                 p_nom=legacy_cap_ds,
                 p_nom_min=legacy_cap_ds,
                 p_nom_max=cap_pot_ds,
                 p_min_pu=0.,
                 p_max_pu=cap_factor_df,
                 type=tech,
                 x=buses.x.values,
                 y=buses.y.values,
                 marginal_cost=marginal_cost,
                 capital_cost=capital_cost)

    return net
Exemple #29
0
def cluster_on_extra_high_voltage(network, busmap, with_time=True):
    """ Create a new clustered pypsa.Network given a busmap mapping all busids
    to other busids of the same set.

    Parameters
    ----------
    network : pypsa.Network
        Container for all network components.
        
    busmap : dict
        Maps old bus_ids to new bus_ids.
        
    with_time : bool
        If true time-varying data will also be aggregated.

    Returns
    -------
    network : pypsa.Network
        Container for all network components.
    """

    network_c = Network()

    buses = aggregatebuses(network, busmap, {
        'x': _leading(busmap, network.buses),
        'y': _leading(busmap, network.buses)
    })

    # keep attached lines
    lines = network.lines.copy()
    mask = lines.bus0.isin(buses.index)
    lines = lines.loc[mask, :]

    # keep attached transformer
    transformers = network.transformers.copy()
    mask = transformers.bus0.isin(buses.index)
    transformers = transformers.loc[mask, :]

    io.import_components_from_dataframe(network_c, buses, "Bus")
    io.import_components_from_dataframe(network_c, lines, "Line")
    io.import_components_from_dataframe(network_c, transformers, "Transformer")

    if with_time:
        network_c.snapshots = network.snapshots
        network_c.set_snapshots(network.snapshots)

    # dealing with generators
    network.generators.control = "PV"
    network.generators['weight'] = 1
    new_df, new_pnl = aggregategenerators(network, busmap, with_time)
    io.import_components_from_dataframe(network_c, new_df, 'Generator')
    for attr, df in iteritems(new_pnl):
        io.import_series_from_dataframe(network_c, df, 'Generator', attr)

    # dealing with all other components
    aggregate_one_ports = components.one_port_components.copy()
    aggregate_one_ports.discard('Generator')

    for one_port in aggregate_one_ports:
        new_df, new_pnl = aggregateoneport(network,
                                           busmap,
                                           component=one_port,
                                           with_time=with_time)
        io.import_components_from_dataframe(network_c, new_df, one_port)
        for attr, df in iteritems(new_pnl):
            io.import_series_from_dataframe(network_c, df, one_port, attr)

    network_c.determine_network_topology()

    return network_c
Exemple #30
0
def to_pypsa(network, mode, timesteps):
    """
    Translate graph based grid representation to PyPSA Network

    For details from a user perspective see API documentation of
    :meth:`~.grid.network.EDisGo.analyze` of the API class
    :class:`~.grid.network.EDisGo`.

    Translating eDisGo's grid topology to PyPSA representation is structured
    into tranlating the topology and adding time series for components of the
    grid. In both cases translation of MV grid only (`mode='mv'`), LV grid only
    (`mode='lv'`), MV and LV (`mode=None`) share some code. The
    code is organized as follows

    * Medium-voltage only (`mode='mv'`): All medium-voltage grid components are
      exported by :func:`mv_to_pypsa` including the LV station. LV grid load
      and generation is considered using :func:`add_aggregated_lv_components`.
      Time series are collected by `_pypsa_load_timeseries` (as example
      for loads, generators and buses) specifying `mode='mv'`). Timeseries
      for aggregated load/generation at substations are determined individually.
    * Low-voltage only (`mode='lv'`): LV grid topology including the MV-LV
      transformer is exported. The slack is defind at primary side of the MV-LV
      transformer.
    * Both level MV+LV (`mode=None`): The entire grid topology is translated to
      PyPSA in order to perform a complete power flow analysis in both levels
      together. First, both grid levels are translated seperately using
      :func:`mv_to_pypsa` and :func:`lv_to_pypsa`. Those are merge by
      :func:`combine_mv_and_lv`. Time series are obtained at once for both grid
      levels.

    This PyPSA interface is aware of translation errors and performs so checks
    on integrity of data converted to PyPSA grid representation

    * Sub-graphs/ Sub-networks: It is ensured the grid has no islanded parts
    * Completeness of time series: It is ensured each component has a time
      series
    * Buses available: Each component (load, generator, line, transformer) is
      connected to a bus. The PyPSA representation is check for completeness of
      buses.
    * Duplicate labels in components DataFrames and components' time series
      DataFrames

    Parameters
    ----------
    network : Network
        eDisGo grid container
    mode : str
        Determines grid levels that are translated to
        `PyPSA grid representation
        <https://www.pypsa.org/doc/components.html#network>`_. Specify

        * None to export MV and LV grid levels. None is the default.
        * ('mv' to export MV grid level only. This includes cumulative load and
          generation from underlying LV grid aggregated at respective LV
          station. This option is implemented, though the rest of edisgo does
          not handle it yet.)
        * ('lv' to export LV grid level only. This option is not yet
           implemented)
    timesteps : :pandas:`pandas.DatetimeIndex<datetimeindex>` or :pandas:`pandas.Timestamp<timestamp>`
        Timesteps specifies which time steps to export to pypsa representation
        and use in power flow analysis.

    Returns
    -------
        PyPSA Network

    """

    # check if timesteps is array-like, otherwise convert to list (necessary
    # to obtain a dataframe when using .loc in time series functions)
    if not hasattr(timesteps, "__len__"):
        timesteps = [timesteps]

    # get topology and time series data
    if mode is None:
        mv_components = mv_to_pypsa(network)
        lv_components = lv_to_pypsa(network)
        components = combine_mv_and_lv(mv_components, lv_components)

        if list(components['Load'].index.values):
            timeseries_load_p_set = _pypsa_load_timeseries(network,
                                                           mode=mode,
                                                           timesteps=timesteps)

        if len(list(components['Generator'].index.values)) > 1:
            timeseries_gen_p_min, timeseries_gen_p_max = \
                _pypsa_generator_timeseries(
                    network, mode=mode, timesteps=timesteps)
            timeseries_storage_p_min, timeseries_storage_p_max = \
                _pypsa_storage_timeseries(
                    network, mode=mode, timesteps=timesteps)

        if list(components['Bus'].index.values):
            timeseries_bus_v_set = _pypsa_bus_timeseries(
                network, components['Bus'].index.tolist(), timesteps=timesteps)
    else:
        raise ValueError("Provide proper mode or leave it empty to export "
                         "entire grid topology.")

    # check topology
    _check_topology(components)

    # create power flow problem
    pypsa_network = PyPSANetwork()
    pypsa_network.edisgo_mode = mode
    pypsa_network.set_snapshots(timesteps)

    # import grid topology to PyPSA network
    # buses are created first to avoid warnings
    pypsa_network.import_components_from_dataframe(components['Bus'], 'Bus')

    for k, comps in components.items():
        if k is not 'Bus' and not comps.empty:
            pypsa_network.import_components_from_dataframe(comps, k)

    # import time series to PyPSA network
    if len(list(components['Generator'].index.values)) > 1:
        import_series_from_dataframe(pypsa_network, timeseries_gen_p_min,
                                     'Generator', 'p_min_pu')
        import_series_from_dataframe(pypsa_network, timeseries_gen_p_max,
                                     'Generator', 'p_max_pu')
        import_series_from_dataframe(pypsa_network, timeseries_storage_p_min,
                                     'Generator', 'p_min_pu')
        import_series_from_dataframe(pypsa_network, timeseries_storage_p_max,
                                     'Generator', 'p_max_pu')

    if list(components['Load'].index.values):
        import_series_from_dataframe(pypsa_network, timeseries_load_p_set,
                                     'Load', 'p_set')

    if list(components['Bus'].index.values):
        import_series_from_dataframe(pypsa_network, timeseries_bus_v_set,
                                     'Bus', 'v_mag_pu_set')

    _check_integrity_of_pypsa(pypsa_network)

    return pypsa_network