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

Linopy transition #796

Closed
wants to merge 70 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
08c4643
Initial update comment
ekatef Jul 18, 2023
28e1168
Align with PyPSA-Eur approach
ekatef Jul 22, 2023
23dcbc4
Merge branch 'main' into dev_linopy
ekatef Nov 17, 2023
4bb5327
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 17, 2023
3dd5078
Merge branch 'pypsa-meets-earth:main' into dev_linopy
ekatef Jan 18, 2024
f2945c4
Merge branch 'main' into dev_linopy
ekatef Feb 15, 2024
7ca9ee5
Fix merge
ekatef Feb 15, 2024
bf4c5ab
Merge branch 'dev_linopy' of https://github.com/ekatef/pypsa-earth in…
ekatef Feb 15, 2024
f11d5da
Merge branch 'main' into dev_linopy
ekatef Feb 22, 2024
4c2d346
Replace aggregategenerators with aggregateoneport
ekatef Feb 25, 2024
97b24e6
Add aggregation strategies as a parameter
ekatef Feb 25, 2024
004a850
Re-define aggregation strategies
ekatef Feb 25, 2024
f19c69c
Update the environment
ekatef Feb 26, 2024
3d72eee
A temporal fix for merge_isolated
ekatef Feb 26, 2024
f94362b
Replace pyomo methods with linopy ones when clustering
ekatef Feb 26, 2024
6b302f8
Update aggregation strategies
ekatef Feb 26, 2024
28877d3
A temporal fix for the solver to keep the environment same
ekatef Feb 29, 2024
060b973
Update solving setup
ekatef Feb 29, 2024
e953d5a
Update solver settings in the config
ekatef Feb 29, 2024
5fd8060
Update environment
ekatef Mar 6, 2024
f86da66
Update a solver for clustering
ekatef Mar 6, 2024
0e7d145
Merge branch 'main' into dev_linopy
ekatef Apr 4, 2024
a2a48f7
Define bus aggregation strategies
ekatef Apr 4, 2024
2be7fe1
Update environment
ekatef Apr 4, 2024
70b6d04
Merge branch 'main' into dev_linopy
ekatef Apr 8, 2024
377455e
Get back merge of isolated nodes
ekatef Apr 16, 2024
a01f188
Add keys to aggregation strategies
ekatef Apr 16, 2024
644c2ea
Update the tutorial config
ekatef Apr 16, 2024
d210ce1
Lift version constraints for pypsa
ekatef Apr 16, 2024
c4016e2
Add Davide's suggestion for calling the solver log
ekatef May 9, 2024
5669f31
Merge branch 'main' into dev_linopy
ekatef May 13, 2024
5991ca7
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 13, 2024
32674db
Remove unprecified columns from lines component
ekatef May 13, 2024
87ff6dd
Merge branch 'dev_linopy' of https://github.com/ekatef/pypsa-earth in…
ekatef May 13, 2024
7026712
Fix typo aggregation_strategies
davide-f May 16, 2024
c444005
Remove get_aggregation_strategies
davide-f May 16, 2024
a7db9bb
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 16, 2024
ac4e5a5
Merge branch 'main' of https://github.com/pypsa-meets-earth/pypsa-ear…
ekatef May 18, 2024
2074e18
Update the default config
ekatef Jun 5, 2024
5998973
Merge remote-tracking branch 'origin/main' into dev_linopy
ekatef Jun 5, 2024
f932c6a
Remove debug parts
ekatef Jun 6, 2024
be6905d
Remove outdated comments
ekatef Jun 14, 2024
fef6951
Update implementation of the constraints
ekatef Jun 14, 2024
a6ee0a5
Maintain consistency in naming
ekatef Jun 14, 2024
577464c
Update doc-strings
ekatef Jun 14, 2024
417bf62
Update equity constraint
ekatef Jun 14, 2024
6b060d0
Fix typos
ekatef Jun 24, 2024
cfb251f
Merge branch 'main' into dev_linopy
ekatef Jun 27, 2024
26849ab
Remove hard-coding for renewable technologies
ekatef Jun 29, 2024
60faf63
Revise naming
ekatef Jun 29, 2024
f54d1f6
Move sign under the common code chunks
ekatef Jun 29, 2024
cbea30e
Add a missed efficiency
ekatef Jun 29, 2024
6a7e770
Select expressions
ekatef Jun 29, 2024
e7bd8bb
Put repetitions into a function
ekatef Jun 29, 2024
0e50477
Add a supplementary variable
ekatef Jun 29, 2024
d014acc
Replace get_var in add_res_constraints
ekatef Jun 29, 2024
56ce52e
Replace a constraint definition for add_res_constraints
ekatef Jun 29, 2024
c1155c3
Fix typo
ekatef Jun 29, 2024
a3cbd04
Add initialization of the model
ekatef Jul 1, 2024
671dbb5
Add a TODO
ekatef Jul 1, 2024
78ca7d0
Update linear expressions
ekatef Jul 1, 2024
564fd05
Update constraints
ekatef Jul 2, 2024
0ea2125
Add TODO
ekatef Jul 2, 2024
b62ed10
Merge branch 'main' into dev_linopy
ekatef Jul 20, 2024
194ff0e
Add a release note
ekatef Jul 21, 2024
e907991
Revise keywords following humanfriendly conventions
ekatef Jul 25, 2024
6ec28f1
Account for Davide's comment
ekatef Aug 12, 2024
f0c95cb
Fix a typo
ekatef Aug 12, 2024
4b5d8da
Add a comment on the approach used for the reserve margin constraint
ekatef Aug 13, 2024
1affa86
Switch-off fetching of isolated networks
ekatef Aug 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Snakefile
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,7 @@ rule add_electricity:

rule simplify_network:
params:
aggregation_strategies=config["cluster_options"]["aggregation_strategies"],
renewable=config["renewable"],
geo_crs=config["crs"]["geo_crs"],
cluster_options=config["cluster_options"],
Expand Down
70 changes: 58 additions & 12 deletions config.default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ cluster_options:
remove_stubs_across_borders: true
p_threshold_drop_isolated: 20 # [MW] isolated buses are being discarded if bus mean power is below the specified threshold
p_threshold_merge_isolated: 300 # [MW] isolated buses are being merged into a single isolated bus if a bus mean power is below the specified threshold
s_threshold_fetch_isolated: 0.05 # [-] a share of the national load for merging an isolated network into a backbone network
s_threshold_fetch_isolated: false # [-] a share of the national load for merging an isolated network into a backbone network
cluster_network:
algorithm: kmeans
feature: solar+onwind-time
Expand Down Expand Up @@ -396,8 +396,6 @@ monte_carlo:
type: beta
args: [0.5, 2]



solving:
options:
formulation: kirchhoff
Expand All @@ -411,15 +409,63 @@ solving:
#nhours: 10
solver:
name: gurobi
threads: 4
method: 2 # barrier (=ipm)
crossover: 0
BarConvTol: 1.e-5
FeasibilityTol: 1.e-6
AggFill: 0
PreDual: 0
GURO_PAR_BARDENSETHRESH: 200

options: gurobi-default

solver_options:
highs-default:
# refer to https://ergo-code.github.io/HiGHS/options/definitions.html#solver
threads: 4
solver: "ipm"
run_crossover: "off"
small_matrix_value: 1e-6
large_matrix_value: 1e9
primal_feasibility_tolerance: 1e-5
dual_feasibility_tolerance: 1e-5
ipm_optimality_tolerance: 1e-4
parallel: "on"
random_seed: 123
gurobi-default:
threads: 4
method: 2 # barrier
crossover: 0
BarConvTol: 1.e-6
Seed: 123
AggFill: 0
PreDual: 0
GURO_PAR_BARDENSETHRESH: 200
gurobi-numeric-focus:
name: gurobi
NumericFocus: 3 # Favour numeric stability over speed
method: 2 # barrier
crossover: 0 # do not use crossover
BarHomogeneous: 1 # Use homogeneous barrier if standard does not converge
BarConvTol: 1.e-5
FeasibilityTol: 1.e-4
OptimalityTol: 1.e-4
ObjScale: -0.5
threads: 8
Seed: 123
gurobi-fallback: # Use gurobi defaults
name: gurobi
crossover: 0
method: 2 # barrier
BarHomogeneous: 1 # Use homogeneous barrier if standard does not converge
BarConvTol: 1.e-5
FeasibilityTol: 1.e-5
OptimalityTol: 1.e-5
Seed: 123
threads: 8
cplex-default:
threads: 4
lpmethod: 4 # barrier
solutiontype: 2 # non basic solution, ie no crossover
barrier.convergetol: 1.e-4 #5
feasopt.tolerance: 1.e-5 #6
cbc-default: {} # Used in CI
glpk-default: {} # Used in CI

mem_mb: 30000 #memory in MB; 20 GB enough for 50+B+I+H2; 100 GB for 181+B+I+H2
runtime: 6h #runtime in humanfriendly style https://humanfriendly.readthedocs.io/en/latest/

plotting:
map:
Expand Down
59 changes: 58 additions & 1 deletion config.tutorial.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ cluster_options:
remove_stubs_across_borders: true
p_threshold_drop_isolated: 20 # [MW] isolated buses are being discarded if bus mean power is below the specified threshold
p_threshold_merge_isolated: 300 # [MW] isolated buses are being merged into a single isolated bus if a bus mean power is below the specified threshold
s_threshold_fetch_isolated: 0.05 # [-] a share of the national load for merging an isolated network into a backbone network
s_threshold_fetch_isolated: false # [-] a share of the national load for merging an isolated network into a backbone network
cluster_network:
algorithm: kmeans
feature: solar+onwind-time
Expand Down Expand Up @@ -405,6 +405,63 @@ solving:
#nhours: 10
solver:
name: glpk
options: glpk-default

solver_options:
highs-default:
# refer to https://ergo-code.github.io/HiGHS/options/definitions.html#solver
threads: 4
solver: "ipm"
run_crossover: "off"
small_matrix_value: 1e-6
large_matrix_value: 1e9
primal_feasibility_tolerance: 1e-5
dual_feasibility_tolerance: 1e-5
ipm_optimality_tolerance: 1e-4
parallel: "on"
random_seed: 123
gurobi-default:
threads: 4
method: 2 # barrier
crossover: 0
BarConvTol: 1.e-6
Seed: 123
AggFill: 0
PreDual: 0
GURO_PAR_BARDENSETHRESH: 200
gurobi-numeric-focus:
name: gurobi
NumericFocus: 3 # Favour numeric stability over speed
method: 2 # barrier
crossover: 0 # do not use crossover
BarHomogeneous: 1 # Use homogeneous barrier if standard does not converge
BarConvTol: 1.e-5
FeasibilityTol: 1.e-4
OptimalityTol: 1.e-4
ObjScale: -0.5
threads: 8
Seed: 123
gurobi-fallback: # Use gurobi defaults
name: gurobi
crossover: 0
method: 2 # barrier
BarHomogeneous: 1 # Use homogeneous barrier if standard does not converge
BarConvTol: 1.e-5
FeasibilityTol: 1.e-5
OptimalityTol: 1.e-5
Seed: 123
threads: 8
cplex-default:
threads: 4
lpmethod: 4 # barrier
solutiontype: 2 # non basic solution, ie no crossover
barrier.convergetol: 1.e-4 #5
feasopt.tolerance: 1.e-5 #6
cbc-default: {} # Used in CI
glpk-default: {} # Used in CI

mem_mb: 30000 #memory in MB; 20 GB enough for 50+B+I+H2; 100 GB for 181+B+I+H2
runtime: 6h #runtime in humanfriendly style https://humanfriendly.readthedocs.io/en/latest/


plotting:
Expand Down
2 changes: 2 additions & 0 deletions doc/release_notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ E.g. if a new rule becomes available describe how to use it `snakemake -j1 run_t

* Add an option to use csv format for custom demand imports. `PR #995 <https://github.com/pypsa-meets-earth/pypsa-earth/pull/995>`__

* Re-implement network optimisation using linopy methods. `PR #796 <https://github.com/pypsa-meets-earth/pypsa-earth/pull/796>`__

**Minor Changes and bug-fixing**

* Minor bug-fixing to run the cluster wildcard min `PR #1019 <https://github.com/pypsa-meets-earth/pypsa-earth/pull/1019>`__
Expand Down
5 changes: 3 additions & 2 deletions envs/environment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ dependencies:
- pip
- mamba # esp for windows build

- pypsa>=0.24, <0.25
- pypsa
Copy link
Member

@davide-f davide-f Aug 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add >=0.2X in agreement to the expected version; it is not backcompatible unfortunately

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in #1065

# - atlite>=0.2.4 # until https://github.com/PyPSA/atlite/issues/244 is not merged
- dask
- powerplantmatching>=0.5.7
Expand All @@ -27,6 +27,7 @@ dependencies:
- memory_profiler
- ruamel.yaml<=0.17.26
- pytables
- pyscipopt # added to compy with the quadratic objective requirement of the clustering script
- lxml
- numpy
- pandas
Expand Down Expand Up @@ -77,7 +78,7 @@ dependencies:

# Default solver for tests (required for CI)
- glpk
- ipopt<3.13.3
- ipopt #<3.13.3
- gurobi

- pip:
Expand Down
25 changes: 0 additions & 25 deletions scripts/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -453,31 +453,6 @@ def dlProgress(count, blockSize, totalSize, roundto=roundto):
urllib.request.urlretrieve(url, file, reporthook=dlProgress, data=data)


def get_aggregation_strategies(aggregation_strategies):
"""
Default aggregation strategies that cannot be defined in .yaml format must
be specified within the function, otherwise (when defaults are passed in
the function's definition) they get lost when custom values are specified
in the config.
"""
import numpy as np

# to handle the new version of PyPSA.
try:
from pypsa.clustering.spatial import _make_consense
except Exception:
# TODO: remove after new release and update minimum pypsa version
from pypsa.clustering.spatial import _make_consense

bus_strategies = dict(country=_make_consense("Bus", "country"))
bus_strategies.update(aggregation_strategies.get("buses", {}))

generator_strategies = {"build_year": lambda x: 0, "lifetime": lambda x: np.inf}
generator_strategies.update(aggregation_strategies.get("generators", {}))

return bus_strategies, generator_strategies


def mock_snakemake(rulename, **wildcards):
"""
This function is expected to be executed from the "scripts"-directory of "
Expand Down
24 changes: 24 additions & 0 deletions scripts/base_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,18 @@ def base_network(

if hvdc_as_lines_config:
lines = pd.concat([lines_ac, lines_dc])
lines.drop(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally, I'd recommend to do the converse:
keep the list of columns that we want and implicitly drop the others.
That should be more consistent. However, do you think it should be placed here or maybe in previous processing script?
If you consider that it is not suited for this PR, we can add an issue instead

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Absolutely agree that explicit definition would work better there. Moreover, the updated PyPSA version is not very stable in what relates to the data structures. Custom columns are not forbidden explicitly, but can lead to some weird issues. So, I'd be very careful in dealing with them.

In a dedicated PR, have moved the definition of lines columns in the previous script.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great! What would be your idea here, merge this and then 1065, merge 1065 here?
(same text also specified below)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have created a PR on top of #1065 which enables linopy in addition to the upgrade of PyPSA version

labels=[
"bus0_lon",
"bus0_lat",
"bus1_lon",
"bus1_lat",
"bus_0_coors",
"bus_1_coors",
],
axis=1,
inplace=True,
)
n.import_components_from_dataframe(lines, "Line")
else:
lines_dc = _set_electrical_parameters_links(links_config, lines_dc)
Expand All @@ -522,6 +534,18 @@ def base_network(
axis=1,
result_type="reduce",
)
lines_ac.drop(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in #1065: no need for the drop in base_network anymore.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great! What would be your idea here, merge this and then 1065, merge 1065 here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great! What would be your idea here, merge this and then 1065, merge 1065 here?

Have created a PR on top of #1065 which enables linopy in addition to the upgrade of PyPSA version

labels=[
"bus0_lon",
"bus0_lat",
"bus1_lon",
"bus1_lat",
"bus_0_coors",
"bus_1_coors",
],
axis=1,
inplace=True,
)
n.import_components_from_dataframe(lines_ac, "Line")
# The columns which names starts with "bus" are mixed up with the third-bus specification
# when executing additional_linkports()
Expand Down
73 changes: 28 additions & 45 deletions scripts/cluster_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,15 +125,14 @@
from functools import reduce

import geopandas as gpd
import linopy
import numpy as np
import pandas as pd
import pyomo.environ as po
import pypsa
from _helpers import (
REGION_COLS,
configure_logging,
create_logger,
get_aggregation_strategies,
sets_path_to_root,
update_p_nom_max,
)
Expand Down Expand Up @@ -336,47 +335,22 @@ def distribute_clusters(
distribution_factor.sum(), 1.0, rtol=1e-3
), f"Country weights L must sum up to 1.0 when distributing clusters. Is {distribution_factor.sum()}."

m = po.ConcreteModel()

def n_bounds(model, *n_id):
"""
Create a function that makes a bound pair for pyomo.

Use n_bounds(model, n_id) if N is Single-Index
Use n_bounds(model, *n_id) if N is Multi-Index
Example: https://pyomo.readthedocs.io/en/stable/pyomo_modeling_components/Variables.html

Returns
-------
bounds = A function (or Python object) that gives a (lower,upper) bound pair i.e.(1,10) for the variable
"""
return (1, N[n_id])

m.n = po.Var(list(distribution_factor.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] - distribution_factor.loc[i] * n_clusters) ** 2
for i in distribution_factor.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 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=distribution_factor.index).round().astype(int)
)
solver_name = "scip"
m.solve(solver_name=solver_name)
return m.solution["n"].to_series().astype(int)


def busmap_for_gadm_clusters(inputs, n, gadm_level, geo_crs, country_list):
Expand Down Expand Up @@ -577,9 +551,10 @@ def clustering_for_n_clusters(
extended_link_costs=0,
focus_weights=None,
):
bus_strategies, generator_strategies = get_aggregation_strategies(
aggregation_strategies
)
line_strategies = aggregation_strategies.get("lines", dict())
line_strategies.update({"geometry": "first", "bounds": "first"})
generator_strategies = aggregation_strategies.get("generators", dict())
one_port_strategies = aggregation_strategies.get("one_ports", dict())

if not isinstance(custom_busmap, pd.Series):
if alternative_clustering:
Expand All @@ -605,12 +580,20 @@ def clustering_for_n_clusters(
clustering = get_clustering_from_busmap(
n,
busmap,
bus_strategies=bus_strategies,
aggregate_generators_weighted=True,
aggregate_generators_carriers=aggregate_carriers,
aggregate_one_ports=["Load", "StorageUnit"],
line_length_factor=line_length_factor,
line_strategies=line_strategies,
generator_strategies=generator_strategies,
bus_strategies={
"lat": "mean",
"lon": "mean",
"tag_substation": "first",
"tag_area": "first",
"country": "first",
},
Comment on lines +589 to +595
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit widely duplicated across scripts. v_nom is also not here, may this be somewhat linked to the issue by Emmanuel?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree that the duplication should be fixed. Implemented in #1065 as an update of the aggregation strategies outside the clustering functions.

Yeah, it looks like transparence is much desired in working with v_nom. Have defined "first" aggregation strategy for v_nom to have a better overview on what is put inside the model.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good! What would be your idea here, merge this and then 1065, merge 1065 here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good! What would be your idea here, merge this and then 1065, merge 1065 here?

I think it could make sense to have a stacked PR: a PR on top of #1065 which enables linopy in addition to the upgrade of PyPSA version

one_port_strategies=one_port_strategies,
scale_link_capital_costs=False,
)

Expand Down
Loading
Loading