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
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()
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]]
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
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
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
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 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
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
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
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
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
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
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
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)
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
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)
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
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
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
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
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
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}/"
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
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
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
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
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
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
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