Skip to content

Conversation

@tanii1125
Copy link
Contributor

@tanii1125 tanii1125 commented Dec 2, 2025

This PR updates the MSD documentation inside MDAnalysis.analysis.msd to show users how to correctly apply the NoJump transformation using on-the-fly trajectory transformations.

Issue #4169 noted that although NoJump exists, there was no example showing how to use it directly in MDAnalysis.

This PR adds:

  • A minimal working example demonstrating NoJump usage
  • Clarification that wrapped coordinates must be unwrapped
  • A modern MDAnalysis-based workflow that does not require external tools

Fixes: #4169
I would appreciate any feedback or suggestions for improvement.
Reviews are very welcome, and I’m happy to revise the PR based on your guidance.


📚 Documentation preview 📚: https://mdanalysis--5165.org.readthedocs.build/en/5165/

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Hello there first time contributor! Welcome to the MDAnalysis community! We ask that all contributors abide by our Code of Conduct and that first time contributors introduce themselves on GitHub Discussions so we can get to know you. You can learn more about participating here. Please also add yourself to package/AUTHORS as part of this PR.

IAlibay
IAlibay previously requested changes Dec 2, 2025
Copy link
Member

@IAlibay IAlibay left a comment

Choose a reason for hiding this comment

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

Not reviewing the code but enforcing license - please don't change the license.

tanii1125 added a commit to tanii1125/mdanalysis that referenced this pull request Dec 2, 2025
@tanii1125
Copy link
Contributor Author

Thank you for pointing this out! I’ve updated the file to restore the correct license header (LGPL v2.1+).

@codecov
Copy link

codecov bot commented Dec 2, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.73%. Comparing base (a029dcd) to head (9a7e0d0).
⚠️ Report is 3 commits behind head on develop.

Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #5165      +/-   ##
===========================================
+ Coverage    92.72%   92.73%   +0.01%     
===========================================
  Files          180      180              
  Lines        22473    22475       +2     
  Branches      3189     3190       +1     
===========================================
+ Hits         20838    20843       +5     
+ Misses        1177     1175       -2     
+ Partials       458      457       -1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@tanii1125 tanii1125 requested a review from IAlibay December 3, 2025 18:08
@tanii1125 tanii1125 closed this Dec 7, 2025
@tanii1125 tanii1125 deleted the fix-msd-nojump-doc branch December 7, 2025 19:20
@tanii1125 tanii1125 restored the fix-msd-nojump-doc branch December 7, 2025 19:20
@tanii1125 tanii1125 reopened this Dec 8, 2025
Copy link
Member

@orbeckst orbeckst left a comment

Choose a reason for hiding this comment

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

Please demonstrate to yourself and your reviewers that your doc update is correct.

Comment on lines 77 to 91
.. code-block:: python
import MDAnalysis as mda
from MDAnalysis.transformations import NoJump
u = mda.Universe(TOP, TRAJ)
# Apply NoJump transformation to unwrap coordinates
nojump = NoJump(u)
u.trajectory.add_transformations(nojump)
# Now the trajectory is unwrapped and MSD can be computed normally:
from MDAnalysis.analysis.msd import EinsteinMSD
MSD = EinsteinMSD(u, select="all", msd_type="xyz")
MSD.run()
Copy link
Member

Choose a reason for hiding this comment

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

Please run code and post images of the output graph. Demonstrate to the reviewers that the example does what you say it is doing.

When you run code, show us the code that you're running, including the input files.

@orbeckst orbeckst self-assigned this Dec 12, 2025
@orbeckst orbeckst mentioned this pull request Dec 12, 2025
@tanii1125
Copy link
Contributor Author

tanii1125 commented Dec 13, 2025

@orbeckst

  • Added the example script used to verify the documentation change.
  • Updated the AUTHORS file to follow the existing formatting.
  • Updated the documentation to clarify that NoJump requires periodic
    boundary conditions, avoiding errors when PBC information is not present.

As input , I used the built-in MDAnalysis random walk test data:

  • RANDOM_WALK_TOPO
  • RANDOM_WALK

Below is the MSD plot produced by running the example script.
After applying NoJump, the MSD increases linearly with lag time, as
expected for normal diffusion, and follows the known 3D Brownian
behavior (≈ 6τ).

image

@tanii1125 tanii1125 requested a review from orbeckst December 13, 2025 14:19
Copy link
Member

@orbeckst orbeckst left a comment

Choose a reason for hiding this comment

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

Is RANDOM_WALK a good example, namely, did you check that it was actually generated with periodic boundary conditions?

In order to demonstrate that the example is working, I'd expect to see a comparison between omitting and using NoJump.

In MDAnalysis you can use the
:class:`~MDAnalysis.transformations.nojump.NoJump`
transformation.
:class:`~MDAnalysis.transformations.nojump.NoJump`
Copy link
Member

Choose a reason for hiding this comment

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

The indentation looks off. Please correct.

@tanii1125
Copy link
Contributor Author

tanii1125 commented Dec 17, 2025

Is RANDOM_WALK a good example, namely, did you check that it was actually generated with periodic boundary conditions?

In order to demonstrate that the example is working, I'd expect to see a comparison between omitting and using NoJump.

Thanks for the clarification,

After checking more carefully, I realized that:-

  • Relying on existing trajectories (e.g. RANDOM_WALK or other test data) is not reliable for demonstrating NoJump, since many of them are either not generated with periodic boundary conditions or do not exhibit explicit single-frame boundary crossings.

Instead of assuming wrapping is present in the input data, I will construct a deterministic test where a periodic box is defined and an artificial jump across the boundary is introduced between consecutive frames. This allows us to verify the intended behavior directly.

@tanii1125
Copy link
Contributor Author

tanii1125 commented Dec 17, 2025

@orbeckst
Without NoJump, periodic wrapping causes an artificial discontinuity; applying NoJump restores the continuous trajectory across the boundary.
Figure_1

the code i used to demonstrate this --

import numpy as np
import matplotlib.pyplot as plt
import MDAnalysis as mda
from MDAnalysis.coordinates.memory import MemoryReader
from MDAnalysis.transformations import NoJump


n_atoms = 1
n_frames = 2
box_length = 10.0  # cubic box, Å

pos0 = np.array([[9.8, 5.0, 5.0]])

pos1 = np.array([[0.2, 5.0, 5.0]])

coords = np.array([pos0, pos1])  # shape (n_frames, n_atoms, 3)

dimensions = np.array([
    [box_length, box_length, box_length, 90, 90, 90],
    [box_length, box_length, box_length, 90, 90, 90],
])

# ----------------------------
# 2. Universe WITHOUT NoJump
# ----------------------------

u_plain = mda.Universe.empty(
    n_atoms,
    trajectory=True,
)

u_plain.load_new(
    coords,
    format=MemoryReader,
    dimensions=dimensions,
)

x_plain = [u_plain.atoms.positions[0,0] for _ in u_plain.trajectory]

# ----------------------------
# 3. Universe WITH NoJump
# ----------------------------

u_nj = mda.Universe.empty(
    n_atoms,
    trajectory=True,
)

u_nj.load_new(
    coords,
    format=MemoryReader,
    dimensions=dimensions,
)

u_nj.trajectory.add_transformations(NoJump())

x_nj = [u_nj.atoms.positions[0,0] for _ in u_nj.trajectory]

# ----------------------------
# 4. Plot
# ----------------------------

frames=[0,1]

plt.figure()
plt.plot(frames, x_plain, marker='o', label="Without nojump")
plt.plot(frames, x_nj,marker='o', label="With nojump")

plt.axhline(box_length, linestyle='--', linewidth=1)
plt.axhline(0, linestyle='--', linewidth=1)

plt.xlabel("Frame")
plt.ylabel("x position (A)")
plt.title("Effect of NoJump on Periodic Boundary Crossing")
plt.legend()
plt.show()

@tanii1125 tanii1125 requested a review from orbeckst December 17, 2025 14:20
Copy link
Member

@orbeckst orbeckst left a comment

Choose a reason for hiding this comment

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

Thank you for testing explicitly. This looks good.

I added a note on setdimensions (see comments). Could you please check that this works as intended (post code and results). Thank you!

tanii1125 and others added 2 commits January 6, 2026 23:05
Co-authored-by: Oliver Beckstein <orbeckst@gmail.com>
Co-authored-by: Oliver Beckstein <orbeckst@gmail.com>
@tanii1125
Copy link
Contributor Author

example

I verified this workflow with a minimal in-memory example starting from a Universe without initial box dimensions. Applying
set_dimensions([...]) followed by NoJump(u) works as intended and correctly unwraps the coordinates.

Code that I used to verify behaviour-

import numpy as np
import matplotlib.pyplot as plt
import MDAnalysis as mda
from MDAnalysis.coordinates.memory import MemoryReader
from MDAnalysis.transformations import NoJump
from MDAnalysis.transformations.boxdimensions import set_dimensions


n_atoms = 1
n_frames = 2
box_length = 10.0  # cubic box, Å

pos0 = np.array([[9.8, 5.0, 5.0]])

pos1 = np.array([[0.2, 5.0, 5.0]])

coords = np.array([pos0, pos1])  # shape (n_frames, n_atoms, 3)

u_nj = mda.Universe.empty(n_atoms, trajectory=True)

u_nj.load_new(
    coords,
    format=MemoryReader,
)  # NO dimensions 

# transformations
u_nj.trajectory.add_transformations(
    set_dimensions([box_length, box_length, box_length, 90, 90, 90]),
    NoJump(u_nj),
)
x_nj = []
for ts in u_nj.trajectory:
    x_nj.append(u_nj.atoms.positions[0, 0])

# ----------------------------
# 2. Universe WITHOUT NoJump
# ----------------------------

u_plain = mda.Universe.empty(
    n_atoms,
    trajectory=True,
)

u_plain.load_new(
    coords,
    format=MemoryReader,
) 

u_plain.trajectory.add_transformations(
    set_dimensions([box_length, box_length, box_length, 90, 90, 90]),
)

x_plain = []
for ts in u_plain.trajectory:
    x_plain.append(u_plain.atoms.positions[0, 0])

# ----------------------------
# 4. Plot
# ----------------------------

frames = list(range(len(x_plain)))

plt.figure()
plt.plot(frames, x_plain, marker='o', label="set_dim only")
plt.plot(frames, x_nj,marker='o', label="set_dim With nojump")


plt.xlabel("Frame")
plt.ylabel("x position (A)")
plt.title("Effect of NoJump on Periodic Boundary Crossing")
plt.legend()
plt.show()

@tanii1125 tanii1125 requested a review from orbeckst January 6, 2026 18:52
Copy link
Member

@orbeckst orbeckst left a comment

Choose a reason for hiding this comment

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

Thanks for checking that set_dimensions, NoJump is working in a simple example without MSD.

You haven't demonstrated yet that it works with MSD but you should now know enough about transformations and PBC to create a proper test case.

My recommendation would be to take the existing RANDOM_WALK trajectory and wrap it in a small box (figure out reasonable dimensions by analyzing the random walk) using wrap and write it to a new file. Then use this file as input for MSD (1) with NoJump, (2) without NoJump and compare as before.

@tanii1125
Copy link
Contributor Author

tanii1125 commented Jan 7, 2026

Got it, I used RANDOM_WALK and wrapped it into a small periodic box and wrote the resulting coordinates to random_walk_wrapped.xtc. This file therefore contains wrapped coordinates and represents a realistic user input trajectory.
Then I used it to demonstrate plot: 1)Without NoJump 2)With NoJump.
Here is the code I used and the resulting plot -

Code-

import numpy as np
import matplotlib.pyplot as plt
import MDAnalysis as mda

from MDAnalysis.tests.datafiles import RANDOM_WALK, RANDOM_WALK_TOPO
from MDAnalysis.transformations import NoJump, wrap
from MDAnalysis.transformations.boxdimensions import set_dimensions
from MDAnalysis.analysis.msd import EinsteinMSD
from MDAnalysis.coordinates.XTC import XTCWriter


# ----------------------------
# create a input file
# ----------------------------

box = [20.0, 20.0, 20.0, 90.0, 90.0, 90.0]
u = mda.Universe(RANDOM_WALK_TOPO, RANDOM_WALK)

u.trajectory.add_transformations(
    set_dimensions(box),
    wrap(u.atoms),
)

with XTCWriter("random_walk_wrapped.xtc",u.atoms.n_atoms) as W:
    for ts in u.trajectory:
        W.write(u.atoms)

# # ----------------------------
# # 1. MSD WITHOUT NoJump
# # ----------------------------

u= mda.Universe(RANDOM_WALK_TOPO,"random_walk_wrapped.xtc")

msd_wrapped = EinsteinMSD(
    u,
    select="all",
    msd_type="xyz",
    fft=False,
)

msd_wrapped.run()

t = msd_wrapped.results.delta_t_values
msd_nojump = msd_wrapped.results.timeseries.copy()

# # ----------------------------
# # 2. MSD WITH NoJump
# # ----------------------------

# # Reload universe to avoid transformation stacking
u = mda.Universe(RANDOM_WALK_TOPO, "random_walk_wrapped.xtc")

u.trajectory.add_transformations(
    NoJump(u),
)

msd_unwrapped = EinsteinMSD(
    u,
    select="all",
    msd_type="xyz",
    fft=False,
)
msd_unwrapped.run()

msd_with_nojump = msd_unwrapped.results.timeseries

# # ----------------------------
# # 3. Plot comparison
# # ----------------------------

plt.figure(figsize=(6, 4))
plt.plot(t, msd_nojump, label="Wrapped (no NoJump)")
plt.plot(t, msd_with_nojump, label="Unwrapped (with NoJump)")
plt.xlabel("Lag time")
plt.ylabel("MSD")
plt.legend()
plt.title("Effect of NoJump on MSD for RANDOM_WALK")
plt.tight_layout()
plt.show()

Output--
to_give

@tanii1125 tanii1125 requested a review from orbeckst January 7, 2026 06:51
Copy link
Member

@orbeckst orbeckst left a comment

Choose a reason for hiding this comment

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

Thank you for checking. This looks good!

I'll fix an indentation problem and then will merge.

@orbeckst
Copy link
Member

orbeckst commented Jan 7, 2026

@IAlibay do you want to have a look here or can I just dismiss your review as stale? There are no changes to the license in the PR.

@orbeckst
Copy link
Member

orbeckst commented Jan 7, 2026

@tanii1125 is your name already in the AUTHORS file? If not, please add it.

@IAlibay
Copy link
Member

IAlibay commented Jan 7, 2026

Please discard my review, thanks!

@tanii1125
Copy link
Contributor Author

@orbeckst, yes my name is in AUTHORS file

@orbeckst orbeckst dismissed IAlibay’s stale review January 7, 2026 17:47

concern addressed, reviewer ok with dismissal

@orbeckst orbeckst merged commit 528b512 into MDAnalysis:develop Jan 7, 2026
24 checks passed
@orbeckst
Copy link
Member

orbeckst commented Jan 7, 2026

Thank you for your contribution @tanii1125 !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Documentation Improvement for Mean Squared Displacement

3 participants