Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
60973ba
Inditial addition of code to calculate time-averaged msd for non-line…
gitsirsha Jun 13, 2025
0dc3db1
updated_documentation_v1_plots_removed
gitsirsha Jun 13, 2025
f6f55a1
Added_name_in_the_package/AUTHORS
gitsirsha Jun 15, 2025
e363635
addressed_review_comments_mainly_removed_linearity_check
gitsirsha Jun 30, 2025
5aa09ca
added results delta t values
gitsirsha Jul 14, 2025
871faa2
initial test added changed changelog and docs
gitsirsha Jul 22, 2025
00fc309
added LAMMPSDUMP_non_linear to __all__
gitsirsha Jul 22, 2025
c90f52e
ran black 24.10.0
gitsirsha Jul 22, 2025
7ad3e09
Revert "ran black 24.10.0"
gitsirsha Jul 22, 2025
b1ee485
added test for all msd types - added test with start stop step
gitsirsha Jul 28, 2025
2bbdffb
ran black 24.4.2
gitsirsha Jul 28, 2025
ee764b7
ran black on datafiles.py
gitsirsha Jul 28, 2025
0f50810
cleanup - removed docs and edited formatting
gitsirsha Jul 28, 2025
7b16b9f
added results.msds_by_particle for non_linear method
gitsirsha Jul 29, 2025
72b3dd3
minor chancge - versionadded to versionchanged
gitsirsha Jul 29, 2025
e367984
dtype removal and cleanup misplaced strings
gitsirsha Jul 30, 2025
069bec5
Added entry to project.toml
gitsirsha Jul 31, 2025
ac4b4b2
Attribute definition edit for cases non_linear : bool
gitsirsha Aug 1, 2025
3ceca5c
Indentation edit
gitsirsha Aug 1, 2025
1a24e36
update warning in msd docs
orbeckst Aug 1, 2025
daad519
Testing doc formatting, WIP
gitsirsha Aug 14, 2025
29df80e
Merge branch 'develop' into feature-Non-linear-time-averaged-msd
gitsirsha Aug 15, 2025
b578a67
Update self.results.delta_t_values #1
gitsirsha Aug 16, 2025
e26f53a
Update self.results.delta_t_values #2
gitsirsha Aug 16, 2025
125e710
Update self.results.delta_t_values #3
gitsirsha Aug 16, 2025
498a061
weird formatting edits
gitsirsha Aug 16, 2025
788758d
removed dump_times
gitsirsha Aug 16, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package/AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ Chronological list of authors
- Tulga-Erdene Sodjargal
- Gareth Elliott
- Marc Schuh

- Sirsha Ganguly

External code
-------------
Expand Down
3 changes: 3 additions & 0 deletions package/CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ Fixes
directly passing them. (Issue #3520, PR #5006)

Enhancements
* Added capability to calculate MSD from frames with irregular (non-linear)
time spacing in analysis.msd.EinsteinMSD with keyword argument
`non_linear=True` (Issue #5028, PR #5066)
* Added `TemplateInferrer` and `RDKitInferrer` dataclasses to the
`RDKitInferring` module to be used by the RDKit converter. (PR #4305)
* Improve speed of GROMOS11 (TRC) reader (Issue #5079, PR #5080)
Expand Down
111 changes: 86 additions & 25 deletions package/MDAnalysis/analysis/msd.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,7 @@

msd = MSD.results.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.

.. code-block:: python

Expand Down Expand Up @@ -173,7 +172,7 @@
start_index = int(start_time/timestep)
end_time = 60
linear_model = linregress(lagtimes[start_index:end_index],
msd[start_index:end_index])
msd[start_index:end_index])
slope = linear_model.slope
error = linear_model.stderr
# dim_fac is 3 as we computed a 3D msd with 'xyz'
Expand Down Expand Up @@ -245,6 +244,7 @@
from .base import AnalysisBase
from ..core import groups
from tqdm import tqdm
import collections

logger = logging.getLogger("MDAnalysis.analysis.msd")

Expand Down Expand Up @@ -281,15 +281,34 @@ class EinsteinMSD(AnalysisBase):
the MSD. Otherwise, use the simple "windowed" algorithm.
The tidynamics package is required for `fft=True`.
Defaults to ``True``.
non_linear : bool
If ``True``, calculates MSD for trajectory where frames are
non-linearly dumped. To use this set `fft=False`.
Defaults to ``False``.

.. versionadded:: 2.10.0


Attributes
----------
dim_fac : int
Dimensionality :math:`d` of the MSD.
results.timeseries : :class:`numpy.ndarray`
The averaged MSD over all the particles with respect to lag-time.
The averaged MSD over all the particles with respect to constant lag-time or
unique Δt intervals.
results.msds_by_particle : :class:`numpy.ndarray`
The MSD of each individual particle with respect to lag-time.
The MSD of each individual particle with respect to constant lag-time or
unique Δt intervals.
for `non_linear=False`: a 2D array of shape (n_lagtimes, n_atoms)
for `non_linear=True`: a 2D array of shape (n_delta_t_values, n_atoms)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The formatting does not look good when rendered, see MDAnalysis.analysis.msd.EinsteinMSD.results.msds_by_particle in the docs. Please change the formatting so that it makes sense to a reader.

Always look at the rendered docs. You can read more about reST formatting with sphinx in the sphinx reST primer and accompanying docs.

image

results.delta_t_values : :class:`numpy.ndarray`
Array of unique Δt (time differences) at which time-averaged MSD values are
computed (for `non_linear=True`).
For `non_linear=False` it is Null array. Time differences are same as the lag-times.
(To access: `lagtimes = np.arange(nframes)*timestep`).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's change this so that it is also correct in non_linear=False, i.e., never a zeros array.

The docs manually compute lagtimes, which is error prone. Now that we have a reason to have the lagtimes in the class, let's make them available for everything.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please point me to the file from where I can edit the example sections in the docs. I believe it is not in the msd.py. I can delete the "lagtimes" computing line. Apologies for the delay, and I appreciate your patience on this PR.
image

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's in msd.py

lagtimes = np.arange(nframes)*timestep # make the lag-time axis

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! Sorry for my oversight.


.. versionadded:: 2.10.0

ag : :class:`AtomGroup`
The :class:`AtomGroup` resulting from your selection
n_frames : int
Expand All @@ -299,27 +318,23 @@ class EinsteinMSD(AnalysisBase):


.. versionadded:: 2.0.0
.. versionchanged:: 2.10.0
Added ability to calculate MSD from samples that are not linearly spaced with the
new `non_linear` keyword argument.
"""

def __init__(self, u, select="all", msd_type="xyz", fft=True, **kwargs):
r"""
Parameters
----------
u : Universe or AtomGroup
An MDAnalysis :class:`Universe` or :class:`AtomGroup`.
select : str
A selection string. Defaults to "all" in which case
all atoms are selected.
msd_type : {'xyz', 'xy', 'yz', 'xz', 'x', 'y', 'z'}
Desired dimensions to be included in the MSD.
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`.
"""
def __init__(
self,
u,
select="all",
msd_type="xyz",
fft=True,
non_linear=False,
**kwargs,
):
if isinstance(u, groups.UpdatingAtomGroup):
raise TypeError(
"UpdatingAtomGroups are not valid for MSD " "computation"
"UpdatingAtomGroups are not valid for MSD computation"
)

super(EinsteinMSD, self).__init__(u.universe.trajectory, **kwargs)
Expand All @@ -329,6 +344,7 @@ def __init__(self, u, select="all", msd_type="xyz", fft=True, **kwargs):
self.msd_type = msd_type
self._parse_msd_type()
self.fft = fft
self.non_linear = non_linear

# local
self.ag = u.select_atoms(self.select)
Expand All @@ -338,6 +354,7 @@ def __init__(self, u, select="all", msd_type="xyz", fft=True, **kwargs):
# result
self.results.msds_by_particle = None
self.results.timeseries = None
self.results.delta_t_values = None

def _prepare(self):
# self.n_frames only available here
Expand Down Expand Up @@ -383,10 +400,13 @@ def _single_frame(self):
]

def _conclude(self):
if self.fft:
self._conclude_fft()
if self.non_linear:
self._conclude_non_linear()
else:
self._conclude_simple()
if self.fft:
self._conclude_fft()
else:
self._conclude_simple()

def _conclude_simple(self):
r"""Calculates the MSD via the simple "windowed" algorithm."""
Expand Down Expand Up @@ -421,3 +441,44 @@ def _conclude_fft(self): # with FFT, np.float64 bit prescision required.
positions[:, n, :]
)
self.results.timeseries = self.results.msds_by_particle.mean(axis=1)

def _conclude_non_linear(self):

dump_times = self.times
n_frames = self.n_frames
n_atoms = self.n_particles
positions = self._position_array.astype(np.float64)

msd_dict = collections.defaultdict(
list
) # Dictionary to collect MSDs: {Δt: [msd1, msd2, ...]}
msds_by_particle_dict = collections.defaultdict(list)

# TODO: optimize the code
# Looping over all the frames as if the referenced gets shifted frame to frame
for i in range(n_frames):
for j in range(i + 1, n_frames):
delta_t = dump_times[j] - dump_times[i]
# Compute displacement and squared displacement
disp = positions[j] - positions[i]
squared_disp = np.sum(disp**2, axis=1)
msd = np.mean(squared_disp)
# Store MSD under corresponding Δt
msd_dict[delta_t].append(msd)
msds_by_particle_dict[delta_t].append(squared_disp)

msd_dict[0] = [0]
msds_by_particle_dict[0.0] = [np.zeros(n_atoms)]

# For each delta_t, stacked all squared_disp arrays and averaging over axis=0 (time origins)
delta_t_values = sorted(msd_dict.keys())
avg_msds = [np.mean(msd_dict[dt]) for dt in delta_t_values]
msds_by_particle_array = np.zeros((len(delta_t_values), n_atoms))
for idx, dt in enumerate(delta_t_values):
# Stack list of arrays like -- (n_time_origins, n_atoms)
arr = np.vstack(msds_by_particle_dict[dt])
msds_by_particle_array[idx, :] = np.mean(arr, axis=0)

self.results.timeseries = np.array(avg_msds)
self.results.delta_t_values = np.array(delta_t_values)
self.results.msds_by_particle = msds_by_particle_array
Loading
Loading