Skip to content

Commit

Permalink
Merge pull request #31 from Becksteinlab/develop
Browse files Browse the repository at this point in the history
v0.1.2 release
  • Loading branch information
ljwoods2 authored Oct 28, 2024
2 parents c3ac798 + a821c58 commit b633c6a
Show file tree
Hide file tree
Showing 9 changed files with 188 additions and 116 deletions.
41 changes: 40 additions & 1 deletion docs/source/protocol_v3.rst
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,9 @@ and its associated body packet (if present) is described in detail.
* - :ref:`forces`
- 15
- ❌
* - :ref:`wait-flag`
- 16
- ❌

.. _disconnect:

Expand Down Expand Up @@ -475,7 +478,7 @@ forces were previously specified for this session in the :ref:`session info pack
.. code-block:: none
Header:
14 (int32) Forces
15 (int32) Forces
<n_atoms> (int32) Number of atoms in the IMD system
Body:
Expand All @@ -486,6 +489,42 @@ forces were previously specified for this session in the :ref:`session info pack
.. versionadded:: 3

.. _wait-flag:

Wait flag
^^^^^^^^^

Sent from the receiver to the simulation engine any time after the :ref:`session info packet <session-info>`
has been sent to request that the simulation engine modify its waiting behavior mid-simulation either
from blocking to non-blocking or vice versa.
Whether or not the simulation engine honors this request is an implementation decision.

Regardless of whether this packet is accepted, the simulation engine will have an initial waiting behavior which applies
to the beginning of the simulation:

1. Blocking: Wait until a receiver is connected to begin execution of the simulation
2. Non-blocking: Begin the simulation regardless of whether a receiver is connected and continuously check on the listening socket for a receiver attempting to connect

The simulation engine's waiting behavior also applies when a receiver disconnects mid-simulation:

1. Blocking: Pause simulation execution and wait until a receiver is connected to resume execution
2. Non-blocking: Continue execution, continuously checking on the listening socket for a receiver attempting to connect

.. code-block:: none
Header:
16 (int32) Wait flag
<val> (int32) Nonzero to set the simulation engine's waiting behavior to blocking, 0
to set the simulation engine's waiting behavior to non-blocking
.. note::

The purpose of this packet is to allow a receiver to monitor the first *n* frames
of a simulation and then disconnect without blocking the continued execution of the
simulation.

.. versionadded:: 3

Packet order
------------

Expand Down
9 changes: 7 additions & 2 deletions docs/source/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ like this: ::
# engine as the trajectory argument

# GROMACS
u = mda.Universe("topology.gro", "localhost:8888")
u = mda.Universe("topology.gro", "imd://localhost:8888")
# NAMD
u = mda.Universe("topology.psf", "localhost:8888")
u = mda.Universe("topology.psf", "imd://localhost:8888")

While this package allows the IMDReader to be automatically selected
based on the trajectory URL matching the pattern 'imd://<host>:<port>',
the format can be explicitly selected by passing the keyword argument
'format="IMD"' to the :class:Universe.
103 changes: 52 additions & 51 deletions imdclient/IMDClient.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,23 @@


class IMDClient:
"""
Parameters
----------
host : str
Hostname of the server
port : int
Port number of the server
n_atoms : int
Number of atoms in the simulation
socket_bufsize : int, (optional)
Size of the socket buffer in bytes. Default is to use the system default
buffer_size : int (optional)
IMDFramebuffer will be filled with as many :class:`IMDFrame` fit in `buffer_size` [``10MB``]
**kwargs : dict (optional)
Additional keyword arguments to pass to the :class:`BaseIMDProducer` and :class:`IMDFrameBuffer`
"""

def __init__(
self,
host,
Expand All @@ -39,22 +56,7 @@ def __init__(
multithreaded=True,
**kwargs,
):
"""
Parameters
----------
host : str
Hostname of the server
port : int
Port number of the server
n_atoms : int
Number of atoms in the simulation
socket_bufsize : int, optional
Size of the socket buffer in bytes. Default is to use the system default
buffer_size : int, optional
IMDFramebuffer will be filled with as many IMDFrames fit in `buffer_size` [``10MB``]
**kwargs : optional
Additional keyword arguments to pass to the IMDProducer and IMDFrameBuffer
"""

self._stopped = False
self._conn = self._connect_to_server(host, port, socket_bufsize)
self._imdsinfo = self._await_IMD_handshake()
Expand Down Expand Up @@ -254,6 +256,25 @@ def _disconnect(self):


class BaseIMDProducer(threading.Thread):
"""
Parameters
----------
conn : socket.socket
Connection object to the server
buffer : IMDFrameBuffer
Buffer object to hold IMD frames. If `multithreaded` is False, this
argument is ignored
sinfo : IMDSessionInfo
Information about the IMD session
n_atoms : int
Number of atoms in the simulation
multithreaded : bool, optional
If True, socket interaction will occur in a separate thread &
frames will be buffered. Single-threaded, blocking IMDClient
should only be used in testing [[``True``]]
"""

def __init__(
self,
Expand All @@ -265,24 +286,6 @@ def __init__(
timeout=5,
**kwargs,
):
"""
Parameters
----------
conn : socket.socket
Connection object to the server
buffer : IMDFrameBuffer
Buffer object to hold IMD frames. If `multithreaded` is False, this
argument is ignored
sinfo : IMDSessionInfo
Information about the IMD session
n_atoms : int
Number of atoms in the simulation
multithreaded : bool, optional
If True, socket interaction will occur in a separate thread &
frames will be buffered. Single-threaded, blocking IMDClient
should only be used in testing [[``True``]]
"""
super(BaseIMDProducer, self).__init__(daemon=True)
self._conn = conn
self._imdsinfo = sinfo
Expand Down Expand Up @@ -638,6 +641,21 @@ class IMDFrameBuffer:
"""
Acts as interface between producer (IMDProducer) and consumer (IMDClient) threads
when IMDClient runs in multithreaded mode
Parameters
----------
imdsinfo : IMDSessionInfo
Information about the IMD session
n_atoms : int
Number of atoms in the simulation
buffer_size : int, optional
Size of the buffer in bytes [``10MB``]
pause_empty_proportion : float, optional
Lower threshold proportion of the buffer's IMDFrames that are empty
before the simulation is paused [``0.25``]
unpause_empty_proportion : float, optional
Proportion of the buffer's IMDFrames that must be empty
before the simulation is unpaused [``0.5``]
"""

def __init__(
Expand All @@ -649,23 +667,6 @@ def __init__(
unpause_empty_proportion=0.5,
**kwargs,
):
"""
Parameters
----------
imdsinfo : IMDSessionInfo
Information about the IMD session
n_atoms : int
Number of atoms in the simulation
buffer_size : int, optional
Size of the buffer in bytes [``10MB``]
pause_empty_proportion : float, optional
Lower threshold proportion of the buffer's IMDFrames that are empty
before the simulation is paused [``0.25``]
unpause_empty_proportion : float, optional
Proportion of the buffer's IMDFrames that must be empty
before the simulation is unpaused [``0.5``]
"""

# Syncing reader and producer
self._producer_finished = False
self._consumer_finished = False
Expand Down
26 changes: 14 additions & 12 deletions imdclient/IMDREADER.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,17 @@
class IMDReader(StreamReaderBase):
"""
Reader for IMD protocol packets.
Parameters
----------
filename : a string of the form "host:port" where host is the hostname
or IP address of the listening GROMACS server and port
is the port number.
n_atoms : int (optional)
number of atoms in the system. defaults to number of atoms
in the topology. don't set this unless you know what you're doing.
kwargs : dict (optional)
keyword arguments passed to the constructed :class:`IMDClient`
"""

format = "IMD"
Expand All @@ -37,17 +48,6 @@ def __init__(
n_atoms=None,
**kwargs,
):
"""
Parameters
----------
filename : a string of the form "host:port" where host is the hostname
or IP address of the listening GROMACS server and port
is the port number.
n_atoms : int (optional)
number of atoms in the system. defaults to number of atoms
in the topology. don't set this unless you know what you're doing.
"""

super(IMDReader, self).__init__(filename, **kwargs)

logger.debug("IMDReader initializing")
Expand Down Expand Up @@ -101,7 +101,9 @@ def _load_imdframe_into_ts(self, imdf):
self.ts.data["dt"] = imdf.dt
self.ts.data["step"] = imdf.step
if imdf.energies is not None:
self.ts.data.update(imdf.energies)
self.ts.data.update(
{k: v for k, v in imdf.energies.items() if k != "step"}
)
if imdf.box is not None:
self.ts.dimensions = core.triclinic_box(*imdf.box)
if imdf.positions is not None:
Expand Down
24 changes: 14 additions & 10 deletions imdclient/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,37 @@
# Command line arguments for 'test_manual.py'
def pytest_addoption(parser):
parser.addoption(
"--topol_arg",
"--topol_path_arg",
action="store",
default=None,
)
parser.addoption(
"--traj_arg",
"--traj_path_arg",
action="store",
default=None,
)
parser.addoption(
"--first_frame_arg", action="store", type=int, default=None
)
parser.addoption("--first_frame_arg", action="store", type=int)


def pytest_generate_tests(metafunc):
# This is called for every test. Only get/set command line arguments
# if the argument is specified in the list of test "fixturenames".
topol = metafunc.config.option.topol_arg
traj = metafunc.config.option.traj_arg
topol = metafunc.config.option.topol_path_arg
traj = metafunc.config.option.traj_path_arg
first_frame = metafunc.config.option.first_frame_arg

if all(
arg in metafunc.fixturenames
for arg in ["topol_arg", "traj_arg", "first_frame_arg"]
for arg in ["topol_path_arg", "traj_path_arg", "first_frame_arg"]
):
if topol is None or traj is None or first_frame is None:
raise ValueError(
"Must pass all three of '--topol_arg <path/to/topology>', "
+ "'--traj_arg <path/to/trajectory>', "
"Must pass all three of '--topol_path_arg <path/to/topology>', "
+ "'--traj_path_arg <path/to/trajectory>', "
+ "'--first_frame_arg <first traj frame to compare to IMD>"
)
metafunc.parametrize("topol_arg", [topol])
metafunc.parametrize("traj_arg", [traj])
metafunc.parametrize("topol_path_arg", [topol])
metafunc.parametrize("traj_path_arg", [traj])
metafunc.parametrize("first_frame_arg", [first_frame])
4 changes: 2 additions & 2 deletions imdclient/tests/test_imdreader.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def __init__(self):
self.n_atoms = traj.n_atoms
self.prec = 3

self.trajectory = f"localhost:{self.port}"
self.trajectory = f"imd://localhost:{self.port}"
self.topology = COORDINATES_TOPOLOGY
self.changing_dimensions = True
self.reader = IMDReader
Expand Down Expand Up @@ -585,7 +585,7 @@ def reader(self, universe, imdsinfo, port):
server.set_imdsessioninfo(imdsinfo)
server.handshake_sequence("localhost", port, first_frame=True)
reader = IMDReader(
f"localhost:{port}",
f"imd://localhost:{port}",
n_atoms=universe.trajectory.n_atoms,
)
server.send_frames(1, 5)
Expand Down
Loading

0 comments on commit b633c6a

Please sign in to comment.