def __init__(self, atoms, unitcell=None): """*atoms* must be an :class:`.Atomic` instance. :arg unitcell: orthorhombic unitcell dimension array with shape ``(3,)`` for KDTree. Default is **None**. :type unitcell: :class:`~numpy.ndarray`""" try: self._acsi = atoms.getACSIndex() except AttributeError: try: self._ag = atoms.getAtoms() unitcell = unitcell or atoms.getUnitcell()[:3] self._indices = atoms.getSelection() except AttributeError: try: ndim, shape = atoms.ndim, atoms.shape except AttributeError: raise TypeError('atoms must be an Atomic or Frame instance' ', not a {0}'.format(type(atoms))) else: if not (ndim == 2 and shape[1] == 3): raise ValueError('atoms.shape must be (n_atoms, 3) or ' '(3,).') self._ag = None self._indices = None self._kdtree = KDTree(atoms, unitcell=unitcell) else: if self._ag is not None: self._acsi = self._ag.getACSIndex() if self._indices is not None: self._indices = self._indices.getIndices() else: self._acsi = None self._kdtree = KDTree(atoms._getCoords(), unitcell=unitcell) else: try: self._ag = atoms.getAtomGroup() except AttributeError: self._ag = atoms self._indices = None self._kdtree = KDTree(atoms._getCoords(), unitcell=unitcell) else: self._indices = atoms._getIndices() self._kdtree = KDTree(atoms._getCoords(), unitcell=unitcell) self._unitcell = unitcell self._atoms = atoms
def __init__(self, atoms, unitcell=None): """*atoms* must be an :class:`.Atomic` instance. When an orthorhombic *unitcell* array is given""" try: self._acsi = atoms.getACSIndex() except AttributeError: try: self._ag = atoms.getAtoms() unitcell = unitcell or atoms.getUnitcell()[:3] self._indices = atoms.getSelection() except AttributeError: try: ndim, shape = atoms.ndim, atoms.shape except AttributeError: raise TypeError('atoms must be an Atomic or Frame instance' ', not a {0}'.format(type(atoms))) else: if not (ndim == 2 and shape[1] == 3): raise ValueError('atoms.shape must be (n_atoms, 3) or ' '(3,).') self._ag = None self._indices = None self._kdtree = KDTree(atoms, unitcell=unitcell) else: if self._ag is not None: self._acsi = self._ag.getACSIndex() if self._indices is not None: self._indices = self._indices.getIndices() else: self._acsi = None self._kdtree = KDTree(self._atoms._getCoords(), unitcell=unitcell) else: try: self._ag = atoms.getAtomGroup() except AttributeError: self._ag = atoms self._indices = None self._kdtree = KDTree(atoms._getCoords(), unitcell=unitcell) else: self._indices = atoms._getIndices() self._kdtree = KDTree(atoms._getCoords(), unitcell=unitcell) self._unitcell = unitcell self._atoms = atoms
def _getKDTree(self, index=None): """Return KDTree for coordinate set at given index.""" if self._n_csets: if index is None: index = self._acsi kdtree = self._kdtrees[index] if kdtree is None: kdtree = KDTree(self._coords[index]) self._kdtrees[index] = kdtree return kdtree else: return None
def calcDistanceMatrix(coords, cutoff=None): """Calculate matrix of distances between coordinates within *cutoff*. Other matrix entries are set to maximum of calculated distances. :arg coords: a coordinate set or an object with :meth:`getCoords` method. :type coords: :class:`~numpy.ndarray`, :class:`.Atomic` :arg cutoff: cutoff distance for searching the KDTree. Default (**None**) is to use the length of the longest coordinate axis. :type cutoff: None, float """ try: 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') n_atoms = coords.shape[0] dist_mat = zeros((n_atoms, n_atoms)) if cutoff is None: cutoff = max(coords.max(axis=0) - coords.min(axis=0)) kdtree = KDTree(coords) kdtree.search(cutoff) dists = kdtree.getDistances() r = 0 for i, j in kdtree.getIndices(): dist_mat[i, j] = dist_mat[j, i] = dists[r] r += 1 for i in range(n_atoms): for j in range(i + 1, n_atoms): if dist_mat[i, j] == 0.: dist_mat[i, j] = dist_mat[j, i] = max(dists) return dist_mat
def buildKirchhoff(self, coords, cutoff=10., gamma=1., **kwargs): """Build Kirchhoff matrix for given coordinate set. :arg coords: a coordinate set or an object with ``getCoords`` method :type coords: :class:`numpy.ndarray` or :class:`.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 :arg kdtree: elect to use KDTree for building Kirchhoff matrix faster, default is **True** :type kdtree: 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.""" try: 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') 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 kwargs.get('kdtree', True): kdtree = KDTree(coords) kdtree.search(cutoff) dist2 = kdtree.getDistances() ** 2 r = 0 for i, j in kdtree.getIndices(): g = gamma(dist2[r], 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: LOGGER.info('Using slower method for building the Kirchhoff.') cutoff2 = cutoff * cutoff mul = np.multiply for i in range(n_atoms): xyz_i = coords[i, :] i_p1 = i+1 i2j = coords[i_p1:, :] - xyz_i mul(i2j, i2j, i2j) for j, dist2 in enumerate(i2j.sum(1)): if dist2 > cutoff2: continue j += i_p1 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 buildHessian(self, coords, cutoff=15., gamma=1., **kwargs): """Build Hessian matrix for given coordinate set. :arg coords: a coordinate set or an object with ``getCoords`` method :type coords: :class:`numpy.ndarray` :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: elect to use sparse matrices, default is **False**. If Scipy is not found, :class:`ImportError` is raised. :type sparse: bool :arg kdtree: elect to use KDTree for building Hessian matrix, default is **False** since KDTree method is slower :type kdtree: 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.""" try: 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') cutoff, g, gamma = checkENMParameters(cutoff, gamma) self._reset() self._cutoff = cutoff self._gamma = g n_atoms = coords.shape[0] dof = n_atoms * 3 LOGGER.timeit('_anm_hessian') 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 kwargs.get('kdtree', False): LOGGER.info('Using KDTree for building the Hessian.') kdtree = KDTree(coords) kdtree.search(cutoff) for i, j in kdtree.getIndices(): 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 i_p1 = i+1 i2j_all = coords[i_p1:, :] - coords[i] for j, dist2 in enumerate((i2j_all ** 2).sum(1)): if dist2 > cutoff2: continue i2j = i2j_all[j] j += i_p1 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.report('Hessian was built in %.2fs.', label='_anm_hessian') self._kirchhoff = kirchhoff self._hessian = hessian self._n_atoms = n_atoms self._dof = dof
def iterNeighbors(atoms, radius, atoms2=None, unitcell=None): """Yield pairs of *atoms* that are within *radius* of each other and the distance between them. If *atoms2* is also provided, one atom from *atoms* and another from *atoms2* will be yielded. If one of *atoms* or *atoms2* is a coordinate array, pairs of indices and distances will be yielded. When orthorhombic *unitcell* dimensions are provided, periodic boundary conditions will be taken into account (see :class:`.KDTree` and also :func:`wrapAtoms` for details). If *atoms* is a :class:`.Frame` instance and *unitcell* is not provided, unitcell information from frame will be if available.""" radius = float(radius) if radius <= 0: raise ValueError('radius must be a positive number') try: acsi = atoms.getACSIndex() except AttributeError: try: ndim, shape = atoms.ndim, atoms.shape except AttributeError: try: uc = atoms.getUnitcell()[:3] except AttributeError: raise TypeError('atoms must be an Atomic or Frame instance or ' 'a coordinate array') else: coords = atoms._getCoords() ag = atoms.getAtoms() if unitcell is None: unitcell = uc if ag is not None: acsi = ag.getACSIndex() _ = atoms.getSelection() if _: indices = _._getIndices() index = lambda i: indices[i] else: index = lambda i: i else: if ndim > 2: raise ValueError('number of dimensions of coordinate array ' 'must be 1 or 2') coords = atoms ag = None acsi = None else: coords = atoms._getCoords() try: ag = atoms.getAtomGroup() indices = atoms.getIndices() index = lambda i: indices[i] except AttributeError: ag = atoms index = lambda i: i if coords.ndim == 1: coords = array([coords]) if atoms2 is None: if len(coords) <= 1: raise ValueError('atoms must be more than 1') kdtree = KDTree(coords, unitcell=unitcell, none=list) _dict = {} if ag is None: for (i, j), r in zip(*kdtree(radius)): yield (i, j, r) else: for (i, j), r in zip(*kdtree(radius)): a1 = _dict.get(i) if a1 is None: a1 = Atom(ag, index(i), acsi) _dict[i] = a1 a2 = _dict.get(j) if a2 is None: a2 = Atom(ag, index(j), acsi) _dict[j] = a2 yield (a1, a2, r) else: try: coords2 = atoms2._getCoords() except AttributeError: try: ndim, shape = atoms2.ndim, atoms2.shape except AttributeError: raise TypeError( 'atoms2 must be an Atomic or Frame instance or ' 'a coordinate array') else: if ndim > 2: raise ValueError('number of dimensions of second ' 'coordinate array must be 1 or 2') coords2 = atoms2 ag2 = None acsi2 = None else: try: acsi2 = atoms2.getACSIndex() except AttributeError: acsi2 = None ag2 = None index2 = None else: try: ag2 = atoms2.getAtomGroup() indices2 = atoms2.getIndices() index2 = lambda i: indices2[i] except AttributeError: ag2 = atoms2 index2 = lambda i: i if coords2.ndim == 1: coords2 = array([coords2]) if len(coords) >= len(coords2): kdtree = KDTree(coords, unitcell=unitcell, none=list) _dict = {} if ag is None or ag2 is None: for j, xyz in enumerate(coords2): for i, r in zip(*kdtree(radius, xyz)): yield (i, j, r) else: for a2 in atoms2.iterAtoms(): for i, r in zip(*kdtree(radius, a2._getCoords())): a1 = _dict.get(i) if a1 is None: a1 = Atom(ag, index(i), acsi) _dict[i] = a1 yield (a1, a2, r) else: kdtree = KDTree(coords2, unitcell=unitcell, none=list) _dict = {} if ag is None or ag2 is None: for i, xyz in enumerate(coords): for j, r in zip(*kdtree(radius, xyz)): yield (i, j, r) else: for a1 in atoms.iterAtoms(): for i, r in zip(*kdtree(radius, a1._getCoords())): a2 = _dict.get(i) if a2 is None: a2 = Atom(ag2, index2(i), acsi2) _dict[i] = a2 yield (a1, a2, r)
def setUp(self): self.coords = tile(arange(10), (3,1)).T.astype(float) self.kdtree = KDTree(self.coords)
self.assertEqual(len(indices), 9, 'KDTree all search failed') for pair, radius in zip(indices, radii): x, y = coords[pair] assert_allclose(((x - y)**2).sum()**0.5, radius, rtol=RTOL, atol=ATOL, err_msg='KDTree all search failed') COORDS = array([[-1., -1., 0.], [-1., 5., 0.], [ 2., 2., 0.], [ 5., -1., 0.], [ 5., 5., 0.],]) UNITCELL = array([4., 4., 0.]) KDTREE = KDTree(COORDS) KDTREE_PBC = KDTree(COORDS, unitcell=UNITCELL) class TestKDTreePBC(unittest.TestCase): def testPointNoPBC(self): KDTREE.search(2, ones(3) * 2) self.assertEqual(1, KDTREE.getCount()) def testPairNoPBC(self): KDTREE.search(2) self.assertEqual(0, KDTREE.getCount()) def testPointPBC(self):
def buildKirchhoff(self, coords, cutoff=10., gamma=1., **kwargs): """Build Kirchhoff matrix for given coordinate set. :arg coords: a coordinate set or an object with ``getCoords`` method :type coords: :class:`numpy.ndarray` or :class:`.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 :arg kdtree: elect to use KDTree for building Kirchhoff matrix faster, default is **True** :type kdtree: 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.""" try: 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') 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 kwargs.get('kdtree', True): kdtree = KDTree(coords) kdtree.search(cutoff) dist2 = kdtree.getDistances()**2 r = 0 for i, j in kdtree.getIndices(): g = gamma(dist2[r], 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: LOGGER.info('Using slower method for building the Kirchhoff.') cutoff2 = cutoff * cutoff mul = np.multiply for i in range(n_atoms): xyz_i = coords[i, :] i_p1 = i + 1 i2j = coords[i_p1:, :] - xyz_i mul(i2j, i2j, i2j) for j, dist2 in enumerate(i2j.sum(1)): if dist2 > cutoff2: continue j += i_p1 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