Source code for MDMC.MD.solvents.solvents

"""Contains helper functions for initiating ``SolventConfig`` subclasses for
solvents

It has two public functions, ``get_solvent_names`` and ``get_solvent_config``,
for initating ``SolventConfig``. It has a number of private functions for
finding ``SolventConfig`` subclasses and ``WaterModel`` subclasses which can be
used as solvents."""

from contextlib import suppress
from glob import glob
from importlib import import_module
from inspect import getmembers, isabstract, isclass
from os.path import basename, dirname, isfile, join

from MDMC.MD import force_fields
from MDMC.MD.force_fields.ff import WaterModel
from MDMC.MD.solvents._solvent_config import SolventConfig
from MDMC.MD.solvents.SPC_config import SPCConfig


def _get_water_models():
    """
    Gets a `list` of ``WaterModel`` force fields that exist

    Returns
    -------
    list
        A `list` of classes which inherit from ``WaterModel``
    """

    force_fields_dir = dirname(force_fields.__file__)
    modules = [import_module('.' + basename(f)[:-3], force_fields.__name__)
               for f in glob(join(force_fields_dir, "*.py"))
               if isfile(f) and not f.startswith('_')]

    w_models = []
    for module in modules:
        # try/except for modules which have no subclasses of SolventConfig and
        # so return an empty list
        with suppress(IndexError):
            w_models.append(getmembers(module,
                                       lambda m: (isclass(m)
                                                  and not isabstract(m)
                                                  and issubclass(m,
                                                                 WaterModel)
                                                  ))[0][1])

    return w_models


def _get_water_model_configs():
    """
    Gets the ``SolventConfig`` for each ``WaterModel``

    This is required because every water model does not have a unique
    ``SolventConfig``. For example, all 3 body water models use ``SPC_config``.

    Returns
    -------
    dict
        {``w_model``: ``solvent_config``} pairs, where each ``w_model`` is a
        `str` specifying an available ``WaterModel``, and ``solvent_config`` is
        the ``SolventConfig`` class that will be used for sovlating with that
        ``WaterModel``.
    """

    w_model_configs = {}
    for w_model in _get_water_models():
        # For 3 body water models, use the SPC216 configuration. This is
        # reasonable because the 3 body models are sufficiently similar that
        # it is assumed that the SPC216 config will require minimal
        # equilibration when used with another 3 body model.
        if w_model.n_body == 3:
            w_model_configs[w_model.__name__] = SPCConfig

    return w_model_configs


def _get_solvent_configs():
    """
    Gets a `dict` of the names of ``SolventConfig`` and their classes

    Returns
    -------
    dict
        {``name``: ``solvent_config``} pairs, where each ``name`` is a `str`
        specifying an available ``SolventConfig`` subclass, and
        ``solvent_config`` is the corresponding class.
    """

    # Import all modules in same directory, except this one
    modules = [import_module('.' + basename(f)[:-3], __package__) for f
               in glob(join(dirname(__file__), "*.py"))
               if isfile(f) and not f.startswith('_') and f != __file__]
    # Get members of all modules if they are solvent_configs (i.e. they are
    # subclasses of SolventConfig)
    s_configs = []
    for module in modules:
        # try/except for modules which have no subclasses of SolventConfig and
        # so return an empty list
        with suppress(IndexError):
            s_configs.append(getmembers(module,
                                        lambda m: (isclass(m)
                                                   and not isabstract(m)
                                                   and issubclass(m,
                                                                  SolventConfig)
                                                   ))[0][1])
    return {s_config.__name__: s_config for s_config in s_configs}


[docs] def get_solvent_config(name): """ Gets the ``solvent_config`` for a solvent Parameters ---------- name : str The name of the solvent Returns ------- ``SolventConfig`` An object from a subclass of ``SolventConfig`` for the specified solvent name """ s_config = SOLVENT_CONFIGS.get(name+'Config', None) if s_config is None: try: s_config = WATER_MODELS[name] except KeyError as error: raise ValueError(f"{name} is not an inbuilt solvent. The inbuilt" f" solvents are: {get_solvent_names()}") from error return s_config()
[docs] def get_solvent_names(): """ Get the names of the inbuilt solvents which can be passed as parameters to ``get_solvent_config`` Returns ------- list A `list` of `str` with the names of the inbuilt solvents """ return list(set(list(WATER_MODELS.keys()) + [name.replace('Config', '') for name in _get_solvent_configs()]))
SOLVENT_CONFIGS = _get_solvent_configs() WATER_MODELS = _get_water_model_configs()