def _reduceModel(matrix, system): """This is the underlying function that reduces models, which shall remain private. ``system`` is a boolean array where **True** indicates system nodes.""" linalg = importLA() other = np.invert(system) ss = matrix[system, :][:, system] so = matrix[system, :][:, other] os = matrix[other, :][:, system] oo = matrix[other, :][:, other] if other.any(): try: invoo = linalg.inv(oo) except: invoo = linalg.pinv(oo) matrix = ss - np.dot(so, np.dot(invoo, os)) else: matrix = ss return matrix
def getTransformation(mob, tar, weights=None): linalg = importLA() if weights is None: mob_com = mob.mean(0) tar_com = tar.mean(0) mob = mob - mob_com tar = tar - tar_com matrix = np.dot(tar.T, mob) else: weights_sum = weights.sum() weights_dot = np.dot(weights.T, weights) mob_com = (mob * weights).sum(axis=0) / weights_sum tar_com = (tar * weights).sum(axis=0) / weights_sum mob = mob - mob_com tar = tar - tar_com matrix = np.dot((tar * weights).T, (mob * weights)) / weights_dot U, s, Vh = linalg.svd(matrix) Id = np.array([ [1, 0, 0], [0, 1, 0], [0, 0, np.sign(linalg.det(matrix))] ]) rotation = np.dot(Vh.T, np.dot(Id, U.T)) return rotation, tar_com - np.dot(mob_com, rotation)
def _getEigvecs(modes, row_norm=False): if isinstance(modes, (ModeSet, NMA)): V = modes.getEigvecs() elif isinstance(modes, Mode): V = modes.getEigvec() elif isinstance(modes, np.ndarray): V = modes else: try: mode0 = modes[0] if isinstance(mode0, Mode): V = np.empty((len(mode0), 0)) for mode in modes: assert isinstance(mode, Mode), 'Modes should be a list of modes.' v = mode.getEigvec() v = np.expand_dims(v, axis=1) V = np.hstack((V, v)) else: V = np.array(modes) except TypeError: TypeError('Modes should be a list of modes.') if V.ndim == 1: V = np.expand_dims(V, axis=1) # normalize the rows so that feature vectors are unit vectors if row_norm: la = importLA() norms = la.norm(V, axis=1) N = np.diag(div0(1., norms)) V = np.dot(N, V) return V
def calcProjection(ensemble, modes, rmsd=True, norm=True): """Returns projection of conformational deviations onto given modes. *ensemble* coordinates are used to calculate the deviations that are projected onto *modes*. For K conformations and M modes, a (K,M) matrix is returned. :arg ensemble: an ensemble, trajectory or a conformation for which deviation(s) will be projected, or a deformation vector :type ensemble: :class:`.Ensemble`, :class:`.Conformation`, :class:`.Vector`, :class:`.Trajectory` :arg modes: up to three normal modes :type modes: :class:`.Mode`, :class:`.ModeSet`, :class:`.NMA` By default root-mean-square deviation (RMSD) along the normal mode is calculated. To calculate the projection pass ``rmsd=True``. :class:`.Vector` instances are accepted as *ensemble* argument to allow for projecting a deformation vector onto normal modes.""" if not isinstance(ensemble, (Ensemble, Conformation, Vector, TrajBase)): raise TypeError('ensemble must be Ensemble, Conformation, Vector, ' 'or a TrajBase, not {0}'.format(type(ensemble))) if not isinstance(modes, (NMA, ModeSet, VectorBase)): raise TypeError('rows must be NMA, ModeSet, or Mode, not {0}'.format( type(modes))) if not modes.is3d(): raise ValueError('modes must be 3-dimensional') if isinstance(ensemble, Vector): n_atoms = ensemble.numAtoms() else: n_atoms = ensemble.numSelected() if n_atoms != modes.numAtoms(): raise ValueError('number of atoms are not the same') if isinstance(ensemble, Vector): if not ensemble.is3d(): raise ValueError('ensemble must be a 3d vector instance') deviations = ensemble._getArray() elif isinstance(ensemble, (Ensemble, Conformation)): deviations = ensemble.getDeviations() else: nfi = ensemble.nextIndex() ensemble.goto(0) deviations = np.array([frame.getDeviations() for frame in ensemble]) ensemble.goto(nfi) if deviations.ndim == 3: deviations = deviations.reshape( (deviations.shape[0], deviations.shape[1] * 3)) elif deviations.ndim == 2: deviations = deviations.reshape((1, deviations.shape[0] * 3)) else: deviations = deviations.reshape((1, deviations.shape[0])) la = importLA() if norm: N = la.norm(deviations) if N != 0: deviations = deviations / N projection = np.dot(deviations, modes._getArray()) if rmsd: projection = (1 / (n_atoms**0.5)) * projection return projection
def calcProjection(ensemble, modes, rmsd=True, norm=True): """Returns projection of conformational deviations onto given modes. *ensemble* coordinates are used to calculate the deviations that are projected onto *modes*. For K conformations and M modes, a (K,M) matrix is returned. :arg ensemble: an ensemble, trajectory or a conformation for which deviation(s) will be projected, or a deformation vector :type ensemble: :class:`.Ensemble`, :class:`.Conformation`, :class:`.Vector`, :class:`.Trajectory` :arg modes: up to three normal modes :type modes: :class:`.Mode`, :class:`.ModeSet`, :class:`.NMA` By default root-mean-square deviation (RMSD) along the normal mode is calculated. To calculate the projection pass ``rmsd=True``. :class:`.Vector` instances are accepted as *ensemble* argument to allow for projecting a deformation vector onto normal modes.""" if not isinstance(ensemble, (Ensemble, Conformation, Vector, TrajBase)): raise TypeError('ensemble must be Ensemble, Conformation, Vector, ' 'or a TrajBase, not {0}'.format(type(ensemble))) if not isinstance(modes, (NMA, ModeSet, VectorBase)): raise TypeError('rows must be NMA, ModeSet, or Mode, not {0}' .format(type(modes))) if not modes.is3d(): raise ValueError('modes must be 3-dimensional') if isinstance(ensemble, Vector): n_atoms = ensemble.numAtoms() else: n_atoms = ensemble.numSelected() if n_atoms != modes.numAtoms(): raise ValueError('number of atoms are not the same') if isinstance(ensemble, Vector): if not ensemble.is3d(): raise ValueError('ensemble must be a 3d vector instance') deviations = ensemble._getArray() elif isinstance(ensemble, (Ensemble, Conformation)): deviations = ensemble.getDeviations() else: nfi = ensemble.nextIndex() ensemble.goto(0) deviations = np.array([frame.getDeviations() for frame in ensemble]) ensemble.goto(nfi) if deviations.ndim == 3: deviations = deviations.reshape((deviations.shape[0], deviations.shape[1] * 3)) elif deviations.ndim == 2: deviations = deviations.reshape((1, deviations.shape[0] * 3)) else: deviations = deviations.reshape((1, deviations.shape[0])) la = importLA() if norm: N = la.norm(deviations) if N != 0: deviations = deviations / N projection = np.dot(deviations, modes._getArray()) if rmsd: projection = (1 / (n_atoms ** 0.5)) * projection return projection
def calcHitTime(model, method='standard'): """Returns the hit and commute times between pairs of nodes calculated based on a :class:`.NMA` object. .. [CB95] Chennubhotla C., Bahar I. Signal Propagation in Proteins and Relation to Equilibrium Fluctuations. *PLoS Comput Biol* **2007** 3(9). :arg model: model to be used to calculate hit times :type model: :class:`.NMA` :arg method: method to be used to calculate hit times. Available options are ``"standard"`` or ``"kirchhoff"``. Default is ``"standard"`` :type method: str :returns: (:class:`~numpy.ndarray`, :class:`~numpy.ndarray`) """ try: K = model.getKirchhoff() except AttributeError: raise TypeError('model must be an NMA instance') if K is None: raise ValueError('model not built') method = method.lower() D = np.diag(K) A = np.diag(D) - K start = time.time() linalg = importLA() if method == 'standard': st = D / sum(D) P = np.dot(np.diag(D**(-1)), A) W = np.ones((len(st), 1)) * st.T Z = linalg.pinv(np.eye(P.shape[0], P.shape[1]) - P + W) H = np.ones((len(st), 1)) * np.diag(Z).T - Z H = H / W H = H.T elif method == 'kirchhoff': K_inv = linalg.pinv(K) sum_D = sum(D) T1 = (sum_D * np.ones((len(D),1)) * np.diag(K_inv)).T T2 = sum_D * K_inv T3_i = np.dot((np.ones((len(D),1)) * D), K_inv) H = T1 - T2 + T3_i - T3_i.T C = H + H.T LOGGER.debug('Hit and commute times are calculated in {0:.2f}s.' .format(time.time()-start)) return H, C
def _superpose(self, **kwargs): """Superpose conformations and update coordinates.""" indices = self._indices weights = self._weights mobs = self._confs if indices is None: idx = False tar = self._coords movs = None else: idx = True if self._weights is not None: weights = weights[indices] tar = self._coords[indices] movs = self._confs linalg = importLA() svd = linalg.svd det = linalg.det if weights is None: tar_com = tar.mean(0) tar_org = (tar - tar_com) mob_org = zeros(tar_org.shape, dtype=mobs.dtype) tar_org = tar_org.T else: weights_sum = weights.sum() weights_dot = dot(weights.T, weights) tar_com = (tar * weights).sum(axis=0) / weights_sum tar_org = (tar - tar_com) mob_org = zeros(tar_org.shape, dtype=mobs.dtype) LOGGER.progress('Superposing ', len(mobs), '_prody_ensemble') for i, mob in enumerate(mobs): if idx: mob = mob[indices] if weights is None: mob_com = mob.mean(0) matrix = dot(tar_org, subtract(mob, mob_com, mob_org)) else: mob_com = (mob * weights).sum(axis=0) / weights_sum subtract(mob, mob_com, mob_org) matrix = dot((tar_org * weights).T, (mob_org * weights)) / weights_dot U, s, Vh = svd(matrix) Id = array([[1, 0, 0], [0, 1, 0], [0, 0, sign(det(matrix))]]) rotation = dot(Vh.T, dot(Id, U.T)) if movs is None: mobs[i] = dot(mob_org, rotation) add(mobs[i], tar_com, mobs[i]) else: add(dot(movs[i], rotation), (tar_com - dot(mob_com, rotation)), movs[i]) LOGGER.update(i, '_prody_ensemble') LOGGER.clear()
def _superpose(self, **kwargs): """Superpose conformations and update coordinates.""" indices = self._indices weights = self._weights mobs = self._confs if indices is None: idx = False tar = self._coords movs = None else: idx = True if self._weights is not None: weights = weights[indices] tar = self._coords[indices] movs = self._confs linalg = importLA() svd = linalg.svd det = linalg.det if weights is None: tar_com = tar.mean(0) tar_org = (tar - tar_com) mob_org = zeros(tar_org.shape, dtype=mobs.dtype) tar_org = tar_org.T else: weights_sum = weights.sum() weights_dot = dot(weights.T, weights) tar_com = (tar * weights).sum(axis=0) / weights_sum tar_org = (tar - tar_com) mob_org = zeros(tar_org.shape, dtype=mobs.dtype) LOGGER.progress('Superposing ', len(mobs), '_prody_ensemble') for i, mob in enumerate(mobs): if idx: mob = mob[indices] if weights is None: mob_com = mob.mean(0) matrix = dot(tar_org, subtract(mob, mob_com, mob_org)) else: mob_com = (mob * weights).sum(axis=0) / weights_sum subtract(mob, mob_com, mob_org) matrix = dot((tar_org * weights).T, (mob_org * weights)) / weights_dot U, s, Vh = svd(matrix) Id = array([[1, 0, 0], [0, 1, 0], [0, 0, sign(det(matrix))]]) rotation = dot(Vh.T, dot(Id, U.T)) if movs is None: mobs[i] = dot(mob_org, rotation) add(mobs[i], tar_com, mobs[i]) else: add(dot(movs[i], rotation), (tar_com - dot(mob_com, rotation)), movs[i]) LOGGER.update(i + 1, label='_prody_ensemble') LOGGER.finish()
def performSVD(self, coordsets): """Calculate principal modes using singular value decomposition (SVD). *coordsets* argument may be a :class:`.Atomic`, :class:`.Ensemble`, or :class:`numpy.ndarray` instance. If *coordsets* is a numpy array, its shape must be ``(n_csets, n_atoms, 3)``. Note that coordinate sets must be aligned prior to SVD calculations. This is a considerably faster way of performing PCA calculations compared to eigenvalue decomposition of covariance matrix, but is an approximate method when heterogeneous datasets are analyzed. Covariance method should be preferred over this one for analysis of ensembles with missing atomic data. See :ref:`pca-xray-calculations` example for comparison of results from SVD and covariance methods.""" linalg = importLA() start = time.time() if not isinstance(coordsets, (Ensemble, Atomic, np.ndarray)): raise TypeError('coordsets must be an Ensemble, Atomic, Numpy ' 'array instance') if isinstance(coordsets, np.ndarray): if (coordsets.ndim != 3 or coordsets.shape[2] != 3 or coordsets.dtype not in (np.float32, float)): raise ValueError('coordsets is not a valid coordinate array') deviations = coordsets - coordsets.mean(0) else: if isinstance(coordsets, Ensemble): deviations = coordsets.getDeviations() elif isinstance(coordsets, Atomic): deviations = (coordsets._getCoordsets() - coordsets._getCoords()) n_confs = deviations.shape[0] if n_confs < 3: raise ValueError('coordsets must have more than 3 coordinate sets') n_atoms = deviations.shape[1] if n_atoms < 3: raise ValueError('coordsets must have more than 3 atoms') dof = n_atoms * 3 deviations = deviations.reshape((n_confs, dof)).T vectors, values, self._temp = linalg.svd(deviations, full_matrices=False) values = (values**2) / n_confs self._dof = dof self._n_atoms = n_atoms which = values > 1e-18 self._eigvals = values[which] self._array = vectors[:, which] self._vars = self._eigvals self._trace = self._vars.sum() self._n_modes = len(self._eigvals) LOGGER.debug('{0} modes were calculated in {1:.2f}s.'.format( self._n_modes, time.time() - start))
def performSVD(self, coordsets): """Calculate principal modes using singular value decomposition (SVD). *coordsets* argument may be a :class:`.Atomic`, :class:`.Ensemble`, or :class:`numpy.ndarray` instance. If *coordsets* is a numpy array, its shape must be ``(n_csets, n_atoms, 3)``. Note that coordinate sets must be aligned prior to SVD calculations. This is a considerably faster way of performing PCA calculations compared to eigenvalue decomposition of covariance matrix, but is an approximate method when heterogeneous datasets are analyzed. Covariance method should be preferred over this one for analysis of ensembles with missing atomic data. See :ref:`pca-xray-calculations` example for comparison of results from SVD and covariance methods.""" linalg = importLA() start = time.time() if not isinstance(coordsets, (Ensemble, Atomic, np.ndarray)): raise TypeError('coordsets must be an Ensemble, Atomic, Numpy ' 'array instance') if isinstance(coordsets, np.ndarray): if (coordsets.ndim != 3 or coordsets.shape[2] != 3 or coordsets.dtype not in (np.float32, float)): raise ValueError('coordsets is not a valid coordinate array') deviations = coordsets - coordsets.mean(0) else: if isinstance(coordsets, Ensemble): deviations = coordsets.getDeviations() elif isinstance(coordsets, Atomic): deviations = (coordsets._getCoordsets() - coordsets._getCoords()) n_confs = deviations.shape[0] if n_confs < 3: raise ValueError('coordsets must have more than 3 coordinate sets') n_atoms = deviations.shape[1] if n_atoms < 3: raise ValueError('coordsets must have more than 3 atoms') dof = n_atoms * 3 deviations = deviations.reshape((n_confs, dof)).T vectors, values, self._temp = linalg.svd(deviations, full_matrices=False) values = (values ** 2) / n_confs self._dof = dof self._n_atoms = n_atoms which = values > 1e-18 self._eigvals = values[which] self._array = vectors[:, which] self._vars = self._eigvals self._trace = self._vars.sum() self._n_modes = len(self._eigvals) LOGGER.debug('{0} modes were calculated in {1:.2f}s.' .format(self._n_modes, time.time()-start))
def _getEigvecs(modes, row_norm=False, dummy_mode=False): la = importLA() if isinstance(modes, (Mode, ModeSet, NMA)): model = modes._model if isinstance(model, MaskedGNM): masked = model.masked model.masked = True V = modes.getArray() model.masked = masked else: V = modes.getArray() elif isinstance(modes, np.ndarray): V = modes else: try: mode0 = modes[0] if isinstance(mode0, Mode): V = np.empty((len(mode0), 0)) for mode in modes: assert isinstance(mode, Mode), 'Modes should be a list of modes.' v = mode.getEigvec() v = np.expand_dims(v, axis=1) V = np.hstack((V, v)) else: V = np.array(modes) except TypeError: raise TypeError('Modes should be a list of modes.') if V.ndim == 1: V = np.expand_dims(V, axis=1) # add a dummy zero mode to the modeset if dummy_mode: v0 = V[:, 0] if np.allclose(v0, np.mean(v0)): dummy_mode = False LOGGER.warn( 'at least one zero mode is detected therefore dummy mode will NOT be added' ) if dummy_mode: n, _ = V.shape v0 = np.ones((n, 1), dtype=V.dtype) v0 /= la.norm(v0) V = np.hstack((v0, V)) LOGGER.debug('a dummy zero mode is added') # normalize the rows so that feature vectors are unit vectors if row_norm: norms = la.norm(V, axis=1) N = np.diag(div0(1., norms)) V = np.dot(N, V) return V
def calcModes(self, n_modes=20, turbo=True): """Calculate principal (or essential) modes. This method uses :func:`scipy.linalg.eigh`, or :func:`numpy.linalg.eigh`, function to diagonalize the covariance matrix. :arg n_modes: number of non-zero eigenvalues/vectors to calculate, default is 20, if **None** or ``'all'`` is given, all modes will be calculated :type n_modes: int :arg turbo: when available, use a memory intensive but faster way to calculate modes, default is **True** :type turbo: bool""" linalg = importLA() if self._cov is None: raise ValueError('covariance matrix is not built or set') start = time.time() dof = self._dof self._clear() if str(n_modes).lower() == 'all': n_modes = None if linalg.__package__.startswith('scipy'): if n_modes is None: eigvals = None n_modes = dof else: n_modes = int(n_modes) if n_modes >= self._dof: eigvals = None n_modes = dof else: eigvals = (dof - n_modes, dof - 1) values, vectors = linalg.eigh(self._cov, turbo=turbo, eigvals=eigvals) else: if n_modes is not None: LOGGER.info('Scipy is not found, all modes are calculated.') values, vectors = linalg.eigh(self._cov) # Order by descending SV revert = list(range(len(values) - 1, -1, -1)) values = values[revert] vectors = vectors[:, revert] which = values > 1e-8 self._eigvals = values[which] self._array = vectors[:, which] self._vars = self._eigvals self._n_modes = len(self._eigvals) LOGGER.debug('{0} modes were calculated in {1:.2f}s.'.format( self._n_modes, time.time() - start))
def superpose(self): """Superpose frame onto the trajectory reference coordinates. Note that transformation matrix is calculated based on selected atoms and applied to all atoms. If atom weights for the trajectory are set, they will be used to calculate the transformation.""" traj = self._traj indices = traj._indices ag = traj._ag if ag is None: mob = mov = self._coords else: mob = mov = ag._getCoords() weights = traj._weights if indices is None: tar = traj._coords mov = None else: tar = traj._coords[indices] mob = mob[indices] if weights is not None: weights = weights[indices] linalg = importLA() if weights is None: mob_com = mob.mean(0) mob_org = mob - mob_com tar_com = tar.mean(0) tar_org = tar - tar_com matrix = np.dot(tar_org.T, mob_org) else: weights_sum = weights.sum() weights_dot = np.dot(weights.T, weights) mob_com = (mob * weights).sum(axis=0) / weights_sum mob_org = mob - mob_com tar_com = (tar * weights).sum(axis=0) / weights_sum tar_org = tar - tar_com matrix = np.dot((tar_org * weights).T, (mob_org * weights)) / weights_dot U, s, Vh = linalg.svd(matrix) Id = np.array([ [1, 0, 0], [0, 1, 0], [0, 0, np.sign(linalg.det(matrix))] ]) rotation = np.dot(Vh.T, np.dot(Id, U.T)) if mov is None: np.add(np.dot(mob_org, rotation), tar_com, mob) else: np.add(np.dot(mov, rotation), (tar_com - np.dot(mob_com, rotation)), mov)
def calcEntropyTransfer(model, ind1, ind2, tau): """This function calculates the entropy transfer from residue indice ind1 to ind2 for a given time constant tau based on GNM. """ if not isinstance(model, NMA): raise TypeError('model must be a NMA instance') elif model.is3d(): raise TypeError('model must be a 1-dimensional NMA instance') linalg = importLA() n_atoms = model.numAtoms() n_modes = model.numModes() eigvecs = model.getEigvecs().T eigvals = model.getEigvals() tau_0 = 1 T = 0 dummy1 = 0 dummy2 = 0 for k in range(n_modes): dummy1 += 1.0 / eigvals[k] * eigvecs[k,ind2] * eigvecs[k,ind2] dummy2 += 1.0 / eigvals[k] * eigvecs[k,ind2] * eigvecs[k,ind2] * np.exp(-eigvals[k]*tau/tau_0) T += 0.5 * np.log(dummy1**2 - dummy2**2) dummy1 = 0 dummy2 = 0 dummy3 = 0 dummy4 = 0 dummy5 = 0 dummy6 = 0 dummy7 = 0 dummy8 = 0 dummy9 = 0 dummy10 = 0 for k in range(n_modes): dummy1 += 1.0 / eigvals[k] * eigvecs[k,ind1] * eigvecs[k,ind1] dummy2 += 1.0 / eigvals[k] * eigvecs[k,ind2] * eigvecs[k,ind2] dummy3 += 1.0 / eigvals[k] * eigvecs[k,ind1] * eigvecs[k,ind2] dummy4 += 1.0 / eigvals[k] * eigvecs[k,ind2] * eigvecs[k,ind2] * np.exp(-eigvals[k]*tau/tau_0) dummy5 += 1.0 / eigvals[k] * eigvecs[k,ind1] * eigvecs[k,ind2] * np.exp(-eigvals[k]*tau/tau_0) dummy9 += 1.0 / eigvals[k] * eigvecs[k,ind2] * eigvecs[k,ind2] * np.exp(-eigvals[k]*tau/tau_0) dummy6 = dummy5 dummy7 = dummy3 dummy8 = dummy2 dummy10 = dummy1 T -= 0.5 * np.log(dummy1*dummy2**2+2*dummy3*dummy4*dummy5-(dummy6**2+dummy7**2)*dummy8-dummy9**2*dummy10) T -= 0.5 * np.log(dummy2) T += 0.5 * np.log(dummy1*dummy2-dummy3**2) return T
def calcOverallNetEntropyTransfer(model, turbo=False): """This function calculates the net entropy transfer for a whole structure with a given time constant tau based on GNM. """ if not isinstance(model, NMA): raise TypeError('model must be a NMA instance') elif model.is3d(): raise TypeError('model must be a 1-dimensional NMA instance') linalg = importLA() n_atoms = model.numAtoms() n_modes = model.numModes() tau_max = 5.0 tau_step = 0.1 taus = np.arange(start=tau_step, stop=tau_max+1e-6, step=tau_step) taus = np.insert(taus,0,0.000001) numTaus = len(taus) netEntropyTransfer = np.zeros((numTaus,n_atoms,n_atoms)) if turbo: try: from joblib import Parallel, delayed import multiprocessing as mp except: LOGGER.report('joblib and multiprocessing is not imported. Running' + 'with sequential execution.') LOGGER.timeit('_ent_trans') n_cpu = mp.cpu_count() netEntropyTransfer = Parallel(n_jobs=n_cpu)(delayed(calcAllEntropyTransfer)(model,taus[i]) \ for i in range(numTaus)) netEntropyTransfer = np.asarray(netEntropyTransfer) else: LOGGER.timeit('_ent_trans') for i in range(len(taus)): netEntropyTransfer[i,:,:] = calcAllEntropyTransfer(model,taus[i]) LOGGER.report('Net Entropy Transfer calculation is completed in %.1fs.', '_ent_trans') overallNetEntropyTransfer = np.zeros((n_atoms,n_atoms)) LOGGER.timeit('_num_int') for i in range(n_atoms): for j in range(n_atoms): if i != j: overallNetEntropyTransfer[i,j] = np.trapz(netEntropyTransfer[:,i,j],taus) LOGGER.report('Numerical integration is completed in %.1fs.', '_num_int') return overallNetEntropyTransfer
def getNormDistFluct(self, coords): """Normalized distance fluctuation """ model = self.getModel() LOGGER.info('Number of chains: {0}, chains: {1}.' .format(len(list(set(coords.getChids()))), \ list(set(coords.getChids())))) try: #coords = coords.select('protein and name CA') coords = (coords._getCoords() if hasattr(coords, '_getCoords') else coords.getCoords()) except AttributeError: try: checkCoords(coords) except TypeError: raise TypeError('coords must be a Numpy array or an object ' 'with `getCoords` method') if not isinstance(model, NMA): LOGGER.info('Calculating new model') model = GNM('prot analysis') model.buildKirchhoff(coords) model.calcModes() linalg = importLA() n_atoms = model.numAtoms() n_modes = model.numModes() LOGGER.timeit('_ndf') from .analysis import calcCrossCorr from numpy import linalg as LA # <dRi, dRi>, <dRj, dRj> = 1 crossC = 2-2*calcCrossCorr(model) r_ij = np.zeros((n_atoms,n_atoms,3)) for i in range(n_atoms): for j in range(i+1,n_atoms): r_ij[i][j] = coords[j,:] - coords[i,:] r_ij[j][i] = r_ij[i][j] r_ij_n = LA.norm(r_ij, axis=2) #with np.errstate(divide='ignore'): r_ij_n[np.diag_indices_from(r_ij_n)] = 1e-5 # div by 0 crossC=abs(crossC) normdistfluct = np.divide(np.sqrt(crossC),r_ij_n) LOGGER.report('NDF calculated in %.2lfs.', label='_ndf') normdistfluct[np.diag_indices_from(normdistfluct)] = 0 # div by 0 return normdistfluct
def calcModes(self, n_modes=20, turbo=True): """Calculate principal (or essential) modes. This method uses :func:`scipy.linalg.eigh`, or :func:`numpy.linalg.eigh`, function to diagonalize the covariance matrix. :arg n_modes: number of non-zero eigenvalues/vectors to calculate, default is 20, if **None** or ``'all'`` is given, all modes will be calculated :type n_modes: int :arg turbo: when available, use a memory intensive but faster way to calculate modes, default is **True** :type turbo: bool""" linalg = importLA() if self._cov is None: raise ValueError('covariance matrix is not built or set') start = time.time() dof = self._dof self._clear() if str(n_modes).lower() == 'all': n_modes = None if linalg.__package__.startswith('scipy'): if n_modes is None: eigvals = None n_modes = dof else: n_modes = int(n_modes) if n_modes >= self._dof: eigvals = None n_modes = dof else: eigvals = (dof - n_modes, dof - 1) values, vectors = linalg.eigh(self._cov, turbo=turbo, eigvals=eigvals) else: if n_modes is not None: LOGGER.info('Scipy is not found, all modes are calculated.') values, vectors = linalg.eigh(self._cov) # Order by descending SV revert = list(range(len(values)-1, -1, -1)) values = values[revert] vectors = vectors[:, revert] which = values > 1e-8 self._eigvals = values[which] self._array = vectors[:, which] self._vars = self._eigvals self._n_modes = len(self._eigvals) LOGGER.debug('{0} modes were calculated in {1:.2f}s.' .format(self._n_modes, time.time()-start))
def getNormDistFluct(self, coords): """Normalized distance fluctuation """ model = self.getModel() LOGGER.info('Number of chains: {0}, chains: {1}.' .format(len(list(set(coords.getChids()))), \ list(set(coords.getChids())))) try: #coords = coords.select('protein and name CA') coords = (coords._getCoords() if hasattr(coords, '_getCoords') else coords.getCoords()) except AttributeError: try: checkCoords(coords) except TypeError: raise TypeError('coords must be a Numpy array or an object ' 'with `getCoords` method') if not isinstance(model, NMA): LOGGER.info('Calculating new model') model = GNM('prot analysis') model.buildKirchhoff(coords) model.calcModes() linalg = importLA() n_atoms = model.numAtoms() n_modes = model.numModes() LOGGER.timeit('_ndf') from .analysis import calcCrossCorr from numpy import linalg as LA # <dRi, dRi>, <dRj, dRj> = 1 crossC = 2 - 2 * calcCrossCorr(model) r_ij = np.zeros((n_atoms, n_atoms, 3)) for i in range(n_atoms): for j in range(i + 1, n_atoms): r_ij[i][j] = coords[j, :] - coords[i, :] r_ij[j][i] = r_ij[i][j] r_ij_n = LA.norm(r_ij, axis=2) #with np.errstate(divide='ignore'): r_ij_n[np.diag_indices_from(r_ij_n)] = 1e-5 # div by 0 crossC = abs(crossC) normdistfluct = np.divide(np.sqrt(crossC), r_ij_n) LOGGER.report('NDF calculated in %.2lfs.', label='_ndf') normdistfluct[np.diag_indices_from(normdistfluct)] = 0 # div by 0 return normdistfluct
def SCN(M, **kwargs): la = importLA() total_count = kwargs.pop('total_count', None) max_loops = kwargs.pop('max_loops', 100) tol = kwargs.pop('tol', 1e-5) N = M.copy() n = 0 d0 = None p = 1 last_p = None while True: C = np.diag(div0(1., np.sum(N, axis=0))) N = np.dot(N, C) R = np.diag(div0(1., np.sum(N, axis=1))) N = np.dot(R, N) n += 1 # check convergence of symmetry d = np.mean(np.abs(N - N.T)) if d0 is not None: p = div0(d, d0) dp = np.abs(p - last_p) if dp < tol: break else: d0 = d LOGGER.debug('Iteration {0}: d = {1}, p = {2}'.format( str(n), str(d), str(p))) last_p = p if max_loops is not None: if n >= max_loops: LOGGER.warn('The SCN algorithm did not converge after {0} ' 'iterations.'.format(max_loops)) break # guarantee symmetry N = (N + N.T) / 2. if total_count is 'original': total_count = np.sum(M) if total_count is not None: sum_N = np.sum(N) k = total_count / sum_N N = N * k return N
def SCN(M, **kwargs): la = importLA() total_count = kwargs.pop('total_count', None) max_loops = kwargs.pop('max_loops', 100) tol = kwargs.pop('tol', 1e-5) N = M.copy() n = 0 d0 = None p = 1 last_p = None while True: C = np.diag(div0(1., np.sum(N, axis=0))) N = np.dot(N, C) R = np.diag(div0(1., np.sum(N, axis=1))) N = np.dot(R, N) n += 1 # check convergence of symmetry d = np.mean(np.abs(N - N.T)) if d0 is not None: p = div0(d, d0) dp = np.abs(p - last_p) if dp < tol: break else: d0 = d LOGGER.debug('Iteration {0}: d = {1}, p = {2}'.format(str(n), str(d), str(p))) last_p = p if max_loops is not None: if n >= max_loops: LOGGER.warn('The SCN algorithm did not converge after {0} ' 'iterations.'.format(max_loops)) break # guarantee symmetry N = (N + N.T) / 2. if total_count is 'original': total_count = np.sum(M) if total_count is not None: sum_N = np.sum(N) k = total_count / sum_N N = N * k return N
def superpose(self): """Superpose frame onto the trajectory reference coordinates. Note that transformation matrix is calculated based on selected atoms and applied to all atoms. If atom weights for the trajectory are set, they will be used to calculate the transformation.""" traj = self._traj indices = traj._indices mob = mov = self._getxyz() weights = traj._weights if indices is None: tar = traj._coords mov = None else: tar = traj._coords[indices] mob = mob[indices] if weights is not None: weights = weights[indices] linalg = importLA() if weights is None: mob_com = mob.mean(0) mob_org = mob - mob_com tar_com = tar.mean(0) tar_org = tar - tar_com matrix = np.dot(tar_org.T, mob_org) else: weights_sum = weights.sum() weights_dot = np.dot(weights.T, weights) mob_com = (mob * weights).sum(axis=0) / weights_sum mob_org = mob - mob_com tar_com = (tar * weights).sum(axis=0) / weights_sum tar_org = tar - tar_com matrix = np.dot((tar_org * weights).T, (mob_org * weights)) / weights_dot U, s, Vh = linalg.svd(matrix) Id = np.array([[1, 0, 0], [0, 1, 0], [0, 0, np.sign(linalg.det(matrix))]]) rotation = np.dot(Vh.T, np.dot(Id, U.T)) if mov is None: np.add(np.dot(mob_org, rotation), tar_com, mob) else: np.add(np.dot(mov, rotation), (tar_com - np.dot(mob_com, rotation)), mov)
def calcHitTime(self, method='Z'): if self._affinity is None: self._buildAffinity() start = time.time() linalg = importLA() if method == 'Z': D = self._diagonal A = self._affinity st = D / sum(D) P = np.dot(np.diag(D**(-1)), A) W = np.ones((len(st),1)) * st.T Z = linalg.pinv(np.eye(P.shape[0], P.shape[1]) - P + W) H = np.ones((len(st),1)) * np.diag(Z).T - Z H = H / W H = H.T elif method == 'K': K = self._kirchhoff D = self._diagonal K_inv = linalg.pinv(K) sum_D = sum(D) T1 = (sum_D * np.ones((len(D),1)) * np.diag(K_inv)).T T2 = sum_D * K_inv T3_i = np.dot((np.ones((len(D),1)) * D), K_inv) H = T1 - T2 + T3_i - T3_i.T self._hitTime = H self._commuteTime = H + H.T LOGGER.debug('Hitting and commute time are calculated in {0:.2f}s.' .format(time.time()-start))
def calcAllEntropyTransfer(model, tau): """This function calculates the net entropy transfer for a whole structure with a given time constant tau based on GNM. """ if not isinstance(model, NMA): raise TypeError('model must be a NMA instance') elif model.is3d(): raise TypeError('model must be a 1-dimensional NMA instance') linalg = importLA() n_atoms = model.numAtoms() n_modes = model.numModes() entropyTransfer = np.zeros((n_atoms,n_atoms)) for i in range(n_atoms): for j in range(n_atoms): if i != j: entropyTransfer[i,j]=calcEntropyTransfer(model,i,j,tau) return entropyTransfer
def _getEigvecs(modes, row_norm=False, remove_zero_rows=False): if isinstance(modes, (ModeSet, NMA)): V = modes.getEigvecs() elif isinstance(modes, Mode): V = modes.getEigvec() elif isinstance(modes, np.ndarray): V = modes else: try: mode0 = modes[0] if isinstance(mode0, Mode): V = np.empty((len(mode0),0)) for mode in modes: assert isinstance(mode, Mode), 'Modes should be a list of modes.' v = mode.getEigvec() v = np.expand_dims(v, axis=1) V = np.hstack((V, v)) else: V = np.array(modes) except TypeError: raise TypeError('Modes should be a list of modes.') if V.ndim == 1: V = np.expand_dims(V, axis=1) # normalize the rows so that feature vectors are unit vectors if row_norm: la = importLA() norms = la.norm(V, axis=1) N = np.diag(div0(1., norms)) V = np.dot(N, V) # remove rows with all zeros m, _ = V.shape mask = np.ones(m, dtype=bool) if remove_zero_rows: mask = V.any(axis=1) V = V[mask] return V, mask
def _getEigvecs(modes, row_norm=False, remove_zero_rows=False): if isinstance(modes, (ModeSet, NMA)): V = modes.getEigvecs() elif isinstance(modes, Mode): V = modes.getEigvec() elif isinstance(modes, np.ndarray): V = modes else: try: mode0 = modes[0] if isinstance(mode0, Mode): V = np.empty((len(mode0),0)) for mode in modes: assert isinstance(mode, Mode), 'Modes should be a list of modes.' v = mode.getEigvec() v = np.expand_dims(v, axis=1) V = np.hstack((V, v)) else: V = np.array(modes) except TypeError: TypeError('Modes should be a list of modes.') if V.ndim == 1: V = np.expand_dims(V, axis=1) # normalize the rows so that feature vectors are unit vectors if row_norm: la = importLA() norms = la.norm(V, axis=1) N = np.diag(div0(1., norms)) V = np.dot(N, V) # remove rows with all zeros m, _ = V.shape mask = np.ones(m, dtype=bool) if remove_zero_rows: mask = V.any(axis=1) V = V[mask] return V, mask
def calcADPs(atom): """Calculate anisotropic displacement parameters (ADPs) from anisotropic temperature factors (ATFs). *atom* must have ATF values set for ADP calculation. ADPs are returned as a tuple, i.e. (eigenvalues, eigenvectors).""" linalg = importLA() if not isinstance(atom, Atom): raise TypeError('atom must be of type Atom, not {0:s}' .format(type(atom))) anisou = atom.getAnisou() if anisou is None: raise ValueError('atom does not have anisotropic temperature factors') element = zeros((3,3)) element[0,0] = anisou[0] element[1,1] = anisou[1] element[2,2] = anisou[2] element[0,1] = element[1,0] = anisou[3] element[0,2] = element[2,0] = anisou[4] element[1,2] = element[2,1] = anisou[5] vals, vecs = linalg.eigh(element) return vals[[2,1,0]], vecs[:, [2,1,0]]
def calcADPs(atom): """Calculate anisotropic displacement parameters (ADPs) from anisotropic temperature factors (ATFs). *atom* must have ATF values set for ADP calculation. ADPs are returned as a tuple, i.e. (eigenvalues, eigenvectors).""" linalg = importLA() if not isinstance(atom, Atom): raise TypeError('atom must be of type Atom, not {0}'.format( type(atom))) anisou = atom.getAnisou() if anisou is None: raise ValueError('atom does not have anisotropic temperature ' 'factors') element = zeros((3, 3)) element[0, 0] = anisou[0] element[1, 1] = anisou[1] element[2, 2] = anisou[2] element[0, 1] = element[1, 0] = anisou[3] element[0, 2] = element[2, 0] = anisou[4] element[1, 2] = element[2, 1] = anisou[5] vals, vecs = linalg.eigh(element) return vals[[2, 1, 0]], vecs[:, [2, 1, 0]]
# -*- coding: utf-8 -*- """This module defines a class and a function for explicit membrane ANM calculations.""" import numpy as np from prody import LOGGER, PY3K from prody.atomic import Atomic, AtomGroup from prody.utilities import importLA, checkCoords, copy from numpy import sqrt, zeros, array, ceil, dot from .anm import ANM from .gnm import checkENMParameters from .editing import _reduceModel LA = importLA() inv = LA.inv pinv = LA.pinv norm = LA.norm __all__ = ['exANM'] class exANM(ANM): """Class for explicit ANM (exANM) method ([FT00]_). Optional arguments build a membrane lattice permit analysis of membrane effect on elastic network models in *exANM* method described in [TL12]_. .. [TL12] Lezon TR, Bahar I, Constraints Imposed by the Membrane Selectively Guide the Alternating Access Dynamics of the Glutamate Transporter GltPh
def calcModes(self, n_modes=20, zeros=False, turbo=True): """Calculate normal modes. This method uses :func:`scipy.linalg.eigh` function to diagonalize the Hessian matrix. When Scipy is not found, :func:`numpy.linalg.eigh` is used. :arg n_modes: number of non-zero eigenvalues/vectors to calculate. If ``None`` is given, all modes will be calculated. :type n_modes: int or None, default is 20 :arg zeros: If ``True``, modes with zero eigenvalues will be kept. :type zeros: bool, default is ``False`` :arg turbo: Use a memory intensive, but faster way to calculate modes. :type turbo: bool, default is ``True`` """ if self._hessian is None: raise ValueError('Hessian matrix is not built or set') assert n_modes is None or isinstance(n_modes, int) and n_modes > 0, \ 'n_modes must be a positive integer' assert isinstance(zeros, bool), 'zeros must be a boolean' assert isinstance(turbo, bool), 'turbo must be a boolean' linalg = importLA() LOGGER.timeit('_anm_calc_modes') shift = 5 if linalg.__package__.startswith('scipy'): if n_modes is None: eigvals = None n_modes = self._dof else: if n_modes >= self._dof: eigvals = None n_modes = self._dof else: eigvals = (0, n_modes + shift) if eigvals: turbo = False if isinstance(self._hessian, np.ndarray): values, vectors = linalg.eigh(self._hessian, turbo=turbo, eigvals=eigvals) else: try: from scipy.sparse import linalg as scipy_sparse_la except ImportError: raise ImportError('failed to import scipy.sparse.linalg, ' 'which is required for sparse matrix ' 'decomposition') try: values, vectors = ( scipy_sparse_la.eigsh(self._hessian, k=n_modes+6, which='SA')) except: values, vectors = ( scipy_sparse_la.eigen_symmetric(self._hessian, k=n_modes+6, which='SA')) else: if n_modes is not None: LOGGER.info('Scipy is not found, all modes are calculated.') values, vectors = linalg.eigh(self._hessian) n_zeros = sum(values < ZERO) if n_zeros < 6: LOGGER.warning('Less than 6 zero eigenvalues are calculated.') shift = n_zeros - 1 elif n_zeros > 6: LOGGER.warning('More than 6 zero eigenvalues are calculated.') shift = n_zeros - 1 if zeros: shift = -1 self._eigvals = values[1+shift:] self._vars = 1 / self._eigvals self._trace = self._vars.sum() if shift: self._array = vectors[:, 1+shift:].copy() else: self._array = vectors self._n_modes = len(self._eigvals) LOGGER.report('{0} modes were calculated in %.2fs.' .format(self._n_modes), label='_anm_calc_modes')
def reduceModel(model, atoms, select): """Return reduced NMA model. Reduces a :class:`.NMA` model to a subset of *atoms* matching *select*. This function behaves differently depending on the type of the *model* argument. For :class:`.ANM` and :class:`.GNM` or other :class:`.NMA` models, force constant matrix for system of interest (specified by the *select*) is derived from the force constant matrix for the *model* by assuming that for any given displacement of the system of interest, other atoms move along in such a way as to minimize the potential energy. This is based on the formulation in [KH00]_. For :class:`.PCA` models, this function simply takes the sub-covariance matrix for selection. .. [KH00] Konrad H, Andrei-Jose P, Serge D, Marie-Claire BF, Gerald RK. Harmonicity in slow protein dynamics. *Chem Phys* **2000** 261:25-37. :arg model: dynamics model :type model: :class:`.ANM`, :class:`.GNM`, or :class:`.PCA` :arg atoms: atoms that were used to build the model :type atoms: :class:`.Atomic` :arg select: an atom selection or a selection string :type select: :class:`.Selection`, str :returns: (:class:`.NMA`, :class:`.Selection`)""" linalg = importLA() if not isinstance(model, NMA): raise TypeError('model must be an NMA instance, not {0}' .format(type(model))) if not isinstance(atoms, (AtomGroup, AtomSubset, AtomMap)): raise TypeError('atoms type is not valid') if len(atoms) <= 1: raise TypeError('atoms must contain more than 1 atoms') if isinstance(model, GNM): matrix = model._kirchhoff elif isinstance(model, ANM): matrix = model._hessian elif isinstance(model, PCA): matrix = model._cov else: raise TypeError('model does not have a valid type derived from NMA') if matrix is None: raise ValueError('model matrix (Hessian/Kirchhoff/Covariance) is not ' 'built') if isinstance(select, str): system = SELECT.getBoolArray(atoms, select) n_sel = sum(system) if n_sel == 0: raise ValueError('select matches 0 atoms') if len(atoms) == n_sel: raise ValueError('select matches all atoms') if isinstance(atoms, AtomGroup): ag = atoms which = np.arange(len(atoms))[system] else: ag = atoms.getAtomGroup() which = atoms._getIndices()[system] sel = Selection(ag, which, select, atoms.getACSIndex()) elif isinstance(select, AtomSubset): sel = select if isinstance(atoms, AtomGroup): if sel.getAtomGroup() != atoms: raise ValueError('select and atoms do not match') system = np.zeros(len(atoms), bool) system[sel._getIndices()] = True else: if atoms.getAtomGroup() != sel.getAtomGroup(): raise ValueError('select and atoms do not match') elif not sel in atoms: raise ValueError('select is not a subset of atoms') idxset = set(atoms._getIndices()) system = np.array([idx in idxset for idx in sel._getIndices()]) else: raise TypeError('select must be a string or a Selection instance') other = np.invert(system) if model.is3d(): system = np.tile(system, (3, 1)).transpose().flatten() other = np.tile(other, (3, 1)).transpose().flatten() ss = matrix[system, :][:, system] if isinstance(model, PCA): eda = PCA(model.getTitle() + ' reduced') eda.setCovariance(ss) return eda, system so = matrix[system, :][:, other] os = matrix[other, :][:, system] oo = matrix[other, :][:, other] matrix = ss - np.dot(so, np.dot(linalg.inv(oo), os)) if isinstance(model, GNM): gnm = GNM(model.getTitle() + ' reduced') gnm.setKirchhoff(matrix) return gnm, sel elif isinstance(model, ANM): anm = ANM(model.getTitle() + ' reduced') anm.setHessian(matrix) return anm, sel elif isinstance(model, PCA): eda = PCA(model.getTitle() + ' reduced') eda.setCovariance(matrix) return eda, sel
def solveEig(M, n_modes=None, zeros=False, turbo=True, is3d=False): linalg = importLA() dof = M.shape[0] expct_n_zeros = 6 if is3d else 1 if n_modes is None: eigvals = None n_modes = dof else: if n_modes >= dof: eigvals = None n_modes = dof else: eigvals = (0, n_modes+expct_n_zeros-1) def _eigh(M, eigvals=None, turbo=True): if linalg.__package__.startswith('scipy'): from scipy.sparse import issparse if eigvals: turbo = False if not issparse(M): values, vectors = linalg.eigh(M, turbo=turbo, eigvals=eigvals) else: try: from scipy.sparse import linalg as scipy_sparse_la except ImportError: raise ImportError('failed to import scipy.sparse.linalg, ' 'which is required for sparse matrix ' 'decomposition') if eigvals: j = eigvals[0] k = eigvals[-1] + 1 else: j = 0 k = dof if k >= dof: k -= 1 LOGGER.warning('Cannot calculate all eigenvalues for sparse matrices, thus ' 'the last eigenvalue is omitted. See scipy.sparse.linalg.eigsh ' 'for more information') values, vectors = scipy_sparse_la.eigsh(M, k=k, which='SA') values = values[j:k] vectors = vectors[:, j:k] else: if n_modes is not None: LOGGER.info('Scipy is not found, all modes were calculated.') else: n_modes = dof values, vectors = linalg.eigh(M) return values, vectors def _calc_n_zero_modes(M): from scipy.sparse import issparse if not issparse(M): w = linalg.eigvalsh(M) else: try: from scipy.sparse import linalg as scipy_sparse_la except ImportError: raise ImportError('failed to import scipy.sparse.linalg, ' 'which is required for sparse matrix ' 'decomposition') w, _ = scipy_sparse_la.eigsh(M, k=dof-1, which='SA') n_zeros = sum(w < ZERO) return n_zeros values, vectors = _eigh(M, eigvals, turbo) n_zeros = sum(values < ZERO) if n_zeros < n_modes + expct_n_zeros: if n_zeros < expct_n_zeros: LOGGER.warning('Fewer than %d (%d) zero eigenvalues were calculated.'%(expct_n_zeros, n_zeros)) elif n_zeros > expct_n_zeros: LOGGER.warning('More than %d (%d) zero eigenvalues were calculated.'%(expct_n_zeros, n_zeros)) else: LOGGER.warning('More than %d zero eigenvalues were detected.'%expct_n_zeros) if not zeros: if n_zeros > expct_n_zeros: if n_zeros == n_modes + expct_n_zeros and n_modes != dof: LOGGER.debug('Determing the number of zero eigenvalues...') # find the actual number of zero modes n_zeros = _calc_n_zero_modes(M) LOGGER.debug('%d zero eigenvalues detected.'%n_zeros) LOGGER.debug('Solving for additional eigenvalues...') start = min(n_modes+expct_n_zeros, dof-1); end = min(n_modes+n_zeros-1, dof-1) values_, vectors_ = _eigh(M, eigvals=(start, end)) values = np.concatenate((values, values_)) vectors = np.hstack((vectors, vectors_)) # final_n_modes may exceed len(eigvals) - no need to fix for the sake of the simplicity of the code final_n_modes = n_zeros + n_modes eigvals = values[n_zeros:final_n_modes] eigvecs = vectors[:, n_zeros:final_n_modes] vars = 1 / eigvals else: eigvals = values[:n_modes] eigvecs = vectors[:, :n_modes] vars = div0(1, values) vars[:n_zeros] = 0. vars = vars[:n_modes] return eigvals, eigvecs, vars
def calcPairDeformationDist(model, coords, ind1, ind2, kbt=1.): """Returns distribution of the deformations in the distance contributed by each mode for selected pair of residues *ind1* *ind2* using *model* from a :class:`.ANM`. Method described in [EB08]_ equation (10) and figure (2). .. [EB08] Eyal E., Bahar I. Toward a Molecular Understanding of the Anisotropic Response of Proteins to External Forces: Insights from Elastic Network Models. *Biophys J* **2008** 94:3424-34355. :arg model: this is an 3-dimensional :class:`NMA` instance from a :class:`.ANM` calculations. :type model: :class:`.ANM` :arg coords: a coordinate set or an object with :meth:`getCoords` method. Recommended: ``coords = parsePDB('pdbfile').select('protein and name CA')``. :type coords: :class:`~numpy.ndarray`. :arg ind1: first residue number. :type ind1: int :arg ind2: secound residue number. :type ind2: int """ try: resnum_list = coords.getResnums() resnam_list = coords.getResnames() coords = (coords._getCoords() if hasattr(coords, '_getCoords') else coords.getCoords()) except AttributeError: try: checkCoords(coords) except TypeError: raise TypeError('coords must be a Numpy array or an object ' 'with `getCoords` method') if not isinstance(model, NMA): raise TypeError('model must be a NMA instance') elif not model.is3d(): raise TypeError('model must be a 3-dimensional NMA instance') elif len(model) == 0: raise ValueError('model must have normal modes calculated') elif model.getStiffness() is None: raise ValueError('model must have stiffness matrix calculated') linalg = importLA() n_atoms = model.numAtoms() n_modes = model.numModes() LOGGER.timeit('_pairdef') r_ij = np.zeros((n_atoms, n_atoms, 3)) r_ij_norm = np.zeros((n_atoms, n_atoms, 3)) for i in range(n_atoms): for j in range(i + 1, n_atoms): r_ij[i][j] = coords[j, :] - coords[i, :] r_ij[j][i] = r_ij[i][j] r_ij_norm[i][j] = r_ij[i][j] / linalg.norm(r_ij[i][j]) r_ij_norm[j][i] = r_ij_norm[i][j] eigvecs = model.getEigvecs() eigvals = model.getEigvals() D_pair_k = [] mode_nr = [] ind1 = ind1 - resnum_list[0] ind2 = ind2 - resnum_list[0] for m in range(6, n_modes): U_ij_k = [(eigvecs[ind1*3][m] - eigvecs[ind2*3][m]), (eigvecs[ind1*3+1][m] \ - eigvecs[ind2*3+1][m]), (eigvecs[ind1*3+2][m] - eigvecs[ind2*3+2][m])] D_ij_k = abs( sqrt(kbt / eigvals[m]) * (np.vdot(r_ij_norm[ind1][ind2], U_ij_k))) D_pair_k.append(D_ij_k) mode_nr.append(m) LOGGER.report('Deformation was calculated in %.2lfs.', label='_pairdef') return mode_nr, D_pair_k
def reduceModel(model, atoms, select): """Returns reduced NMA model. Reduces a :class:`.NMA` model to a subset of *atoms* matching *select*. This function behaves differently depending on the type of the *model* argument. For :class:`.ANM` and :class:`.GNM` or other :class:`.NMA` models, force constant matrix for system of interest (specified by the *select*) is derived from the force constant matrix for the *model* by assuming that for any given displacement of the system of interest, other atoms move along in such a way as to minimize the potential energy. This is based on the formulation in [KH00]_. For :class:`.PCA` models, this function simply takes the sub-covariance matrix for selection. .. [KH00] Hinsen K, Petrescu A-J, Dellerue S, Bellissent-Funel M-C, Kneller GR. Harmonicity in slow protein dynamics. *Chem Phys* **2000** 261:25-37. :arg model: dynamics model :type model: :class:`.ANM`, :class:`.GNM`, or :class:`.PCA` :arg atoms: atoms that were used to build the model :type atoms: :class:`.Atomic` :arg select: an atom selection or a selection string :type select: :class:`.Selection`, str :returns: (:class:`.NMA`, :class:`.Selection`)""" linalg = importLA() if not isinstance(model, NMA): raise TypeError('model must be an NMA instance, not {0}'.format( type(model))) if not isinstance(atoms, (AtomGroup, AtomSubset, AtomMap)): raise TypeError('atoms type is not valid') if len(atoms) <= 1: raise TypeError('atoms must contain more than 1 atoms') if isinstance(model, GNM): matrix = model._kirchhoff elif isinstance(model, ANM): matrix = model._hessian elif isinstance(model, PCA): matrix = model._cov else: raise TypeError('model does not have a valid type derived from NMA') if matrix is None: raise ValueError('model matrix (Hessian/Kirchhoff/Covariance) is not ' 'built') which, select = sliceAtoms(atoms, select) system = np.zeros(model.numAtoms(), dtype=bool) system[which] = True if model.is3d(): system = np.repeat(system, 3) if isinstance(model, PCA): ss = matrix[system, :][:, system] eda = PCA(model.getTitle() + ' reduced') eda.setCovariance(ss) return eda, system else: matrix = _reduceModel(matrix, system) if isinstance(model, GNM): gnm = GNM(model.getTitle() + ' reduced') gnm.setKirchhoff(matrix) return gnm, select elif isinstance(model, ANM): anm = ANM(model.getTitle() + ' reduced') anm.setHessian(matrix) return anm, select elif isinstance(model, PCA): eda = PCA(model.getTitle() + ' reduced') eda.setCovariance(matrix) return eda, select
from .functions import calcENM from .modeset import ModeSet __all__ = [ 'calcAdaptiveANM', 'AANM_ONEWAY', 'AANM_ALTERNATING', 'AANM_BOTHWAYS', 'AANM_DEFAULT' ] AANM_ALTERNATING = 0 AANM_ONEWAY = 1 AANM_BOTHWAYS = 2 AANM_DEFAULT = AANM_ALTERNATING norm = importLA().norm def checkInput(a, b, **kwargs): coordsA = getCoords(a) if isinstance(a, Atomic): title = a.getTitle() atoms = a else: title = None atoms = None coordsB = getCoords(b) if title is None: if isinstance(b, Atomic):
def calcModes(self, n_modes=20, zeros=False, turbo=True): """Calculate normal modes. This method uses :func:`scipy.linalg.eigh` function to diagonalize the Hessian matrix. When Scipy is not found, :func:`numpy.linalg.eigh` is used. :arg n_modes: number of non-zero eigenvalues/vectors to calculate. If ``None`` is given, all modes will be calculated. :type n_modes: int or None, default is 20 :arg zeros: If ``True``, modes with zero eigenvalues will be kept. :type zeros: bool, default is ``False`` :arg turbo: Use a memory intensive, but faster way to calculate modes. :type turbo: bool, default is ``True`` """ if self._hessian is None: raise ValueError("Hessian matrix is not built or set") assert n_modes is None or isinstance(n_modes, int) and n_modes > 0, "n_modes must be a positive integer" assert isinstance(zeros, bool), "zeros must be a boolean" assert isinstance(turbo, bool), "turbo must be a boolean" linalg = importLA() start = time.time() shift = 5 if linalg.__package__.startswith("scipy"): if n_modes is None: eigvals = None n_modes = self._dof else: if n_modes >= self._dof: eigvals = None n_modes = self._dof else: eigvals = (0, n_modes + shift) if eigvals: turbo = False if isinstance(self._hessian, np.ndarray): values, vectors = linalg.eigh(self._hessian, turbo=turbo, eigvals=eigvals) else: try: from scipy.sparse import linalg as scipy_sparse_la except ImportError: raise ImportError( "failed to import scipy.sparse.linalg, " "which is required for sparse matrix " "decomposition" ) try: values, vectors = scipy_sparse_la.eigsh(self._hessian, k=n_modes + 6, which="SA") except: values, vectors = scipy_sparse_la.eigen_symmetric(self._hessian, k=n_modes + 6, which="SA") else: if n_modes is not None: LOGGER.info("Scipy is not found, all modes are calculated.") values, vectors = linalg.eigh(self._hessian) n_zeros = sum(values < ZERO) if n_zeros < 6: LOGGER.warning("Less than 6 zero eigenvalues are calculated.") shift = n_zeros - 1 elif n_zeros > 6: LOGGER.warning("More than 6 zero eigenvalues are calculated.") shift = n_zeros - 1 if zeros: shift = -1 self._eigvals = values[1 + shift :] self._vars = 1 / self._eigvals self._trace = self._vars.sum() self._array = vectors[:, 1 + shift :] self._n_modes = len(self._eigvals) LOGGER.debug("{0} modes were calculated in {1:.2f}s.".format(self._n_modes, time.time() - start))
from prody import LOGGER from .anm import ANM from .gnm import GNM, ZERO from .rtb import RTB from .imanm import imANM from .exanm import exANM from .editing import extendModel from .sampling import sampleModes from prody.atomic import AtomGroup from prody.measure import calcTransformation, applyTransformation, calcRMSD from prody.ensemble import Ensemble from prody.proteins import writePDB, parsePDB, writePDBStream, parsePDBStream from prody.utilities import createStringIO, importLA, mad la = importLA() norm = la.norm __all__ = ['ClustENM', 'ClustRTB', 'ClustImANM', 'ClustExANM'] class ClustENM(Ensemble): ''' ClustENMv2 is the new version of ClustENM(v1) conformation sampling algorithm [KZ16]_. This ANM-based hybrid algorithm requires PDBFixer and OpenMM for performing energy minimization and MD simulations in implicit/explicit solvent. It is Python 3.6 compatible and has been only tested on Linux machines. .. [KZ16] Kurkcuoglu Z., Bahar I., Doruker P., ClustENM: ENM-based sampling of essential conformational space at full atomic resolution. *J Chem* **2016** 12(9):4549-4562. .. [PE17] Eastman P., Swails J., Chodera J.D., McGibbon R.T., Zhao Y., Beauchamp K.A., Wang L.P., Simmonett A.C., Harrigan M.P., Stern C.D., Wiewiora R.P., Brooks B.R., Pande V.S., OpenMM 7: Rapid Development of High Performance Algorithms for Molecular Dynamics. *PLoS Comput Biol* **2017** 13:e1005659.
# -*- coding: utf-8 -*- """ This module defines a class for identifying contacts.""" import numpy as np from prody import LOGGER from prody.atomic import AtomPointer from prody.utilities import importLA from .measure import calcCenter linalg = importLA() __all__ = ['Transformation', 'applyTransformation', 'alignCoordsets', 'calcRMSD', 'calcTransformation', 'superpose', 'moveAtoms', 'wrapAtoms', 'printRMSD'] class Transformation(object): """A class for storing a transformation matrix.""" __slots__ = ['_matrix'] def __init__(self, *args): """Either 4x4 transformation *matrix*, or *rotation* matrix and *translation* vector must be provided at instantiation.""" nargs = len(args) if nargs == 1:
def calcModes(self, n_modes=20, zeros=False, turbo=True, hinges=True): """Calculate normal modes. This method uses :func:`scipy.linalg.eigh` function to diagonalize the Kirchhoff matrix. When Scipy is not found, :func:`numpy.linalg.eigh` is used. :arg n_modes: number of non-zero eigenvalues/vectors to calculate. If ``None`` is given, all modes will be calculated. :type n_modes: int or None, default is 20 :arg zeros: If ``True``, modes with zero eigenvalues will be kept. :type zeros: bool, default is ``False`` :arg turbo: Use a memory intensive, but faster way to calculate modes. :type turbo: bool, default is ``True`` :arg hinges: Identify hinge sites after modes are computed. :type hinges: bool, default is ``True`` """ if self._kirchhoff is None: raise ValueError('Kirchhoff matrix is not built or set') assert n_modes is None or isinstance(n_modes, int) and n_modes > 0, \ 'n_modes must be a positive integer' assert isinstance(zeros, bool), 'zeros must be a boolean' assert isinstance(turbo, bool), 'turbo must be a boolean' linalg = importLA() start = time.time() shift = 0 if linalg.__package__.startswith('scipy'): if n_modes is None: eigvals = None n_modes = self._dof else: if n_modes >= self._dof: eigvals = None n_modes = self._dof else: eigvals = (0, n_modes + shift) if eigvals: turbo = False if isinstance(self._kirchhoff, np.ndarray): values, vectors = linalg.eigh(self._kirchhoff, turbo=turbo, eigvals=eigvals) else: try: from scipy.sparse import linalg as scipy_sparse_la except ImportError: raise ImportError('failed to import scipy.sparse.linalg, ' 'which is required for sparse matrix ' 'decomposition') try: values, vectors = ( scipy_sparse_la.eigsh(self._kirchhoff, k=n_modes + 1, which='SA')) except: values, vectors = ( scipy_sparse_la.eigen_symmetric(self._kirchhoff, k=n_modes + 1, which='SA')) else: if n_modes is not None: LOGGER.info('Scipy is not found, all modes are calculated.') values, vectors = linalg.eigh(self._kirchhoff) n_zeros = sum(values < ZERO) if n_zeros < 1: LOGGER.warning('Less than 1 zero eigenvalues are calculated.') shift = n_zeros - 1 elif n_zeros > 1: LOGGER.warning('More than 1 zero eigenvalues are calculated.') shift = n_zeros - 1 if zeros: shift = -1 self._eigvals = values[1+shift:] self._vars = 1 / self._eigvals self._trace = self._vars.sum() self._array = vectors[:, 1+shift:] self._n_modes = len(self._eigvals) if hinges: self.calcHinges() LOGGER.debug('{0} modes were calculated in {1:.2f}s.' .format(self._n_modes, time.time()-start))
def calcModes(self, n_modes=20, zeros=False, turbo=True): """Calculate normal modes. This method uses :func:`scipy.linalg.eigh` function to diagonalize the Hessian matrix. When Scipy is not found, :func:`numpy.linalg.eigh` is used. :arg n_modes: number of non-zero eigenvalues/vectors to calculate. If **None** or ``'all'`` is given, all modes will be calculated. :type n_modes: int or None, default is 20 :arg zeros: If **True**, modes with zero eigenvalues will be kept. :type zeros: bool, default is **True** :arg turbo: Use a memory intensive, but faster way to calculate modes. :type turbo: bool, default is **True** """ if self._hessian is None: raise ValueError('Hessian matrix is not built or set') if str(n_modes).lower() == 'all': n_modes = None assert n_modes is None or isinstance(n_modes, int) and n_modes > 0, \ 'n_modes must be a positive integer' assert isinstance(zeros, bool), 'zeros must be a boolean' assert isinstance(turbo, bool), 'turbo must be a boolean' self._clear() linalg = importLA() LOGGER.timeit('_anm_calc_modes') shift = 5 if linalg.__package__.startswith('scipy'): if n_modes is None: eigvals = None n_modes = self._dof else: if n_modes >= self._dof: eigvals = None n_modes = self._dof else: eigvals = (0, n_modes + shift) if eigvals: turbo = False if isinstance(self._hessian, np.ndarray): values, vectors = linalg.eigh(self._hessian, turbo=turbo, eigvals=eigvals) else: try: from scipy.sparse import linalg as scipy_sparse_la except ImportError: raise ImportError('failed to import scipy.sparse.linalg, ' 'which is required for sparse matrix ' 'decomposition') try: values, vectors = ( scipy_sparse_la.eigsh(self._hessian, k=n_modes+6, which='SA')) except: values, vectors = ( scipy_sparse_la.eigen_symmetric(self._hessian, k=n_modes+6, which='SA')) else: if n_modes is not None: LOGGER.info('Scipy is not found, all modes are calculated.') values, vectors = np.linalg.eigh(self._hessian) n_zeros = sum(values < ZERO) if n_zeros < 6: LOGGER.warning('Less than 6 zero eigenvalues are calculated.') shift = n_zeros - 1 elif n_zeros > 6: LOGGER.warning('More than 6 zero eigenvalues are calculated.') shift = n_zeros - 1 if zeros: shift = -1 if n_zeros > n_modes: self._eigvals = values[1+shift:] else: self._eigvals = values[1+shift:] self._vars = 1 / self._eigvals self._trace = self._vars.sum() if shift: self._array = vectors[:, 1+shift:].copy() else: self._array = vectors self._n_modes = len(self._eigvals) LOGGER.report('{0} modes were calculated in %.2fs.' .format(self._n_modes), label='_anm_calc_modes')
def showEmbedding(modes, labels=None, trace=True, headtail=True, cmap='prism'): """Visualizes Laplacian embedding of Hi-C data. :arg modes: modes in which loci are embedded. It can only have 2 or 3 modes for the purpose of visualization. :type modes: :class:`ModeSet` :arg labels: a list of integers indicating the segmentation of the sequence. :type labels: list :arg trace: if **True** then the trace of the sequence will be indicated by a grey dashed line. :type trace: bool :arg headtail: if **True** then a star and a closed circle will indicate the head and the tail of the sequence respectively. :type headtail: bool :arg cmap: the color map used to render the *labels*. :type cmap: str """ V, _ = _getEigvecs(modes, True) m, n = V.shape if labels is not None: if len(labels) != m: raise ValueError( 'Modes (%d) and the Hi-C map (%d) should have the same number' ' of atoms. Turn off "masked" if you intended to apply the' ' modes to the full map.' % (m, len(labels))) if n > 3: raise ValueError( 'This function can only visualize the embedding of 2 or 3 modes.') from matplotlib.pyplot import figure, plot, scatter from mpl_toolkits.mplot3d import Axes3D if n == 2: la = importLA() X, Y = V[:, :2].T R = np.array(range(len(X))) R = R / la.norm(R) X *= R Y *= R f = figure() if trace: plot(X, Y, ':', color=[0.3, 0.3, 0.3]) if labels is None: C = 'b' else: C = labels scatter(X, Y, s=30, c=C, cmap=cmap) if headtail: plot(X[:1], Y[:1], 'k*', markersize=12) plot(X[-1:], Y[-1:], 'ko', markersize=12) elif n == 3: X, Y, Z = V[:, :3].T f = figure() ax = Axes3D(f) if trace: ax.plot(X, Y, Z, ':', color=[0.3, 0.3, 0.3]) if labels is None: C = 'b' else: C = labels ax.scatter(X, Y, Z, s=30, c=C, depthshade=True, cmap=cmap) if headtail: ax.plot(X[:1], Y[:1], Z[:1], 'k*', markersize=12) ax.plot(X[-1:], Y[-1:], Z[-1:], 'ko', markersize=12) if SETTINGS['auto_show']: showFigure() return f
def calcModes(self, n_modes=20, zeros=False, turbo=True, hinges=True): """Calculate normal modes. This method uses :func:`scipy.linalg.eigh` function to diagonalize the Kirchhoff matrix. When Scipy is not found, :func:`numpy.linalg.eigh` is used. :arg n_modes: number of non-zero eigenvalues/vectors to calculate. If **None** or ``'all'`` is given, all modes will be calculated. :type n_modes: int or None, default is 20 :arg zeros: If **True**, modes with zero eigenvalues will be kept. :type zeros: bool, default is **True** :arg turbo: Use a memory intensive, but faster way to calculate modes. :type turbo: bool, default is **True** :arg hinges: Identify hinge sites after modes are computed. :type hinges: bool, default is **True** """ if self._kirchhoff is None: raise ValueError('Kirchhoff matrix is not built or set') if str(n_modes).lower() == 'all': n_modes = None assert n_modes is None or isinstance(n_modes, int) and n_modes > 0, \ 'n_modes must be a positive integer' assert isinstance(zeros, bool), 'zeros must be a boolean' assert isinstance(turbo, bool), 'turbo must be a boolean' self._clear() linalg = importLA() start = time.time() shift = 0 if linalg.__package__.startswith('scipy'): if n_modes is None: eigvals = None n_modes = self._dof else: if n_modes >= self._dof: eigvals = None n_modes = self._dof else: eigvals = (0, n_modes + shift) if eigvals: turbo = False if isinstance(self._kirchhoff, np.ndarray): values, vectors = linalg.eigh(self._kirchhoff, turbo=turbo, eigvals=eigvals) else: try: from scipy.sparse import linalg as scipy_sparse_la except ImportError: raise ImportError('failed to import scipy.sparse.linalg, ' 'which is required for sparse matrix ' 'decomposition') try: values, vectors = ( scipy_sparse_la.eigsh(self._kirchhoff, k=n_modes + 1, which='SA')) except: values, vectors = ( scipy_sparse_la.eigen_symmetric(self._kirchhoff, k=n_modes + 1, which='SA')) else: if n_modes is not None: LOGGER.info('Scipy is not found, all modes are calculated.') values, vectors = linalg.eigh(self._kirchhoff) n_zeros = sum(values < ZERO) if n_zeros < 1: LOGGER.warning('Less than 1 zero eigenvalues are calculated.') shift = n_zeros - 1 elif n_zeros > 1: LOGGER.warning('More than 1 zero eigenvalues are calculated.') shift = n_zeros - 1 if zeros: shift = -1 self._eigvals = values[1+shift:] self._vars = 1 / self._eigvals self._trace = self._vars.sum() self._array = vectors[:, 1+shift:] self._n_modes = len(self._eigvals) if hinges: self.calcHinges() LOGGER.debug('{0} modes were calculated in {1:.2f}s.' .format(self._n_modes, time.time()-start))
def showEmbedding(modes, labels=None, trace=True, headtail=True, cmap='prism'): """Visualizes Laplacian embedding of Hi-C data. :arg modes: modes in which loci are embedded. It can only have 2 or 3 modes for the purpose of visualization. :type modes: :class:`ModeSet` :arg labels: a list of integers indicating the segmentation of the sequence. :type labels: list :arg trace: if **True** then the trace of the sequence will be indicated by a grey dashed line. :type trace: bool :arg headtail: if **True** then a star and a closed circle will indicate the head and the tail of the sequence respectively. :type headtail: bool :arg cmap: the color map used to render the *labels*. :type cmap: str """ V, mask = _getEigvecs(modes, True) m,n = V.shape if labels is not None: if len(labels) != m: raise ValueError('Modes (%d) and the Hi-C map (%d) should have the same number' ' of atoms. Turn off "masked" if you intended to apply the' ' modes to the full map.' %(m, len(labels))) if n > 3: raise ValueError('This function can only visualize the embedding of 2 or 3 modes.') from matplotlib.pyplot import figure, plot, scatter from mpl_toolkits.mplot3d import Axes3D if n == 2: la = importLA() X, Y = V[:,:2].T R = np.array(range(len(X))) R = R / la.norm(R) X *= R; Y *= R f = figure() if trace: plot(X, Y, ':', color=[0.3, 0.3, 0.3]) if labels is None: C = 'b' else: C = labels scatter(X, Y, s=30, c=C, cmap=cmap) if headtail: plot(X[:1], Y[:1], 'k*', markersize=12) plot(X[-1:], Y[-1:], 'ko', markersize=12) elif n == 3: X, Y, Z = V[:,:3].T f = figure() ax = Axes3D(f) if trace: ax.plot(X, Y, Z, ':', color=[0.3, 0.3, 0.3]) if labels is None: C = 'b' else: C = labels ax.scatter(X, Y, Z, s=30, c=C, depthshade=True, cmap=cmap) if headtail: ax.plot(X[:1], Y[:1], Z[:1], 'k*', markersize=12) ax.plot(X[-1:], Y[-1:], Z[-1:], 'ko', markersize=12) if SETTINGS['auto_show']: showFigure() return f
def calcPairDeformationDist(model, coords, ind1, ind2, kbt=1.): """Returns distribution of the deformations in the distance contributed by each mode for selected pair of residues *ind1* *ind2* using *model* from a :class:`.ANM`. Method described in [EB08]_ equation (10) and figure (2). .. [EB08] Eyal E., Bahar I. Toward a Molecular Understanding of the Anisotropic Response of Proteins to External Forces: Insights from Elastic Network Models. *Biophys J* **2008** 94:3424-34355. :arg model: this is an 3-dimensional NMA instance from a :class:`.ANM calculations. :type model: :class:`.ANM` :arg coords: a coordinate set or an object with ``getCoords`` method. Recommended: coords = parsePDB('pdbfile').select('protein and name CA'). :type coords: :class:`numpy.ndarray`. :arg ind1: first residue number. :type ind1: int :arg ind2: secound residue number. :type ind2: int """ try: resnum_list = coords.getResnums() resnam_list = coords.getResnames() coords = (coords._getCoords() if hasattr(coords, '_getCoords') else coords.getCoords()) except AttributeError: try: checkCoords(coords) except TypeError: raise TypeError('coords must be a Numpy array or an object ' 'with `getCoords` method') if not isinstance(model, NMA): raise TypeError('model must be a NMA instance') elif not model.is3d(): raise TypeError('model must be a 3-dimensional NMA instance') elif len(model) == 0: raise ValueError('model must have normal modes calculated') elif model.getStiffness() is None: raise ValueError('model must have stiffness matrix calculated') linalg = importLA() n_atoms = model.numAtoms() n_modes = model.numModes() LOGGER.timeit('_pairdef') r_ij = np.zeros((n_atoms,n_atoms,3)) r_ij_norm = np.zeros((n_atoms,n_atoms,3)) for i in range(n_atoms): for j in range(i+1,n_atoms): r_ij[i][j] = coords[j,:] - coords[i,:] r_ij[j][i] = r_ij[i][j] r_ij_norm[i][j] = r_ij[i][j]/linalg.norm(r_ij[i][j]) r_ij_norm[j][i] = r_ij_norm[i][j] eigvecs = model.getEigvecs() eigvals = model.getEigvals() D_pair_k = [] mode_nr = [] ind1 = ind1 - resnum_list[0] ind2 = ind2 - resnum_list[0] for m in xrange(6,n_modes): U_ij_k = [(eigvecs[ind1*3][m] - eigvecs[ind2*3][m]), (eigvecs[ind1*3+1][m] \ - eigvecs[ind2*3+1][m]), (eigvecs[ind1*3+2][m] - eigvecs[ind2*3+2][m])] D_ij_k = abs(np.sqrt(kbt/eigvals[m])*(np.vdot(r_ij_norm[ind1][ind2], U_ij_k))) D_pair_k.append(D_ij_k) mode_nr.append(m) LOGGER.report('Deformation was calculated in %.2lfs.', label='_pairdef') return mode_nr, D_pair_k
def calcPairDeformationDist(model, coords, ind1, ind2, kbt=1., saveFile=False, filename='out', savePlot=False): """Return distribution of the deformations in the distance contributed by each mode for selected pair of residues *ind1* *ind2* using *model* from a :class:`.ANM`. Method described in [EB08]_ equation (10) and figure (2). .. [EB08] Eyal E., Bahar I. Toward a Molecular Understanding of the Anisotropic Response of Proteins to External Forces: Insights from Elastic Network Models. *Biophys J* **2008** 94:3424-34355. :arg model: this is an 3-dimensional NMA instance from a :class:`.ANM calculations. :type model: :class:`.ANM` :arg coords: a coordinate set or an object with ``getCoords`` method. Recommended: coords = parsePDB('pdbfile').select('protein and name CA'). :type coords: :class:`numpy.ndarray`. :arg ind1: first residue number. :type ind1: int :arg ind2: secound residue number. :type ind2: int By default results will not be saved to a *filename* file. To save plot and data file use ``saveFile=True`` and ``savePlot=True``. """ try: resnum_list = coords.getResnums() resnam_list = coords.getResnames() coords = (coords._getCoords() if hasattr(coords, '_getCoords') else coords.getCoords()) except AttributeError: try: checkCoords(coords) except TypeError: raise TypeError('coords must be a Numpy array or an object ' 'with `getCoords` method') if not isinstance(model, NMA): raise TypeError('model must be an NMA instance') elif not model.is3d(): raise TypeError('model must be a 3-dimensional NMA instance') elif len(model) == 0: raise ValueError('model must have normal modes calculated') elif model.getStiffness() is None: raise ValueError('model must have stiffness matrix calculated') linalg = importLA() n_atoms = model.numAtoms() n_modes = model.numModes() LOGGER.timeit('_pairdef') r_ij = np.zeros((n_atoms,n_atoms,3)) r_ij_norm = np.zeros((n_atoms,n_atoms,3)) for i in range(n_atoms): for j in range(i+1,n_atoms): r_ij[i][j] = coords[j,:] - coords[i,:] r_ij[j][i] = r_ij[i][j] r_ij_norm[i][j] = r_ij[i][j]/linalg.norm(r_ij[i][j]) r_ij_norm[j][i] = r_ij_norm[i][j] eigvecs = model.getEigvecs() eigvals = model.getEigvals() D_pair_k = [] mode_nr = [] ind1 = ind1 - resnum_list[0] ind2 = ind2 - resnum_list[0] for m in xrange(6,n_modes): U_ij_k = [(eigvecs[ind1*3][m] - eigvecs[ind2*3][m]), (eigvecs[ind1*3+1][m] \ - eigvecs[ind2*3+1][m]), (eigvecs[ind1*3+2][m] - eigvecs[ind2*3+2][m])] D_ij_k = abs(np.sqrt(kbt/eigvals[m])*(np.vdot(r_ij_norm[ind1][ind2], U_ij_k))) D_pair_k.append(D_ij_k) mode_nr.append(m) LOGGER.report('Deformation was calculated in %.2lfs.', label='_pairdef') if(saveFile == True): fout = open(filename+".txt", 'w') for i in xrange(len(mode_nr)): fout.write("{} {}\n".format(mode_nr[i], D_pair_k[i])) fout.close() LOGGER.info('Data file has been saved.') if(savePlot == True): import matplotlib import matplotlib.pylab as plt matplotlib.rcParams['font.size'] = '16' fig = plt.figure(num=None, figsize=(12,8), dpi=100, facecolor='w') plt.plot(mode_nr, D_pair_k) plt.xlabel('mode (k)', fontsize = '18') plt.ylabel('d$^k$' '($\AA$)', fontsize = '18') plt.savefig(filename+'.png', dpi=100) LOGGER.info('Plot has been saved.') return mode_nr, D_pair_k
def calcADPAxes(atoms, **kwargs): """Return a 3Nx3 array containing principal axes defining anisotropic displacement parameter (ADP, or anisotropic temperature factor) ellipsoids. :arg atoms: a ProDy object for handling atomic data :type atoms: :class:`~.Atomic` :kwarg fract: For an atom, if the fraction of anisotropic displacement explained by its largest axis/eigenvector is less than given value, all axes for that atom will be set to zero. Values larger than 0.33 and smaller than 1.0 are accepted. :type fract: float :kwarg ratio2: For an atom, if the ratio of the second-largest eigenvalue to the largest eigenvalue axis less than or equal to the given value, all principal axes for that atom will be returned. Values less than 1 and greater than 0 are accepted. :type ratio2: float :kwarg ratio3: For an atom, if the ratio of the smallest eigenvalue to the largest eigenvalue is less than or equal to the given value, all principal axes for that atom will be returned. Values less than 1 and greater than 0 are accepted. :type ratio3: float :kwarg ratio: Same as *ratio3*. :type ratio: float Keyword arguments *fract*, *ratio3*, or *ratio3* can be used to set principal axes to 0 for atoms showing relatively lower degree of anisotropy. 3Nx3 axis contains N times 3x3 matrices, one for each given atom. Columns of these 3x3 matrices are the principal axes which are weighted by square root of their eigenvalues. The first columns correspond to largest principal axes. The direction of the principal axes for an atom is determined based on the correlation of the axes vector with the principal axes vector of the previous atom. .. ipython:: python from prody import * protein = parsePDB('1ejg') calphas = protein.select('calpha') adp_axes = calcADPAxes( calphas ) adp_axes.shape These can be written in NMD format as follows: .. ipython:: python nma = NMA('ADPs') nma.setEigens(adp_axes) nma writeNMD('adp_axes.nmd', nma, calphas)""" linalg = importLA() if not isinstance(atoms, Atomic): raise TypeError('atoms must be of type Atomic, not {0}'.format( type(atoms))) anisous = atoms.getAnisous() if anisous is None: raise ValueError('anisotropic temperature factors are not set') n_atoms = atoms.numAtoms() axes = zeros((n_atoms * 3, 3)) variances = zeros((n_atoms, 3)) stddevs = zeros((n_atoms, 3)) anisou = anisous[0] element = zeros((3, 3)) element[0, 0] = anisou[0] element[1, 1] = anisou[1] element[2, 2] = anisou[2] element[0, 1] = element[1, 0] = anisou[3] element[0, 2] = element[2, 0] = anisou[4] element[1, 2] = element[2, 1] = anisou[5] vals, vecs = linalg.eigh(element) # If negative eigenvalues are found (when ADP matrix is not positive # definite) set them to 0 vals[vals < 0] = 0 variances[0] = vals vals = vals**0.5 stddevs[0] = vals axes[0:3, :] = vals * vecs for i in range(1, n_atoms): anisou = anisous[i] element[0, 0] = anisou[0] element[1, 1] = anisou[1] element[2, 2] = anisou[2] element[0, 1] = element[1, 0] = anisou[3] element[0, 2] = element[2, 0] = anisou[4] element[1, 2] = element[2, 1] = anisou[5] vals, vecs = linalg.eigh(element) # If negative eigenvalues are found (when ADP matrix is not positive # definite) set them to 0 vals[vals < 0] = 0 variances[i] = vals vals = vals**0.5 stddevs[i] = vals # Make sure the direction that correlates with the previous atom # is selected vals = vals * sign((vecs * axes[(i - 1) * 3:(i) * 3, :]).sum(0)) axes[i * 3:(i + 1) * 3, :] = vals * vecs # Resort the columns before returning array axes = axes[:, [2, 1, 0]] torf = None if 'fract' in kwargs: fract = float(kwargs['fract']) assert 0.33 < fract < 1.0, 'fract must be > 0.33 and < 1.0' variances = variances[:, [2, 1, 0]] torf = variances[:, 0] / variances.sum(1) > fract elif 'ratio' in kwargs or 'ratio3' in kwargs or 'ratio2' in kwargs: if 'ratio2' in kwargs: ratio = float(kwargs['ratio2']) assert 0 < ratio < 1.0, 'ratio2 must be > 0 and < 1.0' dim = 1 else: ratio = float(kwargs.get('ratio', kwargs.get('ratio3'))) assert 0 < ratio < 1.0, 'ratio or ratio3 must be > 0 and < 1.0' dim = 2 variances = variances[:, [2, 1, 0]] torf = variances[:, dim] / variances[:, 0] <= ratio if torf is not None: torf = tile(torf.reshape((n_atoms, 1)), (1, 3)).reshape( (n_atoms * 3, 1)) axes = axes * torf return axes
def calcADPAxes(atoms, **kwargs): """Returns a 3Nx3 array containing principal axes defining anisotropic displacement parameter (ADP, or anisotropic temperature factor) ellipsoids. :arg atoms: a ProDy object for handling atomic data :type atoms: :class:`~.Atomic` :kwarg fract: For an atom, if the fraction of anisotropic displacement explained by its largest axis/eigenvector is less than given value, all axes for that atom will be set to zero. Values larger than 0.33 and smaller than 1.0 are accepted. :type fract: float :kwarg ratio2: For an atom, if the ratio of the second-largest eigenvalue to the largest eigenvalue axis less than or equal to the given value, all principal axes for that atom will be returned. Values less than 1 and greater than 0 are accepted. :type ratio2: float :kwarg ratio3: For an atom, if the ratio of the smallest eigenvalue to the largest eigenvalue is less than or equal to the given value, all principal axes for that atom will be returned. Values less than 1 and greater than 0 are accepted. :type ratio3: float :kwarg ratio: Same as *ratio3*. :type ratio: float Keyword arguments *fract*, *ratio3*, or *ratio3* can be used to set principal axes to 0 for atoms showing relatively lower degree of anisotropy. 3Nx3 axis contains N times 3x3 matrices, one for each given atom. Columns of these 3x3 matrices are the principal axes which are weighted by square root of their eigenvalues. The first columns correspond to largest principal axes. The direction of the principal axes for an atom is determined based on the correlation of the axes vector with the principal axes vector of the previous atom. .. ipython:: python from prody import * protein = parsePDB('1ejg') calphas = protein.select('calpha') adp_axes = calcADPAxes( calphas ) adp_axes.shape These can be written in NMD format as follows: .. ipython:: python nma = NMA('ADPs') nma.setEigens(adp_axes) nma writeNMD('adp_axes.nmd', nma, calphas)""" linalg = importLA() if not isinstance(atoms, Atomic): raise TypeError('atoms must be of type Atomic, not {0}' .format(type(atoms))) anisous = atoms.getAnisous() if anisous is None: raise ValueError('anisotropic temperature factors are not set') n_atoms = atoms.numAtoms() axes = zeros((n_atoms*3, 3)) variances = zeros((n_atoms, 3)) stddevs = zeros((n_atoms, 3)) anisou = anisous[0] element = zeros((3, 3)) element[0, 0] = anisou[0] element[1, 1] = anisou[1] element[2, 2] = anisou[2] element[0, 1] = element[1, 0] = anisou[3] element[0, 2] = element[2, 0] = anisou[4] element[1, 2] = element[2, 1] = anisou[5] vals, vecs = linalg.eigh(element) # If negative eigenvalues are found (when ADP matrix is not positive # definite) set them to 0 vals[vals < 0] = 0 variances[0] = vals vals = vals**0.5 stddevs[0] = vals axes[0:3, :] = vals * vecs for i in range(1, n_atoms): anisou = anisous[i] element[0, 0] = anisou[0] element[1, 1] = anisou[1] element[2, 2] = anisou[2] element[0, 1] = element[1, 0] = anisou[3] element[0, 2] = element[2, 0] = anisou[4] element[1, 2] = element[2, 1] = anisou[5] vals, vecs = linalg.eigh(element) # If negative eigenvalues are found (when ADP matrix is not positive # definite) set them to 0 vals[vals < 0] = 0 variances[i] = vals vals = vals**0.5 stddevs[i] = vals # Make sure the direction that correlates with the previous atom # is selected vals = vals * sign((vecs * axes[(i-1)*3:(i)*3, :]).sum(0)) axes[i*3:(i+1)*3, :] = vals * vecs # Resort the columns before returning array axes = axes[:, [2, 1, 0]] torf = None if 'fract' in kwargs: fract = float(kwargs['fract']) assert 0.33 < fract < 1.0, 'fract must be > 0.33 and < 1.0' variances = variances[:, [2, 1, 0]] torf = variances[:, 0] / variances.sum(1) > fract elif 'ratio' in kwargs or 'ratio3' in kwargs or 'ratio2' in kwargs: if 'ratio2' in kwargs: ratio = float(kwargs['ratio2']) assert 0 < ratio < 1.0, 'ratio2 must be > 0 and < 1.0' dim = 1 else: ratio = float(kwargs.get('ratio', kwargs.get('ratio3'))) assert 0 < ratio < 1.0, 'ratio or ratio3 must be > 0 and < 1.0' dim = 2 variances = variances[:, [2, 1, 0]] torf = variances[:, dim] / variances[:, 0] <= ratio if torf is not None: torf = tile(torf.reshape((n_atoms, 1)), (1, 3)).reshape((n_atoms*3, 1)) axes = axes * torf return axes
def reduceModel(model, atoms, select): """Returns reduced NMA model. Reduces a :class:`.NMA` model to a subset of *atoms* matching *select*. This function behaves differently depending on the type of the *model* argument. For :class:`.ANM` and :class:`.GNM` or other :class:`.NMA` models, force constant matrix for system of interest (specified by the *select*) is derived from the force constant matrix for the *model* by assuming that for any given displacement of the system of interest, other atoms move along in such a way as to minimize the potential energy. This is based on the formulation in [KH00]_. For :class:`.PCA` models, this function simply takes the sub-covariance matrix for selection. .. [KH00] Hinsen K, Petrescu A-J, Dellerue S, Bellissent-Funel M-C, Kneller GR. Harmonicity in slow protein dynamics. *Chem Phys* **2000** 261:25-37. :arg model: dynamics model :type model: :class:`.ANM`, :class:`.GNM`, or :class:`.PCA` :arg atoms: atoms that were used to build the model :type atoms: :class:`.Atomic` :arg select: an atom selection or a selection string :type select: :class:`.Selection`, str :returns: (:class:`.NMA`, :class:`.Selection`)""" linalg = importLA() if not isinstance(model, NMA): raise TypeError('model must be an NMA instance, not {0}' .format(type(model))) if not isinstance(atoms, (AtomGroup, AtomSubset, AtomMap)): raise TypeError('atoms type is not valid') if len(atoms) <= 1: raise TypeError('atoms must contain more than 1 atoms') if isinstance(model, GNM): matrix = model._kirchhoff elif isinstance(model, ANM): matrix = model._hessian elif isinstance(model, PCA): matrix = model._cov else: raise TypeError('model does not have a valid type derived from NMA') if matrix is None: raise ValueError('model matrix (Hessian/Kirchhoff/Covariance) is not ' 'built') which, select = sliceAtoms(atoms, select) system = np.zeros(model.numAtoms(), dtype=bool) system[which] = True other = np.invert(system) if model.is3d(): system = np.repeat(system, 3) other = np.repeat(other, 3) ss = matrix[system, :][:, system] if isinstance(model, PCA): eda = PCA(model.getTitle() + ' reduced') eda.setCovariance(ss) return eda, system so = matrix[system, :][:, other] os = matrix[other, :][:, system] oo = matrix[other, :][:, other] if other.any(): try: invoo = linalg.inv(oo) except: invoo = linalg.pinv(oo) matrix = ss - np.dot(so, np.dot(invoo, os)) else: matrix = ss if isinstance(model, GNM): gnm = GNM(model.getTitle() + ' reduced') gnm.setKirchhoff(matrix) return gnm, select elif isinstance(model, ANM): anm = ANM(model.getTitle() + ' reduced') anm.setHessian(matrix) return anm, select elif isinstance(model, PCA): eda = PCA(model.getTitle() + ' reduced') eda.setCovariance(matrix) return eda, select