imdclient 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- imdclient/IMDClient.py +896 -0
- imdclient/IMDProtocol.py +164 -0
- imdclient/IMDREADER.py +129 -0
- imdclient/__init__.py +14 -0
- imdclient/backends.py +352 -0
- imdclient/data/__init__.py +0 -0
- imdclient/data/gromacs/md/gromacs_struct.gro +21151 -0
- imdclient/data/gromacs/md/gromacs_v3.top +11764 -0
- imdclient/data/gromacs/md/gromacs_v3_nst1.mdp +58 -0
- imdclient/data/gromacs/md/gromacs_v3_nst1.tpr +0 -0
- imdclient/data/gromacs/md/gromacs_v3_nst1.trr +0 -0
- imdclient/data/lammps/md/lammps_topol.data +8022 -0
- imdclient/data/lammps/md/lammps_trj.h5md +0 -0
- imdclient/data/lammps/md/lammps_v3.in +71 -0
- imdclient/data/namd/md/alanin.dcd +0 -0
- imdclient/data/namd/md/alanin.params +402 -0
- imdclient/data/namd/md/alanin.pdb +77 -0
- imdclient/data/namd/md/alanin.psf +206 -0
- imdclient/data/namd/md/namd_v3.namd +47 -0
- imdclient/results.py +332 -0
- imdclient/streamanalysis.py +1056 -0
- imdclient/streambase.py +199 -0
- imdclient/tests/__init__.py +0 -0
- imdclient/tests/base.py +122 -0
- imdclient/tests/conftest.py +38 -0
- imdclient/tests/datafiles.py +34 -0
- imdclient/tests/server.py +212 -0
- imdclient/tests/test_gromacs.py +33 -0
- imdclient/tests/test_imdclient.py +150 -0
- imdclient/tests/test_imdreader.py +644 -0
- imdclient/tests/test_lammps.py +38 -0
- imdclient/tests/test_manual.py +70 -0
- imdclient/tests/test_namd.py +38 -0
- imdclient/tests/test_stream_analysis.py +61 -0
- imdclient/tests/utils.py +41 -0
- imdclient/utils.py +118 -0
- imdclient-0.1.0.dist-info/AUTHORS.md +23 -0
- imdclient-0.1.0.dist-info/LICENSE +21 -0
- imdclient-0.1.0.dist-info/METADATA +143 -0
- imdclient-0.1.0.dist-info/RECORD +42 -0
- imdclient-0.1.0.dist-info/WHEEL +5 -0
- imdclient-0.1.0.dist-info/top_level.txt +1 -0
imdclient/streambase.py
ADDED
@@ -0,0 +1,199 @@
|
|
1
|
+
from MDAnalysis.coordinates.base import (
|
2
|
+
ReaderBase,
|
3
|
+
FrameIteratorBase,
|
4
|
+
FrameIteratorAll,
|
5
|
+
)
|
6
|
+
import numbers
|
7
|
+
import warnings
|
8
|
+
|
9
|
+
|
10
|
+
class StreamReaderBase(ReaderBase):
|
11
|
+
|
12
|
+
def __init__(self, filename, convert_units=True, **kwargs):
|
13
|
+
super(StreamReaderBase, self).__init__(
|
14
|
+
filename, convert_units=convert_units, **kwargs
|
15
|
+
)
|
16
|
+
self._init_scope = True
|
17
|
+
self._reopen_called = False
|
18
|
+
self._first_ts = None
|
19
|
+
|
20
|
+
def _read_next_timestep(self):
|
21
|
+
# No rewinding- to both load the first frame after __init__
|
22
|
+
# and access it again during iteration, we need to store first ts in mem
|
23
|
+
if not self._init_scope and self._frame == -1:
|
24
|
+
self._frame += 1
|
25
|
+
# can't simply return the same ts again- transformations would be applied twice
|
26
|
+
# instead, return the pre-transformed copy
|
27
|
+
return self._first_ts
|
28
|
+
|
29
|
+
ts = self._read_frame(self._frame + 1)
|
30
|
+
|
31
|
+
if self._init_scope:
|
32
|
+
self._first_ts = self.ts.copy()
|
33
|
+
self._init_scope = False
|
34
|
+
|
35
|
+
return ts
|
36
|
+
|
37
|
+
@property
|
38
|
+
def n_frames(self):
|
39
|
+
"""Changes as stream is processed unlike other readers"""
|
40
|
+
raise RuntimeError(
|
41
|
+
"{}: n_frames is unknown".format(self.__class__.__name__)
|
42
|
+
)
|
43
|
+
|
44
|
+
def __len__(self):
|
45
|
+
raise RuntimeError(
|
46
|
+
"{} has unknown length".format(self.__class__.__name__)
|
47
|
+
)
|
48
|
+
|
49
|
+
def next(self):
|
50
|
+
"""Don't rewind after iteration. When _reopen() is called,
|
51
|
+
an error will be raised
|
52
|
+
"""
|
53
|
+
try:
|
54
|
+
ts = self._read_next_timestep()
|
55
|
+
except (EOFError, IOError):
|
56
|
+
# Don't rewind here like we normally would
|
57
|
+
raise StopIteration from None
|
58
|
+
else:
|
59
|
+
for auxname, reader in self._auxs.items():
|
60
|
+
ts = self._auxs[auxname].update_ts(ts)
|
61
|
+
|
62
|
+
ts = self._apply_transformations(ts)
|
63
|
+
|
64
|
+
return ts
|
65
|
+
|
66
|
+
def rewind(self):
|
67
|
+
"""Raise error on rewind"""
|
68
|
+
raise RuntimeError(
|
69
|
+
"{}: Stream-based readers can't be rewound".format(
|
70
|
+
self.__class__.__name__
|
71
|
+
)
|
72
|
+
)
|
73
|
+
|
74
|
+
# Incompatible methods
|
75
|
+
def copy(self):
|
76
|
+
raise NotImplementedError(
|
77
|
+
"{} does not support copying".format(self.__class__.__name__)
|
78
|
+
)
|
79
|
+
|
80
|
+
def _reopen(self):
|
81
|
+
if self._reopen_called:
|
82
|
+
raise RuntimeError(
|
83
|
+
"{}: Cannot reopen stream".format(self.__class__.__name__)
|
84
|
+
)
|
85
|
+
self._frame = -1
|
86
|
+
self._reopen_called = True
|
87
|
+
|
88
|
+
def __getitem__(self, frame):
|
89
|
+
"""Return the Timestep corresponding to *frame*.
|
90
|
+
|
91
|
+
If `frame` is a integer then the corresponding frame is
|
92
|
+
returned. Negative numbers are counted from the end.
|
93
|
+
|
94
|
+
If frame is a :class:`slice` then an iterator is returned that
|
95
|
+
allows iteration over that part of the trajectory.
|
96
|
+
|
97
|
+
Note
|
98
|
+
----
|
99
|
+
*frame* is a 0-based frame index.
|
100
|
+
"""
|
101
|
+
if isinstance(frame, slice):
|
102
|
+
_, _, step = self.check_slice_indices(
|
103
|
+
frame.start, frame.stop, frame.step
|
104
|
+
)
|
105
|
+
if step is None:
|
106
|
+
return FrameIteratorAll(self)
|
107
|
+
else:
|
108
|
+
return StreamFrameIteratorSliced(self, step)
|
109
|
+
else:
|
110
|
+
raise TypeError(
|
111
|
+
"Streamed trajectories must be an indexed using a slice"
|
112
|
+
)
|
113
|
+
|
114
|
+
def check_slice_indices(self, start, stop, step):
|
115
|
+
if start is not None:
|
116
|
+
raise ValueError(
|
117
|
+
"{}: Cannot expect a start index from a stream, 'start' must be None".format(
|
118
|
+
self.__class__.__name__
|
119
|
+
)
|
120
|
+
)
|
121
|
+
if stop is not None:
|
122
|
+
raise ValueError(
|
123
|
+
"{}: Cannot expect a stop index from a stream, 'stop' must be None".format(
|
124
|
+
self.__class__.__name__
|
125
|
+
)
|
126
|
+
)
|
127
|
+
if step is not None:
|
128
|
+
if isinstance(step, numbers.Integral):
|
129
|
+
if step < 1:
|
130
|
+
raise ValueError(
|
131
|
+
"{}: Cannot go backwards in a stream, 'step' must be > 0".format(
|
132
|
+
self.__class__.__name__
|
133
|
+
)
|
134
|
+
)
|
135
|
+
else:
|
136
|
+
raise ValueError(
|
137
|
+
"{}: 'step' must be an integer".format(
|
138
|
+
self.__class__.__name__
|
139
|
+
)
|
140
|
+
)
|
141
|
+
|
142
|
+
return start, stop, step
|
143
|
+
|
144
|
+
def __getstate__(self):
|
145
|
+
raise NotImplementedError(
|
146
|
+
"{} does not support pickling".format(self.__class__.__name__)
|
147
|
+
)
|
148
|
+
|
149
|
+
def __setstate__(self, state: object):
|
150
|
+
raise NotImplementedError(
|
151
|
+
"{} does not support pickling".format(self.__class__.__name__)
|
152
|
+
)
|
153
|
+
|
154
|
+
def __repr__(self):
|
155
|
+
return (
|
156
|
+
"<{cls} {fname} with continuous stream of {natoms} atoms>"
|
157
|
+
"".format(
|
158
|
+
cls=self.__class__.__name__,
|
159
|
+
fname=self.filename,
|
160
|
+
natoms=self.n_atoms,
|
161
|
+
)
|
162
|
+
)
|
163
|
+
|
164
|
+
|
165
|
+
class StreamFrameIteratorSliced(FrameIteratorBase):
|
166
|
+
|
167
|
+
def __init__(self, trajectory, step):
|
168
|
+
super().__init__(trajectory)
|
169
|
+
self._step = step
|
170
|
+
|
171
|
+
def __iter__(self):
|
172
|
+
# Calling reopen tells reader
|
173
|
+
# it can't be reopened again
|
174
|
+
self.trajectory._reopen()
|
175
|
+
return self
|
176
|
+
|
177
|
+
def __next__(self):
|
178
|
+
try:
|
179
|
+
# Burn the timesteps until we reach the desired step
|
180
|
+
# Don't use next() to avoid unnecessary transformations
|
181
|
+
while self.trajectory._frame + 1 % self.step != 0:
|
182
|
+
self.trajectory._read_next_timestep()
|
183
|
+
except (EOFError, IOError):
|
184
|
+
# Don't rewind here like we normally would
|
185
|
+
raise StopIteration from None
|
186
|
+
|
187
|
+
return self.trajectory.next()
|
188
|
+
|
189
|
+
def __len__(self):
|
190
|
+
raise RuntimeError(
|
191
|
+
"{} has unknown length".format(self.__class__.__name__)
|
192
|
+
)
|
193
|
+
|
194
|
+
def __getitem__(self, frame):
|
195
|
+
raise RuntimeError("Sliced iterator does not support indexing")
|
196
|
+
|
197
|
+
@property
|
198
|
+
def step(self):
|
199
|
+
return self._step
|
File without changes
|
imdclient/tests/base.py
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
from imdclient.IMDClient import IMDClient
|
2
|
+
import pytest
|
3
|
+
from pathlib import Path
|
4
|
+
import os
|
5
|
+
import signal
|
6
|
+
import subprocess
|
7
|
+
import time
|
8
|
+
from numpy.testing import (
|
9
|
+
assert_allclose,
|
10
|
+
)
|
11
|
+
import numpy as np
|
12
|
+
|
13
|
+
import logging
|
14
|
+
|
15
|
+
logger = logging.getLogger("imdclient.IMDClient")
|
16
|
+
|
17
|
+
|
18
|
+
def assert_allclose_with_logging(a, b, rtol=1e-07, atol=0, equal_nan=False):
|
19
|
+
"""
|
20
|
+
Custom function to compare two arrays element-wise, similar to np.testing.assert_allclose,
|
21
|
+
but logs all non-matching values.
|
22
|
+
|
23
|
+
Parameters:
|
24
|
+
a, b : array_like
|
25
|
+
Input arrays to compare.
|
26
|
+
rtol : float
|
27
|
+
Relative tolerance.
|
28
|
+
atol : float
|
29
|
+
Absolute tolerance.
|
30
|
+
equal_nan : bool
|
31
|
+
Whether to compare NaNs as equal.
|
32
|
+
"""
|
33
|
+
# Convert inputs to numpy arrays
|
34
|
+
a = np.asarray(a)
|
35
|
+
b = np.asarray(b)
|
36
|
+
|
37
|
+
# Compute the absolute difference
|
38
|
+
diff = np.abs(a - b)
|
39
|
+
|
40
|
+
# Check if values are within tolerance
|
41
|
+
not_close = diff > (atol + rtol * np.abs(b))
|
42
|
+
|
43
|
+
# Check if there are any NaNs and handle them if necessary
|
44
|
+
if equal_nan:
|
45
|
+
nan_mask = np.isnan(a) & np.isnan(b)
|
46
|
+
not_close &= ~nan_mask
|
47
|
+
|
48
|
+
# Log all the values that are not close
|
49
|
+
if np.any(not_close):
|
50
|
+
print("The following values do not match within tolerance:")
|
51
|
+
for idx in np.argwhere(not_close):
|
52
|
+
logger.debug(
|
53
|
+
f"a[{tuple(idx)}]: {a[tuple(idx)]}, b[{tuple(idx)}]: {b[tuple(idx)]}, diff: {diff[tuple(idx)]}"
|
54
|
+
)
|
55
|
+
# Optionally raise an error after logging if you want it to behave like assert
|
56
|
+
raise AssertionError("Arrays are not almost equal.")
|
57
|
+
else:
|
58
|
+
print("All values are within tolerance.")
|
59
|
+
|
60
|
+
|
61
|
+
class IMDv3IntegrationTest:
|
62
|
+
|
63
|
+
@pytest.fixture()
|
64
|
+
def run_sim_and_wait(self, tmp_path, command, match_string):
|
65
|
+
old_cwd = Path.cwd()
|
66
|
+
os.chdir(tmp_path)
|
67
|
+
p = subprocess.Popen(
|
68
|
+
command,
|
69
|
+
stdout=subprocess.PIPE,
|
70
|
+
stderr=subprocess.STDOUT,
|
71
|
+
shell=True,
|
72
|
+
text=True,
|
73
|
+
bufsize=0,
|
74
|
+
preexec_fn=os.setsid,
|
75
|
+
)
|
76
|
+
t = time.time()
|
77
|
+
|
78
|
+
for stdout_line in iter(p.stdout.readline, ""):
|
79
|
+
logger.debug(f"stdout: {stdout_line}")
|
80
|
+
if match_string in stdout_line:
|
81
|
+
|
82
|
+
break
|
83
|
+
if time.time() - t > 10:
|
84
|
+
raise TimeoutError("Timeout waiting for match string")
|
85
|
+
|
86
|
+
logger.debug("Match string found")
|
87
|
+
yield p
|
88
|
+
os.chdir(old_cwd)
|
89
|
+
os.killpg(os.getpgid(p.pid), signal.SIGTERM)
|
90
|
+
|
91
|
+
@pytest.fixture()
|
92
|
+
def client(self, run_sim_and_wait, universe):
|
93
|
+
client = IMDClient("localhost", 8888, universe.trajectory.n_atoms)
|
94
|
+
yield client
|
95
|
+
client.stop()
|
96
|
+
|
97
|
+
def test_compare_imd_to_true_traj(self, universe, client, first_frame):
|
98
|
+
imdsinfo = client.get_imdsessioninfo()
|
99
|
+
|
100
|
+
for ts in universe.trajectory[first_frame:]:
|
101
|
+
imdf = client.get_imdframe()
|
102
|
+
if imdsinfo.time:
|
103
|
+
assert_allclose(imdf.time, ts.time, atol=1e-03)
|
104
|
+
assert_allclose(imdf.step, ts.data["step"])
|
105
|
+
if imdsinfo.box:
|
106
|
+
assert_allclose_with_logging(
|
107
|
+
imdf.box,
|
108
|
+
ts.triclinic_dimensions,
|
109
|
+
atol=1e-03,
|
110
|
+
)
|
111
|
+
if imdsinfo.positions:
|
112
|
+
assert_allclose_with_logging(
|
113
|
+
imdf.positions, ts.positions, atol=1e-03
|
114
|
+
)
|
115
|
+
if imdsinfo.velocities:
|
116
|
+
assert_allclose_with_logging(
|
117
|
+
imdf.velocities, ts.velocities, atol=1e-03
|
118
|
+
)
|
119
|
+
if imdsinfo.forces:
|
120
|
+
assert_allclose_with_logging(
|
121
|
+
imdf.forces, ts.forces, atol=1e-03
|
122
|
+
)
|
@@ -0,0 +1,38 @@
|
|
1
|
+
"""
|
2
|
+
Global pytest fixtures
|
3
|
+
"""
|
4
|
+
|
5
|
+
|
6
|
+
# Command line arguments for 'test_manual.py'
|
7
|
+
def pytest_addoption(parser):
|
8
|
+
parser.addoption(
|
9
|
+
"--topol_arg",
|
10
|
+
action="store",
|
11
|
+
)
|
12
|
+
parser.addoption(
|
13
|
+
"--traj_arg",
|
14
|
+
action="store",
|
15
|
+
)
|
16
|
+
parser.addoption("--first_frame_arg", action="store", type=int)
|
17
|
+
|
18
|
+
|
19
|
+
def pytest_generate_tests(metafunc):
|
20
|
+
# This is called for every test. Only get/set command line arguments
|
21
|
+
# if the argument is specified in the list of test "fixturenames".
|
22
|
+
topol = metafunc.config.option.topol_arg
|
23
|
+
traj = metafunc.config.option.traj_arg
|
24
|
+
first_frame = metafunc.config.option.first_frame_arg
|
25
|
+
|
26
|
+
if all(
|
27
|
+
arg in metafunc.fixturenames
|
28
|
+
for arg in ["topol_arg", "traj_arg", "first_frame_arg"]
|
29
|
+
):
|
30
|
+
if topol is None or traj is None or first_frame is None:
|
31
|
+
raise ValueError(
|
32
|
+
"Must pass all three of '--topol_arg <path/to/topology>', "
|
33
|
+
+ "'--traj_arg <path/to/trajectory>', "
|
34
|
+
+ "'--first_frame_arg <first traj frame to compare to IMD>"
|
35
|
+
)
|
36
|
+
metafunc.parametrize("topol_arg", [topol])
|
37
|
+
metafunc.parametrize("traj_arg", [traj])
|
38
|
+
metafunc.parametrize("first_frame_arg", [first_frame])
|
@@ -0,0 +1,34 @@
|
|
1
|
+
"""
|
2
|
+
Location of data files
|
3
|
+
======================
|
4
|
+
|
5
|
+
Use as ::
|
6
|
+
|
7
|
+
from imdclient.tests.datafiles import *
|
8
|
+
|
9
|
+
"""
|
10
|
+
|
11
|
+
__all__ = ["LAMMPS_IN", "LAMMPS_TOPOL", "GROMACS_TRAJ", "GROMACS_MDP"]
|
12
|
+
|
13
|
+
from importlib import resources
|
14
|
+
from pathlib import Path
|
15
|
+
|
16
|
+
_data_ref = resources.files("imdclient.data")
|
17
|
+
|
18
|
+
LAMMPS_TOPOL = (_data_ref / "lammps" / "md" / "lammps_topol.data").as_posix()
|
19
|
+
LAMMPS_IN = (_data_ref / "lammps" / "md" / "lammps_v3.in").as_posix()
|
20
|
+
LAMMPS_TRAJ = (_data_ref / "lammps" / "md" / "lammps_trj.h5md").as_posix()
|
21
|
+
GROMACS_TRAJ = (
|
22
|
+
_data_ref / "gromacs" / "md" / "gromacs_v3_nst1.trr"
|
23
|
+
).as_posix()
|
24
|
+
GROMACS_TOPOL = (
|
25
|
+
_data_ref / "gromacs" / "md" / "gromacs_struct.gro"
|
26
|
+
).as_posix()
|
27
|
+
GROMACS_TPR = (_data_ref / "gromacs" / "md" / "gromacs_v3_nst1.tpr").as_posix()
|
28
|
+
NAMD_TOPOL = (_data_ref / "namd" / "md" / "alanin.pdb").as_posix()
|
29
|
+
NAMD_CONF = (_data_ref / "namd" / "md" / "namd_v3.namd").as_posix()
|
30
|
+
NAMD_TRAJ = (_data_ref / "namd" / "md" / "alanin.dcd").as_posix()
|
31
|
+
NAMD_PARAMS = (_data_ref / "namd" / "md" / "alanin.params").as_posix()
|
32
|
+
NAMD_PSF = (_data_ref / "namd" / "md" / "alanin.psf").as_posix()
|
33
|
+
|
34
|
+
del resources
|
@@ -0,0 +1,212 @@
|
|
1
|
+
import socket
|
2
|
+
import struct
|
3
|
+
import numpy as np
|
4
|
+
import threading
|
5
|
+
import time
|
6
|
+
from imdclient.IMDProtocol import (
|
7
|
+
IMDHeaderType,
|
8
|
+
create_header_bytes,
|
9
|
+
create_energy_bytes,
|
10
|
+
IMDHEADERSIZE,
|
11
|
+
)
|
12
|
+
import logging
|
13
|
+
from imdclient.IMDClient import sock_contains_data, read_into_buf, IMDHeader
|
14
|
+
|
15
|
+
logger = logging.getLogger("imdclient.IMDClient")
|
16
|
+
|
17
|
+
|
18
|
+
class InThreadIMDServer:
|
19
|
+
"""Server operates in the same thread as the client (except for the method
|
20
|
+
'handshake_sequence', which spawns a new thread to get past the
|
21
|
+
reader connecting to the server).
|
22
|
+
|
23
|
+
Note that the trajectory units of the provided traj must be in MDAnalysis units
|
24
|
+
"""
|
25
|
+
|
26
|
+
def __init__(self, traj):
|
27
|
+
self.traj = traj
|
28
|
+
self.listen_socket = None
|
29
|
+
self.conn = None
|
30
|
+
self.accept_thread = None
|
31
|
+
|
32
|
+
def set_imdsessioninfo(self, imdsinfo):
|
33
|
+
self.imdsinfo = imdsinfo
|
34
|
+
|
35
|
+
def handshake_sequence(self, host, port, first_frame=True):
|
36
|
+
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
37
|
+
s.bind((host, port))
|
38
|
+
logger.debug(f"InThreadIMDServer: Listening on {host}:{port}")
|
39
|
+
s.listen(60)
|
40
|
+
self.listen_socket = s
|
41
|
+
|
42
|
+
self.accept_thread = threading.Thread(
|
43
|
+
target=self._accept_handshake, args=(first_frame,)
|
44
|
+
)
|
45
|
+
self.accept_thread.start()
|
46
|
+
|
47
|
+
def _accept_handshake(self, first_frame):
|
48
|
+
"""Accepts the connection and sends the handshake, imdsessioninfo (in the case of v3),
|
49
|
+
the first frame (if first_frame is `True`), and expects IMD_GO"""
|
50
|
+
waited = 0
|
51
|
+
|
52
|
+
if sock_contains_data(self.listen_socket, 5):
|
53
|
+
logger.debug(f"InThreadIMDServer: Accepting connection")
|
54
|
+
self.conn, _ = self.listen_socket.accept()
|
55
|
+
# NOTE: may need to reorganize this
|
56
|
+
self.conn.settimeout(5)
|
57
|
+
if self.imdsinfo.version == 2:
|
58
|
+
self._send_handshakeV2()
|
59
|
+
elif self.imdsinfo.version == 3:
|
60
|
+
self._send_handshakeV3()
|
61
|
+
self.expect_packet(IMDHeaderType.IMD_GO)
|
62
|
+
if first_frame:
|
63
|
+
self.send_frame(0)
|
64
|
+
return
|
65
|
+
else:
|
66
|
+
# IMDReader will fail out if it fails to connect
|
67
|
+
return
|
68
|
+
|
69
|
+
def _send_handshakeV2(self):
|
70
|
+
header = struct.pack("!i", IMDHeaderType.IMD_HANDSHAKE.value)
|
71
|
+
header += struct.pack(f"{self.imdsinfo.endianness}i", 2)
|
72
|
+
self.conn.sendall(header)
|
73
|
+
|
74
|
+
def _send_handshakeV3(self):
|
75
|
+
logger.debug(f"InThreadIMDServer: Sending handshake V3")
|
76
|
+
packet = struct.pack("!i", IMDHeaderType.IMD_HANDSHAKE.value)
|
77
|
+
packet += struct.pack(f"{self.imdsinfo.endianness}i", 3)
|
78
|
+
self.conn.sendall(packet)
|
79
|
+
|
80
|
+
sinfo = struct.pack("!ii", IMDHeaderType.IMD_SESSIONINFO.value, 7)
|
81
|
+
time = 1 if self.imdsinfo.time else 0
|
82
|
+
energies = 1 if self.imdsinfo.energies else 0
|
83
|
+
box = 1 if self.imdsinfo.box else 0
|
84
|
+
positions = 1 if self.imdsinfo.positions else 0
|
85
|
+
velocities = 1 if self.imdsinfo.velocities else 0
|
86
|
+
forces = 1 if self.imdsinfo.forces else 0
|
87
|
+
wrapped_coords = 0
|
88
|
+
sinfo += struct.pack(
|
89
|
+
f"{self.imdsinfo.endianness}BBBBBBB",
|
90
|
+
time,
|
91
|
+
energies,
|
92
|
+
box,
|
93
|
+
positions,
|
94
|
+
wrapped_coords,
|
95
|
+
velocities,
|
96
|
+
forces
|
97
|
+
)
|
98
|
+
logger.debug(f"InThreadIMDServer: Sending session info")
|
99
|
+
self.conn.sendall(sinfo)
|
100
|
+
|
101
|
+
def join_accept_thread(self):
|
102
|
+
self.accept_thread.join()
|
103
|
+
|
104
|
+
def _expect_go(self):
|
105
|
+
logger.debug(f"InThreadIMDServer: Waiting for go")
|
106
|
+
head_buf = bytearray(IMDHEADERSIZE)
|
107
|
+
read_into_buf(self.conn, head_buf)
|
108
|
+
header = IMDHeader(head_buf)
|
109
|
+
if header.type != IMDHeaderType.IMD_GO:
|
110
|
+
raise ValueError("Expected IMD_GO packet, got something else")
|
111
|
+
|
112
|
+
def send_frames(self, start, end):
|
113
|
+
for i in range(start, end):
|
114
|
+
self.send_frame(i)
|
115
|
+
|
116
|
+
def send_frame(self, i):
|
117
|
+
endianness = self.imdsinfo.endianness
|
118
|
+
|
119
|
+
if self.imdsinfo.time:
|
120
|
+
time_header = create_header_bytes(IMDHeaderType.IMD_TIME, 1)
|
121
|
+
time = struct.pack(
|
122
|
+
f"{endianness}ddQ",
|
123
|
+
self.traj[i].dt,
|
124
|
+
self.traj[i].time,
|
125
|
+
self.traj[i].frame,
|
126
|
+
)
|
127
|
+
|
128
|
+
self.conn.sendall(time_header + time)
|
129
|
+
|
130
|
+
if self.imdsinfo.energies:
|
131
|
+
energy_header = create_header_bytes(IMDHeaderType.IMD_ENERGIES, 1)
|
132
|
+
energies = create_energy_bytes(
|
133
|
+
i,
|
134
|
+
i + 1,
|
135
|
+
i + 2,
|
136
|
+
i + 3,
|
137
|
+
i + 4,
|
138
|
+
i + 5,
|
139
|
+
i + 6,
|
140
|
+
i + 7,
|
141
|
+
i + 8,
|
142
|
+
i + 9,
|
143
|
+
endianness,
|
144
|
+
)
|
145
|
+
self.conn.sendall(energy_header + energies)
|
146
|
+
|
147
|
+
if self.imdsinfo.box:
|
148
|
+
box_header = create_header_bytes(IMDHeaderType.IMD_BOX, 1)
|
149
|
+
box = np.ascontiguousarray(
|
150
|
+
self.traj[i].triclinic_dimensions, dtype=f"{endianness}f"
|
151
|
+
).tobytes()
|
152
|
+
|
153
|
+
self.conn.sendall(box_header + box)
|
154
|
+
|
155
|
+
if self.imdsinfo.positions:
|
156
|
+
pos_header = create_header_bytes(
|
157
|
+
IMDHeaderType.IMD_FCOORDS, self.traj.n_atoms
|
158
|
+
)
|
159
|
+
|
160
|
+
pos = np.ascontiguousarray(
|
161
|
+
self.traj[i].positions, dtype=f"{endianness}f"
|
162
|
+
).tobytes()
|
163
|
+
|
164
|
+
self.conn.sendall(pos_header + pos)
|
165
|
+
|
166
|
+
if self.imdsinfo.velocities:
|
167
|
+
vel_header = create_header_bytes(
|
168
|
+
IMDHeaderType.IMD_VELOCITIES, self.traj.n_atoms
|
169
|
+
)
|
170
|
+
vel = np.ascontiguousarray(
|
171
|
+
self.traj[i].velocities, dtype=f"{endianness}f"
|
172
|
+
).tobytes()
|
173
|
+
|
174
|
+
self.conn.sendall(vel_header + vel)
|
175
|
+
|
176
|
+
if self.imdsinfo.forces:
|
177
|
+
force_header = create_header_bytes(
|
178
|
+
IMDHeaderType.IMD_FORCES, self.traj.n_atoms
|
179
|
+
)
|
180
|
+
force = np.ascontiguousarray(
|
181
|
+
self.traj[i].forces, dtype=f"{endianness}f"
|
182
|
+
).tobytes()
|
183
|
+
|
184
|
+
self.conn.sendall(force_header + force)
|
185
|
+
|
186
|
+
|
187
|
+
def expect_packet(self, packet_type, expected_length=None):
|
188
|
+
head_buf = bytearray(IMDHEADERSIZE)
|
189
|
+
read_into_buf(self.conn, head_buf)
|
190
|
+
header = IMDHeader(head_buf)
|
191
|
+
if header.type != packet_type:
|
192
|
+
raise ValueError(
|
193
|
+
f"Expected {packet_type} packet, got {header.type}"
|
194
|
+
)
|
195
|
+
if expected_length is not None and header.length != expected_length:
|
196
|
+
raise ValueError(
|
197
|
+
f"Expected packet length {expected_length}, got {header.length}"
|
198
|
+
)
|
199
|
+
|
200
|
+
def disconnect(self):
|
201
|
+
self.conn.shutdown(socket.SHUT_RD)
|
202
|
+
self.conn.close()
|
203
|
+
|
204
|
+
def cleanup(self):
|
205
|
+
try:
|
206
|
+
self.conn.close()
|
207
|
+
except:
|
208
|
+
pass
|
209
|
+
try:
|
210
|
+
self.listen_socket.close()
|
211
|
+
except:
|
212
|
+
pass
|
@@ -0,0 +1,33 @@
|
|
1
|
+
import MDAnalysis as mda
|
2
|
+
import pytest
|
3
|
+
import logging
|
4
|
+
from .base import IMDv3IntegrationTest
|
5
|
+
from .datafiles import GROMACS_TOPOL, GROMACS_TRAJ, GROMACS_TPR
|
6
|
+
|
7
|
+
logger = logging.getLogger("imdclient.IMDClient")
|
8
|
+
file_handler = logging.FileHandler("test.log")
|
9
|
+
formatter = logging.Formatter(
|
10
|
+
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
11
|
+
)
|
12
|
+
file_handler.setFormatter(formatter)
|
13
|
+
logger.addHandler(file_handler)
|
14
|
+
logger.setLevel(logging.DEBUG)
|
15
|
+
|
16
|
+
|
17
|
+
class TestIMDv3Gromacs(IMDv3IntegrationTest):
|
18
|
+
|
19
|
+
@pytest.fixture()
|
20
|
+
def command(self):
|
21
|
+
return f". /usr/local/gromacs/bin/GMXRC && gmx mdrun -s {GROMACS_TPR} -imdport 8888 -imdwait"
|
22
|
+
|
23
|
+
@pytest.fixture()
|
24
|
+
def match_string(self):
|
25
|
+
return "IMD: Will wait until I have a connection and IMD_GO orders."
|
26
|
+
|
27
|
+
@pytest.fixture()
|
28
|
+
def first_frame(self):
|
29
|
+
return 0
|
30
|
+
|
31
|
+
@pytest.fixture()
|
32
|
+
def universe(self):
|
33
|
+
return mda.Universe(GROMACS_TOPOL, GROMACS_TRAJ)
|