diff --git a/build.sh b/build.sh index d87355f57..d09f26189 100755 --- a/build.sh +++ b/build.sh @@ -68,7 +68,6 @@ if [[ "${COVERAGE}" ]]; then PATH=${PATH}:${PWD} lcov -q -c \ -d ${BUILD_DIR}/tests/cpp_unit_tests/CMakeFiles/power_grid_model_unit_tests.dir \ - -d ${BUILD_DIR}/tests/cpp_integration_tests/CMakeFiles/power_grid_model_integration_tests.dir \ -d ${BUILD_DIR}/tests/cpp_validation_tests/CMakeFiles/power_grid_model_validation_tests.dir \ -d ${BUILD_DIR}/tests/native_api_tests/CMakeFiles/power_grid_model_api_tests.dir \ -d ${BUILD_DIR}/power_grid_model_c/power_grid_model_c/CMakeFiles/power_grid_model_c.dir \ diff --git a/docs/advanced_documentation/build-guide.md b/docs/advanced_documentation/build-guide.md index 6b2ca2dbf..04975c418 100644 --- a/docs/advanced_documentation/build-guide.md +++ b/docs/advanced_documentation/build-guide.md @@ -161,7 +161,6 @@ In the developer build the following build targets (directories) are enabled: * `power_grid_model_c`: a dynamic library (`.dll` or `.so`) with stable pure C API/ABI which can be used by any application * `tests/cpp_unit_tests`: the unit test target for the C++ core using the `doctest` framework. -* `tests/cpp_integration_tests`: the integration test target for the C++ core using the `doctest` framework. * `tests/cpp_validation_tests`: the validation test target using the `doctest` framework * `tests/native_api_tests`: the C API test target using the `doctest` framework * `tests/benchmark_cpp`: the C++ benchmark target for performance measure. diff --git a/docs/advanced_documentation/c-api.md b/docs/advanced_documentation/c-api.md index 986b8eb8a..f8f8a17d2 100644 --- a/docs/advanced_documentation/c-api.md +++ b/docs/advanced_documentation/c-api.md @@ -116,7 +116,7 @@ and may vary between power grid model versions, compilers, operating systems and <-- 3 lines. | | | | | | | <-- alignment: a line may start every 4 bytes. iiiift iiiift iiiift <-- data: 6 bytes per line: 4 bytes for the ID, 1 for the from_status and 1 for the to_status. -| ..| ..| ..| <-- padding: (6 mod 4 = 2) bytes after every line. +| ..| ..| ..| <-- padding: (4 - (6 mod 4) = 2) bytes after every line. | | | | <-- aligned size: (6 + 2 = 8) bytes every line. ``` diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/common/enum.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/common/enum.hpp index e24291164..a75961c74 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/common/enum.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/common/enum.hpp @@ -24,7 +24,7 @@ enum class ControlSide : IntS { from = 0, to = 1, side_1 = 0, side_2 = 1, side_3 enum class CalculationType : IntS { power_flow = 0, state_estimation = 1, short_circuit = 2 }; -enum class CalculationSymmetry : IntS { symmetric = 0, asymmetric = 1 }; +enum class CalculationSymmetry : IntS { asymmetric = 0, symmetric = 1 }; enum class CalculationMethod : IntS { default_method = -128, diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/main_model.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/main_model.hpp index 4c7cfa288..5f18b35e8 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/main_model.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/main_model.hpp @@ -56,9 +56,6 @@ class MainModel { } void set_construction_complete() { impl().set_construction_complete(); } - void restore_components(ConstDataset const& update_data) { - impl().restore_components(impl().get_all_sequence_idx_map(update_data)); - } template void add_component(std::vector const& components) { add_component(std::span{components}); diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/main_model_impl.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/main_model_impl.hpp index b4b8b3ab0..a512fc834 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/main_model_impl.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/main_model_impl.hpp @@ -300,35 +300,6 @@ class MainModelImpl, ComponentLis update_components(update_data, 0, sequence_idx_map); } - template void restore_component(SequenceIdxView const& sequence_idx) { - constexpr auto component_index = main_core::utils::index_of_component; - - auto& cached_inverse_update = std::get(cached_inverse_update_); - auto const& component_sequence = std::get(sequence_idx); - - if (!cached_inverse_update.empty()) { - update_component(cached_inverse_update, component_sequence); - cached_inverse_update.clear(); - } - } - - // restore the initial values of all components - void restore_components(SequenceIdxView const& sequence_idx) { - (restore_component(sequence_idx), ...); - - update_state(cached_state_changes_); - cached_state_changes_ = {}; - } - void restore_components(std::array const>, - main_core::utils::n_types> const& sequence_idx) { - restore_components(std::array{std::span{ - std::get>(sequence_idx).get()}...}); - } - void restore_components(main_core::utils::SequenceIdx const& sequence_idx) { - restore_components(std::array{std::span{ - std::get>(sequence_idx)}...}); - } - // set complete construction // initialize internal arrays void set_construction_complete() { @@ -399,6 +370,35 @@ class MainModelImpl, ComponentLis is_asym_parameter_up_to_date_ = is_asym_parameter_up_to_date_ && !changes.topo && !changes.param; } + template void restore_component(SequenceIdxView const& sequence_idx) { + constexpr auto component_index = main_core::utils::index_of_component; + + auto& cached_inverse_update = std::get(cached_inverse_update_); + auto const& component_sequence = std::get(sequence_idx); + + if (!cached_inverse_update.empty()) { + update_component(cached_inverse_update, component_sequence); + cached_inverse_update.clear(); + } + } + + // restore the initial values of all components + void restore_components(SequenceIdxView const& sequence_idx) { + (restore_component(sequence_idx), ...); + + update_state(cached_state_changes_); + cached_state_changes_ = {}; + } + void restore_components(std::array const>, + main_core::utils::n_types> const& sequence_idx) { + restore_components(std::array{std::span{ + std::get>(sequence_idx).get()}...}); + } + void restore_components(main_core::utils::SequenceIdx const& sequence_idx) { + restore_components(std::array{std::span{ + std::get>(sequence_idx)}...}); + } + template requires std::invocable, Idx /*n_math_solvers*/> && diff --git a/power_grid_model_c/power_grid_model_c/include/power_grid_model_c/basics.h b/power_grid_model_c/power_grid_model_c/include/power_grid_model_c/basics.h index ff39e9750..6b769c596 100644 --- a/power_grid_model_c/power_grid_model_c/include/power_grid_model_c/basics.h +++ b/power_grid_model_c/power_grid_model_c/include/power_grid_model_c/basics.h @@ -166,6 +166,14 @@ enum PGM_CalculationMethod { PGM_iec60909 = 5 /**< fault analysis for short circuits using the iec60909 standard */ }; +/** + * @brief Enumeration for calculation and/or component symmetry + */ +enum PGM_SymmetryType { + PGM_asymmetric = 0, /** < asymmetric calculation and/or component */ + PGM_symmetric = 1 /** < symmetric calculation and/or component */ +}; + /** * @brief Enumeration of error codes. * diff --git a/power_grid_model_c/power_grid_model_c/include/power_grid_model_c/options.h b/power_grid_model_c/power_grid_model_c/include/power_grid_model_c/options.h index e1db736ce..3671727a0 100644 --- a/power_grid_model_c/power_grid_model_c/include/power_grid_model_c/options.h +++ b/power_grid_model_c/power_grid_model_c/include/power_grid_model_c/options.h @@ -66,7 +66,7 @@ PGM_API void PGM_set_calculation_method(PGM_Handle* handle, PGM_Options* opt, PG * * @param handle * @param opt The pointer to the option instance. - * @param sym 1 for symmetric calculation; 0 for asymmetric calculation. + * @param sym See #PGM_CalculationSymmetry . 1 for symmetric calculation; 0 for asymmetric calculation. */ PGM_API void PGM_set_symmetric(PGM_Handle* handle, PGM_Options* opt, PGM_Idx sym); diff --git a/power_grid_model_c/power_grid_model_c/src/model.cpp b/power_grid_model_c/power_grid_model_c/src/model.cpp index fa4f5c521..8c921c6f9 100644 --- a/power_grid_model_c/power_grid_model_c/src/model.cpp +++ b/power_grid_model_c/power_grid_model_c/src/model.cpp @@ -75,10 +75,14 @@ constexpr auto get_calculation_type(PGM_Options const& opt) { } constexpr auto get_calculation_symmetry(PGM_Options const& opt) { - if (opt.symmetric == 0) { + switch (opt.symmetric) { + case PGM_asymmetric: return CalculationSymmetry::asymmetric; + case PGM_symmetric: + return CalculationSymmetry::symmetric; + default: + throw MissingCaseForEnumError{"get_calculation_symmetry", opt.tap_changing_strategy}; } - return CalculationSymmetry::symmetric; } constexpr auto get_calculation_method(PGM_Options const& opt) { 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 5e405bbae..4ac301688 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 @@ -25,7 +25,7 @@ class DatasetInfo { std::string name() const { return std::string{handle_.call_with(PGM_dataset_info_name, info_)}; } - Idx is_batch() const { return handle_.call_with(PGM_dataset_info_is_batch, info_); } + bool is_batch() const { return handle_.call_with(PGM_dataset_info_is_batch, info_) != 0; } Idx batch_size() const { return handle_.call_with(PGM_dataset_info_batch_size, info_); } @@ -88,8 +88,10 @@ class DatasetWritable { class DatasetMutable { public: - DatasetMutable(std::string const& dataset, Idx is_batch, Idx batch_size) - : dataset_{handle_.call_with(PGM_create_dataset_mutable, dataset.c_str(), is_batch, batch_size)}, + explicit DatasetMutable(std::string const& dataset, bool is_batch, Idx batch_size) + : handle_{}, + dataset_{ + handle_.call_with(PGM_create_dataset_mutable, dataset.c_str(), (is_batch ? Idx{1} : Idx{0}), batch_size)}, info_{handle_.call_with(PGM_dataset_mutable_get_info, get())} {} RawMutableDataset const* get() const { return dataset_.get(); } @@ -126,12 +128,16 @@ class DatasetMutable { class DatasetConst { public: - DatasetConst(std::string const& dataset, Idx is_batch, Idx batch_size) - : dataset_{handle_.call_with(PGM_create_dataset_const, dataset.c_str(), is_batch, batch_size)}, + explicit DatasetConst(std::string const& dataset, bool is_batch, Idx batch_size) + : handle_{}, + dataset_{ + handle_.call_with(PGM_create_dataset_const, dataset.c_str(), (is_batch ? Idx{1} : Idx{0}), batch_size)}, info_{handle_.call_with(PGM_dataset_const_get_info, get())} {} + DatasetConst(DatasetWritable const& writable_dataset) : dataset_{handle_.call_with(PGM_create_dataset_const_from_writable, writable_dataset.get())}, info_{handle_.call_with(PGM_dataset_const_get_info, get())} {} + DatasetConst(DatasetMutable const& mutable_dataset) : dataset_{handle_.call_with(PGM_create_dataset_const_from_mutable, mutable_dataset.get())}, info_{handle_.call_with(PGM_dataset_const_get_info, get())} {} diff --git a/sonar-project.properties b/sonar-project.properties index 7d3b10ac1..a6b51be2b 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -11,7 +11,7 @@ sonar.projectVersion=1.0 # include C++ and Python source file # since the C++ part is header only, also include the C++ unit test .cpp file -sonar.sources=src,tests/cpp_unit_tests,tests/cpp_integration_tests,tests/cpp_validation_tests,tests/native_api_tests,power_grid_model_c +sonar.sources=src,tests/cpp_unit_tests,tests/cpp_validation_tests,tests/native_api_tests,power_grid_model_c sonar.tests=tests/unit sonar.sourceEncoding=UTF-8 diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b39378df3..6e983b5a0 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -8,6 +8,5 @@ include("${doctest_DIR}/doctest.cmake") add_subdirectory("native_api_tests") add_subdirectory("cpp_unit_tests") -add_subdirectory("cpp_integration_tests") add_subdirectory("cpp_validation_tests") add_subdirectory("benchmark_cpp") diff --git a/tests/cpp_integration_tests/CMakeLists.txt b/tests/cpp_integration_tests/CMakeLists.txt deleted file mode 100644 index f8a4827f6..000000000 --- a/tests/cpp_integration_tests/CMakeLists.txt +++ /dev/null @@ -1,19 +0,0 @@ -# SPDX-FileCopyrightText: Contributors to the Power Grid Model project -# -# SPDX-License-Identifier: MPL-2.0 - -set(PROJECT_SOURCES - "test_entry_point.cpp" - "test_main_model.cpp" -) - -add_executable(power_grid_model_integration_tests ${PROJECT_SOURCES}) - -target_link_libraries(power_grid_model_integration_tests - PRIVATE - power_grid_model - doctest::doctest - nlohmann_json nlohmann_json::nlohmann_json -) - -doctest_discover_tests(power_grid_model_integration_tests) diff --git a/tests/cpp_integration_tests/test_entry_point.cpp b/tests/cpp_integration_tests/test_entry_point.cpp deleted file mode 100644 index 020ee88a4..000000000 --- a/tests/cpp_integration_tests/test_entry_point.cpp +++ /dev/null @@ -1,9 +0,0 @@ -// 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 diff --git a/tests/cpp_integration_tests/test_main_model.cpp b/tests/cpp_integration_tests/test_main_model.cpp deleted file mode 100644 index 6d73158ef..000000000 --- a/tests/cpp_integration_tests/test_main_model.cpp +++ /dev/null @@ -1,929 +0,0 @@ -// SPDX-FileCopyrightText: Contributors to the Power Grid Model project -// -// SPDX-License-Identifier: MPL-2.0 - -#include -#include - -#include - -namespace power_grid_model { -namespace { -using CalculationType::power_flow; -using enum CalculationSymmetry; - -constexpr auto get_default_options(CalculationSymmetry calculation_symmetry, CalculationMethod calculation_method, - Idx threading = -1) { - return MainModel::Options{.calculation_type = power_flow, - .calculation_symmetry = calculation_symmetry, - .calculation_method = calculation_method, - .err_tol = 1e-8, - .max_iter = 20, - .threading = threading}; -} - -struct regular_update { - using update_type = permanent_update_t; -}; - -struct cached_update { - using update_type = cached_update_t; -}; - -namespace test { -constexpr double z_bus_2 = 1.0 / (0.015 + 0.5e6 / 10e3 / 10e3 * 2); -constexpr double z_total = z_bus_2 + 10.0; -constexpr double u1 = 1.05 * z_bus_2 / (z_bus_2 + 10.0); -constexpr double i = 1.05 * 10e3 / z_total / sqrt3; -constexpr double i_shunt = 0.015 / 0.025 * i; -constexpr double i_load = 0.005 / 0.025 * i; -} // namespace test - -struct State { - // TODO(mgovers): values identical to power_flow/dummy-test/input.json validation case - std::vector node_input{{1, 10e3}, {2, 10e3}, {3, 10e3}}; - std::vector line_input{{4, 1, 2, 1, 1, 10.0, 0.0, 0.0, 0.0, 10.0, 0.0, 0.0, 0.0, 1e3}}; - std::vector link_input{{5, 2, 3, 1, 1}}; - std::vector source_input{{6, 1, 1, 1.05, nan, 1e12, nan, nan}, {10, 3, 0, 1.05, 0.0, 1e12, nan, nan}}; - std::vector sym_load_input{{7, 3, 1, LoadGenType::const_y, 0.5e6, 0.0}}; - std::vector asym_load_input{ - {8, 3, 1, LoadGenType::const_y, RealValue{0.5e6 / 3.0}, RealValue{0.0}}}; - std::vector shunt_input{{9, 3, 1, 0.015, 0.0, 0.015, 0.0}}; - - // TODO(mgovers): only power_flow/pandapower pf validation cases have sensor input and they differ from here - // {{{id}, measured_object}, measured_terminal_type, power_sigma, p_measured, q_measured} - std::vector sym_power_sensor_input{ - {11, 4, MeasuredTerminalType::branch_from, 0.02, 1.1e6, 1.1e3, nan, nan}, - {13, 6, MeasuredTerminalType::source, 0.02, 1.3e6, 1.3e3, nan, nan}, - {14, 6, MeasuredTerminalType::source, 0.02, 1.4e6, 1.4e3, nan, nan}, - {15, 9, MeasuredTerminalType::shunt, 0.02, 1.5e6, 1.5e3, nan, nan}, - {16, 7, MeasuredTerminalType::load, 0.02, 1.6e6, 1.6e3, nan, nan}, - {17, 8, MeasuredTerminalType::load, 0.02, 1.7e6, 1.7e3, nan, nan}, - {28, 3, MeasuredTerminalType::node, 0.02, 3.0e6, 3.0e3, nan, nan}}; - - // TODO(mgovers): only power_flow/pandapower pf validation cases have sensor input and they differ from here - // {{{id}, measured_object}, measured_terminal_type, power_sigma, p_measured, q_measured} - std::vector asym_power_sensor_input{{18, - 4, - MeasuredTerminalType::branch_from, - 0.02, - {2.11e6, 2.12e6, 2.13e6}, - {2.11e3, 2.12e3, 2.13e3}, - {nan, nan, nan}, - {nan, nan, nan}}, - {20, - 6, - MeasuredTerminalType::source, - 0.02, - {2.31e6, 2.32e6, 2.33e6}, - {2.31e3, 2.32e3, 2.33e3}, - {nan, nan, nan}, - {nan, nan, nan}}, - {21, - 6, - MeasuredTerminalType::source, - 0.02, - {2.41e6, 2.42e6, 2.43e6}, - {2.41e3, 2.42e3, 2.43e3}, - {nan, nan, nan}, - {nan, nan, nan}}, - {22, - 9, - MeasuredTerminalType::shunt, - 0.02, - {2.51e6, 2.52e6, 2.53e6}, - {2.51e3, 2.52e3, 2.53e3}, - {nan, nan, nan}, - {nan, nan, nan}}, - {23, - 7, - MeasuredTerminalType::load, - 0.02, - {2.61e6, 2.62e6, 2.63e6}, - {2.61e3, 2.62e3, 2.63e3}, - {nan, nan, nan}, - {nan, nan, nan}}, - {24, - 8, - MeasuredTerminalType::load, - 0.02, - {2.71e6, 2.72e6, 2.73e6}, - {2.71e3, 2.72e3, 2.73e3}, - {nan, nan, nan}, - {nan, nan, nan}}, - {29, - 3, - MeasuredTerminalType::node, - 0.02, - {5.01e6, 5.02e6, 5.03e6}, - {5.01e3, 5.02e3, 5.03e3}, - {nan, nan, nan}, - {nan, nan, nan}}}; - - // TODO(mgovers): only power_flow/pandapower pf validation cases have sensor input and they differ from here - // {{{id}, measured_object}, u_sigma, u_measured, u_angle_measured} - std::vector sym_voltage_sensor_input{{25, 1, 105.0, 10.1e3, 0.1}, - {26, 2, 105.0, 10.2e3, 0.2}}; - - // TODO(mgovers): only power_flow/pandapower pf validation cases have sensor input and they differ from here - // {{{id}, measured_object}, u_sigma, u_measured, u_angle_measured} - std::vector asym_voltage_sensor_input{ - {27, 3, 105.0, {10.31e3 / sqrt3, 10.32e3 / sqrt3, 10.33e3 / sqrt3}, {0.0, -deg_120, -deg_240}}}; - - // TODO(mgovers): only used for updating (and proving no different output). nowhere else used in powerflow - // TODO(mgovers): no powerflow validation cases have fault input - std::vector fault_input{{30, 1, FaultType::single_phase_to_ground, FaultPhase::a, 3, 0.1, 0.1}}; - - // output vector - std::vector> sym_node = std::vector>(3); - std::vector> sym_branch = std::vector>(2); - std::vector> sym_appliance = std::vector>(5); - std::vector> asym_node = std::vector>(3); - std::vector> asym_branch = std::vector>(2); - std::vector> asym_appliance = std::vector>(5); - - // individual symmetric - std::vector> sym_line = std::vector>(1); - std::vector> sym_link = std::vector>(1); - std::vector> sym_load_sym = std::vector>(1); - std::vector> sym_load_asym = std::vector>(1); - std::vector> sym_source = std::vector>(2); - std::vector> sym_shunt = std::vector>(1); - std::vector> sym_voltage_sensor = std::vector>(2); - std::vector> asym_voltage_sensor_sym_output = - std::vector>(1); - std::vector> sym_power_sensor = std::vector>(7); - std::vector> asym_power_sensor_sym_output = - std::vector>(7); - - // individual asymmetric - std::vector> asym_line = std::vector>(1); - std::vector> asym_link = std::vector>(1); - std::vector> asym_load_sym = std::vector>(1); - std::vector> asym_load_asym = std::vector>(1); - std::vector> asym_source = std::vector>(2); - std::vector> asym_shunt = std::vector>(1); - std::vector> asym_voltage_sensor = - std::vector>(1); - std::vector> sym_voltage_sensor_asym_output = - std::vector>(2); - std::vector> asym_power_sensor = std::vector>(7); - std::vector> sym_power_sensor_asym_output = - std::vector>(7); - - // update vector - std::vector sym_load_update{{7, 1, 1.0e6, nan}}; - std::vector asym_load_update{{8, 0, RealValue{nan}, RealValue{nan}}}; - std::vector shunt_update{{9, 0, nan, 0.02, nan, 0.02}}; - std::vector shunt_update_2{{6, 0, nan, 0.01, nan, 0.01}}; // used for test case alternate compute mode - std::vector source_update{{10, 1, test::u1, nan}}; - std::vector link_update{{5, 1, 0}}; - std::vector fault_update{{30, 1, FaultType::three_phase, FaultPhase::abc, 1, nan, nan}}; - - // batch update vector - std::vector batch_sym_load_update{{7, 1, 1.0e6, nan}, {7}, {7}, {7}, {7}}; - std::vector batch_asym_load_update{ - {8, 0, RealValue{nan}, RealValue{nan}}, {8}, {8}, {8}, {8}}; - std::vector batch_shunt_update{{9, 0, nan, 0.02, nan, 0.02}, {9}, {9}, {9}, {9}}; - std::vector batch_source_update{{10, 1, test::u1, nan}, {10}, {10}, {10}, {10}}; - std::vector batch_link_update{{5, 1, 0}, {5}, {5}, {5}, {5}}; - std::vector batch_fault_update{ - {30, 1, FaultType::three_phase, FaultPhase::abc, 1, nan, nan}, {30}, {30}, {30}, {30}}; -}; - -auto default_model(State const& state) -> MainModel { - MainModel main_model{50.0, meta_data::meta_data_gen::meta_data}; - main_model.add_component(state.node_input); - main_model.add_component(state.line_input); - main_model.add_component(state.link_input); - main_model.add_component(state.source_input); - main_model.add_component(state.asym_load_input); - main_model.add_component(state.sym_load_input); - main_model.add_component(state.shunt_input); - main_model.add_component(state.sym_power_sensor_input); - main_model.add_component(state.asym_power_sensor_input); - main_model.add_component(state.sym_voltage_sensor_input); - main_model.add_component(state.asym_voltage_sensor_input); - main_model.add_component(state.fault_input); - main_model.set_construction_complete(); - return main_model; -} -} // namespace - -TEST_CASE("Test main model - power flow") { - State state; - auto main_model = default_model(state); - - SUBCASE("Test get indexer") { // TODO(mgovers): needed - std::vector const node_id{2, 1, 3, 2}; - IdxVector const expected_indexer{1, 0, 2, 1}; - IdxVector indexer(4); - main_model.get_indexer("node", node_id.data(), 4, indexer.data()); - CHECK(indexer == expected_indexer); - } - - SUBCASE("Test duplicated id") { // TODO(mgovers): needed; captured in Python test; maybe move to - // test_main_core_input.cpp - MainModel main_model2{50.0, meta_data::meta_data_gen::meta_data}; - state.node_input[1].id = 1; - CHECK_THROWS_AS(main_model2.add_component(state.node_input), ConflictID); - } - - SUBCASE("Test no existing id") { // TODO(mgovers): needed; captured in Python test; maybe move to - // test_main_core_input.cpp - MainModel main_model2{50.0, meta_data::meta_data_gen::meta_data}; - state.line_input[0].from_node = 100; - main_model2.add_component(state.node_input); - CHECK_THROWS_AS(main_model2.add_component(state.line_input), IDNotFound); - } - - SUBCASE("Test id for wrong type") { // TODO(mgovers): needed; captured in Python test but not all flavors; maybe - // move to test_main_core_input.cpp - MainModel main_model2{50.0, meta_data::meta_data_gen::meta_data}; - - state.link_input[0].from_node = 4; - main_model2.add_component(state.node_input); // 1 2 3 - main_model2.add_component(state.line_input); // 4 - CHECK_THROWS_AS(main_model2.add_component(state.link_input), IDWrongType); - - // Fix link input, retry - state.link_input[0].from_node = 2; - main_model2.add_component(state.link_input); // 5 - - main_model2.add_component(state.source_input); // 6 10 - main_model2.add_component(state.sym_load_input); // 7 - main_model2.add_component(state.asym_load_input); // 8 - main_model2.add_component(state.shunt_input); // 9 - - // voltage sensor with a measured id which is not a node (link) - state.sym_voltage_sensor_input[0].measured_object = 5; - CHECK_THROWS_AS(main_model2.add_component(state.sym_voltage_sensor_input), IDWrongType); - - // Test for all MeasuredTerminalType instances - using enum MeasuredTerminalType; - std::vector const mt_types{branch_from, branch_to, generator, load, shunt, source}; - - // power sensor with terminal branch, with a measured id which is not a branch (node) - for (auto const& mt_type : mt_types) { - state.sym_power_sensor_input[0].measured_object = 1; - state.sym_power_sensor_input[0].measured_terminal_type = mt_type; - CHECK_THROWS_AS(main_model2.add_component(state.sym_power_sensor_input), IDWrongType); - } - } -} - -TEST_CASE_TEMPLATE("Test main model - unknown id", settings, regular_update, - cached_update) { // TODO(mgovers): we need this test - State const state; - auto main_model = default_model(state); - - std::vector const source_update2{SourceUpdate{100, true, nan, nan}}; - ConstDataset update_data{false, 1, "update", meta_data::meta_data_gen::meta_data}; - update_data.add_buffer("source", source_update2.size(), source_update2.size(), nullptr, source_update2.data()); - CHECK_THROWS_AS((main_model.update_components(update_data)), IDNotFound); -} - -TEST_CASE_TEMPLATE( - "Test main model - update only load", settings, regular_update, - cached_update) { // TODO(mgovers): we should whitebox-test this instead; values not reproduced by validation tests - State state; - auto main_model = default_model(state); - - ConstDataset update_data{false, 1, "update", meta_data::meta_data_gen::meta_data}; - update_data.add_buffer("sym_load", state.sym_load_update.size(), state.sym_load_update.size(), nullptr, - state.sym_load_update.data()); - update_data.add_buffer("asym_load", state.asym_load_update.size(), state.asym_load_update.size(), nullptr, - state.asym_load_update.data()); - main_model.update_components(update_data); - - SUBCASE("Symmetrical") { - auto const solver_output = - main_model.calculate(get_default_options(symmetric, CalculationMethod::linear)); - main_model.output_result(solver_output, state.sym_node); - main_model.output_result(solver_output, state.sym_branch); - main_model.output_result(solver_output, state.sym_appliance); - CHECK(state.sym_node[0].u_pu == doctest::Approx(1.05)); - CHECK(state.sym_node[1].u_pu == doctest::Approx(test::u1)); - CHECK(state.sym_node[2].u_pu == doctest::Approx(test::u1)); - CHECK(state.sym_branch[0].i_from == doctest::Approx(test::i)); - CHECK(state.sym_appliance[0].i == doctest::Approx(test::i)); - CHECK(state.sym_appliance[1].i == doctest::Approx(0.0)); - CHECK(state.sym_appliance[2].i == doctest::Approx(test::i_load * 2)); - CHECK(state.sym_appliance[3].i == doctest::Approx(0.0)); - CHECK(state.sym_appliance[4].i == doctest::Approx(test::i_shunt)); - } - SUBCASE("Asymmetrical") { - auto const solver_output = main_model.calculate( - get_default_options(asymmetric, CalculationMethod::linear)); - main_model.output_result(solver_output, state.asym_node); - main_model.output_result(solver_output, state.asym_branch); - main_model.output_result(solver_output, state.asym_appliance); - CHECK(state.asym_node[0].u_pu(0) == doctest::Approx(1.05)); - CHECK(state.asym_node[1].u_pu(1) == doctest::Approx(test::u1)); - CHECK(state.asym_node[2].u_pu(2) == doctest::Approx(test::u1)); - CHECK(state.asym_branch[0].i_from(0) == doctest::Approx(test::i)); - CHECK(state.asym_appliance[0].i(1) == doctest::Approx(test::i)); - CHECK(state.asym_appliance[1].i(2) == doctest::Approx(0.0)); - CHECK(state.asym_appliance[2].i(0) == doctest::Approx(test::i_load * 2)); - CHECK(state.asym_appliance[3].i(1) == doctest::Approx(0.0)); - CHECK(state.asym_appliance[4].i(2) == doctest::Approx(test::i_shunt)); - } -} - -TEST_CASE_TEMPLATE( - "Test main model - update load and shunt param", settings, regular_update, - cached_update) { // TODO(mgovers): we should whitebox-test this instead; values not reproduced by validation tests - State state; - auto main_model = default_model(state); - - state.sym_load_update[0].p_specified = 2.5e6; - ConstDataset update_data{false, 1, "update", meta_data::meta_data_gen::meta_data}; - update_data.add_buffer("sym_load", state.sym_load_update.size(), state.sym_load_update.size(), nullptr, - state.sym_load_update.data()); - update_data.add_buffer("asym_load", state.asym_load_update.size(), state.asym_load_update.size(), nullptr, - state.asym_load_update.data()); - update_data.add_buffer("shunt", state.shunt_update.size(), state.shunt_update.size(), nullptr, - state.shunt_update.data()); - main_model.update_components(update_data); - - SUBCASE("Symmetrical") { - auto const solver_output = - main_model.calculate(get_default_options(symmetric, CalculationMethod::linear)); - main_model.output_result(solver_output, state.sym_node); - main_model.output_result(solver_output, state.sym_branch); - main_model.output_result(solver_output, state.sym_appliance); - CHECK(state.sym_node[0].u_pu == doctest::Approx(1.05)); - CHECK(state.sym_node[1].u_pu == doctest::Approx(test::u1)); - CHECK(state.sym_node[2].u_pu == doctest::Approx(test::u1)); - CHECK(state.sym_branch[0].i_from == doctest::Approx(test::i)); - CHECK(state.sym_appliance[0].i == doctest::Approx(test::i)); - CHECK(state.sym_appliance[1].i == doctest::Approx(0.0)); - CHECK(state.sym_appliance[2].i == doctest::Approx(test::i_load * 2 + test::i_shunt)); - CHECK(state.sym_appliance[3].i == doctest::Approx(0.0)); - CHECK(state.sym_appliance[4].i == doctest::Approx(0.0)); - } - SUBCASE("Asymmetrical") { - auto const solver_output = main_model.calculate( - get_default_options(asymmetric, CalculationMethod::linear)); - main_model.output_result(solver_output, state.asym_node); - main_model.output_result(solver_output, state.asym_branch); - main_model.output_result(solver_output, state.asym_appliance); - CHECK(state.asym_node[0].u_pu(0) == doctest::Approx(1.05)); - CHECK(state.asym_node[1].u_pu(1) == doctest::Approx(test::u1)); - CHECK(state.asym_node[2].u_pu(2) == doctest::Approx(test::u1)); - CHECK(state.asym_branch[0].i_from(0) == doctest::Approx(test::i)); - CHECK(state.asym_appliance[0].i(1) == doctest::Approx(test::i)); - CHECK(state.asym_appliance[1].i(2) == doctest::Approx(0.0)); - CHECK(state.asym_appliance[2].i(0) == doctest::Approx(test::i_load * 2 + test::i_shunt)); - CHECK(state.asym_appliance[3].i(1) == doctest::Approx(0.0)); - CHECK(state.asym_appliance[4].i(2) == doctest::Approx(0.0)); - } -} - -TEST_CASE_TEMPLATE( - "Test main model - all updates", settings, regular_update, - cached_update) { // TODO(mgovers): we should whitebox-test this instead; values not reproduced by validation tests - State state; - auto main_model = default_model(state); - - state.sym_load_update[0].p_specified = 2.5e6; - ConstDataset update_data{false, 1, "update", meta_data::meta_data_gen::meta_data}; - update_data.add_buffer("sym_load", state.sym_load_update.size(), state.sym_load_update.size(), nullptr, - state.sym_load_update.data()); - update_data.add_buffer("asym_load", state.asym_load_update.size(), state.asym_load_update.size(), nullptr, - state.asym_load_update.data()); - update_data.add_buffer("shunt", state.shunt_update.size(), state.shunt_update.size(), nullptr, - state.shunt_update.data()); - update_data.add_buffer("source", state.source_update.size(), state.source_update.size(), nullptr, - state.source_update.data()); - update_data.add_buffer("link", state.link_update.size(), state.link_update.size(), nullptr, - state.link_update.data()); - update_data.add_buffer("fault", state.fault_update.size(), state.fault_update.size(), nullptr, - state.fault_update.data()); - - main_model.update_components(update_data); - - SUBCASE("Symmetrical") { - auto const solver_output = - main_model.calculate(get_default_options(symmetric, CalculationMethod::linear)); - main_model.output_result(solver_output, state.sym_node); - main_model.output_result(solver_output, state.sym_branch); - main_model.output_result(solver_output, state.sym_appliance); - CHECK(state.sym_node[0].u_pu == doctest::Approx(1.05)); - CHECK(state.sym_node[1].u_pu == doctest::Approx(1.05)); - CHECK(state.sym_node[2].u_pu == doctest::Approx(test::u1)); - CHECK(state.sym_branch[0].i_from == doctest::Approx(0.0).epsilon(1e-6)); - CHECK(state.sym_appliance[0].i == doctest::Approx(0.0).epsilon(1e-6)); - CHECK(state.sym_appliance[1].i == doctest::Approx(test::i)); - CHECK(state.sym_appliance[2].i == doctest::Approx(test::i)); - CHECK(state.sym_appliance[3].i == doctest::Approx(0.0)); - CHECK(state.sym_appliance[4].i == doctest::Approx(0.0)); - } - SUBCASE("Asymmetrical") { - auto const solver_output = main_model.calculate( - get_default_options(asymmetric, CalculationMethod::linear)); - main_model.output_result(solver_output, state.asym_node); - main_model.output_result(solver_output, state.asym_branch); - main_model.output_result(solver_output, state.asym_appliance); - CHECK(state.asym_node[0].u_pu(0) == doctest::Approx(1.05)); - CHECK(state.asym_node[1].u_pu(1) == doctest::Approx(1.05)); - CHECK(state.asym_node[2].u_pu(2) == doctest::Approx(test::u1)); - CHECK(state.asym_branch[0].i_from(0) == doctest::Approx(0.0).epsilon(1e-6)); - CHECK(state.asym_appliance[0].i(1) == doctest::Approx(0.0).epsilon(1e-6)); - CHECK(state.asym_appliance[1].i(2) == doctest::Approx(test::i)); - CHECK(state.asym_appliance[2].i(0) == doctest::Approx(test::i)); - CHECK(state.asym_appliance[3].i(1) == doctest::Approx(0.0)); - CHECK(state.asym_appliance[4].i(2) == doctest::Approx(0.0)); - } -} - -TEST_CASE_TEMPLATE("Test main model - single permanent update from batch", settings, regular_update, - cached_update) { // TODO(mgovers): we should whitebox-test this instead - State state; - auto main_model = default_model(state); - - state.batch_sym_load_update[0].p_specified = 2.5e6; - ConstDataset update_data{true, 5, "update", meta_data::meta_data_gen::meta_data}; - update_data.add_buffer("sym_load", 1, state.batch_sym_load_update.size(), nullptr, - state.batch_sym_load_update.data()); - update_data.add_buffer("asym_load", 1, state.batch_asym_load_update.size(), nullptr, - state.batch_asym_load_update.data()); - update_data.add_buffer("shunt", 1, state.batch_shunt_update.size(), nullptr, state.batch_shunt_update.data()); - update_data.add_buffer("source", 1, state.batch_source_update.size(), nullptr, state.batch_source_update.data()); - update_data.add_buffer("link", 1, state.batch_link_update.size(), nullptr, state.batch_link_update.data()); - update_data.add_buffer("fault", 1, state.batch_fault_update.size(), nullptr, state.batch_fault_update.data()); - - main_model.update_components(update_data); - - SUBCASE("Symmetrical") { - auto const solver_output = - main_model.calculate(get_default_options(symmetric, CalculationMethod::linear)); - main_model.output_result(solver_output, state.sym_node); - main_model.output_result(solver_output, state.sym_branch); - main_model.output_result(solver_output, state.sym_appliance); - CHECK(state.sym_node[0].u_pu == doctest::Approx(1.05)); - CHECK(state.sym_node[1].u_pu == doctest::Approx(1.05)); - CHECK(state.sym_node[2].u_pu == doctest::Approx(test::u1)); - CHECK(state.sym_branch[0].i_from == doctest::Approx(0.0).epsilon(1e-6)); - CHECK(state.sym_appliance[0].i == doctest::Approx(0.0).epsilon(1e-6)); - CHECK(state.sym_appliance[1].i == doctest::Approx(test::i)); - CHECK(state.sym_appliance[2].i == doctest::Approx(test::i)); - CHECK(state.sym_appliance[3].i == doctest::Approx(0.0)); - CHECK(state.sym_appliance[4].i == doctest::Approx(0.0)); - } - SUBCASE("Asymmetrical") { - auto const solver_output = main_model.calculate( - get_default_options(asymmetric, CalculationMethod::linear)); - main_model.output_result(solver_output, state.asym_node); - main_model.output_result(solver_output, state.asym_branch); - main_model.output_result(solver_output, state.asym_appliance); - CHECK(state.asym_node[0].u_pu(0) == doctest::Approx(1.05)); - CHECK(state.asym_node[1].u_pu(1) == doctest::Approx(1.05)); - CHECK(state.asym_node[2].u_pu(2) == doctest::Approx(test::u1)); - CHECK(state.asym_branch[0].i_from(0) == doctest::Approx(0.0).epsilon(1e-6)); - CHECK(state.asym_appliance[0].i(1) == doctest::Approx(0.0).epsilon(1e-6)); - CHECK(state.asym_appliance[1].i(2) == doctest::Approx(test::i)); - CHECK(state.asym_appliance[2].i(0) == doctest::Approx(test::i)); - CHECK(state.asym_appliance[3].i(1) == doctest::Approx(0.0)); - CHECK(state.asym_appliance[4].i(2) == doctest::Approx(0.0)); - } -} - -TEST_CASE_TEMPLATE("Test main model - restore components", settings, regular_update, - cached_update) { // TODO(mgovers): either whitebox (as a sub-part of batch impl) or otherwise drop - // entirely (tested by batch update) - State state; - auto main_model = default_model(state); - - auto const solver_output_orig = - main_model.calculate(get_default_options(symmetric, CalculationMethod::linear)); - - ConstDataset update_data{false, 1, "update", meta_data::meta_data_gen::meta_data}; - update_data.add_buffer("sym_load", state.sym_load_update.size(), state.sym_load_update.size(), nullptr, - state.sym_load_update.data()); - update_data.add_buffer("asym_load", state.asym_load_update.size(), state.asym_load_update.size(), nullptr, - state.asym_load_update.data()); - - main_model.update_components(update_data); - main_model.restore_components(update_data); - - SUBCASE("Symmetrical") { - auto const solver_output_result = - main_model.calculate(get_default_options(symmetric, CalculationMethod::linear)); - main_model.output_result(solver_output_result, state.sym_node); - main_model.output_result(solver_output_result, state.sym_branch); - main_model.output_result(solver_output_result, state.sym_appliance); - - CHECK(state.sym_node[0].u_pu == doctest::Approx(1.05)); - CHECK(state.sym_node[1].u_pu == doctest::Approx(test::u1)); - CHECK(state.sym_node[2].u_pu == doctest::Approx(test::u1)); - CHECK(state.sym_branch[0].i_from == doctest::Approx(test::i)); - CHECK(state.sym_appliance[0].i == doctest::Approx(test::i)); - CHECK(state.sym_appliance[1].i == doctest::Approx(0.0)); - if constexpr (settings::update_type::value) { - CHECK(state.sym_appliance[2].i == doctest::Approx(test::i_load)); - CHECK(state.sym_appliance[3].i == doctest::Approx(test::i_load)); - } else { - CHECK(state.sym_appliance[2].i == doctest::Approx(test::i_load * 2)); - CHECK(state.sym_appliance[3].i == doctest::Approx(0.0)); - } - CHECK(state.sym_appliance[4].i == doctest::Approx(test::i_shunt)); - } - SUBCASE("Asymmetrical") { - auto const solver_output = main_model.calculate( - get_default_options(asymmetric, CalculationMethod::linear)); - main_model.output_result(solver_output, state.asym_node); - main_model.output_result(solver_output, state.asym_branch); - main_model.output_result(solver_output, state.asym_appliance); - - CHECK(state.asym_node[0].u_pu(0) == doctest::Approx(1.05)); - CHECK(state.asym_node[1].u_pu(1) == doctest::Approx(test::u1)); - CHECK(state.asym_node[2].u_pu(2) == doctest::Approx(test::u1)); - CHECK(state.asym_branch[0].i_from(0) == doctest::Approx(test::i)); - CHECK(state.asym_appliance[0].i(1) == doctest::Approx(test::i)); - CHECK(state.asym_appliance[1].i(2) == doctest::Approx(0.0)); - if constexpr (settings::update_type::value) { - CHECK(state.asym_appliance[2].i(0) == doctest::Approx(test::i_load)); - CHECK(state.asym_appliance[3].i(1) == doctest::Approx(test::i_load)); - } else { - CHECK(state.asym_appliance[2].i(0) == doctest::Approx(test::i_load * 2)); - CHECK(state.asym_appliance[3].i(1) == doctest::Approx(0.0)); - } - CHECK(state.asym_appliance[4].i(2) == doctest::Approx(test::i_shunt)); - } -} - -TEST_CASE_TEMPLATE("Test main model - updates w/ alternating compute mode", settings, regular_update, - cached_update) { // TODO(mgovers): move to api tests; not possible with current validation framework - constexpr auto check_sym = [](MainModel const& model_, auto const& math_output_) { - State state_; - model_.output_result(math_output_, state_.sym_node); - model_.output_result(math_output_, state_.sym_branch); - model_.output_result(math_output_, state_.sym_appliance); - - CHECK(state_.sym_node[0].u_pu == doctest::Approx(1.05)); - CHECK(state_.sym_node[1].u_pu == doctest::Approx(test::u1)); - CHECK(state_.sym_node[2].u_pu == doctest::Approx(test::u1)); - CHECK(state_.sym_branch[0].i_from == doctest::Approx(test::i)); - CHECK(state_.sym_appliance[0].i == doctest::Approx(test::i)); - CHECK(state_.sym_appliance[1].i == doctest::Approx(0.0)); - CHECK(state_.sym_appliance[2].i == doctest::Approx(test::i_load * 2 + test::i_shunt)); - CHECK(state_.sym_appliance[3].i == doctest::Approx(0.0)); - CHECK(state_.sym_appliance[4].i == doctest::Approx(0.0)); - }; - constexpr auto check_asym = [](MainModel const& model_, auto const& math_output_) { - State state_; - model_.output_result(math_output_, state_.asym_node); - model_.output_result(math_output_, state_.asym_branch); - model_.output_result(math_output_, state_.asym_appliance); - CHECK(state_.asym_node[0].u_pu(0) == doctest::Approx(1.05)); - CHECK(state_.asym_node[1].u_pu(1) == doctest::Approx(test::u1)); - CHECK(state_.asym_node[2].u_pu(2) == doctest::Approx(test::u1)); - CHECK(state_.asym_branch[0].i_from(0) == doctest::Approx(test::i)); - CHECK(state_.asym_appliance[0].i(1) == doctest::Approx(test::i)); - CHECK(state_.asym_appliance[1].i(2) == doctest::Approx(0.0)); - CHECK(state_.asym_appliance[2].i(0) == doctest::Approx(test::i_load * 2 + test::i_shunt)); - CHECK(state_.asym_appliance[3].i(1) == doctest::Approx(0.0)); - CHECK(state_.asym_appliance[4].i(2) == doctest::Approx(0.0)); - }; - - State state; - auto main_model = default_model(state); - - state.sym_load_update[0].p_specified = 2.5e6; - - ConstDataset update_data{false, 1, "update", meta_data::meta_data_gen::meta_data}; - update_data.add_buffer("sym_load", state.sym_load_update.size(), state.sym_load_update.size(), nullptr, - state.sym_load_update.data()); - update_data.add_buffer("asym_load", state.asym_load_update.size(), state.asym_load_update.size(), nullptr, - state.asym_load_update.data()); - update_data.add_buffer("shunt", state.shunt_update.size(), state.shunt_update.size(), nullptr, - state.shunt_update.data()); - - // This will lead to no topo change but param change - main_model.update_components(update_data); - - auto const math_output_sym_1 = - main_model.calculate(get_default_options(symmetric, CalculationMethod::linear)); - check_sym(main_model, math_output_sym_1); - - auto const math_output_asym_1 = - main_model.calculate(get_default_options(asymmetric, CalculationMethod::linear)); - check_asym(main_model, math_output_asym_1); - - SUBCASE("No new update") { - // Math state may be fully cached - } - if constexpr (std::same_as) { - SUBCASE("No new parameter change") { - // Math state may be fully cached due to no change - main_model.update_components(update_data); - } - } - SUBCASE("With parameter change") { - // Restore to original state and re-apply same update: causes param change for cached update - main_model.restore_components(update_data); - main_model.update_components(update_data); - } - - auto const math_output_asym_2 = - main_model.calculate(get_default_options(asymmetric, CalculationMethod::linear)); - check_asym(main_model, math_output_asym_2); - - auto const math_output_sym_2 = - main_model.calculate(get_default_options(symmetric, CalculationMethod::linear)); - check_sym(main_model, math_output_sym_2); - - main_model.restore_components(update_data); -} - -namespace { -auto incomplete_input_model(State const& state) -> MainModel { - MainModel main_model{50.0, meta_data::meta_data_gen::meta_data}; - - std::vector const incomplete_source_input{{6, 1, 1, nan, nan, 1e12, nan, nan}, - {10, 3, 1, nan, nan, 1e12, nan, nan}}; - std::vector const incomplete_sym_load_input{{7, 3, 1, LoadGenType::const_y, nan, 0.0}}; - std::vector const incomplete_asym_load_input{ - {8, 3, 1, LoadGenType::const_y, RealValue{nan}, RealValue{0.0}}}; - - main_model.add_component(state.node_input); - main_model.add_component(state.line_input); - main_model.add_component(state.link_input); - main_model.add_component(incomplete_source_input); - main_model.add_component(incomplete_sym_load_input); - main_model.add_component(incomplete_asym_load_input); - main_model.add_component(state.shunt_input); - main_model.set_construction_complete(); - - return main_model; -} -} // namespace - -TEST_CASE("Test main model - incomplete input") { - using CalculationMethod::iterative_current; - using CalculationMethod::linear; - using CalculationMethod::linear_current; - using CalculationMethod::newton_raphson; - - State const state; - auto main_model = default_model(state); - auto test_model = incomplete_input_model(state); - - std::vector complete_source_update{{6, 1, 1.05, nan}, {10, 1, 1.05, 0}}; - std::vector complete_sym_load_update{{7, 1, 0.5e6, nan}}; - std::vector complete_asym_load_update{ - {8, 1, RealValue{0.5e6 / 3.0}, RealValue{nan}}}; - - ConstDataset update_data{false, 1, "update", meta_data::meta_data_gen::meta_data}; - update_data.add_buffer("source", complete_source_update.size(), complete_source_update.size(), nullptr, - complete_source_update.data()); - update_data.add_buffer("sym_load", complete_sym_load_update.size(), complete_sym_load_update.size(), nullptr, - complete_sym_load_update.data()); - update_data.add_buffer("asym_load", complete_asym_load_update.size(), complete_asym_load_update.size(), nullptr, - complete_asym_load_update.data()); - - std::vector incomplete_source_update{{6, na_IntS, nan, nan}, {10, na_IntS, nan, nan}}; - std::vector incomplete_sym_load_update{{7, na_IntS, nan, nan}}; - std::vector incomplete_asym_load_update{ - {8, na_IntS, RealValue{nan}, RealValue{nan}}}; - - ConstDataset incomplete_update_data{false, 1, "update", meta_data::meta_data_gen::meta_data}; - incomplete_update_data.add_buffer("source", incomplete_source_update.size(), incomplete_source_update.size(), - nullptr, incomplete_source_update.data()); - incomplete_update_data.add_buffer("sym_load", incomplete_sym_load_update.size(), incomplete_sym_load_update.size(), - nullptr, incomplete_sym_load_update.data()); - incomplete_update_data.add_buffer("asym_load", incomplete_asym_load_update.size(), - incomplete_asym_load_update.size(), nullptr, incomplete_asym_load_update.data()); - - MainModel const ref_model{main_model}; - - SUBCASE("Asymmetrical - Complete") { // TODO(mgovers): no validation case for asym exists - MutableDataset test_result_data{true, 1, "asym_output", meta_data::meta_data_gen::meta_data}; - MutableDataset ref_result_data{true, 1, "asym_output", meta_data::meta_data_gen::meta_data}; - - std::vector> test_asym_node(state.asym_node.size()); - std::vector> ref_asym_node(state.asym_node.size()); - test_result_data.add_buffer("node", test_asym_node.size(), test_asym_node.size(), nullptr, - test_asym_node.data()); - ref_result_data.add_buffer("node", ref_asym_node.size(), ref_asym_node.size(), nullptr, ref_asym_node.data()); - - SUBCASE("Test linear calculation") { - test_model.calculate(get_default_options(asymmetric, linear), test_result_data, update_data); - main_model.calculate(get_default_options(asymmetric, linear), ref_result_data, update_data); - } - - SUBCASE("Test linear current calculation") { - test_model.calculate(get_default_options(asymmetric, linear_current), test_result_data, update_data); - main_model.calculate(get_default_options(asymmetric, linear_current), ref_result_data, update_data); - } - - SUBCASE("Test iterative current calculation") { - test_model.calculate(get_default_options(asymmetric, iterative_current), test_result_data, update_data); - main_model.calculate(get_default_options(asymmetric, iterative_current), ref_result_data, update_data); - } - - SUBCASE("Test iterative Newton-Rhapson calculation") { - test_model.calculate(get_default_options(asymmetric, newton_raphson), test_result_data, update_data); - main_model.calculate(get_default_options(asymmetric, newton_raphson), ref_result_data, update_data); - } - - for (auto component_idx : {0, 1, 2}) { - CAPTURE(component_idx); - - for (auto phase_idx : {0, 1, 2}) { - CAPTURE(phase_idx); - - CHECK(test_asym_node[component_idx].u_pu(phase_idx) == - doctest::Approx(ref_asym_node[component_idx].u_pu(phase_idx))); - } - } - } - - SUBCASE("Symmetrical - Incomplete") { // TODO(mgovers): not tested elsewhere; maybe test in API model? - MutableDataset test_result_data{true, 1, "sym_output", meta_data::meta_data_gen::meta_data}; - MutableDataset const ref_result_data{true, 1, "sym_output", meta_data::meta_data_gen::meta_data}; - - std::vector> test_sym_node(state.sym_node.size()); - test_result_data.add_buffer("node", test_sym_node.size(), test_sym_node.size(), nullptr, test_sym_node.data()); - - SUBCASE("Target dataset") { - CHECK_THROWS_AS(test_model.calculate({.calculation_type = power_flow, - .calculation_symmetry = symmetric, - .calculation_method = linear, - .err_tol = 1e-8, - .max_iter = 1}, - test_result_data), - SparseMatrixError); - } - SUBCASE("Empty update dataset") { - ConstDataset const update_data{false, 1, "update", meta_data::meta_data_gen::meta_data}; - - CHECK_THROWS_AS(test_model.calculate({.calculation_type = power_flow, - .calculation_symmetry = symmetric, - .calculation_method = linear, - .err_tol = 1e-8, - .max_iter = 1}, - test_result_data, update_data), - SparseMatrixError); - } - SUBCASE("Update dataset") { - CHECK_THROWS_AS(test_model.calculate({.calculation_type = power_flow, - .calculation_symmetry = symmetric, - .calculation_method = linear, - .err_tol = 1e-8, - .max_iter = 1}, - test_result_data, incomplete_update_data), - BatchCalculationError); - } - } - - SUBCASE("Asymmetrical - Incomplete") { // TODO(mgovers): not tested elsewhere; maybe test in API model? - MutableDataset test_result_data{true, 1, "asym_output", meta_data::meta_data_gen::meta_data}; - MutableDataset const ref_result_data{true, 1, "asym_output", meta_data::meta_data_gen::meta_data}; - - std::vector> test_sym_node(state.sym_node.size()); - test_result_data.add_buffer("node", test_sym_node.size(), test_sym_node.size(), nullptr, test_sym_node.data()); - - SUBCASE("Target dataset") { - CHECK_THROWS_AS(test_model.calculate({.calculation_type = power_flow, - .calculation_symmetry = asymmetric, - .calculation_method = linear, - .err_tol = 1e-8, - .max_iter = 1}, - test_result_data), - SparseMatrixError); - } - SUBCASE("Empty update dataset") { - ConstDataset const update_data{false, 1, "update", meta_data::meta_data_gen::meta_data}; - - CHECK_THROWS_AS(test_model.calculate({.calculation_type = power_flow, - .calculation_symmetry = asymmetric, - .calculation_method = linear, - .err_tol = 1e-8, - .max_iter = 1}, - test_result_data, update_data), - SparseMatrixError); - } - SUBCASE("Update dataset") { - CHECK_THROWS_AS(test_model.calculate({.calculation_type = power_flow, - .calculation_symmetry = asymmetric, - .calculation_method = linear, - .err_tol = 1e-8, - .max_iter = 1}, - test_result_data, incomplete_update_data), - BatchCalculationError); - } - } -} - -TEST_CASE("Test main model - Incomplete followed by complete") { // TODO(mgovers): This tests the reset of 2 consecutive - // batch scenarios and definitely needs to be tested - using CalculationMethod::linear; - - State const state; - auto main_model = default_model(state); - auto test_model = incomplete_input_model(state); - - constexpr Idx batch_size = 2; - - std::vector mixed_source_update{ - {6, 1, nan, nan}, {10, 1, nan, nan}, {6, 1, 1.05, nan}, {10, 1, 1.05, 0}}; - std::vector mixed_sym_load_update{{7, 1, nan, 1.0}, {7, 1, 0.5e6, nan}}; - std::vector mixed_asym_load_update{ - {8, 1, RealValue{nan}, RealValue{1.0}}, - {8, 1, RealValue{0.5e6 / 3.0}, RealValue{nan}}}; - - auto const source_indptr = IdxVector{0, 0, static_cast(mixed_source_update.size())}; - - REQUIRE(source_indptr.size() == batch_size + 1); - - ConstDataset mixed_update_data{true, batch_size, "update", meta_data::meta_data_gen::meta_data}; - mixed_update_data.add_buffer("source", 2, 4, nullptr, mixed_source_update.data()); - mixed_update_data.add_buffer("sym_load", 1, 2, nullptr, mixed_sym_load_update.data()); - mixed_update_data.add_buffer("asym_load", 1, 2, nullptr, mixed_asym_load_update.data()); - - ConstDataset second_scenario_update_data{false, 1, "update", meta_data::meta_data_gen::meta_data}; - second_scenario_update_data.add_buffer("source", 2, 2, nullptr, mixed_source_update.data() + 2); - second_scenario_update_data.add_buffer("sym_load", 1, 1, nullptr, mixed_sym_load_update.data() + 1); - second_scenario_update_data.add_buffer("asym_load", 1, 1, nullptr, mixed_asym_load_update.data() + 1); - - SUBCASE("Symmetrical") { - MutableDataset test_result_data{true, batch_size, "sym_output", meta_data::meta_data_gen::meta_data}; - MutableDataset ref_result_data{false, 1, "sym_output", meta_data::meta_data_gen::meta_data}; - - std::vector> test_sym_node(batch_size * state.sym_node.size(), - {na_IntID, na_IntS, nan, nan, nan, nan, nan}); - std::vector> ref_sym_node(state.sym_node.size(), - {na_IntID, na_IntS, nan, nan, nan, nan, nan}); - test_result_data.add_buffer("node", state.sym_node.size(), test_sym_node.size(), nullptr, test_sym_node.data()); - ref_result_data.add_buffer("node", ref_sym_node.size(), ref_sym_node.size(), nullptr, ref_sym_node.data()); - - CHECK_THROWS_AS(test_model.calculate({.calculation_type = power_flow, - .calculation_symmetry = symmetric, - .calculation_method = linear, - .err_tol = 1e-8, - .max_iter = 1}, - test_result_data, mixed_update_data), - BatchCalculationError); - main_model.calculate({.calculation_type = power_flow, - .calculation_symmetry = symmetric, - .calculation_method = linear, - .err_tol = 1e-8, - .max_iter = 1}, - ref_result_data, second_scenario_update_data); - - CHECK(is_nan(test_sym_node[0].u_pu)); - CHECK(is_nan(test_sym_node[1].u_pu)); - CHECK(is_nan(test_sym_node[2].u_pu)); - CHECK(test_sym_node[state.sym_node.size() + 0].u_pu == doctest::Approx(ref_sym_node[0].u_pu)); - CHECK(test_sym_node[state.sym_node.size() + 1].u_pu == doctest::Approx(ref_sym_node[1].u_pu)); - CHECK(test_sym_node[state.sym_node.size() + 2].u_pu == doctest::Approx(ref_sym_node[2].u_pu)); - } - - SUBCASE("Asymmetrical") { - MutableDataset test_result_data{true, batch_size, "asym_output", meta_data::meta_data_gen::meta_data}; - MutableDataset ref_result_data{false, 1, "asym_output", meta_data::meta_data_gen::meta_data}; - - std::vector> test_asym_node( - batch_size * state.sym_node.size(), - {na_IntID, na_IntS, RealValue{nan}, RealValue{nan}, - RealValue{nan}, RealValue{nan}, RealValue{nan}}); - std::vector> ref_asym_node( - state.sym_node.size(), - {na_IntID, na_IntS, RealValue{nan}, RealValue{nan}, - RealValue{nan}, RealValue{nan}, RealValue{nan}}); - test_result_data.add_buffer("node", state.sym_node.size(), test_asym_node.size(), nullptr, - test_asym_node.data()); - ref_result_data.add_buffer("node", ref_asym_node.size(), ref_asym_node.size(), nullptr, ref_asym_node.data()); - - CHECK_THROWS_AS(test_model.calculate({.calculation_type = power_flow, - .calculation_symmetry = asymmetric, - .calculation_method = linear, - .err_tol = 1e-8, - .max_iter = 1}, - test_result_data, mixed_update_data), - BatchCalculationError); - main_model.calculate({.calculation_type = power_flow, - .calculation_symmetry = asymmetric, - .calculation_method = linear, - .err_tol = 1e-8, - .max_iter = 1}, - ref_result_data, second_scenario_update_data); - - for (auto component_idx : {0, 1, 2}) { - CAPTURE(component_idx); - - CHECK(is_nan(test_asym_node[component_idx].u_pu)); - - for (auto phase_idx : {0, 1, 2}) { - CAPTURE(phase_idx); - - CHECK(test_asym_node[state.asym_node.size() + component_idx].u_pu(phase_idx) == - doctest::Approx(ref_asym_node[component_idx].u_pu(phase_idx))); - } - } - } -} - -} // namespace power_grid_model diff --git a/tests/cpp_unit_tests/test_math_solver_pf.hpp b/tests/cpp_unit_tests/test_math_solver_pf.hpp index 85995ef66..1939cff45 100644 --- a/tests/cpp_unit_tests/test_math_solver_pf.hpp +++ b/tests/cpp_unit_tests/test_math_solver_pf.hpp @@ -73,7 +73,7 @@ template struct PFSolverTestGrid : public SteadyStateSol TEST_CASE_TEMPLATE_DEFINE("Test math solver - PF", SolverType, test_math_solver_pf_id) { using sym = typename SolverType::sym; - PFSolverTestGrid grid; + PFSolverTestGrid const grid; // topo and param ptr auto param_ptr = std::make_shared const>(grid.param()); @@ -90,7 +90,7 @@ TEST_CASE_TEMPLATE_DEFINE("Test math solver - PF", SolverType, test_math_solver_ CalculationInfo info; PowerFlowInput const pf_input = grid.pf_input(); - SolverOutput output = run_power_flow(solver, y_bus, pf_input, error_tolerance, num_iter, info); + SolverOutput const output = run_power_flow(solver, y_bus, pf_input, error_tolerance, num_iter, info); assert_output(output, grid.output_ref(), false, result_tolerance); } diff --git a/tests/cpp_unit_tests/test_math_solver_pf_newton_raphson.cpp b/tests/cpp_unit_tests/test_math_solver_pf_newton_raphson.cpp index 3f4572491..96d84ad4e 100644 --- a/tests/cpp_unit_tests/test_math_solver_pf_newton_raphson.cpp +++ b/tests/cpp_unit_tests/test_math_solver_pf_newton_raphson.cpp @@ -16,7 +16,7 @@ TYPE_TO_STRING_AS("NewtonRaphsonPFSolver", namespace power_grid_model::math_solver { namespace { using newton_raphson_pf::PFJacBlock; -} +} // namespace TEST_CASE("Test block") { SUBCASE("symmetric") { diff --git a/tests/cpp_validation_tests/test_validation.cpp b/tests/cpp_validation_tests/test_validation.cpp index eeb3c7a03..1102dbf26 100644 --- a/tests/cpp_validation_tests/test_validation.cpp +++ b/tests/cpp_validation_tests/test_validation.cpp @@ -73,7 +73,7 @@ struct OwningDataset { OwningDataset create_owning_dataset(DatasetWritable& writable_dataset) { auto const& info = writable_dataset.get_info(); - Idx const is_batch = info.is_batch(); + 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}}, @@ -101,7 +101,7 @@ OwningDataset create_owning_dataset(DatasetWritable& writable_dataset) { return owning_dataset; } -OwningDataset create_result_dataset(OwningDataset const& input, std::string const& dataset_name, Idx is_batch = 0, +OwningDataset create_result_dataset(OwningDataset const& input, std::string const& dataset_name, bool is_batch = false, Idx batch_size = 1) { OwningDataset owning_dataset{.dataset{DatasetMutable{dataset_name, is_batch, batch_size}}, .const_dataset = std::nullopt}; @@ -384,7 +384,7 @@ Options get_options(CaseParam const& param, Idx threading = -1) { Options options{}; options.set_calculation_type(calculation_type_mapping.at(param.calculation_type)); options.set_calculation_method(calculation_method_mapping.at(param.calculation_method)); - options.set_symmetric(param.sym ? 1 : 0); + options.set_symmetric(param.sym ? PGM_symmetric : PGM_asymmetric); options.set_err_tol(param.err_tol); options.set_max_iter(param.max_iter); options.set_threading(threading); @@ -589,7 +589,7 @@ void validate_batch_case(CaseParam const& param) { auto const& info = validation_case.update_batch.value().const_dataset.value().get_info(); Idx const batch_size = info.batch_size(); auto const batch_result = - create_result_dataset(validation_case.output_batch.value(), output_prefix, Idx{1}, batch_size); + create_result_dataset(validation_case.output_batch.value(), output_prefix, true, batch_size); // create model Model model{50.0, validation_case.input.const_dataset.value()}; diff --git a/tests/native_api_tests/test_api_model.cpp b/tests/native_api_tests/test_api_model.cpp index ad5a4bf4f..e9fb186aa 100644 --- a/tests/native_api_tests/test_api_model.cpp +++ b/tests/native_api_tests/test_api_model.cpp @@ -58,6 +58,19 @@ Dataset created with the following buffers: namespace power_grid_model_cpp { namespace { +enum class MeasuredTerminalType : IntS { + branch_from = 0, + branch_to = 1, + source = 2, + shunt = 3, + load = 4, + generator = 5, + branch3_1 = 6, + branch3_2 = 7, + branch3_3 = 8, + node = 9 +}; + void check_exception(PowerGridError const& e, PGM_ErrorCode const& reference_error, std::string_view reference_err_msg) { CHECK(e.error_code() == reference_error); @@ -84,7 +97,7 @@ TEST_CASE("API Model") { Options options{}; // input data - DatasetConst input_dataset{"input", 0, 1}; + DatasetConst input_dataset{"input", false, 1}; // node buffer std::vector const node_id{0, 4}; @@ -151,11 +164,11 @@ TEST_CASE("API Model") { // output data Buffer node_output{PGM_def_sym_output_node, 2}; node_output.set_nan(); - DatasetMutable single_output_dataset{"sym_output", 0, 1}; + DatasetMutable single_output_dataset{"sym_output", false, 1}; single_output_dataset.add_buffer("node", 2, 2, nullptr, node_output); Buffer node_batch_output{PGM_def_sym_output_node, 4}; node_batch_output.set_nan(); - DatasetMutable batch_output_dataset{"sym_output", 1, 2}; + DatasetMutable batch_output_dataset{"sym_output", true, 2}; batch_output_dataset.add_buffer("node", 2, 4, nullptr, node_batch_output); std::vector node_result_id(2); @@ -192,14 +205,14 @@ TEST_CASE("API Model") { load_updates_buffer.set_value(PGM_def_update_sym_load_q_specified, load_updates_q_specified.data(), 0, -1); load_updates_buffer.set_value(PGM_def_update_sym_load_q_specified, load_updates_q_specified.data(), 1, -1); // dataset - DatasetConst single_update_dataset{"update", 0, 1}; + DatasetConst single_update_dataset{"update", false, 1}; single_update_dataset.add_buffer("source", 1, 1, nullptr, source_update_buffer); single_update_dataset.add_buffer("sym_load", 1, 1, nullptr, load_updates_buffer.get()); single_update_dataset.add_buffer("line", 2, 2, nullptr, nullptr); single_update_dataset.add_attribute_buffer("line", "id", line_id.data()); single_update_dataset.add_attribute_buffer("line", "from_status", line_from_status.data()); single_update_dataset.add_attribute_buffer("line", "to_status", line_to_status.data()); - DatasetConst batch_update_dataset{"update", 1, 2}; + DatasetConst batch_update_dataset{"update", true, 2}; batch_update_dataset.add_buffer("source", -1, 1, source_update_indptr.data(), source_update_buffer.get()); batch_update_dataset.add_buffer("sym_load", 1, 2, nullptr, load_updates_buffer); batch_update_dataset.add_buffer("line", 2, 4, nullptr, nullptr); @@ -265,14 +278,38 @@ TEST_CASE("API Model") { } } - SUBCASE("Get indexer") { - std::array ids{2, 2}; - std::array indexer{3, 3}; - model.get_indexer("sym_load", 2, ids.data(), indexer.data()); - CHECK(indexer[0] == 0); - CHECK(indexer[1] == 0); - ids[1] = 6; - CHECK_THROWS_AS(model.get_indexer("sym_load", 2, ids.data(), indexer.data()), PowerGridRegularError); + SUBCASE("Test get indexer") { + std::vector const node_id{1, 2, 3}; + std::vector const node_u_rated{10.0e3, 10.0e3, 10.0e3}; + + DatasetConst input_dataset{"input", false, 1}; + input_dataset.add_buffer("node", std::ssize(node_id), std::ssize(node_id), nullptr, nullptr); + input_dataset.add_attribute_buffer("node", "id", node_id.data()); + input_dataset.add_attribute_buffer("node", "u_rated", node_u_rated.data()); + + auto model2 = Model{50.0, input_dataset}; + + SUBCASE("Good weather") { + std::vector const ids_to_index{2, 1, 3, 2}; + std::vector const expected_indexer{1, 0, 2, 1}; + std::vector indexer(ids_to_index.size()); + model2.get_indexer("node", std::ssize(ids_to_index), ids_to_index.data(), indexer.data()); + CHECK(indexer == expected_indexer); + } + SUBCASE("Bad weather: wrong id") { + std::vector const ids_to_index{2, 1, 3, 4}; + std::vector indexer(ids_to_index.size()); + CHECK_THROWS_WITH_AS( + model2.get_indexer("node", std::ssize(ids_to_index), ids_to_index.data(), indexer.data()), + doctest::Contains("The id cannot be found: 4"), PowerGridRegularError); + } + SUBCASE("Bad weather: wrong type") { + std::vector const ids_to_index{2, 1, 3, 2}; + std::vector indexer(ids_to_index.size()); + CHECK_THROWS_WITH_AS( + model2.get_indexer("sym_load", std::ssize(ids_to_index), ids_to_index.data(), indexer.data()), + doctest::Contains("Wrong type for object with id 2"), PowerGridRegularError); + } } SUBCASE("Batch power flow") { @@ -330,7 +367,7 @@ TEST_CASE("API Model") { SUBCASE("Update error in calculation") { ID const bad_load_id = 2; load_buffer.set_value(PGM_def_input_sym_load_id, &bad_load_id, -1); - DatasetConst bad_batch_update_dataset{"update", 1, 2}; + DatasetConst bad_batch_update_dataset{"update", true, 2}; bad_batch_update_dataset.add_buffer("source", -1, 1, source_update_indptr.data(), source_update_buffer.get()); bad_batch_update_dataset.add_buffer("sym_load", 1, 2, nullptr, load_updates_buffer); @@ -464,13 +501,13 @@ TEST_CASE("API Model") { input_sym_load_buffer.set_value(PGM_def_input_sym_load_q_specified, input_sym_load_q_specified.data(), -1); // input dataset - row - DatasetConst input_dataset_row{"input", 0, 1}; + DatasetConst input_dataset_row{"input", false, 1}; input_dataset_row.add_buffer("node", 1, 1, nullptr, input_node_buffer); input_dataset_row.add_buffer("source", 1, 1, nullptr, input_source_buffer); input_dataset_row.add_buffer("sym_load", 1, 1, nullptr, input_sym_load_buffer); // input dataset - col - DatasetConst input_dataset_col{"input", 0, 1}; + DatasetConst input_dataset_col{"input", false, 1}; input_dataset_col.add_buffer("node", 1, 1, nullptr, nullptr); input_dataset_col.add_attribute_buffer("node", "id", input_node_id.data()); input_dataset_col.add_attribute_buffer("node", "u_rated", input_node_u_rated.data()); @@ -519,12 +556,12 @@ TEST_CASE("API Model") { -1); // update dataset - row - DatasetConst update_dataset_row{"update", 1, 2}; + DatasetConst update_dataset_row{"update", true, 2}; update_dataset_row.add_buffer("source", -1, 2, update_source_indptr.data(), update_source_buffer); update_dataset_row.add_buffer("sym_load", -1, 2, update_sym_load_indptr.data(), update_sym_load_buffer); // update dataset - col - DatasetConst update_dataset_col{"update", 1, 2}; + DatasetConst update_dataset_col{"update", true, 2}; update_dataset_col.add_buffer("source", -1, 2, update_source_indptr.data(), nullptr); update_dataset_col.add_attribute_buffer("source", "id", update_source_id.data()); @@ -535,13 +572,13 @@ TEST_CASE("API Model") { update_dataset_col.add_attribute_buffer("sym_load", "q_specified", update_sym_load_q_specified.data()); // update dataset - row no ids - DatasetConst update_dataset_row_no_id{"update", 1, 2}; + DatasetConst update_dataset_row_no_id{"update", true, 2}; update_dataset_row_no_id.add_buffer("source", -1, 2, update_source_indptr.data(), update_source_buffer_no_id); update_dataset_row_no_id.add_buffer("sym_load", -1, 2, update_sym_load_indptr.data(), update_sym_load_buffer_no_id); // update dataset - col no ids - DatasetConst update_dataset_col_no_id{"update", 1, 2}; + DatasetConst update_dataset_col_no_id{"update", true, 2}; update_dataset_col_no_id.add_buffer("source", -1, 2, update_source_indptr.data(), nullptr); update_dataset_col_no_id.add_attribute_buffer("source", "u_ref", update_source_u_ref.data()); @@ -552,7 +589,7 @@ TEST_CASE("API Model") { // output data Buffer batch_node_output{PGM_def_sym_output_node, 2}; batch_node_output.set_nan(); - DatasetMutable batch_output{"sym_output", 1, 2}; + DatasetMutable batch_output{"sym_output", true, 2}; batch_output.add_buffer("node", 1, 2, nullptr, batch_node_output); // options @@ -646,13 +683,13 @@ TEST_CASE("API Model") { input_sym_load_buffer.set_value(PGM_def_input_sym_load_q_specified, input_sym_load_q_specified.data(), -1); // input dataset - row - DatasetConst input_dataset_row{"input", 0, 1}; + DatasetConst input_dataset_row{"input", false, 1}; input_dataset_row.add_buffer("node", 1, 1, nullptr, input_node_buffer); input_dataset_row.add_buffer("source", 1, 1, nullptr, input_source_buffer); input_dataset_row.add_buffer("sym_load", 1, 1, nullptr, input_sym_load_buffer); // input dataset - col - DatasetConst input_dataset_col{"input", 0, 1}; + DatasetConst input_dataset_col{"input", false, 1}; input_dataset_col.add_buffer("node", 1, 1, nullptr, nullptr); input_dataset_col.add_attribute_buffer("node", "id", input_node_id.data()); input_dataset_col.add_attribute_buffer("node", "u_rated", input_node_u_rated.data()); @@ -691,12 +728,12 @@ TEST_CASE("API Model") { update_sym_load_buffer.set_value(PGM_def_update_sym_load_q_specified, update_sym_load_q_specified.data(), -1); // update dataset - row - DatasetConst update_dataset_row{"update", 1, 2}; + DatasetConst update_dataset_row{"update", true, 2}; update_dataset_row.add_buffer("source", -1, 1, source_indptr.data(), update_source_buffer); update_dataset_row.add_buffer("sym_load", -1, 2, sym_load_indptr.data(), update_sym_load_buffer); // update dataset - col - DatasetConst update_dataset_col{"update", 1, 2}; + DatasetConst update_dataset_col{"update", true, 2}; update_dataset_col.add_buffer("source", -1, 1, source_indptr.data(), nullptr); update_dataset_col.add_attribute_buffer("source", "id", update_source_id.data()); @@ -709,7 +746,7 @@ TEST_CASE("API Model") { // output data Buffer output_node_batch{PGM_def_sym_output_node, 2}; output_node_batch.set_nan(); - DatasetMutable output_batch_dataset{"sym_output", 1, 2}; + DatasetMutable output_batch_dataset{"sym_output", true, 2}; output_batch_dataset.add_buffer("node", 1, 2, nullptr, output_node_batch); // options @@ -756,7 +793,7 @@ TEST_CASE("API Model") { CAPTURE(calculation_type); auto const& supported_type_methods = supported_methods.at(calculation_type); - DatasetMutable const output_dataset{output_dataset_types.at(calculation_type), 0, 1}; + DatasetMutable const output_dataset{output_dataset_types.at(calculation_type), false, 1}; for (auto calculation_method : all_methods) { CAPTURE(calculation_method); @@ -779,7 +816,7 @@ TEST_CASE("API Model") { SUBCASE("Forbid link power measurements") { // input data - DatasetConst input_dataset_se{"input", 0, 1}; + DatasetConst input_dataset_se{"input", false, 1}; auto const construct_model = [&input_dataset_se] { return Model{50.0, input_dataset_se}; }; // node buffer @@ -829,6 +866,112 @@ TEST_CASE("API Model") { PowerGridRegularError); } } + + SUBCASE("Test duplicated id") { + std::vector node_id{1, 1, 3}; + DatasetConst input_dataset{"input", false, 1}; + + input_dataset.add_buffer("node", std::ssize(node_id), std::ssize(node_id), nullptr, nullptr); + input_dataset.add_attribute_buffer("node", "id", node_id.data()); + + auto construct_model = [&] { Model{50.0, input_dataset}; }; + CHECK_THROWS_WITH_AS(construct_model(), "Conflicting id detected: 1\n", PowerGridRegularError); + } + + SUBCASE("Test non-existing id") { + std::vector const node_id{1, 2, 3}; + std::vector const node_u_rated{10.0e3, 10.0e3, 10.0e3}; + + std::vector link_id{5}; + std::vector link_from_node{99}; + std::vector link_to_node{3}; + + DatasetConst input_dataset{"input", false, 1}; + + input_dataset.add_buffer("node", std::ssize(node_id), std::ssize(node_id), nullptr, nullptr); + input_dataset.add_attribute_buffer("node", "id", node_id.data()); + input_dataset.add_attribute_buffer("node", "u_rated", node_u_rated.data()); + + input_dataset.add_buffer("link", std::ssize(link_id), std::ssize(link_id), nullptr, nullptr); + input_dataset.add_attribute_buffer("link", "id", link_id.data()); + input_dataset.add_attribute_buffer("link", "from_node", link_from_node.data()); + input_dataset.add_attribute_buffer("link", "to_node", link_to_node.data()); + + auto construct_model = [&] { Model{50.0, input_dataset}; }; + CHECK_THROWS_WITH_AS(construct_model(), "The id cannot be found: 99\n", PowerGridRegularError); + } + + SUBCASE("Test id for wrong type") { + std::vector const node_id{1, 2, 3}; + std::vector const node_u_rated{10.0e3, 10.0e3, 10.0e3}; + + std::vector line_id{9}; + std::vector line_from_node{1}; + std::vector line_to_node{2}; + + std::vector link_id{5}; + std::vector link_from_node{2}; + std::vector link_to_node{3}; + + std::vector sym_voltage_sensor_id{25}; + std::vector sym_voltage_sensor_measured_object{1}; + + std::vector sym_power_sensor_id{28}; + std::vector sym_power_sensor_measured_object{3}; + std::vector sym_power_sensor_measured_terminal_type{MeasuredTerminalType::node}; + + DatasetConst input_dataset{"input", false, 1}; + + input_dataset.add_buffer("node", std::ssize(node_id), std::ssize(node_id), nullptr, nullptr); + input_dataset.add_attribute_buffer("node", "id", node_id.data()); + input_dataset.add_attribute_buffer("node", "u_rated", node_u_rated.data()); + + input_dataset.add_buffer("line", std::ssize(line_id), std::ssize(line_id), nullptr, nullptr); + input_dataset.add_attribute_buffer("line", "id", line_id.data()); + input_dataset.add_attribute_buffer("line", "from_node", line_from_node.data()); + input_dataset.add_attribute_buffer("line", "to_node", line_to_node.data()); + + input_dataset.add_buffer("link", std::ssize(link_id), std::ssize(link_id), nullptr, nullptr); + input_dataset.add_attribute_buffer("link", "id", link_id.data()); + input_dataset.add_attribute_buffer("link", "from_node", link_from_node.data()); + input_dataset.add_attribute_buffer("link", "to_node", link_to_node.data()); + + input_dataset.add_buffer("sym_voltage_sensor", std::ssize(sym_voltage_sensor_id), + std::ssize(sym_voltage_sensor_id), nullptr, nullptr); + input_dataset.add_attribute_buffer("sym_voltage_sensor", "id", sym_voltage_sensor_id.data()); + input_dataset.add_attribute_buffer("sym_voltage_sensor", "measured_object", + sym_voltage_sensor_measured_object.data()); + + input_dataset.add_buffer("sym_power_sensor", std::ssize(sym_power_sensor_id), std::ssize(sym_power_sensor_id), + nullptr, nullptr); + input_dataset.add_attribute_buffer("sym_power_sensor", "id", sym_power_sensor_id.data()); + input_dataset.add_attribute_buffer("sym_power_sensor", "measured_object", + sym_power_sensor_measured_object.data()); + input_dataset.add_attribute_buffer("sym_power_sensor", "measured_terminal_type", + sym_power_sensor_measured_terminal_type.data()); + + auto construct_model = [&] { Model{50.0, input_dataset}; }; + + SUBCASE("Correct type") { CHECK_NOTHROW(construct_model()); } + SUBCASE("Wrong branch terminal node") { + link_from_node[0] = 9; + CHECK_THROWS_WITH_AS(construct_model(), "Wrong type for object with id 9\n", PowerGridRegularError); + } + SUBCASE("Wrong voltage sensor measured object") { + sym_voltage_sensor_measured_object[0] = 5; + CHECK_THROWS_WITH_AS(construct_model(), "Wrong type for object with id 5\n", PowerGridRegularError); + } + SUBCASE("Wrong power sensor measured object type") { + using enum MeasuredTerminalType; + std::vector const mt_types{branch_from, branch_to, generator, load, shunt, source}; + + for (auto const& mt_type : mt_types) { + CAPTURE(mt_type); + sym_power_sensor_measured_terminal_type[0] = mt_type; + CHECK_THROWS_WITH_AS(construct_model(), "Wrong type for object with id 3\n", PowerGridRegularError); + } + } + } } } // namespace power_grid_model_cpp diff --git a/tests/native_api_tests/test_api_model_update.cpp b/tests/native_api_tests/test_api_model_update.cpp index 8640a9ade..8d715ef4a 100644 --- a/tests/native_api_tests/test_api_model_update.cpp +++ b/tests/native_api_tests/test_api_model_update.cpp @@ -2,15 +2,24 @@ // // SPDX-License-Identifier: MPL-2.0 -#include "power_grid_model_cpp.hpp" - #include +#include +#include +#include #include +#include +#include +#include +#include +#include #include +#include namespace { +using namespace std::string_literals; + // Types for template parameters struct row_t {}; struct columnar_t {}; @@ -112,8 +121,8 @@ TEST_CASE_TEMPLATE( using sparsity_type = typename T::sparsity_type; using id_check_type = typename T::id_check_type; - DatasetConst input_dataset{"input", 0, 1}; - DatasetConst update_dataset{"update", 1, 2}; + DatasetConst input_dataset{"input", false, 1}; + DatasetConst update_dataset{"update", true, 2}; std::vector const node_id{0}; std::vector const node_u_rated{100.0}; @@ -222,7 +231,7 @@ TEST_CASE_TEMPLATE( // output dataset Buffer batch_node_output{PGM_def_sym_output_node, 2}; batch_node_output.set_nan(); - DatasetMutable batch_output_dataset{"sym_output", 1, 2}; + DatasetMutable batch_output_dataset{"sym_output", true, 2}; batch_output_dataset.add_buffer("node", 1, 2, nullptr, batch_node_output); Options const batch_options{}; @@ -230,7 +239,8 @@ TEST_CASE_TEMPLATE( SUBCASE("Permanent update") { if constexpr (std::is_same_v) { - CHECK_THROWS_AS(model.update(update_dataset), PowerGridError); + CHECK_THROWS_WITH_AS(model.update(update_dataset), doctest::Contains("The id cannot be found"), + PowerGridError); } else { CHECK_NOTHROW(model.update(update_dataset)); } @@ -244,4 +254,671 @@ TEST_CASE_TEMPLATE( } } +namespace { +using std::numbers::sqrt3; + +enum class CalculationSymmetry : Idx { symmetric = PGM_symmetric, asymmetric = PGM_asymmetric }; +enum class LoadGenType : IntS { + const_pq = 0, // constant power + const_y = 1, // constant element_admittance (impedance) + const_i = 2, // constant current +}; +enum class MeasuredTerminalType : IntS { + branch_from = 0, + branch_to = 1, + source = 2, + shunt = 3, + load = 4, + generator = 5, + branch3_1 = 6, + branch3_2 = 7, + branch3_3 = 8, + node = 9 +}; + +Options get_default_options(PGM_SymmetryType calculation_symmetry, PGM_CalculationMethod calculation_method) { + Options opt; + opt.set_calculation_type(PGM_power_flow); + opt.set_symmetric(calculation_symmetry); + opt.set_calculation_method(calculation_method); + return opt; +} + +namespace test { +constexpr double z_bus_2 = 1.0 / (0.015 + 0.5e6 / 10e3 / 10e3 * 2); +constexpr double z_total = z_bus_2 + 10.0; +constexpr double u1 = 1.05 * z_bus_2 / (z_bus_2 + 10.0); +constexpr double i = 1.05 * 10e3 / z_total / sqrt3; +constexpr double i_shunt = 0.015 / 0.025 * i; +constexpr double i_load = 0.005 / 0.025 * i; +} // namespace test + +struct State { + std::vector node_id{1, 2, 3}; + std::vector node_u_rated{10e3, 10e3, 10e3}; + + std::vector line_id{4}; + std::vector line_from_node{1}; + std::vector line_to_node{2}; + std::vector line_from_status{1}; + std::vector line_to_status{1}; + std::vector line_r1{10.0}; + std::vector line_x1{0.0}; + std::vector line_c1{0.0}; + std::vector line_tan1{0.0}; + std::vector line_r0{10.0}; + std::vector line_x0{0.0}; + std::vector line_c0{0.0}; + std::vector line_tan0{0.0}; + std::vector line_i_n{1e3}; + + std::vector link_id{5}; + std::vector link_from_node{2}; + std::vector link_to_node{3}; + std::vector link_from_status{1}; + std::vector link_to_status{1}; + + std::vector source_id{6, 10}; + std::vector source_node{1, 3}; + std::vector source_status{1, 0}; + std::vector source_u_ref{1.05, 1.05}; + std::vector source_u_ref_angle{nan, 0.0}; + std::vector source_sk{1e12, 1e12}; + + std::vector sym_load_id{7}; + std::vector sym_load_node{3}; + std::vector sym_load_status{1}; + std::vector sym_load_type{LoadGenType::const_y}; + std::vector sym_load_p_specified{0.5e6}; + std::vector sym_load_q_specified{0.0}; + + std::vector asym_load_id{8}; + std::vector asym_load_node{3}; + std::vector asym_load_status{1}; + std::vector asym_load_type{LoadGenType::const_y}; + std::vector asym_load_p_specified{0.5e6 / 3.0, 0.5e6 / 3.0, 0.5e6 / 3.0}; + std::vector asym_load_q_specified{0.0, 0.0, 0.0}; + + std::vector shunt_id{9}; + std::vector shunt_node{3}; + std::vector shunt_status{1}; + std::vector shunt_g1{0.015}; + std::vector shunt_b1{0.0}; + std::vector shunt_g0{0.015}; + std::vector shunt_b0{0.0}; + + auto get_input_dataset() const { + DatasetConst result{"input", false, 1}; + + result.add_buffer("node", std::ssize(node_id), std::ssize(node_id), nullptr, nullptr); + result.add_attribute_buffer("node", "id", node_id.data()); + result.add_attribute_buffer("node", "u_rated", node_u_rated.data()); + + result.add_buffer("line", std::ssize(line_id), std::ssize(line_id), nullptr, nullptr); + result.add_attribute_buffer("line", "id", line_id.data()); + result.add_attribute_buffer("line", "from_node", line_from_node.data()); + result.add_attribute_buffer("line", "to_node", line_to_node.data()); + result.add_attribute_buffer("line", "from_status", line_from_status.data()); + result.add_attribute_buffer("line", "to_status", line_to_status.data()); + result.add_attribute_buffer("line", "r1", line_r1.data()); + result.add_attribute_buffer("line", "x1", line_x1.data()); + result.add_attribute_buffer("line", "c1", line_c1.data()); + result.add_attribute_buffer("line", "tan1", line_tan1.data()); + result.add_attribute_buffer("line", "r0", line_r0.data()); + result.add_attribute_buffer("line", "x0", line_x0.data()); + result.add_attribute_buffer("line", "c0", line_c0.data()); + result.add_attribute_buffer("line", "tan0", line_tan0.data()); + result.add_attribute_buffer("line", "i_n", line_i_n.data()); + + result.add_buffer("link", std::ssize(link_id), std::ssize(link_id), nullptr, nullptr); + result.add_attribute_buffer("link", "id", link_id.data()); + result.add_attribute_buffer("link", "from_node", link_from_node.data()); + result.add_attribute_buffer("link", "to_node", link_to_node.data()); + result.add_attribute_buffer("link", "from_status", link_from_status.data()); + result.add_attribute_buffer("link", "to_status", link_to_status.data()); + + result.add_buffer("source", std::ssize(source_id), std::ssize(source_id), nullptr, nullptr); + result.add_attribute_buffer("source", "id", source_id.data()); + result.add_attribute_buffer("source", "node", source_node.data()); + result.add_attribute_buffer("source", "status", source_status.data()); + result.add_attribute_buffer("source", "u_ref", source_u_ref.data()); + result.add_attribute_buffer("source", "u_ref_angle", source_u_ref_angle.data()); + result.add_attribute_buffer("source", "sk", source_sk.data()); + + result.add_buffer("sym_load", std::ssize(sym_load_id), std::ssize(sym_load_id), nullptr, nullptr); + result.add_attribute_buffer("sym_load", "id", sym_load_id.data()); + result.add_attribute_buffer("sym_load", "node", sym_load_node.data()); + result.add_attribute_buffer("sym_load", "status", sym_load_status.data()); + result.add_attribute_buffer("sym_load", "type", sym_load_type.data()); + result.add_attribute_buffer("sym_load", "p_specified", sym_load_p_specified.data()); + result.add_attribute_buffer("sym_load", "q_specified", sym_load_q_specified.data()); + + result.add_buffer("asym_load", std::ssize(asym_load_id), std::ssize(asym_load_id), nullptr, nullptr); + result.add_attribute_buffer("asym_load", "id", asym_load_id.data()); + result.add_attribute_buffer("asym_load", "node", asym_load_node.data()); + result.add_attribute_buffer("asym_load", "status", asym_load_status.data()); + result.add_attribute_buffer("asym_load", "type", asym_load_type.data()); + result.add_attribute_buffer("asym_load", "p_specified", asym_load_p_specified.data()); + result.add_attribute_buffer("asym_load", "q_specified", asym_load_q_specified.data()); + + result.add_buffer("shunt", std::ssize(shunt_id), std::ssize(shunt_id), nullptr, nullptr); + result.add_attribute_buffer("shunt", "id", shunt_id.data()); + result.add_attribute_buffer("shunt", "node", shunt_node.data()); + result.add_attribute_buffer("shunt", "status", shunt_status.data()); + result.add_attribute_buffer("shunt", "g1", shunt_g1.data()); + result.add_attribute_buffer("shunt", "b1", shunt_b1.data()); + result.add_attribute_buffer("shunt", "g0", shunt_g0.data()); + result.add_attribute_buffer("shunt", "b0", shunt_b0.data()); + + return result; + } +}; +} // namespace + +TEST_CASE("API model - all updates") { + using namespace std::string_literals; + + State const state; + auto const input_dataset = state.get_input_dataset(); + auto const& input_info = input_dataset.get_info(); + auto model = Model{50.0, input_dataset}; + + // update vector + std::vector sym_load_update_id{7}; + std::vector sym_load_update_status{1}; + std::vector sym_load_update_p_specified{2.5e6}; + + std::vector asym_load_update_id{8}; + std::vector asym_load_update_status{0}; + + std::vector shunt_update_id{9}; + std::vector shunt_update_status{0}; + std::vector shunt_update_b1{0.02}; + std::vector shunt_update_b0{0.02}; + + // used for test case alternate compute mode + std::vector const shunt_update_2_id{6}; + std::vector const source_update_2_status{0}; + std::vector const shunt_update_2_b1{0.01}; + std::vector const shunt_update_2_b0{0.01}; + + std::vector source_update_id{10}; + std::vector source_update_status{1}; + std::vector source_update_u_ref{test::u1}; + + std::vector link_update_id{5}; + std::vector link_update_from_status{1}; + std::vector link_update_to_status{0}; + + DatasetConst update_data{"update", true, 1}; + update_data.add_buffer("sym_load", 1, 1, nullptr, nullptr); + update_data.add_attribute_buffer("sym_load", "id", sym_load_update_id.data()); + update_data.add_attribute_buffer("sym_load", "status", sym_load_update_status.data()); + update_data.add_attribute_buffer("sym_load", "p_specified", sym_load_update_p_specified.data()); + + update_data.add_buffer("asym_load", 1, 1, nullptr, nullptr); + update_data.add_attribute_buffer("asym_load", "id", asym_load_update_id.data()); + update_data.add_attribute_buffer("asym_load", "status", asym_load_update_status.data()); + + update_data.add_buffer("shunt", 1, 1, nullptr, nullptr); + update_data.add_attribute_buffer("shunt", "id", shunt_update_id.data()); + update_data.add_attribute_buffer("shunt", "status", shunt_update_status.data()); + update_data.add_attribute_buffer("shunt", "b1", shunt_update_b1.data()); + update_data.add_attribute_buffer("shunt", "b0", shunt_update_b0.data()); + + update_data.add_buffer("source", 1, 1, nullptr, nullptr); + update_data.add_attribute_buffer("source", "id", source_update_id.data()); + update_data.add_attribute_buffer("source", "status", source_update_status.data()); + update_data.add_attribute_buffer("source", "u_ref", source_update_u_ref.data()); + + update_data.add_buffer("link", 1, 1, nullptr, nullptr); + update_data.add_attribute_buffer("link", "id", link_update_id.data()); + update_data.add_attribute_buffer("link", "from_status", link_update_from_status.data()); + update_data.add_attribute_buffer("link", "to_status", link_update_to_status.data()); + + auto const output_dataset_type = "sym_output"s; + for (Idx comp_type_idx = 0; comp_type_idx < input_info.n_components(); ++comp_type_idx) { + CAPTURE(comp_type_idx); + + auto const comp_type = input_info.component_name(comp_type_idx); + CAPTURE(comp_type); + + auto const* comp_meta = MetaData::get_component_by_name(output_dataset_type, comp_type); + auto const total_elements = input_info.component_total_elements(comp_type_idx); + auto const elements_per_scenario = input_info.component_elements_per_scenario(comp_type_idx); + + for (Idx attribute_idx = 0; attribute_idx < MetaData::n_attributes(comp_meta); ++attribute_idx) { + CAPTURE(attribute_idx); + auto const* attr_meta = MetaData::get_attribute_by_idx(comp_meta, attribute_idx); + auto const attribute_name = MetaData::attribute_name(attr_meta); + CAPTURE(attribute_name); + + pgm_type_func_selector(attr_meta, [&]() { + std::vector sym_output_from_batch(total_elements); + std::vector sym_output_from_updated_single(total_elements); + + DatasetMutable output_data_from_batch{output_dataset_type, true, 1}; + DatasetMutable output_data_from_updated_single{output_dataset_type, false, 1}; + + output_data_from_batch.add_buffer(comp_type, elements_per_scenario, total_elements, nullptr, nullptr); + output_data_from_updated_single.add_buffer(comp_type, elements_per_scenario, total_elements, nullptr, + nullptr); + + output_data_from_batch.add_attribute_buffer(comp_type, attribute_name, sym_output_from_batch.data()); + output_data_from_updated_single.add_attribute_buffer(comp_type, attribute_name, + sym_output_from_updated_single.data()); + + auto opt = get_default_options(PGM_symmetric, PGM_linear); + model.calculate(opt, output_data_from_batch, update_data); + model.update(update_data); + model.calculate(opt, output_data_from_updated_single); + + for (Idx i = 0; i < total_elements; ++i) { + CAPTURE(i); + CHECK(sym_output_from_batch[i] == sym_output_from_updated_single[i]); + } + }); + } + } +} + +TEST_CASE("API model - updates w/ alternating compute mode") { + State const state; + auto const input_dataset = state.get_input_dataset(); + auto model = Model{50.0, input_dataset}; + + auto const check_sym = [&] { + std::vector sym_node_output_u_pu(3); + std::vector sym_line_output_i_from(1); + std::vector sym_source_output_i(2); + std::vector sym_sym_load_output_i(1); + std::vector sym_asym_load_output_i(1); + std::vector sym_shunt_output_i(1); + + DatasetMutable sym_output{"sym_output", false, 1}; + sym_output.add_buffer("node", 1, 1, nullptr, nullptr); + sym_output.add_attribute_buffer("node", "u_pu", sym_node_output_u_pu.data()); + + sym_output.add_buffer("line", 1, 1, nullptr, nullptr); + sym_output.add_attribute_buffer("line", "i_from", sym_line_output_i_from.data()); + + sym_output.add_buffer("source", 2, 2, nullptr, nullptr); + sym_output.add_attribute_buffer("source", "i", sym_source_output_i.data()); + + sym_output.add_buffer("sym_load", 1, 1, nullptr, nullptr); + sym_output.add_attribute_buffer("sym_load", "i", sym_sym_load_output_i.data()); + + sym_output.add_buffer("asym_load", 1, 1, nullptr, nullptr); + sym_output.add_attribute_buffer("asym_load", "i", sym_asym_load_output_i.data()); + + sym_output.add_buffer("shunt", 1, 1, nullptr, nullptr); + sym_output.add_attribute_buffer("shunt", "i", sym_shunt_output_i.data()); + + model.calculate(get_default_options(PGM_symmetric, PGM_linear), sym_output); + + CHECK(sym_node_output_u_pu[0] == doctest::Approx(1.05)); + CHECK(sym_node_output_u_pu[1] == doctest::Approx(test::u1)); + CHECK(sym_node_output_u_pu[2] == doctest::Approx(test::u1)); + CHECK(sym_line_output_i_from[0] == doctest::Approx(test::i)); + CHECK(sym_source_output_i[0] == doctest::Approx(test::i)); + CHECK(sym_source_output_i[1] == doctest::Approx(0.0)); + CHECK(sym_sym_load_output_i[0] == doctest::Approx(test::i_load * 2 + test::i_shunt)); + CHECK(sym_asym_load_output_i[0] == doctest::Approx(0.0)); + CHECK(sym_shunt_output_i[0] == doctest::Approx(0.0)); + }; + auto const check_asym = [&] { + std::vector asym_node_output_u_pu(9); + std::vector asym_line_output_i_from(3); + std::vector asym_source_output_i(6); + std::vector asym_sym_load_output_i(3); + std::vector asym_asym_load_output_i(3); + std::vector asym_shunt_output_i(3); + + DatasetMutable asym_output{"asym_output", false, 1}; + asym_output.add_buffer("node", 1, 1, nullptr, nullptr); + asym_output.add_attribute_buffer("node", "u_pu", asym_node_output_u_pu.data()); + + asym_output.add_buffer("line", 1, 1, nullptr, nullptr); + asym_output.add_attribute_buffer("line", "i_from", asym_line_output_i_from.data()); + + asym_output.add_buffer("source", 2, 2, nullptr, nullptr); + asym_output.add_attribute_buffer("source", "i", asym_source_output_i.data()); + + asym_output.add_buffer("sym_load", 1, 1, nullptr, nullptr); + asym_output.add_attribute_buffer("sym_load", "i", asym_sym_load_output_i.data()); + + asym_output.add_buffer("asym_load", 1, 1, nullptr, nullptr); + asym_output.add_attribute_buffer("asym_load", "i", asym_asym_load_output_i.data()); + + asym_output.add_buffer("shunt", 1, 1, nullptr, nullptr); + asym_output.add_attribute_buffer("shunt", "i", asym_shunt_output_i.data()); + + model.calculate(get_default_options(PGM_asymmetric, PGM_linear), asym_output); + + CHECK(asym_node_output_u_pu[0 * 3 + 0] == doctest::Approx(1.05)); + CHECK(asym_node_output_u_pu[1 * 3 + 1] == doctest::Approx(test::u1)); + CHECK(asym_node_output_u_pu[2 * 2 + 1] == doctest::Approx(test::u1)); + CHECK(asym_line_output_i_from[0] == doctest::Approx(test::i)); + CHECK(asym_source_output_i[0 * 3 + 1] == doctest::Approx(test::i)); + CHECK(asym_source_output_i[1 * 3 + 2] == doctest::Approx(0.0)); + CHECK(asym_sym_load_output_i[0] == doctest::Approx(test::i_load * 2 + test::i_shunt)); + CHECK(asym_asym_load_output_i[1] == doctest::Approx(0.0)); + CHECK(asym_shunt_output_i[2] == doctest::Approx(0.0)); + }; + + // update vector + std::vector sym_load_update_id{7}; + std::vector sym_load_update_status{1}; + std::vector sym_load_update_p_specified{2.5e6}; + + std::vector asym_load_update_id{8}; + std::vector asym_load_update_status{0}; + + std::vector shunt_update_id{9}; + std::vector shunt_update_status{0}; + std::vector shunt_update_b1{0.02}; + std::vector shunt_update_b0{0.02}; + + DatasetConst update_data{"update", true, 1}; + update_data.add_buffer("sym_load", 1, 1, nullptr, nullptr); + update_data.add_attribute_buffer("sym_load", "id", sym_load_update_id.data()); + update_data.add_attribute_buffer("sym_load", "status", sym_load_update_status.data()); + update_data.add_attribute_buffer("sym_load", "p_specified", sym_load_update_p_specified.data()); + + update_data.add_buffer("asym_load", 1, 1, nullptr, nullptr); + update_data.add_attribute_buffer("asym_load", "id", asym_load_update_id.data()); + update_data.add_attribute_buffer("asym_load", "status", asym_load_update_status.data()); + + update_data.add_buffer("shunt", 1, 1, nullptr, nullptr); + update_data.add_attribute_buffer("shunt", "id", shunt_update_id.data()); + update_data.add_attribute_buffer("shunt", "status", shunt_update_status.data()); + update_data.add_attribute_buffer("shunt", "b1", shunt_update_b1.data()); + update_data.add_attribute_buffer("shunt", "b0", shunt_update_b0.data()); + + // This will lead to no topo change but param change + model.update(update_data); + + check_sym(); + check_asym(); + + SUBCASE("No new update") { + // Math state may be fully cached + } + SUBCASE("No new parameter change") { + // Math state may be fully cached + model.update(update_data); + } + + check_asym(); + check_sym(); +} + +namespace { +auto get_incomplete_state() -> State { + State result; + + std::ranges::fill(result.source_u_ref, nan); + std::ranges::fill(result.source_u_ref_angle, nan); + std::ranges::fill(result.sym_load_p_specified, nan); + std::ranges::fill(result.asym_load_p_specified, nan); + + return result; +} +} // namespace + +TEST_CASE("API model - incomplete input") { + State const complete_state; + State const incomplete_state = get_incomplete_state(); + + auto test_model = Model{50.0, incomplete_state.get_input_dataset()}; + + for (auto symmetry : {PGM_symmetric, PGM_asymmetric}) { + CAPTURE(symmetry); + + auto const calculation_symmetry = symmetry == PGM_symmetric ? "Symmetric"s : "Asymmetric"s; + auto const output_type = symmetry == PGM_symmetric ? "sym_output"s : "asym_output"s; + + SUBCASE(calculation_symmetry.c_str()) { + auto const* node_output_meta = MetaData::get_component_by_name(output_type, "node"); + + Buffer test_node_output(node_output_meta, std::ssize(complete_state.node_id)); + DatasetMutable test_result_data{output_type, true, 1}; + test_result_data.add_buffer("node", test_node_output.size(), test_node_output.size(), nullptr, + test_node_output); + + SUBCASE("Target dataset") { + CHECK_THROWS_WITH_AS(test_model.calculate(get_default_options(symmetry, PGM_linear), test_result_data), + doctest::Contains("Sparse matrix error, possibly singular matrix!"), + PowerGridRegularError); + } + SUBCASE("Empty single scenario update dataset") { + DatasetConst const empty_update_data{"update", true, 1}; + SUBCASE("Single update") { + test_model.update(empty_update_data); + CHECK_THROWS_WITH_AS( + test_model.calculate(get_default_options(symmetry, PGM_linear), test_result_data), + doctest::Contains("Sparse matrix error, possibly singular matrix!"), PowerGridRegularError); + } + SUBCASE("Batch") { + CHECK_THROWS_WITH_AS(test_model.calculate(get_default_options(symmetry, PGM_linear), + test_result_data, empty_update_data), + doctest::Contains("Sparse matrix error, possibly singular matrix!"), + PowerGridRegularError); + } + } + SUBCASE("Incomplete update dataset") { + DatasetConst incomplete_update_data{"update", true, 1}; + incomplete_update_data.add_buffer("source", std::ssize(incomplete_state.source_id), + std::ssize(incomplete_state.source_id), nullptr, nullptr); + incomplete_update_data.add_attribute_buffer("source", "id", incomplete_state.source_id.data()); + incomplete_update_data.add_attribute_buffer("source", "u_ref", incomplete_state.source_u_ref.data()); + incomplete_update_data.add_attribute_buffer("source", "u_ref_angle", + incomplete_state.source_u_ref_angle.data()); + + incomplete_update_data.add_buffer("sym_load", std::ssize(incomplete_state.sym_load_id), + std::ssize(incomplete_state.sym_load_id), nullptr, nullptr); + incomplete_update_data.add_attribute_buffer("sym_load", "id", incomplete_state.sym_load_id.data()); + incomplete_update_data.add_attribute_buffer("sym_load", "p_specified", + incomplete_state.sym_load_p_specified.data()); + + incomplete_update_data.add_buffer("asym_load", std::ssize(incomplete_state.asym_load_id), + std::ssize(incomplete_state.asym_load_id), nullptr, nullptr); + incomplete_update_data.add_attribute_buffer("asym_load", "id", incomplete_state.asym_load_id.data()); + incomplete_update_data.add_attribute_buffer("asym_load", "p_specified", + incomplete_state.asym_load_p_specified.data()); + + SUBCASE("Single update") { + CHECK_NOTHROW(test_model.update(incomplete_update_data)); + CHECK_THROWS_WITH_AS( + test_model.calculate(get_default_options(symmetry, PGM_linear), test_result_data), + doctest::Contains("Sparse matrix error, possibly singular matrix!"), PowerGridRegularError); + } + SUBCASE("Batch") { + CHECK_THROWS_AS(test_model.calculate(get_default_options(symmetry, PGM_linear), test_result_data, + incomplete_update_data), + PowerGridBatchError); + } + } + SUBCASE("Complete update dataset") { + DatasetConst complete_update_data{"update", true, 1}; + complete_update_data.add_buffer("source", std::ssize(complete_state.source_id), + std::ssize(complete_state.source_id), nullptr, nullptr); + complete_update_data.add_attribute_buffer("source", "id", complete_state.source_id.data()); + complete_update_data.add_attribute_buffer("source", "u_ref", complete_state.source_u_ref.data()); + complete_update_data.add_attribute_buffer("source", "u_ref_angle", + complete_state.source_u_ref_angle.data()); + + complete_update_data.add_buffer("sym_load", std::ssize(complete_state.sym_load_id), + std::ssize(complete_state.sym_load_id), nullptr, nullptr); + complete_update_data.add_attribute_buffer("sym_load", "id", complete_state.sym_load_id.data()); + complete_update_data.add_attribute_buffer("sym_load", "p_specified", + complete_state.sym_load_p_specified.data()); + + complete_update_data.add_buffer("asym_load", std::ssize(complete_state.asym_load_id), + std::ssize(complete_state.asym_load_id), nullptr, nullptr); + complete_update_data.add_attribute_buffer("asym_load", "id", complete_state.asym_load_id.data()); + complete_update_data.add_attribute_buffer("asym_load", "p_specified", + complete_state.asym_load_p_specified.data()); + + auto ref_model = Model{50.0, complete_state.get_input_dataset()}; + Buffer ref_node_output(node_output_meta, std::ssize(complete_state.node_id)); + DatasetMutable ref_result_data{output_type, true, 1}; + ref_result_data.add_buffer("node", ref_node_output.size(), ref_node_output.size(), nullptr, + ref_node_output); + + CHECK_NOTHROW(ref_model.calculate(get_default_options(symmetry, PGM_linear), ref_result_data)); + + SUBCASE("Single calculation") { + test_model.update(complete_update_data); + CHECK_NOTHROW(test_model.calculate(get_default_options(symmetry, PGM_linear), test_result_data, + complete_update_data)); + } + SUBCASE("Batch") { + CHECK_NOTHROW(test_model.calculate(get_default_options(symmetry, PGM_linear), test_result_data, + complete_update_data)); + } + + for (Idx node_idx = 0; node_idx < std::ssize(complete_state.node_id); ++node_idx) { + CAPTURE(node_idx); + + for (Idx attr_idx = 0; attr_idx < MetaData::n_attributes(node_output_meta); ++attr_idx) { + auto const* attr_meta = MetaData::get_attribute_by_idx(node_output_meta, attr_idx); + auto const attr_name = MetaData::attribute_name(attr_meta); + CAPTURE(attr_name); + + pgm_type_func_selector(attr_meta, [&] { + T test_value{nan_value()}; + T ref_value{nan_value()}; + test_node_output.get_value(attr_meta, &test_value, node_idx, 0); + ref_node_output.get_value(attr_meta, &ref_value, node_idx, 0); + + if constexpr (std::is_floating_point_v) { + CHECK(test_value == doctest::Approx(ref_value)); + } else { + CHECK(test_value == ref_value); + } + }); + } + } + } + } + } +} + +TEST_CASE("API model - Incomplete scenario update followed by complete") { + State const complete_state; + State const incomplete_state = get_incomplete_state(); + + auto ref_model = Model{50.0, complete_state.get_input_dataset()}; + auto test_model = Model{50.0, incomplete_state.get_input_dataset()}; + + constexpr Idx batch_size = 2; + auto const n_nodes = static_cast(complete_state.node_id.size()); + + std::vector mixed_source_update_id{6, 10, 6, 10}; + std::vector mixed_source_update_status{1, 1, 1, 1}; + std::vector mixed_source_update_u_ref{nan, nan, 1.05, 1.05}; + std::vector mixed_source_update_u_ref_angle{nan, nan, nan, 0}; + + std::vector mixed_sym_load_update_id{7, 7}; + std::vector mixed_sym_load_update_status{1, 1}; + std::vector mixed_sym_load_update_p_specified{nan, 0.5e6}; + std::vector mixed_sym_load_update_q_specified{1.0, nan}; + + std::vector mixed_asym_load_update_id{8, 8}; + std::vector mixed_asym_load_update_status{1, 1}; + std::vector mixed_asym_load_update_p_specified{nan, nan, nan, 0.5e6 / 3.0, 0.5e6 / 3.0, 0.5e6 / 3.0}; + std::vector mixed_asym_load_update_q_specified{1.0, 1.0, 1.0, nan, nan, nan}; + + std::vector const source_indptr{0, 0, static_cast(mixed_source_update_id.size())}; + + REQUIRE(source_indptr.size() == batch_size + 1); + + DatasetConst mixed_update_data{"update", true, batch_size}; + + mixed_update_data.add_buffer("source", 2, 4, nullptr, nullptr); + mixed_update_data.add_attribute_buffer("source", "id", mixed_source_update_id.data()); + mixed_update_data.add_attribute_buffer("source", "status", mixed_source_update_status.data()); + mixed_update_data.add_attribute_buffer("source", "u_ref", mixed_source_update_u_ref.data()); + mixed_update_data.add_attribute_buffer("source", "u_ref_angle", mixed_source_update_u_ref_angle.data()); + + mixed_update_data.add_buffer("sym_load", 1, 2, nullptr, nullptr); + mixed_update_data.add_attribute_buffer("sym_load", "id", mixed_sym_load_update_id.data()); + mixed_update_data.add_attribute_buffer("sym_load", "status", mixed_sym_load_update_status.data()); + mixed_update_data.add_attribute_buffer("sym_load", "p_specified", mixed_sym_load_update_p_specified.data()); + mixed_update_data.add_attribute_buffer("sym_load", "q_specified", mixed_sym_load_update_q_specified.data()); + + mixed_update_data.add_buffer("asym_load", 1, 2, nullptr, nullptr); + mixed_update_data.add_attribute_buffer("asym_load", "id", mixed_asym_load_update_id.data()); + mixed_update_data.add_attribute_buffer("asym_load", "status", mixed_asym_load_update_status.data()); + mixed_update_data.add_attribute_buffer("asym_load", "p_specified", mixed_asym_load_update_p_specified.data()); + mixed_update_data.add_attribute_buffer("asym_load", "q_specified", mixed_asym_load_update_q_specified.data()); + + DatasetConst second_scenario_update_data{"update", true, 1}; + + second_scenario_update_data.add_buffer("source", 2, 2, nullptr, nullptr); + second_scenario_update_data.add_attribute_buffer("source", "id", mixed_source_update_id.data() + 2); + second_scenario_update_data.add_attribute_buffer("source", "status", mixed_source_update_status.data() + 2); + second_scenario_update_data.add_attribute_buffer("source", "u_ref", mixed_source_update_u_ref.data() + 2); + second_scenario_update_data.add_attribute_buffer("source", "u_ref_angle", + mixed_source_update_u_ref_angle.data() + 2); + + second_scenario_update_data.add_buffer("sym_load", 1, 1, nullptr, nullptr); + second_scenario_update_data.add_attribute_buffer("sym_load", "id", mixed_sym_load_update_id.data() + 1); + second_scenario_update_data.add_attribute_buffer("sym_load", "status", mixed_sym_load_update_status.data() + 1); + second_scenario_update_data.add_attribute_buffer("sym_load", "p_specified", + mixed_sym_load_update_p_specified.data() + 1); + second_scenario_update_data.add_attribute_buffer("sym_load", "q_specified", + mixed_sym_load_update_q_specified.data() + 1); + + second_scenario_update_data.add_buffer("asym_load", 1, 1, nullptr, nullptr); + second_scenario_update_data.add_attribute_buffer("asym_load", "id", mixed_asym_load_update_id.data() + 1); + second_scenario_update_data.add_attribute_buffer("asym_load", "status", mixed_asym_load_update_status.data() + 1); + second_scenario_update_data.add_attribute_buffer("asym_load", "p_specified", + mixed_asym_load_update_p_specified.data() + 1); + second_scenario_update_data.add_attribute_buffer("asym_load", "q_specified", + mixed_asym_load_update_q_specified.data() + 1); + + for (auto symmetry : {PGM_symmetric, PGM_asymmetric}) { + CAPTURE(symmetry); + + auto const calculation_symmetry = symmetry == PGM_symmetric ? "Symmetric"s : "Asymmetric"s; + auto const output_type = symmetry == PGM_symmetric ? "sym_output"s : "asym_output"s; + auto const n_phases = symmetry == PGM_symmetric ? 1 : 3; + + SUBCASE(calculation_symmetry.c_str()) { + DatasetMutable test_result_data{output_type, true, batch_size}; + DatasetMutable ref_result_data{output_type, true, 1}; + + std::vector test_node_output_u_pu(batch_size * n_nodes * n_phases, nan); + std::vector ref_node_output_u_pu(n_nodes * n_phases, nan); + + test_result_data.add_buffer("node", n_nodes, batch_size * n_nodes, nullptr, nullptr); + test_result_data.add_attribute_buffer("node", "u_pu", test_node_output_u_pu.data()); + + ref_result_data.add_buffer("node", n_nodes, n_nodes, nullptr, nullptr); + ref_result_data.add_attribute_buffer("node", "u_pu", ref_node_output_u_pu.data()); + + CHECK_THROWS_AS(test_model.calculate(get_default_options(PGM_symmetric, PGM_linear), test_result_data, + mixed_update_data), + PowerGridBatchError); + + ref_model.calculate(get_default_options(PGM_symmetric, PGM_linear), ref_result_data, + second_scenario_update_data); + + for (auto node_idx = 0; node_idx < n_nodes; ++node_idx) { + CAPTURE(node_idx); + + for (auto phase_idx = 0; phase_idx < n_phases; ++phase_idx) { + CAPTURE(phase_idx); + + CHECK(is_nan(test_node_output_u_pu[node_idx * n_phases + phase_idx])); + CHECK(test_node_output_u_pu[(n_nodes + node_idx) * n_phases + phase_idx] == + doctest::Approx(ref_node_output_u_pu[node_idx * n_phases + phase_idx])); + } + } + } + } +} + } // namespace power_grid_model_cpp diff --git a/tests/native_api_tests/test_api_serialization.cpp b/tests/native_api_tests/test_api_serialization.cpp index caa3d67c8..9b11bff56 100644 --- a/tests/native_api_tests/test_api_serialization.cpp +++ b/tests/native_api_tests/test_api_serialization.cpp @@ -47,7 +47,7 @@ TEST_CASE("API Serialization and Deserialization") { Idx const n_components = 2; Idx const batch_size = 1; - Idx const is_batch = 0; + bool const is_batch = false; std::vector const elements_per_scenario = {1, 2}; std::vector const elements_per_scenario_complete = {1, 1}; std::vector const total_elements = {1, 2};