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/Updated validation to allow for missing sigma #503

Merged
merged 19 commits into from
Mar 1, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion docs/user_manual/components.md
Original file line number Diff line number Diff line change
Expand Up @@ -594,7 +594,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 Concrete 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; in certain cases for state estimation. See the explanation below. | &#10004; | `> 0` |
Jerry-Jinfeng-Guo marked this conversation as resolved.
Show resolved Hide resolved

#### Power Sensor Concrete Types

Expand All @@ -615,6 +615,18 @@ the meaning of `RealValueInput` is different, as shown in the table below.
| `p_sigma` | `RealValueInput` | watt (W) | standard deviation of the active power measurement error. Usually this is the absolute measurement error range divided by 3. | &#10060; see the explanation below. | &#10004; | `> 0` |
| `q_sigma` | `RealValueInput` | volt-ampere-reactive (var) | standard deviation of the reactive power measurement error. Usually this is the absolute measurement error range divided by 3. | &#10060; see the explanation below. | &#10004; | `> 0` |

Valid combinations of `power_sigma`, `p_sigma` and `q_sigma` are:
| `power_sigma` | `p_sigma` | `q_sigma` | result |
Jerry-Jinfeng-Guo marked this conversation as resolved.
Show resolved Hide resolved
Jerry-Jinfeng-Guo marked this conversation as resolved.
Show resolved Hide resolved
|:-------------:|:---------:|:---------:|:--------:|
| x | x | x | &#10004; |
| x | x | | &#10060; |
| x | | x | &#10060; |
| x | | | &#10004; |
| | x | x | &#10004; |
| | x | | &#10060; |
| | | x | &#10060; |
| | | | &#10060; |

```{note}
1. If both `p_sigma` and `q_sigma` are provided, they represent the standard deviation of the active and reactive power, respectively, and the value of `power_sigma` is ignored. Any infinite component invalidates the entire measurement.

Expand Down
21 changes: 21 additions & 0 deletions src/power_grid_model/validation/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,23 @@ def validate_ids_exist(update_data: Dict[str, np.ndarray], input_data: SingleDat
return list(chain(*errors))


def process_power_sigma_and_p_q_sigma(data: SingleDataset, sensor: str) -> None:
Jerry-Jinfeng-Guo marked this conversation as resolved.
Show resolved Hide resolved
"""
Helper function to process the required list when both `p_sigma` and `q_sigma` exist
and valid but `power_sigma` is missing. The field `power_sigma` is set to 1 in this case.
Jerry-Jinfeng-Guo marked this conversation as resolved.
Show resolved Hide resolved
The argument is that when both `p_sigma` and `q_sigma` are valid, the value pf `power_sigma`
is always ignored.
"""
if sensor in data and isinstance(data[sensor], np.ndarray):
sensor_data = data[sensor]
for i in range(len(sensor_data)):
Jerry-Jinfeng-Guo marked this conversation as resolved.
Show resolved Hide resolved
if "p_sigma" in sensor_data.dtype.names and "q_sigma" in sensor_data.dtype.names:
p_sigma = sensor_data["p_sigma"][i]
q_sigma = sensor_data["q_sigma"][i]
if not (np.isnan(p_sigma).any() or np.isnan(q_sigma).any()):
data[sensor]["power_sigma"][i] = 1 # Set to 1 if both p_sigma and q_sigma are valid


def validate_required_values(
data: SingleDataset, calculation_type: Optional[CalculationType] = None, symmetric: bool = True
) -> List[MissingValueError]:
Expand Down Expand Up @@ -331,6 +348,10 @@ def validate_required_values(
required["line"] += ["r0", "x0", "c0", "tan0"]
required["shunt"] += ["g0", "b0"]

# Allow missing `power_sigma` of both `p_sigma` and `q_sigma` are present
process_power_sigma_and_p_q_sigma(data, "sym_power_sensor")
process_power_sigma_and_p_q_sigma(data, "asym_power_sensor")

return list(chain(*(none_missing(data, component, required.get(component, [])) for component in data)))


Expand Down
70 changes: 69 additions & 1 deletion tests/unit/validation/test_validation_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
import numpy as np
import pytest

from power_grid_model import MeasuredTerminalType, initialize_array, power_grid_meta_data
# from power_grid_model import PowerGridModel, LoadGenType, MeasuredTerminalType, initialize_array, CalculationType, CalculationMethod
Jerry-Jinfeng-Guo marked this conversation as resolved.
Show resolved Hide resolved
from power_grid_model import CalculationType, LoadGenType, MeasuredTerminalType, initialize_array, power_grid_meta_data
from power_grid_model.enum import CalculationType, FaultPhase, FaultType
from power_grid_model.validation import assert_valid_input_data
from power_grid_model.validation.errors import (
IdNotInDatasetError,
InfinityError,
Expand Down Expand Up @@ -646,3 +648,69 @@ def test_validate_generic_power_sensor__terminal_types(
all_valid_ids.assert_any_call(
ANY, ANY, field=ANY, ref_components=ref_component, measured_terminal_type=measured_terminal_type
)


def test_power_sigma_or_p_q_sigma():
# node
node = initialize_array("input", "node", 2)
node["id"] = np.array([0, 3])
node["u_rated"] = [10.5e3, 10.5e3]

# line
line = initialize_array("input", "line", 1)
line["id"] = [2]
line["from_node"] = [0]
line["to_node"] = [3]
line["from_status"] = [1]
line["to_status"] = [1]
line["r1"] = [0.001]
line["x1"] = [0.02]
line["c1"] = [0.0]
line["tan1"] = [0.0]
line["i_n"] = [1000.0]

# load
sym_load = initialize_array("input", "sym_load", 1)
sym_load["id"] = [4]
sym_load["node"] = [3]
sym_load["status"] = [1]
sym_load["type"] = [LoadGenType.const_power]
sym_load["p_specified"] = [1e6]
sym_load["q_specified"] = [-1e6]

# source
source = initialize_array("input", "source", 1)
source["id"] = [1]
source["node"] = [0]
source["status"] = [1]
source["u_ref"] = [1.0]

# voltage sensor
voltage_sensor = initialize_array("input", "sym_voltage_sensor", 1)
voltage_sensor["id"] = 5
voltage_sensor["measured_object"] = 0
voltage_sensor["u_sigma"] = [100.0]
voltage_sensor["u_measured"] = [10.5e3]

# power sensor
power_sensor = initialize_array("input", "sym_power_sensor", 2)
power_sensor["id"] = [6, 7]
power_sensor["measured_object"] = [2, 4]
power_sensor["measured_terminal_type"] = [MeasuredTerminalType.branch_from, MeasuredTerminalType.load]
power_sensor["p_measured"] = [1e6, -1e6]
power_sensor["q_measured"] = [1e6, -1e6]
power_sensor["power_sigma"] = [np.nan, 1e9]
power_sensor["p_sigma"] = [1e4, np.nan]
power_sensor["q_sigma"] = [1e9, np.nan]
Jerry-Jinfeng-Guo marked this conversation as resolved.
Show resolved Hide resolved

# all
input_data = {
"node": node,
"line": line,
"sym_load": sym_load,
"source": source,
"sym_voltage_sensor": voltage_sensor,
"sym_power_sensor": power_sensor,
}

assert_valid_input_data(input_data=input_data, calculation_type=CalculationType.state_estimation)
Loading