diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7bce9224f..d07c0dd5e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: hooks: - id: black-jupyter - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.11.2 + rev: v1.13.0 hooks: - id: mypy additional_dependencies: 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 45991884c..eaf12f11f 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 @@ -11,7 +11,17 @@ #include "handle.hpp" #include "power_grid_model_c/dataset.h" + namespace power_grid_model_cpp { +class ComponentTypeNotFound : public PowerGridError { + public: + ComponentTypeNotFound(std::string const& component) + : PowerGridError{[&]() { + using namespace std::string_literals; + return "ComponentType"s + component + " not found"s; + }()} {} + ComponentTypeNotFound(std::string_view component) : ComponentTypeNotFound{std::string{component}} {} +}; class DatasetInfo { @@ -43,6 +53,16 @@ class DatasetInfo { return handle_.call_with(PGM_dataset_info_total_elements, info_, component_idx); } + Idx component_idx(std::string_view component) const { + Idx const n_comp = n_components(); + for (Idx idx = 0; idx < n_comp; ++idx) { + if (component_name(idx) == component) { + return idx; + } + } + throw ComponentTypeNotFound{component}; + } + private: Handle handle_{}; RawDatasetInfo const* info_; @@ -171,6 +191,16 @@ class DatasetConst { detail::UniquePtr dataset_; DatasetInfo info_; }; + +struct OwningMemory { + std::vector buffers; + std::vector> indptrs; +}; + +struct OwningDataset { + DatasetMutable dataset; + OwningMemory storage{}; +}; } // namespace power_grid_model_cpp #endif // POWER_GRID_MODEL_CPP_DATASET_HPP diff --git a/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/handle.hpp b/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/handle.hpp index d08b99e11..47108cc59 100644 --- a/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/handle.hpp +++ b/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/handle.hpp @@ -40,8 +40,8 @@ class PowerGridBatchError : public PowerGridError { std::string error_message; }; - PowerGridBatchError(std::string const& message, std::vector failed_scenarios_c) - : PowerGridError{message}, failed_scenarios_{std::move(failed_scenarios_c)} {} + PowerGridBatchError(std::string message, std::vector failed_scenarios_c) + : PowerGridError{std::move(message)}, failed_scenarios_{std::move(failed_scenarios_c)} {} Idx error_code() const noexcept override { return PGM_batch_error; } std::vector const& failed_scenarios() const { return failed_scenarios_; } diff --git a/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/serialization.hpp b/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/serialization.hpp index bb08fec4b..d1c3181cb 100644 --- a/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/serialization.hpp +++ b/power_grid_model_c/power_grid_model_cpp/include/power_grid_model_cpp/serialization.hpp @@ -9,6 +9,7 @@ #include "basics.hpp" #include "dataset.hpp" #include "handle.hpp" +#include "meta_data.hpp" #include "power_grid_model_c/serialization.h" @@ -90,6 +91,33 @@ class Serializer { power_grid_model_cpp::Handle handle_{}; detail::UniquePtr serializer_; }; + +inline OwningDataset create_owning_dataset(DatasetWritable& writable_dataset) { + auto const& info = writable_dataset.get_info(); + bool const is_batch = info.is_batch(); + Idx const batch_size = info.batch_size(); + auto const& dataset_name = info.name(); + DatasetMutable dataset_mutable{dataset_name, is_batch, batch_size}; + OwningMemory storage{}; + + for (Idx component_idx{}; component_idx < info.n_components(); ++component_idx) { + auto const& component_name = info.component_name(component_idx); + auto const& component_meta = MetaData::get_component_by_name(dataset_name, component_name); + Idx const component_size = info.component_total_elements(component_idx); + Idx const elements_per_scenario = info.component_elements_per_scenario(component_idx); + + auto& current_indptr = storage.indptrs.emplace_back(elements_per_scenario < 0 ? batch_size + 1 : 0); + if (!current_indptr.empty()) { + current_indptr.at(0) = 0; + current_indptr.at(batch_size) = component_size; + } + Idx* const indptr = current_indptr.empty() ? nullptr : current_indptr.data(); + auto& current_buffer = storage.buffers.emplace_back(component_meta, component_size); + writable_dataset.set_buffer(component_name, indptr, current_buffer); + dataset_mutable.add_buffer(component_name, elements_per_scenario, component_size, indptr, current_buffer); + } + return OwningDataset{.dataset = std::move(dataset_mutable), .storage = std::move(storage)}; +} } // namespace power_grid_model_cpp #endif // POWER_GRID_MODEL_CPP_SERIALIZATION_HPP diff --git a/tests/cpp_unit_tests/test_math_solver_common.hpp b/tests/cpp_unit_tests/test_math_solver_common.hpp index 5f07e1bc1..da4f005d8 100644 --- a/tests/cpp_unit_tests/test_math_solver_common.hpp +++ b/tests/cpp_unit_tests/test_math_solver_common.hpp @@ -4,6 +4,8 @@ // In this unit test the powerflow solvers are tested +#pragma once + #include #include #include diff --git a/tests/cpp_unit_tests/test_math_solver_pf.hpp b/tests/cpp_unit_tests/test_math_solver_pf.hpp index 1939cff45..1971d83ea 100644 --- a/tests/cpp_unit_tests/test_math_solver_pf.hpp +++ b/tests/cpp_unit_tests/test_math_solver_pf.hpp @@ -4,6 +4,8 @@ // In this unit test the powerflow solvers are tested +#pragma once + #include "test_math_solver_common.hpp" #include diff --git a/tests/cpp_unit_tests/test_math_solver_se.hpp b/tests/cpp_unit_tests/test_math_solver_se.hpp index 93c4f65fe..1d4268261 100644 --- a/tests/cpp_unit_tests/test_math_solver_se.hpp +++ b/tests/cpp_unit_tests/test_math_solver_se.hpp @@ -4,6 +4,8 @@ // In this unit test the powerflow solvers are tested +#pragma once + #include "test_math_solver_common.hpp" #include diff --git a/tests/cpp_unit_tests/test_optimizer.hpp b/tests/cpp_unit_tests/test_optimizer.hpp index f640d145e..7f24b915d 100644 --- a/tests/cpp_unit_tests/test_optimizer.hpp +++ b/tests/cpp_unit_tests/test_optimizer.hpp @@ -2,6 +2,8 @@ // // SPDX-License-Identifier: MPL-2.0 +#pragma once + #include #include #include diff --git a/tests/cpp_validation_tests/test_validation.cpp b/tests/cpp_validation_tests/test_validation.cpp index c3316acd6..a52a05c7f 100644 --- a/tests/cpp_validation_tests/test_validation.cpp +++ b/tests/cpp_validation_tests/test_validation.cpp @@ -4,7 +4,7 @@ #define PGM_ENABLE_EXPERIMENTAL -#include "power_grid_model_cpp.hpp" +#include #include #include @@ -35,15 +35,6 @@ class UnsupportedValidationCase : public PowerGridError { }()} {} }; -class OptionalNotInitialized : public PowerGridError { - public: - OptionalNotInitialized(std::string const& object) - : PowerGridError{[&]() { - using namespace std::string_literals; - return "Optional "s + object + " object not initialized"s; - }()} {} -}; - using nlohmann::json; auto read_file(std::filesystem::path const& path) { @@ -60,56 +51,11 @@ auto read_json(std::filesystem::path const& path) { return j; } -struct OwningMemory { - std::vector buffers; - std::vector> indptrs; -}; - -struct OwningDataset { - std::optional dataset; - std::optional const_dataset; - OwningMemory storage{}; -}; - -OwningDataset create_owning_dataset(DatasetWritable& writable_dataset) { - auto const& info = writable_dataset.get_info(); - bool const is_batch = info.is_batch(); - Idx const batch_size = info.batch_size(); - auto const& dataset_name = info.name(); - OwningDataset owning_dataset{.dataset{DatasetMutable{dataset_name, is_batch, batch_size}}, - .const_dataset = std::nullopt}; - - for (Idx component_idx{}; component_idx < info.n_components(); ++component_idx) { - auto const& component_name = info.component_name(component_idx); - auto const& component_meta = MetaData::get_component_by_name(dataset_name, component_name); - Idx const component_elements_per_scenario = info.component_elements_per_scenario(component_idx); - Idx const component_size = info.component_total_elements(component_idx); - - auto& current_indptr = owning_dataset.storage.indptrs.emplace_back( - info.component_elements_per_scenario(component_idx) < 0 ? batch_size + 1 : 0); - if (!current_indptr.empty()) { - current_indptr.at(0) = 0; - current_indptr.at(batch_size) = component_size; - } - Idx* const indptr = current_indptr.empty() ? nullptr : current_indptr.data(); - auto& current_buffer = owning_dataset.storage.buffers.emplace_back(component_meta, component_size); - writable_dataset.set_buffer(component_name, indptr, current_buffer); - owning_dataset.dataset.value().add_buffer(component_name, component_elements_per_scenario, component_size, - indptr, current_buffer); - } - owning_dataset.const_dataset = writable_dataset; - return owning_dataset; -} - 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}; + DatasetInfo const& input_info = input.dataset.get_info(); - if (!input.const_dataset.has_value()) { - throw OptionalNotInitialized("DatasetConst"); - } - DatasetInfo const& input_info = input.const_dataset.value().get_info(); + OwningDataset result{.dataset = DatasetMutable{dataset_name, is_batch, batch_size}, .storage{}}; for (Idx component_idx{}; component_idx != input_info.n_components(); ++component_idx) { auto const& component_name = input_info.component_name(component_idx); @@ -117,15 +63,14 @@ OwningDataset create_result_dataset(OwningDataset const& input, std::string cons Idx const component_elements_per_scenario = input_info.component_elements_per_scenario(component_idx); Idx const component_size = input_info.component_total_elements(component_idx); - auto& current_indptr = owning_dataset.storage.indptrs.emplace_back( + auto& current_indptr = result.storage.indptrs.emplace_back( input_info.component_elements_per_scenario(component_idx) < 0 ? batch_size + 1 : 0); Idx const* const indptr = current_indptr.empty() ? nullptr : current_indptr.data(); - auto& current_buffer = owning_dataset.storage.buffers.emplace_back(component_meta, component_size); - owning_dataset.dataset.value().add_buffer(component_name, component_elements_per_scenario, component_size, - indptr, current_buffer); + auto& current_buffer = result.storage.buffers.emplace_back(component_meta, component_size); + result.dataset.add_buffer(component_name, component_elements_per_scenario, component_size, indptr, + current_buffer); } - owning_dataset.const_dataset = owning_dataset.dataset.value(); - return owning_dataset; + return result; } OwningDataset load_dataset(std::filesystem::path const& path) { @@ -259,19 +204,13 @@ void assert_result(OwningDataset const& owning_result, OwningDataset const& owni std::map> atol, double rtol) { using namespace std::string_literals; - if (!owning_result.const_dataset.has_value()) { - throw OptionalNotInitialized("DatasetConst"); - } - DatasetConst const& result = owning_result.const_dataset.value(); + DatasetConst const result{owning_result.dataset}; auto const& result_info = result.get_info(); auto const& result_name = result_info.name(); Idx const result_batch_size = result_info.batch_size(); auto const& storage = owning_result.storage; - if (!owning_reference_result.const_dataset.has_value()) { - throw OptionalNotInitialized("DatasetConst"); - } - DatasetConst const& reference_result = owning_reference_result.const_dataset.value(); + DatasetConst const& reference_result = owning_reference_result.dataset; auto const& reference_result_info = reference_result.get_info(); auto const& reference_result_name = reference_result_info.name(); auto const& reference_storage = owning_reference_result.storage; @@ -574,8 +513,8 @@ void validate_single_case(CaseParam const& param) { // create and run model auto const& options = get_options(param); - Model model{50.0, validation_case.input.const_dataset.value()}; - model.calculate(options, result.dataset.value()); + Model model{50.0, validation_case.input.dataset}; + model.calculate(options, result.dataset); // check results assert_result(result, validation_case.output.value(), param.atol, param.rtol); @@ -586,21 +525,20 @@ void validate_batch_case(CaseParam const& param) { execute_test(param, [&]() { auto const output_prefix = get_output_type(param.calculation_type, param.sym); auto const validation_case = create_validation_case(param, output_prefix); - auto const& info = validation_case.update_batch.value().const_dataset.value().get_info(); + auto const& info = validation_case.update_batch.value().dataset.get_info(); Idx const batch_size = info.batch_size(); auto const batch_result = create_result_dataset(validation_case.output_batch.value(), output_prefix, true, batch_size); // create model - Model model{50.0, validation_case.input.const_dataset.value()}; + Model model{50.0, validation_case.input.dataset}; // check results after whole update is finished for (Idx const threading : {-1, 0, 1, 2}) { CAPTURE(threading); // set options and run auto const& options = get_options(param, threading); - model.calculate(options, batch_result.dataset.value(), - validation_case.update_batch.value().const_dataset.value()); + model.calculate(options, batch_result.dataset, validation_case.update_batch.value().dataset); // check results assert_result(batch_result, validation_case.output_batch.value(), param.atol, param.rtol); diff --git a/tests/native_api_tests/load_dataset.hpp b/tests/native_api_tests/load_dataset.hpp new file mode 100644 index 000000000..4354ab7a1 --- /dev/null +++ b/tests/native_api_tests/load_dataset.hpp @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: Contributors to the Power Grid Model project +// +// SPDX-License-Identifier: MPL-2.0 + +#pragma once + +#include + +namespace power_grid_model_cpp_test { +inline power_grid_model_cpp::OwningDataset load_dataset(std::string const& json_string) { + power_grid_model_cpp::Deserializer deserializer{json_string, PGM_json}; + auto& writable_dataset = deserializer.get_dataset(); + auto owning_dataset = power_grid_model_cpp::create_owning_dataset(writable_dataset); + deserializer.parse_to_buffer(); + return owning_dataset; +} +} // namespace power_grid_model_cpp_test diff --git a/tests/native_api_tests/test_api_model.cpp b/tests/native_api_tests/test_api_model.cpp index 9808715dd..0709749fd 100644 --- a/tests/native_api_tests/test_api_model.cpp +++ b/tests/native_api_tests/test_api_model.cpp @@ -2,6 +2,8 @@ // // SPDX-License-Identifier: MPL-2.0 +#include "load_dataset.hpp" + #include "power_grid_model_cpp.hpp" #include @@ -58,6 +60,9 @@ Dataset created with the following buffers: namespace power_grid_model_cpp { namespace { +using namespace std::string_literals; +using power_grid_model_cpp_test::load_dataset; + enum class MeasuredTerminalType : IntS { branch_from = 0, branch_to = 1, @@ -89,6 +94,78 @@ void check_throws_with(Func&& func, PGM_ErrorCode const& reference_error, std::s check_exception(e, reference_error, reference_err_msg); } } + +auto const complete_state_json = R"json({ + "version": "1.0", + "type": "input", + "is_batch": false, + "attributes": {}, + "data": { + "sym_load": [ + {"id": 2, "node": 0, "status": 1, "type": 2, "p_specified": 0, "q_specified": 500} + ], + "source": [ + {"id": 1, "node": 0, "status": 1, "u_ref": 1, "sk": 1000, "rx_ratio": 0} + ], + "node": [ + {"id": 0, "u_rated": 100}, + {"id": 4, "u_rated": 100} + ], + "line": [ + {"id": 5, "from_node": 0, "to_node": 4, "from_status": 0, "to_status": 1}, + {"id": 6, "from_node": 4, "to_node": 0, "from_status": 0, "to_status": 0} + ] + } +})json"s; + +auto const single_update_json = R"json({ + "version": "1.0", + "type": "update", + "is_batch": false, + "attributes": {}, + "data": { + "source": [ + {"id": 1, "u_ref": 0.5} + ], + "sym_load": [ + {"id": 2, "q_specified": 100} + ], + "line": [ + {"id": 5, "from_status": 0, "to_status": 1}, + {"id": 6, "from_status": 0, "to_status": 0} + ] + } +})json"s; + +auto const batch_update_json = R"json({ + "version": "1.0", + "type": "update", + "is_batch": true, + "attributes": {}, + "data": [ + { + "source": [ + {"id": 1, "u_ref": 0.5} + ], + "sym_load": [ + {"id": 2, "q_specified": 100} + ], + "line": [ + {"id": 5, "from_status": 0, "to_status": 1}, + {"id": 6, "from_status": 0, "to_status": 0} + ] + }, + { + "sym_load": [ + {"id": 2, "q_specified": 300} + ], + "line": [ + {"id": 5, "from_status": 0, "to_status": 0}, + {"id": 6, "from_status": 0, "to_status": 0} + ] + } + ] +})json"s; } // namespace TEST_CASE("API Model") { @@ -96,70 +173,14 @@ TEST_CASE("API Model") { Options options{}; - // input data - DatasetConst input_dataset{"input", false, 1}; - - // node buffer - std::vector const node_id{0, 4}; - std::vector const node_u_rated{100.0, 100.0}; - - // line buffer - std::vector const line_id{5, 6}; - std::vector const line_from_node{0, 4}; - std::vector const line_to_node{4, 0}; - std::vector const line_from_status{0, 1}; - std::vector const line_to_status{1, 0}; - std::vector const batch_line_id{5, 6, 5, 6}; - std::vector const batch_line_from_node{0, 4, 0, 4}; - std::vector const batch_line_to_node{4, 0, 4, 0}; - std::vector const batch_line_from_status{0, 1, 0, 1}; - std::vector const batch_line_to_status{1, 0, 1, 0}; - - // source buffer - ID const source_id = 1; - ID const source_node = 0; - int8_t const source_status = 1; - double const source_u_ref = 1.0; - double const source_sk = 1000.0; - double const source_rx_ratio = 0.0; - Buffer source_buffer{PGM_def_input_source, 1}; - source_buffer.set_nan(); - source_buffer.set_value(PGM_def_input_source_id, &source_id, -1); - source_buffer.set_value(PGM_def_input_source_node, &source_node, 0, sizeof(ID)); - source_buffer.set_value(PGM_def_input_source_status, &source_status, -1); - source_buffer.set_value(PGM_def_input_source_u_ref, &source_u_ref, -1); - source_buffer.set_value(PGM_def_input_source_sk, &source_sk, -1); - source_buffer.set_value(PGM_def_input_source_rx_ratio, &source_rx_ratio, -1); - - // load buffer - ID const load_id = 2; - ID const load_node = 0; - int8_t const load_status = 1; - int8_t const load_type = 2; - double const load_p_specified = 0.0; - double const load_q_specified = 500.0; - Buffer load_buffer{PGM_def_input_sym_load, 1}; - load_buffer.set_value(PGM_def_input_sym_load_id, &load_id, -1); - load_buffer.set_value(PGM_def_input_sym_load_node, &load_node, -1); - load_buffer.set_value(PGM_def_input_sym_load_status, &load_status, -1); - load_buffer.set_value(PGM_def_input_sym_load_type, &load_type, -1); - load_buffer.set_value(PGM_def_input_sym_load_p_specified, &load_p_specified, -1); - load_buffer.set_value(PGM_def_input_sym_load_q_specified, &load_q_specified, -1); - - // add buffers - row - input_dataset.add_buffer("sym_load", 1, 1, nullptr, load_buffer); - input_dataset.add_buffer("source", 1, 1, nullptr, source_buffer); - - // add buffers - columnar - input_dataset.add_buffer("node", 2, 2, 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", 2, 2, 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_attribute_buffer("line", "from_status", line_from_status.data()); - input_dataset.add_attribute_buffer("line", "to_status", line_to_status.data()); + auto const owning_input_dataset = load_dataset(complete_state_json); + auto const& input_dataset = owning_input_dataset.dataset; + + auto const single_owning_update_dataset = load_dataset(single_update_json); + auto const& single_update_dataset = single_owning_update_dataset.dataset; + + auto const batch_owning_update_dataset = load_dataset(batch_update_json); + auto const& batch_update_dataset = batch_owning_update_dataset.dataset; // output data Buffer node_output{PGM_def_sym_output_node, 2}; @@ -182,44 +203,6 @@ TEST_CASE("API Model") { std::vector batch_node_result_u_pu(4); std::vector batch_node_result_u_angle(4); - // update data - ID const source_update_id = 1; - int8_t const source_update_status = std::numeric_limits::min(); - double const source_update_u_ref = 0.5; - double const source_update_u_ref_angle = std::numeric_limits::quiet_NaN(); - Buffer source_update_buffer{PGM_def_update_source, 1}; - source_update_buffer.set_nan(); - source_update_buffer.set_value(PGM_def_update_source_id, &source_update_id, 0, -1); - source_update_buffer.set_value(PGM_def_update_source_status, &source_update_status, 0, -1); - source_update_buffer.set_value(PGM_def_update_source_u_ref, &source_update_u_ref, 0, -1); - source_update_buffer.set_value(PGM_def_update_source_u_ref_angle, &source_update_u_ref_angle, 0, -1); - std::array source_update_indptr{0, 1, 1}; - - std::vector load_updates_id = {2, 2}; - std::vector load_updates_q_specified = {100.0, 300.0}; - Buffer load_updates_buffer{PGM_def_update_sym_load, 2}; - // set nan twice with offset - load_updates_buffer.set_nan(0); - load_updates_buffer.set_nan(1); - load_updates_buffer.set_value(PGM_def_update_sym_load_id, load_updates_id.data(), -1); - 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", 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", 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); - batch_update_dataset.add_attribute_buffer("line", "id", batch_line_id.data()); - batch_update_dataset.add_attribute_buffer("line", "from_status", batch_line_from_status.data()); - batch_update_dataset.add_attribute_buffer("line", "to_status", batch_line_to_status.data()); - // create model Model model{50.0, input_dataset}; @@ -343,43 +326,100 @@ TEST_CASE("API Model") { SUBCASE("Input error handling") { SUBCASE("Construction error") { - ID const bad_load_id = 0; - ID const good_source_update_id = 1; - load_buffer.set_value(PGM_def_input_sym_load_id, &bad_load_id, -1); - source_update_buffer.set_value(PGM_def_update_source_id, &good_source_update_id, 0, -1); - - auto const wrong_model_lambda = [&input_dataset]() { Model const wrong_model{50.0, input_dataset}; }; - - check_throws_with(wrong_model_lambda, PGM_regular_error, "Conflicting id detected:"s); + auto const bad_load_id_state_json = R"json({ + "version": "1.0", + "type": "input", + "is_batch": false, + "attributes": {}, + "data": { + "sym_load": [ + {"id": 0, "node": 0, "status": 1, "type": 2, "p_specified": 0, "q_specified": 500} + ], + "source": [ + {"id": 1, "node": 0, "status": 1, "u_ref": 1, "sk": 1000, "rx_ratio": 0} + ], + "node": [ + {"id": 0, "u_rated": 100}, + {"id": 4, "u_rated": 100} + ], + "line": [ + {"id": 5, "from_node": 0, "to_node": 4, "from_status": 0, "to_status": 1}, + {"id": 6, "from_node": 4, "to_node": 0, "from_status": 0, "to_status": 0} + ] + } +})json"s; + + auto const bad_owning_input_dataset = load_dataset(bad_load_id_state_json); + auto const& bad_input_dataset = bad_owning_input_dataset.dataset; + + auto const bad_model_lambda = [&bad_input_dataset]() { Model const wrong_model{50.0, bad_input_dataset}; }; + + check_throws_with(bad_model_lambda, PGM_regular_error, "Conflicting id detected:"s); } SUBCASE("Update error") { - ID const good_load_id = 2; - ID const bad_source_update_id = 99; - load_buffer.set_value(PGM_def_input_sym_load_id, &good_load_id, -1); - source_update_buffer.set_value(PGM_def_update_source_id, &bad_source_update_id, 0, -1); - - auto const bad_update_lambda = [&model, &single_update_dataset]() { model.update(single_update_dataset); }; + auto const bad_source_id_single_update_json = R"json({ + "version": "1.0", + "type": "update", + "is_batch": false, + "attributes": {}, + "data": { + "source": [ + {"id": 99, "u_ref": 0.5} + ], + "sym_load": [ + {"id": 2, "q_specified": 100} + ], + "line": [ + {"id": 5, "from_status": 0, "to_status": 1}, + {"id": 6, "from_status": 0, "to_status": 0} + ] + } +})json"s; + + auto const bad_single_owning_update_dataset = load_dataset(bad_source_id_single_update_json); + auto const bad_update_lambda = [&model, &bad_single_owning_update_dataset]() { + model.update(bad_single_owning_update_dataset.dataset); + }; check_throws_with(bad_update_lambda, PGM_regular_error, "The id cannot be found:"s); } 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", 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); - bad_batch_update_dataset.add_buffer("line", 2, 4, nullptr, nullptr); // columnar input for line - std::vector const bad_batch_line_id{99, 999, 9999, 99999}; - bad_batch_update_dataset.add_attribute_buffer("line", "id", bad_batch_line_id.data()); - bad_batch_update_dataset.add_attribute_buffer("line", "from_status", batch_line_from_status.data()); - bad_batch_update_dataset.add_attribute_buffer("line", "to_status", batch_line_to_status.data()); + auto const bad_load_id_batch_update_json = R"json({ + "version": "1.0", + "type": "update", + "is_batch": true, + "attributes": {}, + "data": [ + { + "source": [ + {"id": 1, "u_ref": 0.5} + ], + "sym_load": [ + {"id": 2, "q_specified": 100} + ], + "line": [ + {"id": 99, "from_status": 0, "to_status": 1}, + {"id": 999, "from_status": 0, "to_status": 0} + ] + }, + { + "sym_load": [ + {"id": 2, "q_specified": 300} + ], + "line": [ + {"id": 9999, "from_status": 0, "to_status": 0}, + {"id": 99999, "from_status": 0, "to_status": 0} + ] + } + ] +})json"s; + auto const bad_batch_owning_update_dataset = load_dataset(bad_load_id_batch_update_json); auto const bad_calc_with_update_lambda = [&model, &options, &batch_output_dataset, - &bad_batch_update_dataset]() { - model.calculate(options, batch_output_dataset, bad_batch_update_dataset); + &bad_batch_owning_update_dataset]() { + model.calculate(options, batch_output_dataset, bad_batch_owning_update_dataset.dataset); }; check_throws_with(bad_calc_with_update_lambda, PGM_batch_error, "The id cannot be found:"s); } @@ -427,12 +467,41 @@ TEST_CASE("API Model") { SUBCASE("Batch calculation error") { SUBCASE("Line bad line id") { - // wrong id - load_updates_id[1] = 999; - load_updates_buffer.set_value(PGM_def_update_sym_load_id, load_updates_id.data(), 1, -1); - // failed in batch 1 + auto const bad_line_id_batch_update_json = R"json({ + "version": "1.0", + "type": "update", + "is_batch": true, + "attributes": {}, + "data": [ + { + "source": [ + {"id": 1, "u_ref": 0.5} + ], + "sym_load": [ + {"id": 2, "q_specified": 100} + ], + "line": [ + {"id": 5, "from_status": 0, "to_status": 1}, + {"id": 6, "from_status": 0, "to_status": 0} + ] + }, + { + "sym_load": [ + {"id": 999, "q_specified": 300} + ], + "line": [ + {"id": 5, "from_status": 0, "to_status": 0}, + {"id": 6, "from_status": 0, "to_status": 0} + ] + } + ] +})json"s; + + auto const bad_batch_owning_update_dataset = load_dataset(bad_line_id_batch_update_json); + + // failed in batch scenario 1 try { - model.calculate(options, batch_output_dataset, batch_update_dataset); + model.calculate(options, batch_output_dataset, bad_batch_owning_update_dataset.dataset); FAIL("Expected batch calculation error not thrown."); } catch (PowerGridBatchError const& e) { CHECK(e.error_code() == PGM_batch_error); @@ -815,52 +884,59 @@ TEST_CASE("API Model") { } SUBCASE("Forbid link power measurements") { - // input data - DatasetConst input_dataset_se{"input", false, 1}; - auto const construct_model = [&input_dataset_se] { return Model{50.0, input_dataset_se}; }; - - // node buffer - std::vector const node_id_se{1, 2}; - std::vector const node_u_rated_se{10000.0, 10000.0}; - - // link buffer - std::vector const link_id_se{3}; - std::vector const link_from_node_se{1}; - std::vector const link_to_node_se{2}; - - // power sensor - std::vector const power_sensor_id_se{4}; - std::vector const power_sensor_measured_object_se{3}; - std::vector const power_sensor_measured_terminal_type_se{0}; - - input_dataset_se.add_buffer("node", 2, 2, nullptr, nullptr); - input_dataset_se.add_attribute_buffer("node", "id", node_id_se.data()); - input_dataset_se.add_attribute_buffer("node", "u_rated", node_u_rated_se.data()); - - input_dataset_se.add_buffer("link", 1, 1, nullptr, nullptr); - input_dataset_se.add_attribute_buffer("link", "id", link_id_se.data()); - input_dataset_se.add_attribute_buffer("link", "from_node", link_from_node_se.data()); - input_dataset_se.add_attribute_buffer("link", "to_node", link_to_node_se.data()); - SUBCASE("SymPowerSensor") { - input_dataset_se.add_buffer("sym_power_sensor", 1, 1, nullptr, nullptr); - input_dataset_se.add_attribute_buffer("sym_power_sensor", "id", power_sensor_id_se.data()); - input_dataset_se.add_attribute_buffer("sym_power_sensor", "measured_object", - power_sensor_measured_object_se.data()); - input_dataset_se.add_attribute_buffer("sym_power_sensor", "measured_terminal_type", - power_sensor_measured_terminal_type_se.data()); + auto const input_data_se_json = R"json({ + "version": "1.0", + "type": "input", + "is_batch": false, + "attributes": {}, + "data": { + "node": [ + {"id": 1, "u_rated": 10000}, + {"id": 2, "u_rated": 10000} + ], + "link": [ + {"id": 3, "from_node": 1, "to_node": 2} + ], + "sym_power_sensor": [ + {"id": 4, "measured_object": 3, "measured_terminal_type": 0} + ] + } +})json"s; + + auto const owning_input_dataset_se = load_dataset(input_data_se_json); + auto const& input_dataset_se = owning_input_dataset_se.dataset; + + auto const construct_model = [&input_dataset_se] { return Model{50.0, input_dataset_se}; }; CHECK_THROWS_WITH_AS(construct_model(), "PowerSensor measurement is not supported for object of type Link", PowerGridRegularError); } SUBCASE("AsymPowerSensor") { - input_dataset_se.add_buffer("asym_power_sensor", 2, 2, nullptr, nullptr); - input_dataset_se.add_attribute_buffer("asym_power_sensor", "id", power_sensor_id_se.data()); - input_dataset_se.add_attribute_buffer("asym_power_sensor", "measured_object", - power_sensor_measured_object_se.data()); - input_dataset_se.add_attribute_buffer("asym_power_sensor", "measured_terminal_type", - power_sensor_measured_terminal_type_se.data()); + auto const input_data_se_json = R"json({ + "version": "1.0", + "type": "input", + "is_batch": false, + "attributes": {}, + "data": { + "node": [ + {"id": 1, "u_rated": 10000}, + {"id": 2, "u_rated": 10000} + ], + "link": [ + {"id": 3, "from_node": 1, "to_node": 2} + ], + "asym_power_sensor": [ + {"id": 4, "measured_object": 3, "measured_terminal_type": 0} + ] + } +})json"s; + + auto const owning_input_dataset_se = load_dataset(input_data_se_json); + auto const& input_dataset_se = owning_input_dataset_se.dataset; + + auto const construct_model = [&input_dataset_se] { return Model{50.0, input_dataset_se}; }; CHECK_THROWS_WITH_AS(construct_model(), "PowerSensor measurement is not supported for object of type Link", PowerGridRegularError); @@ -868,34 +944,45 @@ TEST_CASE("API Model") { } SUBCASE("Test duplicated id") { - std::vector node_id_2{1, 1, 3}; - DatasetConst input_dataset_2{"input", false, 1}; - - input_dataset_2.add_buffer("node", std::ssize(node_id_2), std::ssize(node_id_2), nullptr, nullptr); - input_dataset_2.add_attribute_buffer("node", "id", node_id_2.data()); + auto const input_data_2_json = R"json({ + "version": "1.0", + "type": "input", + "is_batch": false, + "attributes": {}, + "data": { + "node": [ + {"id": 1}, + {"id": 1}, + {"id": 3} + ] + } +})json"s; + + auto const owning_input_dataset_2 = load_dataset(input_data_2_json); + auto const& input_dataset_2 = owning_input_dataset_2.dataset; auto construct_model = [&] { Model{50.0, input_dataset_2}; }; CHECK_THROWS_WITH_AS(construct_model(), "Conflicting id detected: 1\n", PowerGridRegularError); } SUBCASE("Test non-existing id") { - std::vector const node_id_2{1, 2, 3}; - std::vector const node_u_rated_2{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_2{"input", false, 1}; - - input_dataset_2.add_buffer("node", std::ssize(node_id_2), std::ssize(node_id_2), nullptr, nullptr); - input_dataset_2.add_attribute_buffer("node", "id", node_id_2.data()); - input_dataset_2.add_attribute_buffer("node", "u_rated", node_u_rated_2.data()); - - input_dataset_2.add_buffer("link", std::ssize(link_id), std::ssize(link_id), nullptr, nullptr); - input_dataset_2.add_attribute_buffer("link", "id", link_id.data()); - input_dataset_2.add_attribute_buffer("link", "from_node", link_from_node.data()); - input_dataset_2.add_attribute_buffer("link", "to_node", link_to_node.data()); + auto const input_data_2_json = R"json({ + "version": "1.0", + "type": "input", + "is_batch": false, + "attributes": {}, + "data": { + "node": [ + {"id": 1, "u_rated": 10000}, + {"id": 2, "u_rated": 10000} + ], + "link": [ + {"id": 5, "from_node": 99, "to_node": 2} + ] + } +})json"s; + auto const owning_input_dataset_2 = load_dataset(input_data_2_json); + auto const& input_dataset_2 = owning_input_dataset_2.dataset; auto construct_model = [&] { Model{50.0, input_dataset_2}; }; CHECK_THROWS_WITH_AS(construct_model(), "The id cannot be found: 99\n", PowerGridRegularError); diff --git a/tests/native_api_tests/test_api_model_update.cpp b/tests/native_api_tests/test_api_model_update.cpp index 0614b2535..8dd14f324 100644 --- a/tests/native_api_tests/test_api_model_update.cpp +++ b/tests/native_api_tests/test_api_model_update.cpp @@ -2,6 +2,8 @@ // // SPDX-License-Identifier: MPL-2.0 +#include "load_dataset.hpp" + #include #include #include @@ -82,6 +84,7 @@ TYPE_TO_STRING_AS("row_t, columnar_t, dense_t, invalid_id_t", TypeCombo); namespace power_grid_model_cpp { +using power_grid_model_cpp_test::load_dataset; /* @@ -255,6 +258,7 @@ TEST_CASE_TEMPLATE( } namespace { +using namespace std::string_literals; using std::numbers::sqrt3; enum class CalculationSymmetry : Idx { symmetric = PGM_symmetric, asymmetric = PGM_asymmetric }; @@ -293,188 +297,95 @@ 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; +auto const complete_state_json = R"json({ + "version": "1.0", + "type": "input", + "is_batch": false, + "attributes": {}, + "data": { + "node": [ + {"id": 1, "u_rated": 10000}, + {"id": 2, "u_rated": 10000}, + {"id": 3, "u_rated": 10000} + ], + "line": [ + {"id": 4, "from_node": 1, "to_node": 2, "from_status": 1, "to_status": 1, "r1": 10, "x1": 0, "c1": 0, "tan1": 0, "r0": 10, "x0": 0, "c0": 0, "tan0": 0, "i_n": 1000} + ], + "link": [ + {"id": 5, "from_node": 2, "to_node": 3, "from_status": 1, "to_status": 1} + ], + "source": [ + {"id": 6, "node": 1, "status": 1, "u_ref": 1.05, "sk": 1000000000000}, + {"id": 10, "node": 3, "status": 0, "u_ref": 1.05, "u_ref_angle": 0, "sk": 1000000000000} + ], + "sym_load": [ + {"id": 7, "node": 3, "status": 1, "type": 1, "p_specified": 500000, "q_specified": 0} + ], + "asym_load": [ + {"id": 8, "node": 3, "status": 1, "type": 1, "p_specified": [166666.6666666667, 166666.6666666667, 166666.6666666667], "q_specified": [0, 0, 0]} + ], + "shunt": [ + {"id": 9, "node": 3, "status": 1, "g1": 0.015, "b1": 0, "g0": 0.015, "b0": 0} + ] + } +})json"s; + +auto const update_json = R"json({ + "version": "1.0", + "type": "update", + "is_batch": true, + "attributes": {}, + "data": [ + { + "sym_load": [ + {"id": 7, "status": 1, "p_specified": 2500000} + ], + "asym_load": [ + {"id": 8, "status": 0} + ], + "shunt": [ + {"id": 9, "status": 0, "b1": 0.02, "b0": 0.02} + ], + "source": [ + {"id": 10, "status": 1, "u_ref": 0.84} + ], + "link": [ + {"id": 5, "from_status": 1, "to_status": 0} + ] } -}; + ] +})json"s; + +auto const update_vector_json = R"json({ + "version": "1.0", + "type": "update", + "is_batch": true, + "attributes": {}, + "data": [ + { + "sym_load": [ + {"id": 7, "status": 1, "p_specified": 2500000} + ], + "asym_load": [ + {"id": 8, "status": 0} + ], + "shunt": [ + {"id": 9, "status": 0, "b1": 0.02, "b0": 0.02} + ] + } + ] +})json"s; } // namespace TEST_CASE("API model - all updates") { - using namespace std::string_literals; + auto const owning_input_dataset = load_dataset(complete_state_json); + auto const& input_dataset = owning_input_dataset.dataset; - 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 owning_update_dataset = load_dataset(update_json); + auto const& update_data = owning_update_dataset.dataset; auto const output_dataset_type = "sym_output"s; for (Idx comp_type_idx = 0; comp_type_idx < input_info.n_components(); ++comp_type_idx) { @@ -524,8 +435,9 @@ TEST_CASE("API model - all updates") { } TEST_CASE("API model - updates w/ alternating compute mode") { - State const state; - auto const input_dataset = state.get_input_dataset(); + auto const owning_input_dataset = load_dataset(complete_state_json); + auto const& input_dataset = owning_input_dataset.dataset; + auto model = Model{50.0, input_dataset}; auto const check_sym = [&model] { @@ -607,34 +519,8 @@ TEST_CASE("API model - updates w/ alternating compute mode") { 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()); + auto const owning_update_dataset = load_dataset(update_vector_json); + auto const& update_data = owning_update_dataset.dataset; // This will lead to no topo change but param change model.update(update_data); @@ -655,23 +541,94 @@ TEST_CASE("API model - updates w/ alternating compute mode") { } 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; -} +auto const incomplete_state_json = R"json({ + "version": "1.0", + "type": "input", + "is_batch": false, + "attributes": {}, + "data": { + "node": [ + {"id": 1, "u_rated": 10000}, + {"id": 2, "u_rated": 10000}, + {"id": 3, "u_rated": 10000} + ], + "line": [ + {"id": 4, "from_node": 1, "to_node": 2, "from_status": 1, "to_status": 1, "r1": 10, "x1": 0, "c1": 0, "tan1": 0, "r0": 10, "x0": 0, "c0": 0, "tan0": 0, "i_n": 1000} + ], + "link": [ + {"id": 5, "from_node": 2, "to_node": 3, "from_status": 1, "to_status": 1} + ], + "source": [ + {"id": 6, "node": 1, "status": 1, "sk": 1000000000000}, + {"id": 10, "node": 3, "status": 0, "sk": 1000000000000} + ], + "sym_load": [ + {"id": 7, "node": 3, "status": 1, "type": 1, "q_specified": 0} + ], + "asym_load": [ + {"id": 8, "node": 3, "status": 1, "type": 1, "q_specified": [0, 0, 0]} + ], + "shunt": [ + {"id": 9, "node": 3, "status": 1, "g1": 0.015, "b1": 0, "g0": 0.015, "b0": 0} + ] + } +})json"s; + +auto const incomplete_update_json = R"json({ + "version": "1.0", + "type": "update", + "is_batch": true, + "attributes": {}, + "data": [ + { + "source": [ + {"id": 6}, + {"id": 10} + ], + "sym_load": [ + {"id": 7} + ], + "asym_load": [ + {"id": 8} + ] + } + ] +})json"s; + +auto const complete_update_json = R"json({ + "version": "1.0", + "type": "update", + "is_batch": true, + "attributes": {}, + "data": [ + { + "source": [ + {"id": 6, "u_ref": 1.05}, + {"id": 10, "u_ref": 1.05, "u_ref_angle": 0} + ], + "sym_load": [ + {"id": 7, "p_specified": 500000} + ], + "asym_load": [ + {"id": 8, "p_specified": [166666.6666666667, 166666.6666666667, 166666.6666666667]} + ] + } + ] +})json"s; } // namespace TEST_CASE("API model - incomplete input") { - State const complete_state; - State const incomplete_state = get_incomplete_state(); + auto const complete_owning_input_dataset = load_dataset(complete_state_json); + auto const& complete_input_data = complete_owning_input_dataset.dataset; + + auto const incomplete_owning_input_dataset = load_dataset(incomplete_state_json); + auto const& incomplete_input_data = incomplete_owning_input_dataset.dataset; + + auto const& input_info = complete_input_data.get_info(); + auto const n_nodes = input_info.component_elements_per_scenario(input_info.component_idx("node")); + REQUIRE(n_nodes == 3); - auto test_model = Model{50.0, incomplete_state.get_input_dataset()}; + auto test_model = Model{50.0, incomplete_input_data}; for (auto symmetry : {PGM_symmetric, PGM_asymmetric}) { CAPTURE(symmetry); @@ -682,7 +639,7 @@ TEST_CASE("API model - incomplete input") { 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)); + Buffer test_node_output(node_output_meta, n_nodes); 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); @@ -708,25 +665,8 @@ TEST_CASE("API model - incomplete input") { } } 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()); + auto const owning_update_dataset = load_dataset(incomplete_update_json); + auto const& incomplete_update_data = owning_update_dataset.dataset; SUBCASE("Single update") { CHECK_NOTHROW(test_model.update(incomplete_update_data)); @@ -741,28 +681,11 @@ TEST_CASE("API model - incomplete input") { } } 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)); + auto const owning_update_dataset = load_dataset(complete_update_json); + auto const& complete_update_data = owning_update_dataset.dataset; + + auto ref_model = Model{50.0, complete_input_data}; + Buffer ref_node_output(node_output_meta, n_nodes); 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); @@ -779,7 +702,7 @@ TEST_CASE("API model - incomplete input") { complete_update_data)); } - for (Idx node_idx = 0; node_idx < std::ssize(complete_state.node_id); ++node_idx) { + for (Idx node_idx = 0; node_idx < n_nodes; ++node_idx) { CAPTURE(node_idx); for (Idx attr_idx = 0; attr_idx < MetaData::n_attributes(node_output_meta); ++attr_idx) { @@ -807,79 +730,81 @@ TEST_CASE("API model - incomplete input") { } } +auto const mixed_update_json = R"json({ + "version": "1.0", + "type": "update", + "is_batch": true, + "attributes": {}, + "data": [ + { + "source": [ + {"id": 6, "status": 1}, + {"id": 10, "status": 1} + ], + "sym_load": [ + {"id": 7, "status": 1, "q_specified": 1} + ], + "asym_load": [ + {"id": 8, "status": 1, "q_specified": [1, 1, 1]} + ] + }, + { + "source": [ + {"id": 6, "status": 1, "u_ref": 1.05}, + {"id": 10, "status": 1, "u_ref": 1.05, "u_ref_angle": 0} + ], + "sym_load": [ + {"id": 7, "status": 0, "p_specified": 500000} + ], + "asym_load": [ + {"id": 8, "status": 0, "p_specified": [166666.6666666667, 166666.6666666667, 166666.6666666667]} + ] + } + ] +})json"s; + +auto const second_scenario_update_json = R"json({ + "version": "1.0", + "type": "update", + "is_batch": true, + "attributes": {}, + "data": [ + { + "source": [ + {"id": 6, "status": 1, "u_ref": 1.05}, + {"id": 10, "status": 1, "u_ref": 1.05, "u_ref_angle": 0} + ], + "sym_load": [ + {"id": 7, "status": 1, "p_specified": 500000} + ], + "asym_load": [ + {"id": 8, "status": 1, "p_specified": [null, null, 166666.6666666667], "q_specified": [1, 1, null]} + ] + } + ] +})json"s; + 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); + auto const complete_owning_input_dataset = load_dataset(complete_state_json); + auto const incomplete_owning_input_dataset = load_dataset(incomplete_state_json); + + auto const& complete_input_data = complete_owning_input_dataset.dataset; + auto const& incomplete_input_data = incomplete_owning_input_dataset.dataset; + + auto ref_model = Model{50.0, complete_input_data}; + auto test_model = Model{50.0, incomplete_input_data}; + + auto const& input_info = complete_input_data.get_info(); + auto const n_nodes = input_info.component_elements_per_scenario(input_info.component_idx("node")); + REQUIRE(n_nodes == 3); + + auto const mixed_owning_update_dataset = load_dataset(mixed_update_json); + auto const& mixed_update_data = mixed_owning_update_dataset.dataset; + auto const batch_size = mixed_update_data.get_info().batch_size(); + REQUIRE(batch_size == 2); + + auto const second_scenario_owning_update_dataset = load_dataset(second_scenario_update_json); + auto const& second_scenario_update_data = second_scenario_owning_update_dataset.dataset; for (auto symmetry : {PGM_symmetric, PGM_asymmetric}) { CAPTURE(symmetry);