mdadash 0.0.1__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.
- mdadash/__init__.py +9 -0
- mdadash/backend/__init__.py +0 -0
- mdadash/backend/example_api.py +3 -0
- mdadash/backend/kernel/__init__.py +0 -0
- mdadash/backend/kernel/core.py +210 -0
- mdadash/backend/kernel/manager.py +305 -0
- mdadash/backend/main.py +177 -0
- mdadash/backend/state/__init__.py +0 -0
- mdadash/backend/state/manager.py +81 -0
- mdadash/backend/tests/__init__.py +0 -0
- mdadash/backend/tests/conftest.py +20 -0
- mdadash/backend/tests/test_mdadash.py +29 -0
- mdadash/backend/tests/test_server.py +177 -0
- mdadash/backend/tests/utils.py +14 -0
- mdadash/data/__init__.py +0 -0
- mdadash/data/files.py +19 -0
- mdadash/frontend/dist/assets/index-8hBHiXAA.js +17 -0
- mdadash/frontend/dist/assets/index-Bv73Vvuu.css +1 -0
- mdadash/frontend/dist/assets/materialdesignicons-webfont-B7mPwVP_.ttf +0 -0
- mdadash/frontend/dist/assets/materialdesignicons-webfont-CSr8KVlo.eot +0 -0
- mdadash/frontend/dist/assets/materialdesignicons-webfont-Dp5v-WZN.woff2 +0 -0
- mdadash/frontend/dist/assets/materialdesignicons-webfont-PXm3-2wK.woff +0 -0
- mdadash/frontend/dist/favicon.ico +0 -0
- mdadash/frontend/dist/index.html +14 -0
- mdadash-0.0.1.dist-info/METADATA +268 -0
- mdadash-0.0.1.dist-info/RECORD +31 -0
- mdadash-0.0.1.dist-info/WHEEL +5 -0
- mdadash-0.0.1.dist-info/entry_points.txt +2 -0
- mdadash-0.0.1.dist-info/licenses/AUTHORS.md +23 -0
- mdadash-0.0.1.dist-info/licenses/LICENSE +21 -0
- mdadash-0.0.1.dist-info/top_level.txt +1 -0
mdadash/__init__.py
ADDED
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
3
|
+
import comm
|
|
4
|
+
import MDAnalysis as mda
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class CommHandler:
|
|
8
|
+
"""Comm Handler
|
|
9
|
+
|
|
10
|
+
This class is responsible for handling all the communication to and from
|
|
11
|
+
this kernel. This is the class that interfaces with the KernelManager on
|
|
12
|
+
the server side.
|
|
13
|
+
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self):
|
|
17
|
+
self._comm = None
|
|
18
|
+
self._handlers = {}
|
|
19
|
+
comm.get_comm_manager().register_target(
|
|
20
|
+
"kernel_comm_handler", self._handle_comm_open
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
def register_handler(self, msg_type: str, handler_func: callable) -> None:
|
|
24
|
+
"""Register a handler function for a message type"""
|
|
25
|
+
self._handlers[msg_type] = handler_func
|
|
26
|
+
|
|
27
|
+
def send(self, msg: dict) -> None:
|
|
28
|
+
"""Send a message (response) back on the comm
|
|
29
|
+
|
|
30
|
+
Parameters
|
|
31
|
+
----------
|
|
32
|
+
msg: dict
|
|
33
|
+
A message dictionary
|
|
34
|
+
|
|
35
|
+
"""
|
|
36
|
+
if self._comm is not None:
|
|
37
|
+
self._comm.send(msg)
|
|
38
|
+
else:
|
|
39
|
+
raise ValueError("comm is not open yet") # pragma: no cover
|
|
40
|
+
|
|
41
|
+
def _handle_comm_open(self, _comm: comm.base_comm.BaseComm, _msg):
|
|
42
|
+
"""Internal: Handler when the comm is opened (comm_open)"""
|
|
43
|
+
self._comm = _comm
|
|
44
|
+
# set the handler for comm messages (comm_msg)
|
|
45
|
+
self._comm.on_msg(self._handle_msg)
|
|
46
|
+
|
|
47
|
+
def _handle_msg(self, msg):
|
|
48
|
+
"""Internal: Dispatch the message to the registered handler"""
|
|
49
|
+
content_data = msg["content"]["data"]
|
|
50
|
+
msg_type = content_data["msg_type"]
|
|
51
|
+
if msg_type in self._handlers:
|
|
52
|
+
self._handlers[msg_type](content_data["data"])
|
|
53
|
+
else:
|
|
54
|
+
error_msg = f"{msg_type} does not have a registered handler"
|
|
55
|
+
self.send({"status": "error", "message": error_msg})
|
|
56
|
+
raise ValueError(error_msg)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class UniverseManager:
|
|
60
|
+
"""Universe Manager
|
|
61
|
+
|
|
62
|
+
This class is responsible for managing all MDAnalysis universes. It has
|
|
63
|
+
handlers to interact with the MD simulation. These handlers are invoked by
|
|
64
|
+
comm messages sent from the server.
|
|
65
|
+
|
|
66
|
+
This also provides an iterable and indexable access to the individual
|
|
67
|
+
universes.
|
|
68
|
+
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
def __init__(self):
|
|
72
|
+
self._universes = []
|
|
73
|
+
self._iter_loop_task = None
|
|
74
|
+
self._iter_loop_running = False
|
|
75
|
+
self._iter_loop_resumed = asyncio.Event()
|
|
76
|
+
self._iter_loop_resumed.clear()
|
|
77
|
+
|
|
78
|
+
def __iter__(self) -> iter:
|
|
79
|
+
"""To support iteration"""
|
|
80
|
+
return iter(self._universes)
|
|
81
|
+
|
|
82
|
+
def __len__(self) -> int:
|
|
83
|
+
"""Number of universes"""
|
|
84
|
+
return len(self._universes)
|
|
85
|
+
|
|
86
|
+
def __getitem__(self, index: int):
|
|
87
|
+
"""Return universe based on index"""
|
|
88
|
+
# numeric index based array access
|
|
89
|
+
_max = len(self._universes)
|
|
90
|
+
if 0 <= index < _max:
|
|
91
|
+
return self._universes[index]
|
|
92
|
+
raise ValueError(f"Invalid index {index} of {_max} items")
|
|
93
|
+
|
|
94
|
+
def init_n_universes(self, n: int) -> None:
|
|
95
|
+
"""Initialize array for n universes
|
|
96
|
+
|
|
97
|
+
Parameters
|
|
98
|
+
----------
|
|
99
|
+
n: int
|
|
100
|
+
Number of universes to initialize
|
|
101
|
+
|
|
102
|
+
"""
|
|
103
|
+
self._universes = [None] * n
|
|
104
|
+
|
|
105
|
+
def connect_to_simulations(self, universe_configs: list[dict]) -> None:
|
|
106
|
+
"""Connect to MD simulations
|
|
107
|
+
|
|
108
|
+
Parameters
|
|
109
|
+
----------
|
|
110
|
+
universe_configs: list[dict]
|
|
111
|
+
A list of configurations for universe(s) creation.
|
|
112
|
+
Each dict has universe related config like topology, trajectory,
|
|
113
|
+
imdclient params, user-defined kwargs etc
|
|
114
|
+
|
|
115
|
+
"""
|
|
116
|
+
try:
|
|
117
|
+
for uid, config in enumerate(universe_configs):
|
|
118
|
+
kwargs = {}
|
|
119
|
+
topology = config.get("topology")
|
|
120
|
+
trajectory = config.get("trajectory")
|
|
121
|
+
for key, value in config.items():
|
|
122
|
+
if key in ("topology", "trajectory", "kwargs"):
|
|
123
|
+
continue
|
|
124
|
+
if value is not None:
|
|
125
|
+
kwargs[key] = value
|
|
126
|
+
for name, value in config["kwargs"]:
|
|
127
|
+
if name.strip():
|
|
128
|
+
kwargs[name] = value
|
|
129
|
+
# create universe
|
|
130
|
+
u = mda.Universe(
|
|
131
|
+
topology,
|
|
132
|
+
trajectory,
|
|
133
|
+
**kwargs,
|
|
134
|
+
)
|
|
135
|
+
if uid == 0:
|
|
136
|
+
self._send_tsdata(u)
|
|
137
|
+
self._universes[uid] = u
|
|
138
|
+
# start iter loop for trajectories
|
|
139
|
+
self._iter_loop_resumed.clear()
|
|
140
|
+
self._iter_loop_running = True
|
|
141
|
+
self._iter_loop_task = asyncio.create_task(self._iter_loop())
|
|
142
|
+
comm_handler.send({"status": "ok"})
|
|
143
|
+
except Exception as e: # pylint: disable=broad-exception-caught
|
|
144
|
+
comm_handler.send({"status": "error", "message": str(e)})
|
|
145
|
+
|
|
146
|
+
def _send_tsdata(self, u: mda.Universe):
|
|
147
|
+
"""Internal: Send timestep data out"""
|
|
148
|
+
comm_handler.send(
|
|
149
|
+
{
|
|
150
|
+
"tsinfo": {
|
|
151
|
+
"frame": u.trajectory.frame,
|
|
152
|
+
"tsdata": u.trajectory.ts.data,
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
def disconnect_from_simulations(self, _data: dict) -> None:
|
|
158
|
+
"""Disconnect from MD simulations"""
|
|
159
|
+
self._iter_loop_running = False
|
|
160
|
+
self._iter_loop_task.cancel()
|
|
161
|
+
for u in self._universes:
|
|
162
|
+
u.trajectory.close()
|
|
163
|
+
comm_handler.send({"status": "ok"})
|
|
164
|
+
|
|
165
|
+
def pause_simulations(self, _data: dict) -> None:
|
|
166
|
+
"""Pause MD simulations"""
|
|
167
|
+
self._iter_loop_resumed.clear()
|
|
168
|
+
comm_handler.send({"status": "ok"})
|
|
169
|
+
|
|
170
|
+
def resume_simulations(self, _data: dict) -> None:
|
|
171
|
+
"""Resume MD simulations"""
|
|
172
|
+
self._iter_loop_resumed.set()
|
|
173
|
+
comm_handler.send({"status": "ok"})
|
|
174
|
+
|
|
175
|
+
def _trajectory_next(self, u):
|
|
176
|
+
"""Internal: Iterate trajectory by 1 frame"""
|
|
177
|
+
return u.trajectory.next()
|
|
178
|
+
|
|
179
|
+
async def _iter_loop(self):
|
|
180
|
+
"""Internal: Iteration loop for trajectories"""
|
|
181
|
+
try:
|
|
182
|
+
while self._iter_loop_running:
|
|
183
|
+
await self._iter_loop_resumed.wait()
|
|
184
|
+
for uid, u in enumerate(self._universes):
|
|
185
|
+
try:
|
|
186
|
+
# iterate in thread to not block on a network call here
|
|
187
|
+
await asyncio.to_thread(self._trajectory_next, u)
|
|
188
|
+
if uid == 0:
|
|
189
|
+
self._send_tsdata(u)
|
|
190
|
+
# await asyncio.sleep(0)
|
|
191
|
+
except StopIteration as e: # pragma: no cover
|
|
192
|
+
print(e)
|
|
193
|
+
await asyncio.sleep(0)
|
|
194
|
+
except asyncio.CancelledError:
|
|
195
|
+
pass
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def init_n_universes(data: dict) -> None:
|
|
199
|
+
um.init_n_universes(data.get("n"))
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
um = UniverseManager()
|
|
203
|
+
comm_handler = CommHandler()
|
|
204
|
+
comm_handler.register_handler("init_n_universes", init_n_universes)
|
|
205
|
+
comm_handler.register_handler("connect_to_simulations", um.connect_to_simulations)
|
|
206
|
+
comm_handler.register_handler(
|
|
207
|
+
"disconnect_from_simulations", um.disconnect_from_simulations
|
|
208
|
+
)
|
|
209
|
+
comm_handler.register_handler("pause_simulations", um.pause_simulations)
|
|
210
|
+
comm_handler.register_handler("resume_simulations", um.resume_simulations)
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
import queue
|
|
4
|
+
import sys
|
|
5
|
+
import uuid
|
|
6
|
+
|
|
7
|
+
import socketio
|
|
8
|
+
from jupyter_client import AsyncKernelManager
|
|
9
|
+
|
|
10
|
+
from ..state.manager import StateManager
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# pylint: disable=too-many-instance-attributes
|
|
16
|
+
class KernelManager:
|
|
17
|
+
"""Kernel Manager
|
|
18
|
+
|
|
19
|
+
This class is responsible for managing the AsyncKernelManager (async kernel)
|
|
20
|
+
that runs all the MDAnalysis code. It takes care of starting the async
|
|
21
|
+
kernel, stopping it and communicating with it. It interfaces with the
|
|
22
|
+
CommHandler on the kernel side for messaging.
|
|
23
|
+
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, sm: StateManager, sio: socketio.AsyncServer):
|
|
27
|
+
self.sm = sm
|
|
28
|
+
self.sio = sio
|
|
29
|
+
self.km = AsyncKernelManager(kernel_name="python3")
|
|
30
|
+
self.kc = None
|
|
31
|
+
self._pending_futures = {}
|
|
32
|
+
self._is_running = False
|
|
33
|
+
self.comm_id = uuid.uuid4().hex
|
|
34
|
+
self.listen_task = None
|
|
35
|
+
|
|
36
|
+
async def start(self) -> None:
|
|
37
|
+
"""Start the async kernel"""
|
|
38
|
+
# start the kernel
|
|
39
|
+
await self.km.start_kernel()
|
|
40
|
+
# create a client
|
|
41
|
+
self.kc = self.km.client()
|
|
42
|
+
self.kc.start_channels()
|
|
43
|
+
await self.kc.wait_for_ready()
|
|
44
|
+
# create task to listen on iopub and shell channels
|
|
45
|
+
self.listen_task = asyncio.create_task(self._start_listening())
|
|
46
|
+
# initialize the kernel core
|
|
47
|
+
self.kc.execute("from mdadash.backend.kernel import core")
|
|
48
|
+
# open comms with the kernel
|
|
49
|
+
self._comm_open()
|
|
50
|
+
self._is_running = True
|
|
51
|
+
# initialize n universes in kernel universe manager
|
|
52
|
+
await self.send_message(
|
|
53
|
+
"init_n_universes", {"n": len(self.sm.universe_configs)}
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
async def stop(self) -> None:
|
|
57
|
+
"""Stop the async kernel"""
|
|
58
|
+
self._is_running = False
|
|
59
|
+
# wait for listen task to completely exit
|
|
60
|
+
await self.listen_task
|
|
61
|
+
self.kc.stop_channels()
|
|
62
|
+
self.kc = None
|
|
63
|
+
# shutdown kernel gracefully
|
|
64
|
+
await self.km.shutdown_kernel(now=False)
|
|
65
|
+
await self.km.cleanup_resources()
|
|
66
|
+
|
|
67
|
+
async def _start_listening(self):
|
|
68
|
+
"""Internal: Create separate listen tasks for iopub and shell"""
|
|
69
|
+
await asyncio.gather(
|
|
70
|
+
self._listen_iopub_channel(),
|
|
71
|
+
self._listen_shell_channel(),
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
async def _emit_tsdata(self, tsinfo):
|
|
75
|
+
"""Internal: Emit timestep data"""
|
|
76
|
+
tsdata = tsinfo["tsdata"]
|
|
77
|
+
step = tsdata.get("step", None)
|
|
78
|
+
total_steps = self.sm.universe_configs[0].get("total_steps", None)
|
|
79
|
+
done = (step / total_steps) * 100 if step and total_steps else None
|
|
80
|
+
timestep_info = {
|
|
81
|
+
"frame": tsinfo.get("frame", None),
|
|
82
|
+
"time": tsdata.get("time", None),
|
|
83
|
+
"step": step,
|
|
84
|
+
"done": done,
|
|
85
|
+
"energies": {
|
|
86
|
+
"temperature": tsdata.get("temperature", None),
|
|
87
|
+
"total_energy": tsdata.get("total_energy", None),
|
|
88
|
+
"potential_energy": tsdata.get("potential_energy", None),
|
|
89
|
+
"van_der_walls_energy": tsdata.get("van_der_walls_energy", None),
|
|
90
|
+
"coulomb_energy": tsdata.get("coulomb_energy", None),
|
|
91
|
+
"bonds_energy": tsdata.get("bonds_energy", None),
|
|
92
|
+
"angles_energy": tsdata.get("angles_energy", None),
|
|
93
|
+
"dihedrals_energy": tsdata.get("dihedrals_energy", None),
|
|
94
|
+
"improper_dihedrals_energy": tsdata.get(
|
|
95
|
+
"improper_dihedrals_energy", None
|
|
96
|
+
),
|
|
97
|
+
},
|
|
98
|
+
}
|
|
99
|
+
await self.sio.emit("timestepInfo", timestep_info)
|
|
100
|
+
|
|
101
|
+
# pylint: disable=too-many-branches
|
|
102
|
+
async def _listen_iopub_channel(self):
|
|
103
|
+
"""Internal: Listen on iopub channel"""
|
|
104
|
+
while self._is_running:
|
|
105
|
+
try:
|
|
106
|
+
msg = await self.kc.iopub_channel.get_msg(timeout=0.1)
|
|
107
|
+
msg_type = msg["header"]["msg_type"]
|
|
108
|
+
content = msg["content"]
|
|
109
|
+
parent_id = msg.get("parent_header", {}).get("msg_id")
|
|
110
|
+
# check if a pending future can be resolved with msg
|
|
111
|
+
resolve_future = False
|
|
112
|
+
if parent_id and parent_id in self._pending_futures:
|
|
113
|
+
future = self._pending_futures[parent_id]
|
|
114
|
+
if not future.done():
|
|
115
|
+
resolve_future = True
|
|
116
|
+
# handle different msg_type's
|
|
117
|
+
if msg_type == "comm_msg":
|
|
118
|
+
data = msg["content"]["data"]
|
|
119
|
+
if "tsinfo" in data:
|
|
120
|
+
await self._emit_tsdata(data["tsinfo"])
|
|
121
|
+
elif resolve_future:
|
|
122
|
+
future.set_result(data)
|
|
123
|
+
continue
|
|
124
|
+
elif msg_type == "stream":
|
|
125
|
+
if resolve_future:
|
|
126
|
+
future.set_result(msg["content"]["text"])
|
|
127
|
+
continue
|
|
128
|
+
# redirect kernel stdout and stderr to this server output
|
|
129
|
+
if content["name"] == "stdout" or content["name"] == "stderr":
|
|
130
|
+
output = content["text"]
|
|
131
|
+
file = sys.stdout if content["name"] == "stdout" else sys.stderr
|
|
132
|
+
print(
|
|
133
|
+
f"KERNEL ({content['name']}): {output}", end="", file=file
|
|
134
|
+
)
|
|
135
|
+
elif msg_type == "error":
|
|
136
|
+
# redirect kernel errors to server output
|
|
137
|
+
print(f"KERNEL (error): {content['ename']}: {content['evalue']}")
|
|
138
|
+
if resolve_future:
|
|
139
|
+
future.set_result(content["evalue"])
|
|
140
|
+
continue
|
|
141
|
+
else:
|
|
142
|
+
logger.debug("IOPUB: %s", msg)
|
|
143
|
+
# TODO: handle other message types
|
|
144
|
+
except (asyncio.TimeoutError, queue.Empty):
|
|
145
|
+
continue
|
|
146
|
+
|
|
147
|
+
async def _listen_shell_channel(self):
|
|
148
|
+
"""Internal: Listen on shell channel"""
|
|
149
|
+
while self._is_running:
|
|
150
|
+
try:
|
|
151
|
+
msg = await self.kc.shell_channel.get_msg(timeout=0.1)
|
|
152
|
+
# msg_type = msg["header"]["msg_type"]
|
|
153
|
+
# content = msg["content"]
|
|
154
|
+
logger.debug("SHELL: %s", msg)
|
|
155
|
+
except (asyncio.TimeoutError, queue.Empty):
|
|
156
|
+
continue
|
|
157
|
+
|
|
158
|
+
def _comm_open(self):
|
|
159
|
+
"""Internal: Open comms with the kernel"""
|
|
160
|
+
content = {
|
|
161
|
+
"comm_id": self.comm_id,
|
|
162
|
+
"target_name": "kernel_comm_handler",
|
|
163
|
+
"data": {"msg_type": "handshake"},
|
|
164
|
+
}
|
|
165
|
+
open_msg = self.kc.session.msg("comm_open", content=content)
|
|
166
|
+
self.kc.shell_channel.send(open_msg)
|
|
167
|
+
|
|
168
|
+
async def send_message(self, msg_type: str, data: dict) -> None:
|
|
169
|
+
"""Send message to kernel and don't await a response
|
|
170
|
+
|
|
171
|
+
Parameters
|
|
172
|
+
----------
|
|
173
|
+
msg_type: str
|
|
174
|
+
A message type string that the kernel has a handler registered for
|
|
175
|
+
|
|
176
|
+
data: dict
|
|
177
|
+
Dict that gets passed to the handler in the kernel
|
|
178
|
+
|
|
179
|
+
"""
|
|
180
|
+
content = {
|
|
181
|
+
"comm_id": self.comm_id,
|
|
182
|
+
"target_name": "kernel_comm_handler",
|
|
183
|
+
"data": {"msg_type": msg_type, "data": data},
|
|
184
|
+
}
|
|
185
|
+
data_msg = self.kc.session.msg("comm_msg", content=content)
|
|
186
|
+
self.kc.shell_channel.send(data_msg)
|
|
187
|
+
|
|
188
|
+
async def send_message_await_response(
|
|
189
|
+
self, msg_type: str, data: dict = None, timeout: int = 5
|
|
190
|
+
) -> dict | None:
|
|
191
|
+
"""Send message to kernel and wait for a response (async)
|
|
192
|
+
|
|
193
|
+
Parameters
|
|
194
|
+
----------
|
|
195
|
+
msg_type: str
|
|
196
|
+
A message type string that the kernel has a handler registered for
|
|
197
|
+
|
|
198
|
+
data: dict
|
|
199
|
+
Dict that gets passed to the handler in the kernel
|
|
200
|
+
|
|
201
|
+
timeout: int
|
|
202
|
+
Timeout in seconds
|
|
203
|
+
|
|
204
|
+
"""
|
|
205
|
+
content = {
|
|
206
|
+
"comm_id": self.comm_id,
|
|
207
|
+
"target_name": "kernel_comm_handler",
|
|
208
|
+
"data": {"msg_type": msg_type, "data": data},
|
|
209
|
+
}
|
|
210
|
+
data_msg = self.kc.session.msg("comm_msg", content=content)
|
|
211
|
+
msg_id = data_msg["header"]["msg_id"]
|
|
212
|
+
# add to the _pending_futures to that it gets resolved when the
|
|
213
|
+
# response arrives on the iopub channel
|
|
214
|
+
future = asyncio.get_running_loop().create_future()
|
|
215
|
+
self._pending_futures[msg_id] = future
|
|
216
|
+
self.kc.shell_channel.send(data_msg)
|
|
217
|
+
try:
|
|
218
|
+
return await asyncio.wait_for(future, timeout=timeout)
|
|
219
|
+
except asyncio.TimeoutError as e: # pragma: no cover
|
|
220
|
+
raise TimeoutError("Timed out waiting for kernel response") from e
|
|
221
|
+
finally:
|
|
222
|
+
self._pending_futures.pop(msg_id, None)
|
|
223
|
+
|
|
224
|
+
async def execute_code(self, code: str, timeout: int = 5) -> str:
|
|
225
|
+
"""Execute code in the kernel
|
|
226
|
+
|
|
227
|
+
Parameters
|
|
228
|
+
----------
|
|
229
|
+
code: str
|
|
230
|
+
Code to execute in the kernel
|
|
231
|
+
|
|
232
|
+
timeout: int
|
|
233
|
+
Timeout in seconds
|
|
234
|
+
|
|
235
|
+
"""
|
|
236
|
+
msg_id = self.kc.execute(code)
|
|
237
|
+
future = asyncio.get_running_loop().create_future()
|
|
238
|
+
self._pending_futures[msg_id] = future
|
|
239
|
+
try:
|
|
240
|
+
return await asyncio.wait_for(future, timeout=timeout)
|
|
241
|
+
except asyncio.TimeoutError as e: # pragma: no cover
|
|
242
|
+
raise TimeoutError("Timed out waiting for kernel execute response") from e
|
|
243
|
+
finally:
|
|
244
|
+
self._pending_futures.pop(msg_id, None)
|
|
245
|
+
|
|
246
|
+
async def connect_to_simulations(self) -> dict:
|
|
247
|
+
"""Connect to the MD simulation
|
|
248
|
+
|
|
249
|
+
Returns
|
|
250
|
+
-------
|
|
251
|
+
response: dict
|
|
252
|
+
Response dict indicating status
|
|
253
|
+
|
|
254
|
+
"""
|
|
255
|
+
response = await self.send_message_await_response(
|
|
256
|
+
"connect_to_simulations", self.sm.universe_configs
|
|
257
|
+
)
|
|
258
|
+
if response["status"] == "ok":
|
|
259
|
+
self.sm.running_state["connected"] = True
|
|
260
|
+
self.sm.running_state["running"] = False
|
|
261
|
+
return response
|
|
262
|
+
|
|
263
|
+
async def disconnect_from_simulations(self) -> dict:
|
|
264
|
+
"""Disconnect from the MD simulation
|
|
265
|
+
|
|
266
|
+
Returns
|
|
267
|
+
-------
|
|
268
|
+
response: dict
|
|
269
|
+
Response dict indicating status
|
|
270
|
+
|
|
271
|
+
"""
|
|
272
|
+
response = await self.send_message_await_response(
|
|
273
|
+
"disconnect_from_simulations", {}
|
|
274
|
+
)
|
|
275
|
+
if response["status"] == "ok":
|
|
276
|
+
self.sm.running_state["connected"] = False
|
|
277
|
+
return response
|
|
278
|
+
|
|
279
|
+
async def pause_simulations(self) -> dict:
|
|
280
|
+
"""Pause MD simulations
|
|
281
|
+
|
|
282
|
+
Returns
|
|
283
|
+
-------
|
|
284
|
+
response: dict
|
|
285
|
+
Response dict indicating status
|
|
286
|
+
|
|
287
|
+
"""
|
|
288
|
+
response = await self.send_message_await_response("pause_simulations", {})
|
|
289
|
+
if response["status"] == "ok":
|
|
290
|
+
self.sm.running_state["running"] = False
|
|
291
|
+
return response
|
|
292
|
+
|
|
293
|
+
async def resume_simulations(self) -> dict:
|
|
294
|
+
"""Resume MD simulations
|
|
295
|
+
|
|
296
|
+
Returns
|
|
297
|
+
-------
|
|
298
|
+
response: dict
|
|
299
|
+
Response dict indicating status
|
|
300
|
+
|
|
301
|
+
"""
|
|
302
|
+
response = await self.send_message_await_response("resume_simulations", {})
|
|
303
|
+
if response["status"] == "ok":
|
|
304
|
+
self.sm.running_state["running"] = True
|
|
305
|
+
return response
|