"""These classes provide information about processing APBS output.
The top-level output summarizes or combines results from multiple calculations.
"""
import logging
# from typing import Type
from . import InputFile
from . import check
_LOGGER = logging.getLogger(__name__)
[docs]
class Process(InputFile):
"""Arithmetic operations for output from APBS energy/force calculations.
Objects can be initialized with dictionary/JSON/YAML data with the
following keys:
* ``sums``: add elements together. This is a list of :class:`Operation`
objects.
* ``products``: multiply elements together. This is a list of
:class:`Operation` objects.
* ``exps``: element-wise exponentiation of elements. This is a list of
:class:`Operation` objects.
"""
def __init__(self, dict_=None, yaml=None, json=None):
self._sums = []
self._products = []
self._exps = []
super().__init__(dict_=dict_, yaml=yaml, json=json)
@property
def sums(self) -> list:
"""List of sum :class:`Operation` objects.
:raises TypeError: if list contains something other than
class:`Operation` objects
"""
return self._sums
@sums.setter
def sums(self, list_):
for elem in list_:
if not isinstance(elem, Operation):
raise TypeError(f"Found {type(elem)} object in list.")
self._sums = list_
@property
def products(self) -> list:
"""List of product :class:`Operation` objects.
:raises TypeError: if list contains something other than
class:`Operation` objects
"""
return self._products
@products.setter
def products(self, list_):
for elem in list_:
if not isinstance(elem, Operation):
raise TypeError(f"Found {type(elem)} object in list.")
self._products = list_
@property
def exps(self) -> list:
"""List of exponential :class:`Operation` objects.
:raises TypeError: if list contains something other than
class:`Operation` objects
"""
return self._exps
@exps.setter
def exps(self, list_):
for elem in list_:
if not isinstance(elem, Operation):
raise TypeError(f"Found {type(elem)} object in list.")
self._exps = list_
[docs]
def validate(self):
"""Validate contents of object.
:raises ValueError: if invalid object encountered
"""
errors = []
for obj in self.sums + self.products + self.exps:
try:
obj.validate()
except ValueError as error:
errors.append(
f"Encountered invalid object of type {type(obj)}: {error}."
)
if errors:
errors = " ".join(errors)
raise ValueError(errors)
[docs]
def from_dict(self, input_):
"""Populate object from dictionary.
:param dict input_: input information
:raises KeyError: if elements are missing from the input dictionary
"""
_LOGGER.debug(input_["sums"])
self.sums = [Operation(dict_=dict_) for dict_ in input_["sums"]]
self.products = [
Operation(dict_=dict_) for dict_ in input_["products"]
]
self.exps = [Operation(dict_=dict_) for dict_ in input_["exps"]]
[docs]
def to_dict(self) -> dict:
dict_ = {}
dict_["sums"] = [elem.to_dict() for elem in self.sums]
dict_["products"] = [elem.to_dict() for elem in self.products]
dict_["exps"] = [elem.to_dict() for elem in self.exps]
return dict_
[docs]
class Operation(InputFile):
"""Generic arithmetic operation.
Objects can be initialized with dictionary/JSON/YAML data with the
following keys:
* ``alias``: string for referring to the output of this operation
elsewhere
* ``elements``: a list of elements for the operation; see :class:`Element`
for details
"""
def __init__(self, dict_=None, yaml=None, json=None):
self._alias = None
self._elements = []
super().__init__(dict_=dict_, yaml=yaml, json=json)
@property
def alias(self) -> str:
"""String for referring to the output of this operation elsewhere.
:raises TypeError: if alias is not a string
"""
return self._alias
@alias.setter
def alias(self, value):
if check.is_string(value):
self._alias = value
else:
raise TypeError(f"{value} is not a string.")
@property
def elements(self) -> list:
"""List of :class:`Element` objects for operation.
:raises TypeError: if list contains objects not :class:`Element`
"""
return self._elements
@elements.setter
def elements(self, list_):
for elem in list_:
if not isinstance(elem, Element):
raise TypeError(f"Found {type(elem)} in list.")
self._elements = list_
[docs]
def from_dict(self, dict_):
"""Load object contents from dictionary.
:param dict dict_: dictionary with object contents
:raises KeyError: if dictionary elements missing
"""
self.alias = dict_["alias"]
self.elements = [Element(dict_=edict) for edict in dict_["elements"]]
[docs]
def to_dict(self) -> dict:
dict_ = {"alias": self.alias}
dict_["elements"] = [element.to_dict() for element in self.elements]
return dict_
[docs]
def validate(self):
"""Validate object.
:raises ValueError: if object contents are invalid
"""
errors = []
if self._alias is None:
errors.append("Operation alias cannot be None.")
for element in self._elements:
try:
element.validate()
except ValueError as error:
errors.append(f"Unable to validate element: {error}.")
if errors:
errors = " ".join(errors)
raise ValueError(errors)
[docs]
class Element(InputFile):
"""Element of an arithmetic operation.
Objects can be initialized with dictionary/JSON/YAML data with a list of
the following keys:
* ``alias``: alias for quantity on which to operate
* ``coefficient``: multiplicative coefficient for quantity (e.g., -1.0 to
convert a sum to difference)
"""
def __init__(self, dict_=None, yaml=None, json=None):
self._coefficient_ = None
self._alias = None
super().__init__(dict_=dict_, yaml=yaml, json=json)
@property
def coefficient(self) -> float:
"""Return coefficient for this element.
:raises TypeError: if not a number
"""
return self._coefficient
@coefficient.setter
def coefficient(self, value):
if check.is_number(value):
self._coefficient = value
else:
raise TypeError(f"{value} is not a number.")
@property
def alias(self) -> str:
"""Return alias for object to which this element refers.
:raises TypeError: if alias is not a string.
"""
return self._alias
@alias.setter
def alias(self, value):
if check.is_string(value):
self._alias = value
else:
raise TypeError(f"{value} is not a string.")
[docs]
def validate(self):
errors = []
if self.coefficient is None:
errors.append("Coefficient not set.")
if self.alias is None:
errors.append("Alias not set")
if errors:
error = " ".join(errors)
raise ValueError(error)
[docs]
def from_dict(self, input_):
"""Load object contents from dictionary.
:param dict input_: dictionary with object contents
:raises KeyError: if dictionary elements missing
"""
self.alias = input_["alias"]
self.coefficient = input_["coefficient"]
[docs]
def to_dict(self) -> dict:
return {"alias": self.alias, "coefficient": self.coefficient}