From 826b2ad77f926f4efa34c08b1f7c4babad7fdf6c Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Mon, 2 Mar 2020 19:28:52 +1100 Subject: [PATCH 001/121] added msd class --- package/MDAnalysis/analysis/msd.py | 59 ++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 package/MDAnalysis/analysis/msd.py diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py new file mode 100644 index 00000000000..3d9e43d1496 --- /dev/null +++ b/package/MDAnalysis/analysis/msd.py @@ -0,0 +1,59 @@ +# -*- 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 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 +# + +from __future__ import division, absolute_import +from six.moves import zip + +import os +import errno +import warnings +import bz2 +import functools + +import numpy as np + +import logging + +import MDAnalysis +import MDAnalysis.lib.distances +from MDAnalysis.lib.util import openany +from MDAnalysis.analysis.distances import distance_array +from MDAnalysis.core.groups import AtomGroup +from .base import AnalysisBase + +logger = logging.getLogger("MDAnalysis.analysis.contacts") + + + +class MeanSquaredDisplacement(AnalysisBase): + + + def __init__(self, u, selection, + kwargs=None, **basekwargs): + self.u = u + super(MeanSquaredDisplacement, self).__init__(self.u.trajectory, **basekwargs) + + self.fraction_kwargs = kwargs if kwargs is not None else {} + self.select = select + + From 6540de28fe3c3875437716bbf5f41e1cb62b0c2d Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Mon, 2 Mar 2020 20:41:48 +1100 Subject: [PATCH 002/121] added in msd types and dimension factors --- package/MDAnalysis/analysis/msd.py | 49 ++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 3d9e43d1496..423493fb3be 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -48,12 +48,57 @@ class MeanSquaredDisplacement(AnalysisBase): - def __init__(self, u, selection, + def __init__(self, u, selection, t0, delta_t, msd_type='xyz') kwargs=None, **basekwargs): self.u = u super(MeanSquaredDisplacement, self).__init__(self.u.trajectory, **basekwargs) self.fraction_kwargs = kwargs if kwargs is not None else {} - self.select = select + self.select = selection + self.t0 = t0 + self.delta_t = delta_t + self.msd_type = msd_type + self.dim_fac = 0 + + + def _prepare(self): + parse_msd_type() + select_reference_positions() + + def select_reference_positions(): + + + def parse_msd_type(self): + if self.msd_type = 'xyz': + self._dim = [0,1,2] + self.dim_fac = 3.0 + elif self.msd_type = 'xy': + self._dim = [0,1] + self.dim_fac = 2.0 + elif self.md_type = 'xz': + self._dime = [0,2] + self.dim_fac = 2.0 + elif self.msd_type = 'yz': + self._dim = [1,2] + self.dim_fac = 2.0 + elif self.msd_type = 'x': + self._dim = [0] + self.dim_fac = 1.0 + elif self.msd_type = 'y': + self._dim = [1] + self.dim_fac = 1.0 + elif self.msd_type = 'z': + self._dim = [2] + self.dim_fac = 1.0 + else: + raise ValueError('invalid msd_type specified') + + + + def _run(): + + + def _conclude(self): + From f50622c9857d3c41105fa09856acb5e89c58a4b1 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Mon, 2 Mar 2020 21:43:08 +1100 Subject: [PATCH 003/121] fill out framework to calculate msd --- package/MDAnalysis/analysis/msd.py | 39 +++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 423493fb3be..caf3ef089d2 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -48,48 +48,75 @@ class MeanSquaredDisplacement(AnalysisBase): - def __init__(self, u, selection, t0, delta_t, msd_type='xyz') + def __init__(self, u, selection, t0, delta_t, msd_type='xyz', position_treatment='atom', mass_weighted=False) kwargs=None, **basekwargs): self.u = u super(MeanSquaredDisplacement, self).__init__(self.u.trajectory, **basekwargs) - self.fraction_kwargs = kwargs if kwargs is not None else {} - self.select = selection + self.selection = selection self.t0 = t0 self.delta_t = delta_t self.msd_type = msd_type + self.position_treatment = position_treatment + self.mass_weighted = mass_weighted + self.dim_fac = 0 + self.atoms = None + self.timeseries = None def _prepare(self): parse_msd_type() + check_masses() select_reference_positions() - - def select_reference_positions(): - + def check_masses(self): + self.atoms = u.select_atoms(self.selection) + if (mass_weighted or position_treatment == 'com'): + + #TODO check that all the atoms have masses + else: + pass + + def select_reference_positions(self): + if self.position_treatment == 'atom' + self.timeseries = atoms #TODO work out timeseries + elif self.position_treatment == 'com': + + self.timeseries = calculate_com(atoms) #TODO work out timeseries + else: + self. + def parse_msd_type(self): + if self.msd_type = 'xyz': self._dim = [0,1,2] self.dim_fac = 3.0 + elif self.msd_type = 'xy': self._dim = [0,1] self.dim_fac = 2.0 + elif self.md_type = 'xz': self._dime = [0,2] self.dim_fac = 2.0 + elif self.msd_type = 'yz': self._dim = [1,2] self.dim_fac = 2.0 + elif self.msd_type = 'x': self._dim = [0] self.dim_fac = 1.0 + elif self.msd_type = 'y': self._dim = [1] self.dim_fac = 1.0 + elif self.msd_type = 'z': self._dim = [2] self.dim_fac = 1.0 + else: raise ValueError('invalid msd_type specified') From aa29d3b44c4066ca99239d9cb85c6c11d67c19b5 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Mon, 2 Mar 2020 21:43:08 +1100 Subject: [PATCH 004/121] fill out framework to calculate msd --- package/MDAnalysis/analysis/msd.py | 51 ++++++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 423493fb3be..4ce0c34ba71 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -48,48 +48,75 @@ class MeanSquaredDisplacement(AnalysisBase): - def __init__(self, u, selection, t0, delta_t, msd_type='xyz') + def __init__(self, u, selection, t0, delta_t, msd_type='xyz', position_treatment='atom', mass_weighted=False) kwargs=None, **basekwargs): self.u = u super(MeanSquaredDisplacement, self).__init__(self.u.trajectory, **basekwargs) - self.fraction_kwargs = kwargs if kwargs is not None else {} - self.select = selection + self.selection = selection self.t0 = t0 self.delta_t = delta_t self.msd_type = msd_type + self.position_treatment = position_treatment + self.mass_weighted = mass_weighted + self.dim_fac = 0 + self.atoms = None + self.timeseries = None def _prepare(self): parse_msd_type() + check_masses() select_reference_positions() - - def select_reference_positions(): - + def check_masses(self): + self.atoms = u.select_atoms(self.selection) + if (mass_weighted or position_treatment == 'com'): + + #TODO check that all the atoms have masses + else: + pass + + def select_reference_positions(self): + if self.position_treatment == 'atom' + self.timeseries = atoms #TODO work out timeseries + elif self.position_treatment == 'com': + + self.timeseries = calculate_com(atoms) #TODO work out timeseries + else: + raise ValueError('invalid position_treatment specified') + def parse_msd_type(self): + if self.msd_type = 'xyz': self._dim = [0,1,2] self.dim_fac = 3.0 + elif self.msd_type = 'xy': self._dim = [0,1] self.dim_fac = 2.0 + elif self.md_type = 'xz': self._dime = [0,2] self.dim_fac = 2.0 + elif self.msd_type = 'yz': self._dim = [1,2] self.dim_fac = 2.0 + elif self.msd_type = 'x': self._dim = [0] self.dim_fac = 1.0 + elif self.msd_type = 'y': self._dim = [1] self.dim_fac = 1.0 + elif self.msd_type = 'z': self._dim = [2] self.dim_fac = 1.0 + else: raise ValueError('invalid msd_type specified') @@ -97,6 +124,18 @@ def parse_msd_type(self): def _run(): + + def one_time_point(self,): + for i in self.timeseries: + displ_sq_sum = 0.0 + for j in self.n_atoms: + displ = np.abs(r[self._dim]-r0[self._dim]) + displ_sq = displ*displ + displ_sq_sum += displ_sq + 1.0/self.n_atoms + + + From 671e53719618ec8ca323c34a5a23923e8c18c085 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Tue, 3 Mar 2020 21:00:25 +1100 Subject: [PATCH 005/121] skeleton algorithm for naieve MSD algorithm --- package/MDAnalysis/analysis/msd.py | 75 ++++++++++++++++++------------ 1 file changed, 44 insertions(+), 31 deletions(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 7ccb5fba1ee..9726b4db142 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -48,49 +48,48 @@ class MeanSquaredDisplacement(AnalysisBase): - def __init__(self, u, selection, t0, delta_t, msd_type='xyz', position_treatment='atom', mass_weighted=False) + def __init__(self, u, selection, msd_type='xyz', position_treatment='atom', mass_weighted=False) kwargs=None, **basekwargs): self.u = u super(MeanSquaredDisplacement, self).__init__(self.u.trajectory, **basekwargs) self.selection = selection - self.t0 = t0 - self.delta_t = delta_t self.msd_type = msd_type self.position_treatment = position_treatment self.mass_weighted = mass_weighted + #local self.dim_fac = 0 + self._dim = None self.atoms = None + self.n_frames = self.u.n_frames + self._position_array = None + self. + + #result self.timeseries = None + self.check_input() + + def check_input(self): + check_masses() + parse_msd_type() def _prepare(self): - parse_msd_type() - check_masses() select_reference_positions() + construct_arrays() def check_masses(self): self.atoms = u.select_atoms(self.selection) - if (mass_weighted or position_treatment == 'com'): - - #TODO check that all the atoms have masses + if (self.mass_weighted or self.position_treatment == 'com'): + masses = self.atoms.masses + if masses.any == None: + raise ValueError ('cannot have no mass for mass_weighted=True or position_treatment=com') + raise NotImplementedError else: pass + - def select_reference_positions(self): - if self.position_treatment == 'atom' - self.timeseries = atoms #TODO work out timeseries - elif self.position_treatment == 'com': - - self.timeseries = calculate_com(atoms) #TODO work out timeseries - else: -<<<<<<< HEAD - raise ValueError('invalid position_treatment specified') -======= - self. ->>>>>>> f50622c9857d3c41105fa09856acb5e89c58a4b1 - def parse_msd_type(self): if self.msd_type = 'xyz': @@ -123,20 +122,34 @@ def parse_msd_type(self): else: raise ValueError('invalid msd_type specified') + + def select_reference_positions(self): + if self.position_treatment == 'atom' + self._position_array = self.u.trajectory.timeseries(self.u.select_atoms(self.selection),order='fac') + self.N_particles = self._position_array.shape[0] + elif self.position_treatment == 'com': + raise NotImplementedError + #TODO work out timeseries for com + else: + raise ValueError('invalid position_treatment specified') + def _run(): # naieve algorithm pre vectorisation / without FFT + # r is shape time, nparticles, 3 + msds_byparticle = np.zeros([self.n_frames, self.N_particles]) + lagtimes = np.arange(self.n_frames) + for n in range(self.N_particles): + for i,lag in enumerate(lag_times): + disp = r[:-lag,n,self._dim if lag else None] - r[lag:,n,self._dim] ) + sqdist = np.square(disp).sum(axis=1) + msds[i,n] = sqdist.mean() + msds = msds_byparticle.mean(axis=XX) + - - def _run(): + + - def one_time_point(self,): - for i in self.timeseries: - displ_sq_sum = 0.0 - for j in self.n_atoms: - displ = np.abs(r[self._dim]-r0[self._dim]) - displ_sq = displ*displ - displ_sq_sum += displ_sq - 1.0/self.n_atoms + From b0e296bd489e3e8f6644516516d0afe3b497c0a4 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Wed, 4 Mar 2020 09:16:16 +1100 Subject: [PATCH 006/121] add in fft algorithm skeleton --- package/MDAnalysis/analysis/msd.py | 37 ++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 9726b4db142..aa62d545d41 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -31,6 +31,7 @@ import functools import numpy as np +from scipy import fft,ifft import logging @@ -132,8 +133,16 @@ def select_reference_positions(self): #TODO work out timeseries for com else: raise ValueError('invalid position_treatment specified') - - def _run(): # naieve algorithm pre vectorisation / without FFT + + def _run(self): + if self.fft= True: + self.timeseries = self._run_fft() + else: + self.timeseries = self._run_naieve() + + + + def _run_naieve(self): # naieve algorithm pre vectorisation / without FFT # r is shape time, nparticles, 3 msds_byparticle = np.zeros([self.n_frames, self.N_particles]) lagtimes = np.arange(self.n_frames) @@ -143,8 +152,28 @@ def _run(): # naieve algorithm pre vectorisation / without FFT sqdist = np.square(disp).sum(axis=1) msds[i,n] = sqdist.mean() msds = msds_byparticle.mean(axis=XX) - - + + def _run_fft(self): #with FFT + N=len(r) + D=np.square(r).sum(axis=1) + D=np.append(D,0) + S2=sum([self.autocorrFFT(r[:, i]) for i in range(r.shape[1])]) + Q=2*D.sum() + S1=np.zeros(N) + for m in range(N): + Q=Q-D[m-1]-D[N-m] + S1[m]=Q/(N-m) + return S1-2*S2 + + @classmethod + def autocorrFFT(x): + N=len(x) + F = np.fft.fft(x, n=2*N) #2*N because of zero-padding + PowerSpectralDensity = F * F.conjugate() + res = np.fft.ifft(PowerSpectralDensity) + res= (res[:N]).real #now we have the autocorrelation in convention B + n=N*np.ones(N)-np.arange(0,N) #divide res(m) by (N-m) + return res/n #this is the autocorrelation in convention A From 67fc0d0c58df6eb102106d1d6b3748a7a4867b73 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Wed, 4 Mar 2020 22:19:42 +1100 Subject: [PATCH 007/121] naieve algorithm complete --- package/MDAnalysis/analysis/msd.py | 92 ++++++++----------- .../MDAnalysisTests/analysis/test_msd.py | 54 +++++++++++ 2 files changed, 94 insertions(+), 52 deletions(-) create mode 100644 testsuite/MDAnalysisTests/analysis/test_msd.py diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index aa62d545d41..aef69344c8f 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -31,7 +31,8 @@ import functools import numpy as np -from scipy import fft,ifft +from scipy import fft, ifft + import logging @@ -42,82 +43,78 @@ from MDAnalysis.core.groups import AtomGroup from .base import AnalysisBase -logger = logging.getLogger("MDAnalysis.analysis.contacts") - - -class MeanSquaredDisplacement(AnalysisBase): +class MeanSquaredDisplacement(object): - - def __init__(self, u, selection, msd_type='xyz', position_treatment='atom', mass_weighted=False) - kwargs=None, **basekwargs): + def __init__(self, u, selection, msd_type='xyz', position_treatment='atom', mass_weighted=False, kwargs=None, **basekwargs): + self.u = u - super(MeanSquaredDisplacement, self).__init__(self.u.trajectory, **basekwargs) - self.selection = selection self.msd_type = msd_type self.position_treatment = position_treatment self.mass_weighted = mass_weighted #local + self.fft = False self.dim_fac = 0 self._dim = None self.atoms = None - self.n_frames = self.u.n_frames + self.n_frames = len(self.u.trajectory) self._position_array = None - self. #result self.timeseries = None self.check_input() + self._prepare() def check_input(self): - check_masses() - parse_msd_type() + self.check_masses() + self.parse_msd_type() def _prepare(self): - select_reference_positions() - construct_arrays() + self.select_reference_positions() + #self.construct_arrays() def check_masses(self): - self.atoms = u.select_atoms(self.selection) + self.atoms = self.u.select_atoms(self.selection) if (self.mass_weighted or self.position_treatment == 'com'): masses = self.atoms.masses if masses.any == None: - raise ValueError ('cannot have no mass for mass_weighted=True or position_treatment=com') - raise NotImplementedError + raise ValueError ('cannot have no mass for mass_weighted=True or position_treatment=com') + else: + pass else: pass def parse_msd_type(self): - if self.msd_type = 'xyz': + if self.msd_type == 'xyz': # full tensor self._dim = [0,1,2] self.dim_fac = 3.0 - elif self.msd_type = 'xy': + elif self.msd_type == 'xy': # xy self._dim = [0,1] self.dim_fac = 2.0 - elif self.md_type = 'xz': + elif self.md_type == 'xz': # xz self._dime = [0,2] self.dim_fac = 2.0 - elif self.msd_type = 'yz': + elif self.msd_type == 'yz': # yz self._dim = [1,2] self.dim_fac = 2.0 - elif self.msd_type = 'x': + elif self.msd_type == 'x': # x self._dim = [0] self.dim_fac = 1.0 - elif self.msd_type = 'y': + elif self.msd_type == 'y': # y self._dim = [1] self.dim_fac = 1.0 - elif self.msd_type = 'z': + elif self.msd_type == 'z': # z self._dim = [2] self.dim_fac = 1.0 @@ -125,36 +122,38 @@ def parse_msd_type(self): raise ValueError('invalid msd_type specified') def select_reference_positions(self): - if self.position_treatment == 'atom' + if self.position_treatment == 'atom': self._position_array = self.u.trajectory.timeseries(self.u.select_atoms(self.selection),order='fac') - self.N_particles = self._position_array.shape[0] + self.N_particles = self._position_array.shape[1] elif self.position_treatment == 'com': raise NotImplementedError #TODO work out timeseries for com else: raise ValueError('invalid position_treatment specified') - def _run(self): - if self.fft= True: + def run(self): + + if self.fft == True: self.timeseries = self._run_fft() else: self.timeseries = self._run_naieve() - - def _run_naieve(self): # naieve algorithm pre vectorisation / without FFT - # r is shape time, nparticles, 3 + # _position_array is shape time, nparticles, 3 msds_byparticle = np.zeros([self.n_frames, self.N_particles]) - lagtimes = np.arange(self.n_frames) + lagtimes = np.arange(1,self.n_frames) + msds_byparticle[0,:] = np.zeros(self.N_particles) # preset the zero lagtime so we dont have to iterate through for n in range(self.N_particles): - for i,lag in enumerate(lag_times): - disp = r[:-lag,n,self._dim if lag else None] - r[lag:,n,self._dim] ) + for lag in lagtimes: + disp = self._position_array[:-lag,n,self._dim if lag else None] - self._position_array[lag:,n,self._dim] sqdist = np.square(disp).sum(axis=1) - msds[i,n] = sqdist.mean() - msds = msds_byparticle.mean(axis=XX) - + msds_byparticle[lag,n] = sqdist.mean() + msds = msds_byparticle.mean(axis=1) + return msds + def _run_fft(self): #with FFT - N=len(r) + # _position_array is shape time, nparticles, 3 + N=r.shape(0) D=np.square(r).sum(axis=1) D=np.append(D,0) S2=sum([self.autocorrFFT(r[:, i]) for i in range(r.shape[1])]) @@ -173,17 +172,6 @@ def autocorrFFT(x): res = np.fft.ifft(PowerSpectralDensity) res= (res[:N]).real #now we have the autocorrelation in convention B n=N*np.ones(N)-np.arange(0,N) #divide res(m) by (N-m) - return res/n #this is the autocorrelation in convention A - - - - - - - - - - + return res/n #this is the autocorrelation in convention A - def _conclude(self): diff --git a/testsuite/MDAnalysisTests/analysis/test_msd.py b/testsuite/MDAnalysisTests/analysis/test_msd.py new file mode 100644 index 00000000000..4342486afdd --- /dev/null +++ b/testsuite/MDAnalysisTests/analysis/test_msd.py @@ -0,0 +1,54 @@ +# -*- 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 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 +# +from __future__ import division, absolute_import, print_function + +import pytest +from six.moves import range + +import MDAnalysis as mda +from MDAnalysis.analysis.msd import MeanSquaredDisplacement as MSD + +from numpy.testing import (assert_array_less, + assert_almost_equal, assert_equal) +import numpy as np +from scipy import fft,ifft + +from MDAnalysisTests.datafiles import PSF, DCD, DCD + +SELECTION = 'backbone and name CA and resid 1-10' + +@pytest.fixture(scope='module') +def u(): + return mda.Universe(PSF, DCD) + +@ +def test_class_construct(u): + m = MSD(u, SELECTION, msd_type='xyz', position_treatment='atom', mass_weighted=False) + m.run() + +def unit_step_traj(u): + nstep = 1000 + x = np.arange(nstep) + traj = np.hstack([x,y,z]) + m = MSD(u, SELECTION, msd_type='xyz', position_treatment='atom', mass_weighted=False) + From 3018a629a90de231bc4c2aac5dad9be0ab0b23b1 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Wed, 11 Mar 2020 20:08:11 +1100 Subject: [PATCH 008/121] added in fft compute and tests --- package/MDAnalysis/analysis/msd.py | 78 ++++++++++----- .../MDAnalysisTests/analysis/test_msd.py | 97 +++++++++++++++++-- 2 files changed, 142 insertions(+), 33 deletions(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index aef69344c8f..4a5e5fe12a9 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -31,8 +31,7 @@ import functools import numpy as np -from scipy import fft, ifft - +from scipy import fft,ifft import logging @@ -46,16 +45,16 @@ class MeanSquaredDisplacement(object): - def __init__(self, u, selection, msd_type='xyz', position_treatment='atom', mass_weighted=False, kwargs=None, **basekwargs): + def __init__(self, u, selection, msd_type='xyz', position_treatment='atom', mass_weighted=False, fft=False, kwargs=None, **basekwargs): self.u = u self.selection = selection self.msd_type = msd_type self.position_treatment = position_treatment self.mass_weighted = mass_weighted - + self.fft = fft #local - self.fft = False + self.dim_fac = 0 self._dim = None self.atoms = None @@ -98,8 +97,8 @@ def parse_msd_type(self): self._dim = [0,1] self.dim_fac = 2.0 - elif self.md_type == 'xz': # xz - self._dime = [0,2] + elif self.msd_type == 'xz': # xz + self._dim = [0,2] self.dim_fac = 2.0 elif self.msd_type == 'yz': # yz @@ -134,10 +133,11 @@ def select_reference_positions(self): def run(self): if self.fft == True: - self.timeseries = self._run_fft() + self.timeseries = self._run_fft_dim() else: self.timeseries = self._run_naieve() + def _run_naieve(self): # naieve algorithm pre vectorisation / without FFT # _position_array is shape time, nparticles, 3 msds_byparticle = np.zeros([self.n_frames, self.N_particles]) @@ -153,25 +153,55 @@ def _run_naieve(self): # naieve algorithm pre vectorisation / without FFT def _run_fft(self): #with FFT # _position_array is shape time, nparticles, 3 - N=r.shape(0) - D=np.square(r).sum(axis=1) - D=np.append(D,0) - S2=sum([self.autocorrFFT(r[:, i]) for i in range(r.shape[1])]) - Q=2*D.sum() - S1=np.zeros(N) + particle_msds = [] + N=self._position_array.shape[0] + D=np.square(self._position_array).sum(axis=2) + D=np.append(D,np.zeros(self._position_array.shape[:2]), axis=0) + Q=2*D.sum(axis=0) + S1=np.zeros(self._position_array.shape[:2]) for m in range(N): - Q=Q-D[m-1]-D[N-m] - S1[m]=Q/(N-m) - return S1-2*S2 + Q=Q-D[m-1,:]-D[N-m,:] + S1[m,:]=Q/(N-m) + + corrs = [] + for i in range(self._position_array.shape[2]): + corrs.append(self.autocorrFFT(self._position_array[:,:,i])) + S2= np.sum(corrs,axis=0) + particle_msds.append(S1-2*S2) + + msds = np.concatenate(particle_msds,axis=1).mean(axis=-1) + return msds + + def _run_fft_dim(self): #with FFT + # _position_array is shape time, nparticles, 3 + particle_msds = [] + reshape_positions = self._position_array[:,:,self._dim] + N=reshape_positions.shape[0] + D=np.square(reshape_positions).sum(axis=2) + D=np.append(D,np.zeros(reshape_positions.shape[:2]), axis=0) + Q=2*D.sum(axis=0) + S1=np.zeros(reshape_positions.shape[:2]) + for m in range(N): + Q=Q-D[m-1,:]-D[N-m,:] + S1[m,:]=Q/(N-m) + + corrs = [] + for i in range(reshape_positions.shape[2]): + corrs.append(self.autocorrFFT(reshape_positions[:,:,i])) + S2= np.sum(corrs,axis=0) + + particle_msds.append(S1-2*S2) + msds = np.concatenate(particle_msds,axis=1).mean(axis=-1) + return msds - @classmethod + @staticmethod def autocorrFFT(x): - N=len(x) - F = np.fft.fft(x, n=2*N) #2*N because of zero-padding + N=(x.shape[0]) + F = fft(x, n=2*N, axis=0) #2*N because of zero-padding PowerSpectralDensity = F * F.conjugate() - res = np.fft.ifft(PowerSpectralDensity) - res= (res[:N]).real #now we have the autocorrelation in convention B - n=N*np.ones(N)-np.arange(0,N) #divide res(m) by (N-m) - return res/n #this is the autocorrelation in convention A + res = ifft(PowerSpectralDensity,axis=0) + res = (res[:N]).real #now we have the autocorrelation in convention B + n = np.arange(1, N+1)[::-1] #divide res(m) by (N-m) + return res/n[:, np.newaxis] #this is the autocorrelation in convention A diff --git a/testsuite/MDAnalysisTests/analysis/test_msd.py b/testsuite/MDAnalysisTests/analysis/test_msd.py index 4342486afdd..2800db458cf 100644 --- a/testsuite/MDAnalysisTests/analysis/test_msd.py +++ b/testsuite/MDAnalysisTests/analysis/test_msd.py @@ -22,8 +22,7 @@ # from __future__ import division, absolute_import, print_function -import pytest -from six.moves import range + import MDAnalysis as mda from MDAnalysis.analysis.msd import MeanSquaredDisplacement as MSD @@ -31,24 +30,104 @@ from numpy.testing import (assert_array_less, assert_almost_equal, assert_equal) import numpy as np + from scipy import fft,ifft from MDAnalysisTests.datafiles import PSF, DCD, DCD +import pytest + SELECTION = 'backbone and name CA and resid 1-10' +NSTEP = 1000 @pytest.fixture(scope='module') def u(): return mda.Universe(PSF, DCD) -@ -def test_class_construct(u): +@pytest.fixture(scope='module') +def msd(u): m = MSD(u, SELECTION, msd_type='xyz', position_treatment='atom', mass_weighted=False) m.run() + return m -def unit_step_traj(u): - nstep = 1000 - x = np.arange(nstep) - traj = np.hstack([x,y,z]) - m = MSD(u, SELECTION, msd_type='xyz', position_treatment='atom', mass_weighted=False) +@pytest.fixture(scope='module') +def msd_fft(u): + m = MSD(u, SELECTION, msd_type='xyz', position_treatment='atom', mass_weighted=False, fft=True) + m.run() + return m + + +@pytest.fixture(scope='module') +def dimension_list(): + dimensions = ['xyz', 'xy', 'xz', 'yz', 'x', 'y', 'z'] + return dimensions + + +@pytest.fixture(scope='module') +def step_traj(): + x = np.arange(NSTEP) + traj = np.vstack([x,x,x]).T + traj_reshape = traj.reshape([NSTEP,1,3]) + u = mda.Universe.empty(1) + u.load_new(traj_reshape) + return u + +def characteristic_poly(n,d): + x = np.arange(1,n+1) + y = d*((x-1)*(x-1)) + return y + + + +def test_fft_vs_simple_default(msd, msd_fft): + timeseries_simple = msd.timeseries + print(timeseries_simple) + timeseries_fft = msd_fft.timeseries + print(timeseries_fft) + assert_almost_equal(timeseries_simple, timeseries_fft, decimal=5) + + +def test_fft_vs_simple_all_dims(dimension_list, u): + for dim in dimension_list: + print(dim) + m_simple = MSD(u, SELECTION, msd_type=dim, position_treatment='atom', mass_weighted=False, fft=False) + m_simple.run() + timeseries_simple = m_simple.timeseries + print(timeseries_simple) + m_fft = MSD(u,SELECTION, msd_type=dim, position_treatment='atom', mass_weighted=False, fft=True) + m_fft.run() + timeseries_fft = m_fft.timeseries + print(timeseries_fft) + assert_almost_equal(timeseries_simple, timeseries_fft, decimal=5) + +def test_simple_step_traj_3d(step_traj): # this should fit the polynomial 3x^2 - 6x +3 + m_simple = MSD(step_traj, 'all' , msd_type='xyz', position_treatment='atom', mass_weighted=False, fft=False) + m_simple.run() + poly = characteristic_poly(NSTEP,3) + for i in range(len(poly)): + if poly[i] != m_simple.timeseries[i]: + print('MISMATCH') + print(i) + print(poly[i]) + print(m_simple.timeseries[i]) + # for some reason, this is much more prone to roundoff error? + raise Exception + +def test_fft_step_traj_3d(step_traj): # this should fit the polynomial 3(x-1)**2 + m_fft = MSD(step_traj, 'all' , msd_type='xyz', position_treatment='atom', mass_weighted=False, fft=True) + m_fft.run() + poly3 = characteristic_poly(NSTEP,3) + assert_almost_equal(m_fft.timeseries, poly3) + +def test_fft_step_traj_2d(step_traj): # this should fit the polynomial 2(x-1)**2 + m_fft = MSD(step_traj, 'all' , msd_type='xy', position_treatment='atom', mass_weighted=False, fft=True) + m_fft.run() + poly2 = characteristic_poly(NSTEP,2) + assert_almost_equal(m_fft.timeseries, poly2) + +def test_fft_step_traj_1d(step_traj): # this should fit the polynomial (x-1)**2 + m_fft = MSD(step_traj, 'all' , msd_type='x', position_treatment='atom', mass_weighted=False, fft=True) + m_fft.run() + poly1 = characteristic_poly(NSTEP,1) + assert_almost_equal(m_fft.timeseries, poly1) From 44407e41e7422c0731678aea5c1818d4cd7056c5 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Thu, 12 Mar 2020 08:45:20 +1100 Subject: [PATCH 009/121] cleaned up tests and started working on docs --- package/MDAnalysis/analysis/msd.py | 108 +++++++----------- .../documentation_pages/analysis/msd.rst | 1 + .../documentation_pages/analysis_modules.rst | 2 +- .../MDAnalysisTests/analysis/test_msd.py | 53 ++++----- 4 files changed, 70 insertions(+), 94 deletions(-) create mode 100644 package/doc/sphinx/source/documentation_pages/analysis/msd.rst diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 4a5e5fe12a9..a1c5b59804f 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -21,6 +21,32 @@ # J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 # +""" +Mean Squared Displacement --- :mod:`MDAnalysis.analysis.msd` +============================================================== + +This module implements the calculation of Mean Squared Displacmements (MSDS). + + + +Algorithm +--------- + +1. build a graph of all phosphate distances < cutoff +2. identify the largest connected subgraphs +3. analyse first and second largest graph, which correspond to the leaflets + +For further details see [Michaud-Agrawal2011]_. + + +Classes and Functions +--------------------- + +.. autoclass:: MeanSquaredDisplacement + + +""" + from __future__ import division, absolute_import from six.moves import zip @@ -45,16 +71,15 @@ class MeanSquaredDisplacement(object): - def __init__(self, u, selection, msd_type='xyz', position_treatment='atom', mass_weighted=False, fft=False, kwargs=None, **basekwargs): - + def __init__(self, u, selection, msd_type='xyz', fft=True, kwargs=None, **basekwargs): + r"blah blah blah" + #args self.u = u self.selection = selection self.msd_type = msd_type - self.position_treatment = position_treatment - self.mass_weighted = mass_weighted self.fft = fft + #local - self.dim_fac = 0 self._dim = None self.atoms = None @@ -63,29 +88,13 @@ def __init__(self, u, selection, msd_type='xyz', position_treatment='atom', mass #result self.timeseries = None - - self.check_input() + #prep self._prepare() - def check_input(self): - self.check_masses() - self.parse_msd_type() - + def _prepare(self): + self.parse_msd_type() self.select_reference_positions() - #self.construct_arrays() - - def check_masses(self): - self.atoms = self.u.select_atoms(self.selection) - if (self.mass_weighted or self.position_treatment == 'com'): - masses = self.atoms.masses - if masses.any == None: - raise ValueError ('cannot have no mass for mass_weighted=True or position_treatment=com') - else: - pass - else: - pass - def parse_msd_type(self): @@ -121,24 +130,16 @@ def parse_msd_type(self): raise ValueError('invalid msd_type specified') def select_reference_positions(self): - if self.position_treatment == 'atom': - self._position_array = self.u.trajectory.timeseries(self.u.select_atoms(self.selection),order='fac') - self.N_particles = self._position_array.shape[1] - elif self.position_treatment == 'com': - raise NotImplementedError - #TODO work out timeseries for com - else: - raise ValueError('invalid position_treatment specified') + self._position_array = self.u.trajectory.timeseries(self.u.select_atoms(self.selection),order='fac') + self.N_particles = self._position_array.shape[1] def run(self): - if self.fft == True: - self.timeseries = self._run_fft_dim() + self.timeseries = self._run_fft() else: - self.timeseries = self._run_naieve() + self.timeseries = self._run_simple() - - def _run_naieve(self): # naieve algorithm pre vectorisation / without FFT + def _run_simple(self): # naieve algorithm without FFT # _position_array is shape time, nparticles, 3 msds_byparticle = np.zeros([self.n_frames, self.N_particles]) lagtimes = np.arange(1,self.n_frames) @@ -146,38 +147,17 @@ def _run_naieve(self): # naieve algorithm pre vectorisation / without FFT for n in range(self.N_particles): for lag in lagtimes: disp = self._position_array[:-lag,n,self._dim if lag else None] - self._position_array[lag:,n,self._dim] - sqdist = np.square(disp).sum(axis=1) - msds_byparticle[lag,n] = sqdist.mean() - msds = msds_byparticle.mean(axis=1) + sqdist = np.square(disp, dtype=np.float64).sum(axis=1, dtype=np.float64) #accumulation in anything other than f64 is innacurate + msds_byparticle[lag,n] = np.mean(sqdist, dtype=np.float64) + msds = msds_byparticle.mean(axis=1, dtype=np.float64) return msds def _run_fft(self): #with FFT - # _position_array is shape time, nparticles, 3 - particle_msds = [] - N=self._position_array.shape[0] - D=np.square(self._position_array).sum(axis=2) - D=np.append(D,np.zeros(self._position_array.shape[:2]), axis=0) - Q=2*D.sum(axis=0) - S1=np.zeros(self._position_array.shape[:2]) - for m in range(N): - Q=Q-D[m-1,:]-D[N-m,:] - S1[m,:]=Q/(N-m) - - corrs = [] - for i in range(self._position_array.shape[2]): - corrs.append(self.autocorrFFT(self._position_array[:,:,i])) - S2= np.sum(corrs,axis=0) - particle_msds.append(S1-2*S2) - - msds = np.concatenate(particle_msds,axis=1).mean(axis=-1) - return msds - - def _run_fft_dim(self): #with FFT # _position_array is shape time, nparticles, 3 particle_msds = [] reshape_positions = self._position_array[:,:,self._dim] N=reshape_positions.shape[0] - D=np.square(reshape_positions).sum(axis=2) + D=np.square(reshape_positions).sum(axis=2, dtype=np.float64) D=np.append(D,np.zeros(reshape_positions.shape[:2]), axis=0) Q=2*D.sum(axis=0) S1=np.zeros(reshape_positions.shape[:2]) @@ -188,10 +168,10 @@ def _run_fft_dim(self): #with FFT corrs = [] for i in range(reshape_positions.shape[2]): corrs.append(self.autocorrFFT(reshape_positions[:,:,i])) - S2= np.sum(corrs,axis=0) + S2= np.sum(corrs,axis=0,dtype=np.float64) particle_msds.append(S1-2*S2) - msds = np.concatenate(particle_msds,axis=1).mean(axis=-1) + msds = np.concatenate(particle_msds,axis=1).mean(axis=-1, dtype=np.float64) return msds @staticmethod diff --git a/package/doc/sphinx/source/documentation_pages/analysis/msd.rst b/package/doc/sphinx/source/documentation_pages/analysis/msd.rst new file mode 100644 index 00000000000..4fcc341139b --- /dev/null +++ b/package/doc/sphinx/source/documentation_pages/analysis/msd.rst @@ -0,0 +1 @@ +.. automodule:: MDAnalysis.analysis.msd diff --git a/package/doc/sphinx/source/documentation_pages/analysis_modules.rst b/package/doc/sphinx/source/documentation_pages/analysis_modules.rst index ad145f0cec1..ea1b04620a8 100644 --- a/package/doc/sphinx/source/documentation_pages/analysis_modules.rst +++ b/package/doc/sphinx/source/documentation_pages/analysis_modules.rst @@ -107,7 +107,7 @@ Structure analysis/helanal analysis/rdf analysis/dihedrals - + analysis/msd Volumetric analysis =================== diff --git a/testsuite/MDAnalysisTests/analysis/test_msd.py b/testsuite/MDAnalysisTests/analysis/test_msd.py index 2800db458cf..2366a1518cc 100644 --- a/testsuite/MDAnalysisTests/analysis/test_msd.py +++ b/testsuite/MDAnalysisTests/analysis/test_msd.py @@ -46,23 +46,21 @@ def u(): @pytest.fixture(scope='module') def msd(u): - m = MSD(u, SELECTION, msd_type='xyz', position_treatment='atom', mass_weighted=False) + m = MSD(u, SELECTION, msd_type='xyz', fft=False) m.run() return m @pytest.fixture(scope='module') def msd_fft(u): - m = MSD(u, SELECTION, msd_type='xyz', position_treatment='atom', mass_weighted=False, fft=True) + m = MSD(u, SELECTION, msd_type='xyz', fft=True) m.run() return m - @pytest.fixture(scope='module') def dimension_list(): dimensions = ['xyz', 'xy', 'xz', 'yz', 'x', 'y', 'z'] return dimensions - @pytest.fixture(scope='module') def step_traj(): x = np.arange(NSTEP) @@ -72,61 +70,58 @@ def step_traj(): u.load_new(traj_reshape) return u -def characteristic_poly(n,d): +def characteristic_poly(n,d): #polynomial that describes unit step trajectory MSD x = np.arange(1,n+1) y = d*((x-1)*(x-1)) return y - - def test_fft_vs_simple_default(msd, msd_fft): timeseries_simple = msd.timeseries - print(timeseries_simple) timeseries_fft = msd_fft.timeseries - print(timeseries_fft) assert_almost_equal(timeseries_simple, timeseries_fft, decimal=5) - def test_fft_vs_simple_all_dims(dimension_list, u): for dim in dimension_list: - print(dim) - m_simple = MSD(u, SELECTION, msd_type=dim, position_treatment='atom', mass_weighted=False, fft=False) + m_simple = MSD(u, SELECTION, msd_type=dim, fft=False) m_simple.run() timeseries_simple = m_simple.timeseries - print(timeseries_simple) - m_fft = MSD(u,SELECTION, msd_type=dim, position_treatment='atom', mass_weighted=False, fft=True) + m_fft = MSD(u,SELECTION, msd_type=dim, fft=True) m_fft.run() timeseries_fft = m_fft.timeseries - print(timeseries_fft) assert_almost_equal(timeseries_simple, timeseries_fft, decimal=5) -def test_simple_step_traj_3d(step_traj): # this should fit the polynomial 3x^2 - 6x +3 - m_simple = MSD(step_traj, 'all' , msd_type='xyz', position_treatment='atom', mass_weighted=False, fft=False) +def test_simple_step_traj_3d(step_traj): # this should fit the polynomial 3(x-1)**2 + m_simple = MSD(step_traj, 'all' , msd_type='xyz', fft=False) + m_simple.run() + poly3 = characteristic_poly(NSTEP,3) + assert_almost_equal(m_simple.timeseries, poly3) + +def test_simple_step_traj_2d(step_traj): # this should fit the polynomial 2(x-1)**2 + m_simple = MSD(step_traj, 'all' , msd_type='xy', fft=False) + m_simple.run() + poly2 = characteristic_poly(NSTEP,2) + assert_almost_equal(m_simple.timeseries, poly2) + +def test_simple_step_traj_1d(step_traj): # this should fit the polynomial (x-1)** + m_simple = MSD(step_traj, 'all' , msd_type='x', fft=False) m_simple.run() - poly = characteristic_poly(NSTEP,3) - for i in range(len(poly)): - if poly[i] != m_simple.timeseries[i]: - print('MISMATCH') - print(i) - print(poly[i]) - print(m_simple.timeseries[i]) - # for some reason, this is much more prone to roundoff error? - raise Exception + poly1 = characteristic_poly(NSTEP,1) + assert_almost_equal(m_simple.timeseries, poly1) def test_fft_step_traj_3d(step_traj): # this should fit the polynomial 3(x-1)**2 - m_fft = MSD(step_traj, 'all' , msd_type='xyz', position_treatment='atom', mass_weighted=False, fft=True) + m_fft = MSD(step_traj, 'all' , msd_type='xyz', fft=True) m_fft.run() poly3 = characteristic_poly(NSTEP,3) assert_almost_equal(m_fft.timeseries, poly3) def test_fft_step_traj_2d(step_traj): # this should fit the polynomial 2(x-1)**2 - m_fft = MSD(step_traj, 'all' , msd_type='xy', position_treatment='atom', mass_weighted=False, fft=True) + m_fft = MSD(step_traj, 'all' , msd_type='xy', fft=True) m_fft.run() poly2 = characteristic_poly(NSTEP,2) assert_almost_equal(m_fft.timeseries, poly2) def test_fft_step_traj_1d(step_traj): # this should fit the polynomial (x-1)**2 - m_fft = MSD(step_traj, 'all' , msd_type='x', position_treatment='atom', mass_weighted=False, fft=True) + m_fft = MSD(step_traj, 'all' , msd_type='x', fft=True) m_fft.run() poly1 = characteristic_poly(NSTEP,1) assert_almost_equal(m_fft.timeseries, poly1) From 4711fb6e513a340ef9d7cdb14fc348ba7a5df8d1 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Fri, 13 Mar 2020 12:58:44 +1100 Subject: [PATCH 010/121] ready to open WIP opull request --- package/MDAnalysis/analysis/msd.py | 86 +++++++++++++++++++----------- 1 file changed, 56 insertions(+), 30 deletions(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index a1c5b59804f..721c0e9923d 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -25,18 +25,17 @@ Mean Squared Displacement --- :mod:`MDAnalysis.analysis.msd` ============================================================== -This module implements the calculation of Mean Squared Displacmements (MSDS). +This module implements the calculation of Mean Squared Displacmements (MSD). +MSDs can be used to characterise the speed at which particles move and has its roots +in the study of Brownian motion. For a thorough review see XXX et al. MSDs are computed from +the following expression +Where XX represents an ensemble average over +The computation of the MSD in this way can be computationally intensive due to it's N^2 scaling. +An algorithm to compute the MSD with Nlogn(N) scaling based on a Fast Fourier Transform is known and can be accessed by setting FFT=True. -Algorithm ---------- -1. build a graph of all phosphate distances < cutoff -2. identify the largest connected subgraphs -3. analyse first and second largest graph, which correspond to the leaflets - -For further details see [Michaud-Agrawal2011]_. Classes and Functions @@ -58,21 +57,48 @@ import numpy as np from scipy import fft,ifft - import logging - import MDAnalysis -import MDAnalysis.lib.distances -from MDAnalysis.lib.util import openany -from MDAnalysis.analysis.distances import distance_array -from MDAnalysis.core.groups import AtomGroup -from .base import AnalysisBase + class MeanSquaredDisplacement(object): + r"""Class representing a density on a regular cartesian grid. + + Parameters + ----------∏ + u : + An MDAnalysis Universe :class:`Universe` + selection : + An MDAnalysis selection string + + + + Attributes + ---------- + + + + + Notes + ----- + Notes + + + See Also + -------- + + + Examples + -------- + Typical use: + + + + """ + + def __init__(self, u, selection, msd_type='xyz', fft=True): - def __init__(self, u, selection, msd_type='xyz', fft=True, kwargs=None, **basekwargs): - r"blah blah blah" #args self.u = u self.selection = selection @@ -98,7 +124,7 @@ def _prepare(self): def parse_msd_type(self): - if self.msd_type == 'xyz': # full tensor + if self.msd_type == 'xyz': # full 3d self._dim = [0,1,2] self.dim_fac = 3.0 @@ -131,7 +157,7 @@ def parse_msd_type(self): def select_reference_positions(self): self._position_array = self.u.trajectory.timeseries(self.u.select_atoms(self.selection),order='fac') - self.N_particles = self._position_array.shape[1] + self.N_particles = self._position_array.shape[1] def run(self): if self.fft == True: @@ -154,7 +180,7 @@ def _run_simple(self): # naieve algorithm without FFT def _run_fft(self): #with FFT # _position_array is shape time, nparticles, 3 - particle_msds = [] + msds_byparticle = [] reshape_positions = self._position_array[:,:,self._dim] N=reshape_positions.shape[0] D=np.square(reshape_positions).sum(axis=2, dtype=np.float64) @@ -165,23 +191,23 @@ def _run_fft(self): #with FFT Q=Q-D[m-1,:]-D[N-m,:] S1[m,:]=Q/(N-m) - corrs = [] + S2accumulate = [] for i in range(reshape_positions.shape[2]): - corrs.append(self.autocorrFFT(reshape_positions[:,:,i])) - S2= np.sum(corrs,axis=0,dtype=np.float64) + S2accumulate.append(self.autocorrFFT(reshape_positions[:,:,i])) + S2= np.sum(S2accumulate,axis=0,dtype=np.float64) - particle_msds.append(S1-2*S2) - msds = np.concatenate(particle_msds,axis=1).mean(axis=-1, dtype=np.float64) + msds_byparticle.append(S1-2*S2) + msds = np.concatenate(msds_byparticle,axis=1).mean(axis=-1, dtype=np.float64) return msds @staticmethod def autocorrFFT(x): N=(x.shape[0]) - F = fft(x, n=2*N, axis=0) #2*N because of zero-padding + F = fft(x, n=2*N, axis=0) #zero pad to get non-cyclic autocorrelation PowerSpectralDensity = F * F.conjugate() - res = ifft(PowerSpectralDensity,axis=0) - res = (res[:N]).real #now we have the autocorrelation in convention B - n = np.arange(1, N+1)[::-1] #divide res(m) by (N-m) - return res/n[:, np.newaxis] #this is the autocorrelation in convention A + inverse = ifft(PowerSpectralDensity,axis=0) + autocorr = (inverse[:N]).real #autocorr convention B + n = np.arange(1, N+1)[::-1] + return autocorr/n[:, np.newaxis] #autocorr convention A From f9f7dd9ece05b1e4ffd9371c66618e8340c4b5c7 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Fri, 13 Mar 2020 21:14:31 +1100 Subject: [PATCH 011/121] worked on documentation and examples --- package/MDAnalysis/analysis/msd.py | 133 ++++++++++++++++++++--------- 1 file changed, 95 insertions(+), 38 deletions(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 721c0e9923d..3b0da8a8e44 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -21,29 +21,93 @@ # J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 # -""" +r""" Mean Squared Displacement --- :mod:`MDAnalysis.analysis.msd` ============================================================== -This module implements the calculation of Mean Squared Displacmements (MSD). +This module implements the calculation of Mean Squared Displacmements (MSDs). MSDs can be used to characterise the speed at which particles move and has its roots -in the study of Brownian motion. For a thorough review see XXX et al. MSDs are computed from -the following expression +in the study of Brownian motion. For a full explanation of the best practices for the computation of MSDs and the subsequent calculation of diffusion coefficents the reader is directed to [Maginn2019]_. +MSDs are computed from the following expression: + +.. math:: + + MSD(r_{d}) = \bigg{\langle} \frac{1}{N} \sum_{i=1}^{N} |r_{d} - r_{d}(t_0)|^2 \bigg{\rangle}_{t_{0}} + +Where :math:`N` is the number of equivalent particles the MSD is calculated over, :math:`r` are their coordinates and :math:`d` the desired +dimensionality of the MSD. Note that while the definition of the MSD is universal, there are many practical considerations to computing the MSD +that vary between implementations. In this module, we compute a "windowed" MSD, where the MSD is averaged over all possible lag times :math:`t \le t_{max}`, +where :math:`t_{max}` is the length of th trajectory. + +The computation of the MSD in this way can be computationally intensive due to it's :math:`N^2` scaling with respect to :math:`t_{max}` length of the trajectory. +An algorithm to compute the MSD with :math:`N log(N)` scaling based on a Fast Fourier Transform is known and can be accessed by setting fft=True [Calandri2011]_. +The python implementation was originally found here [SO2015]_. + +Computing an MSD +---------------- +The example computes a 2D MSD in the xy plane. +Files provided as part of the MDAnalysis test suite are used +(in the variables :data:`~MDAnalysis.tests.datafiles.PSF` and +:data:`~MDAnalysis.tests.datafiles.DCD`) + +First load all modules and test data + + >>> import MDAnalysis as mda + >>> import MDAnalysis.analysis.msd as msd + >>> from MDAnalysis.tests.datafiles import PSF, DCD + +Given a universe containing trajectory data we can extract the MSD +Analyis by using the class :class:`MeanSquaredDisplacement` + + >>> u = mda.Universe(PSF, DCD) + >>> MSD = msd.MeanSquaredDisplacement(u, 'backbone', msd_type='xy', fft=True) + >>> MSD.run() + +The MSD can then be accessed as + + >>> msd = MSD.timeseries + +Visual inspection of the MSD is important, so lets take a look at it with a simple plot. + + >>> import matplotlib.pyplot as plt + >>> nframes = len(u.trajectory) + >>> timestep = 2 # this needs to be the time between frames in you trajectory + >>> lagtimes = np.arange(nframes)*timestep # make the lag time axis + >>> plt.plot(msd, lagtimes) + >>> plt.show() + +We can see that the MSD is roughly linear between the XX and XX. +This can be confirmed with a log-log plot + +Computing a Diffusion Coefficent +-------------------------------- + + >>> import scipy.stats + >>> start_time = 500 + >>> start_index = start_time/timestep + >>> end_time = 1000 + >>> end_index = end_time/timestep + >>> end_index = start_time/timestep + >>> plt.plot(msd, lagtimes) + >>> plt.show() -Where XX represents an ensemble average over -The computation of the MSD in this way can be computationally intensive due to it's N^2 scaling. -An algorithm to compute the MSD with Nlogn(N) scaling based on a Fast Fourier Transform is known and can be accessed by setting FFT=True. +From the MSD, diffusion coefficents :math:`D` with the desired dimensionality :math:`d` can be computed by fitting the MSD with respect to the lag time to a linear model. +An example of this is shown in. +References +---------- +.. [Maginn2019] Maginn, E. J.; Messerly, R. A.; Carlson, D. J.; Roe, D. R.; Elliott, J. R. Best Practices for Computing Transport Properties 1. Self-Diffusivity and Viscosity from Equilibrium Molecular Dynamics [Article v1.0]. Living J. Comput. Mol. Sci. 2019, 1 (1). +.. [Calandri2011] Calandrini, V.; Pellegrini, E.; Calligari, P.; Hinsen, K.; Kneller, G. R. NMoldyn-Interfacing Spectroscopic Experiments, Molecular Dynamics Simulations and Models for Time Correlation Functions. Collect. SFN 2011, 12, 201–232. +.. [SO2015] https://stackoverflow.com/questions/34222272/computing-mean-square-displacement-using-python-and-fft Classes and Functions --------------------- .. autoclass:: MeanSquaredDisplacement - """ from __future__ import division, absolute_import @@ -63,38 +127,23 @@ class MeanSquaredDisplacement(object): - r"""Class representing a density on a regular cartesian grid. + r"""Class representing Mean Squared Displacement Parameters - ----------∏ - u : - An MDAnalysis Universe :class:`Universe` - selection : - An MDAnalysis selection string - - - - Attributes ---------- - - - - - Notes - ----- - Notes - - - See Also - -------- - - - Examples - -------- - Typical use: - + u : Universe + An MDAnalysis :class:`Universe` + selection : str + An MDAnalysis selection string + msd_type : str + The dimensions to use for the msd calculation + fft : bool + Use a fast FFT based algorithm for computation of the MSD - + Returns + ------- + timeseries : np.ndarray + the MSD as a function of lag time """ def __init__(self, u, selection, msd_type='xyz', fft=True): @@ -166,7 +215,6 @@ def run(self): self.timeseries = self._run_simple() def _run_simple(self): # naieve algorithm without FFT - # _position_array is shape time, nparticles, 3 msds_byparticle = np.zeros([self.n_frames, self.N_particles]) lagtimes = np.arange(1,self.n_frames) msds_byparticle[0,:] = np.zeros(self.N_particles) # preset the zero lagtime so we dont have to iterate through @@ -179,7 +227,6 @@ def _run_simple(self): # naieve algorithm without FFT return msds def _run_fft(self): #with FFT - # _position_array is shape time, nparticles, 3 msds_byparticle = [] reshape_positions = self._position_array[:,:,self._dim] N=reshape_positions.shape[0] @@ -202,6 +249,16 @@ def _run_fft(self): #with FFT @staticmethod def autocorrFFT(x): + r""" Calculates an autocorrelation function via an FFT + + Parameters + ---------- + x : array to compute the autocorrelation for + + Returns + autocorr : np.ndarray + the autocorrelation + """ N=(x.shape[0]) F = fft(x, n=2*N, axis=0) #zero pad to get non-cyclic autocorrelation PowerSpectralDensity = F * F.conjugate() From 2358a11e39c4a20c482a3d27a890d92c081134af Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Fri, 13 Mar 2020 22:38:59 +1100 Subject: [PATCH 012/121] more work on docs --- package/MDAnalysis/analysis/msd.py | 31 +++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 3b0da8a8e44..2bafac45d26 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -27,7 +27,7 @@ This module implements the calculation of Mean Squared Displacmements (MSDs). MSDs can be used to characterise the speed at which particles move and has its roots -in the study of Brownian motion. For a full explanation of the best practices for the computation of MSDs and the subsequent calculation of diffusion coefficents the reader is directed to [Maginn2019]_. +in the study of Brownian motion. For a full explanation of the best practices for the computation of MSDs and the subsequent calculation of self diffusivities the reader is directed to [Maginn2019]_. MSDs are computed from the following expression: .. math:: @@ -37,11 +37,11 @@ Where :math:`N` is the number of equivalent particles the MSD is calculated over, :math:`r` are their coordinates and :math:`d` the desired dimensionality of the MSD. Note that while the definition of the MSD is universal, there are many practical considerations to computing the MSD that vary between implementations. In this module, we compute a "windowed" MSD, where the MSD is averaged over all possible lag times :math:`t \le t_{max}`, -where :math:`t_{max}` is the length of th trajectory. +where :math:`t_{max}` is the length of the trajectory, thereby maximising the number of samples. -The computation of the MSD in this way can be computationally intensive due to it's :math:`N^2` scaling with respect to :math:`t_{max}` length of the trajectory. +The computation of the MSD in this way can be computationally intensive due to it's :math:`N^2` scaling with respect to :math:`t_{max}`. An algorithm to compute the MSD with :math:`N log(N)` scaling based on a Fast Fourier Transform is known and can be accessed by setting fft=True [Calandri2011]_. -The python implementation was originally found here [SO2015]_. +The python implementation was originally presented here [SO2015]_. Computing an MSD ---------------- @@ -76,24 +76,29 @@ >>> plt.plot(msd, lagtimes) >>> plt.show() -We can see that the MSD is roughly linear between the XX and XX. -This can be confirmed with a log-log plot +We can see that the MSD is roughly linear between a lag-time of 500 and 1000. +This can be confirmed with a log-log plot as is often reccomended[Maginn2019]_. -Computing a Diffusion Coefficent +Computing Self Diffusivity -------------------------------- +Diffusion Coefficents are closely related to the MSD. - >>> import scipy.stats +.. math:: + + D_d = \frac{1}{2d} \lim_{t \to \infty} \frac{d}{dt} MSD(r_{d}) + +From the MSD, diffusion coefficents :math:`D` with the desired dimensionality :math:`d` can be computed by fitting the MSD with respect to the lag time to a linear model. +An example of this is shown in. + + >>> import scipy.stats.linregress as lr >>> start_time = 500 >>> start_index = start_time/timestep >>> end_time = 1000 >>> end_index = end_time/timestep - >>> end_index = start_time/timestep - >>> plt.plot(msd, lagtimes) - >>> plt.show() + >>> linear_model = lr(lagtimes[start_index:end_index], msd[start_index:end_index]) + -From the MSD, diffusion coefficents :math:`D` with the desired dimensionality :math:`d` can be computed by fitting the MSD with respect to the lag time to a linear model. -An example of this is shown in. References From a8d400ae55ded34e2a0985540bb3e8f903e0f030 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Sat, 14 Mar 2020 12:00:38 +1100 Subject: [PATCH 013/121] doc and example improvements --- package/MDAnalysis/analysis/msd.py | 55 +++++++++++++++++++++++------- 1 file changed, 42 insertions(+), 13 deletions(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 2bafac45d26..445cc5069f3 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -76,19 +76,21 @@ >>> plt.plot(msd, lagtimes) >>> plt.show() -We can see that the MSD is roughly linear between a lag-time of 500 and 1000. -This can be confirmed with a log-log plot as is often reccomended[Maginn2019]_. +We can see that the MSD is roughly linear between a lag-time of 500 and 1000. Linearity of a segment of the MSD is required to accurately determine self diffusivity. +This linear segment represents the so called "middle" of the MSD plot, where ballistic trajectories at short time-lags are excluded along with poorly averaged data at long time-lags. +This can be confirmed with a log-log plot as is often reccomended [Maginn2019]_ where the "middle" segment can be identified as having a slope of 1. +Now that we have identified what segment of our MSD to analyse, lets compute a self diffusivity. Computing Self Diffusivity -------------------------------- -Diffusion Coefficents are closely related to the MSD. +Self diffusivity is closely related to the MSD. .. math:: D_d = \frac{1}{2d} \lim_{t \to \infty} \frac{d}{dt} MSD(r_{d}) -From the MSD, diffusion coefficents :math:`D` with the desired dimensionality :math:`d` can be computed by fitting the MSD with respect to the lag time to a linear model. -An example of this is shown in. +From the MSD, self diffusivities :math:`D` with the desired dimensionality :math:`d` can be computed by fitting the MSD with respect to the lag time to a linear model. +An example of this is shown below. >>> import scipy.stats.linregress as lr >>> start_time = 500 @@ -96,9 +98,20 @@ >>> end_time = 1000 >>> end_index = end_time/timestep >>> linear_model = lr(lagtimes[start_index:end_index], msd[start_index:end_index]) + >>> slope = linear_model.slope + >>> error = linear_model.r-value + >>> D = slope * 1/(2*MSD._dim_fac) #dim_fac is 2 as we computed a 2D msd ('xy') +We have now computed a self diffusivity! +Notes +_____ +There are several factors that must be taken into account when setting up and processing trajectories for computation of self diffusivities. +These include specific instructions around simulation settings, using unwrapped trajectories and maintaining relativley small elapsed time between saved frames. +Additionally corrections for finite size effects are sometimes employed along with varied means of estimating errors. +The reader is directed to the following review, which describes many of the common pitfalls [Maginn2019]_. There are other ways to compute self diffusivity including from a Green-Kubo integral. At this point in time these methods are beyond the scope of this module + References @@ -112,6 +125,7 @@ --------------------- .. autoclass:: MeanSquaredDisplacement + :members: """ @@ -141,15 +155,28 @@ class MeanSquaredDisplacement(object): selection : str An MDAnalysis selection string msd_type : str - The dimensions to use for the msd calculation + The dimensions to use for the msd calculation: + one of ('xyz', 'xy', 'yz', 'xz', 'x', 'y', 'z') fft : bool Use a fast FFT based algorithm for computation of the MSD - + Returns ------- - timeseries : np.ndarray + timeseries : :class:`np.ndarray` the MSD as a function of lag time + """ + # Attributes + # ---------- + # _dim_fac : int + # dimensionality of the MSD + # _dim : arraylike + # array used to slice the trajectory along the xyz axis to acheive the correct dimensionality + # _position_array : :class:`np.ndarray` + # positions used to calculate the MSD + # N_particles : int + # number of particles + def __init__(self, u, selection, msd_type='xyz', fft=True): @@ -177,7 +204,7 @@ def _prepare(self): self.select_reference_positions() def parse_msd_type(self): - + if self.msd_type == 'xyz': # full 3d self._dim = [0,1,2] self.dim_fac = 3.0 @@ -231,7 +258,7 @@ def _run_simple(self): # naieve algorithm without FFT msds = msds_byparticle.mean(axis=1, dtype=np.float64) return msds - def _run_fft(self): #with FFT + def _run_fft(self): #with FFT msds_byparticle = [] reshape_positions = self._position_array[:,:,self._dim] N=reshape_positions.shape[0] @@ -254,14 +281,16 @@ def _run_fft(self): #with FFT @staticmethod def autocorrFFT(x): - r""" Calculates an autocorrelation function via an FFT + r""" Calculates an autocorrelation function of the input signal via an FFT Parameters ---------- - x : array to compute the autocorrelation for + x : :class:`np.ndarray` + array to compute the autocorrelation for Returns - autocorr : np.ndarray + ------- + autocorr : :class:`np.ndarray` the autocorrelation """ N=(x.shape[0]) From 0a960e42a659e3edcb880eb86a7a9fa70754d281 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Sun, 15 Mar 2020 00:39:23 +1100 Subject: [PATCH 014/121] improved numerical performance of FFT based algorithm --- package/MDAnalysis/analysis/msd.py | 37 +++++++++----- .../MDAnalysisTests/analysis/test_msd.py | 51 ++++++++++++------- 2 files changed, 56 insertions(+), 32 deletions(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 445cc5069f3..7e2a994878b 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -45,22 +45,23 @@ Computing an MSD ---------------- -The example computes a 2D MSD in the xy plane. +This example computes a 2D MSD for the movement of phospholipid headgroups in the xy plane. +This is a common starting point for computing self diffusivity of phospholipids in a bilayer. Files provided as part of the MDAnalysis test suite are used -(in the variables :data:`~MDAnalysis.tests.datafiles.PSF` and -:data:`~MDAnalysis.tests.datafiles.DCD`) +(in the variables :data:`~MDAnalysis.tests.datafiles.GRO_MEMPROT` and +:data:`~MDAnalysis.tests.datafiles.XTC_MEMPROT`) First load all modules and test data >>> import MDAnalysis as mda >>> import MDAnalysis.analysis.msd as msd - >>> from MDAnalysis.tests.datafiles import PSF, DCD + >>> from MDAnalysis.tests.datafiles import GRO_MEMPROT, XTC_MEMPROT Given a universe containing trajectory data we can extract the MSD Analyis by using the class :class:`MeanSquaredDisplacement` - >>> u = mda.Universe(PSF, DCD) - >>> MSD = msd.MeanSquaredDisplacement(u, 'backbone', msd_type='xy', fft=True) + >>> u = mda.Universe(GRO_MEMPROT, XTC_MEMPROT) + >>> MSD = msd.MeanSquaredDisplacement(u, 'name PO4', msd_type='xy', fft=True) >>> MSD.run() The MSD can then be accessed as @@ -139,9 +140,10 @@ import functools import numpy as np -from scipy import fft,ifft +from numpy.fft import fft,ifft import logging import MDAnalysis +import tidynamics @@ -258,18 +260,17 @@ def _run_simple(self): # naieve algorithm without FFT msds = msds_byparticle.mean(axis=1, dtype=np.float64) return msds - def _run_fft(self): #with FFT + def _run_fft(self): #with FFT, np.f64 bit prescision required. msds_byparticle = [] - reshape_positions = self._position_array[:,:,self._dim] + reshape_positions = self._position_array[:,:,self._dim].astype(np.float64) N=reshape_positions.shape[0] D=np.square(reshape_positions).sum(axis=2, dtype=np.float64) D=np.append(D,np.zeros(reshape_positions.shape[:2]), axis=0) - Q=2*D.sum(axis=0) - S1=np.zeros(reshape_positions.shape[:2]) + Q=2*D.sum(axis=0, dtype=np.float64) + S1=np.zeros(reshape_positions.shape[:2],dtype=np.float64) for m in range(N): Q=Q-D[m-1,:]-D[N-m,:] S1[m,:]=Q/(N-m) - S2accumulate = [] for i in range(reshape_positions.shape[2]): S2accumulate.append(self.autocorrFFT(reshape_positions[:,:,i])) @@ -294,7 +295,17 @@ def autocorrFFT(x): the autocorrelation """ N=(x.shape[0]) - F = fft(x, n=2*N, axis=0) #zero pad to get non-cyclic autocorrelation + + #find closest power of 2 (code adapted from tidynamics) + current_exp = int(np.ceil(np.log2(N+1))) + if N == 2**current_exp: + n_fft = N + if N < 2**current_exp: + n_fft = 2**current_exp + elif N > 2**current_exp: + n_fft = 2**(current_exp+1) + + F = fft(x, n=2*n_fft, axis=0) #zero pad to get non-cyclic autocorrelation PowerSpectralDensity = F * F.conjugate() inverse = ifft(PowerSpectralDensity,axis=0) autocorr = (inverse[:N]).real #autocorr convention B diff --git a/testsuite/MDAnalysisTests/analysis/test_msd.py b/testsuite/MDAnalysisTests/analysis/test_msd.py index 2366a1518cc..4a35f8f4dab 100644 --- a/testsuite/MDAnalysisTests/analysis/test_msd.py +++ b/testsuite/MDAnalysisTests/analysis/test_msd.py @@ -31,14 +31,14 @@ assert_almost_equal, assert_equal) import numpy as np -from scipy import fft,ifft +from numpy.fft import fft,ifft from MDAnalysisTests.datafiles import PSF, DCD, DCD import pytest SELECTION = 'backbone and name CA and resid 1-10' -NSTEP = 1000 +NSTEP = 10000 @pytest.fixture(scope='module') def u(): @@ -62,7 +62,7 @@ def dimension_list(): return dimensions @pytest.fixture(scope='module') -def step_traj(): +def step_traj(): # constant velocity x = np.arange(NSTEP) traj = np.vstack([x,x,x]).T traj_reshape = traj.reshape([NSTEP,1,3]) @@ -70,15 +70,25 @@ def step_traj(): u.load_new(traj_reshape) return u +@pytest.fixture(scope='module') +def random_walk_3d(): + steps = -1 + 2*np.random.randint(0, 2, size=(NSTEP, 3)) + traj = np.cumsum(steps, axis=0) + traj_reshape = traj.reshape([NSTEP,1,3]) + u = mda.Universe.empty(1) + u.load_new(traj_reshape) + return traj + + def characteristic_poly(n,d): #polynomial that describes unit step trajectory MSD - x = np.arange(1,n+1) - y = d*((x-1)*(x-1)) + x = np.arange(0,n) + y = d*x*x return y def test_fft_vs_simple_default(msd, msd_fft): timeseries_simple = msd.timeseries timeseries_fft = msd_fft.timeseries - assert_almost_equal(timeseries_simple, timeseries_fft, decimal=5) + assert_almost_equal(timeseries_simple, timeseries_fft, decimal=4) def test_fft_vs_simple_all_dims(dimension_list, u): for dim in dimension_list: @@ -88,41 +98,44 @@ def test_fft_vs_simple_all_dims(dimension_list, u): m_fft = MSD(u,SELECTION, msd_type=dim, fft=True) m_fft.run() timeseries_fft = m_fft.timeseries - assert_almost_equal(timeseries_simple, timeseries_fft, decimal=5) + assert_almost_equal(timeseries_simple, timeseries_fft, decimal=4) -def test_simple_step_traj_3d(step_traj): # this should fit the polynomial 3(x-1)**2 +def test_simple_step_traj_3d(step_traj): # this should fit the polynomial 3x**2 m_simple = MSD(step_traj, 'all' , msd_type='xyz', fft=False) m_simple.run() + + print(m_simple.timeseries) poly3 = characteristic_poly(NSTEP,3) - assert_almost_equal(m_simple.timeseries, poly3) + print(poly3) + assert_almost_equal(m_simple.timeseries, poly3, decimal=4) -def test_simple_step_traj_2d(step_traj): # this should fit the polynomial 2(x-1)**2 +def test_simple_step_traj_2d(step_traj): # this should fit the polynomial 2x**2 m_simple = MSD(step_traj, 'all' , msd_type='xy', fft=False) m_simple.run() poly2 = characteristic_poly(NSTEP,2) - assert_almost_equal(m_simple.timeseries, poly2) + assert_almost_equal(m_simple.timeseries, poly2, decimal=4) -def test_simple_step_traj_1d(step_traj): # this should fit the polynomial (x-1)** +def test_simple_step_traj_1d(step_traj): # this should fit the polynomial x** m_simple = MSD(step_traj, 'all' , msd_type='x', fft=False) m_simple.run() poly1 = characteristic_poly(NSTEP,1) - assert_almost_equal(m_simple.timeseries, poly1) + assert_almost_equal(m_simple.timeseries, poly1,decimal=4) -def test_fft_step_traj_3d(step_traj): # this should fit the polynomial 3(x-1)**2 +def test_fft_step_traj_3d(step_traj): # this should fit the polynomial 3x**2 m_fft = MSD(step_traj, 'all' , msd_type='xyz', fft=True) m_fft.run() poly3 = characteristic_poly(NSTEP,3) - assert_almost_equal(m_fft.timeseries, poly3) + assert_almost_equal(m_fft.timeseries, poly3, decimal=4) -def test_fft_step_traj_2d(step_traj): # this should fit the polynomial 2(x-1)**2 +def test_fft_step_traj_2d(step_traj): # this should fit the polynomial 2x**2 m_fft = MSD(step_traj, 'all' , msd_type='xy', fft=True) m_fft.run() poly2 = characteristic_poly(NSTEP,2) - assert_almost_equal(m_fft.timeseries, poly2) + assert_almost_equal(m_fft.timeseries, poly2, decimal=4) -def test_fft_step_traj_1d(step_traj): # this should fit the polynomial (x-1)**2 +def test_fft_step_traj_1d(step_traj): # this should fit the polynomial x**2 m_fft = MSD(step_traj, 'all' , msd_type='x', fft=True) m_fft.run() poly1 = characteristic_poly(NSTEP,1) - assert_almost_equal(m_fft.timeseries, poly1) + assert_almost_equal(m_fft.timeseries, poly1, decimal=4) From cc11cc6ec25bcae28599f5904fa49f6a9f221270 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Sun, 15 Mar 2020 13:57:06 +1100 Subject: [PATCH 015/121] added in tidynamics tests --- package/MDAnalysis/analysis/msd.py | 51 ++++++++++++++++--- .../MDAnalysisTests/analysis/test_msd.py | 43 ++++++++++++---- 2 files changed, 79 insertions(+), 15 deletions(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 7e2a994878b..3bcd9efae9b 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -108,13 +108,13 @@ Notes _____ + There are several factors that must be taken into account when setting up and processing trajectories for computation of self diffusivities. These include specific instructions around simulation settings, using unwrapped trajectories and maintaining relativley small elapsed time between saved frames. Additionally corrections for finite size effects are sometimes employed along with varied means of estimating errors. The reader is directed to the following review, which describes many of the common pitfalls [Maginn2019]_. There are other ways to compute self diffusivity including from a Green-Kubo integral. At this point in time these methods are beyond the scope of this module - References ---------- @@ -164,8 +164,8 @@ class MeanSquaredDisplacement(object): Returns ------- - timeseries : :class:`np.ndarray` - the MSD as a function of lag time + MSD : :class:`MeanSquaredDisplacement` + the MSD class """ # Attributes @@ -206,6 +206,20 @@ def _prepare(self): self.select_reference_positions() def parse_msd_type(self): + r""" Sets up the desired dimensionality of the MSD + + Parameters + ---------- + self.msd_type : str + the desired dimensionality fo the MSD + + Returns + ------- + self._dim : list + array-like used to slice the positions to obtain desired dimensionality + self.dim_fac : float + dimension factor d of the MSD + """ if self.msd_type == 'xyz': # full 3d self._dim = [0,1,2] @@ -243,12 +257,26 @@ def select_reference_positions(self): self.N_particles = self._position_array.shape[1] def run(self): + r""" Wrapper to calculate the MSD via either the "simple" or "fft" algorithm. + """ if self.fft == True: self.timeseries = self._run_fft() else: self.timeseries = self._run_simple() def _run_simple(self): # naieve algorithm without FFT + r""" Calculates the MSD via the naieve "windowed" algorithm + + Parameters + ---------- + self._position_array : :class:`np.ndarray` + the particle positions over the course of the trajectory shape (nframes,Nparticles,3) + + Returns + ------- + self.timeseries : :class:`np.ndarray` + the MSD as a function of lagtime + """ msds_byparticle = np.zeros([self.n_frames, self.N_particles]) lagtimes = np.arange(1,self.n_frames) msds_byparticle[0,:] = np.zeros(self.N_particles) # preset the zero lagtime so we dont have to iterate through @@ -261,6 +289,18 @@ def _run_simple(self): # naieve algorithm without FFT return msds def _run_fft(self): #with FFT, np.f64 bit prescision required. + r""" Calculates the MSD via the FCA fast correlation algorithm + + Parameters + ---------- + self._position_array : :class:`np.ndarray` + the particle positions over the course of the trajectory shape (nframes,Nparticles,3) + + Returns + ------- + self.timeseries : :class:`np.ndarray` + the MSD as a function of lagtime + """ msds_byparticle = [] reshape_positions = self._position_array[:,:,self._dim].astype(np.float64) N=reshape_positions.shape[0] @@ -304,12 +344,11 @@ def autocorrFFT(x): n_fft = 2**current_exp elif N > 2**current_exp: n_fft = 2**(current_exp+1) + #closest power of 2 gives the fastest FFTs F = fft(x, n=2*n_fft, axis=0) #zero pad to get non-cyclic autocorrelation PowerSpectralDensity = F * F.conjugate() inverse = ifft(PowerSpectralDensity,axis=0) autocorr = (inverse[:N]).real #autocorr convention B n = np.arange(1, N+1)[::-1] - return autocorr/n[:, np.newaxis] #autocorr convention A - - + return autocorr/n[:, np.newaxis] #autocorr convention A \ No newline at end of file diff --git a/testsuite/MDAnalysisTests/analysis/test_msd.py b/testsuite/MDAnalysisTests/analysis/test_msd.py index 4a35f8f4dab..20b6cde3fec 100644 --- a/testsuite/MDAnalysisTests/analysis/test_msd.py +++ b/testsuite/MDAnalysisTests/analysis/test_msd.py @@ -23,7 +23,6 @@ from __future__ import division, absolute_import, print_function - import MDAnalysis as mda from MDAnalysis.analysis.msd import MeanSquaredDisplacement as MSD @@ -33,29 +32,39 @@ from numpy.fft import fft,ifft -from MDAnalysisTests.datafiles import PSF, DCD, DCD +from MDAnalysisTests.datafiles import PSF, DCD, RANDOM_WALK, RANDOM_WALK_TOPO import pytest +import tidynamics SELECTION = 'backbone and name CA and resid 1-10' NSTEP = 10000 +#universe @pytest.fixture(scope='module') def u(): return mda.Universe(PSF, DCD) +@pytest.fixture(scope='module') +def random_walk_u(): + #100x100 + return mda.Universe(RANDOM_WALK_TOPO, RANDOM_WALK) + +#non fft msd @pytest.fixture(scope='module') def msd(u): m = MSD(u, SELECTION, msd_type='xyz', fft=False) m.run() return m +#fft msd @pytest.fixture(scope='module') def msd_fft(u): m = MSD(u, SELECTION, msd_type='xyz', fft=True) m.run() return m +#all possible dimensions @pytest.fixture(scope='module') def dimension_list(): dimensions = ['xyz', 'xy', 'xz', 'yz', 'x', 'y', 'z'] @@ -70,21 +79,20 @@ def step_traj(): # constant velocity u.load_new(traj_reshape) return u -@pytest.fixture(scope='module') def random_walk_3d(): steps = -1 + 2*np.random.randint(0, 2, size=(NSTEP, 3)) traj = np.cumsum(steps, axis=0) traj_reshape = traj.reshape([NSTEP,1,3]) u = mda.Universe.empty(1) u.load_new(traj_reshape) - return traj - + return u, traj def characteristic_poly(n,d): #polynomial that describes unit step trajectory MSD x = np.arange(0,n) y = d*x*x return y - + +#testing on the PSF, DCD trajectory def test_fft_vs_simple_default(msd, msd_fft): timeseries_simple = msd.timeseries timeseries_fft = msd_fft.timeseries @@ -100,13 +108,11 @@ def test_fft_vs_simple_all_dims(dimension_list, u): timeseries_fft = m_fft.timeseries assert_almost_equal(timeseries_simple, timeseries_fft, decimal=4) +#testing on step trajectory def test_simple_step_traj_3d(step_traj): # this should fit the polynomial 3x**2 m_simple = MSD(step_traj, 'all' , msd_type='xyz', fft=False) m_simple.run() - - print(m_simple.timeseries) poly3 = characteristic_poly(NSTEP,3) - print(poly3) assert_almost_equal(m_simple.timeseries, poly3, decimal=4) def test_simple_step_traj_2d(step_traj): # this should fit the polynomial 2x**2 @@ -139,3 +145,22 @@ def test_fft_step_traj_1d(step_traj): # this should fit the polynomial x**2 poly1 = characteristic_poly(NSTEP,1) assert_almost_equal(m_fft.timeseries, poly1, decimal=4) +#test that tidynamics and our code give the same result for an arbitrary random walk +def test_tidynamics_msd(): + u, array = random_walk_3d() + msd_mda = MSD(u, 'all', msd_type='xyz', fft=True) + msd_mda.run() + msd_mda_msd = msd_mda.timeseries + msd_tidy = tidynamics.msd(array) + assert_almost_equal(msd_mda_msd, msd_tidy) + +#regress against random_walk test data +def test_random_walk_u(random_walk_u): + print(len(random_walk_u.trajectory)) + msd_rw = MSD(random_walk_u, 'all', msd_type='xyz', fft=True) + msd_rw.run() + print(msd_rw.timeseries) + +#regress our random walk against tidynamics + + From e9b17fe8ce61ebaf81d1bb8ead4a9614eed84a6b Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Sun, 15 Mar 2020 15:39:25 +1100 Subject: [PATCH 016/121] clean up deps --- package/MDAnalysis/analysis/msd.py | 9 ++--- .../MDAnalysisTests/analysis/test_msd.py | 35 ++++++++++++++----- 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 3bcd9efae9b..cad3214dacb 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -20,6 +20,7 @@ # MDAnalysis: A Toolkit for the Analysis of Molecular Dynamics Simulations. # J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 # +# This module written by Hugo MacDermott-Opeskin 2020 r""" Mean Squared Displacement --- :mod:`MDAnalysis.analysis.msd` @@ -143,9 +144,6 @@ from numpy.fft import fft,ifft import logging import MDAnalysis -import tidynamics - - class MeanSquaredDisplacement(object): r"""Class representing Mean Squared Displacement @@ -253,7 +251,10 @@ def parse_msd_type(self): raise ValueError('invalid msd_type specified') def select_reference_positions(self): - self._position_array = self.u.trajectory.timeseries(self.u.select_atoms(self.selection),order='fac') + #memory transfer required to access coordinate array + self.u.transfer_to_memory() + idxs = self.u.select_atoms(self.selection).indices + self._position_array = self.u.trajectory.coordinate_array[:,idxs, :] self.N_particles = self._position_array.shape[1] def run(self): diff --git a/testsuite/MDAnalysisTests/analysis/test_msd.py b/testsuite/MDAnalysisTests/analysis/test_msd.py index 20b6cde3fec..b0889e9c057 100644 --- a/testsuite/MDAnalysisTests/analysis/test_msd.py +++ b/testsuite/MDAnalysisTests/analysis/test_msd.py @@ -151,16 +151,35 @@ def test_tidynamics_msd(): msd_mda = MSD(u, 'all', msd_type='xyz', fft=True) msd_mda.run() msd_mda_msd = msd_mda.timeseries - msd_tidy = tidynamics.msd(array) - assert_almost_equal(msd_mda_msd, msd_tidy) + msd_tidy = tidynamics.msd(array.astype(np.float64)) + assert_almost_equal(msd_mda_msd, msd_tidy, decimal=5) -#regress against random_walk test data -def test_random_walk_u(random_walk_u): - print(len(random_walk_u.trajectory)) +#test that tidynamics and our code give the same result for SPECIFIC random walk +def test_random_walk_tidynamics(random_walk_u): msd_rw = MSD(random_walk_u, 'all', msd_type='xyz', fft=True) msd_rw.run() - print(msd_rw.timeseries) + array = msd_rw._position_array.astype(np.float64) + tidy_msds = np.zeros(msd_rw.n_frames) + count = 0 + for mol in range(array.shape[1]): + pos = array[:,mol,:] + mol_msd = tidynamics.msd(pos) + tidy_msds += mol_msd + count += 1.0 + msd_tidy = tidy_msds /count + assert_almost_equal(msd_tidy, msd_rw.timeseries, decimal=5) -#regress our random walk against tidynamics +#regress against random_walk test data +def test_random_walk_u_simple(random_walk_u): + msd_rw = MSD(random_walk_u, 'all', msd_type='xyz', fft=False) + msd_rw.run() + norm = np.linalg.norm(msd_rw.timeseries) + val = 3932.39927487146 + assert_almost_equal(norm, val, decimal=5) - +def test_random_walk_u_fft(random_walk_u): + msd_rw = MSD(random_walk_u, 'all', msd_type='xyz', fft=True) + msd_rw.run() + norm = np.linalg.norm(msd_rw.timeseries) + val = 3932.39927487146 + assert_almost_equal(norm, val, decimal=5) From 09b545887ace54bc457e4b10523ad69d47f881fa Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Sun, 15 Mar 2020 15:50:42 +1100 Subject: [PATCH 017/121] added tidynamics to test deps --- testsuite/setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/testsuite/setup.py b/testsuite/setup.py index 52cfef47471..9268c0dc02e 100755 --- a/testsuite/setup.py +++ b/testsuite/setup.py @@ -185,6 +185,7 @@ def run(self): 'hypothesis', 'psutil>=4.0.2', 'mock>=2.0.0', # replace with unittest.mock in python 3 only version + 'tidynamics>=1.0.0' ], # had 'KeyError' as zipped egg (2MB savings are not worth the # trouble) From a06294ba7ecced9df34ffa2cb88adcc3605b3838 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Sun, 15 Mar 2020 16:59:28 +1100 Subject: [PATCH 018/121] test tidynymaics in test block and updated docs --- package/MDAnalysis/analysis/msd.py | 38 +++++++++++++++--------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index cad3214dacb..087d1ac074f 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -37,32 +37,31 @@ Where :math:`N` is the number of equivalent particles the MSD is calculated over, :math:`r` are their coordinates and :math:`d` the desired dimensionality of the MSD. Note that while the definition of the MSD is universal, there are many practical considerations to computing the MSD -that vary between implementations. In this module, we compute a "windowed" MSD, where the MSD is averaged over all possible lag times :math:`t \le t_{max}`, -where :math:`t_{max}` is the length of the trajectory, thereby maximising the number of samples. +that vary between implementations. In this module, we compute a "windowed" MSD, where the MSD is averaged over all possible lag times :math:`\tau \le \tau_{max}`, +where :math:`\tau_{max}` is the length of the trajectory, thereby maximising the number of samples. -The computation of the MSD in this way can be computationally intensive due to it's :math:`N^2` scaling with respect to :math:`t_{max}`. +The computation of the MSD in this way can be computationally intensive due to it's :math:`N^2` scaling with respect to :math:`\tau_{max}`. An algorithm to compute the MSD with :math:`N log(N)` scaling based on a Fast Fourier Transform is known and can be accessed by setting fft=True [Calandri2011]_. The python implementation was originally presented here [SO2015]_. Computing an MSD ---------------- -This example computes a 2D MSD for the movement of phospholipid headgroups in the xy plane. -This is a common starting point for computing self diffusivity of phospholipids in a bilayer. +This example computes a 3D MSD for the movement of 100 particles undergoing a random walk. Files provided as part of the MDAnalysis test suite are used -(in the variables :data:`~MDAnalysis.tests.datafiles.GRO_MEMPROT` and -:data:`~MDAnalysis.tests.datafiles.XTC_MEMPROT`) +(in the variables :data:`~MDAnalysis.tests.datafiles.RANDOM_WALK` and +:data:`~MDAnalysis.tests.datafiles.RANDOM_WALK_TOPO`) First load all modules and test data >>> import MDAnalysis as mda >>> import MDAnalysis.analysis.msd as msd - >>> from MDAnalysis.tests.datafiles import GRO_MEMPROT, XTC_MEMPROT + >>> from MDAnalysis.tests.datafiles import RANDOM_WALK, RANDOM_WALK_TOPO Given a universe containing trajectory data we can extract the MSD Analyis by using the class :class:`MeanSquaredDisplacement` - >>> u = mda.Universe(GRO_MEMPROT, XTC_MEMPROT) - >>> MSD = msd.MeanSquaredDisplacement(u, 'name PO4', msd_type='xy', fft=True) + >>> u = mda.Universe(RANDOM_WALK, RANDOM_WALK_TOPO) + >>> MSD = msd.MeanSquaredDisplacement(u, 'all', msd_type='xyz', fft=True) >>> MSD.run() The MSD can then be accessed as @@ -73,14 +72,15 @@ >>> import matplotlib.pyplot as plt >>> nframes = len(u.trajectory) - >>> timestep = 2 # this needs to be the time between frames in you trajectory + >>> timestep = 1 # this needs to be the actual time between frames in your trajectory >>> lagtimes = np.arange(nframes)*timestep # make the lag time axis >>> plt.plot(msd, lagtimes) >>> plt.show() -We can see that the MSD is roughly linear between a lag-time of 500 and 1000. Linearity of a segment of the MSD is required to accurately determine self diffusivity. +We can see that the MSD is roughly linear, this is a numerical proof of a known theoretical result that the MSD of a random walk is approximately linear with respect to lagtime, where the slope is approximatley :math:`2*D`. +Note that the majority of MSDs computed from MD trajectories will not be this well behaved. Linearity of a segment of the MSD is required to accurately determine self diffusivity. This linear segment represents the so called "middle" of the MSD plot, where ballistic trajectories at short time-lags are excluded along with poorly averaged data at long time-lags. -This can be confirmed with a log-log plot as is often reccomended [Maginn2019]_ where the "middle" segment can be identified as having a slope of 1. +We can select the "middle" of the MSD by indexing the MSD and the time-lags. Appropriately linear segments of the MSD can be confirmed with a log-log plot as is often reccomended [Maginn2019]_ where the "middle" segment can be identified as having a slope of 1. Now that we have identified what segment of our MSD to analyse, lets compute a self diffusivity. Computing Self Diffusivity @@ -92,17 +92,17 @@ D_d = \frac{1}{2d} \lim_{t \to \infty} \frac{d}{dt} MSD(r_{d}) From the MSD, self diffusivities :math:`D` with the desired dimensionality :math:`d` can be computed by fitting the MSD with respect to the lag time to a linear model. -An example of this is shown below. +An example of this is shown below, using the MSD computed in the example above. The segment between :math:`\tau = 20` and :math:`\tau = 80` is used to demonstrate selection of an MSD segment. - >>> import scipy.stats.linregress as lr - >>> start_time = 500 + >>> from scipy.stats import linregress as lr + >>> start_time = 20 >>> start_index = start_time/timestep - >>> end_time = 1000 + >>> end_time = 80 >>> end_index = end_time/timestep >>> linear_model = lr(lagtimes[start_index:end_index], msd[start_index:end_index]) >>> slope = linear_model.slope - >>> error = linear_model.r-value - >>> D = slope * 1/(2*MSD._dim_fac) #dim_fac is 2 as we computed a 2D msd ('xy') + >>> error = linear_model.rvalue + >>> D = slope * 1/(2*MSD.dim_fac) #dim_fac is 3 as we computed a 3D msd ('xyz') We have now computed a self diffusivity! From 4cf63fdb49cdd781bbf3558aedc29bd2315c0779 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Sun, 15 Mar 2020 18:12:56 +1100 Subject: [PATCH 019/121] try and fix install --- package/setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package/setup.py b/package/setup.py index 01e8aedecc5..39db4d1893f 100755 --- a/package/setup.py +++ b/package/setup.py @@ -558,6 +558,8 @@ def dynamic_author_list(): 'scipy>=1.0.0', 'matplotlib>=1.5.1', 'mock', + 'tidynamics>=1.0.0', + ] if not os.name == 'nt': install_requires.append('gsd>=1.4.0') From e5e362992af549e96bb2557b50e78be94261fe9b Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Sun, 15 Mar 2020 20:07:45 +1100 Subject: [PATCH 020/121] fix single failed build --- testsuite/MDAnalysisTests/analysis/test_msd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testsuite/MDAnalysisTests/analysis/test_msd.py b/testsuite/MDAnalysisTests/analysis/test_msd.py index b0889e9c057..36fe4f5e631 100644 --- a/testsuite/MDAnalysisTests/analysis/test_msd.py +++ b/testsuite/MDAnalysisTests/analysis/test_msd.py @@ -38,7 +38,7 @@ import tidynamics SELECTION = 'backbone and name CA and resid 1-10' -NSTEP = 10000 +NSTEP = 5000 #universe @pytest.fixture(scope='module') From 2c469f01c58978f816129f95f09bcf13a16df4b8 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Sun, 15 Mar 2020 21:08:43 +1100 Subject: [PATCH 021/121] change travis and appveyor settings --- .appveyor.yml | 2 +- .travis.yml | 2 +- package/setup.py | 2 -- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 76339b3fb2f..021620509e2 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -10,7 +10,7 @@ cache: environment: global: CONDA_CHANNELS: conda-forge - CONDA_DEPENDENCIES: pip setuptools wheel cython mock six biopython networkx joblib matplotlib scipy vs2015_runtime pytest mmtf-python GridDataFormats hypothesis pytest-cov codecov + CONDA_DEPENDENCIES: pip setuptools wheel cython mock six biopython networkx joblib matplotlib scipy vs2015_runtime pytest mmtf-python GridDataFormats hypothesis pytest-cov codecov tidynamics PIP_DEPENDENCIES: gsd==1.9.3 duecredit parmed DEBUG: "False" MINGW_64: C:\mingw-w64\x86_64-6.3.0-posix-seh-rt_v5-rev1\mingw64\bin diff --git a/.travis.yml b/.travis.yml index 4e489917400..5395006a768 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,7 +27,7 @@ env: - MAIN_CMD="pytest ${PYTEST_LIST}" - SETUP_CMD="${PYTEST_FLAGS}" - BUILD_CMD="pip install -e package/ && (cd testsuite/ && python setup.py build)" - - CONDA_MIN_DEPENDENCIES="mmtf-python mock six biopython networkx cython matplotlib scipy griddataformats hypothesis gsd codecov" + - CONDA_MIN_DEPENDENCIES="mmtf-python mock six biopython networkx cython matplotlib scipy griddataformats hypothesis gsd codecov tidynamics" - CONDA_DEPENDENCIES="${CONDA_MIN_DEPENDENCIES} seaborn>=0.7.0 clustalw=2.1 netcdf4 scikit-learn joblib>=0.12" - CONDA_CHANNELS='biobuilds conda-forge' - CONDA_CHANNEL_PRIORITY=True diff --git a/package/setup.py b/package/setup.py index 39db4d1893f..01e8aedecc5 100755 --- a/package/setup.py +++ b/package/setup.py @@ -558,8 +558,6 @@ def dynamic_author_list(): 'scipy>=1.0.0', 'matplotlib>=1.5.1', 'mock', - 'tidynamics>=1.0.0', - ] if not os.name == 'nt': install_requires.append('gsd>=1.4.0') From 546f7f20390a816be3b2fbdf27e3a4b0b7eda615 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Sun, 15 Mar 2020 21:16:30 +1100 Subject: [PATCH 022/121] fix .appveyor --- .appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.appveyor.yml b/.appveyor.yml index 021620509e2..352d63cf64e 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -10,7 +10,7 @@ cache: environment: global: CONDA_CHANNELS: conda-forge - CONDA_DEPENDENCIES: pip setuptools wheel cython mock six biopython networkx joblib matplotlib scipy vs2015_runtime pytest mmtf-python GridDataFormats hypothesis pytest-cov codecov tidynamics + CONDA_DEPENDENCIES: pip setuptools wheel cython mock six biopython networkx joblib matplotlib scipy vs2015_runtime pytest mmtf-python GridDataFormats hypothesis pytest-cov codecov chemfiles tidynamics PIP_DEPENDENCIES: gsd==1.9.3 duecredit parmed DEBUG: "False" MINGW_64: C:\mingw-w64\x86_64-6.3.0-posix-seh-rt_v5-rev1\mingw64\bin From 033273bc862a3dfa1fdadf1f149ac0cb39283cde Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Sun, 15 Mar 2020 22:48:52 +1100 Subject: [PATCH 023/121] relax to decimal=3 tol for FFT step_traj test due to failing numpy=1.13 test --- testsuite/MDAnalysisTests/analysis/test_msd.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/testsuite/MDAnalysisTests/analysis/test_msd.py b/testsuite/MDAnalysisTests/analysis/test_msd.py index 36fe4f5e631..9e16287bd82 100644 --- a/testsuite/MDAnalysisTests/analysis/test_msd.py +++ b/testsuite/MDAnalysisTests/analysis/test_msd.py @@ -131,19 +131,19 @@ def test_fft_step_traj_3d(step_traj): # this should fit the polynomial 3x**2 m_fft = MSD(step_traj, 'all' , msd_type='xyz', fft=True) m_fft.run() poly3 = characteristic_poly(NSTEP,3) - assert_almost_equal(m_fft.timeseries, poly3, decimal=4) + assert_almost_equal(m_fft.timeseries, poly3, decimal=3) # this was relaxed from decimal=4 for numpy=1.13 test def test_fft_step_traj_2d(step_traj): # this should fit the polynomial 2x**2 m_fft = MSD(step_traj, 'all' , msd_type='xy', fft=True) m_fft.run() poly2 = characteristic_poly(NSTEP,2) - assert_almost_equal(m_fft.timeseries, poly2, decimal=4) + assert_almost_equal(m_fft.timeseries, poly2, decimal=3) # this was relaxed from decimal=4 for numpy=1.13 test def test_fft_step_traj_1d(step_traj): # this should fit the polynomial x**2 m_fft = MSD(step_traj, 'all' , msd_type='x', fft=True) m_fft.run() poly1 = characteristic_poly(NSTEP,1) - assert_almost_equal(m_fft.timeseries, poly1, decimal=4) + assert_almost_equal(m_fft.timeseries, poly1, decimal=3) # this was relaxed from decimal=4 for numpy=1.13 test #test that tidynamics and our code give the same result for an arbitrary random walk def test_tidynamics_msd(): From 83be862622a68c95f540c798889029e4b55bcebb Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Sun, 15 Mar 2020 22:58:00 +1100 Subject: [PATCH 024/121] switch .indices for .ix for proper indexing of coordinate array --- package/MDAnalysis/analysis/msd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 087d1ac074f..fa7ec186a6d 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -253,7 +253,7 @@ def parse_msd_type(self): def select_reference_positions(self): #memory transfer required to access coordinate array self.u.transfer_to_memory() - idxs = self.u.select_atoms(self.selection).indices + idxs = self.u.select_atoms(self.selection).ix self._position_array = self.u.trajectory.coordinate_array[:,idxs, :] self.N_particles = self._position_array.shape[1] From d318a91db07226210d5dca2ddba1052e02676fdf Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Mon, 16 Mar 2020 11:17:10 +1100 Subject: [PATCH 025/121] implement reccomended changes and improve docs --- package/MDAnalysis/analysis/msd.py | 205 +++++++++--------- .../MDAnalysisTests/analysis/test_msd.py | 4 +- 2 files changed, 110 insertions(+), 99 deletions(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index fa7ec186a6d..80f145789a3 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -26,10 +26,10 @@ Mean Squared Displacement --- :mod:`MDAnalysis.analysis.msd` ============================================================== -This module implements the calculation of Mean Squared Displacmements (MSDs). +This module implements the calculation of Mean Squared Displacmements (MSDs) by the Einstein relation. MSDs can be used to characterise the speed at which particles move and has its roots -in the study of Brownian motion. For a full explanation of the best practices for the computation of MSDs and the subsequent calculation of self diffusivities the reader is directed to [Maginn2019]_. -MSDs are computed from the following expression: +in the study of Brownian motion. For a full explanation of the theory behind MSDs and the subsequent calculation of self diffusivities the reader is directed to [Maginn2019]_. +MSDs can be computed from the following expression, known as "Einstein" formula: .. math:: @@ -58,10 +58,10 @@ >>> from MDAnalysis.tests.datafiles import RANDOM_WALK, RANDOM_WALK_TOPO Given a universe containing trajectory data we can extract the MSD -Analyis by using the class :class:`MeanSquaredDisplacement` +Analyis by using the class :class:`EinsteinMSD` >>> u = mda.Universe(RANDOM_WALK, RANDOM_WALK_TOPO) - >>> MSD = msd.MeanSquaredDisplacement(u, 'all', msd_type='xyz', fft=True) + >>> MSD = msd.EinsteinMSD(u, 'all', msd_type='xyz', fft=True) >>> MSD.run() The MSD can then be accessed as @@ -71,7 +71,7 @@ Visual inspection of the MSD is important, so lets take a look at it with a simple plot. >>> import matplotlib.pyplot as plt - >>> nframes = len(u.trajectory) + >>> nframes = MSD.N_frames >>> timestep = 1 # this needs to be the actual time between frames in your trajectory >>> lagtimes = np.arange(nframes)*timestep # make the lag time axis >>> plt.plot(msd, lagtimes) @@ -81,6 +81,10 @@ Note that the majority of MSDs computed from MD trajectories will not be this well behaved. Linearity of a segment of the MSD is required to accurately determine self diffusivity. This linear segment represents the so called "middle" of the MSD plot, where ballistic trajectories at short time-lags are excluded along with poorly averaged data at long time-lags. We can select the "middle" of the MSD by indexing the MSD and the time-lags. Appropriately linear segments of the MSD can be confirmed with a log-log plot as is often reccomended [Maginn2019]_ where the "middle" segment can be identified as having a slope of 1. + + >>> plt.loglog(msd, lagtimes) + >>> plt.show() + Now that we have identified what segment of our MSD to analyse, lets compute a self diffusivity. Computing Self Diffusivity @@ -126,7 +130,7 @@ Classes and Functions --------------------- -.. autoclass:: MeanSquaredDisplacement +.. autoclass:: EinsteinMSD :members: """ @@ -145,41 +149,37 @@ import logging import MDAnalysis -class MeanSquaredDisplacement(object): - r"""Class representing Mean Squared Displacement - - Parameters - ---------- - u : Universe - An MDAnalysis :class:`Universe` - selection : str - An MDAnalysis selection string - msd_type : str - The dimensions to use for the msd calculation: - one of ('xyz', 'xy', 'yz', 'xz', 'x', 'y', 'z') - fft : bool - Use a fast FFT based algorithm for computation of the MSD - - Returns - ------- - MSD : :class:`MeanSquaredDisplacement` - the MSD class +class EinsteinMSD(object): + r"""Class to calculate Mean Squared Displacement by the Einstein relation. - """ - # Attributes - # ---------- - # _dim_fac : int - # dimensionality of the MSD - # _dim : arraylike - # array used to slice the trajectory along the xyz axis to acheive the correct dimensionality - # _position_array : :class:`np.ndarray` - # positions used to calculate the MSD - # N_particles : int - # number of particles + Attributes + ---------- + dim_fac : float + Dimensionality :math:`d` of the MSD. + timeseries : :class:`np.ndarray` + The MSD with respect to lag-time. + N_frames : int + Number of frames in trajectory. + N_particles : int + Number of particles MSD was calculated over. + """ - def __init__(self, u, selection, msd_type='xyz', fft=True): + def __init__(self, u, selection=None, msd_type='xyz', fft=True): + r""" + Parameters + ---------- + u : Universe + An MDAnalysis :class:`Universe`. + selection : str + An MDAnalysis selection string. Defaults to `None`. + msd_type : {'xyz', 'xy', 'yz', 'xz', 'x', 'y', 'z'} + Desired dimensions to be included in the MSD. Defaults to 'xyz'. + fft : bool + Use a fast FFT based algorithm for computation of the MSD. + Otherwise, use the naieve or "simple" algorithm. Defaults to `True` + """ #args self.u = u self.selection = selection @@ -187,75 +187,83 @@ def __init__(self, u, selection, msd_type='xyz', fft=True): self.fft = fft #local - self.dim_fac = 0 self._dim = None - self.atoms = None - self.n_frames = len(self.u.trajectory) self._position_array = None + + #indexing + self.N_frames = len(self.u.trajectory) + self.N_particles = 0 #result + self.dim_fac = 0 self.timeseries = None + #prep self._prepare() def _prepare(self): - self.parse_msd_type() - self.select_reference_positions() + self._parse_msd_type() + self._select_reference_positions() - def parse_msd_type(self): - r""" Sets up the desired dimensionality of the MSD + def _parse_msd_type(self): + r""" Sets up the desired dimensionality of the MSD. Parameters ---------- - self.msd_type : str - the desired dimensionality fo the MSD - + self.msd_type : {'xyz', 'xy', 'yz', 'xz', 'x', 'y', 'z'} + Dimensions to be included in the MSD. + Returns ------- self._dim : list - array-like used to slice the positions to obtain desired dimensionality + Array-like used to slice the positions to obtain desired dimensionality self.dim_fac : float - dimension factor d of the MSD + Dimension factor :math:`d` of the MSD. + """ + keys = {'x':0, 'y':1, 'z':2} + dim = [] + + self.msd_type = self.msd_type.lower() + print(self.msd_type) + try: + for tok in self.msd_type: + vals = (keys.pop(tok)) + dim.append(vals) + except KeyError: + raise ValueError('invalid msd_type specified, please specify one of xyz, xy, xz, yz, x, y, z ') - if self.msd_type == 'xyz': # full 3d - self._dim = [0,1,2] - self.dim_fac = 3.0 - - elif self.msd_type == 'xy': # xy - self._dim = [0,1] - self.dim_fac = 2.0 + dim.sort() + self._dim = dim + self.dim_fac = float(len(dim)) - elif self.msd_type == 'xz': # xz - self._dim = [0,2] - self.dim_fac = 2.0 - - elif self.msd_type == 'yz': # yz - self._dim = [1,2] - self.dim_fac = 2.0 - - elif self.msd_type == 'x': # x - self._dim = [0] - self.dim_fac = 1.0 - - elif self.msd_type == 'y': # y - self._dim = [1] - self.dim_fac = 1.0 - - elif self.msd_type == 'z': # z - self._dim = [2] - self.dim_fac = 1.0 - - else: - raise ValueError('invalid msd_type specified') + def _select_reference_positions(self): + r""" Constructs array of positions for MSD calculation. + + Parameters + ---------- + self.u : :class:`Universe` + MDAnalysis Universe + self.selection : str + MDAnalysis selection string + + Returns + ------- + self._position_array : :class:`np.ndarray` + Array of particle positions with respect to time shape = (N_frames, N_particles, 3) + self.N_particles : float + Number of particles used in the MSD calculation - def select_reference_positions(self): - #memory transfer required to access coordinate array - self.u.transfer_to_memory() + """ + #avoid memory transfer by iterating idxs = self.u.select_atoms(self.selection).ix - self._position_array = self.u.trajectory.coordinate_array[:,idxs, :] - self.N_particles = self._position_array.shape[1] + position_array_dummy = np.zeros([len(self.u.trajectory), len(self.u.atoms), 3]) + for idx,ts in enumerate(self.u.trajectory): + position_array_dummy[idx,:,:] = self.u.atoms.positions + self._position_array = position_array_dummy[:,idxs, :] + self.N_particles = self._position_array.shape[1] + def run(self): r""" Wrapper to calculate the MSD via either the "simple" or "fft" algorithm. @@ -266,20 +274,21 @@ def run(self): self.timeseries = self._run_simple() def _run_simple(self): # naieve algorithm without FFT - r""" Calculates the MSD via the naieve "windowed" algorithm + r""" Calculates the MSD via the naieve "windowed" algorithm. Parameters ---------- self._position_array : :class:`np.ndarray` - the particle positions over the course of the trajectory shape (nframes,Nparticles,3) + Array of particle positions with respect to time shape (N_frames, N_particles, 3). Returns ------- self.timeseries : :class:`np.ndarray` - the MSD as a function of lagtime + The MSD as a function of lag-time. + """ - msds_byparticle = np.zeros([self.n_frames, self.N_particles]) - lagtimes = np.arange(1,self.n_frames) + msds_byparticle = np.zeros([self.N_frames, self.N_particles]) + lagtimes = np.arange(1,self.N_frames) msds_byparticle[0,:] = np.zeros(self.N_particles) # preset the zero lagtime so we dont have to iterate through for n in range(self.N_particles): for lag in lagtimes: @@ -290,17 +299,18 @@ def _run_simple(self): # naieve algorithm without FFT return msds def _run_fft(self): #with FFT, np.f64 bit prescision required. - r""" Calculates the MSD via the FCA fast correlation algorithm + r""" Calculates the MSD via the FCA fast correlation algorithm. Parameters ---------- self._position_array : :class:`np.ndarray` - the particle positions over the course of the trajectory shape (nframes,Nparticles,3) + Array of particle positions with respect to time shape (N_frames, N_particles, 3). Returns ------- self.timeseries : :class:`np.ndarray` - the MSD as a function of lagtime + The MSD as a function of lagtime. + """ msds_byparticle = [] reshape_positions = self._position_array[:,:,self._dim].astype(np.float64) @@ -314,7 +324,7 @@ def _run_fft(self): #with FFT, np.f64 bit prescision required. S1[m,:]=Q/(N-m) S2accumulate = [] for i in range(reshape_positions.shape[2]): - S2accumulate.append(self.autocorrFFT(reshape_positions[:,:,i])) + S2accumulate.append(self._autocorrFFT(reshape_positions[:,:,i])) S2= np.sum(S2accumulate,axis=0,dtype=np.float64) msds_byparticle.append(S1-2*S2) @@ -322,18 +332,19 @@ def _run_fft(self): #with FFT, np.f64 bit prescision required. return msds @staticmethod - def autocorrFFT(x): - r""" Calculates an autocorrelation function of the input signal via an FFT + def _autocorrFFT(x): + r""" Calculates an autocorrelation function of the input signal via an FFT. Parameters ---------- x : :class:`np.ndarray` - array to compute the autocorrelation for + Array to compute the autocorrelation for. Returns ------- autocorr : :class:`np.ndarray` - the autocorrelation + The autocorrelation of the input signal. + """ N=(x.shape[0]) diff --git a/testsuite/MDAnalysisTests/analysis/test_msd.py b/testsuite/MDAnalysisTests/analysis/test_msd.py index 9e16287bd82..f962713a1e0 100644 --- a/testsuite/MDAnalysisTests/analysis/test_msd.py +++ b/testsuite/MDAnalysisTests/analysis/test_msd.py @@ -24,7 +24,7 @@ import MDAnalysis as mda -from MDAnalysis.analysis.msd import MeanSquaredDisplacement as MSD +from MDAnalysis.analysis.msd import EinsteinMSD as MSD from numpy.testing import (assert_array_less, assert_almost_equal, assert_equal) @@ -159,7 +159,7 @@ def test_random_walk_tidynamics(random_walk_u): msd_rw = MSD(random_walk_u, 'all', msd_type='xyz', fft=True) msd_rw.run() array = msd_rw._position_array.astype(np.float64) - tidy_msds = np.zeros(msd_rw.n_frames) + tidy_msds = np.zeros(msd_rw.N_frames) count = 0 for mol in range(array.shape[1]): pos = array[:,mol,:] From 10e3fc2de6b9167c7d36085e47d528f39a514329 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Mon, 16 Mar 2020 11:21:42 +1100 Subject: [PATCH 026/121] add small test to ensure selection is working --- testsuite/MDAnalysisTests/analysis/test_msd.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/testsuite/MDAnalysisTests/analysis/test_msd.py b/testsuite/MDAnalysisTests/analysis/test_msd.py index f962713a1e0..db76397b3dd 100644 --- a/testsuite/MDAnalysisTests/analysis/test_msd.py +++ b/testsuite/MDAnalysisTests/analysis/test_msd.py @@ -92,6 +92,11 @@ def characteristic_poly(n,d): #polynomial that describes unit step trajectory MS y = d*x*x return y +#test some basic size and shape things +def test_selection_works(msd): + assert_equal(msd.N_particles, 10) + + #testing on the PSF, DCD trajectory def test_fft_vs_simple_default(msd, msd_fft): timeseries_simple = msd.timeseries From 089a2bd9910f99d34d4b31dfec9b57b1c1dd298c Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Mon, 16 Mar 2020 11:54:10 +1100 Subject: [PATCH 027/121] add capability to pull out MSDs per particle --- package/MDAnalysis/analysis/msd.py | 9 +++++++-- testsuite/MDAnalysisTests/analysis/test_msd.py | 16 +++++++++++++++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 80f145789a3..21e27b4690d 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -157,7 +157,9 @@ class EinsteinMSD(object): dim_fac : float Dimensionality :math:`d` of the MSD. timeseries : :class:`np.ndarray` - The MSD with respect to lag-time. + The averaged MSD with respect to lag-time. + msd_per_particle : :class:`np.ndarray` + The MSD of each individual particle with respect to lag-time. N_frames : int Number of frames in trajectory. N_particles : int @@ -197,6 +199,7 @@ def __init__(self, u, selection=None, msd_type='xyz', fft=True): #result self.dim_fac = 0 self.timeseries = None + self.msd_per_particle = None #prep self._prepare() @@ -295,6 +298,7 @@ def _run_simple(self): # naieve algorithm without FFT disp = self._position_array[:-lag,n,self._dim if lag else None] - self._position_array[lag:,n,self._dim] sqdist = np.square(disp, dtype=np.float64).sum(axis=1, dtype=np.float64) #accumulation in anything other than f64 is innacurate msds_byparticle[lag,n] = np.mean(sqdist, dtype=np.float64) + self.msd_per_particle = msds_byparticle msds = msds_byparticle.mean(axis=1, dtype=np.float64) return msds @@ -328,7 +332,8 @@ def _run_fft(self): #with FFT, np.f64 bit prescision required. S2= np.sum(S2accumulate,axis=0,dtype=np.float64) msds_byparticle.append(S1-2*S2) - msds = np.concatenate(msds_byparticle,axis=1).mean(axis=-1, dtype=np.float64) + self.msd_per_particle = np.concatenate(msds_byparticle,axis=1) + msds = self.msd_per_particle.mean(axis=-1, dtype=np.float64) return msds @staticmethod diff --git a/testsuite/MDAnalysisTests/analysis/test_msd.py b/testsuite/MDAnalysisTests/analysis/test_msd.py index db76397b3dd..d7e03a403f2 100644 --- a/testsuite/MDAnalysisTests/analysis/test_msd.py +++ b/testsuite/MDAnalysisTests/analysis/test_msd.py @@ -96,13 +96,17 @@ def characteristic_poly(n,d): #polynomial that describes unit step trajectory MS def test_selection_works(msd): assert_equal(msd.N_particles, 10) - #testing on the PSF, DCD trajectory def test_fft_vs_simple_default(msd, msd_fft): timeseries_simple = msd.timeseries timeseries_fft = msd_fft.timeseries assert_almost_equal(timeseries_simple, timeseries_fft, decimal=4) +def test_fft_vs_simple_default_per_particle(msd, msd_fft): + per_particle_simple = msd.timeseries + per_particle_fft = msd_fft.timeseries + assert_almost_equal(per_particle_simple, per_particle_fft, decimal=4) + def test_fft_vs_simple_all_dims(dimension_list, u): for dim in dimension_list: m_simple = MSD(u, SELECTION, msd_type=dim, fft=False) @@ -113,6 +117,16 @@ def test_fft_vs_simple_all_dims(dimension_list, u): timeseries_fft = m_fft.timeseries assert_almost_equal(timeseries_simple, timeseries_fft, decimal=4) +def test_fft_vs_simple_all_dims_per_particle(dimension_list, u): + for dim in dimension_list: + m_simple = MSD(u, SELECTION, msd_type=dim, fft=False) + m_simple.run() + per_particle_simple = m_simple.timeseries + m_fft = MSD(u,SELECTION, msd_type=dim, fft=True) + m_fft.run() + per_particle_fft = m_fft.timeseries + assert_almost_equal(per_particle_simple, per_particle_fft, decimal=4) + #testing on step trajectory def test_simple_step_traj_3d(step_traj): # this should fit the polynomial 3x**2 m_simple = MSD(step_traj, 'all' , msd_type='xyz', fft=False) From d1084538292621eec01619584d8afabac18e45f2 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Mon, 16 Mar 2020 12:07:10 +1100 Subject: [PATCH 028/121] fix typo in per particle tests --- testsuite/MDAnalysisTests/analysis/test_msd.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/testsuite/MDAnalysisTests/analysis/test_msd.py b/testsuite/MDAnalysisTests/analysis/test_msd.py index d7e03a403f2..715d283f428 100644 --- a/testsuite/MDAnalysisTests/analysis/test_msd.py +++ b/testsuite/MDAnalysisTests/analysis/test_msd.py @@ -103,8 +103,8 @@ def test_fft_vs_simple_default(msd, msd_fft): assert_almost_equal(timeseries_simple, timeseries_fft, decimal=4) def test_fft_vs_simple_default_per_particle(msd, msd_fft): - per_particle_simple = msd.timeseries - per_particle_fft = msd_fft.timeseries + per_particle_simple = msd.msd_per_particle + per_particle_fft = msd_fft.msd_per_particle assert_almost_equal(per_particle_simple, per_particle_fft, decimal=4) def test_fft_vs_simple_all_dims(dimension_list, u): @@ -121,10 +121,10 @@ def test_fft_vs_simple_all_dims_per_particle(dimension_list, u): for dim in dimension_list: m_simple = MSD(u, SELECTION, msd_type=dim, fft=False) m_simple.run() - per_particle_simple = m_simple.timeseries + per_particle_simple = m_simple.msd_per_particle m_fft = MSD(u,SELECTION, msd_type=dim, fft=True) m_fft.run() - per_particle_fft = m_fft.timeseries + per_particle_fft = m_fft.msd_per_particle assert_almost_equal(per_particle_simple, per_particle_fft, decimal=4) #testing on step trajectory From 8ebc23386aeec85fbb9de444cef7cfe0e86fb625 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Fri, 15 May 2020 19:50:46 +1000 Subject: [PATCH 029/121] add in test to show numerical error in constan tvelocity trajectory. --- testsuite/MDAnalysisTests/analysis/test_msd.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/testsuite/MDAnalysisTests/analysis/test_msd.py b/testsuite/MDAnalysisTests/analysis/test_msd.py index 715d283f428..3ea10385d6d 100644 --- a/testsuite/MDAnalysisTests/analysis/test_msd.py +++ b/testsuite/MDAnalysisTests/analysis/test_msd.py @@ -38,7 +38,7 @@ import tidynamics SELECTION = 'backbone and name CA and resid 1-10' -NSTEP = 5000 +NSTEP = 20000 #universe @pytest.fixture(scope='module') @@ -78,6 +78,11 @@ def step_traj(): # constant velocity u = mda.Universe.empty(1) u.load_new(traj_reshape) return u +@pytest.fixture(scope='module') +def step_traj_arr(): # constant velocity + x = np.arange(NSTEP) + traj = np.vstack([x,x,x]).T + return traj def random_walk_3d(): steps = -1 + 2*np.random.randint(0, 2, size=(NSTEP, 3)) @@ -127,7 +132,7 @@ def test_fft_vs_simple_all_dims_per_particle(dimension_list, u): per_particle_fft = m_fft.msd_per_particle assert_almost_equal(per_particle_simple, per_particle_fft, decimal=4) -#testing on step trajectory +#testing on step trajectory in tidynamics def test_simple_step_traj_3d(step_traj): # this should fit the polynomial 3x**2 m_simple = MSD(step_traj, 'all' , msd_type='xyz', fft=False) m_simple.run() @@ -164,6 +169,15 @@ def test_fft_step_traj_1d(step_traj): # this should fit the polynomial x**2 poly1 = characteristic_poly(NSTEP,1) assert_almost_equal(m_fft.timeseries, poly1, decimal=3) # this was relaxed from decimal=4 for numpy=1.13 test +# test tidynamics on a constant velocity trajectory. +def test_step_traj_tidy_3d(step_traj_arr): # this should fit the polynomial y = 3x**2 + msd_tidy = tidynamics.msd(step_traj_arr.astype(np.float64)) + print(msd_tidy) + poly3 = characteristic_poly(NSTEP,3) + print(poly3) + assert_almost_equal(msd_tidy, poly3, decimal=4) # this passes with NSTEP=5000, and 10000 but fails for 20,000 + + #test that tidynamics and our code give the same result for an arbitrary random walk def test_tidynamics_msd(): u, array = random_walk_3d() From e2d383d78d19e6fb1507f9fd14beceb2b5b1a385 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Mon, 18 May 2020 07:52:00 +1000 Subject: [PATCH 030/121] replaced own fft code with tidynamics --- package/MDAnalysis/analysis/msd.py | 57 ++----------------- .../MDAnalysisTests/analysis/test_msd.py | 52 +++++------------ 2 files changed, 19 insertions(+), 90 deletions(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 21e27b4690d..95f303dec0b 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -147,6 +147,7 @@ import numpy as np from numpy.fft import fft,ifft import logging +import tidynamics import MDAnalysis class EinsteinMSD(object): @@ -316,56 +317,10 @@ def _run_fft(self): #with FFT, np.f64 bit prescision required. The MSD as a function of lagtime. """ - msds_byparticle = [] + msds_byparticle = np.zeros([self.N_frames, self.N_particles]) reshape_positions = self._position_array[:,:,self._dim].astype(np.float64) - N=reshape_positions.shape[0] - D=np.square(reshape_positions).sum(axis=2, dtype=np.float64) - D=np.append(D,np.zeros(reshape_positions.shape[:2]), axis=0) - Q=2*D.sum(axis=0, dtype=np.float64) - S1=np.zeros(reshape_positions.shape[:2],dtype=np.float64) - for m in range(N): - Q=Q-D[m-1,:]-D[N-m,:] - S1[m,:]=Q/(N-m) - S2accumulate = [] - for i in range(reshape_positions.shape[2]): - S2accumulate.append(self._autocorrFFT(reshape_positions[:,:,i])) - S2= np.sum(S2accumulate,axis=0,dtype=np.float64) - - msds_byparticle.append(S1-2*S2) - self.msd_per_particle = np.concatenate(msds_byparticle,axis=1) - msds = self.msd_per_particle.mean(axis=-1, dtype=np.float64) + for n in range(self.N_particles): + msds_byparticle[:,n] = tidynamics.msd(reshape_positions[:,n,:]) + self.msd_per_particle= msds_byparticle + msds = msds_byparticle.mean(axis=1, dtype=np.float64) return msds - - @staticmethod - def _autocorrFFT(x): - r""" Calculates an autocorrelation function of the input signal via an FFT. - - Parameters - ---------- - x : :class:`np.ndarray` - Array to compute the autocorrelation for. - - Returns - ------- - autocorr : :class:`np.ndarray` - The autocorrelation of the input signal. - - """ - N=(x.shape[0]) - - #find closest power of 2 (code adapted from tidynamics) - current_exp = int(np.ceil(np.log2(N+1))) - if N == 2**current_exp: - n_fft = N - if N < 2**current_exp: - n_fft = 2**current_exp - elif N > 2**current_exp: - n_fft = 2**(current_exp+1) - #closest power of 2 gives the fastest FFTs - - F = fft(x, n=2*n_fft, axis=0) #zero pad to get non-cyclic autocorrelation - PowerSpectralDensity = F * F.conjugate() - inverse = ifft(PowerSpectralDensity,axis=0) - autocorr = (inverse[:N]).real #autocorr convention B - n = np.arange(1, N+1)[::-1] - return autocorr/n[:, np.newaxis] #autocorr convention A \ No newline at end of file diff --git a/testsuite/MDAnalysisTests/analysis/test_msd.py b/testsuite/MDAnalysisTests/analysis/test_msd.py index 3ea10385d6d..df07150c124 100644 --- a/testsuite/MDAnalysisTests/analysis/test_msd.py +++ b/testsuite/MDAnalysisTests/analysis/test_msd.py @@ -30,15 +30,13 @@ assert_almost_equal, assert_equal) import numpy as np -from numpy.fft import fft,ifft - from MDAnalysisTests.datafiles import PSF, DCD, RANDOM_WALK, RANDOM_WALK_TOPO import pytest import tidynamics SELECTION = 'backbone and name CA and resid 1-10' -NSTEP = 20000 +NSTEP = 10000 #universe @pytest.fixture(scope='module') @@ -107,11 +105,13 @@ def test_fft_vs_simple_default(msd, msd_fft): timeseries_fft = msd_fft.timeseries assert_almost_equal(timeseries_simple, timeseries_fft, decimal=4) +#check fft and simple give same result per particle def test_fft_vs_simple_default_per_particle(msd, msd_fft): per_particle_simple = msd.msd_per_particle per_particle_fft = msd_fft.msd_per_particle assert_almost_equal(per_particle_simple, per_particle_fft, decimal=4) +#check fft and simple give same result for each dimensionality def test_fft_vs_simple_all_dims(dimension_list, u): for dim in dimension_list: m_simple = MSD(u, SELECTION, msd_type=dim, fft=False) @@ -122,6 +122,7 @@ def test_fft_vs_simple_all_dims(dimension_list, u): timeseries_fft = m_fft.timeseries assert_almost_equal(timeseries_simple, timeseries_fft, decimal=4) +#check fft and simple give same result for each particle in each dimension def test_fft_vs_simple_all_dims_per_particle(dimension_list, u): for dim in dimension_list: m_simple = MSD(u, SELECTION, msd_type=dim, fft=False) @@ -132,25 +133,29 @@ def test_fft_vs_simple_all_dims_per_particle(dimension_list, u): per_particle_fft = m_fft.msd_per_particle assert_almost_equal(per_particle_simple, per_particle_fft, decimal=4) -#testing on step trajectory in tidynamics -def test_simple_step_traj_3d(step_traj): # this should fit the polynomial 3x**2 +#testing the "simple" algorithm on constant velocity trajectory + +def test_simple_step_traj_3d(step_traj): # this should fit the polynomial y=3x**2 m_simple = MSD(step_traj, 'all' , msd_type='xyz', fft=False) m_simple.run() poly3 = characteristic_poly(NSTEP,3) assert_almost_equal(m_simple.timeseries, poly3, decimal=4) -def test_simple_step_traj_2d(step_traj): # this should fit the polynomial 2x**2 +def test_simple_step_traj_2d(step_traj): # this should fit the polynomial y=2x**2 m_simple = MSD(step_traj, 'all' , msd_type='xy', fft=False) m_simple.run() poly2 = characteristic_poly(NSTEP,2) assert_almost_equal(m_simple.timeseries, poly2, decimal=4) -def test_simple_step_traj_1d(step_traj): # this should fit the polynomial x** +def test_simple_step_traj_1d(step_traj): # this should fit the polynomial y=x**2 m_simple = MSD(step_traj, 'all' , msd_type='x', fft=False) m_simple.run() poly1 = characteristic_poly(NSTEP,1) assert_almost_equal(m_simple.timeseries, poly1,decimal=4) +#fft based tests require a slight decrease in expected prescision due to roundoff in fft(ifft()) calls +#relative accuracy expected to be around ~1e-12 + def test_fft_step_traj_3d(step_traj): # this should fit the polynomial 3x**2 m_fft = MSD(step_traj, 'all' , msd_type='xyz', fft=True) m_fft.run() @@ -169,38 +174,6 @@ def test_fft_step_traj_1d(step_traj): # this should fit the polynomial x**2 poly1 = characteristic_poly(NSTEP,1) assert_almost_equal(m_fft.timeseries, poly1, decimal=3) # this was relaxed from decimal=4 for numpy=1.13 test -# test tidynamics on a constant velocity trajectory. -def test_step_traj_tidy_3d(step_traj_arr): # this should fit the polynomial y = 3x**2 - msd_tidy = tidynamics.msd(step_traj_arr.astype(np.float64)) - print(msd_tidy) - poly3 = characteristic_poly(NSTEP,3) - print(poly3) - assert_almost_equal(msd_tidy, poly3, decimal=4) # this passes with NSTEP=5000, and 10000 but fails for 20,000 - - -#test that tidynamics and our code give the same result for an arbitrary random walk -def test_tidynamics_msd(): - u, array = random_walk_3d() - msd_mda = MSD(u, 'all', msd_type='xyz', fft=True) - msd_mda.run() - msd_mda_msd = msd_mda.timeseries - msd_tidy = tidynamics.msd(array.astype(np.float64)) - assert_almost_equal(msd_mda_msd, msd_tidy, decimal=5) - -#test that tidynamics and our code give the same result for SPECIFIC random walk -def test_random_walk_tidynamics(random_walk_u): - msd_rw = MSD(random_walk_u, 'all', msd_type='xyz', fft=True) - msd_rw.run() - array = msd_rw._position_array.astype(np.float64) - tidy_msds = np.zeros(msd_rw.N_frames) - count = 0 - for mol in range(array.shape[1]): - pos = array[:,mol,:] - mol_msd = tidynamics.msd(pos) - tidy_msds += mol_msd - count += 1.0 - msd_tidy = tidy_msds /count - assert_almost_equal(msd_tidy, msd_rw.timeseries, decimal=5) #regress against random_walk test data def test_random_walk_u_simple(random_walk_u): @@ -210,6 +183,7 @@ def test_random_walk_u_simple(random_walk_u): val = 3932.39927487146 assert_almost_equal(norm, val, decimal=5) +#regress against random_walk test data def test_random_walk_u_fft(random_walk_u): msd_rw = MSD(random_walk_u, 'all', msd_type='xyz', fft=True) msd_rw.run() From 73bc1f7455301ee1505bcf8ff34031ae9892549b Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Mon, 18 May 2020 09:33:32 +1000 Subject: [PATCH 031/121] add duecredit citation --- package/MDAnalysis/analysis/msd.py | 8 +++++++- testsuite/MDAnalysisTests/analysis/test_msd.py | 6 +++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 95f303dec0b..e0d95d98405 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -125,7 +125,6 @@ .. [Maginn2019] Maginn, E. J.; Messerly, R. A.; Carlson, D. J.; Roe, D. R.; Elliott, J. R. Best Practices for Computing Transport Properties 1. Self-Diffusivity and Viscosity from Equilibrium Molecular Dynamics [Article v1.0]. Living J. Comput. Mol. Sci. 2019, 1 (1). .. [Calandri2011] Calandrini, V.; Pellegrini, E.; Calligari, P.; Hinsen, K.; Kneller, G. R. NMoldyn-Interfacing Spectroscopic Experiments, Molecular Dynamics Simulations and Models for Time Correlation Functions. Collect. SFN 2011, 12, 201–232. -.. [SO2015] https://stackoverflow.com/questions/34222272/computing-mean-square-displacement-using-python-and-fft Classes and Functions --------------------- @@ -149,6 +148,13 @@ import logging import tidynamics import MDAnalysis +from ..due import due, Doi + +due.cite(Doi("10.21105/joss.00877"), + description="Mean Squared Displacements with tidynamics", + path="MDAnalysis.analysis.msd", + cite_module=True) +del Doi class EinsteinMSD(object): r"""Class to calculate Mean Squared Displacement by the Einstein relation. diff --git a/testsuite/MDAnalysisTests/analysis/test_msd.py b/testsuite/MDAnalysisTests/analysis/test_msd.py index df07150c124..f9cfa47e513 100644 --- a/testsuite/MDAnalysisTests/analysis/test_msd.py +++ b/testsuite/MDAnalysisTests/analysis/test_msd.py @@ -156,19 +156,19 @@ def test_simple_step_traj_1d(step_traj): # this should fit the polynomial y=x**2 #fft based tests require a slight decrease in expected prescision due to roundoff in fft(ifft()) calls #relative accuracy expected to be around ~1e-12 -def test_fft_step_traj_3d(step_traj): # this should fit the polynomial 3x**2 +def test_fft_step_traj_3d(step_traj): # this should fit the polynomial y=3x**2 m_fft = MSD(step_traj, 'all' , msd_type='xyz', fft=True) m_fft.run() poly3 = characteristic_poly(NSTEP,3) assert_almost_equal(m_fft.timeseries, poly3, decimal=3) # this was relaxed from decimal=4 for numpy=1.13 test -def test_fft_step_traj_2d(step_traj): # this should fit the polynomial 2x**2 +def test_fft_step_traj_2d(step_traj): # this should fit the polynomial y=2x**2 m_fft = MSD(step_traj, 'all' , msd_type='xy', fft=True) m_fft.run() poly2 = characteristic_poly(NSTEP,2) assert_almost_equal(m_fft.timeseries, poly2, decimal=3) # this was relaxed from decimal=4 for numpy=1.13 test -def test_fft_step_traj_1d(step_traj): # this should fit the polynomial x**2 +def test_fft_step_traj_1d(step_traj): # this should fit the polynomial y=x**2 m_fft = MSD(step_traj, 'all' , msd_type='x', fft=True) m_fft.run() poly1 = characteristic_poly(NSTEP,1) From c71f5e68adfa26070fc7d5286269504d86c682ff Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Mon, 18 May 2020 09:33:52 +1000 Subject: [PATCH 032/121] add FCA citation --- package/MDAnalysis/analysis/msd.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index e0d95d98405..3bfd2873d8c 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -42,7 +42,6 @@ The computation of the MSD in this way can be computationally intensive due to it's :math:`N^2` scaling with respect to :math:`\tau_{max}`. An algorithm to compute the MSD with :math:`N log(N)` scaling based on a Fast Fourier Transform is known and can be accessed by setting fft=True [Calandri2011]_. -The python implementation was originally presented here [SO2015]_. Computing an MSD ---------------- @@ -156,6 +155,12 @@ cite_module=True) del Doi +due.cite(Doi("10.1051/sfn/201112010"), + description="FCA fast correlation algorithm", + path="MDAnalysis.analysis.msd", + cite_module=True) +del Doi + class EinsteinMSD(object): r"""Class to calculate Mean Squared Displacement by the Einstein relation. From ddbc4e36ca755d59f7fe436c1bec03f78069f6a9 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Mon, 18 May 2020 09:44:42 +1000 Subject: [PATCH 033/121] update requires and fix docs build --- package/MDAnalysis/analysis/msd.py | 1 - package/setup.py | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 3bfd2873d8c..0862b6e927e 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -153,7 +153,6 @@ description="Mean Squared Displacements with tidynamics", path="MDAnalysis.analysis.msd", cite_module=True) -del Doi due.cite(Doi("10.1051/sfn/201112010"), description="FCA fast correlation algorithm", diff --git a/package/setup.py b/package/setup.py index 01e8aedecc5..53057faa479 100755 --- a/package/setup.py +++ b/package/setup.py @@ -558,6 +558,7 @@ def dynamic_author_list(): 'scipy>=1.0.0', 'matplotlib>=1.5.1', 'mock', + 'tidynamics>=1.0.0' ] if not os.name == 'nt': install_requires.append('gsd>=1.4.0') From 523f81f0e068a556d57884bf6bd0422bdac5bb7b Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Mon, 18 May 2020 09:59:07 +1000 Subject: [PATCH 034/121] update citation --- package/MDAnalysis/analysis/msd.py | 1 - 1 file changed, 1 deletion(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 0862b6e927e..33e433c43d7 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -153,7 +153,6 @@ description="Mean Squared Displacements with tidynamics", path="MDAnalysis.analysis.msd", cite_module=True) - due.cite(Doi("10.1051/sfn/201112010"), description="FCA fast correlation algorithm", path="MDAnalysis.analysis.msd", From 86d59bb8b2fec9f1d78e4e9aa2f0e821d40fd0b7 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Mon, 18 May 2020 10:13:51 +1000 Subject: [PATCH 035/121] fix appveyor and travis yml, removing duplication --- .appveyor.yml | 3 +-- .travis.yml | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 39c930afd42..c18aee87b50 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -10,8 +10,7 @@ cache: environment: global: CONDA_CHANNELS: conda-forge - CONDA_DEPENDENCIES: pip setuptools wheel cython mock six biopython networkx joblib matplotlib scipy vs2015_runtime pytest mmtf-python GridDataFormats hypothesis pytest-cov codecov chemfiles tidynamics - CONDA_DEPENDENCIES: pip setuptools wheel cython mock six biopython networkx joblib matplotlib scipy vs2015_runtime pytest mmtf-python GridDataFormats hypothesis pytest-cov codecov chemfiles tqdm + CONDA_DEPENDENCIES: pip setuptools wheel cython mock six biopython networkx joblib matplotlib scipy vs2015_runtime pytest mmtf-python GridDataFormats hypothesis pytest-cov codecov chemfiles tqdm tidynamics PIP_DEPENDENCIES: gsd==1.9.3 duecredit parmed DEBUG: "False" MINGW_64: C:\mingw-w64\x86_64-6.3.0-posix-seh-rt_v5-rev1\mingw64\bin diff --git a/.travis.yml b/.travis.yml index 37692e55b69..fb08204f9f8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,8 +28,6 @@ env: - SETUP_CMD="${PYTEST_FLAGS}" - BUILD_CMD="pip install -e package/ && (cd testsuite/ && python setup.py build)" - CONDA_MIN_DEPENDENCIES="mmtf-python mock six biopython networkx cython matplotlib scipy griddataformats hypothesis gsd codecov tidynamics" - - CONDA_DEPENDENCIES="${CONDA_MIN_DEPENDENCIES} seaborn>=0.7.0 clustalw=2.1 netcdf4 scikit-learn joblib>=0.12" - - CONDA_MIN_DEPENDENCIES="mmtf-python mock six biopython networkx cython matplotlib scipy griddataformats hypothesis gsd codecov" - CONDA_DEPENDENCIES="${CONDA_MIN_DEPENDENCIES} seaborn>=0.7.0 clustalw=2.1 netcdf4 scikit-learn joblib>=0.12 chemfiles tqdm>=4.43.0" - CONDA_CHANNELS='biobuilds conda-forge' - CONDA_CHANNEL_PRIORITY=True From a934882bbd300685c77ec37aee59d19988065d06 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Mon, 18 May 2020 10:31:12 +1000 Subject: [PATCH 036/121] fix typo in travis.yml --- package/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/setup.py b/package/setup.py index a334797b85d..2682d0aa158 100755 --- a/package/setup.py +++ b/package/setup.py @@ -558,7 +558,7 @@ def dynamic_author_list(): 'scipy>=1.0.0', 'matplotlib>=1.5.1', 'mock', - 'tidynamics>=1.0.0' + 'tidynamics>=1.0.0', 'tqdm>=4.43.0', ] if not os.name == 'nt': From 7759da74010c12194dcfee45baa38114c9b1c873 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Mon, 18 May 2020 14:21:27 +1000 Subject: [PATCH 037/121] fix failing numpy=1.13 nets and update CHANGELOG --- package/CHANGELOG | 1 + testsuite/MDAnalysisTests/analysis/test_msd.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/package/CHANGELOG b/package/CHANGELOG index 48a3d7dba33..641e5cc7e77 100644 --- a/package/CHANGELOG +++ b/package/CHANGELOG @@ -83,6 +83,7 @@ Fixes * Contact Analysis class respects PBC (Issue #2368) Enhancements + * Added computation of Mean Squared Displacements (PR #2619) * Added .frames and .times arrays to AnalysisBase (Issue #2661) * Added elements attribute to PDBParser (Issue #2553, #2647) * Added new density.DensityAnalysis (Issue #2502) diff --git a/testsuite/MDAnalysisTests/analysis/test_msd.py b/testsuite/MDAnalysisTests/analysis/test_msd.py index f9cfa47e513..fc942c06f35 100644 --- a/testsuite/MDAnalysisTests/analysis/test_msd.py +++ b/testsuite/MDAnalysisTests/analysis/test_msd.py @@ -36,7 +36,7 @@ import tidynamics SELECTION = 'backbone and name CA and resid 1-10' -NSTEP = 10000 +NSTEP = 5000 #universe @pytest.fixture(scope='module') From 851daa3dacf287f8aff033a73e54a9bbb62fb868 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Mon, 18 May 2020 21:13:36 +1000 Subject: [PATCH 038/121] Update package/CHANGELOG Co-authored-by: Oliver Beckstein --- package/CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/CHANGELOG b/package/CHANGELOG index 641e5cc7e77..e849775cc14 100644 --- a/package/CHANGELOG +++ b/package/CHANGELOG @@ -83,7 +83,7 @@ Fixes * Contact Analysis class respects PBC (Issue #2368) Enhancements - * Added computation of Mean Squared Displacements (PR #2619) + * Added computation of Mean Squared Displacements (#2438, PR #2619) * Added .frames and .times arrays to AnalysisBase (Issue #2661) * Added elements attribute to PDBParser (Issue #2553, #2647) * Added new density.DensityAnalysis (Issue #2502) From d23ee0ed89e156ea0708dd1fc66f5c1c0558715e Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Mon, 18 May 2020 21:17:05 +1000 Subject: [PATCH 039/121] Update package/MDAnalysis/analysis/msd.py Co-authored-by: Oliver Beckstein --- package/MDAnalysis/analysis/msd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 33e433c43d7..ded0f98f9a4 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -189,7 +189,7 @@ def __init__(self, u, selection=None, msd_type='xyz', fft=True): Desired dimensions to be included in the MSD. Defaults to 'xyz'. fft : bool Use a fast FFT based algorithm for computation of the MSD. - Otherwise, use the naieve or "simple" algorithm. Defaults to `True` + Otherwise, use the naive or "simple" algorithm. Defaults to ``True``. """ #args From 9af111a4bd463a3685d0b63062dea145fa791e81 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Tue, 19 May 2020 12:18:57 +1000 Subject: [PATCH 040/121] clean up imports and typos --- package/MDAnalysis/analysis/msd.py | 46 ++++++++++++------------------ 1 file changed, 19 insertions(+), 27 deletions(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 33e433c43d7..22796cc8077 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -136,17 +136,10 @@ from __future__ import division, absolute_import from six.moves import zip -import os -import errno -import warnings -import bz2 -import functools import numpy as np -from numpy.fft import fft,ifft import logging import tidynamics -import MDAnalysis from ..due import due, Doi due.cite(Doi("10.21105/joss.00877"), @@ -159,7 +152,7 @@ cite_module=True) del Doi -class EinsteinMSD(object): +class EinsteinMSD(AnalysisBase): r"""Class to calculate Mean Squared Displacement by the Einstein relation. Attributes @@ -172,7 +165,7 @@ class EinsteinMSD(object): The MSD of each individual particle with respect to lag-time. N_frames : int Number of frames in trajectory. - N_particles : int + n_particles : int Number of particles MSD was calculated over. """ @@ -204,7 +197,7 @@ def __init__(self, u, selection=None, msd_type='xyz', fft=True): #indexing self.N_frames = len(self.u.trajectory) - self.N_particles = 0 + self.n_particles = 0 #result self.dim_fac = 0 @@ -239,17 +232,16 @@ def _parse_msd_type(self): dim = [] self.msd_type = self.msd_type.lower() - print(self.msd_type) try: for tok in self.msd_type: - vals = (keys.pop(tok)) + vals = keys.pop(tok) dim.append(vals) except KeyError: - raise ValueError('invalid msd_type specified, please specify one of xyz, xy, xz, yz, x, y, z ') + raise ValueError(f'invalid msd_type: {self.msd_type} specified, please specify one of xyz, xy, xz, yz, x, y, z ') dim.sort() self._dim = dim - self.dim_fac = float(len(dim)) + self.dim_fac = len(dim) def _select_reference_positions(self): r""" Constructs array of positions for MSD calculation. @@ -264,18 +256,18 @@ def _select_reference_positions(self): Returns ------- self._position_array : :class:`np.ndarray` - Array of particle positions with respect to time shape = (N_frames, N_particles, 3) - self.N_particles : float + Array of particle positions with respect to time shape = (N_frames, n_particles, 3) + self.n_particles : float Number of particles used in the MSD calculation """ #avoid memory transfer by iterating idxs = self.u.select_atoms(self.selection).ix - position_array_dummy = np.zeros([len(self.u.trajectory), len(self.u.atoms), 3]) - for idx,ts in enumerate(self.u.trajectory): + position_array_dummy = np.zeros((len(self.u.trajectory), len(self.u.atoms), 3)) + for idx, ts in enumerate(self.u.trajectory): position_array_dummy[idx,:,:] = self.u.atoms.positions - self._position_array = position_array_dummy[:,idxs, :] - self.N_particles = self._position_array.shape[1] + self._position_array = position_array_dummy[:,idxs,:] + self.n_particles = self._position_array.shape[1] def run(self): @@ -292,7 +284,7 @@ def _run_simple(self): # naieve algorithm without FFT Parameters ---------- self._position_array : :class:`np.ndarray` - Array of particle positions with respect to time shape (N_frames, N_particles, 3). + Array of particle positions with respect to time shape (N_frames, n_particles, 3). Returns ------- @@ -300,10 +292,10 @@ def _run_simple(self): # naieve algorithm without FFT The MSD as a function of lag-time. """ - msds_byparticle = np.zeros([self.N_frames, self.N_particles]) + msds_byparticle = np.zeros((self.N_frames, self.n_particles)) lagtimes = np.arange(1,self.N_frames) - msds_byparticle[0,:] = np.zeros(self.N_particles) # preset the zero lagtime so we dont have to iterate through - for n in range(self.N_particles): + msds_byparticle[0,:] = np.zeros(self.n_particles) # preset the zero lagtime so we dont have to iterate through + for n in range(self.n_particles): for lag in lagtimes: disp = self._position_array[:-lag,n,self._dim if lag else None] - self._position_array[lag:,n,self._dim] sqdist = np.square(disp, dtype=np.float64).sum(axis=1, dtype=np.float64) #accumulation in anything other than f64 is innacurate @@ -318,7 +310,7 @@ def _run_fft(self): #with FFT, np.f64 bit prescision required. Parameters ---------- self._position_array : :class:`np.ndarray` - Array of particle positions with respect to time shape (N_frames, N_particles, 3). + Array of particle positions with respect to time shape (N_frames, n_particles, 3). Returns ------- @@ -326,9 +318,9 @@ def _run_fft(self): #with FFT, np.f64 bit prescision required. The MSD as a function of lagtime. """ - msds_byparticle = np.zeros([self.N_frames, self.N_particles]) + msds_byparticle = np.zeros((self.N_frames, self.n_particles)) reshape_positions = self._position_array[:,:,self._dim].astype(np.float64) - for n in range(self.N_particles): + for n in range(self.n_particles): msds_byparticle[:,n] = tidynamics.msd(reshape_positions[:,n,:]) self.msd_per_particle= msds_byparticle msds = msds_byparticle.mean(axis=1, dtype=np.float64) From 0f637dd3e07beb4a65b923471a48794b7111d9e3 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Wed, 20 May 2020 13:58:31 +1000 Subject: [PATCH 041/121] framework moved to subclass AnalysisBase --- package/MDAnalysis/analysis/msd.py | 83 ++++++++++++++++-------------- 1 file changed, 43 insertions(+), 40 deletions(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 73e71208efd..26c87db9120 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -70,14 +70,14 @@ Visual inspection of the MSD is important, so lets take a look at it with a simple plot. >>> import matplotlib.pyplot as plt - >>> nframes = MSD.N_frames + >>> nframes = MSD.n_frames >>> timestep = 1 # this needs to be the actual time between frames in your trajectory >>> lagtimes = np.arange(nframes)*timestep # make the lag time axis >>> plt.plot(msd, lagtimes) >>> plt.show() -We can see that the MSD is roughly linear, this is a numerical proof of a known theoretical result that the MSD of a random walk is approximately linear with respect to lagtime, where the slope is approximatley :math:`2*D`. -Note that the majority of MSDs computed from MD trajectories will not be this well behaved. Linearity of a segment of the MSD is required to accurately determine self diffusivity. +We can see that the MSD is approximately linear, this is a numerical proof of a known theoretical result that the MSD of a random walk is approximately linear with respect to lagtime, where the slope is approximately :math:`2*D`. +A segment of the MSD is required to be linear to accurately determine self-diffusivity. This linear segment represents the so called "middle" of the MSD plot, where ballistic trajectories at short time-lags are excluded along with poorly averaged data at long time-lags. We can select the "middle" of the MSD by indexing the MSD and the time-lags. Appropriately linear segments of the MSD can be confirmed with a log-log plot as is often reccomended [Maginn2019]_ where the "middle" segment can be identified as having a slope of 1. @@ -94,7 +94,7 @@ D_d = \frac{1}{2d} \lim_{t \to \infty} \frac{d}{dt} MSD(r_{d}) -From the MSD, self diffusivities :math:`D` with the desired dimensionality :math:`d` can be computed by fitting the MSD with respect to the lag time to a linear model. +From the MSD, self-diffusivities :math:`D` with the desired dimensionality :math:`d` can be computed by fitting the MSD with respect to the lag time to a linear model. An example of this is shown below, using the MSD computed in the example above. The segment between :math:`\tau = 20` and :math:`\tau = 80` is used to demonstrate selection of an MSD segment. >>> from scipy.stats import linregress as lr @@ -113,8 +113,8 @@ Notes _____ -There are several factors that must be taken into account when setting up and processing trajectories for computation of self diffusivities. -These include specific instructions around simulation settings, using unwrapped trajectories and maintaining relativley small elapsed time between saved frames. +There are several factors that must be taken into account when setting up and processing trajectories for computation of self-diffusivities. +These include specific instructions around simulation settings, using unwrapped trajectories and maintaining relatively small elapsed time between saved frames. Additionally corrections for finite size effects are sometimes employed along with varied means of estimating errors. The reader is directed to the following review, which describes many of the common pitfalls [Maginn2019]_. There are other ways to compute self diffusivity including from a Green-Kubo integral. At this point in time these methods are beyond the scope of this module @@ -134,8 +134,6 @@ """ from __future__ import division, absolute_import -from six.moves import zip - import numpy as np import logging @@ -157,13 +155,13 @@ class EinsteinMSD(AnalysisBase): Attributes ---------- - dim_fac : float + dim_fac : int Dimensionality :math:`d` of the MSD. timeseries : :class:`np.ndarray` The averaged MSD with respect to lag-time. msd_per_particle : :class:`np.ndarray` The MSD of each individual particle with respect to lag-time. - N_frames : int + n_frames : int Number of frames in trajectory. n_particles : int Number of particles MSD was calculated over. @@ -185,6 +183,8 @@ def __init__(self, u, selection=None, msd_type='xyz', fft=True): Otherwise, use the naive or "simple" algorithm. Defaults to ``True``. """ + super(EinsteinMSD, self).__init__(universe.trajectory, **kwargs) + #args self.u = u self.selection = selection @@ -194,23 +194,24 @@ def __init__(self, u, selection=None, msd_type='xyz', fft=True): #local self._dim = None self._position_array = None + self._atoms = None #indexing - self.N_frames = len(self.u.trajectory) - self.n_particles = 0 + self._n_frames = 0 + self._n_particles = 0 #result self.dim_fac = 0 self.timeseries = None self.msd_per_particle = None - #prep - self._prepare() - - def _prepare(self): self._parse_msd_type() - self._select_reference_positions() + self._atoms = self.u.select_atoms(self.selection) + self._n_frames = len(self._u.trajectory) + self._n_particles = len(self._atoms) + self._position_array = np.zeros((self._n_frames, self._n_particles, 3)) + def _parse_msd_type(self): r""" Sets up the desired dimensionality of the MSD. @@ -224,10 +225,11 @@ def _parse_msd_type(self): ------- self._dim : list Array-like used to slice the positions to obtain desired dimensionality - self.dim_fac : float + self.dim_fac : int Dimension factor :math:`d` of the MSD. """ + #TODO fix to dict keys = {'x':0, 'y':1, 'z':2} dim = [] @@ -243,7 +245,7 @@ def _parse_msd_type(self): self._dim = dim self.dim_fac = len(dim) - def _select_reference_positions(self): + def _single_frame(self): r""" Constructs array of positions for MSD calculation. Parameters @@ -256,35 +258,36 @@ def _select_reference_positions(self): Returns ------- self._position_array : :class:`np.ndarray` - Array of particle positions with respect to time shape = (N_frames, n_particles, 3) + Array of particle positions with respect to time shape = (n_frames, n_particles, 3) self.n_particles : float Number of particles used in the MSD calculation """ #avoid memory transfer by iterating - idxs = self.u.select_atoms(self.selection).ix - position_array_dummy = np.zeros((len(self.u.trajectory), len(self.u.atoms), 3)) - for idx, ts in enumerate(self.u.trajectory): - position_array_dummy[idx,:,:] = self.u.atoms.positions - self._position_array = position_array_dummy[:,idxs,:] - self.n_particles = self._position_array.shape[1] + self._position_array[] = self._atoms.positions + + + # idxs = self.u.select_atoms(self.selection).ix + # position_array_dummy = np.zeros((len(self.u.trajectory), len(self.u.atoms), 3)) + # for idx, ts in enumerate(self.u.trajectory): + # position_array_dummy[idx,:,:] = self.u.atoms.positions + # self._position_array = position_array_dummy[:,idxs,:] + - def run(self): - r""" Wrapper to calculate the MSD via either the "simple" or "fft" algorithm. - """ + def _conclude(self): if self.fft == True: - self.timeseries = self._run_fft() + self.timeseries = self._conclude_fft() else: - self.timeseries = self._run_simple() + self.timeseries = self._conclude_simple() - def _run_simple(self): # naieve algorithm without FFT - r""" Calculates the MSD via the naieve "windowed" algorithm. + def _conclude_simple(self): # simple algorithm without FFT + r""" Calculates the MSD via the simple "windowed" algorithm. Parameters ---------- self._position_array : :class:`np.ndarray` - Array of particle positions with respect to time shape (N_frames, n_particles, 3). + Array of particle positions with respect to time shape (n_frames, n_particles, 3). Returns ------- @@ -292,25 +295,25 @@ def _run_simple(self): # naieve algorithm without FFT The MSD as a function of lag-time. """ - msds_byparticle = np.zeros((self.N_frames, self.n_particles)) - lagtimes = np.arange(1,self.N_frames) + msds_byparticle = np.zeros((self.n_frames, self.n_particles)) + lagtimes = np.arange(1,self.n_frames) msds_byparticle[0,:] = np.zeros(self.n_particles) # preset the zero lagtime so we dont have to iterate through for n in range(self.n_particles): for lag in lagtimes: disp = self._position_array[:-lag,n,self._dim if lag else None] - self._position_array[lag:,n,self._dim] - sqdist = np.square(disp, dtype=np.float64).sum(axis=1, dtype=np.float64) #accumulation in anything other than f64 is innacurate + sqdist = np.square(disp, dtype=np.float64).sum(axis=1, dtype=np.float64) #accumulation in anything other than f64 is inaccurate msds_byparticle[lag,n] = np.mean(sqdist, dtype=np.float64) self.msd_per_particle = msds_byparticle msds = msds_byparticle.mean(axis=1, dtype=np.float64) return msds - def _run_fft(self): #with FFT, np.f64 bit prescision required. + def _conclude_fft(self): #with FFT, np.float64 bit prescision required. r""" Calculates the MSD via the FCA fast correlation algorithm. Parameters ---------- self._position_array : :class:`np.ndarray` - Array of particle positions with respect to time shape (N_frames, n_particles, 3). + Array of particle positions with respect to time shape (n_frames, n_particles, 3). Returns ------- @@ -318,7 +321,7 @@ def _run_fft(self): #with FFT, np.f64 bit prescision required. The MSD as a function of lagtime. """ - msds_byparticle = np.zeros((self.N_frames, self.n_particles)) + msds_byparticle = np.zeros((self.n_frames, self.n_particles)) reshape_positions = self._position_array[:,:,self._dim].astype(np.float64) for n in range(self.n_particles): msds_byparticle[:,n] = tidynamics.msd(reshape_positions[:,n,:]) From 4c5f48705cda48f83aeb00699296a73ea232718f Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Wed, 20 May 2020 14:38:21 +1000 Subject: [PATCH 042/121] AnalysisBase compatible and passing tests --- package/MDAnalysis/analysis/msd.py | 30 ++++++++----------- .../MDAnalysisTests/analysis/test_msd.py | 2 +- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 26c87db9120..435c3fecfe6 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -139,6 +139,7 @@ import logging import tidynamics from ..due import due, Doi +from .base import AnalysisBase due.cite(Doi("10.21105/joss.00877"), description="Mean Squared Displacements with tidynamics", @@ -168,7 +169,7 @@ class EinsteinMSD(AnalysisBase): """ - def __init__(self, u, selection=None, msd_type='xyz', fft=True): + def __init__(self, u, selection=None, msd_type='xyz', fft=True, **kwargs): r""" Parameters ---------- @@ -183,10 +184,11 @@ def __init__(self, u, selection=None, msd_type='xyz', fft=True): Otherwise, use the naive or "simple" algorithm. Defaults to ``True``. """ - super(EinsteinMSD, self).__init__(universe.trajectory, **kwargs) + self.u = u + + super(EinsteinMSD, self).__init__(self.u.trajectory, **kwargs) #args - self.u = u self.selection = selection self.msd_type = msd_type self.fft = fft @@ -208,7 +210,7 @@ def __init__(self, u, selection=None, msd_type='xyz', fft=True): def _prepare(self): self._parse_msd_type() self._atoms = self.u.select_atoms(self.selection) - self._n_frames = len(self._u.trajectory) + self._n_frames = len(self.u.trajectory) self._n_particles = len(self._atoms) self._position_array = np.zeros((self._n_frames, self._n_particles, 3)) @@ -252,20 +254,14 @@ def _single_frame(self): ---------- self.u : :class:`Universe` MDAnalysis Universe - self.selection : str - MDAnalysis selection string Returns ------- self._position_array : :class:`np.ndarray` Array of particle positions with respect to time shape = (n_frames, n_particles, 3) - self.n_particles : float - Number of particles used in the MSD calculation - """ - #avoid memory transfer by iterating - self._position_array[] = self._atoms.positions + self._position_array[self._frame_index,:,:] = self._atoms.positions # idxs = self.u.select_atoms(self.selection).ix # position_array_dummy = np.zeros((len(self.u.trajectory), len(self.u.atoms), 3)) @@ -295,13 +291,13 @@ def _conclude_simple(self): # simple algorithm without FFT The MSD as a function of lag-time. """ - msds_byparticle = np.zeros((self.n_frames, self.n_particles)) + msds_byparticle = np.zeros((self._n_frames, self._n_particles)) lagtimes = np.arange(1,self.n_frames) - msds_byparticle[0,:] = np.zeros(self.n_particles) # preset the zero lagtime so we dont have to iterate through - for n in range(self.n_particles): + msds_byparticle[0,:] = np.zeros(self._n_particles) # preset the zero lagtime so we don't have to iterate through + for n in range(self._n_particles): for lag in lagtimes: disp = self._position_array[:-lag,n,self._dim if lag else None] - self._position_array[lag:,n,self._dim] - sqdist = np.square(disp, dtype=np.float64).sum(axis=1, dtype=np.float64) #accumulation in anything other than f64 is inaccurate + sqdist = np.square(disp, dtype=np.float64).sum(axis=1, dtype=np.float64) #accumulation in np.float64 required msds_byparticle[lag,n] = np.mean(sqdist, dtype=np.float64) self.msd_per_particle = msds_byparticle msds = msds_byparticle.mean(axis=1, dtype=np.float64) @@ -321,9 +317,9 @@ def _conclude_fft(self): #with FFT, np.float64 bit prescision required. The MSD as a function of lagtime. """ - msds_byparticle = np.zeros((self.n_frames, self.n_particles)) + msds_byparticle = np.zeros((self._n_frames, self._n_particles)) reshape_positions = self._position_array[:,:,self._dim].astype(np.float64) - for n in range(self.n_particles): + for n in range(self._n_particles): msds_byparticle[:,n] = tidynamics.msd(reshape_positions[:,n,:]) self.msd_per_particle= msds_byparticle msds = msds_byparticle.mean(axis=1, dtype=np.float64) diff --git a/testsuite/MDAnalysisTests/analysis/test_msd.py b/testsuite/MDAnalysisTests/analysis/test_msd.py index fc942c06f35..297c9d80e05 100644 --- a/testsuite/MDAnalysisTests/analysis/test_msd.py +++ b/testsuite/MDAnalysisTests/analysis/test_msd.py @@ -97,7 +97,7 @@ def characteristic_poly(n,d): #polynomial that describes unit step trajectory MS #test some basic size and shape things def test_selection_works(msd): - assert_equal(msd.N_particles, 10) + assert_equal(msd._n_particles, 10) #testing on the PSF, DCD trajectory def test_fft_vs_simple_default(msd, msd_fft): From 78f7d4fb6ced68dff6ae2fd10b01966eb649830f Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Wed, 20 May 2020 14:51:14 +1000 Subject: [PATCH 043/121] fix msd_by_particle and corresponding tests --- package/MDAnalysis/analysis/msd.py | 33 +++++++------------ .../MDAnalysisTests/analysis/test_msd.py | 8 ++--- 2 files changed, 15 insertions(+), 26 deletions(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 435c3fecfe6..4ba227809f0 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -205,7 +205,7 @@ def __init__(self, u, selection=None, msd_type='xyz', fft=True, **kwargs): #result self.dim_fac = 0 self.timeseries = None - self.msd_per_particle = None + self.msds_by_particle = None def _prepare(self): self._parse_msd_type() @@ -213,7 +213,8 @@ def _prepare(self): self._n_frames = len(self.u.trajectory) self._n_particles = len(self._atoms) self._position_array = np.zeros((self._n_frames, self._n_particles, 3)) - + self.msds_by_particle = np.zeros((self._n_frames, self._n_particles)) + # self.timeseries not set here def _parse_msd_type(self): r""" Sets up the desired dimensionality of the MSD. @@ -262,15 +263,7 @@ def _single_frame(self): """ self._position_array[self._frame_index,:,:] = self._atoms.positions - - # idxs = self.u.select_atoms(self.selection).ix - # position_array_dummy = np.zeros((len(self.u.trajectory), len(self.u.atoms), 3)) - # for idx, ts in enumerate(self.u.trajectory): - # position_array_dummy[idx,:,:] = self.u.atoms.positions - # self._position_array = position_array_dummy[:,idxs,:] - - - + def _conclude(self): if self.fft == True: self.timeseries = self._conclude_fft() @@ -291,17 +284,15 @@ def _conclude_simple(self): # simple algorithm without FFT The MSD as a function of lag-time. """ - msds_byparticle = np.zeros((self._n_frames, self._n_particles)) lagtimes = np.arange(1,self.n_frames) - msds_byparticle[0,:] = np.zeros(self._n_particles) # preset the zero lagtime so we don't have to iterate through + self.msds_by_particle[0,:] = np.zeros(self._n_particles) # preset the zero lagtime so we don't have to iterate through for n in range(self._n_particles): for lag in lagtimes: disp = self._position_array[:-lag,n,self._dim if lag else None] - self._position_array[lag:,n,self._dim] sqdist = np.square(disp, dtype=np.float64).sum(axis=1, dtype=np.float64) #accumulation in np.float64 required - msds_byparticle[lag,n] = np.mean(sqdist, dtype=np.float64) - self.msd_per_particle = msds_byparticle - msds = msds_byparticle.mean(axis=1, dtype=np.float64) - return msds + self.msds_by_particle[lag,n] = np.mean(sqdist, dtype=np.float64) + msd = self.msds_by_particle.mean(axis=1, dtype=np.float64) + return msd def _conclude_fft(self): #with FFT, np.float64 bit prescision required. r""" Calculates the MSD via the FCA fast correlation algorithm. @@ -317,10 +308,8 @@ def _conclude_fft(self): #with FFT, np.float64 bit prescision required. The MSD as a function of lagtime. """ - msds_byparticle = np.zeros((self._n_frames, self._n_particles)) reshape_positions = self._position_array[:,:,self._dim].astype(np.float64) for n in range(self._n_particles): - msds_byparticle[:,n] = tidynamics.msd(reshape_positions[:,n,:]) - self.msd_per_particle= msds_byparticle - msds = msds_byparticle.mean(axis=1, dtype=np.float64) - return msds + self.msds_by_particle[:,n] = tidynamics.msd(reshape_positions[:,n,:]) + msd = self.msds_by_particle.mean(axis=1, dtype=np.float64) + return msd diff --git a/testsuite/MDAnalysisTests/analysis/test_msd.py b/testsuite/MDAnalysisTests/analysis/test_msd.py index 297c9d80e05..75f830107a1 100644 --- a/testsuite/MDAnalysisTests/analysis/test_msd.py +++ b/testsuite/MDAnalysisTests/analysis/test_msd.py @@ -107,8 +107,8 @@ def test_fft_vs_simple_default(msd, msd_fft): #check fft and simple give same result per particle def test_fft_vs_simple_default_per_particle(msd, msd_fft): - per_particle_simple = msd.msd_per_particle - per_particle_fft = msd_fft.msd_per_particle + per_particle_simple = msd.msds_by_particle + per_particle_fft = msd_fft.msds_by_particle assert_almost_equal(per_particle_simple, per_particle_fft, decimal=4) #check fft and simple give same result for each dimensionality @@ -127,10 +127,10 @@ def test_fft_vs_simple_all_dims_per_particle(dimension_list, u): for dim in dimension_list: m_simple = MSD(u, SELECTION, msd_type=dim, fft=False) m_simple.run() - per_particle_simple = m_simple.msd_per_particle + per_particle_simple = m_simple.msds_by_particle m_fft = MSD(u,SELECTION, msd_type=dim, fft=True) m_fft.run() - per_particle_fft = m_fft.msd_per_particle + per_particle_fft = m_fft.msds_by_particle assert_almost_equal(per_particle_simple, per_particle_fft, decimal=4) #testing the "simple" algorithm on constant velocity trajectory From b458ae887b3c64c754996f4999830cb021945b38 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Wed, 20 May 2020 14:57:57 +1000 Subject: [PATCH 044/121] removed some inline comments that were useless --- package/MDAnalysis/analysis/msd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 4ba227809f0..055476eac22 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -270,7 +270,7 @@ def _conclude(self): else: self.timeseries = self._conclude_simple() - def _conclude_simple(self): # simple algorithm without FFT + def _conclude_simple(self): r""" Calculates the MSD via the simple "windowed" algorithm. Parameters From 09416cb78a78f17c4326a8e9dc0c8324a47d7279 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Wed, 20 May 2020 15:29:35 +1000 Subject: [PATCH 045/121] changed parse_msd_type to a clearer selection syntax --- package/MDAnalysis/analysis/msd.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 055476eac22..7963f20b561 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -232,21 +232,16 @@ def _parse_msd_type(self): Dimension factor :math:`d` of the MSD. """ - #TODO fix to dict - keys = {'x':0, 'y':1, 'z':2} - dim = [] + keys = {'x':[0], 'y':[1], 'z':[2], 'xy':[0,1], 'xz':[0,2], 'yz':[1,2], 'xyz':[0,1,2]} self.msd_type = self.msd_type.lower() + try: - for tok in self.msd_type: - vals = keys.pop(tok) - dim.append(vals) + self._dim = keys[self.msd_type] except KeyError: - raise ValueError(f'invalid msd_type: {self.msd_type} specified, please specify one of xyz, xy, xz, yz, x, y, z ') - - dim.sort() - self._dim = dim - self.dim_fac = len(dim) + raise ValueError(f'invalid msd_type: {self.msd_type} specified, please specify one of xyz, xy, xz, yz, x, y, z') + + self.dim_fac = len(self._dim) def _single_frame(self): r""" Constructs array of positions for MSD calculation. From 60f0cca1e85d04320b558ab524170b0ae97347d9 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Wed, 20 May 2020 16:10:02 +1000 Subject: [PATCH 046/121] remove fstring causing failed builds --- package/MDAnalysis/analysis/msd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 7963f20b561..e4618386763 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -239,7 +239,7 @@ def _parse_msd_type(self): try: self._dim = keys[self.msd_type] except KeyError: - raise ValueError(f'invalid msd_type: {self.msd_type} specified, please specify one of xyz, xy, xz, yz, x, y, z') + raise ValueError('invalid msd_type: {} specified, please specify one of xyz, xy, xz, yz, x, y, z'.format self.msd_type) self.dim_fac = len(self._dim) From 38da36edb20370e0b3da27ba5a5ce2a18fcb1ec9 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Wed, 20 May 2020 16:10:43 +1000 Subject: [PATCH 047/121] fix typo in fstring removal --- package/MDAnalysis/analysis/msd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index e4618386763..1181eaa89fd 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -239,7 +239,7 @@ def _parse_msd_type(self): try: self._dim = keys[self.msd_type] except KeyError: - raise ValueError('invalid msd_type: {} specified, please specify one of xyz, xy, xz, yz, x, y, z'.format self.msd_type) + raise ValueError('invalid msd_type: {} specified, please specify one of xyz, xy, xz, yz, x, y, z'.format(self.msd_type) self.dim_fac = len(self._dim) From 0e22adf6171d06c08b3a74fb288c19622aac72be Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Wed, 20 May 2020 16:11:04 +1000 Subject: [PATCH 048/121] fix another typo in fstring removal --- package/MDAnalysis/analysis/msd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 1181eaa89fd..64a35c36ed6 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -239,7 +239,7 @@ def _parse_msd_type(self): try: self._dim = keys[self.msd_type] except KeyError: - raise ValueError('invalid msd_type: {} specified, please specify one of xyz, xy, xz, yz, x, y, z'.format(self.msd_type) + raise ValueError('invalid msd_type: {} specified, please specify one of xyz, xy, xz, yz, x, y, z'.format(self.msd_type)) self.dim_fac = len(self._dim) From 504e19ce5253bf3b4544354ffac16467d896ba98 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Wed, 20 May 2020 20:56:46 +1000 Subject: [PATCH 049/121] clean up half changes to select kwarg --- package/MDAnalysis/analysis/msd.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 64a35c36ed6..cdb44c8df94 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -27,9 +27,9 @@ ============================================================== This module implements the calculation of Mean Squared Displacmements (MSDs) by the Einstein relation. -MSDs can be used to characterise the speed at which particles move and has its roots -in the study of Brownian motion. For a full explanation of the theory behind MSDs and the subsequent calculation of self diffusivities the reader is directed to [Maginn2019]_. -MSDs can be computed from the following expression, known as "Einstein" formula: +MSDs can be used to characterize the speed at which particles move and has its roots +in the study of Brownian motion. For a full explanation of the theory behind MSDs and the subsequent calculation of self-diffusivities the reader is directed to [Maginn2019]_. +MSDs can be computed from the following expression, known as the "Einstein" formula: .. math:: @@ -84,11 +84,11 @@ >>> plt.loglog(msd, lagtimes) >>> plt.show() -Now that we have identified what segment of our MSD to analyse, lets compute a self diffusivity. +Now that we have identified what segment of our MSD to analyse, lets compute a self-diffusivity. -Computing Self Diffusivity +Computing Self-Diffusivity -------------------------------- -Self diffusivity is closely related to the MSD. +Self-diffusivity is closely related to the MSD. .. math:: @@ -107,7 +107,7 @@ >>> error = linear_model.rvalue >>> D = slope * 1/(2*MSD.dim_fac) #dim_fac is 3 as we computed a 3D msd ('xyz') -We have now computed a self diffusivity! +We have now computed a self-diffusivity! Notes @@ -116,7 +116,7 @@ There are several factors that must be taken into account when setting up and processing trajectories for computation of self-diffusivities. These include specific instructions around simulation settings, using unwrapped trajectories and maintaining relatively small elapsed time between saved frames. Additionally corrections for finite size effects are sometimes employed along with varied means of estimating errors. -The reader is directed to the following review, which describes many of the common pitfalls [Maginn2019]_. There are other ways to compute self diffusivity including from a Green-Kubo integral. At this point in time these methods are beyond the scope of this module +The reader is directed to the following review, which describes many of the common pitfalls [Maginn2019]_. There are other ways to compute self-diffusivity including from a Green-Kubo integral. At this point in time these methods are beyond the scope of this module References @@ -140,6 +140,9 @@ import tidynamics from ..due import due, Doi from .base import AnalysisBase +from MDAnalysis.core.groups import AtomGroup, UpdatingAtomGroup +from MDAnalysis.core.universe import Universe + due.cite(Doi("10.21105/joss.00877"), description="Mean Squared Displacements with tidynamics", @@ -169,7 +172,7 @@ class EinsteinMSD(AnalysisBase): """ - def __init__(self, u, selection=None, msd_type='xyz', fft=True, **kwargs): + def __init__(self, u, select=None, msd_type='xyz', fft=True, **kwargs): r""" Parameters ---------- @@ -189,7 +192,7 @@ def __init__(self, u, selection=None, msd_type='xyz', fft=True, **kwargs): super(EinsteinMSD, self).__init__(self.u.trajectory, **kwargs) #args - self.selection = selection + self.select = select self.msd_type = msd_type self.fft = fft @@ -209,7 +212,7 @@ def __init__(self, u, selection=None, msd_type='xyz', fft=True, **kwargs): def _prepare(self): self._parse_msd_type() - self._atoms = self.u.select_atoms(self.selection) + self._atoms = self.u.select_atoms(self.select) self._n_frames = len(self.u.trajectory) self._n_particles = len(self._atoms) self._position_array = np.zeros((self._n_frames, self._n_particles, 3)) From e8054031bd281d2ad411258d8501bb65fa434f68 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Wed, 20 May 2020 21:28:52 +1000 Subject: [PATCH 050/121] clean up references --- package/MDAnalysis/analysis/msd.py | 12 +++++------- .../sphinx/source/documentation_pages/references.rst | 12 ++++++++++++ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index cdb44c8df94..3dca6bfe3d0 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -38,10 +38,10 @@ Where :math:`N` is the number of equivalent particles the MSD is calculated over, :math:`r` are their coordinates and :math:`d` the desired dimensionality of the MSD. Note that while the definition of the MSD is universal, there are many practical considerations to computing the MSD that vary between implementations. In this module, we compute a "windowed" MSD, where the MSD is averaged over all possible lag times :math:`\tau \le \tau_{max}`, -where :math:`\tau_{max}` is the length of the trajectory, thereby maximising the number of samples. +where :math:`\tau_{max}` is the length of the trajectory, thereby maximizing the number of samples. The computation of the MSD in this way can be computationally intensive due to it's :math:`N^2` scaling with respect to :math:`\tau_{max}`. -An algorithm to compute the MSD with :math:`N log(N)` scaling based on a Fast Fourier Transform is known and can be accessed by setting fft=True [Calandri2011]_. +An algorithm to compute the MSD with :math:`N log(N)` scaling based on a Fast Fourier Transform is known and can be accessed by setting fft=True [Calandri2011]_ [Buyl2018]_. Computing an MSD ---------------- @@ -122,8 +122,9 @@ References ---------- -.. [Maginn2019] Maginn, E. J.; Messerly, R. A.; Carlson, D. J.; Roe, D. R.; Elliott, J. R. Best Practices for Computing Transport Properties 1. Self-Diffusivity and Viscosity from Equilibrium Molecular Dynamics [Article v1.0]. Living J. Comput. Mol. Sci. 2019, 1 (1). -.. [Calandri2011] Calandrini, V.; Pellegrini, E.; Calligari, P.; Hinsen, K.; Kneller, G. R. NMoldyn-Interfacing Spectroscopic Experiments, Molecular Dynamics Simulations and Models for Time Correlation Functions. Collect. SFN 2011, 12, 201–232. +.. [Maginn2019] Maginn, E. J., Messerly, R. A., Carlson, D. J.; Roe, D. R., Elliott, J. R. Best Practices for Computing Transport Properties 1. Self-Diffusivity and Viscosity from Equilibrium Molecular Dynamics [Article v1.0]. Living J. Comput. Mol. Sci. 2019, 1 (1). +.. [Calandri2011] Calandrini, V., Pellegrini, E., Calligari, P., Hinsen, K., Kneller, G. R. NMoldyn-Interfacing Spectroscopic Experiments, Molecular Dynamics Simulations and Models for Time Correlation Functions. Collect. SFN 2011 12, 201–232. +.. [Buyl2018] Buyl, P. tidynamics: A tiny package to compute the dynamics of stochastic and molecular simulations. Journal of Open Source Software, 2018 3(28), 877. Classes and Functions --------------------- @@ -140,9 +141,6 @@ import tidynamics from ..due import due, Doi from .base import AnalysisBase -from MDAnalysis.core.groups import AtomGroup, UpdatingAtomGroup -from MDAnalysis.core.universe import Universe - due.cite(Doi("10.21105/joss.00877"), description="Mean Squared Displacements with tidynamics", diff --git a/package/doc/sphinx/source/documentation_pages/references.rst b/package/doc/sphinx/source/documentation_pages/references.rst index 594f0719748..99733474d1f 100644 --- a/package/doc/sphinx/source/documentation_pages/references.rst +++ b/package/doc/sphinx/source/documentation_pages/references.rst @@ -166,7 +166,19 @@ If you use :meth:`~MDAnalysis.analysis.pca.PCA.cumulative_overlap` or .. _`10.1016/j.str.2007.12.011`: https://dx.doi.org/10.1016/j.str.2007.12.011 +If you use the Mean Squared Displacement analysis code in +:mod:`MDAnalysis.analysis.msd` please cite [Calandri2011]_ and [Buyl2019]_. +.. [Calandri2011] Calandrini, V., Pellegrini, E., Calligari, P., Hinsen, K., Kneller, G. R. + NMoldyn-Interfacing Spectroscopic Experiments, Molecular Dynamics Simulations and Models for Time Correlation Functions. + *Collect. SFN*, **12**, 201–232 (2011). doi: `10.1051/sfn/201112010`_ + +.. _`10.1051/sfn/201112010`: https://doi.org/10.1051/sfn/201112010 + +.. [Buyl2018] Buyl, P. tidynamics: A tiny package to compute the dynamics of stochastic and molecular simulations. Journal of Open Source Software, + 3(28), 877 (2018). doi: `10.21105/joss.00877`_ + +.. _`10.21105/joss.00877`: https://doi.org/10.21105/joss.00877 .. _citations-using-duecredit: From 6f70f060eb41a1adbb423024978a074e7565145f Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Thu, 21 May 2020 15:17:08 +1000 Subject: [PATCH 051/121] fix docstring references --- package/MDAnalysis/analysis/msd.py | 6 ++++-- .../doc/sphinx/source/documentation_pages/references.rst | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 3dca6bfe3d0..871fb5f36cd 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -43,6 +43,8 @@ The computation of the MSD in this way can be computationally intensive due to it's :math:`N^2` scaling with respect to :math:`\tau_{max}`. An algorithm to compute the MSD with :math:`N log(N)` scaling based on a Fast Fourier Transform is known and can be accessed by setting fft=True [Calandri2011]_ [Buyl2018]_. +Please cite [Calandri2011]_ [Buyl2018]_ if you use this module in addition to the normal MDAnalysis citations. + Computing an MSD ---------------- This example computes a 3D MSD for the movement of 100 particles undergoing a random walk. @@ -123,8 +125,8 @@ ---------- .. [Maginn2019] Maginn, E. J., Messerly, R. A., Carlson, D. J.; Roe, D. R., Elliott, J. R. Best Practices for Computing Transport Properties 1. Self-Diffusivity and Viscosity from Equilibrium Molecular Dynamics [Article v1.0]. Living J. Comput. Mol. Sci. 2019, 1 (1). -.. [Calandri2011] Calandrini, V., Pellegrini, E., Calligari, P., Hinsen, K., Kneller, G. R. NMoldyn-Interfacing Spectroscopic Experiments, Molecular Dynamics Simulations and Models for Time Correlation Functions. Collect. SFN 2011 12, 201–232. -.. [Buyl2018] Buyl, P. tidynamics: A tiny package to compute the dynamics of stochastic and molecular simulations. Journal of Open Source Software, 2018 3(28), 877. + + Classes and Functions --------------------- diff --git a/package/doc/sphinx/source/documentation_pages/references.rst b/package/doc/sphinx/source/documentation_pages/references.rst index 99733474d1f..1a00a2d8b99 100644 --- a/package/doc/sphinx/source/documentation_pages/references.rst +++ b/package/doc/sphinx/source/documentation_pages/references.rst @@ -167,7 +167,7 @@ If you use :meth:`~MDAnalysis.analysis.pca.PCA.cumulative_overlap` or .. _`10.1016/j.str.2007.12.011`: https://dx.doi.org/10.1016/j.str.2007.12.011 If you use the Mean Squared Displacement analysis code in -:mod:`MDAnalysis.analysis.msd` please cite [Calandri2011]_ and [Buyl2019]_. +:mod:`MDAnalysis.analysis.msd` please cite [Calandri2011]_ and [Buyl2018]_. .. [Calandri2011] Calandrini, V., Pellegrini, E., Calligari, P., Hinsen, K., Kneller, G. R. NMoldyn-Interfacing Spectroscopic Experiments, Molecular Dynamics Simulations and Models for Time Correlation Functions. From 4894bbddd8774543a166cbf5e544e9e47f7f7a0d Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Fri, 22 May 2020 17:21:17 +1000 Subject: [PATCH 052/121] improve docs and add image --- package/MDAnalysis/analysis/msd.py | 35 ++++++++++++++---- .../sphinx/source/images/msd_demo_plot.png | Bin 0 -> 31830 bytes 2 files changed, 27 insertions(+), 8 deletions(-) create mode 100644 package/doc/sphinx/source/images/msd_demo_plot.png diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 871fb5f36cd..a22d41353fe 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -26,6 +26,10 @@ Mean Squared Displacement --- :mod:`MDAnalysis.analysis.msd` ============================================================== +:Authors: Hugo MacDermott-Opeskin +:Year: 2020 +:Copyright: GNU Public License v2 + This module implements the calculation of Mean Squared Displacmements (MSDs) by the Einstein relation. MSDs can be used to characterize the speed at which particles move and has its roots in the study of Brownian motion. For a full explanation of the theory behind MSDs and the subsequent calculation of self-diffusivities the reader is directed to [Maginn2019]_. @@ -75,15 +79,30 @@ >>> nframes = MSD.n_frames >>> timestep = 1 # this needs to be the actual time between frames in your trajectory >>> lagtimes = np.arange(nframes)*timestep # make the lag time axis - >>> plt.plot(msd, lagtimes) + >>> fig = plt.figure() + >>> ax = plt.axes() + >>> ax.plot(lagtimes, msd, color="black", linestyle="-", label=r'3D random walk') # plot the actual MSD + >>> exact = lagtimes*6 + >>> ax.plot(lagtimes, exact, color="black", linestyle="--", label=r'$y=2 D\tau$') # plot the exact result >>> plt.show() -We can see that the MSD is approximately linear, this is a numerical proof of a known theoretical result that the MSD of a random walk is approximately linear with respect to lagtime, where the slope is approximately :math:`2*D`. -A segment of the MSD is required to be linear to accurately determine self-diffusivity. +Which gives us the following plot of the MSD with respect to lag-time (:math:`\tau`). +We can see that the MSD is approximately linear with respect to :math:`\tau`. +This is a numerical example of a known theoretical result that the MSD of a random walk is linear with respect to lagtime, with a slope of :math:`2d`. +In this expression :math:`d` is the dimensionality of the MSD which for our 3D MSD is equal to 3. +For comparison we have plotted the line :math:`y=6\tau` to which an ensemble of 3D random walks should converge. + +.. _figure-msd: + +.. figure:: /images/msd_demo_plot.png + :scale: 100 % + :alt: MSD plot + +Note that a segment of the MSD is required to be linear to accurately determine self-diffusivity. This linear segment represents the so called "middle" of the MSD plot, where ballistic trajectories at short time-lags are excluded along with poorly averaged data at long time-lags. We can select the "middle" of the MSD by indexing the MSD and the time-lags. Appropriately linear segments of the MSD can be confirmed with a log-log plot as is often reccomended [Maginn2019]_ where the "middle" segment can be identified as having a slope of 1. - >>> plt.loglog(msd, lagtimes) + >>> plt.loglog(lagtimes, msd) >>> plt.show() Now that we have identified what segment of our MSD to analyse, lets compute a self-diffusivity. @@ -97,13 +116,13 @@ D_d = \frac{1}{2d} \lim_{t \to \infty} \frac{d}{dt} MSD(r_{d}) From the MSD, self-diffusivities :math:`D` with the desired dimensionality :math:`d` can be computed by fitting the MSD with respect to the lag time to a linear model. -An example of this is shown below, using the MSD computed in the example above. The segment between :math:`\tau = 20` and :math:`\tau = 80` is used to demonstrate selection of an MSD segment. +An example of this is shown below, using the MSD computed in the example above. The segment between :math:`\tau = 20` and :math:`\tau = 60` is used to demonstrate selection of an MSD segment. >>> from scipy.stats import linregress as lr >>> start_time = 20 - >>> start_index = start_time/timestep - >>> end_time = 80 - >>> end_index = end_time/timestep + >>> start_index = int(start_time/timestep) + >>> end_time = 60 + >>> end_index = int(end_time/timestep) >>> linear_model = lr(lagtimes[start_index:end_index], msd[start_index:end_index]) >>> slope = linear_model.slope >>> error = linear_model.rvalue diff --git a/package/doc/sphinx/source/images/msd_demo_plot.png b/package/doc/sphinx/source/images/msd_demo_plot.png new file mode 100644 index 0000000000000000000000000000000000000000..f3ecc25c252adb541c3745febfba6538ad9d7ca7 GIT binary patch literal 31830 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 Q1DvvZhkOV literal 0 HcmV?d00001 From 3a48b6154002dfe8206325e3c3736aafffc7059d Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Fri, 22 May 2020 17:29:56 +1000 Subject: [PATCH 053/121] spelling in docs --- package/MDAnalysis/analysis/msd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index a22d41353fe..d56143e1104 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -203,7 +203,7 @@ def __init__(self, u, select=None, msd_type='xyz', fft=True, **kwargs): Desired dimensions to be included in the MSD. Defaults to 'xyz'. fft : bool Use a fast FFT based algorithm for computation of the MSD. - Otherwise, use the naive or "simple" algorithm. Defaults to ``True``. + Otherwise, use the simple "windowed" algorithm. Defaults to ``True``. """ self.u = u From 0d3448726c541a9f8fb16837ceb138328ec446aa Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Sat, 23 May 2020 20:42:02 +1000 Subject: [PATCH 054/121] remove inner loop over n_particles in simple MSD algorithm --- package/MDAnalysis/analysis/msd.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index d56143e1104..3dd3f80897f 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -303,11 +303,10 @@ def _conclude_simple(self): """ lagtimes = np.arange(1,self.n_frames) self.msds_by_particle[0,:] = np.zeros(self._n_particles) # preset the zero lagtime so we don't have to iterate through - for n in range(self._n_particles): - for lag in lagtimes: - disp = self._position_array[:-lag,n,self._dim if lag else None] - self._position_array[lag:,n,self._dim] - sqdist = np.square(disp, dtype=np.float64).sum(axis=1, dtype=np.float64) #accumulation in np.float64 required - self.msds_by_particle[lag,n] = np.mean(sqdist, dtype=np.float64) + for lag in lagtimes: + disp = self._position_array[:-lag,:,self._dim] - self._position_array[lag:,:,self._dim] + sqdist = np.square(disp, dtype=np.float64).sum(axis=-1, dtype=np.float64) #accumulation in np.float64 required + self.msds_by_particle[lag,:] = np.mean(sqdist, axis=0, dtype=np.float64) msd = self.msds_by_particle.mean(axis=1, dtype=np.float64) return msd From 4ca2bf7a96f0f6c7c4859240db0a4333f36b3830 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Sat, 23 May 2020 22:02:38 +1000 Subject: [PATCH 055/121] add seeding for np.random.randint --- testsuite/MDAnalysisTests/analysis/test_msd.py | 1 + 1 file changed, 1 insertion(+) diff --git a/testsuite/MDAnalysisTests/analysis/test_msd.py b/testsuite/MDAnalysisTests/analysis/test_msd.py index 75f830107a1..4a96755955a 100644 --- a/testsuite/MDAnalysisTests/analysis/test_msd.py +++ b/testsuite/MDAnalysisTests/analysis/test_msd.py @@ -83,6 +83,7 @@ def step_traj_arr(): # constant velocity return traj def random_walk_3d(): + np.random.seed(1) steps = -1 + 2*np.random.randint(0, 2, size=(NSTEP, 3)) traj = np.cumsum(steps, axis=0) traj_reshape = traj.reshape([NSTEP,1,3]) From 8249a65883b35b0cf3fc3bdfb719bf8f69a38870 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Tue, 26 May 2020 08:40:43 +1000 Subject: [PATCH 056/121] add pytest.mark.parametrize and move global vars to pytest.fixtures --- .../MDAnalysisTests/analysis/test_msd.py | 69 ++++++++++--------- 1 file changed, 38 insertions(+), 31 deletions(-) diff --git a/testsuite/MDAnalysisTests/analysis/test_msd.py b/testsuite/MDAnalysisTests/analysis/test_msd.py index 4a96755955a..b09e23961e9 100644 --- a/testsuite/MDAnalysisTests/analysis/test_msd.py +++ b/testsuite/MDAnalysisTests/analysis/test_msd.py @@ -35,8 +35,15 @@ import pytest import tidynamics -SELECTION = 'backbone and name CA and resid 1-10' -NSTEP = 5000 +@pytest.fixture(scope='module') +def SELECTION(): + selection = 'backbone and name CA and resid 1-10' + return selection + +@pytest.fixture(scope='module') +def NSTEP(): + nstep = 5000 + return nstep #universe @pytest.fixture(scope='module') @@ -50,14 +57,14 @@ def random_walk_u(): #non fft msd @pytest.fixture(scope='module') -def msd(u): +def msd(u, SELECTION): m = MSD(u, SELECTION, msd_type='xyz', fft=False) m.run() return m #fft msd @pytest.fixture(scope='module') -def msd_fft(u): +def msd_fft(u, SELECTION): m = MSD(u, SELECTION, msd_type='xyz', fft=True) m.run() return m @@ -69,7 +76,7 @@ def dimension_list(): return dimensions @pytest.fixture(scope='module') -def step_traj(): # constant velocity +def step_traj(NSTEP): # constant velocity x = np.arange(NSTEP) traj = np.vstack([x,x,x]).T traj_reshape = traj.reshape([NSTEP,1,3]) @@ -77,12 +84,12 @@ def step_traj(): # constant velocity u.load_new(traj_reshape) return u @pytest.fixture(scope='module') -def step_traj_arr(): # constant velocity +def step_traj_arr(NSTEP): # constant velocity x = np.arange(NSTEP) traj = np.vstack([x,x,x]).T return traj -def random_walk_3d(): +def random_walk_3d(NSTEP): np.random.seed(1) steps = -1 + 2*np.random.randint(0, 2, size=(NSTEP, 3)) traj = np.cumsum(steps, axis=0) @@ -113,42 +120,42 @@ def test_fft_vs_simple_default_per_particle(msd, msd_fft): assert_almost_equal(per_particle_simple, per_particle_fft, decimal=4) #check fft and simple give same result for each dimensionality -def test_fft_vs_simple_all_dims(dimension_list, u): - for dim in dimension_list: - m_simple = MSD(u, SELECTION, msd_type=dim, fft=False) - m_simple.run() - timeseries_simple = m_simple.timeseries - m_fft = MSD(u,SELECTION, msd_type=dim, fft=True) - m_fft.run() - timeseries_fft = m_fft.timeseries - assert_almost_equal(timeseries_simple, timeseries_fft, decimal=4) +@pytest.mark.parametrize("dim", ['xyz', 'xy', 'xz', 'yz', 'x', 'y', 'z']) +def test_fft_vs_simple_all_dims(u, SELECTION, dim): + m_simple = MSD(u, SELECTION, msd_type=dim, fft=False) + m_simple.run() + timeseries_simple = m_simple.timeseries + m_fft = MSD(u,SELECTION, msd_type=dim, fft=True) + m_fft.run() + timeseries_fft = m_fft.timeseries + assert_almost_equal(timeseries_simple, timeseries_fft, decimal=4) #check fft and simple give same result for each particle in each dimension -def test_fft_vs_simple_all_dims_per_particle(dimension_list, u): - for dim in dimension_list: - m_simple = MSD(u, SELECTION, msd_type=dim, fft=False) - m_simple.run() - per_particle_simple = m_simple.msds_by_particle - m_fft = MSD(u,SELECTION, msd_type=dim, fft=True) - m_fft.run() - per_particle_fft = m_fft.msds_by_particle - assert_almost_equal(per_particle_simple, per_particle_fft, decimal=4) +@pytest.mark.parametrize("dim", ['xyz', 'xy', 'xz', 'yz', 'x', 'y', 'z']) +def test_fft_vs_simple_all_dims_per_particle(u, SELECTION, dim): + m_simple = MSD(u, SELECTION, msd_type=dim, fft=False) + m_simple.run() + per_particle_simple = m_simple.msds_by_particle + m_fft = MSD(u,SELECTION, msd_type=dim, fft=True) + m_fft.run() + per_particle_fft = m_fft.msds_by_particle + assert_almost_equal(per_particle_simple, per_particle_fft, decimal=4) #testing the "simple" algorithm on constant velocity trajectory -def test_simple_step_traj_3d(step_traj): # this should fit the polynomial y=3x**2 +def test_simple_step_traj_3d(step_traj, NSTEP): # this should fit the polynomial y=3x**2 m_simple = MSD(step_traj, 'all' , msd_type='xyz', fft=False) m_simple.run() poly3 = characteristic_poly(NSTEP,3) assert_almost_equal(m_simple.timeseries, poly3, decimal=4) -def test_simple_step_traj_2d(step_traj): # this should fit the polynomial y=2x**2 +def test_simple_step_traj_2d(step_traj, NSTEP): # this should fit the polynomial y=2x**2 m_simple = MSD(step_traj, 'all' , msd_type='xy', fft=False) m_simple.run() poly2 = characteristic_poly(NSTEP,2) assert_almost_equal(m_simple.timeseries, poly2, decimal=4) -def test_simple_step_traj_1d(step_traj): # this should fit the polynomial y=x**2 +def test_simple_step_traj_1d(step_traj, NSTEP): # this should fit the polynomial y=x**2 m_simple = MSD(step_traj, 'all' , msd_type='x', fft=False) m_simple.run() poly1 = characteristic_poly(NSTEP,1) @@ -157,19 +164,19 @@ def test_simple_step_traj_1d(step_traj): # this should fit the polynomial y=x**2 #fft based tests require a slight decrease in expected prescision due to roundoff in fft(ifft()) calls #relative accuracy expected to be around ~1e-12 -def test_fft_step_traj_3d(step_traj): # this should fit the polynomial y=3x**2 +def test_fft_step_traj_3d(step_traj, NSTEP): # this should fit the polynomial y=3x**2 m_fft = MSD(step_traj, 'all' , msd_type='xyz', fft=True) m_fft.run() poly3 = characteristic_poly(NSTEP,3) assert_almost_equal(m_fft.timeseries, poly3, decimal=3) # this was relaxed from decimal=4 for numpy=1.13 test -def test_fft_step_traj_2d(step_traj): # this should fit the polynomial y=2x**2 +def test_fft_step_traj_2d(step_traj, NSTEP): # this should fit the polynomial y=2x**2 m_fft = MSD(step_traj, 'all' , msd_type='xy', fft=True) m_fft.run() poly2 = characteristic_poly(NSTEP,2) assert_almost_equal(m_fft.timeseries, poly2, decimal=3) # this was relaxed from decimal=4 for numpy=1.13 test -def test_fft_step_traj_1d(step_traj): # this should fit the polynomial y=x**2 +def test_fft_step_traj_1d(step_traj, NSTEP): # this should fit the polynomial y=x**2 m_fft = MSD(step_traj, 'all' , msd_type='x', fft=True) m_fft.run() poly1 = characteristic_poly(NSTEP,1) From 63b8fffd9dcd8aabea7a15e8359721051ead8145 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Tue, 26 May 2020 09:27:31 +1000 Subject: [PATCH 057/121] refactor constant velocity test using pytest.mark.parametrize --- .../MDAnalysisTests/analysis/test_msd.py | 48 +++++-------------- 1 file changed, 12 insertions(+), 36 deletions(-) diff --git a/testsuite/MDAnalysisTests/analysis/test_msd.py b/testsuite/MDAnalysisTests/analysis/test_msd.py index b09e23961e9..49b8ff01649 100644 --- a/testsuite/MDAnalysisTests/analysis/test_msd.py +++ b/testsuite/MDAnalysisTests/analysis/test_msd.py @@ -142,46 +142,22 @@ def test_fft_vs_simple_all_dims_per_particle(u, SELECTION, dim): assert_almost_equal(per_particle_simple, per_particle_fft, decimal=4) #testing the "simple" algorithm on constant velocity trajectory - -def test_simple_step_traj_3d(step_traj, NSTEP): # this should fit the polynomial y=3x**2 - m_simple = MSD(step_traj, 'all' , msd_type='xyz', fft=False) - m_simple.run() - poly3 = characteristic_poly(NSTEP,3) - assert_almost_equal(m_simple.timeseries, poly3, decimal=4) - -def test_simple_step_traj_2d(step_traj, NSTEP): # this should fit the polynomial y=2x**2 - m_simple = MSD(step_traj, 'all' , msd_type='xy', fft=False) +@pytest.mark.parametrize("dim, dim_factor", [('xyz',3), ('xy',2), ('xz',2), ('yz',2), ('x',1), ('y',1), ('z',1)]) +def test_simple_step_traj_all_dims(step_traj, NSTEP, dim, dim_factor): # this should fit the polynomial y=dim_factor*x**2 + m_simple = MSD(step_traj, 'all' , msd_type=dim, fft=False) m_simple.run() - poly2 = characteristic_poly(NSTEP,2) - assert_almost_equal(m_simple.timeseries, poly2, decimal=4) - -def test_simple_step_traj_1d(step_traj, NSTEP): # this should fit the polynomial y=x**2 - m_simple = MSD(step_traj, 'all' , msd_type='x', fft=False) - m_simple.run() - poly1 = characteristic_poly(NSTEP,1) - assert_almost_equal(m_simple.timeseries, poly1,decimal=4) + poly = characteristic_poly(NSTEP,dim_factor) + assert_almost_equal(m_simple.timeseries, poly, decimal=4) +#testing the fft algorithm on constant velocity trajectory #fft based tests require a slight decrease in expected prescision due to roundoff in fft(ifft()) calls #relative accuracy expected to be around ~1e-12 - -def test_fft_step_traj_3d(step_traj, NSTEP): # this should fit the polynomial y=3x**2 - m_fft = MSD(step_traj, 'all' , msd_type='xyz', fft=True) - m_fft.run() - poly3 = characteristic_poly(NSTEP,3) - assert_almost_equal(m_fft.timeseries, poly3, decimal=3) # this was relaxed from decimal=4 for numpy=1.13 test - -def test_fft_step_traj_2d(step_traj, NSTEP): # this should fit the polynomial y=2x**2 - m_fft = MSD(step_traj, 'all' , msd_type='xy', fft=True) - m_fft.run() - poly2 = characteristic_poly(NSTEP,2) - assert_almost_equal(m_fft.timeseries, poly2, decimal=3) # this was relaxed from decimal=4 for numpy=1.13 test - -def test_fft_step_traj_1d(step_traj, NSTEP): # this should fit the polynomial y=x**2 - m_fft = MSD(step_traj, 'all' , msd_type='x', fft=True) - m_fft.run() - poly1 = characteristic_poly(NSTEP,1) - assert_almost_equal(m_fft.timeseries, poly1, decimal=3) # this was relaxed from decimal=4 for numpy=1.13 test - +@pytest.mark.parametrize("dim, dim_factor", [('xyz',3), ('xy',2), ('xz',2), ('yz',2), ('x',1), ('y',1), ('z',1)]) +def test_fft_step_traj_all_dims(step_traj, NSTEP, dim, dim_factor): # this should fit the polynomial y=dim_factor*x**2 + m_simple = MSD(step_traj, 'all' , msd_type=dim, fft=True) + m_simple.run() + poly = characteristic_poly(NSTEP,dim_factor) + assert_almost_equal(m_simple.timeseries, poly, decimal=3) # this was relaxed from decimal=4 for numpy=1.13 test #regress against random_walk test data def test_random_walk_u_simple(random_walk_u): From 0fc07f730f3683913bf397e8355dce72232e7b43 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Tue, 26 May 2020 09:44:23 +1000 Subject: [PATCH 058/121] add tidynamics versioning --- .appveyor.yml | 2 +- .travis.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index c18aee87b50..dfb227c372d 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -10,7 +10,7 @@ cache: environment: global: CONDA_CHANNELS: conda-forge - CONDA_DEPENDENCIES: pip setuptools wheel cython mock six biopython networkx joblib matplotlib scipy vs2015_runtime pytest mmtf-python GridDataFormats hypothesis pytest-cov codecov chemfiles tqdm tidynamics + CONDA_DEPENDENCIES: pip setuptools wheel cython mock six biopython networkx joblib matplotlib scipy vs2015_runtime pytest mmtf-python GridDataFormats hypothesis pytest-cov codecov chemfiles tqdm tidynamics>=1.0.0 PIP_DEPENDENCIES: gsd==1.9.3 duecredit parmed DEBUG: "False" MINGW_64: C:\mingw-w64\x86_64-6.3.0-posix-seh-rt_v5-rev1\mingw64\bin diff --git a/.travis.yml b/.travis.yml index fb08204f9f8..eeaf23b82b3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,7 +27,7 @@ env: - MAIN_CMD="pytest ${PYTEST_LIST}" - SETUP_CMD="${PYTEST_FLAGS}" - BUILD_CMD="pip install -e package/ && (cd testsuite/ && python setup.py build)" - - CONDA_MIN_DEPENDENCIES="mmtf-python mock six biopython networkx cython matplotlib scipy griddataformats hypothesis gsd codecov tidynamics" + - CONDA_MIN_DEPENDENCIES="mmtf-python mock six biopython networkx cython matplotlib scipy griddataformats hypothesis gsd codecov tidynamics>=1.0.0" - CONDA_DEPENDENCIES="${CONDA_MIN_DEPENDENCIES} seaborn>=0.7.0 clustalw=2.1 netcdf4 scikit-learn joblib>=0.12 chemfiles tqdm>=4.43.0" - CONDA_CHANNELS='biobuilds conda-forge' - CONDA_CHANNEL_PRIORITY=True From 0d69b6591ee92be2f9d980fd158cbd50b5829af6 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Tue, 26 May 2020 09:55:25 +1000 Subject: [PATCH 059/121] move tidynamics to out of CONDA_MIN_DEPENDENCIES --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index eeaf23b82b3..cd743bfa8e4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,8 +27,8 @@ env: - MAIN_CMD="pytest ${PYTEST_LIST}" - SETUP_CMD="${PYTEST_FLAGS}" - BUILD_CMD="pip install -e package/ && (cd testsuite/ && python setup.py build)" - - CONDA_MIN_DEPENDENCIES="mmtf-python mock six biopython networkx cython matplotlib scipy griddataformats hypothesis gsd codecov tidynamics>=1.0.0" - - CONDA_DEPENDENCIES="${CONDA_MIN_DEPENDENCIES} seaborn>=0.7.0 clustalw=2.1 netcdf4 scikit-learn joblib>=0.12 chemfiles tqdm>=4.43.0" + - CONDA_MIN_DEPENDENCIES="mmtf-python mock six biopython networkx cython matplotlib scipy griddataformats hypothesis gsd codecov" + - CONDA_DEPENDENCIES="${CONDA_MIN_DEPENDENCIES} seaborn>=0.7.0 clustalw=2.1 netcdf4 scikit-learn joblib>=0.12 chemfiles tqdm>=4.43.0 tidynamics>=1.0.0" - CONDA_CHANNELS='biobuilds conda-forge' - CONDA_CHANNEL_PRIORITY=True - PIP_DEPENDENCIES="duecredit parmed" From 98d4b48a6b7e602fc989d8555691d93a9e20387a Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Mon, 1 Jun 2020 21:46:25 +1000 Subject: [PATCH 060/121] Update package/MDAnalysis/analysis/msd.py Co-authored-by: Oliver Beckstein --- package/MDAnalysis/analysis/msd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 3dd3f80897f..8c6eed28382 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -33,7 +33,7 @@ This module implements the calculation of Mean Squared Displacmements (MSDs) by the Einstein relation. MSDs can be used to characterize the speed at which particles move and has its roots in the study of Brownian motion. For a full explanation of the theory behind MSDs and the subsequent calculation of self-diffusivities the reader is directed to [Maginn2019]_. -MSDs can be computed from the following expression, known as the "Einstein" formula: +MSDs can be computed from the following expression, known as the _Einstein formula_: .. math:: From 7d530eb4d8c81c2cdb4b16ddea03e2d0db6b78e8 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Mon, 1 Jun 2020 21:46:54 +1000 Subject: [PATCH 061/121] change docs markup Co-authored-by: Oliver Beckstein --- package/MDAnalysis/analysis/msd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 8c6eed28382..06c456a7e9a 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -45,7 +45,7 @@ where :math:`\tau_{max}` is the length of the trajectory, thereby maximizing the number of samples. The computation of the MSD in this way can be computationally intensive due to it's :math:`N^2` scaling with respect to :math:`\tau_{max}`. -An algorithm to compute the MSD with :math:`N log(N)` scaling based on a Fast Fourier Transform is known and can be accessed by setting fft=True [Calandri2011]_ [Buyl2018]_. +An algorithm to compute the MSD with :math:`N log(N)` scaling based on a Fast Fourier Transform is known and can be accessed by setting ``fft=True`` [Calandri2011]_ [Buyl2018]_. Please cite [Calandri2011]_ [Buyl2018]_ if you use this module in addition to the normal MDAnalysis citations. From 2c760eb3deece82e7232af1e225966ef306c39a0 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Mon, 1 Jun 2020 21:48:04 +1000 Subject: [PATCH 062/121] spelling Co-authored-by: Lily Wang <31115101+lilyminium@users.noreply.github.com> --- package/MDAnalysis/analysis/msd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 06c456a7e9a..7a8c459bcce 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -30,7 +30,7 @@ :Year: 2020 :Copyright: GNU Public License v2 -This module implements the calculation of Mean Squared Displacmements (MSDs) by the Einstein relation. +This module implements the calculation of Mean Squared Displacements (MSDs) by the Einstein relation. MSDs can be used to characterize the speed at which particles move and has its roots in the study of Brownian motion. For a full explanation of the theory behind MSDs and the subsequent calculation of self-diffusivities the reader is directed to [Maginn2019]_. MSDs can be computed from the following expression, known as the _Einstein formula_: From a7136c07f2821da1072c66c501db2dfa8b6a615e Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Mon, 1 Jun 2020 21:48:23 +1000 Subject: [PATCH 063/121] spelling Co-authored-by: Lily Wang <31115101+lilyminium@users.noreply.github.com> --- package/MDAnalysis/analysis/msd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 7a8c459bcce..bea11b994dc 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -44,7 +44,7 @@ that vary between implementations. In this module, we compute a "windowed" MSD, where the MSD is averaged over all possible lag times :math:`\tau \le \tau_{max}`, where :math:`\tau_{max}` is the length of the trajectory, thereby maximizing the number of samples. -The computation of the MSD in this way can be computationally intensive due to it's :math:`N^2` scaling with respect to :math:`\tau_{max}`. +The computation of the MSD in this way can be computationally intensive due to its :math:`N^2` scaling with respect to :math:`\tau_{max}`. An algorithm to compute the MSD with :math:`N log(N)` scaling based on a Fast Fourier Transform is known and can be accessed by setting ``fft=True`` [Calandri2011]_ [Buyl2018]_. Please cite [Calandri2011]_ [Buyl2018]_ if you use this module in addition to the normal MDAnalysis citations. From ef6b6f48e26e5388448ed86f43c1e68a6ea34c2d Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Mon, 1 Jun 2020 21:48:46 +1000 Subject: [PATCH 064/121] caps Co-authored-by: Lily Wang <31115101+lilyminium@users.noreply.github.com> --- package/MDAnalysis/analysis/msd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index bea11b994dc..0fddbbc3509 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -63,7 +63,7 @@ >>> from MDAnalysis.tests.datafiles import RANDOM_WALK, RANDOM_WALK_TOPO Given a universe containing trajectory data we can extract the MSD -Analyis by using the class :class:`EinsteinMSD` +analysis by using the class :class:`EinsteinMSD` >>> u = mda.Universe(RANDOM_WALK, RANDOM_WALK_TOPO) >>> MSD = msd.EinsteinMSD(u, 'all', msd_type='xyz', fft=True) From f4aeaea63834f2f7b27833416a9474c0123a9a95 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Mon, 1 Jun 2020 21:49:01 +1000 Subject: [PATCH 065/121] spelling Co-authored-by: Lily Wang <31115101+lilyminium@users.noreply.github.com> --- package/MDAnalysis/analysis/msd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 0fddbbc3509..9897ceaac5e 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -73,7 +73,7 @@ >>> msd = MSD.timeseries -Visual inspection of the MSD is important, so lets take a look at it with a simple plot. +Visual inspection of the MSD is important, so let's take a look at it with a simple plot. >>> import matplotlib.pyplot as plt >>> nframes = MSD.n_frames From 121ac170915af819699e6968d3a5bb9ca7a2c939 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Mon, 1 Jun 2020 21:49:40 +1000 Subject: [PATCH 066/121] spelling Co-authored-by: Lily Wang <31115101+lilyminium@users.noreply.github.com> --- package/MDAnalysis/analysis/msd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 9897ceaac5e..3fe06bc3ff0 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -86,7 +86,7 @@ >>> ax.plot(lagtimes, exact, color="black", linestyle="--", label=r'$y=2 D\tau$') # plot the exact result >>> plt.show() -Which gives us the following plot of the MSD with respect to lag-time (:math:`\tau`). +This gives us the plot of the MSD with respect to lag-time (:math:`\tau`). We can see that the MSD is approximately linear with respect to :math:`\tau`. This is a numerical example of a known theoretical result that the MSD of a random walk is linear with respect to lagtime, with a slope of :math:`2d`. In this expression :math:`d` is the dimensionality of the MSD which for our 3D MSD is equal to 3. From 09bedac793364bf7ce4f93b20dfece643d50c264 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Mon, 1 Jun 2020 21:50:07 +1000 Subject: [PATCH 067/121] phrasing Co-authored-by: Lily Wang <31115101+lilyminium@users.noreply.github.com> --- package/MDAnalysis/analysis/msd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 3fe06bc3ff0..039cad7584f 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -89,7 +89,7 @@ This gives us the plot of the MSD with respect to lag-time (:math:`\tau`). We can see that the MSD is approximately linear with respect to :math:`\tau`. This is a numerical example of a known theoretical result that the MSD of a random walk is linear with respect to lagtime, with a slope of :math:`2d`. -In this expression :math:`d` is the dimensionality of the MSD which for our 3D MSD is equal to 3. +In this expression :math:`d` is the dimensionality of the MSD. For our 3D MSD, this is 3. For comparison we have plotted the line :math:`y=6\tau` to which an ensemble of 3D random walks should converge. .. _figure-msd: From e070fb831641e06c5a5342d432f9cc6468fba381 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Mon, 1 Jun 2020 21:50:29 +1000 Subject: [PATCH 068/121] spelling Co-authored-by: Lily Wang <31115101+lilyminium@users.noreply.github.com> --- package/MDAnalysis/analysis/msd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 039cad7584f..f1c8fd424b7 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -105,7 +105,7 @@ >>> plt.loglog(lagtimes, msd) >>> plt.show() -Now that we have identified what segment of our MSD to analyse, lets compute a self-diffusivity. +Now that we have identified what segment of our MSD to analyse, let's compute a self-diffusivity. Computing Self-Diffusivity -------------------------------- From d8c81cdbad6b95f51af3f2ad6929ca66a1d16753 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Mon, 1 Jun 2020 21:50:51 +1000 Subject: [PATCH 069/121] spelling Co-authored-by: Lily Wang <31115101+lilyminium@users.noreply.github.com> --- package/MDAnalysis/analysis/msd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index f1c8fd424b7..4d424176100 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -116,7 +116,7 @@ D_d = \frac{1}{2d} \lim_{t \to \infty} \frac{d}{dt} MSD(r_{d}) From the MSD, self-diffusivities :math:`D` with the desired dimensionality :math:`d` can be computed by fitting the MSD with respect to the lag time to a linear model. -An example of this is shown below, using the MSD computed in the example above. The segment between :math:`\tau = 20` and :math:`\tau = 60` is used to demonstrate selection of an MSD segment. +An example of this is shown below, using the MSD computed in the example above. The segment between :math:`\tau = 20` and :math:`\tau = 60` is used to demonstrate selection of a MSD segment. >>> from scipy.stats import linregress as lr >>> start_time = 20 From 1ac4086b3757bc85d901f0a3a4bb72edb7b44caf Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Mon, 1 Jun 2020 21:51:38 +1000 Subject: [PATCH 070/121] spelling Co-authored-by: Lily Wang <31115101+lilyminium@users.noreply.github.com> --- package/MDAnalysis/analysis/msd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 4d424176100..450678639d1 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -135,7 +135,7 @@ _____ There are several factors that must be taken into account when setting up and processing trajectories for computation of self-diffusivities. -These include specific instructions around simulation settings, using unwrapped trajectories and maintaining relatively small elapsed time between saved frames. +These include specific instructions around simulation settings, using unwrapped trajectories and maintaining a relatively small elapsed time between saved frames. Additionally corrections for finite size effects are sometimes employed along with varied means of estimating errors. The reader is directed to the following review, which describes many of the common pitfalls [Maginn2019]_. There are other ways to compute self-diffusivity including from a Green-Kubo integral. At this point in time these methods are beyond the scope of this module From 7a58d7316f44bf2065cb8f9b271e68bde24bdc96 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Mon, 1 Jun 2020 21:52:08 +1000 Subject: [PATCH 071/121] spelling Co-authored-by: Lily Wang <31115101+lilyminium@users.noreply.github.com> --- package/MDAnalysis/analysis/msd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 450678639d1..919c281afc0 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -136,7 +136,7 @@ There are several factors that must be taken into account when setting up and processing trajectories for computation of self-diffusivities. These include specific instructions around simulation settings, using unwrapped trajectories and maintaining a relatively small elapsed time between saved frames. -Additionally corrections for finite size effects are sometimes employed along with varied means of estimating errors. +Additionally, corrections for finite size effects are sometimes employed along with various means of estimating errors. The reader is directed to the following review, which describes many of the common pitfalls [Maginn2019]_. There are other ways to compute self-diffusivity including from a Green-Kubo integral. At this point in time these methods are beyond the scope of this module From c3ae32b73355cc72b5b255049250c3d6fed93138 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Mon, 1 Jun 2020 21:52:55 +1000 Subject: [PATCH 072/121] markup Co-authored-by: Lily Wang <31115101+lilyminium@users.noreply.github.com> --- package/MDAnalysis/analysis/msd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 919c281afc0..09e52e0c091 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -202,7 +202,7 @@ def __init__(self, u, select=None, msd_type='xyz', fft=True, **kwargs): msd_type : {'xyz', 'xy', 'yz', 'xz', 'x', 'y', 'z'} Desired dimensions to be included in the MSD. Defaults to 'xyz'. fft : bool - Use a fast FFT based algorithm for computation of the MSD. + If ``True``, uses a fast FFT based algorithm for computation of the MSD. Otherwise, use the simple "windowed" algorithm. Defaults to ``True``. """ From 7764f9e7b632cc0797df462c936274b06e2f2c7c Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Mon, 1 Jun 2020 21:53:45 +1000 Subject: [PATCH 073/121] spelling Co-authored-by: Lily Wang <31115101+lilyminium@users.noreply.github.com> --- package/MDAnalysis/analysis/msd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 09e52e0c091..70cd9c9eafa 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -137,7 +137,7 @@ There are several factors that must be taken into account when setting up and processing trajectories for computation of self-diffusivities. These include specific instructions around simulation settings, using unwrapped trajectories and maintaining a relatively small elapsed time between saved frames. Additionally, corrections for finite size effects are sometimes employed along with various means of estimating errors. -The reader is directed to the following review, which describes many of the common pitfalls [Maginn2019]_. There are other ways to compute self-diffusivity including from a Green-Kubo integral. At this point in time these methods are beyond the scope of this module +The reader is directed to the following review, which describes many of the common pitfalls [Maginn2019]_. There are other ways to compute self-diffusivity, such as from a Green-Kubo integral. At this point in time, these methods are beyond the scope of this module. References From 9005ad53b793f359fef21d31a978f0f3ac6b02db Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Mon, 1 Jun 2020 21:55:04 +1000 Subject: [PATCH 074/121] change dimensions of base array Co-authored-by: Lily Wang <31115101+lilyminium@users.noreply.github.com> --- package/MDAnalysis/analysis/msd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 70cd9c9eafa..b691267529c 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -234,7 +234,7 @@ def _prepare(self): self._atoms = self.u.select_atoms(self.select) self._n_frames = len(self.u.trajectory) self._n_particles = len(self._atoms) - self._position_array = np.zeros((self._n_frames, self._n_particles, 3)) + self._position_array = np.zeros((self.n_frames, self._n_particles, self.dim_fac)) self.msds_by_particle = np.zeros((self._n_frames, self._n_particles)) # self.timeseries not set here From e898226f03c40446f649a0407659183c51894303 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Mon, 1 Jun 2020 21:56:03 +1000 Subject: [PATCH 075/121] keep dim of _position_array consistent Co-authored-by: Lily Wang <31115101+lilyminium@users.noreply.github.com> --- package/MDAnalysis/analysis/msd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index b691267529c..9e09054382b 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -279,7 +279,7 @@ def _single_frame(self): Array of particle positions with respect to time shape = (n_frames, n_particles, 3) """ - self._position_array[self._frame_index,:,:] = self._atoms.positions + self._position_array[self._frame_index] = self._atoms.positions[:, self._dim] def _conclude(self): if self.fft == True: From 3f5f5f3dcdf310e0d363215783270230ffa079a3 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Tue, 2 Jun 2020 09:47:16 +1000 Subject: [PATCH 076/121] pep8 lint --- package/MDAnalysis/analysis/msd.py | 64 +++++++++++++++++------------- 1 file changed, 37 insertions(+), 27 deletions(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 9e09054382b..b1de3b99239 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -173,9 +173,10 @@ cite_module=True) del Doi + class EinsteinMSD(AnalysisBase): r"""Class to calculate Mean Squared Displacement by the Einstein relation. - + Attributes ---------- dim_fac : int @@ -207,24 +208,24 @@ def __init__(self, u, select=None, msd_type='xyz', fft=True, **kwargs): """ self.u = u - + super(EinsteinMSD, self).__init__(self.u.trajectory, **kwargs) - #args + # args self.select = select self.msd_type = msd_type self.fft = fft - - #local + + # local self._dim = None self._position_array = None self._atoms = None - - #indexing + + # indexing self._n_frames = 0 self._n_particles = 0 - #result + # result self.dim_fac = 0 self.timeseries = None self.msds_by_particle = None @@ -234,13 +235,14 @@ def _prepare(self): self._atoms = self.u.select_atoms(self.select) self._n_frames = len(self.u.trajectory) self._n_particles = len(self._atoms) - self._position_array = np.zeros((self.n_frames, self._n_particles, self.dim_fac)) + self._position_array = np.zeros( + (self.n_frames, self._n_particles, self.dim_fac)) self.msds_by_particle = np.zeros((self._n_frames, self._n_particles)) # self.timeseries not set here - + def _parse_msd_type(self): r""" Sets up the desired dimensionality of the MSD. - + Parameters ---------- self.msd_type : {'xyz', 'xy', 'yz', 'xz', 'x', 'y', 'z'} @@ -254,25 +256,27 @@ def _parse_msd_type(self): Dimension factor :math:`d` of the MSD. """ - keys = {'x':[0], 'y':[1], 'z':[2], 'xy':[0,1], 'xz':[0,2], 'yz':[1,2], 'xyz':[0,1,2]} + keys = {'x': [0], 'y': [1], 'z': [2], 'xy': [0, 1], + 'xz': [0, 2], 'yz': [1, 2], 'xyz': [0, 1, 2]} self.msd_type = self.msd_type.lower() try: self._dim = keys[self.msd_type] except KeyError: - raise ValueError('invalid msd_type: {} specified, please specify one of xyz, xy, xz, yz, x, y, z'.format(self.msd_type)) + raise ValueError( + 'invalid msd_type: {} specified, please specify one of xyz, xy, xz, yz, x, y, z'.format(self.msd_type)) self.dim_fac = len(self._dim) def _single_frame(self): r""" Constructs array of positions for MSD calculation. - + Parameters ---------- self.u : :class:`Universe` MDAnalysis Universe - + Returns ------- self._position_array : :class:`np.ndarray` @@ -280,16 +284,16 @@ def _single_frame(self): """ self._position_array[self._frame_index] = self._atoms.positions[:, self._dim] - + def _conclude(self): if self.fft == True: self.timeseries = self._conclude_fft() else: self.timeseries = self._conclude_simple() - def _conclude_simple(self): + def _conclude_simple(self): r""" Calculates the MSD via the simple "windowed" algorithm. - + Parameters ---------- self._position_array : :class:`np.ndarray` @@ -301,18 +305,22 @@ def _conclude_simple(self): The MSD as a function of lag-time. """ - lagtimes = np.arange(1,self.n_frames) - self.msds_by_particle[0,:] = np.zeros(self._n_particles) # preset the zero lagtime so we don't have to iterate through + lagtimes = np.arange(1, self.n_frames) + # preset the zero lagtime so we don't have to iterate through + self.msds_by_particle[0, :] = np.zeros(self._n_particles) for lag in lagtimes: - disp = self._position_array[:-lag,:,self._dim] - self._position_array[lag:,:,self._dim] - sqdist = np.square(disp, dtype=np.float64).sum(axis=-1, dtype=np.float64) #accumulation in np.float64 required - self.msds_by_particle[lag,:] = np.mean(sqdist, axis=0, dtype=np.float64) + disp = self._position_array[:-lag, :, self._dim] - \ + self._position_array[lag:, :, self._dim] + sqdist = np.square(disp, dtype=np.float64).sum( + axis=-1, dtype=np.float64) # accumulation in np.float64 required + self.msds_by_particle[lag, :] = np.mean( + sqdist, axis=0, dtype=np.float64) msd = self.msds_by_particle.mean(axis=1, dtype=np.float64) return msd - def _conclude_fft(self): #with FFT, np.float64 bit prescision required. + def _conclude_fft(self): # with FFT, np.float64 bit prescision required. r""" Calculates the MSD via the FCA fast correlation algorithm. - + Parameters ---------- self._position_array : :class:`np.ndarray` @@ -324,8 +332,10 @@ def _conclude_fft(self): #with FFT, np.float64 bit prescision required. The MSD as a function of lagtime. """ - reshape_positions = self._position_array[:,:,self._dim].astype(np.float64) + reshape_positions = self._position_array[:, :, self._dim].astype( + np.float64) for n in range(self._n_particles): - self.msds_by_particle[:,n] = tidynamics.msd(reshape_positions[:,n,:]) + self.msds_by_particle[:, n] = tidynamics.msd( + reshape_positions[:, n, :]) msd = self.msds_by_particle.mean(axis=1, dtype=np.float64) return msd From e4e46baecf2ff422be97a9eceec2c903b6569f58 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Tue, 2 Jun 2020 11:31:53 +1000 Subject: [PATCH 077/121] fix PEP8 compliance for line length --- package/MDAnalysis/analysis/msd.py | 132 +++++++++++++++++++---------- 1 file changed, 89 insertions(+), 43 deletions(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index b1de3b99239..a8c5484f106 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -30,29 +30,41 @@ :Year: 2020 :Copyright: GNU Public License v2 -This module implements the calculation of Mean Squared Displacements (MSDs) by the Einstein relation. -MSDs can be used to characterize the speed at which particles move and has its roots -in the study of Brownian motion. For a full explanation of the theory behind MSDs and the subsequent calculation of self-diffusivities the reader is directed to [Maginn2019]_. -MSDs can be computed from the following expression, known as the _Einstein formula_: +This module implements the calculation of Mean Squared Displacements (MSDs) +by the Einstein relation. MSDs can be used to characterize the speed at +which particles move and has its roots in the study of Brownian motion. +For a full explanation of the theory behind MSDs and the subsequent +calculation of self-diffusivities the reader is directed to [Maginn2019]_. +MSDs can be computed from the following expression, known as the +_Einstein formula_: .. math:: - MSD(r_{d}) = \bigg{\langle} \frac{1}{N} \sum_{i=1}^{N} |r_{d} - r_{d}(t_0)|^2 \bigg{\rangle}_{t_{0}} + MSD(r_{d}) = \bigg{\langle} \frac{1}{N} \sum_{i=1}^{N} |r_{d} + - r_{d}(t_0)|^2 \bigg{\rangle}_{t_{0}} -Where :math:`N` is the number of equivalent particles the MSD is calculated over, :math:`r` are their coordinates and :math:`d` the desired -dimensionality of the MSD. Note that while the definition of the MSD is universal, there are many practical considerations to computing the MSD -that vary between implementations. In this module, we compute a "windowed" MSD, where the MSD is averaged over all possible lag times :math:`\tau \le \tau_{max}`, -where :math:`\tau_{max}` is the length of the trajectory, thereby maximizing the number of samples. +Where :math:`N` is the number of equivalent particles the MSD is calculated +over, :math:`r` are their coordinates and :math:`d` the desired dimensionality +of the MSD. Note that while the definition of the MSD is universal, there are +many practical considerations to computing the MSD that vary between +implementations. In this module, we compute a "windowed" MSD, where the MSD +is averaged over all possible lag times :math:`\tau \le \tau_{max}`, +where :math:`\tau_{max}` is the length of the trajectory, thereby maximizing +the number of samples. -The computation of the MSD in this way can be computationally intensive due to its :math:`N^2` scaling with respect to :math:`\tau_{max}`. -An algorithm to compute the MSD with :math:`N log(N)` scaling based on a Fast Fourier Transform is known and can be accessed by setting ``fft=True`` [Calandri2011]_ [Buyl2018]_. +The computation of the MSD in this way can be computationally intensive due to +its :math:`N^2` scaling with respect to :math:`\tau_{max}`. An algorithm to +compute the MSD with :math:`N log(N)` scaling based on a Fast Fourier +Transform is known and can be accessed by setting ``fft=True`` [Calandri2011]_ +[Buyl2018]_. -Please cite [Calandri2011]_ [Buyl2018]_ if you use this module in addition to the normal MDAnalysis citations. +Please cite [Calandri2011]_ [Buyl2018]_ if you use this module in addition to +the normal MDAnalysis citations. Computing an MSD ---------------- -This example computes a 3D MSD for the movement of 100 particles undergoing a random walk. -Files provided as part of the MDAnalysis test suite are used +This example computes a 3D MSD for the movement of 100 particles undergoing a +random walk. Files provided as part of the MDAnalysis test suite are used (in the variables :data:`~MDAnalysis.tests.datafiles.RANDOM_WALK` and :data:`~MDAnalysis.tests.datafiles.RANDOM_WALK_TOPO`) @@ -73,24 +85,30 @@ >>> msd = MSD.timeseries -Visual inspection of the MSD is important, so let's take a look at it with a simple plot. +Visual inspection of the MSD is important, so let's take a look at it with a + simple plot. >>> import matplotlib.pyplot as plt >>> nframes = MSD.n_frames - >>> timestep = 1 # this needs to be the actual time between frames in your trajectory + >>> timestep = 1 # this needs to be the actual time between frames in your + trajectory >>> lagtimes = np.arange(nframes)*timestep # make the lag time axis >>> fig = plt.figure() >>> ax = plt.axes() - >>> ax.plot(lagtimes, msd, color="black", linestyle="-", label=r'3D random walk') # plot the actual MSD + >>> ax.plot(lagtimes, msd, color="black", linestyle="-", + label=r'3D random walk') # plot the actual MSD >>> exact = lagtimes*6 - >>> ax.plot(lagtimes, exact, color="black", linestyle="--", label=r'$y=2 D\tau$') # plot the exact result + >>> ax.plot(lagtimes, exact, color="black", linestyle="--", + label=r'$y=2 D\tau$') # plot the exact result >>> plt.show() This gives us the plot of the MSD with respect to lag-time (:math:`\tau`). We can see that the MSD is approximately linear with respect to :math:`\tau`. -This is a numerical example of a known theoretical result that the MSD of a random walk is linear with respect to lagtime, with a slope of :math:`2d`. -In this expression :math:`d` is the dimensionality of the MSD. For our 3D MSD, this is 3. -For comparison we have plotted the line :math:`y=6\tau` to which an ensemble of 3D random walks should converge. +This is a numerical example of a known theoretical result that the MSD of a +random walk is linear with respect to lagtime, with a slope of :math:`2d`. +In this expression :math:`d` is the dimensionality of the MSD. For our 3D MSD, +this is 3. For comparison we have plotted the line :math:`y=6\tau` to which an +ensemble of 3D random walks should converge. .. _figure-msd: @@ -98,14 +116,20 @@ :scale: 100 % :alt: MSD plot -Note that a segment of the MSD is required to be linear to accurately determine self-diffusivity. -This linear segment represents the so called "middle" of the MSD plot, where ballistic trajectories at short time-lags are excluded along with poorly averaged data at long time-lags. -We can select the "middle" of the MSD by indexing the MSD and the time-lags. Appropriately linear segments of the MSD can be confirmed with a log-log plot as is often reccomended [Maginn2019]_ where the "middle" segment can be identified as having a slope of 1. +Note that a segment of the MSD is required to be linear to accurately +determine self-diffusivity. This linear segment represents the so called +"middle" of the MSD plot, where ballistic trajectories at short time-lags are +excluded along with poorly averaged data at long time-lags. We can select the +"middle" of the MSD by indexing the MSD and the time-lags. Appropriately +linear segments of the MSD can be confirmed with a log-log plot as is often +reccomended [Maginn2019]_ where the "middle" segment can be identified as +having a slope of 1. >>> plt.loglog(lagtimes, msd) >>> plt.show() -Now that we have identified what segment of our MSD to analyse, let's compute a self-diffusivity. +Now that we have identified what segment of our MSD to analyse, let's compute +a self-diffusivity. Computing Self-Diffusivity -------------------------------- @@ -115,18 +139,23 @@ D_d = \frac{1}{2d} \lim_{t \to \infty} \frac{d}{dt} MSD(r_{d}) -From the MSD, self-diffusivities :math:`D` with the desired dimensionality :math:`d` can be computed by fitting the MSD with respect to the lag time to a linear model. -An example of this is shown below, using the MSD computed in the example above. The segment between :math:`\tau = 20` and :math:`\tau = 60` is used to demonstrate selection of a MSD segment. +From the MSD, self-diffusivities :math:`D` with the desired dimensionality +:math:`d` can be computed by fitting the MSD with respect to the lag time to +a linear model. An example of this is shown below, using the MSD computed in +the example above. The segment between :math:`\tau = 20` and :math:`\tau = 60` +is used to demonstrate selection of a MSD segment. >>> from scipy.stats import linregress as lr >>> start_time = 20 >>> start_index = int(start_time/timestep) >>> end_time = 60 >>> end_index = int(end_time/timestep) - >>> linear_model = lr(lagtimes[start_index:end_index], msd[start_index:end_index]) + >>> linear_model = lr(lagtimes[start_index:end_index], + msd[start_index:end_index]) >>> slope = linear_model.slope >>> error = linear_model.rvalue - >>> D = slope * 1/(2*MSD.dim_fac) #dim_fac is 3 as we computed a 3D msd ('xyz') + >>> D = slope * 1/(2*MSD.dim_fac) + #dim_fac is 3 as we computed a 3D msd ('xyz') We have now computed a self-diffusivity! @@ -134,16 +163,25 @@ Notes _____ -There are several factors that must be taken into account when setting up and processing trajectories for computation of self-diffusivities. -These include specific instructions around simulation settings, using unwrapped trajectories and maintaining a relatively small elapsed time between saved frames. -Additionally, corrections for finite size effects are sometimes employed along with various means of estimating errors. -The reader is directed to the following review, which describes many of the common pitfalls [Maginn2019]_. There are other ways to compute self-diffusivity, such as from a Green-Kubo integral. At this point in time, these methods are beyond the scope of this module. +There are several factors that must be taken into account when setting up and +processing trajectories for computation of self-diffusivities. +These include specific instructions around simulation settings, using +unwrapped trajectories and maintaining a relatively small elapsed time between +saved frames. Additionally, corrections for finite size effects are sometimes +employed along with various means of estimating errors. The reader is directed +to the following review, which describes many of the common pitfalls +[Maginn2019]_. There are other ways to compute self-diffusivity, such as from +a Green-Kubo integral. At this point in time, these methods are beyond the +scope of this module. References ---------- -.. [Maginn2019] Maginn, E. J., Messerly, R. A., Carlson, D. J.; Roe, D. R., Elliott, J. R. Best Practices for Computing Transport Properties 1. Self-Diffusivity and Viscosity from Equilibrium Molecular Dynamics [Article v1.0]. Living J. Comput. Mol. Sci. 2019, 1 (1). +.. [Maginn2019] Maginn, E. J., Messerly, R. A., Carlson, D. J.; Roe, D. +R., Elliott, J. R. Best Practices for Computing Transport Properties 1. +Self-Diffusivity and Viscosity from Equilibrium Molecular Dynamics +[Article v1.0]. Living J. Comput. Mol. Sci. 2019, 1 (1). @@ -203,8 +241,10 @@ def __init__(self, u, select=None, msd_type='xyz', fft=True, **kwargs): msd_type : {'xyz', 'xy', 'yz', 'xz', 'x', 'y', 'z'} Desired dimensions to be included in the MSD. Defaults to 'xyz'. fft : bool - If ``True``, uses a fast FFT based algorithm for computation of the MSD. - Otherwise, use the simple "windowed" algorithm. Defaults to ``True``. + If ``True``, uses a fast FFT based algorithm for computation of + the MSD. Otherwise, use the simple "windowed" algorithm. + Defaults to ``True``. + """ self.u = u @@ -251,7 +291,8 @@ def _parse_msd_type(self): Returns ------- self._dim : list - Array-like used to slice the positions to obtain desired dimensionality + Array-like used to slice the positions to obtain desired + dimensionality self.dim_fac : int Dimension factor :math:`d` of the MSD. @@ -265,7 +306,8 @@ def _parse_msd_type(self): self._dim = keys[self.msd_type] except KeyError: raise ValueError( - 'invalid msd_type: {} specified, please specify one of xyz, xy, xz, yz, x, y, z'.format(self.msd_type)) + 'invalid msd_type: {} specified, please specify one of xyz, \ + xy, xz, yz, x, y, z'.format(self.msd_type)) self.dim_fac = len(self._dim) @@ -280,10 +322,12 @@ def _single_frame(self): Returns ------- self._position_array : :class:`np.ndarray` - Array of particle positions with respect to time shape = (n_frames, n_particles, 3) + Array of particle positions with respect to time + shape = (n_frames, n_particles, 3) """ - self._position_array[self._frame_index] = self._atoms.positions[:, self._dim] + self._position_array[self._frame_index] = + self._atoms.positions[:, self._dim] def _conclude(self): if self.fft == True: @@ -297,7 +341,8 @@ def _conclude_simple(self): Parameters ---------- self._position_array : :class:`np.ndarray` - Array of particle positions with respect to time shape (n_frames, n_particles, 3). + Array of particle positions with respect to time + shape (n_frames, n_particles, 3). Returns ------- @@ -312,7 +357,7 @@ def _conclude_simple(self): disp = self._position_array[:-lag, :, self._dim] - \ self._position_array[lag:, :, self._dim] sqdist = np.square(disp, dtype=np.float64).sum( - axis=-1, dtype=np.float64) # accumulation in np.float64 required + axis=-1, dtype=np.float64) #np.float64 required self.msds_by_particle[lag, :] = np.mean( sqdist, axis=0, dtype=np.float64) msd = self.msds_by_particle.mean(axis=1, dtype=np.float64) @@ -324,7 +369,8 @@ def _conclude_fft(self): # with FFT, np.float64 bit prescision required. Parameters ---------- self._position_array : :class:`np.ndarray` - Array of particle positions with respect to time shape (n_frames, n_particles, 3). + Array of particle positions with respect to time + shape (n_frames, n_particles, 3). Returns ------- From d09f79c9df828f7c285abbd7d05dd09b66a54410 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Wed, 3 Jun 2020 10:38:02 +1000 Subject: [PATCH 078/121] update docs for PEP8 compliance --- package/MDAnalysis/analysis/msd.py | 94 +++++++++++++++++------------- 1 file changed, 52 insertions(+), 42 deletions(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index a8c5484f106..cacab367a06 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -36,14 +36,14 @@ For a full explanation of the theory behind MSDs and the subsequent calculation of self-diffusivities the reader is directed to [Maginn2019]_. MSDs can be computed from the following expression, known as the -_Einstein formula_: +_Einstein_ _formula_: .. math:: MSD(r_{d}) = \bigg{\langle} \frac{1}{N} \sum_{i=1}^{N} |r_{d} - r_{d}(t_0)|^2 \bigg{\rangle}_{t_{0}} -Where :math:`N` is the number of equivalent particles the MSD is calculated +where :math:`N` is the number of equivalent particles the MSD is calculated over, :math:`r` are their coordinates and :math:`d` the desired dimensionality of the MSD. Note that while the definition of the MSD is universal, there are many practical considerations to computing the MSD that vary between @@ -70,37 +70,44 @@ First load all modules and test data - >>> import MDAnalysis as mda - >>> import MDAnalysis.analysis.msd as msd - >>> from MDAnalysis.tests.datafiles import RANDOM_WALK, RANDOM_WALK_TOPO +.. code-block:: python + + import MDAnalysis as mda + import MDAnalysis.analysis.msd as msd + from MDAnalysis.tests.datafiles import RANDOM_WALK, RANDOM_WALK_TOPO Given a universe containing trajectory data we can extract the MSD analysis by using the class :class:`EinsteinMSD` - >>> u = mda.Universe(RANDOM_WALK, RANDOM_WALK_TOPO) - >>> MSD = msd.EinsteinMSD(u, 'all', msd_type='xyz', fft=True) - >>> MSD.run() +.. code-block:: python + + u = mda.Universe(RANDOM_WALK, RANDOM_WALK_TOPO) + MSD = msd.EinsteinMSD(u, 'all', msd_type='xyz', fft=True) + MSD.run() The MSD can then be accessed as - >>> msd = MSD.timeseries +.. code-block:: python + + msd = MSD.timeseries Visual inspection of the MSD is important, so let's take a look at it with a simple plot. - >>> import matplotlib.pyplot as plt - >>> nframes = MSD.n_frames - >>> timestep = 1 # this needs to be the actual time between frames in your - trajectory - >>> lagtimes = np.arange(nframes)*timestep # make the lag time axis - >>> fig = plt.figure() - >>> ax = plt.axes() - >>> ax.plot(lagtimes, msd, color="black", linestyle="-", - label=r'3D random walk') # plot the actual MSD - >>> exact = lagtimes*6 - >>> ax.plot(lagtimes, exact, color="black", linestyle="--", - label=r'$y=2 D\tau$') # plot the exact result - >>> plt.show() +.. code-block:: python + + import matplotlib.pyplot as plt + nframes = MSD.n_frames + timestep = 1 # this needs to be the actual time between frames + lagtimes = np.arange(nframes)*timestep # make the lag time axis + fig = plt.figure() + ax = plt.axes() + # plot the actual MSD + ax.plot(lagtimes, msd, lc="black", ls="-", label=r'3D random walk') + exact = lagtimes*6 + # plot the exact result + ax.plot(lagtimes, exact, lc="black", ls="--", label=r'$y=2 D\tau$') + plt.show() This gives us the plot of the MSD with respect to lag-time (:math:`\tau`). We can see that the MSD is approximately linear with respect to :math:`\tau`. @@ -125,8 +132,10 @@ reccomended [Maginn2019]_ where the "middle" segment can be identified as having a slope of 1. - >>> plt.loglog(lagtimes, msd) - >>> plt.show() +.. code-block:: python + + plt.loglog(lagtimes, msd) + plt.show() Now that we have identified what segment of our MSD to analyse, let's compute a self-diffusivity. @@ -145,17 +154,18 @@ the example above. The segment between :math:`\tau = 20` and :math:`\tau = 60` is used to demonstrate selection of a MSD segment. - >>> from scipy.stats import linregress as lr - >>> start_time = 20 - >>> start_index = int(start_time/timestep) - >>> end_time = 60 - >>> end_index = int(end_time/timestep) - >>> linear_model = lr(lagtimes[start_index:end_index], - msd[start_index:end_index]) - >>> slope = linear_model.slope - >>> error = linear_model.rvalue - >>> D = slope * 1/(2*MSD.dim_fac) - #dim_fac is 3 as we computed a 3D msd ('xyz') +.. code-block:: python + + from scipy.stats import linregress as lr + start_time = 20 + start_index = int(start_time/timestep) + end_time = 60 + linear_model = lr(lagtimes[start_index:end_index], \ + msd[start_index:end_index]) + slope = linear_model.slope + error = linear_model.rvalue + # dim_fac is 3 as we computed a 3D msd with 'xyz' + D = slope * 1/(2*MSD.dim_fac) We have now computed a self-diffusivity! @@ -178,10 +188,10 @@ References ---------- -.. [Maginn2019] Maginn, E. J., Messerly, R. A., Carlson, D. J.; Roe, D. -R., Elliott, J. R. Best Practices for Computing Transport Properties 1. -Self-Diffusivity and Viscosity from Equilibrium Molecular Dynamics -[Article v1.0]. Living J. Comput. Mol. Sci. 2019, 1 (1). +.. [Maginn2019] Maginn, E. J., Messerly, R. A., Carlson, D. J.; Roe, D. R., + Elliott, J. R. Best Practices for Computing Transport Properties 1. + Self-Diffusivity and Viscosity from Equilibrium Molecular Dynamics + [Article v1.0]. Living J. Comput. Mol. Sci. 2019, 1 (1). @@ -326,8 +336,8 @@ def _single_frame(self): shape = (n_frames, n_particles, 3) """ - self._position_array[self._frame_index] = - self._atoms.positions[:, self._dim] + self._position_array[self._frame_index] = \ + self._atoms.positions[:, self._dim] def _conclude(self): if self.fft == True: @@ -357,7 +367,7 @@ def _conclude_simple(self): disp = self._position_array[:-lag, :, self._dim] - \ self._position_array[lag:, :, self._dim] sqdist = np.square(disp, dtype=np.float64).sum( - axis=-1, dtype=np.float64) #np.float64 required + axis=-1, dtype=np.float64) # np.float64 required self.msds_by_particle[lag, :] = np.mean( sqdist, axis=0, dtype=np.float64) msd = self.msds_by_particle.mean(axis=1, dtype=np.float64) From cff23b08fa00c3c475f9d27400e64bf78a6d215a Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Wed, 3 Jun 2020 11:26:50 +1000 Subject: [PATCH 079/121] add more references and tidy docs --- package/MDAnalysis/analysis/msd.py | 44 +++++++++++++++++++----------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index cacab367a06..91dd80633b2 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -178,20 +178,31 @@ These include specific instructions around simulation settings, using unwrapped trajectories and maintaining a relatively small elapsed time between saved frames. Additionally, corrections for finite size effects are sometimes -employed along with various means of estimating errors. The reader is directed -to the following review, which describes many of the common pitfalls -[Maginn2019]_. There are other ways to compute self-diffusivity, such as from -a Green-Kubo integral. At this point in time, these methods are beyond the -scope of this module. +employed along with various means of estimating errors [Yeh2004]_ [Bulow2020]_. +The reader is directed to the following review, which describes many of the +common pitfalls [Maginn2019]_. There are other ways to compute +self-diffusivity, such as from a Green-Kubo integral. At this point in time, +these methods are beyond the scope of this module. References ---------- .. [Maginn2019] Maginn, E. J., Messerly, R. A., Carlson, D. J.; Roe, D. R., - Elliott, J. R. Best Practices for Computing Transport Properties 1. - Self-Diffusivity and Viscosity from Equilibrium Molecular Dynamics - [Article v1.0]. Living J. Comput. Mol. Sci. 2019, 1 (1). + Elliott, J. R. Best Practices for Computing Transport + Properties 1. Self-Diffusivity and Viscosity from Equilibrium + Molecular Dynamics [Article v1.0]. Living J. Comput. Mol. Sci. + 2019, 1 (1). + +.. [Yeh2004] Yeh, I. C.; Hummer, G. System-Size Dependence of Diffusion + Coefficients and Viscosities from Molecular Dynamics + Simulations with Periodic Boundary Conditions. + J. Phys. Chem. B 2004, 108 (40), 15873–15879. + +.. [Bulow2020] von Bülow, S.; Bullerjahn, J. T.; Hummer, G. Systematic + Errors in Diffusion Coefficients from Long-Time Molecular + Dynamics Simulations at Constant Pressure. 2020. + arXiv:2003.09205 [Cond-Mat, Physics:Physics]. @@ -229,9 +240,9 @@ class EinsteinMSD(AnalysisBase): ---------- dim_fac : int Dimensionality :math:`d` of the MSD. - timeseries : :class:`np.ndarray` + timeseries : :class:`numpy.ndarray` The averaged MSD with respect to lag-time. - msd_per_particle : :class:`np.ndarray` + msd_per_particle : :class:`numpy.ndarray` The MSD of each individual particle with respect to lag-time. n_frames : int Number of frames in trajectory. @@ -247,7 +258,8 @@ def __init__(self, u, select=None, msd_type='xyz', fft=True, **kwargs): u : Universe An MDAnalysis :class:`Universe`. selection : str - An MDAnalysis selection string. Defaults to `None`. + An MDAnalysis selection string. Defaults to `None` in which case + all atoms are selected. msd_type : {'xyz', 'xy', 'yz', 'xz', 'x', 'y', 'z'} Desired dimensions to be included in the MSD. Defaults to 'xyz'. fft : bool @@ -331,7 +343,7 @@ def _single_frame(self): Returns ------- - self._position_array : :class:`np.ndarray` + self._position_array : :class:`numpy.ndarray` Array of particle positions with respect to time shape = (n_frames, n_particles, 3) """ @@ -350,13 +362,13 @@ def _conclude_simple(self): Parameters ---------- - self._position_array : :class:`np.ndarray` + self._position_array : :class:`numpy.ndarray` Array of particle positions with respect to time shape (n_frames, n_particles, 3). Returns ------- - self.timeseries : :class:`np.ndarray` + self.timeseries : :class:`numpy.ndarray` The MSD as a function of lag-time. """ @@ -378,13 +390,13 @@ def _conclude_fft(self): # with FFT, np.float64 bit prescision required. Parameters ---------- - self._position_array : :class:`np.ndarray` + self._position_array : :class:`numpy.ndarray` Array of particle positions with respect to time shape (n_frames, n_particles, 3). Returns ------- - self.timeseries : :class:`np.ndarray` + self.timeseries : :class:`numpy.ndarray` The MSD as a function of lagtime. """ From ba6f37a7d2108787df2afb09cf084ea2259cd17f Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Wed, 3 Jun 2020 11:36:32 +1000 Subject: [PATCH 080/121] removed repetitive structure in self.msd --- package/MDAnalysis/analysis/msd.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 91dd80633b2..54af45cf4da 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -353,9 +353,9 @@ def _single_frame(self): def _conclude(self): if self.fft == True: - self.timeseries = self._conclude_fft() + self._conclude_fft() else: - self.timeseries = self._conclude_simple() + self._conclude_simple() def _conclude_simple(self): r""" Calculates the MSD via the simple "windowed" algorithm. @@ -373,8 +373,6 @@ def _conclude_simple(self): """ lagtimes = np.arange(1, self.n_frames) - # preset the zero lagtime so we don't have to iterate through - self.msds_by_particle[0, :] = np.zeros(self._n_particles) for lag in lagtimes: disp = self._position_array[:-lag, :, self._dim] - \ self._position_array[lag:, :, self._dim] @@ -382,8 +380,7 @@ def _conclude_simple(self): axis=-1, dtype=np.float64) # np.float64 required self.msds_by_particle[lag, :] = np.mean( sqdist, axis=0, dtype=np.float64) - msd = self.msds_by_particle.mean(axis=1, dtype=np.float64) - return msd + self.timeseries = self.msds_by_particle.mean(axis=1, dtype=np.float64) def _conclude_fft(self): # with FFT, np.float64 bit prescision required. r""" Calculates the MSD via the FCA fast correlation algorithm. @@ -405,5 +402,4 @@ def _conclude_fft(self): # with FFT, np.float64 bit prescision required. for n in range(self._n_particles): self.msds_by_particle[:, n] = tidynamics.msd( reshape_positions[:, n, :]) - msd = self.msds_by_particle.mean(axis=1, dtype=np.float64) - return msd + self.timeseries = self.msds_by_particle.mean(axis=1, dtype=np.float64) From 42720d8c6cd29c9e2d7ac6a584fa129a5b195474 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Wed, 3 Jun 2020 11:51:44 +1000 Subject: [PATCH 081/121] clean up and PEP8 tests --- .../MDAnalysisTests/analysis/test_msd.py | 97 +++++++++++-------- 1 file changed, 58 insertions(+), 39 deletions(-) diff --git a/testsuite/MDAnalysisTests/analysis/test_msd.py b/testsuite/MDAnalysisTests/analysis/test_msd.py index 49b8ff01649..84ad3d8f73f 100644 --- a/testsuite/MDAnalysisTests/analysis/test_msd.py +++ b/testsuite/MDAnalysisTests/analysis/test_msd.py @@ -35,140 +35,159 @@ import pytest import tidynamics + @pytest.fixture(scope='module') def SELECTION(): selection = 'backbone and name CA and resid 1-10' return selection + @pytest.fixture(scope='module') def NSTEP(): nstep = 5000 return nstep -#universe + @pytest.fixture(scope='module') def u(): return mda.Universe(PSF, DCD) + @pytest.fixture(scope='module') def random_walk_u(): - #100x100 + # 100x100 return mda.Universe(RANDOM_WALK_TOPO, RANDOM_WALK) -#non fft msd + @pytest.fixture(scope='module') def msd(u, SELECTION): + # non fft msd m = MSD(u, SELECTION, msd_type='xyz', fft=False) m.run() return m -#fft msd + @pytest.fixture(scope='module') def msd_fft(u, SELECTION): + # fft msd m = MSD(u, SELECTION, msd_type='xyz', fft=True) m.run() return m -#all possible dimensions -@pytest.fixture(scope='module') -def dimension_list(): - dimensions = ['xyz', 'xy', 'xz', 'yz', 'x', 'y', 'z'] - return dimensions @pytest.fixture(scope='module') -def step_traj(NSTEP): # constant velocity +def step_traj(NSTEP): # constant velocity x = np.arange(NSTEP) - traj = np.vstack([x,x,x]).T - traj_reshape = traj.reshape([NSTEP,1,3]) + traj = np.vstack([x, x, x]).T + traj_reshape = traj.reshape([NSTEP, 1, 3]) u = mda.Universe.empty(1) u.load_new(traj_reshape) return u + + @pytest.fixture(scope='module') -def step_traj_arr(NSTEP): # constant velocity +def step_traj_arr(NSTEP): # constant velocity x = np.arange(NSTEP) - traj = np.vstack([x,x,x]).T + traj = np.vstack([x, x, x]).T return traj + def random_walk_3d(NSTEP): np.random.seed(1) steps = -1 + 2*np.random.randint(0, 2, size=(NSTEP, 3)) traj = np.cumsum(steps, axis=0) - traj_reshape = traj.reshape([NSTEP,1,3]) + traj_reshape = traj.reshape([NSTEP, 1, 3]) u = mda.Universe.empty(1) u.load_new(traj_reshape) return u, traj -def characteristic_poly(n,d): #polynomial that describes unit step trajectory MSD - x = np.arange(0,n) + +def characteristic_poly(n, d): # polynomial that describes unit step traj MSD + x = np.arange(0, n) y = d*x*x return y -#test some basic size and shape things + def test_selection_works(msd): + # test some basic size and shape things assert_equal(msd._n_particles, 10) -#testing on the PSF, DCD trajectory + def test_fft_vs_simple_default(msd, msd_fft): + # testing on the PSF, DCD trajectory timeseries_simple = msd.timeseries timeseries_fft = msd_fft.timeseries assert_almost_equal(timeseries_simple, timeseries_fft, decimal=4) -#check fft and simple give same result per particle + def test_fft_vs_simple_default_per_particle(msd, msd_fft): + # check fft and simple give same result per particle per_particle_simple = msd.msds_by_particle per_particle_fft = msd_fft.msds_by_particle assert_almost_equal(per_particle_simple, per_particle_fft, decimal=4) -#check fft and simple give same result for each dimensionality + @pytest.mark.parametrize("dim", ['xyz', 'xy', 'xz', 'yz', 'x', 'y', 'z']) def test_fft_vs_simple_all_dims(u, SELECTION, dim): + # check fft and simple give same result for each dimensionality m_simple = MSD(u, SELECTION, msd_type=dim, fft=False) m_simple.run() timeseries_simple = m_simple.timeseries - m_fft = MSD(u,SELECTION, msd_type=dim, fft=True) + m_fft = MSD(u, SELECTION, msd_type=dim, fft=True) m_fft.run() timeseries_fft = m_fft.timeseries assert_almost_equal(timeseries_simple, timeseries_fft, decimal=4) -#check fft and simple give same result for each particle in each dimension + @pytest.mark.parametrize("dim", ['xyz', 'xy', 'xz', 'yz', 'x', 'y', 'z']) def test_fft_vs_simple_all_dims_per_particle(u, SELECTION, dim): + # check fft and simple give same result for each particle in each dimension m_simple = MSD(u, SELECTION, msd_type=dim, fft=False) m_simple.run() per_particle_simple = m_simple.msds_by_particle - m_fft = MSD(u,SELECTION, msd_type=dim, fft=True) + m_fft = MSD(u, SELECTION, msd_type=dim, fft=True) m_fft.run() per_particle_fft = m_fft.msds_by_particle assert_almost_equal(per_particle_simple, per_particle_fft, decimal=4) -#testing the "simple" algorithm on constant velocity trajectory -@pytest.mark.parametrize("dim, dim_factor", [('xyz',3), ('xy',2), ('xz',2), ('yz',2), ('x',1), ('y',1), ('z',1)]) -def test_simple_step_traj_all_dims(step_traj, NSTEP, dim, dim_factor): # this should fit the polynomial y=dim_factor*x**2 - m_simple = MSD(step_traj, 'all' , msd_type=dim, fft=False) + +@pytest.mark.parametrize("dim, dim_factor", \ +[('xyz', 3), ('xy', 2), ('xz', 2), ('yz', 2), ('x', 1), ('y', 1), ('z', 1)]) +def test_simple_step_traj_all_dims(step_traj, NSTEP, dim, dim_factor): + # testing the "simple" algorithm on constant velocity trajectory + # should fit the polynomial y=dim_factor*x**2 + m_simple = MSD(step_traj, 'all', msd_type=dim, fft=False) m_simple.run() - poly = characteristic_poly(NSTEP,dim_factor) + poly = characteristic_poly(NSTEP, dim_factor) assert_almost_equal(m_simple.timeseries, poly, decimal=4) -#testing the fft algorithm on constant velocity trajectory -#fft based tests require a slight decrease in expected prescision due to roundoff in fft(ifft()) calls -#relative accuracy expected to be around ~1e-12 -@pytest.mark.parametrize("dim, dim_factor", [('xyz',3), ('xy',2), ('xz',2), ('yz',2), ('x',1), ('y',1), ('z',1)]) -def test_fft_step_traj_all_dims(step_traj, NSTEP, dim, dim_factor): # this should fit the polynomial y=dim_factor*x**2 - m_simple = MSD(step_traj, 'all' , msd_type=dim, fft=True) + +@pytest.mark.parametrize("dim, dim_factor", \ +[('xyz', 3), ('xy', 2), ('xz', 2), ('yz', 2), ('x', 1), ('y', 1), ('z', 1)]) +def test_fft_step_traj_all_dims(step_traj, NSTEP, dim, dim_factor): + # testing the fft algorithm on constant velocity trajectory + # this should fit the polynomial y=dim_factor*x**2 + # fft based tests require a slight decrease in expected prescision + # primarily due to roundoff in fft(ifft()) calls. + # relative accuracy expected to be around ~1e-12 + m_simple = MSD(step_traj, 'all', msd_type=dim, fft=True) m_simple.run() - poly = characteristic_poly(NSTEP,dim_factor) - assert_almost_equal(m_simple.timeseries, poly, decimal=3) # this was relaxed from decimal=4 for numpy=1.13 test + poly = characteristic_poly(NSTEP, dim_factor) + # this was relaxed from decimal=4 for numpy=1.13 test + assert_almost_equal(m_simple.timeseries, poly, decimal=3) + -#regress against random_walk test data def test_random_walk_u_simple(random_walk_u): + # regress against random_walk test data msd_rw = MSD(random_walk_u, 'all', msd_type='xyz', fft=False) msd_rw.run() norm = np.linalg.norm(msd_rw.timeseries) val = 3932.39927487146 assert_almost_equal(norm, val, decimal=5) -#regress against random_walk test data + def test_random_walk_u_fft(random_walk_u): + # regress against random_walk test data msd_rw = MSD(random_walk_u, 'all', msd_type='xyz', fft=True) msd_rw.run() norm = np.linalg.norm(msd_rw.timeseries) From 0f5a009e87b5ff48d428a746c3e3aac180e7b980 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Wed, 3 Jun 2020 13:15:51 +1000 Subject: [PATCH 082/121] fix typo --- testsuite/MDAnalysisTests/analysis/test_msd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testsuite/MDAnalysisTests/analysis/test_msd.py b/testsuite/MDAnalysisTests/analysis/test_msd.py index 84ad3d8f73f..b9bd2a65608 100644 --- a/testsuite/MDAnalysisTests/analysis/test_msd.py +++ b/testsuite/MDAnalysisTests/analysis/test_msd.py @@ -151,7 +151,7 @@ def test_fft_vs_simple_all_dims_per_particle(u, SELECTION, dim): assert_almost_equal(per_particle_simple, per_particle_fft, decimal=4) -@pytest.mark.parametrize("dim, dim_factor", \ +@pytest.mark.parametrize("dim, dim_factor", \ [('xyz', 3), ('xy', 2), ('xz', 2), ('yz', 2), ('x', 1), ('y', 1), ('z', 1)]) def test_simple_step_traj_all_dims(step_traj, NSTEP, dim, dim_factor): # testing the "simple" algorithm on constant velocity trajectory From cc53e1b743da94c010aa4327df74b739fb7bba3d Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Wed, 3 Jun 2020 13:16:22 +1000 Subject: [PATCH 083/121] refactor to only use the right dimension --- package/MDAnalysis/analysis/msd.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 54af45cf4da..55421d9c0e1 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -284,7 +284,7 @@ def __init__(self, u, select=None, msd_type='xyz', fft=True, **kwargs): self._atoms = None # indexing - self._n_frames = 0 + self._n_frames = 0 #this is set in the baseclass self._n_particles = 0 # result @@ -295,7 +295,7 @@ def __init__(self, u, select=None, msd_type='xyz', fft=True, **kwargs): def _prepare(self): self._parse_msd_type() self._atoms = self.u.select_atoms(self.select) - self._n_frames = len(self.u.trajectory) + self._n_frames = self.n_frames #set in base class self._n_particles = len(self._atoms) self._position_array = np.zeros( (self.n_frames, self._n_particles, self.dim_fac)) @@ -374,8 +374,8 @@ def _conclude_simple(self): """ lagtimes = np.arange(1, self.n_frames) for lag in lagtimes: - disp = self._position_array[:-lag, :, self._dim] - \ - self._position_array[lag:, :, self._dim] + disp = self._position_array[:-lag, :, :] - \ + self._position_array[lag:, :, :] sqdist = np.square(disp, dtype=np.float64).sum( axis=-1, dtype=np.float64) # np.float64 required self.msds_by_particle[lag, :] = np.mean( @@ -397,7 +397,7 @@ def _conclude_fft(self): # with FFT, np.float64 bit prescision required. The MSD as a function of lagtime. """ - reshape_positions = self._position_array[:, :, self._dim].astype( + reshape_positions = self._position_array[:, :, :].astype( np.float64) for n in range(self._n_particles): self.msds_by_particle[:, n] = tidynamics.msd( From 79f93a44d934aa67dd30d992a90dfe1b2e9ad4a1 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Wed, 3 Jun 2020 13:25:27 +1000 Subject: [PATCH 084/121] change docs for array sizing --- package/MDAnalysis/analysis/msd.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 55421d9c0e1..10bec002aff 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -284,7 +284,7 @@ def __init__(self, u, select=None, msd_type='xyz', fft=True, **kwargs): self._atoms = None # indexing - self._n_frames = 0 #this is set in the baseclass + self._n_frames = 0 # this is set in the baseclass self._n_particles = 0 # result @@ -295,7 +295,7 @@ def __init__(self, u, select=None, msd_type='xyz', fft=True, **kwargs): def _prepare(self): self._parse_msd_type() self._atoms = self.u.select_atoms(self.select) - self._n_frames = self.n_frames #set in base class + self._n_frames = self.n_frames # set in base class self._n_particles = len(self._atoms) self._position_array = np.zeros( (self.n_frames, self._n_particles, self.dim_fac)) @@ -345,9 +345,10 @@ def _single_frame(self): ------- self._position_array : :class:`numpy.ndarray` Array of particle positions with respect to time - shape = (n_frames, n_particles, 3) + shape = (n_frames, n_particles, dim_fac) """ - + # shape of position array set here, use span in last dimension + # from this point on self._position_array[self._frame_index] = \ self._atoms.positions[:, self._dim] @@ -364,7 +365,7 @@ def _conclude_simple(self): ---------- self._position_array : :class:`numpy.ndarray` Array of particle positions with respect to time - shape (n_frames, n_particles, 3). + shape (n_frames, n_particles, dim_fac). Returns ------- @@ -389,7 +390,7 @@ def _conclude_fft(self): # with FFT, np.float64 bit prescision required. ---------- self._position_array : :class:`numpy.ndarray` Array of particle positions with respect to time - shape (n_frames, n_particles, 3). + shape (n_frames, n_particles, dim_fac). Returns ------- From e9f4cf4eb7cfa705ab27c5979d52765ac1ce2014 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Wed, 3 Jun 2020 13:49:17 +1000 Subject: [PATCH 085/121] authroship note --- testsuite/MDAnalysisTests/analysis/test_msd.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/testsuite/MDAnalysisTests/analysis/test_msd.py b/testsuite/MDAnalysisTests/analysis/test_msd.py index b9bd2a65608..3d733db3baa 100644 --- a/testsuite/MDAnalysisTests/analysis/test_msd.py +++ b/testsuite/MDAnalysisTests/analysis/test_msd.py @@ -20,6 +20,8 @@ # MDAnalysis: A Toolkit for the Analysis of Molecular Dynamics Simulations. # J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 # +# This module written by Hugo MacDermott-Opeskin 2020 + from __future__ import division, absolute_import, print_function From b909aadac7be2d672a767d782d693c214d676125 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Wed, 3 Jun 2020 15:54:53 +1000 Subject: [PATCH 086/121] move tidynamics to try except import --- package/MDAnalysis/analysis/msd.py | 41 +++++++++++++++++++----------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 10bec002aff..8f50cda4f85 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -218,10 +218,11 @@ import numpy as np import logging -import tidynamics from ..due import due, Doi from .base import AnalysisBase +logger = logging.getLogger('MDAnalysis.analysis.msd') + due.cite(Doi("10.21105/joss.00877"), description="Mean Squared Displacements with tidynamics", path="MDAnalysis.analysis.msd", @@ -276,30 +277,27 @@ def __init__(self, u, select=None, msd_type='xyz', fft=True, **kwargs): # args self.select = select self.msd_type = msd_type + self._parse_msd_type() + self.fft = fft # local - self._dim = None + self._atoms = self.u.select_atoms(self.select) + self._n_particles = len(self._atoms) + self._n_frames = None # this is set in the baseclass self._position_array = None - self._atoms = None - - # indexing - self._n_frames = 0 # this is set in the baseclass - self._n_particles = 0 # result - self.dim_fac = 0 - self.timeseries = None self.msds_by_particle = None + self.timeseries = None def _prepare(self): - self._parse_msd_type() - self._atoms = self.u.select_atoms(self.select) - self._n_frames = self.n_frames # set in base class - self._n_particles = len(self._atoms) - self._position_array = np.zeros( - (self.n_frames, self._n_particles, self.dim_fac)) + # only available here + self._n_frames = self.n_frames + # these need to be zeroed prior to each run() call self.msds_by_particle = np.zeros((self._n_frames, self._n_particles)) + self._position_array = np.zeros( + (self._n_frames, self._n_particles, self.dim_fac)) # self.timeseries not set here def _parse_msd_type(self): @@ -398,6 +396,19 @@ def _conclude_fft(self): # with FFT, np.float64 bit prescision required. The MSD as a function of lagtime. """ + try: + import tidynamics + except ImportError: + ("""ERROR --- tidynamics was not found! + + tidynamics is required to compute an FFT based MSD (default) + + try installing it using pip eg: + + pip install tidynamics + + or set fft=False""") + reshape_positions = self._position_array[:, :, :].astype( np.float64) for n in range(self._n_particles): From bac625b7edddf2040fb85fdda889ec1dcd350f3a Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Wed, 3 Jun 2020 16:05:04 +1000 Subject: [PATCH 087/121] typo --- package/MDAnalysis/analysis/msd.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 8f50cda4f85..a9015283692 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -278,13 +278,12 @@ def __init__(self, u, select=None, msd_type='xyz', fft=True, **kwargs): self.select = select self.msd_type = msd_type self._parse_msd_type() - self.fft = fft # local self._atoms = self.u.select_atoms(self.select) self._n_particles = len(self._atoms) - self._n_frames = None # this is set in the baseclass + self._n_frames = None # this is set in the baseclass in _prepare self._position_array = None # result @@ -408,7 +407,7 @@ def _conclude_fft(self): # with FFT, np.float64 bit prescision required. pip install tidynamics or set fft=False""") - + reshape_positions = self._position_array[:, :, :].astype( np.float64) for n in range(self._n_particles): From 1aef2990d5c8f7c4c4270430e276759d1576ee2f Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Wed, 3 Jun 2020 16:15:26 +1000 Subject: [PATCH 088/121] lag time to lag-time --- package/MDAnalysis/analysis/msd.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index a9015283692..aea7f189c84 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -48,7 +48,7 @@ of the MSD. Note that while the definition of the MSD is universal, there are many practical considerations to computing the MSD that vary between implementations. In this module, we compute a "windowed" MSD, where the MSD -is averaged over all possible lag times :math:`\tau \le \tau_{max}`, +is averaged over all possible lag-times :math:`\tau \le \tau_{max}`, where :math:`\tau_{max}` is the length of the trajectory, thereby maximizing the number of samples. @@ -99,7 +99,7 @@ import matplotlib.pyplot as plt nframes = MSD.n_frames timestep = 1 # this needs to be the actual time between frames - lagtimes = np.arange(nframes)*timestep # make the lag time axis + lagtimes = np.arange(nframes)*timestep # make the lag-time axis fig = plt.figure() ax = plt.axes() # plot the actual MSD @@ -112,7 +112,7 @@ This gives us the plot of the MSD with respect to lag-time (:math:`\tau`). We can see that the MSD is approximately linear with respect to :math:`\tau`. This is a numerical example of a known theoretical result that the MSD of a -random walk is linear with respect to lagtime, with a slope of :math:`2d`. +random walk is linear with respect to lag-time, with a slope of :math:`2d`. In this expression :math:`d` is the dimensionality of the MSD. For our 3D MSD, this is 3. For comparison we have plotted the line :math:`y=6\tau` to which an ensemble of 3D random walks should converge. @@ -149,7 +149,7 @@ D_d = \frac{1}{2d} \lim_{t \to \infty} \frac{d}{dt} MSD(r_{d}) From the MSD, self-diffusivities :math:`D` with the desired dimensionality -:math:`d` can be computed by fitting the MSD with respect to the lag time to +:math:`d` can be computed by fitting the MSD with respect to the lag-time to a linear model. An example of this is shown below, using the MSD computed in the example above. The segment between :math:`\tau = 20` and :math:`\tau = 60` is used to demonstrate selection of a MSD segment. @@ -185,6 +185,9 @@ these methods are beyond the scope of this module. +Note also that computation of MSDs is highly memory intensive. If this is +proving a problem, judicious use of start, stop, step indexing is required. + References ---------- @@ -291,7 +294,7 @@ def __init__(self, u, select=None, msd_type='xyz', fft=True, **kwargs): self.timeseries = None def _prepare(self): - # only available here + # self.n_frames only available here self._n_frames = self.n_frames # these need to be zeroed prior to each run() call self.msds_by_particle = np.zeros((self._n_frames, self._n_particles)) @@ -392,7 +395,7 @@ def _conclude_fft(self): # with FFT, np.float64 bit prescision required. Returns ------- self.timeseries : :class:`numpy.ndarray` - The MSD as a function of lagtime. + The MSD as a function of lag-time. """ try: From 2648d0576bb75684f9dd4875e12a4dabb791b7c1 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Wed, 3 Jun 2020 16:33:18 +1000 Subject: [PATCH 089/121] add start stop step tests --- .../MDAnalysisTests/analysis/test_msd.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/testsuite/MDAnalysisTests/analysis/test_msd.py b/testsuite/MDAnalysisTests/analysis/test_msd.py index 3d733db3baa..75fba71b7b8 100644 --- a/testsuite/MDAnalysisTests/analysis/test_msd.py +++ b/testsuite/MDAnalysisTests/analysis/test_msd.py @@ -163,6 +163,17 @@ def test_simple_step_traj_all_dims(step_traj, NSTEP, dim, dim_factor): poly = characteristic_poly(NSTEP, dim_factor) assert_almost_equal(m_simple.timeseries, poly, decimal=4) +@pytest.mark.parametrize("dim, dim_factor", \ +[('xyz', 3), ('xy', 2), ('xz', 2), ('yz', 2), ('x', 1), ('y', 1), ('z', 1)]) +def test_simple_start_stop_step_all_dims(step_traj, NSTEP, dim, dim_factor): + # testing the "simple" algorithm on constant velocity trajectory + # test start stop step is working correctly + m_simple = MSD(step_traj, 'all', msd_type=dim, fft=False, start=10, \ + stop=1000, step=10) + m_simple.run() + poly = characteristic_poly(NSTEP, dim_factor) + assert_almost_equal(m_simple.timeseries, poly[10:1000:10], decimal=4) + @pytest.mark.parametrize("dim, dim_factor", \ [('xyz', 3), ('xy', 2), ('xz', 2), ('yz', 2), ('x', 1), ('y', 1), ('z', 1)]) @@ -178,6 +189,17 @@ def test_fft_step_traj_all_dims(step_traj, NSTEP, dim, dim_factor): # this was relaxed from decimal=4 for numpy=1.13 test assert_almost_equal(m_simple.timeseries, poly, decimal=3) +@pytest.mark.parametrize("dim, dim_factor", \ +[('xyz', 3), ('xy', 2), ('xz', 2), ('yz', 2), ('x', 1), ('y', 1), ('z', 1)]) +def test_fft_start_stop_step_all_dims(step_traj, NSTEP, dim, dim_factor): + # testing the fft algorithm on constant velocity trajectory + # test start stop step is working correctly + m_simple = MSD(step_traj, 'all', msd_type=dim, fft=True, start=10, \ + stop=1000, step=10) + m_simple.run() + poly = characteristic_poly(NSTEP, dim_factor) + assert_almost_equal(m_simple.timeseries, poly[10:1000:10], decimal=3) + def test_random_walk_u_simple(random_walk_u): # regress against random_walk test data From eccc5ead83b3e69350961e072bfce18952c01ad4 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Wed, 10 Jun 2020 18:52:02 +1000 Subject: [PATCH 090/121] fix start stop step tests --- testsuite/MDAnalysisTests/analysis/test_msd.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/testsuite/MDAnalysisTests/analysis/test_msd.py b/testsuite/MDAnalysisTests/analysis/test_msd.py index 75fba71b7b8..b9b430e3aea 100644 --- a/testsuite/MDAnalysisTests/analysis/test_msd.py +++ b/testsuite/MDAnalysisTests/analysis/test_msd.py @@ -168,9 +168,8 @@ def test_simple_step_traj_all_dims(step_traj, NSTEP, dim, dim_factor): def test_simple_start_stop_step_all_dims(step_traj, NSTEP, dim, dim_factor): # testing the "simple" algorithm on constant velocity trajectory # test start stop step is working correctly - m_simple = MSD(step_traj, 'all', msd_type=dim, fft=False, start=10, \ - stop=1000, step=10) - m_simple.run() + m_simple = MSD(step_traj, 'all', msd_type=dim, fft=False) + m_simple.run(start=10, stop=1000, step=10) poly = characteristic_poly(NSTEP, dim_factor) assert_almost_equal(m_simple.timeseries, poly[10:1000:10], decimal=4) @@ -194,9 +193,8 @@ def test_fft_step_traj_all_dims(step_traj, NSTEP, dim, dim_factor): def test_fft_start_stop_step_all_dims(step_traj, NSTEP, dim, dim_factor): # testing the fft algorithm on constant velocity trajectory # test start stop step is working correctly - m_simple = MSD(step_traj, 'all', msd_type=dim, fft=True, start=10, \ - stop=1000, step=10) - m_simple.run() + m_simple = MSD(step_traj, 'all', msd_type=dim, fft=True) + m_simple.run(start=10, stop=1000, step=10) poly = characteristic_poly(NSTEP, dim_factor) assert_almost_equal(m_simple.timeseries, poly[10:1000:10], decimal=3) From f27b0da90d5069e6efc0f1be57693a2fc20a088e Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Wed, 10 Jun 2020 19:06:50 +1000 Subject: [PATCH 091/121] fix test offsets --- testsuite/MDAnalysisTests/analysis/test_msd.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/testsuite/MDAnalysisTests/analysis/test_msd.py b/testsuite/MDAnalysisTests/analysis/test_msd.py index b9b430e3aea..b6ef0e7b4bc 100644 --- a/testsuite/MDAnalysisTests/analysis/test_msd.py +++ b/testsuite/MDAnalysisTests/analysis/test_msd.py @@ -171,7 +171,8 @@ def test_simple_start_stop_step_all_dims(step_traj, NSTEP, dim, dim_factor): m_simple = MSD(step_traj, 'all', msd_type=dim, fft=False) m_simple.run(start=10, stop=1000, step=10) poly = characteristic_poly(NSTEP, dim_factor) - assert_almost_equal(m_simple.timeseries, poly[10:1000:10], decimal=4) + # polynomial must take offset start into account + assert_almost_equal(m_simple.timeseries, poly[0:990:10], decimal=4) @pytest.mark.parametrize("dim, dim_factor", \ @@ -196,7 +197,8 @@ def test_fft_start_stop_step_all_dims(step_traj, NSTEP, dim, dim_factor): m_simple = MSD(step_traj, 'all', msd_type=dim, fft=True) m_simple.run(start=10, stop=1000, step=10) poly = characteristic_poly(NSTEP, dim_factor) - assert_almost_equal(m_simple.timeseries, poly[10:1000:10], decimal=3) + # polynomial must take offset start into account + assert_almost_equal(m_simple.timeseries, poly[0:990:10], decimal=3) def test_random_walk_u_simple(random_walk_u): From 33a890789fd8f9470410d0cc4531bef6440229c5 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Wed, 10 Jun 2020 19:36:43 +1000 Subject: [PATCH 092/121] change docs Co-authored-by: Oliver Beckstein --- package/MDAnalysis/analysis/msd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index aea7f189c84..1a484ecb285 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -186,7 +186,7 @@ Note also that computation of MSDs is highly memory intensive. If this is -proving a problem, judicious use of start, stop, step indexing is required. +proving a problem, judicious use of the ``start``, ``stop``, ``step`` indexing is required. References ---------- From fb7f4588ca748eaf35e62a6f0dc8a6716513d92b Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Wed, 10 Jun 2020 19:44:25 +1000 Subject: [PATCH 093/121] clean up extra variables --- package/MDAnalysis/analysis/msd.py | 10 ++++------ testsuite/MDAnalysisTests/analysis/test_msd.py | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index aea7f189c84..9a4daa4db0d 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -285,8 +285,7 @@ def __init__(self, u, select=None, msd_type='xyz', fft=True, **kwargs): # local self._atoms = self.u.select_atoms(self.select) - self._n_particles = len(self._atoms) - self._n_frames = None # this is set in the baseclass in _prepare + self.n_particles = len(self._atoms) self._position_array = None # result @@ -295,11 +294,10 @@ def __init__(self, u, select=None, msd_type='xyz', fft=True, **kwargs): def _prepare(self): # self.n_frames only available here - self._n_frames = self.n_frames # these need to be zeroed prior to each run() call - self.msds_by_particle = np.zeros((self._n_frames, self._n_particles)) + self.msds_by_particle = np.zeros((self.n_frames, self.n_particles)) self._position_array = np.zeros( - (self._n_frames, self._n_particles, self.dim_fac)) + (self.n_frames, self.n_particles, self.dim_fac)) # self.timeseries not set here def _parse_msd_type(self): @@ -413,7 +411,7 @@ def _conclude_fft(self): # with FFT, np.float64 bit prescision required. reshape_positions = self._position_array[:, :, :].astype( np.float64) - for n in range(self._n_particles): + for n in range(self.n_particles): self.msds_by_particle[:, n] = tidynamics.msd( reshape_positions[:, n, :]) self.timeseries = self.msds_by_particle.mean(axis=1, dtype=np.float64) diff --git a/testsuite/MDAnalysisTests/analysis/test_msd.py b/testsuite/MDAnalysisTests/analysis/test_msd.py index b6ef0e7b4bc..6516c9da58a 100644 --- a/testsuite/MDAnalysisTests/analysis/test_msd.py +++ b/testsuite/MDAnalysisTests/analysis/test_msd.py @@ -112,7 +112,7 @@ def characteristic_poly(n, d): # polynomial that describes unit step traj MSD def test_selection_works(msd): # test some basic size and shape things - assert_equal(msd._n_particles, 10) + assert_equal(msd.n_particles, 10) def test_fft_vs_simple_default(msd, msd_fft): From 5e8c17f1ed702cd7a8f669a66ca9069b8b82397b Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Wed, 10 Jun 2020 20:24:00 +1000 Subject: [PATCH 094/121] fix docs --- package/MDAnalysis/analysis/msd.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index eb6784e6859..faa8ad4d131 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -36,7 +36,7 @@ For a full explanation of the theory behind MSDs and the subsequent calculation of self-diffusivities the reader is directed to [Maginn2019]_. MSDs can be computed from the following expression, known as the -_Einstein_ _formula_: +**Einstein formula**: .. math:: @@ -56,7 +56,9 @@ its :math:`N^2` scaling with respect to :math:`\tau_{max}`. An algorithm to compute the MSD with :math:`N log(N)` scaling based on a Fast Fourier Transform is known and can be accessed by setting ``fft=True`` [Calandri2011]_ -[Buyl2018]_. +[Buyl2018]_. The FFT-based approach requires that the +`tidynamics `_ package is +installed; otherwise the code will raise an :exc:`ImportError`. Please cite [Calandri2011]_ [Buyl2018]_ if you use this module in addition to the normal MDAnalysis citations. From 41dde3f021c591211a7f06bf77abb5b8c4fb6551 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Wed, 10 Jun 2020 20:34:04 +1000 Subject: [PATCH 095/121] fix import Error --- package/MDAnalysis/analysis/msd.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index faa8ad4d131..5b471cad771 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -223,6 +223,7 @@ import numpy as np import logging +from six import raise_from from ..due import due, Doi from .base import AnalysisBase @@ -401,7 +402,7 @@ def _conclude_fft(self): # with FFT, np.float64 bit prescision required. try: import tidynamics except ImportError: - ("""ERROR --- tidynamics was not found! + raise_from(ImportError("""ERROR --- tidynamics was not found! tidynamics is required to compute an FFT based MSD (default) @@ -409,7 +410,7 @@ def _conclude_fft(self): # with FFT, np.float64 bit prescision required. pip install tidynamics - or set fft=False""") + or set fft=False"""), None,) reshape_positions = self._position_array[:, :, :].astype( np.float64) From 269ea057e67bb3717577efe0049e6536034966df Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Thu, 11 Jun 2020 09:45:54 +1000 Subject: [PATCH 096/121] Update package/MDAnalysis/analysis/msd.py Co-authored-by: Oliver Beckstein --- package/MDAnalysis/analysis/msd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 5b471cad771..a9ba2c54c81 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -354,7 +354,7 @@ def _single_frame(self): self._atoms.positions[:, self._dim] def _conclude(self): - if self.fft == True: + if self.fft: self._conclude_fft() else: self._conclude_simple() From 2feb81ee05050018d98819c92c701a0592af591e Mon Sep 17 00:00:00 2001 From: Oliver Beckstein Date: Wed, 10 Jun 2020 17:21:07 -0700 Subject: [PATCH 097/121] corrected indentation and updated CHANGELOG (Emacs also auto-corrected a gazillion of whitespace, sorry) --- package/CHANGELOG | 4 +- package/MDAnalysis/analysis/msd.py | 142 ++++++++++++++--------------- 2 files changed, 73 insertions(+), 73 deletions(-) diff --git a/package/CHANGELOG b/package/CHANGELOG index adac5ab8371..5b723675d37 100644 --- a/package/CHANGELOG +++ b/package/CHANGELOG @@ -13,13 +13,14 @@ The rules for this file: * release numbers follow "Semantic Versioning" http://semver.org ------------------------------------------------------------------------------ -??/??/?? richardjgowers +??/??/?? richardjgowers, hmacdope * 2.0.0 Fixes Enhancements + * Added computation of Mean Squared Displacements (#2438, PR #2619) Deprecations * Dropped Python 2 support @@ -110,7 +111,6 @@ Fixes * Contact Analysis class respects PBC (Issue #2368) Enhancements - * Added computation of Mean Squared Displacements (#2438, PR #2619) * Added support for FHI-AIMS input files (PR #2705) * vastly improved support for 32-bit Windows (PR #2696) * Added methods to compute the root-mean-square-inner-product of subspaces diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index a9ba2c54c81..b9b7a9fd460 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -30,34 +30,34 @@ :Year: 2020 :Copyright: GNU Public License v2 -This module implements the calculation of Mean Squared Displacements (MSDs) -by the Einstein relation. MSDs can be used to characterize the speed at -which particles move and has its roots in the study of Brownian motion. -For a full explanation of the theory behind MSDs and the subsequent -calculation of self-diffusivities the reader is directed to [Maginn2019]_. -MSDs can be computed from the following expression, known as the +This module implements the calculation of Mean Squared Displacements (MSDs) +by the Einstein relation. MSDs can be used to characterize the speed at +which particles move and has its roots in the study of Brownian motion. +For a full explanation of the theory behind MSDs and the subsequent +calculation of self-diffusivities the reader is directed to [Maginn2019]_. +MSDs can be computed from the following expression, known as the **Einstein formula**: .. math:: - MSD(r_{d}) = \bigg{\langle} \frac{1}{N} \sum_{i=1}^{N} |r_{d} + MSD(r_{d}) = \bigg{\langle} \frac{1}{N} \sum_{i=1}^{N} |r_{d} - r_{d}(t_0)|^2 \bigg{\rangle}_{t_{0}} -where :math:`N` is the number of equivalent particles the MSD is calculated -over, :math:`r` are their coordinates and :math:`d` the desired dimensionality -of the MSD. Note that while the definition of the MSD is universal, there are -many practical considerations to computing the MSD that vary between -implementations. In this module, we compute a "windowed" MSD, where the MSD -is averaged over all possible lag-times :math:`\tau \le \tau_{max}`, -where :math:`\tau_{max}` is the length of the trajectory, thereby maximizing +where :math:`N` is the number of equivalent particles the MSD is calculated +over, :math:`r` are their coordinates and :math:`d` the desired dimensionality +of the MSD. Note that while the definition of the MSD is universal, there are +many practical considerations to computing the MSD that vary between +implementations. In this module, we compute a "windowed" MSD, where the MSD +is averaged over all possible lag-times :math:`\tau \le \tau_{max}`, +where :math:`\tau_{max}` is the length of the trajectory, thereby maximizing the number of samples. -The computation of the MSD in this way can be computationally intensive due to -its :math:`N^2` scaling with respect to :math:`\tau_{max}`. An algorithm to -compute the MSD with :math:`N log(N)` scaling based on a Fast Fourier -Transform is known and can be accessed by setting ``fft=True`` [Calandri2011]_ +The computation of the MSD in this way can be computationally intensive due to +its :math:`N^2` scaling with respect to :math:`\tau_{max}`. An algorithm to +compute the MSD with :math:`N log(N)` scaling based on a Fast Fourier +Transform is known and can be accessed by setting ``fft=True`` [Calandri2011]_ [Buyl2018]_. The FFT-based approach requires that the -`tidynamics `_ package is +`tidynamics `_ package is installed; otherwise the code will raise an :exc:`ImportError`. Please cite [Calandri2011]_ [Buyl2018]_ if you use this module in addition to @@ -79,7 +79,7 @@ from MDAnalysis.tests.datafiles import RANDOM_WALK, RANDOM_WALK_TOPO Given a universe containing trajectory data we can extract the MSD -analysis by using the class :class:`EinsteinMSD` +analysis by using the class :class:`EinsteinMSD` .. code-block:: python @@ -100,15 +100,15 @@ import matplotlib.pyplot as plt nframes = MSD.n_frames - timestep = 1 # this needs to be the actual time between frames + timestep = 1 # this needs to be the actual time between frames lagtimes = np.arange(nframes)*timestep # make the lag-time axis fig = plt.figure() ax = plt.axes() # plot the actual MSD - ax.plot(lagtimes, msd, lc="black", ls="-", label=r'3D random walk') + ax.plot(lagtimes, msd, lc="black", ls="-", label=r'3D random walk') exact = lagtimes*6 # plot the exact result - ax.plot(lagtimes, exact, lc="black", ls="--", label=r'$y=2 D\tau$') + ax.plot(lagtimes, exact, lc="black", ls="--", label=r'$y=2 D\tau$') plt.show() This gives us the plot of the MSD with respect to lag-time (:math:`\tau`). @@ -116,22 +116,22 @@ This is a numerical example of a known theoretical result that the MSD of a random walk is linear with respect to lag-time, with a slope of :math:`2d`. In this expression :math:`d` is the dimensionality of the MSD. For our 3D MSD, -this is 3. For comparison we have plotted the line :math:`y=6\tau` to which an +this is 3. For comparison we have plotted the line :math:`y=6\tau` to which an ensemble of 3D random walks should converge. -.. _figure-msd: - -.. figure:: /images/msd_demo_plot.png - :scale: 100 % - :alt: MSD plot - -Note that a segment of the MSD is required to be linear to accurately -determine self-diffusivity. This linear segment represents the so called -"middle" of the MSD plot, where ballistic trajectories at short time-lags are -excluded along with poorly averaged data at long time-lags. We can select the -"middle" of the MSD by indexing the MSD and the time-lags. Appropriately -linear segments of the MSD can be confirmed with a log-log plot as is often -reccomended [Maginn2019]_ where the "middle" segment can be identified as +.. _figure-msd: + +.. figure:: /images/msd_demo_plot.png + :scale: 100 % + :alt: MSD plot + +Note that a segment of the MSD is required to be linear to accurately +determine self-diffusivity. This linear segment represents the so called +"middle" of the MSD plot, where ballistic trajectories at short time-lags are +excluded along with poorly averaged data at long time-lags. We can select the +"middle" of the MSD by indexing the MSD and the time-lags. Appropriately +linear segments of the MSD can be confirmed with a log-log plot as is often +reccomended [Maginn2019]_ where the "middle" segment can be identified as having a slope of 1. .. code-block:: python @@ -139,7 +139,7 @@ plt.loglog(lagtimes, msd) plt.show() -Now that we have identified what segment of our MSD to analyse, let's compute +Now that we have identified what segment of our MSD to analyse, let's compute a self-diffusivity. Computing Self-Diffusivity @@ -148,12 +148,12 @@ .. math:: - D_d = \frac{1}{2d} \lim_{t \to \infty} \frac{d}{dt} MSD(r_{d}) + D_d = \frac{1}{2d} \lim_{t \to \infty} \frac{d}{dt} MSD(r_{d}) -From the MSD, self-diffusivities :math:`D` with the desired dimensionality -:math:`d` can be computed by fitting the MSD with respect to the lag-time to -a linear model. An example of this is shown below, using the MSD computed in -the example above. The segment between :math:`\tau = 20` and :math:`\tau = 60` +From the MSD, self-diffusivities :math:`D` with the desired dimensionality +:math:`d` can be computed by fitting the MSD with respect to the lag-time to +a linear model. An example of this is shown below, using the MSD computed in +the example above. The segment between :math:`\tau = 20` and :math:`\tau = 60` is used to demonstrate selection of a MSD segment. .. code-block:: python @@ -167,7 +167,7 @@ slope = linear_model.slope error = linear_model.rvalue # dim_fac is 3 as we computed a 3D msd with 'xyz' - D = slope * 1/(2*MSD.dim_fac) + D = slope * 1/(2*MSD.dim_fac) We have now computed a self-diffusivity! @@ -176,37 +176,37 @@ _____ There are several factors that must be taken into account when setting up and -processing trajectories for computation of self-diffusivities. -These include specific instructions around simulation settings, using -unwrapped trajectories and maintaining a relatively small elapsed time between -saved frames. Additionally, corrections for finite size effects are sometimes +processing trajectories for computation of self-diffusivities. +These include specific instructions around simulation settings, using +unwrapped trajectories and maintaining a relatively small elapsed time between +saved frames. Additionally, corrections for finite size effects are sometimes employed along with various means of estimating errors [Yeh2004]_ [Bulow2020]_. -The reader is directed to the following review, which describes many of the -common pitfalls [Maginn2019]_. There are other ways to compute -self-diffusivity, such as from a Green-Kubo integral. At this point in time, +The reader is directed to the following review, which describes many of the +common pitfalls [Maginn2019]_. There are other ways to compute +self-diffusivity, such as from a Green-Kubo integral. At this point in time, these methods are beyond the scope of this module. -Note also that computation of MSDs is highly memory intensive. If this is +Note also that computation of MSDs is highly memory intensive. If this is proving a problem, judicious use of the ``start``, ``stop``, ``step`` indexing is required. References ---------- -.. [Maginn2019] Maginn, E. J., Messerly, R. A., Carlson, D. J.; Roe, D. R., - Elliott, J. R. Best Practices for Computing Transport - Properties 1. Self-Diffusivity and Viscosity from Equilibrium - Molecular Dynamics [Article v1.0]. Living J. Comput. Mol. Sci. +.. [Maginn2019] Maginn, E. J., Messerly, R. A., Carlson, D. J.; Roe, D. R., + Elliott, J. R. Best Practices for Computing Transport + Properties 1. Self-Diffusivity and Viscosity from Equilibrium + Molecular Dynamics [Article v1.0]. Living J. Comput. Mol. Sci. 2019, 1 (1). .. [Yeh2004] Yeh, I. C.; Hummer, G. System-Size Dependence of Diffusion - Coefficients and Viscosities from Molecular Dynamics - Simulations with Periodic Boundary Conditions. + Coefficients and Viscosities from Molecular Dynamics + Simulations with Periodic Boundary Conditions. J. Phys. Chem. B 2004, 108 (40), 15873–15879. - -.. [Bulow2020] von Bülow, S.; Bullerjahn, J. T.; Hummer, G. Systematic - Errors in Diffusion Coefficients from Long-Time Molecular - Dynamics Simulations at Constant Pressure. 2020. + +.. [Bulow2020] von Bülow, S.; Bullerjahn, J. T.; Hummer, G. Systematic + Errors in Diffusion Coefficients from Long-Time Molecular + Dynamics Simulations at Constant Pressure. 2020. arXiv:2003.09205 [Cond-Mat, Physics:Physics]. @@ -265,12 +265,12 @@ def __init__(self, u, select=None, msd_type='xyz', fft=True, **kwargs): u : Universe An MDAnalysis :class:`Universe`. selection : str - An MDAnalysis selection string. Defaults to `None` in which case + An MDAnalysis selection string. Defaults to `None` in which case all atoms are selected. msd_type : {'xyz', 'xy', 'yz', 'xz', 'x', 'y', 'z'} Desired dimensions to be included in the MSD. Defaults to 'xyz'. fft : bool - If ``True``, uses a fast FFT based algorithm for computation of + If ``True``, uses a fast FFT based algorithm for computation of the MSD. Otherwise, use the simple "windowed" algorithm. Defaults to ``True``. @@ -314,7 +314,7 @@ def _parse_msd_type(self): Returns ------- self._dim : list - Array-like used to slice the positions to obtain desired + Array-like used to slice the positions to obtain desired dimensionality self.dim_fac : int Dimension factor :math:`d` of the MSD. @@ -345,7 +345,7 @@ def _single_frame(self): Returns ------- self._position_array : :class:`numpy.ndarray` - Array of particle positions with respect to time + Array of particle positions with respect to time shape = (n_frames, n_particles, dim_fac) """ # shape of position array set here, use span in last dimension @@ -354,7 +354,7 @@ def _single_frame(self): self._atoms.positions[:, self._dim] def _conclude(self): - if self.fft: + if self.fft: self._conclude_fft() else: self._conclude_simple() @@ -365,7 +365,7 @@ def _conclude_simple(self): Parameters ---------- self._position_array : :class:`numpy.ndarray` - Array of particle positions with respect to time + Array of particle positions with respect to time shape (n_frames, n_particles, dim_fac). Returns @@ -390,7 +390,7 @@ def _conclude_fft(self): # with FFT, np.float64 bit prescision required. Parameters ---------- self._position_array : :class:`numpy.ndarray` - Array of particle positions with respect to time + Array of particle positions with respect to time shape (n_frames, n_particles, dim_fac). Returns @@ -407,9 +407,9 @@ def _conclude_fft(self): # with FFT, np.float64 bit prescision required. tidynamics is required to compute an FFT based MSD (default) try installing it using pip eg: - + pip install tidynamics - + or set fft=False"""), None,) reshape_positions = self._position_array[:, :, :].astype( From 141dca87513d5df007222e566a0e9876b809673c Mon Sep 17 00:00:00 2001 From: Oliver Beckstein Date: Wed, 10 Jun 2020 20:02:35 -0700 Subject: [PATCH 098/121] EinsteinMSD: also document run() method --- package/MDAnalysis/analysis/msd.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index b9b7a9fd460..e29327cbf5e 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -211,11 +211,12 @@ -Classes and Functions ---------------------- +Classes +------- .. autoclass:: EinsteinMSD :members: + :inherited-members: """ From d007ac9ee425fb881b4f9166e66ae74685c400e5 Mon Sep 17 00:00:00 2001 From: Oliver Beckstein Date: Wed, 10 Jun 2020 20:06:59 -0700 Subject: [PATCH 099/121] reorganized analysis docs toc for structure --- .../source/documentation_pages/analysis_modules.rst | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/package/doc/sphinx/source/documentation_pages/analysis_modules.rst b/package/doc/sphinx/source/documentation_pages/analysis_modules.rst index b8705fb87c4..ac4c7c94f28 100644 --- a/package/doc/sphinx/source/documentation_pages/analysis_modules.rst +++ b/package/doc/sphinx/source/documentation_pages/analysis_modules.rst @@ -101,13 +101,23 @@ Polymers Structure ========= +Macromolecules +-------------- + .. toctree:: :maxdepth: 1 analysis/gnm analysis/helanal - analysis/rdf analysis/dihedrals + +Liquids +------- + +.. toctree:: + :maxdepth: 1 + + analysis/rdf analysis/msd Volumetric analysis From e1dbbbe1509808c50951c2bcb6f18a05b78b709f Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Fri, 12 Jun 2020 09:27:50 +1000 Subject: [PATCH 100/121] Update package/MDAnalysis/analysis/msd.py Co-authored-by: Lily Wang <31115101+lilyminium@users.noreply.github.com> --- package/MDAnalysis/analysis/msd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index e29327cbf5e..db2874f4a6a 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -158,7 +158,7 @@ .. code-block:: python - from scipy.stats import linregress as lr + from scipy.stats import linregress start_time = 20 start_index = int(start_time/timestep) end_time = 60 From 923e161a4bb5985fd9d71332a24097b7f2b61456 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Fri, 12 Jun 2020 09:28:07 +1000 Subject: [PATCH 101/121] Update package/MDAnalysis/analysis/msd.py Co-authored-by: Lily Wang <31115101+lilyminium@users.noreply.github.com> --- package/MDAnalysis/analysis/msd.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index db2874f4a6a..d197b9e38cc 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -162,8 +162,8 @@ start_time = 20 start_index = int(start_time/timestep) end_time = 60 - linear_model = lr(lagtimes[start_index:end_index], \ - msd[start_index:end_index]) + linear_model = linregress(lagtimes[start_index:end_index], + msd[start_index:end_index]) slope = linear_model.slope error = linear_model.rvalue # dim_fac is 3 as we computed a 3D msd with 'xyz' From 607e228421bf54e60333f7a59b4e27d151535ffa Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Fri, 12 Jun 2020 09:28:36 +1000 Subject: [PATCH 102/121] Update package/MDAnalysis/analysis/msd.py Co-authored-by: Lily Wang <31115101+lilyminium@users.noreply.github.com> --- package/MDAnalysis/analysis/msd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index d197b9e38cc..a4f21a8f314 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -188,7 +188,7 @@ Note also that computation of MSDs is highly memory intensive. If this is -proving a problem, judicious use of the ``start``, ``stop``, ``step`` indexing is required. +proving a problem, judicious use of the ``start``, ``stop``, ``step`` keywords to control which frames are incorporated may be required. References ---------- From e983f83f0dc6ef2ba94087facdbf12de0f51544a Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Fri, 12 Jun 2020 09:30:30 +1000 Subject: [PATCH 103/121] fix Co-authored-by: Lily Wang <31115101+lilyminium@users.noreply.github.com> --- package/MDAnalysis/analysis/msd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index a4f21a8f314..b7c4f30b0a8 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -330,7 +330,7 @@ def _parse_msd_type(self): self._dim = keys[self.msd_type] except KeyError: raise ValueError( - 'invalid msd_type: {} specified, please specify one of xyz, \ + 'invalid msd_type: {} specified, please specify one of xyz, ' xy, xz, yz, x, y, z'.format(self.msd_type)) self.dim_fac = len(self._dim) From 7d4e54f26ed257b4e159696d9b393bb0f574baa0 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Fri, 12 Jun 2020 09:31:30 +1000 Subject: [PATCH 104/121] typo Co-authored-by: Lily Wang <31115101+lilyminium@users.noreply.github.com> --- package/MDAnalysis/analysis/msd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index b7c4f30b0a8..cb07b080070 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -331,7 +331,7 @@ def _parse_msd_type(self): except KeyError: raise ValueError( 'invalid msd_type: {} specified, please specify one of xyz, ' - xy, xz, yz, x, y, z'.format(self.msd_type)) + 'xy, xz, yz, x, y, z'.format(self.msd_type)) self.dim_fac = len(self._dim) From 8498f1a2f6dc1013a500c83da39d4fe67d700243 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Fri, 12 Jun 2020 09:32:23 +1000 Subject: [PATCH 105/121] change import Co-authored-by: Lily Wang <31115101+lilyminium@users.noreply.github.com> --- package/MDAnalysis/analysis/msd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index cb07b080070..4e836099fa4 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -403,7 +403,7 @@ def _conclude_fft(self): # with FFT, np.float64 bit prescision required. try: import tidynamics except ImportError: - raise_from(ImportError("""ERROR --- tidynamics was not found! + raise ImportError("""ERROR --- tidynamics was not found! tidynamics is required to compute an FFT based MSD (default) From e39433e2cdbf83c568089cbb042ecd33e0496cb3 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Fri, 12 Jun 2020 09:32:50 +1000 Subject: [PATCH 106/121] change import error Co-authored-by: Lily Wang <31115101+lilyminium@users.noreply.github.com> --- package/MDAnalysis/analysis/msd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 4e836099fa4..10b053f41eb 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -411,7 +411,7 @@ def _conclude_fft(self): # with FFT, np.float64 bit prescision required. pip install tidynamics - or set fft=False"""), None,) + or set fft=False""") reshape_positions = self._position_array[:, :, :].astype( np.float64) From 50b9f110fa754084221f2aaf3a3ef92c6a778df5 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Fri, 12 Jun 2020 09:33:21 +1000 Subject: [PATCH 107/121] Update package/MDAnalysis/analysis/msd.py Co-authored-by: Lily Wang <31115101+lilyminium@users.noreply.github.com> --- package/MDAnalysis/analysis/msd.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 10b053f41eb..b642a04c5ff 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -413,8 +413,7 @@ def _conclude_fft(self): # with FFT, np.float64 bit prescision required. or set fft=False""") - reshape_positions = self._position_array[:, :, :].astype( - np.float64) + positions = self._position_array.astype(np.float64) for n in range(self.n_particles): self.msds_by_particle[:, n] = tidynamics.msd( reshape_positions[:, n, :]) From af5cedf010509beb09ef63a5afcc1c627a68d222 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Fri, 12 Jun 2020 09:36:04 +1000 Subject: [PATCH 108/121] newline Co-authored-by: Lily Wang <31115101+lilyminium@users.noreply.github.com> --- package/MDAnalysis/analysis/msd.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index b642a04c5ff..0a592be44ab 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -415,6 +415,5 @@ def _conclude_fft(self): # with FFT, np.float64 bit prescision required. positions = self._position_array.astype(np.float64) for n in range(self.n_particles): - self.msds_by_particle[:, n] = tidynamics.msd( - reshape_positions[:, n, :]) + self.msds_by_particle[:, n] = tidynamics.msd(positions[:, n]) self.timeseries = self.msds_by_particle.mean(axis=1, dtype=np.float64) From e76f1644335d0052a3f17b7e23e72628a96aa2a3 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Fri, 12 Jun 2020 09:38:29 +1000 Subject: [PATCH 109/121] Revert "newline" This reverts commit af5cedf010509beb09ef63a5afcc1c627a68d222. --- package/MDAnalysis/analysis/msd.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 0a592be44ab..b642a04c5ff 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -415,5 +415,6 @@ def _conclude_fft(self): # with FFT, np.float64 bit prescision required. positions = self._position_array.astype(np.float64) for n in range(self.n_particles): - self.msds_by_particle[:, n] = tidynamics.msd(positions[:, n]) + self.msds_by_particle[:, n] = tidynamics.msd( + reshape_positions[:, n, :]) self.timeseries = self.msds_by_particle.mean(axis=1, dtype=np.float64) From 1d62c0c39bdb0f45ab854ba549392994fd08662d Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Fri, 12 Jun 2020 09:39:18 +1000 Subject: [PATCH 110/121] change docs Co-authored-by: Lily Wang <31115101+lilyminium@users.noreply.github.com> --- package/MDAnalysis/analysis/msd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 0a592be44ab..61d7e89bbba 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -249,7 +249,7 @@ class EinsteinMSD(AnalysisBase): dim_fac : int Dimensionality :math:`d` of the MSD. timeseries : :class:`numpy.ndarray` - The averaged MSD with respect to lag-time. + The averaged MSD over all the particles with respect to lag-time. msd_per_particle : :class:`numpy.ndarray` The MSD of each individual particle with respect to lag-time. n_frames : int From a55ad304966ceb6c47b73063d1857f3d7b559186 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Fri, 12 Jun 2020 09:39:45 +1000 Subject: [PATCH 111/121] change docs Co-authored-by: Lily Wang <31115101+lilyminium@users.noreply.github.com> --- package/MDAnalysis/analysis/msd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 61d7e89bbba..58bf319c352 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -265,7 +265,7 @@ def __init__(self, u, select=None, msd_type='xyz', fft=True, **kwargs): ---------- u : Universe An MDAnalysis :class:`Universe`. - selection : str + select : str An MDAnalysis selection string. Defaults to `None` in which case all atoms are selected. msd_type : {'xyz', 'xy', 'yz', 'xz', 'x', 'y', 'z'} From 491da98253179ca00368e9997d23392c03bfd277 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Fri, 12 Jun 2020 09:42:33 +1000 Subject: [PATCH 112/121] change docs Co-authored-by: Lily Wang <31115101+lilyminium@users.noreply.github.com> --- package/MDAnalysis/analysis/msd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 58bf319c352..6880a8a8b12 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -266,7 +266,7 @@ def __init__(self, u, select=None, msd_type='xyz', fft=True, **kwargs): u : Universe An MDAnalysis :class:`Universe`. select : str - An MDAnalysis selection string. Defaults to `None` in which case + A selection string. Defaults to `None` in which case all atoms are selected. msd_type : {'xyz', 'xy', 'yz', 'xz', 'x', 'y', 'z'} Desired dimensions to be included in the MSD. Defaults to 'xyz'. From 4d9c2d59690893752b7f6250e2a6f177ffa5aa04 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Fri, 12 Jun 2020 09:47:15 +1000 Subject: [PATCH 113/121] confusion over revert --- package/MDAnalysis/analysis/msd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 429da645069..d652593fabe 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -416,5 +416,5 @@ def _conclude_fft(self): # with FFT, np.float64 bit prescision required. positions = self._position_array.astype(np.float64) for n in range(self.n_particles): self.msds_by_particle[:, n] = tidynamics.msd( - reshape_positions[:, n, :]) + positions[:, n, :]) self.timeseries = self.msds_by_particle.mean(axis=1, dtype=np.float64) From 0cf1882881fe384e11173f9dd8296131fd88438a Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Fri, 12 Jun 2020 09:53:50 +1000 Subject: [PATCH 114/121] change author notes etc --- package/MDAnalysis/analysis/msd.py | 9 +++------ testsuite/MDAnalysisTests/analysis/test_msd.py | 5 +---- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index d652593fabe..eab7118c41c 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -220,11 +220,8 @@ """ -from __future__ import division, absolute_import - import numpy as np import logging -from six import raise_from from ..due import due, Doi from .base import AnalysisBase @@ -331,7 +328,7 @@ def _parse_msd_type(self): except KeyError: raise ValueError( 'invalid msd_type: {} specified, please specify one of xyz, ' - 'xy, xz, yz, x, y, z'.format(self.msd_type)) + 'xy, xz, yz, x, y, z'.format(self.msd_type)) self.dim_fac = len(self._dim) @@ -351,8 +348,8 @@ def _single_frame(self): """ # shape of position array set here, use span in last dimension # from this point on - self._position_array[self._frame_index] = \ - self._atoms.positions[:, self._dim] + self._position_array[self._frame_index] = + self._atoms.positions[:, self._dim] def _conclude(self): if self.fft: diff --git a/testsuite/MDAnalysisTests/analysis/test_msd.py b/testsuite/MDAnalysisTests/analysis/test_msd.py index 6516c9da58a..88081f94cc1 100644 --- a/testsuite/MDAnalysisTests/analysis/test_msd.py +++ b/testsuite/MDAnalysisTests/analysis/test_msd.py @@ -22,14 +22,11 @@ # # This module written by Hugo MacDermott-Opeskin 2020 -from __future__ import division, absolute_import, print_function - import MDAnalysis as mda from MDAnalysis.analysis.msd import EinsteinMSD as MSD -from numpy.testing import (assert_array_less, - assert_almost_equal, assert_equal) +from numpy.testing import (assert_almost_equal, assert_equal) import numpy as np from MDAnalysisTests.datafiles import PSF, DCD, RANDOM_WALK, RANDOM_WALK_TOPO From f1c7bd29b65bb4a76829fdad7db01af3c3420a67 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Fri, 12 Jun 2020 09:59:28 +1000 Subject: [PATCH 115/121] small edits --- package/MDAnalysis/analysis/msd.py | 3 +-- testsuite/MDAnalysisTests/analysis/test_msd.py | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index eab7118c41c..8dcd5a1240a 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -20,7 +20,6 @@ # MDAnalysis: A Toolkit for the Analysis of Molecular Dynamics Simulations. # J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 # -# This module written by Hugo MacDermott-Opeskin 2020 r""" Mean Squared Displacement --- :mod:`MDAnalysis.analysis.msd` @@ -250,7 +249,7 @@ class EinsteinMSD(AnalysisBase): msd_per_particle : :class:`numpy.ndarray` The MSD of each individual particle with respect to lag-time. n_frames : int - Number of frames in trajectory. + Number of frames included in the analysis. n_particles : int Number of particles MSD was calculated over. diff --git a/testsuite/MDAnalysisTests/analysis/test_msd.py b/testsuite/MDAnalysisTests/analysis/test_msd.py index 88081f94cc1..7b6090021b4 100644 --- a/testsuite/MDAnalysisTests/analysis/test_msd.py +++ b/testsuite/MDAnalysisTests/analysis/test_msd.py @@ -20,7 +20,6 @@ # MDAnalysis: A Toolkit for the Analysis of Molecular Dynamics Simulations. # J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 # -# This module written by Hugo MacDermott-Opeskin 2020 import MDAnalysis as mda From 7321bb6e832577040d510fdf4176c3355f90ce1e Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Fri, 12 Jun 2020 10:22:01 +1000 Subject: [PATCH 116/121] rip out docstrings for internal functions --- package/MDAnalysis/analysis/msd.py | 46 +----------------------------- 1 file changed, 1 insertion(+), 45 deletions(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 8dcd5a1240a..eb196fadbc2 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -269,6 +269,7 @@ def __init__(self, u, select=None, msd_type='xyz', fft=True, **kwargs): fft : bool If ``True``, uses a fast FFT based algorithm for computation of the MSD. Otherwise, use the simple "windowed" algorithm. + The tidynamics package is required for `fft=True`. Defaults to ``True``. @@ -303,19 +304,6 @@ def _prepare(self): def _parse_msd_type(self): r""" Sets up the desired dimensionality of the MSD. - Parameters - ---------- - self.msd_type : {'xyz', 'xy', 'yz', 'xz', 'x', 'y', 'z'} - Dimensions to be included in the MSD. - - Returns - ------- - self._dim : list - Array-like used to slice the positions to obtain desired - dimensionality - self.dim_fac : int - Dimension factor :math:`d` of the MSD. - """ keys = {'x': [0], 'y': [1], 'z': [2], 'xy': [0, 1], 'xz': [0, 2], 'yz': [1, 2], 'xyz': [0, 1, 2]} @@ -334,16 +322,6 @@ def _parse_msd_type(self): def _single_frame(self): r""" Constructs array of positions for MSD calculation. - Parameters - ---------- - self.u : :class:`Universe` - MDAnalysis Universe - - Returns - ------- - self._position_array : :class:`numpy.ndarray` - Array of particle positions with respect to time - shape = (n_frames, n_particles, dim_fac) """ # shape of position array set here, use span in last dimension # from this point on @@ -359,17 +337,6 @@ def _conclude(self): def _conclude_simple(self): r""" Calculates the MSD via the simple "windowed" algorithm. - Parameters - ---------- - self._position_array : :class:`numpy.ndarray` - Array of particle positions with respect to time - shape (n_frames, n_particles, dim_fac). - - Returns - ------- - self.timeseries : :class:`numpy.ndarray` - The MSD as a function of lag-time. - """ lagtimes = np.arange(1, self.n_frames) for lag in lagtimes: @@ -384,17 +351,6 @@ def _conclude_simple(self): def _conclude_fft(self): # with FFT, np.float64 bit prescision required. r""" Calculates the MSD via the FCA fast correlation algorithm. - Parameters - ---------- - self._position_array : :class:`numpy.ndarray` - Array of particle positions with respect to time - shape (n_frames, n_particles, dim_fac). - - Returns - ------- - self.timeseries : :class:`numpy.ndarray` - The MSD as a function of lag-time. - """ try: import tidynamics From 18eb09c7f2e278cee29b26b77b23684489c1b03d Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Fri, 12 Jun 2020 10:33:33 +1000 Subject: [PATCH 117/121] fix docstrings and line conts --- package/MDAnalysis/analysis/msd.py | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index eb196fadbc2..73ee0952755 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -239,6 +239,20 @@ class EinsteinMSD(AnalysisBase): r"""Class to calculate Mean Squared Displacement by the Einstein relation. + Parameters + ---------- + u : Universe + An MDAnalysis :class:`Universe`. + select : str + A selection string. Defaults to `None` in which case + all atoms are selected. + msd_type : {'xyz', 'xy', 'yz', 'xz', 'x', 'y', 'z'} + Desired dimensions to be included in the MSD. Defaults to 'xyz'. + fft : bool + If ``True``, uses a fast FFT based algorithm for computation of + the MSD. Otherwise, use the simple "windowed" algorithm. + The tidynamics package is required for `fft=True`. + Defaults to ``True``. Attributes ---------- @@ -270,8 +284,7 @@ def __init__(self, u, select=None, msd_type='xyz', fft=True, **kwargs): If ``True``, uses a fast FFT based algorithm for computation of the MSD. Otherwise, use the simple "windowed" algorithm. The tidynamics package is required for `fft=True`. - Defaults to ``True``. - + Defaults to ``True``. """ self.u = u @@ -325,8 +338,8 @@ def _single_frame(self): """ # shape of position array set here, use span in last dimension # from this point on - self._position_array[self._frame_index] = - self._atoms.positions[:, self._dim] + self._position_array[self._frame_index] = ( + self._atoms.positions[:, self._dim]) def _conclude(self): if self.fft: @@ -340,8 +353,8 @@ def _conclude_simple(self): """ lagtimes = np.arange(1, self.n_frames) for lag in lagtimes: - disp = self._position_array[:-lag, :, :] - \ - self._position_array[lag:, :, :] + disp = (self._position_array[:-lag, :, :] - + self._position_array[lag:, :, :]) sqdist = np.square(disp, dtype=np.float64).sum( axis=-1, dtype=np.float64) # np.float64 required self.msds_by_particle[lag, :] = np.mean( From 353ae3ea025de7de9c2fe2660c898ed65e937f01 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Fri, 12 Jun 2020 10:51:04 +1000 Subject: [PATCH 118/121] remove extra dtypes --- package/MDAnalysis/analysis/msd.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 73ee0952755..59b394161b1 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -352,14 +352,12 @@ def _conclude_simple(self): """ lagtimes = np.arange(1, self.n_frames) + positions = self._position_array.astype(np.float64) for lag in lagtimes: - disp = (self._position_array[:-lag, :, :] - - self._position_array[lag:, :, :]) - sqdist = np.square(disp, dtype=np.float64).sum( - axis=-1, dtype=np.float64) # np.float64 required - self.msds_by_particle[lag, :] = np.mean( - sqdist, axis=0, dtype=np.float64) - self.timeseries = self.msds_by_particle.mean(axis=1, dtype=np.float64) + disp = positions[:-lag, :, :] - positions[lag:, :, :] + sqdist = np.square(disp).sum(axis=-1) + self.msds_by_particle[lag, :] = np.mean(sqdist, axis=0) + self.timeseries = self.msds_by_particle.mean(axis=1) def _conclude_fft(self): # with FFT, np.float64 bit prescision required. r""" Calculates the MSD via the FCA fast correlation algorithm. From 78911b8c636b329b0eb012e7a8887e74d56848df Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Fri, 12 Jun 2020 10:52:39 +1000 Subject: [PATCH 119/121] more dtypes removed --- package/MDAnalysis/analysis/msd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 59b394161b1..cc00aa77018 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -380,4 +380,4 @@ def _conclude_fft(self): # with FFT, np.float64 bit prescision required. for n in range(self.n_particles): self.msds_by_particle[:, n] = tidynamics.msd( positions[:, n, :]) - self.timeseries = self.msds_by_particle.mean(axis=1, dtype=np.float64) + self.timeseries = self.msds_by_particle.mean(axis=1) From 6513f8baabf880587436e65a78579691abd87561 Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Fri, 12 Jun 2020 14:54:53 +1000 Subject: [PATCH 120/121] remove select from kwargs --- package/MDAnalysis/analysis/msd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index cc00aa77018..d5484c281bf 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -269,7 +269,7 @@ class EinsteinMSD(AnalysisBase): """ - def __init__(self, u, select=None, msd_type='xyz', fft=True, **kwargs): + def __init__(self, u, select, msd_type='xyz', fft=True, **kwargs): r""" Parameters ---------- From 16dca6491e09b695efc99a0b6ee2e9c3b9fb29ae Mon Sep 17 00:00:00 2001 From: Hugo MacDermott-Opeskin Date: Sun, 14 Jun 2020 12:19:48 +1000 Subject: [PATCH 121/121] fix docs build error --- package/MDAnalysis/analysis/msd.py | 1 + 1 file changed, 1 insertion(+) diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index d5484c281bf..96202647271 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -239,6 +239,7 @@ class EinsteinMSD(AnalysisBase): r"""Class to calculate Mean Squared Displacement by the Einstein relation. + Parameters ---------- u : Universe