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

Open
wants to merge 70 commits into
base: main
Choose a base branch
from
Open

Linopy transition #796

wants to merge 70 commits into from

Conversation

ekatef
Copy link
Member

@ekatef ekatef commented Jul 18, 2023

Closes #494

Integrate linopy following PyPSA/pypsa-eur#625

Checklist

  • I consent to the release of this PR's code under the AGPLv3 license and non-code contributions under CC0-1.0 and CC-BY-4.0.
  • I tested my contribution locally and it seems to work fine.
  • Code and workflow changes are sufficiently documented.
  • Newly introduced dependencies are added to envs/environment.yaml and doc/requirements.txt.
  • Changes in configuration options are added in all of config.default.yaml and config.tutorial.yaml.
  • Add a test config or line additions to test/ (note tests are changing the config.tutorial.yaml)
  • Changes in configuration options are also documented in doc/configtables/*.csv and line references are adjusted in doc/configuration.rst and doc/tutorial.rst.
  • A note for the release notes doc/release_notes.rst is amended in the format of previous release notes, including reference to the requested PR.

@ekatef
Copy link
Member Author

ekatef commented Jul 18, 2023

Currently works locally with cbc. Points which need attention:

  • there is a mismatch between batteries efficiency and links which has temporary resolved by using a single efficiency value, but needs additional investigation
  • what does mean replacement p with p_set in add_operational_reserve_margin_constraint()?
  • the solver log has disappeared from the logger files, while output of solver logging into a console presents if running solving from VSCode but is skipped is using a plain terminal
  • there is a fuzzy feeling that double-check is needed on transforming to xarray; in particular, the current calculation of ext_carrier_i differ from PyPSA-Eur implementation https://github.com/PyPSA/pypsa-eur/blob/6505d78f4e9a1362e8309bcd5b4b8b99ac60337c/scripts/solve_network.py#L350

@ekatef
Copy link
Member Author

ekatef commented Jul 19, 2023

I can reproduce CI error locally when using lgpk, while the workflow works with cbc

@pz-max
Copy link
Member

pz-max commented Jul 19, 2023

Cool! Just a comment. I would not follow my Linopy PR only but also check if the PyPSA-EUR main changed some parts.

@davide-f
Copy link
Member

Hello @ekatef, that's great news! :D
To answer to a question, the use of p_set is the more appropriate way to get access to the demand time series of load.

Regarding solver log, it may make sense to check the latest pypsa-eur implementation as I guess they have accounted for that.

As a remark, this morning we talked with Leon and it turns out that to merge this PR requires changes to the -sec version, which may be delicate also for some of their projects.
So, it is a good idea to investigate and prepare this PR, but it can be merged only when the alignment can be performed also with the Sec version.
What is your feeling?

@ekatef
Copy link
Member Author

ekatef commented Jul 20, 2023

Hey @davide-f!

Thanks a lot for the detailed analysis :)

Ah, I see: the load is an input, not a result. So, n.loads_t.p_set is in fact more appropriate than n.loads_t.p. Thanks for the explanation!

Regarding a potential merge of this PR, absolutely agree that it's crucial to ensure that the changes introduced don't break anything. Actually, I perceive this work as a kind of experiment to estimate how much effort would be needed for transition to linopy interface. If you see a potential to align this with requirements of PyPSA-Sec at some stage, that is great and happy to adjust this PR accordingly.

My general impression is that linopy transition is not as challenging as it could be expected from the first sight (thanks to @pz-max PR in PyPSA-Eur and clear instructions)

@ekatef
Copy link
Member Author

ekatef commented Jul 21, 2023

Some adjustments were done to get closer to PyPSA-Eur implementation. However, glpk solution still breaks. The reason seems to be the following command is given to a solver: 'glpsol --lp /var/folders/qn/vpndfm21795ckkq89np1ckp40000gn/T/linopy-problem-p4tiu2uu.lp --output /var/folders/qn/vpndfm21795ckkq89np1ckp40000gn/T/linopy-solve-7tkku64p.sol --solver_logfile /Users/ek/_github_/pypsa-earth/logs/NG_BJ/solve_network/elec_s_6_ec_lcopt_Co2L-4H_solver.log'

According to a warning, there is no --solver_logfile option available for glpsol, while it is transferred intorun_glpk as solver_options (not sure yet about the reason).

@ekatef
Copy link
Member Author

ekatef commented Aug 2, 2023

Results of additional testing:

  1. now highs is captured and used;
  2. some introduced changes make a testing problem infeasible, and it looks like the reason for it is that the problem formulation somehow gets lost:
INFO:linopy.solvers:Log file at /tmp/highs.log.
ERROR:   getOptionIndex: Option "solver_logfile" is unknown
Presolving model
Problem status detected on presolve: Infeasible
Model   status      : Infeasible
Objective value     :  0.0000000000e+00
HiGHS run time      :          0.00
WARNING:linopy.constants:Optimization failed: 
Status: warning
Termination condition: infeasible
Solution: 0 primals, 0 duals
Objective: nan
Solver model: available
Solver message: infeasible
  1. there are some troubles with logfile options for cbc:
No match for solver_logfile - ? for list of commands
No match for logs/MY/solve_network/elec_s_10_ec_lcopt_Co2L-2H_solver.log - ? for list of commands

@davide-f
Copy link
Member

IT may be good to revive this PR and finalize it when possible, to be able to use the latest pypsa version.
How far do you think we are to finalize?

I am aware that you have a lot on your desk right now, no pressure at all :) we can give priorities and tackle them one by one

@ekatef
Copy link
Member Author

ekatef commented Nov 17, 2023

IT may be good to revive this PR and finalize it when possible, to be able to use the latest pypsa version. How far do you think we are to finalize?

I am aware that you have a lot on your desk right now, no pressure at all :) we can give priorities and tackle them one by one

@davide-f agree :) I'd expect that something about a week of focused work should be enough to resolve the remained questions. As discussed, #919 and #903 seem to have higher priorities now. Also, not sure what is the current status of PyPSA-Earth-Sec in terms of pyomo/linopy dependencies.

Anyway, after #919 and #903 will be finished, happy to get back to this PR. Ping me please in case this would get urgent.

@davide-f davide-f linked an issue Dec 2, 2023 that may be closed by this pull request
@ekatef
Copy link
Member Author

ekatef commented Feb 26, 2024

Testing with the updated PyPSA version (0.25.2 is used locally).

Clustering is failing currently:

  1. the default SCIP solver (copy-pasted from PyPSA-Eur) is not included into PyPSA-Earth environment;
  2. ipopt is installed but not seen by linopy for some reason.

CI is failing due to conda being unhappy: Operation too slow. Less than 30 bytes/sec transferred the last 60 seconds.

@ekatef
Copy link
Member Author

ekatef commented Jul 1, 2024

A couple of notes on linopy grammar:

  1. pypsa.Network() doesn't initialise model property -> an explicit call n.optimize.create_model() is needed
  2. Multiplication is not commutative in the current linopy implementation: n.model["Generator-p"] * n.snapshot_weightings.generators works, while n.snapshot_weightings.generators * n.model["Generator-p"]` throws an error.
  3. Not sure what is the way to make groupby(some_grouper).sum() work on linopy variables.

@ekatef
Copy link
Member Author

ekatef commented Jul 3, 2024

Local testing on reproducibility of objective value

main version

INFO:main:Objective function: 5813422825.0
INFO:main:Objective constant: 2319283737.695124

linopy version

INFO:main:Objective function: 5473150322.268269
INFO:main:Objective constant: 2319359288.9025226

That means ~6% difference for the objective function and basically same value for the objective constant (1e-3%, but here is rather a question of numerical precision). So, the PR seems to work properly.

As a usability note, that is not obvious at all from the CI logs. The PR changes format of solver traceback due to different conventions of lopf and linopy, which makes finding correspondance quite tricky.

@ekatef ekatef marked this pull request as ready for review July 4, 2024 13:53
@ekatef
Copy link
Member Author

ekatef commented Jul 4, 2024

@finozzifa would be very grateful if you would have time to check this PR.

Pre-commit is currently unhappy due to excessively long lines in the configuration file. But that won't be fixed in this PR.

Another concern is backward compatibility. I'm currently looking into that, but not sure if it'll be possible to find a code solution for that.

@ekatef
Copy link
Member Author

ekatef commented Jul 10, 2024

As discussed with @davide-f, the backward compatibility issues can be tackled by maintenance measures and adding versioning of the configuration files. A possible implementation for versioning #1058.

This PR is ready for review.

@ekatef ekatef requested a review from davide-f July 10, 2024 09:30
Copy link
Member

@davide-f davide-f left a comment

Choose a reason for hiding this comment

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

Added some first comments :) Great work.
Indeed this PR is quite large too already, let's find the best way to finalize it :)
Let's remember to add a line in the release_note too

glpk-default: {} # Used in CI

mem: 30000 #memory in MB; 20 GB enough for 50+B+I+H2; 100 GB for 181+B+I+H2
walltime: "12:00:00"
Copy link
Member

Choose a reason for hiding this comment

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

In pypsa-eur the option is named "runtime" and has a slightly different naming option, e.g. 12h, though it may be compatible.
Do you think we can adopt the same naming and writing convention?

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah, I see! There has been an update in keywords in PyPSA-Eur intended to follow humanfriendly conventions. Ok, let's add this for consistency :)

@@ -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

@@ -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

Comment on lines +589 to +595
bus_strategies={
"lat": "mean",
"lon": "mean",
"tag_substation": "first",
"tag_area": "first",
"country": "first",
},
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

Comment on lines +1003 to +1010
# aggregation_strategies = snakemake.params.cluster_options.get(
# "aggregation_strategies", {}
# )
## translate str entries of aggregation_strategies to pd.Series functions:
# aggregation_strategies = {
# p: {k: getattr(pd.Series, v) for k, v in aggregation_strategies[p].items()}
# for p in aggregation_strategies.keys()
# }
Copy link
Member

Choose a reason for hiding this comment

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

Can you explain?
No more needed?

Copy link
Member Author

@ekatef ekatef Jul 21, 2024

Choose a reason for hiding this comment

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

Yes, the format transformation is not needed anymore. The aggregation strategies are read directly from the config via Snakemake parameters.

Have removed the commented-out parts.

linexpr,
network_lopf,
)
from pypsa.optimization.abstract import optimize_transmission_expansion_iteratively
Copy link
Member

Choose a reason for hiding this comment

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

Have you locally tested the features here changed? I feel like not all of them are parsed into the CI [which is not good too...]

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes! I have refactored add_RES_constraints and tested it's behaviour which has (obviously) required to add some fixes to account for linopy grammar.

There may be some advanced stuff which would be nice to investigate in more depth, but on the level of basic functionality it works.

Copy link
Member

Choose a reason for hiding this comment

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

Good as is !


logger = create_logger(__name__)


def prepare_network(n, solve_opts):
def prepare_network(n, solve_opts, config=None):
Copy link
Member

Choose a reason for hiding this comment

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

why optional?

Copy link
Member Author

Choose a reason for hiding this comment

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

May you clarify please? Not sure I really get the point 🙂

Copy link
Member

Choose a reason for hiding this comment

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

I'd propose to have config a mandatory argument, do we need the "=None"?

Copy link
Member Author

Choose a reason for hiding this comment

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

Frankly speaking, it has been just a copy-paste from PyPSA-Eur. But agree that it feels more natural to have config as a mandatory argument. Fixed in the stacked PR: this line

@ekatef ekatef mentioned this pull request Jul 21, 2024
8 tasks
@ekatef
Copy link
Member Author

ekatef commented Jul 21, 2024

Added some first comments :) Great work. Indeed this PR is quite large too already, let's find the best way to finalize it :) Let's remember to add a line in the release_note too

@davide-f thanks a lot for the great review! You have made me recognise that PR mixes two different things: PyPSA update and actually linopy transition. Moreover, the updated PyPSA version leads to some odd behaviour in the case when custom columns are present in the components on the network, which should be tracked regardless linopy methods.

Have opened #1065 and moved there the changes from this PR which relate to PyPSA update. Would you mind to continue the discussion on the aggregation strategies there? Then, for this PR we can focus on the solving part only. What do you think?

Copy link
Member

@davide-f davide-f left a comment

Choose a reason for hiding this comment

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

I've added few comments; the PR looks in good state; not sure your idea on how to integrate the changes propsoed in 1045.
A weird think I noticed is that the objective function of the problem has significantly changed, from something like 2.6 10^9 to 3.7e+09
Have you noticed it?


logger = create_logger(__name__)


def prepare_network(n, solve_opts):
def prepare_network(n, solve_opts, config=None):
Copy link
Member

Choose a reason for hiding this comment

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

I'd propose to have config a mandatory argument, do we need the "=None"?

linexpr,
network_lopf,
)
from pypsa.optimization.abstract import optimize_transmission_expansion_iteratively
Copy link
Member

Choose a reason for hiding this comment

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

Good as is !

Comment on lines +589 to +595
bus_strategies={
"lat": "mean",
"lon": "mean",
"tag_substation": "first",
"tag_area": "first",
"country": "first",
},
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?

@@ -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.

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

@@ -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.

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

@@ -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

-n.snapshot_weightings.stores * scaling,
get_var(n, "StorageUnit", "spill").T,
)
# TODO: double check that this is really needed, why do have to subtract the spillage
Copy link
Member

Choose a reason for hiding this comment

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

I believe so because in the rhs we have the inflow but not all inflow will be used; spill will be lost.
I don't see charging and discharging efficiencies here, so probably there may be some misalignment because of them.
So this todo should be revised into checking the efficiencies and probably good to raise an issue here.
Colleagues may have experienced minor issues about this maybe

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. I'm also not clear why the inflow and spillage are split in the current formulation which also doesn't make easier implementation of the efficiencies. In any case, we are following here the formulation of PyPSA-Eur, and it could probably make sense to revise both formulations. Happy to open an issue on that

0, np.inf, coords=[sns, n.generators.index], name="Generator-r"
)
reserve = n.model["Generator-r"]
lhs = reserve.sum("Generator")
Copy link
Member

Choose a reason for hiding this comment

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

This seems like RES generators can offer reserve too or am I wrong?

Copy link
Member Author

Choose a reason for hiding this comment

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

A very interesting point! As far as I understand, yes. We follow PyPSA-Eur in using the approach of GenX when defining the operational reserve constraint. That implies that the reserve margin formulation "optimal dispatch" of RES (whatever it means) and demand response. Have added a short comment on that in the doc string

I fear including wind and solar into the available reserve is against the current regulations in many countries, and it could make sense to have more flexible settings here. Does it sound reasonable for you?

"The add_RES_constraints functionality is still work in progress. "
"Unexpected results might be incurred, particularly if "
"The add_RES_constraints() is still work in progress. "
"Unexpected results might be incurre, particularly if "
Copy link
Member

Choose a reason for hiding this comment

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

incurred?

Copy link
Member Author

Choose a reason for hiding this comment

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

True, have over-done fixing the RES function 😄 Fixed

.T.groupby(ggrouper, axis=1)
.apply(join_exprs)
(n.model["Generator-p"].loc[:, gens_i] * n.snapshot_weightings.generators)
# .groupby(ggrouper.to_xarray())
Copy link
Member

Choose a reason for hiding this comment

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

what about this todo?

Copy link
Member Author

Choose a reason for hiding this comment

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

I had to stop here as groupby() has a bit specific dynamics in linopy and feels like a feature under development. In the current version I have not been to make it work, but the behaviour of groupby() has just changed recently, and more changes can be expected, as far as I can understand.

Moreover, I'm not quite sure this grouping is really needed. Currently, a share of RES is set for each country in a dedicated constraint, but this share is same for all the countries. I believe there may be some room for improvement here which we can track as a dedicated issue, as that is quite a frequent request to improve representation of RES development plans

@ekatef
Copy link
Member Author

ekatef commented Aug 13, 2024

Thanks a lot for the in-depth review @davide-f!

Along the way, we have identified a number of the additional issues, both related to this PR and completely independent ones. The list looks like follows

  1. Fetching isolated networks gets broken at the stage of applying get_switchable_as_dense( ) when aggregated storage units and loads. The issue looks quite weird and I have not been able to debug it properly. Currently, it's "fixed" by switching-off the functionality to merge isolated grids. That is not a solution which which I'm happy but it could make sense to tackle it separately.
  2. add_RES_constraints( ) may need further revisions.
  3. A revision seems to be needed for implementation of the hydro-related part in add_EQ_constraints( ) [both in PyPSA-Earth and PyPSA-Eur]
  4. It may be a good idea to adjust a definition of the reserve margin constraint in add_operational_reserve_margin_constraint( ) to make it compatible with the current regulation status in the most countries of the world.

Regarding the overall implementation strategy, my feeling is that it makes sense to disentangle PyPSA upgrade from enabling linopy. Have opened #1065 to update PyPSA and a stacked PR to enable linopy. Both PRs are mainly cherry-picking from this PR which should hopefully facilitate the final checks.

The PyPSA update leads to ~1% change of the objective function, while enabling linopy leaves the objective function pretty much the same (~1e-5 is the relative difference). So, I think this work is close to be finished, keeping in mind the points above 🙂

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

pypsa>=0.25 requires upgrade of network clustering Integrate Linopy
3 participants