Skip to content

feat: improved n.add#896

Merged
lkstrp merged 30 commits intoPyPSA:masterfrom
lkstrp:refactor-add-components
Sep 23, 2024
Merged

feat: improved n.add#896
lkstrp merged 30 commits intoPyPSA:masterfrom
lkstrp:refactor-add-components

Conversation

@lkstrp
Copy link
Copy Markdown
Member

@lkstrp lkstrp commented May 17, 2024

Closes:

Changes proposed in this Pull Request

  • n.mremove and n.add are depricated and merged into n.remove and `n.add
  • n.import_components_from_dataframe and n.import_series_from_dataframe are deprecated (moved to internal function)

Todos:

  • Test performance

Checklist

  • Code changes are sufficiently documented; i.e. new functions contain docstrings and further explanations may be given in doc.
  • Unit tests for new features were added (if applicable).
  • Newly introduced dependencies are added to environment.yaml, environment_docs.yaml and setup.py (if applicable).
  • A note for the release notes doc/release_notes.rst of the upcoming release is included.
  • I consent to the release of this PR's code under the MIT license.

@lkstrp lkstrp marked this pull request as draft May 21, 2024 08:14
@lkstrp lkstrp changed the title [DRAFT] #895 Refactor: Generalize add component functions Refactor: Generalize add component functions May 22, 2024
@lkstrp
Copy link
Copy Markdown
Member Author

lkstrp commented May 22, 2024

The changes described above are now implemented.

As this is an API change, we should think about whether other changes in terms of better code completion/type hinting would make sense in the future. So that nothing needs to be deprecated again. Code completion/hinting could be added on two levels: To see what components exist and also what the defaults are.

A couple of options (feel free to come up with anything else):

EDIT:

1. Component container class

from pypsa import components
n.add(components.Generators(), names, **kwargs) # Instance or class, see below

Pros:

  • Best backwards compatibility
  • Most discoverability
  • Most future-proof and consistent with a potential custom Component class (only optional)
  • Custom components support
  • Most pythonic (IMHO)
  • We don't have to change anything now

Cons:

  • More code to add something
  • Maybe too many different ways to add something, which could be confusing?

2. Add class

n.add.generators(names, **kwargs)

Pros:

  • Similar to the statistics module\
  • Discoverability, but not for default values

Cons:

  • Custom components are a bit more hacky
  • Needs deprecation
  • Least pythonic (IMHO)

3. Function per component

n.add_generators(names, **kwargs)

Pros:

  • Easy to use and pythonic
  • Discoverability, but not for default values
  • Honestly, I don't see more

Cons:

  • Custom components
  • Needs deprecation
Option 1 Option 2 Option 3
Backwards compatibility Yes No, only via old method No, only via old method
Component discoverability Yes Yes Yes
Component attribute discoverability Yes Yes Yes
Component attribute default value discoverability Yes No No

@coroa
Copy link
Copy Markdown
Member

coroa commented Jun 10, 2024

Hi, i see this has been lying dormant. Sorry for not moving on it. Thoughts:

Must-haves:

  1. Stay backwards compatible for a depreciation period, ie. n.add("Generator", ...) would have to continue to work.
  2. We need to test performance of the change (maybe construct a sector network and measure the times)

Interface:
PyPSA is currently lacking discoverability of the actual component names, which is what you address above by what you call component hints i think; but even more so of the allowed attributes (ie. is it p_max_pu or s_max_pu or committable, comittable (for those of us not being native english speakers). ie. i typically need the component docs open in a browser tab, when i design my model. This means, it would be good if pylance or jedi could see these argument options and could show meaningful doc strings about them, which i think is impossible with option 1, and would need option 2 or 3. I am not a big fan of the capital letter component names, so i have a slight preference for option 3: net.add_generators (but i would use the plural). The drawback is the custom component system, which allows you to add new component types (not sure, if we autogenerated those methods, jedi or pylance would be able to show their doc strings).

Thanks for the proposal

@coroa
Copy link
Copy Markdown
Member

coroa commented Jun 10, 2024

If we are talking about api change, i also would like to try to gather everything up into objects eventually (ie. a Generators object, which holds the dataframes for static and dynamic attributes as well as adders).

net.generators -> net.generators.static (or df)
net.generators_t -> net.generators.varying (or pnl)
net.add("Generator", ...) -> net.generators.add(...)

But only to let it float once. Don't try to go there directly.

@FabianHofmann
Copy link
Copy Markdown
Contributor

FabianHofmann commented Jun 10, 2024

EDIT: It might be that a ComponentDataFrame could be a strategy (like the GeoDataFrame), but really not sure how much work this would require

@lkstrp
Copy link
Copy Markdown
Member Author

lkstrp commented Jun 21, 2024

Following up on this:
I edited plural/ capital form for the options and summarized pros and cons with a table. So, see again the first post.

Yes, I mean discoverability with what I called component hints. There are actually three levels of discoverability: components, component attributes and component attribute default values. With the n.add() right now, we are lacking all of them. All three options allow for component and attribute discoverability. Option 1 also allows for default values, in a way.

With option 1 there are basically a couple of options on how to add components:

# Old way, no deprecation needed
n.add('generators', names='gen', p_set=100)

# Mix, no attr discoverability
from pypsa.components import Generators
n.add(Generators(), names='gen', p_set=100)
# We could discuss to if passing the instance or class is the better approach here. Maybe instance if now further attributes are assigned in n.add, and class if there are assigned in there.

# Full discoverability
generators = Generators(names='gen', p_set=100)
n.add(generators)
# To get default value
print(generators.p_nom)

This is only the add discussion. The second discussion is on how to construct component classes. With option 1 this would beautifully align with those API changes:

net.generators -> net.generators.static (or df)
net.generators_t -> net.generators.varying (or pnl)
net.add("Generator", ...) -> net.generators.add(...)

The issue with the GeoDataFrame similar approach is that we have too many dimensions for that: list of components, attributes, and optional snapshots. GeoDataFrames don't have that issue. And if we mix them in one data frame, we lose all the advantages of this in the first place (e.g. sticking to the fact that net.generators is a data frame, like right now, and not a custom class). So I think a custom class with the API shown above and the adding system like above would be the best way.

But this is a second discussion and does not need to be decided right now. Now it would be great to agree on one of the options to get this PR merged.

@FabianHofmann
Copy link
Copy Markdown
Contributor

@lkstrp I really like the approach in here:

# Full discoverability
generators = Generators(names='gen', p_set=100)
n.add(generators)

It paves the way to the component class that @coroa mentioned.
I am just wondering whether defaults should be stored in an extra sub-field, like

generators.defaults.p_nom 

such that

generators.p_nom

returns the actual assigned value.

@lkstrp
Copy link
Copy Markdown
Member Author

lkstrp commented Jun 24, 2024

Regarding defaults, isn't it simpler and less overhead to just let generators.p_nom be the assigned value if something was assigned and the default value if nothing was assigned. Default values after something has been overassigned could still be checked by initiating a new empty class.

I move this discussion to a new issue #936

@lkstrp lkstrp mentioned this pull request Jun 24, 2024
@lkstrp lkstrp force-pushed the refactor-add-components branch from f3caacc to 3322b7d Compare June 24, 2024 14:55
@lkstrp lkstrp marked this pull request as ready for review June 24, 2024 15:27
@lkstrp lkstrp requested a review from FabianHofmann June 24, 2024 15:27
@lkstrp lkstrp requested review from FabianHofmann and removed request for fneum July 10, 2024 15:09
@codecov
Copy link
Copy Markdown

codecov bot commented Jul 10, 2024

Codecov Report

Attention: Patch coverage is 69.44444% with 33 lines in your changes missing coverage. Please review.

Project coverage is 78.28%. Comparing base (b8fbebf) to head (7ca92c9).
Report is 23 commits behind head on master.

Files Patch % Lines
pypsa/components.py 67.53% 19 Missing and 6 partials ⚠️
pypsa/clustering/spatial.py 50.00% 6 Missing ⚠️
pypsa/io.py 89.47% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #896      +/-   ##
==========================================
- Coverage   78.32%   78.28%   -0.05%     
==========================================
  Files          22       22              
  Lines        4785     4799      +14     
  Branches     1015     1019       +4     
==========================================
+ Hits         3748     3757       +9     
- Misses        770      775       +5     
  Partials      267      267              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Copy Markdown
Member

@fneum fneum left a comment

Choose a reason for hiding this comment

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

I agree with most changes.

One larger issue is that the processing of indices is not consistently applied when adding multiple components at once; some of the safeguards in n.madd maybe have not been carried over.

Copy link
Copy Markdown
Contributor

@FabianHofmann FabianHofmann left a comment

Choose a reason for hiding this comment

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

this is great as always @lkstrp, let me try that out locally today.

One thing I would love to see as well is a more sensible and consistent order of columns in static dataframes as well as an alignment of the orders in the timeseries dataframes' index and static dataframes' columns. I am not sure if this fits here or should be moved to another PR

Comment on lines +1083 to +1088
logger.warning(
"The following %s are already defined and will be skipped "
"(use overwrite=True to overwrite): %s",
n.components[cls_name]["list_name"],
", ".join(duplicated_components),
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think we should raise an error here instead. Like this, we give a clear feedback, logger warnings are often ignored by users according to my experience.

@lkstrp
Copy link
Copy Markdown
Member Author

lkstrp commented Sep 6, 2024

True, right now, especially when overwriting certain components, it can be inconsistent.

What do you have in mind? Just hard-coded alphabetically? Or in order of when they were added? The first could be added here. The second is a bit trickier because of the two Import from Series and Import from Dataframe functions. I'd like to get rid of them at some point anyway, so it would make sense to add a consistent order then.

@FabianHofmann
Copy link
Copy Markdown
Contributor

Either alphabetically or in the order of the components_attrs/buses.csv etc. However, the latter does not include additional columns like bus2, bus3 for links for example, which would go then to the end. So I would say alphabetical it is.

@lkstrp
Copy link
Copy Markdown
Member Author

lkstrp commented Sep 9, 2024

Either alphabetically or in the order of the components_attrs/buses.csv etc. However, the latter does not include additional columns like bus2, bus3 for links for example, which would go then to the end. So I would say alphabetical it is.

Based on the attributes order in components_attrs/buses.csv has a better usability, doesn't it? It is also less of a change to the current state. I just pushed a changed for that, it is not much.

I was also talking about the order of components (component names). Here alphabetical would make sense, but this will need some test rewrites as well. Or do you want to keep it as it is by last added? This just leads to issues if you overwrite components, because they are added again at the end (could also be fixed though).

@FabianHofmann @fneum So I would like to get a confirmation on:

  • component attrs: sorted as in components_attrs, other attrs at the end
  • component names: alphabetical

@FabianHofmann
Copy link
Copy Markdown
Contributor

@lkstrp I like the ordering according to component_attrs it gives you more control (and we can think about better orders in the future). The only flaw it has is the bus2, bus3 columns. We should have them much more at the beginning, right after bus1. however we could solves this. there, is a special function for adding these on the fly update_linkports_component_attrs introduced in https://github.com/PyPSA/PyPSA/pull/669/files. when we adapt this function to meet sensible bus columns ordering then all is good.

@FabianHofmann
Copy link
Copy Markdown
Contributor

@lkstrp I like the ordering according to component_attrs it gives you more control (and we can think about better orders in the future). The only flaw it has is the bus2, bus3 columns. We should have them much more at the beginning, right after bus1. however we could solves this. there, is a special function for adding these on the fly update_linkports_component_attrs introduced in https://github.com/PyPSA/PyPSA/pull/669/files. when we adapt this function to meet sensible bus columns ordering then all is good.

and the efficiencies, i.e. efficiency2, efficiency3

@fneum
Copy link
Copy Markdown
Member

fneum commented Sep 9, 2024

Ok!

@lkstrp lkstrp changed the title Refactor: Generalize add component functions feat: improved n.add Sep 20, 2024
@lkstrp lkstrp merged commit d441c35 into PyPSA:master Sep 23, 2024
@lkstrp lkstrp deleted the refactor-add-components branch April 9, 2025 08:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

4 participants