Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cluster network replace pyomo #903

Merged
merged 9 commits into from
Jan 31, 2024
10 changes: 0 additions & 10 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,6 @@ jobs:
run: |
echo -ne "url: ${CDSAPI_URL}\nkey: ${CDSAPI_TOKEN}\n" > ~/.cdsapirc

- name: Add solver to environment
run: |
echo -e "- glpk\n- ipopt<3.13.3" >> envs/environment.yaml
if: ${{ matrix.os }} == 'windows-latest'

- name: Add solver to environment
run: |
echo -e "- glpk\n- ipopt" >> envs/environment.yaml
if: ${{ matrix.os }} != 'windows-latest'

- name: Setup micromamba
uses: mamba-org/setup-micromamba@v1
with:
Expand Down
5 changes: 5 additions & 0 deletions doc/release_notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ Upcoming Release
* The rule ``plot_network`` has been split into separate rules for plotting
electricity, hydrogen and gas networks.

* To determine the optimal topology to meet the number of clusters, the workflow used pyomo in combination with ``ipopt`` or ``gurobi``. This dependency has been replaced by using ``linopy`` in combination with ``scipopt`` or ``gurobi``. The environment file has been updated accordingly.

* The ``highs`` solver was added to the default environment file.



PyPSA-Eur 0.9.0 (5th January 2024)
==================================
Expand Down
5 changes: 3 additions & 2 deletions envs/environment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@ dependencies:
- netcdf4
- networkx
- scipy
- glpk
- shapely>=2.0
- pyomo
- pyscipopt
- matplotlib
- proj
- fiona
Expand All @@ -47,7 +48,6 @@ dependencies:
- tabula-py
- pyxlsb
- graphviz
- ipopt

# Keep in conda environment when calling ipython
- ipython
Expand All @@ -60,3 +60,4 @@ dependencies:

- pip:
- tsam>=2.3.1
- highspy
47 changes: 19 additions & 28 deletions scripts/cluster_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,14 +122,15 @@
"""

import logging
import os
import warnings
from functools import reduce

import geopandas as gpd
import linopy
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import pyomo.environ as po
import pypsa
import seaborn as sns
from _helpers import configure_logging, update_p_nom_max
Expand Down Expand Up @@ -214,7 +215,7 @@ def get_feature_for_hac(n, buses_i=None, feature=None):
return feature_data


def distribute_clusters(n, n_clusters, focus_weights=None, solver_name="cbc"):
def distribute_clusters(n, n_clusters, focus_weights=None, solver_name="scip"):
"""
Determine the number of clusters per country.
"""
Expand Down Expand Up @@ -254,31 +255,22 @@ def distribute_clusters(n, n_clusters, focus_weights=None, solver_name="cbc"):
L.sum(), 1.0, rtol=1e-3
), f"Country weights L must sum up to 1.0 when distributing clusters. Is {L.sum()}."

m = po.ConcreteModel()

def n_bounds(model, *n_id):
return (1, N[n_id])

m.n = po.Var(list(L.index), bounds=n_bounds, domain=po.Integers)
m.tot = po.Constraint(expr=(po.summation(m.n) == n_clusters))
m.objective = po.Objective(
expr=sum((m.n[i] - L.loc[i] * n_clusters) ** 2 for i in L.index),
sense=po.minimize,
m = linopy.Model()
clusters = m.add_variables(
lower=1, upper=N, coords=[L.index], name="n", integer=True
)

opt = po.SolverFactory(solver_name)
if solver_name == "appsi_highs" or not opt.has_capability("quadratic_objective"):
logger.warning(
f"The configured solver `{solver_name}` does not support quadratic objectives. Falling back to `ipopt`."
m.add_constraints(clusters.sum() == n_clusters, name="tot")
# leave out constant in objective (L * n_clusters) ** 2
m.objective = (clusters * clusters - 2 * clusters * L * n_clusters).sum()
if solver_name == "gurobi":
logging.getLogger("gurobipy").propagate = False
elif solver_name != "scip":
logger.info(
f"The configured solver `{solver_name}` does not support quadratic objectives. Falling back to `scip`."
)
opt = po.SolverFactory("ipopt")

results = opt.solve(m)
assert (
results["Solver"][0]["Status"] == "ok"
), f"Solver returned non-optimally: {results}"

return pd.Series(m.n.get_values(), index=L.index).round().astype(int)
solver_name = "scip"
m.solve(solver_name=solver_name)
return m.solution["n"].to_series().astype(int)


def busmap_for_n_clusters(
Expand Down Expand Up @@ -372,7 +364,7 @@ def busmap_for_country(x):

return (
n.buses.groupby(["country", "sub_network"], group_keys=False)
.apply(busmap_for_country)
.apply(busmap_for_country, include_groups=False)
.squeeze()
.rename("busmap")
)
Expand All @@ -385,7 +377,7 @@ def clustering_for_n_clusters(
aggregate_carriers=None,
line_length_factor=1.25,
aggregation_strategies=dict(),
solver_name="cbc",
solver_name="scip",
algorithm="hac",
feature=None,
extended_link_costs=0,
Expand Down Expand Up @@ -462,7 +454,6 @@ def plot_busmap_for_n_clusters(n, n_clusters, fn=None):

params = snakemake.params
solver_name = snakemake.config["solving"]["solver"]["name"]
solver_name = "appsi_highs" if solver_name == "highs" else solver_name

n = pypsa.Network(snakemake.input.network)

Expand Down
Loading