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 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
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
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')
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
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
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
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
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')
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
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))
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