imdclient 0.1.0__py3-none-any.whl → 0.1.2__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 CHANGED
@@ -30,6 +30,23 @@ logger = logging.getLogger(__name__)
30
30
 
31
31
 
32
32
  class IMDClient:
33
+ """
34
+ Parameters
35
+ ----------
36
+ host : str
37
+ Hostname of the server
38
+ port : int
39
+ Port number of the server
40
+ n_atoms : int
41
+ Number of atoms in the simulation
42
+ socket_bufsize : int, (optional)
43
+ Size of the socket buffer in bytes. Default is to use the system default
44
+ buffer_size : int (optional)
45
+ IMDFramebuffer will be filled with as many :class:`IMDFrame` fit in `buffer_size` [``10MB``]
46
+ **kwargs : dict (optional)
47
+ Additional keyword arguments to pass to the :class:`BaseIMDProducer` and :class:`IMDFrameBuffer`
48
+ """
49
+
33
50
  def __init__(
34
51
  self,
35
52
  host,
@@ -39,22 +56,7 @@ class IMDClient:
39
56
  multithreaded=True,
40
57
  **kwargs,
41
58
  ):
42
- """
43
- Parameters
44
- ----------
45
- host : str
46
- Hostname of the server
47
- port : int
48
- Port number of the server
49
- n_atoms : int
50
- Number of atoms in the simulation
51
- socket_bufsize : int, optional
52
- Size of the socket buffer in bytes. Default is to use the system default
53
- buffer_size : int, optional
54
- IMDFramebuffer will be filled with as many IMDFrames fit in `buffer_size` [``10MB``]
55
- **kwargs : optional
56
- Additional keyword arguments to pass to the IMDProducer and IMDFrameBuffer
57
- """
59
+
58
60
  self._stopped = False
59
61
  self._conn = self._connect_to_server(host, port, socket_bufsize)
60
62
  self._imdsinfo = self._await_IMD_handshake()
@@ -254,6 +256,25 @@ class IMDClient:
254
256
 
255
257
 
256
258
  class BaseIMDProducer(threading.Thread):
259
+ """
260
+
261
+ Parameters
262
+ ----------
263
+ conn : socket.socket
264
+ Connection object to the server
265
+ buffer : IMDFrameBuffer
266
+ Buffer object to hold IMD frames. If `multithreaded` is False, this
267
+ argument is ignored
268
+ sinfo : IMDSessionInfo
269
+ Information about the IMD session
270
+ n_atoms : int
271
+ Number of atoms in the simulation
272
+ multithreaded : bool, optional
273
+ If True, socket interaction will occur in a separate thread &
274
+ frames will be buffered. Single-threaded, blocking IMDClient
275
+ should only be used in testing [[``True``]]
276
+
277
+ """
257
278
 
258
279
  def __init__(
259
280
  self,
@@ -265,24 +286,6 @@ class BaseIMDProducer(threading.Thread):
265
286
  timeout=5,
266
287
  **kwargs,
267
288
  ):
268
- """
269
- Parameters
270
- ----------
271
- conn : socket.socket
272
- Connection object to the server
273
- buffer : IMDFrameBuffer
274
- Buffer object to hold IMD frames. If `multithreaded` is False, this
275
- argument is ignored
276
- sinfo : IMDSessionInfo
277
- Information about the IMD session
278
- n_atoms : int
279
- Number of atoms in the simulation
280
- multithreaded : bool, optional
281
- If True, socket interaction will occur in a separate thread &
282
- frames will be buffered. Single-threaded, blocking IMDClient
283
- should only be used in testing [[``True``]]
284
-
285
- """
286
289
  super(BaseIMDProducer, self).__init__(daemon=True)
287
290
  self._conn = conn
288
291
  self._imdsinfo = sinfo
@@ -638,6 +641,21 @@ class IMDFrameBuffer:
638
641
  """
639
642
  Acts as interface between producer (IMDProducer) and consumer (IMDClient) threads
640
643
  when IMDClient runs in multithreaded mode
644
+
645
+ Parameters
646
+ ----------
647
+ imdsinfo : IMDSessionInfo
648
+ Information about the IMD session
649
+ n_atoms : int
650
+ Number of atoms in the simulation
651
+ buffer_size : int, optional
652
+ Size of the buffer in bytes [``10MB``]
653
+ pause_empty_proportion : float, optional
654
+ Lower threshold proportion of the buffer's IMDFrames that are empty
655
+ before the simulation is paused [``0.25``]
656
+ unpause_empty_proportion : float, optional
657
+ Proportion of the buffer's IMDFrames that must be empty
658
+ before the simulation is unpaused [``0.5``]
641
659
  """
642
660
 
643
661
  def __init__(
@@ -649,23 +667,6 @@ class IMDFrameBuffer:
649
667
  unpause_empty_proportion=0.5,
650
668
  **kwargs,
651
669
  ):
652
- """
653
- Parameters
654
- ----------
655
- imdsinfo : IMDSessionInfo
656
- Information about the IMD session
657
- n_atoms : int
658
- Number of atoms in the simulation
659
- buffer_size : int, optional
660
- Size of the buffer in bytes [``10MB``]
661
- pause_empty_proportion : float, optional
662
- Lower threshold proportion of the buffer's IMDFrames that are empty
663
- before the simulation is paused [``0.25``]
664
- unpause_empty_proportion : float, optional
665
- Proportion of the buffer's IMDFrames that must be empty
666
- before the simulation is unpaused [``0.5``]
667
- """
668
-
669
670
  # Syncing reader and producer
670
671
  self._producer_finished = False
671
672
  self._consumer_finished = False
imdclient/IMDREADER.py CHANGED
@@ -24,6 +24,17 @@ logger = logging.getLogger("imdclient.IMDClient")
24
24
  class IMDReader(StreamReaderBase):
25
25
  """
26
26
  Reader for IMD protocol packets.
27
+
28
+ Parameters
29
+ ----------
30
+ filename : a string of the form "host:port" where host is the hostname
31
+ or IP address of the listening GROMACS server and port
32
+ is the port number.
33
+ n_atoms : int (optional)
34
+ number of atoms in the system. defaults to number of atoms
35
+ in the topology. don't set this unless you know what you're doing.
36
+ kwargs : dict (optional)
37
+ keyword arguments passed to the constructed :class:`IMDClient`
27
38
  """
28
39
 
29
40
  format = "IMD"
@@ -37,17 +48,6 @@ class IMDReader(StreamReaderBase):
37
48
  n_atoms=None,
38
49
  **kwargs,
39
50
  ):
40
- """
41
- Parameters
42
- ----------
43
- filename : a string of the form "host:port" where host is the hostname
44
- or IP address of the listening GROMACS server and port
45
- is the port number.
46
- n_atoms : int (optional)
47
- number of atoms in the system. defaults to number of atoms
48
- in the topology. don't set this unless you know what you're doing.
49
- """
50
-
51
51
  super(IMDReader, self).__init__(filename, **kwargs)
52
52
 
53
53
  logger.debug("IMDReader initializing")
@@ -101,7 +101,9 @@ class IMDReader(StreamReaderBase):
101
101
  self.ts.data["dt"] = imdf.dt
102
102
  self.ts.data["step"] = imdf.step
103
103
  if imdf.energies is not None:
104
- self.ts.data.update(imdf.energies)
104
+ self.ts.data.update(
105
+ {k: v for k, v in imdf.energies.items() if k != "step"}
106
+ )
105
107
  if imdf.box is not None:
106
108
  self.ts.dimensions = core.triclinic_box(*imdf.box)
107
109
  if imdf.positions is not None:
@@ -6,33 +6,37 @@ Global pytest fixtures
6
6
  # Command line arguments for 'test_manual.py'
7
7
  def pytest_addoption(parser):
8
8
  parser.addoption(
9
- "--topol_arg",
9
+ "--topol_path_arg",
10
10
  action="store",
11
+ default=None,
11
12
  )
12
13
  parser.addoption(
13
- "--traj_arg",
14
+ "--traj_path_arg",
14
15
  action="store",
16
+ default=None,
17
+ )
18
+ parser.addoption(
19
+ "--first_frame_arg", action="store", type=int, default=None
15
20
  )
16
- parser.addoption("--first_frame_arg", action="store", type=int)
17
21
 
18
22
 
19
23
  def pytest_generate_tests(metafunc):
20
24
  # This is called for every test. Only get/set command line arguments
21
25
  # 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
26
+ topol = metafunc.config.option.topol_path_arg
27
+ traj = metafunc.config.option.traj_path_arg
24
28
  first_frame = metafunc.config.option.first_frame_arg
25
29
 
26
30
  if all(
27
31
  arg in metafunc.fixturenames
28
- for arg in ["topol_arg", "traj_arg", "first_frame_arg"]
32
+ for arg in ["topol_path_arg", "traj_path_arg", "first_frame_arg"]
29
33
  ):
30
34
  if topol is None or traj is None or first_frame is None:
31
35
  raise ValueError(
32
- "Must pass all three of '--topol_arg <path/to/topology>', "
33
- + "'--traj_arg <path/to/trajectory>', "
36
+ "Must pass all three of '--topol_path_arg <path/to/topology>', "
37
+ + "'--traj_path_arg <path/to/trajectory>', "
34
38
  + "'--first_frame_arg <first traj frame to compare to IMD>"
35
39
  )
36
- metafunc.parametrize("topol_arg", [topol])
37
- metafunc.parametrize("traj_arg", [traj])
40
+ metafunc.parametrize("topol_path_arg", [topol])
41
+ metafunc.parametrize("traj_path_arg", [traj])
38
42
  metafunc.parametrize("first_frame_arg", [first_frame])
@@ -55,7 +55,7 @@ class IMDReference(BaseReference):
55
55
  self.n_atoms = traj.n_atoms
56
56
  self.prec = 3
57
57
 
58
- self.trajectory = f"localhost:{self.port}"
58
+ self.trajectory = f"imd://localhost:{self.port}"
59
59
  self.topology = COORDINATES_TOPOLOGY
60
60
  self.changing_dimensions = True
61
61
  self.reader = IMDReader
@@ -585,7 +585,7 @@ class TestStreamIteration:
585
585
  server.set_imdsessioninfo(imdsinfo)
586
586
  server.handshake_sequence("localhost", port, first_frame=True)
587
587
  reader = IMDReader(
588
- f"localhost:{port}",
588
+ f"imd://localhost:{port}",
589
589
  n_atoms=universe.trajectory.n_atoms,
590
590
  )
591
591
  server.send_frames(1, 5)
@@ -1,18 +1,24 @@
1
- from imdclient.IMDClient import IMDClient
1
+ from imdclient.IMDREADER import IMDReader
2
2
  import pytest
3
3
  import MDAnalysis as mda
4
4
  from MDAnalysisTests.coordinates.base import assert_timestep_almost_equal
5
5
  from numpy.testing import (
6
- assert_array_almost_equal,
7
- assert_almost_equal,
8
6
  assert_allclose,
9
7
  )
10
8
  import numpy as np
11
9
  from .base import assert_allclose_with_logging
10
+ from pathlib import Path
12
11
 
13
12
  import logging
14
13
 
15
14
  logger = logging.getLogger("imdclient.IMDClient")
15
+ file_handler = logging.FileHandler("manual_test.log")
16
+ formatter = logging.Formatter(
17
+ "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
18
+ )
19
+ file_handler.setFormatter(formatter)
20
+ logger.addHandler(file_handler)
21
+ logger.setLevel(logging.DEBUG)
16
22
 
17
23
 
18
24
  class TestIMDv3Manual:
@@ -23,48 +29,63 @@ class TestIMDv3Manual:
23
29
  and then run this command relative to the root of the cloned respository:
24
30
 
25
31
  pytest -s imdclient/tests/test_manual.py \
26
- --topol_arg <path/to/topology> \
27
- --traj_arg <path/to/trajectory> \
32
+ --topol_path_arg <path/to/topology> \
33
+ --traj_path_arg <path/to/trajectory> \
28
34
  --first_frame_arg <first traj frame to compare to IMD>
29
35
 
30
- Where the topology is the same topology as the IMD system, the trajectory is the trajectory
31
- to compare to IMD data read from the socket, and the first frame is the first frame of the
36
+ Where the topology is the same topology as the IMD system, the trajectory is the path where
37
+ the trajectory of the running simulation is being written, and the first frame is the first frame of the
32
38
  trajectory which should be compared to IMD data read from the socket (0 for GROMACS and NAMD, 1 for LAMMPS)
33
39
  """
34
40
 
35
41
  @pytest.fixture()
36
- def universe(self, topol_arg, traj_arg):
37
- return mda.Universe(topol_arg, traj_arg)
42
+ def true_u(self, imd_u, topol_path_arg, traj_path_arg):
43
+ return mda.Universe(topol_path_arg, traj_path_arg)
38
44
 
39
45
  @pytest.fixture()
40
- def client(self, universe):
41
- client = IMDClient("localhost", 8888, universe.trajectory.n_atoms)
42
- yield client
43
- client.stop()
46
+ def imd_u(self, topol_path_arg, tmp_path):
47
+ tmp_u = mda.Universe(topol_path_arg, "imd://localhost:8888")
48
+ with mda.Writer(
49
+ f"{tmp_path.as_posix()}/imd_test_traj.trr", tmp_u.atoms.n_atoms
50
+ ) as w:
51
+ for ts in tmp_u.trajectory:
52
+ w.write(tmp_u.atoms)
53
+ imd_u = mda.Universe(
54
+ topol_path_arg, f"{tmp_path.as_posix()}/imd_test_traj.trr"
55
+ )
56
+ yield imd_u
44
57
 
45
- def test_compare_imd_to_true_traj(self, universe, client, first_frame_arg):
46
- imdsinfo = client.get_imdsessioninfo()
58
+ def test_compare_imd_to_true_traj(self, true_u, imd_u, first_frame_arg):
47
59
 
48
- for ts in universe.trajectory[first_frame_arg:]:
49
- imdf = client.get_imdframe()
50
- if imdsinfo.time:
51
- assert_allclose(imdf.time, ts.time, atol=1e-03)
52
- assert_allclose(imdf.step, ts.data["step"])
53
- if imdsinfo.box:
60
+ for i in range(first_frame_arg, len(true_u.trajectory)):
61
+ assert_allclose(
62
+ true_u.trajectory[i].time, imd_u.trajectory[i].time, atol=1e-03
63
+ )
64
+ assert_allclose(
65
+ true_u.trajectory[i].data["step"],
66
+ imd_u.trajectory[i].data["step"],
67
+ )
68
+ if true_u.trajectory[i].dimensions is not None:
54
69
  assert_allclose_with_logging(
55
- imdf.box,
56
- ts.triclinic_dimensions,
70
+ true_u.trajectory[i].dimensions,
71
+ imd_u.trajectory[i].dimensions,
57
72
  atol=1e-03,
58
73
  )
59
- if imdsinfo.positions:
74
+ if true_u.trajectory[i].has_positions:
60
75
  assert_allclose_with_logging(
61
- imdf.positions, ts.positions, atol=1e-03
76
+ true_u.trajectory[i].positions,
77
+ imd_u.trajectory[i].positions,
78
+ atol=1e-03,
62
79
  )
63
- if imdsinfo.velocities:
80
+ if true_u.trajectory[i].has_velocities:
64
81
  assert_allclose_with_logging(
65
- imdf.velocities, ts.velocities, atol=1e-03
82
+ true_u.trajectory[i].velocities,
83
+ imd_u.trajectory[i].velocities,
84
+ atol=1e-03,
66
85
  )
67
- if imdsinfo.forces:
86
+ if true_u.trajectory[i].has_forces:
68
87
  assert_allclose_with_logging(
69
- imdf.forces, ts.forces, atol=1e-03
88
+ true_u.trajectory[i].forces,
89
+ imd_u.trajectory[i].forces,
90
+ atol=1e-03,
70
91
  )
@@ -38,7 +38,7 @@ class TestStreamAnalysis:
38
38
  server.set_imdsessioninfo(imdsinfo)
39
39
  server.handshake_sequence("localhost", port, first_frame=True)
40
40
 
41
- imd_universe = mda.Universe(COORDINATES_TOPOLOGY, f"localhost:{port}")
41
+ imd_universe = mda.Universe(COORDINATES_TOPOLOGY, f"imd://localhost:{port}")
42
42
  server.send_frames(1, 5)
43
43
 
44
44
  yield imd_universe
imdclient/utils.py CHANGED
@@ -43,22 +43,22 @@ class timeit(object):
43
43
  # always propagate exceptions forward
44
44
  return False
45
45
 
46
-
47
46
  # NOTE: think of other edge cases as well- should be robust
48
47
  def parse_host_port(filename):
48
+ if not filename.startswith("imd://"):
49
+ raise ValueError("IMDReader: URL must be in the format 'imd://host:port'")
50
+
49
51
  # Check if the format is correct
50
- parts = filename.split(":")
52
+ parts = filename.split("imd://")[1].split(":")
51
53
  if len(parts) == 2:
52
- host = parts[0] # Hostname part
54
+ host = parts[0]
53
55
  try:
54
- port = int(parts[1]) # Convert the port part to an integer
56
+ port = int(parts[1])
55
57
  return (host, port)
56
58
  except ValueError:
57
- # Handle the case where the port is not a valid integer
58
- raise ValueError("Port must be an integer")
59
+ raise ValueError("IMDReader: Port must be an integer")
59
60
  else:
60
- # Handle the case where the format does not match "host:port"
61
- raise ValueError("Filename must be in the format 'host:port'")
61
+ raise ValueError("IMDReader: URL must be in the format 'imd://host:port'")
62
62
 
63
63
 
64
64
  def approximate_timestep_memsize(