Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v0.1.2 release #31

Merged
merged 12 commits into from
Oct 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
695 changes: 674 additions & 21 deletions LICENSE

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,20 @@ IMDClient
| **Latest release** | [![Last release tag][badge_release]][url_latest_release] ![GitHub commits since latest release (by date) for a branch][badge_commits_since] [![Documentation Status][badge_docs]][url_docs]|
| :----------------- | :------- |
| **Status** | [![GH Actions Status][badge_actions]][url_actions] [![codecov][badge_codecov]][url_codecov] |
| **Community** | [![License: GPL v2][badge_license]][url_license] [![Powered by MDAnalysis][badge_mda]][url_mda]|
| **Community** | [![License: GPL v3][badge_license]][url_license] [![Powered by MDAnalysis][badge_mda]][url_mda]|

[badge_actions]: https://github.com/becksteinlab/imdclient/actions/workflows/gh-ci.yaml/badge.svg
[badge_codecov]: https://codecov.io/gh/becksteinlab/imdclient/branch/main/graph/badge.svg
[badge_commits_since]: https://img.shields.io/github/commits-since/becksteinlab/imdclient/latest
[badge_docs]: https://readthedocs.org/projects/imdclient/badge/?version=latest
[badge_license]: https://img.shields.io/badge/License-MIT-blue.svg
[badge_license]: https://img.shields.io/badge/License-GPLv3-blue.svg
[badge_mda]: https://img.shields.io/badge/powered%20by-MDAnalysis-orange.svg?logoWidth=16&logo=data:image/x-icon;base64,AAABAAEAEBAAAAEAIAAoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJD+XwCY/fEAkf3uAJf97wGT/a+HfHaoiIWE7n9/f+6Hh4fvgICAjwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACT/yYAlP//AJ///wCg//8JjvOchXly1oaGhv+Ghob/j4+P/39/f3IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJH8aQCY/8wAkv2kfY+elJ6al/yVlZX7iIiI8H9/f7h/f38UAAAAAAAAAAAAAAAAAAAAAAAAAAB/f38egYF/noqAebF8gYaagnx3oFpUUtZpaWr/WFhY8zo6OmT///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgICAn46Ojv+Hh4b/jouJ/4iGhfcAAADnAAAA/wAAAP8AAADIAAAAAwCj/zIAnf2VAJD/PAAAAAAAAAAAAAAAAICAgNGHh4f/gICA/4SEhP+Xl5f/AwMD/wAAAP8AAAD/AAAA/wAAAB8Aov9/ALr//wCS/Z0AAAAAAAAAAAAAAACBgYGOjo6O/4mJif+Pj4//iYmJ/wAAAOAAAAD+AAAA/wAAAP8AAABhAP7+FgCi/38Axf4fAAAAAAAAAAAAAAAAiIiID4GBgYKCgoKogoB+fYSEgZhgYGDZXl5e/m9vb/9ISEjpEBAQxw8AAFQAAAAAAAAANQAAADcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAjo6Mb5iYmP+cnJz/jY2N95CQkO4pKSn/AAAA7gAAAP0AAAD7AAAAhgAAAAEAAAAAAAAAAACL/gsAkv2uAJX/QQAAAAB9fX3egoKC/4CAgP+NjY3/c3Nz+wAAAP8AAAD/AAAA/wAAAPUAAAAcAAAAAAAAAAAAnP4NAJL9rgCR/0YAAAAAfX19w4ODg/98fHz/i4uL/4qKivwAAAD/AAAA/wAAAP8AAAD1AAAAGwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALGxsVyqqqr/mpqa/6mpqf9KSUn/AAAA5QAAAPkAAAD5AAAAhQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADkUFBSuZ2dn/3V1df8uLi7bAAAATgBGfyQAAAA2AAAAMwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB0AAADoAAAA/wAAAP8AAAD/AAAAWgC3/2AAnv3eAJ/+dgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9AAAA/wAAAP8AAAD/AAAA/wAKDzEAnP3WAKn//wCS/OgAf/8MAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIQAAANwAAADtAAAA7QAAAMAAABUMAJn9gwCe/e0Aj/2LAP//AQAAAAAAAAAA
[badge_release]: https://img.shields.io/github/release-pre/becksteinlab/imdclient.svg
[url_actions]: https://github.com/becksteinlab/imdclient/actions?query=branch%3Amain+workflow%3Agh-ci
[url_codecov]: https://codecov.io/gh/becksteinlab/imdclient/branch/main
[url_docs]: https://imdclient.readthedocs.io/en/latest/?badge=latest
[url_latest_release]: https://github.com/becksteinlab/imdclient/releases
[url_license]: https://www.gnu.org/licenses/gpl-2.0
[url_license]: https://www.gnu.org/licenses/gpl-3.0
[url_mda]: https://www.mdanalysis.org

Receiver for IMDv3 protocol from simulation engines like Gromacs, LAMMPS, and NAMD.
Expand Down Expand Up @@ -88,7 +88,7 @@ pip install ".[test,doc]"
### Copyright

The IMDClient source code is hosted at https://github.com/becksteinlab/imdclient
and is available under the MIT license (see the file [LICENSE](https://github.com/becksteinlab/imdclient/blob/main/LICENSE)).
and is available under the GNU General Public License, version 3 (see the file [LICENSE](https://github.com/becksteinlab/imdclient/blob/main/LICENSE)).

Copyright (c) 2024, Lawson

Expand Down
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
Loading