Example #1
0
def find_mic(v, cell, pbc=True):
    """Finds the minimum-image representation of vector(s) v using either one
    of two find mic algorithms depending on the given cell, v and pbc."""

    pbc = cell.any(1) & pbc2pbc(pbc)
    dim = np.sum(pbc)
    v = np.asarray(v)
    single = v.ndim == 1
    v = np.atleast_2d(v)

    if dim > 0:
        naive_find_mic_is_safe = False
        if dim == 3:
            vmin, vlen = naive_find_mic(v, cell)
            # naive find mic is safe only for the following condition
            if (vlen < 0.5 * min(cell.lengths())).all():
                naive_find_mic_is_safe = True  # hence skip Minkowski reduction

        if not naive_find_mic_is_safe:
            vmin, vlen = general_find_mic(v, cell, pbc=pbc)
    else:
        vmin = v.copy()
        vlen = np.linalg.norm(vmin, axis=1)

    if single:
        return vmin[0], vlen[0]
    else:
        return vmin, vlen
Example #2
0
def find_mic(v, cell, pbc=True):
    """Finds the minimum-image representation of vector(s) v"""

    pbc = cell.any(1) & pbc2pbc(pbc)
    v = np.array(v)
    single = len(v.shape) == 1
    v = np.atleast_2d(v)

    if np.sum(pbc) > 0:
        cell = complete_cell(cell)
        rcell, _ = minkowski_reduce(cell, pbc=pbc)

        # in a Minkowski-reduced cell we only need to test nearest neighbors
        cs = [np.arange(-1 * p, p + 1) for p in pbc]
        neighbor_cells = list(itertools.product(*cs))

        positions = wrap_positions(v, rcell, pbc=pbc, eps=0)
        vmin = positions.copy()
        vlen = np.linalg.norm(positions, axis=1)
        for nbr in neighbor_cells:
            trial = positions + np.dot(rcell.T, nbr)
            trial_len = np.linalg.norm(trial, axis=1)

            indices = np.where(trial_len < vlen)
            vmin[indices] = trial[indices]
            vlen[indices] = trial_len[indices]
    else:
        vmin = v.copy()
        vlen = np.linalg.norm(vmin, axis=1)

    if single:
        return vmin[0], vlen[0]
    else:
        return vmin, vlen
Example #3
0
    def __init__(self, n_top=None, dE=1.0, cos_dist_max=5e-3, rcut=20.,
                 binwidth=0.05, sigma=0.02, nsigma=4, pbc=True,
                 maxdims=None, recalculate=False):
        self.n_top = n_top or 0
        self.dE = dE
        self.cos_dist_max = cos_dist_max
        self.rcut = rcut
        self.binwidth = binwidth
        self.pbc = pbc2pbc(pbc)

        if maxdims is None:
            self.maxdims = [None] * 3
        else:
            self.maxdims = maxdims

        self.sigma = sigma
        self.nsigma = nsigma
        self.recalculate = recalculate
        self.dimensions = self.pbc.sum()

        if self.dimensions == 1 or self.dimensions == 2:
            for direction in range(3):
                if not self.pbc[direction]:
                    if self.maxdims[direction] is not None:
                        if self.maxdims[direction] <= 0:
                            e = '''If a max thickness is specificed in maxdims
                                  for a non-periodic direction, it has to be
                                  strictly positive.'''
                            raise ValueError(e)
Example #4
0
    def minkowski_reduce(self):
        """Minkowski-reduce this cell, returning new cell and mapping.

        See also :func:`ase.geometry.minkowski_reduction.minkowski_reduce`."""
        from ase.geometry.minkowski_reduction import minkowski_reduce
        cell, op = minkowski_reduce(self, self.any(1) & pbc2pbc(self._pbc))
        result = Cell(cell)
        result._pbc = self._pbc.copy()
        return result, op
Example #5
0
    def get_bravais_lattice(self, eps=2e-4, *, pbc=True):
        """Return :class:`~ase.lattice.BravaisLattice` for this cell:

        >>> cell = Cell.fromcellpar([4, 4, 4, 60, 60, 60])
        >>> print(cell.get_bravais_lattice())
        FCC(a=5.65685)

        .. note:: The Bravais lattice object follows the AFlow
           conventions.  ``cell.get_bravais_lattice().tocell()`` may
           differ from the original cell by a permutation or other
           operation which maps it to the AFlow convention.  For
           example, the orthorhombic lattice enforces a < b < c.

           To build a bandpath for a particular cell, use
           :meth:`ase.cell.Cell.bandpath` instead of this method.
           This maps the kpoints back to the original input cell.

        """
        from ase.lattice import identify_lattice
        pbc = self.any(1) & pbc2pbc(pbc)
        lat, op = identify_lattice(self, eps=eps, pbc=pbc)
        return lat
Example #6
0
def wrap_positions(positions,
                   cell,
                   pbc=True,
                   center=(0.5, 0.5, 0.5),
                   pretty_translation=False,
                   eps=1e-7):
    """Wrap positions to unit cell.

    Returns positions changed by a multiple of the unit cell vectors to
    fit inside the space spanned by these vectors.  See also the
    :meth:`ase.Atoms.wrap` method.

    Parameters:

    positions: float ndarray of shape (n, 3)
        Positions of the atoms
    cell: float ndarray of shape (3, 3)
        Unit cell vectors.
    pbc: one or 3 bool
        For each axis in the unit cell decides whether the positions
        will be moved along this axis.
    center: three float
        The positons in fractional coordinates that the new positions
        will be nearest possible to.
    pretty_translation: bool
        Translates atoms such that fractional coordinates are minimized.
    eps: float
        Small number to prevent slightly negative coordinates from being
        wrapped.

    Example:

    >>> from ase.geometry import wrap_positions
    >>> wrap_positions([[-0.1, 1.01, -0.5]],
    ...                [[1, 0, 0], [0, 1, 0], [0, 0, 4]],
    ...                pbc=[1, 1, 0])
    array([[ 0.9 ,  0.01, -0.5 ]])
    """

    if not hasattr(center, '__len__'):
        center = (center, ) * 3

    pbc = pbc2pbc(pbc)
    shift = np.asarray(center) - 0.5 - eps

    # Don't change coordinates when pbc is False
    shift[np.logical_not(pbc)] = 0.0

    assert np.asarray(cell)[np.asarray(pbc)].any(axis=1).all(), (cell, pbc)

    cell = complete_cell(cell)
    fractional = np.linalg.solve(cell.T, np.asarray(positions).T).T - shift

    if pretty_translation:
        fractional = translate_pretty(fractional, pbc)
        shift = np.asarray(center) - 0.5
        shift[np.logical_not(pbc)] = 0.0
        fractional += shift
    else:
        for i, periodic in enumerate(pbc):
            if periodic:
                fractional[:, i] %= 1.0
                fractional[:, i] += shift[i]

    return np.dot(fractional, cell)
Example #7
0
def get_2d_bravais_lattice(origcell, eps=2e-4, *, pbc=True):

    pbc = origcell.any(1) & pbc2pbc(pbc)
    if list(pbc) != [1, 1, 0]:
        raise UnsupportedLattice(
            'Can only get 2D Bravais lattice of cell with '
            'pbc==[1, 1, 0]; but we have {}'.format(pbc))

    nonperiodic = pbc.argmin()
    # Start with op = I
    ops = [np.eye(3)]
    for i in range(-1, 1):
        for j in range(-1, 1):
            op = [[1, j], [i, 1]]
            if np.abs(np.linalg.det(op)) > 1e-5:
                # Only touch periodic dirs:
                op = np.insert(op, nonperiodic, [0, 0], 0)
                op = np.insert(op, nonperiodic, ~pbc, 1)
                ops.append(np.array(op))

    def allclose(a, b):
        return np.allclose(a, b, atol=eps)

    symrank = 0
    for op in ops:
        cell = Cell(op.dot(origcell))
        cellpar = cell.cellpar()
        angles = cellpar[3:]
        # Find a, b and gamma
        gamma = angles[~pbc][0]
        a, b = cellpar[:3][pbc]

        anglesm90 = np.abs(angles - 90)
        # Maximum one angle different from 90 deg in 2d please
        if np.sum(anglesm90 > eps) > 1:
            continue

        all_lengths_equal = abs(a - b) < eps

        if all_lengths_equal:
            if allclose(gamma, 90):
                lat = SQR(a)
                rank = 5
            elif allclose(gamma, 120):
                lat = HEX2D(a)
                rank = 4
            else:
                lat = CRECT(a, gamma)
                rank = 3
        else:
            if allclose(gamma, 90):
                lat = RECT(a, b)
                rank = 2
            else:
                lat = OBL(a, b, gamma)
                rank = 1

        op = lat.get_transformation(origcell, eps=eps)
        if not allclose(
                np.dot(op, lat.tocell())[pbc][:, pbc],
                origcell.array[pbc][:, pbc]):
            msg = ('Cannot recognize cell at all somehow! {}, {}, {}'.format(
                a, b, gamma))
            raise RuntimeError(msg)
        if rank > symrank:
            finalop = op
            symrank = rank
            finallat = lat

    return finallat, finalop.T
Example #8
0
def identify_lattice(cell, eps=2e-4, *, pbc=True):
    """Find Bravais lattice representing this cell.

    Returns Bravais lattice object representing the cell along with
    an operation that, applied to the cell, yields the same lengths
    and angles as the Bravais lattice object."""

    pbc = cell.any(1) & pbc2pbc(pbc)
    npbc = sum(pbc)

    if npbc == 1:
        i = np.argmax(pbc)  # index of periodic axis
        a = cell[i, i]
        if a < 0 or cell[i, [i - 1, i - 2]].any():
            raise ValueError(
                'Not a 1-d cell ASE can handle: {cell}.'.format(cell=cell))
        if i == 0:
            op = np.eye(3)
        elif i == 1:
            op = np.array([[0, 1, 0], [1, 0, 0], [0, 0, 1]])
        else:
            op = np.array([[0, 0, 1], [0, 1, 0], [1, 0, 0]])
        return LINE(a), op

    if npbc == 2:
        lat, op = get_2d_bravais_lattice(cell, eps, pbc=pbc)
        return lat, op

    if npbc != 3:
        raise ValueError('System must be periodic either '
                         'along all three axes, '
                         'along two first axes or, '
                         'along the third axis.  '
                         'Got pbc={}'.format(pbc))

    from ase.geometry.bravais_type_engine import niggli_op_table

    if cell.rank < 3:
        raise ValueError('Expected 3 linearly independent cell vectors')
    rcell, reduction_op = cell.niggli_reduce(eps=eps)

    # We tabulate the cell's Niggli-mapped versions so we don't need to
    # redo any work when the same Niggli-operation appears multiple times
    # in the table:
    memory = {}

    # We loop through the most symmetric kinds (CUB etc.) and return
    # the first one we find:
    for latname in LatticeChecker.check_order:
        # There may be multiple Niggli operations that produce valid
        # lattices, at least for MCL.  In that case we will pick the
        # one whose angle is closest to 90, but it means we cannot
        # just return the first one we find so we must remember then:
        matching_lattices = []

        for op_key in niggli_op_table[latname]:
            checker_and_op = memory.get(op_key)
            if checker_and_op is None:
                normalization_op = np.array(op_key).reshape(3, 3)
                candidate = Cell(np.linalg.inv(normalization_op.T) @ rcell)
                checker = LatticeChecker(candidate, eps=eps)
                memory[op_key] = (checker, normalization_op)
            else:
                checker, normalization_op = checker_and_op

            lat = checker.query(latname)
            if lat is not None:
                op = normalization_op @ np.linalg.inv(reduction_op)
                matching_lattices.append((lat, op))

        # Among any matching lattices, return the one with lowest
        # orthogonality defect:
        best = None
        best_defect = np.inf
        for lat, op in matching_lattices:
            cell = lat.tocell()
            lengths = cell.lengths()
            defect = np.prod(lengths) / cell.volume
            if defect < best_defect:
                best = lat, op
                best_defect = defect

        if best is not None:
            return best
Example #9
0
def minkowski_reduce(cell, pbc=True):
    """Calculate a Minkowski-reduced lattice basis.  The reduced basis
    has the shortest possible vector lengths and has
    norm(a) <= norm(b) <= norm(c).

    Implements the method described in:

    Low-dimensional Lattice Basis Reduction Revisited
    Nguyen, Phong Q. and Stehlé, Damien,
    ACM Trans. Algorithms 5(4) 46:1--46:48, 2009
    https://doi.org/10.1145/1597036.1597050

    Parameters:

    cell: array
        The lattice basis to reduce (in row-vector format).
    pbc: array, optional
        The periodic boundary conditions of the cell (Default `True`).
        If `pbc` is provided, only periodic cell vectors are reduced.

    Returns:

    rcell: array
        The reduced lattice basis.
    op: array
        The unimodular matrix transformation (rcell = op @ cell).
    """
    pbc = pbc2pbc(pbc)
    dim = pbc.sum()

    op = np.eye(3).astype(np.int)
    if dim == 2:
        perm = np.argsort(pbc, kind='merge')[::-1]  # stable sort
        pcell = cell[perm][:, perm]

        norms = np.linalg.norm(pcell, axis=1)
        norms[2] = float("inf")
        indices = np.argsort(norms)
        op = op[indices]

        hu, hv = reduction_gauss(pcell, op[0], op[1])

        op[0] = hu
        op[1] = hv
        invperm = np.argsort(perm)
        op = op[invperm][:, invperm]

    elif dim == 3:
        _, op = reduction_full(cell)

    # maintain cell handedness
    if dim == 3:
        if np.sign(np.linalg.det(cell)) != np.sign(np.linalg.det(op @ cell)):
            op = -op
    elif dim == 2:
        index = np.argmin(pbc)
        _cell = cell.copy()
        _cell[index] = (1, 1, 1)
        _rcell = op @ cell
        _rcell[index] = (1, 1, 1)

        if np.sign(np.linalg.det(_cell)) != np.sign(np.linalg.det(_rcell)):
            index = np.argmax(pbc)
            op[index] *= -1

    norms1 = np.sort(np.linalg.norm(cell, axis=1))
    norms2 = np.sort(np.linalg.norm(op @ cell, axis=1))
    if not (norms2 <= norms1 + 1E-12).all():
        raise RuntimeError("Minkowski reduction failed")
    return op @ cell, op
def minkowski_reduce(cell, pbc=True):
    """Calculate a Minkowski-reduced lattice basis.  The reduced basis
    has the shortest possible vector lengths and has
    norm(a) <= norm(b) <= norm(c).

    Implements the method described in:

    Low-dimensional Lattice Basis Reduction Revisited
    Nguyen, Phong Q. and Stehlé, Damien,
    ACM Trans. Algorithms 5(4) 46:1--46:48, 2009
    https://doi.org/10.1145/1597036.1597050

    Parameters:

    cell: array
        The lattice basis to reduce (in row-vector format).
    pbc: array, optional
        The periodic boundary conditions of the cell (Default `True`).
        If `pbc` is provided, only periodic cell vectors are reduced.

    Returns:

    rcell: array
        The reduced lattice basis.
    op: array
        The unimodular matrix transformation (rcell = op @ cell).
    """
    cell = Cell(cell)
    pbc = pbc2pbc(pbc)
    dim = pbc.sum()
    op = np.eye(3, dtype=int)
    if is_minkowski_reduced(cell, pbc):
        return cell, op

    if dim == 2:
        # permute cell so that first two vectors are the periodic ones
        perm = np.argsort(pbc, kind='merge')[::-1]    # stable sort
        pcell = cell[perm][:, perm]

        # perform gauss reduction
        norms = np.linalg.norm(pcell, axis=1)
        norms[2] = float("inf")
        indices = np.argsort(norms)
        op = op[indices]
        hu, hv = reduction_gauss(pcell, op[0], op[1])
        op[0] = hu
        op[1] = hv

        # undo above permutation
        invperm = np.argsort(perm)
        op = op[invperm][:, invperm]

        # maintain cell handedness
        index = np.argmin(pbc)
        normal = np.cross(cell[index - 2], cell[index - 1])
        normal /= np.linalg.norm(normal)

        _cell = cell.copy()
        _cell[index] = normal
        _rcell = op @ cell
        _rcell[index] = normal
        if _cell.handedness != Cell(_rcell).handedness:
            op[index - 1] *= -1

    elif dim == 3:
        _, op = reduction_full(cell)
        # maintain cell handedness
        if cell.handedness != Cell(op @ cell).handedness:
            op = -op

    norms1 = np.sort(np.linalg.norm(cell, axis=1))
    norms2 = np.sort(np.linalg.norm(op @ cell, axis=1))
    if (norms2 > norms1 + TOL).any():
        raise RuntimeError("Minkowski reduction failed")
    return op @ cell, op
def is_minkowski_reduced(cell, pbc=True):
    """Tests if a cell is Minkowski-reduced.

    Parameters:

    cell: array
        The lattice basis to test (in row-vector format).
    pbc: array, optional
        The periodic boundary conditions of the cell (Default `True`).
        If `pbc` is provided, only periodic cell vectors are tested.

    Returns:

    is_reduced: bool
        True if cell is Minkowski-reduced, False otherwise.
    """

    """These conditions are due to Minkowski, but a nice description in English
    can be found in the thesis of Carine Jaber: "Algorithmic approaches to
    Siegel's fundamental domain", https://www.theses.fr/2017UBFCK006.pdf
    This is also good background reading for Minkowski reduction.

    0D and 1D cells are trivially reduced. For 2D cells, the conditions which
    an already-reduced basis fulfil are:
    |b1| ≤ |b2|
    |b2| ≤ |b1 - b2|
    |b2| ≤ |b1 + b2|

    For 3D cells, the conditions which an already-reduced basis fulfil are:
    |b1| ≤ |b2| ≤ |b3|

    |b1 + b2|      ≥ |b2|
    |b1 + b3|      ≥ |b3|
    |b2 + b3|      ≥ |b3|
    |b1 - b2|      ≥ |b2|
    |b1 - b3|      ≥ |b3|
    |b2 - b3|      ≥ |b3|
    |b1 + b2 + b3| ≥ |b3|
    |b1 - b2 + b3| ≥ |b3|
    |b1 + b2 - b3| ≥ |b3|
    |b1 - b2 - b3| ≥ |b3|
    """
    pbc = pbc2pbc(pbc)
    dim = pbc.sum()
    if dim <= 1:
        return True

    if dim == 2:
        # reorder cell vectors to [shortest, longest, aperiodic]
        cell = cell.copy()
        cell[np.argmin(pbc)] = 0
        norms = np.linalg.norm(cell, axis=1)
        cell = cell[np.argsort(norms)[[1, 2, 0]]]

        A = [[0, 1, 0],
             [1, -1, 0],
             [1, 1, 0]]
        lhs = np.linalg.norm(A @ cell, axis=1)
        norms = np.linalg.norm(cell, axis=1)
        rhs = norms[[0, 1, 1]]
    else:
        A = [[0, 1, 0],
             [0, 0, 1],
             [1, 1, 0],
             [1, 0, 1],
             [0, 1, 1],
             [1, -1, 0],
             [1, 0, -1],
             [0, 1, -1],
             [1, 1, 1],
             [1, -1, 1],
             [1, 1, -1],
             [1, -1, -1]]
        lhs = np.linalg.norm(A @ cell, axis=1)
        norms = np.linalg.norm(cell, axis=1)
        rhs = norms[[0, 1, 1, 2, 2, 1, 2, 2, 2, 2, 2, 2]]
    return (lhs >= rhs - TOL).all()