Example #1
0
    def runManyStepsAlternating(self, n_steps, **kwargs):
        LOGGER.timeit('_prody_runManySteps')
        n_start = self.numSteps
        while self.numSteps < n_start + n_steps:
            n_modes = self.n_modes

            self.runStep(structA=self.structA,
                         structB=self.structB,
                         reduceSelA=self.reduceSelA,
                         reduceSelB=self.reduceSelB,
                         alignSelA=self.alignSelA,
                         alignSelB=self.alignSelB,
                         n_modes=n_modes,
                         **kwargs)
            LOGGER.debug(
                'Total time so far is %.2f minutes' %
                ((time.time() - LOGGER._times['_prody_runManySteps']) / 60))

            self.runStep(structA=self.structB,
                         structB=self.structA,
                         reduceSelA=self.reduceSelB,
                         reduceSelB=self.reduceSelA,
                         alignSelA=self.alignSelB,
                         alignSelB=self.alignSelA,
                         n_modes=n_modes,
                         **kwargs)
            LOGGER.debug(
                'Total time so far is %.2f minutes' %
                ((time.time() - LOGGER._times['_prody_runManySteps']) / 60))

            converged = self.checkConvergence()
            if converged:
                self.structA.setCoords(
                    self.coordsA
                )  # That way the original object is back to normal
                self.structB.setCoords(
                    self.coordsB
                )  # That way the original object is back to normal
                LOGGER.debug(
                    'Process completed in %.2f hours' %
                    ((time.time() - LOGGER._times['_prody_runManySteps']) /
                     3600))
                break

        ensemble = Ensemble('combined trajectory')
        ensemble.setAtoms(self.structA)
        for coordset in self.ensembleA.getCoordsets():
            ensemble.addCoordset(coordset)
        for coordset in reversed(self.ensembleB.getCoordsets()):
            ensemble.addCoordset(coordset)

        if self.outputPDB:
            writePDB(self.filename, ensemble)

        if self.outputDCD:
            writeDCD(self.filename, ensemble)

        return
Example #2
0
def calcOneWayAdaptiveANM(a, b, n_steps, **kwargs):
    """Runs one-way adaptivate ANM. """

    n_modes = kwargs.pop('n_modes', 20)

    coordsA, coordsB, title, atoms, weights, maskA, maskB, rmsd = checkInput(
        a, b, **kwargs)
    coordsA = coordsA.copy()

    LOGGER.timeit('_prody_calcAdaptiveANM')
    n = 0
    resetFmin = True
    defvecs = []
    rmsds = [rmsd]
    ensemble = Ensemble(title + '_aANM')
    ensemble.setAtoms(atoms)
    ensemble.setCoords(coordsB)
    ensemble.setWeights(weights)
    ensemble.addCoordset(coordsA.copy())
    while n < n_steps:
        LOGGER.info('\nStarting cycle {0} with initial structure {1}'.format(
            n + 1, title))
        n_modes = calcStep(coordsA,
                           coordsB,
                           n_modes,
                           ensemble,
                           defvecs,
                           rmsds,
                           mask=maskA,
                           resetFmin=resetFmin,
                           **kwargs)
        n += 1
        resetFmin = False
        if n_modes == 0:
            LOGGER.report('One-way Adaptive ANM converged in %.2fs.',
                          '_prody_calcAdaptiveANM')
            break

    return ensemble
Example #3
0
    def generate(self, n_confs=100, rrmsd=None, k1=9, k2=3, th_lrmsd=6):
        '''
		This program is for generating new conformations.
		'''
        self.n_confs = n_confs
        self.rrmsd = rrmsd
        self.k1 = k1
        self.k2 = k2
        self.th_lrmsd = th_lrmsd
        self.eigval = self.allresidue.getEigvals()[6:]
        self.eigvec = self.allresidue.getArray().T[6:]
        self.nresil = len(self.eigvec[0]) / 3 - self.nresir

        n_confs = int(n_confs)
        n_atoms = self.allatom.numAtoms()
        initial = self.atoms.getCoords()

        print "number of total atoms for complex: ", n_atoms

        ind = self.Bset()
        array = self.allatom._getArray().T[6:]
        confs = []
        '''
		#check
		x, Rrmsd, Lrmsd = self.rejectsampling(ind)

		coords =  np.zeros(3*(self.nresil+self.nresir))

		for j in range(2*k):
			coords +=  x[j]/np.sqrt(self.eigval[ind[j]]) * self.eigvec[ind[j]]

		print ("#%d Rrmsd=%.3f Lrmsd=%.10f" %(1+1, Rrmsd, Lrmsd))
		print np.sqrt( np.sum(coords[0:3*self.nresir]**2)/self.nresir ), np.sqrt( np.sum(coords[3*self.nresir:]**2)/self.nresil )


		print array[0][0:50], np.linalg.norm(array[0]), self.eigvec[0][0:10]
		#checkend
'''

        for i in range(n_confs):

            x, Rrmsd, Lrmsd = self.rejectsampling(ind)
            coords = np.zeros(3 * n_atoms)

            for j in range(k1 + k2):

                coords += x[j] / np.sqrt(self.eigval[ind[j]]) * array[ind[j]]

            confs.append(coords.reshape((n_atoms, 3)))

            print("#%d Rrmsd=%.3f Lrmsd=%.3f" % (i + 1, Rrmsd, Lrmsd))

        ensemble = Ensemble('Conformations along {0}'.format(self.allatom))

        ensemble.setCoords(initial)

        ensemble.addCoordset(np.array(confs) + initial)

        return ensemble
Example #4
0
    def __getitem__(self, index):

        if self._closed:
            raise ValueError('I/O operation on closed file')

        if isinstance(index, int):
            return self.getFrame(index)

        elif isinstance(index, (slice, list, np.ndarray)):
            if isinstance(index, slice):
                ens = Ensemble('{0:s} ({1[0]:d}:{1[1]:d}:{1[2]:d})'.format(
                                    self._title, index.indices(len(self))))
            else:
                ens = Ensemble('{0:s} slice'.format(self._title))
            ens.setCoords(self.getCoords())
            if self._weights is not None:
                ens.setWeights(self._weights.copy())
            ens.addCoordset(self.getCoordsets(index))
            ens.setAtoms(self._atoms)
            return ens

        else:
            raise IndexError('invalid index')
Example #5
0
def sampleModes(modes, atoms=None, n_confs=1000, rmsd=1.0):
    """Return an ensemble of randomly sampled conformations along given 
    *modes*.  If *atoms* are provided, sampling will be around its active 
    coordinate set.  Otherwise, sampling is around the 0 coordinate set.
    
    :arg modes: Modes along which sampling will be performed.
    :type modes: :class:`~.Mode`, :class:`~.ModeSet`, :class:`~.PCA`, 
                 :class:`~.ANM` or :class:`~.NMA`   
    
    :arg atoms: Atoms whose active coordinate set will be used as the initial 
        conformation.
    :type atoms: :class:`~.Atomic`  
    
    :arg n_confs: Number of conformations to generate. Default is 1000.
    :type n_steps: int 
    
    :arg rmsd: The average RMSD that the conformations will have with 
        respect to the initial conformation. Default is 1.0 A.
    :type rmsd: float 
    
    For given normal modes :math:`[u_1 u_2 ... u_m]` and their eigenvalues
    :math:`[\lambda_1 \lambda_2 ... \lambda_m]`, a new conformation 
    is sampled using the relation:
        
    .. math::
    
       R_k = R_0 + s \sum_{i=1}^{m} r_i^k \lambda^{-0.5}_i u_i 
    
    :math:`R_0` is the active coordinate set of *atoms*.
    :math:`[r_1^k r_2^k ... r_m^k]` are normally distributed random numbers 
    generated for conformation :math:`k` using :func:`numpy.random.randn`.
    
    RMSD of the new conformation from :math:`R_0` can be calculated as
     
    .. math::
        
      RMSD^k = \sqrt{ {\\left( s \sum_{i=1}^{m} r_i^k \lambda^{-0.5}_i u_i  \\right)}^{2} / N } = \\frac{s}{ \sqrt{N}} \sqrt{ \sum_{i=1}^{m} (r_i^k)^2 \lambda^{-1}_i  } 


    Average :math:`RMSD` of the generated conformations from the initial conformation is: 
        
    .. math::
        
      \\left< RMSD^k \\right> = \\frac{s}{ \sqrt{N}} \\left< \sqrt{ \sum_{i=1}^{m} (r_i^k)^2 \lambda^{-1}_i } \\right>

 
    From this relation :math:`s` scaling factor obtained using the relation 
    
    .. math::
       
       s =  \\left< RMSD^k \\right> \sqrt{N} {\\left< \sqrt{ \sum_{i=1}^{m} (r_i)^2 \lambda^{-1}_i} \\right>}^{-1}
       
     
    Note that random numbers are generated before conformations are 
    sampled, hence exact value of :math:`s` is known from this relation to
    ensure that the generated ensemble will have user given average *rmsd* 
    value. 
     
    Note that if modes are from a :class:`~.PCA`, variances are used instead of 
    inverse eigenvalues, i.e. :math:`\sigma_i \sim \lambda^{-1}_i`.
    
    |more| See also :func:`~.showEllipsoid`.
    
    .. plot::
       :context:
       :include-source:
        
       # Generate 300 conformations using ANM modes 1-3
       ensemble = sampleModes( p38_anm[:3], n_confs=500 )
       # Project these conformations onto the space spanned by these modes
       plt.figure(figsize=(5,4))
       showProjection(ensemble, p38_anm[:3], rmsd=True)
       
    .. plot::
       :context:
       :nofigs:
        
       plt.close('all')"""
       
    if not isinstance(modes, (Mode, NMA, ModeSet)):
        raise TypeError('modes must be a NMA or ModeSet instance, '
                        'not {0:s}'.format(type(modes)))
    if not modes.is3d():
        raise ValueError('modes must be from a 3-dimensional model')
    n_confs = int(n_confs)
    n_atoms = modes.numAtoms()
    initial = None
    if atoms is not None:
        if not isinstance(atoms, (Atomic)):
            raise TypeError('{0:s} is not correct type for atoms'
                            .format(type(atoms)))
        if atoms.numAtoms() != n_atoms:
            raise ValueError('number of atoms do not match')
        initial = atoms.getCoords()

    rmsd = float(rmsd)
    LOGGER.info('Parameter: rmsd = {0:.2f} A'.format(rmsd))
    n_confs = int(n_confs)
    LOGGER.info('Parameter: n_confs = {0:d}'.format(n_confs))
    
    
    if isinstance(modes, Mode):
        n_modes = 1
        variances = np.array([modes.getVariance()])
    else:
        n_modes = len(modes)
        variances = modes.getVariances()
    if np.any(variances == 0):
        raise ValueError('one or more modes has zero variance')
    randn = np.random.standard_normal((n_confs, n_modes))
    coef = ((randn ** 2 * variances).sum(1) ** 0.5).mean()
    scale = n_atoms**0.5 * rmsd / coef
    
    LOGGER.info('Modes are scaled by {0:g}.'.format(scale))
    
    confs = []
    append = confs.append
    scale = scale * variances ** 0.5
    array = modes._getArray()
    if array.ndim > 1:
        for i in range(n_confs):
            append( (array * scale * randn[i]).sum(1).reshape((n_atoms, 3)) )
    else:
        for i in range(n_confs):
            append( (array * scale * randn[i]).reshape((n_atoms, 3)) )

    ensemble = Ensemble('Conformations along {0:s}'.format(modes))
    if initial is None:
        ensemble.setCoords(np.zeros((n_atoms, 3)))
        ensemble.addCoordset(np.array(confs))
    else:    
        ensemble.setCoords(initial)
        ensemble.addCoordset(np.array(confs) + initial)
    return ensemble  
Example #6
0
def traverseMode(mode, atoms, n_steps=10, rmsd=1.5):
    """Generates a trajectory along a given *mode*, which can be used to
    animate fluctuations in an external program. 
    
    :arg mode: Mode along which a trajectory will be generated.
    :type mode: :class:`~.Mode`   
    
    :arg atoms: Atoms whose active coordinate set will be used as the initial 
        conformation.
    :type atoms: :class:`~.Atomic` 
    
    :arg n_steps: Number of steps to take along each direction. 
        For example, for ``n_steps=10``, 20 conformations will be 
        generated along the first mode. Default is 10.
    :type n_steps: int 
    
    :arg rmsd: The maximum RMSD that the conformations will have with 
        respect to the initial conformation. Default is 1.5 A.
    :type rmsd: float

    :returns: :class:`~.Ensemble`
    
    For given normal mode :math:`u_i`, its eigenvalue
    :math:`\lambda_i`, number of steps :math:`n`, and maximum :math:`RMSD`  
    conformations :math:`[R_{-n} R_{-n+1} ... R_{-1} R_0 R_1 ... R_n]` are 
    generated.
    
    :math:`R_0` is the active coordinate set of *atoms*. 
    :math:`R_k = R_0 + sk\lambda_iu_i`, where :math:`s` is found using
    :math:`s = ((N (\\frac{RMSD}{n})^2) / \lambda_i^{-1}) ^{0.5}`, where
    :math:`N` is the number of atoms.
    
    
    .. plot::
       :context:
       :include-source:
        
       trajectory = traverseMode( p38_anm[0], p38_structure.select('calpha'), 
                                  n_steps=8, rmsd=1.4 )
       rmsd = calcRMSD(trajectory)
       plt.figure(figsize=(5,4))
       plt.plot(rmsd, '-o')
       plt.xlabel('Frame index')
       plt.ylabel('RMSD (A)')
       
    .. plot::
       :context:
       :nofigs:
        
       plt.close('all')"""
       
    if not isinstance(mode, VectorBase):
        raise TypeError('mode must be a Mode or Vector instance, '
                        'not {0:s}'.format(type(mode)))
    if not mode.is3d():
        raise ValueError('mode must be from a 3-dimensional model.')
    n_atoms = mode.numAtoms()
    initial = None
    if atoms is not None:
        if not isinstance(atoms, Atomic):
            raise TypeError('{0:s} is not correct type for atoms'
                            .format(type(atoms)))
        if atoms.numAtoms() != n_atoms:
            raise ValueError('number of atoms do not match')
        initial = atoms.getCoords()

    name = str(mode)
    
    rmsd = float(rmsd) + 0.000004
    LOGGER.info('Parameter: rmsd = {0:.2f} A'.format(rmsd))
    n_steps = int(n_steps)
    LOGGER.info('Parameter: n_steps = {0:d}'.format(n_steps))
    step = rmsd / n_steps
    LOGGER.info('Step size is {0:.2f} A RMSD'.format(step))
    arr = mode.getArrayNx3()
    var = mode.getVariance()
    scale = ((n_atoms * step**2) / var) **0.5
    LOGGER.info('Mode is scaled by {0:g}.'.format(scale))

    array = arr * var**0.5 * scale 
    confs_add = [initial + array]
    for s in range(1, n_steps):
        confs_add.append( confs_add[-1] + array)
    confs_sub = [initial - array]
    for s in range(1, n_steps):
        confs_sub.append( confs_sub[-1] - array)
    confs_sub.reverse()
    ensemble = Ensemble('Conformations along {0:s}'.format(name))
    ensemble.setCoords(initial)    
    ensemble.addCoordset(np.array(confs_sub + [initial] + confs_add))
    return ensemble
Example #7
0
def sampleModes(modes, atoms=None, n_confs=1000, rmsd=1.0):
    """Returns an ensemble of randomly sampled conformations along given
    *modes*.  If *atoms* are provided, sampling will be around its active
    coordinate set.  Otherwise, sampling is around the 0 coordinate set.

    :arg modes: modes along which sampling will be performed
    :type modes: :class:`.Mode`, :class:`.ModeSet`, :class:`.PCA`,
                 :class:`.ANM` or :class:`.NMA`

    :arg atoms: atoms whose active coordinate set will be used as the initial
        conformation
    :type atoms: :class:`.Atomic`

    :arg n_confs: number of conformations to generate, default is 1000
    :type n_steps: int

    :arg rmsd: average RMSD that the conformations will have with
        respect to the initial conformation, default is 1.0 Å
    :type rmsd: float

    :returns: :class:`.Ensemble`

    For given normal modes :math:`[u_1 u_2 ... u_m]` and their eigenvalues
    :math:`[\\lambda_1 \\lambda_2 ... \\lambda_m]`, a new conformation
    is sampled using the relation:

    .. math::

       R_k = R_0 + s \\sum_{i=1}^{m} r_i^k \\lambda^{-0.5}_i u_i

    :math:`R_0` is the active coordinate set of *atoms*.
    :math:`[r_1^k r_2^k ... r_m^k]` are normally distributed random numbers
    generated for conformation :math:`k` using :func:`numpy.random.randn`.

    RMSD of the new conformation from :math:`R_0` can be calculated as

    .. math::

      RMSD^k = \\sqrt{ {\\left( s \\sum_{i=1}^{m} r_i^k \\lambda^{-0.5}_i u_i  \\right)}^{2} / N } = \\frac{s}{ \\sqrt{N}} \\sqrt{ \\sum_{i=1}^{m} (r_i^k)^2 \\lambda^{-1}_i  }


    Average :math:`RMSD` of the generated conformations from the initial conformation is:

    .. math::

      \\left< RMSD^k \\right> = \\frac{s}{ \\sqrt{N}} \\left< \\sqrt{ \\sum_{i=1}^{m} (r_i^k)^2 \\lambda^{-1}_i } \\right>


    From this relation :math:`s` scaling factor obtained using the relation

    .. math::

       s =  \\left< RMSD^k \\right> \\sqrt{N} {\\left< \\sqrt{ \\sum_{i=1}^{m} (r_i)^2 \\lambda^{-1}_i} \\right>}^{-1}


    Note that random numbers are generated before conformations are
    sampled, hence exact value of :math:`s` is known from this relation to
    ensure that the generated ensemble will have user given average *rmsd*
    value.

    Note that if modes are from a :class:`.PCA`, variances are used instead of
    inverse eigenvalues, i.e. :math:`\\sigma_i \\sim \\lambda^{-1}_i`.

    See also :func:`.showEllipsoid`."""

    if not isinstance(modes, (Mode, NMA, ModeSet)):
        raise TypeError('modes must be a NMA or ModeSet instance, '
                        'not {0}'.format(type(modes)))
    if not modes.is3d():
        raise ValueError('modes must be from a 3-dimensional model')
    n_confs = int(n_confs)
    n_atoms = modes.numAtoms()
    initial = None
    if atoms is not None:
        if isinstance(atoms, Atomic):
            if atoms.numAtoms() != n_atoms:
                raise ValueError('number of atoms do not match')
            initial = atoms.getCoords()
        elif isinstance(atoms, np.ndarray):
            initial = atoms
        else:
            raise TypeError('{0} is not correct type for atoms'
                            .format(type(atoms)))

    rmsd = float(rmsd)
    LOGGER.info('Parameter: rmsd = {0:.2f} A'.format(rmsd))
    n_confs = int(n_confs)
    LOGGER.info('Parameter: n_confs = {0}'.format(n_confs))

    if isinstance(modes, Mode):
        n_modes = 1
        variances = np.array([modes.getVariance()])
        magnitudes = np.array([abs(modes)])
    else:
        n_modes = len(modes)
        variances = modes.getVariances()
        magnitudes = np.array([abs(mode) for mode in modes])

    if np.any(variances == 0):
        raise ValueError('one or more modes has zero variance')
    randn = np.random.standard_normal((n_confs, n_modes))
    coef = ((randn ** 2 * variances).sum(1) ** 0.5).mean()
    scale = n_atoms**0.5 * rmsd / coef

    LOGGER.info('Modes are scaled by {0}.'.format(scale))

    confs = []
    append = confs.append
    scale = scale / magnitudes * variances ** 0.5

    array = modes._getArray()
    if array.ndim > 1:
        for i in range(n_confs):
            append((array * scale * randn[i]).sum(1).reshape((n_atoms, 3)))
    else:
        for i in range(n_confs):
            append((array * scale * randn[i]).reshape((n_atoms, 3)))

    ensemble = Ensemble('Conformations along {0}'.format(modes))
    if initial is None:
        ensemble.setCoords(np.zeros((n_atoms, 3)))
        ensemble.addCoordset(np.array(confs))
    else:
        ensemble.setCoords(initial)
        ensemble.addCoordset(np.array(confs) + initial)
    return ensemble
Example #8
0
def traverseMode(mode, atoms, n_steps=10, rmsd=1.5, **kwargs):
    """Generates a trajectory along a given *mode*, which can be used to
    animate fluctuations in an external program.

    :arg mode: mode along which a trajectory will be generated
    :type mode: :class:`.Mode`

    :arg atoms: atoms whose active coordinate set will be used as the initial
        conformation
    :type atoms: :class:`.Atomic`

    :arg n_steps: number of steps to take along each direction,
        for example, for ``n_steps=10``, 20 conformations will be
        generated along *mode* with structure *atoms* in between, 
        default is 10.
    :type n_steps: int

    :arg rmsd: maximum RMSD that the conformations will have with
        respect to the initial conformation, default is 1.5 Å
    :type rmsd: float

    :arg pos: whether to include steps in the positive mode
        direction, default is **True**
    :type pos: bool

    :arg neg: whether to include steps in the negative mode
        direction, default is **True**
    :type neg: bool

    :arg reverse: whether to reverse the direction
        default is **False**
    :type reverse: bool

    :returns: :class:`.Ensemble`

    For given normal mode :math:`u_i`, its eigenvalue
    :math:`\\lambda_i`, number of steps :math:`n`, and maximum :math:`RMSD`
    conformations :math:`[R_{-n} R_{-n+1} ... R_{-1} R_0 R_1 ... R_n]` are
    generated.

    :math:`R_0` is the active coordinate set of *atoms*.
    :math:`R_k = R_0 + sk\\lambda_iu_i`, where :math:`s` is found using
    :math:`s = ((N (\\frac{RMSD}{n})^2) / \\lambda_i^{-1}) ^{0.5}`, where
    :math:`N` is the number of atoms."""

    pos = kwargs.get('pos', True)
    neg = kwargs.get('neg', True)
    reverse = kwargs.get('reverse', False)

    if pos is False and neg is False:
        raise ValueError('pos and neg cannot both be False')

    if not isinstance(mode, VectorBase):
        raise TypeError('mode must be a Mode or Vector instance, '
                        'not {0}'.format(type(mode)))
    if not mode.is3d():
        raise ValueError('mode must be from a 3-dimensional model.')
    n_atoms = mode.numAtoms()
    initial = None
    if atoms is not None:
        if not isinstance(atoms, Atomic):
            raise TypeError('{0} is not correct type for atoms'
                            .format(type(atoms)))
        if atoms.numAtoms() != n_atoms:
            raise ValueError('number of atoms do not match')
        initial = atoms.getCoords()

    name = str(mode)

    rmsd = float(rmsd) + 0.000004
    LOGGER.info('Parameter: rmsd = {0:.2f} A'.format(rmsd))
    n_steps = int(n_steps)
    LOGGER.info('Parameter: n_steps = {0}'.format(n_steps))
    step = rmsd / n_steps
    LOGGER.info('Step size is {0:.2f} A RMSD'.format(step))
    arr = mode.getArrayNx3()
    try:
        var = mode.getVariance()
    except AttributeError:
        var = 1.
    scale = ((n_atoms * step**2) / var) ** 0.5
    LOGGER.info('Mode is scaled by {0}.'.format(scale))

    array = arr * var**0.5 * scale / abs(mode)
    confs_add = [initial + array]
    for s in range(1, n_steps):
        confs_add.append(confs_add[-1] + array)
    confs_sub = [initial - array]
    for s in range(1, n_steps):
        confs_sub.append(confs_sub[-1] - array)
    confs_sub.reverse()
    ensemble = Ensemble('Conformations along {0}'.format(name))
    ensemble.setAtoms(atoms)
    ensemble.setCoords(initial)

    conf_list = [initial]
    if pos:
        conf_list = conf_list + confs_add
    if  neg:
        conf_list = confs_sub + conf_list
    conf_array = np.array(conf_list)

    if reverse:
        conf_array = conf_array[::-1]

    ensemble.addCoordset(conf_array)
    return ensemble
Example #9
0
    def __getitem__(self, index):

        if self._closed:
            raise ValueError('I/O operation on closed file')

        if isinstance(index, int):
            return self.getFrame(index)

        elif isinstance(index, (slice, list, ndarray)):
            if isinstance(index, slice):
                ens = Ensemble('{0} ({1[0]}:{1[1]}:{1[2]})'.format(
                    self._title, index.indices(len(self))))
            else:
                ens = Ensemble('{0} slice'.format(self._title))
            ens.setCoords(self.getCoords())
            if self._weights is not None:
                ens.setWeights(self._weights.copy())
            ens.addCoordset(self.getCoordsets(index))
            ens.setAtoms(self._atoms)
            return ens

        else:
            raise IndexError('invalid index')
Example #10
0
def calcBothWaysAdaptiveANM(a, b, n_steps, **kwargs):
    """Runs both-way adaptivate ANM. """

    n_modes0 = n_modes = kwargs.pop('n_modes', 20)

    coordsA, coordsB, title, atoms, weights, maskA, maskB, rmsd = checkInput(
        a, b, **kwargs)
    coordsA = coordsA.copy()
    coordsB = coordsB.copy()

    LOGGER.timeit('_prody_calcAdaptiveANM')
    n = 0
    resetFmin = True
    defvecs = []
    rmsds = [rmsd]
    ensA = Ensemble('A')
    ensA.setCoords(coordsA)
    ensA.setWeights(weights)
    ensA.addCoordset(coordsA.copy())

    ensB = Ensemble('B')
    ensB.setCoords(coordsB.copy())
    ensB.setWeights(weights)
    ensB.addCoordset(coordsB.copy())

    while n < n_steps:
        LOGGER.info('\nStarting cycle {0} with {1}'.format(
            n + 1, getTitle(a, 'structure A')))
        n_modes = calcStep(coordsA,
                           coordsB,
                           n_modes,
                           ensA,
                           defvecs,
                           rmsds,
                           mask=maskA,
                           resetFmin=resetFmin,
                           **kwargs)
        n += 1
        resetFmin = False

        if n_modes == 0:
            break

    n = 0
    n_modes = n_modes0
    resetFmin = True
    while n < n_steps:
        LOGGER.info('\nStarting cycle {0} with structure {1}'.format(
            n + 1, getTitle(b, 'structure B')))
        n_modes = calcStep(coordsB,
                           coordsA,
                           n_modes,
                           ensB,
                           defvecs,
                           rmsds,
                           mask=maskB,
                           resetFmin=resetFmin,
                           **kwargs)
        n += 1
        resetFmin = False

        if n_modes == 0:
            LOGGER.report('Alternating Adaptive ANM converged in %.2fs.',
                          '_prody_calcAdaptiveANM')
            break

    ensemble = ensA + ensB[::-1]
    ensemble.setTitle(title + '_aANM')
    ensemble.setAtoms(atoms)
    ensemble.setCoords(ensB.getCoords())

    LOGGER.report('Both-way Adaptive ANM converged in %.2fs.',
                  '_prody_calcAdaptiveANM')

    return ensemble
Example #11
0
    def __init__(self, structA, structB, **kwargs):
        self.structA = structA
        self.structB = structB
        self.coordsA = structA.getCoords()
        self.coordsB = structB.getCoords()

        self.cutoff = kwargs.pop('cutoff',
                                 15.0)  # default cutoff for ANM.buildHessian
        self.gamma = kwargs.pop('gamma',
                                1.0)  # default gamma for ANM.buildHessian

        self.alignSel = kwargs.get('alignSel', None)
        self.alignSelA = kwargs.get('alignSelA', self.alignSel)
        self.alignSelB = kwargs.get('alignSelB', self.alignSel)

        self.reduceSel = kwargs.pop('reduceSel', None)
        self.reduceSelA = kwargs.get('reduceSelA', self.reduceSel)
        self.reduceSelB = kwargs.get('reduceSelB', self.reduceSel)

        if self.alignSelA is None:
            self.alignSelA = self.reduceSelA

        if self.alignSelB is None:
            self.alignSelB = self.reduceSelB

        self.trim = kwargs.pop('trim', 'slice')

        if self.reduceSelA is None:
            self.reduceSelA = self.alignSelA

        if self.reduceSelB is None:
            self.reduceSelB = self.alignSelB

        if self.alignSelA is None:
            structA_sel = self.structA
        else:
            structA_sel = self.structA.select(self.alignSelA)

        if self.alignSelB is None:
            structB_sel = self.structB
        else:
            structB_sel = self.structB.select(self.alignSelB)

        self.mapping_func = kwargs.pop('mapping_func', mapOntoChains)
        self.seqid = kwargs.pop('seqid', 90.)
        self.coverage = kwargs.pop('overlap', 70.)
        self.coverage = kwargs.pop('coverage', self.coverage)
        self.pwalign = kwargs.pop('pwalign', None)
        self.pwalign = kwargs.pop('mapping', self.pwalign)

        try:
            _, T = superpose(structA_sel, structB_sel)
            #structA = applyTransformation(T, structA)
            structB_amap = structB_sel
        except:
            structB_amap = sum(
                np.array(
                    mapping_func(structB_sel,
                                 structA_sel,
                                 overlap=self.coverage,
                                 seqid=self.seqid,
                                 pwalign=self.pwalign))[:, 0])
            _, T = superpose(structA_sel, structB_amap)
            #structA = applyTransformation(T, structA)

        rmsd = calcRMSD(structA_sel, structB_amap)
        self.rmsds = [rmsd]
        self.dList = []
        self.numSteps = 0

        self.anmA = kwargs.pop('anmA', None)
        self.anmB = kwargs.pop('anmB', None)

        if self.anmA is not None:
            self.anmListA = [self.anmA]
        else:
            self.anmListA = []

        if self.anmB is not None:
            self.anmListB = [self.anmB]
        else:
            self.anmListB = []

        self.n_modes = kwargs.get('n_modes', 20)
        self.numModesList = []
        self.whichModesA = []
        self.whichModesB = []

        self.maxModes = kwargs.get('maxModes', None)
        if self.maxModes is None:
            self.maxModes = 3 * self.structA.numAtoms() - 6

        if not isinstance(self.maxModes, (int, float)):
            raise TypeError('maxModes should be an integer or float')
        if self.maxModes < 1:
            self.maxModes = int(self.maxModes * 3 * self.structA.numAtoms() -
                                6)
        if self.maxModes > 3 * self.structA.numAtoms() - 6:
            self.maxModes = 3 * self.structA.numAtoms() - 6

        self.Fmin = kwargs.get('Fmin', None)
        self.Fmin_max = kwargs.get('Fmin_max', 0.6)
        self.resetFmin = kwargs.get('resetFmin', False)
        self.f = kwargs.get('f', 0.2)

        self.ensembleA = Ensemble(structA)
        self.ensembleA.addCoordset(structA)

        self.ensembleB = Ensemble(structB)
        self.ensembleB.addCoordset(structB)

        self.outputDCD = kwargs.get('outputDCD', False)
        self.outputPDB = kwargs.get('outputPDB', False)

        self.filename = kwargs.get('filename', None)
        if self.filename is None:
            self.filename = self.structA.getTitle().replace(' ', '_')

        self.rmsd_diff_cutoff = kwargs.get('rmsd_diff_cutoff', 0.05)
        self.target_rmsd = kwargs.get('target_rmsd', 1.0)

        LOGGER.info(
            'Initialised Adaptive ANM with RMSD {:4.3f}\n'.format(rmsd))
Example #12
0
class AdaptiveANM(object):
    """Class for adaptive ANM analysis of proteins ([ZY09]_).

    This implementation differs from the original one in that it sorts the 
    modes by overlap prior to cumulative overlap calculations for efficiency.

    .. [PD00] Zheng Yang, Peter Májek, Ivet Bahar. Allosteric Transitions of 
              Supramolecular Systems Explored by Network Models: Application to 
              Chaperonin GroEL. *PLOS Comp Biol* **2009** 40:512-524.

    arg structA: the starting structure for the transition
    type structA: :class:`.Atomic`

    arg structB: the target structure for the transition
    type structB: :class:`.Atomic`

    arg alignSelA: selection string for selecting atoms from structA
        that can be aligned with those from structB. If not provided,
        these values will be taken from alignSel or reduceSelA or 
        worked out with a mapping function. 
    type alignSelA: str

    arg alignSelB: selection string for selecting atoms from structB
        that can be aligned with those from structA. If not provided,
        these values will be taken from alignSel or reduceSelA or 
        worked out with a mapping function. 
    type alignSelB: str

    arg alignSel: selection string for selecting from both structA and structB
        for alignment. This kwarg can be used when the same selection string works 
        for both. It populates alignSelA and alignSelB when no values are given 
        for them individually.

    arg reduceSelA: selection string for selecting atoms from structA
        for slicing or reducing the ANM to only consider part of the structure 
        for targeting. This is populated by reduceSel or alignSelA if not provided.
    type reduceSelA: str

    arg reduceSelB: selection string for selecting atoms from structB
        for slicing or reducing the ANM to only consider part of the structure 
        for targeting. This is populated by reduceSel or alignSelB if not provided.
    type reduceSelB: str

    arg reduceSel: selection string for selecting from both structA and structB
        for reducing the number of atoms used in targeting comparisons.
        This kwarg can be used when the same selection string works for both.
        It populates reduceSelA and reduceSelB when no values are given for them.

    Other kwargs can be provided that work for internal functions such as 
    :func:`.buildHessian`, :func:`.calcModes` and mapping functions, which are 
    only used if *alignSel* and *reduceSel* are not provided. 
    """
    def __init__(self, structA, structB, **kwargs):
        self.structA = structA
        self.structB = structB
        self.coordsA = structA.getCoords()
        self.coordsB = structB.getCoords()

        self.cutoff = kwargs.pop('cutoff',
                                 15.0)  # default cutoff for ANM.buildHessian
        self.gamma = kwargs.pop('gamma',
                                1.0)  # default gamma for ANM.buildHessian

        self.alignSel = kwargs.get('alignSel', None)
        self.alignSelA = kwargs.get('alignSelA', self.alignSel)
        self.alignSelB = kwargs.get('alignSelB', self.alignSel)

        self.reduceSel = kwargs.pop('reduceSel', None)
        self.reduceSelA = kwargs.get('reduceSelA', self.reduceSel)
        self.reduceSelB = kwargs.get('reduceSelB', self.reduceSel)

        if self.alignSelA is None:
            self.alignSelA = self.reduceSelA

        if self.alignSelB is None:
            self.alignSelB = self.reduceSelB

        self.trim = kwargs.pop('trim', 'slice')

        if self.reduceSelA is None:
            self.reduceSelA = self.alignSelA

        if self.reduceSelB is None:
            self.reduceSelB = self.alignSelB

        if self.alignSelA is None:
            structA_sel = self.structA
        else:
            structA_sel = self.structA.select(self.alignSelA)

        if self.alignSelB is None:
            structB_sel = self.structB
        else:
            structB_sel = self.structB.select(self.alignSelB)

        self.mapping_func = kwargs.pop('mapping_func', mapOntoChains)
        self.seqid = kwargs.pop('seqid', 90.)
        self.coverage = kwargs.pop('overlap', 70.)
        self.coverage = kwargs.pop('coverage', self.coverage)
        self.pwalign = kwargs.pop('pwalign', None)
        self.pwalign = kwargs.pop('mapping', self.pwalign)

        try:
            _, T = superpose(structA_sel, structB_sel)
            #structA = applyTransformation(T, structA)
            structB_amap = structB_sel
        except:
            structB_amap = sum(
                np.array(
                    mapping_func(structB_sel,
                                 structA_sel,
                                 overlap=self.coverage,
                                 seqid=self.seqid,
                                 pwalign=self.pwalign))[:, 0])
            _, T = superpose(structA_sel, structB_amap)
            #structA = applyTransformation(T, structA)

        rmsd = calcRMSD(structA_sel, structB_amap)
        self.rmsds = [rmsd]
        self.dList = []
        self.numSteps = 0

        self.anmA = kwargs.pop('anmA', None)
        self.anmB = kwargs.pop('anmB', None)

        if self.anmA is not None:
            self.anmListA = [self.anmA]
        else:
            self.anmListA = []

        if self.anmB is not None:
            self.anmListB = [self.anmB]
        else:
            self.anmListB = []

        self.n_modes = kwargs.get('n_modes', 20)
        self.numModesList = []
        self.whichModesA = []
        self.whichModesB = []

        self.maxModes = kwargs.get('maxModes', None)
        if self.maxModes is None:
            self.maxModes = 3 * self.structA.numAtoms() - 6

        if not isinstance(self.maxModes, (int, float)):
            raise TypeError('maxModes should be an integer or float')
        if self.maxModes < 1:
            self.maxModes = int(self.maxModes * 3 * self.structA.numAtoms() -
                                6)
        if self.maxModes > 3 * self.structA.numAtoms() - 6:
            self.maxModes = 3 * self.structA.numAtoms() - 6

        self.Fmin = kwargs.get('Fmin', None)
        self.Fmin_max = kwargs.get('Fmin_max', 0.6)
        self.resetFmin = kwargs.get('resetFmin', False)
        self.f = kwargs.get('f', 0.2)

        self.ensembleA = Ensemble(structA)
        self.ensembleA.addCoordset(structA)

        self.ensembleB = Ensemble(structB)
        self.ensembleB.addCoordset(structB)

        self.outputDCD = kwargs.get('outputDCD', False)
        self.outputPDB = kwargs.get('outputPDB', False)

        self.filename = kwargs.get('filename', None)
        if self.filename is None:
            self.filename = self.structA.getTitle().replace(' ', '_')

        self.rmsd_diff_cutoff = kwargs.get('rmsd_diff_cutoff', 0.05)
        self.target_rmsd = kwargs.get('target_rmsd', 1.0)

        LOGGER.info(
            'Initialised Adaptive ANM with RMSD {:4.3f}\n'.format(rmsd))

    def runStep(self, **kwargs):
        """Run a single step of adaptive ANM. 
        Modes will be calculated for *structA* and the subset with 
        a cumulative overlap above a threshold defined by *Fmin* 
        is used for transitioning towards *structB*.
        
        By default this function uses values from initialisation but 
        they can be over-ridden if desired. For example, in bi-directional 
        adaptive ANM, we switch *structA* and *structB*, *alignSelA* and *alignSelB*,
        and *reduceSelA* and *reduceSelB*
        """

        structA = kwargs.pop('structA', self.structA)
        structB = kwargs.pop('structA', self.structB)

        alignSel = kwargs.pop('alignSel', self.alignSel)
        alignSelA = kwargs.pop('alignSelA', self.alignSelA)
        alignSelB = kwargs.pop('alignSelB', self.alignSelB)

        reduceSel = kwargs.pop('reduceSel', self.reduceSel)
        reduceSelA = kwargs.pop('reduceSelA', self.reduceSelA)
        reduceSelB = kwargs.pop('reduceSelB', self.reduceSelB)

        if reduceSelA is None:
            reduceSelA = reduceSel

        if reduceSelB is None:
            reduceSelB = reduceSel

        if alignSelA is None:
            if alignSelA is None:
                alignSelA = reduceSelA

            if alignSelB is None:
                alignSelB = reduceSelB
        else:
            if alignSelA is None:
                alignSelA = alignSel

            if alignSelB is None:
                alignSelB = alignSel

        Fmin = kwargs.get('Fmin', self.Fmin)

        f = kwargs.get('f', self.f)

        outputDCD = kwargs.get('outputDCD', self.outputDCD)
        outputPDB = kwargs.get('outputPDB', self.outputPDB)
        filename = kwargs.get('filename', self.filename)

        LOGGER.info('\nStarting cycle {0} with initial structure {1}'.format(
            self.numSteps + 1, structA))

        mapping_func = kwargs.get('mapping_func', mapOntoChains)

        if alignSelA is None:
            structA_sel = structA
        else:
            structA_sel = structA.select(alignSelA)

        if alignSelB is None:
            structB_sel = structB
        else:
            structB_sel = structB.select(alignSelB)

        mapping_func = kwargs.pop('mapping_func', self.mapping_func)
        seqid = kwargs.pop('seqid', self.seqid)
        coverage = kwargs.pop('overlap', self.coverage)
        coverage = kwargs.pop('coverage', coverage)
        pwalign = kwargs.pop('pwalign', self.pwalign)
        pwalign = kwargs.pop('mapping', pwalign)

        try:
            _, T = superpose(structA_sel, structB_sel)
            structA = applyTransformation(T, structA)
        except:
            structB_amap = sum(
                np.array(
                    mapping_func(structB_sel,
                                 structA_sel,
                                 overlap=coverage,
                                 seqid=seqid,
                                 pwalign=pwalign))[:, 0])
            _, T = superpose(structA_sel, structB_amap)
            structA = applyTransformation(T, structA)

        maxModes = kwargs.get('maxModes', self.maxModes)
        if not isinstance(maxModes, (int, float)):
            raise TypeError('maxModes should be an integer or float')
        if maxModes < 1:
            maxModes = int(maxModes * 3 * self.structA.numAtoms() - 6)
        if maxModes > 3 * self.structA.numAtoms() - 6:
            maxModes = 3 * self.structA.numAtoms() - 6

        if self.n_modes > maxModes:
            self.n_modes = maxModes

        trim = kwargs.pop('trim', self.trim)
        anmA, _ = calcENM(structA, n_modes=self.n_modes)

        if trim == 'slice':
            trim_anmA, _ = sliceModel(anmA, structA, reduceSelA)
        elif trim == 'reduce':
            trim_anmA, _ = reduceModel(anmA, structA, reduceSelA)
            trim_anmA.calcModes(n_modes=self.n_modes)
        else:
            trim_anmA = anmA

        coordsA = structA.getCoords()
        coordsA_sel = structA_sel.getCoords()
        coordsB_sel = structB_sel.getCoords()

        defvec = coordsB_sel - coordsA_sel
        d = defvec.flatten()
        self.dList.append(d)

        if Fmin is None:
            if self.numSteps == 0 or self.resetFmin:
                Fmin = 0.  # Select the first mode only
            else:
                Fmin = 1 - np.sqrt(
                    np.linalg.norm(self.dList[self.numSteps]) /
                    np.linalg.norm(self.dList[0]))

        if Fmin > self.Fmin_max:
            Fmin = self.Fmin_max

        LOGGER.info(
            'Fmin is {:4.3f}, corresponding to a cumulative overlap of {:4.3f}'
            .format(Fmin, np.sqrt(Fmin)))

        trim_d = sliceAtomicData(d, structA_sel, reduceSelA)
        overlaps = np.dot(trim_d, trim_anmA.getEigvecs())
        overlap_sorting_indices = list(
            reversed(list(np.argsort(abs(overlaps)))))
        overlaps = overlaps[overlap_sorting_indices]

        if trim == 'reduce':
            sliced_anmA, _ = sliceModel(anmA, structA, reduceSelA)
            modesetA = ModeSet(trim_anmA, overlap_sorting_indices)
            _, overlap_sorting_indices = matchModes(modesetA,
                                                    sliced_anmA,
                                                    index=True)

        modesetA = ModeSet(anmA, overlap_sorting_indices)

        normalised_overlaps = overlaps / np.linalg.norm(d)
        c_sq = np.cumsum(np.power(normalised_overlaps, 2), axis=0)

        modesCrossingFmin = np.where(c_sq <= Fmin)[0]
        numModes = len(modesCrossingFmin)
        if numModes == 0:
            numModes = 1
            modesCrossingFmin = [0]

        self.numModesList.append(numModes)

        if numModes == 1:
            LOGGER.info('Using 1 mode with overlap {0} (Mode {1})'.format(
                '{:4.3f}'.format(np.sqrt(c_sq[0])),
                modesetA.getIndices()[0] + 1))
        elif numModes < 11:
            LOGGER.info(
                'Using {0} modes with cumulative overlap {1} (Modes {2} and {3})'
                .format(
                    numModes, '{:4.3f}'.format(np.sqrt(c_sq[numModes - 1])),
                    ', '.join([
                        str(entry)
                        for entry in modesetA.getIndices()[:numModes - 1] + 1
                    ]), str(modesetA.getIndices()[numModes - 1] + 1)))
        else:
            LOGGER.info(
                'Using {0} modes with cumulative overlap {1} (Modes {2}, ... and {3}) with max mode number {4} and min mode number {5}'
                .format(
                    numModes, '{:4.3f}'.format(np.sqrt(c_sq[numModes - 1])),
                    ', '.join([
                        str(entry) for entry in modesetA.getIndices()[:10] + 1
                    ]), str(modesetA.getIndices()[numModes - 1] + 1),
                    np.max(modesetA.getIndices()[:numModes] + 1),
                    np.min(modesetA.getIndices()[:numModes] + 1)))

        if np.max(modesetA.getIndices()[:numModes]) > self.n_modes - 5:
            self.n_modes *= 10

        if self.n_modes > 3 * self.structA.numAtoms() - 6:
            self.n_modes = 3 * self.structA.numAtoms() - 6

        v = np.sum(np.multiply(overlaps[:numModes],
                               modesetA.getEigvecs()[:, :numModes]),
                   axis=1).reshape(coordsA.shape)

        trim_v = sliceAtomicData(v.reshape(-1), structA,
                                 reduceSelA).reshape(-1, 3)
        s_min = sum(np.multiply(trim_v.flatten(), trim_d)) / sum(
            np.power(trim_v.flatten(), 2))

        new_coordsA = coordsA + f * s_min * v

        if structA == self.structA:
            self.anmA = anmA
            self.anmListA.append(modesetA)
            self.structA.setCoords(new_coordsA)
            self.ensembleA.addCoordset(new_coordsA)
            self.whichModesA.append(modesetA[modesCrossingFmin])
        elif structA == self.structB:
            self.anmB = anmA
            self.anmListB.append(modesetA)
            self.structB.setCoords(new_coordsA)
            self.ensembleB.addCoordset(new_coordsA)
            self.whichModesB.append(modesetA[modesCrossingFmin])

        new_coordsA_reduceSel = structA.select(reduceSelA).getCoords()
        coordsB_reduceSel = structB.select(reduceSelB).getCoords()
        rmsd = calcRMSD(new_coordsA_reduceSel, coordsB_reduceSel)

        LOGGER.info('Current RMSD is {:4.3f}\n'.format(rmsd))

        self.numSteps += 1

        self.rmsds.append(rmsd)

        if outputPDB:
            writePDB(filename + '_A', self.ensembleA)
            LOGGER.clear()
            writePDB(filename + '_B', self.ensembleB)
            LOGGER.clear()

        if outputDCD:
            writeDCD(filename + '_A', self.ensembleA)
            LOGGER.clear()
            writeDCD(filename + '_B', self.ensembleB)
            LOGGER.clear()

        return

    def checkConvergence(self, **kwargs):
        converged = False
        rmsd_diff_cutoff = kwargs.get('rmsd_diff_cutoff',
                                      self.rmsd_diff_cutoff)
        target_rmsd = kwargs.get('target_rmsd', self.target_rmsd)

        if self.rmsds[-2] - self.rmsds[-1] < rmsd_diff_cutoff:
            LOGGER.warn(
                'The RMSD decrease fell below {0}'.format(rmsd_diff_cutoff))
            converged = True

        if self.rmsds[-1] < target_rmsd:
            LOGGER.warn(
                'The RMSD fell below target RMSD {0}'.format(target_rmsd))
            converged = True

        all_dists = np.array([
            calcDistance(self.structA, self.structA[i])
            for i in range(self.structA.numAtoms())
        ])
        min_dists = np.array([
            np.min([np.min(all_dists[i, :i]),
                    np.min(all_dists[i, i + 1:])])
            for i in range(1,
                           self.structA.numAtoms() - 1)
        ])
        if max(min_dists) > self.cutoff:
            LOGGER.warn(
                'A bead has become disconnected. Adaptive ANM cannot proceed without unrealistic deformations'
            )
            converged = True

        return converged

    def runManySteps(self, n_steps, **kwargs):
        LOGGER.timeit('_prody_runManySteps')
        n_start = self.numSteps
        while self.numSteps < n_start + n_steps:
            self.runStep(structA=self.structA, structB=self.structB, **kwargs)
            LOGGER.debug(
                'Total time so far is %.2f minutes' %
                ((time.time() - LOGGER._times['_prody_runManySteps']) / 60))
            converged = self.checkConvergence()
            if converged:
                self.structA.setCoords(
                    self.coordsA
                )  # That way the original object is back to normal
                self.structB.setCoords(
                    self.coordsB
                )  # That way the original object is back to normal
                LOGGER.debug(
                    'Process completed in %.2f hours' %
                    ((time.time() - LOGGER._times['_prody_runManySteps']) /
                     3600))
                break

    def runManyStepsAlternating(self, n_steps, **kwargs):
        LOGGER.timeit('_prody_runManySteps')
        n_start = self.numSteps
        while self.numSteps < n_start + n_steps:
            n_modes = self.n_modes

            self.runStep(structA=self.structA,
                         structB=self.structB,
                         reduceSelA=self.reduceSelA,
                         reduceSelB=self.reduceSelB,
                         alignSelA=self.alignSelA,
                         alignSelB=self.alignSelB,
                         n_modes=n_modes,
                         **kwargs)
            LOGGER.debug(
                'Total time so far is %.2f minutes' %
                ((time.time() - LOGGER._times['_prody_runManySteps']) / 60))

            self.runStep(structA=self.structB,
                         structB=self.structA,
                         reduceSelA=self.reduceSelB,
                         reduceSelB=self.reduceSelA,
                         alignSelA=self.alignSelB,
                         alignSelB=self.alignSelA,
                         n_modes=n_modes,
                         **kwargs)
            LOGGER.debug(
                'Total time so far is %.2f minutes' %
                ((time.time() - LOGGER._times['_prody_runManySteps']) / 60))

            converged = self.checkConvergence()
            if converged:
                self.structA.setCoords(
                    self.coordsA
                )  # That way the original object is back to normal
                self.structB.setCoords(
                    self.coordsB
                )  # That way the original object is back to normal
                LOGGER.debug(
                    'Process completed in %.2f hours' %
                    ((time.time() - LOGGER._times['_prody_runManySteps']) /
                     3600))
                break

        ensemble = Ensemble('combined trajectory')
        ensemble.setAtoms(self.structA)
        for coordset in self.ensembleA.getCoordsets():
            ensemble.addCoordset(coordset)
        for coordset in reversed(self.ensembleB.getCoordsets()):
            ensemble.addCoordset(coordset)

        if self.outputPDB:
            writePDB(self.filename, ensemble)

        if self.outputDCD:
            writeDCD(self.filename, ensemble)

        return

    def runManyStepsFurthestEachWay(self, n_steps, **kwargs):
        LOGGER.timeit('_prody_runManySteps')
        n_start = self.numSteps
        n_modes = self.n_modes

        LOGGER.info('\n\nStarting from struct A ({0})'.format(self.structA))
        while self.numSteps < n_start + n_steps:
            self.runStep(structA=self.structA,
                         structB=self.structB,
                         reduceSelA=self.reduceSelA,
                         reduceSelB=self.reduceSelB,
                         alignSelA=self.alignSelB,
                         alignSelB=self.alignSelA,
                         n_modes=n_modes,
                         **kwargs)
            LOGGER.debug(
                'Total time so far is %.2f minutes' %
                ((time.time() - LOGGER._times['_prody_runManySteps']) / 60))
            converged = self.checkConvergence()
            if converged:
                self.structA.setCoords(
                    self.coordsA
                )  # That way the original object is back to normal
                self.structB.setCoords(
                    self.coordsB
                )  # That way the original object is back to normal
                LOGGER.warn('The part starting from structA converged.')
                break

        LOGGER.info('\n\nStarting from structB ({0})'.format(self.structB))
        self.resetFmin = True
        while self.numSteps < n_start + n_steps:
            self.runStep(structA=self.structB,
                         structB=self.structA,
                         reduceSelA=self.reduceSelB,
                         reduceSelB=self.reduceSelA,
                         alignSelA=self.alignSelB,
                         alignSelB=self.alignSelA,
                         n_modes=n_modes,
                         **kwargs)
            LOGGER.debug(
                'Total time so far is %.2f minutes' %
                ((time.time() - LOGGER._times['_prody_runManySteps']) / 60))
            self.resetFmin = False
            converged = self.checkConvergence()
            if converged:
                self.structA.setCoords(
                    self.coordsA
                )  # That way the original object is back to normal
                self.structB.setCoords(
                    self.coordsB
                )  # That way the original object is back to normal
                LOGGER.warn('The part starting from structB converged.')
                break

        ensemble = Ensemble('combined trajectory')
        ensemble.setAtoms(self.structA)
        for coordset in self.ensembleA.getCoordsets():
            ensemble.addCoordset(coordset)
        for coordset in reversed(self.ensembleB.getCoordsets()):
            ensemble.addCoordset(coordset)

        if self.outputPDB:
            writePDB(self.filename, ensemble)

        if self.outputDCD:
            writeDCD(self.filename, ensemble)

        LOGGER.debug(
            'Process completed in %.2f hours' %
            ((time.time() - LOGGER._times['_prody_runManySteps']) / 3600))

        return