clustering strategies moved to configurables
This commit is contained in:
parent
21183f7b23
commit
bef4967e84
@ -22,6 +22,10 @@ countries: ['AL', 'AT', 'BA', 'BE', 'BG', 'CH', 'CZ', 'DE', 'DK', 'EE', 'ES', 'F
|
|||||||
clustering:
|
clustering:
|
||||||
simplify:
|
simplify:
|
||||||
to_substations: false # network is simplified to nodes with positive or negative power injection (i.e. substations or offwind connections)
|
to_substations: false # network is simplified to nodes with positive or negative power injection (i.e. substations or offwind connections)
|
||||||
|
aggregation_strategies:
|
||||||
|
generators:
|
||||||
|
p_nom_max: "sum" # use "min" for more conservative assumptions
|
||||||
|
p_nom_min: "sum"
|
||||||
|
|
||||||
snapshots:
|
snapshots:
|
||||||
start: "2013-01-01"
|
start: "2013-01-01"
|
||||||
|
@ -22,6 +22,10 @@ countries: ['BE']
|
|||||||
clustering:
|
clustering:
|
||||||
simplify:
|
simplify:
|
||||||
to_substations: false # network is simplified to nodes with positive or negative power injection (i.e. substations or offwind connections)
|
to_substations: false # network is simplified to nodes with positive or negative power injection (i.e. substations or offwind connections)
|
||||||
|
aggregation_strategies:
|
||||||
|
generators:
|
||||||
|
p_nom_max: "sum" # use "min" for more conservative assumptions
|
||||||
|
p_nom_min: "sum"
|
||||||
|
|
||||||
snapshots:
|
snapshots:
|
||||||
start: "2013-03-01"
|
start: "2013-03-01"
|
||||||
|
@ -11,11 +11,10 @@ Relevant Settings
|
|||||||
|
|
||||||
.. code:: yaml
|
.. code:: yaml
|
||||||
|
|
||||||
focus_weights:
|
clustering:
|
||||||
|
aggregation_strategies:
|
||||||
|
|
||||||
renewable: (keys)
|
focus_weights:
|
||||||
{technology}:
|
|
||||||
potential:
|
|
||||||
|
|
||||||
solving:
|
solving:
|
||||||
solver:
|
solver:
|
||||||
@ -259,15 +258,16 @@ def busmap_for_n_clusters(n, n_clusters, solver_name, focus_weights=None, algori
|
|||||||
|
|
||||||
|
|
||||||
def clustering_for_n_clusters(n, n_clusters, custom_busmap=False, aggregate_carriers=None,
|
def clustering_for_n_clusters(n, n_clusters, custom_busmap=False, aggregate_carriers=None,
|
||||||
line_length_factor=1.25, potential_mode='simple', solver_name="cbc",
|
line_length_factor=1.25, aggregation_strategies=dict(), solver_name="cbc",
|
||||||
algorithm="kmeans", extended_link_costs=0, focus_weights=None):
|
algorithm="kmeans", extended_link_costs=0, focus_weights=None):
|
||||||
|
|
||||||
if potential_mode == 'simple':
|
bus_strategies = dict(country=_make_consense("Bus", "country"))
|
||||||
p_nom_max_strategy = pd.Series.sum
|
bus_strategies.update(aggregation_strategies.get("buses", {}))
|
||||||
elif potential_mode == 'conservative':
|
generator_strategies = aggregation_strategies.get("generators", {"p_nom_max": "sum"})
|
||||||
p_nom_max_strategy = pd.Series.min
|
|
||||||
else:
|
# this snippet supports compatibility of PyPSA and PyPSA-EUR:
|
||||||
raise AttributeError(f"potential_mode should be one of 'simple' or 'conservative' but is '{potential_mode}'")
|
if "p_nom_max" in generator_strategies:
|
||||||
|
if generator_strategies["p_nom_max"] == "min": generator_strategies["p_nom_max"] = np.min
|
||||||
|
|
||||||
if not isinstance(custom_busmap, pd.Series):
|
if not isinstance(custom_busmap, pd.Series):
|
||||||
busmap = busmap_for_n_clusters(n, n_clusters, solver_name, focus_weights, algorithm)
|
busmap = busmap_for_n_clusters(n, n_clusters, solver_name, focus_weights, algorithm)
|
||||||
@ -276,19 +276,12 @@ def clustering_for_n_clusters(n, n_clusters, custom_busmap=False, aggregate_carr
|
|||||||
|
|
||||||
clustering = get_clustering_from_busmap(
|
clustering = get_clustering_from_busmap(
|
||||||
n, busmap,
|
n, busmap,
|
||||||
bus_strategies=dict(country=_make_consense("Bus", "country")),
|
bus_strategies=bus_strategies,
|
||||||
aggregate_generators_weighted=True,
|
aggregate_generators_weighted=True,
|
||||||
aggregate_generators_carriers=aggregate_carriers,
|
aggregate_generators_carriers=aggregate_carriers,
|
||||||
aggregate_one_ports=["Load", "StorageUnit"],
|
aggregate_one_ports=["Load", "StorageUnit"],
|
||||||
line_length_factor=line_length_factor,
|
line_length_factor=line_length_factor,
|
||||||
generator_strategies={'p_nom_max': p_nom_max_strategy,
|
generator_strategies=generator_strategies,
|
||||||
'p_nom_min': pd.Series.sum,
|
|
||||||
'p_min_pu': pd.Series.mean,
|
|
||||||
'marginal_cost': pd.Series.mean,
|
|
||||||
'committable': np.any,
|
|
||||||
'ramp_limit_up': pd.Series.max,
|
|
||||||
'ramp_limit_down': pd.Series.max,
|
|
||||||
},
|
|
||||||
scale_link_capital_costs=False)
|
scale_link_capital_costs=False)
|
||||||
|
|
||||||
if not n.links.empty:
|
if not n.links.empty:
|
||||||
@ -375,8 +368,8 @@ if __name__ == "__main__":
|
|||||||
"The `potential` configuration option must agree for all renewable carriers, for now!"
|
"The `potential` configuration option must agree for all renewable carriers, for now!"
|
||||||
)
|
)
|
||||||
return v
|
return v
|
||||||
potential_mode = consense(pd.Series([snakemake.config['renewable'][tech]['potential']
|
aggregation_strategies = snakemake.config["clustering"].get("aggregation_strategies", {})
|
||||||
for tech in renewable_carriers]))
|
|
||||||
custom_busmap = snakemake.config["enable"].get("custom_busmap", False)
|
custom_busmap = snakemake.config["enable"].get("custom_busmap", False)
|
||||||
if custom_busmap:
|
if custom_busmap:
|
||||||
custom_busmap = pd.read_csv(snakemake.input.custom_busmap, index_col=0, squeeze=True)
|
custom_busmap = pd.read_csv(snakemake.input.custom_busmap, index_col=0, squeeze=True)
|
||||||
@ -384,12 +377,12 @@ if __name__ == "__main__":
|
|||||||
logger.info(f"Imported custom busmap from {snakemake.input.custom_busmap}")
|
logger.info(f"Imported custom busmap from {snakemake.input.custom_busmap}")
|
||||||
|
|
||||||
clustering = clustering_for_n_clusters(n, n_clusters, custom_busmap, aggregate_carriers,
|
clustering = clustering_for_n_clusters(n, n_clusters, custom_busmap, aggregate_carriers,
|
||||||
line_length_factor, potential_mode,
|
line_length_factor, aggregation_strategies,
|
||||||
snakemake.config['solving']['solver']['name'],
|
snakemake.config['solving']['solver']['name'],
|
||||||
"kmeans", hvac_overhead_cost, focus_weights)
|
"kmeans", hvac_overhead_cost, focus_weights)
|
||||||
|
|
||||||
update_p_nom_max(n)
|
|
||||||
|
|
||||||
|
update_p_nom_max(clustering.network)
|
||||||
|
|
||||||
clustering.network.export_to_netcdf(snakemake.output.network)
|
clustering.network.export_to_netcdf(snakemake.output.network)
|
||||||
for attr in ('busmap', 'linemap'): #also available: linemap_positive, linemap_negative
|
for attr in ('busmap', 'linemap'): #also available: linemap_positive, linemap_negative
|
||||||
getattr(clustering, attr).to_csv(snakemake.output[attr])
|
getattr(clustering, attr).to_csv(snakemake.output[attr])
|
||||||
|
@ -13,6 +13,10 @@ Relevant Settings
|
|||||||
|
|
||||||
.. code:: yaml
|
.. code:: yaml
|
||||||
|
|
||||||
|
clustering:
|
||||||
|
simplify:
|
||||||
|
aggregation_strategies:
|
||||||
|
|
||||||
costs:
|
costs:
|
||||||
USD2013_to_EUR2013:
|
USD2013_to_EUR2013:
|
||||||
discountrate:
|
discountrate:
|
||||||
@ -22,10 +26,6 @@ Relevant Settings
|
|||||||
electricity:
|
electricity:
|
||||||
max_hours:
|
max_hours:
|
||||||
|
|
||||||
renewables: (keys)
|
|
||||||
{technology}:
|
|
||||||
potential:
|
|
||||||
|
|
||||||
lines:
|
lines:
|
||||||
length_factor:
|
length_factor:
|
||||||
|
|
||||||
@ -320,7 +320,7 @@ def remove_stubs(n, costs, config, output):
|
|||||||
|
|
||||||
return n, busmap
|
return n, busmap
|
||||||
|
|
||||||
def aggregate_to_substations(n, buses_i=None):
|
def aggregate_to_substations(n, config, aggregation_strategies=dict(), buses_i=None):
|
||||||
# can be used to aggregate a selection of buses to electrically closest neighbors
|
# can be used to aggregate a selection of buses to electrically closest neighbors
|
||||||
# if no buses are given, nodes that are no substations or without offshore connection are aggregated
|
# if no buses are given, nodes that are no substations or without offshore connection are aggregated
|
||||||
|
|
||||||
@ -345,19 +345,29 @@ def aggregate_to_substations(n, buses_i=None):
|
|||||||
busmap = n.buses.index.to_series()
|
busmap = n.buses.index.to_series()
|
||||||
busmap.loc[buses_i] = dist.idxmin(1)
|
busmap.loc[buses_i] = dist.idxmin(1)
|
||||||
|
|
||||||
|
# default aggregation strategies must be specified within the function, otherwise (when defaults are passed in
|
||||||
|
# the function's definition) they get lost in case custom values for different variables are specified in the config
|
||||||
|
bus_strategies = dict(country=_make_consense("Bus", "country"))
|
||||||
|
bus_strategies.update(aggregation_strategies.get("buses", {}))
|
||||||
|
generator_strategies = aggregation_strategies.get("generators", {"p_nom_max": "sum"})
|
||||||
|
|
||||||
|
# this snippet supports compatibility of PyPSA and PyPSA-EUR:
|
||||||
|
if "p_nom_max" in generator_strategies:
|
||||||
|
if generator_strategies["p_nom_max"] == "min": generator_strategies["p_nom_max"] = np.min
|
||||||
|
|
||||||
clustering = get_clustering_from_busmap(n, busmap,
|
clustering = get_clustering_from_busmap(n, busmap,
|
||||||
bus_strategies=dict(country=_make_consense("Bus", "country")),
|
bus_strategies=bus_strategies,
|
||||||
aggregate_generators_weighted=True,
|
aggregate_generators_weighted=True,
|
||||||
aggregate_generators_carriers=None,
|
aggregate_generators_carriers=None,
|
||||||
aggregate_one_ports=["Load", "StorageUnit"],
|
aggregate_one_ports=["Load", "StorageUnit"],
|
||||||
line_length_factor=1.0,
|
line_length_factor=1.0,
|
||||||
generator_strategies={'p_nom_max': 'sum'},
|
generator_strategies=generator_strategies,
|
||||||
scale_link_capital_costs=False)
|
scale_link_capital_costs=False)
|
||||||
|
|
||||||
return clustering.network, busmap
|
return clustering.network, busmap
|
||||||
|
|
||||||
|
|
||||||
def cluster(n, n_clusters, config):
|
def cluster(n, n_clusters, config, aggregation_strategies=dict()):
|
||||||
logger.info(f"Clustering to {n_clusters} buses")
|
logger.info(f"Clustering to {n_clusters} buses")
|
||||||
|
|
||||||
focus_weights = config.get('focus_weights', None)
|
focus_weights = config.get('focus_weights', None)
|
||||||
@ -365,16 +375,9 @@ def cluster(n, n_clusters, config):
|
|||||||
renewable_carriers = pd.Index([tech
|
renewable_carriers = pd.Index([tech
|
||||||
for tech in n.generators.carrier.unique()
|
for tech in n.generators.carrier.unique()
|
||||||
if tech.split('-', 2)[0] in config['renewable']])
|
if tech.split('-', 2)[0] in config['renewable']])
|
||||||
def consense(x):
|
|
||||||
v = x.iat[0]
|
clustering = clustering_for_n_clusters(n, n_clusters, custom_busmap=False,
|
||||||
assert ((x == v).all() or x.isnull().all()), (
|
aggregation_strategies=aggregation_strategies,
|
||||||
"The `potential` configuration option must agree for all renewable carriers, for now!"
|
|
||||||
)
|
|
||||||
return v
|
|
||||||
potential_mode = (consense(pd.Series([config['renewable'][tech]['potential']
|
|
||||||
for tech in renewable_carriers]))
|
|
||||||
if len(renewable_carriers) > 0 else 'conservative')
|
|
||||||
clustering = clustering_for_n_clusters(n, n_clusters, custom_busmap=False, potential_mode=potential_mode,
|
|
||||||
solver_name=config['solving']['solver']['name'],
|
solver_name=config['solving']['solver']['name'],
|
||||||
focus_weights=focus_weights)
|
focus_weights=focus_weights)
|
||||||
|
|
||||||
@ -389,6 +392,8 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
n = pypsa.Network(snakemake.input.network)
|
n = pypsa.Network(snakemake.input.network)
|
||||||
|
|
||||||
|
aggregation_strategies = snakemake.config["clustering"].get("aggregation_strategies", {})
|
||||||
|
|
||||||
n, trafo_map = simplify_network_to_380(n)
|
n, trafo_map = simplify_network_to_380(n)
|
||||||
|
|
||||||
Nyears = n.snapshot_weightings.objective.sum() / 8760
|
Nyears = n.snapshot_weightings.objective.sum() / 8760
|
||||||
@ -402,11 +407,11 @@ if __name__ == "__main__":
|
|||||||
busmaps = [trafo_map, simplify_links_map, stub_map]
|
busmaps = [trafo_map, simplify_links_map, stub_map]
|
||||||
|
|
||||||
if snakemake.config.get('clustering', {}).get('simplify', {}).get('to_substations', False):
|
if snakemake.config.get('clustering', {}).get('simplify', {}).get('to_substations', False):
|
||||||
n, substation_map = aggregate_to_substations(n)
|
n, substation_map = aggregate_to_substations(n, snakemake.config, aggregation_strategies)
|
||||||
busmaps.append(substation_map)
|
busmaps.append(substation_map)
|
||||||
|
|
||||||
if snakemake.wildcards.simpl:
|
if snakemake.wildcards.simpl:
|
||||||
n, cluster_map = cluster(n, int(snakemake.wildcards.simpl), snakemake.config)
|
n, cluster_map = cluster(n, int(snakemake.wildcards.simpl), snakemake.config, aggregation_strategies)
|
||||||
busmaps.append(cluster_map)
|
busmaps.append(cluster_map)
|
||||||
|
|
||||||
# some entries in n.buses are not updated in previous functions, therefore can be wrong. as they are not needed
|
# some entries in n.buses are not updated in previous functions, therefore can be wrong. as they are not needed
|
||||||
|
Loading…
Reference in New Issue
Block a user