#pylint: disable=no-member
"""
Numeric types and enumerations in *hynet*.
"""
from enum import Enum
import numpy as np
from scipy.sparse import coo_matrix
hynet_id_ = np.int64
hynet_int_ = np.int64
hynet_float_ = np.float64
hynet_complex_ = np.complex128
hynet_sparse_ = coo_matrix
# REMARK to sparse matrices in *hynet*:
# -------------------------------------
#
# First of all, KEEP THE COORDINATE SPARSITY FORMAT, even if the definition
# suggests that it may be modified ;) For performance reasons, I had to utilize
# its particular data organisation and a modification would break some code.
#
# Anyway, after several rounds of performance optimization the selection of the
# SciPy COO format is well motivated. Besides the fast creation of sparse
# matrices (e.g. in SystemModel) and enabling of efficient data reorganisation
# (e.g. in QCQP), the most profound reason for its choice is memory efficiency.
# For larger grids, thousands of sparse matrices are generated to construct the
# OPF QCQP and, for the originally employed CSR format (which is beneficial for
# slicing, indexing, and arithmetic), this soon leads to QCQP objects of 100MB
# and more. This high memory consumption is a particular issue with parallel
# and distributed processing, where certain objects are pickled. For example,
# during the constraint generation in SystemModel.get_problem, this caused
# Python's multiprocessing package to crash for the 13659-bus grid of the
# PGLib. Switching to the COO format reduced the memory consumption
# considerably, e.g., for scenario 0 of the German 2030 NEP grid with 1524
# buses the pickle of its QCQP object was reduced from 113.7MB to 7.9MB, i.e.,
# by 93%.
#
# Finally, a particularity of COO matrices should be kept in mind, e.g., if a
# a new solver interface is implemented. This format permits duplicate entries,
# i.e., the matrix' attributes ``row``, ``col``, and ``data`` can contain
# entries that refer to the same matrix element, in which case they should be
# summed up. This is performed, e.g., during a conversion to CSR or CSC.
#
if hynet_sparse_(((1, 2), ((0, 0), (0, 0))), shape=(1, 1)).tocsr()[0, 0] != 3:
raise RuntimeError("Sparse matrix initialization is incompatible.")
hynet_eps = np.finfo(hynet_float_).eps # Machine epsilon
[docs]class BusType(Enum):
"""Type of voltage waveform experienced at the bus."""
AC = 'ac'
DC = 'dc'
def __str__(self):
return self.value.upper()
[docs]class BranchType(Enum):
"""Type of entity modeled by the branch."""
LINE = 'line'
TRANSFORMER = 'transformer'
def __str__(self):
return self.value.upper()
[docs]class InjectorType(Enum):
"""Type of entity modeled by the injector."""
# Categories
CONVENTIONAL = 'conventional'
RENEWABLE = 'renewable'
PROSUMER = 'prosumer'
LOAD = 'load'
COMPENSATION = 'compensation'
# Subcategories for conventional generation
COAL = 'conventional:coal'
GAS = 'conventional:gas'
NUCLEAR = 'conventional:nuclear'
# Subcategories for renewables
HYDRO = 'renewable:hydro'
WIND = 'renewable:wind'
PV = 'renewable:pv'
BIOMASS = 'renewable:biomass'
GEOTHERMAL = 'renewable:geothermal'
[docs] def is_conventional(self):
"""Return ``True`` if it is a conventional generation utility."""
return self.value.startswith('conventional')
[docs] def is_renewable(self):
"""Return ``True`` if it is a renewables-based generation utility."""
return self.value.startswith('renewable')
[docs] def is_prosumer(self):
"""Return ``True`` if it is a prosumer."""
return self.value.startswith('prosumer')
[docs] def is_load(self):
"""Return ``True`` if it is a load."""
return self.value.startswith('load')
[docs] def is_compensation(self):
"""Return ``True`` if it is a reactive power compensator."""
return self.value.startswith('compensation')
def __str__(self):
"""
Return a formatted string for the injector type.
This formatted string is generated from the value of the element, which
a hierarchy of categories separated by a colon, i.e.,
::
category:subcategory:subsubcategory
During formatting, any underscores are replaced by blanks.
"""
categories = self.value.replace('_', ' ').split(':')
type_string = categories.pop(0)
if categories:
type_string += ' (' + '/'.join(categories) + ')'
return type_string.upper()
[docs]class EntityType(Enum):
"""Type of entity that is deactivated in a scenario."""
BUS = 'bus'
BRANCH = 'branch'
CONVERTER = 'converter'
SHUNT = 'shunt'
INJECTOR = 'injector'
def __str__(self):
return self.value.upper()
def __lt__(self, other):
# REMARK: This operator is required by SQLAlchemy as EntityType is
# part of the primary key in DBScenarioInactivity
return self.value < other.value
[docs]class ConstraintType(Enum):
"""Type of constraint for the QCQP specification."""
EQUALITY = 'equality'
INEQUALITY = 'inequality'
[docs]class SolverType(Enum):
"""Type of problem that can be solved with the solver."""
QCQP = 'QCQP'
SDR = 'SDR'
SOCR = 'SOCR'
[docs]class SolverStatus(Enum):
"""Status returned by a solver after performing an optimization."""
SOLVED = 0
INACCURATE = 1
UNBOUNDED = -1
INFEASIBLE = -2
FAILED = -4
[docs]class DBInfoKey(Enum):
"""Valid keys for the ``db_info`` table in a *hynet* grid database."""
VERSION = "version"
GRID_NAME = "grid_name"
BASE_MVA = "base_mva"
DESCRIPTION = "description"
def __str__(self):
return self.value