{simplify,cluster}_network: Fix two-step clustering and introduce m suffix

- a network name like elec_s1000_ now clusters the network down to 1000 buses in
  the simplify step.
- an 'm' after the number of clusters as in elec_s1000_181m_ skips the
  aggregation of renewable generators and just moves them to the new clustered
  bus in the second clustering step.
- to distribute the number of clusters to countries a small quadratic
  optimization is now performed to minimize \sum_i (n_i - L_i/\sum_j L_j N)**2,
  where n_i >= 1.
This commit is contained in:
Jonas Hörsch 2018-02-19 10:03:25 +01:00
parent 7f3a1a6cd0
commit 2c0b66c510
3 changed files with 56 additions and 10 deletions

View File

@ -1,11 +1,11 @@
configfile: "config.yaml" configfile: "config.yaml"
localrules: all, prepare_links_p_nom, base_network, build_powerplants, add_electricity, add_sectors, prepare_network, extract_summaries, plot_network, scenario_comparions localrules: all, prepare_links_p_nom, base_network, build_renewable_potentials, build_powerplants, add_electricity, add_sectors, prepare_network, extract_summaries, plot_network, scenario_comparions
wildcard_constraints: wildcard_constraints:
lv="[0-9\.]+", lv="[0-9\.]+",
simpl="[a-zA-Z0-9]*", simpl="[a-zA-Z0-9]*",
clusters="[0-9]+", clusters="[0-9]+m?",
sectors="[+a-zA-Z0-9]+", sectors="[+a-zA-Z0-9]+",
opts="[-+a-zA-Z0-9]+" opts="[-+a-zA-Z0-9]+"
@ -97,7 +97,7 @@ rule simplify_network:
regions_offshore="resources/regions_offshore_{network}_s{simpl}.geojson" regions_offshore="resources/regions_offshore_{network}_s{simpl}.geojson"
benchmark: "benchmarks/simplify_network/{network}_s{simpl}" benchmark: "benchmarks/simplify_network/{network}_s{simpl}"
threads: 1 threads: 1
resources: mem_mb=3000 resources: mem_mb=4000
script: "scripts/simplify_network.py" script: "scripts/simplify_network.py"
rule cluster_network: rule cluster_network:
@ -133,7 +133,14 @@ rule prepare_network:
script: "scripts/prepare_network.py" script: "scripts/prepare_network.py"
def partition(w): def partition(w):
return 'vres' if int(w.clusters) >= 256 else 'x-men' n_clusters = int(w.clusters[:-1] if w.clusters.endswith('m') else w.clusters)
return 'vres' if n_clusters >= 256 else 'x-men'
def memory(w):
if w.clusters.endswith('m'):
return 61000
else:
return 4890+310 * int(w.clusters)
rule solve_network: rule solve_network:
input: "networks/{network}_s{simpl}_{clusters}_lv{lv}_{opts}.nc" input: "networks/{network}_s{simpl}_{clusters}_lv{lv}_{opts}.nc"
@ -147,7 +154,7 @@ rule solve_network:
benchmark: "benchmarks/solve_network/{network}_s{simpl}_{clusters}_lv{lv}_{opts}" benchmark: "benchmarks/solve_network/{network}_s{simpl}_{clusters}_lv{lv}_{opts}"
threads: 4 threads: 4
resources: resources:
mem_mb=lambda w: 4890+310 * int(w.clusters), # without 5000 too small for 256 mem_mb=memory,
x_men=lambda w: 1 if partition(w) == 'x-men' else 0, x_men=lambda w: 1 if partition(w) == 'x-men' else 0,
vres=lambda w: 1 if partition(w) == 'vres' else 0 vres=lambda w: 1 if partition(w) == 'vres' else 0
script: "scripts/solve_network.py" script: "scripts/solve_network.py"

View File

@ -18,6 +18,8 @@ import networkx as nx
from six import iteritems from six import iteritems
from six.moves import reduce from six.moves import reduce
import pyomo.environ as po
import pypsa import pypsa
from pypsa.io import import_components_from_dataframe, import_series_from_dataframe from pypsa.io import import_components_from_dataframe, import_series_from_dataframe
from pypsa.networkclustering import (busmap_by_stubs, busmap_by_kmeans, from pypsa.networkclustering import (busmap_by_stubs, busmap_by_kmeans,
@ -84,10 +86,37 @@ def distribute_clusters_exactly(n, n_clusters):
else: else:
return distribute_clusters(n, n_clusters) return distribute_clusters(n, n_clusters)
def distribute_clusters_optim(n, n_clusters, solver_name='gurobi'):
L = (n.loads_t.p_set.mean()
.groupby(n.loads.bus).sum()
.groupby([n.buses.country, n.buses.sub_network]).sum()
.pipe(normed))
m = po.ConcreteModel()
m.n = po.Var(list(L.index), bounds=(1, None), domain=po.Integers)
m.tot = po.Constraint(expr=(po.summation(m.n) == n_clusters))
m.objective = po.Objective(expr=po.sum((m.n[i] - L.loc[i]*n_clusters)**2
for i in L.index),
sense=po.minimize)
opt = po.SolverFactory(solver_name)
if isinstance(opt, pypsa.opf.PersistentSolver):
opt.set_instance(m)
results = opt.solve(m)
assert results['Solver'][0]['Status'].key == 'ok', "Solver returned non-optimally: {}".format(results)
return pd.Series(m.n.get_values(), index=L.index).astype(int)
def busmap_for_n_clusters(n, n_clusters): def busmap_for_n_clusters(n, n_clusters):
n.determine_network_topology() n.determine_network_topology()
n_clusters = distribute_clusters_exactly(n, n_clusters) if 'snakemake' in globals():
solver_name = snakemake.config['solving']['solver']['name']
else:
solver_name = "gurobi"
n_clusters = distribute_clusters_optim(n, n_clusters, solver_name=solver_name)
def busmap_for_country(x): def busmap_for_country(x):
prefix = x.name[0] + x.name[1] + ' ' prefix = x.name[0] + x.name[1] + ' '
if len(x) == 1: if len(x) == 1:
@ -103,11 +132,15 @@ def plot_busmap_for_n_clusters(n, n_clusters=50):
n.plot(bus_colors=busmap.map(dict(zip(cs, cr)))) n.plot(bus_colors=busmap.map(dict(zip(cs, cr))))
del cs, cr del cs, cr
def clustering_for_n_clusters(n, n_clusters): def clustering_for_n_clusters(n, n_clusters, aggregate_renewables=True):
aggregate_generators_carriers = (None if aggregate_renewables
else (pd.Index(n.generators.carrier.unique())
.difference(['onwind', 'offwind', 'solar'])))
clustering = get_clustering_from_busmap( clustering = get_clustering_from_busmap(
n, busmap_for_n_clusters(n, n_clusters), n, busmap_for_n_clusters(n, n_clusters),
bus_strategies=dict(country=_make_consense("Bus", "country")), bus_strategies=dict(country=_make_consense("Bus", "country")),
aggregate_generators_weighted=True, aggregate_generators_weighted=True,
aggregate_generators_carriers=aggregate_generators_carriers,
aggregate_one_ports=["Load", "StorageUnit"] aggregate_one_ports=["Load", "StorageUnit"]
) )
@ -153,8 +186,14 @@ if __name__ == "__main__":
n = pypsa.Network(snakemake.input.network) n = pypsa.Network(snakemake.input.network)
if snakemake.wildcards.clusters.endswith('m'):
n_clusters = int(snakemake.wildcards.clusters[:-1])
aggregate_renewables = False
else:
n_clusters = int(snakemake.wildcards.clusters) n_clusters = int(snakemake.wildcards.clusters)
clustering = clustering_for_n_clusters(n, n_clusters) aggregate_renewables = True
clustering = clustering_for_n_clusters(n, n_clusters, aggregate_renewables)
clustering.network.export_to_netcdf(snakemake.output.network) clustering.network.export_to_netcdf(snakemake.output.network)

View File

@ -208,7 +208,7 @@ if __name__ == "__main__":
if snakemake.wildcards.simpl: if snakemake.wildcards.simpl:
n_clusters = int(snakemake.wildcards.simpl) n_clusters = int(snakemake.wildcards.simpl)
clustering = clustering_for_n_clusters(n_clusters) clustering = clustering_for_n_clusters(n, n_clusters)
n = clustering.network n = clustering.network
busmaps.append(clustering.busmap) busmaps.append(clustering.busmap)