Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature addition to check if p_sigma and q_sigma co-(in)exist. #425

Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
b8a430e
Feature addition to check if `p_sigma` and `q_sigma` co-(in)exist.
Jerry-Jinfeng-Guo Nov 10, 2023
d3c6b7d
minor edit, add both p_sigma and q_sigma when at least one exists
Jerry-Jinfeng-Guo Nov 10, 2023
c576856
Fix broken rule
Jerry-Jinfeng-Guo Nov 13, 2023
2772901
Updated some function name; changed the logic by screening field to p…
Jerry-Jinfeng-Guo Nov 14, 2023
e61bb9f
patch ^
Jerry-Jinfeng-Guo Nov 14, 2023
da451f4
Rewritten the function to validate the value, instead of via `required`
Jerry-Jinfeng-Guo Nov 15, 2023
c59619b
Pylint pleasing
Jerry-Jinfeng-Guo Nov 15, 2023
f58d91c
Reformatted
Jerry-Jinfeng-Guo Nov 15, 2023
efb8dd4
Added a test case for bad `p_sigma` and `q_sigma`
Jerry-Jinfeng-Guo Nov 16, 2023
b5d4cb6
Fixed random dataset to arbitrary set; fixed a few typos; fixed/simpl…
Jerry-Jinfeng-Guo Nov 17, 2023
3e19796
Merge branch 'main' into feature/DGC-2051-data-validation-routine-in-…
mgovers Nov 20, 2023
7e6a23b
Fixed the logic to include partial fill in the `(p_sigma, q_sigma)` p…
Jerry-Jinfeng-Guo Nov 21, 2023
ed8e975
Merge branch 'feature/DGC-2051-data-validation-routine-in-python' of …
Jerry-Jinfeng-Guo Nov 21, 2023
5e76627
Updated the validation test to check error id - component id match;
Jerry-Jinfeng-Guo Nov 21, 2023
afe4620
Updated some logic part with `any(axis=-1)`, addressing `asym_power_s…
Jerry-Jinfeng-Guo Nov 22, 2023
9e3b605
Added test case for single component but twice (the other 2-component…
Jerry-Jinfeng-Guo Nov 22, 2023
de49617
Final touch; code cleaning; function simplification
Jerry-Jinfeng-Guo Nov 24, 2023
5a668a0
Merge branch 'main' into feature/DGC-2051-data-validation-routine-in-…
mgovers Nov 24, 2023
0c58dee
Merge branch 'main' into feature/DGC-2051-data-validation-routine-in-…
Jerry-Jinfeng-Guo Nov 24, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

repos:
- repo: https://github.com/fsfe/reuse-tool
rev: v1.0.0
rev: v2.1.0
hooks:
- id: reuse
- repo: https://github.com/pycqa/isort
Expand Down
2 changes: 1 addition & 1 deletion docs/user_manual/components.md
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,7 @@ Because of this distribution, at least one appliance is required to be connected
| name | data type | unit | description | required | update | valid values |
| ------------------------ | ----------------------------------------------------------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------: | :------: | :--------------------------------------------------: |
| `measured_terminal_type` | {py:class}`MeasuredTerminalType <power_grid_model.enum.MeasuredTerminalType>` | - | indicate if it measures an `appliance` or a `branch` | &#10004; | &#10060; | the terminal type should match the `measured_object` |
| `power_sigma` | `double` | volt-ampere (VA) | standard deviation of the measurement error. Usually this is the absolute measurement error range divided by 3. See {hoverxreftooltip}`user_manual/components:Power Sensor Complete Types`. | &#10024; only for state estimation. | &#10004; | `> 0` |
| `power_sigma` | `double` | volt-ampere (VA) | standard deviation of the measurement error. Usually this is the absolute measurement error range divided by 3. See {hoverxreftooltip}`user_manual/components:Power Sensor Concrete Types`. | &#10024; only for state estimation. | &#10004; | `> 0` |
mgovers marked this conversation as resolved.
Show resolved Hide resolved

#### Power Sensor Concrete Types

Expand Down
33 changes: 33 additions & 0 deletions src/power_grid_model/validation/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
InvalidIdError,
MissingValueError,
MultiComponentNotUniqueError,
MultiFieldValidationError,
NotBetweenError,
NotBetweenOrAtError,
NotBooleanError,
Expand Down Expand Up @@ -697,6 +698,38 @@ def none_missing(data: SingleDataset, component: str, fields: Union[str, List[st
return errors


def valid_p_q_sigma(data: SingleDataset, component: str) -> List[MultiFieldValidationError]:
"""
Check validity of the pair `(p_sigma, q_sigma)` for 'sym_power_sensor' and 'asym_power_sensor'.

Args:
data: The input/update data set for all components
component: The component of interest, in this case only 'sym_power_sensor' or 'asym_power_sensor'

Returns:
A list containing zero or one MultiFieldValidationError, listing the p_sigma and q_sigma mismatch.
Note that with asymetric power sensors, partial assignment of p_sigma and q_sigma is also considered mismatch.
"""
errors = []
p_sigma = data[component]["p_sigma"]
q_sigma = data[component]["q_sigma"]
p_nan = np.isnan(p_sigma)
q_nan = np.isnan(q_sigma)
p_inf = np.isinf(p_sigma)
q_inf = np.isinf(q_sigma)
if p_sigma.ndim > 1: # if component == 'asym_power_sensor':
p_nan = p_nan.any(axis=-1)
q_nan = q_nan.any(axis=-1)
p_inf = p_inf.any(axis=-1)
q_inf = q_inf.any(axis=-1)
mis_match = p_nan != q_nan
mis_match |= p_inf.any() or q_inf.any()
Jerry-Jinfeng-Guo marked this conversation as resolved.
Show resolved Hide resolved
if mis_match.any():
ids = [data[component]["id"].flatten().tolist()]
Jerry-Jinfeng-Guo marked this conversation as resolved.
Show resolved Hide resolved
errors.append(MultiFieldValidationError(component, ["p_sigma", "q_sigma"], ids))
return errors


def all_valid_clocks(
data: SingleDataset, component: str, clock_field: str, winding_from_field: str, winding_to_field: str
) -> List[TransformerClockError]:
Expand Down
3 changes: 3 additions & 0 deletions src/power_grid_model/validation/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
all_valid_fault_phases,
all_valid_ids,
none_missing,
valid_p_q_sigma,
)
from power_grid_model.validation.utils import update_input_data

Expand Down Expand Up @@ -708,6 +709,8 @@ def validate_generic_power_sensor(data: SingleDataset, component: str) -> List[V
ref_components="node",
measured_terminal_type=MeasuredTerminalType.node,
)
if component in ("sym_power_sensor", "asym_power_sensor"):
errors += valid_p_q_sigma(data, component)

return errors

Expand Down
137 changes: 137 additions & 0 deletions tests/unit/validation/test_validation_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@
from power_grid_model.validation.errors import (
IdNotInDatasetError,
InfinityError,
InvalidIdError,
MissingValueError,
MultiComponentNotUniqueError,
MultiFieldValidationError,
NotUniqueError,
)
from power_grid_model.validation.validation import (
assert_valid_data_structure,
Expand Down Expand Up @@ -431,6 +434,140 @@ def test_validate_values__infinite_sigmas(sensor_type, parameter):
assert not isinstance(error, InfinityError)


@pytest.mark.parametrize(
("sensor_type", "parameter", "values", "error_types"),
[
("sym_power_sensor", ["p_sigma", "q_sigma"], [NaN, NaN], [InvalidIdError, NotUniqueError]),
(
"sym_power_sensor",
["p_sigma", "q_sigma"],
[0.1, NaN],
[InvalidIdError, NotUniqueError, MultiFieldValidationError],
),
(
"sym_power_sensor",
["p_sigma", "q_sigma"],
[NaN, 0.1],
[InvalidIdError, NotUniqueError, MultiFieldValidationError],
),
("sym_power_sensor", ["p_sigma", "q_sigma"], [0.1, 0.1], [InvalidIdError, NotUniqueError]),
(
"asym_power_sensor",
["p_sigma", "q_sigma"],
[[NaN, NaN, NaN], [NaN, NaN, NaN]],
[InvalidIdError, NotUniqueError],
),
(
"asym_power_sensor",
["p_sigma", "q_sigma"],
[[0.1, NaN, 0.1], [NaN, 0.1, NaN]],
[InvalidIdError, NotUniqueError, MultiFieldValidationError],
),
(
"asym_power_sensor",
["p_sigma", "q_sigma"],
[[0.1, NaN, NaN], [NaN, NaN, NaN]],
[InvalidIdError, NotUniqueError, MultiFieldValidationError],
),
(
"asym_power_sensor",
["p_sigma", "q_sigma"],
[[NaN, NaN, NaN], [0.1, NaN, NaN]],
[InvalidIdError, NotUniqueError, MultiFieldValidationError],
),
(
"asym_power_sensor",
["p_sigma", "q_sigma"],
[[0.1, 0.1, 0.1], [NaN, NaN, NaN]],
[InvalidIdError, NotUniqueError, MultiFieldValidationError],
),
(
"asym_power_sensor",
["p_sigma", "q_sigma"],
[[NaN, NaN, NaN], [0.1, 0.1, 0.1]],
[InvalidIdError, NotUniqueError, MultiFieldValidationError],
),
(
"asym_power_sensor",
["p_sigma", "q_sigma"],
[[NaN, NaN, NaN], [0.1, NaN, NaN]],
[InvalidIdError, NotUniqueError, MultiFieldValidationError],
),
(
"asym_power_sensor",
["p_sigma", "q_sigma"],
[[0.1, NaN, NaN], [0.1, NaN, NaN]],
[InvalidIdError, NotUniqueError, MultiFieldValidationError],
),
(
"asym_power_sensor",
["p_sigma", "q_sigma"],
[[0.1, 0.1, 0.1], [0.1, 0.1, 0.1]],
[InvalidIdError, NotUniqueError],
),
],
)
def test_validate_values__bad_p_q_sigma(sensor_type, parameter, values, error_types):
Jerry-Jinfeng-Guo marked this conversation as resolved.
Show resolved Hide resolved
def arbitrary_fill(array, sensor_type, parameter, values):
if sensor_type == "sym_power_sensor":
array[parameter[0]] = values[0]
array[parameter[1]] = values[1]
else:
array[parameter[0]][0][0] = values[0][0]
array[parameter[0]][0][1] = values[0][1]
array[parameter[0]][0][2] = values[0][2]
array[parameter[1]][0][0] = values[1][0]
array[parameter[1]][0][1] = values[1][1]
array[parameter[1]][0][2] = values[1][2]

sensor_array = initialize_array("input", sensor_type, 3)
arbitrary_fill(sensor_array, sensor_type, parameter, values)
all_errors = validate_values({sensor_type: sensor_array})

for error in all_errors:
assert any(isinstance(error, error_type) for error_type in error_types)
assert sensor_array["id"][0] == error.ids[0]


@pytest.mark.parametrize(
("values", "error_types"),
[
([[NaN, NaN], [[NaN, NaN, NaN], [NaN, NaN, NaN]]], [InvalidIdError]),
([[0.1, NaN], [[NaN, NaN, NaN], [NaN, NaN, NaN]]], [InvalidIdError, MultiFieldValidationError]),
([[NaN, NaN], [[NaN, 0.1, NaN], [NaN, NaN, NaN]]], [InvalidIdError, MultiFieldValidationError]),
([[NaN, NaN], [[NaN, 0.1, NaN], [NaN, 0.1, NaN]]], [InvalidIdError, MultiFieldValidationError]),
([[0.1, 0.1], [[NaN, NaN, NaN], [NaN, NaN, NaN]]], [InvalidIdError, MultiFieldValidationError]),
([[0.1, 0.1], [[NaN, NaN, NaN], [NaN, NaN, NaN]]], [InvalidIdError]),
([[NaN, NaN], [[0.1, 0.1, 0.1], [0.1, 0.1, 0.1]]], [InvalidIdError]),
([[0.1, 0.1], [[0.1, 0.1, 0.1], [0.1, 0.1, 0.1]]], [InvalidIdError]),
],
)
def test_validate_values__bad_p_q_sigma_both_components(values, error_types):
def two_component_data(values):
node = initialize_array("input", "node", 1)
node["id"] = 123
sym_power_sensor = initialize_array("input", "sym_power_sensor", 1)
sym_power_sensor["p_sigma"] = values[0][0]
sym_power_sensor["q_sigma"] = values[0][1]
sym_power_sensor["id"] = 456
asym_power_sensor = initialize_array("input", "asym_power_sensor", 1)
asym_power_sensor["p_measured"] = values[1][0]
asym_power_sensor["q_measured"] = values[1][1]
asym_power_sensor["id"] = 789
mgovers marked this conversation as resolved.
Show resolved Hide resolved

return {
"node": node,
"sym_power_sensor": sym_power_sensor,
"asym_power_sensor": asym_power_sensor,
}

data = two_component_data(values)
all_errors = validate_values(data)
for error in all_errors:
assert any(isinstance(error, error_type) for error_type in error_types)
assert data[error.component]["id"][0] == error.ids[0]
Jerry-Jinfeng-Guo marked this conversation as resolved.
Show resolved Hide resolved


@pytest.mark.parametrize("measured_terminal_type", MeasuredTerminalType)
@patch("power_grid_model.validation.validation.validate_base", new=MagicMock())
@patch("power_grid_model.validation.validation.all_greater_than_zero", new=MagicMock())
Expand Down