Source code for mdadash.backend.analyses.helix_analysis

"""
Helix Analysis
"""

import logging
import warnings
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.helix_analysis import HELANAL

from mdadash.backend.widgets.base import WidgetBase

logger = logging.getLogger(__name__)


[docs] class HelixAnalysis(WidgetBase): """ **Helix Analysis** This widget uses `MDAnalysis.analysis.helix_analysis.HELANAL`_ to perform `Helix analysis`_ and plot different computed properties. Plots can be chosen for the following computed properties: * ``local_twists`` * ``local_nres_per_turn`` * ``local_bends`` * ``local_heights`` * ``local_screw_angles`` .. _MDAnalysis.analysis.helix_analysis.HELANAL: https://docs.mdanalysis.org/ stable/documentation_pages/analysis/helix_analysis.html #MDAnalysis.analysis.helix_analysis.HELANAL .. _Helix analysis: https://userguide.mdanalysis.org/stable/examples/ analysis/structure/helanal.html **Inputs** Run frequency 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`` Selection MDAnalysis selection phrase Default: ``resid 1:10 and name CA`` .. note:: HELANAL is designed to work on the alpha-carbon atoms of protein residues and the selection must return at least 9 residues Property Computed property to plot Default: ``local_twists`` 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/helix_analysis_output.jpg :alt: Helix Analysis output .. tip:: This widget supports batching and can run in parallel """ name = "Helix Analysis" description = "Helix analysis using HELANAL" _doclink = ( "https://mdadash.readthedocs.io/en/latest/autosummary/" "mdadash.backend.analyses.helix_analysis.html" ) _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": "selection", "name": "Selection", "description": "MDAnalysis selection phrase", "type": "str", "validations": ["required"], }, { "attribute": "property", "name": "Property", "description": "Computed property to plot", "type": "select", "items": [ "local_twists", "local_nres_per_turn", "local_bends", "local_heights", "local_screw_angles", ], }, { "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.selection = "resid 1:10 and name CA" self.property = "local_twists" self.ha = None self.title = "Helix Analysis" 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.y_labels = { "local_twists": "Average local twist (degrees)", "local_nres_per_turn": "Average residues per turn", "local_bends": "Average local bends (degrees)", "local_heights": "Average rise of each local helix (Å)", "local_screw_angles": "Average local screw angle (degrees)", } 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.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.ax.set_ylabel(self.y_labels[self.property]) 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_ha(self): """Update atom groups when selection phrases change""" self.ha = HELANAL(self.u, select=self.selection) self.title = f"Helix analysis of '{self.selection}'" self._set_title()
[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_ha() 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 ("selection", "property"): self._reset_plot_values() self._create_ha() elif attribute == "plot_refresh_frequency": self.plot_refresh_count = 1
def _compute_current_frame(self): """Compute values for current frame""" with warnings.catch_warnings(): warnings.simplefilter("ignore", category=RuntimeWarning) self.ha.run(frames=[self.u.trajectory.frame]) results = getattr(self.ha.results, self.property) mean_values = results.mean(axis=1) return ( self.u.trajectory.ts.data["step"], self.u.trajectory.ts.data["time"], mean_values[0], ) def _compute_batch(self): """Compute values for current batch""" with warnings.catch_warnings(): warnings.simplefilter("ignore", category=RuntimeWarning) self.ha.run() results = getattr(self.ha.results, self.property) mean_values = results.mean(axis=1) values = [] for i, v in enumerate(mean_values): _ = self.u.trajectory[i] values.append( ( self.u.trajectory.ts.data["step"], self.u.trajectory.ts.data["time"], v, ) ) 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)