Skip to content

Commit

Permalink
add to/from json. closed #17
Browse files Browse the repository at this point in the history
  • Loading branch information
philopon committed Jun 9, 2017
1 parent a9435f2 commit 13e1bf7
Show file tree
Hide file tree
Showing 5 changed files with 211 additions and 45 deletions.
15 changes: 8 additions & 7 deletions mordred/CPSA.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,28 +208,29 @@ def _SA(self):
return PPSA(self._version)


class WxSAMixin(object):
def calculate(self, SA, ASA):
return SA * np.sum(ASA) / 1000.0


class WNSA(WxSAMixin, FNSA):
class WNSA(FNSA):
r"""surface weighted charged partial negative surface area descriptor.
:type version: int
:param version: one of :py:attr:`versions`
"""
__slots__ = ()

def calculate(self, SA, ASA):
return SA * np.sum(ASA) / 1000.0


class WPSA(WxSAMixin, FPSA):
class WPSA(FPSA):
r"""surface weighted charged partial positive surface area descriptor.
:type version: int
:param version: one of :py:attr:`versions`
"""
__slots__ = ()

def calculate(self, SA, ASA):
return SA * np.sum(ASA) / 1000.0


class RNCG(CPSABase):
r"""relative negative charge descriptor."""
Expand Down
54 changes: 53 additions & 1 deletion mordred/_base/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from importlib import import_module
from ..error import MissingValueBase

from .descriptor import Descriptor
from .descriptor import Descriptor, UnaryOperatingDescriptor, ConstDescriptor, BinaryOperatingDescriptor
from .calculator import Calculator, get_descriptors_from_module
from .parallel import parallel

Expand Down Expand Up @@ -50,6 +50,58 @@ def _Descriptor__call__(self, mol, id=-1):

return v

def _from_json(obj, descs):
name = obj.get('name')
args = obj.get('args') or {}
if name is None:
raise ValueError('invalid json: {}'.format(obj))

if name == UnaryOperatingDescriptor.__name__:
return UnaryOperatingDescriptor(
args['name'],
args['operator'],
_from_json(args['value'])
)

elif name == BinaryOperatingDescriptor.__name__:
return BinaryOperatingDescriptor(
args['name'],
args['operator'],
_from_json(args['left']),
_from_json(args['right'])
)

cls = descs.get(name)
if cls is None:
raise ValueError('unknown class: {}'.format(name))

instance = cls(**(obj.get('args') or {}))
return instance


@classmethod
def _Descriptor_from_json(self, obj):
'''create Descriptor instance from json dict.
Parameters:
obj(dict): descriptor dict
Returns:
Descriptor: descriptor
'''
descs = getattr(self, '_all_descriptors', None)

if descs is None:
from mordred import descriptors
descs = {
cls.__name__: cls
for cls in get_descriptors_from_module(descriptors, submodule=True)
}
descs[ConstDescriptor.__name__] = ConstDescriptor
self._all_descriptors = descs

return _from_json(obj, descs)

Descriptor.__call__ = _Descriptor__call__
Descriptor.from_json = _Descriptor_from_json
Calculator._parallel = parallel
33 changes: 33 additions & 0 deletions mordred/_base/calculator.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,39 @@ def __setstate__(self, dict):
self._kekulizes = dict.get('_kekulizes', set([True, False]))
self._require_3D = dict.get('_require_3D', False)

@classmethod
def from_json(cls, obj):
'''create Calculator from json descriptor objects
Parameters:
obj(list or dict): descriptors to register
Returns:
Calculator: calculator
'''
calc = cls()
calc.register_json(obj)
return calc

def register_json(self, obj):
'''register Descriptors from json descriptor objects
Parameters:
obj(list or dict): descriptors to register
'''
if not isinstance(obj, list):
obj = [obj]

self.register(Descriptor.from_json(j) for j in obj)

def to_json(self):
'''convert descriptors to json serializable data
Returns:
list: descriptors
'''
return [d.to_json() for d in self.descriptors]

def __reduce_ex__(self, version):
return self.__class__, (), {
'_descriptors': self._descriptors,
Expand Down
144 changes: 107 additions & 37 deletions mordred/_base/descriptor.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,21 @@ def __init__(self, error):
self.error = error


class Descriptor(six.with_metaclass(ABCMeta, object)):
class DescriptorMeta(ABCMeta):
def __new__(cls, classname, bases, dict):
__init__ = dict.get('__init__')
if __init__ is None:
for base in bases:
__init__ = getattr(base, '__init__', None)
if __init__ is not None:
break

dict['parameter_names'] = tuple(inspect.getfullargspec(__init__).args[1:])

return ABCMeta.__new__(cls, classname, bases, dict)


class Descriptor(six.with_metaclass(DescriptorMeta, object)):
r"""abstract base class of descriptors.
Attributes:
Expand Down Expand Up @@ -53,6 +67,27 @@ def parameters(self):
'''
raise NotImplementedError('not implemented Descriptor.parameters method')

def get_parameter_dict(self):
return dict(zip(self.parameter_names, self.parameters()))

def to_json(self):
'''convert to json serializable dictionary.
Returns:
dict: dictionary of descriptor
'''
d, ps = self._to_json()
if len(ps) == 0:
return {'name': d}
else:
return {'name': d, 'args': ps}

def _to_json(self):
d = self.__class__.__name__
ps = self.get_parameter_dict()

return d, {k: getattr(v, 'as_argument', v) for k, v in ps.items()}

@abstractmethod
def calculate(self):
r"""[abstractmethod] calculate descriptor value.
Expand Down Expand Up @@ -171,29 +206,29 @@ def unary(self):
def _binary_common(name, operator):
def binary(self, other):
if not isinstance(other, Descriptor):
other = ConstDescriptor(str(other), other)
other = ConstDescriptor(other)

return BinaryOperatingDescriptor(name.format(self, other), operator, self, other)

return binary

__add__ = _binary_common('({}+{})', operator.add)
__sub__ = _binary_common('({}-{})', operator.sub)
__mul__ = _binary_common('({}*{})', operator.mul)
__truediv__ = _binary_common('({}/{})', operator.truediv)
__floordiv__ = _binary_common('({}//{})', operator.floordiv)
__mod__ = _binary_common('({}%{})', operator.mod)
__pow__ = _binary_common('({}**{})', operator.pow)
__add__ = _binary_common('({}+{})', '+')
__sub__ = _binary_common('({}-{})', '-')
__mul__ = _binary_common('({}*{})', '*')
__truediv__ = _binary_common('({}/{})', '/')
__floordiv__ = _binary_common('({}//{})', '//')
__mod__ = _binary_common('({}%{})', '%')
__pow__ = _binary_common('({}**{})', '**')

__neg__ = _unary_common('-{}', operator.neg)
__pos__ = _unary_common('+{}', operator.pos)
__abs__ = _unary_common('|{}|', operator.abs)
__neg__ = _unary_common('-{}', '-')
__pos__ = _unary_common('+{}', '+')
__abs__ = _unary_common('|{}|', 'abs')
__trunc__ = _unary_common('trunc({})', 'trunc')

if six.PY3:
__ceil__ = _unary_common('ceil({})', np.ceil)
__floor__ = _unary_common('floor({})', np.floor)
__ceil__ = _unary_common('ceil({})', 'ceil')
__floor__ = _unary_common('floor({})', 'floor')

__trunc__ = _unary_common('trunc({})', np.trunc)


def is_descriptor_class(desc):
Expand All @@ -214,24 +249,41 @@ class UnaryOperatingDescriptor(Descriptor):
def preset(cls):
return cls()

operators = {
'-': operator.neg,
'+': operator.pos,
'abs': operator.abs,
'trunc': np.trunc,
'ceil': np.ceil,
'floor': np.floor,
}

def parameters(self):
return self.name, self.operator, self.value
return self._name, self._operator, self._value

def __init__(self, name, operator, value):
self.name = name
self.operator = operator
self.value = value
self._name = name
self._operator = operator
self._fn = self.operators[operator]
self._value = value

def _to_json(self):
return self.__class__.__name__, {
'name': self._name,
'operator': self._operator,
'value': self._value.to_json(),
}

def __str__(self):
return self.name
return self._name

def dependencies(self):
return {
'value': self.value,
'value': self._value,
}

def calculate(self, value):
return self.operator(value)
return self._fn(value)


class ConstDescriptor(Descriptor):
Expand All @@ -240,41 +292,59 @@ def preset(cls):
return cls()

def parameters(self):
return self.name, self.value
return self._value,

def __init__(self, name, value):
self.name = name
self.value = value
def __init__(self, value):
self._value = value

def __str__(self):
return self.name
return str(self._value)

def calculate(self):
return self.value
return self._value


class BinaryOperatingDescriptor(Descriptor):
@classmethod
def preset(cls):
return cls()

operators = {
'+': operator.add,
'-': operator.sub,
'*': operator.mul,
'/': operator.truediv,
'//': operator.floordiv,
'%': operator.mod,
'**': operator.pow,
}

def _to_json(self):
return self.__class__.__name__, {
'name': self._name,
'operator': self._operator,
'left': self._left.to_json(),
'right': self._right.to_json(),
}

def parameters(self):
return self.name, self.operator, self.left, self.right
return self._name, self._operator, self._left, self._right

def __init__(self, name, operator, left, right):
self.name = name
self.operator = operator
self.left = left
self.right = right
self._name = name
self._operator = operator
self._fn = self.operators[operator]
self._left = left
self._right = right

def __str__(self):
return self.name
return self._name

def dependencies(self):
return {
'left': self.left,
'right': self.right,
'left': self._left,
'right': self._right,
}

def calculate(self, left, right):
return self.operator(left, right)
return self._fn(left, right)
10 changes: 10 additions & 0 deletions mordred/tests/test_json.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from nose.tools import eq_
import json
from mordred import Calculator, descriptors


def test_json():
calc = Calculator(descriptors)
j = json.dumps(calc.to_json())
calc2 = Calculator.from_json(json.loads(j))
eq_(calc.descriptors, calc2.descriptors)

0 comments on commit 13e1bf7

Please sign in to comment.