From 3fb4339f70b54157baaae20efa9b4d120eb00ab6 Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Wed, 17 Dec 2025 12:24:17 +0100 Subject: [PATCH 01/82] try to see cli Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- power_grid_model_c/CMakeLists.txt | 1 + .../power_grid_model_cli/CMakeLists.txt | 27 +++++++++++++++++++ .../power_grid_model_cli/main.cpp | 18 +++++++++++++ 3 files changed, 46 insertions(+) create mode 100644 power_grid_model_c/power_grid_model_cli/CMakeLists.txt create mode 100644 power_grid_model_c/power_grid_model_cli/main.cpp diff --git a/power_grid_model_c/CMakeLists.txt b/power_grid_model_c/CMakeLists.txt index 698351326f..5559e72375 100644 --- a/power_grid_model_c/CMakeLists.txt +++ b/power_grid_model_c/CMakeLists.txt @@ -5,3 +5,4 @@ add_subdirectory("power_grid_model") add_subdirectory("power_grid_model_c") add_subdirectory("power_grid_model_cpp") +add_subdirectory("power_grid_model_cli") diff --git a/power_grid_model_c/power_grid_model_cli/CMakeLists.txt b/power_grid_model_c/power_grid_model_cli/CMakeLists.txt new file mode 100644 index 0000000000..4e7159df91 --- /dev/null +++ b/power_grid_model_c/power_grid_model_cli/CMakeLists.txt @@ -0,0 +1,27 @@ +# SPDX-FileCopyrightText: Contributors to the Power Grid Model project +# +# SPDX-License-Identifier: MPL-2.0 + +add_executable(power-grid-model main.cpp) + +target_link_libraries(power-grid-model PRIVATE power_grid_model_c) +target_link_libraries(power-grid-model PRIVATE power_grid_model_cpp) + +set_property(TARGET power-grid-model PROPERTY INSTALL_RPATH_USE_LINK_PATH FALSE) +if(APPLE) + set_property(TARGET power-grid-model PROPERTY INSTALL_RPATH "@loader_path/../${CMAKE_INSTALL_LIBDIR}") +elseif(UNIX) # Linux, BSD (not Windows/macOS) + set_property(TARGET power-grid-model PROPERTY INSTALL_RPATH "$ORIGIN/../${CMAKE_INSTALL_LIBDIR}") +endif() + + +target_compile_definitions( + power-grid-model + PRIVATE PGM_ENABLE_EXPERIMENTAL +) + +install( + TARGETS power-grid-model + EXPORT power_grid_modelTargets + COMPONENT power_grid_model +) diff --git a/power_grid_model_c/power_grid_model_cli/main.cpp b/power_grid_model_c/power_grid_model_cli/main.cpp new file mode 100644 index 0000000000..c057636550 --- /dev/null +++ b/power_grid_model_c/power_grid_model_cli/main.cpp @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: Contributors to the Power Grid Model project +// +// SPDX-License-Identifier: MPL-2.0 + +#include + +#include + +int main() { + power_grid_model_cpp::Handle handle; + if (handle.get() != nullptr) { + std::cout << "Successfully created a Power Grid Model handle." << std::endl; + return 0; + } else { + std::cout << "Failed to create a Power Grid Model handle." << std::endl; + return 1; + } +} From e6805a122945c37cf45d964014d9e128b9a59d33 Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Wed, 17 Dec 2025 14:21:11 +0100 Subject: [PATCH 02/82] add cli11 Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- CMakeLists.txt | 1 + power_grid_model_c/power_grid_model_cli/CMakeLists.txt | 1 + 2 files changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1bb14a5b44..8535425704 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,6 +38,7 @@ find_package(Boost REQUIRED) find_package(Eigen3 CONFIG REQUIRED) find_package(nlohmann_json CONFIG REQUIRED) find_package(msgpack-cxx REQUIRED) +find_package(CLI11 CONFIG REQUIRED) if(NOT WIN32) # thread diff --git a/power_grid_model_c/power_grid_model_cli/CMakeLists.txt b/power_grid_model_c/power_grid_model_cli/CMakeLists.txt index 4e7159df91..f7a36fb736 100644 --- a/power_grid_model_c/power_grid_model_cli/CMakeLists.txt +++ b/power_grid_model_c/power_grid_model_cli/CMakeLists.txt @@ -6,6 +6,7 @@ add_executable(power-grid-model main.cpp) target_link_libraries(power-grid-model PRIVATE power_grid_model_c) target_link_libraries(power-grid-model PRIVATE power_grid_model_cpp) +target_link_libraries(power-grid-model PRIVATE CLI11::CLI11) set_property(TARGET power-grid-model PROPERTY INSTALL_RPATH_USE_LINK_PATH FALSE) if(APPLE) From ca03120a6417b97b96d9b149a9f49c7a7cb9da0d Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Wed, 17 Dec 2025 15:00:10 +0100 Subject: [PATCH 03/82] try to define options Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- .../power_grid_model_cli/CMakeLists.txt | 5 ++- .../power_grid_model_cli/cli_options.cpp | 37 +++++++++++++++++ .../power_grid_model_cli/cli_options.hpp | 40 +++++++++++++++++++ .../power_grid_model_cli/main.cpp | 20 ++++++---- 4 files changed, 93 insertions(+), 9 deletions(-) create mode 100644 power_grid_model_c/power_grid_model_cli/cli_options.cpp create mode 100644 power_grid_model_c/power_grid_model_cli/cli_options.hpp diff --git a/power_grid_model_c/power_grid_model_cli/CMakeLists.txt b/power_grid_model_c/power_grid_model_cli/CMakeLists.txt index f7a36fb736..568e261557 100644 --- a/power_grid_model_c/power_grid_model_cli/CMakeLists.txt +++ b/power_grid_model_c/power_grid_model_cli/CMakeLists.txt @@ -2,7 +2,10 @@ # # SPDX-License-Identifier: MPL-2.0 -add_executable(power-grid-model main.cpp) +add_executable(power-grid-model + main.cpp + cli_options.cpp +) target_link_libraries(power-grid-model PRIVATE power_grid_model_c) target_link_libraries(power-grid-model PRIVATE power_grid_model_cpp) diff --git a/power_grid_model_c/power_grid_model_cli/cli_options.cpp b/power_grid_model_c/power_grid_model_cli/cli_options.cpp new file mode 100644 index 0000000000..ffccb69084 --- /dev/null +++ b/power_grid_model_c/power_grid_model_cli/cli_options.cpp @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: Contributors to the Power Grid Model project +// +// SPDX-License-Identifier: MPL-2.0 + +#include "cli_options.hpp" + +namespace power_grid_model_cpp { + +int parse_cli_options(int argc, char** argv, ClIOptions& options) { + (void)argc; + (void)argv; + (void)options; + return 0; +} + +std::ostream& operator<<(std::ostream& os, ClIOptions const& options) { + os << "CLI Options:\n"; + os << "Input file: " << options.input_file << "\n"; + os << "Batch update file: " << options.batch_update_file << "\n"; + os << "Output file: " << options.output_file << "\n"; + + os << "Calculation type: " << options.calculation_type << "\n"; + os << "Calculation method: " << options.calculation_method << "\n"; + os << "Symmetric calculation: " << options.symmetric_calculation << "\n"; + os << "Error tolerance: " << options.error_tolerance << "\n"; + os << "Max iterations: " << options.max_iterations << "\n"; + os << "Threading: " << options.threading << "\n"; + os << "Short circuit voltage scaling: " << options.short_circuit_voltage_scaling << "\n"; + os << "Tap changing strategy: " << options.tap_changing_strategy << "\n"; + + os << "Use msgpack output serialization: " << options.use_msgpack_output_serialization << "\n"; + os << "Output JSON indent: " << options.output_json_indent << "\n"; + os << "Use compact serialization: " << options.use_compact_serialization << "\n"; + return os; +} + +} // namespace power_grid_model_cpp diff --git a/power_grid_model_c/power_grid_model_cli/cli_options.hpp b/power_grid_model_c/power_grid_model_cli/cli_options.hpp new file mode 100644 index 0000000000..8aa80d9c52 --- /dev/null +++ b/power_grid_model_c/power_grid_model_cli/cli_options.hpp @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: Contributors to the Power Grid Model project +// +// SPDX-License-Identifier: MPL-2.0 + +#include + +#include +#include +#include +#include +#include + +namespace power_grid_model_cpp { + +struct ClIOptions { + std::filesystem::path input_file; + std::filesystem::path batch_update_file; + std::filesystem::path output_file; + + Idx calculation_type{PGM_power_flow}; + Idx calculation_method{PGM_default_method}; + Idx symmetric_calculation{PGM_symmetric}; + double error_tolerance{1e-8}; + Idx max_iterations{20}; + Idx threading{-1}; + Idx short_circuit_voltage_scaling{PGM_short_circuit_voltage_scaling_maximum}; + Idx tap_changing_strategy{PGM_tap_changing_strategy_disabled}; + + bool use_msgpack_output_serialization{false}; + Idx output_json_indent{2}; + bool use_compact_serialization{false}; + + std::map> output_component_attribute_filters; + + friend std::ostream& operator<<(std::ostream& os, ClIOptions const& options); +}; + +int parse_cli_options(int argc, char** argv, ClIOptions& options); + +} // namespace power_grid_model_cpp diff --git a/power_grid_model_c/power_grid_model_cli/main.cpp b/power_grid_model_c/power_grid_model_cli/main.cpp index c057636550..084c72af5e 100644 --- a/power_grid_model_c/power_grid_model_cli/main.cpp +++ b/power_grid_model_c/power_grid_model_cli/main.cpp @@ -2,17 +2,21 @@ // // SPDX-License-Identifier: MPL-2.0 +#include "cli_options.hpp" + #include #include -int main() { - power_grid_model_cpp::Handle handle; - if (handle.get() != nullptr) { - std::cout << "Successfully created a Power Grid Model handle." << std::endl; - return 0; - } else { - std::cout << "Failed to create a Power Grid Model handle." << std::endl; - return 1; +using namespace power_grid_model_cpp; + +int main(int argc, char** argv) { + ClIOptions cli_options; + if (int parse_result = parse_cli_options(argc, argv, cli_options); parse_result != 0) { + return parse_result; } + + std::cout << cli_options << std::endl; + + return 0; } From 8c85d8143693da797a6895ca48d007c4b1374392 Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Wed, 17 Dec 2025 15:29:10 +0100 Subject: [PATCH 04/82] path does not work Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- .../power_grid_model_cli/cli_options.cpp | 21 ++++++++++++++----- .../power_grid_model_cli/cli_options.hpp | 13 +++++++++--- .../power_grid_model_cli/main.cpp | 4 ++-- 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/power_grid_model_c/power_grid_model_cli/cli_options.cpp b/power_grid_model_c/power_grid_model_cli/cli_options.cpp index ffccb69084..61c6547da5 100644 --- a/power_grid_model_c/power_grid_model_cli/cli_options.cpp +++ b/power_grid_model_c/power_grid_model_cli/cli_options.cpp @@ -4,13 +4,24 @@ #include "cli_options.hpp" +#include + namespace power_grid_model_cpp { -int parse_cli_options(int argc, char** argv, ClIOptions& options) { - (void)argc; - (void)argv; - (void)options; - return 0; +CLIResult parse_cli_options(int argc, char** argv, ClIOptions& options) { + CLI::App app{"Power Grid Model CLI"}; + + app.add_option("-i,--input", options.input_file, "Input file path")->required()->check(CLI::ExistingFile); + app.add_option("-b,--batch-update", options.batch_update_file, "Batch update file path")->check(CLI::ExistingFile); + app.add_option("-o,--output", options.output_file, "Output file path")->required()->check(CLI::ExistingDirectory); + + try { + app.parse(argc, argv); + } catch (const CLI::ParseError& e) { + return {.exit_code = app.exit(e), .should_exit = true}; + } + + return {.exit_code = 0, .should_exit = false}; } std::ostream& operator<<(std::ostream& os, ClIOptions const& options) { diff --git a/power_grid_model_c/power_grid_model_cli/cli_options.hpp b/power_grid_model_c/power_grid_model_cli/cli_options.hpp index 8aa80d9c52..f898a9469d 100644 --- a/power_grid_model_c/power_grid_model_cli/cli_options.hpp +++ b/power_grid_model_c/power_grid_model_cli/cli_options.hpp @@ -6,12 +6,19 @@ #include #include +#include #include #include -#include namespace power_grid_model_cpp { +struct CLIResult { + int exit_code; + bool should_exit; + + operator bool() const { return should_exit || exit_code != 0; } +}; + struct ClIOptions { std::filesystem::path input_file; std::filesystem::path batch_update_file; @@ -25,7 +32,7 @@ struct ClIOptions { Idx threading{-1}; Idx short_circuit_voltage_scaling{PGM_short_circuit_voltage_scaling_maximum}; Idx tap_changing_strategy{PGM_tap_changing_strategy_disabled}; - + bool use_msgpack_output_serialization{false}; Idx output_json_indent{2}; bool use_compact_serialization{false}; @@ -35,6 +42,6 @@ struct ClIOptions { friend std::ostream& operator<<(std::ostream& os, ClIOptions const& options); }; -int parse_cli_options(int argc, char** argv, ClIOptions& options); +CLIResult parse_cli_options(int argc, char** argv, ClIOptions& options); } // namespace power_grid_model_cpp diff --git a/power_grid_model_c/power_grid_model_cli/main.cpp b/power_grid_model_c/power_grid_model_cli/main.cpp index 084c72af5e..91ada34484 100644 --- a/power_grid_model_c/power_grid_model_cli/main.cpp +++ b/power_grid_model_c/power_grid_model_cli/main.cpp @@ -12,8 +12,8 @@ using namespace power_grid_model_cpp; int main(int argc, char** argv) { ClIOptions cli_options; - if (int parse_result = parse_cli_options(argc, argv, cli_options); parse_result != 0) { - return parse_result; + if (auto parse_result = parse_cli_options(argc, argv, cli_options); parse_result) { + return parse_result.exit_code; } std::cout << cli_options << std::endl; From caefe3f332b1a865d871e00061eff1e15f0afbca Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Wed, 17 Dec 2025 16:16:10 +0100 Subject: [PATCH 05/82] add custom validator Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- .../power_grid_model_cli/cli_options.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/power_grid_model_c/power_grid_model_cli/cli_options.cpp b/power_grid_model_c/power_grid_model_cli/cli_options.cpp index 61c6547da5..118b5f3b9e 100644 --- a/power_grid_model_c/power_grid_model_cli/cli_options.cpp +++ b/power_grid_model_c/power_grid_model_cli/cli_options.cpp @@ -11,10 +11,22 @@ namespace power_grid_model_cpp { CLIResult parse_cli_options(int argc, char** argv, ClIOptions& options) { CLI::App app{"Power Grid Model CLI"}; + CLI::Validator existing_parent_dir_validator{ + [](std::string& input) { + std::filesystem::path p{input}; + auto parent = p.has_parent_path() ? p.parent_path() : std::filesystem::path{"."}; + if (parent.empty() || !std::filesystem::exists(parent) || !std::filesystem::is_directory(parent)) { + return std::string("The parent directory of the specified path does not exist or is not a directory."); + } + return std::string{}; + }, + "ExistingParentDirectory"}; + app.add_option("-i,--input", options.input_file, "Input file path")->required()->check(CLI::ExistingFile); app.add_option("-b,--batch-update", options.batch_update_file, "Batch update file path")->check(CLI::ExistingFile); - app.add_option("-o,--output", options.output_file, "Output file path")->required()->check(CLI::ExistingDirectory); - + app.add_option("-o,--output", options.output_file, "Output file path") + ->required() + ->check(existing_parent_dir_validator); try { app.parse(argc, argv); } catch (const CLI::ParseError& e) { From 76d6da26ae0776125b7a3437f16ed1b78c12f570 Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Wed, 17 Dec 2025 16:30:56 +0100 Subject: [PATCH 06/82] add type and method Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- .../power_grid_model_cli/cli_options.cpp | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/power_grid_model_c/power_grid_model_cli/cli_options.cpp b/power_grid_model_c/power_grid_model_cli/cli_options.cpp index 118b5f3b9e..e8933f0696 100644 --- a/power_grid_model_c/power_grid_model_cli/cli_options.cpp +++ b/power_grid_model_c/power_grid_model_cli/cli_options.cpp @@ -6,8 +6,12 @@ #include +#include + namespace power_grid_model_cpp { +using EnumMap = std::map; + CLIResult parse_cli_options(int argc, char** argv, ClIOptions& options) { CLI::App app{"Power Grid Model CLI"}; @@ -27,6 +31,27 @@ CLIResult parse_cli_options(int argc, char** argv, ClIOptions& options) { app.add_option("-o,--output", options.output_file, "Output file path") ->required() ->check(existing_parent_dir_validator); + app.add_option("-c,--calculation-type", options.calculation_type, "Calculation type") + ->transform(CLI::CheckedTransformer( + EnumMap{ + {"power_flow", PGM_power_flow}, + {"short_circuit", PGM_short_circuit}, + {"state_estimation", PGM_state_estimation}, + }, + CLI::ignore_case)); + app.add_option("-m,--calculation-method", options.calculation_method, "Calculation method") + ->transform(CLI::CheckedTransformer( + EnumMap{ + {"default", PGM_default_method}, + {"newton_raphson", PGM_newton_raphson}, + {"iterative_linear", PGM_iterative_linear}, + {"iterative_current", PGM_iterative_current}, + {"linear_current", PGM_linear_current}, + {"iec60909", PGM_iec60909}, + }, + CLI::ignore_case)); + + try { app.parse(argc, argv); } catch (const CLI::ParseError& e) { From 25cb9f7326e053e225451b154f19014c6da19578 Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Wed, 17 Dec 2025 16:42:01 +0100 Subject: [PATCH 07/82] almost all options Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- .../power_grid_model_cli/cli_options.cpp | 33 +++++++++++++++++-- .../power_grid_model_cli/cli_options.hpp | 2 +- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/power_grid_model_c/power_grid_model_cli/cli_options.cpp b/power_grid_model_c/power_grid_model_cli/cli_options.cpp index e8933f0696..5bee33757f 100644 --- a/power_grid_model_c/power_grid_model_cli/cli_options.cpp +++ b/power_grid_model_c/power_grid_model_cli/cli_options.cpp @@ -50,8 +50,37 @@ CLIResult parse_cli_options(int argc, char** argv, ClIOptions& options) { {"iec60909", PGM_iec60909}, }, CLI::ignore_case)); - - + app.add_flag("-s,--symmetric-calculation,!-a,!--asymmetric-calculation", options.symmetric_calculation, + "Use symmetric calculation (1) or not (0)"); + app.add_option("-e,--error-tolerance", options.error_tolerance, "Error tolerance for iterative calculations"); + app.add_option("-x,--max-iterations", options.max_iterations, + "Maximum number of iterations for iterative calculations"); + app.add_option("-t,--threading", options.threading, "Number of threads to use (-1 for automatic selection)"); + app.add_option("--short-circuit-voltage-scaling", options.short_circuit_voltage_scaling, + "Short circuit voltage scaling") + ->transform(CLI::CheckedTransformer( + EnumMap{ + {"minimum", PGM_short_circuit_voltage_scaling_minimum}, + {"maximum", PGM_short_circuit_voltage_scaling_maximum}, + }, + CLI::ignore_case)); + app.add_option("--tap-changing-strategy", options.tap_changing_strategy, "Tap changing strategy") + ->transform(CLI::CheckedTransformer( + EnumMap{ + {"disabled", PGM_tap_changing_strategy_disabled}, + {"any", PGM_tap_changing_strategy_any_valid_tap}, + {"min_voltage", PGM_tap_changing_strategy_min_voltage_tap}, + {"max_voltage", PGM_tap_changing_strategy_max_voltage_tap}, + {"fast_any", PGM_tap_changing_strategy_fast_any_tap}, + }, + CLI::ignore_case)); + app.add_flag("--msgpack,--use-msgpack-output-serialization", options.use_msgpack_output_serialization, + "Use MessagePack output serialization"); + app.add_option("--indent,--output-json-indent", options.output_json_indent, + "Number of spaces to indent JSON output"); + app.add_flag("--compact,--use-compact-serialization", options.use_compact_serialization, + "Use compact serialization (no extra whitespace)"); + try { app.parse(argc, argv); } catch (const CLI::ParseError& e) { diff --git a/power_grid_model_c/power_grid_model_cli/cli_options.hpp b/power_grid_model_c/power_grid_model_cli/cli_options.hpp index f898a9469d..a9868e7286 100644 --- a/power_grid_model_c/power_grid_model_cli/cli_options.hpp +++ b/power_grid_model_c/power_grid_model_cli/cli_options.hpp @@ -26,7 +26,7 @@ struct ClIOptions { Idx calculation_type{PGM_power_flow}; Idx calculation_method{PGM_default_method}; - Idx symmetric_calculation{PGM_symmetric}; + bool symmetric_calculation{PGM_symmetric}; double error_tolerance{1e-8}; Idx max_iterations{20}; Idx threading{-1}; From be83d85720d8937317e19142bbdc3f8ab64107f5 Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Wed, 17 Dec 2025 16:54:43 +0100 Subject: [PATCH 08/82] callback to set default Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- .../power_grid_model_cli/cli_options.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/power_grid_model_c/power_grid_model_cli/cli_options.cpp b/power_grid_model_c/power_grid_model_cli/cli_options.cpp index 5bee33757f..93579fbaff 100644 --- a/power_grid_model_c/power_grid_model_cli/cli_options.cpp +++ b/power_grid_model_c/power_grid_model_cli/cli_options.cpp @@ -78,8 +78,15 @@ CLIResult parse_cli_options(int argc, char** argv, ClIOptions& options) { "Use MessagePack output serialization"); app.add_option("--indent,--output-json-indent", options.output_json_indent, "Number of spaces to indent JSON output"); - app.add_flag("--compact,--use-compact-serialization", options.use_compact_serialization, - "Use compact serialization (no extra whitespace)"); + auto compact_flag = + app.add_flag("--compact,--use-compact-serialization,!--no-compact,!--no-compact-serialization", + options.use_compact_serialization, "Use compact serialization (no extra whitespace)"); + + app.callback([&options, compact_flag]() { + if (compact_flag->count() == 0 && options.use_msgpack_output_serialization) { + options.use_compact_serialization = true; + } + }); try { app.parse(argc, argv); From 68cae248741be7f421d7e4df2bfd8bd0da597343 Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Wed, 17 Dec 2025 17:06:09 +0100 Subject: [PATCH 09/82] add callback Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- power_grid_model_c/power_grid_model_cli/cli_options.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/power_grid_model_c/power_grid_model_cli/cli_options.cpp b/power_grid_model_c/power_grid_model_cli/cli_options.cpp index 93579fbaff..29e20e4b99 100644 --- a/power_grid_model_c/power_grid_model_cli/cli_options.cpp +++ b/power_grid_model_c/power_grid_model_cli/cli_options.cpp @@ -81,6 +81,13 @@ CLIResult parse_cli_options(int argc, char** argv, ClIOptions& options) { auto compact_flag = app.add_flag("--compact,--use-compact-serialization,!--no-compact,!--no-compact-serialization", options.use_compact_serialization, "Use compact serialization (no extra whitespace)"); + std::vector output_components; + app.add_option("--oc,--output-component", output_components, + "Filter output to only include specified components (can be specified multiple times)"); + std::vector output_attributes; + app.add_option("--oa,--output-attribute", output_attributes, + "Filter output to only include specified attributes, in the format `component.attribute` (can be " + "specified multiple times)"); app.callback([&options, compact_flag]() { if (compact_flag->count() == 0 && options.use_msgpack_output_serialization) { From 42e237ded3ce46927d72711d699e6e9cfde527d0 Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Wed, 17 Dec 2025 21:40:25 +0100 Subject: [PATCH 10/82] add seperate callback Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- .../power_grid_model_cli/cli_options.cpp | 29 ++++++++++++++----- .../power_grid_model_cli/cli_options.hpp | 2 ++ 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/power_grid_model_c/power_grid_model_cli/cli_options.cpp b/power_grid_model_c/power_grid_model_cli/cli_options.cpp index 29e20e4b99..14dd567620 100644 --- a/power_grid_model_c/power_grid_model_cli/cli_options.cpp +++ b/power_grid_model_c/power_grid_model_cli/cli_options.cpp @@ -12,6 +12,24 @@ namespace power_grid_model_cpp { using EnumMap = std::map; +struct CLIPostCallback { + ClIOptions& options; + CLI::Option* msgpack_flag; + CLI::Option* compact_flag; + + void operator()() { + // default msgpack output if input or batch update is msgpack and user did not specify output format + if (msgpack_flag->count() == 0 && + (options.input_msgpack_serialization || options.batch_update_msgpack_serialization)) { + options.use_msgpack_output_serialization = true; + } + // default compact serialization if msgpack output and user did not specify compact option + if (compact_flag->count() == 0 && options.use_msgpack_output_serialization) { + options.use_compact_serialization = true; + } + } +}; + CLIResult parse_cli_options(int argc, char** argv, ClIOptions& options) { CLI::App app{"Power Grid Model CLI"}; @@ -74,8 +92,9 @@ CLIResult parse_cli_options(int argc, char** argv, ClIOptions& options) { {"fast_any", PGM_tap_changing_strategy_fast_any_tap}, }, CLI::ignore_case)); - app.add_flag("--msgpack,--use-msgpack-output-serialization", options.use_msgpack_output_serialization, - "Use MessagePack output serialization"); + auto msgpack_flag = + app.add_flag("--msgpack,--use-msgpack-output-serialization,!--json,!--use-json-output-serialization", + options.use_msgpack_output_serialization, "Use MessagePack output serialization"); app.add_option("--indent,--output-json-indent", options.output_json_indent, "Number of spaces to indent JSON output"); auto compact_flag = @@ -89,11 +108,7 @@ CLIResult parse_cli_options(int argc, char** argv, ClIOptions& options) { "Filter output to only include specified attributes, in the format `component.attribute` (can be " "specified multiple times)"); - app.callback([&options, compact_flag]() { - if (compact_flag->count() == 0 && options.use_msgpack_output_serialization) { - options.use_compact_serialization = true; - } - }); + app.callback(CLIPostCallback{.options = options, .msgpack_flag = msgpack_flag, .compact_flag = compact_flag}); try { app.parse(argc, argv); diff --git a/power_grid_model_c/power_grid_model_cli/cli_options.hpp b/power_grid_model_c/power_grid_model_cli/cli_options.hpp index a9868e7286..e9829e72ac 100644 --- a/power_grid_model_c/power_grid_model_cli/cli_options.hpp +++ b/power_grid_model_c/power_grid_model_cli/cli_options.hpp @@ -23,6 +23,8 @@ struct ClIOptions { std::filesystem::path input_file; std::filesystem::path batch_update_file; std::filesystem::path output_file; + bool input_msgpack_serialization{false}; + bool batch_update_msgpack_serialization{false}; Idx calculation_type{PGM_power_flow}; Idx calculation_method{PGM_default_method}; From ef61137f48634457a876b05b0571474f72f080e3 Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Wed, 17 Dec 2025 21:53:32 +0100 Subject: [PATCH 11/82] add msgpack checker Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- .../power_grid_model_cli/cli_options.cpp | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/power_grid_model_c/power_grid_model_cli/cli_options.cpp b/power_grid_model_c/power_grid_model_cli/cli_options.cpp index 14dd567620..2c1d46c584 100644 --- a/power_grid_model_c/power_grid_model_cli/cli_options.cpp +++ b/power_grid_model_c/power_grid_model_cli/cli_options.cpp @@ -6,6 +6,7 @@ #include +#include #include namespace power_grid_model_cpp { @@ -18,6 +19,11 @@ struct CLIPostCallback { CLI::Option* compact_flag; void operator()() { + // detect if input file is msgpack + options.input_msgpack_serialization = is_msgpack_file(options.input_file); + // detect if batch update file is msgpack + options.batch_update_msgpack_serialization = + !options.batch_update_file.empty() && is_msgpack_file(options.batch_update_file); // default msgpack output if input or batch update is msgpack and user did not specify output format if (msgpack_flag->count() == 0 && (options.input_msgpack_serialization || options.batch_update_msgpack_serialization)) { @@ -28,6 +34,20 @@ struct CLIPostCallback { options.use_compact_serialization = true; } } + + static bool is_msgpack_file(std::filesystem::path const& path) { + std::ifstream file{path, std::ios::binary}; + if (!file.is_open()) { + return false; + } + uint8_t header; + file.read(reinterpret_cast(&header), 1); + if (!file) { + return false; + } + // Check for fixmap (0x80-0x8f), map16 (0xde), or map32 (0xdf) + return (header >= 0x80 && header <= 0x8f) || header == 0xde || header == 0xdf; + } }; CLIResult parse_cli_options(int argc, char** argv, ClIOptions& options) { From 6d511a9428a23351760da0ed96dc316d412869a6 Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Wed, 17 Dec 2025 22:26:31 +0100 Subject: [PATCH 12/82] add parser Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- .../power_grid_model_cli/cli_options.cpp | 89 ++++++++++++++++--- .../power_grid_model_cli/cli_options.hpp | 4 +- 2 files changed, 81 insertions(+), 12 deletions(-) diff --git a/power_grid_model_c/power_grid_model_cli/cli_options.cpp b/power_grid_model_c/power_grid_model_cli/cli_options.cpp index 2c1d46c584..e861da3be0 100644 --- a/power_grid_model_c/power_grid_model_cli/cli_options.cpp +++ b/power_grid_model_c/power_grid_model_cli/cli_options.cpp @@ -17,8 +17,31 @@ struct CLIPostCallback { ClIOptions& options; CLI::Option* msgpack_flag; CLI::Option* compact_flag; + std::vector const& output_components; + std::vector const& output_attributes; void operator()() { + set_default_values(); + set_output_dataset(); + add_component_output_filter(); + add_attribute_output_filter(); + } + + static bool is_msgpack_file(std::filesystem::path const& path) { + std::ifstream file{path, std::ios::binary}; + if (!file.is_open()) { + return false; + } + uint8_t header; + file.read(reinterpret_cast(&header), 1); + if (!file) { + return false; + } + // Check for fixmap (0x80-0x8f), map16 (0xde), or map32 (0xdf) + return (header >= 0x80 && header <= 0x8f) || header == 0xde || header == 0xdf; + } + + void set_default_values() { // detect if input file is msgpack options.input_msgpack_serialization = is_msgpack_file(options.input_file); // detect if batch update file is msgpack @@ -35,18 +58,58 @@ struct CLIPostCallback { } } - static bool is_msgpack_file(std::filesystem::path const& path) { - std::ifstream file{path, std::ios::binary}; - if (!file.is_open()) { - return false; + void set_output_dataset() { + if (options.calculation_type == PGM_power_flow || options.calculation_type == PGM_state_estimation) { + if (options.symmetric_calculation) { + options.output_dataset_name = "sym_output"; + } else { + options.output_dataset_name = "asym_output"; + } + } else { + // options.calculation_type == PGM_short_circuit + options.output_dataset_name = "sc_output"; } - uint8_t header; - file.read(reinterpret_cast(&header), 1); - if (!file) { - return false; + options.output_dataset = MetaData::get_dataset_by_name(options.output_dataset_name); + } + + void add_component_output_filter() { + for (auto const& comp_name : output_components) { + try { + auto const component = MetaData::get_component_by_name(options.output_dataset_name, comp_name); + options.output_component_attribute_filters[component] = {}; + } catch (PowerGridError const&) { + throw CLI::ValidationError("output-component", "Component '" + comp_name + "' not found in dataset '" + + options.output_dataset_name + "'."); + } + } + } + + void add_attribute_output_filter() { + for (auto const& attr_full_name : output_attributes) { + auto dot_pos = attr_full_name.find('.'); + if (dot_pos == std::string::npos || dot_pos == 0 || dot_pos == attr_full_name.size() - 1) { + throw CLI::ValidationError("output-attribute", "Attribute '" + attr_full_name + + "' is not in the format 'component.attribute'."); + } + auto comp_name = attr_full_name.substr(0, dot_pos); + auto attr_name = attr_full_name.substr(dot_pos + 1); + MetaComponent const* component = nullptr; + try { + component = MetaData::get_component_by_name(options.output_dataset_name, comp_name); + } catch (PowerGridError const&) { + throw CLI::ValidationError("output-attribute", "Component '" + comp_name + "' not found in dataset '" + + options.output_dataset_name + "'."); + } + MetaAttribute const* attribute = nullptr; + try { + attribute = MetaData::get_attribute_by_name(options.output_dataset_name, comp_name, attr_name); + } catch (PowerGridError const&) { + throw CLI::ValidationError("output-attribute", + "Attribute '" + attr_name + "' not found in component '" + comp_name + + "' of dataset '" + options.output_dataset_name + "'."); + } + options.output_component_attribute_filters[component].insert(attribute); } - // Check for fixmap (0x80-0x8f), map16 (0xde), or map32 (0xdf) - return (header >= 0x80 && header <= 0x8f) || header == 0xde || header == 0xdf; } }; @@ -128,7 +191,11 @@ CLIResult parse_cli_options(int argc, char** argv, ClIOptions& options) { "Filter output to only include specified attributes, in the format `component.attribute` (can be " "specified multiple times)"); - app.callback(CLIPostCallback{.options = options, .msgpack_flag = msgpack_flag, .compact_flag = compact_flag}); + app.callback(CLIPostCallback{.options = options, + .msgpack_flag = msgpack_flag, + .compact_flag = compact_flag, + .output_components = output_components, + .output_attributes = output_attributes}); try { app.parse(argc, argv); diff --git a/power_grid_model_c/power_grid_model_cli/cli_options.hpp b/power_grid_model_c/power_grid_model_cli/cli_options.hpp index e9829e72ac..fd06c018b0 100644 --- a/power_grid_model_c/power_grid_model_cli/cli_options.hpp +++ b/power_grid_model_c/power_grid_model_cli/cli_options.hpp @@ -39,7 +39,9 @@ struct ClIOptions { Idx output_json_indent{2}; bool use_compact_serialization{false}; - std::map> output_component_attribute_filters; + std::string output_dataset_name; + MetaDataset const* output_dataset{nullptr}; + std::map> output_component_attribute_filters; friend std::ostream& operator<<(std::ostream& os, ClIOptions const& options); }; From 7551d28daa4b1bbf76b2d7a971cf5cb234097e91 Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Thu, 18 Dec 2025 13:29:50 +0100 Subject: [PATCH 13/82] try attribute buffers Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- .../include/power_grid_model_cpp/dataset.hpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/dataset.hpp b/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/dataset.hpp index 3adb5cf44b..22aab01ff6 100644 --- a/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/dataset.hpp +++ b/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/dataset.hpp @@ -206,9 +206,24 @@ class DatasetConst { DatasetInfo info_; }; +using AttributeBufferPtr = std::unique_ptr>; +template struct AttributeBufferCreator { + static void delete_ptr(RawDataConstPtr ptr) { delete[] reinterpret_cast(ptr); } + + AttributeBufferPtr operator()(Idx size, bool fill_in_nan = false) const { + auto ptr = AttributeBufferPtr{new T[size], &delete_ptr}; + auto raw_ptr = reinterpret_cast(ptr.get()); + if (fill_in_nan) { + std::fill_n(raw_ptr, size, nan_value()); + } + return ptr; + } +}; + struct OwningMemory { std::vector buffers; std::vector> indptrs; + std::vector> attribute_buffers; }; struct OwningDataset { From feab93d4834590cdadec7ba0627574661e1b426c Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Thu, 18 Dec 2025 13:41:32 +0100 Subject: [PATCH 14/82] add attribute indications, not complete Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- .../power_grid_model_cpp/serialization.hpp | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/serialization.hpp b/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/serialization.hpp index 9f1f7a9592..af11059c47 100644 --- a/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/serialization.hpp +++ b/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/serialization.hpp @@ -97,8 +97,10 @@ inline OwningDataset create_owning_dataset(DatasetWritable& writable_dataset) { bool const is_batch = info.is_batch(); Idx const batch_size = info.batch_size(); auto const& dataset_name = info.name(); - DatasetMutable dataset_mutable{dataset_name, is_batch, batch_size}; - OwningMemory storage{}; + OwningDataset owning_dataset{.dataset = DatasetMutable{dataset_name, is_batch, batch_size}, + .storage = OwningMemory{}}; + DatasetMutable& dataset_mutable = owning_dataset.dataset; + OwningMemory& storage = owning_dataset.storage; for (Idx component_idx{}; component_idx < info.n_components(); ++component_idx) { auto const& component_name = info.component_name(component_idx); @@ -112,12 +114,17 @@ inline OwningDataset create_owning_dataset(DatasetWritable& writable_dataset) { current_indptr.at(batch_size) = component_size; } Idx* const indptr = current_indptr.empty() ? nullptr : current_indptr.data(); - auto& current_buffer = storage.buffers.emplace_back(component_meta, component_size); - writable_dataset.set_buffer(component_name, indptr, current_buffer); - dataset_mutable.add_buffer(component_name, elements_per_scenario, component_size, indptr, current_buffer); + if (info.has_attribute_indications(component_idx)) { + auto& current_buffer = storage.buffers.emplace_back(component_meta, 0); + writable_dataset.set_buffer(component_name, indptr, current_buffer); + dataset_mutable.add_buffer(component_name, elements_per_scenario, component_size, indptr, current_buffer); + } else { + auto& current_buffer = storage.buffers.emplace_back(component_meta, component_size); + writable_dataset.set_buffer(component_name, indptr, current_buffer); + dataset_mutable.add_buffer(component_name, elements_per_scenario, component_size, indptr, current_buffer); + } } - return OwningDataset{// NOLINT(modernize-use-designated-initializers) - std::move(dataset_mutable), std::move(storage)}; + return owning_dataset; } } // namespace power_grid_model_cpp From f51afe2fa7f9ca502297a0744d1190a997af6442 Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Thu, 18 Dec 2025 13:45:58 +0100 Subject: [PATCH 15/82] reproduce error Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- .../include/power_grid_model_cpp/buffer.hpp | 2 ++ .../power_grid_model_cpp/serialization.hpp | 2 +- tests/data/power_flow/1os2msr/input.json | 31 +++++++++++-------- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/buffer.hpp b/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/buffer.hpp index 47bcc8156f..b5eaa57fe2 100644 --- a/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/buffer.hpp +++ b/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/buffer.hpp @@ -17,6 +17,8 @@ class Buffer { Buffer(MetaComponent const* component, Idx size) : component_{component}, size_{size}, buffer_{handle_.call_with(PGM_create_buffer, component, size)} {}; + Buffer() : component_{nullptr}, size_{0}, buffer_{nullptr} {}; + RawDataConstPtr get() const { return buffer_.get(); } RawDataPtr get() { return buffer_.get(); } diff --git a/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/serialization.hpp b/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/serialization.hpp index af11059c47..570471f866 100644 --- a/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/serialization.hpp +++ b/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/serialization.hpp @@ -115,7 +115,7 @@ inline OwningDataset create_owning_dataset(DatasetWritable& writable_dataset) { } Idx* const indptr = current_indptr.empty() ? nullptr : current_indptr.data(); if (info.has_attribute_indications(component_idx)) { - auto& current_buffer = storage.buffers.emplace_back(component_meta, 0); + auto& current_buffer = storage.buffers.emplace_back(); writable_dataset.set_buffer(component_name, indptr, current_buffer); dataset_mutable.add_buffer(component_name, elements_per_scenario, component_size, indptr, current_buffer); } else { diff --git a/tests/data/power_flow/1os2msr/input.json b/tests/data/power_flow/1os2msr/input.json index 71246d01b0..20af820012 100644 --- a/tests/data/power_flow/1os2msr/input.json +++ b/tests/data/power_flow/1os2msr/input.json @@ -2,21 +2,26 @@ "version": "1.0", "type": "input", "is_batch": false, - "attributes": {}, + "attributes": { + "node": [ + "id", + "u_rated" + ] + }, "data": { "node": [ - { - "id": 1, - "u_rated": 10.5e3 - }, - { - "id": 2, - "u_rated": 10.5e3 - }, - { - "id": 3, - "u_rated": 10.5e3 - } + [ + 1, + 10.5e3 + ], + [ + 2, + 10.5e3 + ], + [ + 3, + 10.5e3 + ] ], "line": [ { From 2a7c959923b9a00e757777738429577d9da13e40 Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Thu, 18 Dec 2025 13:59:15 +0100 Subject: [PATCH 16/82] attribute ptr not working yet Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- .../include/power_grid_model_cpp/dataset.hpp | 9 ++++----- .../power_grid_model_cpp/serialization.hpp | 17 ++++++++++++++--- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/dataset.hpp b/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/dataset.hpp index 22aab01ff6..18386e580d 100644 --- a/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/dataset.hpp +++ b/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/dataset.hpp @@ -207,11 +207,10 @@ class DatasetConst { }; using AttributeBufferPtr = std::unique_ptr>; -template struct AttributeBufferCreator { - static void delete_ptr(RawDataConstPtr ptr) { delete[] reinterpret_cast(ptr); } - - AttributeBufferPtr operator()(Idx size, bool fill_in_nan = false) const { - auto ptr = AttributeBufferPtr{new T[size], &delete_ptr}; +struct AttributeBufferCreator { + template AttributeBufferPtr operator()(Idx size, bool fill_in_nan = false) const { + static constexpr auto delete_ptr = [](RawDataConstPtr ptr) { delete[] reinterpret_cast(ptr); }; + auto ptr = AttributeBufferPtr{new T[size], delete_ptr}; auto raw_ptr = reinterpret_cast(ptr.get()); if (fill_in_nan) { std::fill_n(raw_ptr, size, nan_value()); diff --git a/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/serialization.hpp b/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/serialization.hpp index 570471f866..1057ae4a3a 100644 --- a/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/serialization.hpp +++ b/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/serialization.hpp @@ -92,7 +92,8 @@ class Serializer { detail::UniquePtr serializer_; }; -inline OwningDataset create_owning_dataset(DatasetWritable& writable_dataset) { +// TODO change this later to false +inline OwningDataset create_owning_dataset(DatasetWritable& writable_dataset, bool enable_columnar_buffers = true) { auto const& info = writable_dataset.get_info(); bool const is_batch = info.is_batch(); Idx const batch_size = info.batch_size(); @@ -104,7 +105,7 @@ inline OwningDataset create_owning_dataset(DatasetWritable& writable_dataset) { for (Idx component_idx{}; component_idx < info.n_components(); ++component_idx) { auto const& component_name = info.component_name(component_idx); - auto const& component_meta = MetaData::get_component_by_name(dataset_name, component_name); + auto const component_meta = MetaData::get_component_by_name(dataset_name, component_name); Idx const component_size = info.component_total_elements(component_idx); Idx const elements_per_scenario = info.component_elements_per_scenario(component_idx); @@ -114,12 +115,22 @@ inline OwningDataset create_owning_dataset(DatasetWritable& writable_dataset) { current_indptr.at(batch_size) = component_size; } Idx* const indptr = current_indptr.empty() ? nullptr : current_indptr.data(); - if (info.has_attribute_indications(component_idx)) { + if (info.has_attribute_indications(component_idx) && enable_columnar_buffers) { auto& current_buffer = storage.buffers.emplace_back(); writable_dataset.set_buffer(component_name, indptr, current_buffer); dataset_mutable.add_buffer(component_name, elements_per_scenario, component_size, indptr, current_buffer); + auto const& attribute_indications = info.attribute_indications(component_idx); + // auto& current_attribute_buffers = storage.attribute_buffers.emplace_back(0); + for (auto const& attribute_name : attribute_indications) { + auto const attribute_meta = + MetaData::get_attribute_by_name(dataset_name, component_name, attribute_name); + auto const attribute_ctype = MetaData::attribute_ctype(attribute_meta); + (void)attribute_ctype; + // current_attribute_buffers.emplace_back(); // TODO change this + } } else { auto& current_buffer = storage.buffers.emplace_back(component_meta, component_size); + storage.attribute_buffers.emplace_back({}); // empty attribute buffers writable_dataset.set_buffer(component_name, indptr, current_buffer); dataset_mutable.add_buffer(component_name, elements_per_scenario, component_size, indptr, current_buffer); } From 6a95741be8254e7d35749bb1c0f61cbd496837dd Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Thu, 18 Dec 2025 14:06:29 +0100 Subject: [PATCH 17/82] attribute buffer works Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- .../include/power_grid_model_cpp/dataset.hpp | 1 + .../include/power_grid_model_cpp/serialization.hpp | 12 +++++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/dataset.hpp b/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/dataset.hpp index 18386e580d..7e842e1c52 100644 --- a/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/dataset.hpp +++ b/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/dataset.hpp @@ -9,6 +9,7 @@ #include "basics.hpp" #include "buffer.hpp" #include "handle.hpp" +#include "utils.hpp" #include "power_grid_model_c/dataset.h" diff --git a/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/serialization.hpp b/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/serialization.hpp index 1057ae4a3a..7c0caaaa7a 100644 --- a/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/serialization.hpp +++ b/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/serialization.hpp @@ -10,6 +10,7 @@ #include "dataset.hpp" #include "handle.hpp" #include "meta_data.hpp" +#include "utils.hpp" #include "power_grid_model_c/serialization.h" @@ -120,17 +121,22 @@ inline OwningDataset create_owning_dataset(DatasetWritable& writable_dataset, bo writable_dataset.set_buffer(component_name, indptr, current_buffer); dataset_mutable.add_buffer(component_name, elements_per_scenario, component_size, indptr, current_buffer); auto const& attribute_indications = info.attribute_indications(component_idx); - // auto& current_attribute_buffers = storage.attribute_buffers.emplace_back(0); + auto& current_attribute_buffers = storage.attribute_buffers.emplace_back(std::vector{}); for (auto const& attribute_name : attribute_indications) { auto const attribute_meta = MetaData::get_attribute_by_name(dataset_name, component_name, attribute_name); auto const attribute_ctype = MetaData::attribute_ctype(attribute_meta); (void)attribute_ctype; - // current_attribute_buffers.emplace_back(); // TODO change this + current_attribute_buffers.emplace_back( + pgm_type_func_selector(attribute_ctype, AttributeBufferCreator{}, component_size)); + writable_dataset.set_attribute_buffer(component_name, attribute_name, + current_attribute_buffers.back().get()); + dataset_mutable.add_attribute_buffer(component_name, attribute_name, + current_attribute_buffers.back().get()); } } else { auto& current_buffer = storage.buffers.emplace_back(component_meta, component_size); - storage.attribute_buffers.emplace_back({}); // empty attribute buffers + storage.attribute_buffers.emplace_back(std::vector{}); // empty attribute buffers writable_dataset.set_buffer(component_name, indptr, current_buffer); dataset_mutable.add_buffer(component_name, elements_per_scenario, component_size, indptr, current_buffer); } From cdeb453aa97e7e8913f81cce9233cb9f7988f43f Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Thu, 18 Dec 2025 14:46:45 +0100 Subject: [PATCH 18/82] revert to false in columnar default Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- .../include/power_grid_model_cpp/serialization.hpp | 3 +-- tests/cpp_validation_tests/test_validation.cpp | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/serialization.hpp b/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/serialization.hpp index 7c0caaaa7a..fde3861ebb 100644 --- a/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/serialization.hpp +++ b/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/serialization.hpp @@ -93,8 +93,7 @@ class Serializer { detail::UniquePtr serializer_; }; -// TODO change this later to false -inline OwningDataset create_owning_dataset(DatasetWritable& writable_dataset, bool enable_columnar_buffers = true) { +inline OwningDataset create_owning_dataset(DatasetWritable& writable_dataset, bool enable_columnar_buffers = false) { auto const& info = writable_dataset.get_info(); bool const is_batch = info.is_batch(); Idx const batch_size = info.batch_size(); diff --git a/tests/cpp_validation_tests/test_validation.cpp b/tests/cpp_validation_tests/test_validation.cpp index 8dd01ff942..6a6d0d4957 100644 --- a/tests/cpp_validation_tests/test_validation.cpp +++ b/tests/cpp_validation_tests/test_validation.cpp @@ -63,7 +63,7 @@ OwningDataset create_result_dataset(OwningDataset const& input, std::string cons return result; } -OwningDataset load_dataset(std::filesystem::path const& path) { +OwningDataset load_dataset(std::filesystem::path const& path, bool enable_columnar_buffers = false) { auto read_file = [](std::filesystem::path const& read_file_path) { std::ifstream const f{read_file_path}; std::ostringstream buffer; @@ -73,7 +73,7 @@ OwningDataset load_dataset(std::filesystem::path const& path) { Deserializer deserializer{read_file(path), PGM_json}; auto& writable_dataset = deserializer.get_dataset(); - auto dataset = create_owning_dataset(writable_dataset); + auto dataset = create_owning_dataset(writable_dataset, enable_columnar_buffers); deserializer.parse_to_buffer(); return dataset; } @@ -566,7 +566,7 @@ struct ValidationCase { ValidationCase create_validation_case(CaseParam const& param, std::string const& output_type) { // input ValidationCase validation_case{.param = param, - .input = load_dataset(param.case_dir / "input.json"), + .input = load_dataset(param.case_dir / "input.json", true), .output = std::nullopt, .update_batch = std::nullopt, .output_batch = std::nullopt}; From 597ffbf74d19279bb3f0f67ce81ab11a73915eae Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Fri, 19 Dec 2025 13:03:02 +0100 Subject: [PATCH 19/82] move onwning dataset Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- .../include/power_grid_model_cpp/dataset.hpp | 54 +++++++++++++++++++ .../power_grid_model_cpp/serialization.hpp | 50 ----------------- .../cpp_validation_tests/test_validation.cpp | 2 +- tests/native_api_tests/load_dataset.hpp | 2 +- 4 files changed, 56 insertions(+), 52 deletions(-) diff --git a/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/dataset.hpp b/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/dataset.hpp index 7e842e1c52..a3b0f37538 100644 --- a/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/dataset.hpp +++ b/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/dataset.hpp @@ -229,6 +229,60 @@ struct OwningMemory { struct OwningDataset { DatasetMutable dataset; OwningMemory storage{}; + + static inline OwningDataset create_owning_dataset(DatasetWritable& writable_dataset, + bool enable_columnar_buffers = false) { + auto const& info = writable_dataset.get_info(); + bool const is_batch = info.is_batch(); + Idx const batch_size = info.batch_size(); + auto const& dataset_name = info.name(); + OwningDataset owning_dataset{.dataset = DatasetMutable{dataset_name, is_batch, batch_size}, + .storage = OwningMemory{}}; + DatasetMutable& dataset_mutable = owning_dataset.dataset; + OwningMemory& storage = owning_dataset.storage; + + for (Idx component_idx{}; component_idx < info.n_components(); ++component_idx) { + auto const& component_name = info.component_name(component_idx); + auto const component_meta = MetaData::get_component_by_name(dataset_name, component_name); + Idx const component_size = info.component_total_elements(component_idx); + Idx const elements_per_scenario = info.component_elements_per_scenario(component_idx); + + auto& current_indptr = storage.indptrs.emplace_back(elements_per_scenario < 0 ? batch_size + 1 : 0); + if (!current_indptr.empty()) { + current_indptr.at(0) = 0; + current_indptr.at(batch_size) = component_size; + } + Idx* const indptr = current_indptr.empty() ? nullptr : current_indptr.data(); + if (info.has_attribute_indications(component_idx) && enable_columnar_buffers) { + auto& current_buffer = storage.buffers.emplace_back(); + writable_dataset.set_buffer(component_name, indptr, current_buffer); + dataset_mutable.add_buffer(component_name, elements_per_scenario, component_size, indptr, + current_buffer); + auto const& attribute_indications = info.attribute_indications(component_idx); + auto& current_attribute_buffers = + storage.attribute_buffers.emplace_back(std::vector{}); + for (auto const& attribute_name : attribute_indications) { + auto const attribute_meta = + MetaData::get_attribute_by_name(dataset_name, component_name, attribute_name); + auto const attribute_ctype = MetaData::attribute_ctype(attribute_meta); + (void)attribute_ctype; + current_attribute_buffers.emplace_back( + pgm_type_func_selector(attribute_ctype, AttributeBufferCreator{}, component_size)); + writable_dataset.set_attribute_buffer(component_name, attribute_name, + current_attribute_buffers.back().get()); + dataset_mutable.add_attribute_buffer(component_name, attribute_name, + current_attribute_buffers.back().get()); + } + } else { + auto& current_buffer = storage.buffers.emplace_back(component_meta, component_size); + storage.attribute_buffers.emplace_back(std::vector{}); // empty attribute buffers + writable_dataset.set_buffer(component_name, indptr, current_buffer); + dataset_mutable.add_buffer(component_name, elements_per_scenario, component_size, indptr, + current_buffer); + } + } + return owning_dataset; + } }; } // namespace power_grid_model_cpp diff --git a/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/serialization.hpp b/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/serialization.hpp index fde3861ebb..f28a68b076 100644 --- a/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/serialization.hpp +++ b/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/serialization.hpp @@ -10,7 +10,6 @@ #include "dataset.hpp" #include "handle.hpp" #include "meta_data.hpp" -#include "utils.hpp" #include "power_grid_model_c/serialization.h" @@ -93,55 +92,6 @@ class Serializer { detail::UniquePtr serializer_; }; -inline OwningDataset create_owning_dataset(DatasetWritable& writable_dataset, bool enable_columnar_buffers = false) { - auto const& info = writable_dataset.get_info(); - bool const is_batch = info.is_batch(); - Idx const batch_size = info.batch_size(); - auto const& dataset_name = info.name(); - OwningDataset owning_dataset{.dataset = DatasetMutable{dataset_name, is_batch, batch_size}, - .storage = OwningMemory{}}; - DatasetMutable& dataset_mutable = owning_dataset.dataset; - OwningMemory& storage = owning_dataset.storage; - - for (Idx component_idx{}; component_idx < info.n_components(); ++component_idx) { - auto const& component_name = info.component_name(component_idx); - auto const component_meta = MetaData::get_component_by_name(dataset_name, component_name); - Idx const component_size = info.component_total_elements(component_idx); - Idx const elements_per_scenario = info.component_elements_per_scenario(component_idx); - - auto& current_indptr = storage.indptrs.emplace_back(elements_per_scenario < 0 ? batch_size + 1 : 0); - if (!current_indptr.empty()) { - current_indptr.at(0) = 0; - current_indptr.at(batch_size) = component_size; - } - Idx* const indptr = current_indptr.empty() ? nullptr : current_indptr.data(); - if (info.has_attribute_indications(component_idx) && enable_columnar_buffers) { - auto& current_buffer = storage.buffers.emplace_back(); - writable_dataset.set_buffer(component_name, indptr, current_buffer); - dataset_mutable.add_buffer(component_name, elements_per_scenario, component_size, indptr, current_buffer); - auto const& attribute_indications = info.attribute_indications(component_idx); - auto& current_attribute_buffers = storage.attribute_buffers.emplace_back(std::vector{}); - for (auto const& attribute_name : attribute_indications) { - auto const attribute_meta = - MetaData::get_attribute_by_name(dataset_name, component_name, attribute_name); - auto const attribute_ctype = MetaData::attribute_ctype(attribute_meta); - (void)attribute_ctype; - current_attribute_buffers.emplace_back( - pgm_type_func_selector(attribute_ctype, AttributeBufferCreator{}, component_size)); - writable_dataset.set_attribute_buffer(component_name, attribute_name, - current_attribute_buffers.back().get()); - dataset_mutable.add_attribute_buffer(component_name, attribute_name, - current_attribute_buffers.back().get()); - } - } else { - auto& current_buffer = storage.buffers.emplace_back(component_meta, component_size); - storage.attribute_buffers.emplace_back(std::vector{}); // empty attribute buffers - writable_dataset.set_buffer(component_name, indptr, current_buffer); - dataset_mutable.add_buffer(component_name, elements_per_scenario, component_size, indptr, current_buffer); - } - } - return owning_dataset; -} } // namespace power_grid_model_cpp #endif // POWER_GRID_MODEL_CPP_SERIALIZATION_HPP diff --git a/tests/cpp_validation_tests/test_validation.cpp b/tests/cpp_validation_tests/test_validation.cpp index 6a6d0d4957..f6c2625f92 100644 --- a/tests/cpp_validation_tests/test_validation.cpp +++ b/tests/cpp_validation_tests/test_validation.cpp @@ -73,7 +73,7 @@ OwningDataset load_dataset(std::filesystem::path const& path, bool enable_column Deserializer deserializer{read_file(path), PGM_json}; auto& writable_dataset = deserializer.get_dataset(); - auto dataset = create_owning_dataset(writable_dataset, enable_columnar_buffers); + auto dataset = OwningDataset::create_owning_dataset(writable_dataset, enable_columnar_buffers); deserializer.parse_to_buffer(); return dataset; } diff --git a/tests/native_api_tests/load_dataset.hpp b/tests/native_api_tests/load_dataset.hpp index 4354ab7a1b..5b858517ed 100644 --- a/tests/native_api_tests/load_dataset.hpp +++ b/tests/native_api_tests/load_dataset.hpp @@ -10,7 +10,7 @@ namespace power_grid_model_cpp_test { inline power_grid_model_cpp::OwningDataset load_dataset(std::string const& json_string) { power_grid_model_cpp::Deserializer deserializer{json_string, PGM_json}; auto& writable_dataset = deserializer.get_dataset(); - auto owning_dataset = power_grid_model_cpp::create_owning_dataset(writable_dataset); + auto owning_dataset = power_grid_model_cpp::OwningDataset::create_owning_dataset(writable_dataset); deserializer.parse_to_buffer(); return owning_dataset; } From 22c2c95a8bcf34e6bd8220f664e2b2bb9abc404f Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Fri, 19 Dec 2025 13:11:30 +0100 Subject: [PATCH 20/82] re-locate Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- .../include/power_grid_model_cpp/dataset.hpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/dataset.hpp b/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/dataset.hpp index a3b0f37538..68c4c69490 100644 --- a/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/dataset.hpp +++ b/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/dataset.hpp @@ -259,8 +259,7 @@ struct OwningDataset { dataset_mutable.add_buffer(component_name, elements_per_scenario, component_size, indptr, current_buffer); auto const& attribute_indications = info.attribute_indications(component_idx); - auto& current_attribute_buffers = - storage.attribute_buffers.emplace_back(std::vector{}); + auto& current_attribute_buffers = storage.attribute_buffers.emplace_back(); for (auto const& attribute_name : attribute_indications) { auto const attribute_meta = MetaData::get_attribute_by_name(dataset_name, component_name, attribute_name); @@ -275,7 +274,7 @@ struct OwningDataset { } } else { auto& current_buffer = storage.buffers.emplace_back(component_meta, component_size); - storage.attribute_buffers.emplace_back(std::vector{}); // empty attribute buffers + storage.attribute_buffers.emplace_back(); // empty attribute buffers writable_dataset.set_buffer(component_name, indptr, current_buffer); dataset_mutable.add_buffer(component_name, elements_per_scenario, component_size, indptr, current_buffer); From 3e2b60833b8621e8e26b6ba91f5476a041da03a5 Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Fri, 19 Dec 2025 13:38:07 +0100 Subject: [PATCH 21/82] move create result dataset Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- .../include/power_grid_model_cpp/dataset.hpp | 24 +++++++++++++++++ .../cpp_validation_tests/test_validation.cpp | 26 ++----------------- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/dataset.hpp b/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/dataset.hpp index 68c4c69490..a3ddb8996d 100644 --- a/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/dataset.hpp +++ b/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/dataset.hpp @@ -282,6 +282,30 @@ struct OwningDataset { } return owning_dataset; } + + static OwningDataset create_result_dataset(OwningDataset const& ref_dataset, std::string const& dataset_name, + bool is_batch = false, Idx batch_size = 1) { + DatasetInfo const& ref_info = ref_dataset.dataset.get_info(); + + OwningDataset result{.dataset = DatasetMutable{dataset_name, is_batch, batch_size}, .storage{}}; + + for (Idx component_idx{}; component_idx != ref_info.n_components(); ++component_idx) { + auto const& component_name = ref_info.component_name(component_idx); + auto const& component_meta = MetaData::get_component_by_name(dataset_name, component_name); + Idx const component_elements_per_scenario = ref_info.component_elements_per_scenario(component_idx); + if (component_elements_per_scenario < 0) { + throw PowerGridError{"Cannot create result dataset for component with variable size per scenario"}; + } + Idx const component_size = ref_info.component_total_elements(component_idx); + + result.storage.indptrs.emplace_back(); + Idx const* const indptr = nullptr; + auto& current_buffer = result.storage.buffers.emplace_back(component_meta, component_size); + result.dataset.add_buffer(component_name, component_elements_per_scenario, component_size, indptr, + current_buffer); + } + return result; + } }; } // namespace power_grid_model_cpp diff --git a/tests/cpp_validation_tests/test_validation.cpp b/tests/cpp_validation_tests/test_validation.cpp index f6c2625f92..ab8d81ea5d 100644 --- a/tests/cpp_validation_tests/test_validation.cpp +++ b/tests/cpp_validation_tests/test_validation.cpp @@ -41,28 +41,6 @@ auto read_json(std::filesystem::path const& path) { return j; } -OwningDataset create_result_dataset(OwningDataset const& input, std::string const& dataset_name, bool is_batch = false, - Idx batch_size = 1) { - DatasetInfo const& input_info = input.dataset.get_info(); - - OwningDataset result{.dataset = DatasetMutable{dataset_name, is_batch, batch_size}, .storage{}}; - - for (Idx component_idx{}; component_idx != input_info.n_components(); ++component_idx) { - auto const& component_name = input_info.component_name(component_idx); - auto const& component_meta = MetaData::get_component_by_name(dataset_name, component_name); - Idx const component_elements_per_scenario = input_info.component_elements_per_scenario(component_idx); - Idx const component_size = input_info.component_total_elements(component_idx); - - auto& current_indptr = result.storage.indptrs.emplace_back( - input_info.component_elements_per_scenario(component_idx) < 0 ? batch_size + 1 : 0); - Idx const* const indptr = current_indptr.empty() ? nullptr : current_indptr.data(); - auto& current_buffer = result.storage.buffers.emplace_back(component_meta, component_size); - result.dataset.add_buffer(component_name, component_elements_per_scenario, component_size, indptr, - current_buffer); - } - return result; -} - OwningDataset load_dataset(std::filesystem::path const& path, bool enable_columnar_buffers = false) { auto read_file = [](std::filesystem::path const& read_file_path) { std::ifstream const f{read_file_path}; @@ -638,7 +616,7 @@ void validate_single_case(CaseParam const& param) { execute_test(param, [¶m](Subcase& subcase) { auto const output_prefix = get_output_type(param.calculation_type, param.sym); auto const validation_case = create_validation_case(param, output_prefix); - auto const result = create_result_dataset(validation_case.output.value(), output_prefix); + auto const result = OwningDataset::create_result_dataset(validation_case.output.value(), output_prefix); // create and run model auto const& options = get_options(param); @@ -657,7 +635,7 @@ void validate_batch_case(CaseParam const& param) { auto const& info = validation_case.update_batch.value().dataset.get_info(); Idx const batch_size = info.batch_size(); auto const batch_result = - create_result_dataset(validation_case.output_batch.value(), output_prefix, true, batch_size); + OwningDataset::create_result_dataset(validation_case.output_batch.value(), output_prefix, true, batch_size); // create model Model model{50.0, validation_case.input.dataset}; From e4d5552812198803279ae6072534c4ffdb8f7d73 Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Fri, 19 Dec 2025 13:44:05 +0100 Subject: [PATCH 22/82] use constructor Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- .../include/power_grid_model_cpp/dataset.hpp | 37 +++++++------------ .../cpp_validation_tests/test_validation.cpp | 7 ++-- tests/native_api_tests/load_dataset.hpp | 2 +- 3 files changed, 18 insertions(+), 28 deletions(-) diff --git a/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/dataset.hpp b/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/dataset.hpp index a3ddb8996d..2d40fa1cbc 100644 --- a/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/dataset.hpp +++ b/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/dataset.hpp @@ -230,16 +230,13 @@ struct OwningDataset { DatasetMutable dataset; OwningMemory storage{}; - static inline OwningDataset create_owning_dataset(DatasetWritable& writable_dataset, - bool enable_columnar_buffers = false) { + OwningDataset(DatasetWritable& writable_dataset, bool enable_columnar_buffers = false) + : dataset{writable_dataset.get_info().name(), writable_dataset.get_info().is_batch(), + writable_dataset.get_info().batch_size()}, + storage{} { auto const& info = writable_dataset.get_info(); - bool const is_batch = info.is_batch(); Idx const batch_size = info.batch_size(); auto const& dataset_name = info.name(); - OwningDataset owning_dataset{.dataset = DatasetMutable{dataset_name, is_batch, batch_size}, - .storage = OwningMemory{}}; - DatasetMutable& dataset_mutable = owning_dataset.dataset; - OwningMemory& storage = owning_dataset.storage; for (Idx component_idx{}; component_idx < info.n_components(); ++component_idx) { auto const& component_name = info.component_name(component_idx); @@ -256,8 +253,7 @@ struct OwningDataset { if (info.has_attribute_indications(component_idx) && enable_columnar_buffers) { auto& current_buffer = storage.buffers.emplace_back(); writable_dataset.set_buffer(component_name, indptr, current_buffer); - dataset_mutable.add_buffer(component_name, elements_per_scenario, component_size, indptr, - current_buffer); + dataset.add_buffer(component_name, elements_per_scenario, component_size, indptr, current_buffer); auto const& attribute_indications = info.attribute_indications(component_idx); auto& current_attribute_buffers = storage.attribute_buffers.emplace_back(); for (auto const& attribute_name : attribute_indications) { @@ -269,26 +265,23 @@ struct OwningDataset { pgm_type_func_selector(attribute_ctype, AttributeBufferCreator{}, component_size)); writable_dataset.set_attribute_buffer(component_name, attribute_name, current_attribute_buffers.back().get()); - dataset_mutable.add_attribute_buffer(component_name, attribute_name, - current_attribute_buffers.back().get()); + dataset.add_attribute_buffer(component_name, attribute_name, + current_attribute_buffers.back().get()); } } else { auto& current_buffer = storage.buffers.emplace_back(component_meta, component_size); storage.attribute_buffers.emplace_back(); // empty attribute buffers writable_dataset.set_buffer(component_name, indptr, current_buffer); - dataset_mutable.add_buffer(component_name, elements_per_scenario, component_size, indptr, - current_buffer); + dataset.add_buffer(component_name, elements_per_scenario, component_size, indptr, current_buffer); } } - return owning_dataset; } - static OwningDataset create_result_dataset(OwningDataset const& ref_dataset, std::string const& dataset_name, - bool is_batch = false, Idx batch_size = 1) { + OwningDataset(OwningDataset const& ref_dataset, std::string const& dataset_name, bool is_batch = false, + Idx batch_size = 1) + : dataset{dataset_name, is_batch, batch_size}, storage{} { DatasetInfo const& ref_info = ref_dataset.dataset.get_info(); - OwningDataset result{.dataset = DatasetMutable{dataset_name, is_batch, batch_size}, .storage{}}; - for (Idx component_idx{}; component_idx != ref_info.n_components(); ++component_idx) { auto const& component_name = ref_info.component_name(component_idx); auto const& component_meta = MetaData::get_component_by_name(dataset_name, component_name); @@ -298,13 +291,11 @@ struct OwningDataset { } Idx const component_size = ref_info.component_total_elements(component_idx); - result.storage.indptrs.emplace_back(); + storage.indptrs.emplace_back(); Idx const* const indptr = nullptr; - auto& current_buffer = result.storage.buffers.emplace_back(component_meta, component_size); - result.dataset.add_buffer(component_name, component_elements_per_scenario, component_size, indptr, - current_buffer); + auto& current_buffer = storage.buffers.emplace_back(component_meta, component_size); + dataset.add_buffer(component_name, component_elements_per_scenario, component_size, indptr, current_buffer); } - return result; } }; } // namespace power_grid_model_cpp diff --git a/tests/cpp_validation_tests/test_validation.cpp b/tests/cpp_validation_tests/test_validation.cpp index ab8d81ea5d..0c94122935 100644 --- a/tests/cpp_validation_tests/test_validation.cpp +++ b/tests/cpp_validation_tests/test_validation.cpp @@ -51,7 +51,7 @@ OwningDataset load_dataset(std::filesystem::path const& path, bool enable_column Deserializer deserializer{read_file(path), PGM_json}; auto& writable_dataset = deserializer.get_dataset(); - auto dataset = OwningDataset::create_owning_dataset(writable_dataset, enable_columnar_buffers); + OwningDataset dataset{writable_dataset, enable_columnar_buffers}; deserializer.parse_to_buffer(); return dataset; } @@ -616,7 +616,7 @@ void validate_single_case(CaseParam const& param) { execute_test(param, [¶m](Subcase& subcase) { auto const output_prefix = get_output_type(param.calculation_type, param.sym); auto const validation_case = create_validation_case(param, output_prefix); - auto const result = OwningDataset::create_result_dataset(validation_case.output.value(), output_prefix); + OwningDataset const result{validation_case.output.value(), output_prefix}; // create and run model auto const& options = get_options(param); @@ -634,8 +634,7 @@ void validate_batch_case(CaseParam const& param) { auto const validation_case = create_validation_case(param, output_prefix); auto const& info = validation_case.update_batch.value().dataset.get_info(); Idx const batch_size = info.batch_size(); - auto const batch_result = - OwningDataset::create_result_dataset(validation_case.output_batch.value(), output_prefix, true, batch_size); + OwningDataset const batch_result{validation_case.output_batch.value(), output_prefix, true, batch_size}; // create model Model model{50.0, validation_case.input.dataset}; diff --git a/tests/native_api_tests/load_dataset.hpp b/tests/native_api_tests/load_dataset.hpp index 5b858517ed..3f06986399 100644 --- a/tests/native_api_tests/load_dataset.hpp +++ b/tests/native_api_tests/load_dataset.hpp @@ -10,7 +10,7 @@ namespace power_grid_model_cpp_test { inline power_grid_model_cpp::OwningDataset load_dataset(std::string const& json_string) { power_grid_model_cpp::Deserializer deserializer{json_string, PGM_json}; auto& writable_dataset = deserializer.get_dataset(); - auto owning_dataset = power_grid_model_cpp::OwningDataset::create_owning_dataset(writable_dataset); + power_grid_model_cpp::OwningDataset owning_dataset{writable_dataset}; deserializer.parse_to_buffer(); return owning_dataset; } From 41ea15b5aff832204830fad02bae416b54e76891 Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Fri, 19 Dec 2025 13:47:54 +0100 Subject: [PATCH 23/82] add ctype Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- .../include/power_grid_model_cpp/dataset.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/dataset.hpp b/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/dataset.hpp index 2d40fa1cbc..3e438794de 100644 --- a/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/dataset.hpp +++ b/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/dataset.hpp @@ -260,7 +260,6 @@ struct OwningDataset { auto const attribute_meta = MetaData::get_attribute_by_name(dataset_name, component_name, attribute_name); auto const attribute_ctype = MetaData::attribute_ctype(attribute_meta); - (void)attribute_ctype; current_attribute_buffers.emplace_back( pgm_type_func_selector(attribute_ctype, AttributeBufferCreator{}, component_size)); writable_dataset.set_attribute_buffer(component_name, attribute_name, From 682ff3e7a4c35f1e0f76cedafb4ae255a52275eb Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Fri, 19 Dec 2025 14:11:31 +0100 Subject: [PATCH 24/82] add filter Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- .../include/power_grid_model_cpp/dataset.hpp | 44 ++++++++++++++++--- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/dataset.hpp b/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/dataset.hpp index 3e438794de..ef8878ec79 100644 --- a/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/dataset.hpp +++ b/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/dataset.hpp @@ -13,6 +13,9 @@ #include "power_grid_model_c/dataset.h" +#include +#include + namespace power_grid_model_cpp { class ComponentTypeNotFound : public PowerGridError { public: @@ -276,24 +279,53 @@ struct OwningDataset { } } - OwningDataset(OwningDataset const& ref_dataset, std::string const& dataset_name, bool is_batch = false, - Idx batch_size = 1) + OwningDataset( + OwningDataset const& ref_dataset, std::string const& dataset_name, bool is_batch = false, Idx batch_size = 1, + std::map> output_component_attribute_filters = {}) : dataset{dataset_name, is_batch, batch_size}, storage{} { DatasetInfo const& ref_info = ref_dataset.dataset.get_info(); + bool const enable_filters = !output_component_attribute_filters.empty(); for (Idx component_idx{}; component_idx != ref_info.n_components(); ++component_idx) { auto const& component_name = ref_info.component_name(component_idx); auto const& component_meta = MetaData::get_component_by_name(dataset_name, component_name); + // skip components not in the filter + if (enable_filters && !output_component_attribute_filters.contains(component_meta)) { + continue; + } + + // get size info from reference dataset Idx const component_elements_per_scenario = ref_info.component_elements_per_scenario(component_idx); if (component_elements_per_scenario < 0) { throw PowerGridError{"Cannot create result dataset for component with variable size per scenario"}; } Idx const component_size = ref_info.component_total_elements(component_idx); - storage.indptrs.emplace_back(); - Idx const* const indptr = nullptr; - auto& current_buffer = storage.buffers.emplace_back(component_meta, component_size); - dataset.add_buffer(component_name, component_elements_per_scenario, component_size, indptr, current_buffer); + + std::set const& attribute_filter = + output_component_attribute_filters.contains(component_meta) + ? output_component_attribute_filters.at(component_meta) + : std::set{}; + if (attribute_filter.empty()) { + // create full row buffer + auto& component_buffer = storage.buffers.emplace_back(component_meta, component_size); + storage.attribute_buffers.emplace_back(); // empty attribute buffers + dataset.add_buffer(component_name, component_elements_per_scenario, component_size, nullptr, + component_buffer); + } else { + // push nullptr as row buffer, and start attribute buffers + auto& component_buffer = storage.buffers.emplace_back(); + storage.attribute_buffers.emplace_back(); + dataset.add_buffer(component_name, component_elements_per_scenario, component_size, nullptr, + component_buffer); + for (auto const attribute_meta : attribute_filter) { + auto const attribute_name = MetaData::attribute_name(attribute_meta); + auto const attribute_ctype = MetaData::attribute_ctype(attribute_meta); + auto& attribute_buffer = storage.attribute_buffers.back().emplace_back( + pgm_type_func_selector(attribute_ctype, AttributeBufferCreator{}, component_size)); + dataset.add_attribute_buffer(component_name, attribute_name, attribute_buffer.get()); + } + } } } }; From c87f79ec5ed6eb297a85733c3d3c3c9419bba4ed Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Fri, 19 Dec 2025 14:14:08 +0100 Subject: [PATCH 25/82] const reference Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- .../include/power_grid_model_cpp/dataset.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/dataset.hpp b/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/dataset.hpp index ef8878ec79..9d25e0e4fc 100644 --- a/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/dataset.hpp +++ b/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/dataset.hpp @@ -281,7 +281,7 @@ struct OwningDataset { OwningDataset( OwningDataset const& ref_dataset, std::string const& dataset_name, bool is_batch = false, Idx batch_size = 1, - std::map> output_component_attribute_filters = {}) + std::map> const& output_component_attribute_filters = {}) : dataset{dataset_name, is_batch, batch_size}, storage{} { DatasetInfo const& ref_info = ref_dataset.dataset.get_info(); bool const enable_filters = !output_component_attribute_filters.empty(); From 11eef1de4f2d99c9c001c5f3bbf516bfde40c400 Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Fri, 19 Dec 2025 15:34:33 +0100 Subject: [PATCH 26/82] get c_string Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- .../include/power_grid_model_cpp/serialization.hpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/serialization.hpp b/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/serialization.hpp index f28a68b076..0f111b0057 100644 --- a/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/serialization.hpp +++ b/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/serialization.hpp @@ -83,8 +83,11 @@ class Serializer { } std::string get_to_zero_terminated_string(Idx use_compact_list, Idx indent) { - return std::string{ - handle_.call_with(PGM_serializer_get_to_zero_terminated_string, get(), use_compact_list, indent)}; + return std::string{get_to_zero_terminated_c_string(use_compact_list, indent)}; + } + + char const* get_to_zero_terminated_c_string(Idx use_compact_list, Idx indent) { + return handle_.call_with(PGM_serializer_get_to_zero_terminated_string, get(), use_compact_list, indent); } private: From 382f52da8c4411c20eaea4a84c74071974786689 Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Fri, 19 Dec 2025 15:42:12 +0100 Subject: [PATCH 27/82] load dataset Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- .../power_grid_model_cpp/serialization.hpp | 23 +++++++++++++++++++ .../cpp_validation_tests/test_validation.cpp | 23 ++++--------------- 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/serialization.hpp b/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/serialization.hpp index 0f111b0057..300f59f8b5 100644 --- a/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/serialization.hpp +++ b/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/serialization.hpp @@ -14,6 +14,8 @@ #include "power_grid_model_c/serialization.h" #include +#include +#include namespace power_grid_model_cpp { class Deserializer { @@ -95,6 +97,27 @@ class Serializer { detail::UniquePtr serializer_; }; +inline OwningDataset load_dataset(std::filesystem::path const& path, PGM_SerializationFormat serialization_format, + bool enable_columnar_buffers = false) { + auto read_file = [](std::filesystem::path const& read_file_path) { + std::ifstream f{read_file_path, std::ios::binary | std::ios::ate}; + if (!f) { + throw std::runtime_error("Failed to open file: " + read_file_path.string()); + } + auto const file_size = f.tellg(); + f.seekg(0, std::ios::beg); + std::vector buffer(static_cast(file_size)); + f.read(buffer.data(), file_size); + return buffer; + }; + + Deserializer deserializer{read_file(path), serialization_format}; + auto& writable_dataset = deserializer.get_dataset(); + OwningDataset dataset{writable_dataset, enable_columnar_buffers}; + deserializer.parse_to_buffer(); + return dataset; +} + } // namespace power_grid_model_cpp #endif // POWER_GRID_MODEL_CPP_SERIALIZATION_HPP diff --git a/tests/cpp_validation_tests/test_validation.cpp b/tests/cpp_validation_tests/test_validation.cpp index 0c94122935..332347baa4 100644 --- a/tests/cpp_validation_tests/test_validation.cpp +++ b/tests/cpp_validation_tests/test_validation.cpp @@ -41,21 +41,6 @@ auto read_json(std::filesystem::path const& path) { return j; } -OwningDataset load_dataset(std::filesystem::path const& path, bool enable_columnar_buffers = false) { - auto read_file = [](std::filesystem::path const& read_file_path) { - std::ifstream const f{read_file_path}; - std::ostringstream buffer; - buffer << f.rdbuf(); - return buffer.str(); - }; - - Deserializer deserializer{read_file(path), PGM_json}; - auto& writable_dataset = deserializer.get_dataset(); - OwningDataset dataset{writable_dataset, enable_columnar_buffers}; - deserializer.parse_to_buffer(); - return dataset; -} - template std::string get_as_string(T const& attribute_value) { std::stringstream sstr; sstr << std::setprecision(16); @@ -544,17 +529,17 @@ struct ValidationCase { ValidationCase create_validation_case(CaseParam const& param, std::string const& output_type) { // input ValidationCase validation_case{.param = param, - .input = load_dataset(param.case_dir / "input.json", true), + .input = load_dataset(param.case_dir / "input.json", PGM_json, true), .output = std::nullopt, .update_batch = std::nullopt, .output_batch = std::nullopt}; // output and update if (!param.is_batch) { - validation_case.output = load_dataset(param.case_dir / (output_type + ".json")); + validation_case.output = load_dataset(param.case_dir / (output_type + ".json"), PGM_json); } else { - validation_case.update_batch = load_dataset(param.case_dir / "update_batch.json"); - validation_case.output_batch = load_dataset(param.case_dir / (output_type + "_batch.json")); + validation_case.update_batch = load_dataset(param.case_dir / "update_batch.json", PGM_json); + validation_case.output_batch = load_dataset(param.case_dir / (output_type + "_batch.json"), PGM_json); } return validation_case; } From 33d4fc05de16f444057b19acda7f9a087db113dc Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Fri, 19 Dec 2025 20:53:02 +0100 Subject: [PATCH 28/82] save dataset Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- .../include/power_grid_model_cpp/serialization.hpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/serialization.hpp b/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/serialization.hpp index 300f59f8b5..78853a8498 100644 --- a/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/serialization.hpp +++ b/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/serialization.hpp @@ -118,6 +118,19 @@ inline OwningDataset load_dataset(std::filesystem::path const& path, PGM_Seriali return dataset; } +inline void save_dataset(std::filesystem::path const& path, DatasetConst const& dataset, + PGM_SerializationFormat serialization_format, Idx use_compact_list, Idx indent = 2) { + Serializer serializer{dataset, serialization_format}; + std::string_view serialized_data = serialization_format == PGM_msgpack + ? serializer.get_to_binary_buffer(use_compact_list) + : serializer.get_to_zero_terminated_c_string(use_compact_list, indent); + std::ofstream f{path, std::ios::binary}; + if (!f) { + throw std::runtime_error("Failed to open file for writing: " + path.string()); + } + f << serialized_data; +} + } // namespace power_grid_model_cpp #endif // POWER_GRID_MODEL_CPP_SERIALIZATION_HPP From 5aa7b196f157e9869e0de7f9e6bc5a1953acf501 Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Fri, 19 Dec 2025 21:07:01 +0100 Subject: [PATCH 29/82] add calculate Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- .../power_grid_model_cli/CMakeLists.txt | 1 + .../{cli_options.hpp => cli_functions.hpp} | 2 ++ .../power_grid_model_cli/cli_options.cpp | 2 +- power_grid_model_c/power_grid_model_cli/main.cpp | 15 ++++++++++++++- .../power_grid_model_cli/pgm_calculation.cpp | 13 +++++++++++++ 5 files changed, 31 insertions(+), 2 deletions(-) rename power_grid_model_c/power_grid_model_cli/{cli_options.hpp => cli_functions.hpp} (96%) create mode 100644 power_grid_model_c/power_grid_model_cli/pgm_calculation.cpp diff --git a/power_grid_model_c/power_grid_model_cli/CMakeLists.txt b/power_grid_model_c/power_grid_model_cli/CMakeLists.txt index 568e261557..7d4e9ff464 100644 --- a/power_grid_model_c/power_grid_model_cli/CMakeLists.txt +++ b/power_grid_model_c/power_grid_model_cli/CMakeLists.txt @@ -5,6 +5,7 @@ add_executable(power-grid-model main.cpp cli_options.cpp + pgm_calculation.cpp ) target_link_libraries(power-grid-model PRIVATE power_grid_model_c) diff --git a/power_grid_model_c/power_grid_model_cli/cli_options.hpp b/power_grid_model_c/power_grid_model_cli/cli_functions.hpp similarity index 96% rename from power_grid_model_c/power_grid_model_cli/cli_options.hpp rename to power_grid_model_c/power_grid_model_cli/cli_functions.hpp index fd06c018b0..bba6cdeca0 100644 --- a/power_grid_model_c/power_grid_model_cli/cli_options.hpp +++ b/power_grid_model_c/power_grid_model_cli/cli_functions.hpp @@ -48,4 +48,6 @@ struct ClIOptions { CLIResult parse_cli_options(int argc, char** argv, ClIOptions& options); +void pgm_calculation(ClIOptions const& options); + } // namespace power_grid_model_cpp diff --git a/power_grid_model_c/power_grid_model_cli/cli_options.cpp b/power_grid_model_c/power_grid_model_cli/cli_options.cpp index e861da3be0..a47a1b706a 100644 --- a/power_grid_model_c/power_grid_model_cli/cli_options.cpp +++ b/power_grid_model_c/power_grid_model_cli/cli_options.cpp @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: MPL-2.0 -#include "cli_options.hpp" +#include "cli_functions.hpp" #include diff --git a/power_grid_model_c/power_grid_model_cli/main.cpp b/power_grid_model_c/power_grid_model_cli/main.cpp index 91ada34484..dad9f3287e 100644 --- a/power_grid_model_c/power_grid_model_cli/main.cpp +++ b/power_grid_model_c/power_grid_model_cli/main.cpp @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: MPL-2.0 -#include "cli_options.hpp" +#include "cli_functions.hpp" #include @@ -18,5 +18,18 @@ int main(int argc, char** argv) { std::cout << cli_options << std::endl; + try { + pgm_calculation(cli_options); + } catch (PowerGridError const& e) { + std::cerr << "PowerGridError: " << e.what() << std::endl; + return e.error_code(); + } catch (std::exception const& e) { + std::cerr << "Exception: " << e.what() << std::endl; + return -666; + } catch (...) { + std::cerr << "Unknown exception caught." << std::endl; + return -999; + } + return 0; } diff --git a/power_grid_model_c/power_grid_model_cli/pgm_calculation.cpp b/power_grid_model_c/power_grid_model_cli/pgm_calculation.cpp new file mode 100644 index 0000000000..afd187cc42 --- /dev/null +++ b/power_grid_model_c/power_grid_model_cli/pgm_calculation.cpp @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: Contributors to the Power Grid Model project +// +// SPDX-License-Identifier: MPL-2.0 + +#include "cli_functions.hpp" + +namespace power_grid_model_cpp { + +void pgm_calculation(ClIOptions const& options) { + (void)options; +} + +} // namespace power_grid_model_cpp From f5e10b06d3a4bb41d3d9571168438ca3f3ab725e Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Fri, 19 Dec 2025 21:08:50 +0100 Subject: [PATCH 30/82] is batch Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- power_grid_model_c/power_grid_model_cli/cli_functions.hpp | 1 + power_grid_model_c/power_grid_model_cli/cli_options.cpp | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/power_grid_model_c/power_grid_model_cli/cli_functions.hpp b/power_grid_model_c/power_grid_model_cli/cli_functions.hpp index bba6cdeca0..40509e5837 100644 --- a/power_grid_model_c/power_grid_model_cli/cli_functions.hpp +++ b/power_grid_model_c/power_grid_model_cli/cli_functions.hpp @@ -25,6 +25,7 @@ struct ClIOptions { std::filesystem::path output_file; bool input_msgpack_serialization{false}; bool batch_update_msgpack_serialization{false}; + bool is_batch{false}; Idx calculation_type{PGM_power_flow}; Idx calculation_method{PGM_default_method}; diff --git a/power_grid_model_c/power_grid_model_cli/cli_options.cpp b/power_grid_model_c/power_grid_model_cli/cli_options.cpp index a47a1b706a..b31a528781 100644 --- a/power_grid_model_c/power_grid_model_cli/cli_options.cpp +++ b/power_grid_model_c/power_grid_model_cli/cli_options.cpp @@ -44,9 +44,10 @@ struct CLIPostCallback { void set_default_values() { // detect if input file is msgpack options.input_msgpack_serialization = is_msgpack_file(options.input_file); + // detect if batch update file is provided + options.is_batch = !options.batch_update_file.empty(); // detect if batch update file is msgpack - options.batch_update_msgpack_serialization = - !options.batch_update_file.empty() && is_msgpack_file(options.batch_update_file); + options.batch_update_msgpack_serialization = options.is_batch && is_msgpack_file(options.batch_update_file); // default msgpack output if input or batch update is msgpack and user did not specify output format if (msgpack_flag->count() == 0 && (options.input_msgpack_serialization || options.batch_update_msgpack_serialization)) { From e3f4191edefe14ec0b44b244d00bcef303e14854 Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Fri, 19 Dec 2025 21:20:28 +0100 Subject: [PATCH 31/82] first run succeeds Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- .../power_grid_model_cli/cli_functions.hpp | 4 +- .../power_grid_model_cli/pgm_calculation.cpp | 44 ++++++++++++++++++- 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/power_grid_model_c/power_grid_model_cli/cli_functions.hpp b/power_grid_model_c/power_grid_model_cli/cli_functions.hpp index 40509e5837..329059b1e4 100644 --- a/power_grid_model_c/power_grid_model_cli/cli_functions.hpp +++ b/power_grid_model_c/power_grid_model_cli/cli_functions.hpp @@ -27,6 +27,8 @@ struct ClIOptions { bool batch_update_msgpack_serialization{false}; bool is_batch{false}; + double system_frequency{50.0}; + Idx calculation_type{PGM_power_flow}; Idx calculation_method{PGM_default_method}; bool symmetric_calculation{PGM_symmetric}; @@ -49,6 +51,6 @@ struct ClIOptions { CLIResult parse_cli_options(int argc, char** argv, ClIOptions& options); -void pgm_calculation(ClIOptions const& options); +void pgm_calculation(ClIOptions const& cli_options); } // namespace power_grid_model_cpp diff --git a/power_grid_model_c/power_grid_model_cli/pgm_calculation.cpp b/power_grid_model_c/power_grid_model_cli/pgm_calculation.cpp index afd187cc42..1fa2acf36d 100644 --- a/power_grid_model_c/power_grid_model_cli/pgm_calculation.cpp +++ b/power_grid_model_c/power_grid_model_cli/pgm_calculation.cpp @@ -4,10 +4,50 @@ #include "cli_functions.hpp" +#include + namespace power_grid_model_cpp { -void pgm_calculation(ClIOptions const& options) { - (void)options; +void pgm_calculation(ClIOptions const& cli_options) { + // Load input dataset + OwningDataset input_dataset = + load_dataset(cli_options.input_file, cli_options.input_msgpack_serialization ? PGM_msgpack : PGM_json); + + // Apply batch updates if provided + std::optional batch_update_dataset{std::nullopt}; + if (cli_options.is_batch) { + batch_update_dataset = load_dataset(cli_options.batch_update_file, + cli_options.batch_update_msgpack_serialization ? PGM_msgpack : PGM_json); + } + Idx const batch_size = cli_options.is_batch ? batch_update_dataset->dataset.get_info().batch_size() : 1; + + // create result dataset + OwningDataset result_dataset{input_dataset, cli_options.output_dataset_name, cli_options.is_batch, batch_size, + cli_options.output_component_attribute_filters}; + // create model + Model model{cli_options.system_frequency, input_dataset.dataset}; + // create calculation options + Options calc_options{}; + calc_options.set_calculation_type(cli_options.calculation_type); + calc_options.set_calculation_method(cli_options.calculation_method); + calc_options.set_symmetric(cli_options.symmetric_calculation); + calc_options.set_err_tol(cli_options.error_tolerance); + calc_options.set_max_iter(cli_options.max_iterations); + calc_options.set_threading(cli_options.threading); + calc_options.set_short_circuit_voltage_scaling(cli_options.short_circuit_voltage_scaling); + calc_options.set_tap_changing_strategy(cli_options.tap_changing_strategy); + + // perform calculation + if (cli_options.is_batch) { + model.calculate(calc_options, result_dataset.dataset, batch_update_dataset->dataset); + } else { + model.calculate(calc_options, result_dataset.dataset); + } + + // Save output dataset + save_dataset(cli_options.output_file, result_dataset.dataset, + cli_options.use_msgpack_output_serialization ? PGM_msgpack : PGM_json, + cli_options.use_compact_serialization ? 1 : 0, cli_options.output_json_indent); } } // namespace power_grid_model_cpp From 9db168810367c7e91f83b18d357e8c6b6f9d0093 Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Fri, 19 Dec 2025 21:22:45 +0100 Subject: [PATCH 32/82] add frequency Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- power_grid_model_c/power_grid_model_cli/cli_options.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/power_grid_model_c/power_grid_model_cli/cli_options.cpp b/power_grid_model_c/power_grid_model_cli/cli_options.cpp index b31a528781..da69f5e96a 100644 --- a/power_grid_model_c/power_grid_model_cli/cli_options.cpp +++ b/power_grid_model_c/power_grid_model_cli/cli_options.cpp @@ -133,6 +133,7 @@ CLIResult parse_cli_options(int argc, char** argv, ClIOptions& options) { app.add_option("-o,--output", options.output_file, "Output file path") ->required() ->check(existing_parent_dir_validator); + app.add_option("-f,--system-frequency", options.system_frequency, "System frequency in Hz, default is 50.0 Hz."); app.add_option("-c,--calculation-type", options.calculation_type, "Calculation type") ->transform(CLI::CheckedTransformer( EnumMap{ From 456179928ca461d142c4da29f8b05779444202c6 Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Fri, 19 Dec 2025 21:28:51 +0100 Subject: [PATCH 33/82] use enum Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- .../power_grid_model_cli/cli_functions.hpp | 4 ++-- power_grid_model_c/power_grid_model_cli/cli_options.cpp | 9 +++++---- .../power_grid_model_cli/pgm_calculation.cpp | 7 +++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/power_grid_model_c/power_grid_model_cli/cli_functions.hpp b/power_grid_model_c/power_grid_model_cli/cli_functions.hpp index 329059b1e4..a4fb83649c 100644 --- a/power_grid_model_c/power_grid_model_cli/cli_functions.hpp +++ b/power_grid_model_c/power_grid_model_cli/cli_functions.hpp @@ -23,8 +23,8 @@ struct ClIOptions { std::filesystem::path input_file; std::filesystem::path batch_update_file; std::filesystem::path output_file; - bool input_msgpack_serialization{false}; - bool batch_update_msgpack_serialization{false}; + PGM_SerializationFormat input_serialization_format{PGM_json}; + PGM_SerializationFormat batch_update_serialization_format{PGM_json}; bool is_batch{false}; double system_frequency{50.0}; diff --git a/power_grid_model_c/power_grid_model_cli/cli_options.cpp b/power_grid_model_c/power_grid_model_cli/cli_options.cpp index da69f5e96a..919732b04a 100644 --- a/power_grid_model_c/power_grid_model_cli/cli_options.cpp +++ b/power_grid_model_c/power_grid_model_cli/cli_options.cpp @@ -43,14 +43,15 @@ struct CLIPostCallback { void set_default_values() { // detect if input file is msgpack - options.input_msgpack_serialization = is_msgpack_file(options.input_file); + options.input_serialization_format = is_msgpack_file(options.input_file) ? PGM_msgpack : PGM_json; // detect if batch update file is provided options.is_batch = !options.batch_update_file.empty(); // detect if batch update file is msgpack - options.batch_update_msgpack_serialization = options.is_batch && is_msgpack_file(options.batch_update_file); + options.batch_update_serialization_format = + options.is_batch && is_msgpack_file(options.batch_update_file) ? PGM_msgpack : PGM_json; // default msgpack output if input or batch update is msgpack and user did not specify output format - if (msgpack_flag->count() == 0 && - (options.input_msgpack_serialization || options.batch_update_msgpack_serialization)) { + if (msgpack_flag->count() == 0 && (options.input_serialization_format == PGM_msgpack || + options.batch_update_serialization_format == PGM_msgpack)) { options.use_msgpack_output_serialization = true; } // default compact serialization if msgpack output and user did not specify compact option diff --git a/power_grid_model_c/power_grid_model_cli/pgm_calculation.cpp b/power_grid_model_c/power_grid_model_cli/pgm_calculation.cpp index 1fa2acf36d..ad70cefd18 100644 --- a/power_grid_model_c/power_grid_model_cli/pgm_calculation.cpp +++ b/power_grid_model_c/power_grid_model_cli/pgm_calculation.cpp @@ -10,14 +10,13 @@ namespace power_grid_model_cpp { void pgm_calculation(ClIOptions const& cli_options) { // Load input dataset - OwningDataset input_dataset = - load_dataset(cli_options.input_file, cli_options.input_msgpack_serialization ? PGM_msgpack : PGM_json); + OwningDataset input_dataset = load_dataset(cli_options.input_file, cli_options.input_serialization_format); // Apply batch updates if provided std::optional batch_update_dataset{std::nullopt}; if (cli_options.is_batch) { - batch_update_dataset = load_dataset(cli_options.batch_update_file, - cli_options.batch_update_msgpack_serialization ? PGM_msgpack : PGM_json); + batch_update_dataset = + load_dataset(cli_options.batch_update_file, cli_options.batch_update_serialization_format); } Idx const batch_size = cli_options.is_batch ? batch_update_dataset->dataset.get_info().batch_size() : 1; From 90810bd96ddc7f7ae5b41341b706f8c5bfd1a99b Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Fri, 19 Dec 2025 21:54:07 +0100 Subject: [PATCH 34/82] fix a bug Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- .../include/power_grid_model_cpp/dataset.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/dataset.hpp b/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/dataset.hpp index 9d25e0e4fc..23d955b149 100644 --- a/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/dataset.hpp +++ b/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/dataset.hpp @@ -299,7 +299,7 @@ struct OwningDataset { if (component_elements_per_scenario < 0) { throw PowerGridError{"Cannot create result dataset for component with variable size per scenario"}; } - Idx const component_size = ref_info.component_total_elements(component_idx); + Idx const component_size = component_elements_per_scenario * batch_size; storage.indptrs.emplace_back(); std::set const& attribute_filter = From 187f614c97cb90035a33e99714dbc9a882a1b5df Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Tue, 27 Jan 2026 12:41:00 +0100 Subject: [PATCH 35/82] modify version Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- cmake/pgm_version.cmake | 7 +++++++ power_grid_model_c/power_grid_model_c/CMakeLists.txt | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/cmake/pgm_version.cmake b/cmake/pgm_version.cmake index 816b6f651f..9cdd26eadc 100644 --- a/cmake/pgm_version.cmake +++ b/cmake/pgm_version.cmake @@ -13,3 +13,10 @@ string( PGM_VERSION "${_PGM_VERSION_STRIPPED}" ) +string( + REGEX REPLACE + "^([0-9]+)\\.[0-9]+(\\.[0-9]+)?.*" + "\\1" + PGM_VERSION_MAJOR + "${PGM_VERSION}" +) diff --git a/power_grid_model_c/power_grid_model_c/CMakeLists.txt b/power_grid_model_c/power_grid_model_c/CMakeLists.txt index 14a8ad9868..6e9c575cd3 100644 --- a/power_grid_model_c/power_grid_model_c/CMakeLists.txt +++ b/power_grid_model_c/power_grid_model_c/CMakeLists.txt @@ -44,7 +44,7 @@ set_target_properties( power_grid_model_c PROPERTIES VERSION ${PGM_VERSION} - SOVERSION ${PGM_VERSION} + SOVERSION ${PGM_VERSION_MAJOR} INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE INTERPROCEDURAL_OPTIMIZATION_RELWITHDEBINFO TRUE ) From 4284e026cde0569f117959686e81bbbdf7706573 Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Tue, 27 Jan 2026 14:52:39 +0100 Subject: [PATCH 36/82] MD batch Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- .../power_grid_model_cli/cli_functions.hpp | 4 +- .../power_grid_model_cli/cli_options.cpp | 38 ++++++++++++----- .../power_grid_model_cli/pgm_calculation.cpp | 41 +++++++++++++++---- 3 files changed, 61 insertions(+), 22 deletions(-) diff --git a/power_grid_model_c/power_grid_model_cli/cli_functions.hpp b/power_grid_model_c/power_grid_model_cli/cli_functions.hpp index a4fb83649c..1ba750bb7a 100644 --- a/power_grid_model_c/power_grid_model_cli/cli_functions.hpp +++ b/power_grid_model_c/power_grid_model_cli/cli_functions.hpp @@ -21,10 +21,10 @@ struct CLIResult { struct ClIOptions { std::filesystem::path input_file; - std::filesystem::path batch_update_file; + std::vector batch_update_file; std::filesystem::path output_file; PGM_SerializationFormat input_serialization_format{PGM_json}; - PGM_SerializationFormat batch_update_serialization_format{PGM_json}; + std::vector batch_update_serialization_format; bool is_batch{false}; double system_frequency{50.0}; diff --git a/power_grid_model_c/power_grid_model_cli/cli_options.cpp b/power_grid_model_c/power_grid_model_cli/cli_options.cpp index 919732b04a..549c2ddbee 100644 --- a/power_grid_model_c/power_grid_model_cli/cli_options.cpp +++ b/power_grid_model_c/power_grid_model_cli/cli_options.cpp @@ -6,8 +6,12 @@ #include +#include #include #include +#include +#include +#include namespace power_grid_model_cpp { @@ -27,31 +31,36 @@ struct CLIPostCallback { add_attribute_output_filter(); } - static bool is_msgpack_file(std::filesystem::path const& path) { + static PGM_SerializationFormat get_serialization_format(std::string const& argument_type, + std::filesystem::path const& path) { std::ifstream file{path, std::ios::binary}; if (!file.is_open()) { - return false; + throw CLI::ValidationError(argument_type, "Unable to open file: " + path.string()); } uint8_t header; file.read(reinterpret_cast(&header), 1); if (!file) { - return false; + throw CLI::ValidationError(argument_type, "Unable to read from file: " + path.string()); } // Check for fixmap (0x80-0x8f), map16 (0xde), or map32 (0xdf) - return (header >= 0x80 && header <= 0x8f) || header == 0xde || header == 0xdf; + bool const is_msgpack = (header >= 0x80 && header <= 0x8f) || header == 0xde || header == 0xdf; + return is_msgpack ? PGM_msgpack : PGM_json; } void set_default_values() { // detect if input file is msgpack - options.input_serialization_format = is_msgpack_file(options.input_file) ? PGM_msgpack : PGM_json; + options.input_serialization_format = get_serialization_format("input", options.input_file); // detect if batch update file is provided options.is_batch = !options.batch_update_file.empty(); // detect if batch update file is msgpack - options.batch_update_serialization_format = - options.is_batch && is_msgpack_file(options.batch_update_file) ? PGM_msgpack : PGM_json; - // default msgpack output if input or batch update is msgpack and user did not specify output format + options.batch_update_serialization_format.resize(options.batch_update_file.size()); + std::transform(options.batch_update_file.cbegin(), options.batch_update_file.cend(), + options.batch_update_serialization_format.begin(), + [](auto const& path) { return get_serialization_format("batch-update", path); }); + // default msgpack output if input or any of the batch updates is msgpack and user did not specify output format if (msgpack_flag->count() == 0 && (options.input_serialization_format == PGM_msgpack || - options.batch_update_serialization_format == PGM_msgpack)) { + std::ranges::any_of(options.batch_update_serialization_format, + [](auto format) { return format == PGM_msgpack; }))) { options.use_msgpack_output_serialization = true; } // default compact serialization if msgpack output and user did not specify compact option @@ -130,7 +139,11 @@ CLIResult parse_cli_options(int argc, char** argv, ClIOptions& options) { "ExistingParentDirectory"}; app.add_option("-i,--input", options.input_file, "Input file path")->required()->check(CLI::ExistingFile); - app.add_option("-b,--batch-update", options.batch_update_file, "Batch update file path")->check(CLI::ExistingFile); + app.add_option("-b,--batch-update", options.batch_update_file, + "Batch update file path. Can be specified multiple times.\n" + "If multiple files are specified, the core will intepret them as the cartesian product of all " + "combinations of all scenarios in the list of batch datasets.") + ->check(CLI::ExistingFile); app.add_option("-o,--output", options.output_file, "Output file path") ->required() ->check(existing_parent_dir_validator); @@ -212,7 +225,10 @@ CLIResult parse_cli_options(int argc, char** argv, ClIOptions& options) { std::ostream& operator<<(std::ostream& os, ClIOptions const& options) { os << "CLI Options:\n"; os << "Input file: " << options.input_file << "\n"; - os << "Batch update file: " << options.batch_update_file << "\n"; + os << "Batch update file: \n"; + for (auto const& file : options.batch_update_file) { + os << '\t' << file << "\n"; + } os << "Output file: " << options.output_file << "\n"; os << "Calculation type: " << options.calculation_type << "\n"; diff --git a/power_grid_model_c/power_grid_model_cli/pgm_calculation.cpp b/power_grid_model_c/power_grid_model_cli/pgm_calculation.cpp index ad70cefd18..62738295dc 100644 --- a/power_grid_model_c/power_grid_model_cli/pgm_calculation.cpp +++ b/power_grid_model_c/power_grid_model_cli/pgm_calculation.cpp @@ -4,25 +4,48 @@ #include "cli_functions.hpp" +#include #include +#include namespace power_grid_model_cpp { +struct BatchDatasets { + explicit BatchDatasets(ClIOptions const& cli_options) { + if (!cli_options.is_batch) { + return; + } + for (auto const& [batch_file, format] : + std::views::zip(cli_options.batch_update_file, cli_options.batch_update_serialization_format)) { + datasets.emplace_back(load_dataset(batch_file, format)); + dataset_consts.emplace_back(datasets.back().dataset); + } + assert(!datasets.empty()); + for (auto it = dataset_consts.begin(); it != dataset_consts.end() - 1; ++it) { + it->set_next_cartesian_product_dimension(*(it + 1)); + } + } + + DatasetConst const& head() const { + assert(!dataset_consts.empty()); + return dataset_consts.front(); + } + + Idx batch_size{1}; + std::vector datasets; + std::vector dataset_consts; +}; + void pgm_calculation(ClIOptions const& cli_options) { // Load input dataset OwningDataset input_dataset = load_dataset(cli_options.input_file, cli_options.input_serialization_format); // Apply batch updates if provided - std::optional batch_update_dataset{std::nullopt}; - if (cli_options.is_batch) { - batch_update_dataset = - load_dataset(cli_options.batch_update_file, cli_options.batch_update_serialization_format); - } - Idx const batch_size = cli_options.is_batch ? batch_update_dataset->dataset.get_info().batch_size() : 1; + BatchDatasets const batch_datasets{cli_options}; // create result dataset - OwningDataset result_dataset{input_dataset, cli_options.output_dataset_name, cli_options.is_batch, batch_size, - cli_options.output_component_attribute_filters}; + OwningDataset result_dataset{input_dataset, cli_options.output_dataset_name, cli_options.is_batch, + batch_datasets.batch_size, cli_options.output_component_attribute_filters}; // create model Model model{cli_options.system_frequency, input_dataset.dataset}; // create calculation options @@ -38,7 +61,7 @@ void pgm_calculation(ClIOptions const& cli_options) { // perform calculation if (cli_options.is_batch) { - model.calculate(calc_options, result_dataset.dataset, batch_update_dataset->dataset); + model.calculate(calc_options, result_dataset.dataset, batch_datasets.head()); } else { model.calculate(calc_options, result_dataset.dataset); } From 91587ac1c4202954b414678aef931a5607e30f4e Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Tue, 27 Jan 2026 14:56:29 +0100 Subject: [PATCH 37/82] no lint Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- power_grid_model_c/power_grid_model_cli/cli_options.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/power_grid_model_c/power_grid_model_cli/cli_options.cpp b/power_grid_model_c/power_grid_model_cli/cli_options.cpp index 549c2ddbee..7a9e7a6bf8 100644 --- a/power_grid_model_c/power_grid_model_cli/cli_options.cpp +++ b/power_grid_model_c/power_grid_model_cli/cli_options.cpp @@ -18,11 +18,13 @@ namespace power_grid_model_cpp { using EnumMap = std::map; struct CLIPostCallback { + // NOLINTBEGIN(cppcoreguidelines-avoid-const-or-ref-data-members) ClIOptions& options; CLI::Option* msgpack_flag; CLI::Option* compact_flag; std::vector const& output_components; std::vector const& output_attributes; + // NOLINTEND(cppcoreguidelines-avoid-const-or-ref-data-members) void operator()() { set_default_values(); From fdf672f4f7ab13de543170cb10dc831bce172125 Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Tue, 27 Jan 2026 15:02:51 +0100 Subject: [PATCH 38/82] add version to c-api Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- power_grid_model_c/power_grid_model_c/CMakeLists.txt | 2 ++ .../power_grid_model_c/include/power_grid_model_c/handle.h | 7 +++++++ power_grid_model_c/power_grid_model_c/src/handle.cpp | 3 +++ 3 files changed, 12 insertions(+) diff --git a/power_grid_model_c/power_grid_model_c/CMakeLists.txt b/power_grid_model_c/power_grid_model_c/CMakeLists.txt index 6e9c575cd3..4fea23cee5 100644 --- a/power_grid_model_c/power_grid_model_c/CMakeLists.txt +++ b/power_grid_model_c/power_grid_model_c/CMakeLists.txt @@ -31,6 +31,8 @@ file( target_link_libraries(power_grid_model_c PRIVATE power_grid_model) +target_compile_definitions(power_grid_model_c PRIVATE PGM_VERSION="${PGM_VERSION}") + target_sources( power_grid_model_c PUBLIC diff --git a/power_grid_model_c/power_grid_model_c/include/power_grid_model_c/handle.h b/power_grid_model_c/power_grid_model_c/include/power_grid_model_c/handle.h index c06b5f43d4..4ca0d958eb 100644 --- a/power_grid_model_c/power_grid_model_c/include/power_grid_model_c/handle.h +++ b/power_grid_model_c/power_grid_model_c/include/power_grid_model_c/handle.h @@ -104,6 +104,13 @@ PGM_API char const** PGM_batch_errors(PGM_Handle const* handle); */ PGM_API void PGM_clear_error(PGM_Handle* handle); +/** + * @brief Get the version of the Power Grid Model library. + * + * @return A pointer to a zero-terminated string representing the version. + */ +PGM_API char const* PGM_version(void); + #ifdef __cplusplus } #endif diff --git a/power_grid_model_c/power_grid_model_c/src/handle.cpp b/power_grid_model_c/power_grid_model_c/src/handle.cpp index 1bfb5e3763..20dee456d9 100644 --- a/power_grid_model_c/power_grid_model_c/src/handle.cpp +++ b/power_grid_model_c/power_grid_model_c/src/handle.cpp @@ -18,6 +18,8 @@ using namespace power_grid_model; using power_grid_model_c::clear_error; using power_grid_model_c::compile_time_safe_cast; + +constexpr char const* version = PGM_VERSION; } // namespace // create and destroy handle @@ -52,3 +54,4 @@ char const** PGM_batch_errors(PGM_Handle const* handle) { return handle_ref.batch_errs_c_str.data(); } void PGM_clear_error(PGM_Handle* handle) { clear_error(handle); } +char const* PGM_version(void) { return version; } From 2365392904d380da2fa67368a45a541fa5200b2f Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Tue, 27 Jan 2026 15:05:44 +0100 Subject: [PATCH 39/82] test version Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- tests/native_api_tests/CMakeLists.txt | 2 ++ tests/native_api_tests/test_api_utils.cpp | 2 ++ 2 files changed, 4 insertions(+) diff --git a/tests/native_api_tests/CMakeLists.txt b/tests/native_api_tests/CMakeLists.txt index 0306b86117..216ee837c6 100644 --- a/tests/native_api_tests/CMakeLists.txt +++ b/tests/native_api_tests/CMakeLists.txt @@ -15,6 +15,8 @@ set(PROJECT_SOURCES add_executable(power_grid_model_api_tests ${PROJECT_SOURCES}) +target_compile_definitions(power_grid_model_api_tests PRIVATE PGM_VERSION="${PGM_VERSION}") + target_link_libraries( power_grid_model_api_tests PRIVATE diff --git a/tests/native_api_tests/test_api_utils.cpp b/tests/native_api_tests/test_api_utils.cpp index 2818b22b93..8b200f58a8 100644 --- a/tests/native_api_tests/test_api_utils.cpp +++ b/tests/native_api_tests/test_api_utils.cpp @@ -25,4 +25,6 @@ TEST_CASE("API Utils") { CHECK(is_nan(nan_value())); } } + +TEST_CASE("Check version") { CHECK(std::string{PGM_VERSION} == std::string{PGM_version()}); } } // namespace power_grid_model_cpp From e1711ecc5174fb53c7976458ac20c0bbba5d8d36 Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Tue, 27 Jan 2026 15:07:50 +0100 Subject: [PATCH 40/82] add version Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- power_grid_model_c/power_grid_model_cli/cli_options.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/power_grid_model_c/power_grid_model_cli/cli_options.cpp b/power_grid_model_c/power_grid_model_cli/cli_options.cpp index 7a9e7a6bf8..2943709127 100644 --- a/power_grid_model_c/power_grid_model_cli/cli_options.cpp +++ b/power_grid_model_c/power_grid_model_cli/cli_options.cpp @@ -127,7 +127,8 @@ struct CLIPostCallback { }; CLIResult parse_cli_options(int argc, char** argv, ClIOptions& options) { - CLI::App app{"Power Grid Model CLI"}; + std::string const version_str = std::string("Power Grid Model CLI\n Version: ") + PGM_version(); + CLI::App app{version_str}; CLI::Validator existing_parent_dir_validator{ [](std::string& input) { From 6f0785796f45ae9db2b5f451b3a2e89b08da7c48 Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Tue, 27 Jan 2026 15:14:45 +0100 Subject: [PATCH 41/82] add version to cli Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- power_grid_model_c/power_grid_model_cli/cli_options.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/power_grid_model_c/power_grid_model_cli/cli_options.cpp b/power_grid_model_c/power_grid_model_cli/cli_options.cpp index 2943709127..33cb564c71 100644 --- a/power_grid_model_c/power_grid_model_cli/cli_options.cpp +++ b/power_grid_model_c/power_grid_model_cli/cli_options.cpp @@ -141,6 +141,7 @@ CLIResult parse_cli_options(int argc, char** argv, ClIOptions& options) { }, "ExistingParentDirectory"}; + app.set_version_flag("-v,--version", PGM_version()); app.add_option("-i,--input", options.input_file, "Input file path")->required()->check(CLI::ExistingFile); app.add_option("-b,--batch-update", options.batch_update_file, "Batch update file path. Can be specified multiple times.\n" From b32df3413fcc01414aa254e5d8c3db6e5363a56a Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Tue, 27 Jan 2026 15:20:56 +0100 Subject: [PATCH 42/82] add version in python Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- src/power_grid_model/__init__.py | 4 ++++ src/power_grid_model/_core/power_grid_core.py | 13 ++++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/power_grid_model/__init__.py b/src/power_grid_model/__init__.py index e4dc65f431..83c2fb38d9 100644 --- a/src/power_grid_model/__init__.py +++ b/src/power_grid_model/__init__.py @@ -5,6 +5,7 @@ """Power Grid Model""" from power_grid_model._core.dataset_definitions import ComponentType, DatasetType +from power_grid_model._core.power_grid_core import pgm_version from power_grid_model._core.power_grid_meta import ( attribute_dtype, attribute_empty_value, @@ -29,6 +30,8 @@ ) from power_grid_model.typing import ComponentAttributeMapping +__version__ = pgm_version + __all__ = [ "AngleMeasurementType", "Branch3Side", @@ -47,6 +50,7 @@ "ShortCircuitVoltageScaling", "TapChangingStrategy", "WindingType", + "__version__", "attribute_dtype", "attribute_empty_value", "initialize_array", diff --git a/src/power_grid_model/_core/power_grid_core.py b/src/power_grid_model/_core/power_grid_core.py index 3ad3170829..260ff057c6 100644 --- a/src/power_grid_model/_core/power_grid_core.py +++ b/src/power_grid_model/_core/power_grid_core.py @@ -167,8 +167,8 @@ def make_c_binding(func: Callable): c_restype = c_size_t # set argument in dll # mostly with handle pointer, except destroy function - is_destroy_func = "destroy" in name - if is_destroy_func: + is_func_without_handle = ("destroy" in name) or ("version" in name) + if is_func_without_handle: getattr(_CDLL, f"PGM_{name}").argtypes = c_argtypes else: getattr(_CDLL, f"PGM_{name}").argtypes = [HandlePtr, *c_argtypes] @@ -176,7 +176,7 @@ def make_c_binding(func: Callable): # binding function def cbind_func(self, *args, **kwargs): - c_inputs = [] if "destroy" in name else [self._handle] + c_inputs = [] if is_func_without_handle else [self._handle] args = chain(args, (kwargs[key] for key in py_argnames[len(args) :])) for arg in args: if isinstance(arg, str): @@ -241,6 +241,10 @@ def batch_errors(self) -> CStrPtr: # type: ignore[empty-body, valid-type] # ty def clear_error(self) -> None: # type: ignore[empty-body] pass # pragma: no cover + @make_c_binding + def version(self) -> str: # type: ignore[empty-body] + pass # pragma: no cover + @make_c_binding def meta_n_datasets(self) -> int: # type: ignore[empty-body] pass # pragma: no cover @@ -575,3 +579,6 @@ def get_power_grid_core() -> PowerGridCore: except AttributeError: _thread_local_data.power_grid_core = PowerGridCore() return _thread_local_data.power_grid_core + + +pgm_version = get_power_grid_core().version() From 8f4db9c8ac5e8d32d18f0c5b5f0768927c9d1bb1 Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Tue, 27 Jan 2026 15:24:31 +0100 Subject: [PATCH 43/82] east const Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- power_grid_model_c/power_grid_model_cli/cli_options.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/power_grid_model_c/power_grid_model_cli/cli_options.cpp b/power_grid_model_c/power_grid_model_cli/cli_options.cpp index 33cb564c71..748af35a04 100644 --- a/power_grid_model_c/power_grid_model_cli/cli_options.cpp +++ b/power_grid_model_c/power_grid_model_cli/cli_options.cpp @@ -219,7 +219,7 @@ CLIResult parse_cli_options(int argc, char** argv, ClIOptions& options) { try { app.parse(argc, argv); - } catch (const CLI::ParseError& e) { + } catch (CLI::ParseError const& e) { return {.exit_code = app.exit(e), .should_exit = true}; } From 88c1f9edbd6bbbdefbf4327d6a172584a7600bf7 Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Wed, 28 Jan 2026 14:35:27 +0100 Subject: [PATCH 44/82] add cli python shim Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- .../_core/power_grid_model_c/run_pgm_cli.py | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 src/power_grid_model/_core/power_grid_model_c/run_pgm_cli.py diff --git a/src/power_grid_model/_core/power_grid_model_c/run_pgm_cli.py b/src/power_grid_model/_core/power_grid_model_c/run_pgm_cli.py new file mode 100644 index 0000000000..1424dde74c --- /dev/null +++ b/src/power_grid_model/_core/power_grid_model_c/run_pgm_cli.py @@ -0,0 +1,61 @@ +# SPDX-FileCopyrightText: Contributors to the Power Grid Model project +# +# SPDX-License-Identifier: MPL-2.0 + +import os +import platform +import sys +from importlib.resources import files +from pathlib import Path + + +def get_pgm_cli_path() -> Path: + """ + Returns the path to PGM dynamic library. + """ + package_dir = Path(str(files(__package__))) + bin_dir = package_dir / "bin" + platform_name = platform.uname().system + + # determine DLL file name + if platform_name == "Windows": + exe_file = Path("power-grid-model.exe") + elif platform_name == "Darwin" or platform.system() == "Linux": + exe_file = Path("power-grid-model") + else: + raise NotImplementedError(f"Unsupported platform: {platform_name}") + bin_path = bin_dir / exe_file + + # determine editable path to the DLL + # __file__ + # -> power_grid_model_c (..) + # -> _core (..) + # -> power_grid_model (..) + # -> src (..) + # -> repo_root (..) + # -> build + # -> bin + editable_dir = Path(__file__).resolve().parent.parent.parent.parent.parent / "build" / "bin" + editable_bin_path = editable_dir / exe_file + + # first try to load from bin_path + # then editable_bin_path + # then if it is inside conda, this Python shim should never be called, instead user calls the exe directly + # then for anything else, raise an error + if bin_path.exists(): + final_bin_path = bin_path + elif editable_bin_path.exists(): + final_bin_path = editable_bin_path + elif os.environ.get("CONDA_PREFIX"): + raise ImportError( + "PGM CLI Python shim should not be called inside conda environment. Please call the executable directly." + ) + else: + raise ImportError(f"Could not find executable: {exe_file}. Your PGM installation may be broken.") + + return final_bin_path + + +def main(): + exe_path = get_pgm_cli_path() + os.execv(str(exe_path), [str(exe_path), *sys.argv[1:]]) # noqa: S606 From 33a51a9cd6ad67bf1138fee163791f94245f05fc Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Wed, 28 Jan 2026 14:38:37 +0100 Subject: [PATCH 45/82] add script Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 2ee3cafba6..1d7b20b910 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -80,6 +80,9 @@ Discussion = "https://github.com/orgs/PowerGridModel/discussions" [project.entry-points."cmake.root"] power_grid_model = "power_grid_model._core.power_grid_model_c" +[project.scripts] +power-grid-model = "power_grid_model._core.power_grid_model_c.run_pgm_cli:main" + [tool.scikit-build] logging.level = "INFO" From 6143364397dc62473105a684c2976c47379c71b0 Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Wed, 28 Jan 2026 14:45:51 +0100 Subject: [PATCH 46/82] modify names Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- .../_core/power_grid_model_c/run_pgm_cli.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/power_grid_model/_core/power_grid_model_c/run_pgm_cli.py b/src/power_grid_model/_core/power_grid_model_c/run_pgm_cli.py index 1424dde74c..45e8e3c8dd 100644 --- a/src/power_grid_model/_core/power_grid_model_c/run_pgm_cli.py +++ b/src/power_grid_model/_core/power_grid_model_c/run_pgm_cli.py @@ -11,13 +11,13 @@ def get_pgm_cli_path() -> Path: """ - Returns the path to PGM dynamic library. + Returns the path to PGM CLI executable. """ package_dir = Path(str(files(__package__))) bin_dir = package_dir / "bin" platform_name = platform.uname().system - # determine DLL file name + # determine executable file name if platform_name == "Windows": exe_file = Path("power-grid-model.exe") elif platform_name == "Darwin" or platform.system() == "Linux": @@ -26,7 +26,7 @@ def get_pgm_cli_path() -> Path: raise NotImplementedError(f"Unsupported platform: {platform_name}") bin_path = bin_dir / exe_file - # determine editable path to the DLL + # determine editable path to the executable # __file__ # -> power_grid_model_c (..) # -> _core (..) From b2c02c3bf515767afb0ad5baf47ec1fed561f27b Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Thu, 29 Jan 2026 13:15:36 +0100 Subject: [PATCH 47/82] add cli version check Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- tests/unit/test_cli.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 tests/unit/test_cli.py diff --git a/tests/unit/test_cli.py b/tests/unit/test_cli.py new file mode 100644 index 0000000000..9d3c3eb269 --- /dev/null +++ b/tests/unit/test_cli.py @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: Contributors to the Power Grid Model project +# +# SPDX-License-Identifier: MPL-2.0 + + +import subprocess + +from power_grid_model import __version__ + + +def test_cli_version(): + result = subprocess.run(["power-grid-model", "--version"], capture_output=True, text=True, check=True) + assert __version__ in result.stdout From b393199ba8ca8d83c880bf13dc1df108394c7d0f Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Thu, 29 Jan 2026 13:24:34 +0100 Subject: [PATCH 48/82] add three part Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- power_grid_model_c/power_grid_model_cli/cli_functions.hpp | 2 ++ power_grid_model_c/power_grid_model_cli/cli_options.cpp | 2 ++ power_grid_model_c/power_grid_model_cli/main.cpp | 6 ++++-- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/power_grid_model_c/power_grid_model_cli/cli_functions.hpp b/power_grid_model_c/power_grid_model_cli/cli_functions.hpp index 1ba750bb7a..4210a7440f 100644 --- a/power_grid_model_c/power_grid_model_cli/cli_functions.hpp +++ b/power_grid_model_c/power_grid_model_cli/cli_functions.hpp @@ -46,6 +46,8 @@ struct ClIOptions { MetaDataset const* output_dataset{nullptr}; std::map> output_component_attribute_filters; + bool verbose{false}; + friend std::ostream& operator<<(std::ostream& os, ClIOptions const& options); }; diff --git a/power_grid_model_c/power_grid_model_cli/cli_options.cpp b/power_grid_model_c/power_grid_model_cli/cli_options.cpp index 748af35a04..f0ad0ab5d8 100644 --- a/power_grid_model_c/power_grid_model_cli/cli_options.cpp +++ b/power_grid_model_c/power_grid_model_cli/cli_options.cpp @@ -210,6 +210,7 @@ CLIResult parse_cli_options(int argc, char** argv, ClIOptions& options) { app.add_option("--oa,--output-attribute", output_attributes, "Filter output to only include specified attributes, in the format `component.attribute` (can be " "specified multiple times)"); + app.add_flag("--verbose", options.verbose, "Enable verbose output"); app.callback(CLIPostCallback{.options = options, .msgpack_flag = msgpack_flag, @@ -247,6 +248,7 @@ std::ostream& operator<<(std::ostream& os, ClIOptions const& options) { os << "Use msgpack output serialization: " << options.use_msgpack_output_serialization << "\n"; os << "Output JSON indent: " << options.output_json_indent << "\n"; os << "Use compact serialization: " << options.use_compact_serialization << "\n"; + os << "Verbose: " << options.verbose << "\n"; return os; } diff --git a/power_grid_model_c/power_grid_model_cli/main.cpp b/power_grid_model_c/power_grid_model_cli/main.cpp index dad9f3287e..c69a819d24 100644 --- a/power_grid_model_c/power_grid_model_cli/main.cpp +++ b/power_grid_model_c/power_grid_model_cli/main.cpp @@ -12,11 +12,13 @@ using namespace power_grid_model_cpp; int main(int argc, char** argv) { ClIOptions cli_options; - if (auto parse_result = parse_cli_options(argc, argv, cli_options); parse_result) { + if (auto const parse_result = parse_cli_options(argc, argv, cli_options); parse_result) { return parse_result.exit_code; } - std::cout << cli_options << std::endl; + if (cli_options.verbose) { + std::cout << cli_options << std::endl; + } try { pgm_calculation(cli_options); From f71abcd68496c1d7d858499125eef43a2d207500 Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Thu, 29 Jan 2026 13:35:54 +0100 Subject: [PATCH 49/82] add cli tests Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- .../power_grid_model_cli/CMakeLists.txt | 19 ++++++++------- tests/CMakeLists.txt | 1 + tests/cpp_cli_tests/CMakeLists.txt | 24 +++++++++++++++++++ tests/cpp_cli_tests/test_entry_point.cpp | 9 +++++++ 4 files changed, 44 insertions(+), 9 deletions(-) create mode 100644 tests/cpp_cli_tests/CMakeLists.txt create mode 100644 tests/cpp_cli_tests/test_entry_point.cpp diff --git a/power_grid_model_c/power_grid_model_cli/CMakeLists.txt b/power_grid_model_c/power_grid_model_cli/CMakeLists.txt index 7d4e9ff464..279d232703 100644 --- a/power_grid_model_c/power_grid_model_cli/CMakeLists.txt +++ b/power_grid_model_c/power_grid_model_cli/CMakeLists.txt @@ -2,31 +2,32 @@ # # SPDX-License-Identifier: MPL-2.0 -add_executable(power-grid-model +add_executable(power_grid_model_cli main.cpp cli_options.cpp pgm_calculation.cpp ) -target_link_libraries(power-grid-model PRIVATE power_grid_model_c) -target_link_libraries(power-grid-model PRIVATE power_grid_model_cpp) -target_link_libraries(power-grid-model PRIVATE CLI11::CLI11) +target_link_libraries(power_grid_model_cli PRIVATE power_grid_model_c) +target_link_libraries(power_grid_model_cli PRIVATE power_grid_model_cpp) +target_link_libraries(power_grid_model_cli PRIVATE CLI11::CLI11) -set_property(TARGET power-grid-model PROPERTY INSTALL_RPATH_USE_LINK_PATH FALSE) +set_property(TARGET power_grid_model_cli PROPERTY INSTALL_RPATH_USE_LINK_PATH FALSE) +set_property(TARGET power_grid_model_cli PROPERTY OUTPUT_NAME "power-grid-model") if(APPLE) - set_property(TARGET power-grid-model PROPERTY INSTALL_RPATH "@loader_path/../${CMAKE_INSTALL_LIBDIR}") + set_property(TARGET power_grid_model_cli PROPERTY INSTALL_RPATH "@loader_path/../${CMAKE_INSTALL_LIBDIR}") elseif(UNIX) # Linux, BSD (not Windows/macOS) - set_property(TARGET power-grid-model PROPERTY INSTALL_RPATH "$ORIGIN/../${CMAKE_INSTALL_LIBDIR}") + set_property(TARGET power_grid_model_cli PROPERTY INSTALL_RPATH "$ORIGIN/../${CMAKE_INSTALL_LIBDIR}") endif() target_compile_definitions( - power-grid-model + power_grid_model_cli PRIVATE PGM_ENABLE_EXPERIMENTAL ) install( - TARGETS power-grid-model + TARGETS power_grid_model_cli EXPORT power_grid_modelTargets COMPONENT power_grid_model ) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6e983b5a03..98a6d8722c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -10,3 +10,4 @@ add_subdirectory("native_api_tests") add_subdirectory("cpp_unit_tests") add_subdirectory("cpp_validation_tests") add_subdirectory("benchmark_cpp") +add_subdirectory("cpp_cli_tests") diff --git a/tests/cpp_cli_tests/CMakeLists.txt b/tests/cpp_cli_tests/CMakeLists.txt new file mode 100644 index 0000000000..9580e4cbcc --- /dev/null +++ b/tests/cpp_cli_tests/CMakeLists.txt @@ -0,0 +1,24 @@ +# SPDX-FileCopyrightText: Contributors to the Power Grid Model project +# +# SPDX-License-Identifier: MPL-2.0 + +set(PROJECT_SOURCES "test_entry_point.cpp") + +add_executable(power_grid_model_cli_tests ${PROJECT_SOURCES}) + +target_link_libraries( + power_grid_model_cli_tests + PRIVATE + power_grid_model_cpp + doctest::doctest + nlohmann_json + nlohmann_json::nlohmann_json +) +target_compile_definitions( + power_grid_model_cli_tests + PRIVATE + POWER_GRID_MODEL_CLI_EXECUTABLE="$" +) +add_dependencies(power_grid_model_cli_tests power_grid_model_cli) + +doctest_discover_tests(power_grid_model_cli_tests) diff --git a/tests/cpp_cli_tests/test_entry_point.cpp b/tests/cpp_cli_tests/test_entry_point.cpp new file mode 100644 index 0000000000..020ee88a48 --- /dev/null +++ b/tests/cpp_cli_tests/test_entry_point.cpp @@ -0,0 +1,9 @@ +// SPDX-FileCopyrightText: Contributors to the Power Grid Model project +// +// SPDX-License-Identifier: MPL-2.0 + +// main cpp file + +#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN + +#include From 0b90c9b87744349f7a8c41fd94a44547b838d146 Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Thu, 29 Jan 2026 13:51:24 +0100 Subject: [PATCH 50/82] start cli test Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- tests/cpp_cli_tests/CMakeLists.txt | 2 +- tests/cpp_cli_tests/test_cli.cpp | 52 ++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 tests/cpp_cli_tests/test_cli.cpp diff --git a/tests/cpp_cli_tests/CMakeLists.txt b/tests/cpp_cli_tests/CMakeLists.txt index 9580e4cbcc..e8474339c5 100644 --- a/tests/cpp_cli_tests/CMakeLists.txt +++ b/tests/cpp_cli_tests/CMakeLists.txt @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: MPL-2.0 -set(PROJECT_SOURCES "test_entry_point.cpp") +set(PROJECT_SOURCES "test_entry_point.cpp" "test_cli.cpp") add_executable(power_grid_model_cli_tests ${PROJECT_SOURCES}) diff --git a/tests/cpp_cli_tests/test_cli.cpp b/tests/cpp_cli_tests/test_cli.cpp new file mode 100644 index 0000000000..caa7066d0f --- /dev/null +++ b/tests/cpp_cli_tests/test_cli.cpp @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: Contributors to the Power Grid Model project +// +// SPDX-License-Identifier: MPL-2.0 + +#define PGM_ENABLE_EXPERIMENTAL + +#include +#include +#include +#include +#include +#include +#include + +namespace power_grid_model_cpp { + +namespace fs = std::filesystem; + +constexpr std::string_view cli_executable = POWER_GRID_MODEL_CLI_EXECUTABLE; + +fs::path get_cli_tmp_path() { + // Get the system temp directory + fs::path const tmpdir = fs::temp_directory_path(); + fs::path const cli_test_dir = tmpdir / "pgm_cli_test"; + + // Remove the dir if it exists (including contents) + if (fs::exists(cli_test_dir)) { + fs::remove_all(cli_test_dir); + } + + // Create the empty directory + if (!fs::create_directory(cli_test_dir)) { + throw std::runtime_error("Failed to create cli_test temp directory"); + } + + // Return the path + return cli_test_dir; +} + +TEST_CASE("Test CLI version") { + std::string const command = std::string{cli_executable} + " --version" + " > version.txt"; + int ret = std::system(command.c_str()); + REQUIRE(ret == 0); + fs::path const version_file = get_cli_tmp_path() / "version.txt"; + std::ifstream version_ifs("version.txt"); + REQUIRE(version_ifs); + std::string version_line; + std::getline(version_ifs, version_line); + CHECK(version_line == PGM_version()); +} + +} // namespace power_grid_model_cpp From 072ea2033c4b152b6ceaba6c68b7644f6f8654ef Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Thu, 29 Jan 2026 14:58:22 +0100 Subject: [PATCH 51/82] test version Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- tests/cpp_cli_tests/test_cli.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/tests/cpp_cli_tests/test_cli.cpp b/tests/cpp_cli_tests/test_cli.cpp index caa7066d0f..74aadd6d0c 100644 --- a/tests/cpp_cli_tests/test_cli.cpp +++ b/tests/cpp_cli_tests/test_cli.cpp @@ -21,7 +21,12 @@ constexpr std::string_view cli_executable = POWER_GRID_MODEL_CLI_EXECUTABLE; fs::path get_cli_tmp_path() { // Get the system temp directory fs::path const tmpdir = fs::temp_directory_path(); - fs::path const cli_test_dir = tmpdir / "pgm_cli_test"; + // Return the path + return tmpdir / "pgm_cli_test"; +} + +void clear_and_create_cli_tmp_path() { + fs::path const cli_test_dir = get_cli_tmp_path(); // Remove the dir if it exists (including contents) if (fs::exists(cli_test_dir)) { @@ -32,17 +37,15 @@ fs::path get_cli_tmp_path() { if (!fs::create_directory(cli_test_dir)) { throw std::runtime_error("Failed to create cli_test temp directory"); } - - // Return the path - return cli_test_dir; } TEST_CASE("Test CLI version") { - std::string const command = std::string{cli_executable} + " --version" + " > version.txt"; + clear_and_create_cli_tmp_path(); + fs::path const version_file = get_cli_tmp_path() / "version.txt"; + std::string const command = std::string{cli_executable} + " --version" + " > " + version_file.string(); int ret = std::system(command.c_str()); REQUIRE(ret == 0); - fs::path const version_file = get_cli_tmp_path() / "version.txt"; - std::ifstream version_ifs("version.txt"); + std::ifstream version_ifs(version_file); REQUIRE(version_ifs); std::string version_line; std::getline(version_ifs, version_line); From 0d961ac7a7c8fb27efc3f9e50a696fa5e385077d Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Thu, 29 Jan 2026 15:08:31 +0100 Subject: [PATCH 52/82] add input data Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- tests/cpp_cli_tests/test_cli.cpp | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/cpp_cli_tests/test_cli.cpp b/tests/cpp_cli_tests/test_cli.cpp index 74aadd6d0c..e61b9b773f 100644 --- a/tests/cpp_cli_tests/test_cli.cpp +++ b/tests/cpp_cli_tests/test_cli.cpp @@ -14,8 +14,29 @@ namespace power_grid_model_cpp { +namespace { +using namespace std::string_literals; namespace fs = std::filesystem; +// input +auto const input_json = R"json({ + "version": "1.0", + "type": "input", + "is_batch": false, + "attributes": {}, + "data": { + "sym_load": [ + {"id": 2, "node": 0, "status": 1, "type": 0, "p_specified": 0, "q_specified": 0} + ], + "source": [ + {"id": 1, "node": 0, "status": 1, "u_ref": 1, "sk": 1e20} + ], + "node": [ + {"id": 0, "u_rated": 10e3} + ] + } +})json"s; + constexpr std::string_view cli_executable = POWER_GRID_MODEL_CLI_EXECUTABLE; fs::path get_cli_tmp_path() { @@ -25,6 +46,8 @@ fs::path get_cli_tmp_path() { return tmpdir / "pgm_cli_test"; } +fs::path get_cli_input_path() { return get_cli_tmp_path() / "input.json"; } + void clear_and_create_cli_tmp_path() { fs::path const cli_test_dir = get_cli_tmp_path(); @@ -39,6 +62,15 @@ void clear_and_create_cli_tmp_path() { } } +void save_input_data() { + fs::path const input_file = get_cli_input_path(); + std::ofstream ofs(input_file); + ofs << input_json; + ofs.close(); +} + +} // namespace + TEST_CASE("Test CLI version") { clear_and_create_cli_tmp_path(); fs::path const version_file = get_cli_tmp_path() / "version.txt"; From e8ff08bbaa010a0ebe930f790fdf4d64239c75ad Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Thu, 29 Jan 2026 15:10:02 +0100 Subject: [PATCH 53/82] save names Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- tests/cpp_cli_tests/test_cli.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/cpp_cli_tests/test_cli.cpp b/tests/cpp_cli_tests/test_cli.cpp index e61b9b773f..f3acb50425 100644 --- a/tests/cpp_cli_tests/test_cli.cpp +++ b/tests/cpp_cli_tests/test_cli.cpp @@ -39,17 +39,17 @@ auto const input_json = R"json({ constexpr std::string_view cli_executable = POWER_GRID_MODEL_CLI_EXECUTABLE; -fs::path get_cli_tmp_path() { +fs::path tmp_path() { // Get the system temp directory fs::path const tmpdir = fs::temp_directory_path(); // Return the path return tmpdir / "pgm_cli_test"; } -fs::path get_cli_input_path() { return get_cli_tmp_path() / "input.json"; } +fs::path input_path() { return tmp_path() / "input.json"; } -void clear_and_create_cli_tmp_path() { - fs::path const cli_test_dir = get_cli_tmp_path(); +void clear_and_create_tmp_path() { + fs::path const cli_test_dir = tmp_path(); // Remove the dir if it exists (including contents) if (fs::exists(cli_test_dir)) { @@ -63,7 +63,7 @@ void clear_and_create_cli_tmp_path() { } void save_input_data() { - fs::path const input_file = get_cli_input_path(); + fs::path const input_file = input_path(); std::ofstream ofs(input_file); ofs << input_json; ofs.close(); @@ -72,8 +72,8 @@ void save_input_data() { } // namespace TEST_CASE("Test CLI version") { - clear_and_create_cli_tmp_path(); - fs::path const version_file = get_cli_tmp_path() / "version.txt"; + clear_and_create_tmp_path(); + fs::path const version_file = tmp_path() / "version.txt"; std::string const command = std::string{cli_executable} + " --version" + " > " + version_file.string(); int ret = std::system(command.c_str()); REQUIRE(ret == 0); From 0d8e7fc990d3f89ca933c66db0ad5b067593a664 Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Fri, 30 Jan 2026 13:09:02 +0100 Subject: [PATCH 54/82] save data Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- tests/cpp_cli_tests/test_cli.cpp | 57 ++++++++++++++++++++++++-------- 1 file changed, 44 insertions(+), 13 deletions(-) diff --git a/tests/cpp_cli_tests/test_cli.cpp b/tests/cpp_cli_tests/test_cli.cpp index f3acb50425..fb811e451b 100644 --- a/tests/cpp_cli_tests/test_cli.cpp +++ b/tests/cpp_cli_tests/test_cli.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -48,6 +49,8 @@ fs::path tmp_path() { fs::path input_path() { return tmp_path() / "input.json"; } +fs::path stdout_path() { return tmp_path() / "stdout.txt"; } + void clear_and_create_tmp_path() { fs::path const cli_test_dir = tmp_path(); @@ -62,26 +65,54 @@ void clear_and_create_tmp_path() { } } -void save_input_data() { - fs::path const input_file = input_path(); - std::ofstream ofs(input_file); - ofs << input_json; - ofs.close(); +void save_data(std::string_view json_data, fs::path const& path, PGM_SerializationFormat format) { + std::ofstream ofs(path); + if (!ofs) { + throw std::runtime_error("Failed to open file for writing: " + path.string()); + } + if (format == PGM_json) { + ofs << json_data; + } else { + nlohmann::json const j = nlohmann::json::parse(json_data); + std::string msgpack_buffer; + nlohmann::json::to_msgpack(j, msgpack_buffer); + ofs << msgpack_buffer; + } +} + +void prepare_data() { + clear_and_create_tmp_path(); + save_data(input_json, input_path(), PGM_json); +} + +std::string read_stdout_content() { + fs::path const file_name = stdout_path(); + std::ifstream version_ifs(file_name); + REQUIRE(version_ifs); + + // Get file size + version_ifs.seekg(0, std::ios::end); + std::streamsize size = version_ifs.tellg(); + version_ifs.seekg(0, std::ios::beg); + + // Read the entire file + std::string file_content(size, '\0'); + version_ifs.read(file_content.data(), size); + return file_content; } } // namespace TEST_CASE("Test CLI version") { - clear_and_create_tmp_path(); - fs::path const version_file = tmp_path() / "version.txt"; - std::string const command = std::string{cli_executable} + " --version" + " > " + version_file.string(); + prepare_data(); + std::string const command = std::string{cli_executable} + " --version" + " > " + stdout_path().string(); int ret = std::system(command.c_str()); + std::string const file_content = read_stdout_content(); + INFO("CLI stdout content: ", file_content); REQUIRE(ret == 0); - std::ifstream version_ifs(version_file); - REQUIRE(version_ifs); - std::string version_line; - std::getline(version_ifs, version_line); - CHECK(version_line == PGM_version()); + // Extract the first line + std::string const first_line = file_content.substr(0, file_content.find('\n')); + CHECK(first_line == PGM_version()); } } // namespace power_grid_model_cpp From 9a9e110ec2d9c4eb219f13764b9c141c7c7498cf Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Fri, 30 Jan 2026 13:13:04 +0100 Subject: [PATCH 55/82] binary mode Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- tests/cpp_cli_tests/test_cli.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/cpp_cli_tests/test_cli.cpp b/tests/cpp_cli_tests/test_cli.cpp index fb811e451b..aaf16e56f7 100644 --- a/tests/cpp_cli_tests/test_cli.cpp +++ b/tests/cpp_cli_tests/test_cli.cpp @@ -66,7 +66,7 @@ void clear_and_create_tmp_path() { } void save_data(std::string_view json_data, fs::path const& path, PGM_SerializationFormat format) { - std::ofstream ofs(path); + std::ofstream ofs(path, std::ios::binary); if (!ofs) { throw std::runtime_error("Failed to open file for writing: " + path.string()); } From 8ec1cdf1bb7f8f4a7d307ecee496a3934d438ff1 Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Fri, 30 Jan 2026 13:14:43 +0100 Subject: [PATCH 56/82] use const expr Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- tests/cpp_cli_tests/test_cli.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/cpp_cli_tests/test_cli.cpp b/tests/cpp_cli_tests/test_cli.cpp index aaf16e56f7..7c8f17b453 100644 --- a/tests/cpp_cli_tests/test_cli.cpp +++ b/tests/cpp_cli_tests/test_cli.cpp @@ -16,11 +16,10 @@ namespace power_grid_model_cpp { namespace { -using namespace std::string_literals; namespace fs = std::filesystem; // input -auto const input_json = R"json({ +constexpr std::string_view input_json = R"json({ "version": "1.0", "type": "input", "is_batch": false, @@ -36,7 +35,7 @@ auto const input_json = R"json({ {"id": 0, "u_rated": 10e3} ] } -})json"s; +})json"; constexpr std::string_view cli_executable = POWER_GRID_MODEL_CLI_EXECUTABLE; From c078bcc5d31191a39beed116a909984deb918e35 Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Fri, 30 Jan 2026 13:34:06 +0100 Subject: [PATCH 57/82] have address issue Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- tests/cpp_cli_tests/test_cli.cpp | 118 ++++++++++++++++++++++++++++--- 1 file changed, 107 insertions(+), 11 deletions(-) diff --git a/tests/cpp_cli_tests/test_cli.cpp b/tests/cpp_cli_tests/test_cli.cpp index 7c8f17b453..975c3135ea 100644 --- a/tests/cpp_cli_tests/test_cli.cpp +++ b/tests/cpp_cli_tests/test_cli.cpp @@ -18,7 +18,6 @@ namespace power_grid_model_cpp { namespace { namespace fs = std::filesystem; -// input constexpr std::string_view input_json = R"json({ "version": "1.0", "type": "input", @@ -37,6 +36,93 @@ constexpr std::string_view input_json = R"json({ } })json"; +constexpr std::string_view batch_u_ref_json = R"json({ + "version": "1.0", + "type": "update", + "is_batch": true, + "attributes": {}, + "data": [ + { + "source": [ + {"u_ref": 0.9} + ] + }, + { + "source": [ + {"u_ref": 1.0} + ] + }, + { + "source": [ + {"u_ref": 1.1} + ] + } + ] +})json"; + +constexpr std::string_view batch_p_json = R"json({ + "version": "1.0", + "type": "update", + "is_batch": true, + "attributes": { "sym_load": ["p_specified"] }, + "data": [ + { + "sym_load": [ + [1e6] + ] + }, + { + "sym_load": [ + [2e6] + ] + }, + { + "sym_load": [ + [3e6] + ] + }, + { + "sym_load": [ + [4e6] + ] + } + ] +})json"; + +constexpr std::string_view batch_q_json = R"json({ + "version": "1.0", + "type": "update", + "is_batch": true, + "attributes": {}, + "data": [ + { + "sym_load": [ + {"q_specified": 0.1e6} + ] + }, + { + "sym_load": [ + {"q_specified": 0.2e6} + ] + }, + { + "sym_load": [ + {"q_specified": 0.3e6} + ] + }, + { + "sym_load": [ + {"q_specified": 0.4e6} + ] + }, + { + "sym_load": [ + {"q_specified": 0.5e6} + ] + } + ] +})json"; + constexpr std::string_view cli_executable = POWER_GRID_MODEL_CLI_EXECUTABLE; fs::path tmp_path() { @@ -47,6 +133,10 @@ fs::path tmp_path() { } fs::path input_path() { return tmp_path() / "input.json"; } +fs::path batch_u_ref_path() { return tmp_path() / "batch_u_ref.json"; } +fs::path batch_p_path() { return tmp_path() / "batch_p.json"; } +fs::path batch_q_path() { return tmp_path() / "batch_q.json"; } +fs::path batch_p_path_msgpack() { return tmp_path() / "batch_p.pgmb"; } fs::path stdout_path() { return tmp_path() / "stdout.txt"; } @@ -65,23 +155,29 @@ void clear_and_create_tmp_path() { } void save_data(std::string_view json_data, fs::path const& path, PGM_SerializationFormat format) { - std::ofstream ofs(path, std::ios::binary); - if (!ofs) { - throw std::runtime_error("Failed to open file for writing: " + path.string()); - } - if (format == PGM_json) { - ofs << json_data; + if (std::ofstream ofs(path, std::ios::binary); ofs) { + if (format == PGM_json) { + ofs << json_data; + } else { + nlohmann::json const j = nlohmann::json::parse(json_data); + std::string msgpack_buffer; + nlohmann::json::to_msgpack(j, msgpack_buffer); + ofs << msgpack_buffer; + } } else { - nlohmann::json const j = nlohmann::json::parse(json_data); - std::string msgpack_buffer; - nlohmann::json::to_msgpack(j, msgpack_buffer); - ofs << msgpack_buffer; + throw std::runtime_error("Failed to open file for writing: " + path.string()); } + // try to read the file, discard results + load_dataset(path, format, true); } void prepare_data() { clear_and_create_tmp_path(); save_data(input_json, input_path(), PGM_json); + save_data(batch_u_ref_json, batch_u_ref_path(), PGM_json); + save_data(batch_p_json, batch_p_path(), PGM_json); + save_data(batch_p_json, batch_p_path_msgpack(), PGM_msgpack); + save_data(batch_q_json, batch_q_path(), PGM_json); } std::string read_stdout_content() { From 9da03d0007c4ec5f439a2ab540aa91ddcbfe4aba Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Fri, 30 Jan 2026 13:42:45 +0100 Subject: [PATCH 58/82] fix a bug on read-after-free Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- .../include/power_grid_model_cpp/serialization.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/serialization.hpp b/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/serialization.hpp index aa3ac5918b..57298875fc 100644 --- a/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/serialization.hpp +++ b/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/serialization.hpp @@ -111,7 +111,8 @@ inline OwningDataset load_dataset(std::filesystem::path const& path, PGM_Seriali return buffer; }; - Deserializer deserializer{read_file(path), serialization_format}; + auto const file_content = read_file(path); + Deserializer deserializer{file_content, serialization_format}; auto& writable_dataset = deserializer.get_dataset(); OwningDataset dataset{writable_dataset, enable_columnar_buffers}; deserializer.parse_to_buffer(); From 25949194a1a579bc6b5c23e886b81c59c3d3f79a Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Fri, 30 Jan 2026 16:03:23 +0100 Subject: [PATCH 59/82] use ref Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- .../power_grid_model_cli/cli_options.cpp | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/power_grid_model_c/power_grid_model_cli/cli_options.cpp b/power_grid_model_c/power_grid_model_cli/cli_options.cpp index f0ad0ab5d8..0006649376 100644 --- a/power_grid_model_c/power_grid_model_cli/cli_options.cpp +++ b/power_grid_model_c/power_grid_model_cli/cli_options.cpp @@ -20,8 +20,8 @@ using EnumMap = std::map; struct CLIPostCallback { // NOLINTBEGIN(cppcoreguidelines-avoid-const-or-ref-data-members) ClIOptions& options; - CLI::Option* msgpack_flag; - CLI::Option* compact_flag; + CLI::Option& msgpack_flag; + CLI::Option& compact_flag; std::vector const& output_components; std::vector const& output_attributes; // NOLINTEND(cppcoreguidelines-avoid-const-or-ref-data-members) @@ -60,13 +60,13 @@ struct CLIPostCallback { options.batch_update_serialization_format.begin(), [](auto const& path) { return get_serialization_format("batch-update", path); }); // default msgpack output if input or any of the batch updates is msgpack and user did not specify output format - if (msgpack_flag->count() == 0 && (options.input_serialization_format == PGM_msgpack || - std::ranges::any_of(options.batch_update_serialization_format, - [](auto format) { return format == PGM_msgpack; }))) { + if (msgpack_flag.count() == 0 && (options.input_serialization_format == PGM_msgpack || + std::ranges::any_of(options.batch_update_serialization_format, + [](auto format) { return format == PGM_msgpack; }))) { options.use_msgpack_output_serialization = true; } // default compact serialization if msgpack output and user did not specify compact option - if (compact_flag->count() == 0 && options.use_msgpack_output_serialization) { + if (compact_flag.count() == 0 && options.use_msgpack_output_serialization) { options.use_compact_serialization = true; } } @@ -195,14 +195,14 @@ CLIResult parse_cli_options(int argc, char** argv, ClIOptions& options) { {"fast_any", PGM_tap_changing_strategy_fast_any_tap}, }, CLI::ignore_case)); - auto msgpack_flag = - app.add_flag("--msgpack,--use-msgpack-output-serialization,!--json,!--use-json-output-serialization", - options.use_msgpack_output_serialization, "Use MessagePack output serialization"); + auto& msgpack_flag = + *app.add_flag("--msgpack,--use-msgpack-output-serialization,!--json,!--use-json-output-serialization", + options.use_msgpack_output_serialization, "Use MessagePack output serialization"); app.add_option("--indent,--output-json-indent", options.output_json_indent, "Number of spaces to indent JSON output"); - auto compact_flag = - app.add_flag("--compact,--use-compact-serialization,!--no-compact,!--no-compact-serialization", - options.use_compact_serialization, "Use compact serialization (no extra whitespace)"); + auto& compact_flag = + *app.add_flag("--compact,--use-compact-serialization,!--no-compact,!--no-compact-serialization", + options.use_compact_serialization, "Use compact serialization (no extra whitespace)"); std::vector output_components; app.add_option("--oc,--output-component", output_components, "Filter output to only include specified components (can be specified multiple times)"); From c72e1697b18ddd146e80ec5b0bd08df842c07e66 Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Mon, 2 Feb 2026 10:00:17 +0100 Subject: [PATCH 60/82] add command Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- tests/cpp_cli_tests/test_cli.cpp | 96 +++++++++++++++++++++++++++++++- 1 file changed, 95 insertions(+), 1 deletion(-) diff --git a/tests/cpp_cli_tests/test_cli.cpp b/tests/cpp_cli_tests/test_cli.cpp index 975c3135ea..2cb10abd6d 100644 --- a/tests/cpp_cli_tests/test_cli.cpp +++ b/tests/cpp_cli_tests/test_cli.cpp @@ -8,8 +8,11 @@ #include #include #include +#include #include +#include #include +#include #include #include @@ -18,6 +21,9 @@ namespace power_grid_model_cpp { namespace { namespace fs = std::filesystem; +// namespace for hardcode json +namespace { + constexpr std::string_view input_json = R"json({ "version": "1.0", "type": "input", @@ -125,6 +131,8 @@ constexpr std::string_view batch_q_json = R"json({ constexpr std::string_view cli_executable = POWER_GRID_MODEL_CLI_EXECUTABLE; +} // namespace + fs::path tmp_path() { // Get the system temp directory fs::path const tmpdir = fs::temp_directory_path(); @@ -137,7 +145,9 @@ fs::path batch_u_ref_path() { return tmp_path() / "batch_u_ref.json"; } fs::path batch_p_path() { return tmp_path() / "batch_p.json"; } fs::path batch_q_path() { return tmp_path() / "batch_q.json"; } fs::path batch_p_path_msgpack() { return tmp_path() / "batch_p.pgmb"; } - +fs::path output_path(PGM_SerializationFormat format) { + return format == PGM_json ? tmp_path() / "output.json" : tmp_path() / "output.pgmb"; +} fs::path stdout_path() { return tmp_path() / "stdout.txt"; } void clear_and_create_tmp_path() { @@ -196,6 +206,90 @@ std::string read_stdout_content() { return file_content; } +struct CLITestCase { + bool is_batch{false}; + bool batch_p_msgpack{false}; + bool has_frequency{false}; + bool has_calculation_type{false}; + bool has_calculation_method{false}; + std::optional symmettry{}; + bool has_error_tolerance{false}; + bool has_max_iterations{false}; + bool has_threading{false}; + std::optional output_serialization{}; + std::optional output_json_indent{}; + std::optional output_compact_serialization{}; + bool component_filter{false}; + bool attribute_filter{false}; + + PGM_SerializationFormat get_output_format() const { + if (output_serialization.has_value()) { + return output_serialization.value(); + } else if (is_batch && batch_p_msgpack) { + return PGM_msgpack; + } else { + return PGM_json; + } + } + + std::string build_command() const { + std::stringstream command; + command << cli_executable; + command << " -i " << input_path(); + if (is_batch) { + command << " -b " << batch_u_ref_path(); + command << " -b " << (batch_p_msgpack ? batch_p_path_msgpack() : batch_p_path()); + command << " -b " << batch_q_path(); + } + command << " -o " << output_path(get_output_format()); + if (has_frequency) { + command << " --system-frequency 50.0"; + } + if (has_calculation_type) { + command << " --calculation-type " << "power_flow"; + } + if (has_calculation_method) { + command << " --calculation-method " << "newton_raphson"; + } + if (symmettry.has_value()) { + command << (symmettry.value() == PGM_symmetric ? " -s" : " -a"); + } + if (has_error_tolerance) { + command << " --error-tolerance 1e-8"; + } + if (has_max_iterations) { + command << " --max-iterations 20"; + } + if (has_threading) { + command << " --threading -1"; + } + if (output_serialization.has_value()) { + if (output_serialization.value() == PGM_msgpack) { + command << " --msgpack"; + } else { + command << " --json"; + } + } + if (output_json_indent.has_value()) { + command << " --indent " << output_json_indent.value(); + } + if (output_compact_serialization.has_value()) { + if (output_compact_serialization.value()) { + command << " --compact"; + } else { + command << " --no-compact"; + } + } + if (component_filter) { + command << " --oc source"; + } + if (attribute_filter) { + command << " --oa source.u_ref"; + } + return command.str(); + } +}; + } // namespace TEST_CASE("Test CLI version") { From a5f3f0cacd352beb2fa6e94b7917e388b841379f Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Mon, 2 Feb 2026 10:02:40 +0100 Subject: [PATCH 61/82] start run command Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- tests/cpp_cli_tests/test_cli.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/cpp_cli_tests/test_cli.cpp b/tests/cpp_cli_tests/test_cli.cpp index 2cb10abd6d..99681c4a4a 100644 --- a/tests/cpp_cli_tests/test_cli.cpp +++ b/tests/cpp_cli_tests/test_cli.cpp @@ -286,9 +286,16 @@ struct CLITestCase { if (attribute_filter) { command << " --oa source.u_ref"; } + command << " > " << stdout_path(); return command.str(); } -}; + + void run_command() const { + std::string const command = build_command(); + INFO("CLI command: ", command); + int ret = std::system(command.c_str()); + REQUIRE(ret == 0); + }; } // namespace From 30be936d42c9847d7cc92230dfca848464d9435b Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Mon, 2 Feb 2026 10:03:13 +0100 Subject: [PATCH 62/82] fix typo Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- tests/cpp_cli_tests/test_cli.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/cpp_cli_tests/test_cli.cpp b/tests/cpp_cli_tests/test_cli.cpp index 99681c4a4a..234da4d383 100644 --- a/tests/cpp_cli_tests/test_cli.cpp +++ b/tests/cpp_cli_tests/test_cli.cpp @@ -295,7 +295,8 @@ struct CLITestCase { INFO("CLI command: ", command); int ret = std::system(command.c_str()); REQUIRE(ret == 0); - }; + } +}; } // namespace From f0cf8f8f4358754ce53d548e51e884c54d0ca0b3 Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Mon, 2 Feb 2026 10:26:25 +0100 Subject: [PATCH 63/82] start check result Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- tests/cpp_cli_tests/test_cli.cpp | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/tests/cpp_cli_tests/test_cli.cpp b/tests/cpp_cli_tests/test_cli.cpp index 234da4d383..dfc7873ab9 100644 --- a/tests/cpp_cli_tests/test_cli.cpp +++ b/tests/cpp_cli_tests/test_cli.cpp @@ -231,6 +231,20 @@ struct CLITestCase { return PGM_json; } } + bool has_output_filter() const { return component_filter || attribute_filter; } + PGM_SymmetryType get_symmetry() const { + if (symmettry.has_value()) { + return symmettry.value(); + } else { + return PGM_symmetric; + } + } + bool output_columnar() const { + if (output_compact_serialization.has_value()) { + return output_compact_serialization.value(); + } + return get_output_format() == PGM_msgpack; + } std::string build_command() const { std::stringstream command; @@ -290,11 +304,17 @@ struct CLITestCase { return command.str(); } - void run_command() const { + void check_results() const { + fs::path const out_path = output_path(get_output_format()); + OwningDataset const output_dataset = load_dataset(out_path, get_output_format(), true); + } + + void run_command_and_check() const { std::string const command = build_command(); INFO("CLI command: ", command); int ret = std::system(command.c_str()); REQUIRE(ret == 0); + check_results(); } }; From 4261575581ba4b2b5a5a9ca735783d629374574a Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Mon, 2 Feb 2026 10:27:36 +0100 Subject: [PATCH 64/82] add prepare data Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- tests/cpp_cli_tests/test_cli.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/cpp_cli_tests/test_cli.cpp b/tests/cpp_cli_tests/test_cli.cpp index dfc7873ab9..4f065fa647 100644 --- a/tests/cpp_cli_tests/test_cli.cpp +++ b/tests/cpp_cli_tests/test_cli.cpp @@ -310,6 +310,7 @@ struct CLITestCase { } void run_command_and_check() const { + prepare_data(); std::string const command = build_command(); INFO("CLI command: ", command); int ret = std::system(command.c_str()); From 158ff7f222b42eeab83b43cda0b9a9af5479967d Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Mon, 2 Feb 2026 10:29:08 +0100 Subject: [PATCH 65/82] check stdout Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- tests/cpp_cli_tests/test_cli.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/cpp_cli_tests/test_cli.cpp b/tests/cpp_cli_tests/test_cli.cpp index 4f065fa647..b8e1379fd0 100644 --- a/tests/cpp_cli_tests/test_cli.cpp +++ b/tests/cpp_cli_tests/test_cli.cpp @@ -314,6 +314,8 @@ struct CLITestCase { std::string const command = build_command(); INFO("CLI command: ", command); int ret = std::system(command.c_str()); + std::string const stdout_content = read_stdout_content(); + INFO("CLI stdout content: ", stdout_content); REQUIRE(ret == 0); check_results(); } From d87c6ed8d8195bc851aa51a6ef6fe939fca81539 Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Mon, 2 Feb 2026 10:45:17 +0100 Subject: [PATCH 66/82] calculate i source Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- tests/cpp_cli_tests/test_cli.cpp | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/tests/cpp_cli_tests/test_cli.cpp b/tests/cpp_cli_tests/test_cli.cpp index b8e1379fd0..1e57fe6328 100644 --- a/tests/cpp_cli_tests/test_cli.cpp +++ b/tests/cpp_cli_tests/test_cli.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -20,6 +21,7 @@ namespace power_grid_model_cpp { namespace { namespace fs = std::filesystem; +using std::numbers::sqrt3; // namespace for hardcode json namespace { @@ -206,6 +208,31 @@ std::string read_stdout_content() { return file_content; } +std::vector get_i_source_ref() { + // 3-D batch update + double const u_rated = 10e3; + std::vector const u_ref{0.9, 1.0, 1.1}; + std::vector const p_specified{1e6, 2e6, 3e6, 4e6}; + std::vector const q_specified{0.1e6, 0.2e6, 0.3e6, 0.4e6, 0.5e6}; + Idx const size_u_ref = std::ssize(u_ref); + Idx const size_p_specified = std::ssize(p_specified); + Idx const size_q_specified = std::ssize(q_specified); + Idx const total_batch_size = size_u_ref * size_p_specified * size_q_specified; + + // calculate source current manually + std::vector i_source_ref(total_batch_size); + for (Idx i = 0; i < size_u_ref; ++i) { + for (Idx j = 0; j < size_p_specified; ++j) { + for (Idx k = 0; k < size_q_specified; ++k) { + Idx const index = i * size_p_specified * size_q_specified + j * size_q_specified + k; + double const s = std::abs(std::complex{p_specified[j], q_specified[k]}); + i_source_ref[index] = s / (sqrt3 * u_rated * u_ref[i]); + } + } + } + return i_source_ref; +} + struct CLITestCase { bool is_batch{false}; bool batch_p_msgpack{false}; @@ -298,7 +325,7 @@ struct CLITestCase { command << " --oc source"; } if (attribute_filter) { - command << " --oa source.u_ref"; + command << " --oa source.i"; } command << " > " << stdout_path(); return command.str(); From 8ce3ac0b6f6dfcf0227978a51177a0a7ef081ba3 Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Mon, 2 Feb 2026 12:53:01 +0100 Subject: [PATCH 67/82] get batch results Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- tests/cpp_cli_tests/test_cli.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/cpp_cli_tests/test_cli.cpp b/tests/cpp_cli_tests/test_cli.cpp index 1e57fe6328..0c08dae2fb 100644 --- a/tests/cpp_cli_tests/test_cli.cpp +++ b/tests/cpp_cli_tests/test_cli.cpp @@ -208,7 +208,10 @@ std::string read_stdout_content() { return file_content; } -std::vector get_i_source_ref() { +std::vector get_i_source_ref(bool is_batch) { + if (!is_batch) { + return {0.0}; + } // 3-D batch update double const u_rated = 10e3; std::vector const u_ref{0.9, 1.0, 1.1}; @@ -333,7 +336,11 @@ struct CLITestCase { void check_results() const { fs::path const out_path = output_path(get_output_format()); - OwningDataset const output_dataset = load_dataset(out_path, get_output_format(), true); + OwningDataset const output_owning_dataset = load_dataset(out_path, get_output_format(), true); + auto const i_source_ref = get_i_source_ref(is_batch); + Idx const batch_size = output_owning_dataset.dataset.get_info().batch_size(); + REQUIRE(batch_size == std::ssize(i_source_ref)); + REQUIRE(is_batch == output_owning_dataset.dataset.get_info().is_batch()); } void run_command_and_check() const { From 3b8900f17ea5ef619a8ecbe40d4c88308c74d3fb Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Mon, 2 Feb 2026 13:28:02 +0100 Subject: [PATCH 68/82] get buffer Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- .../include/power_grid_model_cpp/dataset.hpp | 2 + tests/cpp_cli_tests/test_cli.cpp | 44 +++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/dataset.hpp b/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/dataset.hpp index 7d383650eb..d0ec6eb7f2 100644 --- a/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/dataset.hpp +++ b/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/dataset.hpp @@ -240,6 +240,8 @@ class AttributeBuffer { RawDataPtr get() { return pgm_type_func_selector(MetaData::attribute_ctype(attribute_), PtrGetter{*this}); } + MetaAttribute const* get_attribute() const { return attribute_; } + private: MetaAttribute const* attribute_{nullptr}; VariantType buffer_; diff --git a/tests/cpp_cli_tests/test_cli.cpp b/tests/cpp_cli_tests/test_cli.cpp index 0c08dae2fb..a7b2d77e4d 100644 --- a/tests/cpp_cli_tests/test_cli.cpp +++ b/tests/cpp_cli_tests/test_cli.cpp @@ -236,6 +236,13 @@ std::vector get_i_source_ref(bool is_batch) { return i_source_ref; } +struct BufferRef { + PGM_SymmetryType symmetric{PGM_symmetric}; + bool use_attribute_buffer{false}; + Buffer const* row_buffer; + AttributeBuffer const* attribute_buffer; +}; + struct CLITestCase { bool is_batch{false}; bool batch_p_msgpack{false}; @@ -334,6 +341,43 @@ struct CLITestCase { return command.str(); } + BufferRef get_source_buffer(OwningDataset const& dataset) const { + auto const& owning_memory = dataset.storage; + auto const& info = dataset.dataset.get_info(); + Idx const source_idx = info.component_idx("source"); + auto const* const row_buffer = [this, &owning_memory, &info, source_idx]() { + if (has_output_filter()) { + REQUIRE(info.n_components() == 1); + REQUIRE(source_idx == 0); + } + return &owning_memory.buffers[source_idx]; + }(); + auto const* const attribute_buffer = [this, &owning_memory, &info, row_buffer, + source_idx]() -> AttributeBuffer const* { + if (output_columnar()) { + REQUIRE(row_buffer->get() == nullptr); + if (attribute_filter) { + REQUIRE(owning_memory.attribute_buffers[source_idx].size() == 1); + return &owning_memory.attribute_buffers[source_idx][0]; + } else { + for (auto const& attr_buf : owning_memory.attribute_buffers[source_idx]) { + if (MetaData::attribute_name(attr_buf.get_attribute()) == "i") { + return &attr_buf; + } + } + DOCTEST_FAIL("Attribute 'i' buffer not found"); + } + } + // when no filter, buffer should not be nullptr, and return nullptr for attribute buffer + REQUIRE(row_buffer->get() != nullptr); + return nullptr; + }(); + return BufferRef{.symmetric = get_symmetry(), + .use_attribute_buffer = output_columnar(), + .row_buffer = row_buffer, + .attribute_buffer = attribute_buffer}; + } + void check_results() const { fs::path const out_path = output_path(get_output_format()); OwningDataset const output_owning_dataset = load_dataset(out_path, get_output_format(), true); From a6525e3fe1795a26ecc55bde1ef3d2a5beabb824 Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Mon, 2 Feb 2026 14:03:44 +0100 Subject: [PATCH 69/82] test still fails. cli seems to work Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- .../power_grid_model_cli/pgm_calculation.cpp | 4 + .../include/power_grid_model_cpp/dataset.hpp | 2 + tests/cpp_cli_tests/test_cli.cpp | 81 +++++++++++++++++-- 3 files changed, 81 insertions(+), 6 deletions(-) diff --git a/power_grid_model_c/power_grid_model_cli/pgm_calculation.cpp b/power_grid_model_c/power_grid_model_cli/pgm_calculation.cpp index 62738295dc..cdcf882590 100644 --- a/power_grid_model_c/power_grid_model_cli/pgm_calculation.cpp +++ b/power_grid_model_c/power_grid_model_cli/pgm_calculation.cpp @@ -5,6 +5,8 @@ #include "cli_functions.hpp" #include +#include +#include #include #include @@ -24,6 +26,8 @@ struct BatchDatasets { for (auto it = dataset_consts.begin(); it != dataset_consts.end() - 1; ++it) { it->set_next_cartesian_product_dimension(*(it + 1)); } + batch_size = std::transform_reduce(datasets.begin(), datasets.end(), Idx{1}, std::multiplies{}, + [](OwningDataset const& ds) { return ds.dataset.get_info().batch_size(); }); } DatasetConst const& head() const { diff --git a/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/dataset.hpp b/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/dataset.hpp index d0ec6eb7f2..95e9f37b46 100644 --- a/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/dataset.hpp +++ b/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/dataset.hpp @@ -242,6 +242,8 @@ class AttributeBuffer { MetaAttribute const* get_attribute() const { return attribute_; } + template std::vector const& get_data_vector() const { return std::get>(buffer_); } + private: MetaAttribute const* attribute_{nullptr}; VariantType buffer_; diff --git a/tests/cpp_cli_tests/test_cli.cpp b/tests/cpp_cli_tests/test_cli.cpp index a7b2d77e4d..28bd5e6baf 100644 --- a/tests/cpp_cli_tests/test_cli.cpp +++ b/tests/cpp_cli_tests/test_cli.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -241,6 +242,40 @@ struct BufferRef { bool use_attribute_buffer{false}; Buffer const* row_buffer; AttributeBuffer const* attribute_buffer; + + void check_i_source(std::vector const& i_source_ref) const { + Idx const batch_size = i_source_ref.size(); + for (Idx idx = 0; idx < batch_size; ++idx) { + double const i_calculated = [this, idx]() { + if (use_attribute_buffer) { + if (symmetric == PGM_symmetric) { + auto const& data_vector = attribute_buffer->get_data_vector(); + return data_vector.at(idx); + } else { + auto const& data_vector = attribute_buffer->get_data_vector>(); + auto const& val_array = data_vector.at(idx); + CHECK(val_array[0] == doctest::Approx(val_array[1])); + CHECK(val_array[0] == doctest::Approx(val_array[2])); + return val_array[0]; + } + } else { + // use row buffer + if (symmetric == PGM_symmetric) { + double value{}; + row_buffer->get_value(PGM_def_sym_output_source_i, &value, idx, 1); + return value; + } else { + std::array val_array{}; + row_buffer->get_value(PGM_def_asym_output_source_i, val_array.data(), idx, 3); + CHECK(val_array[0] == doctest::Approx(val_array[1])); + CHECK(val_array[0] == doctest::Approx(val_array[2])); + return val_array[0]; + } + } + }(); + CHECK(i_calculated == doctest::Approx(i_source_ref.at(idx))); + } + } }; struct CLITestCase { @@ -249,7 +284,7 @@ struct CLITestCase { bool has_frequency{false}; bool has_calculation_type{false}; bool has_calculation_method{false}; - std::optional symmettry{}; + std::optional symmetry{}; bool has_error_tolerance{false}; bool has_max_iterations{false}; bool has_threading{false}; @@ -270,8 +305,8 @@ struct CLITestCase { } bool has_output_filter() const { return component_filter || attribute_filter; } PGM_SymmetryType get_symmetry() const { - if (symmettry.has_value()) { - return symmettry.value(); + if (symmetry.has_value()) { + return symmetry.value(); } else { return PGM_symmetric; } @@ -302,8 +337,8 @@ struct CLITestCase { if (has_calculation_method) { command << " --calculation-method " << "newton_raphson"; } - if (symmettry.has_value()) { - command << (symmettry.value() == PGM_symmetric ? " -s" : " -a"); + if (symmetry.has_value()) { + command << (symmetry.value() == PGM_symmetric ? " -s" : " -a"); } if (has_error_tolerance) { command << " --error-tolerance 1e-8"; @@ -352,7 +387,7 @@ struct CLITestCase { } return &owning_memory.buffers[source_idx]; }(); - auto const* const attribute_buffer = [this, &owning_memory, &info, row_buffer, + auto const* const attribute_buffer = [this, &owning_memory, row_buffer, source_idx]() -> AttributeBuffer const* { if (output_columnar()) { REQUIRE(row_buffer->get() == nullptr); @@ -385,6 +420,8 @@ struct CLITestCase { Idx const batch_size = output_owning_dataset.dataset.get_info().batch_size(); REQUIRE(batch_size == std::ssize(i_source_ref)); REQUIRE(is_batch == output_owning_dataset.dataset.get_info().is_batch()); + auto const buffer_ref = get_source_buffer(output_owning_dataset); + buffer_ref.check_i_source(i_source_ref); } void run_command_and_check() const { @@ -413,4 +450,36 @@ TEST_CASE("Test CLI version") { CHECK(first_line == PGM_version()); } +TEST_CASE("Test run CLI") { + std::vector test_cases = { + // basic non-batch, symmetric, json + CLITestCase{}, + // basic batch, symmetric, json + CLITestCase{.is_batch = true}, + // batch, asymmetric, msgpack + CLITestCase{.is_batch = true, .symmetry = PGM_asymmetric, .output_serialization = PGM_msgpack}, + // batch, symmetric, json, with all options set + CLITestCase{.is_batch = true, + .has_frequency = true, + .has_calculation_type = true, + .has_calculation_method = true, + .symmetry = PGM_symmetric, + .has_error_tolerance = true, + .has_max_iterations = true, + .has_threading = true, + .output_serialization = PGM_json, + .output_json_indent = 4, + .output_compact_serialization = false}, + // batch, asymmetric, msgpack, with component and attribute filter + CLITestCase{.is_batch = true, + .symmetry = PGM_asymmetric, + .output_serialization = PGM_msgpack, + .component_filter = true, + .attribute_filter = true}, + }; + for (auto const& test_case : test_cases) { + SUBCASE(test_case.build_command().c_str()) { test_case.run_command_and_check(); } + } +} + } // namespace power_grid_model_cpp From 96177aa5d746c3f47d00fe98f7cba0615bfaa64a Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Mon, 2 Feb 2026 14:29:37 +0100 Subject: [PATCH 70/82] test work Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- tests/cpp_cli_tests/test_cli.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/cpp_cli_tests/test_cli.cpp b/tests/cpp_cli_tests/test_cli.cpp index 28bd5e6baf..95311f35ee 100644 --- a/tests/cpp_cli_tests/test_cli.cpp +++ b/tests/cpp_cli_tests/test_cli.cpp @@ -262,11 +262,11 @@ struct BufferRef { // use row buffer if (symmetric == PGM_symmetric) { double value{}; - row_buffer->get_value(PGM_def_sym_output_source_i, &value, idx, 1); + row_buffer->get_value(PGM_def_sym_output_source_i, &value, idx, 0); return value; } else { std::array val_array{}; - row_buffer->get_value(PGM_def_asym_output_source_i, val_array.data(), idx, 3); + row_buffer->get_value(PGM_def_asym_output_source_i, val_array.data(), idx, 0); CHECK(val_array[0] == doctest::Approx(val_array[1])); CHECK(val_array[0] == doctest::Approx(val_array[2])); return val_array[0]; @@ -460,6 +460,7 @@ TEST_CASE("Test run CLI") { CLITestCase{.is_batch = true, .symmetry = PGM_asymmetric, .output_serialization = PGM_msgpack}, // batch, symmetric, json, with all options set CLITestCase{.is_batch = true, + .batch_p_msgpack = true, .has_frequency = true, .has_calculation_type = true, .has_calculation_method = true, @@ -469,7 +470,9 @@ TEST_CASE("Test run CLI") { .has_threading = true, .output_serialization = PGM_json, .output_json_indent = 4, - .output_compact_serialization = false}, + .output_compact_serialization = true, + .component_filter = true, + .attribute_filter = true}, // batch, asymmetric, msgpack, with component and attribute filter CLITestCase{.is_batch = true, .symmetry = PGM_asymmetric, From fc37ebea97a692f1ece9e6427ab5b41ad53f2462 Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Mon, 2 Feb 2026 14:32:37 +0100 Subject: [PATCH 71/82] fix test Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- tests/cpp_cli_tests/test_cli.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/cpp_cli_tests/test_cli.cpp b/tests/cpp_cli_tests/test_cli.cpp index 95311f35ee..60955fc6ad 100644 --- a/tests/cpp_cli_tests/test_cli.cpp +++ b/tests/cpp_cli_tests/test_cli.cpp @@ -451,7 +451,7 @@ TEST_CASE("Test CLI version") { } TEST_CASE("Test run CLI") { - std::vector test_cases = { + std::vector const test_cases = { // basic non-batch, symmetric, json CLITestCase{}, // basic batch, symmetric, json From 6f30f2ae7fd27178a0dae44e770145282e1ca50b Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Mon, 2 Feb 2026 14:34:07 +0100 Subject: [PATCH 72/82] re-order include Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- tests/cpp_cli_tests/test_cli.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/cpp_cli_tests/test_cli.cpp b/tests/cpp_cli_tests/test_cli.cpp index 60955fc6ad..446693fdf9 100644 --- a/tests/cpp_cli_tests/test_cli.cpp +++ b/tests/cpp_cli_tests/test_cli.cpp @@ -4,16 +4,18 @@ #define PGM_ENABLE_EXPERIMENTAL -#include +#include +#include + #include +#include + +#include #include #include #include -#include #include #include -#include -#include #include #include #include From 051c14f2c26b99994101627b0fd7b73d87a3ddd0 Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Mon, 2 Feb 2026 18:39:25 +0100 Subject: [PATCH 73/82] fix clang tidy Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- .../power_grid_model_cli/cli_functions.hpp | 4 +++- .../power_grid_model_cli/cli_options.cpp | 12 ++++++------ power_grid_model_c/power_grid_model_cli/main.cpp | 10 +++++----- .../power_grid_model_cli/pgm_calculation.cpp | 6 +++--- 4 files changed, 17 insertions(+), 15 deletions(-) diff --git a/power_grid_model_c/power_grid_model_cli/cli_functions.hpp b/power_grid_model_c/power_grid_model_cli/cli_functions.hpp index 4210a7440f..57994bc85b 100644 --- a/power_grid_model_c/power_grid_model_cli/cli_functions.hpp +++ b/power_grid_model_c/power_grid_model_cli/cli_functions.hpp @@ -20,6 +20,7 @@ struct CLIResult { }; struct ClIOptions { + // NOLINTBEGIN(clang-analyzer-optin.performance.Padding) std::filesystem::path input_file; std::vector batch_update_file; std::filesystem::path output_file; @@ -31,7 +32,7 @@ struct ClIOptions { Idx calculation_type{PGM_power_flow}; Idx calculation_method{PGM_default_method}; - bool symmetric_calculation{PGM_symmetric}; + bool symmetric_calculation{static_cast(PGM_symmetric)}; double error_tolerance{1e-8}; Idx max_iterations{20}; Idx threading{-1}; @@ -47,6 +48,7 @@ struct ClIOptions { std::map> output_component_attribute_filters; bool verbose{false}; + // NOLINTEND(clang-analyzer-optin.performance.Padding) friend std::ostream& operator<<(std::ostream& os, ClIOptions const& options); }; diff --git a/power_grid_model_c/power_grid_model_cli/cli_options.cpp b/power_grid_model_c/power_grid_model_cli/cli_options.cpp index 0006649376..d9df6e2451 100644 --- a/power_grid_model_c/power_grid_model_cli/cli_options.cpp +++ b/power_grid_model_c/power_grid_model_cli/cli_options.cpp @@ -88,7 +88,7 @@ struct CLIPostCallback { void add_component_output_filter() { for (auto const& comp_name : output_components) { try { - auto const component = MetaData::get_component_by_name(options.output_dataset_name, comp_name); + auto const* const component = MetaData::get_component_by_name(options.output_dataset_name, comp_name); options.output_component_attribute_filters[component] = {}; } catch (PowerGridError const&) { throw CLI::ValidationError("output-component", "Component '" + comp_name + "' not found in dataset '" + @@ -117,9 +117,9 @@ struct CLIPostCallback { try { attribute = MetaData::get_attribute_by_name(options.output_dataset_name, comp_name, attr_name); } catch (PowerGridError const&) { - throw CLI::ValidationError("output-attribute", - "Attribute '" + attr_name + "' not found in component '" + comp_name + - "' of dataset '" + options.output_dataset_name + "'."); + std::string const error_msg = "Attribute '" + attr_name + "' not found in component '" + comp_name + + "' of dataset '" + options.output_dataset_name + "'."; + throw CLI::ValidationError("output-attribute", error_msg); } options.output_component_attribute_filters[component].insert(attribute); } @@ -130,9 +130,9 @@ CLIResult parse_cli_options(int argc, char** argv, ClIOptions& options) { std::string const version_str = std::string("Power Grid Model CLI\n Version: ") + PGM_version(); CLI::App app{version_str}; - CLI::Validator existing_parent_dir_validator{ + CLI::Validator const existing_parent_dir_validator{ [](std::string& input) { - std::filesystem::path p{input}; + std::filesystem::path const p{input}; auto parent = p.has_parent_path() ? p.parent_path() : std::filesystem::path{"."}; if (parent.empty() || !std::filesystem::exists(parent) || !std::filesystem::is_directory(parent)) { return std::string("The parent directory of the specified path does not exist or is not a directory."); diff --git a/power_grid_model_c/power_grid_model_cli/main.cpp b/power_grid_model_c/power_grid_model_cli/main.cpp index c69a819d24..29ea6f2864 100644 --- a/power_grid_model_c/power_grid_model_cli/main.cpp +++ b/power_grid_model_c/power_grid_model_cli/main.cpp @@ -17,19 +17,19 @@ int main(int argc, char** argv) { } if (cli_options.verbose) { - std::cout << cli_options << std::endl; + std::cout << cli_options << '\n'; } try { pgm_calculation(cli_options); } catch (PowerGridError const& e) { - std::cerr << "PowerGridError: " << e.what() << std::endl; - return e.error_code(); + std::cerr << "PowerGridError: " << e.what() << '\n'; + return static_cast(e.error_code()); } catch (std::exception const& e) { - std::cerr << "Exception: " << e.what() << std::endl; + std::cerr << "Exception: " << e.what() << '\n'; return -666; } catch (...) { - std::cerr << "Unknown exception caught." << std::endl; + std::cerr << "Unknown exception caught." << '\n'; return -999; } diff --git a/power_grid_model_c/power_grid_model_cli/pgm_calculation.cpp b/power_grid_model_c/power_grid_model_cli/pgm_calculation.cpp index cdcf882590..e742fed251 100644 --- a/power_grid_model_c/power_grid_model_cli/pgm_calculation.cpp +++ b/power_grid_model_c/power_grid_model_cli/pgm_calculation.cpp @@ -42,14 +42,14 @@ struct BatchDatasets { void pgm_calculation(ClIOptions const& cli_options) { // Load input dataset - OwningDataset input_dataset = load_dataset(cli_options.input_file, cli_options.input_serialization_format); + OwningDataset const input_dataset = load_dataset(cli_options.input_file, cli_options.input_serialization_format); // Apply batch updates if provided BatchDatasets const batch_datasets{cli_options}; // create result dataset - OwningDataset result_dataset{input_dataset, cli_options.output_dataset_name, cli_options.is_batch, - batch_datasets.batch_size, cli_options.output_component_attribute_filters}; + OwningDataset const result_dataset{input_dataset, cli_options.output_dataset_name, cli_options.is_batch, + batch_datasets.batch_size, cli_options.output_component_attribute_filters}; // create model Model model{cli_options.system_frequency, input_dataset.dataset}; // create calculation options From 8fa77999ef152d89a51176a59ca59f5a8d28d33a Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Mon, 2 Feb 2026 18:42:37 +0100 Subject: [PATCH 74/82] fix const correctness Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- power_grid_model_c/power_grid_model_cli/pgm_calculation.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/power_grid_model_c/power_grid_model_cli/pgm_calculation.cpp b/power_grid_model_c/power_grid_model_cli/pgm_calculation.cpp index e742fed251..bf989297e7 100644 --- a/power_grid_model_c/power_grid_model_cli/pgm_calculation.cpp +++ b/power_grid_model_c/power_grid_model_cli/pgm_calculation.cpp @@ -48,8 +48,9 @@ void pgm_calculation(ClIOptions const& cli_options) { BatchDatasets const batch_datasets{cli_options}; // create result dataset - OwningDataset const result_dataset{input_dataset, cli_options.output_dataset_name, cli_options.is_batch, - batch_datasets.batch_size, cli_options.output_component_attribute_filters}; + // NOLINTNEXTLINE(misc-const-correctness) + OwningDataset result_dataset{input_dataset, cli_options.output_dataset_name, cli_options.is_batch, + batch_datasets.batch_size, cli_options.output_component_attribute_filters}; // create model Model model{cli_options.system_frequency, input_dataset.dataset}; // create calculation options From f855724acb9fb6a1dccea1fb906f176b4931ebb9 Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Tue, 3 Feb 2026 10:29:03 +0100 Subject: [PATCH 75/82] fix some errors' Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- .../power_grid_model_cli/cli_functions.hpp | 3 +-- power_grid_model_c/power_grid_model_cli/cli_options.cpp | 9 +++++++-- tests/native_api_tests/CMakeLists.txt | 5 ++++- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/power_grid_model_c/power_grid_model_cli/cli_functions.hpp b/power_grid_model_c/power_grid_model_cli/cli_functions.hpp index 57994bc85b..871dd758a8 100644 --- a/power_grid_model_c/power_grid_model_cli/cli_functions.hpp +++ b/power_grid_model_c/power_grid_model_cli/cli_functions.hpp @@ -19,8 +19,8 @@ struct CLIResult { operator bool() const { return should_exit || exit_code != 0; } }; +// NOLINTNEXTLINE(clang-analyzer-optin.performance.Padding) struct ClIOptions { - // NOLINTBEGIN(clang-analyzer-optin.performance.Padding) std::filesystem::path input_file; std::vector batch_update_file; std::filesystem::path output_file; @@ -48,7 +48,6 @@ struct ClIOptions { std::map> output_component_attribute_filters; bool verbose{false}; - // NOLINTEND(clang-analyzer-optin.performance.Padding) friend std::ostream& operator<<(std::ostream& os, ClIOptions const& options); }; diff --git a/power_grid_model_c/power_grid_model_cli/cli_options.cpp b/power_grid_model_c/power_grid_model_cli/cli_options.cpp index d9df6e2451..c17c8128b2 100644 --- a/power_grid_model_c/power_grid_model_cli/cli_options.cpp +++ b/power_grid_model_c/power_grid_model_cli/cli_options.cpp @@ -117,8 +117,13 @@ struct CLIPostCallback { try { attribute = MetaData::get_attribute_by_name(options.output_dataset_name, comp_name, attr_name); } catch (PowerGridError const&) { - std::string const error_msg = "Attribute '" + attr_name + "' not found in component '" + comp_name + - "' of dataset '" + options.output_dataset_name + "'."; + std::string error_msg = "Attribute '"; + error_msg += attr_name; + error_msg += "' not found in component '"; + error_msg += comp_name; + error_msg += "' of dataset '"; + error_msg += options.output_dataset_name; + error_msg += "'."; throw CLI::ValidationError("output-attribute", error_msg); } options.output_component_attribute_filters[component].insert(attribute); diff --git a/tests/native_api_tests/CMakeLists.txt b/tests/native_api_tests/CMakeLists.txt index 216ee837c6..7f23f54efa 100644 --- a/tests/native_api_tests/CMakeLists.txt +++ b/tests/native_api_tests/CMakeLists.txt @@ -15,7 +15,10 @@ set(PROJECT_SOURCES add_executable(power_grid_model_api_tests ${PROJECT_SOURCES}) -target_compile_definitions(power_grid_model_api_tests PRIVATE PGM_VERSION="${PGM_VERSION}") +target_compile_definitions( + power_grid_model_api_tests + PRIVATE PGM_VERSION="${PGM_VERSION}" +) target_link_libraries( power_grid_model_api_tests From a10710f501bc39ce270ceed48e5e4a9be5213191 Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Tue, 3 Feb 2026 10:37:09 +0100 Subject: [PATCH 76/82] fix return code Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- power_grid_model_c/power_grid_model_cli/main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/power_grid_model_c/power_grid_model_cli/main.cpp b/power_grid_model_c/power_grid_model_cli/main.cpp index 29ea6f2864..bbf0032e25 100644 --- a/power_grid_model_c/power_grid_model_cli/main.cpp +++ b/power_grid_model_c/power_grid_model_cli/main.cpp @@ -27,10 +27,10 @@ int main(int argc, char** argv) { return static_cast(e.error_code()); } catch (std::exception const& e) { std::cerr << "Exception: " << e.what() << '\n'; - return -666; + return 1; } catch (...) { std::cerr << "Unknown exception caught." << '\n'; - return -999; + return 1; } return 0; From f606ccd8793462658a786752e9e9bab23c7218ac Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Tue, 3 Feb 2026 10:44:33 +0100 Subject: [PATCH 77/82] fix coverage Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- power_grid_model_c/power_grid_model_cli/cli_options.cpp | 2 +- tests/cpp_cli_tests/test_cli.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/power_grid_model_c/power_grid_model_cli/cli_options.cpp b/power_grid_model_c/power_grid_model_cli/cli_options.cpp index c17c8128b2..d75686fc23 100644 --- a/power_grid_model_c/power_grid_model_cli/cli_options.cpp +++ b/power_grid_model_c/power_grid_model_cli/cli_options.cpp @@ -233,7 +233,7 @@ CLIResult parse_cli_options(int argc, char** argv, ClIOptions& options) { } std::ostream& operator<<(std::ostream& os, ClIOptions const& options) { - os << "CLI Options:\n"; + os << "Run PGM with following CLI Options:\n"; os << "Input file: " << options.input_file << "\n"; os << "Batch update file: \n"; for (auto const& file : options.batch_update_file) { diff --git a/tests/cpp_cli_tests/test_cli.cpp b/tests/cpp_cli_tests/test_cli.cpp index 446693fdf9..04da0cdce3 100644 --- a/tests/cpp_cli_tests/test_cli.cpp +++ b/tests/cpp_cli_tests/test_cli.cpp @@ -374,7 +374,8 @@ struct CLITestCase { if (attribute_filter) { command << " --oa source.i"; } - command << " > " << stdout_path(); + command << " --verbose"; + command << " > " << stdout_path() << " 2>&1"; return command.str(); } From ccf7d1e6e137fb7ea5eaa5829bb2266d97cbcc9a Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Tue, 3 Feb 2026 10:56:31 +0100 Subject: [PATCH 78/82] [skip ci] start conda Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- .github/conda_pgm_env.yml | 1 + .github/workflows/build-test-release.yml | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/conda_pgm_env.yml b/.github/conda_pgm_env.yml index 1845c03ec8..1d22f4e891 100644 --- a/.github/conda_pgm_env.yml +++ b/.github/conda_pgm_env.yml @@ -16,6 +16,7 @@ dependencies: - numpy - cmake - ninja + - cli11 # test deps - pytest - pytest-cov diff --git a/.github/workflows/build-test-release.yml b/.github/workflows/build-test-release.yml index d87186b1a7..07697eecb6 100644 --- a/.github/workflows/build-test-release.yml +++ b/.github/workflows/build-test-release.yml @@ -281,7 +281,10 @@ jobs: run: python -m pip install . --no-build-isolation --no-deps -C wheel.cmake=false - name: Test - run: pytest + run: | + pytest + power-grid-model --help + power-grid-model --version github-release: name: Create and release assets to GitHub From d536a2218512702679435d713886cb1e05f02199 Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Tue, 3 Feb 2026 11:05:14 +0100 Subject: [PATCH 79/82] add conda prepare Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- conda_build_preparation.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 conda_build_preparation.py diff --git a/conda_build_preparation.py b/conda_build_preparation.py new file mode 100644 index 0000000000..7a6d1497dc --- /dev/null +++ b/conda_build_preparation.py @@ -0,0 +1,31 @@ +# SPDX-FileCopyrightText: Contributors to the Power Grid Model project +# +# SPDX-License-Identifier: MPL-2.0 + +# This script modifies the pyproject.toml file to remove specific sections +# [project.entry-points."cmake.root"] and [project.scripts] +# and deletes the run_pgm_cli.py file from the source directory. +# It is intended to be run as part of the conda build preparation process. +# So that conda environment will not be confused with PyPI style Python shim and entry points. + +import re +from pathlib import Path + +# Read the root pyproject.toml +pyproject_path = Path(__file__).parent / "pyproject.toml" +content = pyproject_path.read_text() + +# Remove [project.entry-points."cmake.root"] section +content = re.sub(r'\n\[project\.entry-points\."cmake\.root"\].*?(?=\n\[|\Z)', "", content, flags=re.DOTALL) + +# Remove [project.scripts] section +content = re.sub(r"\n\[project\.scripts\].*?(?=\n\[|\Z)", "", content, flags=re.DOTALL) + +# Write back to pyproject.toml +pyproject_path.write_text(content) + +# Remove run_pgm_cli.py file +run_pgm_cli_path = ( + Path(__file__).parent / "src" / "power_grid_model" / "_core" / "power_grid_model_c" / "run_pgm_cli.py" +) +run_pgm_cli_path.unlink(missing_ok=True) From 743b3669f9e66e7d14ee904aaeae81a11baa1f4c Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Tue, 3 Feb 2026 11:07:47 +0100 Subject: [PATCH 80/82] fix conda build in PGM CI Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- .github/conda_pgm_env.yml | 2 +- .github/workflows/build-test-release.yml | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/conda_pgm_env.yml b/.github/conda_pgm_env.yml index 1d22f4e891..6c3be6c6cf 100644 --- a/.github/conda_pgm_env.yml +++ b/.github/conda_pgm_env.yml @@ -5,7 +5,7 @@ name: conda-pgm-env dependencies: # build env - - python=3.12 + - python=3.14 - pip - scikit-build-core # build deps diff --git a/.github/workflows/build-test-release.yml b/.github/workflows/build-test-release.yml index 07697eecb6..fbd2ec3486 100644 --- a/.github/workflows/build-test-release.yml +++ b/.github/workflows/build-test-release.yml @@ -278,7 +278,9 @@ jobs: cmake --install build/ - name: Build python - run: python -m pip install . --no-build-isolation --no-deps -C wheel.cmake=false + run: | + python conda_build_preparation.py + python -m pip install . --no-build-isolation --no-deps -C wheel.cmake=false - name: Test run: | From 98ac9d9cdb768cfe6992547c334a45581f576a72 Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Tue, 3 Feb 2026 12:21:07 +0100 Subject: [PATCH 81/82] fix clang-tidy Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- power_grid_model_c/power_grid_model_cli/pgm_calculation.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/power_grid_model_c/power_grid_model_cli/pgm_calculation.cpp b/power_grid_model_c/power_grid_model_cli/pgm_calculation.cpp index bf989297e7..2018ba6a6c 100644 --- a/power_grid_model_c/power_grid_model_cli/pgm_calculation.cpp +++ b/power_grid_model_c/power_grid_model_cli/pgm_calculation.cpp @@ -57,7 +57,7 @@ void pgm_calculation(ClIOptions const& cli_options) { Options calc_options{}; calc_options.set_calculation_type(cli_options.calculation_type); calc_options.set_calculation_method(cli_options.calculation_method); - calc_options.set_symmetric(cli_options.symmetric_calculation); + calc_options.set_symmetric(static_cast(cli_options.symmetric_calculation)); calc_options.set_err_tol(cli_options.error_tolerance); calc_options.set_max_iter(cli_options.max_iterations); calc_options.set_threading(cli_options.threading); From 37987418799ce681231dd82eb2898d8f376db05d Mon Sep 17 00:00:00 2001 From: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> Date: Tue, 3 Feb 2026 13:26:24 +0100 Subject: [PATCH 82/82] clang-tidy Signed-off-by: Tony Xiang <19280867+TonyXiang8787@users.noreply.github.com> --- tests/cpp_cli_tests/test_cli.cpp | 70 ++++++++++++++++---------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/tests/cpp_cli_tests/test_cli.cpp b/tests/cpp_cli_tests/test_cli.cpp index 04da0cdce3..0374cd864c 100644 --- a/tests/cpp_cli_tests/test_cli.cpp +++ b/tests/cpp_cli_tests/test_cli.cpp @@ -202,7 +202,7 @@ std::string read_stdout_content() { // Get file size version_ifs.seekg(0, std::ios::end); - std::streamsize size = version_ifs.tellg(); + std::streamsize const size = version_ifs.tellg(); version_ifs.seekg(0, std::ios::beg); // Read the entire file @@ -246,34 +246,33 @@ struct BufferRef { AttributeBuffer const* attribute_buffer; void check_i_source(std::vector const& i_source_ref) const { - Idx const batch_size = i_source_ref.size(); + Idx const batch_size = static_cast(i_source_ref.size()); for (Idx idx = 0; idx < batch_size; ++idx) { double const i_calculated = [this, idx]() { if (use_attribute_buffer) { if (symmetric == PGM_symmetric) { auto const& data_vector = attribute_buffer->get_data_vector(); return data_vector.at(idx); - } else { - auto const& data_vector = attribute_buffer->get_data_vector>(); - auto const& val_array = data_vector.at(idx); - CHECK(val_array[0] == doctest::Approx(val_array[1])); - CHECK(val_array[0] == doctest::Approx(val_array[2])); - return val_array[0]; - } - } else { - // use row buffer - if (symmetric == PGM_symmetric) { - double value{}; - row_buffer->get_value(PGM_def_sym_output_source_i, &value, idx, 0); - return value; - } else { - std::array val_array{}; - row_buffer->get_value(PGM_def_asym_output_source_i, val_array.data(), idx, 0); - CHECK(val_array[0] == doctest::Approx(val_array[1])); - CHECK(val_array[0] == doctest::Approx(val_array[2])); - return val_array[0]; } + // else: use attribute buffer with asymmetric data + auto const& data_vector = attribute_buffer->get_data_vector>(); + auto const& val_array = data_vector.at(idx); + CHECK(val_array[0] == doctest::Approx(val_array[1])); + CHECK(val_array[0] == doctest::Approx(val_array[2])); + return val_array[0]; } + // else: use row buffer + if (symmetric == PGM_symmetric) { + double value{}; + row_buffer->get_value(PGM_def_sym_output_source_i, &value, idx, 0); + return value; + } + // else: use row buffer with asymmetric data + std::array val_array{}; + row_buffer->get_value(PGM_def_asym_output_source_i, val_array.data(), idx, 0); + CHECK(val_array[0] == doctest::Approx(val_array[1])); + CHECK(val_array[0] == doctest::Approx(val_array[2])); + return val_array[0]; }(); CHECK(i_calculated == doctest::Approx(i_source_ref.at(idx))); } @@ -299,19 +298,18 @@ struct CLITestCase { PGM_SerializationFormat get_output_format() const { if (output_serialization.has_value()) { return output_serialization.value(); - } else if (is_batch && batch_p_msgpack) { + } + if (is_batch && batch_p_msgpack) { return PGM_msgpack; - } else { - return PGM_json; } + return PGM_json; } bool has_output_filter() const { return component_filter || attribute_filter; } PGM_SymmetryType get_symmetry() const { if (symmetry.has_value()) { return symmetry.value(); - } else { - return PGM_symmetric; } + return PGM_symmetric; } bool output_columnar() const { if (output_compact_serialization.has_value()) { @@ -396,15 +394,15 @@ struct CLITestCase { REQUIRE(row_buffer->get() == nullptr); if (attribute_filter) { REQUIRE(owning_memory.attribute_buffers[source_idx].size() == 1); - return &owning_memory.attribute_buffers[source_idx][0]; - } else { - for (auto const& attr_buf : owning_memory.attribute_buffers[source_idx]) { - if (MetaData::attribute_name(attr_buf.get_attribute()) == "i") { - return &attr_buf; - } + return owning_memory.attribute_buffers[source_idx].data(); + } + // else: search for 'i' attribute buffer + for (auto const& attr_buf : owning_memory.attribute_buffers[source_idx]) { + if (MetaData::attribute_name(attr_buf.get_attribute()) == "i") { + return &attr_buf; } - DOCTEST_FAIL("Attribute 'i' buffer not found"); } + DOCTEST_FAIL("Attribute 'i' buffer not found"); } // when no filter, buffer should not be nullptr, and return nullptr for attribute buffer REQUIRE(row_buffer->get() != nullptr); @@ -431,7 +429,8 @@ struct CLITestCase { prepare_data(); std::string const command = build_command(); INFO("CLI command: ", command); - int ret = std::system(command.c_str()); + // NOLINTNEXTLINE(cert-env33-c,concurrency-mt-unsafe) + int const ret = std::system(command.c_str()); std::string const stdout_content = read_stdout_content(); INFO("CLI stdout content: ", stdout_content); REQUIRE(ret == 0); @@ -444,7 +443,8 @@ struct CLITestCase { TEST_CASE("Test CLI version") { prepare_data(); std::string const command = std::string{cli_executable} + " --version" + " > " + stdout_path().string(); - int ret = std::system(command.c_str()); + // NOLINTNEXTLINE(cert-env33-c,concurrency-mt-unsafe) + int const ret = std::system(command.c_str()); std::string const file_content = read_stdout_content(); INFO("CLI stdout content: ", file_content); REQUIRE(ret == 0);