Source code for apbs.input_file.calculate.generic

"""Generic objects for polar solvation calculations.

This only contains common properties that are complex objects (e.g., not
strings, lists, etc.)
"""
import logging
from .. import check
from .. import InputFile


_LOGGER = logging.getLogger(__name__)


[docs]class Ion(InputFile): """Description of a single mobile ion species. Objects can be initialized with dictionary/JSON/YAML data with the following keys: * ``charge``: charge of ion; see :func:`charge` * ``radius``: radius of ion; see :func:`radius` * ``concentration``: concentration of ion species; see :func:`concentration` """ def __init__(self, dict_=None, json=None, yaml=None): self._charge = None self._radius = None self._concentration = None super().__init__(dict_=dict_, yaml=yaml, json=json)
[docs] def to_dict(self) -> dict: return { "charge": self.charge, "radius": self.radius, "concentration": self.concentration, }
[docs] def from_dict(self, dict_): """Populate object from dictionary. :raises KeyError: if dictionary missing information """ self.charge = dict_["charge"] self.radius = dict_["radius"] self.concentration = dict_["concentration"]
[docs] def validate(self): _ = self.charge _ = self.radius _ = self.concentration
@property def charge(self) -> float: """The charge (in electrons) of the ion. :raises TypeError: if set to something that is not a number """ if self._charge is None: raise TypeError("None is not a number.") return self._charge @charge.setter def charge(self, value): if not check.is_number(value): raise TypeError(f"Value {value} is not a number.") self._charge = value @property def radius(self) -> float: """The radius (in Å) of the ion. :raises TypeError: if set to something that is not a positive number """ if self._radius is None: raise TypeError("None is not a number.") return self._radius @radius.setter def radius(self, value): if not check.is_positive_definite(value): raise TypeError(f"Value {value} is not a positive number.") self._radius = value @property def concentration(self) -> float: """Concentration (in M) of ion species. :raises TypeError: if not a positive number or zero """ if self._concentration is None: raise TypeError("None is not a number.") return self._concentration @concentration.setter def concentration(self, value): if not check.is_positive_semidefinite(value): raise TypeError( f"Value {value} is not a positive semi-definite number." ) self._concentration = value
[docs]class MobileIons(InputFile): """Provide information about mobile ion species in system. Objects can be initialized with dictionary/JSON/YAML data with the following keys: * ``species``: list of ion species; see :func:`species` """ def __init__(self, dict_=None, json=None, yaml=None): self._species = [] super().__init__(dict_=dict_, yaml=yaml, json=json) @property def species(self) -> list: """List of mobile ion species. :returns: list of :class:`Ion` objects :raises TypeError: if set to something other than a list of :class:`Ion` objects """ return self._species @species.setter def species(self, value): if not isinstance(value, (list, tuple)): raise TypeError( f"Value {value} (type {type(value)}) is not a list." ) for elem in value: if not isinstance(elem, Ion): raise TypeError( f"List element {elem} (type {type(elem)}) is not of class " f"Ion." ) self._species.append(elem)
[docs] def from_dict(self, dict_): for elem in dict_.get("species", []): self._species.append(Ion(dict_=elem))
[docs] def to_dict(self) -> dict: return {"species": [elem.to_dict() for elem in self.species]}
[docs] def validate(self): net_charge = 0.0 for ion in self.species: ion.validate() net_charge += ion.charge * ion.concentration if net_charge != 0.0: raise ValueError( f"The net mobile ion charge ({net_charge} e) is not zero." )
[docs]class WriteMap(InputFile): """Write the specified property to a map. Objects can be initialized with dictionary/JSON/YAML data with the following keys: * ``property``: what property is being written to the map; see :func:`property` * ``format``: output format; see :func:`format` * ``path``: a suggested path and file name for the map; see :func:`path` """ def __init__(self, dict_=None, yaml=None, json=None): self._property = None self._format = None self._path = None super().__init__(dict_=dict_, yaml=yaml, json=json)
[docs] def from_dict(self, input_): self.property = input_["property"] self.format = input_["format"] self.path = input_["path"]
[docs] def to_dict(self) -> dict: return { "property": self.property, "format": self.format, "path": self.path, }
[docs] def validate(self): errors = [] if self.property is None: errors.append("property not set.") if self.format is None: errors.append("format not set.") if self.path is None: errors.append("path not set.") if errors: raise ValueError(" ".join(errors))
@property def path(self) -> str: """Suggested path for writing results. This path is only a suggestion; if parallel calculations are performed, then the filename will be modified to include the processor number for the output. :raises TypeError: if not set to string. """ return self._path @path.setter def path(self, value): if not check.is_string(value): raise TypeError( f"Value {value} (type {type(value)}) is not a string." ) self._path = value @property def format(self) -> str: """Format for writing output. Allowed formats (see documentation for details) include: * ``dx``: OpenDX-format data. This is the preferred format for APBS input/output. * ``dx.gz``: GZipped OpenDX-format data. * ``flat``: Write out data as a plain text file. * ``uhbd``: UHBD-format data. :raises TypeError: if not set to a strinng :raises ValueError: if invalid format specified """ return self._format @format.setter def format(self, value): if not check.is_string(value): raise TypeError( f"Value {value} (type {type(value)}) is not a string." ) value = value.lower() if value not in ["dx", "dx.gz", "flat", "uhbd"]: raise ValueError(f"Value {value} is not an allowed format.") self._format = value @property def property(self) -> str: """Property to write to map. See the documentation for a discussion of units for these properties. One of: * ``charge density``: Write out the biomolecular charge distribution in units of :math:`e_c` (electron charge) per Å\\ :sup:`3`. * ``potential``: Write out the electrostatic potential over the entire problem domain in units of :math:`k_b \\, T \\, e_c^{-1}`. * ``solvent accessibility``: Write out the solvent accessibility defined by the molecular surface definition (see :func:`FiniteDifference.surface_definition`). Values are unitless and range from 0 (inaccessible) to 1 (accessible). * ``ion accessibility``: Write out the inflated van der Waals-based ion accessibility (see :func:`FiniteDifference.surface_definition`). Values are unitless and range from 0 (inaccessible) to 1 (accessible). * ``laplacian``: Write out the Laplacian of the potential :math:`\\nabla^2 \\phi` in units of k\\ :sub:`B` T e\\ :sub:`c`\\ :sup:`-1` Å\\ :sup:`-2`. * ``energy density``: Write out the "energy density" :math:`-\\nabla \\cdot \\epsilon \\nabla \\phi` in units of k\\ :sub:`B` T e\\ :sub:`c`\\ :sup:`-1` Å\\ :sup:`-2`. * ``ion number density``: Write out the total mobile ion number density for all ion species in units of M. The output is calculated according to the formula (for nonlinear PB calculations): :math:`\\rho(x) = \\sum_i^N {\\bar{\\rho}_i e^{-q_i\\phi(x) - V_i (x)}}`, where :math:`N` is the number of ion species, :math:`\\bar{\\rho}_i` is the bulk density of ion species :math:`i`, :math:`q_i` is the charge of ion species :math:`i`, :math:`\\phi(x)` is the electrostatic potential, and :math:`V_i` is the solute-ion interaction potential for species :math:`i`. * ``ion charge density``: Write out the total mobile ion charge density for all ion species in units of e\\ :sub:`c` M. The output is calculated according to the formula (for nonlinear PB calculations): :math:`\\rho(x) = \\sum_i^N {\\bar{\\rho}_i q_i e^{-q_i\\phi(x) - V_i(x)}}`, where :math:`N` is the number of ion species, :math:`\\bar{\\rho}_i` is the bulk density of ion species :math:`i`, :math:`q_i` is the charge of ion species :math:`i`, :math:`\\phi(x)` is the electrostatic potential, and :math:`V_i` is the solute-ion interaction potential for species :math:`i`. * ``dielectric x`` or ``dielectric y`` or ``dielectric z``: Write out the dielectric map shifted by 1/2 grid spacing in the {``x``, ``y``, ``z``}-direction. The values are unitless. :raises TypeError: if not a string :raises ValueError: if invalid value """ return self._property @property.setter def property(self, value): if not check.is_string(value): raise TypeError( f"Value {value} (type {type(value)}) is not a string." ) value = value.lower() if value not in [ "charge density", "potential", "atom potential", "solvent accessibility", "ion accessibility", "laplacian", "energy density", "ion number density", "ion charge density", "dielectric x", "dielectric y", "dielectric z", ]: raise ValueError(f"Property {value} is invalid.") self._property = value
[docs]class UseMap(InputFile): """Use a previously read in map. Objects can be initialized with dictionary/JSON/YAML data with the following keys: * ``property``: what property being loaded from the map; see :func:`property` * ``alias``: alias assigned when reading in map; see :func:`alias` """ def __init__(self, dict_=None, yaml=None, json=None): self._property = None self._alias = None super().__init__(dict_=dict_, yaml=yaml, json=json)
[docs] def from_dict(self, input_): """Load object from dictionary. :raises KeyError: if missing items """ self.property = input_["property"] self.alias = input_["alias"]
[docs] def to_dict(self) -> dict: return {"property": self.property, "alias": self.alias}
[docs] def validate(self): errors = [] if self.property is None: errors.append("property not set.") if self.alias is None: errors.append("alias not set.") if errors: raise ValueError(" ".join(errors))
@property def property(self) -> str: """Specify the property being read from the map. One of the following values: * ``dielectric``: Dielectric function map (as read in :ref:`read_new_input`); this causes the :func:`FiniteDifference.solute_dielectric`, :func:`FiniteDifference.solvent_dielectric`, :func:`FiniteDifference.solvent_radius`, :func:`FiniteDifference.surface_method`, and :func:`FiniteDifference.surface spline window` properties to be ignored, along with the radii of the solute atoms. Note that :func:`FiniteDifference.solute_dielectric` and :func:`FiniteDifference.solvent_dielectric` are still used for some boundary condition calculations (see :func:`FiniteDifference.boundary_condition`) * ``ion accessibility``: Mobile ion-accessibility function map (as read in :ref:`read_new_input`); this causes the :func:`FiniteDifference.surface_method`, and :func:`FiniteDifference.surface spline window` properties to be ignored, along with the radii of the solute atoms. The :func:`FiniteDifference.ions` property is not ignored and will still be used. * ``charge density``: Charge distribution map (as read in :ref:`read_new_input`); this causes the :func:`charge discretization` parameter and the charges of the biomolecular atoms to be ignored when assembling the fixed charge distribution for the Poisson-Boltzmann equation. * ``potential``: Potential map (as read in :ref:`read_new_input`); this is used to set the boundary condition and causes the :func:`boundary_condition` property to be ignored. :raises TypeError: if not string :raises ValueError: if not valid value """ return self._property @property.setter def property(self, value): if not check.is_string(value): raise TypeError( f"Value {value} (type {type(value)}) is not a string." ) value = value.lower() if value not in [ "dielectric", "ion accessibility", "charge density", "potential", ]: raise ValueError(f"Value {value} is not valid.") self._property = value