예제 #1
0
    def test_lll_basis(self):
        a = np.array([1., 0.1, 0.])
        b = np.array([0., 2., 0.])
        c = np.array([0., 0., 3.])

        l1 = Lattice([a, b, c])
        l2 = Lattice([a + b, b + c, c])

        ccoords = np.array([[1, 1, 2], [2, 2, 1.5]])
        l1_fcoords = l1.get_fractional_coords(ccoords)
        l2_fcoords = l2.get_fractional_coords(ccoords)

        self.assertArrayAlmostEqual(l1.matrix, l2.lll_matrix)
        self.assertArrayAlmostEqual(np.dot(l2.lll_mapping, l2.matrix),
                                    l1.matrix)

        self.assertArrayAlmostEqual(np.dot(l2_fcoords, l2.matrix),
                                    np.dot(l1_fcoords, l1.matrix))

        lll_fcoords = l2.get_lll_frac_coords(l2_fcoords)

        self.assertArrayAlmostEqual(lll_fcoords, l1_fcoords)
        self.assertArrayAlmostEqual(l1.get_cartesian_coords(lll_fcoords),
                                    np.dot(lll_fcoords, l2.lll_matrix))

        self.assertArrayAlmostEqual(l2.get_frac_coords_from_lll(lll_fcoords),
                                    l2_fcoords)
예제 #2
0
    def test_lll_basis(self):
        a = np.array([1., 0.1, 0.])
        b = np.array([0., 2., 0.])
        c = np.array([0., 0., 3.])

        l1 = Lattice([a, b, c])
        l2 = Lattice([a + b, b + c, c])

        ccoords = np.array([[1, 1, 2], [2, 2, 1.5]])
        l1_fcoords = l1.get_fractional_coords(ccoords)
        l2_fcoords = l2.get_fractional_coords(ccoords)

        self.assertArrayAlmostEqual(l1.matrix, l2.lll_matrix)
        self.assertArrayAlmostEqual(np.dot(l2.lll_mapping, l2.matrix),
                                    l1.matrix)

        self.assertArrayAlmostEqual(np.dot(l2_fcoords, l2.matrix),
                                    np.dot(l1_fcoords, l1.matrix))

        lll_fcoords = l2.get_lll_frac_coords(l2_fcoords)

        self.assertArrayAlmostEqual(lll_fcoords, l1_fcoords)
        self.assertArrayAlmostEqual(l1.get_cartesian_coords(lll_fcoords),
                                    np.dot(lll_fcoords, l2.lll_matrix))

        self.assertArrayAlmostEqual(l2.get_frac_coords_from_lll(lll_fcoords),
                                    l2_fcoords)
예제 #3
0
    def write_vib_file(self, xyz_file, qpoint, displ, do_real=True, frac_coords=True, scale_matrix=None, max_supercell=None):
        """
        write into the file descriptor xyz_file the positions and displacements of the atoms

        Args:
            xyz_file: file_descriptor
            qpoint: qpoint to be analyzed
            displ: eigendisplacements to be analyzed
            do_real: True if you want to get only real part, False means imaginary part
            frac_coords: True if the eigendisplacements are given in fractional coordinates
            scale_matrix: Scale matrix for supercell
            max_supercell: Maximum size of supercell vectors with respect to primitive cell
        """
        if scale_matrix is None:
            if max_supercell is None:
                raise ValueError("If scale_matrix is not provided, please provide max_supercell !")

            scale_matrix = self.get_smallest_supercell(qpoint, max_supercell=max_supercell)

        old_lattice = self._lattice
        new_lattice = Lattice(np.dot(scale_matrix, old_lattice.matrix))

        tvects = self.get_trans_vect(scale_matrix)

        new_displ = np.zeros(3, dtype=np.float)

        fmtstr = "{{}} {{:.{0}f}} {{:.{0}f}} {{:.{0}f}} {{:.{0}f}} {{:.{0}f}} {{:.{0}f}}\n".format(6)

        for at, site in enumerate(self):
            for t in tvects:
                if do_real:
                    new_displ[:] = np.real(np.exp(2*1j*np.pi*(np.dot(qpoint,t)))*displ[at,:])
                else:
                    new_displ[:] = np.imag(np.exp(2*1j*np.pi*(np.dot(qpoint,t)))*displ[at,:])
                if frac_coords:
                    # Convert to fractional coordinates.
                    new_displ = self.lattice.get_cartesian_coords(new_displ)

                # We don't normalize here !!!
                fcoords = site.frac_coords + t

                coords = old_lattice.get_cartesian_coords(fcoords)

                new_fcoords = new_lattice.get_fractional_coords(coords)

                # New_fcoords -> map into 0 - 1
                new_fcoords = np.mod(new_fcoords, 1)
                coords = new_lattice.get_cartesian_coords(new_fcoords)

                xyz_file.write(fmtstr.format(site.specie, coords[0], coords[1], coords[2], new_displ[0], new_displ[1], new_displ[2]))
예제 #4
0
def calc_lattice_energy_and_pot(atomic_coords_without_defect: List[np.ndarray],
                                charge: int,
                                defect_center_coords: List[float],
                                ewald: "Ewald",
                                lattice: Lattice) -> Tuple[float, List[float]]:
    """

    Args:
        atomic_coords_without_defect (List):
            List of fractional coordinates for all the atomic sites except
            for the defect.
            e.g., [[0.0, 0.0, 0.0], [0.1, 0.1, 0.1], ...]
        charge:
            Defect charge state.
        defect_center_coords:
            Defect center position in fractional coordinates.
        ewald:
            Ewald object.
        lattice:
            Lattice object for the supercell.

    Returns:
        Tuple of the lattice energy and list of the model potential.
    """

    volume = lattice.volume
    coeff, diff_pot, mod_ewald_param, root_det_epsilon = \
        constants_for_anisotropic_ewald_sum(charge, ewald, volume)
    # model potential and lattice energy
    model_pot = []
    for r in atomic_coords_without_defect:
        # Ewald real part
        # \sum erfc(ewald*\sqrt(R*\epsilon_inv*R))
        # / \sqrt(det(\epsilon)) / \sqrt(R*\epsilon_inv*R) [1/A]
        shift = lattice.get_cartesian_coords(r - defect_center_coords)

        real_part, reciprocal_part = \
            calc_ewald_sum(ewald.dielectric_tensor,
                           ewald.real_lattice_set(True, shift),
                           ewald.reciprocal_lattice_set(),
                           mod_ewald_param=mod_ewald_param,
                           root_det_epsilon=root_det_epsilon,
                           volume=volume)

        model_pot.append((real_part + reciprocal_part + diff_pot) * coeff)
    lattice_energy = point_charge_energy(charge, ewald, volume)
    return lattice_energy, model_pot
예제 #5
0
    def get_displacements(self):
        """
        Return the initial structure and displacements for each time step.
        Used to interface with the DiffusionAnalyzer.

        Returns:
            Structure object, numpy array of displacements
        """
        lattice = Lattice([[self.box_lengths[0], 0, 0],
                           [0, self.box_lengths[1], 0],
                           [0, 0, self.box_lengths[2]]])
        mass_to_symbol = dict(
            (round(y["Atomic mass"], 1), x) for x, y in _pt_data.items())
        unique_atomic_masses = np.array(self.lammps_data.atomic_masses)[:, 1]
        frac_coords = []
        for step in range(self.timesteps.size):
            begin = step * self.natoms
            end = (step + 1) * self.natoms
            mol_vector_structured = \
                self.trajectory[begin:end][:][["x", "y", "z"]]
            new_shape = mol_vector_structured.shape + (-1, )
            mol_vector = mol_vector_structured.view(
                np.float64).reshape(new_shape)
            coords = mol_vector.copy()
            if step == 0:
                species = [
                    mass_to_symbol[round(unique_atomic_masses[atype - 1], 1)]
                    for atype in self.trajectory[begin:end][:]["atom_type"]
                ]
                structure = Structure(lattice,
                                      species,
                                      coords,
                                      coords_are_cartesian=True)
            step_frac_coords = [
                lattice.get_fractional_coords(crd) for crd in coords
            ]
            frac_coords.append(np.array(step_frac_coords)[:, None])
        frac_coords = np.concatenate(frac_coords, axis=1)
        dp = frac_coords[:, 1:] - frac_coords[:, :-1]
        dp = dp - np.round(dp)
        f_disp = np.cumsum(dp, axis=1)
        disp = lattice.get_cartesian_coords(f_disp)
        return structure, disp
예제 #6
0
    def test_get_distance_and_image_strict(self):
        for count in range(10):
            lengths = [np.random.randint(1, 100) for i in range(3)]
            lattice = [np.random.rand(3) * lengths[i]
                               for i in range(3)]
            lattice = Lattice(np.array(lattice))

            f1 = np.random.rand(3)
            f2 = np.random.rand(3)

            scope = list(range(-3, 4))
            min_image_dist = (float("inf"), None)
            for image in itertools.product(scope, scope, scope):
                cart = lattice.get_cartesian_coords(f1 - (f2 + image))
                dist = np.dot(cart, cart) ** 0.5
                if dist < min_image_dist[0]:
                    min_image_dist = (dist, image)

            pmg_result = lattice.get_distance_and_image(f1, f2)
            self.assertGreaterEqual(min_image_dist[0] + 1e-7, pmg_result[0])
예제 #7
0
    def test_get_distance_and_image_strict(self):
        for count in range(10):
            lengths = [np.random.randint(1, 100) for i in range(3)]
            lattice = [np.random.rand(3) * lengths[i] for i in range(3)]
            lattice = Lattice(np.array(lattice))

            f1 = np.random.rand(3)
            f2 = np.random.rand(3)

            scope = list(range(-3, 4))
            min_image_dist = (float("inf"), None)
            for image in itertools.product(scope, scope, scope):
                cart = lattice.get_cartesian_coords(f1 - (f2 + image))
                dist = np.dot(cart, cart)**0.5
                if dist < min_image_dist[0]:
                    min_image_dist = (dist, image)

            pmg_result = lattice.get_distance_and_image(f1, f2)
            self.assertGreaterEqual(min_image_dist[0] + 1e-7, pmg_result[0])
            if abs(min_image_dist[0] - pmg_result[0]) < 1e-12:
                self.assertArrayAlmostEqual(min_image_dist[1], pmg_result[1])
예제 #8
0
파일: output.py 프로젝트: setten/pymatgen
    def get_displacements(self):
        """
        Return the initial structure and displacements for each time step.
        Used to interface with the DiffusionAnalyzer.

        Returns:
            Structure object, numpy array of displacements
        """
        lattice = Lattice([[self.box_lengths[0], 0, 0],
                           [0, self.box_lengths[1], 0],
                           [0, 0, self.box_lengths[2]]])
        mass_to_symbol = dict(
            (round(y["Atomic mass"], 1), x) for x, y in _pt_data.items())
        unique_atomic_masses = np.array([d["mass"] for d in self.lammps_data.masses])
        frac_coords = []
        for step in range(self.timesteps.size):
            begin = step * self.natoms
            end = (step + 1) * self.natoms
            mol_vector_structured = \
                self.trajectory[begin:end][:][["x", "y", "z"]]
            new_shape = mol_vector_structured.shape + (-1,)
            mol_vector = mol_vector_structured.view(np.float64).reshape(
                new_shape)
            coords = mol_vector.copy()
            if step == 0:
                species = [
                    mass_to_symbol[round(unique_atomic_masses[atype - 1], 1)]
                    for atype in self.trajectory[begin:end][:]["atom_type"]]
                structure = Structure(lattice, species, coords,
                                      coords_are_cartesian=True)
            step_frac_coords = [lattice.get_fractional_coords(crd)
                                for crd in coords]
            frac_coords.append(np.array(step_frac_coords)[:, None])
        frac_coords = np.concatenate(frac_coords, axis=1)
        dp = frac_coords[:, 1:] - frac_coords[:, :-1]
        dp = dp - np.round(dp)
        f_disp = np.cumsum(dp, axis=1)
        disp = lattice.get_cartesian_coords(f_disp)
        return structure, disp
예제 #9
0
    def write_vib_file(self,
                       xyz_file,
                       qpoint,
                       displ,
                       do_real=True,
                       frac_coords=True,
                       scale_matrix=None,
                       max_supercell=None):
        """
        write into the file descriptor xyz_file the positions and displacements of the atoms

        Args:
            xyz_file: file_descriptor
            qpoint: qpoint to be analyzed
            displ: eigendisplacements to be analyzed
            do_real: True if you want to get only real part, False means imaginary part
            frac_coords: True if the eigendisplacements are given in fractional coordinates
            scale_matrix: Scale matrix for supercell
            max_supercell: Maximum size of supercell vectors with respect to primitive cell
        """
        if scale_matrix is None:
            if max_supercell is None:
                raise ValueError(
                    "If scale_matrix is not provided, please provide max_supercell !"
                )

            scale_matrix = self.get_smallest_supercell(
                qpoint, max_supercell=max_supercell)

        old_lattice = self._lattice
        new_lattice = Lattice(np.dot(scale_matrix, old_lattice.matrix))

        tvects = self.get_trans_vect(scale_matrix)

        new_displ = np.zeros(3, dtype=np.float)

        fmtstr = "{{}} {{:.{0}f}} {{:.{0}f}} {{:.{0}f}} {{:.{0}f}} {{:.{0}f}} {{:.{0}f}}\n".format(
            6)

        for at, site in enumerate(self):
            for t in tvects:
                if do_real:
                    new_displ[:] = np.real(
                        np.exp(2 * 1j * np.pi * (np.dot(qpoint, t))) *
                        displ[at, :])
                else:
                    new_displ[:] = np.imag(
                        np.exp(2 * 1j * np.pi * (np.dot(qpoint, t))) *
                        displ[at, :])
                if frac_coords:
                    # Convert to fractional coordinates.
                    new_displ = self.lattice.get_cartesian_coords(new_displ)

                # We don't normalize here !!!
                fcoords = site.frac_coords + t

                coords = old_lattice.get_cartesian_coords(fcoords)

                new_fcoords = new_lattice.get_fractional_coords(coords)

                # New_fcoords -> map into 0 - 1
                new_fcoords = np.mod(new_fcoords, 1)
                coords = new_lattice.get_cartesian_coords(new_fcoords)

                xyz_file.write(
                    fmtstr.format(site.specie, coords[0], coords[1], coords[2],
                                  new_displ[0], new_displ[1], new_displ[2]))
예제 #10
0
class Structure(SiteCollection, MSONable):
    """
    Basic Structure object with periodicity. Essentially a sequence of
    PeriodicSites having a common lattice. Structure is made to be immutable
    so that they can function as keys in a dict. Modifications should be done
    by making a new Structure using the structure_modifier module or your own
    methods. Structure extends Sequence and Hashable, which means that in many
    cases, it can be used like any Python sequence. Iterating through a
    structure is equivalent to going through the sites in sequence.
    """

    def __init__(self, lattice, species, coords, validate_proximity=False,
                 to_unit_cell=False, coords_are_cartesian=False,
                 site_properties=None):
        """
        Create a periodic structure.

        Args:
            lattice:
                The lattice, either as a pymatgen.core.lattice.Lattice or
                simply as any 2D array. Each row should correspond to a lattice
                vector. E.g., [[10,0,0], [20,10,0], [0,0,30]] specifies a
                lattice with lattice vectors [10,0,0], [20,10,0] and [0,0,30].
            species:
                List of species on each site. Can take in flexible input,
                including:

                i.  A sequence of element / specie specified either as string
                    symbols, e.g. ["Li", "Fe2+", "P", ...] or atomic numbers,
                    e.g., (3, 56, ...) or actual Element or Specie objects.

                ii. List of dict of elements/species and occupancies, e.g.,
                    [{"Fe" : 0.5, "Mn":0.5}, ...]. This allows the setup of
                    disordered structures.
            fractional_coords:
                list of fractional coordinates of each species.
            validate_proximity:
                Whether to check if there are sites that are less than 0.01 Ang
                apart. Defaults to False.
            coords_are_cartesian:
                Set to True if you are providing coordinates in cartesian
                coordinates. Defaults to False.
            site_properties:
                Properties associated with the sites as a dict of sequences,
                e.g., {"magmom":[5,5,5,5]}. The sequences have to be the same
                length as the atomic species and fractional_coords.
                Defaults to None for no properties.
        """
        if len(species) != len(coords):
            raise StructureError("The list of atomic species must be of the"
                                 "same length as the list of fractional"
                                 " coordinates.")

        if isinstance(lattice, Lattice):
            self._lattice = lattice
        else:
            self._lattice = Lattice(lattice)

        sites = []
        for i in xrange(len(species)):
            prop = None
            if site_properties:
                prop = {k: v[i] for k, v in site_properties.items()}
            sites.append(PeriodicSite(species[i], coords[i],
                                      self._lattice, to_unit_cell,
                                      coords_are_cartesian,
                                      properties=prop))
        self._sites = tuple(sites)
        if validate_proximity and not self.is_valid():
            raise StructureError(("Structure contains sites that are ",
                                  "less than 0.01 Angstrom apart!"))

    @staticmethod
    def from_sites(sites):
        """
        Convenience constructor to make a Structure from a list of sites.

        Args:
            sites:
                Sequence of PeriodicSites. Sites must have the same lattice.
        """
        props = collections.defaultdict(list)
        lattice = None
        for site in sites:
            if not lattice:
                lattice = site.lattice
            elif site.lattice != lattice:
                raise ValueError("Sites must belong to the same lattice")
            for k, v in site.properties.items():
                props[k].append(v)
        return Structure(lattice,
                         [site.species_and_occu for site in sites],
                         [site.frac_coords for site in sites],
                         site_properties=props)

    @property
    def sites(self):
        """
        Returns an iterator for the sites in the Structure.
        """
        return self._sites

    @property
    def lattice(self):
        """
        Lattice of the structure.
        """
        return self._lattice

    def lattice_vectors(self, space="r"):
        """
        Returns the vectors of the unit cell in Angstrom.

        Args:
            space:
                "r" for real space vectors, "g" for reciprocal space basis
                vectors.
        """
        if space.lower() == "r":
            return self.lattice.matrix
        if space.lower() == "g":
            return self.lattice.reciprocal_lattice.matrix
        raise ValueError("Wrong value for space: %s " % space)

    @property
    def density(self):
        """
        Returns the density in units of g/cc
        """
        constant = AMU_TO_KG * 1000 / 1e-24
        return self.composition.weight / self.volume * constant

    def __eq__(self, other):
        if other is None:
            return False
        if len(self) != len(other):
            return False
        if self._lattice != other._lattice:
            return False
        for site in self:
            if site not in other:
                return False
        return True

    def __ne__(self, other):
        return not self.__eq__(other)

    def __hash__(self):
        # For now, just use the composition hash code.
        return self.composition.__hash__()

    @property
    def frac_coords(self):
        """
        Returns the fractional coordinates.
        """
        return [site.frac_coords for site in self._sites]

    @property
    def volume(self):
        """
        Returns the volume of the structure.
        """
        return self._lattice.volume

    def get_distance(self, i, j, jimage=None):
        """
        Get distance between site i and j assuming periodic boundary
        conditions. If the index jimage of two sites atom j is not specified it
        selects the jimage nearest to the i atom and returns the distance and
        jimage indices in terms of lattice vector translations if the index
        jimage of atom j is specified it returns the distance between the i
        atom and the specified jimage atom.

        Args:
            i:
                Index of first site
            j:
                Index of second site
            jimage:
                Number of lattice translations in each lattice direction.

                Default is None for nearest image.

        Returns:
            distance
        """
        return self[i].distance(self[j], jimage)

    def get_sites_in_sphere(self, pt, r, include_index=False):
        """
        Find all sites within a sphere from the point. This includes sites
        in other periodic images.

        Algorithm:

        1. place sphere of radius r in crystal and determine minimum supercell
           (parallelpiped) which would contain a sphere of radius r. for this
           we need the projection of a_1 on a unit vector perpendicular
           to a_2 & a_3 (i.e. the unit vector in the direction b_1) to
           determine how many a_1"s it will take to contain the sphere.

           Nxmax = r * length_of_b_1 / (2 Pi)

        2. keep points falling within r.

        Args:
            pt:
                cartesian coordinates of center of sphere.
            r:
                radius of sphere.
            include_index:
                boolean that determines whether the non-supercell site index
                is included in the returned data

        Returns:
            [(site, dist) ...] since most of the time, subsequent processing
            requires the distance.
        """
        site_fcoords = np.mod(self.frac_coords, 1)
        neighbors = []
        for fcoord, dist, i in get_points_in_sphere_pbc(self._lattice,
                                                        site_fcoords, pt, r):
            nnsite = PeriodicSite(self[i].species_and_occu,
                                  fcoord, self._lattice,
                                  properties=self[i].properties)
            neighbors.append((nnsite, dist) if not include_index
                             else (nnsite, dist, i))
        return neighbors

    def get_neighbors(self, site, r, include_index=False):
        """
        Get all neighbors to a site within a sphere of radius r.  Excludes the
        site itself.

        Args:
            site:
                site, which is the center of the sphere.
            r:
                radius of sphere.
            include_index:
                boolean that determines whether the non-supercell site index
                is included in the returned data

        Returns:
            [(site, dist) ...] since most of the time, subsequent processing
            requires the distance.
        """
        nn = self.get_sites_in_sphere(site.coords, r,
                                      include_index=include_index)
        return [d for d in nn if site != d[0]]

    def get_all_neighbors(self, r, include_index=False):
        """
        Get neighbors for each atom in the unit cell, out to a distance r
        Returns a list of list of neighbors for each site in structure.
        Use this method if you are planning on looping over all sites in the
        crystal. If you only want neighbors for a particular site, use the
        method get_neighbors as it may not have to build such a large supercell
        However if you are looping over all sites in the crystal, this method
        is more efficient since it only performs one pass over a large enough
        supercell to contain all possible atoms out to a distance r.
        The return type is a [(site, dist) ...] since most of the time,
        subsequent processing requires the distance.

        Args:
            r:
                radius of sphere.
            include_index:
                boolean that determines whether the non-supercell site index
                is included in the returned data

        Returns:
            A list of a list of nearest neighbors for each site, i.e.,
            [[(site, dist, index) ...], ..]
            Index only supplied if include_index = True.
            The index is the index of the site in the original (non-supercell)
            structure. This is needed for ewaldmatrix by keeping track of which
            sites contribute to the ewald sum.
        """

        # Use same algorithm as get_sites_in_sphere to determine supercell but
        # loop over all atoms in crystal
        recp_len = self.lattice.reciprocal_lattice.abc
        sr = r + 0.15
        nmax = [sr * l / (2 * math.pi) for l in recp_len]
        site_nminmax = []
        floor = math.floor
        for site in self:
            pcoords = site.frac_coords
            inmax = [int(floor(pcoords[i] + nmax[i])) for i in xrange(3)]
            inmin = [int(floor(pcoords[i] - nmax[i])) for i in xrange(3)]
            site_nminmax.append(zip(inmin, inmax))

        nmin = [min([i[j][0] for i in site_nminmax]) for j in xrange(3)]
        nmax = [max([i[j][1] for i in site_nminmax]) for j in xrange(3)]

        all_ranges = [range(nmin[i], nmax[i] + 1) for i in xrange(3)]

        neighbors = [list() for i in xrange(len(self._sites))]
        all_fcoords = np.mod(self.frac_coords, 1)

        site_coords = np.array(self.cart_coords)
        latt = self._lattice
        frac_2_cart = latt.get_cartesian_coords
        n = len(self)
        indices = np.array(range(n))
        for image in itertools.product(*all_ranges):
            for (j, fcoord) in enumerate(all_fcoords):
                fcoords = fcoord + image
                coords = frac_2_cart(fcoords)
                submat = np.tile(coords, (n, 1))
                dists = (site_coords - submat) ** 2
                dists = np.sqrt(dists.sum(axis=1))
                withindists = (dists <= r) * (dists > 1e-8)
                sp = self[j].species_and_occu
                props = self[j].properties
                for i in indices[withindists]:
                    nnsite = PeriodicSite(sp, fcoords, latt,
                                          properties=props)
                    item = (nnsite, dists[i], j) if include_index else (
                        nnsite, dists[i])
                    neighbors[i].append(item)
        return neighbors

    def get_neighbors_in_shell(self, origin, r, dr):
        """
        Returns all sites in a shell centered on origin (coords) between radii
        r-dr and r+dr.

        Args:
            origin:
                cartesian coordinates of center of sphere.
            r:
                inner radius of shell.
            dr:
                width of shell.

        Returns:
            [(site, dist) ...] since most of the time, subsequent processing
            requires the distance.
        """
        outer = self.get_sites_in_sphere(origin, r + dr)
        inner = r - dr
        return [(site, dist) for (site, dist) in outer if dist > inner]

    def get_sorted_structure(self):
        """
        Get a sorted copy of the structure.
        Sites are sorted by the electronegativity of the species.
        """
        sites = sorted(self)
        return Structure.from_sites(sites)

    def get_reduced_structure(self, reduction_algo="niggli"):
        """
        Get a reduced structure.

        Args:
            reduction_algo:
                The lattice reduction algorithm to use. Currently supported
                options are "niggli" or "LLL".
        """
        if reduction_algo == "niggli":
            reduced_latt = self._lattice.get_niggli_reduced_lattice()
        elif reduction_algo == "LLL":
            reduced_latt = self._lattice.get_lll_reduced_lattice()
        else:
            raise ValueError("Invalid reduction algo : {}"
                             .format(reduction_algo))

        return Structure(reduced_latt, self.species_and_occu, self.cart_coords,
                         coords_are_cartesian=True, to_unit_cell=True)

    def copy(self, site_properties=None, sanitize=False):
        """
        Convenience method to get a copy of the structure, with options to add
        site properties.

        Args:
            site_properties:
                Properties to add or override. The properties are specified in
                the same way as the constructor, i.e., as a dict of the form
                {property: [values]}. The properties should be in the order of
                the *original* structure if you are performing sanitization.
            sanitize:
                If True, this method will return a sanitized structure.
                Sanitization performs a few things: (i) The sites are sorted
                by electronegativity, (ii) a LLL lattice reduction is carried
                out to obtain a relatively orthogonalized cell, (iii) all
                fractional coords for sites are mapped into the unit cell.

        Returns:
            A copy of the Structure, with optionally new site_properties and
            optionally sanitized.
        """
        props = self.site_properties
        if site_properties:
            props.update(site_properties)
        if not sanitize:
            return Structure(self._lattice,
                             [site.species_and_occu for site in self],
                             [site.frac_coords for site in self],
                             site_properties=props)
        else:
            reduced_latt = self._lattice.get_lll_reduced_lattice()
            new_sites = []
            for i, site in enumerate(self):
                frac_coords = reduced_latt.get_fractional_coords(site.coords)
                site_props = {}
                for p in props:
                    site_props[p] = props[p][i]
                new_sites.append(PeriodicSite(site.species_and_occu,
                                              frac_coords, reduced_latt,
                                              to_unit_cell=True,
                                              properties=site_props))
            new_sites = sorted(new_sites)
            return Structure.from_sites(new_sites)

    def interpolate(self, end_structure, nimages=10):
        """
        Interpolate between this structure and end_structure. Useful for
        construction of NEB inputs.

        Args:
            end_structure:
                structure to interpolate between this structure and end.
            nimages:
                number of interpolation images. Defaults to 10 images.

        Returns:
            List of interpolated structures. The starting and ending
            structures included as the first and last structures respectively.
            A total of (nimages + 1) structures are returned.
        """
        #Check length of structures
        if len(self) != len(end_structure):
            raise ValueError("Structures have different lengths!")

        #Check that both structures have the same lattice
        if not np.allclose(self.lattice.matrix, end_structure.lattice.matrix):
            raise ValueError("Structures with different lattices!")

        #Check that both structures have the same species
        for i in range(0, len(self)):
            if self[i].species_and_occu != end_structure[i].species_and_occu:
                raise ValueError("Different species!\nStructure 1:\n" +
                                 str(self) + "\nStructure 2\n" +
                                 str(end_structure))

        start_coords = np.array(self.frac_coords)
        end_coords = np.array(end_structure.frac_coords)

        vec = end_coords - start_coords
        structs = [Structure(self.lattice,
                             [site.species_and_occu for site in self._sites],
                             start_coords + float(x) / float(nimages) * vec,
                             site_properties=self.site_properties)
                   for x in range(0, nimages + 1)]
        return structs

    def get_primitive_structure(self, tolerance=0.25):
        """
        This finds a smaller unit cell than the input. Sometimes it doesn"t
        find the smallest possible one, so this method is recursively called
        until it is unable to find a smaller cell.

        The method works by finding possible smaller translations
        and then using that translational symmetry instead of one of the
        lattice basis vectors if more than one vector is found (usually the
        case for large cells) the one with the smallest norm is used.

        Things are done in fractional coordinates because its easier to
        translate back to the unit cell.

        NOTE: if the tolerance is greater than 1/2 the minimum inter-site
        distance, the algorithm may find 2 non-equivalent sites that are
        within tolerance of each other. The algorithm will reject this
        lattice.

        Args:
            tolerance:
                Tolerance for each coordinate of a particular site. For
                example, [0.5, 0, 0.5] in cartesian coordinates will be
                considered to be on the same coordinates as [0, 0, 0] for a
                tolerance of 0.5. Defaults to 0.5.

        Returns:
            The most primitive structure found. The returned structure is
            guaranteed to have len(new structure) <= len(structure).
        """
        original_volume = self.volume
        (reduced_formula, num_fu) =\
            self.composition.get_reduced_composition_and_factor()

        min_vol = original_volume * 0.5 / num_fu

        #get the possible symmetry vectors
        sites = sorted(self._sites, key=lambda site: site.species_string)
        grouped_sites = [list(a[1]) for a
                         in itertools.groupby(sites,
                                              key=lambda s: s.species_string)]
        min_site_list = min(grouped_sites, key=lambda group: len(group))

        min_site_list = [site.to_unit_cell for site in min_site_list]
        org = min_site_list[0].coords
        possible_vectors = [min_site_list[i].coords - org
                            for i in xrange(1, len(min_site_list))]

        #Let's try to use the shortest vector possible first. Allows for faster
        #convergence to primitive cell.
        possible_vectors = sorted(possible_vectors,
                                  key=lambda x: np.linalg.norm(x))

        # Pre-create a few varibles for faster lookup.
        all_coords = [site.coords for site in sites]
        all_sp = [site.species_and_occu for site in sites]
        new_structure = None

        #all lattice points need to be projected to 0 under new basis
        l_points = np.array([[0, 0, 1], [0, 1, 0], [0, 1, 1], [1, 0, 0],
                             [1, 0, 1], [1, 1, 0], [1, 1, 1]])
        l_points = self._lattice.get_cartesian_coords(l_points)

        for v, repl_pos in itertools.product(possible_vectors, xrange(3)):
            #Try combinations of new lattice vectors with existing lattice
            #vectors.
            latt = self._lattice.matrix
            latt[repl_pos] = v

            #Exclude coplanar lattices from consideration.
            if abs(np.dot(np.cross(latt[0], latt[1]), latt[2])) < min_vol:
                continue
            latt = Lattice(latt)

            #Convert to fractional tol
            tol = tolerance / np.array(latt.abc)

            #check validity of new basis
            new_l_points = latt.get_fractional_coords(l_points)
            f_l_dist = np.abs(new_l_points - np.round(new_l_points))
            if np.any(f_l_dist > tol[None, None, :]):
                continue

            all_frac = latt.get_fractional_coords(np.array(all_coords))

            #calculate grouping of equivalent sites, represented by
            #adjacency matrix
            fdist = all_frac[None, :, :] - all_frac[:, None, :]
            fdist = np.abs(fdist - np.round(fdist))
            groups = np.all(fdist < tol[None, None, :], axis=2)

            #check that all group sizes are the same
            sizes = np.unique(np.sum(groups, axis=0))
            if len(sizes) > 1:
                continue

            #check that reduction in number of sites was by the same
            #amount as the volume reduction
            if round(self._lattice.volume / latt.volume) != sizes[0]:
                continue

            new_sp = []
            new_frac = []
            #this flag is set to ensure that all sites in a group are
            #the same species, it is set to false if a group is found
            #where this is not the case
            correct = True

            added = np.zeros(len(groups), dtype='bool')
            for i, g in enumerate(groups):
                if added[i]:
                    continue
                indices = np.where(g)[0]
                i0 = indices[0]
                sp = all_sp[i0]
                added[indices] = 1
                if not all([all_sp[i] == sp for i in indices]):
                    correct = False
                    break
                new_sp.append(all_sp[i0])
                new_frac.append(all_frac[i0])

            if correct:
                new_structure = Structure(latt, new_sp, new_frac,
                                          to_unit_cell=True)
                break

        if new_structure and len(new_structure) != len(self):
            # If a more primitive structure has been found, try to find an
            # even more primitive structure again.
            return new_structure.get_primitive_structure(tolerance=tolerance)
        else:
            return self

    def __repr__(self):
        outs = ["Structure Summary", repr(self.lattice)]
        for s in self:
            outs.append(repr(s))
        return "\n".join(outs)

    def __str__(self):
        outs = ["Structure Summary ({s})".format(s=str(self.composition)),
                "Reduced Formula: {}"
                .format(self.composition.reduced_formula)]
        to_s = lambda x: "%0.6f" % x
        outs.append("abc   : " + " ".join([to_s(i).rjust(10)
                                           for i in self.lattice.abc]))
        outs.append("angles: " + " ".join([to_s(i).rjust(10)
                                           for i in self.lattice.angles]))
        outs.append("Sites ({i})".format(i=len(self)))
        for i, site in enumerate(self):
            outs.append(" ".join([str(i + 1), site.species_string,
                                  " ".join([to_s(j).rjust(12)
                                            for j in site.frac_coords])]))
        return "\n".join(outs)

    @property
    def to_dict(self):
        """
        Json-serializable dict representation of Structure
        """
        d = {"@module": self.__class__.__module__,
             "@class": self.__class__.__name__,
             "lattice": self._lattice.to_dict, "sites": []}
        for site in self:
            site_dict = site.to_dict
            del site_dict["lattice"]
            del site_dict["@module"]
            del site_dict["@class"]
            d["sites"].append(site_dict)
        return d

    @staticmethod
    def from_dict(d):
        """
        Reconstitute a Structure object from a dict representation of Structure
        created using to_dict.

        Args:
            d:
                dict representation of structure.

        Returns:
            Structure object
        """
        lattice = Lattice.from_dict(d["lattice"])
        sites = [PeriodicSite.from_dict(sd, lattice) for sd in d["sites"]]
        return Structure.from_sites(sites)
예제 #11
0
파일: voronoi.py 프로젝트: gmp007/amset
    def __init__(
        self,
        reciprocal_lattice: Lattice,
        original_points: np.ndarray,
        original_dim: np.ndarray,
        extra_points: np.ndarray,
        ir_to_full_idx: Optional[np.ndarray] = None,
        extra_ir_points_idx: Optional[np.ndarray] = None,
        nworkers: int = pdefaults["nworkers"],
    ):
        """

        Add a warning about only using the symmetry options if you are sure your
        extra k-points have been symmetrized

        Args:
            original_points:
            nworkers:
        """
        self._nworkers = nworkers if nworkers != -1 else cpu_count()
        self._final_points = np.concatenate([original_points, extra_points])
        self._reciprocal_lattice = reciprocal_lattice

        if ir_to_full_idx is None:
            ir_to_full_idx = np.arange(
                len(original_points) + len(extra_points))

        if extra_ir_points_idx is None:
            extra_ir_points_idx = np.arange(len(extra_points))

        logger.debug("Initializing periodic Voronoi calculator")
        all_points = np.concatenate((original_points, extra_points))

        logger.debug("  ├── getting supercell k-points")
        supercell_points = get_supercell_points(all_points)
        supercell_idxs = np.arange(supercell_points.shape[0])

        # filter points far from the zone boundary, this will lead to errors for
        # very small meshes < 5x5x5 but we are not interested in those
        mask = ((supercell_points > -0.75) &
                (supercell_points < 0.75)).all(axis=1)
        supercell_points = supercell_points[mask]
        supercell_idxs = supercell_idxs[mask]

        # want points in cartesian space so we can define a regular spherical
        # cutoff even if reciprocal lattice is not cubic. If we used a
        # fractional cutoff, the cutoff regions would not be spherical
        logger.debug("  ├── getting cartesian points")
        cart_points = reciprocal_lattice.get_cartesian_coords(supercell_points)
        cart_extra_points = reciprocal_lattice.get_cartesian_coords(
            extra_points[extra_ir_points_idx])

        # small cutoff is slightly larger than the max regular grid spacing
        # means at least 1 neighbour point will always be included in each
        # direction, need to find cartesian length which covers the longest direction
        # of the mesh
        spacing = 1 / original_dim
        body_diagonal = reciprocal_lattice.get_cartesian_coords(spacing)
        xy = reciprocal_lattice.get_cartesian_coords(
            [spacing[0], spacing[1], 0])
        xz = reciprocal_lattice.get_cartesian_coords(
            [spacing[0], 0, spacing[2]])
        yz = reciprocal_lattice.get_cartesian_coords(
            [0, spacing[1], spacing[2]])

        len_diagonal = np.linalg.norm(body_diagonal)
        len_xy = np.linalg.norm(xy)
        len_xz = np.linalg.norm(xz)
        len_yz = np.linalg.norm(yz)

        small_cutoff = (np.max([len_diagonal, len_xy, len_xz, len_yz]) * 1.6)
        big_cutoff = (small_cutoff * 1.77)

        logger.debug("  ├── initializing ball tree")

        # use BallTree for quickly evaluating which points are within cutoffs
        tree = BallTree(cart_points)

        n_supercell_points = len(supercell_points)

        # big points are those which surround the extra points within the big cutoff
        # (including the extra points themselves)
        logger.debug("  ├── calculating points in big radius")
        big_points_idx = _query_radius_iteratively(tree, n_supercell_points,
                                                   cart_extra_points,
                                                   big_cutoff)

        # Voronoi points are those we actually include in the Voronoi diagram
        self._voronoi_points = cart_points[big_points_idx]

        # small points are the points in all_points (i.e., original + extra points) for
        # which we want to calculate the Voronoi volumes. Outside the small cutoff, the
        # weights will just be the regular grid weight.
        logger.debug("  └── calculating points in small radius")
        small_points_idx = _query_radius_iteratively(tree, n_supercell_points,
                                                     cart_extra_points,
                                                     small_cutoff)

        # get the irreducible small points
        small_points_in_all_points = supercell_idxs[small_points_idx] % len(
            all_points)
        mapping = ir_to_full_idx[small_points_in_all_points]
        unique_mappings, ir_idx = np.unique(mapping, return_index=True)
        small_points_idx = small_points_idx[ir_idx]

        # get a mapping to go from the ir small points to the full BZ.
        groups = groupby(np.arange(len(all_points)), ir_to_full_idx)
        grouped_ir = groups[unique_mappings]
        counts = [len(g) for g in grouped_ir]
        self._expand_ir = np.repeat(np.arange(len(ir_idx)), counts)

        # get the indices of the expanded ir_small_points in all_points
        self._volume_in_final_idx = np.concatenate(grouped_ir)

        # get the indices of ir_small_points_idx (i.e., the points for which we will
        # calculate the volume) in voronoi_points
        self._volume_points_idx = _get_loc(big_points_idx, small_points_idx)

        # Prepopulate the final volumes array. By default, each point has the
        # volume of the original mesh. Note: at this point, the extra points
        # will have zero volume. This will array will be updated by
        # compute_volumes
        self._volume = reciprocal_lattice.volume
        self._final_volumes = np.full(len(all_points),
                                      1 / len(original_points))
        self._final_volumes[len(original_points):] = 0
        self._final_volumes[self._volume_in_final_idx] = 0
예제 #12
0
def make_supercell(structure,
                   distance,
                   method='bec',
                   wrap=True,
                   standardize=True,
                   do_niggli_first=True,
                   diagonal=False,
                   implementation='fort',
                   verbosity=1):
    """
    Creates from a given structure a supercell based on the required minimal dimension
    :param structure: The pymatgen structure to create the supercell for
    :param float distance: The minimum image distance as a float,
        The cell created will not have any periodic image below this distance
    :param str method: The method to get the optimal supercell. For now, the only
        implemented option is *best enclosing cell*
    :param bool wrap: Wrap the atoms into the created cell after replication
    :param bool standardize: Standardize the created cell.
        This is done based on the rules in Hinuma etal, http://arxiv.org/abs/1506.01455
        However, only rules for the triclinic case are applied, so further
        standardization using spglib is recommended, if a truly standardized
        cell is required.
    :param bool do_niggli_first: Start with a niggli reduction of the cell,
        to enable a faster search. Disable if there are problems with the reduction
        of if the cell is already Niggli or LLL reduced.
    :param bool diagonal: Whether to return the diagonal solution, instead
        of the optimal cell.
    :param str implementation: Either fortran ('fort') or python-implementation ('pyth'),
        defaults to 'fort'
    :param int verbosity: Sets the verbosity level.

    :returns: A new pymatgen core structure instance and the used scaling matrix
    :returns: The scaling matrix used.
    """
    if not isinstance(structure, Structure):
        raise TypeError("Structure passed has to be a pymatgen structure")
    try:
        distance = float(distance)
        assert distance > 1e-12, "Non-positive number"
    except Exception as e:
        print("You have to pass positive float or integer as distance")
        raise e

    if not isinstance(wrap, bool):
        raise TypeError("wrap has to be a boolean")
    if not isinstance(standardize, bool):
        raise TypeError("standardize has to be a boolean")

    # I'm getting the niggli reduced structure as first:
    if verbosity > 1:
        print("given cell:\n", structure._lattice)
    if do_niggli_first:
        starting_structure = structure.get_reduced_structure(
            reduction_algo=u'niggli')
    else:
        starting_structure = structure

    if verbosity > 1:
        print("starting cell:\n", starting_structure._lattice)
        for i, v in enumerate(starting_structure._lattice.matrix):
            print(i, np.linalg.norm(v))

    # the lattice of the niggle reduced structure:
    lattice_cellvecs = np.array(starting_structure._lattice.matrix,
                                dtype=np.float64)
    # trial_vecs are all possible vectors sorted by the norm
    if method == 'bec':
        if diagonal:
            lattice_cellvecs = np.array(lattice_cellvecs)
            # I get the diagonal solutions
            scale_matrix, supercell_cellvecs = get_diagonal_solution_bec(
                lattice_cellvecs, distance)
        else:
            # I get all possible midpoint vectors, based on the distance,
            # which for BEC method is the diameter of the sphere
            (norms_of_sorted_Gr_r2, sorted_Gc_r2, sorted_Gr_r2, r_outer,
             v_diag) = get_possible_solutions(lattice_cellvecs,
                                              distance,
                                              verbosity=verbosity)
            if verbosity:
                print("I received {} possible solutions".format(
                    len(norms_of_sorted_Gr_r2)))
            # I pass these trial vectors into the function to find the minimum volume:
            if implementation == 'pyth':
                scale_matrix, supercell_cellvecs = get_optimal_solution_bec(
                    norms_of_sorted_Gr_r2,
                    sorted_Gc_r2,
                    sorted_Gr_r2,
                    r_outer,
                    v_diag,
                    r_inner=distance,
                    verbosity=verbosity)
            elif implementation == 'fort':
                scale_matrix, supercell_cellvecs = fort_optimal_supercell_bec(
                    norms_of_sorted_Gr_r2, sorted_Gc_r2, sorted_Gr_r2, r_outer,
                    v_diag, distance, verbosity, len(norms_of_sorted_Gr_r2))
            else:
                raise RuntimeError("Implementation {}".formt(implementation))
    elif method == 'hnf':
        if diagonal:
            lattice_cellvecs = np.array(lattice_cellvecs)
            scale_matrix, supercell_cellvecs = get_diagonal_solution_hnf(
                lattice_cellvecs, distance)
        else:
            if implementation == 'pyth':
                scale_matrix, supercell_cellvecs = get_optimal_solution_hnf(
                    lattice_cellvecs, distance, verbosity)
            elif implementation == 'fort':
                scale_matrix, supercell_cellvecs = fort_optimal_supercell_hnf(
                    lattice_cellvecs, distance, verbosity)
            else:
                raise RuntimeError("Implementation {}".formt(implementation))

        #raise NotImplementedError("HNF has not been fully implemented")
    else:
        raise ValueError("Unknown method {}".format(method))
    # Constructing the new lattice:
    new_lattice = Lattice(supercell_cellvecs)
    # I create f_lat, which are the fractional lattice points of the niggle_reduced:
    f_lat = lattice_points_in_supercell(scale_matrix)
    # and transforrm to cartesian coords here:
    c_lat = new_lattice.get_cartesian_coords(f_lat)
    #~ cellT = supercell_cellvecs.T

    if verbosity > 1:
        print("Given Scaling:\n")
        print(scale_matrix)
        print("Given lattice:\n")
        print(new_lattice)
        for i, v in enumerate(new_lattice.matrix):
            print(i, np.linalg.norm(v))

    new_sites = []
    if verbosity:
        print("Done, constructing structure")
    for site in starting_structure:
        for v in c_lat:
            new_sites.append(
                PeriodicSite(site.species_and_occu,
                             site.coords + v,
                             new_lattice,
                             properties=site.properties,
                             coords_are_cartesian=True,
                             to_unit_cell=wrap))

    supercell = Structure.from_sites(new_sites)

    if standardize:
        supercell = standardize_cell(supercell, wrap)
        if verbosity > 1:
            print("Cell after standardization:\n", new_lattice)
            for i, v in enumerate(new_lattice.matrix):
                print(i, np.linalg.norm(v))
    return supercell, scale_matrix
예제 #13
0
    def __init__(self,
                 reciprocal_lattice: Lattice,
                 original_points: np.ndarray,
                 original_dim: np.ndarray,
                 extra_points: np.ndarray,
                 nworkers: int = pdefaults["nworkers"]):
        """

        Args:
            original_points:
            nworkers:
        """
        self._nworkers = nworkers if nworkers != -1 else cpu_count()

        supercell_points = get_supercell_points([2, 2, 2], original_points)

        # want points in cartesian space so we can define a regular spherical
        # cutoff even if reciprocal lattice is not cubic. If we used a
        # fractional cutoff, the cutoff regions would not be spherical
        cart_points = reciprocal_lattice.get_cartesian_coords(supercell_points)

        cart_extra_points = reciprocal_lattice.get_cartesian_coords(
            extra_points)

        # small cutoff is slighly larger than the max regular grid spacing
        # means at least 1 neighbour point will always be included in each
        # direction
        dim_lengths = np.dot(1 / original_dim, reciprocal_lattice.matrix)
        small_cutoff = np.max(dim_lengths) * 1.01
        big_cutoff = small_cutoff * 2

        # use BallTree for quickly evaluating which points are within cutoffs
        tree = BallTree(cart_points)

        # big cutoff points are those which surround the extra points within
        # the big cutoff (it does not include the extra points themselves)
        big_cutoff_points_idx = np.concatenate(tree.query_radius(
            cart_extra_points, big_cutoff),
                                               axis=0)

        # Voronoi points are those we actually calculate in the Voronoi diagram
        # e.g. the big points + extra points
        voronoi_points = supercell_points[big_cutoff_points_idx]
        self._voronoi_points = np.concatenate((voronoi_points, extra_points))

        # small points are the points in original_points for which we want to
        # calculate the Voronoi volumes. Note this does not include the
        # indices of the extra points. Outside the small cutoff, the weights
        # will just be the regular grid weight.
        small_cutoff_points_idx = np.concatenate(tree.query_radius(
            cart_extra_points, small_cutoff),
                                                 axis=0)

        # get the indices of small_cutoff_points in voronoi_points
        small_in_voronoi_idx = _get_loc(big_cutoff_points_idx,
                                        small_cutoff_points_idx)

        # get the indices of the small cutoff points + extra points
        # in voronoi points that we want the volumes for. The extra points
        # were just added at the end of big_cutoff_points, so getting their
        # indices is simple
        self._volume_points_idx = np.concatenate(
            (small_in_voronoi_idx,
             np.arange(len(extra_points)) + len(big_cutoff_points_idx)))

        # get the indices of the small_cutoff_points (not including the extra
        # points) in the original mesh. this works because the supercell
        # points are in the same order as the original mesh, just repeated for
        # each cell in the supercell
        small_in_original_idx = (small_cutoff_points_idx %
                                 len(original_points))

        # get the indices of the small cutoff points + extra points in the
        # final volume array. Note that the final volume array has the same
        # order as original_mesh + extra_points
        self._volume_in_final_idx = np.concatenate(
            (small_in_original_idx,
             np.arange(len(extra_points)) + len(original_points)))

        # prepopulate the final volumes array. By default, each point has the
        # volume of the original mesh. Note: at this point, the extra points
        # will have zero volume. This will array will be updated by
        # compute_volumes
        self._final_volumes = np.full(
            len(original_points) + len(extra_points), 1 / len(original_points))
        self._final_volumes[len(original_points):] = 0