Skip to content

Commit

Permalink
Merge pull request #13 from Becksteinlab/develop
Browse files Browse the repository at this point in the history
Overhaul
  • Loading branch information
ljwoods2 authored Sep 23, 2024
2 parents 7e7d3e7 + 93ae9df commit 854c77e
Show file tree
Hide file tree
Showing 11 changed files with 599 additions and 49 deletions.
5 changes: 5 additions & 0 deletions .github/workflows/gromacs-integration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ jobs:
install-tests: true
installer: conda
shell: bash -l {0}

- name: Install package
run: |
python --version
python -m pip install . --no-deps
- name: Python information
run: |
Expand Down
6 changes: 5 additions & 1 deletion .github/workflows/lammps-integration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,18 @@ jobs:
show-channel-urls: true
miniconda-version: latest


- name: Install MDAnalysis version
uses: MDAnalysis/install-mdanalysis@main
with:
version: "2.7.0"
install-tests: true
installer: conda
shell: bash -l {0}

- name: Install package
run: |
python --version
python -m pip install . --no-deps
- name: Python information
run: |
Expand Down
7 changes: 6 additions & 1 deletion .github/workflows/namd-integration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ jobs:
installer: conda
shell: bash -l {0}

- name: Install package
run: |
python --version
python -m pip install . --no-deps
- name: Python information
run: |
which python
Expand All @@ -68,7 +73,7 @@ jobs:

- name: Build namd
run: |
cd namd/namd-3.0
cd namd
tar xf charm-8.0.0.tar
cd charm-8.0.0
./build charm++ ucx-linux-x86_64 ompipmix --with-production
Expand Down
14 changes: 6 additions & 8 deletions imdclient/IMDClient.py
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,6 @@ def run(self):
logger.debug("IMDProducer: Stopping run loop")
# Tell consumer not to expect more frames to be added
self._buf.notify_producer_finished()
return

def _expect_header(self, expected_type, expected_value=None):

Expand Down Expand Up @@ -564,6 +563,12 @@ def _parse_imdframe(self):
logger.debug(
f"IMDProducer: Time: {self._imdf.time}, dt: {self._imdf.dt}, step: {self._imdf.step}"
)
if self._imdsinfo.energies:
self._expect_header(IMDHeaderType.IMD_ENERGIES, expected_value=1)
self._read(self._energies)
self._imdf.energies.update(
parse_energy_bytes(self._energies, self._imdsinfo.endianness)
)
if self._imdsinfo.box:
self._expect_header(IMDHeaderType.IMD_BOX, expected_value=1)
self._read(self._box)
Expand Down Expand Up @@ -604,13 +609,6 @@ def _parse_imdframe(self):
).reshape((self._n_atoms, 3)),
)

if self._imdsinfo.energies:
self._expect_header(IMDHeaderType.IMD_ENERGIES, expected_value=1)
self._read(self._energies)
self._imdf.energies.update(
parse_energy_bytes(self._energies, self._imdsinfo.endianness)
)

def __del__(self):
logger.debug("IMDProducer: I am being deleted")

Expand Down
1 change: 1 addition & 0 deletions imdclient/IMDREADER.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ class IMDReader(ReaderBase):
"""

format = "IMD"
one_pass = True

@store_init_arguments
def __init__(
Expand Down
Binary file modified imdclient/data/lammps_trj.h5md
Binary file not shown.
4 changes: 2 additions & 2 deletions imdclient/data/lammps_v3.in
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,11 @@ fix 1 all nve

# Create source of truth trajectory
# dump h5md1 all h5md 1 lammps_trj.h5md position velocity force box yes
# dump_modify h5md1 unwrap on
# dump_modify h5md1 unwrap no

## IMD settings
# https://docs.lammps.org/fix_imd.html
fix 2 all imd 8888 version 3 unwrap on nowait off
fix 2 all imd 8888 version 3 unwrap off nowait off

## Run MD sim
run 100
Expand Down
45 changes: 9 additions & 36 deletions imdclient/tests/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,9 @@ def assert_allclose_with_logging(a, b, rtol=1e-07, atol=0, equal_nan=False):
class IMDv3IntegrationTest:

@pytest.fixture()
def run_sim_and_wait(self, command, match_string, simulation):
def run_sim_and_wait(self, tmp_path, command, match_string):
old_cwd = Path.cwd()
os.chdir(tmp_path)
p = subprocess.Popen(
command,
stdout=subprocess.PIPE,
Expand All @@ -87,14 +89,8 @@ def run_sim_and_wait(self, command, match_string, simulation):

logger.debug("Match string found")
yield p
os.killpg(os.getpgid(p.pid), signal.SIGTERM)

@pytest.fixture()
def simulation(self, tmp_path):
old_cwd = Path.cwd()
os.chdir(tmp_path)
yield
os.chdir(old_cwd)
os.killpg(os.getpgid(p.pid), signal.SIGTERM)

@pytest.fixture()
def client(self, run_sim_and_wait, universe):
Expand All @@ -108,44 +104,21 @@ def test_compare_imd_to_true_traj(self, universe, client, first_frame):
for ts in universe.trajectory[first_frame:]:
imdf = client.get_imdframe()
if imdsinfo.time:
assert_allclose(imdf.time, ts.time)
assert_allclose(imdf.time, ts.time, atol=1e-03)
assert_allclose(imdf.step, ts.data["step"])
if imdsinfo.box:
assert_allclose_with_logging(
imdf.box,
ts.triclinic_dimensions,
atol=1e-02,
atol=1e-03,
)
if imdsinfo.positions:
assert_allclose_with_logging(
imdf.positions, ts.positions, atol=1e-02
imdf.positions, ts.positions, atol=1e-03
)
if imdsinfo.velocities:
assert_allclose_with_logging(
imdf.velocities, ts.velocities, atol=1e-02
imdf.velocities, ts.velocities, atol=1e-03
)
if imdsinfo.forces:
assert_allclose_with_logging(imdf.forces, ts.forces, atol=1e-02)

# imdf = client.get_imdframe()
# if imdsinfo.time:
# assert_allclose(imdf.time, universe.trajectory[1].time)
# assert_allclose(imdf.step, universe.trajectory[1].data["step"])
# if imdsinfo.box:
# assert_allclose_with_logging(
# imdf.box,
# universe.trajectory[1].triclinic_dimensions,
# atol=1e-03,
# )
# if imdsinfo.positions:
# assert_allclose_with_logging(
# imdf.positions, universe.trajectory[1].positions, atol=1e-03
# )
# if imdsinfo.velocities:
# assert_allclose_with_logging(
# imdf.velocities, universe.trajectory[1].velocities, atol=1e-03
# )
# if imdsinfo.forces:
# assert_allclose_with_logging(
# imdf.forces, universe.trajectory[1].forces, atol=1e-03
# )
assert_allclose_with_logging(imdf.forces, ts.forces, atol=1e-03)
145 changes: 145 additions & 0 deletions imdclient/tests/test_vdos.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import pytest
from imdclient.vdos import VDOS
import MDAnalysis as mda
from MDAnalysis.lib.log import ProgressBar


TOPOL = "/scratch/mheyden1/POPC/run-NVE.tpr"
TRAJ = "/scratch/mheyden1/POPC/run-NVE.trr"

# Source of truth

import numpy as np
from scipy.fft import fft


class vdos:
def __init__(self, sel, nCorr):
self.sel = sel
self.nCorr = nCorr
self.nRes = sel.residues.n_residues
# initialize lists and arrays
# Note: atMassLists are lists of numpy arrays
# => they are not a numpy arrays themselves
self.atMassLists = (
[]
) # list of numpy arrays of atomic masses for each residue
for res in self.sel.residues:
# Note: np.newaxis is used to make the array 2D
self.atMassLists.append(
res.atoms.masses[:, np.newaxis].astype("float64")
)
# numpy array of residue masses from MDAnalysis
self.resMassList = sel.residues.masses
# numpy array buffers for COM position, COM velocity, angular momentum, and (sorted) moments of inertia
self.COMposBuffer = np.zeros((nCorr, self.nRes, 3), dtype=np.float64)
self.COMvelBuffer = np.zeros((nCorr, self.nRes, 3), dtype=np.float64)
# time and frequency axes for correlation functions and VDoS
self.tau = np.zeros(nCorr, dtype=np.float64)
self.wavenumber = np.zeros(nCorr, dtype=np.float64)
# numpy arrays for correlation functions and VDoS
# -> rigid body translation
self.trCorr = np.zeros((nCorr, self.nRes), dtype=np.float64)
self.trVDoS = np.zeros((nCorr, self.nRes), dtype=np.float64)
# initialize counter for normalization
self.corrCnt = np.zeros(self.nRes, dtype=int)
# flag for normalization
self.normalized = 0

def processStep(self, tStep, time):
"""process a single time step of the simulation"""
idx = tStep % self.nCorr
if tStep < self.nCorr:
self.tau[tStep] = time
pos = []
vel = []
r = 0
# compute center of mass position and velocity
for res in self.sel.residues:
pos.append(res.atoms.positions.astype("float64"))
vel.append(res.atoms.velocities.astype("float64"))
# self.COMposBuffer[idx,r] = res.atoms.center_of_mass() # => too slow & no equivalent for velocity
self.COMposBuffer[idx, r] = (
np.sum(self.atMassLists[r] * pos[-1], axis=0)
/ self.resMassList[r]
)
self.COMvelBuffer[idx, r] = (
np.sum(self.atMassLists[r] * vel[-1], axis=0)
/ self.resMassList[r]
)
r += 1
# if sufficient data is available in buffers, compute correlation functions
if tStep >= self.nCorr - 1:
self.calcCorr(tStep + 1)

def calcCorr(self, start):
"""compute correlation functions for all data in buffers"""
# compute time correlation function for COM translation (for each residue)
for i in range(self.nCorr):
j = start % self.nCorr
k = (j + i) % self.nCorr
# for each residue
self.trCorr[i] += np.sum(
self.COMvelBuffer[j] * self.COMvelBuffer[k], axis=1
)
self.corrCnt += 1

def normalize(self):
"""normalize correlation functions by number of data points"""
if self.normalized == 0:
for i in range(self.nRes):
self.trCorr[:, i] *= self.resMassList[i] / self.corrCnt[i]
self.normalized = 1

def calcVDOS(self):
"""compute vibrational density of states from time correlation functions"""
period = (self.tau[1] - self.tau[0]) * (2 * self.nCorr - 1)
wn0 = (1.0 / period) * 33.35641
self.wavenumber = np.arange(0, self.nCorr) * wn0
tmp1 = np.zeros(2 * self.nCorr - 1, dtype=np.float64)
for i in range(self.nRes):
for j in range(self.nCorr):
tmp1[j] = self.trCorr[j][i]
for j in range(1, self.nCorr):
k = 2 * self.nCorr - j - 1
tmp1[k] = tmp1[j]
tmp1 = fft(tmp1)
for j in range(self.nCorr):
self.trVDoS[j][i] = tmp1[j].real


@pytest.fixture
def true_vdos():
u = mda.Universe(TOPOL, TRAJ)
sel = u.select_atoms("resname POPC")
true_vdos = vdos(sel, 200)
tStep = 0
for ts in ProgressBar(u.trajectory[:400]):
true_vdos.processStep(tStep, ts.time)
tStep += 1
true_vdos.normalize()
true_vdos.calcVDOS()
yield (
true_vdos.trCorr,
true_vdos.trVDoS,
true_vdos.wavenumber,
true_vdos.tau,
)


@pytest.fixture
def mda_vdos():
u = mda.Universe(TOPOL, TRAJ)
sel = u.select_atoms("resname POPC")
vdos = VDOS(u.trajectory, sel, nCorr=200).run(stop=400)
yield (
vdos.results.trCorr,
vdos.results.trVDoS,
vdos.results.wavenumber,
vdos.results.tau,
)


def test_compare_vdos(true_vdos, mda_vdos):
for true, mda in zip(true_vdos, mda_vdos):
np.testing.assert_allclose(true, mda, rtol=1e-3)
Loading

0 comments on commit 854c77e

Please sign in to comment.