Skip to content
Snippets Groups Projects
Commit 79eae12f authored by Vanessa Stehr's avatar Vanessa Stehr
Browse files

implement toJsonDict for valueTypes

Uncertainties still missing, and got to research if objects need to be converted to str
parent 17e82fc8
Branches
No related tags found
No related merge requests found
Pipeline #52071 passed
......@@ -3,8 +3,7 @@ class AbstractQuantityTypeData:
pass
def __len__(self):
raise NotImplementedError()
return NotImplemented
def toJsonDict(self):
# raise NotImplementedError()
return {'foo': 'bar'} # only for testing, until al children have implemented this class
return NotImplemented
\ No newline at end of file
......@@ -50,6 +50,6 @@ def parse(jsonDict: Union[dict, list]):
if isinstance(jsonDict, list):
return DccCharsXMLList(data=jsonDict)
elif isinstance(jsonDict, dict):
raise NotImplementedError
return NotImplemented
return DccCharsXMLList(data=jsonDict)
......@@ -61,12 +61,12 @@ class DccNoQuantity(AbstractQuantityTypeData):
}
result = {}
for key, value in self.__dict__.items():
if value:
if value is not None:
if key in keyMapping:
result[keyMapping[key]] = value
elif key == 'data':
for key, value in self.data.items():
if value:
if value is not None:
result[dataKeyMapping[key]] = value
if set(self.data.keys()) - set(dataKeyMapping.keys()):
pass # TODO: This should not happen, maybe add a warning?
......
......@@ -17,6 +17,9 @@ class SiComplexList(AbstractValueType):
self.phase = phase
self.uncertainty = uncertainty
def toJsonDict(self):
return NotImplemented
def __len__(self) -> int:
return len(self.data)
......
......@@ -24,7 +24,7 @@ class SiList(AbstractListType):
}
attributes = super().toJsonDict() # converts all the data in 'children'
for key, value in self.__dict__.items():
if value:
if value is not None:
if key in keyMapping:
attributes[keyMapping[key]] = value
elif key == 'children':
......
......@@ -13,6 +13,7 @@ from helpers import *
from parseUncertainties import parseUncertainties, uncertaintyKeys
import numpy as np
class SiRealList(AbstractValueType):
def __init__(
......@@ -91,7 +92,7 @@ class SiRealList(AbstractValueType):
params = {
key: value
for key, value in vars(self).items()
if value != None and key != "data"
if value is not None and key != "data"
}
paramStr = ", ".join(f"{key}: {str(value)}" for key, value in params.items())
......@@ -100,7 +101,7 @@ class SiRealList(AbstractValueType):
return f"{str(self.data)}{paramStr}"
def __eq__(self, value: object) -> bool:
#TODO: implement comparison for non unc values
# TODO: implement comparison for non unc values
warnings.warn(
"Can't compare objects with uncertainty. If you need to know if the objects have the same value and deviation, use SiRealList.hasSameValue(list) or SiRealList.hasSameParameters(SiRealList).",
RuntimeWarning,
......@@ -125,6 +126,66 @@ class SiRealList(AbstractValueType):
and self.dateTime == other.dateTime
)
def _toJsonDictSingleValue(self):
keyMapping = {
"data": "si:value",
"unit": "si:unit",
"label": "si:label",
"dateTime": "si:dateTime",
}
attributes = {}
for key, value in self.__dict__.items():
if value is not None:
if key in keyMapping:
if isinstance(value, np.ndarray):
value = value.tolist()
if isinstance(value, list):
if len(value) > 1:
raise AttributeError(
"You tried to generate a JSON Dict in a single-value-format, but the object contained more than one entry for one of its attributes!"
)
value = value[0]
attributes[keyMapping[key]] = value
elif key == 'originType':
pass
else:
attributes[key] = value
# TODO: This should not happen, maybe add a warning?
if self.originType == "si:constant":
return {"si:constant": attributes}
else:
return {"si:real": attributes}
def _toJsonDictList(self):
keyMapping = {
'data': 'si:valueXMLList',
'unit': 'si:unitXMLList',
'label': 'si:labelXMLList',
'dateTime': 'si:dateTimeXMLList'
}
attributes = {}
for key, value in self.__dict__.items():
if value is not None:
if key in keyMapping:
if isinstance(value, np.ndarray):
value = value.tolist()
attributes[keyMapping[key]] = value
elif key == 'originType':
pass
else:
attributes[key] = value
# TODO: This should not happen, maybe add a warning?
return {'si:realListXMLList': attributes}
def toJsonDict(self):
if self.originType in ['si:real', 'si:constant']:
for key, value in self.__dict__.items():
if value is not None and(isinstance(value, list) or isinstance(value, np.ndarray)) and len(value) > 1:
warnings.warn(RuntimeWarning(f'Serializing siRealList object with originType {self.originType}, but one of the attributes contains a list! Using type si:realListXMLList instead.'))
return self._toJsonDictList()
return self._toJsonDictSingleValue()
return self._toJsonDictList()
def __neg__(self):
new_data = [-item for item in self.data]
return SiRealList(
......@@ -285,7 +346,7 @@ class SiRealList(AbstractValueType):
# SiRealList
elif isinstance(other, SiRealList):
# Require that both self.unit and other.unit are universal.
# Require that both self.unit and other.unit are universal.
if len(self.unit) != 1 or len(other.unit) != 1:
raise NotImplementedError(
"Multiplication or Division for SiRealList with non-universal units is not implemented."
......@@ -294,12 +355,12 @@ class SiRealList(AbstractValueType):
# Get the scaling factor so that other's unit is converted into self's unit.
scale, scaledUnit = self.unit[0].isScalablyEqualTo(other.unit[0])
result = deepcopy(self)
if scaledUnit!=None:
if scaledUnit != None:
result.unit = [self.unit[0] * scaledUnit]
else:
#the units are not scalable equal so scalfactor will be 1 since we now have new unit like 5 V *2 m= 10 Vm
scale=1.0
result.unit = [self.unit[0]*other.unit[0]]
# the units are not scalable equal so scalfactor will be 1 since we now have new unit like 5 V *2 m= 10 Vm
scale = 1.0
result.unit = [self.unit[0] * other.unit[0]]
# Determine the proper multiplication based on the length of data.
if len(self) == 1:
# self has a single value: multiply it with each scaled other.data element.
......@@ -310,9 +371,13 @@ class SiRealList(AbstractValueType):
result.data = [d * (scale * other.data[0]) for d in self.data]
elif len(self) == len(other):
# Both have the same number of data points: multiply elementwise.
result.data = [d1 * (scale * d2) for d1, d2 in zip(self.data, other.data)]
result.data = [
d1 * (scale * d2) for d1, d2 in zip(self.data, other.data)
]
else:
raise AttributeError("Data lengths must be equal or one must be a singleton!")
raise AttributeError(
"Data lengths must be equal or one must be a singleton!"
)
if self.label and other.label:
result.label = joinLabels(self.label, other.label, "*")
......@@ -427,8 +492,6 @@ class SiRealList(AbstractValueType):
else:
return NotImplemented
def __getitem__(self, index) -> SiRealList:
"""
Return a new SiRealList instance containing only the elements specified by `index`.
......@@ -438,7 +501,6 @@ class SiRealList(AbstractValueType):
to be universal and is not sub‐indexed.
"""
# Helper to subset an attribute (label/unit/dateTime)
def subset_attr(attr):
if attr is None:
......@@ -461,7 +523,7 @@ class SiRealList(AbstractValueType):
try:
data_arr = np.array(self.data, dtype=object)
new_data = data_arr[index]
#TODO add runtime warning here if len do not match
# TODO add runtime warning here if len do not match
except Exception as e:
raise IndexError(f"Error indexing data {self.data}: {e}")
# When index is an int, np.array returns a single element; we wrap it in a list.
......@@ -480,7 +542,7 @@ class SiRealList(AbstractValueType):
label=new_label,
unit=new_unit,
dateTime=new_dateTime,
originType=self.originType
originType=self.originType,
)
def __array__(self, dtype=None):
......@@ -492,8 +554,8 @@ class SiRealList(AbstractValueType):
"""
Returns a NumPy array of just the numerical values extracted from the data.
"""
if np.issubdtype(self.data.dtype,np.number):
#we have non uncer data so we directly return the data
if np.issubdtype(self.data.dtype, np.number):
# we have non uncer data so we directly return the data
return self.data
else:
return get_valsFromUFloatArrays(self.data)
......@@ -503,8 +565,8 @@ class SiRealList(AbstractValueType):
"""
Returns a NumPy array of just the uncertainties extracted from the data.
"""
if np.issubdtype(self.data.dtype,np.number):
#we have non uncer data so we directly return the data
if np.issubdtype(self.data.dtype, np.number):
# we have non uncer data so we directly return the data
raise AttributeError("This siRealList doesn't has any uncer associated!")
else:
return get_uncerFromUFloatArrays(self.data)
......@@ -535,7 +597,9 @@ class SiRealList(AbstractValueType):
try:
return list(np.reshape(np.array(attr, dtype=object), newshape))
except Exception as e:
warnings.warn(f"Could not reshape attribute {attr}: {e}", RuntimeWarning)
warnings.warn(
f"Could not reshape attribute {attr}: {e}", RuntimeWarning
)
return attr
# Reshape associated attributes.
......@@ -556,12 +620,18 @@ class SiRealList(AbstractValueType):
if not goalType:
goalType = self.originType
if not goalType in ["si:real", "si:constant", "si:realListXMLList"]:
warnings.warn(f"Unknown goal type {goalType}, will use si:realListXMLList instead.")
warnings.warn(
f"Unknown goal type {goalType}, will use si:realListXMLList instead."
)
goalType = "si:realListXMLList"
if not self.hasValidData():
raise AttributeError(f"Length of attributes must be 1 or equal to data!\n Data length: {len(self)}; label length: {len(self.label)}; unit length: {len(self.unit)}; dateTime length: {len(self.dateTime)}")
raise AttributeError(
f"Length of attributes must be 1 or equal to data!\n Data length: {len(self)}; label length: {len(self.label)}; unit length: {len(self.unit)}; dateTime length: {len(self.dateTime)}"
)
if len(self) != 1 and goalType != "si:realListXMLList":
warnings.warn("Data list longer than 1, can only serialize to list formats. Will use si:realListXMLList.")
warnings.warn(
"Data list longer than 1, can only serialize to list formats. Will use si:realListXMLList."
)
goalType = "si:realListXMLList"
resultDict = {}
if self.label:
......@@ -594,12 +664,12 @@ def parseSingleValue(jsonDict: dict, relativeUncertainty: dict = None):
if "si:label" in jsonDict.keys(): # optional
siRealListArgs["label"] = ensureList(jsonDict["si:label"])
siRealListArgs["unit"] = ensureList(jsonDict["si:unit"]) # required
siRealListArgs["unit"] = ensureList(jsonDict["si:unit"]) # required
if "si:dateTime" in jsonDict.keys(): # optional
siRealListArgs["dateTime"] = ensureList(jsonDict["si:dateTime"])
data = ensureList(jsonDict["si:value"]) # required
data = ensureList(jsonDict["si:value"]) # required
siRealListArgs["data"] = parseUncertainties(
jsonDict=jsonDict, data=data, relativeUncertainty=relativeUncertainty
)
......@@ -692,7 +762,9 @@ def _parseData(data):
if isinstance(data, list) or isinstance(data, np.ndarray):
if isListOfTypes(data, [tuple]):
try:
return np.array([ufloat(value=value, stdunc=unc) for value, unc in data])
return np.array(
[ufloat(value=value, stdunc=unc) for value, unc in data]
)
except:
raise ValueError(
f"List of tuples did not match expected format. Expecting tuples of form (value, unc). Data given was {data}"
......@@ -721,10 +793,12 @@ def _parseData(data):
and isListOfTypes(values, [int, float, np.integer, np.floating])
and isListOfTypes(uncs, [int, float, np.integer, np.floating])
):
return np.array([
ufloat(value=value, stdunc=unc)
for value, unc in zip(values, uncs)
])
return np.array(
[
ufloat(value=value, stdunc=unc)
for value, unc in zip(values, uncs)
]
)
elif isinstance(data, tuple):
if len(data) == 2:
values, uncs = data
......@@ -737,9 +811,12 @@ def _parseData(data):
and isListOfTypes(values, [int, float, np.integer, np.floating])
and isListOfTypes(uncs, [int, float, np.integer, np.floating])
):
return np.array([
ufloat(value=value, stdunc=unc) for value, unc in zip(values, uncs)
])
return np.array(
[
ufloat(value=value, stdunc=unc)
for value, unc in zip(values, uncs)
]
)
elif isinstance(data, dict):
valueKeys = [key for key in data.keys() if re.match(r"^value", key)]
uncKeys = [key for key in data.keys() if re.match(r"^unc", key)]
......@@ -757,7 +834,7 @@ def _parseData(data):
):
result = []
for value, unc in zip(values, uncs):
result.append(ufloat(value,unc))
result.append(ufloat(value, unc))
return np.array(result)
else:
raise ValueError(
......@@ -766,5 +843,3 @@ def _parseData(data):
raise ValueError(
"Could not parse data. Valid formats are 1-dimensional list (of unc objects or values without unc), list of tuples (value, unc), tuple of lists ([value1, ...], [unc1, ...]), dict {value: [...], unc: [...]}, and 2-dimensional array [[value1, ...], [unc1, ...]]"
)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment