Source code for mdadash.backend.analyses.native_contacts

"""
Native Contacts Analysis
"""

import logging
from collections import deque
from typing import ClassVar

import matplotlib.pyplot as plt
from joblib import delayed
from matplotlib.backends.backend_agg import FigureCanvasAgg
from MDAnalysis.analysis import contacts

from mdadash.backend.widgets.base import WidgetBase

logger = logging.getLogger(__name__)


[docs] class NativeContacts(WidgetBase): """ **Native Contacts Analysis** This widget uses `MDAnalysis.analysis.contacts.Contacts`_ to calculate `Fraction of native contacts`_ between two contacting groups. .. note:: The two contacting AtomGroups in their reference conformation are from the reference timestep available for the trajectory (:attr:`~mdadash.backend.kernel.core.BufferedTrajectory.reference_ts`). .. _MDAnalysis.analysis.contacts.Contacts: https://docs.mdanalysis.org/stable/ documentation_pages/analysis/contacts.html#MDAnalysis.analysis.contacts.Contacts .. _Fraction of native contacts: https://userguide.mdanalysis.org/stable/ examples/analysis/distances_and_contacts/contacts_native_fraction.html **Inputs** Run frequency .. compound:: The frequency with which the widget is run - `every-frame` or `batch` Default: ``every-frame`` Run mode The mode in which the widget is run - `serial` or `parallel` Default: ``serial`` Contacting Group 1 MDAnalysis selection phrase of first group Default: ``protein and name CA`` Contacting Group 2 MDAnalysis selection phrase of second group Default: ``protein and name CA`` Radius Radius within which contacts exist in refgroup Default: ``4.5`` Method Method to use for cut off - `hard_cut`, `soft_cut` or `radius_cut` Default: ``hard_cut`` PBC Uses periodic boundary conditions to calculate distances Default: ``True`` Custom title Custom title for the plot Default: '' Max values Max values to show in plot Default: ``100`` Plot refresh frequency The frequency with which the plot is refreshed (every n frames). This only applies when the run frequency is ``every-frame`` Default: ``1`` Reset on connect Reset the plot on every connect Default: ``False`` X-axis X-axis value - `time` or `step` Default: ``time`` **Output** Here is an example output plot of this widget: .. figure:: /_static/images/native_contacts_output.jpg :alt: Native Contacts output .. tip:: This widget supports batching and can run in parallel """ name = "Native Contacts" description = "Native Contacts Analysis" _doclink = ( "https://mdadash.readthedocs.io/en/latest/autosummary/" "mdadash.backend.analyses.native_contacts.html" ) _notes = ( "The two contacting AtomGroups in their reference conformation are from the" "reference timestep available for the trajectory (`u.trajectory.reference_ts`)." ) _inputs: ClassVar = [ { "attribute": "_run_frequency", "name": "Run frequency", "description": "The frequency with which the widget is run", "type": "select", "items": [ "every-frame", "batch", ], }, { "attribute": "_run_mode", "name": "Run mode", "description": "The mode in which the widget is run", "type": "select", "items": [ "serial", "parallel", ], }, { "attribute": "selection1", "name": "Contacting Group 1", "description": "MDAnalysis selection phrase of first group", "type": "str", "validations": ["required"], }, { "attribute": "selection2", "name": "Contacting Group 2", "description": "MDAnalysis selection phrase of second group", "type": "str", "validations": ["required"], }, { "attribute": "radius", "name": "Radius", "description": "Radius within which contacts exist in refgroup", "type": "float", "validations": ["min:0.0"], }, { "attribute": "method", "name": "Method", "description": "Method to use for cut off", "type": "select", "items": [ "hard_cut", "soft_cut", "radius_cut", ], }, { "attribute": "pbc", "name": "PBC", "description": "Uses periodic boundary conditions to calculate distances", "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", "validations": ["min:0"], }, { "attribute": "plot_refresh_frequency", "name": "Plot refresh frequency", "description": "The frequency with which the plot is refreshed (every n frames)", "type": "int", "validations": ["min:1"], }, { "attribute": "reset_on_connect", "name": "Reset on connect", "description": "Reset the plot on every connect", "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 and name CA" self.selection2 = "protein and name CA" self.radius = 4.5 self.method = "hard_cut" self.pbc = True self.contacts = None self.refgroup_ag1 = None self.refgroup_ag2 = None self.title = "Native Contacts" self.custom_title = None self.default_maxlen = 100 self.maxlen = self.default_maxlen self.plot_refresh_count = 1 self.plot_refresh_frequency = 1 self.reset_on_connect = False 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.canvas = FigureCanvasAgg(self.fig) (self.plot,) = self.ax.plot([], []) self.ax.set_ylabel("Fraction of contacts") 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.plot_refresh_count = 1 self._set_x_values() def _set_title(self): """Set plot title""" self.ax.set_title( self.custom_title.replace("\\n", "\n") 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 _create_contacts(self): """Update atom groups when selection phrases change""" # Use the reference timestep to create the refgroups _ = self.u.trajectory.reference_ts self.refgroup_ag1 = self.u.select_atoms(self.selection1) self.refgroup_ag2 = self.u.select_atoms(self.selection2) self.contacts = contacts.Contacts( self.u, select=(self.selection1, self.selection2), refgroup=(self.refgroup_ag1, self.refgroup_ag2), radius=self.radius, method=self.method, pbc=self.pbc, ) # reset to current frame self.reset_frame_latest() self.title = ( f"Native contacts between\n'{self.selection1}' and '{self.selection2}'" ) self._set_title() self._update_plot(self._compute_current_frame())
[docs] def on_post_create(self): """:meth:`~mdadash.backend.widgets.base.WidgetBase.on_post_create` handler""" self._set_title() self._reset_plot_values()
[docs] def on_post_connect(self): """:meth:`~mdadash.backend.widgets.base.WidgetBase.on_post_connect` handler""" self._create_contacts() self.plot_refresh_count = 1 if self.reset_on_connect: # pragma: no cover self._reset_plot_values()
[docs] def on_input_change(self, attribute, _old_value, new_value): """:meth:`~mdadash.backend.widgets.base.WidgetBase.on_input_change` handler""" if attribute == "maxlen": if new_value < 0: self.maxlen = self.default_maxlen self._reset_plot_values() elif attribute == "x_type": self._set_x_values() elif attribute == "custom_title": self._set_title() elif attribute in ( "selection1", "selection2", "radius", "method", "pbc", ): self._reset_plot_values() self._create_contacts() elif attribute == "plot_refresh_frequency": self.plot_refresh_count = 1
def _compute_current_frame(self): """Compute values for current frame""" self.contacts.run(frames=[self.u.trajectory.frame]) return ( self.u.trajectory.ts.data["step"], self.u.trajectory.ts.data["time"], self.contacts.results.timeseries[0][1], ) def _compute_batch(self): """Compute values for current batch""" self.contacts.run() values = [] for i, (_, q) in enumerate(self.contacts.results.timeseries): _ = self.u.trajectory[i] values.append( ( self.u.trajectory.ts.data["step"], self.u.trajectory.ts.data["time"], q, ) ) return values def _update_plot(self, values): """Append values and update plot""" if isinstance(values, tuple): values = [values] # update plot points for value in values: (steps, times, v) = value self.steps.append(steps) self.times.append(times) self.y_values.append(v) # update plot if self._run_frequency == "batch" or ( self.plot_refresh_count == 1 or self.plot_refresh_count % self.plot_refresh_frequency == 0 ): self.plot.set_data(self.x_values, self.y_values) self.ax.relim() self.ax.autoscale_view() self.display_canvas(self.canvas) self.plot_refresh_count += 1
[docs] def run_every_frame(self): """:meth:`~mdadash.backend.widgets.base.WidgetBase.run_every_frame` handler""" self._update_plot(self._compute_current_frame())
[docs] def run_batch(self): """:meth:`~mdadash.backend.widgets.base.WidgetBase.run_batch` handler""" self._update_plot(self._compute_batch())
[docs] def get_parallel_job(self): """:meth:`~mdadash.backend.widgets.base.WidgetBase.get_parallel_job` handler""" if self._run_frequency == "batch": return delayed(self._compute_batch)() return delayed(self._compute_current_frame)()
[docs] def apply_parallel_results(self, values): """:meth:`~mdadash.backend.widgets.base.WidgetBase.apply_parallel_results` handler""" self._update_plot(values)