From 8c69fc013124416a3d2d0cce88cc5aa04a63ed47 Mon Sep 17 00:00:00 2001 From: IAlibay Date: Fri, 23 Feb 2024 05:56:01 +0000 Subject: [PATCH] update for external mdahole2 mdakit --- .github/actions/setup-deps/action.yaml | 3 + package/MDAnalysis/analysis/hole2/__init__.py | 279 +--- package/MDAnalysis/analysis/hole2/hole.py | 1400 +---------------- .../MDAnalysis/analysis/hole2/templates.py | 123 +- package/MDAnalysis/analysis/hole2/utils.py | 539 +------ package/doc/sphinx/source/conf.py | 1 + package/pyproject.toml | 1 + 7 files changed, 52 insertions(+), 2294 deletions(-) diff --git a/.github/actions/setup-deps/action.yaml b/.github/actions/setup-deps/action.yaml index 392aeeb428c..e5a0794450c 100644 --- a/.github/actions/setup-deps/action.yaml +++ b/.github/actions/setup-deps/action.yaml @@ -31,6 +31,8 @@ inputs: default: 'hypothesis' matplotlib: default: 'matplotlib-base' + mdahole2: + default: 'mdahole2-base' mda_xdrlib: default: 'mda-xdrlib' mmtf-python: @@ -116,6 +118,7 @@ runs: ${{ inputs.griddataformats }} ${{ inputs.hypothesis }} ${{ inputs.matplotlib }} + ${{ inputs.mdahole2 }} ${{ inputs.mda_xdrlib }} ${{ inputs.mmtf-python }} ${{ inputs.numpy }} diff --git a/package/MDAnalysis/analysis/hole2/__init__.py b/package/MDAnalysis/analysis/hole2/__init__.py index beab152b301..9cd00591dfe 100644 --- a/package/MDAnalysis/analysis/hole2/__init__.py +++ b/package/MDAnalysis/analysis/hole2/__init__.py @@ -34,280 +34,15 @@ :footcite:p:`Smart1993,Smart1996` to analyse an ion channel pore or transporter pathway :footcite:p:`Stelzl2014`. -Using HOLE on a PDB file ------------------------- +.. deprecated:: 2.8.0 + This module is deprecated in favour of the mdakit + `mdahole2 `_ and + will be removed in MDAnalysis 3.0.0. -Use the :func:``hole`` function to run `HOLE`_ on a single PDB file. For example, -the code below runs the `HOLE`_ program installed at '~/hole2/exe/hole' :: - - from MDAnalysis.tests.datafiles import PDB_HOLE - from MDAnalysis.analysis import hole2 - profiles = hole2.hole(PDB_HOLE, executable='~/hole2/exe/hole') - # to create a VMD surface of the pore - hole2.create_vmd_surface(filename='hole.vmd') - -``profiles`` is a dictionary of HOLE profiles, indexed by the frame number. If only -a PDB file is passed to the function, there will only be one profile at frame 0. -You can visualise the pore by loading your PDB file into VMD, and in -Extensions > Tk Console, type:: - - source hole.vmd - -You can also pass a DCD trajectory with the same atoms in the same order as -your PDB file with the ``dcd`` keyword argument. In that case, ``profiles`` will -contain multiple HOLE profiles, indexed by frame. - -The HOLE program will create some output files: - -* an output file (default name: hole.out) -* an sphpdb file (default name: hole.sph) -* a file of van der Waals' radii - (if not specified with ``vdwradii_file``. Default name: simple2.rad) -* a symlink of your PDB or DCD files (if the original name is too long) -* the input text (if you specify ``infile``) - -By default (`keep_files=True`), these files are kept. If you would like to -delete the files after the function is wrong, set `keep_files=False`. Keep in -mind that if you delete the sphpdb file, you cannot then create a VMD surface. - - -Using HOLE on a trajectory --------------------------- - -You can also run HOLE on a trajectory through the :class:`HoleAnalysis` class. -This behaves similarly to the ``hole`` function, although arguments such as ``cpoint`` -and ``cvect`` become runtime arguments for the :meth:`~HoleAnalysis.run` function. - -The class can be set-up and run like a normal MDAnalysis analysis class:: - - import MDAnalysis as mda - from MDAnalysis.tests.datafiles import MULTIPDB_HOLE - from MDAnalysis.analysis import hole2 - - u = mda.Universe(MULTIPDB_HOLE) - - ha = hole2.HoleAnalysis(u, executable='~/hole2/exe/hole') as h2: - ha.run() - ha.create_vmd_surface(filename='hole.vmd') - -The VMD surface created by the class updates the pore for each frame of the trajectory. -Use it as normal by loading your trajectory in VMD and sourcing the file in the Tk Console. - -You can access the actual profiles generated in the ``results`` attribute:: - - print(ha.results.profiles) - -Again, HOLE writes out files for each frame. If you would like to delete these files -after the analysis, you can call :meth:`~HoleAnalysis.delete_temporary_files`:: - - ha.delete_temporary_files() - -Alternatively, you can use HoleAnalysis as a context manager that deletes temporary -files when you are finished with the context manager:: - - with hole2.HoleAnalysis(u, executable='~/hole2/exe/hole') as h2: - h2.run() - h2.create_vmd_surface() - - -Using HOLE with VMD -------------------- - -The :program:`sos_triangle` program that is part of HOLE_ can write an input -file for VMD_ to display a triangulated surface of the pore found by -:program:`hole`. This functionality is available with the -:meth:`HoleAnalysis.create_vmd_surface` method -[#create_vmd_surface_function]_. For an input trajectory MDAnalysis writes a -*trajectory* of pore surfaces that can be animated in VMD together with the -frames from the trajectory. - - -Analyzing a full trajectory -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -To analyze a full trajectory and write pore surfaces for all frames to file -:file:`hole_surface.vmd`, use :: - - import MDAnalysis as mda - from MDAnalysis.analysis import hole2 - - # load example trajectory MULTIPDB_HOLE - from MDAnalysis.tests.datafiles import MULTIPDB_HOLE - - u = mda.Universe(MULTIPDB_HOLE) - - with hole2.HoleAnalysis(u, executable='~/hole2/exe/hole') as h2: - h2.run() - h2.create_vmd_surface(filename="hole_surface.vmd") - -In VMD, load your trajectory and then in the tcl console -(e.g.. :menuselection:`Extensions --> Tk Console`) load the surface -trajectory: - -.. code-block:: tcl - - source hole_surface.vmd - -If you only want to *subsample the trajectory* and only show the surface at -specific frames then you can either load the trajectory with the same -subsampling into VMD or create a subsampled trajectory. - - -Creating subsampled HOLE surface -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -For example, if we want to start displaying at frame 1 (i.e., skip frame 0), stop at frame 7, and -only show every other frame (step 2) then the HOLE analysis will be :: - - with hole2.HoleAnalysis(u, executable='~/hole2/exe/hole') as h2: - h2.run(start=1, stop=9, step=2) - h2.create_vmd_surface(filename="hole_surface_subsampled.vmd") - -The commands produce the file ``hole_surface_subsampled.vmd`` that can be loaded into VMD. - -.. Note:: - - Python (and MDAnalysis) stop indices are *exclusive* so the parameters - ``start=1``, ``stop=9``, and ``step=2`` will analyze frames 1, 3, 5, 7. - -.. _Loading-a-trajectory-into-VMD-with-subsampling: - -Loading a trajectory into VMD with subsampling -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Load your system into VMD. This can mean to load the topology file with -:menuselection:`File --> New Molecule` and adding the trajectory with -:menuselection:`File --> Load Data into Molecule` or just :menuselection:`File ---> New Molecule`. - -When loading the trajectory, subsample the frames by setting parametes in in -the :guilabel:`Frames` section. Select *First: 1*, *Last: 7*, *Stride: 2*. Then -:guilabel:`Load` everything. - -.. Note:: - - VMD considers the stop/last frame to be *inclusive* so you need to typically - choose one less than the ``stop`` value that you selected in MDAnalysis. - -Then load the surface trajectory: - -.. code-block:: tcl - - source hole_surface_subsampled.vmd - -You should see a different surface for each frame in the trajectory. [#vmd_extra_frame]_ - - -Creating a subsampled trajectory -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Instead of having VMD subsample the trajectory as described in -:ref:`Loading-a-trajectory-into-VMD-with-subsampling` we can write a subsampled -trajectory to a file. Although it requires more disk space, it can be -convenient if we want to visualize the system repeatedly. - -The example trajectory comes as a multi-PDB file so we need a suitable topology -file. If you already have a topology file such as a PSF, TPR, or PRMTOP file -then skip this step. We write frame 0 as a PDB :file:`frame0.pdb` (which we -will use as the topology in VMD):: - - u.atoms.write("frame0.pdb") - -Then write the actual trajectory in a convenient format such as TRR (or -DCD). Note that we apply the same slicing (``start=1``, ``stop=9``, ``step=2``) -to the trajectory itself and then use it as the value for the ``frames`` -parameter of :meth:`AtomGroup.write` -method:: - - u.atoms.write("subsampled.trr", frames=u.trajectory[1:9:2]) - -This command creates the subsampled trajectory file :file:`subsampled.trr` in -TRR format. - -In VMD we load the topology and the trajectory and then load the surface. In -our example we have a PDB file (:file:`frame0.pdb`) as topology so we need to -remove the first frame [#vmd_extra_frame]_ (skip the "trim" step below if you -are using a true topology file such as PSF, TPR, or PRMTOP). To keep this -example compact, we are using the tcl command line interface in VMD_ -(:menuselection:`Extensions --> Tk Console`) for loading and trimming the -trajectory; you can use the menu commands if you prefer. - -.. code-block:: tcl - - # load topology and subsampled trajectory - mol load pdb frame0.pdb trr subsampled.trr - - # trim first frame (frame0) -- SKIP if using PSF, TPR, PRMTOP - animate delete beg 0 end 0 - - # load the HOLE surface trajectory - source hole_surface_subsampled.vmd - -You can now animate your molecule together with the surface and render it. - - -.. _HOLE: http://www.holeprogram.org -.. _VMD: https://www.ks.uiuc.edu/Research/vmd/ - -Functions and classes ---------------------- - -.. autofunction:: hole - -.. autoclass:: HoleAnalysis - :members: - - -.. rubric:: References - -.. footbibliography:: - -.. rubric:: Footnotes - -.. Footnotes - -.. [#create_vmd_surface_function] If you use the :class:`hole` class to run - :program:`hole` on a single PDB file then you can use - :func:`MDAnalysis.analysis.hole2.utils.create_vmd_surface` - function to manually run :program:`sph_process` and - :program:`sos_triangle` on the output files andcr eate a surface - file. - -.. [#vmd_extra_frame] If you loaded your system in VMD_ from separate topology - and trajectory files and the topology file contained coordinates - (such as a PDB or GRO) file then your trajectory will have an - extra initial frame containing the coordinates from your topology - file. Delete the initial frame with :menuselection:`Molecule --> - Delete Frames` by setting *First* to 0 and *Last* to 0 and - selecting :guilabel:`Delete`. - -.. [#HOLEDCD] PDB files are not the only files that :program:`hole` can - read. In principle, it is also able to read CHARMM DCD - trajectories and generate a hole profile for each frame. However, - native support for DCD in :program:`hole` is patchy and not every - DCD is recognized. In particular, At the moment, DCDs generated - with MDAnalysis are not accepted by HOLE. To overcome this - PDB / DCD limitation, use :class:`HoleAnalysis` which creates - temporary PDB files for each frame of a - :class:`~MDAnalysis.core.universe.Universe` or - :class:`~MDAnalysis.core.universe.AtomGroup` and runs - :func:``hole`` on each of them. +See Also +-------- +:mod:`mdahole2.analysis.hole` """ -from ...due import due, Doi from .hole import hole, HoleAnalysis from .utils import create_vmd_surface - -due.cite(Doi("10.1016/S0006-3495(93)81293-1"), - description="HOLE program", - path="MDAnalysis.analysis.hole2", - cite_module=True) -due.cite(Doi("10.1016/S0263-7855(97)00009-X"), - description="HOLE program", - path="MDAnalysis.analysis.hole2", - cite_module=True) -due.cite(Doi("10.1016/j.jmb.2013.10.024"), - description="HOLE trajectory analysis with orderparameters", - path="MDAnalysis.analysis.hole2", - cite_module=True) -del Doi diff --git a/package/MDAnalysis/analysis/hole2/hole.py b/package/MDAnalysis/analysis/hole2/hole.py index 4167e0e50c8..679d41e20cb 100644 --- a/package/MDAnalysis/analysis/hole2/hole.py +++ b/package/MDAnalysis/analysis/hole2/hole.py @@ -20,1399 +20,11 @@ # MDAnalysis: A Toolkit for the Analysis of Molecular Dynamics Simulations. # J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 # -import os -import errno -import shutil -import tempfile -import textwrap -import logging -import itertools import warnings +from mdahole2.analysis.hole import hole, HoleAnalysis -import numpy as np -import matplotlib -import matplotlib.pyplot as plt -from collections import OrderedDict - -from MDAnalysis.lib.util import deprecate -from ...exceptions import ApplicationError -from ..base import AnalysisBase -from ...lib import util -from .utils import (check_and_fix_long_filename, write_simplerad2, - set_up_hole_input, run_hole, collect_hole, - create_vmd_surface) -from .templates import (hole_input, hole_lines, vmd_script_array, - vmd_script_function, exe_err, - IGNORE_RESIDUES) - -logger = logging.getLogger(__name__) - - -@deprecate(release="2.6.0", remove="3.0.0", - message=("This method has been moved to the MDAKit hole2-mdakit: " - "https://github.com/MDAnalysis/hole2-mdakit")) -def hole(pdbfile, - infile_text=None, - infile=None, - outfile='hole.out', - sphpdb_file='hole.sph', - vdwradii_file=None, - executable='hole', - tmpdir=os.path.curdir, - sample=0.2, - end_radius=22.0, - cpoint=None, - cvect=None, - random_seed=None, - ignore_residues=IGNORE_RESIDUES, - output_level=0, - dcd=None, - dcd_iniskip=0, - dcd_step=1, - keep_files=True): - r"""Run :program:`hole` on a single frame or a DCD trajectory. - - :program:`hole` is part of the HOLE_ suite of programs. It is used to - analyze channels and cavities in proteins, especially ion channels. - - Only a subset of all `HOLE control parameters `_ - is supported and can be set with keyword arguments. - - Parameters - ---------- - - pdbfile : str - The `filename` is used as input for HOLE in the "COORD" card of the - input file. It specifies the name of a PDB coordinate file to be - used. This must be in Brookhaven protein databank format or - something closely approximating this. Both ATOM and HETATM records - are read. - infile_text: str, optional - HOLE input text or template. If set to ``None``, the function will - create the input text from the other parameters. - infile: str, optional - File to write the HOLE input text for later inspection. If set to - ``None``, the input text is not written out. - outfile : str, optional - file name of the file collecting HOLE's output (which can be - parsed using :meth:`collect_hole(outfile)`. - sphpdb_file : str, optional - path to the HOLE sph file, a PDB-like file containing the - coordinates of the pore centers. - The coordinates are set to the sphere centres and the occupancies - are the sphere radii. All centres are assigned the atom name QSS and - residue name SPH and the residue number is set to the storage - number of the centre. In VMD, sph - objects are best displayed as "Points". Displaying .sph objects - rather than rendered or dot surfaces can be useful to analyze the - distance of particular atoms from the sphere-centre line. - .sph files can be used to produce molecular graphical - output from a hole run, by using the - :program:`sph_process` program to read the .sph file. - vdwradii_file: str, optional - path to the file specifying van der Waals radii for each atom. If - set to ``None``, then a set of default radii, - :data:`SIMPLE2_RAD`, is used (an extension of ``simple.rad`` from - the HOLE distribution). - executable: str, optional - Path to the :program:`hole` executable. - (e.g. ``~/hole2/exe/hole``). If - :program:`hole` is found on the :envvar:`PATH`, then the bare - executable name is sufficient. - tmpdir: str, optional - The temporary directory that files can be symlinked to, to shorten - the path name. HOLE can only read filenames up to a certain length. - sample : float, optional - distance of sample points in Å. - Specifies the distance between the planes used in the HOLE - procedure. The default value should be reasonable for most - purposes. However, if you wish to visualize a very tight - constriction then specify a smaller value. - This value determines how many points in the pore profile are - calculated. - end_radius : float, optional - Radius in Å, which is considered to be the end of the pore. This - keyword can be used to specify the radius above which the - program regards a result as indicating that the end of the pore - has been reached. This may need to be increased for large channels, - or reduced for small channels. - cpoint : array_like, 'center_of_geometry' or None, optional - coordinates of a point inside the pore, e.g. ``[12.3, 0.7, - 18.55]``. If set to ``None`` (the default) then HOLE's own search - algorithm is used. - ``cpoint`` specifies a point which lies within the channel. For - simple channels (e.g. gramicidin), results do not show great - sensitivity to the exact point taken. An easy way to produce an - initial point is to use molecular graphics to find two atoms which - lie either side of the pore and to average their coordinates. Or - if the channel structure contains water molecules or counter ions - then take the coordinates of one of these (and use the - ``ignore_residues`` keyword to ignore them in the pore radius - calculation). - If this card is not specified, then HOLE (from version 2.2) - attempts to guess where the channel will be. The procedure - assumes the channel is reasonably symmetric. The initial guess on - cpoint will be the centroid of all alpha carbon atoms (name 'CA' - in pdb file). This is then refined by a crude grid search up to 5 - Å from the original position. This procedure works most of the - time but is far from infallible — results should be - carefully checked (with molecular graphics) if it is used. - cvect : array_like, optional - Search direction, should be parallel to the pore axis, - e.g. ``[0,0,1]`` for the z-axis. - If this keyword is ``None`` (the default), then HOLE attempts to guess - where the channel will be. The procedure assumes that the channel is - reasonably symmetric. The guess will be either along the X axis - (1,0,0), Y axis (0,1,0) or Z axis (0,0,1). If the structure is not - aligned on one of these axis the results will clearly be - approximate. If a guess is used then results should be carefully - checked. - random_seed : int, optional - integer number to start the random number generator. - By default, - :program:`hole` will use the time of the day. - For reproducible runs (e.g., for testing) set ``random_seed`` - to an integer. - ignore_residues : array_like, optional - sequence of three-letter residues that are not taken into - account during the calculation; wildcards are *not* - supported. Note that all residues must have 3 letters. Pad - with space on the right-hand side if necessary. - output_level : int, optional - Determines the output of output in the ``outfile``. - For automated processing, this must be < 3. - 0: Full text output, - 1: All text output given except "run in progress" (i.e., - detailed contemporary description of what HOLE is doing). - 2: Ditto plus no graph type output - only leaving minimum - radius and conductance calculations. - 3: All text output other than input card mirroring and error messages - turned off. - dcd : str, optional - File name of CHARMM-style DCD trajectory (must be supplied together with a - matching PDB file `filename`) and then HOLE runs its analysis on - each frame. HOLE can *not* read DCD trajectories written by MDAnalysis, - which are NAMD-style (see Notes). Note that structural parameters - determined for each individual structure are written in a tagged - format so that it is possible to extract the information from the text - output file using a :program:`grep` command. The reading of the file - can be controlled by the ``dcd_step`` keyword and/or setting - ``dcd_iniskip`` to the number of frames to be skipped - initially. - dcd_step : int, optional - step size for going through the trajectory (skips ``dcd_step-1`` - frames). - keep_files : bool, optional - Whether to keep the HOLE output files and possible temporary - symlinks after running the function. - - - Returns - ------- - - dict - A dictionary of :class:`numpy.recarray`\ s, indexed by frame. - - - Notes - ----- - - HOLE is very picky and does not read all DCD-like formats [#HOLEDCD]_. - If in doubt, look into the `outfile` for error diagnostics. - - - .. versionadded:: 1.0 - - """ - - if output_level > 3: - msg = 'output_level ({}) needs to be < 3 in order to extract a HOLE profile!' - warnings.warn(msg.format(output_level)) - - # get executable - exe = shutil.which(executable) - if exe is None: - raise OSError(errno.ENOENT, exe_err.format(name=executable, - kw='executable')) - - # get temp files - tmp_files = [outfile, sphpdb_file] - - short_filename = check_and_fix_long_filename(pdbfile, tmpdir=tmpdir) - if os.path.islink(short_filename): - tmp_files.append(short_filename) - - if dcd is not None: - dcd = check_and_fix_long_filename(dcd, tmpdir=tmpdir) - if os.path.islink(dcd): - tmp_files.append(dcd) - - if vdwradii_file is not None: - vdwradii_file = check_and_fix_long_filename(vdwradii_file, - tmpdir=tmpdir) - else: - vdwradii_file = write_simplerad2() - tmp_files.append(vdwradii_file) - - infile_text = set_up_hole_input(short_filename, - infile_text=infile_text, - infile=infile, - sphpdb_file=sphpdb_file, - vdwradii_file=vdwradii_file, - tmpdir=tmpdir, sample=sample, - end_radius=end_radius, - cpoint=cpoint, cvect=cvect, - random_seed=random_seed, - ignore_residues=ignore_residues, - output_level=output_level, - dcd=dcd, - dcd_iniskip=dcd_iniskip, - dcd_step=dcd_step-1) - - run_hole(outfile=outfile, infile_text=infile_text, executable=exe) - recarrays = collect_hole(outfile=outfile) - - if not keep_files: - for file in tmp_files: - try: - os.unlink(file) - except OSError: - pass - - return recarrays - - -class HoleAnalysis(AnalysisBase): - r""" - Run :program:`hole` on a trajectory. - - :program:`hole` is part of the HOLE_ suite of programs. It is used to - analyze channels and cavities in proteins, especially ion channels. - - Only a subset of all `HOLE control parameters `_ - is supported and can be set with keyword arguments. - - This class creates temporary PDB files for each frame and runs HOLE on - the frame. It can be used normally, or as a context manager. If used as a - context manager, the class will try to delete any temporary files created - by HOLE, e.g. sphpdb files and logfiles. :: - - with hole2.HoleAnalysis(u, executable='~/hole2/exe/hole') as h2: - h2.run() - h2.create_vmd_surface() - - Parameters - ---------- - - universe : Universe or AtomGroup - The Universe or AtomGroup to apply the analysis to. - select : string, optional - The selection string to create an atom selection that the HOLE - analysis is applied to. - vdwradii_file : str, optional - path to the file specifying van der Waals radii for each atom. If - set to ``None``, then a set of default radii, - :data:`SIMPLE2_RAD`, is used (an extension of ``simple.rad`` from - the HOLE distribution). - executable : str, optional - Path to the :program:`hole` executable. - (e.g. ``~/hole2/exe/hole``). If - :program:`hole` is found on the :envvar:`PATH`, then the bare - executable name is sufficient. - tmpdir : str, optional - The temporary directory that files can be symlinked to, to shorten - the path name. HOLE can only read filenames up to a certain length. - cpoint : array_like, 'center_of_geometry' or None, optional - coordinates of a point inside the pore, e.g. ``[12.3, 0.7, - 18.55]``. If set to ``None`` (the default) then HOLE's own search - algorithm is used. - ``cpoint`` specifies a point which lies within the channel. For - simple channels (e.g. gramicidin), results do not show great - sensitivity to the exact point taken. An easy way to produce an - initial point is to use molecular graphics to find two atoms which - lie either side of the pore and to average their coordinates. Or - if the channel structure contains water molecules or counter ions - then take the coordinates of one of these (and use the - ``ignore_residues`` keyword to ignore them in the pore radius - calculation). - If this card is not specified, then HOLE (from version 2.2) - attempts to guess where the channel will be. The procedure - assumes the channel is reasonably symmetric. The initial guess on - cpoint will be the centroid of all alpha carbon atoms (name 'CA' - in pdb file). This is then refined by a crude grid search up to 5 - Å from the original position. This procedure works most of the - time but is far from infallible — results should be - carefully checked (with molecular graphics) if it is used. - cvect : array_like, optional - Search direction, should be parallel to the pore axis, - e.g. ``[0,0,1]`` for the z-axis. - If this keyword is ``None`` (the default), then HOLE attempts to guess - where the channel will be. The procedure assumes that the channel is - reasonably symmetric. The guess will be either along the X axis - (1,0,0), Y axis (0,1,0) or Z axis (0,0,1). If the structure is not - aligned on one of these axis the results will clearly be - approximate. If a guess is used then results should be carefully - checked. - sample : float, optional - distance of sample points in Å. - Specifies the distance between the planes used in the HOLE - procedure. The default value should be reasonable for most - purposes. However, if you wish to visualize a very tight - constriction then specify a smaller value. - This value determines how many points in the pore profile are - calculated. - end_radius : float, optional - Radius in Å, which is considered to be the end of the pore. This - keyword can be used to specify the radius above which the - program regards a result as indicating that the end of the pore - has been reached. This may need to be increased for large channels, - or reduced for small channels. - output_level : int, optional - Determines the output of output in the ``outfile``. - For automated processing, this must be < 3. - 0: Full text output, - 1: All text output given except "run in progress" (i.e., - detailed contemporary description of what HOLE is doing). - 2: Ditto plus no graph type output - only leaving minimum - radius and conductance calculations. - 3: All text output other than input card mirroring and error messages - turned off. - ignore_residues : array_like, optional - sequence of three-letter residues that are not taken into - account during the calculation; wildcards are *not* - supported. Note that all residues must have 3 letters. Pad - with space on the right-hand side if necessary. - prefix : str, optional - Prefix for HOLE output files. - write_input_files : bool, optional - Whether to write out the input HOLE text as files. - Files are called `hole.inp`. - - - Attributes - ---------- - results.sphpdbs: numpy.ndarray - Array of sphpdb filenames - - .. versionadded:: 2.0.0 - - results.outfiles: numpy.ndarray - Arrau of output filenames - - .. versionadded:: 2.0.0 - - results.profiles: dict - Profiles generated by HOLE2. - A dictionary of :class:`numpy.recarray`\ s, indexed by frame. - - .. versionadded:: 2.0.0 - - sphpdbs: numpy.ndarray - Alias of :attr:`results.sphpdbs` - - .. deprecated:: 2.0.0 - This will be removed in MDAnalysis 3.0.0. Please use - :attr:`results.sphpdbs` instead. - - outfiles: numpy.ndarray - Alias of :attr:`results.outfiles` - - .. deprecated:: 2.0.0 - This will be removed in MDAnalysis 3.0.0. Please use - :attr:`results.outfiles` instead. - - profiles: dict - Alias of :attr:`results.profiles` - - .. deprecated:: 2.0.0 - This will be removed in MDAnalysis 3.0.0. Please use - :attr:`results.profiles` instead. - - .. versionadded:: 1.0 - - .. versionchanged:: 2.0.0 - :attr:`sphpdbs`, :attr:`outfiles` and :attr:`profiles ` - are now stored in a :class:`MDAnalysis.analysis.base.Results` - instance. - - .. deprecated:: 2.6.0 - This class has been moved to the MDAKit - `hole2-mdakit `_ and will - be removed for the core MDAnalysis library in version 3.0 - - """ - - input_file = '{prefix}hole{i:03d}.inp' - output_file = '{prefix}hole{i:03d}.out' - sphpdb_file = '{prefix}hole{i:03d}.sph' - - input_file = '{prefix}hole{i:03d}.inp' - output_file = '{prefix}hole{i:03d}.out' - sphpdb_file = '{prefix}hole{i:03d}.sph' - - hole_header = textwrap.dedent(""" - ! Input file for Oliver Smart's HOLE program - ! written by MDAnalysis.analysis.hole2.HoleAnalysis - ! for a Universe - ! u = mda.Universe({} - ! ) - ! Frame {{i}} - """) - - hole_body = textwrap.dedent(""" - COORD {{coordinates}} - RADIUS {radius} - SPHPDB {{sphpdb}} - SAMPLE {sample:f} - ENDRAD {end_radius:f} - IGNORE {ignore} - SHORTO {output_level:d} - """) - - _guess_cpoint = False - - def __init__(self, universe, - select='protein', - verbose=False, - ignore_residues=IGNORE_RESIDUES, - vdwradii_file=None, - executable='hole', - sos_triangle='sos_triangle', - sph_process='sph_process', - tmpdir=os.path.curdir, - cpoint=None, - cvect=None, - sample=0.2, - end_radius=22, - output_level=0, - prefix=None, - write_input_files=False): - super(HoleAnalysis, self).__init__(universe.universe.trajectory, - verbose=verbose) - - wmsg = ("This class has been moved to the MDAKit `hole2-mdakit` " - "(https://github.com/MDAnalysis/hole2-mdakit) and will be " - "removed in version 3.0.") - warnings.warn(wmsg, DeprecationWarning) - - if output_level > 3: - msg = 'output_level ({}) needs to be < 3 in order to extract a HOLE profile!' - warnings.warn(msg.format(output_level)) - - if prefix is None: - prefix = '' - - if isinstance(cpoint, str): - if 'geometry' in cpoint.lower(): - self._guess_cpoint = True - self.cpoint = '{cpoint[0]:.10f} {cpoint[1]:.10f} {cpoint[2]:.10f}' - else: - self._guess_cpoint = False - self.cpoint = cpoint - - self.prefix = prefix - self.cvect = cvect - self.sample = sample - self.end_radius = end_radius - self.output_level = output_level - self.write_input_files = write_input_files - self.select = select - self.ag = universe.select_atoms(select, updating=True) - self.universe = universe - self.tmpdir = tmpdir - self.ignore_residues = ignore_residues - - # --- finding executables ---- - hole = shutil.which(executable) - if hole is None: - raise OSError(errno.ENOENT, exe_err.format(name=executable, - kw='executable')) - self.base_path = os.path.dirname(hole) - - sos_triangle_path = shutil.which(sos_triangle) - if sos_triangle_path is None: - path = os.path.join(self.base_path, sos_triangle) - sos_triangle_path = shutil.which(path) - if sos_triangle_path is None: - raise OSError(errno.ENOENT, exe_err.format(name=sos_triangle, - kw='sos_triangle')) - sph_process_path = shutil.which(sph_process) - if sph_process_path is None: - path = os.path.join(self.base_path, sph_process) - sph_process_path = shutil.which(path) - if sph_process_path is None: - raise OSError(errno.ENOENT, exe_err.format(name=sph_process, - kw='sph_process')) - - self.exe = { - 'hole': hole, - 'sos_triangle': sos_triangle_path, - 'sph_process': sph_process_path - } - - # --- setting up temp files ---- - self.tmp_files = [] - if vdwradii_file is not None: - self.vdwradii_file = check_and_fix_long_filename(vdwradii_file, - tmpdir=self.tmpdir) - if os.path.islink(self.vdwradii_file): - self.tmp_files.append(self.vdwradii_file) - else: - self.vdwradii_file = write_simplerad2() - self.tmp_files.append(self.vdwradii_file) - - # --- setting up input header ---- - filenames = [universe.filename] - try: - filenames.extend(universe.trajectory.filenames) - except AttributeError: - filenames.append(universe.trajectory.filename) - filenames = [name for name in filenames if name is not None] - hole_filenames = '\n! '.join(filenames) - self._input_header = self.hole_header.format(hole_filenames) - - def run(self, start=None, stop=None, step=None, verbose=None, - random_seed=None): - """ - Perform the calculation - - Parameters - ---------- - start : int, optional - start frame of analysis - - stop : int, optional - stop frame of analysis - - step : int, optional - number of frames to skip between each analysed frame - - verbose : bool, optional - Turn on verbosity - - random_seed : int, optional - integer number to start the random number generator. - By default, - :program:`hole` will use the time of the day. - For reproducible runs (e.g., for testing) set ``random_seed`` - to an integer. - """ - self.random_seed = random_seed - return super(HoleAnalysis, self).run(start=start, stop=stop, - step=step, verbose=verbose) - - @property - def sphpdbs(self): - wmsg = ("The `sphpdbs` attribute was deprecated in " - "MDAnalysis 2.0.0 and will be removed in MDAnalysis 3.0.0. " - "Please use `results.sphpdbs` instead.") - warnings.warn(wmsg, DeprecationWarning) - return self.results.sphpdbs - - @property - def outfiles(self): - wmsg = ("The `outfiles` attribute was deprecated in " - "MDAnalysis 2.0.0 and will be removed in MDAnalysis 3.0.0. " - "Please use `results.outfiles` instead.") - warnings.warn(wmsg, DeprecationWarning) - return self.results.outfiles - - @property - def profiles(self): - wmsg = ("The `profiles` attribute was deprecated in " - "MDAnalysis 2.0.0 and will be removed in MDAnalysis 3.0.0. " - "Please use `results.profiles` instead.") - warnings.warn(wmsg, DeprecationWarning) - return self.results.profiles - - def _prepare(self): - """Set up containers and generate input file text""" - # set up containers - self.results.sphpdbs = np.zeros(self.n_frames, dtype=object) - self.results.outfiles = np.zeros(self.n_frames, dtype=object) - self.results.profiles = {} - - # generate input file - body = set_up_hole_input('', - infile_text=self.hole_body, - infile=None, - vdwradii_file=self.vdwradii_file, - tmpdir=self.tmpdir, - sample=self.sample, - end_radius=self.end_radius, - cpoint=self.cpoint, - cvect=self.cvect, - random_seed=self.random_seed, - ignore_residues=self.ignore_residues, - output_level=self.output_level, - dcd=None) - - self.infile_text = self._input_header + body - - def guess_cpoint(self): - """Guess a point inside the pore. - - This method simply uses the center of geometry of the selection as a - guess. - - Returns - ------- - float: - center of geometry of selected AtomGroup - """ - return self.ag.center_of_geometry() - - def _single_frame(self): - """Run HOLE analysis and collect profiles""" - # set up files - frame = self._ts.frame - i = self._frame_index - outfile = self.output_file.format(prefix=self.prefix, i=frame) - sphpdb = self.sphpdb_file.format(prefix=self.prefix, i=frame) - self.results.sphpdbs[i] = sphpdb - self.results.outfiles[i] = outfile - if outfile not in self.tmp_files: - self.tmp_files.append(outfile) - if sphpdb not in self.tmp_files: - self.tmp_files.append(sphpdb) - else: - self.tmp_files.append(sphpdb + '.old') - - # temp pdb - logger.info('HOLE analysis frame {}'.format(frame)) - fd, pdbfile = tempfile.mkstemp(suffix='.pdb') - os.close(fd) # close immediately (Issue 129) - - # get infile text - fmt_kwargs = {'i': frame, 'coordinates': pdbfile, 'sphpdb': sphpdb} - if self._guess_cpoint: - fmt_kwargs['cpoint'] = self.guess_cpoint() - infile_text = self.infile_text.format(**fmt_kwargs) - - if self.write_input_files: - infile = self.input_file.format(prefix=self.prefix, i=frame) - with open(infile, 'w') as f: - f.write(infile_text) - - try: - self.ag.write(pdbfile) - run_hole(outfile=outfile, infile_text=infile_text, - executable=self.exe['hole']) - finally: - try: - os.unlink(pdbfile) - except OSError: - pass - - recarrays = collect_hole(outfile=outfile) - try: - self.results.profiles[frame] = recarrays[0] - except KeyError: - msg = 'No profile found in HOLE output. Output level: {}' - logger.info(msg.format(self.output_level)) - - def create_vmd_surface(self, filename='hole.vmd', dot_density=15, - no_water_color='red', one_water_color='green', - double_water_color='blue'): - """Process HOLE output to create a smooth pore surface suitable for VMD. - - Takes the ``sphpdb`` file for each frame and feeds it to `sph_process - `_ and - `sos_triangle - `_ as - described under `Visualization of HOLE results - `_. - - Load the output file *filename* into VMD in :menuselection:`Extensions - --> Tk Console` :: - - source hole.vmd - - The level of detail is determined by ``dot_density``. - The surface will be colored by ``no_water_color``, ``one_water_color``, and - ``double_water_color``. You can change these in the - Tk Console:: - - set no_water_color blue - - - Parameters - ---------- - filename: str, optional - file to write the pore surfaces to. - - dot_density: int, optional - density of facets for generating a 3D pore representation. - The number controls the density of dots that will be used. - A sphere of dots is placed on each centre determined in the - Monte Carlo procedure. The actual number of dots written is - controlled by ``dot_density`` and the ``sample`` level of the - original analysis. ``dot_density`` should be set between 5 - (few dots per sphere) and 35 (many dots per sphere). - - no_water_color: str, optional - Color of the surface where the pore radius is too tight for a - water molecule. - - one_water_color: str, optional - Color of the surface where the pore can fit one water molecule. - - double_water_color: str, optional - Color of the surface where the radius is at least double the - minimum radius for one water molecule. - - - Returns - ------- - str - ``filename`` with the pore surfaces. - - """ - if not np.any(self.results.get("sphpdbs", [])): - raise ValueError('No sphpdb files to read. Try calling run()') - - frames = [] - for i, frame in enumerate(self.frames): - sphpdb = self.results.sphpdbs[i] - tmp_tri = create_vmd_surface(sphpdb=sphpdb, - sph_process=self.exe['sph_process'], - sos_triangle=self.exe['sos_triangle'], - dot_density=dot_density) - - shapes = [[], [], []] - with open(tmp_tri) as f: - for line in f: - if line.startswith('draw color'): - color = line.split()[-1].lower() - if color == 'red': - dest = shapes[0] - elif color == 'green': - dest = shapes[1] - elif color == 'blue': - dest = shapes[2] - else: - msg = 'Encountered unknown color {}' - raise ValueError(msg.format(color)) - - if line.startswith('draw trinorm'): - line = line.strip('draw trinorm').strip() - dest.append('{{ {} }}'.format(line)) - try: - os.unlink(tmp_tri) - except OSError: - pass - - tri = '{ { ' + ' } { '.join(list(map(' '.join, shapes))) + ' } }' - frames.append(f'set triangles({i}) ' + tri) - - trinorms = '\n'.join(frames) - vmd_1 = vmd_script_array.format(no_water_color=no_water_color, - one_water_color=one_water_color, - double_water_color=double_water_color) - vmd_text = vmd_1 + trinorms + vmd_script_function - - with open(filename, 'w') as f: - f.write(vmd_text) - - return filename - - def min_radius(self): - """Return the minimum radius over all profiles as a function of q""" - profiles = self.results.get("profiles") - if not profiles: - raise ValueError('No profiles available. Try calling run()') - return np.array([[q, p.radius.min()] for q, p in profiles.items()]) - - def delete_temporary_files(self): - """Delete temporary files""" - for f in self.tmp_files: - try: - os.unlink(f) - except OSError: - pass - self.tmp_files = [] - self.results.outfiles = [] - self.results.sphpdbs = [] - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - """Delete temporary files on exit""" - self.delete_temporary_files() - - def _process_plot_kwargs(self, frames=None, - color=None, cmap='viridis', - linestyle='-'): - """Process the colors and linestyles for plotting - - Parameters - ---------- - frames : array-like, optional - Frames to plot. If ``None``, plots all of them. - color : str or array-like, optional - Color or colors for the plot. If ``None``, colors are - drawn from ``cmap``. - cmap : str, optional - color map to make colors for the plot if ``color`` is - not given. Names should be from the ``matplotlib.pyplot.cm`` - module. - linestyle : str or array-like, optional - Line style for the plot. - - - Returns - ------- - (array-like, array-like, array-like) - frames, colors, linestyles - """ - - if frames is None: - frames = self.frames - else: - frames = util.asiterable(frames) - - if color is None: - colormap = matplotlib.colormaps.get_cmap(cmap) - norm = matplotlib.colors.Normalize(vmin=min(frames), - vmax=max(frames)) - colors = colormap(norm(frames)) - else: - colors = itertools.cycle(util.asiterable(color)) - - linestyles = itertools.cycle(util.asiterable(linestyle)) - - return frames, colors, linestyles - - def plot(self, frames=None, - color=None, cmap='viridis', - linestyle='-', y_shift=0.0, - label=True, ax=None, - legend_loc='best', **kwargs): - r"""Plot HOLE profiles :math:`R(\zeta)` in a 1D graph. - - Lines are colored according to the specified ``color`` or - drawn from the color map ``cmap``. One line is - plotted for each trajectory frame. - - Parameters - ---------- - frames: array-like, optional - Frames to plot. If ``None``, plots all of them. - color: str or array-like, optional - Color or colors for the plot. If ``None``, colors are - drawn from ``cmap``. - cmap: str, optional - color map to make colors for the plot if ``color`` is - not given. Names should be from the ``matplotlib.pyplot.cm`` - module. - linestyle: str or array-like, optional - Line style for the plot. - y_shift : float, optional - displace each :math:`R(\zeta)` profile by ``y_shift`` in the - :math:`y`-direction for clearer visualization. - label : bool or string, optional - If ``False`` then no legend is - displayed. - ax : :class:`matplotlib.axes.Axes` - If no `ax` is supplied or set to ``None`` then the plot will - be added to the current active axes. - legend_loc : str, optional - Location of the legend. - kwargs : `**kwargs` - All other `kwargs` are passed to :func:`matplotlib.pyplot.plot`. - - - Returns - ------- - ax : :class:`~matplotlib.axes.Axes` - Axes with the plot, either `ax` or the current axes. - - """ - - if not self.results.get("profiles"): - raise ValueError('No profiles available. Try calling run()') - - if ax is None: - fig, ax = plt.subplots() - - fcl = self._process_plot_kwargs(frames=frames, color=color, - cmap=cmap, linestyle=linestyle) - - for i, (frame, c, ls) in enumerate(zip(*fcl)): - profile = self.results.profiles[frame] - dy = i*y_shift - ax.plot(profile.rxn_coord, profile.radius+dy, color=c, - linestyle=ls, zorder=-frame, label=str(frame), - **kwargs) - - ax.set_xlabel(r"Pore coordinate $\zeta$ ($\AA$)") - ax.set_ylabel(r"HOLE radius $R$ ($\AA$)") - if label == True: - ax.legend(loc=legend_loc) - return ax - - def plot3D(self, frames=None, - color=None, cmap='viridis', - linestyle='-', ax=None, r_max=None, - ylabel='Frames', **kwargs): - r"""Stacked 3D graph of profiles :math:`R(\zeta)`. - - Lines are colored according to the specified ``color`` or - drawn from the color map ``cmap``. One line is - plotted for each trajectory frame. - - Parameters - ---------- - frames : array-like, optional - Frames to plot. If ``None``, plots all of them. - color : str or array-like, optional - Color or colors for the plot. If ``None``, colors are - drawn from ``cmap``. - cmap : str, optional - color map to make colors for the plot if ``color`` is - not given. Names should be from the ``matplotlib.pyplot.cm`` - module. - linestyle : str or array-like, optional - Line style for the plot. - r_max : float, optional - only display radii up to ``r_max``. If ``None``, all radii are - plotted. - ax : :class:`matplotlib.axes.Axes` - If no `ax` is supplied or set to ``None`` then the plot will - be added to the current active axes. - ylabel : str, optional - Y-axis label. - **kwargs : - All other `kwargs` are passed to :func:`matplotlib.pyplot.plot`. - - - Returns - ------- - ax : :class:`~mpl_toolkits.mplot3d.Axes3D` - Axes with the plot, either `ax` or the current axes. - - """ - - if not self.results.get("profiles"): - raise ValueError('No profiles available. Try calling run()') - - from mpl_toolkits.mplot3d import Axes3D - - if ax is None: - fig = plt.figure() - ax = fig.add_subplot(111, projection='3d') - - fcl = self._process_plot_kwargs(frames=frames, - color=color, cmap=cmap, - linestyle=linestyle) - - for frame, c, ls in zip(*fcl): - profile = self.results.profiles[frame] - if r_max is None: - radius = profile.radius - rxn_coord = profile.rxn_coord - else: - # does not seem to work with masked arrays but with nan hack! - # http://stackoverflow.com/questions/4913306/python-matplotlib-mplot3d-how-do-i-set-a-maximum-value-for-the-z-axis - rxn_coord = profile.rxn_coord - radius = profile.radius.copy() - radius[radius > r_max] = np.nan - ax.plot(rxn_coord, frame*np.ones_like(rxn_coord), radius, - color=c, linestyle=ls, zorder=-frame, label=str(frame), - **kwargs) - - ax.set_xlabel(r"Pore coordinate $\zeta$ ($\AA$)") - ax.set_ylabel(ylabel) - ax.set_zlabel(r"HOLE radius $R$ ($\AA$)") - plt.tight_layout() - - return ax - - def over_order_parameters(self, order_parameters, frames=None): - """Get HOLE profiles sorted over order parameters ``order_parameters``. - - Parameters - ---------- - order_parameters : array-like or string - Sequence or text file containing order parameters (float - numbers) corresponding to the frames in the trajectory. Must - be same length as trajectory. - frames : array-like, optional - Selected frames to return. If ``None``, returns all of them. - - Returns - ------- - collections.OrderedDict - sorted dictionary of {order_parameter:profile} - - """ - if not self.results.get("profiles"): - raise ValueError('No profiles available. Try calling run()') - if isinstance(order_parameters, str): - try: - order_parameters = np.loadtxt(order_parameters) - except IOError: - raise ValueError('Data file not found: {}'.format(order_parameters)) - except (ValueError, TypeError): - msg = ('Could not parse given file: {}. ' - '`order_parameters` must be array-like ' - 'or a filename with array data ' - 'that can be read by np.loadtxt') - raise ValueError(msg.format(order_parameters)) - - - order_parameters = np.asarray(order_parameters) - - if len(order_parameters) != len(self._trajectory): - msg = ('The number of order parameters ({}) must match the ' - 'length of the trajectory ({} frames)') - raise ValueError(msg.format(len(order_parameters), - len(self._trajectory))) - - if frames is None: - frames = self.frames - else: - frames = np.asarray(util.asiterable(frames)) - - idx = np.argsort(order_parameters[frames]) - sorted_frames = frames[idx] - - profiles = OrderedDict() - for frame in sorted_frames: - profiles[order_parameters[frame]] = self.results.profiles[frame] - - return profiles - - def plot_order_parameters(self, order_parameters, - aggregator=min, - frames=None, - color='blue', - linestyle='-', ax=None, - ylabel=r'Minimum HOLE pore radius $r$ ($\AA$)', - xlabel='Order parameter', - **kwargs): - r"""Plot HOLE radii over order parameters. This function needs - an ``aggregator`` function to reduce the ``radius`` array to a - single value, e.g. ``min``, ``max``, or ``np.mean``. - - Parameters - ---------- - order_parameters : array-like or string - Sequence or text file containing order parameters (float - numbers) corresponding to the frames in the trajectory. Must - be same length as trajectory. - aggregator : callable, optional - Function applied to the radius array of each profile to - reduce it to one representative value. - frames : array-like, optional - Frames to plot. If ``None``, plots all of them. - color : str or array-like, optional - Color for the plot. - linestyle : str or array-like, optional - Line style for the plot. - ax : :class:`matplotlib.axes.Axes` - If no `ax` is supplied or set to ``None`` then the plot will - be added to the current active axes. - xlabel : str, optional - X-axis label. - ylabel : str, optional - Y-axis label. - **kwargs : - All other `kwargs` are passed to :func:`matplotlib.pyplot.plot`. - - - Returns - ------- - ax : :class:`~matplotlib.axes.Axes` - Axes with the plot, either `ax` or the current axes. - - """ - - op_profiles = self.over_order_parameters(order_parameters, - frames=frames) - - if ax is None: - fig, ax = plt.subplots() - - data = [[x, aggregator(p.radius)] for x, p in op_profiles.items()] - arr = np.array(data) - ax.plot(arr[:, 0], arr[:, 1], color=color, linestyle=linestyle) - - ax.set_xlabel(xlabel) - ax.set_ylabel(ylabel) - return ax - - def gather(self, frames=None, flat=False): - """Gather the fields of each profile recarray together. - - Parameters - ---------- - frames : int or iterable of ints, optional - Profiles to include by frame. If ``None``, includes - all frames. - flat : bool, optional - Whether to flatten the list of field arrays into a - single array. - - Returns - ------- - dict - dictionary of fields - """ - if frames is None: - frames = self.frames - frames = util.asiterable(frames) - profiles = [self.results.profiles[k] for k in frames] - - rxncoords = [p.rxn_coord for p in profiles] - radii = [p.radius for p in profiles] - cen_line_Ds = [p.cen_line_D for p in profiles] - - if flat: - rxncoords = np.concatenate(rxncoords) - radii = np.concatenate(radii) - cen_line_Ds = np.concatenate(cen_line_Ds) - - dct = {'rxn_coord': rxncoords, - 'radius': radii, - 'cen_line_D': cen_line_Ds} - return dct - - def bin_radii(self, frames=None, bins=100, range=None): - """Collects the pore radii into bins by reaction coordinate. - - Parameters - ---------- - frames : int or iterable of ints, optional - Profiles to include by frame. If ``None``, includes - all frames. - bins : int or iterable of edges, optional - If bins is an int, it defines the number of equal-width bins in the given - range. If bins is a sequence, it defines a monotonically increasing array of - bin edges, including the rightmost edge, allowing for non-uniform bin widths. - range : (float, float), optional - The lower and upper range of the bins. - If not provided, ``range`` is simply ``(a.min(), a.max())``, - where ``a`` is the array of reaction coordinates. - Values outside the range are ignored. The first element of the range must be - less than or equal to the second. - - - Returns - ------- - list of arrays of floats - List of radii present in each bin - array of (float, float) - Edges of each bin - """ - agg = self.gather(frames=frames, flat=True) - coords = agg['rxn_coord'] - - if not util.iterable(bins): - if range is None: - range = (coords.min(), coords.max()) - xmin, xmax = range - if xmin == xmax: - xmin -= 0.5 - xmax += 0.5 - bins = np.linspace(xmin, xmax, bins+1, endpoint=True) - else: - bins = np.asarray(bins) - bins = bins[np.argsort(bins)] - - idx = np.argsort(coords) - coords = coords[idx] - radii = agg['radius'][idx] - # left: inserts at i where coords[:i] < edge - # right: inserts at i where coords[:i] <= edge - # r_ concatenates - bin_idx = np.r_[coords.searchsorted(bins, side='right')] - binned = [radii[i:j] for i, j in zip(bin_idx[:-1], bin_idx[1:])] - return binned, bins - - def histogram_radii(self, aggregator=np.mean, frames=None, - bins=100, range=None): - """Histograms the pore radii into bins by reaction coordinate, - aggregate the radii with an `aggregator` function, and returns the - aggregated radii and bin edges. - - Parameters - ---------- - aggregator : callable, optional - this function must take an iterable of floats and return a - single value. - frames : int or iterable of ints, optional - Profiles to include by frame. If ``None``, includes - all frames. - bins : int or iterable of edges, optional - If bins is an int, it defines the number of equal-width bins in the given - range. If bins is a sequence, it defines a monotonically increasing array of - bin edges, including the rightmost edge, allowing for non-uniform bin widths. - range : (float, float), optional - The lower and upper range of the bins. - If not provided, ``range`` is simply ``(a.min(), a.max())``, - where ``a`` is the array of reaction coordinates. - Values outside the range are ignored. The first element of the range must be - less than or equal to the second. - - - Returns - ------- - array of floats - histogrammed, aggregate value of radii - array of (float, float) - Edges of each bin - """ - binned, bins = self.bin_radii(frames=frames, bins=bins, range=range) - return np.array(list(map(aggregator, binned))), bins - - def plot_mean_profile(self, bins=100, range=None, - frames=None, color='blue', - linestyle='-', ax=None, - xlabel='Frame', fill_alpha=0.3, - n_std=1, legend=True, - legend_loc='best', - **kwargs): - """Collects the pore radii into bins by reaction coordinate. - - Parameters - ---------- - frames : int or iterable of ints, optional - Profiles to include by frame. If ``None``, includes - all frames. - bins : int or iterable of edges, optional - If bins is an int, it defines the number of equal-width bins in the given - range. If bins is a sequence, it defines a monotonically increasing array of - bin edges, including the rightmost edge, allowing for non-uniform bin widths. - range : (float, float), optional - The lower and upper range of the bins. - If not provided, ``range`` is simply ``(a.min(), a.max())``, - where ``a`` is the array of reaction coordinates. - Values outside the range are ignored. The first element of the range must be - less than or equal to the second. - color : str or array-like, optional - Color for the plot. - linestyle : str or array-like, optional - Line style for the plot. - ax : :class:`matplotlib.axes.Axes` - If no `ax` is supplied or set to ``None`` then the plot will - be added to the current active axes. - xlabel : str, optional - X-axis label. - fill_alpha : float, optional - Opacity of filled standard deviation area - n_std : int, optional - Number of standard deviations from the mean to fill between. - legend : bool, optional - Whether to plot a legend. - legend_loc : str, optional - Location of legend. - **kwargs : - All other `kwargs` are passed to :func:`matplotlib.pyplot.plot`. - - Returns - ------- - ax : :class:`~matplotlib.axes.Axes` - Axes with the plot, either `ax` or the current axes. - - """ - - binned, bins = self.bin_radii(frames=frames, bins=bins, range=range) - mean = np.array(list(map(np.mean, binned))) - midpoints = 0.5 * (bins[1:] + bins[:-1]) - - fig, ax = plt.subplots() - if n_std: - std = np.array(list(map(np.std, binned))) - ax.fill_between(midpoints, mean-(n_std*std), mean+(n_std*std), - color=color, alpha=fill_alpha, - label='{} std'.format(n_std)) - ax.plot(midpoints, mean, color=color, - linestyle=linestyle, label='mean', **kwargs) - ax.set_xlabel(r"Pore coordinate $\zeta$ ($\AA$)") - ax.set_ylabel(r"HOLE radius $R$ ($\AA$)") - if legend: - ax.legend(loc=legend_loc) - return ax - - def plot3D_order_parameters(self, order_parameters, - frames=None, - color=None, - cmap='viridis', - linestyle='-', ax=None, - r_max=None, - ylabel=r'Order parameter', - **kwargs): - r"""Plot HOLE radii over order parameters as a 3D graph. - - Lines are colored according to the specified ``color`` or - drawn from the color map ``cmap``. One line is - plotted for each trajectory frame. - - Parameters - ---------- - order_parameters : array-like or string - Sequence or text file containing order parameters(float - numbers) corresponding to the frames in the trajectory. Must - be same length as trajectory. - frames : array-like, optional - Frames to plot. If ``None``, plots all of them. - color : str or array-like, optional - Color or colors for the plot. If ``None``, colors are - drawn from ``cmap``. - cmap : str, optional - color map to make colors for the plot if ``color`` is - not given. Names should be from the ``matplotlib.pyplot.cm`` - module. - linestyle : str or array-like, optional - Line style for the plot. - ax : : class: `matplotlib.axes.Axes` - If no `ax` is supplied or set to ``None`` then the plot will - be added to the current active axes. - r_max : float, optional - only display radii up to ``r_max``. If ``None``, all radii are - plotted. - ylabel : str, optional - Y-axis label. - **kwargs : - All other `kwargs` are passed to: func: `matplotlib.pyplot.plot`. - - Returns - ------- - ax: : class: `~mpl_toolkits.mplot3d.Axes3D` - Axes with the plot, either `ax` or the current axes. - - """ - op_profiles = self.over_order_parameters(order_parameters, - frames=frames) - - from mpl_toolkits.mplot3d import Axes3D - - if ax is None: - fig = plt.figure() - ax = fig.add_subplot(111, projection='3d') - - ocl = self._process_plot_kwargs(frames=list(op_profiles.keys()), - color=color, cmap=cmap, - linestyle=linestyle) - - for op, c, ls in zip(*ocl): - profile = op_profiles[op] - if r_max is None: - radius = profile.radius - rxn_coord = profile.rxn_coord - else: - # does not seem to work with masked arrays but with nan hack! - # http://stackoverflow.com/questions/4913306/python-matplotlib-mplot3d-how-do-i-set-a-maximum-value-for-the-z-axis - rxn_coord = profile.rxn_coord - radius = profile.radius.copy() - radius[radius > r_max] = np.nan - ax.plot(rxn_coord, op*np.ones_like(rxn_coord), radius, - color=c, linestyle=ls, zorder=int(-op), label=str(op), - **kwargs) - - ax.set_xlabel(r"Pore coordinate $\zeta$ ($\AA$)") - ax.set_ylabel(ylabel) - ax.set_zlabel(r"HOLE radius $R$ ($\AA$)") - plt.tight_layout() - return ax +wmsg = ("Deprecation in version 2.8.0\n" + "MDAnalysis.analysis.hole2 is deprecated in favour of the " + "MDAKit madahole2 (https://www.mdanalysis.org/mdahole2/) " + "and will be removed in MDAnalysis version 3.0.0") +warnings.warn(wmsg, category=DeprecationWarning) diff --git a/package/MDAnalysis/analysis/hole2/templates.py b/package/MDAnalysis/analysis/hole2/templates.py index 96ddb55dfa3..cc04011162b 100644 --- a/package/MDAnalysis/analysis/hole2/templates.py +++ b/package/MDAnalysis/analysis/hole2/templates.py @@ -32,108 +32,21 @@ Templates used in :mod:`MDAnalysis.analysis.hole2.hole` """ - -exe_err = ('HOLE binary {name} not found. {name} must be on the ' - 'PATH, or the path must provided with the keyword ' - 'argument: {kw}') - -IGNORE_RESIDUES = ["SOL", "WAT", "TIP", "HOH", "K ", "NA ", "CL "] - - -#: Built-in HOLE radii (based on ``simple.rad`` from the HOLE_ distribution): -#: van der Waals radii are AMBER united atom from Weiner et al. (1984), JACS, vol 106 pp765-768. -#: *Simple* - Only use one value for each element C O H etc. -#: Added radii for K+, NA+, CL- (Pauling hydration radius from Hille 2002). -#: The data file can be written with the convenience function :func:`write_simplerad2`. -SIMPLE2_RAD = r""" -remark: Time-stamp: <2005-11-21 13:57:55 oliver> [OB] -remark: van der Waals radii: AMBER united atom -remark: from Weiner et al. (1984), JACS, vol 106 pp765-768 -remark: Simple - Only use one value for each element C O H etc. -remark: van der Waals radii -remark: general last -VDWR C??? ??? 1.85 -VDWR O??? ??? 1.65 -VDWR S??? ??? 2.00 -VDWR N??? ??? 1.75 -VDWR H??? ??? 1.00 -VDWR H? ??? 1.00 -VDWR P??? ??? 2.10 -remark: ASN, GLN polar H (odd names for these atoms in xplor) -VDWR E2? GLN 1.00 -VDWR D2? ASN 1.00 -remark: amber lone pairs on sulphurs -VDWR LP?? ??? 0.00 -remark: for some funny reason it wants radius for K even though -remark: it is on the exclude list -remark: Use Pauling hydration radius (Hille 2001) [OB] -VDWR K? ??? 1.33 -VDWR NA? ??? 0.95 -VDWR CL? ??? 1.81 -remark: funny hydrogens in gA structure [OB] -VDWR 1H? ??? 1.00 -VDWR 2H? ??? 1.00 -VDWR 3H? ??? 1.00 -remark: need bond rad for molqpt option -BOND C??? 0.85 -BOND N??? 0.75 -BOND O??? 0.7 -BOND S??? 1.1 -BOND H??? 0.5 -BOND P??? 1.0 -BOND ???? 0.85 -""" - -hole_input = """ -! Input file for Oliver Smart's HOLE program -! written by MDAnalysis.analysis.hole2.hole -! filename = {filename} -COORD {coordinates} -RADIUS {radius} -SPHPDB {sphpdb} -SAMPLE {sample:f} -ENDRAD {end_radius:f} -IGNORE {ignore} -SHORTO {output_level:d} -""" - -hole_lines = { - 'cpoint': 'CPOINT {:.10f} {:.10f} {:.10f}\n', - 'cvect': 'CVECT {:.10f} {:.10f} {:.10f}\n', - 'random_seed': 'RASEED {}\n', - 'dcd': 'CHARMD {dcd}\nCHARMS {iniskip:d} {step:d}\n', -} - -vmd_script_array = """\ -set no_water_color {no_water_color} -set one_water_color {one_water_color} -set double_water_color {double_water_color} -array set triangles {{}} -""" - -vmd_script_function = r""" -global vmd_frame; -trace add variable vmd_frame([molinfo top]) write drawFrame - -proc drawFrame { name element op } { - global vmd_frame triangles no_water_color one_water_color double_water_color; - set frame $vmd_frame([molinfo top]) - draw delete all; - - draw color $no_water_color; - foreach shape [lindex $triangles($frame) 0] { - draw trinorm {*}$shape - } - draw color $one_water_color; - foreach shape [lindex $triangles($frame) 1] { - draw trinorm {*}$shape - } - draw color $double_water_color; - foreach shape [lindex $triangles($frame) 2] { - draw trinorm {*}$shape - } - } - -drawFrame 0 0 0 -""" - +import warnings + +from mdahole2.analysis.templates import ( + exe_err, + IGNORE_RESIDUES, + SIMPLE2_RAD, + hole_input, + hole_lines, + vmd_script_array, + vmd_script_function, + +) + +wmsg = ("Deprecation in version 2.8.0\n" + "MDAnalysis.analysis.hole2 is deprecated in favour of the " + "MDAKit madahole2 (https://www.mdanalysis.org/mdahole2/) " + "and will be removed in MDAnalysis version 3.0.0") +warnings.warn(wmsg, category=DeprecationWarning) diff --git a/package/MDAnalysis/analysis/hole2/utils.py b/package/MDAnalysis/analysis/hole2/utils.py index 66b7b495d29..5149ac14ea4 100644 --- a/package/MDAnalysis/analysis/hole2/utils.py +++ b/package/MDAnalysis/analysis/hole2/utils.py @@ -34,526 +34,19 @@ Helper functions used in :mod:`MDAnalysis.analysis.hole2.hole` """ -import logging -import tempfile -import subprocess -import os -import numpy as np -import errno - -from ...lib import util -from ...exceptions import ApplicationError -from .templates import (SIMPLE2_RAD, IGNORE_RESIDUES, hole_input, - hole_lines, exe_err) - -logger = logging.getLogger(__name__) - - -def write_simplerad2(filename="simple2.rad"): - """Write the built-in radii in :data:`SIMPLE2_RAD` to `filename`. - - Does nothing if `filename` already exists. - - Parameters - ---------- - filename : str, optional - output file name; the default is "simple2.rad" - - Returns - ------- - filename : str - returns the name of the data file - """ - - if not os.path.exists(filename): - with open(filename, "w") as rad: - rad.write(SIMPLE2_RAD + "\n") - logger.debug("Created simple radii file {}".format(filename)) - return filename - - -def check_and_fix_long_filename(filename, tmpdir=os.path.curdir, - max_length=70, - make_symlink=True): - """Return a file name suitable for HOLE. - - HOLE is limited to filenames <= ``max_length``. This method - - 1. returns `filename` if HOLE can process it - 2. returns a relative path (see :func:`os.path.relpath`) if that shortens the - path sufficiently - 3. creates a symlink to `filename` (:func:`os.symlink`) in a safe temporary - directory and returns the path of the symlink. - - Parameters - ---------- - filename : str - file name to be processed - tmpdir : str, optional - By default the temporary directory is created inside the current - directory in order to keep that path name short. This can be changed - with the `tmpdir` keyword (e.g. one can use "/tmp"). The default is - the current directory :data:`os.path.curdir`. - - Returns - ------- - str - path to the file that has a length less than - ``max_length`` - - Raises - ------ - RuntimeError - If none of the tricks for filename shortening worked. In this case, - manually rename the file or recompile your version of HOLE. - """ - - if len(filename) <= max_length: - return filename - - msg = ('HOLE will not read {} ' - 'because it has more than {} characters.') - logger.debug(msg.format(filename, max_length)) - - # try a relative path - new_name = os.path.relpath(filename) - if len(new_name) <= max_length: - msg = 'Using relative path: {} -> {}' - logger.debug(msg.format(filename, new_name)) - return new_name - - if make_symlink: - # shorten path by creating a symlink inside a safe temp dir - _, ext = os.path.splitext(filename) - dirname = os.path.relpath(tempfile.mkdtemp(dir=tmpdir)) - newname = os.path.join(dirname, os.path.basename(filename)) - if len(newname) > max_length: - fd, newname = tempfile.mkstemp(suffix=ext, dir=dirname) - os.close(fd) - os.unlink(newname) - - if len(newname) > max_length: - newname = os.path.relpath(newname) - - if len(newname) <= max_length: - os.symlink(filename, newname) - msg = 'Using symlink: {} -> {}' - logger.debug(msg.format(filename, newname)) - return newname - - msg = 'Failed to shorten filename {}' - raise RuntimeError(msg.format(filename)) - - -def set_up_hole_input(pdbfile, - infile_text=None, - infile=None, - sphpdb_file='hole.sph', - vdwradii_file=None, - tmpdir=os.path.curdir, - sample=0.2, - end_radius=22, - cpoint=None, - cvect=None, - random_seed=None, - ignore_residues=IGNORE_RESIDUES, - output_level=0, - dcd=None, - dcd_iniskip=0, - dcd_step=1): - """ - Generate HOLE input for the parameters. - - Parameters - ---------- - pdbfile : str - The `filename` is used as input for HOLE in the "COORD" card of the - input file. It specifies the name of a PDB coordinate file to be - used. This must be in Brookhaven protein databank format or - something closely approximating this. Both ATOM and HETATM records - are read. - - infile_text: str, optional - HOLE input text or template. If set to ``None``, the function will - create the input text from the other parameters. - Default: ``None`` - - infile: str, optional - File to write the HOLE input text for later inspection. If set to - ``None``, the input text is not written out. - Default: ``None`` - - sphpdb_file : str, optional - path to the HOLE sph file, a PDB-like file containing the - coordinates of the pore centers. - The coordinates are set to the sphere centres and the occupancies - are the sphere radii. All centres are assigned the atom name QSS and - residue name SPH and the residue number is set to the storage - number of the centre. In VMD, sph - objects are best displayed as "Points". Displaying .sph objects - rather than rendered or dot surfaces can be useful to analyze the - distance of particular atoms from the sphere-centre line. - .sph files can be used to produce molecular graphical - output from a hole run, by using the - :program:`sph_process` program to read the .sph file. - Default: 'hole.sph' - - vdwradii_file: str, optional - path to the file specifying van der Waals radii for each atom. If - set to ``None``, then a set of default radii, - :data:`SIMPLE2_RAD`, is used (an extension of ``simple.rad`` from - the HOLE distribution). Default: ``None`` - - tmpdir: str, optional - The temporary directory that files can be symlinked to, to shorten - the path name. HOLE can only read filenames up to a certain length. - Default: current working directory - - sample : float, optional - distance of sample points in Å. - Specifies the distance between the planes used in the HOLE - procedure. The default value should be reasonable for most - purposes. However, if you wish to visualize a very tight - constriction then specify a smaller value. - This value determines how many points in the pore profile are - calculated. Default: 0.2 - - end_radius : float, optional - Radius in Å, which is considered to be the end of the pore. This - keyword can be used to specify the radius above which the - program regards a result as indicating that the end of the pore - has been reached. This may need to be increased for large channels, - or reduced for small channels. Default: 22.0 - - cpoint : array_like, 'center_of_geometry' or None, optional - coordinates of a point inside the pore, e.g. ``[12.3, 0.7, - 18.55]``. If set to ``None`` (the default) then HOLE's own search - algorithm is used. - ``cpoint`` specifies a point which lies within the channel. For - simple channels (e.g. gramicidin), results do not show great - sensitivity to the exact point taken. An easy way to produce an - initial point is to use molecular graphics to find two atoms which - lie either side of the pore and to average their coordinates. Or - if the channel structure contains water molecules or counter ions - then take the coordinates of one of these (and use the - `ignore_residues` keyword to ignore them in the pore radius - calculation). - If this card is not specified, then HOLE (from version 2.2) - attempts to guess where the channel will be. The procedure - assumes the channel is reasonably symmetric. The initial guess on - cpoint will be the centroid of all alpha carbon atoms (name 'CA' - in pdb file). This is then refined by a crude grid search up to 5 - Å from the original position. This procedure works most of the - time but is far from infallible — results should be - carefully checked (with molecular graphics) if it is used. - Default: None - - cvect : array_like, optional - Search direction, should be parallel to the pore axis, - e.g. ``[0,0,1]`` for the z-axis. - If this keyword is ``None`` (the default), then HOLE attempts to guess - where the channel will be. The procedure assumes that the channel is - reasonably symmetric. The guess will be either along the X axis - (1,0,0), Y axis (0,1,0) or Z axis (0,0,1). If the structure is not - aligned on one of these axis the results will clearly be - approximate. If a guess is used then results should be carefully - checked. Default: None - - random_seed : int, optional - integer number to start the random number generator. - By default, - :program:`hole` will use the time of the day. - For reproducible runs (e.g., for testing) set ``random_seed`` - to an integer. Default: ``None`` - - ignore_residues : array_like, optional - sequence of three-letter residues that are not taken into - account during the calculation; wildcards are *not* - supported. Note that all residues must have 3 letters. Pad - with space on the right-hand side if necessary. - Default: {}. - - output_level : int, optional - Determines the output of output in the ``outfile``. - For automated processing, this must be < 3. - 0: Full text output, - 1: All text output given except "run in progress" (i.e., - detailed contemporary description of what HOLE is doing). - 2: Ditto plus no graph type output - only leaving minimum - radius and conductance calculations. - 3: All text output other than input card mirroring and error messages - turned off. - Default: 0 - - dcd : str, optional - File name of DCD trajectory (must be supplied together with a - matching PDB file `filename`) and then HOLE runs its analysis on - each frame. - It does multiple HOLE runs on positions taken from a CHARMM binary - dynamics format DCD trajectory file. The ``dcd`` file must have - exactly the same number of atoms in exactly the same order as the - pdb file specified by ``pdbfile``. Note that if this option is used - the pdb file is used as a template only - the coordinates are - ignored. Note that structural parameters determined for each - individual structure are written in a tagged format so that it is - possible to extract the information from the text output file using - a :program:`grep` command. The reading of the file can be - controlled by the ``dcd_step`` keyword and/or setting - ``dcd_iniskip`` to the number of frames to be skipped - initially. - - dcd_step : int, optional - step size for going through the trajectory (skips ``dcd_step-1`` - frames). Default: 1 - - Returns - ------- - str - input text to run HOLE - - - .. versionadded:: 1.0 - - """.format(IGNORE_RESIDUES) - - short_filename = check_and_fix_long_filename(pdbfile, tmpdir=tmpdir) - if vdwradii_file is not None: - vdwradii_file = check_and_fix_long_filename(vdwradii_file, - tmpdir=tmpdir) - else: - vdwradii_file = write_simplerad2() - - if dcd is not None: - dcd = check_and_fix_long_filename(dcd, tmpdir=tmpdir) - - if infile_text is None: - infile_text = hole_input - - residues = ' '.join(ignore_residues) - - infile_text = infile_text.format(filename=pdbfile, - coordinates=short_filename, - radius=vdwradii_file, - sphpdb=sphpdb_file, - sample=sample, - end_radius=end_radius, - ignore=residues, - output_level=output_level) - - if random_seed is not None: - random_seed = int(random_seed) - infile_text += hole_lines['random_seed'].format(random_seed) - logger.info("Fixed random number seed {} for reproducible " - "runs.".format(random_seed)) - - if cpoint is not None: - if isinstance(cpoint, str): - infile_text += 'CPOINT ' + cpoint + '\n' - else: - infile_text += hole_lines['cpoint'].format(*cpoint) - else: - logger.info("HOLE will guess CPOINT") - - if cvect is not None: - infile_text += hole_lines['cvect'].format(*cvect) - else: - logger.info("HOLE will guess CVECT") - - if dcd is not None: - infile_text += hole_lines['dcd'].format(dcd=dcd, - iniskip=dcd_iniskip, - step=dcd_step) - - if infile is not None: - with open(infile, 'w') as f: - f.write(infile_text) - msg = 'Wrote HOLE input file {} for inspection' - logger.debug(msg.format(infile)) - - return infile_text - - -def run_hole(outfile, infile_text, executable): - """Run the HOLE program. - - Parameters - ---------- - outfile: str - Output file name - infile_text: str - HOLE input text - (typically generated by :func:`set_up_hole_input`) - executable: str - HOLE executable - - - Returns - ------- - str - Output file name - """ - with open(outfile, 'w') as output: - proc = subprocess.Popen(executable, stdin=subprocess.PIPE, - stdout=output) - stdout, stderr = proc.communicate(infile_text.encode('utf-8')) - - # check output in case of errors - with open(outfile, 'r') as output: - for line in output: - if line.strip().startswith(('*** ERROR ***', 'ERROR')): - proc.returncode = 255 - break - - # die in case of error - if proc.returncode != 0: - msg = 'HOLE failure ({}). Check output {}' - logger.fatal(msg.format(proc.returncode, outfile)) - if stderr is not None: - logger.fatal(stderr) - raise ApplicationError(proc.returncode, - msg.format(executable, outfile)) - - logger.info('HOLE finished. Output: {}'.format(outfile)) - return outfile - - -def collect_hole(outfile='hole.out'): - """Collect data from HOLE output - - Parameters - ---------- - outfile: str, optional - HOLE output file to read. Default: 'hole.out' - - - Returns - ------- - dict - Dictionary of HOLE profiles as record arrays - """ - fmt = util.FORTRANReader('3F12') - recarrays = {} - - with open(outfile, 'r') as output: - toggle_read = False - profile = 0 - records = [] - for line in output: - line = line.rstrip() # preserve columns in FORTRAN output - # multiple frames from dcd in? - if line.startswith(" Starting calculation for position number"): - fields = line.split() - profile = int(fields[5]) - records = [] - logger.debug('Started reading profile {}'.format(profile)) - continue - - # found data - if line.startswith(' cenxyz.cvec'): - toggle_read = True - logger.debug('Started reading data') - continue - - if toggle_read: - if len(line.strip()) != 0: - try: - rxncoord, radius, cenlineD = fmt.read(line) - except: - msg = 'Problem parsing line: {}. Check output file {}' - logger.exception(msg.format(line, outfile)) - raise - records.append((rxncoord, radius, cenlineD)) - continue - # end of data - else: - toggle_read = False - names = ['rxn_coord', 'radius', 'cen_line_D'] - recarr = np.rec.fromrecords(records, - formats='f8, f8, f8', - names=names) - recarrays[profile] = recarr - - return recarrays - - -def create_vmd_surface(sphpdb='hole.sph', - filename=None, - sph_process='sph_process', - sos_triangle='sos_triangle', - dot_density=15): - """Create VMD surface file from sphpdb file. - - Parameters - ---------- - sphpdb: str, optional - sphpdb file to read. Default: 'hole.sph' - filename: str, optional - output VMD surface file. If ``None``, a temporary file - is generated. Default: ``None`` - sph_process: str, optional - Executable for ``sph_process`` program. Default: 'sph_process' - sos_triangle: str, optional - Executable for ``sos_triangle`` program. Default: 'sos_triangle' - dot_density: int, optional - density of facets for generating a 3D pore representation. - The number controls the density of dots that will be used. - A sphere of dots is placed on each centre determined in the - Monte Carlo procedure. The actual number of dots written is - controlled by ``dot_density`` and the ``sample`` level of the - original analysis. ``dot_density`` should be set between 5 - (few dots per sphere) and 35 (many dots per sphere). - Default: 15 - - - Returns - ------- - str - the output filename for the VMD surface - - """ - fd, tmp_sos = tempfile.mkstemp(suffix=".sos", text=True) - os.close(fd) - - sph_process_path = util.which(sph_process) - if sph_process_path is None: - raise OSError(errno.ENOENT, exe_err.format(name=sph_process, - kw='sph_process')) - base_path = os.path.dirname(sph_process_path) - - sos_triangle_path = util.which(sos_triangle) - if sos_triangle_path is None: - path = os.path.join(base_path, sos_triangle) - sos_triangle_path = util.which(path) - if sos_triangle_path is None: - raise OSError(errno.ENOENT, exe_err.format(name=sos_triangle, - kw='sos_triangle')) - try: - output = subprocess.check_output([sph_process_path, "-sos", "-dotden", - str(dot_density), "-color", sphpdb, - tmp_sos], stderr=subprocess.STDOUT) - except subprocess.CalledProcessError as err: - os.unlink(tmp_sos) - logger.fatal("sph_process failed ({0})".format(err.returncode)) - raise OSError(err.returncode, "sph_process failed") from None - except: - os.unlink(tmp_sos) - raise - - if filename is None: - fd, filename = tempfile.mkstemp(suffix=".sos", text=True) - os.close(fd) - try: - # Could check: os.devnull if subprocess.DEVNULL not available (>3.3) - # Suppress stderr messages of sos_triangle - with open(tmp_sos) as sos, open(filename, "w") as triangles, \ - open(os.devnull, 'w') as FNULL: - subprocess.check_call( - [sos_triangle_path, "-s"], stdin=sos, stdout=triangles, - stderr=FNULL) - except subprocess.CalledProcessError as err: - logger.fatal("sos_triangle failed ({0})".format(err.returncode)) - raise OSError(err.returncode, "sos_triangle failed") from None - finally: - os.unlink(tmp_sos) - - return filename +import warnings + +from mdahole2.analysis.utils import ( + write_simplerad2, + check_and_fix_long_filename, + set_up_hole_input, + run_hole, + collect_hole, + create_vmd_surface +) + +wmsg = ("Deprecation in version 2.8.0\n" + "MDAnalysis.analysis.hole2 is deprecated in favour of the " + "MDAKit madahole2 (https://www.mdanalysis.org/mdahole2/) " + "and will be removed in MDAnalysis version 3.0.0") +warnings.warn(wmsg, category=DeprecationWarning) diff --git a/package/doc/sphinx/source/conf.py b/package/doc/sphinx/source/conf.py index 7c644b122ff..dfc6c606a13 100644 --- a/package/doc/sphinx/source/conf.py +++ b/package/doc/sphinx/source/conf.py @@ -350,4 +350,5 @@ class KeyStyle(UnsrtStyle): 'rdkit': ('https://rdkit.org/docs/', None), 'waterdynamics': ('https://www.mdanalysis.org/waterdynamics/', None), 'pathsimanalysis': ('https://www.mdanalysis.org/PathSimAnalysis/', None), + 'mdahole2': ('https://www.mdanalysis.org/mdahole2/', None), } diff --git a/package/pyproject.toml b/package/pyproject.toml index 5b81bc4e007..e6fcdb40213 100644 --- a/package/pyproject.toml +++ b/package/pyproject.toml @@ -42,6 +42,7 @@ dependencies = [ 'mda-xdrlib', 'waterdynamics', 'pathsimanalysis', + 'mdahole2', ] keywords = [ "python", "science", "chemistry", "biophysics", "molecular-dynamics",