# -*- coding: utf-8 -*-
from .serializable import Serializable
from .eq import DeepEqual
from .classes import Classes
from .quantifiable import Quantifiable
from functools import lru_cache
from collections import OrderedDict
import builtins
from math import sqrt
import array
import logging
# create logger
logger = logging.getLogger(__name__)
# logger.debug('level %d' % (logger.getEffectiveLevel()))
bltns = vars(builtins)
ENDIAN = 'little'
""" Endianess of products generated. """
# data_type in parameter/column descriptors vs `data.__class__.__name__`
DataTypes = {
'array': 'array.array',
'baseProduct': 'BaseProduct',
'binary': 'int',
'boolean': 'bool',
'byte': 'int',
'complex': 'complex',
'finetime': 'FineTime',
'finetime1': 'FineTime1',
'float': 'float',
'hex': 'int',
'integer': 'int',
'list': 'list',
'mapContext': 'MapContext',
'product': 'Product',
'quaternion': 'Quaternion',
'short': 'int',
'string': 'str',
'tuple': 'tuple',
# 'numericParameter': 'NumericParameter',
# 'dateParameter': 'DateParameter',
# 'stringParameter': 'StringParameter',
'vector': 'Vector',
'vector2d': 'Vector2D',
'vector3d': 'Vector3D',
'': 'None'
}
""" Allowed data (Parameter and Dataset) types and the corresponding classe names.
The keys are mnemonics for humans; the values are type(x).__name__.
"""
DataTypeNames = {}
for tn, tt in DataTypes.items():
if tt == 'int':
DataTypeNames[tt] = 'integer'
else:
DataTypeNames[tt] = tn
DataTypeNames.update({
'NoneType': '',
'dict': 'vector',
'OrderedDict': 'vector',
'UserDict': 'vector',
'ODict': 'vector',
})
""" maps class type names to human-friendly types, reverse `DataTypes`. """
del tt, tn
try:
import numpy as np
# numpy type to python type https://stackoverflow.com/a/34919415/13472124
# nptype_to_pythontype = {v: getattr(bltns, k)
# for k, v in np.typeDict.items() if k}
pass
except ImportError:
pass
# https://docs.python.org/3.6/library/ctypes.html#fundamental-data-types
ctype_to_typecode = {
'c_bool': 't_', # Bool
'c_char': 'b', # 1-char bytes
'c_wchar': 'b', # 1-char string
'c_byte': 'b', # int? signed char
'c_ubyte': 'B', # unsigned char
'c_short': 'h', # signed short
'c_ushort': 'H', # unsigned short
'c_int': 'i', # signed int
'c_uint': 'I', # unsigned int
'c_long': 'l', # signed long
'c_ulong': 'L', # unsigned long
'c_longlong': 'q', # signed long long
'c_ulonglong': 'Q', # unsigned long long
'c_float': 'f', # float
'c_double': 'd', # double
'c_char_p': 'u', # string
}
# from https://stackoverflow.com/a/53702352/13472124 by tel
[docs]def get_typecodes():
import numpy as np
import ctypes as ct
import pprint
# np.ctypeslib.as_ctypes_type(dtype)
simple_types = [
ct.c_byte, ct.c_short, ct.c_int, ct.c_long, ct.c_longlong,
ct.c_ubyte, ct.c_ushort, ct.c_uint, ct.c_ulong, ct.c_ulonglong,
ct.c_float, ct.c_double,
]
return pprint.pprint([
{np.dtype(ctype).str: ctype.__name__ for ctype in simple_types},
{ctype_to_typecode[ctype.__name__]: np.dtype(
ctype).itemsize for ctype in simple_types}
])
# generated with get_typecodes() above
# w/o endian
numpytype_to_ctype = {
'f4': 'c_float',
'f8': 'c_double',
'i2': 'c_short',
'i4': 'c_int',
'i8': 'c_long',
'u2': 'c_ushort',
'u4': 'c_uint',
'u8': 'c_ulong',
'i1': 'c_byte',
'u1': 'c_ubyte'
}
# generated with get_typecodes() above
typecode_itemsize = {'B': 1,
'H': 2,
'I': 4,
'L': 8,
'b': 1,
'd': 8,
'f': 4,
'h': 2,
'i': 4,
'l': 8}
numpy_dtypekind_to_typecode = {
'b': 't_', # 'boolean',
'i': 'i', # integer',
'u': 'I', # unsigned integer',
'f': 'd', # float',
'c': 'c', # complex float',
'm': 'datetime.timedelta', # timedelta',
'M': 'datetime, # datetime',
'O': 'object', # object',
'S': 'B', # string',
'U': 'B', # unicode string',
'V': 'V', # fixed chunk of memory for other type ( void )',
}
[docs]def numpytype_to_typecode(x): return ctype_to_typecode[numpytype_to_ctype[x]]
[docs]def cast(val, typ_, namespace=None):
"""Casts the input value to type specified.
For example 'binary' type '0x9' is casted to int 9.
Parameters
----------
val : multiple
value to be casted.
typ_ : string
"human name" of `datatypes.DataTypeNames`. e.g. `integer`, `vector2d`, 'list'.
namespace : dict
namespace to look up classes. default is `Classes.mapping`.
Returns
-------
multiple
Casted value.
Raises
------
"""
t = DataTypes[typ_]
vstring = str(val).lower()
tbd = bltns.get(t, None) # lookup_bd(t)
if tbd:
if t == 'int':
base = 16 if vstring.startswith(
'0x') else 2 if vstring.startswith('0b') else 10
return tbd(vstring, base)
elif t == 'bytes':
return int(val).to_bytes(1, ENDIAN)
return tbd(val)
else:
return Classes.mapping[t](val) if namespace is None else namespace[t](val)
[docs]class Vector(Quantifiable, Serializable, DeepEqual):
""" N dimensional vector.
If description, type etc meta data is needed, use a Parameter.
A Vector can compare with a value whose type is in ``DataTypes``, the quantity being used is the magnitude.
"""
[docs] def __init__(self, components=None, **kwds):
""" invoked with no argument results in a vector of
[0, 0, 0] components.
Parameters
----------
Returns
-------
"""
if components is None:
self._data = [0, 0, 0]
else:
self.setComponents(components)
super().__init__(**kwds)
@ property
def components(self):
""" for property getter
Parameters
----------
Returns
-------
"""
return self.getComponents()
@ components.setter
def components(self, components):
""" for property setter
Parameters
----------
Returns
-------
"""
self.setComponents(components)
[docs] def getComponents(self):
""" Returns the actual components that is allowed for the components
of this vector.
Parameters
----------
Returns
-------
"""
return self._data
[docs] def setComponents(self, components):
""" Replaces the current components of this vector.
Parameters
----------
Returns
-------
"""
# for c in components:
# if not isinstance(c, Number):
# raise TypeError('Components must all be numbers.')
# must be list to make round-trip Json
self._data = list(components)
def __eq__(self, obj, verbose=False, **kwds):
""" can compare value """
if type(obj).__name__ in DataTypes.values():
return sqrt(sum(x*x for x in self._data)) == obj
else:
return super(Vector, self).__eq__(obj)
def __lt__(self, obj):
""" can compare value """
if type(obj).__name__ in DataTypes.values():
return sqrt(sum(x*x for x in self._data)) < obj
else:
return super(Vector, self).__lt__(obj)
def __gt__(self, obj):
""" can compare value """
if type(obj).__name__ in DataTypes.values():
return sqrt(sum(x*x for x in self._data)) > obj
else:
return super(Vector, self).__gt__(obj)
def __le__(self, obj):
""" can compare value """
if type(obj).__name__ in DataTypes.values():
return sqrt(sum(x*x for x in self._data)) <= obj
else:
return super(Vector, self).__le__(obj)
def __ge__(self, obj):
""" can compare value """
if type(obj).__name__ in DataTypes.values():
return sqrt(sum(x*x for x in self._data)) >= obj
else:
return super(Vector, self).__ge__(obj)
def __len__(self):
"""
Parameters
----------
Returns
-------
"""
return len(self._data)
[docs] def toString(self, level=0, **kwds):
return self.__repr__()
__str__ = toString
string = toString
txt = toString
def __getstate__(self):
""" Can be encoded with serializableEncoder
Parameters
----------
Returns
-------
"""
return OrderedDict(
components=list(self._data),
unit=self._unit,
typecode=self._typecode)
[docs]class Vector2D(Vector):
""" Vector with 2-component data"""
[docs] def __init__(self, components=None, **kwds):
""" invoked with no argument results in a vector of
[0, 0] components
Parameters
----------
Returns
-------
"""
super().__init__(**kwds)
if components is None:
self._data = [0, 0]
else:
self.setComponents(components)
[docs]class Vector3D(Vector):
""" Vector with 3-component data"""
[docs] def __init__(self, components=None, **kwds):
""" invoked with no argument results in a vector of
[0, 0, 0]] components
Parameters
----------
Returns
-------
"""
super().__init__(**kwds)
if components is None:
self._data = [0, 0, 0]
else:
self.setComponents(components)
[docs]class Quaternion(Vector):
""" Quaternion with 4-component data.
"""
[docs] def __init__(self, components=None, **kwds):
""" invoked with no argument results in a vector of
[0, 0, 0, 0] components
Parameters
----------
Returns
-------
"""
super(Quaternion, self).__init__(**kwds)
if components is None:
self._data = [0, 0, 0, 0]
else:
self.setComponents(components)