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
)