imdclient 0.1.4__py3-none-any.whl → 0.2.0b0__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.
@@ -1,6 +1,9 @@
1
- import MDAnalysis as mda
2
- import pytest
3
1
  import logging
2
+ from pathlib import Path
3
+ import re
4
+
5
+ import pytest
6
+
4
7
  from .base import IMDv3IntegrationTest
5
8
  from .datafiles import (
6
9
  GROMACS_GRO,
@@ -8,7 +11,6 @@ from .datafiles import (
8
11
  GROMACS_MDP_NST_1,
9
12
  GROMACS_MDP_NST_8,
10
13
  )
11
- from pathlib import Path
12
14
 
13
15
  logger = logging.getLogger("imdclient.IMDClient")
14
16
  file_handler = logging.FileHandler("gromacs_test.log")
@@ -46,6 +48,16 @@ class TestIMDv3Gromacs(IMDv3IntegrationTest):
46
48
  def topol(self):
47
49
  return Path(GROMACS_GRO).name
48
50
 
51
+ @pytest.fixture()
52
+ def dt(self, mdp):
53
+ pattern = re.compile(r"^\s*dt\s*=\s*(\S+)")
54
+ with open(mdp, "r") as file:
55
+ for line in file:
56
+ match = pattern.match(line)
57
+ if match:
58
+ return float(match.group(1))
59
+ raise ValueError(f"No dt found in {mdp}")
60
+
49
61
  # @pytest.fixture()
50
62
  # def match_string(self):
51
63
  # return "IMD: Will wait until I have a connection and IMD_GO orders."
@@ -1,10 +1,17 @@
1
1
  """Test for IMDClient functionality"""
2
2
 
3
+ import logging
4
+
5
+ import pytest
6
+ from numpy.testing import (
7
+ assert_allclose,
8
+ )
9
+ import MDAnalysis as mda
3
10
  from MDAnalysisTests.datafiles import (
4
11
  COORDINATES_TOPOLOGY,
5
12
  COORDINATES_H5MD,
6
13
  )
7
- import MDAnalysis as mda
14
+
8
15
  from imdclient.IMDClient import imdframe_memsize, IMDClient
9
16
  from imdclient.IMDProtocol import IMDHeaderType
10
17
  from .utils import (
@@ -12,12 +19,6 @@ from .utils import (
12
19
  create_default_imdsinfo_v3,
13
20
  )
14
21
  from .server import InThreadIMDServer
15
- from MDAnalysisTests.coordinates.base import (
16
- assert_allclose,
17
- )
18
- from MDAnalysisTests.coordinates.test_xdr import TRRReference
19
- import logging
20
- import pytest
21
22
 
22
23
 
23
24
  logger = logging.getLogger("imdclient.IMDClient")
@@ -1,7 +1,11 @@
1
- import MDAnalysis as mda
2
- import pytest
3
- from pathlib import Path
4
1
  import logging
2
+ from pathlib import Path
3
+ import re
4
+
5
+ import pytest
6
+ import MDAnalysis as mda
7
+
8
+ from .minimalreader import MinimalReader
5
9
  from .base import IMDv3IntegrationTest
6
10
  from .datafiles import LAMMPS_TOPOL, LAMMPS_IN_NST_1, LAMMPS_IN_NST_8
7
11
 
@@ -48,10 +52,15 @@ class TestIMDv3Lammps(IMDv3IntegrationTest):
48
52
  else:
49
53
  return 0
50
54
 
51
- # Not present in lammps-produced H5MD
52
55
  @pytest.fixture()
53
- def comp_dt(self):
54
- return False
56
+ def dt(self, inp):
57
+ pattern = re.compile(r"^\s*timestep\s*(\S+)")
58
+ with open(inp, "r") as file:
59
+ for line in file:
60
+ match = pattern.match(line)
61
+ if match:
62
+ return float(match.group(1))
63
+ raise ValueError(f"No dt found in {inp}")
55
64
 
56
65
  # This must wait until after imd stream has ended
57
66
  @pytest.fixture()
@@ -66,18 +75,12 @@ class TestIMDv3Lammps(IMDv3IntegrationTest):
66
75
 
67
76
  @pytest.fixture()
68
77
  def imd_u(self, docker_client, topol, tmp_path, port):
69
- u = mda.Universe(
70
- (tmp_path / topol),
71
- f"imd://localhost:{port}",
72
- atom_style="id type x y z",
73
- )
74
- with mda.Writer(
75
- (tmp_path / "imd.trr").as_posix(), u.trajectory.n_atoms
76
- ) as w:
77
- for ts in u.trajectory:
78
- w.write(u.atoms)
79
- yield mda.Universe(
80
- (tmp_path / topol),
81
- (tmp_path / "imd.trr"),
78
+ n_atoms = mda.Universe(
79
+ tmp_path / topol,
82
80
  atom_style="id type x y z",
81
+ convert_units=False,
82
+ ).atoms.n_atoms
83
+ u = MinimalReader(
84
+ f"imd://localhost:{port}", n_atoms=n_atoms, process_stream=True
83
85
  )
86
+ yield u
@@ -1,5 +1,4 @@
1
- import imdclient
2
- from imdclient.IMD import IMDReader
1
+ from .minimalreader import MinimalReader
3
2
  import MDAnalysis as mda
4
3
  from numpy.testing import assert_allclose
5
4
  import numpy as np
@@ -91,17 +90,15 @@ def load_true_universe(topol_path, traj_path):
91
90
 
92
91
  def load_imd_universe(topol_path, tmp_path):
93
92
  # Pass atom_style (ignored if not using LAMMPS topol)
94
- tmp_u = mda.Universe(
93
+ n_atoms = mda.Universe(
95
94
  topol_path,
96
- "imd://localhost:8888",
97
95
  atom_style="id type x y z",
96
+ convert_units=False,
97
+ ).atoms.n_atoms
98
+ tmp_u = MinimalReader(
99
+ f"imd://localhost:8888", n_atoms=n_atoms, process_stream=True
98
100
  )
99
- tmp_traj_file = f"{tmp_path}/imd_test_traj.trr"
100
- with mda.Writer(tmp_traj_file, tmp_u.atoms.n_atoms) as w:
101
- for ts in tmp_u.trajectory:
102
- w.write(tmp_u.atoms)
103
- time.sleep(10) # Give MPI ranks a chance to release FD
104
- return mda.Universe(topol_path, tmp_traj_file, atom_style="id type x y z")
101
+ return tmp_u
105
102
 
106
103
 
107
104
  def test_compare_imd_to_true_traj_vel(imd_u, true_u_vel, first_frame):
@@ -123,23 +120,27 @@ def test_compare_imd_to_true_traj_forces(imd_u, true_u_force, first_frame):
123
120
  )
124
121
 
125
122
 
126
- def test_compare_imd_to_true_traj(imd_u, true_u, first_frame, vel, force, dt):
123
+ def test_compare_imd_to_true_traj(
124
+ imd_u, true_u, first_frame, vel, force, dt, step
125
+ ):
127
126
  for i in range(first_frame, len(true_u.trajectory)):
128
127
  assert_allclose(
129
128
  true_u.trajectory[i].time,
130
129
  imd_u.trajectory[i - first_frame].time,
131
130
  atol=1e-03,
132
131
  )
133
- if dt:
132
+ # Issue #63
133
+ # if dt:
134
+ # assert_allclose(
135
+ # true_u.trajectory[i].dt,
136
+ # imd_u.trajectory[i - first_frame].dt,
137
+ # atol=1e-03,
138
+ # )
139
+ if step:
134
140
  assert_allclose(
135
- true_u.trajectory[i].dt,
136
- imd_u.trajectory[i - first_frame].dt,
137
- atol=1e-03,
141
+ true_u.trajectory[i].data["step"],
142
+ imd_u.trajectory[i - first_frame].data["step"],
138
143
  )
139
- assert_allclose(
140
- true_u.trajectory[i].data["step"],
141
- imd_u.trajectory[i - first_frame].data["step"],
142
- )
143
144
  assert_allclose_with_logging(
144
145
  true_u.trajectory[i].dimensions,
145
146
  imd_u.trajectory[i - first_frame].dimensions,
@@ -207,6 +208,8 @@ def main():
207
208
  vel_in_trr = args.vel_path is None
208
209
  force_in_trr = args.force_path is None
209
210
  dt_in_trr = not args.topol_path.endswith(".data")
211
+ # True when not using DCDReader
212
+ step_in_trr = not args.traj_path.endswith(".coor")
210
213
 
211
214
  test_compare_imd_to_true_traj(
212
215
  imd_u,
@@ -215,6 +218,7 @@ def main():
215
218
  vel_in_trr,
216
219
  force_in_trr,
217
220
  dt_in_trr,
221
+ step_in_trr,
218
222
  )
219
223
 
220
224
  if args.vel_path is not None:
@@ -223,9 +227,7 @@ def main():
223
227
  )
224
228
  true_vel = load_true_universe(args.topol_path, args.vel_path)
225
229
  print("Comparing velocities...")
226
- test_compare_imd_to_true_traj_vel(
227
- imd_u, true_vel, args.first_frame
228
- )
230
+ test_compare_imd_to_true_traj_vel(imd_u, true_vel, args.first_frame)
229
231
 
230
232
  if args.force_path is not None:
231
233
  print(
@@ -1,6 +1,13 @@
1
- import MDAnalysis as mda
2
- import pytest
3
1
  import logging
2
+ from pathlib import Path
3
+ import re
4
+
5
+ import pytest
6
+ from numpy.testing import (
7
+ assert_allclose,
8
+ )
9
+ import MDAnalysis as mda
10
+
4
11
  from .base import IMDv3IntegrationTest, assert_allclose_with_logging
5
12
  from .datafiles import (
6
13
  NAMD_TOPOL,
@@ -9,10 +16,6 @@ from .datafiles import (
9
16
  NAMD_PARAMS,
10
17
  NAMD_PSF,
11
18
  )
12
- from pathlib import Path
13
- from numpy.testing import (
14
- assert_allclose,
15
- )
16
19
 
17
20
  logger = logging.getLogger("imdclient.IMDClient")
18
21
  file_handler = logging.FileHandler("namd_test.log")
@@ -26,6 +29,10 @@ logger.setLevel(logging.DEBUG)
26
29
 
27
30
  class TestIMDv3NAMD(IMDv3IntegrationTest):
28
31
 
32
+ @pytest.fixture()
33
+ def container_name(self):
34
+ return "ghcr.io/becksteinlab/streaming-namd-docker:main-common-cpu"
35
+
29
36
  @pytest.fixture(params=[NAMD_CONF_NST_1, NAMD_CONF_NST_8])
30
37
  def inp(self, request):
31
38
  return request.param
@@ -42,6 +49,18 @@ class TestIMDv3NAMD(IMDv3IntegrationTest):
42
49
  def topol(self):
43
50
  return Path(NAMD_TOPOL).name
44
51
 
52
+ @pytest.fixture()
53
+ def dt(self, inp):
54
+ pattern = re.compile(r"^\s*timestep\s*(\S+)")
55
+ with open(inp, "r") as file:
56
+ for line in file:
57
+ match = pattern.match(line)
58
+ if match:
59
+ # NAMD timestep is in femtoseconds, convert to picoseconds
60
+ # as IMDv3 expects dt in ps, 1fs = 0.001ps
61
+ return float(match.group(1)) * 0.001
62
+ raise ValueError(f"No dt found in {inp}")
63
+
45
64
  @pytest.fixture()
46
65
  def true_u(self, topol, imd_u, tmp_path):
47
66
  u = mda.Universe(
@@ -75,40 +94,43 @@ class TestIMDv3NAMD(IMDv3IntegrationTest):
75
94
  return 0
76
95
 
77
96
  # Compare coords, box, time, dt, step
78
- def test_compare_imd_to_true_traj(self, imd_u, true_u, first_frame):
97
+ def test_compare_imd_to_true_traj(self, imd_u, true_u, first_frame, dt):
79
98
  for i in range(first_frame, len(true_u.trajectory)):
99
+
80
100
  assert_allclose(
81
101
  true_u.trajectory[i].time,
82
102
  imd_u.trajectory[i - first_frame].time,
83
103
  atol=1e-03,
84
104
  )
105
+
85
106
  assert_allclose(
86
- true_u.trajectory[i].dt,
107
+ dt,
87
108
  imd_u.trajectory[i - first_frame].dt,
88
109
  atol=1e-03,
89
110
  )
90
- assert_allclose(
91
- true_u.trajectory[i].data["step"],
92
- imd_u.trajectory[i - first_frame].data["step"],
93
- )
111
+ # step in DCDReader is frame index, not integration step
112
+ # don't compare step
113
+
94
114
  assert_allclose_with_logging(
95
115
  true_u.trajectory[i].dimensions,
96
116
  imd_u.trajectory[i - first_frame].dimensions,
97
117
  atol=1e-03,
98
118
  )
119
+
99
120
  assert_allclose_with_logging(
100
121
  true_u.trajectory[i].positions,
101
122
  imd_u.trajectory[i - first_frame].positions,
102
123
  atol=1e-03,
103
124
  )
104
125
 
126
+ # Since NAMD does not write velocities, forces to the DCD file, we need to do so seperately by extracting that info from their respective DCD files
105
127
  # Compare velocities
106
- def test_compare_imd_to_true_traj_vel(
107
- self, imd_u, true_u_vel, first_frame
108
- ):
128
+ def test_compare_imd_to_true_traj_vel(self, imd_u, true_u_vel, first_frame):
109
129
  for i in range(first_frame, len(true_u_vel.trajectory)):
130
+
110
131
  assert_allclose_with_logging(
111
- true_u_vel.trajectory[i].positions,
132
+ # Unit conversion
133
+ true_u_vel.trajectory[i].positions * 20.45482706,
112
134
  imd_u.trajectory[i - first_frame].velocities,
113
135
  atol=1e-03,
114
136
  )
@@ -118,6 +140,7 @@ class TestIMDv3NAMD(IMDv3IntegrationTest):
118
140
  self, imd_u, true_u_force, first_frame
119
141
  ):
120
142
  for i in range(first_frame, len(true_u_force.trajectory)):
143
+
121
144
  assert_allclose_with_logging(
122
145
  true_u_force.trajectory[i].positions,
123
146
  imd_u.trajectory[i - first_frame].forces,
@@ -0,0 +1,31 @@
1
+ # tests/test_utils.py
2
+ import pytest
3
+ from imdclient.utils import parse_host_port
4
+
5
+
6
+ @pytest.mark.parametrize(
7
+ "server_address,host_port",
8
+ [
9
+ ("imd://localhost:8888", ("localhost", 8888)),
10
+ ("imd://example.com:12345", ("example.com", 12345)),
11
+ ],
12
+ )
13
+ def test_parse_host_port_valid(server_address, host_port):
14
+ assert parse_host_port(server_address) == host_port
15
+
16
+
17
+ @pytest.mark.parametrize(
18
+ "server_address",
19
+ [
20
+ "", # empty
21
+ "http://localhost:80", # wrong protocol prefix
22
+ "imd://", # missing host and port
23
+ "imd://localhost:", # missing port
24
+ "imd://:8080", # missing host
25
+ "imd://host:notaport", # port not integer
26
+ "imd://host:80:90", # too many segments
27
+ ],
28
+ )
29
+ def test_parse_host_port_invalid(server_address):
30
+ with pytest.raises(ValueError):
31
+ parse_host_port(server_address)
imdclient/utils.py CHANGED
@@ -43,23 +43,6 @@ class timeit(object):
43
43
  # always propagate exceptions forward
44
44
  return False
45
45
 
46
- # NOTE: think of other edge cases as well- should be robust
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
-
51
- # Check if the format is correct
52
- parts = filename.split("imd://")[1].split(":")
53
- if len(parts) == 2:
54
- host = parts[0]
55
- try:
56
- port = int(parts[1])
57
- return (host, port)
58
- except ValueError:
59
- raise ValueError("IMDReader: Port must be an integer")
60
- else:
61
- raise ValueError("IMDReader: URL must be in the format 'imd://host:port'")
62
-
63
46
 
64
47
  def approximate_timestep_memsize(
65
48
  n_atoms, energies, dimensions, positions, velocities, forces
@@ -116,3 +99,53 @@ def sock_contains_data(sock, timeout) -> bool:
116
99
  [sock], [], [], timeout
117
100
  )
118
101
  return sock in ready_to_read
102
+
103
+
104
+ def parse_host_port(filename):
105
+ """
106
+ Parses a URL in the format 'imd://host:port' and returns the host and port.
107
+ Parameters
108
+ ----------
109
+ filename : str
110
+ The URL to parse, must be in the format 'imd://host:port'.
111
+
112
+ Returns
113
+ -------
114
+ tuple[str, int]
115
+ A 2-tuple ``(host, port)`` where `host` is the host server name
116
+ and `port` is the TCP port number.
117
+ Raises
118
+ ------
119
+ ValueError
120
+ If the URL is not in the correct format or if the host or port is invalid.
121
+
122
+ Examples
123
+ --------
124
+ >>> parse_host_port("imd://localhost:8888")
125
+ ('localhost', 8888)
126
+ >>> parse_host_port("invalid://localhost:12345")
127
+ Traceback (most recent call last):
128
+ ... ValueError: IMDClient: URL must be in the format 'imd://host:port'
129
+ """
130
+ if not filename.startswith("imd://"):
131
+ raise ValueError(
132
+ "IMDClient: URL must be in the format 'imd://host:port'"
133
+ )
134
+
135
+ # Check if the format is correct
136
+ parts = filename.split("imd://")[1].split(":")
137
+ if len(parts) == 2:
138
+ host = parts[0]
139
+ if not host:
140
+ raise ValueError("IMDClient: Host cannot be empty")
141
+ if not parts[1]:
142
+ raise ValueError("IMDClient: Port cannot be empty")
143
+ try:
144
+ port = int(parts[1])
145
+ return (host, port)
146
+ except ValueError:
147
+ raise ValueError("IMDClient: Port must be an integer")
148
+ else:
149
+ raise ValueError(
150
+ "IMDClient: URL must be in the format 'imd://host:port'"
151
+ )
@@ -1,29 +1,36 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: imdclient
3
- Version: 0.1.4
3
+ Version: 0.2.0b0
4
4
  Summary: Receiver for IMD v2 and v3 data from simulation engines like Gromacs, LAMMPS, and NAMD
5
5
  Author-email: Lawson <ljwoods2@asu.edu>
6
- Maintainer-email: Lawson <ljwoods2@asu.edu>
6
+ Maintainer-email: Lawson <ljwoods2@asu.edu>, Amruthesh Thirumalaiswamy <athiru12@asu.edu>
7
7
  License: Copyright 2024 Lawson Woods
8
8
 
9
9
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
10
10
 
11
11
  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
14
+
15
+ Project-URL: source, https://github.com/becksteinlab/imdclient
16
+ Project-URL: documentation, https://imdclient.readthedocs.io
12
17
  Keywords: molecular simulations
13
- Requires-Python: >=3.9
18
+ Requires-Python: >=3.10
14
19
  Description-Content-Type: text/markdown
15
20
  License-File: LICENSE
16
21
  License-File: AUTHORS.md
17
- Requires-Dist: MDAnalysis>=2.7.0
22
+ Requires-Dist: numpy
18
23
  Provides-Extra: test
19
24
  Requires-Dist: pytest>=6.0; extra == "test"
20
25
  Requires-Dist: pytest-xdist>=2.5; extra == "test"
21
26
  Requires-Dist: pytest-cov>=3.0; extra == "test"
22
- Requires-Dist: MDAnalysisTests>=2.7.0; extra == "test"
27
+ Requires-Dist: MDAnalysis; extra == "test"
28
+ Requires-Dist: MDAnalysisTests; extra == "test"
23
29
  Requires-Dist: docker-py; extra == "test"
24
30
  Provides-Extra: doc
25
31
  Requires-Dist: sphinx; extra == "doc"
26
- Requires-Dist: sphinx_rtd_theme; extra == "doc"
32
+ Requires-Dist: mdanalysis-sphinx-theme>=1.0.1; extra == "doc"
33
+ Dynamic: license-file
27
34
 
28
35
  IMDClient
29
36
  ==============================
@@ -32,86 +39,99 @@ IMDClient
32
39
  | **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]|
33
40
  | :----------------- | :------- |
34
41
  | **Status** | [![GH Actions Status][badge_actions]][url_actions] [![codecov][badge_codecov]][url_codecov] |
35
- | **Community** | [![License: MIT][badge_license]][url_license] [![Powered by MDAnalysis][badge_mda]][url_mda]|
42
+ | **Community** | [![License: MIT][badge_license]][url_license]
36
43
 
37
44
  [badge_actions]: https://github.com/becksteinlab/imdclient/actions/workflows/gh-ci.yaml/badge.svg
38
45
  [badge_codecov]: https://codecov.io/gh/becksteinlab/imdclient/branch/main/graph/badge.svg
39
46
  [badge_commits_since]: https://img.shields.io/github/commits-since/becksteinlab/imdclient/latest
40
47
  [badge_docs]: https://readthedocs.org/projects/imdclient/badge/?version=latest
41
48
  [badge_license]: https://img.shields.io/badge/License-MIT-blue.svg
42
- [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
43
49
  [badge_release]: https://img.shields.io/github/release-pre/becksteinlab/imdclient.svg
44
50
  [url_actions]: https://github.com/becksteinlab/imdclient/actions?query=branch%3Amain+workflow%3Agh-ci
45
51
  [url_codecov]: https://codecov.io/gh/becksteinlab/imdclient/branch/main
46
52
  [url_docs]: https://imdclient.readthedocs.io/en/latest/?badge=latest
47
53
  [url_latest_release]: https://github.com/becksteinlab/imdclient/releases
48
54
  [url_license]: https://opensource.org/license/mit
49
- [url_mda]: https://www.mdanalysis.org
50
55
 
51
56
  Receiver for [IMDv3 protocol](https://imdclient.readthedocs.io/en/latest/protocol_v3.html) from simulation engines like Gromacs, LAMMPS, and NAMD.
52
57
 
53
- IMDClient is bound by a [Code of Conduct](https://github.com/becksteinlab/imdreader/blob/main/CODE_OF_CONDUCT.md).
58
+ IMDClient is bound by a [Code of Conduct](https://github.com/becksteinlab/imdclient/blob/main/CODE_OF_CONDUCT.md).
54
59
 
55
60
  ### Installation
56
61
 
57
- IMDClient is available via PyPi and can be installed with pip:
58
- ```bash
62
+ #### Install via mamba (recommended)
63
+ To install the latest release of IMDClient from conda-forge:
64
+
65
+ ```
66
+ mamba install -c conda-forge imdclient
67
+ ```
68
+
69
+ #### Install via pip
70
+ To install the latest release of IMDClient from PyPI:
71
+
72
+ ```
59
73
  pip install imdclient
60
74
  ```
61
75
 
62
- To build IMDClient from source,
63
- we highly recommend using virtual environments.
64
- If possible, we strongly recommend that you use
65
- [Anaconda](https://docs.conda.io/en/latest/) as your package manager.
66
- Below we provide instructions both for `conda` and
67
- for `pip`.
76
+ ---
68
77
 
69
- #### With conda
78
+ ### Building from Source
79
+ To build IMDClient from source, we highly recommend using virtual environments.
80
+ If possible, we recommend that you use [mamba](https://mamba.readthedocs.io/en/latest/) as your package manager through [miniforge](https://github.com/conda-forge/miniforge). (You can substitute `conda` for `mamba` in the commands below if you prefer.)
70
81
 
71
- Ensure that you have [conda](https://docs.conda.io/projects/conda/en/latest/user-guide/install/index.html) installed.
82
+ Below we provide instructions both for `mamba` and for `pip`.
72
83
 
73
- Create a virtual environment and activate it:
84
+ #### Source build with mamba
85
+ 1. Ensure you have [mamba](https://mamba.readthedocs.io/en/latest/installation/mamba-installation.html) installed.
86
+
87
+ 2. Create and activate a new environment:
74
88
 
75
89
  ```
76
- conda create --name imdclient
77
- conda activate imdclient
90
+ mamba create --name imdclient
91
+ mamba activate imdclient
78
92
  ```
79
93
 
80
- <!-- Install the development and documentation dependencies:
94
+ 3. (Recommended) Install all dependencies using the provided environment YAML files for a clean and reproducible environment:
81
95
 
82
96
  ```
83
- conda env update --name imdreader --file devtools/conda-envs/test_env.yaml
84
- conda env update --name imdreader --file docs/requirements.yaml
85
- ``` -->
97
+ mamba env update --name imdclient --file devtools/conda-envs/test_env.yaml
98
+ mamba env update --name imdclient --file docs/requirements.yaml
99
+ ```
86
100
 
87
- Build this package from source:
101
+ 4. Build and install IMDClient in editable mode:
88
102
 
89
103
  ```
90
104
  pip install -e .
91
105
  ```
92
106
 
93
- If you want to update your dependencies (which can be risky!), run:
107
+ 5. (Optional) Update dependencies:
94
108
 
95
109
  ```
96
- conda update --all
110
+ mamba update --all
97
111
  ```
98
112
 
99
- And when you are finished, you can exit the virtual environment with:
113
+ 6. Deactivate the environment when finished:
100
114
 
101
115
  ```
102
- conda deactivate
116
+ mamba deactivate
103
117
  ```
104
118
 
105
- #### With pip
119
+ #### Source build with pip
106
120
 
107
- To build the package from source, run:
121
+ 1. (Optional) Create and activate a virtual environment:
122
+
123
+ ```
124
+ python -m venv venv
125
+ source venv/bin/activate
126
+ ```
127
+
128
+ 2. Install IMDClient from source:
108
129
 
109
130
  ```
110
131
  pip install .
111
132
  ```
112
133
 
113
- If you want to create a development environment, install
114
- the dependencies required for tests and docs with:
134
+ 3. (Optional) For development (tests and docs):
115
135
 
116
136
  ```
117
137
  pip install ".[test,doc]"
@@ -122,11 +142,12 @@ pip install ".[test,doc]"
122
142
  The IMDClient source code is hosted at https://github.com/becksteinlab/imdclient
123
143
  and is available under the MIT license (see the file [LICENSE](https://github.com/becksteinlab/imdclient/blob/main/LICENSE)).
124
144
 
125
- Copyright (c) 2024, Lawson
145
+ Copyright (c) 2024-2025, imdclient [AUTHORS](https://github.com/Becksteinlab/imdclient/blob/main/AUTHORS.md)
126
146
 
127
147
 
128
148
  #### Acknowledgements
129
149
 
130
150
  Project based on the
131
151
  [MDAnalysis Cookiecutter](https://github.com/MDAnalysis/cookiecutter-mda) version 0.1.
132
- <!-- Please cite [MDAnalysis](https://github.com/MDAnalysis/mdanalysis#citation) when using IMDReader in published work. -->
152
+
153
+ **If you use IMDClient in your research, please cite [IMDClient](https://github.com/Becksteinlab/imdclient) in your publications.**