Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
8085e5b
start slice dataset
TonyXiang8787 Nov 10, 2025
73d85c5
add slice scenario
TonyXiang8787 Nov 10, 2025
234f38d
Merge branch 'main' into experimental/multi-dimension-batch
TonyXiang8787 Nov 10, 2025
be44d80
batch dimension
TonyXiang8787 Nov 10, 2025
1fd7ed3
calculation implementation
TonyXiang8787 Nov 10, 2025
45f4d72
error handling still needs to be done
TonyXiang8787 Nov 10, 2025
06cb330
error handling
TonyXiang8787 Nov 10, 2025
a4fc01e
add batch dimensions
TonyXiang8787 Nov 10, 2025
118655f
batch dimension
TonyXiang8787 Nov 10, 2025
3b41f28
start test
TonyXiang8787 Nov 10, 2025
d88db97
start test
TonyXiang8787 Nov 10, 2025
1b1190d
api will not work as intended
TonyXiang8787 Nov 10, 2025
21c31c5
api will not work as intended
TonyXiang8787 Nov 10, 2025
6e8c081
adjust md dataset
TonyXiang8787 Nov 11, 2025
0b989b4
add dataset
TonyXiang8787 Nov 11, 2025
14b4039
crash yet
TonyXiang8787 Nov 11, 2025
3338881
fix bounds checking
TonyXiang8787 Nov 11, 2025
01c86a9
remove span
TonyXiang8787 Nov 11, 2025
7786b0c
fix clang tidy
TonyXiang8787 Nov 11, 2025
5a3f394
format|
TonyXiang8787 Nov 11, 2025
2650968
[skip ci] add cfunc in python
TonyXiang8787 Nov 11, 2025
9aaa6bd
force nullptr
TonyXiang8787 Nov 11, 2025
e4aa439
add options
TonyXiang8787 Nov 11, 2025
237681f
proxy for multidimensional in python
TonyXiang8787 Nov 11, 2025
b602f2e
modify main calculate input
TonyXiang8787 Nov 11, 2025
3e79237
type annotation
TonyXiang8787 Nov 11, 2025
8d8d80c
[skip ci] not working yet
TonyXiang8787 Nov 11, 2025
61cfd57
fix dimensions
TonyXiang8787 Nov 11, 2025
a1331ba
fix mypy
TonyXiang8787 Nov 12, 2025
36707d9
Merge branch 'main' into experimental/multi-dimension-batch
TonyXiang8787 Nov 23, 2025
9c27e6e
empty slice
TonyXiang8787 Nov 25, 2025
bce908e
Merge branch 'main' into experimental/multi-dimension-batch
TonyXiang8787 Nov 25, 2025
7faa6c1
begin next
TonyXiang8787 Nov 25, 2025
c4b854e
revert api change
TonyXiang8787 Nov 25, 2025
cb67ae3
model calculate for chaining
TonyXiang8787 Nov 25, 2025
a7352f2
add dataset chaining
TonyXiang8787 Nov 25, 2025
b3c8901
revert cpp interface
TonyXiang8787 Nov 25, 2025
fa6fdf9
c-api ready
TonyXiang8787 Nov 25, 2025
65888cd
revert python side
TonyXiang8787 Nov 25, 2025
61e915f
adjust python chaining
TonyXiang8787 Nov 25, 2025
fb22a70
python side ready
TonyXiang8787 Nov 25, 2025
79936a6
fix format
TonyXiang8787 Nov 26, 2025
2e0ff73
fix format
TonyXiang8787 Nov 26, 2025
fda2687
Merge branch 'main' into experimental/md-batch-chaining
TonyXiang8787 Dec 11, 2025
e106133
Update tests/native_api_tests/test_api_model_multi_dimension.cpp
TonyXiang8787 Dec 11, 2025
951fa68
fix comments
TonyXiang8787 Dec 11, 2025
04b1abd
Merge branch 'main' into experimental/md-batch-chaining
TonyXiang8787 Dec 12, 2025
4306491
refactor stride size
TonyXiang8787 Dec 12, 2025
6f1b5a1
change set/get next in cpp
TonyXiang8787 Dec 12, 2025
b472331
change get/set next in capi
TonyXiang8787 Dec 12, 2025
3185c93
modify python side set next
TonyXiang8787 Dec 12, 2025
b18d06c
add c-api docs
TonyXiang8787 Dec 12, 2025
5967452
try python docs
TonyXiang8787 Dec 12, 2025
e203ff1
try space
TonyXiang8787 Dec 12, 2025
d222d3c
adjust python docs
TonyXiang8787 Dec 12, 2025
d1dc1cf
avoid cyclic chain
TonyXiang8787 Dec 12, 2025
26f6ca4
format
TonyXiang8787 Dec 12, 2025
f58e13f
sonar
mgovers Dec 15, 2025
30ec19c
Update power_grid_model_c/power_grid_model_c/include/power_grid_model…
TonyXiang8787 Dec 15, 2025
54a8d01
do not use chain word
TonyXiang8787 Dec 15, 2025
7b106f2
Merge branch 'main' into experimental/md-batch-chaining
TonyXiang8787 Dec 15, 2025
a1c0710
Update power_grid_model_c/power_grid_model_c/src/model.cpp
TonyXiang8787 Dec 15, 2025
76dec6e
Update power_grid_model_c/power_grid_model_c/include/power_grid_model…
TonyXiang8787 Dec 15, 2025
f5e69df
Update src/power_grid_model/_core/power_grid_model.py
TonyXiang8787 Dec 15, 2025
7e2c865
modify test case
TonyXiang8787 Dec 15, 2025
c208af1
modify test case
TonyXiang8787 Dec 15, 2025
6728662
Merge branch 'main' into experimental/md-batch-chaining
TonyXiang8787 Dec 19, 2025
fb9486c
Merge branch 'main' into experimental/md-batch-chaining
figueroa1395 Dec 22, 2025
f5ae291
add documentation
nitbharambe Dec 31, 2025
b1626bd
change print
nitbharambe Dec 31, 2025
974437a
rename to cartesian product
nitbharambe Jan 2, 2026
ce0a529
Apply suggestion from @nitbharambe
nitbharambe Jan 5, 2026
247d855
Merge branch 'main' into experimental/md-batch-chaining
figueroa1395 Jan 5, 2026
195ba5f
resolve comments
nitbharambe Jan 6, 2026
57d5149
resolve comments 2
nitbharambe Jan 6, 2026
79be1f8
Merge branch 'main' into experimental/md-batch-chaining
TonyXiang8787 Jan 6, 2026
5840870
fix threading issue
TonyXiang8787 Jan 6, 2026
13141a3
Merge branch 'experimental/md-batch-chaining' into docs/batch-chaining
TonyXiang8787 Jan 6, 2026
1035a80
Merge pull request #1246 from PowerGridModel/docs/batch-chaining
TonyXiang8787 Jan 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 78 additions & 20 deletions docs/examples/Power Flow Example.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -1146,6 +1146,64 @@
"print(output_data[ComponentType.line][\"i_from\"])"
]
},
{
"cell_type": "markdown",
"id": "344efbe8",
"metadata": {},
"source": [
"#### Cartesian product of batch datasets\n",
"\n",
"It is possible to conduct a batch calculation of multiple datasets in form of a cartesian product of their scenarios.\n",
"Assume certain batch datasets with N1, N2, N3, ... scenarios. \n",
"This would give us $N1 * N2 * N3 * ...$ possible combinations via the cartesian product.\n",
"The resultant output data is in flat form and it has dimension of N1 * N2 * N3 with first dataset being the highest\n",
"dimension. \n",
"This can be beneficial in reducing complexity of implementation of such batch calculation \n",
"along with keeping the size of such resultant update_data to a minimum.\n",
"\n",
"```note\n",
"The data validation module mentioned in #validation-(optional) does not support cartesian product of datasets. \n",
"It is advised to combine the datasets them manually via numpy to use the validator.\n",
"```"
]
},
{
"cell_type": "markdown",
"id": "5de1a2f7",
"metadata": {},
"source": [
"We can combine the time series mutation for all n-1 contingencies together in following way.\n",
"Both the batch datasets are passed together in `update_data` in a list."
]
},
{
"cell_type": "code",
"execution_count": 29,
"id": "d690eeb7",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Output data has shape (30, 3)\n",
"[0 1 1]\n",
"[0 1 1]\n",
"[1 0 1]\n",
"[1 0 1]\n"
]
}
],
"source": [
"output_data = model.calculate_power_flow(update_data=[n_min_1_mutation_update_specific, time_series_mutation])\n",
"print(\"Output data has shape\", output_data[ComponentType.line].shape)\n",
"line_output = output_data[ComponentType.line][\"energized\"]\n",
"print(line_output[0, :])\n",
"print(line_output[1, :])\n",
"print(line_output[10, :])\n",
"print(line_output[11, :])"
]
},
{
"attachments": {},
"cell_type": "markdown",
Expand Down Expand Up @@ -1210,7 +1268,7 @@
},
{
"cell_type": "code",
"execution_count": 29,
"execution_count": 30,
"id": "b5f10bae",
"metadata": {},
"outputs": [
Expand Down Expand Up @@ -1270,7 +1328,7 @@
},
{
"cell_type": "code",
"execution_count": 30,
"execution_count": 31,
"id": "1a221507",
"metadata": {},
"outputs": [
Expand Down Expand Up @@ -1312,15 +1370,15 @@
},
{
"cell_type": "code",
"execution_count": 31,
"execution_count": 32,
"id": "541af620",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Iteration failed to converge after 20 iterations! Max deviation: 3.54512e-16, error tolerance: 1e-20.\n",
"Iteration failed to converge after 20 iterations! Max deviation: 3.54512293063893e-16, error tolerance: 1e-20.\n",
"\n",
"Try validate_input_data() or validate_batch_data() to validate your data.\n",
"\n"
Expand Down Expand Up @@ -1360,7 +1418,7 @@
},
{
"cell_type": "code",
"execution_count": 32,
"execution_count": 33,
"id": "20d8285c",
"metadata": {},
"outputs": [],
Expand All @@ -1371,7 +1429,7 @@
},
{
"cell_type": "code",
"execution_count": 33,
"execution_count": 34,
"id": "b702eb15",
"metadata": {},
"outputs": [
Expand All @@ -1384,7 +1442,7 @@
"\n",
"Failed scenarios: [3 7]\n",
"Succeeded scenarios: [0 1 2 4 5 6 8 9]\n",
"Error messages: ['The id cannot be found: 1000\\n', 'Sparse matrix error, possibly singular matrix!\\nIf you get this error from state estimation, it might mean the system is not fully observable, i.e. not enough measurements.\\nIt might also mean that you are running into a corner case where PGM cannot resolve yet.See https://github.com/PowerGridModel/power-grid-model/issues/864.']\n"
"Error messages: ['The id cannot be found: 1000\\n', 'Sparse matrix error, possibly singular matrix!\\nIf you get this error from state estimation, it might mean the system is not fully observable, i.e. not enough measurements.\\nIt might also mean that you are running into a corner case where PGM cannot resolve yet.\\nSee https://github.com/PowerGridModel/power-grid-model/issues/864.']\n"
]
}
],
Expand All @@ -1406,7 +1464,7 @@
},
{
"cell_type": "code",
"execution_count": 34,
"execution_count": 35,
"id": "1ba71901",
"metadata": {},
"outputs": [
Expand All @@ -1415,16 +1473,16 @@
"output_type": "stream",
"text": [
"Node data with invalid results\n",
"[[ 9.99401170e-001 9.92685785e-001 9.94521366e-001]\n",
" [ 9.99347687e-001 9.86226389e-001 9.89352855e-001]\n",
" [ 9.99288384e-001 9.79654011e-001 9.84095542e-001]\n",
" [-2.66881060e+116 2.33997016e-302 6.70346672e-198]\n",
" [ 9.99151380e-001 9.66149483e-001 9.73298790e-001]\n",
" [ 9.99073166e-001 9.59205860e-001 9.67750710e-001]\n",
" [ 9.98988099e-001 9.52126208e-001 9.62096474e-001]\n",
" [-2.44756775e+092 5.35663612e-256 1.91838796e-203]\n",
" [ 9.98796126e-001 9.37530046e-001 9.50447962e-001]\n",
" [ 9.98688504e-001 9.29997471e-001 9.44441670e-001]]\n",
"[[9.99401170e-001 9.92685785e-001 9.94521366e-001]\n",
" [9.99347687e-001 9.86226389e-001 9.89352855e-001]\n",
" [9.99288384e-001 9.79654011e-001 9.84095542e-001]\n",
" [2.18565566e-312 4.89761332e-310 2.97079411e-313]\n",
" [9.99151380e-001 9.66149483e-001 9.73298790e-001]\n",
" [9.99073166e-001 9.59205860e-001 9.67750710e-001]\n",
" [9.98988099e-001 9.52126208e-001 9.62096474e-001]\n",
" [4.89761332e-310 4.89761332e-310 4.89761332e-310]\n",
" [9.98796126e-001 9.37530046e-001 9.50447962e-001]\n",
" [9.98688504e-001 9.29997471e-001 9.44441670e-001]]\n",
"Node data with only valid results\n",
"[[0.99940117 0.99268579 0.99452137]\n",
" [0.99934769 0.98622639 0.98935286]\n",
Expand Down Expand Up @@ -1467,7 +1525,7 @@
],
"metadata": {
"kernelspec": {
"display_name": "venv",
"display_name": "power-grid-model",
"language": "python",
"name": "python3"
},
Expand All @@ -1481,7 +1539,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.13.3"
"version": "3.14.2"
}
},
"nbformat": 4,
Expand Down
850 changes: 843 additions & 7 deletions docs/examples/Validation Examples.ipynb

Large diffs are not rendered by default.

22 changes: 22 additions & 0 deletions docs/user_manual/calculations.md
Original file line number Diff line number Diff line change
Expand Up @@ -1092,6 +1092,28 @@ for component_idx, scenario in enumerate(line_update):
independent_update_data = {'line': line_update}
```

### Cartesian product of Batch Datasets

Consider an example of running a contingency analysis with a timeseries data.
Or maybe probablistic data along with timeseries data.
In such simulations, it is required to perform a loadflow on a cartesian product of situations.
This is possible to do via providing the `update_data` with a list of multiple batch datasets.
ie. a list[{py:class}`BatchDataset <power_grid_model.data_types.BatchDataset>`]
The datasets can be of row based or columnar format.
The output of such calculation would be flattened with dimension $scenarios * components$.

#### Example: Cartesian product of datasets

```py
# 5 scenarios of timeseries
load_update = initialize_array('update', 'sym_load', (5, 1))
# (Fill load_update)
line_update = initialize_array('update', 'line', (3, 1))
# (Fill line_update)

product_update_data = [{'line': load_update}, {'sym_load': line_udpate }]
```

### Parallel Computing

The batch calculation supports shared memory multi-threading parallel computing.
Expand Down
18 changes: 8 additions & 10 deletions docs/user_manual/data-validator.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,6 @@ An alternative approach would be to validate only when an exception is raised, b
will raise exceptions, most of them wil just yield invalid results without warning.
The main validation functions and classes can be included from `power_grid_model.validation`.

Two helper type definitions are used throughout the validation functions, `InputData` and `UpdateData`.
They are not special types or classes, but merely type hinting aliases:

```python
InputData = dict[str, np.ndarray]
UpdateData = dict[str, np.ndarray | dict[str, np.ndarray]]
```

```{seealso}
Check the [example](examples/Validation%20Examples.ipynb) for an example of function applications.
```
Expand Down Expand Up @@ -49,11 +41,13 @@ class ValidationError:
### Manual validation

The validation functions below can be used to validate input/batch data manually.
The functions require `input_data: InputData`, which is power-grid-model input data, and `symmetric: bool`, stating if
The functions require `input_data` of {py:class}`SingleDataset <power_grid_model.data_types.SingleDataset>` type,
which is power-grid-model input data, and `symmetric: bool`, stating if
the data will be used for symmetric or asymmetric calculations.
`calculation_type: CalculationType` is optional and can be supplied to allow missing values for unused fields; see the
[API reference](../api_reference/python-api-reference.md#enum) for more information.
To validate update/batch data `update_data: UpdateData`, power-grid-model update data, should also be supplied.
To validate update/batch data `update_data` of {py:class}`BatchDataset <power_grid_model.data_types.BatchDataset>` type,
power-grid-model update data, should also be supplied.

- `validate_input_data(input_data, calculation_type, symmetric) -> list[ValidationError]` validates input_data.
- `validate_batch_data(input_data, update_data, calculation_type, symmetric) -> dict[int, list[ValidationError]]`
Expand All @@ -68,6 +62,10 @@ In such cases, the latter is leading when only running batch calculations.
Running single calculations on an incomplete input data set is, of course, unsupported.
```

Validating a cartesian product of datasets used in PGM's `update_data` via providing it with `list[BatchDataset]`is
not straightforward.
User should convert such multiple dataset into a single flat batch dataset.

### Assertions

Instead of manual validation it is possible to use the assertion functions below to assert valid data.
Expand Down
9 changes: 9 additions & 0 deletions docs/user_manual/performance-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,15 @@ If you are running on a system where memory is the bottle-neck, using a columnar
footprint.
This may or may not induce a slight computational overhead during calculations.

Some simulations might require a cartesian product of scenarios of two batch datasets.
This can be done by passing them to `update_data` as a list
ie. a list[{py:class}`BatchDataset <power_grid_model.data_types.BatchDataset>`]
(Check [Power Flow Example](../examples/Power%20Flow%20Example.ipynb)).
This gets treated as a cartesian product of the provided datasets and the combination of scenarios gets handled
internally.
Hence there is no need to allocate memory for full `N1 * N2 * ...` scenarios for a cartesian product of data sets with
scenario size `N1, N2, ...`.

### Output data volume

For most use cases, only certain output values are relevant.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,9 @@ template <dataset_type_tag dataset_type_> class Dataset {
}
constexpr bool is_dense(Idx const i) const { return is_dense(buffers_[i]); }
constexpr bool is_dense(Buffer const& buffer) const { return buffer.indptr.empty(); }
constexpr bool is_dense() const {
return std::ranges::all_of(buffers_, [this](Buffer const& buffer) { return is_dense(buffer); });
}
constexpr bool is_sparse(std::string_view component, bool with_attribute_buffers = false) const {
Idx const idx = find_component(component, false);
if (idx == invalid_index) {
Expand Down Expand Up @@ -510,10 +513,52 @@ template <dataset_type_tag dataset_type_> class Dataset {
return result;
}

// get slice dataset from batch
Dataset get_slice_scenario(Idx begin, Idx end) const
requires(!is_indptr_mutable_v<dataset_type>)
{
assert(begin <= end);
assert(0 <= begin);
assert(end <= batch_size());
assert(is_batch());
assert(is_dense());

// empty slice
if (begin == end) {
Dataset result{true, 0, dataset_info_.dataset->name, *meta_data_};
result.add_buffer("node", 0, 0, nullptr, nullptr);
return result;
}

// start with begin
Dataset result = get_individual_scenario(begin);
Idx const batch_size = end - begin;
result.dataset_info_.is_batch = true;
result.dataset_info_.batch_size = batch_size;
for (auto& component_info : result.dataset_info_.component_info) {
Idx const size = component_info.elements_per_scenario * batch_size;
component_info.total_elements = size;
}
return result;
}

void set_next_cartesian_product_dimension(Dataset const* next) {
Dataset const* current = next;
while (current != nullptr) {
if (this == current) {
throw DatasetError{"Cannot create cyclic cartesian product dimension linked list!\n"};
}
current = current->get_next_cartesian_product_dimension();
}
next_ = next;
}
Dataset const* get_next_cartesian_product_dimension() const { return next_; }

private:
MetaData const* meta_data_;
DatasetInfo dataset_info_;
std::vector<Buffer> buffers_;
Dataset const* next_{};

std::span<Indptr> get_indptr_span(Indptr* indptr) const {
return std::span{indptr, static_cast<size_t>(batch_size() + 1)};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,22 @@ PGM_API void PGM_dataset_const_add_attribute_buffer(PGM_Handle* handle, PGM_Cons
*/
PGM_API PGM_DatasetInfo const* PGM_dataset_const_get_info(PGM_Handle* handle, PGM_ConstDataset const* dataset);

/**
* @brief Set the next const dataset as cartesian product dimension.
*
* This function allows users to run a batch calculation with multiple dimensions of scenarios.
* The way users can archive this is to combine multiple batch datasets
* to create a multi-dimension batch calculation using a linked list pattern. The calculation core will
* interpret the combined dataset as a cartesian product on a linked list of all the scenarios.
* Each batch dataset in the linked list represents one dimension of the cartesian product.
*
* @param handle
* @param dataset
* @param next_dataset The next dataset in the linked list.
*/
PGM_API void PGM_dataset_const_set_next_cartesian_product_dimension(PGM_Handle* handle, PGM_ConstDataset* dataset,
PGM_ConstDataset const* next_dataset);

/**
* @brief Get the dataset info of the instance PGM_WritableDataset.
* @param handle
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,11 @@ PGM_API void PGM_get_indexer(PGM_Handle* handle, PGM_PowerGridModel const* model
* If batch_dataset == NULL, it is a one-time calculation.
* If batch_dataset != NULL, it is a batch calculation with batch update in the batch_dataset.
*
* The user can use the function set_next_cartesian_product_dimension() to combine multiple batch datasets
* to create a multi-dimension batch calculation using a linked list pattern. The calculation core will
* interpret the combined dataset as a cartesian product on a linked list of all the scenarios.
* Each batch dataset in the linked list represents one dimension of the cartesian product.
*
* You need to pre-allocate all output buffer.
*
* Use PGM_error_code() and PGM_error_message() to check the error.
Expand Down
7 changes: 7 additions & 0 deletions power_grid_model_c/power_grid_model_c/src/dataset.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,13 @@ void PGM_dataset_const_add_attribute_buffer(PGM_Handle* handle, PGM_ConstDataset
PGM_regular_error);
}

void PGM_dataset_const_set_next_cartesian_product_dimension(PGM_Handle* handle, PGM_ConstDataset* dataset,
PGM_ConstDataset const* next_dataset) {
call_with_catch(
handle, [dataset, next_dataset]() { dataset->set_next_cartesian_product_dimension(next_dataset); },
PGM_regular_error);
}

PGM_DatasetInfo const* PGM_dataset_const_get_info(PGM_Handle* /*unused*/, PGM_ConstDataset const* dataset) {
return &dataset->get_description();
}
Expand Down
Loading
Loading