from math import inf, isnan
from copy import copy
import logging
from numpy import any
from pint import DimensionalityError
from . import ureg
from .errors import OutOfRangeError
[docs]class Linked:
def __init__(self, unit='dimensionless', rng=[-inf, inf], linked_properties={}):
self.key = ''
self.subject_key = ''
self.property = ''
self.unit = ureg.parse_units(unit)
self.rng = rng * self.unit
self._linked_properties = linked_properties
self._args = dict(
zip(list(linked_properties.keys()), [set(args.values()) for args in linked_properties.values()]))
self._depended_on = list(set([y for x in self._args.values() for y in x]))
def __get__(self, instance, owner):
if hasattr(instance, self.key):
value = getattr(instance, self.key)
return value
else:
return None
def __set__(self, instance, value):
upd = set()
if isinstance(value, set):
upd = value
value = getattr(instance, self.property)
sorted_fun = Linked.get_sorted_functions(upd, self._args)
for func in sorted_fun:
kwargs = copy(self._linked_properties[func])
for arg in kwargs.keys():
kwargs[arg] = getattr(instance, kwargs[arg])
if any([x is None for x in kwargs.values()]):
continue
value = func(**kwargs)
logging.debug('Property {} calculated'.format(self.property))
break
if type(getattr(instance, self.property)) != type(value) or (
getattr(instance, self.property) != value and value is not None):
try:
if isinstance(value, ureg.Quantity):
value = value.to(self.unit)
except DimensionalityError as e:
raise DimensionalityError(e.units1, e.units2, e.dim1, e.dim2,
'Wrong dimensions when setting {} with value {}'.format(
self.property, value))
if not Linked.in_range(value, self.rng):
raise OutOfRangeError(value, self.rng, self.property)
setattr(instance, self.key, value)
if self.property not in upd:
upd.add(self.property)
getattr(instance, self.subject_key).send(instance, upd)
[docs] @staticmethod
def in_range(value, rng):
if isinstance(value, ureg.Quantity):
return any(((rng[0].m <= value.m) & (rng[1].m >= value.m))) or any(isnan(value.m))
else:
return any(((rng[0].m <= value) & (rng[1].m >= value))) or any(isnan(value))
[docs] @staticmethod
def argument_weight(visited, arg):
return len(visited.intersection(arg)) / len(arg)
[docs] @staticmethod
def get_sorted_functions(visited, args):
return [func[0] for func in sorted(args.items(), key=lambda value: Linked.argument_weight(visited, value[1]))]