def buildPDBEnsemble(atomics, ref=None, title='Unknown', labels=None, atommaps=None, unmapped=None, **kwargs): """Builds a :class:`.PDBEnsemble` from a given reference structure and a list of structures (:class:`.Atomic` instances). Note that the reference should be included in the list as well. :arg atomics: a list of :class:`.Atomic` instances :type atomics: list :arg ref: reference structure or the index to the reference in *atomics*. If **None**, then the first item in *atomics* will be considered as the reference. If it is a :class:`.PDBEnsemble` instance, then *atomics* will be appended to the existing ensemble. Default is **None** :type ref: int, :class:`.Chain`, :class:`.Selection`, or :class:`.AtomGroup` :arg title: the title of the ensemble :type title: str :arg labels: labels of the conformations :type labels: list :arg degeneracy: whether only the active coordinate set (**True**) or all the coordinate sets (**False**) of each structure should be added to the ensemble. Default is **True** :type degeneracy: bool :arg occupancy: minimal occupancy of columns (range from 0 to 1). Columns whose occupancy is below this value will be trimmed :type occupancy: float :arg atommaps: labels of *atomics* that were mapped and added into the ensemble. This is an output argument :type atommaps: list :arg unmapped: labels of *atomics* that cannot be included in the ensemble. This is an output argument :type unmapped: list :arg subset: a subset for selecting particular atoms from the input structures. Default is ``"all"`` :type subset: str :arg superpose: if set to ``'iter'``, :func:`.PDBEnsemble.iterpose` will be used to superpose the structures, otherwise conformations will be superposed with respect to the reference specified by *ref* unless set to ``False``. Default is ``'iter'`` :type superpose: str, bool """ occupancy = kwargs.pop('occupancy', None) degeneracy = kwargs.pop('degeneracy', True) subset = str(kwargs.get('subset', 'all')).lower() superpose = kwargs.pop('superpose', 'iter') superpose = kwargs.pop('iterpose', superpose) debug = kwargs.pop('debug', {}) if 'mapping_func' in kwargs: raise DeprecationWarning( 'mapping_func is deprecated. Please see release notes for ' 'more details: http://prody.csb.pitt.edu/manual/release/v1.11_series.html' ) start = time.time() if not isListLike(atomics): raise TypeError('atomics should be list-like') if len(atomics) == 1 and degeneracy is True: raise ValueError('atomics should have at least two items') if labels is not None: if len(labels) != len(atomics): raise TypeError('Labels and atomics must have the same lengths.') else: labels = [] for atoms in atomics: if atoms is None: labels.append(None) else: labels.append(atoms.getTitle()) if ref is None: target = atomics[0] elif isinstance(ref, Integral): target = atomics[ref] elif isinstance(ref, PDBEnsemble): target = ref._atoms else: target = ref # initialize a PDBEnsemble with reference atoms and coordinates isrefset = False if isinstance(ref, PDBEnsemble): ensemble = ref else: # select the subset of reference beforehand for the sake of efficiency if subset != 'all': target = target.select(subset) ensemble = PDBEnsemble(title) if isinstance(target, Atomic): ensemble.setAtoms(target) ensemble.setCoords(target.getCoords()) isrefset = True else: ensemble._n_atoms = len(target) isrefset = False # build the ensemble if unmapped is None: unmapped = [] if atommaps is None: atommaps = [] LOGGER.progress('Building the ensemble...', len(atomics), '_prody_buildPDBEnsemble') for i, atoms in enumerate(atomics): if atoms is None: unmapped.append(labels[i]) continue LOGGER.update(i, 'Mapping %s to the reference...' % atoms.getTitle(), label='_prody_buildPDBEnsemble') try: atoms.getHierView() except AttributeError: raise TypeError( 'atomics must be a list of instances having the access to getHierView' ) if subset != 'all': atoms = atoms.select(subset) # find the mapping of chains of atoms to those of target debug[labels[i]] = {} atommaps_ = alignChains(atoms, target, debug=debug[labels[i]], **kwargs) if len(atommaps_) == 0: unmapped.append(labels[i]) continue else: atommaps.extend(atommaps_) # add the atommaps to the ensemble for atommap in atommaps_: lbl = pystr(labels[i]) if len(atommaps_) > 1: chids = np.unique(atommap.getChids()) strchids = ''.join(chids) lbl += '_%s' % strchids ensemble.addCoordset(atommap, weights=atommap.getFlags('mapped'), label=lbl, degeneracy=degeneracy) if not isrefset: ensemble.setCoords(atommap.getCoords()) isrefset = True LOGGER.finish() if occupancy is not None: ensemble = trimPDBEnsemble(ensemble, occupancy=occupancy) if superpose == 'iter': ensemble.iterpose() elif superpose is not False: ensemble.superpose() LOGGER.info('Ensemble ({0} conformations) were built in {1:.2f}s.'.format( ensemble.numConfs(), time.time() - start)) if unmapped: LOGGER.warn('{0} structures cannot be mapped.'.format(len(unmapped))) return ensemble
def combineEnsembles(target, mobile, **kwargs): """Combines two ensembles by mapping the **atoms** of **mobile** to that of **target**.""" iterpose = kwargs.pop('superpose', True) iterpose = kwargs.pop('iterpose', iterpose) title = kwargs.pop('title', None) def findIndices(A, B): """Finds indices of values of A in B.""" B = np.asarray(B) ret = np.zeros_like(A) for i, a in enumerate(A): indices = np.where(B == a)[0] if len(indices): index = indices[0] else: index = -1 ret[i] = index return ret ens0, ens1 = target, mobile atoms0 = ens0.getAtoms() atoms1 = ens1.getAtoms() if atoms0 is None: raise ValueError('target must have associated atoms') if atoms1 is None: raise ValueError('mobile must have associated atoms') w0 = ens0.getWeights() w1 = ens1.getWeights() coords0 = ens0.getCoordsets() coords1 = ens1.getCoordsets() if isinstance(ens0, PDBEnsemble): labels0 = ens0.getLabels() else: labels0 = None if isinstance(ens1, PDBEnsemble): labels1 = ens1.getLabels() else: labels1 = None # obtain atommaps: atoms1 -> atoms0 atommaps = alignChains(atoms1, atoms0, **kwargs) if len(atommaps) == 0: raise ValueError('mobile cannot be mapped onto target. ' 'Try again with relaxed seqid and/or coverage') # combine the atommaps atommap = atommaps[0] weights = atommap.getFlags('mapped') # extract mappings from atommap if hasattr(atoms1, 'getIndices'): all_indices = atoms1.getIndices() else: all_indices = np.arange(atoms1.numAtoms()) I = findIndices(atommap._indices, all_indices) J = atommap.getMapping() # map the coordinates: ens1 -> ens0 n_csets = coords1.shape[0] n_atoms = atoms0.numAtoms() coords2 = np.zeros((n_csets, n_atoms, 3)) coords2[:, J, :] = coords1[:, I, :] if w1 is not None: w2 = np.zeros((n_csets, n_atoms, 1)) w2[:, J, :] = w1[:, I, :] else: w2 = None if w2 is None: w2 = weights else: w2 *= weights # build the new ensemble if title is None: title = '%s + %s' % (target.getTitle(), mobile.getTitle()) ens = PDBEnsemble(title) ens.setAtoms(atoms0) ens.setCoords(atoms0.getCoords()) ens.addCoordset(coords0, weights=w0, label=labels0) ens.addCoordset(coords2, weights=w2, label=labels1) if iterpose: ens.iterpose() return ens
def realignModes(modes, atoms, ref): """Align *modes* in the original frame based on *atoms* onto another frame based on *ref* using the transformation from alignment of *atoms* to *ref* :arg modes: multiple 3D modes :type modes: :class:`.ModeSet`, :class:`.ANM`, :class:`.PCA` :arg atoms: central structure related to *modes* to map onto *ref* Inserting *atoms* into an ensemble and projecting onto *modes* should give all zeros :type atoms: :class:`.Atomic` :arg ref: reference structure for mapping :type ref: :class:`.Atomic` """ if not isinstance(modes, (ModeSet, NMA)): raise TypeError('modes should be a ModeSet of NMA instance') if not modes.is3d(): raise ValueError('modes should be 3D for this function to work') if not isinstance(atoms, Atomic): raise TypeError('atoms should be an Atomic instance') if not isinstance(ref, Atomic): raise TypeError('ref should be an Atomic instance') n_atoms = modes.numAtoms() if atoms.numAtoms() != n_atoms: raise ValueError( 'atoms and modes should have the same number of atoms') def_coords = np.array( [atoms.getCoords() + mode.getArrayNx3() for mode in modes]) def_ens = PDBEnsemble('applied eigvecs') def_ens.setCoords(atoms) def_ens.setAtoms(atoms) def_ens.addCoordset(atoms) def_ens.addCoordset(def_coords) if not np.allclose(calcProjection(def_ens[0], modes), np.zeros(modes.numModes())): raise ValueError('projection of atoms onto modes (via an ensemble) ' 'is not all zeros so atoms is not appropriate') if ref.numAtoms() != n_atoms: ref = alignChains(ref, atoms)[0] def_ens.setCoords(ref) def_ens.superpose() new_vectors = np.array([ np.array(coords - def_ens.getCoordsets()[0]).flatten() for coords in def_ens.getCoordsets()[1:] ]).T # initialise a new modes object with the same type result = type(modes)() result.setEigens(new_vectors, modes.getEigvals()) return result