Model Interface

Here we describe and show how to interact with the Grid object.

Grid definition

An empty Grid can be defined using the Grid.empty method. This initializes the Grid with all empty arrays. The Grid has a append method which takes an array and appends it to the relevant array-type based on it’s class definition and extends the graph as needed.

from power_grid_model_ds import Grid
from power_grid_model_ds.arrays import NodeArray

grid = Grid.empty()

nodes = NodeArray(
    id=[2, 3, 4, 5, 12],
    u_rated=[10_500.0] * 4 + [400.0],
)
grid.append(nodes)

Alternatively a Grid object can be initialized from a txt file (mainly for testing purposes) or from cached data.

To create a random Grid object a generator is provided.

from power_grid_model_ds.generators import RadialGridGenerator

grid_generator = RadialGridGenerator(grid_class=Grid, nr_nodes=1000)
grid = grid_generator.run(seed=0)

Topological modification

Having a grid it is possible to make modification to the grid while keeping track of the different representations and properties. To add a line to an existing grid

from power_grid_model_ds.arrays import LineArray

new_line_array = LineArray.zeros(1)
new_line_array.from_node = 2
new_line_array.to_node = 5
grid.add_branch(branch=new_line_array)

Or to activate an existing inactive branch in the grid

target_line = grid.arrays.line.get(1)
grid.make_active(target_branch_array=target_line)

As seen above, you can also apply multiple modifications in one go using the grid.append method.

Array interface

The array container is build around an extension of numpy arrays with the FancyArray class. This allows for easy and consistent definition of array types, recognition of array-type from its class and features which improve readability such as dot-notation and autocompletion. It contains a ._data attribute with the base numpy array and extra settings can be provided using ._defaults and ._str_lengths. Note these values should only be used in defining the array classes and remain private when using the arrays.

Array definition

You can create your own array by subclassing FancyArray. Array-columns can be defined by adding class attributes with the column name and the numpy dtype.

Example:

import numpy as np
from numpy.typing import NDArray

from power_grid_model_ds.fancypy import FancyArray


class MyArray(FancyArray):
    id: NDArray[np.int_]
    name: NDArray[np.str_]
    value: NDArray[np.float64]

It is possible to provide defaults for columns using

class MyDefaultedArray(MyArray):
    _defaults = {"id": -1, "name": "default", "value": 1.0}

These are used when initializing an array with the .empty method

array = MyDefaultedArray.empty(3)

Note on string-columns:

The default length for string columns is stored in _DEFAULT_STR_LENGTH. To change this, you can set the _str_lengths class attribute. Example:

class MyArray(FancyArray):
    name: NDArray[np.str_]
    _str_lengths = {"name": 100}

Where possible, it is recommended use IntEnum’s instead of string-columns to reduce memory usage.

Array loops

Looping over large arrays can incur a performance hit caused by conversion of each element to the original FancyArray subclass. When you want to implement a faster loop over the array you can choose to access the array.data directly and create the loop using

for row in array.data:
    pass

This looses the FancyArray class in the row but can be accepted when this is not used in the further for loop.

Array inheritance

You can inherit attributes from one array to another

class MyFirstArray(FancyArray):
    my_first_attribute: NDArray[np.int_]


class MySecondArray(MyFirstArray):
    my_second_attribute: NDArray[np.int_]

This gives the following dtype for MySecondArray: [('my_first_attribute', '<i8'), ('my_second_attribute', '<i8')]

Graph interface

When using the Grid this also provides access to extra graph analysis functionality. This can be used to investigate graph structure for example looking for a substation node or finding all nodes downstream the given node.

substation_node = grid.get_nearest_substation_node(node_id=102)
downstream_nodes = grid.get_downstream_nodes(node_id=102)

The underlying NetworkGraph elements of an active and complete network graph also provide extra analysis functionality. These include a shortest path analysis, component analysis or breath first search functionality.

all_paths = grid.graphs.active_graph.get_all_paths(56, 41)
components = grid.graphs.active_graph.get_components(substation_nodes=np.array([1, 2, 3]))
connected = grid.graphs.active_graph.get_connected(node_id=56)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[11], line 2
      1 all_paths = grid.graphs.active_graph.get_all_paths(56, 41)
----> 2 components = grid.graphs.active_graph.get_components(substation_nodes=np.array([1, 2, 3]))
      3 connected = grid.graphs.active_graph.get_connected(node_id=56)

File ~/checkouts/readthedocs.org/user_builds/power-grid-model-ds/checkouts/v1.3.3a12209263019/src/power_grid_model_ds/_core/model/graphs/models/base.py:258, in BaseGraphModel.get_components(self, substation_nodes)
    256     def get_components(self, substation_nodes: list[int] | None = None) -> list[list[int]]:
    257         """Returns all separate components when the substation_nodes are removed of the graph as lists"""
--> 258         if substation_nodes:
    259             warnings.warn(
    260                 message="""
    261 get_components: substation_nodes argument is deprecated and will be removed in a future release.
   (...)    269                 stacklevel=2,
    270             )
    271         with self.tmp_remove_nodes(substation_nodes or []):

ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()