Skip to content

Commit

Permalink
Merge pull request #620 from PowerGridModel/feature/basic-validation-…
Browse files Browse the repository at this point in the history
…check-tap-regulator

[FEATURE] basic validation checks for tap regulator
  • Loading branch information
mgovers authored Jun 6, 2024
2 parents 4473582 + 7676a08 commit 7ec3f41
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 8 deletions.
9 changes: 6 additions & 3 deletions src/power_grid_model/validation/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ def field_str(self) -> str:

class MultiComponentValidationError(ValidationError):
"""
Base class for an error that applies to multiple component, and as a consequence also to multiple fields.
Base class for an error that applies to multiple components, and, subsequently, multiple fields.
Even if both fields have the same name, they are considered to be different fields and notated as such.
E.g. the two fields `id` fields of the `node` and `line` component: [('node', 'id'), ('line', 'id')].
"""
Expand Down Expand Up @@ -237,9 +237,9 @@ class InvalidEnumValueError(SingleFieldValidationError):
"""

_message = "Field {field} contains invalid {enum} values for {n} {objects}."
enum: Type[Enum]
enum: Union[Type[Enum], List[Type[Enum]]]

def __init__(self, component: str, field: str, ids: List[int], enum: Type[Enum]):
def __init__(self, component: str, field: str, ids: List[int], enum: Union[Type[Enum], List[Type[Enum]]]):
super().__init__(component, field, ids)
self.enum = enum

Expand All @@ -248,6 +248,9 @@ def enum_str(self) -> str:
"""
A string representation of the field to which this error applies.
"""
if isinstance(self.enum, list):
return ",".join(e.__name__ for e in self.enum)

return self.enum.__name__

def __eq__(self, other):
Expand Down
13 changes: 9 additions & 4 deletions src/power_grid_model/validation/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,7 @@ def all_cross_unique(


def all_valid_enum_values(
data: SingleDataset, component: str, field: str, enum: Type[Enum]
data: SingleDataset, component: str, field: str, enum: Union[Type[Enum], List[Type[Enum]]]
) -> List[InvalidEnumValueError]:
"""
Check that for all records of a particular type of component, the values in the 'field' column are valid values for
Expand All @@ -481,14 +481,19 @@ def all_valid_enum_values(
data (SingleDataset): The input/update data set for all components
component (str): The component of interest
field (str): The field of interest
enum (Type[Enum]): The enum type to validate against
enum (Type[Enum]): The enum type to validate against, or a list of such enum types
Returns:
A list containing zero or one InvalidEnumValueError, listing all ids where the value in the field of interest
was not a valid value in the supplied enum type.
"""
valid = [nan_type(component, field)] + list(enum)
invalid = np.isin(data[component][field], np.array(valid, dtype=np.int8), invert=True)
enums: List[Type[Enum]] = enum if isinstance(enum, list) else [enum]

valid = {nan_type(component, field)}
for enum_type in enums:
valid.update(list(enum_type))

invalid = np.isin(data[component][field], np.array(list(valid), dtype=np.int8), invert=True)
if invalid.any():
ids = data[component]["id"][invalid].flatten().tolist()
return [InvalidEnumValueError(component, field, ids, enum)]
Expand Down
32 changes: 32 additions & 0 deletions src/power_grid_model/validation/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,13 @@ def validate_required_values(
"tap_max",
"tap_size",
]

# Regulators
required["regulator"] = required["base"] + ["regulated_object", "status"]
required["transformer_tap_regulator"] = required["regulator"]
if calculation_type is None or calculation_type == CalculationType.power_flow:
required["transformer_tap_regulator"] += ["control_side", "u_set", "u_band"]

# Appliances
required["appliance"] = required["base"] + ["node", "status"]
required["source"] = required["appliance"].copy()
Expand Down Expand Up @@ -476,6 +483,9 @@ def validate_values(data: SingleDataset, calculation_type: Optional[CalculationT
if calculation_type in (None, CalculationType.short_circuit) and "fault" in data:
errors += validate_fault(data)

if calculation_type in (None, CalculationType.power_flow) and "transformer_tap_regulator" in data:
errors += validate_transformer_tap_regulator(data)

return errors


Expand Down Expand Up @@ -806,3 +816,25 @@ def validate_fault(data: SingleDataset) -> List[ValidationError]:
errors += all_enabled_identical(data, "fault", "fault_type", "status")
errors += all_enabled_identical(data, "fault", "fault_phase", "status")
return errors


def validate_regulator(data: SingleDataset, component: str) -> List[ValidationError]:
errors = validate_base(data, component)
errors += all_valid_ids(
data,
component,
field="regulated_object",
ref_components=["transformer", "three_winding_transformer"],
)
return errors


def validate_transformer_tap_regulator(data: SingleDataset) -> List[ValidationError]:
errors = validate_regulator(data, "transformer_tap_regulator")
errors += all_boolean(data, "transformer_tap_regulator", "status")
errors += all_valid_enum_values(data, "transformer_tap_regulator", "control_side", [BranchSide, Branch3Side])
errors += all_greater_than_or_equal_to_zero(data, "transformer_tap_regulator", "u_set")
errors += all_greater_than_zero(data, "transformer_tap_regulator", "u_band")
errors += all_greater_than_or_equal_to_zero(data, "transformer_tap_regulator", "line_drop_compensation_r", 0.0)
errors += all_greater_than_or_equal_to_zero(data, "transformer_tap_regulator", "line_drop_compensation_x", 0.0)
return errors
38 changes: 38 additions & 0 deletions tests/unit/validation/test_input_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,16 @@ def input_data() -> Dict[str, np.ndarray]:
three_winding_transformer["pk_13_max"] = [-40, nan_type("three_winding_transformer", "pk_12_max"), 40, 50]
three_winding_transformer["pk_23_max"] = [-120, nan_type("three_winding_transformer", "pk_12_max"), 40, 30]

transformer_tap_regulator = initialize_array("input", "transformer_tap_regulator", 4)
transformer_tap_regulator["id"] = [51, 52, 53, 1]
transformer_tap_regulator["status"] = [0, -1, 2, 5]
transformer_tap_regulator["regulated_object"] = [14, 15, 28, 2]
transformer_tap_regulator["control_side"] = [1, 2, 0, 60]
transformer_tap_regulator["u_set"] = [100, -100, 100, 100]
transformer_tap_regulator["u_band"] = [100, -4, 100, 0]
transformer_tap_regulator["line_drop_compensation_r"] = [0.0, -1.0, 1.0, 2.0]
transformer_tap_regulator["line_drop_compensation_x"] = [0.0, 4.0, 2.0, -4.0]

source = initialize_array("input", "source", 3)
source["id"] = [16, 17, 1]
source["node"] = [10, 1, 2]
Expand Down Expand Up @@ -238,6 +248,7 @@ def input_data() -> Dict[str, np.ndarray]:
"link": link,
"transformer": transformer,
"three_winding_transformer": three_winding_transformer,
"transformer_tap_regulator": transformer_tap_regulator,
"source": source,
"shunt": shunt,
"sym_load": sym_load,
Expand Down Expand Up @@ -273,6 +284,7 @@ def test_validate_input_data_sym_calculation(input_data):
("transformer", "id"),
("three_winding_transformer", "id"),
("fault", "id"),
("transformer_tap_regulator", "id"),
],
[
("asym_gen", 1),
Expand Down Expand Up @@ -301,6 +313,7 @@ def test_validate_input_data_sym_calculation(input_data):
("transformer", 1),
("three_winding_transformer", 1),
("fault", 1),
("transformer_tap_regulator", 1),
],
)
in validation_errors
Expand Down Expand Up @@ -550,6 +563,31 @@ def test_validate_three_winding_transformer_ukpkminmax(input_data):
assert NotGreaterOrEqualError("three_winding_transformer", "pk_23_max", [1], 0) in validation_errors


def test_validate_input_data_transformer_tap_regulator(input_data):
validation_errors = validate_input_data(input_data, calculation_type=CalculationType.power_flow)
assert NotBooleanError("transformer_tap_regulator", "status", [52, 1, 53]) in validation_errors
assert (
InvalidIdError(
"transformer_tap_regulator", "regulated_object", [1], ["transformer", "three_winding_transformer"]
)
in validation_errors
)
assert (
InvalidEnumValueError("transformer_tap_regulator", "control_side", [1], [BranchSide, Branch3Side])
in validation_errors
)
# TODO (nbharambe) Add control side error after it is included
# assert InvalidControlSideError("transformer_tap_regulator", "control_side", [52], BranchSide) in validation_errors
assert NotGreaterOrEqualError("transformer_tap_regulator", "u_set", [52], 0.0) in validation_errors
assert NotGreaterThanError("transformer_tap_regulator", "u_band", [52, 1], 0.0) in validation_errors
assert (
NotGreaterOrEqualError("transformer_tap_regulator", "line_drop_compensation_r", [52], 0.0) in validation_errors
)
assert (
NotGreaterOrEqualError("transformer_tap_regulator", "line_drop_compensation_x", [1], 0.0) in validation_errors
)


def test_fault(input_data):
validation_errors = validate_input_data(input_data, calculation_type=CalculationType.short_circuit)
assert InvalidEnumValueError("fault", "fault_type", [50], FaultType) in validation_errors
Expand Down
11 changes: 10 additions & 1 deletion tests/unit/validation/test_rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import pytest

from power_grid_model import LoadGenType, initialize_array
from power_grid_model.enum import FaultPhase, FaultType
from power_grid_model.enum import Branch3Side, BranchSide, FaultPhase, FaultType
from power_grid_model.validation.errors import (
ComparisonError,
FaultPhaseError,
Expand Down Expand Up @@ -329,6 +329,15 @@ def test_all_valid_enum_values():
errors = all_valid_enum_values(valid, "sym_load", "type", LoadGenType)
assert not errors

valid = {"transformer_tap_regulator": initialize_array("input", "transformer_tap_regulator", 5)}
valid["transformer_tap_regulator"]["id"] = np.arange(5)
valid["transformer_tap_regulator"]["control_side"] = np.arange(-1, 4)
errors = all_valid_enum_values(valid, "transformer_tap_regulator", "control_side", [BranchSide, Branch3Side])
assert len(errors) == 1
assert (
InvalidEnumValueError("transformer_tap_regulator", "control_side", [0, 4], [BranchSide, Branch3Side]) in errors
)


def test_all_valid_ids():
# This data is for testing purpuse
Expand Down

0 comments on commit 7ec3f41

Please sign in to comment.