File: base.py

package info (click to toggle)
mdanalysis 2.10.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 116,696 kB
  • sloc: python: 92,135; ansic: 8,156; makefile: 215; sh: 138
file content (228 lines) | stat: -rw-r--r-- 6,993 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
# -*- Mode: python; tab-width: 4; indent-tabs-mode:nil; coding: utf-8 -*-
# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4
#
# MDAnalysis --- https://www.mdanalysis.org
# Copyright (c) 2006-2017 The MDAnalysis Development Team and contributors
# (see the file AUTHORS for the full list of names)
#
# Released under the Lesser GNU Public Licence, v2 or any higher version
#
# Please cite your use of MDAnalysis in published work:
#
# R. J. Gowers, M. Linke, J. Barnoud, T. J. E. Reddy, M. N. Melo, S. L. Seyler,
# D. L. Dotson, J. Domanski, S. Buchoux, I. M. Kenney, and O. Beckstein.
# MDAnalysis: A Python package for the rapid analysis of molecular dynamics
# simulations. In S. Benthall and S. Rostrup editors, Proceedings of the 15th
# Python in Science Conference, pages 102-109, Austin, TX, 2016. SciPy.
# doi: 10.25080/majora-629e541a-00e
#
# N. Michaud-Agrawal, E. J. Denning, T. B. Woolf, and O. Beckstein.
# MDAnalysis: A Toolkit for the Analysis of Molecular Dynamics Simulations.
# J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787
#
"""
Base guesser classes --- :mod:`MDAnalysis.guesser.base`
================================================================

Derive context-specific guesser classes from the base class in this module.

Classes
-------

.. autoclass:: GuesserBase
   :members:
   :inherited-members:

.. autofunction:: get_guesser

"""
from .. import _GUESSERS, _TOPOLOGY_ATTRS
from ..core.topologyattrs import _Connection
import numpy as np
import logging
from typing import Dict
import copy

logger = logging.getLogger("MDAnalysis.guesser.base")


class _GuesserMeta(type):
    """Internal: guesser classes registration

    When classes which inherit from GuesserBase are *defined*
    this metaclass makes it known to MDAnalysis.  'context'
    attribute  are read:
     - `context` defines the context of the guesser class for example:
       forcefield specific context  as MartiniGuesser
       and file specific context as PDBGuesser.

    Eg::

      class FooGuesser(GuesserBase):
          format = 'foo'

    .. versionadded:: 2.8.0
    """

    def __init__(cls, name, bases, classdict):
        type.__init__(type, name, bases, classdict)

        _GUESSERS[classdict["context"].upper()] = cls


class GuesserBase(metaclass=_GuesserMeta):
    """Base class for context-specific guessers to inherit from

    Parameters
    ----------
    universe : Universe, optional
        Supply a Universe to the Guesser. This then becomes the source of atom
        attributes to be used in guessing processes. (this is relevant to how
        the universe's guess_TopologyAttrs API works.
        See :meth:`~MDAnalysis.core.universe.Universe.guess_TopologyAttrs`).
    **kwargs : dict, optional
        To pass additional data to the guesser that can be used with
              different methods.


    .. versionadded:: 2.8.0

    """

    context = "base"
    _guesser_methods: Dict = {}

    def __init__(self, universe=None, **kwargs):
        self._universe = universe
        self._kwargs = kwargs

    def update_kwargs(self, **kwargs):
        self._kwargs.update(kwargs)

    def copy(self):
        """Return a copy of this Guesser"""
        kwargs = copy.deepcopy(self._kwargs)
        new = self.__class__(universe=None, **kwargs)
        return new

    def is_guessable(self, attr_to_guess):
        """check if the passed atrribute can be guessed by the guesser class

        Parameters
        ----------
        guess: str
            Attribute to be guessed then added to the Universe

        Returns
        -------
        bool
        """
        if attr_to_guess.lower() in self._guesser_methods:
            return True

        return False

    def guess_attr(self, attr_to_guess, force_guess=False):
        """map the attribute to be guessed with the apporpiate guessing method

        Parameters
        ----------
        attr_to_guess: str
            an atrribute to be guessed then to be added to the universe
        force_guess: bool
            To indicate wether to only partialy guess the empty values of the
            attribute or to overwrite all existing values by guessed one

        Returns
        -------
        NDArray of guessed values

        """
        try:
            top_attr = _TOPOLOGY_ATTRS[attr_to_guess]
        except KeyError:
            raise KeyError(
                f"{attr_to_guess} is not a recognized MDAnalysis "
                "topology attribute"
            )
        # make attribute to guess plural
        attr_to_guess = top_attr.attrname

        try:
            guesser_method = self._guesser_methods[attr_to_guess]
        except KeyError:
            raise ValueError(
                f"{type(self).__name__} cannot guess this "
                f"attribute: {attr_to_guess}"
            )

        # Connection attributes should be just returned as they are always
        # appended to the Universe. ``force_guess`` handling should happen
        # at Universe level.
        if issubclass(top_attr, _Connection):
            return guesser_method()

        # check if the topology already has the attribute to partially guess it
        if hasattr(self._universe.atoms, attr_to_guess) and not force_guess:
            attr_values = np.array(
                getattr(self._universe.atoms, attr_to_guess, None)
            )

            empty_values = top_attr.are_values_missing(attr_values)

            if True in empty_values:
                # pass to the guesser_method boolean mask to only guess the
                # empty values
                attr_values[empty_values] = guesser_method(
                    indices_to_guess=empty_values
                )
                return attr_values

            else:
                logger.info(
                    f"There is no empty {attr_to_guess} values. Guesser did "
                    f"not guess any new values for {attr_to_guess} attribute"
                )
                return None
        else:
            return np.array(guesser_method())


def get_guesser(context, u=None, **kwargs):
    """get an appropiate guesser to the Universe and pass
       the Universe to the guesser

    Parameters
    ----------
    u: Universe
        to be passed to the guesser
    context: str or Guesser
    **kwargs : dict, optional
        Extra arguments are passed to the guesser.

    Returns
    -------
    Guesser class

    Raises
    ------
    * :exc:`KeyError` upon failing to return a guesser class

    .. versionadded:: 2.8.0

    """
    if isinstance(context, GuesserBase):
        context._universe = u
        context.update_kwargs(**kwargs)
        return context
    try:
        if issubclass(context, GuesserBase):
            return context(u, **kwargs)
    except TypeError:
        pass

    try:
        guesser = _GUESSERS[context.upper()](u, **kwargs)
    except KeyError:
        raise KeyError("Unidentified guesser type {0}".format(context))
    return guesser