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.
Files changed (42) hide show
  1. imdclient/IMDClient.py +896 -0
  2. imdclient/IMDProtocol.py +164 -0
  3. imdclient/IMDREADER.py +129 -0
  4. imdclient/__init__.py +14 -0
  5. imdclient/backends.py +352 -0
  6. imdclient/data/__init__.py +0 -0
  7. imdclient/data/gromacs/md/gromacs_struct.gro +21151 -0
  8. imdclient/data/gromacs/md/gromacs_v3.top +11764 -0
  9. imdclient/data/gromacs/md/gromacs_v3_nst1.mdp +58 -0
  10. imdclient/data/gromacs/md/gromacs_v3_nst1.tpr +0 -0
  11. imdclient/data/gromacs/md/gromacs_v3_nst1.trr +0 -0
  12. imdclient/data/lammps/md/lammps_topol.data +8022 -0
  13. imdclient/data/lammps/md/lammps_trj.h5md +0 -0
  14. imdclient/data/lammps/md/lammps_v3.in +71 -0
  15. imdclient/data/namd/md/alanin.dcd +0 -0
  16. imdclient/data/namd/md/alanin.params +402 -0
  17. imdclient/data/namd/md/alanin.pdb +77 -0
  18. imdclient/data/namd/md/alanin.psf +206 -0
  19. imdclient/data/namd/md/namd_v3.namd +47 -0
  20. imdclient/results.py +332 -0
  21. imdclient/streamanalysis.py +1056 -0
  22. imdclient/streambase.py +199 -0
  23. imdclient/tests/__init__.py +0 -0
  24. imdclient/tests/base.py +122 -0
  25. imdclient/tests/conftest.py +38 -0
  26. imdclient/tests/datafiles.py +34 -0
  27. imdclient/tests/server.py +212 -0
  28. imdclient/tests/test_gromacs.py +33 -0
  29. imdclient/tests/test_imdclient.py +150 -0
  30. imdclient/tests/test_imdreader.py +644 -0
  31. imdclient/tests/test_lammps.py +38 -0
  32. imdclient/tests/test_manual.py +70 -0
  33. imdclient/tests/test_namd.py +38 -0
  34. imdclient/tests/test_stream_analysis.py +61 -0
  35. imdclient/tests/utils.py +41 -0
  36. imdclient/utils.py +118 -0
  37. imdclient-0.1.0.dist-info/AUTHORS.md +23 -0
  38. imdclient-0.1.0.dist-info/LICENSE +21 -0
  39. imdclient-0.1.0.dist-info/METADATA +143 -0
  40. imdclient-0.1.0.dist-info/RECORD +42 -0
  41. imdclient-0.1.0.dist-info/WHEEL +5 -0
  42. imdclient-0.1.0.dist-info/top_level.txt +1 -0
@@ -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
@@ -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)