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
|
# -*- Mode: python; tab-width: 4; indent-tabs-mode:nil; coding:utf-8 -*-
# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 fileencoding=utf-8
#
# 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.1 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
#
"""AtomGroup accessors --- :mod:`MDAnalysis.core.accessors`
====================================================================
This module provides classes for accessing and converting :class:`~MDAnalysis.core.groups.AtomGroup`
objects. It is used for the :meth:`~MDAnalysis.core.groups.AtomGroup.convert_to`
method to make it usable in two different ways: ``ag.convert_to("PACKAGE")`` or
``ag.convert_to.package()``
Example
-------
.. code-block:: python
>>> class SpeechWrapper:
... def __init__(self, person):
... self.person = person
... def __call__(self, *args):
... print(self.person.name, "says", *args)
... def whoami(self):
... print("I am %s" % self.person.name)
...
>>> class Person:
... def __init__(self, name):
... self.name = name
... say = Accessor("say", SpeechWrapper)
...
>>> bob = Person("Bob")
>>> bob.say("hello")
Bob says hello
>>> bob.say.whoami()
I am Bob
Classes
-------
.. autoclass:: Accessor
:members:
.. autoclass:: ConverterWrapper
:members:
"""
from functools import partial, update_wrapper
from .. import _CONVERTERS
class Accessor:
"""Used to pass data between two classes
Parameters
----------
name : str
Name of the property in the parent class
accessor : class
A class that needs access to its parent's instance
Example
-------
If you want the property to be named "convert_to" in the AtomGroup class,
use:
.. code-block:: python
>>> class AtomGroup:
>>> # ...
>>> convert_to = Accessor("convert_to", ConverterWrapper)
And when calling ``ag.convert_to.rdkit()``, the "rdkit" method of the
ConverterWrapper will be able to have access to "ag"
.. versionadded:: 2.0.0
"""
def __init__(self, name, accessor):
self._accessor = accessor
self._name = name
def __get__(self, obj, cls):
if obj is None:
# accessing from class instead of instance
return self._accessor
# instances the accessor class with the parent object as argument
wrapped = self._accessor(obj)
# replace the parent object's property with the wrapped instance
# so we avoid reinstantiating the accessor everytime `obj.<_name>`
# is called
object.__setattr__(obj, self._name, wrapped)
return wrapped
class ConverterWrapper:
"""Convert :class:`AtomGroup` to a structure from another Python
package.
The converters are accessible to any AtomGroup through the ``convert_to``
property. `ag.convert_to` will return this ConverterWrapper, which can be
called directly with the name of the destination package as a string
(similarly to the old API), or through custom methods named after the
package (in lowercase) that are automatically constructed thanks to
metaclass magic.
Example
-------
The code below converts a Universe to a :class:`parmed.structure.Structure`
.. code-block:: python
>>> import MDAnalysis as mda
>>> from MDAnalysis.tests.datafiles import GRO
>>> u = mda.Universe(GRO)
>>> parmed_structure = u.atoms.convert_to('PARMED')
>>> parmed_structure
<Structure 47681 atoms; 11302 residues; 0 bonds; PBC (triclinic); NOT parametrized>
You can also directly use ``u.atoms.convert_to.parmed()``
Parameters
----------
package: str
The name of the package to convert to, e.g. ``"PARMED"``
*args:
Positional arguments passed to the converter
**kwargs:
Keyword arguments passed to the converter
Returns
-------
output:
An instance of the structure type from another package.
Raises
------
ValueError:
No converter was found for the required package
.. versionadded:: 1.0.0
.. versionchanged:: 2.0.0
Moved the ``convert_to`` method to its own class. The old API is still
available and is now case-insensitive to package names, it also accepts
positional and keyword arguments. Each converter function can also
be accessed as a method with the name of the package in lowercase, i.e.
`convert_to.parmed()`
"""
_CONVERTERS = {}
def __init__(self, ag):
"""
Parameters
----------
ag : AtomGroup
The AtomGroup to convert
"""
self._ag = ag
for lib, converter_cls in _CONVERTERS.items():
method_name = lib.lower()
# makes sure we always use the same instance of the converter
# no matter which atomgroup instance called it
try:
converter = self._CONVERTERS[method_name]
except KeyError:
converter = converter_cls().convert
# store in class attribute
self._CONVERTERS[method_name] = converter
# create partial function that passes ag to the converter
convert = partial(converter, self._ag)
# copy docstring and metadata to the partial function
# note: it won't work with help()
update_wrapper(convert, converter)
setattr(self, method_name, convert)
def __call__(self, package, *args, **kwargs):
try:
convert = getattr(self, package.lower())
except AttributeError:
raise ValueError(
f"No {package!r} converter found. Available: "
f"{' '.join(self._CONVERTERS.keys())}"
) from None
return convert(*args, **kwargs)
|