-
Notifications
You must be signed in to change notification settings - Fork 723
Feature non linear time averaged msd #5066
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 7 commits
60973ba
0dc3db1
f6f55a1
e363635
5aa09ca
871faa2
00fc309
c90f52e
7ad3e09
b1ee485
2bbdffb
ee764b7
0f50810
7b16b9f
72b3dd3
e367984
069bec5
ac4b4b2
3ceca5c
1a24e36
daad519
29df80e
b578a67
e26f53a
125e710
498a061
788758d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
gitsirsha marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -173,7 +173,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]) | ||||||||||||||
gitsirsha marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||
slope = linear_model.slope | ||||||||||||||
error = linear_model.stderr | ||||||||||||||
# dim_fac is 3 as we computed a 3D msd with 'xyz' | ||||||||||||||
|
@@ -245,6 +245,7 @@ | |||||||||||||
from .base import AnalysisBase | ||||||||||||||
from ..core import groups | ||||||||||||||
from tqdm import tqdm | ||||||||||||||
import collections | ||||||||||||||
|
||||||||||||||
logger = logging.getLogger("MDAnalysis.analysis.msd") | ||||||||||||||
|
||||||||||||||
|
@@ -281,6 +282,10 @@ 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``. | ||||||||||||||
orbeckst marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||
|
||||||||||||||
Attributes | ||||||||||||||
---------- | ||||||||||||||
|
@@ -290,6 +295,8 @@ class EinsteinMSD(AnalysisBase): | |||||||||||||
The averaged MSD over all the particles with respect to lag-time. | ||||||||||||||
results.msds_by_particle : :class:`numpy.ndarray` | ||||||||||||||
The MSD of each individual particle with respect to lag-time. | ||||||||||||||
orbeckst marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||
results.delta_t_values : :class:`numpy.ndarray` | ||||||||||||||
Array of unique Δt (time differences) at which time-averaged MSD values are computed. | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Indicate when this new results attribute was added so that users understand which version of the code they need
Suggested change
|
||||||||||||||
ag : :class:`AtomGroup` | ||||||||||||||
The :class:`AtomGroup` resulting from your selection | ||||||||||||||
n_frames : int | ||||||||||||||
|
@@ -298,10 +305,12 @@ class EinsteinMSD(AnalysisBase): | |||||||||||||
Number of particles MSD was calculated over. | ||||||||||||||
|
||||||||||||||
|
||||||||||||||
.. versionadded:: 2.0.0 | ||||||||||||||
orbeckst marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||
.. versionadded:: 2.10.0 | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just FYI: Btw, if a review added something as a suggestion, you can just directly commit the suggestion or add multiple suggestions to a batch and then commit the batch, all through the GH web interface. This typically saves you time. (Generally, "suggestion" really means "this needs to be addressed, either you take it directly or you write it yourself". In some cases it is meant as a point for discussion but these cases should be clear from the context.)
gitsirsha marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||
""" | ||||||||||||||
|
||||||||||||||
def __init__(self, u, select="all", msd_type="xyz", fft=True, **kwargs): | ||||||||||||||
def __init__( | ||||||||||||||
self, u, select="all", msd_type="xyz", fft=True, non_linear=False, **kwargs | ||||||||||||||
): | ||||||||||||||
r""" | ||||||||||||||
Parameters | ||||||||||||||
---------- | ||||||||||||||
|
@@ -316,11 +325,12 @@ def __init__(self, u, select="all", 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`. | ||||||||||||||
non_linear : bool | ||||||||||||||
gitsirsha marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||
If ``True``, calculates MSD for trajectory where frames are | ||||||||||||||
non-linearly dumped. To use this set `fft=False`. | ||||||||||||||
""" | ||||||||||||||
if isinstance(u, groups.UpdatingAtomGroup): | ||||||||||||||
raise TypeError( | ||||||||||||||
"UpdatingAtomGroups are not valid for MSD " "computation" | ||||||||||||||
) | ||||||||||||||
raise TypeError("UpdatingAtomGroups are not valid for MSD " "computation") | ||||||||||||||
|
||||||||||||||
super(EinsteinMSD, self).__init__(u.universe.trajectory, **kwargs) | ||||||||||||||
|
||||||||||||||
|
@@ -329,6 +339,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) | ||||||||||||||
|
@@ -338,16 +349,13 @@ 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 | ||||||||||||||
orbeckst marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||
|
||||||||||||||
def _prepare(self): | ||||||||||||||
# self.n_frames only available here | ||||||||||||||
# these need to be zeroed prior to each run() call | ||||||||||||||
self.results.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.results.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.results.timeseries not set here | ||||||||||||||
|
||||||||||||||
def _parse_msd_type(self): | ||||||||||||||
|
@@ -378,15 +386,16 @@ def _single_frame(self): | |||||||||||||
r"""Constructs array of positions for MSD calculation.""" | ||||||||||||||
# shape of position array set here, use span in last dimension | ||||||||||||||
# from this point on | ||||||||||||||
self._position_array[self._frame_index] = self.ag.positions[ | ||||||||||||||
:, self._dim | ||||||||||||||
] | ||||||||||||||
self._position_array[self._frame_index] = self.ag.positions[:, self._dim] | ||||||||||||||
|
||||||||||||||
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.""" | ||||||||||||||
|
@@ -417,7 +426,43 @@ def _conclude_fft(self): # with FFT, np.float64 bit prescision required. | |||||||||||||
|
||||||||||||||
positions = self._position_array.astype(np.float64) | ||||||||||||||
for n in tqdm(range(self.n_particles)): | ||||||||||||||
self.results.msds_by_particle[:, n] = tidynamics.msd( | ||||||||||||||
positions[:, n, :] | ||||||||||||||
) | ||||||||||||||
self.results.msds_by_particle[:, n] = tidynamics.msd(positions[:, n, :]) | ||||||||||||||
self.results.timeseries = self.results.msds_by_particle.mean(axis=1) | ||||||||||||||
|
||||||||||||||
def _conclude_non_linear(self): | ||||||||||||||
|
||||||||||||||
dump_times = self.times | ||||||||||||||
gitsirsha marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||
n_frames = self.n_frames | ||||||||||||||
n_atoms = self.n_particles | ||||||||||||||
positions = self._position_array.astype(np.float64) | ||||||||||||||
orbeckst marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||
|
||||||||||||||
msd_dict = collections.defaultdict( | ||||||||||||||
list | ||||||||||||||
) # Dictionary to collect MSDs: {Δt: [msd1, msd2, ...]} | ||||||||||||||
gitsirsha marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||
|
||||||||||||||
# Looping over all the frames as if the referenced gets shifted frame to frame | ||||||||||||||
orbeckst marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||
for i in range(n_frames): | ||||||||||||||
for j in range(i + 1, n_frames): | ||||||||||||||
delta_t = dump_times[j] - dump_times[i] | ||||||||||||||
gitsirsha marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||
|
||||||||||||||
# 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) | ||||||||||||||
|
||||||||||||||
msd_dict[0] = [0] | ||||||||||||||
|
||||||||||||||
# Prepare averaged results | ||||||||||||||
delta_t_values = [] | ||||||||||||||
avg_msds = [] | ||||||||||||||
for delta_t in sorted(msd_dict.keys()): | ||||||||||||||
msd_list = msd_dict[delta_t] | ||||||||||||||
avg_msd = np.mean(msd_list) | ||||||||||||||
delta_t_values.append(delta_t) | ||||||||||||||
avg_msds.append(avg_msd) | ||||||||||||||
|
||||||||||||||
self.results.timeseries = np.array(avg_msds, dtype=np.float64) | ||||||||||||||
self.results.delta_t_values = np.array(delta_t_values, dtype=np.float64) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,7 +28,13 @@ | |
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 MDAnalysisTests.datafiles import ( | ||
PSF, | ||
DCD, | ||
RANDOM_WALK, | ||
RANDOM_WALK_TOPO, | ||
LAMMPSDUMP_non_linear, | ||
) | ||
from MDAnalysisTests.util import block_import, import_not_available | ||
|
||
import pytest | ||
|
@@ -124,9 +130,7 @@ def test_msdtype_error(self, u, SELECTION, msdtype): | |
("z", 1), | ||
], | ||
) | ||
def test_simple_step_traj_all_dims( | ||
self, step_traj, NSTEP, dim, dim_factor | ||
): | ||
def test_simple_step_traj_all_dims(self, 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) | ||
|
@@ -146,18 +150,14 @@ def test_simple_step_traj_all_dims( | |
("z", 1), | ||
], | ||
) | ||
def test_simple_start_stop_step_all_dims( | ||
self, step_traj, NSTEP, dim, dim_factor | ||
): | ||
def test_simple_start_stop_step_all_dims(self, 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) | ||
m_simple.run(start=10, stop=1000, step=10) | ||
poly = characteristic_poly(NSTEP, dim_factor) | ||
# polynomial must take offset start into account | ||
assert_almost_equal( | ||
m_simple.results.timeseries, poly[0:990:10], decimal=4 | ||
) | ||
assert_almost_equal(m_simple.results.timeseries, poly[0:990:10], decimal=4) | ||
|
||
def test_random_walk_u_simple(self, random_walk_u): | ||
# regress against random_walk test data | ||
|
@@ -252,18 +252,14 @@ def test_fft_step_traj_all_dims(self, step_traj, NSTEP, dim, dim_factor): | |
("z", 1), | ||
], | ||
) | ||
def test_fft_start_stop_step_all_dims( | ||
self, step_traj, NSTEP, dim, dim_factor | ||
): | ||
def test_fft_start_stop_step_all_dims(self, 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) | ||
m_simple.run(start=10, stop=1000, step=10) | ||
poly = characteristic_poly(NSTEP, dim_factor) | ||
# polynomial must take offset start into account | ||
assert_almost_equal( | ||
m_simple.results.timeseries, poly[0:990:10], decimal=3 | ||
) | ||
assert_almost_equal(m_simple.results.timeseries, poly[0:990:10], decimal=3) | ||
|
||
def test_random_walk_u_fft(self, random_walk_u): | ||
# regress against random_walk test data | ||
|
@@ -272,3 +268,144 @@ def test_random_walk_u_fft(self, random_walk_u): | |
norm = np.linalg.norm(msd_rw.results.timeseries) | ||
val = 3932.39927487146 | ||
assert_almost_equal(norm, val, decimal=5) | ||
|
||
|
||
def test_msd_non_linear(): | ||
u = mda.Universe(LAMMPSDUMP_non_linear, format="LAMMPSDUMP") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Make the universe a fixture, e.g. |
||
|
||
msd = MSD(u, select="all", msd_type="xyz", non_linear=True) | ||
msd.run() | ||
|
||
result_msd = msd.results.timeseries | ||
result_delta_t = msd.results.delta_t_values | ||
|
||
assert result_msd.ndim == 1 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you assert shape itself then you don't need to check ndim. |
||
assert result_msd.shape[0] > 0 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. assert the exact value that's specific for your test data There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, check the full shape
or something similar. |
||
|
||
assert result_delta_t.ndim == 1 | ||
assert result_delta_t.shape[0] > 0 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. assert the exact value that's specific for your test data |
||
|
||
expected_msd = np.array( | ||
[ | ||
0.00000000e00, | ||
7.70976963e-05, | ||
2.90842662e-04, | ||
6.55040347e-04, | ||
1.20610926e-03, | ||
2.52547250e-03, | ||
3.31645965e-03, | ||
5.38852795e-03, | ||
1.01941562e-02, | ||
1.24745603e-02, | ||
1.35380300e-02, | ||
1.57475527e-02, | ||
2.85165801e-02, | ||
3.50591021e-02, | ||
3.81292797e-02, | ||
3.96176470e-02, | ||
3.83551274e-02, | ||
5.51041371e-02, | ||
5.95049433e-02, | ||
6.07026502e-02, | ||
6.14434181e-02, | ||
6.19512436e-02, | ||
6.61293773e-02, | ||
9.46607497e-02, | ||
1.01300585e-01, | ||
9.96583811e-02, | ||
9.81112279e-02, | ||
9.72780657e-02, | ||
9.69221886e-02, | ||
1.29442431e-01, | ||
1.80752226e-01, | ||
1.86358673e-01, | ||
1.98140564e-01, | ||
2.00603000e-01, | ||
1.99094789e-01, | ||
1.97272787e-01, | ||
1.96156023e-01, | ||
2.67664446e-01, | ||
4.50987076e-01, | ||
4.02344442e-01, | ||
3.91458056e-01, | ||
4.10370922e-01, | ||
4.22997445e-01, | ||
4.26217251e-01, | ||
4.26484034e-01, | ||
4.26360794e-01, | ||
6.91315347e-01, | ||
9.94317423e-01, | ||
1.19622365e00, | ||
1.04919180e00, | ||
1.06437594e00, | ||
1.09426432e00, | ||
1.10194082e00, | ||
1.10275424e00, | ||
1.10383947e00, | ||
1.10493159e00, | ||
] | ||
) | ||
|
||
expected_delta_t = np.array( | ||
[ | ||
0.000e00, | ||
1.000e00, | ||
2.000e00, | ||
3.000e00, | ||
4.000e00, | ||
6.000e00, | ||
7.000e00, | ||
8.000e00, | ||
1.200e01, | ||
1.400e01, | ||
1.500e01, | ||
1.600e01, | ||
2.400e01, | ||
2.800e01, | ||
3.000e01, | ||
3.100e01, | ||
3.200e01, | ||
4.800e01, | ||
5.600e01, | ||
6.000e01, | ||
6.200e01, | ||
6.300e01, | ||
6.400e01, | ||
9.600e01, | ||
1.120e02, | ||
1.200e02, | ||
1.240e02, | ||
1.260e02, | ||
1.270e02, | ||
1.280e02, | ||
1.920e02, | ||
2.240e02, | ||
2.400e02, | ||
2.480e02, | ||
2.520e02, | ||
2.540e02, | ||
2.550e02, | ||
2.560e02, | ||
3.840e02, | ||
4.480e02, | ||
4.800e02, | ||
4.960e02, | ||
5.040e02, | ||
5.080e02, | ||
5.100e02, | ||
5.110e02, | ||
5.120e02, | ||
7.680e02, | ||
8.960e02, | ||
9.600e02, | ||
9.920e02, | ||
1.008e03, | ||
1.016e03, | ||
1.020e03, | ||
1.022e03, | ||
1.023e03, | ||
] | ||
) | ||
|
||
np.testing.assert_allclose(result_msd, expected_msd, rtol=1e-5) | ||
np.testing.assert_allclose(result_delta_t, expected_delta_t, rtol=1e-5) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. At the top of the file, from numpy.testing import assert_allclose and then use |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Focus on the user-visible changes and keep it short
Also, traditionally we add the newest changes under the heading, i.e., move it to directly under "Enhancements"