|
8 | 8 |
|
9 | 9 | import matplotlib.pyplot as plt |
10 | 10 | from IPython.display import display |
| 11 | +from joblib import delayed |
11 | 12 | from MDAnalysis.exceptions import NoDataError |
12 | 13 | from MDAnalysis.lib.distances import calc_bonds |
13 | 14 |
|
@@ -79,6 +80,26 @@ class COMDistance(WidgetBase): |
79 | 80 | description = "Distance between two COMs" |
80 | 81 |
|
81 | 82 | _inputs: ClassVar = [ |
| 83 | + { |
| 84 | + "attribute": "_run_frequency", |
| 85 | + "name": "Run frequency", |
| 86 | + "description": "The frequency with which the widget is run", |
| 87 | + "type": "select", |
| 88 | + "items": [ |
| 89 | + "every-frame", |
| 90 | + "batch", |
| 91 | + ], |
| 92 | + }, |
| 93 | + { |
| 94 | + "attribute": "_run_mode", |
| 95 | + "name": "Run mode", |
| 96 | + "description": "The mode in which the widget is run", |
| 97 | + "type": "select", |
| 98 | + "items": [ |
| 99 | + "serial", |
| 100 | + "parallel", |
| 101 | + ], |
| 102 | + }, |
82 | 103 | { |
83 | 104 | "attribute": "selection1", |
84 | 105 | "name": "Selection 1", |
@@ -234,27 +255,68 @@ def on_input_change(self, attribute, _old_value, new_value): |
234 | 255 | if reset_plot: |
235 | 256 | self._reset_plot_values() |
236 | 257 |
|
237 | | - def run_every_frame(self): |
238 | | - """every-frame run handler""" |
| 258 | + def _compute_current_frame(self): |
| 259 | + """Compute for current frame""" |
239 | 260 | try: |
240 | 261 | com1 = self.ag1.center_of_mass(unwrap=True) |
241 | 262 | com2 = self.ag2.center_of_mass(unwrap=True) |
242 | 263 | except NoDataError: # pragma: no cover |
243 | 264 | # unwrap can fail if there is no bonds info |
244 | 265 | com1 = self.ag1.center_of_mass() |
245 | 266 | com2 = self.ag2.center_of_mass() |
246 | | - dist = calc_bonds(com1, com2, box=self.u.dimensions) |
247 | | - self.y_values.append(dist) |
248 | | - self.steps.append(self.u.trajectory.ts.data["step"]) |
249 | | - self.times.append(self.u.trajectory.ts.data["time"]) |
250 | | - # update plot |
| 267 | + return ( |
| 268 | + self.u.trajectory.ts.data["step"], |
| 269 | + self.u.trajectory.ts.data["time"], |
| 270 | + calc_bonds(com1, com2, box=self.u.dimensions), |
| 271 | + ) |
| 272 | + |
| 273 | + def _compute_batch(self): |
| 274 | + """Compute for current batch""" |
| 275 | + values = [] |
| 276 | + for i in range(self.u.trajectory.buffer_size): |
| 277 | + _ = self.u.trajectory[i] |
| 278 | + values.append(self._compute_current_frame()) |
| 279 | + return values |
| 280 | + |
| 281 | + def _update_plot(self, values): |
| 282 | + """Append values and update plot""" |
| 283 | + if isinstance(values, tuple): |
| 284 | + values = [values] |
| 285 | + alerted = False |
| 286 | + paused = False |
| 287 | + for value in values: |
| 288 | + (steps, times, dist) = value |
| 289 | + self.steps.append(steps) |
| 290 | + self.times.append(times) |
| 291 | + self.y_values.append(dist) |
| 292 | + if dist > self.max_distance: |
| 293 | + if self.max_distance_alert and not alerted: |
| 294 | + self.alert(f"Distance between '{self.title}' > {self.max_distance}") |
| 295 | + alerted = True |
| 296 | + if self.max_distance_pause and not paused: |
| 297 | + self.pause_simulation() |
| 298 | + paused = True |
| 299 | + # update plot points |
251 | 300 | self.plot.set_data(self.x_values, self.y_values) |
252 | 301 | self.ax.relim() |
253 | 302 | self.ax.autoscale_view() |
254 | 303 | self.fig.canvas.draw() |
255 | 304 | display(self.fig) |
256 | | - if dist > self.max_distance: |
257 | | - if self.max_distance_alert: |
258 | | - self.alert(f"Distance between '{self.title}' > {self.max_distance}") |
259 | | - if self.max_distance_pause: |
260 | | - self.pause_simulation() |
| 305 | + |
| 306 | + def run_every_frame(self): |
| 307 | + """every-frame run handler""" |
| 308 | + self._update_plot(self._compute_current_frame()) |
| 309 | + |
| 310 | + def run_batch(self): |
| 311 | + """batch run handler""" |
| 312 | + self._update_plot(self._compute_batch()) |
| 313 | + |
| 314 | + def get_parallel_job(self): |
| 315 | + """get parallel job handler""" |
| 316 | + if self._run_frequency == "batch": |
| 317 | + return delayed(self._compute_batch)() |
| 318 | + return delayed(self._compute_current_frame)() |
| 319 | + |
| 320 | + def apply_parallel_results(self, values): |
| 321 | + """apply parallel results handler""" |
| 322 | + self._update_plot(values) |
0 commit comments