-
Notifications
You must be signed in to change notification settings - Fork 713
feature: IMDReader Integration #4923
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
base: develop
Are you sure you want to change the base?
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
""" | ||
MDAnalysis IMDReader | ||
^^^^^^^^^^^^^^^^^^^^ | ||
|
||
.. autoclass:: IMDReader | ||
:members: | ||
:inherited-members: | ||
|
||
""" | ||
|
||
from MDAnalysis.coordinates import core | ||
from MDAnalysis.lib.util import store_init_arguments | ||
from MDAnalysis.coordinates.util import parse_host_port | ||
from MDAnalysis.coordinates.base import StreamReaderBase | ||
|
||
try: | ||
import imdclient | ||
from imdclient.IMDClient import IMDClient | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These need test coverage with mocks There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry @hmacdope, I'm not entirely sure what you mean here, could you please elaborate, thank you! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @hmacdope do you have a moment to add some pointers for mocks, e.g., examples in the current tests that you have in mind? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can get coverage on the import guards by mocking the import using something like this. This was taken verbaitm from a PR of mine but the general idea should carry over nicely. Doesn't use mock directly but still effective. def test_HAS_DISTOPIA_distopia_too_old():
# mock a version of distopia that is too old
sys.modules.pop("distopia", None)
sys.modules.pop("MDAnalysis.lib._distopia", None)
module_name = "distopia"
mocked_module = ModuleType(module_name)
# too old version
mocked_module.__version__ = "0.1.0"
sys.modules[module_name] = mocked_module
import MDAnalysis.lib._distopia
assert HAS_DISTOPIA |
||
except ImportError: | ||
HAS_IMDCLIENT = False | ||
|
||
# Allow building doucmnetation without imdclient | ||
amruthesht marked this conversation as resolved.
Show resolved
Hide resolved
|
||
import types | ||
|
||
class MockIMDClient: | ||
pass | ||
imdclient = types.ModuleType("imdclient") | ||
imdclient.IMDClient = MockIMDClient | ||
|
||
else: | ||
HAS_IMDCLIENT = True | ||
|
||
import logging | ||
|
||
logger = logging.getLogger("imdclient.IMDClient") | ||
amruthesht marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
|
||
class IMDReader(StreamReaderBase): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I'm getting confused about which PR is for which thing. @orbeckst given our discussion earlier this week, and your comment above which I take to be "IMDClient is still in flux", does it not make sense for the IMDReader to exist upstream and then just import it here? (edit: here my intent is "well then you could make releases and it wouldn't be limited to MDA release frequency"). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have to split IMDReader from imdclient and make a version of imdclient without IMDReader (which is in the works Becksteinlab/imdclient#54 ). At the same time we are moving what was split off into coordinates.IMD. Amru is working on both at the moment. The way IMDReader depends on imdclient is not the problem, and imdclient itself is also pretty stable, it's just that the tests for imdclient have made use of a lot of MDAnalysis/IMDReader for convenience, and we now have to rewrite some of these tests to use bare-bones python. |
||
""" | ||
Reader for IMD protocol packets. | ||
|
||
Parameters | ||
---------- | ||
filename : a string of the form "host:port" where host is the hostname | ||
amruthesht marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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. | ||
amruthesht marked this conversation as resolved.
Show resolved
Hide resolved
|
||
kwargs : dict (optional) | ||
keyword arguments passed to the constructed :class:`IMDClient` | ||
""" | ||
|
||
format = "IMD" | ||
one_pass = True | ||
|
||
@store_init_arguments | ||
def __init__( | ||
self, | ||
filename, | ||
convert_units=True, | ||
n_atoms=None, | ||
**kwargs, | ||
): | ||
if not HAS_IMDCLIENT: | ||
raise ImportError( | ||
"IMDReader requires the imdclient package. " | ||
"Please install it with 'pip install imdclient'." | ||
) | ||
|
||
super(IMDReader, self).__init__(filename, **kwargs) | ||
|
||
self._imdclient = None | ||
logger.debug("IMDReader initializing") | ||
|
||
if n_atoms is None: | ||
raise ValueError("IMDReader: n_atoms must be specified") | ||
self.n_atoms = n_atoms | ||
|
||
host, port = parse_host_port(filename) | ||
|
||
# This starts the simulation | ||
self._imdclient = IMDClient(host, port, n_atoms, **kwargs) | ||
|
||
imdsinfo = self._imdclient.get_imdsessioninfo() | ||
# NOTE: after testing phase, fail out on IMDv2 | ||
|
||
self.ts = self._Timestep( | ||
self.n_atoms, | ||
positions=imdsinfo.positions, | ||
velocities=imdsinfo.velocities, | ||
forces=imdsinfo.forces, | ||
**self._ts_kwargs, | ||
) | ||
|
||
self._frame = -1 | ||
|
||
try: | ||
self._read_next_timestep() | ||
except StopIteration: | ||
raise RuntimeError("IMDReader: No data found in stream") | ||
|
||
def _read_frame(self, frame): | ||
|
||
try: | ||
imdf = self._imdclient.get_imdframe() | ||
except EOFError as e: | ||
raise e | ||
|
||
self._frame = frame | ||
self._load_imdframe_into_ts(imdf) | ||
|
||
logger.debug(f"IMDReader: Loaded frame {self._frame}") | ||
return self.ts | ||
|
||
def _load_imdframe_into_ts(self, imdf): | ||
self.ts.frame = self._frame | ||
if imdf.time is not None: | ||
self.ts.time = imdf.time | ||
# NOTE: timestep.pyx "dt" method is suspicious bc it uses "new" keyword for a float | ||
self.ts.data["dt"] = imdf.dt | ||
self.ts.data["step"] = imdf.step | ||
if imdf.energies is not None: | ||
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: | ||
# must call copy because reference is expected to reset | ||
# see 'test_frame_collect_all_same' in MDAnalysisTests.coordinates.base | ||
self.ts.positions = imdf.positions | ||
amruthesht marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if imdf.velocities is not None: | ||
self.ts.velocities = imdf.velocities | ||
if imdf.forces is not None: | ||
self.ts.forces = imdf.forces | ||
|
||
@staticmethod | ||
def _format_hint(thing): | ||
try: | ||
parse_host_port(thing) | ||
except: | ||
return False | ||
return HAS_IMDCLIENT and True | ||
|
||
def close(self): | ||
"""Gracefully shut down the reader. Stops the producer thread.""" | ||
logger.debug("IMDReader close() called") | ||
if self._imdclient is not None: | ||
self._imdclient.stop() | ||
# NOTE: removeme after testing | ||
logger.debug("IMDReader shut down gracefully.") | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's going to take time to get imdclient to a stage where the imdclient package does not actually affect MDAnalysis.
Is there a way that we could temporarily (for initial CI testing) install imdclient from a branch or tarball, e.g., in a
pip
section? Then we could fairly rapidly create a preliminary (unpublished) imdclient package without IMDReader.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By initial CI testing, do you mean "in this PR"?
There's a pip section just below, which should work if you put in the git location for pip install, but also you can just temporarily modify the CI script to do an additional pip install if it's for "testing within the PR itself".
If it's "after merge", this would require more discussion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just for right now to bootstrap the PR.
I don't want to merge without a solid conda-forge imdclient package in place.