Source code for VeraGridEngine.IO.raw.psse_object
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
# SPDX-License-Identifier: MPL-2.0
import uuid as uuidlib
from typing import List, Dict, TypeVar, Any, Sequence, Tuple
from VeraGridEngine.IO.base.units import Unit
from VeraGridEngine.IO.raw.psse_property import PsseProperty
[docs]
def uuid_from_seed(seed: str):
"""
:param seed:
:return:
"""
return uuidlib.uuid5(uuidlib.UUID("f6fbb469-7173-5f64-a3af-d463f1f4e8f9"), seed).hex
[docs]
def format_raw_float(value: float) -> str:
"""
Format a float in engineering notation with 5 decimals.
Ensure the formatted string doesn't exceed 8 characters when possible.
"""
# Attempt engineering format with 5 decimals
formatted = f"{value:.5E}"
# Split into mantissa and exponent
mantissa, exponent = formatted.split("E")
exponent = int(exponent)
# Adjust the exponent to a multiple of 3
eng_exponent = 3 * (exponent // 3)
eng_mantissa = float(mantissa) * (10 ** (exponent - eng_exponent))
# Format the result
result = f"{eng_mantissa:.5f}E{eng_exponent:+03}"
# Ensure it fits within 8 characters
if len(result) <= 11:
return result
else:
# Fall back to a shorter general format if too long
return f"{value:.5g}"
[docs]
class RawObjectMeta(type):
"""
Metaclass that builds RAW property schema per class from class-level declarations.
"""
def __new__(mcs, name, bases, namespace):
"""
Build class-level schema by aggregating base + local property declarations.
:param name: Class name
:param bases: Base classes
:param namespace: Class namespace
:return: class object
"""
cls = super().__new__(mcs, name, bases, namespace)
aggregated_props: List[PsseProperty] = list()
for base in bases:
base_props: Tuple[PsseProperty, ...] = base.__dict__.get("CLASS_PROPERTIES", tuple())
for prop in base_props:
aggregated_props.append(prop)
local_props: Tuple[PsseProperty, ...] = namespace.get("LOCAL_PROPERTIES", tuple())
for prop in local_props:
aggregated_props.append(prop)
cls.CLASS_PROPERTIES = tuple(aggregated_props)
cls.CLASS_REGISTERED_PROPERTIES = dict()
for prop in cls.CLASS_PROPERTIES:
cls.CLASS_REGISTERED_PROPERTIES[prop.property_name] = prop
return cls
[docs]
class RawObject(metaclass=RawObjectMeta):
"""
PSSeObject
"""
LOCAL_PROPERTIES: Tuple[PsseProperty, ...] = (
PsseProperty(property_name="idtag",
rawx_key="uuid:string",
class_type=str,
description="Element UUID",
unit=Unit()),
)
CLASS_REGISTERED_PROPERTIES: Dict[str, PsseProperty] = dict()
CLASS_PROPERTIES: Tuple[PsseProperty, ...] = tuple()
"""
PSSeObject
"""
def __init__(self, class_name):
self.class_name = class_name
self.version = 33
self.idtag = uuidlib.uuid4().hex # always initialize with random uuid
self_cls: type = type(self)
self.__registered_properties = self_cls.CLASS_REGISTERED_PROPERTIES
self.__properties = self_cls.CLASS_PROPERTIES
[docs]
def get_rdfid(self) -> str:
"""
Convert the idtag to RDFID
:return: UUID converted to RDFID
"""
lenghts = [8, 4, 4, 4, 12]
chunks = list()
s = 0
for l in lenghts:
a = self.idtag[s:s + l]
chunks.append(a)
s += l
return "-".join(chunks)
[docs]
def get_properties(self) -> List[PsseProperty]:
"""
Get list of properties
:return: List[PsseProperty]
"""
return list(self.__registered_properties.values())
[docs]
def get_prop_value(self, prop: PsseProperty):
"""
Get property value
:param prop:
:return:
"""
return object.__getattribute__(self, prop.property_name)
[docs]
def get_rawx_dict(self) -> Dict[str, PsseProperty]:
"""
Get the RAWX property dictionary
:return: Dict[str, PsseProperty]
"""
return {value.rawx_key: value
for key, value in self.__registered_properties.items()}
[docs]
def register_property(self,
property_name: str,
rawx_key: str,
class_type: TypeVar | object,
unit: Unit = Unit(),
denominator_unit: Unit = Unit(),
description: str = '',
max_chars=None,
min_value=-1e20,
max_value=1e20,
format_rule=None):
"""
Register property of this object
:param property_name:
:param rawx_key:
:param class_type:
:param unit:
:param denominator_unit:
:param description:
:param max_chars:
:param min_value:
:param max_value:
:param format_rule: some formatting rule
"""
raise RuntimeError("RawObject.register_property() is disabled. Use class-level LOCAL_PROPERTIES.")
[docs]
def format_raw_line_prop(self, props: List[str]):
"""
Format a list of property names
:param props: list of property names
:return:
"""
lst = list()
for p in props:
if p in self.__registered_properties:
prop = self.__registered_properties[p]
val = object.__getattribute__(self, p)
if prop.class_type == str:
lst.append("'" + val + "'")
else:
lst.append(str(val))
return ", ".join(lst)
[docs]
@staticmethod
def extend_or_curtail(data: List[Any], n: int) -> List[Any]:
"""
Extends of curtails the input so that it marches what's expected
:param data: list of values
:param n: expected number of items
:return: extended or curtailed data
"""
if len(data) == n:
# it's ok
return data
elif len(data) < n:
# extend
diff = n - len(data)
extra = [0 for _ in range(diff)]
return data + extra
elif len(data) > n:
# curtail
return [data[i] for i in range(n)]
[docs]
def format_raw_line(self, props: List[str]) -> str:
"""
Format a list of values
:param props: list of property names
:return:
"""
lst = list()
for p_name in props:
prop = self.__registered_properties.get(p_name, None)
if prop is None:
raise Exception(f'Raw property {p_name} not found when trying to format it :(')
else:
val = object.__getattribute__(self, p_name)
if prop.class_type == str:
str_val = str(val)
if prop.max_chars is not None:
lst.append(f"'{str_val.ljust(prop.max_chars)}'")
else:
lst.append(f"'{str_val}'")
elif prop.class_type == float:
if prop.format_rule is None:
str_val = format_raw_float(value=val)
else:
str_val = f"{val:{prop.format_rule}}"
if prop.max_chars is not None:
lst.append(f"{str_val.rjust(prop.max_chars)}")
else:
lst.append(f"{str_val.rjust(8)}")
elif prop.class_type == int:
str_val = str(val)
if prop.max_chars is not None:
lst.append(f"{str_val.rjust(prop.max_chars)}")
else:
lst.append(f"{str_val.rjust(6)}")
else:
lst.append(str(val))
return ", ".join(lst)
[docs]
def get_raw_line(self, version: int) -> str:
"""
Get raw line
:param version: PSSe version
:return: Raw string representing this object
"""
return ""
[docs]
def get_id(self) -> str:
"""
Get a PSSe ID
Each device will implement its own way of generating this
:return: id
"""
return ""
def __str__(self):
return self.get_id()
[docs]
def get_uuid5(self):
"""
Generate UUID with the seed given by get_id()
:return: UUID based on the PSSe seed
"""
return uuid_from_seed(seed=self.get_seed())
[docs]
def set_registered_property_value(self, property_name: str, value: Any) -> bool:
"""
Set a registered property value using explicit object attribute assignment.
:param property_name: Registered property name.
:param value: Value to assign.
:return: ``True`` when the property exists and was assigned.
"""
if property_name in self.__registered_properties:
object.__setattr__(self, property_name, value)
return True
else:
return False
[docs]
def get_registered_property_value(self, property_name: str) -> Any:
"""
Get a registered property value using explicit object attribute access.
:param property_name: Registered property name.
:return: Property value.
:raises KeyError: If the property is not registered.
"""
if property_name in self.__registered_properties:
return object.__getattribute__(self, property_name)
else:
raise KeyError(property_name)
[docs]
def try_parse(self, values: Sequence[Any]) -> None:
"""
Copy *values* into this object following _ATTR_ORDER.
Extra elements are ignored; missing ones keep their default.
"""
for i, val in enumerate(values):
attr = self.__properties[i].property_name
self.set_registered_property_value(attr, val)
[docs]
def try_parse2(self, values: Sequence[Any], prop_names: Sequence[str]) -> None:
"""
Copy *values* into this object following _ATTR_ORDER.
Extra elements are ignored; missing ones keep their default.
:param values: values to set
:param prop_names: array of names
"""
if len(values) <= len(prop_names):
n = len(values)
else:
n = len(prop_names)
for i in range(n):
property_name = prop_names[i]
if property_name in self.__registered_properties:
self.set_registered_property_value(property_name, values[i])
else:
print(f"PSS/e property does not exist :( '{property_name}'")