diff --git a/code_generation/data/attribute_classes/input.json b/code_generation/data/attribute_classes/input.json index dea1c0fdd..7cf8b5c97 100644 --- a/code_generation/data/attribute_classes/input.json +++ b/code_generation/data/attribute_classes/input.json @@ -494,6 +494,45 @@ "description": "line drop compensation reactance" } ] + }, + { + "name": "GenericCurrentSensorInput", + "base": "SensorInput", + "attributes": [ + { + "data_type": "MeasuredTerminalType", + "names": "measured_terminal_type", + "description": "type of measured terminal" + }, + { + "data_type": "AngleMeasurementType", + "names": "angle_measurement_type", + "description": "type of angle measurement" + }, + { + "data_type": "double", + "names": [ + "i_sigma", + "i_angle_sigma" + ], + "description": "sigma of error margin of current (angle) measurement" + } + ] + }, + { + "name": "CurrentSensorInput", + "base": "GenericCurrentSensorInput", + "is_template": true, + "attributes": [ + { + "data_type": "RealValue", + "names": [ + "i_measured", + "i_angle_measured" + ], + "description": "measured current and current angle" + } + ] } ] } \ No newline at end of file diff --git a/code_generation/data/attribute_classes/output.json b/code_generation/data/attribute_classes/output.json index 3fd789e6d..b8c310850 100644 --- a/code_generation/data/attribute_classes/output.json +++ b/code_generation/data/attribute_classes/output.json @@ -293,6 +293,21 @@ "base": "BaseOutput", "is_template": false, "attributes": [] + }, + { + "name": "CurrentSensorOutput", + "base": "BaseOutput", + "is_template": true, + "attributes": [ + { + "data_type": "RealValue", + "names": [ + "i_residual", + "i_angle_residual" + ], + "description": "deviation between the measured value and calculated value" + } + ] } ] } \ No newline at end of file diff --git a/code_generation/data/attribute_classes/update.json b/code_generation/data/attribute_classes/update.json index a57205b17..db87d7bd1 100644 --- a/code_generation/data/attribute_classes/update.json +++ b/code_generation/data/attribute_classes/update.json @@ -243,6 +243,29 @@ "description": "line drop compensation reactance" } ] + }, + { + "name": "CurrentSensorUpdate", + "base": "BaseUpdate", + "is_template": true, + "attributes": [ + { + "data_type": "double", + "names": [ + "i_sigma", + "i_angle_sigma" + ], + "description": "sigma of error margin of current (angle) measurement" + }, + { + "data_type": "RealValue", + "names": [ + "i_measured", + "i_angle_measured" + ], + "description": "measured current and current angle" + } + ] } ] } \ No newline at end of file diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/auxiliary/input.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/auxiliary/input.hpp index ec9eaed24..661579d1c 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/auxiliary/input.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/auxiliary/input.hpp @@ -444,6 +444,52 @@ struct TransformerTapRegulatorInput { operator RegulatorInput const&() const { return reinterpret_cast(*this); } }; +struct GenericCurrentSensorInput { + ID id{na_IntID}; // ID of the object + ID measured_object{na_IntID}; // ID of the measured object + MeasuredTerminalType measured_terminal_type{static_cast(na_IntS)}; // type of measured terminal + AngleMeasurementType angle_measurement_type{static_cast(na_IntS)}; // type of angle measurement + double i_sigma{nan}; // sigma of error margin of current (angle) measurement + double i_angle_sigma{nan}; // sigma of error margin of current (angle) measurement + + // implicit conversions to BaseInput + operator BaseInput&() { return reinterpret_cast(*this); } + operator BaseInput const&() const { return reinterpret_cast(*this); } + + // implicit conversions to SensorInput + operator SensorInput&() { return reinterpret_cast(*this); } + operator SensorInput const&() const { return reinterpret_cast(*this); } +}; + +template +struct CurrentSensorInput { + using sym = sym_type; + + ID id{na_IntID}; // ID of the object + ID measured_object{na_IntID}; // ID of the measured object + MeasuredTerminalType measured_terminal_type{static_cast(na_IntS)}; // type of measured terminal + AngleMeasurementType angle_measurement_type{static_cast(na_IntS)}; // type of angle measurement + double i_sigma{nan}; // sigma of error margin of current (angle) measurement + double i_angle_sigma{nan}; // sigma of error margin of current (angle) measurement + RealValue i_measured{nan}; // measured current and current angle + RealValue i_angle_measured{nan}; // measured current and current angle + + // implicit conversions to BaseInput + operator BaseInput&() { return reinterpret_cast(*this); } + operator BaseInput const&() const { return reinterpret_cast(*this); } + + // implicit conversions to SensorInput + operator SensorInput&() { return reinterpret_cast(*this); } + operator SensorInput const&() const { return reinterpret_cast(*this); } + + // implicit conversions to GenericCurrentSensorInput + operator GenericCurrentSensorInput&() { return reinterpret_cast(*this); } + operator GenericCurrentSensorInput const&() const { return reinterpret_cast(*this); } +}; + +using SymCurrentSensorInput = CurrentSensorInput; +using AsymCurrentSensorInput = CurrentSensorInput; + } // namespace power_grid_model diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/auxiliary/meta_gen/input.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/auxiliary/meta_gen/input.hpp index 7cca2754f..27deffff7 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/auxiliary/meta_gen/input.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/auxiliary/meta_gen/input.hpp @@ -396,6 +396,38 @@ struct get_attributes_list { }; }; +template<> +struct get_attributes_list { + static constexpr std::array value{ + // all attributes including base class + + meta_data_gen::get_meta_attribute<&GenericCurrentSensorInput::id>(offsetof(GenericCurrentSensorInput, id), "id"), + meta_data_gen::get_meta_attribute<&GenericCurrentSensorInput::measured_object>(offsetof(GenericCurrentSensorInput, measured_object), "measured_object"), + meta_data_gen::get_meta_attribute<&GenericCurrentSensorInput::measured_terminal_type>(offsetof(GenericCurrentSensorInput, measured_terminal_type), "measured_terminal_type"), + meta_data_gen::get_meta_attribute<&GenericCurrentSensorInput::angle_measurement_type>(offsetof(GenericCurrentSensorInput, angle_measurement_type), "angle_measurement_type"), + meta_data_gen::get_meta_attribute<&GenericCurrentSensorInput::i_sigma>(offsetof(GenericCurrentSensorInput, i_sigma), "i_sigma"), + meta_data_gen::get_meta_attribute<&GenericCurrentSensorInput::i_angle_sigma>(offsetof(GenericCurrentSensorInput, i_angle_sigma), "i_angle_sigma"), + }; +}; + +template +struct get_attributes_list> { + using sym = sym_type; + + static constexpr std::array value{ + // all attributes including base class + + meta_data_gen::get_meta_attribute<&CurrentSensorInput::id>(offsetof(CurrentSensorInput, id), "id"), + meta_data_gen::get_meta_attribute<&CurrentSensorInput::measured_object>(offsetof(CurrentSensorInput, measured_object), "measured_object"), + meta_data_gen::get_meta_attribute<&CurrentSensorInput::measured_terminal_type>(offsetof(CurrentSensorInput, measured_terminal_type), "measured_terminal_type"), + meta_data_gen::get_meta_attribute<&CurrentSensorInput::angle_measurement_type>(offsetof(CurrentSensorInput, angle_measurement_type), "angle_measurement_type"), + meta_data_gen::get_meta_attribute<&CurrentSensorInput::i_sigma>(offsetof(CurrentSensorInput, i_sigma), "i_sigma"), + meta_data_gen::get_meta_attribute<&CurrentSensorInput::i_angle_sigma>(offsetof(CurrentSensorInput, i_angle_sigma), "i_angle_sigma"), + meta_data_gen::get_meta_attribute<&CurrentSensorInput::i_measured>(offsetof(CurrentSensorInput, i_measured), "i_measured"), + meta_data_gen::get_meta_attribute<&CurrentSensorInput::i_angle_measured>(offsetof(CurrentSensorInput, i_angle_measured), "i_angle_measured"), + }; +}; + diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/auxiliary/meta_gen/output.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/auxiliary/meta_gen/output.hpp index 3d4b62e96..186ec3b56 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/auxiliary/meta_gen/output.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/auxiliary/meta_gen/output.hpp @@ -247,6 +247,20 @@ struct get_attributes_list { }; }; +template +struct get_attributes_list> { + using sym = sym_type; + + static constexpr std::array value{ + // all attributes including base class + + meta_data_gen::get_meta_attribute<&CurrentSensorOutput::id>(offsetof(CurrentSensorOutput, id), "id"), + meta_data_gen::get_meta_attribute<&CurrentSensorOutput::energized>(offsetof(CurrentSensorOutput, energized), "energized"), + meta_data_gen::get_meta_attribute<&CurrentSensorOutput::i_residual>(offsetof(CurrentSensorOutput, i_residual), "i_residual"), + meta_data_gen::get_meta_attribute<&CurrentSensorOutput::i_angle_residual>(offsetof(CurrentSensorOutput, i_angle_residual), "i_angle_residual"), + }; +}; + diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/auxiliary/meta_gen/update.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/auxiliary/meta_gen/update.hpp index d4781bfe8..0e73efc8f 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/auxiliary/meta_gen/update.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/auxiliary/meta_gen/update.hpp @@ -197,6 +197,21 @@ struct get_attributes_list { }; }; +template +struct get_attributes_list> { + using sym = sym_type; + + static constexpr std::array value{ + // all attributes including base class + + meta_data_gen::get_meta_attribute<&CurrentSensorUpdate::id>(offsetof(CurrentSensorUpdate, id), "id"), + meta_data_gen::get_meta_attribute<&CurrentSensorUpdate::i_sigma>(offsetof(CurrentSensorUpdate, i_sigma), "i_sigma"), + meta_data_gen::get_meta_attribute<&CurrentSensorUpdate::i_angle_sigma>(offsetof(CurrentSensorUpdate, i_angle_sigma), "i_angle_sigma"), + meta_data_gen::get_meta_attribute<&CurrentSensorUpdate::i_measured>(offsetof(CurrentSensorUpdate, i_measured), "i_measured"), + meta_data_gen::get_meta_attribute<&CurrentSensorUpdate::i_angle_measured>(offsetof(CurrentSensorUpdate, i_angle_measured), "i_angle_measured"), + }; +}; + diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/auxiliary/output.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/auxiliary/output.hpp index 54de1fa69..1fae40845 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/auxiliary/output.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/auxiliary/output.hpp @@ -245,6 +245,23 @@ struct RegulatorShortCircuitOutput { operator BaseOutput const&() const { return reinterpret_cast(*this); } }; +template +struct CurrentSensorOutput { + using sym = sym_type; + + ID id{na_IntID}; // ID of the object + IntS energized{na_IntS}; // whether the object is energized + RealValue i_residual{nan}; // deviation between the measured value and calculated value + RealValue i_angle_residual{nan}; // deviation between the measured value and calculated value + + // implicit conversions to BaseOutput + operator BaseOutput&() { return reinterpret_cast(*this); } + operator BaseOutput const&() const { return reinterpret_cast(*this); } +}; + +using SymCurrentSensorOutput = CurrentSensorOutput; +using AsymCurrentSensorOutput = CurrentSensorOutput; + } // namespace power_grid_model diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/auxiliary/static_asserts/input.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/auxiliary/static_asserts/input.hpp index ee2288a36..ee0052125 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/auxiliary/static_asserts/input.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/auxiliary/static_asserts/input.hpp @@ -517,6 +517,124 @@ static_assert(offsetof(TransformerTapRegulatorInput, id) == offsetof(RegulatorIn static_assert(offsetof(TransformerTapRegulatorInput, regulated_object) == offsetof(RegulatorInput, regulated_object)); static_assert(offsetof(TransformerTapRegulatorInput, status) == offsetof(RegulatorInput, status)); +// static asserts for GenericCurrentSensorInput +static_assert(std::is_standard_layout_v); +// static asserts for conversion of GenericCurrentSensorInput to BaseInput +static_assert(std::alignment_of_v >= std::alignment_of_v); +static_assert(std::same_as); +static_assert(offsetof(GenericCurrentSensorInput, id) == offsetof(BaseInput, id)); +// static asserts for conversion of GenericCurrentSensorInput to SensorInput +static_assert(std::alignment_of_v >= std::alignment_of_v); +static_assert(std::same_as); +static_assert(std::same_as); +static_assert(offsetof(GenericCurrentSensorInput, id) == offsetof(SensorInput, id)); +static_assert(offsetof(GenericCurrentSensorInput, measured_object) == offsetof(SensorInput, measured_object)); + +// static asserts for CurrentSensorInput +static_assert(std::is_standard_layout_v>); +// static asserts for conversion of CurrentSensorInput to BaseInput +static_assert(std::alignment_of_v> >= std::alignment_of_v); +static_assert(std::same_as::id), decltype(BaseInput::id)>); +static_assert(offsetof(CurrentSensorInput, id) == offsetof(BaseInput, id)); +// static asserts for conversion of CurrentSensorInput to SensorInput +static_assert(std::alignment_of_v> >= std::alignment_of_v); +static_assert(std::same_as::id), decltype(SensorInput::id)>); +static_assert(std::same_as::measured_object), decltype(SensorInput::measured_object)>); +static_assert(offsetof(CurrentSensorInput, id) == offsetof(SensorInput, id)); +static_assert(offsetof(CurrentSensorInput, measured_object) == offsetof(SensorInput, measured_object)); +// static asserts for conversion of CurrentSensorInput to GenericCurrentSensorInput +static_assert(std::alignment_of_v> >= std::alignment_of_v); +static_assert(std::same_as::id), decltype(GenericCurrentSensorInput::id)>); +static_assert(std::same_as::measured_object), decltype(GenericCurrentSensorInput::measured_object)>); +static_assert(std::same_as::measured_terminal_type), decltype(GenericCurrentSensorInput::measured_terminal_type)>); +static_assert(std::same_as::angle_measurement_type), decltype(GenericCurrentSensorInput::angle_measurement_type)>); +static_assert(std::same_as::i_sigma), decltype(GenericCurrentSensorInput::i_sigma)>); +static_assert(std::same_as::i_angle_sigma), decltype(GenericCurrentSensorInput::i_angle_sigma)>); +static_assert(offsetof(CurrentSensorInput, id) == offsetof(GenericCurrentSensorInput, id)); +static_assert(offsetof(CurrentSensorInput, measured_object) == offsetof(GenericCurrentSensorInput, measured_object)); +static_assert(offsetof(CurrentSensorInput, measured_terminal_type) == offsetof(GenericCurrentSensorInput, measured_terminal_type)); +static_assert(offsetof(CurrentSensorInput, angle_measurement_type) == offsetof(GenericCurrentSensorInput, angle_measurement_type)); +static_assert(offsetof(CurrentSensorInput, i_sigma) == offsetof(GenericCurrentSensorInput, i_sigma)); +static_assert(offsetof(CurrentSensorInput, i_angle_sigma) == offsetof(GenericCurrentSensorInput, i_angle_sigma)); +// static asserts for CurrentSensorInput +static_assert(std::is_standard_layout_v>); +// static asserts for conversion of CurrentSensorInput to BaseInput +static_assert(std::alignment_of_v> >= std::alignment_of_v); +static_assert(std::same_as::id), decltype(BaseInput::id)>); +static_assert(offsetof(CurrentSensorInput, id) == offsetof(BaseInput, id)); +// static asserts for conversion of CurrentSensorInput to SensorInput +static_assert(std::alignment_of_v> >= std::alignment_of_v); +static_assert(std::same_as::id), decltype(SensorInput::id)>); +static_assert(std::same_as::measured_object), decltype(SensorInput::measured_object)>); +static_assert(offsetof(CurrentSensorInput, id) == offsetof(SensorInput, id)); +static_assert(offsetof(CurrentSensorInput, measured_object) == offsetof(SensorInput, measured_object)); +// static asserts for conversion of CurrentSensorInput to GenericCurrentSensorInput +static_assert(std::alignment_of_v> >= std::alignment_of_v); +static_assert(std::same_as::id), decltype(GenericCurrentSensorInput::id)>); +static_assert(std::same_as::measured_object), decltype(GenericCurrentSensorInput::measured_object)>); +static_assert(std::same_as::measured_terminal_type), decltype(GenericCurrentSensorInput::measured_terminal_type)>); +static_assert(std::same_as::angle_measurement_type), decltype(GenericCurrentSensorInput::angle_measurement_type)>); +static_assert(std::same_as::i_sigma), decltype(GenericCurrentSensorInput::i_sigma)>); +static_assert(std::same_as::i_angle_sigma), decltype(GenericCurrentSensorInput::i_angle_sigma)>); +static_assert(offsetof(CurrentSensorInput, id) == offsetof(GenericCurrentSensorInput, id)); +static_assert(offsetof(CurrentSensorInput, measured_object) == offsetof(GenericCurrentSensorInput, measured_object)); +static_assert(offsetof(CurrentSensorInput, measured_terminal_type) == offsetof(GenericCurrentSensorInput, measured_terminal_type)); +static_assert(offsetof(CurrentSensorInput, angle_measurement_type) == offsetof(GenericCurrentSensorInput, angle_measurement_type)); +static_assert(offsetof(CurrentSensorInput, i_sigma) == offsetof(GenericCurrentSensorInput, i_sigma)); +static_assert(offsetof(CurrentSensorInput, i_angle_sigma) == offsetof(GenericCurrentSensorInput, i_angle_sigma)); +// static asserts for SymCurrentSensorInput +static_assert(std::is_standard_layout_v); +// static asserts for conversion of SymCurrentSensorInput to BaseInput +static_assert(std::alignment_of_v >= std::alignment_of_v); +static_assert(std::same_as); +static_assert(offsetof(SymCurrentSensorInput, id) == offsetof(BaseInput, id)); +// static asserts for conversion of SymCurrentSensorInput to SensorInput +static_assert(std::alignment_of_v >= std::alignment_of_v); +static_assert(std::same_as); +static_assert(std::same_as); +static_assert(offsetof(SymCurrentSensorInput, id) == offsetof(SensorInput, id)); +static_assert(offsetof(SymCurrentSensorInput, measured_object) == offsetof(SensorInput, measured_object)); +// static asserts for conversion of SymCurrentSensorInput to GenericCurrentSensorInput +static_assert(std::alignment_of_v >= std::alignment_of_v); +static_assert(std::same_as); +static_assert(std::same_as); +static_assert(std::same_as); +static_assert(std::same_as); +static_assert(std::same_as); +static_assert(std::same_as); +static_assert(offsetof(SymCurrentSensorInput, id) == offsetof(GenericCurrentSensorInput, id)); +static_assert(offsetof(SymCurrentSensorInput, measured_object) == offsetof(GenericCurrentSensorInput, measured_object)); +static_assert(offsetof(SymCurrentSensorInput, measured_terminal_type) == offsetof(GenericCurrentSensorInput, measured_terminal_type)); +static_assert(offsetof(SymCurrentSensorInput, angle_measurement_type) == offsetof(GenericCurrentSensorInput, angle_measurement_type)); +static_assert(offsetof(SymCurrentSensorInput, i_sigma) == offsetof(GenericCurrentSensorInput, i_sigma)); +static_assert(offsetof(SymCurrentSensorInput, i_angle_sigma) == offsetof(GenericCurrentSensorInput, i_angle_sigma)); +// static asserts for AsymCurrentSensorInput +static_assert(std::is_standard_layout_v); +// static asserts for conversion of AsymCurrentSensorInput to BaseInput +static_assert(std::alignment_of_v >= std::alignment_of_v); +static_assert(std::same_as); +static_assert(offsetof(AsymCurrentSensorInput, id) == offsetof(BaseInput, id)); +// static asserts for conversion of AsymCurrentSensorInput to SensorInput +static_assert(std::alignment_of_v >= std::alignment_of_v); +static_assert(std::same_as); +static_assert(std::same_as); +static_assert(offsetof(AsymCurrentSensorInput, id) == offsetof(SensorInput, id)); +static_assert(offsetof(AsymCurrentSensorInput, measured_object) == offsetof(SensorInput, measured_object)); +// static asserts for conversion of AsymCurrentSensorInput to GenericCurrentSensorInput +static_assert(std::alignment_of_v >= std::alignment_of_v); +static_assert(std::same_as); +static_assert(std::same_as); +static_assert(std::same_as); +static_assert(std::same_as); +static_assert(std::same_as); +static_assert(std::same_as); +static_assert(offsetof(AsymCurrentSensorInput, id) == offsetof(GenericCurrentSensorInput, id)); +static_assert(offsetof(AsymCurrentSensorInput, measured_object) == offsetof(GenericCurrentSensorInput, measured_object)); +static_assert(offsetof(AsymCurrentSensorInput, measured_terminal_type) == offsetof(GenericCurrentSensorInput, measured_terminal_type)); +static_assert(offsetof(AsymCurrentSensorInput, angle_measurement_type) == offsetof(GenericCurrentSensorInput, angle_measurement_type)); +static_assert(offsetof(AsymCurrentSensorInput, i_sigma) == offsetof(GenericCurrentSensorInput, i_sigma)); +static_assert(offsetof(AsymCurrentSensorInput, i_angle_sigma) == offsetof(GenericCurrentSensorInput, i_angle_sigma)); + } // namespace power_grid_model::test diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/auxiliary/static_asserts/output.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/auxiliary/static_asserts/output.hpp index 54a5d5f55..9de483618 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/auxiliary/static_asserts/output.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/auxiliary/static_asserts/output.hpp @@ -295,6 +295,39 @@ static_assert(std::same_as +static_assert(std::is_standard_layout_v>); +// static asserts for conversion of CurrentSensorOutput to BaseOutput +static_assert(std::alignment_of_v> >= std::alignment_of_v); +static_assert(std::same_as::id), decltype(BaseOutput::id)>); +static_assert(std::same_as::energized), decltype(BaseOutput::energized)>); +static_assert(offsetof(CurrentSensorOutput, id) == offsetof(BaseOutput, id)); +static_assert(offsetof(CurrentSensorOutput, energized) == offsetof(BaseOutput, energized)); +// static asserts for CurrentSensorOutput +static_assert(std::is_standard_layout_v>); +// static asserts for conversion of CurrentSensorOutput to BaseOutput +static_assert(std::alignment_of_v> >= std::alignment_of_v); +static_assert(std::same_as::id), decltype(BaseOutput::id)>); +static_assert(std::same_as::energized), decltype(BaseOutput::energized)>); +static_assert(offsetof(CurrentSensorOutput, id) == offsetof(BaseOutput, id)); +static_assert(offsetof(CurrentSensorOutput, energized) == offsetof(BaseOutput, energized)); +// static asserts for SymCurrentSensorOutput +static_assert(std::is_standard_layout_v); +// static asserts for conversion of SymCurrentSensorOutput to BaseOutput +static_assert(std::alignment_of_v >= std::alignment_of_v); +static_assert(std::same_as); +static_assert(std::same_as); +static_assert(offsetof(SymCurrentSensorOutput, id) == offsetof(BaseOutput, id)); +static_assert(offsetof(SymCurrentSensorOutput, energized) == offsetof(BaseOutput, energized)); +// static asserts for AsymCurrentSensorOutput +static_assert(std::is_standard_layout_v); +// static asserts for conversion of AsymCurrentSensorOutput to BaseOutput +static_assert(std::alignment_of_v >= std::alignment_of_v); +static_assert(std::same_as); +static_assert(std::same_as); +static_assert(offsetof(AsymCurrentSensorOutput, id) == offsetof(BaseOutput, id)); +static_assert(offsetof(AsymCurrentSensorOutput, energized) == offsetof(BaseOutput, energized)); + } // namespace power_grid_model::test diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/auxiliary/static_asserts/update.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/auxiliary/static_asserts/update.hpp index dc244f783..7579606b2 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/auxiliary/static_asserts/update.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/auxiliary/static_asserts/update.hpp @@ -221,6 +221,31 @@ static_assert(std::same_as +static_assert(std::is_standard_layout_v>); +// static asserts for conversion of CurrentSensorUpdate to BaseUpdate +static_assert(std::alignment_of_v> >= std::alignment_of_v); +static_assert(std::same_as::id), decltype(BaseUpdate::id)>); +static_assert(offsetof(CurrentSensorUpdate, id) == offsetof(BaseUpdate, id)); +// static asserts for CurrentSensorUpdate +static_assert(std::is_standard_layout_v>); +// static asserts for conversion of CurrentSensorUpdate to BaseUpdate +static_assert(std::alignment_of_v> >= std::alignment_of_v); +static_assert(std::same_as::id), decltype(BaseUpdate::id)>); +static_assert(offsetof(CurrentSensorUpdate, id) == offsetof(BaseUpdate, id)); +// static asserts for SymCurrentSensorUpdate +static_assert(std::is_standard_layout_v); +// static asserts for conversion of SymCurrentSensorUpdate to BaseUpdate +static_assert(std::alignment_of_v >= std::alignment_of_v); +static_assert(std::same_as); +static_assert(offsetof(SymCurrentSensorUpdate, id) == offsetof(BaseUpdate, id)); +// static asserts for AsymCurrentSensorUpdate +static_assert(std::is_standard_layout_v); +// static asserts for conversion of AsymCurrentSensorUpdate to BaseUpdate +static_assert(std::alignment_of_v >= std::alignment_of_v); +static_assert(std::same_as); +static_assert(offsetof(AsymCurrentSensorUpdate, id) == offsetof(BaseUpdate, id)); + } // namespace power_grid_model::test diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/auxiliary/update.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/auxiliary/update.hpp index d5dba7a67..a30b69fba 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/auxiliary/update.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/auxiliary/update.hpp @@ -209,6 +209,24 @@ struct TransformerTapRegulatorUpdate { operator RegulatorUpdate const&() const { return reinterpret_cast(*this); } }; +template +struct CurrentSensorUpdate { + using sym = sym_type; + + ID id{na_IntID}; // ID of the object + double i_sigma{nan}; // sigma of error margin of current (angle) measurement + double i_angle_sigma{nan}; // sigma of error margin of current (angle) measurement + RealValue i_measured{nan}; // measured current and current angle + RealValue i_angle_measured{nan}; // measured current and current angle + + // implicit conversions to BaseUpdate + operator BaseUpdate&() { return reinterpret_cast(*this); } + operator BaseUpdate const&() const { return reinterpret_cast(*this); } +}; + +using SymCurrentSensorUpdate = CurrentSensorUpdate; +using AsymCurrentSensorUpdate = CurrentSensorUpdate; + } // namespace power_grid_model diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/calculation_parameters.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/calculation_parameters.hpp index 53cf13953..8e70e3f11 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/calculation_parameters.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/calculation_parameters.hpp @@ -108,6 +108,20 @@ template struct PowerSensorCalcParam { RealValue q_variance{}; // variance (sigma^2) of the error range of the reactive power, in p.u. }; +// current sensor calculation parameters for state estimation +// The value is the complex current +// * for branches, the direction is node -> branch +template struct CurrentSensorCalcParam { + using sym = sym_type; + + static constexpr bool symmetric{is_symmetric_v}; + + AngleMeasurementType angle_measurement_type{}; + ComplexValue value{}; + double i_real_variance{}; // variance (sigma^2) of the error range of real part of the current, in p.u. + double i_imag_variance{}; // variance (sigma^2) of the error range of imaginary part of the current, in p.u. +}; + template concept sensor_calc_param_type = std::derived_from> || 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 a75961c74..f8a7bd5d0 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 @@ -126,4 +126,9 @@ enum class SearchMethod : IntS { // Which type of tap search method for finite e binary_search = 1, // use binary search: half a tap range at a time }; +enum class AngleMeasurementType : IntS { // The type of the angle measurement for current sensors + local = 0, // local = 0, the angle is relative to the local voltage angle + global = 1, // global = 1, the angle is relative to the global voltage angle +}; + } // namespace power_grid_model diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/common/exception.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/common/exception.hpp index c97c84e93..a995401cf 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/common/exception.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/common/exception.hpp @@ -163,6 +163,14 @@ class InvalidMeasuredObject : public PowerGridError { } }; +class InvalidMeasuredTerminalType : public PowerGridError { + public: + InvalidMeasuredTerminalType(MeasuredTerminalType const terminal_type, std::string const& sensor) { + append_msg(sensor + " measurement is not supported for object of type " + + detail::to_string(static_cast(terminal_type))); + } +}; + class InvalidRegulatedObject : public PowerGridError { public: InvalidRegulatedObject(std::string const& object, std::string const& regulator) { diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/component/current_sensor.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/component/current_sensor.hpp new file mode 100644 index 000000000..cbfd1ac8c --- /dev/null +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/component/current_sensor.hpp @@ -0,0 +1,162 @@ +// SPDX-FileCopyrightText: Contributors to the Power Grid Model project +// +// SPDX-License-Identifier: MPL-2.0 + +#pragma once + +#include "sensor.hpp" + +#include "../calculation_parameters.hpp" +#include "../common/common.hpp" +#include "../common/enum.hpp" +#include "../common/exception.hpp" + +namespace power_grid_model { + +class GenericCurrentSensor : public Sensor { + public: + static constexpr char const* name = "generic_current_sensor"; + + explicit GenericCurrentSensor(GenericCurrentSensorInput const& generic_current_sensor_input) + : Sensor{generic_current_sensor_input}, + terminal_type_{generic_current_sensor_input.measured_terminal_type}, + angle_measurement_type_{generic_current_sensor_input.angle_measurement_type} {} + + MeasuredTerminalType get_terminal_type() const { return terminal_type_; } + AngleMeasurementType get_angle_measurement_type() const { return angle_measurement_type_; } + + template CurrentSensorOutput get_output(ComplexValue const& i) const { + if constexpr (is_symmetric_v) { + return get_sym_output(i); + } else { + return get_asym_output(i); + } + } + + template CurrentSensorOutput get_null_output() const { + return {.id = id(), .energized = false, .i_residual = {}, .i_angle_residual = {}}; + } + + SensorShortCircuitOutput get_null_sc_output() const { return {.id = id(), .energized = 0}; } + + // getter for calculation param + template CurrentSensorCalcParam calc_param() const { + if constexpr (is_symmetric_v) { + return sym_calc_param(); + } else { + return asym_calc_param(); + } + } + + private: + MeasuredTerminalType terminal_type_; + AngleMeasurementType angle_measurement_type_; + + // virtual function getter for sym and asym param + // override them in real sensors function + virtual CurrentSensorCalcParam sym_calc_param() const = 0; + virtual CurrentSensorCalcParam asym_calc_param() const = 0; + + virtual CurrentSensorOutput get_sym_output(ComplexValue const& i) const = 0; + virtual CurrentSensorOutput get_asym_output(ComplexValue const& i) const = 0; +}; + +template class CurrentSensor : public GenericCurrentSensor { + public: + using current_sensor_symmetry = current_sensor_symmetry_; + + static constexpr char const* name = + is_symmetric_v ? "sym_current_sensor" : "asym_current_sensor"; + using InputType = CurrentSensorInput; + using UpdateType = CurrentSensorUpdate; + template using OutputType = CurrentSensorOutput; + + explicit CurrentSensor(CurrentSensorInput const& current_sensor_input, double u_rated) + : GenericCurrentSensor{current_sensor_input}, + i_angle_measured_{current_sensor_input.i_angle_measured}, + i_angle_sigma_{current_sensor_input.i_angle_sigma}, + base_current_{base_power_3p * inv_sqrt3 / u_rated}, + base_current_inv_{1.0 / base_current_} { + set_current(current_sensor_input); + + switch (current_sensor_input.measured_terminal_type) { + using enum MeasuredTerminalType; + case branch_from: + case branch_to: + case branch3_1: + case branch3_2: + case branch3_3: + break; + default: + throw InvalidMeasuredTerminalType{current_sensor_input.measured_terminal_type, "Current sensor"}; + } + }; + + UpdateChange update(CurrentSensorUpdate const& update_data) { + if (!is_nan(update_data.i_sigma)) { + i_sigma_ = update_data.i_sigma * base_current_inv_; + } + if (!is_nan(update_data.i_angle_sigma)) { + i_angle_sigma_ = update_data.i_angle_sigma; + } + update_real_value(update_data.i_measured, i_measured_, base_current_inv_); + update_real_value(update_data.i_angle_measured, i_angle_measured_, 1.0); + return {false, false}; + } + + CurrentSensorUpdate + inverse(CurrentSensorUpdate update_data) const { + assert(update_data.id == this->id() || is_nan(update_data.id)); + + set_if_not_nan(update_data.i_sigma, i_sigma_ * base_current_); + set_if_not_nan(update_data.i_angle_sigma, i_angle_sigma_); + set_if_not_nan(update_data.i_measured, i_measured_ * base_current_); + set_if_not_nan(update_data.i_angle_measured, i_angle_measured_); + + return update_data; + } + + private: + RealValue i_measured_{}; + RealValue i_angle_measured_{}; + double i_sigma_{}; + double i_angle_sigma_{}; + double base_current_{}; + double base_current_inv_{}; + + void set_current(CurrentSensorInput const& input) { + i_sigma_ = input.i_sigma * base_current_inv_; + i_measured_ = input.i_measured * base_current_inv_; + } + + // TODO when filling the functions below take in mind that i_angle_sigma is optional + + CurrentSensorCalcParam sym_calc_param() const final { + CurrentSensorCalcParam calc_param{}; + // TODO + return calc_param; + } + CurrentSensorCalcParam asym_calc_param() const final { + CurrentSensorCalcParam calc_param{}; + // TODO + return calc_param; + } + CurrentSensorOutput get_sym_output(ComplexValue const& i) const final { + return get_generic_output(i); + } + CurrentSensorOutput get_asym_output(ComplexValue const& i) const final { + return get_generic_output(i); + } + template + CurrentSensorOutput get_generic_output(ComplexValue const& i) const { + CurrentSensorOutput output{}; + // TODO + (void)i; // Suppress unused variable warning + return output; + } +}; + +using SymCurrentSensor = CurrentSensor; +using AsymCurrentSensor = CurrentSensor; + +} // namespace power_grid_model diff --git a/tests/cpp_unit_tests/CMakeLists.txt b/tests/cpp_unit_tests/CMakeLists.txt index 53bdeb029..12c4d801a 100644 --- a/tests/cpp_unit_tests/CMakeLists.txt +++ b/tests/cpp_unit_tests/CMakeLists.txt @@ -48,6 +48,7 @@ set(PROJECT_SOURCES "test_math_solver_se_newton_raphson.cpp" "test_math_solver_se_iterative_linear.cpp" "test_math_solver_sc.cpp" + "test_current_sensor.cpp" ) add_executable(power_grid_model_unit_tests ${PROJECT_SOURCES}) diff --git a/tests/cpp_unit_tests/test_current_sensor.cpp b/tests/cpp_unit_tests/test_current_sensor.cpp new file mode 100644 index 000000000..d5f0e1920 --- /dev/null +++ b/tests/cpp_unit_tests/test_current_sensor.cpp @@ -0,0 +1,235 @@ +// SPDX-FileCopyrightText: Contributors to the Power Grid Model project +// +// SPDX-License-Identifier: MPL-2.0 + +#include + +#include + +namespace power_grid_model { +namespace { +auto const r_nan = RealValue{nan}; + +void check_nan_preserving_equality(std::floating_point auto actual, std::floating_point auto expected) { + if (is_nan(expected)) { + is_nan(actual); + } else { + CHECK(actual == doctest::Approx(expected)); + } +} +void check_nan_preserving_equality(RealValue const& actual, RealValue const& expected) { + for (auto i : {0, 1, 2}) { + CAPTURE(i); + check_nan_preserving_equality(actual(i), expected(i)); + } +} +} // namespace + +TEST_CASE("Test current sensor") { + SUBCASE("Symmetric Current Sensor") { + for (auto const terminal_type : + {MeasuredTerminalType::branch_from, MeasuredTerminalType::branch_to, MeasuredTerminalType::branch3_1, + MeasuredTerminalType::branch3_2, MeasuredTerminalType::branch3_3}) { + CAPTURE(terminal_type); + + CurrentSensorInput sym_current_sensor_input{}; + sym_current_sensor_input.id = 0; + sym_current_sensor_input.measured_object = 1; + sym_current_sensor_input.measured_terminal_type = terminal_type; + sym_current_sensor_input.angle_measurement_type = AngleMeasurementType::local; + sym_current_sensor_input.i_sigma = 1.0; + sym_current_sensor_input.i_measured = 1.0 * 1e3; + sym_current_sensor_input.i_angle_measured = 0.0; + sym_current_sensor_input.i_angle_sigma = nan; + + double const u_rated = 10.0e3; + double const base_current = base_power_3p / u_rated / sqrt3; + + ComplexValue const i_sym = (1.0 * 1e3 + 1i * 0.0) / base_current; + ComplexValue const i_asym = i_sym * RealValue{1.0}; + + CurrentSensor sym_current_sensor{sym_current_sensor_input, u_rated}; + + CurrentSensorCalcParam sym_sensor_param = sym_current_sensor.calc_param(); + CurrentSensorCalcParam asym_sensor_param = sym_current_sensor.calc_param(); + + CurrentSensorOutput sym_sensor_output = sym_current_sensor.get_output(i_sym); + CurrentSensorOutput sym_sensor_output_asym_param = + sym_current_sensor.get_output(i_asym); + + // Check symmetric sensor output for symmetric parameters + CHECK(sym_sensor_param.angle_measurement_type == AngleMeasurementType::local); + CHECK(sym_sensor_param.i_real_variance == doctest::Approx(0.0)); + CHECK(sym_sensor_param.i_imag_variance == doctest::Approx(0.0)); + CHECK(real(sym_sensor_param.value) == doctest::Approx(0.0)); + CHECK(imag(sym_sensor_param.value) == doctest::Approx(0.0)); + + CHECK(is_nan(sym_sensor_output.id)); + CHECK(is_nan(sym_sensor_output.energized)); + CHECK(is_nan(sym_sensor_output.i_residual)); + CHECK(is_nan(sym_sensor_output.i_angle_residual)); + + // Check symmetric sensor output for asymmetric parameters + CHECK(asym_sensor_param.i_real_variance == doctest::Approx(0.0)); + CHECK(asym_sensor_param.i_imag_variance == doctest::Approx(0.0)); + CHECK(real(asym_sensor_param.value[0]) == doctest::Approx(0.0)); + CHECK(imag(asym_sensor_param.value[1]) == doctest::Approx(0.0)); + + CHECK(is_nan(sym_sensor_output_asym_param.id)); + CHECK(is_nan(sym_sensor_output_asym_param.energized)); + CHECK(is_nan(sym_sensor_output_asym_param.i_residual[0])); + CHECK(is_nan(sym_sensor_output_asym_param.i_angle_residual[1])); + + CHECK(sym_current_sensor.get_terminal_type() == terminal_type); + + CHECK(sym_current_sensor.get_angle_measurement_type() == AngleMeasurementType::local); + } + SUBCASE("Wrong measured terminal type") { + for (auto const terminal_type : + {MeasuredTerminalType::source, MeasuredTerminalType::shunt, MeasuredTerminalType::load, + MeasuredTerminalType::generator, MeasuredTerminalType::node}) { + CHECK_THROWS_AS((CurrentSensor{ + {1, 1, terminal_type, AngleMeasurementType::local, 1.0, 1.0, 1.0, 1.0}, 1.0}), + InvalidMeasuredTerminalType); + } + } + } + SUBCASE("Update inverse - sym") { + constexpr auto i_measured = 1.0; + constexpr auto i_angle_measured = 2.0; + constexpr auto i_sigma = 3.0; + constexpr auto i_angle_sigma = 4.0; + constexpr auto u_rated = 10.0e3; + CurrentSensor const current_sensor{{1, 1, MeasuredTerminalType::branch3_1, + AngleMeasurementType::local, i_sigma, i_angle_sigma, + i_measured, i_angle_measured}, + u_rated}; + + CurrentSensorUpdate cs_update{1, nan, nan, nan, nan}; + auto expected = cs_update; + + SUBCASE("Identical") { + // default values + } + + SUBCASE("i_sigma") { + SUBCASE("same") { cs_update.i_sigma = i_sigma; } + SUBCASE("different") { cs_update.i_sigma = 0.0; } + expected.i_sigma = i_sigma; + } + + SUBCASE("i_angle_sigma") { + SUBCASE("same") { cs_update.i_angle_sigma = i_angle_sigma; } + SUBCASE("different") { cs_update.i_angle_sigma = 0.0; } + expected.i_angle_sigma = i_angle_sigma; + } + + SUBCASE("i_measured") { + SUBCASE("same") { cs_update.i_measured = i_measured; } + SUBCASE("different") { cs_update.i_measured = 0.0; } + expected.i_measured = i_measured; + } + + SUBCASE("i_angle_measured") { + SUBCASE("same") { cs_update.i_angle_measured = i_angle_measured; } + SUBCASE("different") { cs_update.i_angle_measured = 0.0; } + expected.i_angle_measured = i_angle_measured; + } + + SUBCASE("multiple") { + cs_update.i_sigma = 0.0; + cs_update.i_angle_sigma = 0.0; + cs_update.i_measured = 0.0; + cs_update.i_angle_measured = 0.0; + expected.i_sigma = i_sigma; + expected.i_angle_sigma = i_angle_sigma; + expected.i_measured = i_measured; + expected.i_angle_measured = i_angle_measured; + } + + auto const inv = current_sensor.inverse(cs_update); + + CHECK(inv.id == expected.id); + check_nan_preserving_equality(inv.i_sigma, expected.i_sigma); + check_nan_preserving_equality(inv.i_angle_sigma, expected.i_angle_sigma); + check_nan_preserving_equality(inv.i_measured, expected.i_measured); + check_nan_preserving_equality(inv.i_angle_measured, expected.i_angle_measured); + } + + SUBCASE("Update inverse - asym") { + RealValue i_measured = {1.0, 2.0, 3.0}; + RealValue i_angle_measured = {4.0, 5.0, 6.0}; + constexpr auto i_sigma = 3.0; + constexpr auto i_angle_sigma = 4.0; + constexpr auto u_rated = 10.0e3; + MeasuredTerminalType const measured_terminal_type = MeasuredTerminalType::branch_from; + + CurrentSensorUpdate cs_update{1, nan, nan, r_nan, r_nan}; + auto expected = cs_update; + + SUBCASE("Identical") { + // default values + } + + SUBCASE("i_sigma") { + SUBCASE("same") { cs_update.i_sigma = i_sigma; } + SUBCASE("different") { cs_update.i_sigma = 0.0; } + expected.i_sigma = i_sigma; + } + + SUBCASE("i_angle_sigma") { + SUBCASE("same") { cs_update.i_angle_sigma = i_angle_sigma; } + SUBCASE("different") { cs_update.i_angle_sigma = 0.0; } + expected.i_angle_sigma = i_angle_sigma; + } + + SUBCASE("i_measured") { + SUBCASE("same") { cs_update.i_measured = i_measured; } + SUBCASE("1 different") { + cs_update.i_measured = {0.0, nan, nan}; + expected.i_measured = {i_measured(0), nan, nan}; + } + SUBCASE("all different") { + cs_update.i_measured = {0.0, 0.1, 0.2}; + expected.i_measured = i_measured; + } + } + + SUBCASE("i_angle_measured") { + SUBCASE("same") { cs_update.i_angle_measured = i_angle_measured; } + SUBCASE("1 different") { + cs_update.i_angle_measured = {0.0, nan, nan}; + expected.i_angle_measured = {i_angle_measured(0), nan, nan}; + } + SUBCASE("all different") { + cs_update.i_angle_measured = {0.0, 0.1, 0.2}; + expected.i_angle_measured = i_angle_measured; + } + } + + SUBCASE("multiple") { + cs_update.i_sigma = 0.0; + cs_update.i_angle_sigma = 0.1; + cs_update.i_measured = {0.0, 0.2, 0.4}; + cs_update.i_angle_measured = {0.0, 0.3, 0.6}; + expected.i_sigma = i_sigma; + expected.i_angle_sigma = i_angle_sigma; + expected.i_measured = i_measured; + expected.i_angle_measured = i_angle_measured; + } + + CurrentSensor const current_sensor{{1, 1, measured_terminal_type, AngleMeasurementType::local, + i_sigma, i_angle_sigma, i_measured, i_angle_measured}, + u_rated}; + + auto const inv = current_sensor.inverse(cs_update); + + CHECK(inv.id == expected.id); + check_nan_preserving_equality(inv.i_sigma, expected.i_sigma); + check_nan_preserving_equality(inv.i_angle_sigma, expected.i_angle_sigma); + check_nan_preserving_equality(inv.i_measured, expected.i_measured); + check_nan_preserving_equality(inv.i_angle_measured, expected.i_angle_measured); + } +} + +} // namespace power_grid_model