From cdb353cc722fdc59f7c8c811b9e1f1639aa8cf44 Mon Sep 17 00:00:00 2001 From: tarekelgindy Date: Mon, 11 Dec 2023 17:07:42 -0700 Subject: [PATCH] using UUIDs for base representation. Commented out network construction --- ditto/models/base.py | 9 ++- ditto/store.py | 159 +++++++++++++++++++++++++++++-------------- 2 files changed, 115 insertions(+), 53 deletions(-) diff --git a/ditto/models/base.py b/ditto/models/base.py index 92496d7c..8070d17d 100644 --- a/ditto/models/base.py +++ b/ditto/models/base.py @@ -29,12 +29,19 @@ class DiTToBaseModel(BaseModel): json_encoders = json_encoders ) - name: Annotated[str,Field( + UUID: Annotated[str,Field( description="Name of the element in the DiTTo model", title="name", json_schema_extra = {"cim_value":"name"} )] + name: Annotated[Optional[str],Field( + description="Name of the element in the DiTTo model", + title="name", + default = None + json_schema_extra = {"cim_value":"name"} + )] + substation_name: Annotated[Optional[str], Field( description="Name of the substation the element is under", title="substation_name", diff --git a/ditto/store.py b/ditto/store.py index f5916d24..7fa759c3 100644 --- a/ditto/store.py +++ b/ditto/store.py @@ -24,9 +24,9 @@ import logging import types from functools import partial -from .network.network import Network +#from .network.network import Network +#from .modify.modify import Modifier -from .modify.modify import Modifier from .models.node import Node logger = logging.getLogger(__name__) @@ -54,24 +54,28 @@ def __init__(self): """Store constructor. self._elements is a two-level dictionary which stores all the elements being represented in a model. The first level of the dictionary is keyed by the element types, which map to a dictionary. - The second level of the dictionary is keyed by the element names, which map to a list of objects. - The names within each element type should be unique, resulting in lists of length 1. - {element_type: {element_name: [element_object]}} + The second level of the dictionary is keyed by the element UUIDs, which map to an object. + The UUIDs within each element type should be unique. + {element_type: {element_UUID: element_object}} + + self._names is a two-level dictionary which maps the names of elements to a list of UUIDs. + The first level of the dictionary is keyed by the element types, which map to a dictionary. + The second level of the dictionary is keyed by element names, which map to a list of UUIDs. self._network is a Network object which stores the networkx graph representation of the model. """ self._elements = defaultdict(dict) - self._network = Network() + self._names = defaultdict(dict) + #self._network = Network() def __repr__(self): """Representation of the Store object. Includes count of the number of elements in the Store. - self._elements has the form {element_type: {element_name: [element_object]}} - Elements with duplicate names are considered to be separate elements. + self._elements has the form {element_type: {element_UUID: element_object}} """ num_elements = 0 for k, v in self._elements.items(): - num_elements += len(len(v)) + num_elements += len(v) return "{}.{}(elements={})".format( self.__class__.__module__, self.__class__.__name__, @@ -84,18 +88,18 @@ def __getitem__(self, name): DeprecationWarning ) element = None - for element_type, elements in self._elements.items(): - if name in elements: + for element_type, names in self._names.items(): + if name in names: + if len(names[name]) > 1: + raise DuplicateNameError( + f"Store[name] is not supported when the name is duplicate within an element type" + ) + uuid = next(iter(self._names[element_type][name])) + element = self._elements[element_type][uuid] if element is not None: raise DuplicateNameError( f"Store[name] is not supported when the name is duplicate across element types" ) - if len(elements[name]) > 1: - raise DuplicateNameError( - f"Store[name] is not supported when the name is duplicate within an element type" - ) - element = elements[name][0] - if element is None: raise ElementNotFoundError @@ -107,18 +111,27 @@ def __setitem__(self, name, element): DeprecationWarning ) - if element.name != name + if element.name != name: raise Exception(f"key={name} must be the element name") self.add_element(element) - def _raise_if_not_found(self, element_type, name): + def _raise_if_name_not_found(self, element_type, name): if element_type not in self._elements: raise ElementNotFoundError(f"{element_type} is not stored") - if name not in self._elements[element_type]: + if name not in self._names[element_type]: + raise ElementNotFoundError(f"{element_type}.{name} name is not found") + + def _raise_if_duplicate(self, element_type, name): + if element_type not in self._elements: + raise ElementNotFoundError(f"{element_type} is not stored") + + if name not in self._names[element_type]: raise ElementNotFoundError(f"{element_type}.{name} is not stored") - self._model_names[k] = v + + if len(self._names[element_type][name]) > 1: + raise DuplicateNameError(f"{element_type}.{name} is duplicated") def add_element(self, element): """Add an element to the Store. @@ -135,28 +148,59 @@ def add_element(self, element): """ if not isinstance(element, DiTToBaseModel): raise InvalidElementType(f"type={type(element)} cannot be added") - if not hasattr(element, "name"): - raise InvalidElementType(f"type={type(element)} cannot be added. Must define 'name' attribute.") + if not hasattr(element, "UUID"): + raise InvalidElementType(f"type={type(element)} cannot be added. Must define 'UUID' attribute.") element_type = type(element) elements_by_type = self._elements[element_type] - element_name_list = [] - if element.name in elements_by_type: - logger.warning(f"Warning: {element_type}.{element.name} already exists. Adding duplicate.") - element_name_list = elements_by_type[element.name] + if element.UUID in elements_by_type: + raise DuplicateUUIDError(f"{element_type}.{element.UUID} already exists") + elements_by_type[element.UUID] = element + if element.name is not None: + element_name_set = set() + if element.name in self._names[element_type]: + element_name_set = self._names[element_type][element.name] + element_name_set.add(element.UUID) + if len(element_name_set) > 1: + logger.warning(f"Warning: {element_type}.{element.name} is duplicated. Adding duplicate name.") + self._names[element_type][element.name] = element_name_set + logger.debug(f"added element with name: {element_type}.{element.name}") + else: + logger.debug(f"added element with UUID: {element_type}.{element.UUID}, as no name provided") - element_name_list.append(element) - elements_by_type[element.name] = element_name_list - element.build(self) - logger.debug(f"added {element_type}.{element.name}") def clear_elements(self): """Clear all stored elements.""" self._elements.clear() + self._names.clear() logger.debug("Cleared all elements") - def get_elements(self, element_type, name): - """Return an list of elements from the that match the element_type and name parameters. + def get_element(self, element_type, name): + """Return the element that matches the element_type and name parameters. + Parameters + ---------- + element_type : class + class for the requested model, such as Load + name : str + element name + Returns + ------- + DiTToBaseModel + Raises + ------ + ElementNotFoundError + Raised if the element_type is not stored. + DuplicateNameError + Raised if the name is not unique within the type of element. + """ + self._raise_if_duplicate(element_type, name) + uuid = next(iter(self._names[element_type][name])) + return self._elements[element_type][uuid] + + def get_all_elements(self, element_type, name): + """Return an list of elements that match the element_type and name parameters. + May include elements with the same name and element type. + Parameters ---------- element_type : class @@ -171,10 +215,11 @@ class for the requested model, such as Load ElementNotFoundError Raised if the element_type is not stored. """ - self._raise_if_not_found(element_type, name) - if len(self._elements[element_type][name]) > 1: - logger.warning(f"Warning: {element_type}.{name} is duplicated. Returning all.") - return self._elements[element_type][name] + self._raise_if_name_not_found(element_type, name) + element_list = [] + for uuid in self._names[element_type][name]: + element_list.append(self._elements[element_type][uuid]) + return element_list def iter_elements(self, element_type=None, filter_func=None): @@ -202,11 +247,11 @@ def iter_elements(self, element_type=None, filter_func=None): else: elements_containers = self._elements.values() - for element_names in elements_containers: - for element_list in element_names.values(): + for element_uuids in elements_containers: + for element_list in element_uuids.values(): for element in element_list: if filter_func is not None and not filter_func(element): - logger.debug("skip %s.%s", type(element), element.name) + logger.debug(f"skip {type(element)}.element.name") continue yield element @@ -241,15 +286,24 @@ def remove_element(self, element): Raised if the element is not stored. """ element_type = type(element) - self._raise_if_not_found(element_type, element.name) - if len(self._elements[element_type][element.name]) > 1: - logger.warning(f"Warning: {element_type}.{element.name} is duplicated. Removing all.") - self._elements[element_type].pop(element.name) - logger.debug(f"Removed all elements with {element_type}.{element.name} from store") + if element_type not in self._elements: + raise ElementNotFoundError(f"{element_type} is not stored") + if element.UUID not in self._elements[element_type]: + raise UUIDNotFoundError(f"{element_type}.{element.UUID} is not stored") + self._elements[element_type].pop(element.UUID) + if element.name is not None: + if element.name not in self._names[element_type]: + raise ElementNotFoundError(f"{element_type}.{element.name} is not stored") + self._names[element_type][element.name].remove(element.UUID) + if len(self._names[element_type][element.name]) == 0: + self._names[element_type].pop(element.name) + logger.debug(f"Removed element with name {element_type}.{element.name} and UUID {element_type}.{element.UUID} from store") + else: + logger.debug(f"Removed {element_type}.{element.UUID} from store") if not self._elements[element_type]: self._elements.pop(element_type) - logger.debug("Removed %s from store", element_type) + logger.debug(f"Removed {element_type} from store") def build_networkx(self, source=None): if source is not None: @@ -279,8 +333,8 @@ def delete_cycles(self): for j in self.models: if hasattr(j, "name") and j.name == edge: logger.debug("deleting " + edge) - modifier = Modifier() - modifier.delete_element(self, j) + #modifier = Modifier() + #modifier.delete_element(self, j) self.build_networkx() def direct_from_source(self, source="sourcebus"): @@ -308,8 +362,8 @@ def delete_disconnected_nodes(self): connected_nodes = self._network.get_nodes() if not i.name in connected_nodes: logger.debug("deleting " + i.name) - modifier = Modifier() - modifier.delete_element(self, i) + #modifier = Modifier() + #modifier.delete_element(self, i) if isinstance(i, Node) and hasattr(i, "name") and i.name is None: self.remove_element(i) @@ -333,12 +387,13 @@ def get_internal_edges(self, nodeset): return self._network.find_internal_edges(nodeset) class DuplicateNameError(Exception): - """Raised when a duplicate name is detected.""" + """Raised when a duplicate name is detected where not permitted.""" +class DuplicateUUIDError(Exception): + """Raised when a duplicate UUID is detected.""" class ElementNotFoundError(Exception): """Raised when an element is not stored.""" - class InvalidElementType(Exception): """Raised when an invalid type is used."""