Note

You can download this example as a Jupyter notebook or start it in interactive mode.

Components class#

Version 0.33 of PyPSA introduces a structural refactoring of how component data is stored and accessed. The new structure adds an extra layer to move all component-specific data from the networks class to a new component class. With version 0.33, most of these changes will be unnoticeable to the user.

But this makes it easy to add new features. Below are some simple examples to show which other features could be added in the future. If you have any ideas, wishes, feedback or suggestions, please let us know via PyPSA/PyPSA#issues.

Note that this is experimental. Features will be added and the newly introduced API may change. You can still use PyPSA as usual. Major API changes will not be introduced before version 1.0. While the Components class does not introduce any breaking changes, the ``ComponentsStore`` leads to slightly different behavior for ``n.components``. See the explanation below.

Also, while all classes and methods have docstrings, there is no dedicated documentation yet.

General#

[1]:
import pypsa

n = pypsa.examples.scigrid_de()
WARNING:pypsa.io:Importing network from PyPSA version v0.17.1 while current version is v0.33.0. Read the release notes at https://pypsa.readthedocs.io/en/latest/release_notes.html to prepare your network for import.
INFO:pypsa.io:Imported network scigrid-de.nc has buses, generators, lines, loads, storage_units, transformers

Components class#

So far, components data was directly attached to the network object (e.g. n.generators, n.generators_t etc.). While you still can access the data there, both actually sit now in a new Components class:

[2]:
c = n.components.generators  # also via alias n.c.generators
c
[2]:
PyPSA 'Generator' Components
----------------------------
Attached to PyPSA Network 'scigrid-de'
Components: 1423

The datasets for static and dynamic data can be accessed via the class now, but also still via the old network properties:

[3]:
c.static.head()
[3]:
bus control type p_nom p_nom_mod p_nom_extendable p_nom_min p_nom_max p_min_pu p_max_pu ... min_up_time min_down_time up_time_before down_time_before ramp_limit_up ramp_limit_down ramp_limit_start_up ramp_limit_shut_down weight p_nom_opt
Generator
1 Gas 1 PQ 121.0 0.0 False 0.0 inf 0.0 1.0 ... 0 0 1 0 NaN NaN 1.0 1.0 1.0 0.0
1 Hard Coal 1 PQ 272.0 0.0 False 0.0 inf 0.0 1.0 ... 0 0 1 0 NaN NaN 1.0 1.0 1.0 0.0
102 Gas 102 PQ 67.9 0.0 False 0.0 inf 0.0 1.0 ... 0 0 1 0 NaN NaN 1.0 1.0 1.0 0.0
108 Run of River 108 PQ 63.1 0.0 False 0.0 inf 0.0 1.0 ... 0 0 1 0 NaN NaN 1.0 1.0 1.0 0.0
108 Waste 108 PQ 38.0 0.0 False 0.0 inf 0.0 1.0 ... 0 0 1 0 NaN NaN 1.0 1.0 1.0 0.0

5 rows × 37 columns

[4]:
c.dynamic.keys()
[4]:
dict_keys(['p_min_pu', 'p_max_pu', 'p_set', 'q_set', 'marginal_cost', 'marginal_cost_quadratic', 'efficiency', 'stand_by_cost', 'ramp_limit_up', 'ramp_limit_down', 'p', 'q', 'status', 'mu_upper', 'mu_lower', 'mu_p_set', 'mu_ramp_limit_up', 'mu_ramp_limit_down'])
[5]:
# Both ways refer to the same DataFrame/ Dict Container of
print(c.static is n.generators)
print(c.dynamic is n.generators_t)
True
True

Components Store#

There have been some major changes to n.components, which is now the basic store for all components. Before version 0.33, n.components was a dictionary containing only the default component data, and no static or dynamic data. Now it contains both (as described above), while still allowing access to the default data:

[6]:
print(f"List name: '{n.components['Generator'].list_name}'")
print(f"Description: '{n.components['Generator'].description}'")
List name: 'generators'
Description: 'Power generator.'

But the iteration behaviour is different. While a dictionary only returns the keys, the new `ComponentsStore’ object returns the components themselves (similar to a list). This leads to a break when using it:

[7]:
for comp in n.components:
    break
/tmp/ipykernel_4629/3163957973.py:1: DeprecationWarning: Iterating over `n.components` yields the values instead of keys from v0.33.0. This behavior might be breaking. Use `n.components.keys()` to iterate over the keys. To suppress this warning set `pypsa.options.warnings.components_store_iter = False`.
  for comp in n.components:

and __contains__/ x in n.components is not supported anymore:

[8]:
"x" in n.components
---------------------------------------------------------------------------
DeprecationWarning                        Traceback (most recent call last)
Cell In[8], line 1
----> 1 "x" in n.components

File ~/checkouts/readthedocs.org/user_builds/pypsa/envs/latest/lib/python3.13/site-packages/pypsa/definitions/components.py:207, in ComponentsStore.__contains__(self, item)
    197 """
    198 Check if component is in store.
    199 """
    200 msg = (
    201     "Checking if a component is in `n.components` using the 'in' operator "
    202     "is deprecated. Use `item in n.components.keys()` to retain the old "
   (...)
    205     "notes for more information."
    206 )
--> 207 raise DeprecationWarning(msg)

DeprecationWarning: Checking if a component is in `n.components` using the 'in' operator is deprecated. Use `item in n.components.keys()` to retain the old behavior. But with v0.33.0 custom components are deprecated and therefore keys in `n.components` never change. Check the release notes for more information.

Examples#

[9]:
c = n.components.generators

Simple alias properties#

[10]:
# Basic component information
print(f"Component name: '{c.name}'")
print(f"Component list name: '{c.list_name}'")
print(f"Component type: '{c.type}'")

# Other maybe useful properties
print(f"Nominal attribute: '{c.nominal_attr}'")
Component name: 'Generator'
Component list name: 'generators'
Component type: 'controllable_one_port'
Nominal attribute: 'p_nom'
[11]:
# Quick access to attribute units
c.units.head()
[11]:
unit
attribute
p_nom MW
p_nom_mod MW
p_nom_min MW
p_nom_max MW
p_min_pu per unit
[12]:
# Get ports of component (e.g. for multiport components)
n.c.links.ports
[12]:
['0', '1']
[13]:
# Check if component is attached to network
if c.attached:
    print(f"{c} is attached to {c.n}")
PyPSA 'Generator' Components is attached to PyPSA Network 'scigrid-de'

Rename components and propagate new names through network#

[14]:
# Old names
print(f"Old bus names: {', '.join(c.static.head(2).index)}")
Old bus names: 1 Gas, 1 Hard Coal
[15]:
# Rename "1 Gas" component
c = n.components.buses
rename_map = {"1": "Super Bus"}
c.rename_component_names(**rename_map)
[16]:
# New names
print(f"New bus names: {', '.join(c.static.head(2).index)}")
New bus names: Super Bus, 2
[17]:
# Changes in other components of network
n.c.generators.static.head(2)
[17]:
bus control type p_nom p_nom_mod p_nom_extendable p_nom_min p_nom_max p_min_pu p_max_pu ... min_up_time min_down_time up_time_before down_time_before ramp_limit_up ramp_limit_down ramp_limit_start_up ramp_limit_shut_down weight p_nom_opt
Generator
1 Gas Super Bus PQ 121.0 0.0 False 0.0 inf 0.0 1.0 ... 0 0 1 0 NaN NaN 1.0 1.0 1.0 0.0
1 Hard Coal Super Bus PQ 272.0 0.0 False 0.0 inf 0.0 1.0 ... 0 0 1 0 NaN NaN 1.0 1.0 1.0 0.0

2 rows × 37 columns

Calculate line length from attached buses#

[18]:
c = n.c.lines
c.calculate_line_length()
[18]:
0      34432.796096
1      59701.666027
2      32242.741010
3      30559.154647
4      21574.543367
           ...
847      608.501911
848      774.034099
849      781.865928
850      816.599959
851      836.332814
Length: 852, dtype: float64

Those are just a couple of simple examples. Many other features could be added. If you have any ideas, wishes, feedback or suggestions, please let us know via the PyPSA/PyPSA#issues.