Skip to content

Commit

Permalink
using UUIDs for base representation. Commented out network construction
Browse files Browse the repository at this point in the history
  • Loading branch information
tareknrel committed Dec 12, 2023
1 parent 5e9e209 commit cdb353c
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 53 deletions.
9 changes: 8 additions & 1 deletion ditto/models/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
159 changes: 107 additions & 52 deletions ditto/store.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)
Expand Down Expand Up @@ -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__,
Expand All @@ -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

Expand All @@ -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.
Expand All @@ -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
Expand All @@ -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):
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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"):
Expand Down Expand Up @@ -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)
Expand All @@ -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."""

0 comments on commit cdb353c

Please sign in to comment.