"""
Distance between two center-of-masses
"""
import logging
from collections import deque
import matplotlib.pyplot as plt
from IPython.display import display
from MDAnalysis.exceptions import NoDataError
from MDAnalysis.lib.distances import calc_bonds
from mdadash.backend.widgets.base import WidgetBase
logger = logging.getLogger(__name__)
[docs]
class COMDistance(WidgetBase):
"""COM Distance
Distance between two center-of-masses (COMs)
"""
name = "COMDistance"
description = "Distance between two COMs"
_inputs = [
{
"attribute": "selection1",
"name": "Selection 1",
"description": "First MDAnalysis selection phrase",
"type": "str",
"validations": ["required"],
},
{
"attribute": "selection2",
"name": "Selection 2",
"description": "Second MDAnalysis selection phrase",
"type": "str",
"validations": ["required"],
},
{
"attribute": "periodic",
"name": "Periodic",
"description": "Select with periodic boundary conditions",
"type": "bool",
},
{
"attribute": "updating",
"name": "Updating",
"description": "Update selection during each timestep",
"type": "bool",
},
{
"attribute": "custom_title",
"name": "Custom title",
"description": "Custom title for the plot",
"type": "str",
},
{
"attribute": "maxlen",
"name": "Max values",
"description": "Max values to show in plot",
"type": "int",
},
{
"attribute": "max_distance",
"name": "Max distance",
"description": "Max distance for alert check",
"type": "float",
},
{
"attribute": "max_distance_alert",
"name": "Alert if distance > 'Max distance'",
"type": "bool",
},
{
"attribute": "max_distance_pause",
"name": "Pause simulation if distance > 'Max distance'",
"type": "bool",
},
{
"attribute": "x_type",
"name": "X-axis",
"type": "toggle",
"options": [
{"name": "Time", "value": "time"},
{"name": "Step", "value": "step"},
],
},
]
def __init__(self):
super().__init__()
self.selection1 = "protein"
self.selection2 = "resid 1"
self.periodic = True
self.updating = False
self.ag1 = None
self.ag2 = None
self.max_distance = 50.0
self.max_distance_alert = False
self.max_distance_pause = False
self.title = "Distance between COMs"
self.custom_title = None
self.default_maxlen = 100
self.maxlen = self.default_maxlen
self.x_type = "time"
self.x_values = None
self._setup_plot()
self._reset_plot_values()
def _setup_plot(self):
"""Setup matplotlib plot"""
self.fig, self.ax = plt.subplots()
(self.plot,) = self.ax.plot([], [])
self.ax.set_ylabel("Distance (Å)")
self.ax.grid(True)
self._set_title()
def _reset_plot_values(self):
"""Reset plot values"""
self.steps = deque(maxlen=self.maxlen)
self.times = deque(maxlen=self.maxlen)
self.y_values = deque(maxlen=self.maxlen)
self._set_x_values()
def _set_title(self):
"""Set plot title"""
self.ax.set_title(self.custom_title if self.custom_title else self.title)
def _set_x_values(self):
"""Set the values for the x-axis"""
if self.x_type == "step":
x_label = "Step"
self.x_values = self.steps
else:
x_label = "Time (ps)"
self.x_values = self.times
self.ax.set_xlabel(x_label)
def _update_selections(self):
"""Update atom groups when selection phrases change"""
self.ag1 = self.u.select_atoms(
self.selection1, periodic=self.periodic, updating=self.updating
)
self.ag2 = self.u.select_atoms(
self.selection2, periodic=self.periodic, updating=self.updating
)
self.title = f"{self.selection1} <---> {self.selection2}"
self._set_title()
[docs]
def on_post_create(self):
"""on_post_create handler"""
self._set_title()
self._reset_plot_values()
[docs]
def on_post_connect(self):
"""on_post_connect handler"""
self._update_selections()
[docs]
def run_every_frame(self):
"""every-frame run handler"""
try:
com1 = self.ag1.center_of_mass(unwrap=True)
com2 = self.ag2.center_of_mass(unwrap=True)
except NoDataError: # pragma: no cover
# unwrap can fail if there is no bonds info
com1 = self.ag1.center_of_mass()
com2 = self.ag2.center_of_mass()
dist = calc_bonds(com1, com2, box=self.u.dimensions)
self.y_values.append(dist)
self.steps.append(self.u.trajectory.ts.data["step"])
self.times.append(self.u.trajectory.ts.data["time"])
# update plot
self.plot.set_data(self.x_values, self.y_values)
self.ax.relim()
self.ax.autoscale_view()
self.fig.canvas.draw()
display(self.fig)
if dist > self.max_distance:
if self.max_distance_alert:
self.alert(f"Distance between '{self.title}' > {self.max_distance}")
if self.max_distance_pause:
self.pause_simulation()