Esempio n. 1
0
    def translated_edges(sl1, sl2, distance, color):
        # get distance in terms of unit cells
        d_cell = (distance + site_offsets[sl1] -
                  site_offsets[sl2]) @ np.linalg.inv(basis_vectors)

        if not np.all(is_approx_int(d_cell, atol=atol)):
            # error out
            msg = f"{distance} is invalid distance vector between sublattices {sl1}->{sl2}"
            # see if the user flipped the vector accidentally
            d_cell = (distance + site_offsets[sl2] -
                      site_offsets[sl1]) @ np.linalg.inv(basis_vectors)
            if np.all(is_approx_int(d_cell, atol=atol)):
                msg += f" (but valid {sl2}->{sl1})"
            raise ValueError(msg)

        d_cell = np.asarray(np.rint(d_cell), dtype=int)

        # catches self-referential and other unrealisable long edges
        if not np.all(d_cell < extent):
            raise ValueError(
                f"Distance vector {distance} does not fit into the lattice")

        # Unit cells of starting points
        start_min = np.where(pbc, 0, np.maximum(0, -d_cell))
        start_max = np.where(pbc, extent, extent - np.maximum(0, d_cell))
        start_ranges = [slice(lo, hi) for lo, hi in zip(start_min, start_max)]
        start = np.mgrid[start_ranges].reshape(len(extent), -1).T
        end = (start + d_cell) % extent

        # Convert to site indices
        start = site_to_idx((start, sl1), extent, site_offsets)
        end = site_to_idx((end, sl2), extent, site_offsets)

        return [(*edge, color) for edge in zip(start, end)]
Esempio n. 2
0
    def to_reciprocal_lattice(self, ks: Array) -> Array:
        """
        Converts wave vectors from Cartesian axes to reciprocal lattice vectors.

        Arguments:
            ks: wave vectors in Cartesian axes. Multidimensional arrays are accepted,
                the Cartesian coordinates must form the last dimension.

        Returns:
            The same wave vectors in the reciprocal basis **of the simulation box.**
            Valid wave vector components in this basis are integers in (periodic BCs)
            or zero (in open BCs).

        Throws an `InvalidWaveVectorError` if any of the supplied wave vectors
        are not reciprocal lattice vectors of the simulation box.
        """
        # Ensure that ks has at least 2 dimensions
        ks = _np.asarray(ks)
        if ks.ndim == 1:
            ks = ks[_np.newaxis, :]

        result = ks @ self._lattice_dims.T / (2 * pi)
        # Check that these are integers
        is_valid = is_approx_int(result)
        if not _np.all(is_valid):
            raise InvalidWaveVectorError(
                "Some wave vectors are not reciprocal lattice vectors of the simulation"
                "box spanned by\n"
                + "\n".join(
                    [
                        str(self._lattice_dims[i])
                        + (" (PBC)" if self.pbc[i] else " (OBC)")
                        for i in range(self.ndim)
                    ]
                )
            )

        result = _np.asarray(_np.rint(result), dtype=int)
        # For axes with non-periodic BCs, the k-component must be 0
        is_valid = _np.logical_or(self.pbc, result == 0)
        if not _np.all(is_valid):
            raise InvalidWaveVectorError(
                "Some wave vectors are inconisistent with open boundary conditions"
            )

        return result
Esempio n. 3
0
def screw_group(angle: float, trans: Array,
                origin: Array = (0, 0, 0)) -> PointGroup:
    """Returns the `PointGroup` generated by a screw composed of a translation
    by `trans` and a rotation by `angle` degrees around its direction.

    The axis passes through `origin` (defaults to the Cartesian origin).
    The order of the group is controlled by `angle`.
    The output is only a valid `PointGroup` after supplying a `unit_cell`
    consistent with the screw axis; otherwise, operations like `product_table`
    will fail.
    """
    out = [Identity()]
    trans = np.asarray(trans)
    for i in count(start=1):
        if is_approx_int(i * angle / 360):
            break
        out.append(screw(i * angle, i * trans, origin))
    return PointGroup(out, ndim=3)
Esempio n. 4
0
def __to_rational(x: float) -> Tuple[int, int]:
    denom = is_approx_int(x * np.arange(1, 100), atol=_naming_tol)
    if not denom.any():
        raise ValueError
    denom = np.arange(1, 100)[denom][0]
    return int(np.rint(x * denom)), denom