def addCoordset(self, coords, allcoordsets=True): """Add coordinate set(s) as conformation(s). :class:`~prody.atomic.Atomic` instances are accepted as *coords* argument. If *allcoordsets* is ``True``, all coordinate sets from the :class:`~prody.atomic.Atomic` instance will be appended to the ensemble. Otherwise, only the active coordinate set will be appended. """ assert isinstance(allcoordsets, bool), 'allcoordsets must be boolean' if not isinstance(coords, np.ndarray): if isinstance(coords, (Atomic, Ensemble)): atoms = coords if allcoordsets: coords = atoms.getCoordsets() else: coords = atoms.getCoords() if coords is None: raise ValueError('{0:s} must contain coordinate data' .format(atoms)) else: raise TypeError('coords must be a Numpy array or ' 'ProDy Atomic or Ensemble instance') coords = checkCoords(coords, arg='coords', cset=True, n_atoms=self._n_atoms, reshape=True) if self._n_atoms == 0: self._n_atoms = coords.shape[-2] n_confs = coords.shape[0] if self._confs is None: self._confs = coords else: self._confs = np.concatenate((self._confs, coords), axis=0) self._n_csets += n_confs
def addCoordset(self, coords, label=None): """Add a coordinate set. *coords* argument may be an object instance with ``getCoordsets`` method.""" if not isinstance(coords, np.ndarray): coords = coords.getCoordsets() if self._coords is None: self.setCoords(coords) return coords = checkCoords(coords, 'coords', cset=True, n_atoms=self._n_atoms, reshape=True) diff = coords.shape[0] self._coords = np.concatenate((self._coords, coords), axis=0) self._n_csets = self._coords.shape[0] timestamps = self._timestamps self._timestamps = np.zeros(self._n_csets) self._timestamps[:len(timestamps)] = timestamps self._timestamps[len(timestamps):] = time() self._kdtrees.extend([None] * diff) if isinstance(label, (str, NoneType)): self._cslabels += [label] * diff elif isinstance(label, (list, tuple)): if len(label) == diff: if all([isinstance(lbl, str) for lbl in label]): self._cslabels += label else: LOGGER.warning('all items of `label` must be strings') else: LOGGER.warning('`label` list must have same length as the ' '`coords` array') else: LOGGER.warning('`label` must be a string or list of strings')
def setCoords(self, coords): """Set reference coordinates.""" if not isinstance(coords, np.ndarray): try: coords = coords.getCoords() except AttributeError: raise TypeError('coords must be a Numpy array or must have ' 'getCoordinates attribute') self._coords = checkCoords(coords, arg='coords', n_atoms=self._n_atoms, cset=False)
def addCoordset(self, coords, weights=None, allcoordsets=True): """Add coordinate set(s) as conformation(s). :class:`~prody.atomic.Atomic` instances are accepted as *coords* argument. If *allcoordsets* is ``True``, all coordinate sets from the :class:`~prody.atomic.Atomic` instance will be appended to the ensemble. Otherwise, only the active coordinate set will be appended. *weights* is an optional argument. If provided, its length must match number of atoms. Weights of missing (not resolved) atoms must be equal to ``0`` and weights of those that are resolved can be anything greater than ``0``. If not provided, weights of atoms in this coordinate set will be set equal to ``1``.""" assert isinstance(allcoordsets, bool), 'allcoordsets must be boolean' if weights is not None: assert isinstance(weights, np.ndarray), 'weights must be ndarray' ag = None if isinstance(coords, Atomic): atoms = coords if isinstance(coords, AtomGroup): ag = atoms else: ag = atoms.getAtomGroup() if allcoordsets: coords = atoms.getCoordsets() else: coords = atoms.getCoords() title = ag.getTitle() elif isinstance(coords, np.ndarray): title = 'Unknown' else: title = str(coords) try: if allcoordsets: coords = coords.getCoordsets() else: coords = coords.getCoords() except AttributeError: raise TypeError('coords must be a Numpy array or must have ' 'getCoordinates attribute') coords = checkCoords(coords, 'coords', cset=True, n_atoms=self._n_atoms, reshape=True) n_csets, n_atoms, _ = coords.shape if self._n_atoms == 0: self._n_atoms = n_atoms if weights is None: weights = np.ones((n_csets, n_atoms, 1), dtype=float) else: weights = checkWeights(weights, n_atoms, n_csets) while ' ' in title: title = title.replace(' ', ' ') title = title.replace(' ', '_') if n_csets > 1: self._labels += ['{0:s}_{1:d}' .format(title, i+1) for i in range(n_csets)] else: if ag is not None and ag.numCoordsets() > 1: self._labels.append('{0:s}_{1:d}'.format(title, atoms.getACSIndex())) else: self._labels.append(title) if self._confs is None and self._weights is None: self._confs = coords self._weights = weights self._n_csets = n_csets elif self._confs is not None and self._weights is not None: self._confs = np.concatenate((self._confs, coords), axis=0) self._weights = np.concatenate((self._weights, weights), axis=0) self._n_csets += n_csets else: raise RuntimeError('_confs and _weights must be set or None at ' 'the same time')
def buildHessian(self, coords, cutoff=15.0, gamma=1.0, **kwargs): """Build Hessian matrix for given coordinate set. :arg coords: a coordinate set or anything with getCoordinates method :type coords: :class:`~numpy.ndarray` or :class:`~prody.atomic.Atomic` :arg cutoff: cutoff distance (Å) for pairwise interactions, default is 15.0 Å, minimum is 4.0 Å :type cutoff: float :arg gamma: spring constant, default is 1.0 :type gamma: float, :class:`Gamma` :arg sparse: slect to use sparse matrices. Default is ``False``. If Scipy is not found, :class:`ImportError` is raised. :type sparse: bool Instances of :class:`Gamma` classes and custom functions are accepted as *gamma* argument. When Scipy is available, user can select to use sparse matrices for efficient usage of memory at the cost of computation speed.""" slow = kwargs.get("slow", False) try: from KDTree import KDTree except ImportError: KDTree = False if not slow and not KDTree: LOGGER.info("Using a slower method for building the Hessian " "matrix.") if not isinstance(coords, np.ndarray): try: coords = coords.getCoords() except AttributeError: raise TypeError("coords must be a Numpy array or must have " "getCoordinates attribute") coords = checkCoords(coords, "coords") cutoff, g, gamma = checkENMParameters(cutoff, gamma) self._reset() self._cutoff = cutoff self._gamma = g n_atoms = coords.shape[0] dof = n_atoms * 3 start = time.time() if kwargs.get("sparse", False): try: from scipy import sparse as scipy_sparse except ImportError: raise ImportError("failed to import scipy.sparse, which is " "required for sparse matrix calculations") kirchhoff = scipy_sparse.lil_matrix((n_atoms, n_atoms)) hessian = scipy_sparse.lil_matrix((dof, dof)) else: kirchhoff = np.zeros((n_atoms, n_atoms), "d") hessian = np.zeros((dof, dof), float) if not slow and KDTree: kdtree = getKDTree(coords) kdtree.all_search(cutoff) for i, j in kdtree.all_get_indices(): i2j = coords[j] - coords[i] dist2 = np.dot(i2j, i2j) g = gamma(dist2, i, j) super_element = np.outer(i2j, i2j) * (-g / dist2) res_i3 = i * 3 res_i33 = res_i3 + 3 res_j3 = j * 3 res_j33 = res_j3 + 3 hessian[res_i3:res_i33, res_j3:res_j33] = super_element hessian[res_j3:res_j33, res_i3:res_i33] = super_element hessian[res_i3:res_i33, res_i3:res_i33] = hessian[res_i3:res_i33, res_i3:res_i33] - super_element hessian[res_j3:res_j33, res_j3:res_j33] = hessian[res_j3:res_j33, res_j3:res_j33] - super_element kirchhoff[i, j] = -g kirchhoff[j, i] = -g kirchhoff[i, i] = kirchhoff[i, i] - g kirchhoff[j, j] = kirchhoff[j, j] - g else: cutoff2 = cutoff * cutoff for i in range(n_atoms): res_i3 = i * 3 res_i33 = res_i3 + 3 xyz_i = coords[i, :] for j in range(i + 1, n_atoms): i2j = coords[j, :] - xyz_i dist2 = np.dot(i2j, i2j) if dist2 > cutoff2: continue g = gamma(dist2, i, j) res_j3 = j * 3 res_j33 = res_j3 + 3 super_element = np.outer(i2j, i2j) * (-g / dist2) hessian[res_i3:res_i33, res_j3:res_j33] = super_element hessian[res_j3:res_j33, res_i3:res_i33] = super_element hessian[res_i3:res_i33, res_i3:res_i33] = hessian[res_i3:res_i33, res_i3:res_i33] - super_element hessian[res_j3:res_j33, res_j3:res_j33] = hessian[res_j3:res_j33, res_j3:res_j33] - super_element kirchhoff[i, j] = -g kirchhoff[j, i] = -g kirchhoff[i, i] = kirchhoff[i, i] - g kirchhoff[j, j] = kirchhoff[j, j] - g LOGGER.info("Hessian was built in {0:.2f}s.".format(time.time() - start)) self._kirchhoff = kirchhoff self._hessian = hessian self._n_atoms = n_atoms self._dof = dof
def setCoords(self, coords, label=None): """Set coordinates. *coords* may be a :class:`numpy.ndarray` instance or an object instance with ``getCoordsets`` method. If the shape of the coordinates array is (n_csets,n_atoms,3), the given array will replace all coordinate sets. To avoid it, :meth:`addCoordset` may be used. If the shape of the *coords* array is (n_atoms,3) or (1,n_atoms,3), the coordinate set will replace the coordinates of the currently active set. *label* argument may be used to label coordinate sets. *label* may be a string or a list of strings length equal to the number of coordinate sets.""" if not isinstance(coords, np.ndarray): coords = coords.getCoordsets() coords = checkCoords(coords, 'coords', cset=True, n_atoms=self._n_atoms, reshape=True) if self._n_atoms == 0: self._n_atoms = coords.shape[-2] acsi = None if self._coords is None: self._coords = coords self._n_csets = coords.shape[0] self._acsi = 0 self._setTimeStamp() if isinstance(label, (NoneType, str)): self._cslabels = [label] * self._n_csets elif isinstance(label, (list, tuple)): if len(label) == self._n_csets: self._cslabels = label else: self._cslabels = [None] * self._n_csets LOGGER.warning('Length of `label` does not match number ' 'of coordinate sets.') else: if coords.shape[0] == 1: acsi = self._acsi self._coords[acsi] = coords[0] self._setTimeStamp(acsi) if isinstance(label, str): self._cslabels[self._acsi] = label else: self._coords = coords self._n_csets = coords.shape[0] self._acsi = min(self._n_csets - 1, self._acsi) self._setTimeStamp() if acsi is None: if isinstance(label, (str, NoneType)): self._cslabels = [label] * self._n_csets elif isinstance(label, (list, tuple)): if len(label) == self._n_csets: if all([isinstance(lbl, str) for lbl in label]): self._cslabels += label else: LOGGER.warning('all items of label must be strings') else: LOGGER.warning('label must have same length as the ' 'coords array') else: LOGGER.warning('label must be a string or list of strings') elif label is not None: if isinstance(label, str): self._cslabels[acsi] = label elif isinstance(label, (list, tuple)): if len(label) == 1: if isinstance(label[0], str): self._cslabels[acsi] = label else: LOGGER.warning('all items of label must be strings') else: LOGGER.warning('length of label must be one') else: LOGGER.warning('label must be a string or list of strings')
def buildKirchhoff(self, coords, cutoff=10., gamma=1., **kwargs): """Build Kirchhoff matrix for given coordinate set. :arg coords: a coordinate set or anything with getCoordinates method :type coords: :class:`~numpy.ndarray` or :class:`~prody.atomic.Atomic` :arg cutoff: cutoff distance (Å) for pairwise interactions default is 10.0 Å, , minimum is 4.0 Å :type cutoff: float :arg gamma: spring constant, default is 1.0 :type gamma: float :arg sparse: Elect to use sparse matrices. Default is ``False``. If Scipy is not found, :class:`ImportError` is raised. :type sparse: bool Instances of :class:`Gamma` classes and custom functions are accepted as *gamma* argument. When Scipy is available, user can select to use sparse matrices for efficient usage of memory at the cost of computation speed.""" slow = kwargs.get('slow', False) try: from KDTree import KDTree except ImportError: KDTree = False if not slow and not KDTree: LOGGER.info('Using a slower method for building the Kirchhoff ' 'matrix.') if not isinstance(coords, np.ndarray): try: coords = coords.getCoords() except AttributeError: raise TypeError('coords must be a Numpy array or must have ' 'getCoordinates attribute') coords = checkCoords(coords, 'coords') cutoff, g, gamma = checkENMParameters(cutoff, gamma) self._reset() self._cutoff = cutoff self._gamma = g n_atoms = coords.shape[0] start = time.time() if kwargs.get('sparse', False): try: from scipy import sparse as scipy_sparse except ImportError: raise ImportError('failed to import scipy.sparse, which is ' 'required for sparse matrix calculations') kirchhoff = scipy_sparse.lil_matrix((n_atoms, n_atoms)) else: kirchhoff = np.zeros((n_atoms, n_atoms), 'd') if not slow and KDTree: kdtree = getKDTree(coords) kdtree.all_search(cutoff) radii = kdtree.all_get_radii() r = 0 for i, j in kdtree.all_get_indices(): g = gamma(radii[r]**2, i, j) kirchhoff[i, j] = -g kirchhoff[j, i] = -g kirchhoff[i, i] = kirchhoff[i, i] + g kirchhoff[j, j] = kirchhoff[j, j] + g r += 1 else: cutoff2 = cutoff * cutoff for i in range(n_atoms): xyz_i = coords[i, :] for j in range(i+1, n_atoms): i2j = coords[j, :] - xyz_i dist2 = np.dot(i2j, i2j) if dist2 > cutoff2: continue g = gamma(dist2, i, j) kirchhoff[i, j] = -g kirchhoff[j, i] = -g kirchhoff[i, i] = kirchhoff[i, i] + g kirchhoff[j, j] = kirchhoff[j, j] + g LOGGER.debug('Kirchhoff was built in {0:.2f}s.' .format(time.time()-start)) self._kirchhoff = kirchhoff self._n_atoms = n_atoms self._dof = n_atoms
def write(self, coords, unitcell=None, **kwargs): """Write *coords* to the file. Number of atoms will be determined based on the size of the first coordinate set. If *unitcell* is provided for the first coordinate set, it will be expected for the following coordinate sets as well. The following keywords are used when writing the first coordinate set for files open at 'w' mode: :arg timestep: timestep used for integration, default is 1 :arg firsttimestep: number of the first timestep, default is 0 :arg framefreq: number of timesteps between frames, default is 1""" if self._closed: raise ValueError('I/O operation on closed file') if self._mode == 'r': raise IOError('File not open for writing') coords = checkCoords(coords, 'coords', True, dtype=np.float32) if coords.ndim == 2: n_atoms = coords.shape[0] coords = [coords] else: n_atoms = coords.shape[1] if self._n_atoms == 0: self._n_atoms = n_atoms else: if self._n_atoms != n_atoms: raise ValueError('coords to not have correct number ' 'of atoms') dcd = self._file pack_i_4N = pack('i', self._n_atoms * 4) if self._n_csets == 0: if unitcell is None: self._unitcell = False else: self._unitcell = True timestep = float(kwargs.get('timestep', 1.0)) first_ts = int(kwargs.get('firsttimestep', 0)) framefreq = int(kwargs.get('framefreq', 1)) n_fixed = 0 pack_i_0 = pack('i', 0) pack_ix4_0x4 = pack('i'*4, 0, 0, 0, 0) pack_i_1 = pack('i', 1) pack_i_2 = pack('i', 2) pack_i_4 = pack('i', 4) pack_i_84 = pack('i', 84) pack_i_164 = pack('i', 164) dcd.write(pack_i_84) dcd.write('CORD') dcd.write(pack_i_0) # 0 Number of frames in file, none written yet dcd.write(pack('i', first_ts)) # 1 Starting timestep dcd.write(pack('i', framefreq)) # 2 Timesteps between frames dcd.write(pack_i_0) # 3 Number of timesteps in simulation dcd.write(pack_i_0) # 4 NAMD writes NSTEP or ISTART - NSAVC here? dcd.write(pack_ix4_0x4) # 5, 6, 7, 8 dcd.write(pack('f', timestep)) # 9 timestep dcd.write(pack('i', int(self._unitcell))) # 10 with unitcell dcd.write(pack_ix4_0x4) # 11, 12, 13, 14 dcd.write(pack_ix4_0x4) # 15, 16, 17, 18 dcd.write(pack('i', 24)) # 19 Pretend to be CHARMM version 24 dcd.write(pack_i_84) dcd.write(pack_i_164) dcd.write(pack_i_2) dcd.write('{0:80s}'.format('Created by ProDy')) dcd.write('{0:80s}'.format('REMARKS Created ' + now().strftime('%d %B, %Y at %H:%M'))) dcd.write(pack_i_164) dcd.write(pack_i_4) dcd.write(pack('i', n_atoms)) dcd.write(pack_i_4) self._first_byte = dcd.tell() if self._unitcell: if unitcell is None: raise TypeError('unitcell data is expected') else: uc = unitcell uc[3:] = np.sin((PISQUARE/90) * (90-uc[3:])) uc = uc[[0,3,1,4,5,2]] pack_i_48 = pack('i', 48) dcd.seek(0, 2) for xyz in coords: if self._unitcell: dcd.write(pack_i_48) uc.tofile(dcd) dcd.write(pack_i_48) xyz = xyz.T dcd.write(pack_i_4N) xyz[0].tofile(dcd) dcd.write(pack_i_4N) dcd.write(pack_i_4N) xyz[1].tofile(dcd) dcd.write(pack_i_4N) dcd.write(pack_i_4N) xyz[2].tofile(dcd) dcd.write(pack_i_4N) self._n_csets += 1 dcd.seek(8, 0) dcd.write(pack('i', self._n_csets)) dcd.seek(0, 2) self._nfi = self._n_csets