Multi-dimensional batch calculation example

In this notebook we will walk through an example of multi-dimensional batch calculations, or calculations using a cartesian product of batch datasets.

Why are multi-dimensional batch calculations relevant?

It is possible to conduct a batch calculation of multiple datasets in form of a cartesian product of their scenarios. Assume certain batch datasets with \(N1\), \(N2\), \(N3\), … scenarios. This would give us \(N1 * N2 *N3 ...\) possible combinations via the cartesian product. The resulting output data is in flat form, with dimension of \(N1 * N2 *N3 ...\), where the first dataset has the highest dimension. This can be beneficial in reducing the complexity of implementing such a batch calculation along with keeping the size of such resulting update_data to a minimum.

Note: For a more comprehensive description, see our documentation. For a more detailed guide on how to use this feature, see our powerflow and validation examples.

Example Network

We use a simple network with 3 nodes, 1 source, 3 lines, and 2 loads. As shown below:

 -----------------------line_8---------------
 |                                          |
node_1 ---line_3--- node_2 ----line_5---- node_6
 |                    |                     |
source_10          sym_load_4           sym_load_7

The 3 nodes are connected in a triangular way by 3 lines.

# some basic imports
import numpy as np

from power_grid_model import (
    AttributeType,
    CalculationType,
    ComponentType,
    DatasetType,
    LoadGenType,
    PowerGridModel,
    attribute_dtype,
    initialize_array,
)
from power_grid_model.validation import assert_valid_batch_data, assert_valid_input_data

Input data and model construction

# node
node = initialize_array(DatasetType.input, ComponentType.node, 3)
node[AttributeType.id] = np.array([1, 2, 6])
node[AttributeType.u_rated] = [10.5e3, 10.5e3, 10.5e3]

# line
line = initialize_array(DatasetType.input, ComponentType.line, 3)
line[AttributeType.id] = [3, 5, 8]
line[AttributeType.from_node] = [1, 2, 1]
line[AttributeType.to_node] = [2, 6, 6]
line[AttributeType.from_status] = [1, 1, 1]
line[AttributeType.to_status] = [1, 1, 1]
line[AttributeType.r1] = [0.25, 0.25, 0.25]
line[AttributeType.x1] = [0.2, 0.2, 0.2]
line[AttributeType.c1] = [10e-6, 10e-6, 10e-6]
line[AttributeType.tan1] = [0.0, 0.0, 0.0]
line[AttributeType.i_n] = [1000, 1000, 1000]

# load
sym_load = initialize_array(DatasetType.input, ComponentType.sym_load, 2)
sym_load[AttributeType.id] = [4, 7]
sym_load[AttributeType.node] = [2, 6]
sym_load[AttributeType.status] = [1, 1]
sym_load[AttributeType.type] = [LoadGenType.const_power, LoadGenType.const_power]
sym_load[AttributeType.p_specified] = [20e6, 10e6]
sym_load[AttributeType.q_specified] = [5e6, 2e6]

# source
source = {
    AttributeType.id: np.array([10], dtype=attribute_dtype(DatasetType.input, ComponentType.source, AttributeType.id)),
    AttributeType.node: np.array(
        [1], dtype=attribute_dtype(DatasetType.input, ComponentType.source, AttributeType.node)
    ),
    AttributeType.status: np.array(
        [1], dtype=attribute_dtype(DatasetType.input, ComponentType.source, AttributeType.status)
    ),
    AttributeType.u_ref: np.array(
        [1.0], dtype=attribute_dtype(DatasetType.input, ComponentType.source, AttributeType.u_ref)
    ),
}

# all
input_data = {
    ComponentType.node: node,
    ComponentType.line: line,
    ComponentType.sym_load: sym_load,
    ComponentType.source: source,
}

# validate input data
assert_valid_input_data(input_data=input_data, calculation_type=CalculationType.power_flow)

# create model
model = PowerGridModel(input_data)

Batch Calculation

Before performing a multi-dimensional batch calculation, let’s consider the following simple batch calculation examples:

  • Time series batch calculation.

  • N-1 batch calculation.

Time series profile

The following code creates a load profile with 10 timestamps for the two loads. The two loads are always present for all mutation scenarios.

# update data for time series batch calculation
load_profile = initialize_array(DatasetType.update, ComponentType.sym_load, (10, 2))
load_profile[AttributeType.id] = [[4, 7]]  # note broadcasting here
# this is a scale of load from 0% to 100%
# the array is an (10, 2) shape, each row is a scenario, each column is an object
load_profile[AttributeType.p_specified] = [[30e6, 15e6]] * np.linspace(0, 1, 10).reshape(-1, 1)

time_series_mutation = {ComponentType.sym_load: load_profile}

# validate update data
assert_valid_batch_data(
    input_data=input_data, update_data=time_series_mutation, calculation_type=CalculationType.power_flow
)

# run batch calculation
output_data = model.calculate_power_flow(update_data=time_series_mutation)

# print all scenarios' results for i_from of all lines given the load profile
print(output_data[ComponentType.line][AttributeType.i_from])
[[ 193.06162675   64.92360593  137.59086941]
 [ 248.65360093   72.28087746  185.86691646]
 [ 368.12834615   90.73999329  285.46275837]
 [ 510.20016036  115.51167381  401.15982574]
 [ 662.04311447  143.73983633  523.57045909]
 [ 819.63118685  174.08971222  649.95104138]
 [ 981.46004118  205.93991655  779.31144601]
 [1146.90787963  238.98921914  911.25418138]
 [1315.7236725   273.08968816 1045.6266953 ]
 [1487.83778526  308.17354058 1182.39546494]]

N-1 Scenario where only the changed objects are specified

The following code creates a N-1 scenario for all three lines. There are 3 scenarios, in each scenario, the from/to status of one line is switched off. In this dataset we only specify one line per mutation.

# update data for N-1 batch calculation
line_profile = initialize_array(
    DatasetType.update, ComponentType.line, (3, 1)
)  # 3 scenarios, 1 object mutation per scenario
# for each mutation, only one object is specified
line_profile[AttributeType.id] = [[3], [5], [8]]
# specify only the changed status (switch off) of one line
line_profile[AttributeType.from_status] = [[0], [0], [0]]
line_profile[AttributeType.to_status] = [[0], [0], [0]]

n_min_1_mutation_update_specific = {ComponentType.line: line_profile}

# validate update data
assert_valid_batch_data(
    input_data=input_data, update_data=n_min_1_mutation_update_specific, calculation_type=CalculationType.power_flow
)

# run batch calculation
output_data = model.calculate_power_flow(update_data=n_min_1_mutation_update_specific)

# print all scenarios' results for i_from of all lines given the N-1 mutations
print(output_data[ComponentType.line][AttributeType.i_from])
[[   0.         1352.02947002 1962.69857764]
 [1199.97577809    0.          573.32693369]
 [1877.3560102   634.81512055    0.        ]]

Cartesian product of batch datasets

What if we want to perform a batch update that contains all the possible permutations of updates given by the time series and N-1 batch calculations presented above? This is where the cartesian product of batch datasets (or multi-dimensional batch calculation) comes into play.

We can now combine the time series mutation for all n-1 contingencies automatically and efficiently by passing all the batch datasets togethers as a list in the update_data.

# define the cartesian product of batch datasets
multi_dimensional_update_data = [n_min_1_mutation_update_specific, time_series_mutation]

# perform multi-dimensional batch calculations "as usual"
output_data = model.calculate_power_flow(update_data=multi_dimensional_update_data)

# print some results
print("Output data has shape", output_data[ComponentType.line].shape)
line_output = output_data[ComponentType.line][AttributeType.energized]
print(line_output[0, :])
print(line_output[1, :])
print(line_output[10, :])
print(line_output[11, :])
Output data has shape (30, 3)
[0 1 1]
[0 1 1]
[1 0 1]
[1 0 1]

Validation of cartesian product of batch datasets

Validation of a cartesian product of batch datasets is meant to be done individually on the datasets that compose the product. Validation of the cartesian product itself is not supported, as this is not another data structure, but just a list of datasets.

# validating individual update data is enough to validate the multi-dimensional batch data
assert_valid_batch_data(
    input_data=input_data, update_data=n_min_1_mutation_update_specific, calculation_type=CalculationType.power_flow
)

assert_valid_batch_data(
    input_data=input_data, update_data=time_series_mutation, calculation_type=CalculationType.power_flow
)