Skip to content

feat: introduce component class#1075

Merged
lkstrp merged 18 commits intomasterfrom
component-class
Dec 6, 2024
Merged

feat: introduce component class#1075
lkstrp merged 18 commits intomasterfrom
component-class

Conversation

@lkstrp
Copy link
Copy Markdown
Member

@lkstrp lkstrp commented Nov 7, 2024

Ref #936

Changes proposed in this Pull Request

Summary

  • Creates an additional layer to move all component-specific data from the network class to a new component class
  • Cleans up all cross references of n.component(), n.generators, n.generators_t and the equivalents in sub-networks
  • Allows for tons of API changes and improvements because of this extra layer
  • Does not change any existing API and also does not add too much new (yet)

Components Class

  • Structure: ComponentsData -> Components -> Generators, GenericComponents etc.
    • ComponentsData: Simple data class containing all stored data
      • As discussed, I played around a lot with changing the storage structure here (e.g. static and dynamic). Especially regarding the extra dimension that comes with stochastic networks.
        • But there is no nice way to map any new structure (no matter if xarray or pandas) back to the existing API and keep full backwards compatibility. So static and dynamic are untouched.
    • Components: Abstract base class that contains all logic relevant to all components.
      • Abstract, so should not be initialised by itself (could easily be, but this is an API discussion)
    • Generators, GenericComponents etc: child classes to be used
      • Contains only component-specific logic
      • Could be then used via
        >>> pypsa.Generators(names=[1,2,3], p_nom=5, p_max_pu=p_max_pu)
        PyPSA 'Generator' Components
        ----------------------------
        Components: 3
        • Adding components during initialisation is not yet implemented
  • Attached status
    • To allow for logic that needs other information from the network, but lets a Components object be self-contained, it can be either attached (self.n = None) or not attached (self.n = n).
      • self.n points to the attached network, self.attached is just a boolean indicating the same, and self.n_save can be used for functionality that requires an attached in any case
    • Right now this only affects self.get_active_assets(), which needs access to investment periods (and even that could be integrated into the components class).
    • If indices, crs, etc. match, would be checked during the attachment process.
      • e.g:
        n = pypsa.Network()
        generators =  pypsa.Generators(names=[1,2,3], p_nom=5, p_max_pu=p_max_pu)
        n.add(generators)
        • Also not implemented yet, since this PR doesn't move existing logic
  • SubNetwork
    • Components in Sub-Networks also just return a filtered view (of subnetwork specific components) of the parent Network
    • sn.generators(), sn.generators_i() , sn.components() is not deprecated yet, but points to the same generalized view

Components Store

  • All components are stored behind n.components.
    • e.g.
      n.components.generators.static # same as n.generators
      n.components.generators.dynamic # same as n.generators_t
      • This is just where the data is stored and does not have to be the preferred interface. It could be pointed to from anywhere (as done already).
      • Full backwards compatibility with the existing n.components is maintained.
  • The store is similar to the existing custom Dict, but with the possibility of some extra functionality:
    • e.g.
      >>> components = ComponentsStore()
      >>> components["generator"] = Generators()
      >>> components["storage"] = Storage()
      >>> components["generator"] = 'generator class instance
      generator instance
      >>> components[["generator", "storage"]]
      >>> components[["generator", "storage"]]
      [generator instance, storage instance].
      • which could replace n.iterate_components().
    • Other things are possible. I am not sure how the iterator should work:
      • Same behaviour as dict (.keys, .values, .items could be used)
        >>> for c_name in n.components:
        >>> print(c_name)
        generators
        • So only the key (str) is returned. To do anything you would always have to state c = n.components[c_name] and c_name could also be accessed via c.name.
      • Another option (and my vote) is list behaviour, which would also fit the above multiple indexing
       >>> for c in n.components:
       >>> print(c)
      generator instance

Components Type

  • Component types (data stored in components.csv, component_attrs/) are now stored at pypsa module level instead of dynamically during network initialisation.
  • They are stored in an immutable ComponentsTypeInfo data class with all fixed attributes: name, list_name, description, category, defaults (previously attrs) and standard_types (which I moved there).
  • Each Components instance is associated with a component type (ct) and therefore does not need to store any of the above information.
    • e.g. only contains ct, n, static and dynamic.
  • Custom components can be added at module level and then added during network initialisation via a list of components.

ToDos

In this PR

  • Add tests
  • Check with pypsa-eur
  • Performance tests
  • Implement custom component class workflow
  • Better alias access

API discussion and Docs, moving logic, new features, xarray view and so on, I would move to other PRs.

Copy link
Copy Markdown
Member

@coroa coroa left a comment

Choose a reason for hiding this comment

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

Puh that thing is huge, i only covered a small bit of the full amount of code. Most of it is looking good, but well alot.

)
return pd.DataFrame(active).any(axis=1) & self.static.active
for key, value in super().items():
if key != "Network": # TODO Drop Network completly?
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why is the Network even in the componentstore. In my understanding Network is no component. For instance what are its static or dynamic attributes?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good question. Added by Tom 7 years ago to the components.csv and was never touched since then. It is removed now. If there are people who rely on n.components.Network, this will break now.

['Generator class instance', 'Storage class instance']
"""
if isinstance(item, (list, set)):
return [self[key] for key in item]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

For consistency with DataFrame it might make sense to wrap this in a new ComponentsStore instance.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Hmm. I see, but not sure if I agree. For DataFrame consistency it makes sense and for them it also is useful since you wanna use DataFrame specific features on your sliced df.
But here there is no use case where you would need any ComponentsStore functionality on a sliced version. And then it just complicates things for no reason. If this returns a list, the ComponentsStore is always a complete representation of all components for a specific Network.

@lkstrp
Copy link
Copy Markdown
Member Author

lkstrp commented Nov 20, 2024

I added the missing features for this PR (custom component class workflow and better alias access) and resolved the existing review comments.
The question of how to iterate over n.components has now been resolved with a custom behaviour: value-only iteration, but .items() and .keys() can still be used.

Puh that thing is huge

Yeah, it got a bit too big, I agree. Sorry. @FabianHofmann @coroa If you don't have any bigger structural concerns, I would say let's merge this thing. Maybe after another release with the statistic expressions. I will go through some tests (add pytests, performance, pypsa-eur) but if they all work, the actual user impact should be quite minimal. And a lot can come after this.

@Irieo Irieo mentioned this pull request Nov 20, 2024
45 tasks
@FabianHofmann
Copy link
Copy Markdown
Contributor

@lkstrp I still can't find the time to look into this (even though I intrinsically would like to). So merging after the tests pass would be fine for me as this will unleash so much new potential for the package 🤘

1 similar comment
@FabianHofmann
Copy link
Copy Markdown
Contributor

@lkstrp I still can't find the time to look into this (even though I intrinsically would like to). So merging after the tests pass would be fine for me as this will unleash so much new potential for the package 🤘

@lkstrp lkstrp added this to the v0.33 milestone Nov 26, 2024
@fneum
Copy link
Copy Markdown
Member

fneum commented Dec 2, 2024

Ok, to merge from my side when these two TODOs are checked:

  • Check with pypsa-eur
  • Performance tests

@lkstrp
Copy link
Copy Markdown
Member Author

lkstrp commented Dec 2, 2024

I will also add a couple of improvements and more checks for custom components class since I got some example use cases now. But will also not merge before v0.32 is released.

EDIT: Moving this to another PR, we are too bloated already.

@lkstrp lkstrp merged commit c36338c into master Dec 6, 2024
@lkstrp lkstrp deleted the component-class branch December 9, 2024 15:55
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.

4 participants