From 4975b8db9816385a60070909b713df61e19f702f Mon Sep 17 00:00:00 2001 From: Margherita Capitani Date: Fri, 3 Jan 2025 14:18:24 +0100 Subject: [PATCH 01/16] Fix_add_bus_gen --- Snakefile | 2 +- scripts/add_electricity.py | 6 ++--- scripts/build_demand.py | 37 +++++++++++++------------ scripts/create_network.py | 55 +++++++++++++++++++++++++++++++++----- 4 files changed, 70 insertions(+), 30 deletions(-) diff --git a/Snakefile b/Snakefile index d1a03e9..bbb75db 100644 --- a/Snakefile +++ b/Snakefile @@ -113,7 +113,6 @@ rule build_demand: }, sample_profile=PROFILE, building_csv="resources/buildings/buildings_type.csv", - create_network="networks/base.nc", microgrid_shapes="resources/shapes/microgrid_shapes.geojson", clusters_with_buildings="resources/buildings/cluster_with_buildings.geojson", output: @@ -147,6 +146,7 @@ rule build_shapes: rule create_network: input: clusters="resources/buildings/clustered_buildings.geojson", + load="resources/demand/microgrid_load.csv", output: "networks/base.nc", log: diff --git a/scripts/add_electricity.py b/scripts/add_electricity.py index 46ed76b..6e91315 100644 --- a/scripts/add_electricity.py +++ b/scripts/add_electricity.py @@ -215,7 +215,7 @@ def attach_wind_and_solar( # {microgrid}, " " + tech, # TODO: review indexes # bus=f"new_bus_{microgrid}", - bus=ds.indexes["bus"], + bus="microgrids_gen_bus", carrier=tech, p_nom_extendable=tech in extendable_carriers["Generator"], p_nom_max=ds["p_nom_max"].to_pandas(), # look at the config @@ -280,7 +280,7 @@ def attach_conventional_generators( "Generator", ppl.index, carrier=ppl.carrier, - bus=ppl.bus, + bus="microgrids_gen_bus", p_nom_min=ppl.p_nom.where(ppl.carrier.isin(conventional_carriers), 0), p_nom=ppl.p_nom.where(ppl.carrier.isin(conventional_carriers), 0), p_nom_extendable=ppl.carrier.isin(extendable_carriers["Generator"]) @@ -331,7 +331,7 @@ def attach_storageunits(n, costs, number_microgrids, technologies, extendable_ca "StorageUnit", microgrid_ids, " " + tech, - bus=["bus_9"], + bus="microgrids_gen_bus", carrier=tech, p_nom_extendable=True, capital_cost=costs.at[tech, "capital_cost"], diff --git a/scripts/build_demand.py b/scripts/build_demand.py index 37f1f27..41ff6fb 100644 --- a/scripts/build_demand.py +++ b/scripts/build_demand.py @@ -187,7 +187,6 @@ def estimate_microgrid_population(raster_path, shapes_path, output_file): def calculate_load( - n, p, raster_path, shapes_path, @@ -201,7 +200,7 @@ def calculate_load( ): """ Calculate the microgrid demand based on a load profile provided as input, - appropriately scaled according to the population calculated for each cluster + appropriately scaled according to the population calculated for each cluster. The output includes a time-indexed DataFrame containing the load for each bus in the microgrid and is saved as a CSV file. @@ -224,18 +223,19 @@ def calculate_load( microgrids_list : dict Dictionary with microgrid names as keys and their cluster information as values. start_date : str - Start date for filtering the time series data + Start date for filtering the time series data. end_date : str - End date for filtering the time series data + End date for filtering the time series data. inclusive : str Specifies whether the filtering is inclusive of the start or end date. Possible values: "left" or "right". + Returns ------- pd.DataFrame DataFrame containing the calculated load profile for all microgrids. """ - # Estimate the population for the two microgrid + # Estimate the population for the two microgrids pop_microgrid = estimate_microgrid_population(raster_path, shapes_path, output_file) # Load the building classification data building_class = pd.read_csv(input_path) @@ -248,16 +248,15 @@ def calculate_load( time_index = pd.date_range(start="2013-01-01", end="2013-12-31 23:00:00", freq="h") df = df.set_index(time_index) - # Apply time filtering based on the specified start and end dates - if inclusive == "left": - end_date = (pd.to_datetime(end_date) - pd.Timedelta(days=1)).strftime( - "%Y-%m-%d" - ) + # Generate the snapshots range for filtering + snapshots_range = pd.date_range(start=start_date, end=end_date, freq="h", inclusive="both") - df_filtered = df.loc[start_date:end_date] # Filter the time series data + # Filter the DataFrame based on the specified time range + df_filtered = df.loc[snapshots_range] per_unit_load = df_filtered["per_unit_load"].values + # Loop over each microgrid - for grid_name, grid_data in microgrids_list.items(): + for grid_name in microgrids_list.keys(): # Filter buildings belonging to the current microgrid total_buildings = building_class[building_class["name_microgrid"] == grid_name] total_buildings = total_buildings["count"].sum() @@ -286,21 +285,24 @@ def calculate_load( load_per_cluster.rename(columns=new_column_names, inplace=True) # Add the DataFrame for the microgrid to the dictionary microgrid_dataframes[grid_name] = load_per_cluster + # Concatenate all microgrid DataFrames horizontally all_load_per_cluster = pd.concat(microgrid_dataframes.values(), axis=1) - # Add time indexing based on the PyPSA network snapshots - if hasattr(n, "snapshots") and len(n.snapshots) == len(all_load_per_cluster): - all_load_per_cluster.insert(0, "timestamp", n.snapshots) + + # Verify that the length of snapshots matches the length of the load data + if len(snapshots_range) == len(all_load_per_cluster): + all_load_per_cluster.insert(0, "timestamp", snapshots_range) else: raise ValueError("Mismatch between the length of snapshots and load data rows.") + # Save the cumulative results to a CSV file all_load_per_cluster.to_csv(output_file, index=False) return all_load_per_cluster + def calculate_load_ramp( input_file_buildings, - n, p, raster_path, shapes_path, @@ -454,7 +456,6 @@ def calculate_load_ramp( configure_logging(snakemake) - n = pypsa.Network(snakemake.input.create_network) sample_profile = snakemake.input["sample_profile"] tier_percent = snakemake.params.tier["tier_percent"] date_start = snakemake.params.snapshots["start"] @@ -483,7 +484,6 @@ def calculate_load_ramp( ) if build_demand_model == 0: calculate_load( - n, snakemake.config["load"]["scaling_factor"], worldpop_path, snakemake.input["microgrid_shapes"], @@ -499,7 +499,6 @@ def calculate_load_ramp( elif build_demand_model == 1: calculate_load_ramp( snakemake.input["clusters_with_buildings"], - n, snakemake.config["load"]["scaling_factor"], worldpop_path, snakemake.input["microgrid_shapes"], diff --git a/scripts/create_network.py b/scripts/create_network.py index f59596f..dd54c26 100644 --- a/scripts/create_network.py +++ b/scripts/create_network.py @@ -37,13 +37,40 @@ def create_network(): # Return the created network return n - -def create_microgrid_network(n, input_file, voltage_level, line_type, microgrid_list): +def calculate_power_node_position(input_path, cluster_path): + load_file = pd.read_csv(input_path, index_col=False) + load_sums = load_file.sum(numeric_only=True) + gdf = gpd.read_file(cluster_path) + data = [] + for _, feature in gdf.iterrows(): + cluster = feature["cluster"] + coordinates = feature.geometry.coords[0] + data.append({"cluster": cluster, "x": coordinates[0], "y": coordinates[1]}) + # Create a DataFrame + df = pd.DataFrame(data) + # Add load_sums as a new column to the DataFrame + df["cluster_load"] = load_sums.values + weights = [] + for i in load_sums.values: + weight = i/sum(load_sums.values) + weights.append(weight) + df["weight"] = weights + x = 0 + y = 0 + for i in range(len(df)): + x += df.loc[i, "x"] * df.loc[i, "weight"] + y += df.loc[i, "y"] * df.loc[i, "weight"] + + return x,y + + + +def create_microgrid_network(n, input_file, voltage_level, line_type, microgrid_list, input_path): """ Creates local microgrid networks within the PyPSA network. The local microgrid networks are distribution networks created based on the buildings data, stored in "resources/buildings/microgrids_buildings.geojson". Each bus corresponds to a cluster of buildings within a microgrid, with its coordinates defined in the input GeoJSON file. - The lines connecting buses are determined using Delaunay triangulation,ensuring minimal total line length. + The lines connecting buses are determined using Delaunay triangulation, ensuring minimal total line length. The function avoids duplicate buses and ensures buses are assigned to the correct SubNetwork. Parameters ---------- @@ -68,6 +95,18 @@ def create_microgrid_network(n, input_file, voltage_level, line_type, microgrid_ data = gpd.read_file(input_file) bus_coords = set() # Keep track of bus coordinates to avoid duplicates + # List to store bus names and their positions for triangulation + microgrid_buses = [] + bus_positions = [] + + # Get the center coordinates of the microgrid bus + x_center, y_center = calculate_power_node_position(input_path, input_file) + # Add the central bus to the network + gen_bus_name = "microgrids_gen_bus" + n.add("Bus", gen_bus_name, x=x_center, y=y_center, v_nom=voltage_level) + microgrid_buses.append(gen_bus_name) + bus_positions.append((x_center, y_center)) + for grid_name, grid_data in microgrid_list.items(): # Filter data for the current microgrid grid_data = data[data["name_microgrid"] == grid_name] @@ -76,9 +115,6 @@ def create_microgrid_network(n, input_file, voltage_level, line_type, microgrid_ if grid_name not in n.sub_networks.index: n.add("SubNetwork", grid_name, carrier="electricity") - # List to store bus names and their positions for triangulation - microgrid_buses = [] - bus_positions = [] for _, feature in grid_data.iterrows(): point_geom = feature.geometry @@ -99,6 +135,7 @@ def create_microgrid_network(n, input_file, voltage_level, line_type, microgrid_ microgrid_buses.append(bus_name) bus_positions.append((x, y)) + # Check if there are enough points for triangulation if len(bus_positions) < 3: print(f"Not enough points for triangulation in {grid_name}.") @@ -247,8 +284,12 @@ def create_microgrid_network(n, input_file, voltage_level, line_type, microgrid_ snakemake.config["electricity"]["voltage"], snakemake.config["electricity"]["line_type"], microgrids_list, + snakemake.input["load"], ) - + #calculate_power_node_position( + # snakemake.input["load"], + # snakemake.input["clusters"] + #) # add_bus_at_center(n, # snakemake.config["microgrids_list"], # snakemake.config["electricity"]["voltage"], From 14d1634e6cc8bc5c853fe1b2004c944b747de4b6 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 3 Jan 2025 13:24:56 +0000 Subject: [PATCH 02/16] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- scripts/build_demand.py | 5 +++-- scripts/create_network.py | 22 +++++++++++----------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/scripts/build_demand.py b/scripts/build_demand.py index 41ff6fb..620b962 100644 --- a/scripts/build_demand.py +++ b/scripts/build_demand.py @@ -249,7 +249,9 @@ def calculate_load( df = df.set_index(time_index) # Generate the snapshots range for filtering - snapshots_range = pd.date_range(start=start_date, end=end_date, freq="h", inclusive="both") + snapshots_range = pd.date_range( + start=start_date, end=end_date, freq="h", inclusive="both" + ) # Filter the DataFrame based on the specified time range df_filtered = df.loc[snapshots_range] @@ -300,7 +302,6 @@ def calculate_load( return all_load_per_cluster - def calculate_load_ramp( input_file_buildings, p, diff --git a/scripts/create_network.py b/scripts/create_network.py index dd54c26..dc6bc75 100644 --- a/scripts/create_network.py +++ b/scripts/create_network.py @@ -37,6 +37,7 @@ def create_network(): # Return the created network return n + def calculate_power_node_position(input_path, cluster_path): load_file = pd.read_csv(input_path, index_col=False) load_sums = load_file.sum(numeric_only=True) @@ -52,7 +53,7 @@ def calculate_power_node_position(input_path, cluster_path): df["cluster_load"] = load_sums.values weights = [] for i in load_sums.values: - weight = i/sum(load_sums.values) + weight = i / sum(load_sums.values) weights.append(weight) df["weight"] = weights x = 0 @@ -61,11 +62,12 @@ def calculate_power_node_position(input_path, cluster_path): x += df.loc[i, "x"] * df.loc[i, "weight"] y += df.loc[i, "y"] * df.loc[i, "weight"] - return x,y + return x, y - -def create_microgrid_network(n, input_file, voltage_level, line_type, microgrid_list, input_path): +def create_microgrid_network( + n, input_file, voltage_level, line_type, microgrid_list, input_path +): """ Creates local microgrid networks within the PyPSA network. The local microgrid networks are distribution networks created based on the buildings data, stored in "resources/buildings/microgrids_buildings.geojson". @@ -106,7 +108,7 @@ def create_microgrid_network(n, input_file, voltage_level, line_type, microgrid_ n.add("Bus", gen_bus_name, x=x_center, y=y_center, v_nom=voltage_level) microgrid_buses.append(gen_bus_name) bus_positions.append((x_center, y_center)) - + for grid_name, grid_data in microgrid_list.items(): # Filter data for the current microgrid grid_data = data[data["name_microgrid"] == grid_name] @@ -115,7 +117,6 @@ def create_microgrid_network(n, input_file, voltage_level, line_type, microgrid_ if grid_name not in n.sub_networks.index: n.add("SubNetwork", grid_name, carrier="electricity") - for _, feature in grid_data.iterrows(): point_geom = feature.geometry bus_name = f"{grid_name}_bus_{feature['cluster']}" @@ -135,7 +136,6 @@ def create_microgrid_network(n, input_file, voltage_level, line_type, microgrid_ microgrid_buses.append(bus_name) bus_positions.append((x, y)) - # Check if there are enough points for triangulation if len(bus_positions) < 3: print(f"Not enough points for triangulation in {grid_name}.") @@ -286,10 +286,10 @@ def create_microgrid_network(n, input_file, voltage_level, line_type, microgrid_ microgrids_list, snakemake.input["load"], ) - #calculate_power_node_position( - # snakemake.input["load"], - # snakemake.input["clusters"] - #) + # calculate_power_node_position( + # snakemake.input["load"], + # snakemake.input["clusters"] + # ) # add_bus_at_center(n, # snakemake.config["microgrids_list"], # snakemake.config["electricity"]["voltage"], From e77ca0caf04db36aeca541df21c53c3dcb33096f Mon Sep 17 00:00:00 2001 From: Margherita Capitani Date: Thu, 9 Jan 2025 11:25:44 +0100 Subject: [PATCH 03/16] Update_multi_microgrid --- scripts/add_electricity.py | 171 ++++++++++++++++++------------------- scripts/build_shapes.py | 2 +- scripts/create_network.py | 35 ++++---- 3 files changed, 99 insertions(+), 109 deletions(-) diff --git a/scripts/add_electricity.py b/scripts/add_electricity.py index 6e91315..fa17dc7 100644 --- a/scripts/add_electricity.py +++ b/scripts/add_electricity.py @@ -87,20 +87,13 @@ def _add_missing_carriers_from_costs(n, costs, carriers): def load_costs(tech_costs, config, elec_config, Nyears=1): """ - Set all asset costs and other parameters. + set all asset costs and other parameters """ - idx = pd.IndexSlice costs = pd.read_csv(tech_costs, index_col=list(range(3))).sort_index() - costs.loc[ - costs.unit.str.contains("/kWel"), "value" - ] *= 1e3 # Convert EUR/kW to EUR/MW - costs.loc[ - costs.unit.str.contains("/kWh"), "value" - ] *= 1e3 # Convert EUR/kWh to EUR/MWh - costs.loc[costs.unit.str.contains("USD"), "value"] *= config[ - "USD2013_to_EUR2013" - ] # Convert USD to EUR + # correct units to MW and EUR + costs.loc[costs.unit.str.contains("/kW"), "value"] *= 1e3 + costs.loc[costs.unit.str.contains("USD"), "value"] *= config["USD2013_to_EUR2013"] costs = ( costs.loc[idx[:, config["year"], :], "value"] @@ -156,13 +149,17 @@ def costs_for_storage(store, link1, link2=None, max_hours=1.0): max_hours = elec_config["max_hours"] costs.loc["battery"] = costs_for_storage( - costs.loc["lithium"], + costs.loc[ + "lithium" + ], # line 119 in file costs.csv' which was battery storage was modified into lithium (same values left) costs.loc["battery inverter"], max_hours=max_hours["battery"], ) - + max_hours = elec_config["max_hours"] costs.loc["battery"] = costs_for_storage( - costs.loc["lead acid"], + costs.loc[ + "lead acid" + ], # line 120 in file 'costs.csv' which was battery storage was modified into lithium (same values left) costs.loc["battery inverter"], max_hours=max_hours["battery"], ) @@ -193,45 +190,39 @@ def attach_wind_and_solar( # Add any missing carriers from the costs data to the tech_modelling variable _add_missing_carriers_from_costs(n, costs, tech_modelling) - number_microgrids = len(number_microgrids.keys()) - microgrid_ids = [f"microgrid_{i+1}" for i in range(number_microgrids)] + microgrid_ids = [f"microgrid_{i+1}" for i in range(len(number_microgrids.keys()))] # Iterate over each technology for tech in tech_modelling: - # Iterate through each microgrid - # for microgrid in microgrid_ids: #TODO: review this function - # Open the dataset for the current technology from the input_profiles with xr.open_dataset(getattr(snakemake.input, "profile_" + tech)) as ds: # If the dataset's "bus" index is empty, skip to the next technology if ds.indexes["bus"].empty: continue - suptech = tech.split("-", 2)[0] - # Add the wind and solar generators to the power network - n.madd( - "Generator", - ds.indexes["bus"], - # {microgrid}, - " " + tech, # TODO: review indexes - # bus=f"new_bus_{microgrid}", - bus="microgrids_gen_bus", - carrier=tech, - p_nom_extendable=tech in extendable_carriers["Generator"], - p_nom_max=ds["p_nom_max"].to_pandas(), # look at the config - weight=ds["weight"].to_pandas(), - marginal_cost=costs.at[suptech, "marginal_cost"], - capital_cost=costs.at[tech, "capital_cost"], - efficiency=costs.at[suptech, "efficiency"], - p_set=ds["profile"] - .transpose("time", "bus") - .to_pandas() - .reindex(n.snapshots), - p_max_pu=ds["profile"] - .transpose("time", "bus") - .to_pandas() - .reindex(n.snapshots), - ) + suptech = tech.split("-", 2)[0] + # Add the wind and solar generators to the power network + + for bus_index in ds.indexes["bus"]: + gen_bus = f"{bus_index}" + n.madd( + "Generator", + [f"{tech}_{bus_index}"], + carrier=tech, + bus=gen_bus, + p_nom_extendable=tech in extendable_carriers["Generator"], + p_nom_max=ds["p_nom_max"].to_pandas().loc[bus_index], + weight=ds["weight"].to_pandas().loc[bus_index], + marginal_cost=costs.at[suptech, "marginal_cost"], + capital_cost=costs.at[tech, "capital_cost"], + efficiency=costs.at[suptech, "efficiency"], + p_set=ds["profile"] + .to_pandas() + .reindex(n.snapshots).loc[:, bus_index], + p_max_pu=ds["profile"] + .to_pandas() + .reindex(n.snapshots).loc[:, bus_index], + ) def load_powerplants(ppl_fn): @@ -262,11 +253,15 @@ def attach_conventional_generators( extendable_carriers, conventional_config, conventional_inputs, + number_microgrids ): + # Create a set of all conventional and extendable carriers carriers = set(conventional_carriers) | set(extendable_carriers["Generator"]) + # Add any missing carriers from the costs data to the "carriers" variable _add_missing_carriers_from_costs(n, costs, carriers) + # Filter the ppl dataframe to only include the relevant carriers ppl = ( ppl.query("carrier in @carriers") .join(costs, on="carrier", rsuffix="_r") @@ -274,26 +269,29 @@ def attach_conventional_generators( ) ppl["efficiency"] = ppl.efficiency.fillna(ppl.efficiency) - buses_i = n.buses.index - - n.madd( - "Generator", - ppl.index, - carrier=ppl.carrier, - bus="microgrids_gen_bus", - p_nom_min=ppl.p_nom.where(ppl.carrier.isin(conventional_carriers), 0), - p_nom=ppl.p_nom.where(ppl.carrier.isin(conventional_carriers), 0), - p_nom_extendable=ppl.carrier.isin(extendable_carriers["Generator"]) - | (ppl.carrier == "diesel"), - efficiency=ppl.efficiency, - marginal_cost=ppl.marginal_cost, - capital_cost=ppl.capital_cost, - build_year=ppl.datein.fillna(0).astype(int), - lifetime=(ppl.dateout - ppl.datein).fillna(np.inf), - ) + # Get the index of the buses in the power network + microgrid_ids = [f"microgrid_{i+1}" for i in range(len(number_microgrids.keys()))] + for microgrid in microgrid_ids: + gen_bus = f"{microgrid}_gen_bus" + gen_name = [f"{microgrid}_gen"] + + # Add conventional generators to each bus in the power network (one for microgrid) + n.madd( + "Generator", + gen_name, + carrier=ppl.carrier, + bus=gen_bus, + p_nom_min=ppl.p_nom.where(ppl.carrier.isin(conventional_carriers), 0), + p_nom=ppl.p_nom.where(ppl.carrier.isin(conventional_carriers), 0), + p_nom_extendable=ppl.carrier.isin(extendable_carriers["Generator"]), + efficiency=ppl.efficiency, + marginal_cost=ppl.marginal_cost, + build_year=1984, #TODO: review this + lifetime=(ppl.dateout - ppl.datein).fillna(np.inf), + ) for carrier in conventional_config: - # Generatori con tecnologia influenzata + # Generators with technology affected idx = n.generators.query("carrier == @carrier").index for attr in list(set(conventional_config[carrier]) & set(n.generators)): @@ -308,7 +306,7 @@ def attach_conventional_generators( n.generators.loc[idx].bus.map(bus_values).dropna() ) else: - # Single value affecting all k technology generators regardless of country. + # Single value affecting all generators of technology k indiscriminately of country n.generators.loc[idx, attr] = values @@ -327,24 +325,26 @@ def attach_storageunits(n, costs, number_microgrids, technologies, extendable_ca # Add the storage units to the power network for tech in technologies: - n.madd( - "StorageUnit", - microgrid_ids, - " " + tech, - bus="microgrids_gen_bus", - carrier=tech, - p_nom_extendable=True, - capital_cost=costs.at[tech, "capital_cost"], - marginal_cost=costs.at[tech, "marginal_cost"], - efficiency_store=costs.at[ - lookup_store["battery"], "efficiency" - ], # Lead_acid and lithium have the same value - efficiency_dispatch=costs.at[ - lookup_dispatch["battery"], "efficiency" - ], # Lead_acid and lithium have the same value - max_hours=max_hours["battery"], # Lead_acid and lithium have the same value - cyclic_state_of_charge=True, - ) + microgrid_ids = [f"microgrid_{i+1}" for i in range(len(number_microgrids.keys()))] + for microgrid in microgrid_ids: + gen_bus = f"{microgrid}_gen_bus" + n.madd( + "StorageUnit", + [f"{microgrid}_{tech}"], + bus=gen_bus, + carrier=tech, + p_nom_extendable=True, + capital_cost=costs.at[tech, "capital_cost"], + marginal_cost=costs.at[tech, "marginal_cost"], + efficiency_store=costs.at[ + lookup_store["battery"], "efficiency" + ], # Lead_acid and lithium have the same value + efficiency_dispatch=costs.at[ + lookup_dispatch["battery"], "efficiency" + ], # Lead_acid and lithium have the same value + max_hours=max_hours["battery"], # Lead_acid and lithium have the same value + cyclic_state_of_charge=True, + ) def attach_load(n, load_file, tech_modelling): @@ -355,12 +355,6 @@ def attach_load(n, load_file, tech_modelling): n.madd("Load", demand_df.columns, bus=demand_df.columns, p_set=demand_df) -def update_transmission_costs(n, costs, length_factor=1.0, simple_hvdc_costs=False): - n.lines["capital_cost"] = ( - n.lines["length"] * length_factor * costs.at["MVAC overhead", "capital_cost"] - ) - - if __name__ == "__main__": if "snakemake" not in globals(): from _helpers_dist import mock_snakemake @@ -406,6 +400,7 @@ def update_transmission_costs(n, costs, length_factor=1.0, simple_hvdc_costs=Fal snakemake.config["electricity"]["extendable_carriers"], snakemake.config.get("conventional", {}), conventional_inputs, + snakemake.config["microgrids_list"] ) attach_storageunits( @@ -423,6 +418,4 @@ def update_transmission_costs(n, costs, length_factor=1.0, simple_hvdc_costs=Fal snakemake.config["tech_modelling"]["load_carriers"], ) - update_transmission_costs(n, costs, length_factor=1.0, simple_hvdc_costs=False) - n.export_to_netcdf(snakemake.output[0]) diff --git a/scripts/build_shapes.py b/scripts/build_shapes.py index 481ed40..0c8f492 100644 --- a/scripts/build_shapes.py +++ b/scripts/build_shapes.py @@ -84,7 +84,7 @@ def create_bus_regions(microgrids_list, output_path): # Iterate over each column in the DataFrame for col in range(len(microgrids_list_df.columns)): values = microgrids_list_df.iloc[:, col] - microgrid_name = microgrids_list_df.columns[col] + "_bus_renewable" + microgrid_name = microgrids_list_df.columns[col] + "_gen_bus" # Define the vertices of the rectangle Top_left = (values[0], values[3]) diff --git a/scripts/create_network.py b/scripts/create_network.py index dd54c26..1658a34 100644 --- a/scripts/create_network.py +++ b/scripts/create_network.py @@ -37,10 +37,9 @@ def create_network(): # Return the created network return n -def calculate_power_node_position(input_path, cluster_path): - load_file = pd.read_csv(input_path, index_col=False) +def calculate_power_node_position(load_file, cluster_bus): load_sums = load_file.sum(numeric_only=True) - gdf = gpd.read_file(cluster_path) + gdf = cluster_bus data = [] for _, feature in gdf.iterrows(): cluster = feature["cluster"] @@ -52,7 +51,7 @@ def calculate_power_node_position(input_path, cluster_path): df["cluster_load"] = load_sums.values weights = [] for i in load_sums.values: - weight = i/sum(load_sums.values) + weight = i / sum(load_sums.values) weights.append(weight) df["weight"] = weights x = 0 @@ -61,7 +60,8 @@ def calculate_power_node_position(input_path, cluster_path): x += df.loc[i, "x"] * df.loc[i, "weight"] y += df.loc[i, "y"] * df.loc[i, "weight"] - return x,y + return x, y + @@ -93,29 +93,27 @@ def create_microgrid_network(n, input_file, voltage_level, line_type, microgrid_ """ data = gpd.read_file(input_file) + load = pd.read_csv(input_path) bus_coords = set() # Keep track of bus coordinates to avoid duplicates - # List to store bus names and their positions for triangulation - microgrid_buses = [] - bus_positions = [] - - # Get the center coordinates of the microgrid bus - x_center, y_center = calculate_power_node_position(input_path, input_file) - # Add the central bus to the network - gen_bus_name = "microgrids_gen_bus" - n.add("Bus", gen_bus_name, x=x_center, y=y_center, v_nom=voltage_level) - microgrid_buses.append(gen_bus_name) - bus_positions.append((x_center, y_center)) - for grid_name, grid_data in microgrid_list.items(): + # List to store bus names and their positions for triangulation + microgrid_buses = [] + bus_positions = [] + # Filter data for the current microgrid grid_data = data[data["name_microgrid"] == grid_name] + load_data = load[[col for col in load.columns if grid_name in col]] + x_gen_bus, y_gen_bus = calculate_power_node_position(load_data, grid_data) + gen_bus_name = f"{grid_name}_gen_bus" + n.add("Bus", gen_bus_name, x=x_gen_bus, y=y_gen_bus, v_nom=voltage_level, sub_network=grid_name) + microgrid_buses.append(gen_bus_name) + bus_positions.append((x_gen_bus, y_gen_bus)) # Create a SubNetwork for the current microgrid if it does not exist if grid_name not in n.sub_networks.index: n.add("SubNetwork", grid_name, carrier="electricity") - for _, feature in grid_data.iterrows(): point_geom = feature.geometry bus_name = f"{grid_name}_bus_{feature['cluster']}" @@ -135,7 +133,6 @@ def create_microgrid_network(n, input_file, voltage_level, line_type, microgrid_ microgrid_buses.append(bus_name) bus_positions.append((x, y)) - # Check if there are enough points for triangulation if len(bus_positions) < 3: print(f"Not enough points for triangulation in {grid_name}.") From 78e3f10606859f9da671b89fede6a955139fafc8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 9 Jan 2025 10:34:31 +0000 Subject: [PATCH 04/16] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- scripts/add_electricity.py | 22 ++++++++++++++-------- scripts/create_network.py | 9 ++++++++- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/scripts/add_electricity.py b/scripts/add_electricity.py index fa17dc7..e9b8f32 100644 --- a/scripts/add_electricity.py +++ b/scripts/add_electricity.py @@ -202,7 +202,7 @@ def attach_wind_and_solar( suptech = tech.split("-", 2)[0] # Add the wind and solar generators to the power network - + for bus_index in ds.indexes["bus"]: gen_bus = f"{bus_index}" n.madd( @@ -218,10 +218,12 @@ def attach_wind_and_solar( efficiency=costs.at[suptech, "efficiency"], p_set=ds["profile"] .to_pandas() - .reindex(n.snapshots).loc[:, bus_index], + .reindex(n.snapshots) + .loc[:, bus_index], p_max_pu=ds["profile"] .to_pandas() - .reindex(n.snapshots).loc[:, bus_index], + .reindex(n.snapshots) + .loc[:, bus_index], ) @@ -253,7 +255,7 @@ def attach_conventional_generators( extendable_carriers, conventional_config, conventional_inputs, - number_microgrids + number_microgrids, ): # Create a set of all conventional and extendable carriers carriers = set(conventional_carriers) | set(extendable_carriers["Generator"]) @@ -286,7 +288,7 @@ def attach_conventional_generators( p_nom_extendable=ppl.carrier.isin(extendable_carriers["Generator"]), efficiency=ppl.efficiency, marginal_cost=ppl.marginal_cost, - build_year=1984, #TODO: review this + build_year=1984, # TODO: review this lifetime=(ppl.dateout - ppl.datein).fillna(np.inf), ) @@ -325,7 +327,9 @@ def attach_storageunits(n, costs, number_microgrids, technologies, extendable_ca # Add the storage units to the power network for tech in technologies: - microgrid_ids = [f"microgrid_{i+1}" for i in range(len(number_microgrids.keys()))] + microgrid_ids = [ + f"microgrid_{i+1}" for i in range(len(number_microgrids.keys())) + ] for microgrid in microgrid_ids: gen_bus = f"{microgrid}_gen_bus" n.madd( @@ -342,7 +346,9 @@ def attach_storageunits(n, costs, number_microgrids, technologies, extendable_ca efficiency_dispatch=costs.at[ lookup_dispatch["battery"], "efficiency" ], # Lead_acid and lithium have the same value - max_hours=max_hours["battery"], # Lead_acid and lithium have the same value + max_hours=max_hours[ + "battery" + ], # Lead_acid and lithium have the same value cyclic_state_of_charge=True, ) @@ -400,7 +406,7 @@ def attach_load(n, load_file, tech_modelling): snakemake.config["electricity"]["extendable_carriers"], snakemake.config.get("conventional", {}), conventional_inputs, - snakemake.config["microgrids_list"] + snakemake.config["microgrids_list"], ) attach_storageunits( diff --git a/scripts/create_network.py b/scripts/create_network.py index fe50f5c..0bc9ec4 100644 --- a/scripts/create_network.py +++ b/scripts/create_network.py @@ -108,7 +108,14 @@ def create_microgrid_network( load_data = load[[col for col in load.columns if grid_name in col]] x_gen_bus, y_gen_bus = calculate_power_node_position(load_data, grid_data) gen_bus_name = f"{grid_name}_gen_bus" - n.add("Bus", gen_bus_name, x=x_gen_bus, y=y_gen_bus, v_nom=voltage_level, sub_network=grid_name) + n.add( + "Bus", + gen_bus_name, + x=x_gen_bus, + y=y_gen_bus, + v_nom=voltage_level, + sub_network=grid_name, + ) microgrid_buses.append(gen_bus_name) bus_positions.append((x_gen_bus, y_gen_bus)) From 5b003f6d57d4879e57bb6bce5c78b34eeabc68d7 Mon Sep 17 00:00:00 2001 From: Margherita Capitani Date: Thu, 9 Jan 2025 12:21:20 +0100 Subject: [PATCH 05/16] Fix_error_in_create_network --- scripts/create_network.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/scripts/create_network.py b/scripts/create_network.py index fe50f5c..5897e71 100644 --- a/scripts/create_network.py +++ b/scripts/create_network.py @@ -38,8 +38,7 @@ def create_network(): return n -def calculate_power_node_position(input_path, cluster_path): - load_file = pd.read_csv(input_path, index_col=False) +def calculate_power_node_position(load_file, cluster_bus): load_sums = load_file.sum(numeric_only=True) gdf = cluster_bus data = [] @@ -64,7 +63,6 @@ def calculate_power_node_position(input_path, cluster_path): return x, y - def create_microgrid_network( n, input_file, voltage_level, line_type, microgrid_list, input_path ): From 19f2742f752f5ca2609e27faf43f00eda003b6ff Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 9 Jan 2025 11:22:55 +0000 Subject: [PATCH 06/16] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- scripts/create_network.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/create_network.py b/scripts/create_network.py index 67695dc..1f11616 100644 --- a/scripts/create_network.py +++ b/scripts/create_network.py @@ -63,6 +63,7 @@ def calculate_power_node_position(load_file, cluster_bus): return x, y + def create_microgrid_network( n, input_file, voltage_level, line_type, microgrid_list, input_path ): From bf64f79201d8a7bd0fc8ffc7fe4fb0267f1c2105 Mon Sep 17 00:00:00 2001 From: Margherita Capitani Date: Mon, 13 Jan 2025 18:22:13 +0100 Subject: [PATCH 07/16] Fix_Add_electricity --- scripts/add_electricity.py | 206 +++++++++++++++++-------------------- 1 file changed, 96 insertions(+), 110 deletions(-) diff --git a/scripts/add_electricity.py b/scripts/add_electricity.py index e9b8f32..da40326 100644 --- a/scripts/add_electricity.py +++ b/scripts/add_electricity.py @@ -190,41 +190,45 @@ def attach_wind_and_solar( # Add any missing carriers from the costs data to the tech_modelling variable _add_missing_carriers_from_costs(n, costs, tech_modelling) - microgrid_ids = [f"microgrid_{i+1}" for i in range(len(number_microgrids.keys()))] + number_microgrids = len(number_microgrids.keys()) + microgrid_ids = [f"microgrid_{i+1}" for i in range(number_microgrids)] # Iterate over each technology for tech in tech_modelling: + # Iterate through each microgrid + # for microgrid in microgrid_ids: #TODO: review this function + # Open the dataset for the current technology from the input_profiles with xr.open_dataset(getattr(snakemake.input, "profile_" + tech)) as ds: # If the dataset's "bus" index is empty, skip to the next technology if ds.indexes["bus"].empty: continue - suptech = tech.split("-", 2)[0] - # Add the wind and solar generators to the power network - - for bus_index in ds.indexes["bus"]: - gen_bus = f"{bus_index}" - n.madd( - "Generator", - [f"{tech}_{bus_index}"], - carrier=tech, - bus=gen_bus, - p_nom_extendable=tech in extendable_carriers["Generator"], - p_nom_max=ds["p_nom_max"].to_pandas().loc[bus_index], - weight=ds["weight"].to_pandas().loc[bus_index], - marginal_cost=costs.at[suptech, "marginal_cost"], - capital_cost=costs.at[tech, "capital_cost"], - efficiency=costs.at[suptech, "efficiency"], - p_set=ds["profile"] - .to_pandas() - .reindex(n.snapshots) - .loc[:, bus_index], - p_max_pu=ds["profile"] - .to_pandas() - .reindex(n.snapshots) - .loc[:, bus_index], - ) + suptech = tech.split("-", 2)[0] + # Add the wind and solar generators to the power network + n.madd( + "Generator", + ds.indexes["bus"], + # {microgrid}, + " " + tech, # TODO: review indexes + # bus=f"new_bus_{microgrid}", + bus=ds.indexes["bus"], + carrier=tech, + p_nom_extendable=tech in extendable_carriers["Generator"], + p_nom_max=ds["p_nom_max"].to_pandas(), # look at the config + weight=ds["weight"].to_pandas(), + marginal_cost=costs.at[suptech, "marginal_cost"], + capital_cost=costs.at[tech, "capital_cost"], + efficiency=costs.at[suptech, "efficiency"], + p_set=ds["profile"] + .transpose("time", "bus") + .to_pandas() + .reindex(n.snapshots), + p_max_pu=ds["profile"] + .transpose("time", "bus") + .to_pandas() + .reindex(n.snapshots), + ) def load_powerplants(ppl_fn): @@ -246,70 +250,58 @@ def load_powerplants(ppl_fn): .replace({"carrier": carrier_dict}) ) - def attach_conventional_generators( - n, - costs, - ppl, - conventional_carriers, - extendable_carriers, - conventional_config, - conventional_inputs, - number_microgrids, -): - # Create a set of all conventional and extendable carriers - carriers = set(conventional_carriers) | set(extendable_carriers["Generator"]) - - # Add any missing carriers from the costs data to the "carriers" variable - _add_missing_carriers_from_costs(n, costs, carriers) - - # Filter the ppl dataframe to only include the relevant carriers - ppl = ( - ppl.query("carrier in @carriers") - .join(costs, on="carrier", rsuffix="_r") - .rename(index=lambda s: "C" + str(s)) - ) - ppl["efficiency"] = ppl.efficiency.fillna(ppl.efficiency) - - # Get the index of the buses in the power network - microgrid_ids = [f"microgrid_{i+1}" for i in range(len(number_microgrids.keys()))] - for microgrid in microgrid_ids: - gen_bus = f"{microgrid}_gen_bus" - gen_name = [f"{microgrid}_gen"] - - # Add conventional generators to each bus in the power network (one for microgrid) - n.madd( - "Generator", - gen_name, - carrier=ppl.carrier, - bus=gen_bus, - p_nom_min=ppl.p_nom.where(ppl.carrier.isin(conventional_carriers), 0), - p_nom=ppl.p_nom.where(ppl.carrier.isin(conventional_carriers), 0), - p_nom_extendable=ppl.carrier.isin(extendable_carriers["Generator"]), - efficiency=ppl.efficiency, - marginal_cost=ppl.marginal_cost, - build_year=1984, # TODO: review this - lifetime=(ppl.dateout - ppl.datein).fillna(np.inf), + n, + costs, + ppl, + conventional_carriers, + extendable_carriers, + conventional_config, + conventional_inputs, + number_microgrids, + ): + carriers = set(conventional_carriers) | set(extendable_carriers["Generator"]) + _add_missing_carriers_from_costs(n, costs, carriers) + + ppl = ( + ppl.query("carrier in @carriers") + .join(costs, on="carrier", rsuffix="_r") + .rename(index=lambda s: "C" + str(s)) ) + ppl["efficiency"] = ppl.efficiency.fillna(ppl.efficiency) + microgrid_ids = [f"microgrid_{i+1}" for i in range(len(number_microgrids))] + for microgrid in microgrid_ids: + ppl.index = ppl.index + "_" + microgrid #TODO: review this + ppl["bus"] = microgrid + "_gen_bus" + n.madd( + "Generator", + ppl.index, + carrier=ppl.carrier, + bus=ppl.bus, + p_nom_min=ppl.p_nom.where(ppl.carrier.isin(conventional_carriers), 0), + p_nom=ppl.p_nom.where(ppl.carrier.isin(conventional_carriers), 0), + p_nom_extendable=ppl.carrier.isin(extendable_carriers["Generator"]), + efficiency=ppl.efficiency, + marginal_cost=ppl.marginal_cost, + capital_cost=ppl.capital_cost, + build_year=ppl.datein.fillna(0).astype(pd.Int64Dtype()), + lifetime=(ppl.dateout - ppl.datein).fillna(np.inf), + ) - for carrier in conventional_config: - # Generators with technology affected - idx = n.generators.query("carrier == @carrier").index + for carrier in conventional_config: + idx = n.generators.query("carrier == @carrier").index - for attr in list(set(conventional_config[carrier]) & set(n.generators)): - values = conventional_config[carrier][attr] + for attr in list(set(conventional_config[carrier]) & set(n.generators)): + values = conventional_config[carrier][attr] - if f"conventional_{carrier}_{attr}" in conventional_inputs: - # Values affecting generators of technology k country-specific - # First map generator buses to countries; then map countries to p_max_pu - values = pd.read_csv(values, index_col=0).iloc[:, 0] - bus_values = n.buses.country.map(values) - n.generators[attr].update( - n.generators.loc[idx].bus.map(bus_values).dropna() - ) - else: - # Single value affecting all generators of technology k indiscriminately of country - n.generators.loc[idx, attr] = values + if f"conventional_{carrier}_{attr}" in conventional_inputs: + values = pd.read_csv(values, index_col=0).iloc[:, 0] + bus_values = n.buses.country.map(values) + n.generators[attr].update( + n.generators.loc[idx].bus.map(bus_values).dropna() + ) + else: + n.generators.loc[idx, attr] = values def attach_storageunits(n, costs, number_microgrids, technologies, extendable_carriers): @@ -326,31 +318,25 @@ def attach_storageunits(n, costs, number_microgrids, technologies, extendable_ca microgrid_ids = [f"microgrid_{i+1}" for i in range(len(number_microgrids))] # Add the storage units to the power network - for tech in technologies: - microgrid_ids = [ - f"microgrid_{i+1}" for i in range(len(number_microgrids.keys())) - ] - for microgrid in microgrid_ids: - gen_bus = f"{microgrid}_gen_bus" - n.madd( - "StorageUnit", - [f"{microgrid}_{tech}"], - bus=gen_bus, - carrier=tech, - p_nom_extendable=True, - capital_cost=costs.at[tech, "capital_cost"], - marginal_cost=costs.at[tech, "marginal_cost"], - efficiency_store=costs.at[ - lookup_store["battery"], "efficiency" - ], # Lead_acid and lithium have the same value - efficiency_dispatch=costs.at[ - lookup_dispatch["battery"], "efficiency" - ], # Lead_acid and lithium have the same value - max_hours=max_hours[ - "battery" - ], # Lead_acid and lithium have the same value - cyclic_state_of_charge=True, - ) + for i, tech in enumerate(technologies): + n.madd( + "StorageUnit", + [microgrid_ids[i]], + " " + tech, + bus=[f"microgrid_{i+1}_gen_bus"], + carrier=tech, + p_nom_extendable=True, + capital_cost=costs.at[tech, "capital_cost"], + marginal_cost=costs.at[tech, "marginal_cost"], + efficiency_store=costs.at[ + lookup_store["battery"], "efficiency" + ], # Lead_acid and lithium have the same value + efficiency_dispatch=costs.at[ + lookup_dispatch["battery"], "efficiency" + ], # Lead_acid and lithium have the same value + max_hours=max_hours["battery"], # Lead_acid and lithium have the same value + cyclic_state_of_charge=True, + ) def attach_load(n, load_file, tech_modelling): From f5efc5583294b71d7f520dfb1fe3a78f0916925a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2025 17:22:56 +0000 Subject: [PATCH 08/16] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- scripts/add_electricity.py | 101 +++++++++++++++++++------------------ 1 file changed, 51 insertions(+), 50 deletions(-) diff --git a/scripts/add_electricity.py b/scripts/add_electricity.py index da40326..c4c91e8 100644 --- a/scripts/add_electricity.py +++ b/scripts/add_electricity.py @@ -250,58 +250,59 @@ def load_powerplants(ppl_fn): .replace({"carrier": carrier_dict}) ) + def attach_conventional_generators( - n, - costs, - ppl, - conventional_carriers, - extendable_carriers, - conventional_config, - conventional_inputs, - number_microgrids, - ): - carriers = set(conventional_carriers) | set(extendable_carriers["Generator"]) - _add_missing_carriers_from_costs(n, costs, carriers) - - ppl = ( - ppl.query("carrier in @carriers") - .join(costs, on="carrier", rsuffix="_r") - .rename(index=lambda s: "C" + str(s)) + n, + costs, + ppl, + conventional_carriers, + extendable_carriers, + conventional_config, + conventional_inputs, + number_microgrids, +): + carriers = set(conventional_carriers) | set(extendable_carriers["Generator"]) + _add_missing_carriers_from_costs(n, costs, carriers) + + ppl = ( + ppl.query("carrier in @carriers") + .join(costs, on="carrier", rsuffix="_r") + .rename(index=lambda s: "C" + str(s)) + ) + ppl["efficiency"] = ppl.efficiency.fillna(ppl.efficiency) + microgrid_ids = [f"microgrid_{i+1}" for i in range(len(number_microgrids))] + for microgrid in microgrid_ids: + ppl.index = ppl.index + "_" + microgrid # TODO: review this + ppl["bus"] = microgrid + "_gen_bus" + n.madd( + "Generator", + ppl.index, + carrier=ppl.carrier, + bus=ppl.bus, + p_nom_min=ppl.p_nom.where(ppl.carrier.isin(conventional_carriers), 0), + p_nom=ppl.p_nom.where(ppl.carrier.isin(conventional_carriers), 0), + p_nom_extendable=ppl.carrier.isin(extendable_carriers["Generator"]), + efficiency=ppl.efficiency, + marginal_cost=ppl.marginal_cost, + capital_cost=ppl.capital_cost, + build_year=ppl.datein.fillna(0).astype(pd.Int64Dtype()), + lifetime=(ppl.dateout - ppl.datein).fillna(np.inf), ) - ppl["efficiency"] = ppl.efficiency.fillna(ppl.efficiency) - microgrid_ids = [f"microgrid_{i+1}" for i in range(len(number_microgrids))] - for microgrid in microgrid_ids: - ppl.index = ppl.index + "_" + microgrid #TODO: review this - ppl["bus"] = microgrid + "_gen_bus" - n.madd( - "Generator", - ppl.index, - carrier=ppl.carrier, - bus=ppl.bus, - p_nom_min=ppl.p_nom.where(ppl.carrier.isin(conventional_carriers), 0), - p_nom=ppl.p_nom.where(ppl.carrier.isin(conventional_carriers), 0), - p_nom_extendable=ppl.carrier.isin(extendable_carriers["Generator"]), - efficiency=ppl.efficiency, - marginal_cost=ppl.marginal_cost, - capital_cost=ppl.capital_cost, - build_year=ppl.datein.fillna(0).astype(pd.Int64Dtype()), - lifetime=(ppl.dateout - ppl.datein).fillna(np.inf), - ) - - for carrier in conventional_config: - idx = n.generators.query("carrier == @carrier").index - - for attr in list(set(conventional_config[carrier]) & set(n.generators)): - values = conventional_config[carrier][attr] - - if f"conventional_{carrier}_{attr}" in conventional_inputs: - values = pd.read_csv(values, index_col=0).iloc[:, 0] - bus_values = n.buses.country.map(values) - n.generators[attr].update( - n.generators.loc[idx].bus.map(bus_values).dropna() - ) - else: - n.generators.loc[idx, attr] = values + + for carrier in conventional_config: + idx = n.generators.query("carrier == @carrier").index + + for attr in list(set(conventional_config[carrier]) & set(n.generators)): + values = conventional_config[carrier][attr] + + if f"conventional_{carrier}_{attr}" in conventional_inputs: + values = pd.read_csv(values, index_col=0).iloc[:, 0] + bus_values = n.buses.country.map(values) + n.generators[attr].update( + n.generators.loc[idx].bus.map(bus_values).dropna() + ) + else: + n.generators.loc[idx, attr] = values def attach_storageunits(n, costs, number_microgrids, technologies, extendable_carriers): From ab97a11ac70360fa6e33cf49438dbb896a67ac89 Mon Sep 17 00:00:00 2001 From: Margherita Capitani Date: Tue, 14 Jan 2025 08:44:53 +0100 Subject: [PATCH 09/16] Last_fix --- scripts/add_electricity.py | 39 +++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/scripts/add_electricity.py b/scripts/add_electricity.py index da40326..57c4c31 100644 --- a/scripts/add_electricity.py +++ b/scripts/add_electricity.py @@ -318,25 +318,26 @@ def attach_storageunits(n, costs, number_microgrids, technologies, extendable_ca microgrid_ids = [f"microgrid_{i+1}" for i in range(len(number_microgrids))] # Add the storage units to the power network - for i, tech in enumerate(technologies): - n.madd( - "StorageUnit", - [microgrid_ids[i]], - " " + tech, - bus=[f"microgrid_{i+1}_gen_bus"], - carrier=tech, - p_nom_extendable=True, - capital_cost=costs.at[tech, "capital_cost"], - marginal_cost=costs.at[tech, "marginal_cost"], - efficiency_store=costs.at[ - lookup_store["battery"], "efficiency" - ], # Lead_acid and lithium have the same value - efficiency_dispatch=costs.at[ - lookup_dispatch["battery"], "efficiency" - ], # Lead_acid and lithium have the same value - max_hours=max_hours["battery"], # Lead_acid and lithium have the same value - cyclic_state_of_charge=True, - ) + for tech in technologies: + for microgrid in microgrid_ids: + n.madd( + "StorageUnit", + [microgrid], + " " + tech, + bus=[f"{microgrid}_gen_bus"], + carrier=tech, + p_nom_extendable=True, + capital_cost=costs.at[tech, "capital_cost"], + marginal_cost=costs.at[tech, "marginal_cost"], + efficiency_store=costs.at[ + lookup_store["battery"], "efficiency" + ], # Lead_acid and lithium have the same value + efficiency_dispatch=costs.at[ + lookup_dispatch["battery"], "efficiency" + ], # Lead_acid and lithium have the same value + max_hours=max_hours["battery"], # Lead_acid and lithium have the same value + cyclic_state_of_charge=True, + ) def attach_load(n, load_file, tech_modelling): From 82121783b425ff81e2bef6accb1da34014619ad7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 14 Jan 2025 07:45:12 +0000 Subject: [PATCH 10/16] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- scripts/add_electricity.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/add_electricity.py b/scripts/add_electricity.py index b1e1ba7..1f53a93 100644 --- a/scripts/add_electricity.py +++ b/scripts/add_electricity.py @@ -336,7 +336,9 @@ def attach_storageunits(n, costs, number_microgrids, technologies, extendable_ca efficiency_dispatch=costs.at[ lookup_dispatch["battery"], "efficiency" ], # Lead_acid and lithium have the same value - max_hours=max_hours["battery"], # Lead_acid and lithium have the same value + max_hours=max_hours[ + "battery" + ], # Lead_acid and lithium have the same value cyclic_state_of_charge=True, ) From f80de984caac44b2af4530bc6230411013824807 Mon Sep 17 00:00:00 2001 From: Margherita Capitani Date: Tue, 21 Jan 2025 10:02:34 +0100 Subject: [PATCH 11/16] Fix_after_comment --- resources/powerplants.csv | 2 +- scripts/add_electricity.py | 43 ++++++++++++++++++++------------------ scripts/create_network.py | 10 --------- 3 files changed, 24 insertions(+), 31 deletions(-) diff --git a/resources/powerplants.csv b/resources/powerplants.csv index 14c386e..a0ff1fc 100644 --- a/resources/powerplants.csv +++ b/resources/powerplants.csv @@ -1,2 +1,2 @@ ,Name,Fueltype,Technology,Set,Country,Capacity,Efficiency,Duration,Volume_Mm3,DamHeight_m,StorageCapacity_MWh,DateIn,DateRetrofit,DateOut,lat,lon,EIC,projectID,bus -1,New_Diesel_Generator,Diesel,,PP,SL,0.0,,0.0,0.0,0.0,0.0,1986,1986.0,2031,-21.1667,27.5167,{nan},{'GPD': {'WRI1023018'}},bus_9 +1,New_Diesel_Generator,Diesel,,PP,SL,0.0,,0.0,0.0,0.0,0.0,1986,1986.0,2031,-21.1667,27.5167,{nan},{'GPD': {'WRI1023018'}},microgrid_1_gen_bus diff --git a/scripts/add_electricity.py b/scripts/add_electricity.py index 1f53a93..b293471 100644 --- a/scripts/add_electricity.py +++ b/scripts/add_electricity.py @@ -259,9 +259,9 @@ def attach_conventional_generators( extendable_carriers, conventional_config, conventional_inputs, - number_microgrids, ): carriers = set(conventional_carriers) | set(extendable_carriers["Generator"]) + _add_missing_carriers_from_costs(n, costs, carriers) ppl = ( @@ -270,38 +270,42 @@ def attach_conventional_generators( .rename(index=lambda s: "C" + str(s)) ) ppl["efficiency"] = ppl.efficiency.fillna(ppl.efficiency) - microgrid_ids = [f"microgrid_{i+1}" for i in range(len(number_microgrids))] - for microgrid in microgrid_ids: - ppl.index = ppl.index + "_" + microgrid # TODO: review this - ppl["bus"] = microgrid + "_gen_bus" - n.madd( - "Generator", - ppl.index, - carrier=ppl.carrier, - bus=ppl.bus, - p_nom_min=ppl.p_nom.where(ppl.carrier.isin(conventional_carriers), 0), - p_nom=ppl.p_nom.where(ppl.carrier.isin(conventional_carriers), 0), - p_nom_extendable=ppl.carrier.isin(extendable_carriers["Generator"]), - efficiency=ppl.efficiency, - marginal_cost=ppl.marginal_cost, - capital_cost=ppl.capital_cost, - build_year=ppl.datein.fillna(0).astype(pd.Int64Dtype()), - lifetime=(ppl.dateout - ppl.datein).fillna(np.inf), - ) + + buses_i = n.buses.index + + n.madd( + "Generator", + ppl.index, + carrier=ppl.carrier, + bus=ppl.bus, + p_nom_min=ppl.p_nom.where(ppl.carrier.isin(conventional_carriers), 0), + p_nom=ppl.p_nom.where(ppl.carrier.isin(conventional_carriers), 0), + p_nom_extendable=ppl.carrier.isin(extendable_carriers["Generator"]) + | (ppl.carrier == "diesel"), + efficiency=ppl.efficiency, + marginal_cost=ppl.marginal_cost, + capital_cost=ppl.capital_cost, + build_year=ppl.datein.fillna(0).astype(int), + lifetime=(ppl.dateout - ppl.datein).fillna(np.inf), + ) for carrier in conventional_config: + # Generatori con tecnologia influenzata idx = n.generators.query("carrier == @carrier").index for attr in list(set(conventional_config[carrier]) & set(n.generators)): values = conventional_config[carrier][attr] if f"conventional_{carrier}_{attr}" in conventional_inputs: + # Values affecting generators of technology k country-specific + # First map generator buses to countries; then map countries to p_max_pu values = pd.read_csv(values, index_col=0).iloc[:, 0] bus_values = n.buses.country.map(values) n.generators[attr].update( n.generators.loc[idx].bus.map(bus_values).dropna() ) else: + # Single value affecting all k technology generators regardless of country. n.generators.loc[idx, attr] = values @@ -396,7 +400,6 @@ def attach_load(n, load_file, tech_modelling): snakemake.config["electricity"]["extendable_carriers"], snakemake.config.get("conventional", {}), conventional_inputs, - snakemake.config["microgrids_list"], ) attach_storageunits( diff --git a/scripts/create_network.py b/scripts/create_network.py index 1f11616..2fbcb98 100644 --- a/scripts/create_network.py +++ b/scripts/create_network.py @@ -291,15 +291,5 @@ def create_microgrid_network( microgrids_list, snakemake.input["load"], ) - # calculate_power_node_position( - # snakemake.input["load"], - # snakemake.input["clusters"] - # ) - # add_bus_at_center(n, - # snakemake.config["microgrids_list"], - # snakemake.config["electricity"]["voltage"], - # snakemake.config["electricity"]["line_type"]) - - # plot_microgrid_network(n) a = 12 n.export_to_netcdf(snakemake.output[0]) From b1c14696bdbe3126039a0535de9d8a2b4e4ddb6e Mon Sep 17 00:00:00 2001 From: Margherita Capitani Date: Tue, 21 Jan 2025 12:46:02 +0100 Subject: [PATCH 12/16] New_fix --- scripts/build_demand.py | 7 +------ scripts/create_network.py | 29 +++++++---------------------- 2 files changed, 8 insertions(+), 28 deletions(-) diff --git a/scripts/build_demand.py b/scripts/build_demand.py index 620b962..e2378d5 100644 --- a/scripts/build_demand.py +++ b/scripts/build_demand.py @@ -290,12 +290,7 @@ def calculate_load( # Concatenate all microgrid DataFrames horizontally all_load_per_cluster = pd.concat(microgrid_dataframes.values(), axis=1) - - # Verify that the length of snapshots matches the length of the load data - if len(snapshots_range) == len(all_load_per_cluster): - all_load_per_cluster.insert(0, "timestamp", snapshots_range) - else: - raise ValueError("Mismatch between the length of snapshots and load data rows.") + all_load_per_cluster.index = snapshots_range # Save the cumulative results to a CSV file all_load_per_cluster.to_csv(output_file, index=False) diff --git a/scripts/create_network.py b/scripts/create_network.py index 2fbcb98..59c4479 100644 --- a/scripts/create_network.py +++ b/scripts/create_network.py @@ -40,28 +40,13 @@ def create_network(): def calculate_power_node_position(load_file, cluster_bus): load_sums = load_file.sum(numeric_only=True) - gdf = cluster_bus - data = [] - for _, feature in gdf.iterrows(): - cluster = feature["cluster"] - coordinates = feature.geometry.coords[0] - data.append({"cluster": cluster, "x": coordinates[0], "y": coordinates[1]}) - # Create a DataFrame - df = pd.DataFrame(data) - # Add load_sums as a new column to the DataFrame - df["cluster_load"] = load_sums.values - weights = [] - for i in load_sums.values: - weight = i / sum(load_sums.values) - weights.append(weight) - df["weight"] = weights - x = 0 - y = 0 - for i in range(len(df)): - x += df.loc[i, "x"] * df.loc[i, "weight"] - y += df.loc[i, "y"] * df.loc[i, "weight"] - - return x, y + load_sums.index = cluster_bus["cluster"] + gdf = cluster_bus.set_index("cluster") + gdf["cluster_load"] = load_sums.values.T + x_wgt_avg = (gdf.geometry.x * load_sums / load_sums).sum() + y_wgt_avg = (gdf.geometry.y * load_sums / load_sums).sum() + + return x_wgt_avg, y_wgt_avg def create_microgrid_network( From 8ed3873707ae54eccd64e303bf72103195143f06 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 21 Jan 2025 11:48:04 +0000 Subject: [PATCH 13/16] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- scripts/create_network.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/create_network.py b/scripts/create_network.py index 59c4479..457de44 100644 --- a/scripts/create_network.py +++ b/scripts/create_network.py @@ -41,9 +41,9 @@ def create_network(): def calculate_power_node_position(load_file, cluster_bus): load_sums = load_file.sum(numeric_only=True) load_sums.index = cluster_bus["cluster"] - gdf = cluster_bus.set_index("cluster") + gdf = cluster_bus.set_index("cluster") gdf["cluster_load"] = load_sums.values.T - x_wgt_avg = (gdf.geometry.x * load_sums / load_sums).sum() + x_wgt_avg = (gdf.geometry.x * load_sums / load_sums).sum() y_wgt_avg = (gdf.geometry.y * load_sums / load_sums).sum() return x_wgt_avg, y_wgt_avg From 2950308b9f1f77bc0c06760d95ac98a27f9a0355 Mon Sep 17 00:00:00 2001 From: Margherita Capitani Date: Tue, 21 Jan 2025 12:52:57 +0100 Subject: [PATCH 14/16] Little_Fix --- config.distribution.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config.distribution.yaml b/config.distribution.yaml index 5278275..ae787a2 100644 --- a/config.distribution.yaml +++ b/config.distribution.yaml @@ -61,9 +61,9 @@ build_demand_type: std: "on" # type allows to select the mode by which the microgrid demand profile is generated. # 0 = a predetermined hourly profile is used -# 1 = an average hourly profile is calculated by exploiting the ramp tool -# 2 = an average hourly profile and its standard deviation is calculated using the ramp tool, -# and both quantities are used to calculate demand. +# 0 = a predetermined hourly profile is used +# 1 = an average hourly profile is calculated by exploiting the ramp tool when std on also the +# standard deviation is calculated and used to calculate demand. # definition of the Coordinate Reference Systems crs: From 56091e0ff36e05096d30ad694f4afc2c78fbad1b Mon Sep 17 00:00:00 2001 From: Margherita Capitani Date: Tue, 21 Jan 2025 16:34:43 +0100 Subject: [PATCH 15/16] Last_fix --- scripts/create_network.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/create_network.py b/scripts/create_network.py index 457de44..4a1db24 100644 --- a/scripts/create_network.py +++ b/scripts/create_network.py @@ -43,8 +43,8 @@ def calculate_power_node_position(load_file, cluster_bus): load_sums.index = cluster_bus["cluster"] gdf = cluster_bus.set_index("cluster") gdf["cluster_load"] = load_sums.values.T - x_wgt_avg = (gdf.geometry.x * load_sums / load_sums).sum() - y_wgt_avg = (gdf.geometry.y * load_sums / load_sums).sum() + x_wgt_avg = (gdf.geometry.x * load_sums).sum() / load_sums.sum() + y_wgt_avg = (gdf.geometry.y * load_sums).sum() / load_sums.sum() return x_wgt_avg, y_wgt_avg From c3a0bc43b910572adf79ccb03a1191cb8ffaa0a8 Mon Sep 17 00:00:00 2001 From: Margherita Capitani Date: Thu, 30 Jan 2025 17:36:09 +0100 Subject: [PATCH 16/16] FIx_build_demand --- config.distribution.yaml | 7 +++---- scripts/build_demand.py | 4 ++-- test/config.distribution.test.yaml | 6 +++++- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/config.distribution.yaml b/config.distribution.yaml index ae787a2..260c7e8 100644 --- a/config.distribution.yaml +++ b/config.distribution.yaml @@ -57,12 +57,11 @@ house_area_limit: area_limit: 255 # All buildings without a specified tag, having an area less than house_limit will be considered houses build_demand_type: - type: 0 + type: "From_file" std: "on" # type allows to select the mode by which the microgrid demand profile is generated. -# 0 = a predetermined hourly profile is used -# 0 = a predetermined hourly profile is used -# 1 = an average hourly profile is calculated by exploiting the ramp tool when std on also the +# From_file = a predetermined hourly profile is used +# Ramp = an average hourly profile is calculated by exploiting the ramp tool when std on also the # standard deviation is calculated and used to calculate demand. # definition of the Coordinate Reference Systems diff --git a/scripts/build_demand.py b/scripts/build_demand.py index e2378d5..c3f7fc1 100644 --- a/scripts/build_demand.py +++ b/scripts/build_demand.py @@ -478,7 +478,7 @@ def calculate_load_ramp( snakemake.input["microgrid_shapes"], snakemake.output["electric_load"], ) - if build_demand_model == 0: + if build_demand_model == "From_file": calculate_load( snakemake.config["load"]["scaling_factor"], worldpop_path, @@ -492,7 +492,7 @@ def calculate_load_ramp( inclusive, ) - elif build_demand_model == 1: + elif build_demand_model == "Ramp": calculate_load_ramp( snakemake.input["clusters_with_buildings"], snakemake.config["load"]["scaling_factor"], diff --git a/test/config.distribution.test.yaml b/test/config.distribution.test.yaml index 4c46967..7dbbb81 100644 --- a/test/config.distribution.test.yaml +++ b/test/config.distribution.test.yaml @@ -55,8 +55,12 @@ house_area_limit: area_limit: 255 build_demand_type: - type: 0 + type: "From_file" std: "on" +# type allows to select the mode by which the microgrid demand profile is generated. +# From_file = a predetermined hourly profile is used +# Ramp = an average hourly profile is calculated by exploiting the ramp tool when std on also the +# standard deviation is calculated and used to calculate demand. # definition of the Coordinate Reference Systems crs: