From 789fda1a6ae639d9319a1b34428ff3faa9cd392f Mon Sep 17 00:00:00 2001 From: Lily Wang Date: Sat, 8 Feb 2020 02:43:04 +1100 Subject: [PATCH 1/7] added trajectories --- doc/source/conf.py | 2 +- doc/source/trajectories.rst | 154 +++++++++++++++++++++++++++++++++++- 2 files changed, 151 insertions(+), 5 deletions(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index c649e829b..48c74133b 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -70,7 +70,7 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', '.ipynb_checkpoints', '**/.ipynb_checkpoints', 'scripts'] +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', '.ipynb_checkpoints', '**/.ipynb_checkpoints', 'scripts', '.*.ipynb', '.*'] # -- Options for HTML output ------------------------------------------------- diff --git a/doc/source/trajectories.rst b/doc/source/trajectories.rst index f59414975..a9924fd06 100644 --- a/doc/source/trajectories.rst +++ b/doc/source/trajectories.rst @@ -1,11 +1,157 @@ .. -*- coding: utf-8 -*- -=============== +============ Trajectories -=============== +============ -MDAnalysis groups dynamic data about a :code:`Universe` into its trajectory. This is typically loaded from a trajectory file. +In MDAnalysis, static data is contained in your universe Topology, while dynamic data is drawn from its trajectory at ``Universe.trajectory``. This is typically loaded from a trajectory file and includes information such as: -Trajectory transformations + * atom coordinates (``Universe.atoms.positions``) + * box size (``Universe.dimensions``) + * velocities and forces (if your file format contains the data) (``Universe.atoms.velocities``) + +Although these properties look static, they are actually dynamic, and the data contained within can change. +In order to remain memory-efficient, MDAnalysis does not load every frame of your trajectory into memory at once. Instead, a Universe has a state: the particular timestep that it is currently associated with in the trajectory. When the timestep changes, the data in the properties above shifts accordingly. + +The typical way to change a timestep is to index it. ``Universe.trajectory`` can be thought of as a list of :class:`~MDAnalysis.coordinates.base.Timestep`\ s, a data structure that holds information for the current time frame. For example, you can query its length. + +.. ipython:: python + + import MDAnalysis as mda + from MDAnalysis.tests.datafiles import PSF, DCD + + u = mda.Universe(PSF, DCD) + len(u.trajectory) + +When a trajectory is first loaded from a file, it is set to the first frame by default. + +.. ipython:: python + + print(u.trajectory.ts, u.trajectory.time) + +Indexing the trajectory returns the timestep for that frame, and sets the Universe to point to that frame until the timestep next changes. + +.. ipython:: python + + u.trajectory[3] + +.. ipython:: python + + print('Time of fourth frame', u.trajectory.time) + +Many tasks involve applying a function to each frame of a trajectory. For these, you need to iterate through the frames, *even if you don't directly use the timestep*. This is because the act of iterating moves the Universe onto the next frame, changing the dynamic atom coordinates. + +Trajectories can be sliced as below, if you only want to work on a subset of frames. + +.. ipython:: python + + protein = u.select_atoms('protein') + for ts in u.trajectory[:20:4]: + # the radius of gyration depends on the changing positions + rad = protein.radius_of_gyration() + print('frame={}: radgyr={}'.format(ts.frame, rad)) + +Note that after iterating over the trajectory, the frame is always set back to the first frame, even if your loop stopped before the trajectory end. + +.. ipython:: python + + u.trajectory.frame + +Because MDAnalysis will pull trajectory data directly from the file it is reading from, changes to atom coordinates and box dimensions will not persist once the frame is changed. The only way to make these changes permanent is to load the trajectory into memory, or to write a new trajectory to file for every frame. For example, to set a cubic box size for every frame and write it out to a file:: + + with mda.Writer('with_box.trr', 'w', n_atoms=u.atoms.n_atoms) as w: + for ts in u.trajectory: + ts.dimensions = [10, 10, 10, 90, 90, 90] + w.write(u.atoms) + + u_with_box = mda.Universe(PSF, 'with_box.trr') + + +Sometimes you may wish to only transform part of the trajectory, or to not write a file out. In these cases, MDAnalysis supports "on-the-fly" transformations that are performed on a frame when it is read. + + + +On-the-fly transformations ========================== +An on-the-fly transformation is a function that modifies the data contained in a trajectory :class:`~MDAnalysis.coordinates.base.Timestep`. It is called for each current time step as it is loaded into memory. A transformation function must also return the current :class:`~MDAnalysis.coordinates.base.Timestep`, as transformations are often chained together. + +The :mod:`MDAnalysis.transformations` module contains a collection of transformations. For example, :func:`~MDAnalysis.transformations.fit.fit_rot_trans` can perform a mass-weighted alignment on an :class:`~MDAnalysis.core.groups.AtomGroup` to a reference. + +.. ipython:: python + + from MDAnalysis.transformations import fit + + protein = u.select_atoms('protein') + align_transform = fit.fit_rot_trans(protein, protein, weights='mass') + u.trajectory.add_transformations(align_transform) + +Other implemented transformations include functions to :mod:`~MDAnalysis.transformations.translate`, :mod:`~MDAnalysis.transformations.rotate`, :mod:`~MDAnalysis.transformations.fit` an :class:`~MDAnalysis.core.groups.AtomGroup` to a reference, and :mod:`~MDAnalysis.transformations.wrap` or unwrap an :class:`~MDAnalysis.core.groups.AtomGroup` in the unit cell. + +If you need a different transformation, it is easy to implement your own. + +---------------------- +Custom transformations +---------------------- + +At its core, a transformation function must only take a :class:`~MDAnalysis.coordinates.base.Timestep` as its input and return the :class:`~MDAnalysis.coordinates.base.Timestep` as the output. + +.. ipython:: python + + def up_by_2(ts): + """Translates atoms up by 2 angstrom""" + ts.positions += np.array([0.0, 0.0, 0.2]) + return ts + + u = mda.Universe(PSF, DCD, transformations=[up_by_2]) + + +If your transformation needs other arguments, you will need to wrap your core transformation with a wrapper function that can accept the other arguments. + +.. ipython:: python + + def up_by_x(x): + """Translates atoms up by x angstrom""" + def wrapped(ts): + """Handles the actual Timestep""" + ts.positions += np.array([0.0, 0.0, float(x)]) + return ts + return wrapped + + # load Universe with transformations that move it up by 7 angstrom + u = mda.Universe(PSF, DCD, transformations=[up_by_x(5), up_by_x(2)]) + + +Alternatively, you can use :func:`functools.partial` to substitute the other arguments. + +.. ipython:: python + + import functools + + def up_by_x(ts, x): + ts.positions += np.array([0.0, 0.0, float(x)]) + return x + + up_by_5 = functools.partial(up_by_x, x=5) + u = mda.Universe(PSF, DCD, transformations=[up_by_5]) + +Above we have shown that a :class:`~MDAnalysis.core.universe.Universe` can be created with transformations directly. If your transformation depends on something within the :class:`~MDAnalysis.core.universe.Universe` (e.g. it needs to operate on a particular :class:`~MDAnalysis.core.groups.AtomGroup`), then you can load the :class:`~MDAnalysis.core.universe.Universe` and use the :meth:`~MDAnalysis.core.universe.Universe.add_transformations` method to add transformations. + +You can only add transformations *once*, so add your entire workflow at the same time. The below code transforms the trajectory so that the first 100 residues are translated up by 10 angstrom, and the remaining residues are translated down 10 angstrom. + +.. ipython:: python + + def ag_up_by_x(ag, x): + def wrapped(ts): + ag.positions += np.array([0.0, 0.0, float(x)]) + return ts + return wrapped + + u = mda.Universe(PSF, DCD) + res_to_100 = u.residues[:100].atoms + res_after_100 = u.residues[100:].atoms + + workflow = [ag_up_by_x(res_to_100, 10), + ag_up_by_x(res_after_100, -10)] + u.trajectory.add_transformations(*workflow) + \ No newline at end of file From 0b1da87fb5d6752fd2d2fea0c89b86f79e339678 Mon Sep 17 00:00:00 2001 From: Lily Wang Date: Sat, 8 Feb 2020 18:08:30 +1100 Subject: [PATCH 2/7] separated trajectories and added slicing --- doc/source/index.rst | 9 ++- .../trajectories/slicing_trajectories.rst | 65 +++++++++++++++++ doc/source/trajectories/trajectories.rst | 73 +++++++++++++++++++ .../transformations.rst} | 72 +----------------- 4 files changed, 147 insertions(+), 72 deletions(-) create mode 100644 doc/source/trajectories/slicing_trajectories.rst create mode 100644 doc/source/trajectories/trajectories.rst rename doc/source/{trajectories.rst => trajectories/transformations.rst} (53%) diff --git a/doc/source/index.rst b/doc/source/index.rst index 326703780..d7519b013 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -48,7 +48,14 @@ Gromacs_, CHARMM_, VMD_ and PyMol_ (see :ref:`selection-exporters`). It uses Num groups_of_atoms selections topology_system - trajectories + +.. toctree:: + :maxdepth: 1 + :caption: Trajectories + + trajectories/trajectories + trajectories/slicing_trajectories + trajectories/transformations .. toctree:: :maxdepth: 1 diff --git a/doc/source/trajectories/slicing_trajectories.rst b/doc/source/trajectories/slicing_trajectories.rst new file mode 100644 index 000000000..707bfcbed --- /dev/null +++ b/doc/source/trajectories/slicing_trajectories.rst @@ -0,0 +1,65 @@ +.. -*- coding: utf-8 -*- +.. _slicing-trajectories: + +==================== +Slicing trajectories +==================== + +MDAnalysis trajectories can be indexed to return a :class:`~MDAnalysis.coordinates.base.Timestep`, or sliced to give a :class:`~MDAnalysis.coordinates.base.FrameIterator`. + +.. ipython:: python + + import MDAnalysis as mda + from MDAnalysis.tests.datafiles import PSF, DCD + + u = mda.Universe(PSF, DCD) + u.trajectory[4] + + +Indexing a trajectory shifts the :class:`~MDAnalysis.core.universe.Universe` to point towards that particular frame, updating dynamic data such as ``Universe.atoms.positions``. + +.. ipython:: python + + u.trajectory.frame + +*Creating* a :class:`~MDAnalysis.coordinates.base.FrameIterator` by slicing a trajectory does not shift the :class:`~MDAnalysis.core.universe.Universe` to a new frame, but *iterating* over the sliced trajectory will rewind the trajectory back to the first frame. + +.. ipython::: python + + fiter = u.trajectory[10::2] + u.trajectory.frame + +.. ipython:: python + + fiter = u.trajectory[10::10] + frames = [ts.frame for ts in fiter] + print(frames, u.trajectory.frame) + +You can also create a sliced trajectory with boolean indexing and fancy indexing. Boolean indexing allows you to select only frames that meet a certain condition, by passing a :class:`~numpy.ndarray` with the same length as the original trajectory. Only frames that have a boolean value of ``True`` will be in the resulting :class:`~MDAnalysis.coordinates.base.FrameIterator`. For example, to select only the frames of the trajectory with an RMSD under 3 angstrom: + +.. ipython:: python + + from MDAnalysis.analysis import rms + + protein = u.select_atoms('protein') + rmsd = rms.RMSD(protein, protein).run() + bools = rmsd.rmsd.T[-1] < 2 + print(bools) + +.. ipython:: python + + fiter = u.trajectory[bools] + print([ts.frame for ts in fiter]) + +You can also use fancy indexing to control the order of specific frames. + +.. ipython:: python + + indices = [10, 2, 3, 9, 4, 55, 2] + print([ts.frame for ts in u.trajectory[indices]]) + +You can even slice a :class:`~MDAnalysis.coordinates.base.FrameIterator` to create a new :class:`~MDAnalysis.coordinates.base.FrameIterator`. + +.. ipython:: python + + print([ts.frame for ts in fiter[::3]]) diff --git a/doc/source/trajectories/trajectories.rst b/doc/source/trajectories/trajectories.rst new file mode 100644 index 000000000..c6d764eda --- /dev/null +++ b/doc/source/trajectories/trajectories.rst @@ -0,0 +1,73 @@ +.. -*- coding: utf-8 -*- +.. _trajectories: + +============ +Trajectories +============ + +In MDAnalysis, static data is contained in your universe Topology, while dynamic data is drawn from its trajectory at ``Universe.trajectory``. This is typically loaded from a trajectory file and includes information such as: + + * atom coordinates (``Universe.atoms.positions``) + * box size (``Universe.dimensions``) + * velocities and forces (if your file format contains the data) (``Universe.atoms.velocities``) + +Although these properties look static, they are actually dynamic, and the data contained within can change. +In order to remain memory-efficient, MDAnalysis does not load every frame of your trajectory into memory at once. Instead, a Universe has a state: the particular timestep that it is currently associated with in the trajectory. When the timestep changes, the data in the properties above shifts accordingly. + +The typical way to change a timestep is to index it. ``Universe.trajectory`` can be thought of as a list of :class:`~MDAnalysis.coordinates.base.Timestep`\ s, a data structure that holds information for the current time frame. For example, you can query its length. + +.. ipython:: python + + import MDAnalysis as mda + from MDAnalysis.tests.datafiles import PSF, DCD + + u = mda.Universe(PSF, DCD) + len(u.trajectory) + +When a trajectory is first loaded from a file, it is set to the first frame by default. + +.. ipython:: python + + print(u.trajectory.ts, u.trajectory.time) + +Indexing the trajectory returns the timestep for that frame, and sets the Universe to point to that frame until the timestep next changes. + +.. ipython:: python + + u.trajectory[3] + +.. ipython:: python + + print('Time of fourth frame', u.trajectory.time) + +Many tasks involve applying a function to each frame of a trajectory. For these, you need to iterate through the frames, *even if you don't directly use the timestep*. This is because the act of iterating moves the Universe onto the next frame, changing the dynamic atom coordinates. + +Trajectories can also be :ref:`sliced ` if you only want to work on a subset of frames. + +.. ipython:: python + + protein = u.select_atoms('protein') + for ts in u.trajectory[:20:4]: + # the radius of gyration depends on the changing positions + rad = protein.radius_of_gyration() + print('frame={}: radgyr={}'.format(ts.frame, rad)) + +Note that after iterating over the trajectory, the frame is always set back to the first frame, even if your loop stopped before the trajectory end. + +.. ipython:: python + + u.trajectory.frame + +Because MDAnalysis will pull trajectory data directly from the file it is reading from, changes to atom coordinates and box dimensions will not persist once the frame is changed. The only way to make these changes permanent is to load the trajectory into memory, or to write a new trajectory to file for every frame. For example, to set a cubic box size for every frame and write it out to a file:: + + with mda.Writer('with_box.trr', 'w', n_atoms=u.atoms.n_atoms) as w: + for ts in u.trajectory: + ts.dimensions = [10, 10, 10, 90, 90, 90] + w.write(u.atoms) + + u_with_box = mda.Universe(PSF, 'with_box.trr') + + +Sometimes you may wish to only transform part of the trajectory, or to not write a file out. In these cases, MDAnalysis supports :ref:`"on-the-fly" transformations ` that are performed on a frame when it is read. + + diff --git a/doc/source/trajectories.rst b/doc/source/trajectories/transformations.rst similarity index 53% rename from doc/source/trajectories.rst rename to doc/source/trajectories/transformations.rst index a9924fd06..13b465fde 100644 --- a/doc/source/trajectories.rst +++ b/doc/source/trajectories/transformations.rst @@ -1,75 +1,5 @@ .. -*- coding: utf-8 -*- - -============ -Trajectories -============ - -In MDAnalysis, static data is contained in your universe Topology, while dynamic data is drawn from its trajectory at ``Universe.trajectory``. This is typically loaded from a trajectory file and includes information such as: - - * atom coordinates (``Universe.atoms.positions``) - * box size (``Universe.dimensions``) - * velocities and forces (if your file format contains the data) (``Universe.atoms.velocities``) - -Although these properties look static, they are actually dynamic, and the data contained within can change. -In order to remain memory-efficient, MDAnalysis does not load every frame of your trajectory into memory at once. Instead, a Universe has a state: the particular timestep that it is currently associated with in the trajectory. When the timestep changes, the data in the properties above shifts accordingly. - -The typical way to change a timestep is to index it. ``Universe.trajectory`` can be thought of as a list of :class:`~MDAnalysis.coordinates.base.Timestep`\ s, a data structure that holds information for the current time frame. For example, you can query its length. - -.. ipython:: python - - import MDAnalysis as mda - from MDAnalysis.tests.datafiles import PSF, DCD - - u = mda.Universe(PSF, DCD) - len(u.trajectory) - -When a trajectory is first loaded from a file, it is set to the first frame by default. - -.. ipython:: python - - print(u.trajectory.ts, u.trajectory.time) - -Indexing the trajectory returns the timestep for that frame, and sets the Universe to point to that frame until the timestep next changes. - -.. ipython:: python - - u.trajectory[3] - -.. ipython:: python - - print('Time of fourth frame', u.trajectory.time) - -Many tasks involve applying a function to each frame of a trajectory. For these, you need to iterate through the frames, *even if you don't directly use the timestep*. This is because the act of iterating moves the Universe onto the next frame, changing the dynamic atom coordinates. - -Trajectories can be sliced as below, if you only want to work on a subset of frames. - -.. ipython:: python - - protein = u.select_atoms('protein') - for ts in u.trajectory[:20:4]: - # the radius of gyration depends on the changing positions - rad = protein.radius_of_gyration() - print('frame={}: radgyr={}'.format(ts.frame, rad)) - -Note that after iterating over the trajectory, the frame is always set back to the first frame, even if your loop stopped before the trajectory end. - -.. ipython:: python - - u.trajectory.frame - -Because MDAnalysis will pull trajectory data directly from the file it is reading from, changes to atom coordinates and box dimensions will not persist once the frame is changed. The only way to make these changes permanent is to load the trajectory into memory, or to write a new trajectory to file for every frame. For example, to set a cubic box size for every frame and write it out to a file:: - - with mda.Writer('with_box.trr', 'w', n_atoms=u.atoms.n_atoms) as w: - for ts in u.trajectory: - ts.dimensions = [10, 10, 10, 90, 90, 90] - w.write(u.atoms) - - u_with_box = mda.Universe(PSF, 'with_box.trr') - - -Sometimes you may wish to only transform part of the trajectory, or to not write a file out. In these cases, MDAnalysis supports "on-the-fly" transformations that are performed on a frame when it is read. - - +.. _transformations: On-the-fly transformations ========================== From fb17d1c2d4aad173f1ad2795f378f94b5bb1a5e5 Mon Sep 17 00:00:00 2001 From: Lily Wang Date: Mon, 24 Feb 2020 15:42:03 +1100 Subject: [PATCH 3/7] add clarification --- doc/source/conf.py | 23 ++++--- doc/source/groups_of_atoms.rst | 5 +- doc/source/topology_system.rst | 1 + .../trajectories/slicing_trajectories.rst | 6 +- doc/source/trajectories/trajectories.rst | 2 +- doc/source/trajectories/transformations.rst | 61 ++++++++++--------- 6 files changed, 56 insertions(+), 42 deletions(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 48c74133b..518249224 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -13,6 +13,7 @@ # import os # import sys +from collections import OrderedDict import MDAnalysis as mda # import subprocess import sphinx_rtd_theme @@ -28,11 +29,11 @@ # Get Travis to regenerate txt tables by re-running scripts # before deploying docs. # Turned off for now as 0.21.0 not released yet. -# This allows us to gitignore .txt files as well as +# This allows us to gitignore .txt files as well as # auto-recreate tables for each deployment. -# Turn off if using sphinx_autobuild as this will autobuild +# Turn off if using sphinx_autobuild as this will autobuild # to infinity. -# +# # subprocess.call('./scripts/generate_all.sh') # -- General configuration --------------------------------------------------- @@ -70,7 +71,8 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', '.ipynb_checkpoints', '**/.ipynb_checkpoints', 'scripts', '.*.ipynb', '.*'] +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', + '.ipynb_checkpoints', '**/.ipynb_checkpoints', 'scripts', '.*.ipynb', '.*'] # -- Options for HTML output ------------------------------------------------- @@ -90,9 +92,8 @@ color = {'orange': '#FF9200', 'gray': '#808080', 'white': '#FFFFFF', - 'black': '#000000',} + 'black': '#000000', } -from collections import OrderedDict extra_nav_links = OrderedDict() extra_nav_links['MDAnalysis'] = 'http://mdanalysis.org' extra_nav_links['docs'] = 'http://docs.mdanalysis.org' @@ -107,7 +108,7 @@ 'display_version': True, 'prev_next_buttons_location': 'bottom', 'style_external_links': False, - 'style_nav_header_background': 'white', #'#e76900', # dark orange + 'style_nav_header_background': 'white', # '#e76900', # dark orange # Toc options 'collapse_navigation': True, 'sticky_navigation': True, @@ -120,14 +121,14 @@ # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. html_favicon = "_static/logos/mdanalysis-logo.ico" -html_logo = '_static/logos/user_guide.png' +html_logo = '_static/logos/user_guide.png' # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] -html_css_files = ['custom.css']#, 'readable.css'] +html_css_files = ['custom.css'] # , 'readable.css'] # Custom sidebar templates, maps document names to template names. # alabaster sidebars @@ -153,10 +154,12 @@ DEFAULT_EMBED_REQUIREJS_URL, ] +ipython_warning_is_error = False + # substitutions MDAnalysis_version = '0.20.1' # rst-epilog implements substitutions rst_epilog = """ .. |MDAnalysis_version| replace:: {0} -""".format(MDAnalysis_version) \ No newline at end of file +""".format(MDAnalysis_version) diff --git a/doc/source/groups_of_atoms.rst b/doc/source/groups_of_atoms.rst index d3b6d4135..35b09f128 100644 --- a/doc/source/groups_of_atoms.rst +++ b/doc/source/groups_of_atoms.rst @@ -21,10 +21,11 @@ A :class:`~MDAnalysis.core.groups.Residue` is composed of :class:`~MDAnalysis.co The corresponding container groups are :class:`~MDAnalysis.core.groups.ResidueGroup` and :class:`~MDAnalysis.core.groups.SegmentGroup`. These have similar properties and available methods as :class:`~MDAnalysis.core.groups.AtomGroup`. .. ipython:: python + :okwarning: import MDAnalysis as mda - from MDAnalysis.tests.datafiles import TPR2019B3 - u = mda.Universe(TPR2019B3) + from MDAnalysis.tests.datafiles import TPR, XTC + u = mda.Universe(TPR, XTC) ag = u.atoms.select_atoms('resname ARG and name CA') ag diff --git a/doc/source/topology_system.rst b/doc/source/topology_system.rst index faf4a93ec..4102d854f 100644 --- a/doc/source/topology_system.rst +++ b/doc/source/topology_system.rst @@ -88,6 +88,7 @@ Each of the attributes above can be added to a Universe if it was not available * :code:`values` (optional) : if :code:`topologyattr` is a string, the values for that attribute. This can be :code:`None` if the attribute has default values defined, e.g. :code:`bfactors`. .. ipython:: python + :okwarning: import MDAnalysis as mda from MDAnalysis.tests.datafiles import PSF diff --git a/doc/source/trajectories/slicing_trajectories.rst b/doc/source/trajectories/slicing_trajectories.rst index 707bfcbed..b9f2ae52c 100644 --- a/doc/source/trajectories/slicing_trajectories.rst +++ b/doc/source/trajectories/slicing_trajectories.rst @@ -18,6 +18,10 @@ MDAnalysis trajectories can be indexed to return a :class:`~MDAnalysis.coordinat Indexing a trajectory shifts the :class:`~MDAnalysis.core.universe.Universe` to point towards that particular frame, updating dynamic data such as ``Universe.atoms.positions``. +.. note:: + + The trajectory frame is not read from the MD data. It is the internal index assigned by MDAnalysis. + .. ipython:: python u.trajectory.frame @@ -35,7 +39,7 @@ Indexing a trajectory shifts the :class:`~MDAnalysis.core.universe.Universe` to frames = [ts.frame for ts in fiter] print(frames, u.trajectory.frame) -You can also create a sliced trajectory with boolean indexing and fancy indexing. Boolean indexing allows you to select only frames that meet a certain condition, by passing a :class:`~numpy.ndarray` with the same length as the original trajectory. Only frames that have a boolean value of ``True`` will be in the resulting :class:`~MDAnalysis.coordinates.base.FrameIterator`. For example, to select only the frames of the trajectory with an RMSD under 3 angstrom: +You can also create a sliced trajectory with boolean indexing and fancy indexing. Boolean indexing allows you to select only frames that meet a certain condition, by passing a :class:`~numpy.ndarray` with the same length as the original trajectory. Only frames that have a boolean value of ``True`` will be in the resulting :class:`~MDAnalysis.coordinates.base.FrameIterator`. For example, to select only the frames of the trajectory with an RMSD under 2 angstrom: .. ipython:: python diff --git a/doc/source/trajectories/trajectories.rst b/doc/source/trajectories/trajectories.rst index c6d764eda..0c372d18a 100644 --- a/doc/source/trajectories/trajectories.rst +++ b/doc/source/trajectories/trajectories.rst @@ -24,7 +24,7 @@ The typical way to change a timestep is to index it. ``Universe.trajectory`` can u = mda.Universe(PSF, DCD) len(u.trajectory) -When a trajectory is first loaded from a file, it is set to the first frame by default. +When a trajectory is first loaded from a file, it is set to the first frame (with index 0), by default. .. ipython:: python diff --git a/doc/source/trajectories/transformations.rst b/doc/source/trajectories/transformations.rst index 13b465fde..907d559ab 100644 --- a/doc/source/trajectories/transformations.rst +++ b/doc/source/trajectories/transformations.rst @@ -4,19 +4,45 @@ On-the-fly transformations ========================== -An on-the-fly transformation is a function that modifies the data contained in a trajectory :class:`~MDAnalysis.coordinates.base.Timestep`. It is called for each current time step as it is loaded into memory. A transformation function must also return the current :class:`~MDAnalysis.coordinates.base.Timestep`, as transformations are often chained together. +An on-the-fly transformation is a function that silently modifies the dynamic data contained in a trajectory :class:`~MDAnalysis.coordinates.base.Timestep` (typically coordinates) as it is loaded into memory. It is called for each current time step to transform data into your desired representation. A transformation function must also return the current :class:`~MDAnalysis.coordinates.base.Timestep`, as transformations are often chained together. The :mod:`MDAnalysis.transformations` module contains a collection of transformations. For example, :func:`~MDAnalysis.transformations.fit.fit_rot_trans` can perform a mass-weighted alignment on an :class:`~MDAnalysis.core.groups.AtomGroup` to a reference. .. ipython:: python - from MDAnalysis.transformations import fit + import MDAnalysis as mda + from MDAnalysis.tests.datafiles import TPR, XTC + from MDAnalysis import transformations as trans + u = mda.Universe(TPR, XTC) protein = u.select_atoms('protein') - align_transform = fit.fit_rot_trans(protein, protein, weights='mass') + align_transform = trans.fit_rot_trans(protein, protein, weights='mass') u.trajectory.add_transformations(align_transform) -Other implemented transformations include functions to :mod:`~MDAnalysis.transformations.translate`, :mod:`~MDAnalysis.transformations.rotate`, :mod:`~MDAnalysis.transformations.fit` an :class:`~MDAnalysis.core.groups.AtomGroup` to a reference, and :mod:`~MDAnalysis.transformations.wrap` or unwrap an :class:`~MDAnalysis.core.groups.AtomGroup` in the unit cell. +Other implemented transformations include functions to :mod:`~MDAnalysis.transformations.translate`, :mod:`~MDAnalysis.transformations.rotate`, :mod:`~MDAnalysis.transformations.fit` an :class:`~MDAnalysis.core.groups.AtomGroup` to a reference, and :mod:`~MDAnalysis.transformations.wrap` or unwrap groups in the unit cell. + +Although you can only call :meth:`~MDAnalysis.coordinates.base.ProtoReader.add_transformations` *once*, you can pass in multiple transformations in a list, which will be executed in order. For example, the below workflow: + +* makes all molecules whole (unwraps them over periodic boundary conditions) +* centers the protein in the center of the box +* wraps water back into the box + +.. ipython:: python + + # create new Universe for new transformations + u = mda.Universe(TPR, XTC) + protein = u.select_atoms('protein') + water = u.select_atoms('resname SOL') + workflow = [trans.unwrap(u.atoms), + trans.center_in_box(protein, center='geometry'), + trans.wrap(water, compound='residues')] + u.trajectory.add_transformations(*workflow) + +If your transformation does not depend on something within the :class:`~MDAnalysis.core.universe.Universe` (e.g. a chosen :class:`~MDAnalysis.core.groups.AtomGroup`), you can also create a :class:`~MDAnalysis.core.universe.Universe` directly with transformations. The code below translates coordinates 1 angstrom up on the z-axis: + +.. ipython:: python + + u = mda.Universe(TPR, XTC, transformations=[trans.translate([0, 0, 1])]) If you need a different transformation, it is easy to implement your own. @@ -33,7 +59,7 @@ At its core, a transformation function must only take a :class:`~MDAnalysis.coor ts.positions += np.array([0.0, 0.0, 0.2]) return ts - u = mda.Universe(PSF, DCD, transformations=[up_by_2]) + u = mda.Universe(TPR, XTC, transformations=[up_by_2]) If your transformation needs other arguments, you will need to wrap your core transformation with a wrapper function that can accept the other arguments. @@ -49,7 +75,7 @@ If your transformation needs other arguments, you will need to wrap your core tr return wrapped # load Universe with transformations that move it up by 7 angstrom - u = mda.Universe(PSF, DCD, transformations=[up_by_x(5), up_by_x(2)]) + u = mda.Universe(TPR, XTC, transformations=[up_by_x(5), up_by_x(2)]) Alternatively, you can use :func:`functools.partial` to substitute the other arguments. @@ -63,25 +89,4 @@ Alternatively, you can use :func:`functools.partial` to substitute the other arg return x up_by_5 = functools.partial(up_by_x, x=5) - u = mda.Universe(PSF, DCD, transformations=[up_by_5]) - -Above we have shown that a :class:`~MDAnalysis.core.universe.Universe` can be created with transformations directly. If your transformation depends on something within the :class:`~MDAnalysis.core.universe.Universe` (e.g. it needs to operate on a particular :class:`~MDAnalysis.core.groups.AtomGroup`), then you can load the :class:`~MDAnalysis.core.universe.Universe` and use the :meth:`~MDAnalysis.core.universe.Universe.add_transformations` method to add transformations. - -You can only add transformations *once*, so add your entire workflow at the same time. The below code transforms the trajectory so that the first 100 residues are translated up by 10 angstrom, and the remaining residues are translated down 10 angstrom. - -.. ipython:: python - - def ag_up_by_x(ag, x): - def wrapped(ts): - ag.positions += np.array([0.0, 0.0, float(x)]) - return ts - return wrapped - - u = mda.Universe(PSF, DCD) - res_to_100 = u.residues[:100].atoms - res_after_100 = u.residues[100:].atoms - - workflow = [ag_up_by_x(res_to_100, 10), - ag_up_by_x(res_after_100, -10)] - u.trajectory.add_transformations(*workflow) - \ No newline at end of file + u = mda.Universe(TPR, XTC, transformations=[up_by_5]) \ No newline at end of file From def2b3b7ac6ac5edaca99918dc88ffbf4811087d Mon Sep 17 00:00:00 2001 From: Lily Wang Date: Sun, 7 Jun 2020 22:49:18 +1000 Subject: [PATCH 4/7] hide trajectories toc --- doc/source/index.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/index.rst b/doc/source/index.rst index 68a613557..69b7ab46b 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -88,6 +88,7 @@ Wherever possible, do not take these conversations to private channels, includin .. toctree:: :maxdepth: 1 :caption: Trajectories + :hidden: trajectories/trajectories trajectories/slicing_trajectories From 8520c7d8163bed14d4ef75fa62c0f599ab0531b6 Mon Sep 17 00:00:00 2001 From: Lily Wang Date: Thu, 11 Jun 2020 19:08:24 +1000 Subject: [PATCH 5/7] updated version --- doc/source/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index bc074b142..62f2d0c6b 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -192,7 +192,7 @@ def sort_authors(filename): """ # substitutions -MDAnalysis_version = '0.20.1' +MDAnalysis_version = '1.0.0' # rst-epilog implements substitutions rst_epilog = """ From ffe31b1da423a6a6f03a547db51f0b305541b390 Mon Sep 17 00:00:00 2001 From: Lily Wang Date: Thu, 11 Jun 2020 20:00:26 +1000 Subject: [PATCH 6/7] add boxes to otf transformations --- doc/source/trajectories/transformations.rst | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/doc/source/trajectories/transformations.rst b/doc/source/trajectories/transformations.rst index 907d559ab..4f8bc59b8 100644 --- a/doc/source/trajectories/transformations.rst +++ b/doc/source/trajectories/transformations.rst @@ -86,7 +86,19 @@ Alternatively, you can use :func:`functools.partial` to substitute the other arg def up_by_x(ts, x): ts.positions += np.array([0.0, 0.0, float(x)]) - return x + return ts up_by_5 = functools.partial(up_by_x, x=5) - u = mda.Universe(TPR, XTC, transformations=[up_by_5]) \ No newline at end of file + u = mda.Universe(TPR, XTC, transformations=[up_by_5]) + +On-the-fly transformation functions can be applied to any property of a Timestep, not just the atom positions. For example, to give each frame of a trajectory a box: + +.. ipython:: python + + def set_box(ts): + # creates box of length 1 on x-axis, 1 on y-axis, 2 on z-axis + # angles are all 90 degrees + ts.dimensions = [1, 1, 2, 90, 90, 90] + return ts + + u = mda.Universe(TPR, XTC, transformations=[set_box]) From 130174a22de75b0f1985eac2f16f50761bf9efef Mon Sep 17 00:00:00 2001 From: Lily Wang Date: Fri, 12 Jun 2020 02:28:22 +1000 Subject: [PATCH 7/7] less pathological cell --- doc/source/trajectories/transformations.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/source/trajectories/transformations.rst b/doc/source/trajectories/transformations.rst index 4f8bc59b8..b540a0214 100644 --- a/doc/source/trajectories/transformations.rst +++ b/doc/source/trajectories/transformations.rst @@ -96,9 +96,10 @@ On-the-fly transformation functions can be applied to any property of a Timestep .. ipython:: python def set_box(ts): - # creates box of length 1 on x-axis, 1 on y-axis, 2 on z-axis + # creates box of length 10 on x-axis, 20 on y-axis, 30 on z-axis # angles are all 90 degrees - ts.dimensions = [1, 1, 2, 90, 90, 90] + ts.dimensions = [10, 20, 30, 90, 90, 90] return ts u = mda.Universe(TPR, XTC, transformations=[set_box]) +