예제 #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 __init__(
        self,
        species: Union[str, Element, Species, DummySpecies, Dict, Composition],
        coords: Union[Tuple, List, np.ndarray],
        lattice: Lattice,
        to_unit_cell: bool = False,
        coords_are_cartesian: bool = False,
        properties: dict = None,
        skip_checks: bool = False,
    ):
        """
        Create a periodic site.

        :param species: Species on the site. Can be:
            i.  A Composition-type object (preferred)
            ii. An  element / species specified either as a string
                symbols, e.g. "Li", "Fe2+", "P" or atomic numbers,
                e.g., 3, 56, or actual Element or Species objects.
            iii.Dict of elements/species and occupancies, e.g.,
                {"Fe" : 0.5, "Mn":0.5}. This allows the setup of
                disordered structures.
        :param coords: Cartesian coordinates of site.
        :param lattice: Lattice associated with the site.
        :param to_unit_cell: Translates fractional coordinate to the
            basic unit cell, i.e. all fractional coordinates satisfy 0
            <= a < 1. Defaults to False.
        :param coords_are_cartesian: Set to True if you are providing
            cartesian coordinates. Defaults to False.
        :param properties: Properties associated with the site as a dict, e.g.
            {"magmom": 5}. Defaults to None.
        :param skip_checks: Whether to ignore all the usual checks and just
            create the site. Use this if the PeriodicSite is created in a
            controlled manner and speed is desired.
        """

        if coords_are_cartesian:
            frac_coords = lattice.get_fractional_coords(coords)
        else:
            frac_coords = coords

        if to_unit_cell:
            frac_coords = np.mod(frac_coords, 1)

        if not skip_checks:
            frac_coords = np.array(frac_coords)
            if not isinstance(species, Composition):
                try:
                    species = Composition({get_el_sp(species): 1})
                except TypeError:
                    species = Composition(species)

            totaloccu = species.num_atoms
            if totaloccu > 1 + Composition.amount_tolerance:
                raise ValueError("Species occupancies sum to more than 1!")

        self._lattice = lattice
        self._frac_coords = frac_coords
        self._species = species
        self._coords = None
        self.properties = properties or {}
예제 #4
0
 def __init__(self, structure, scaling_matrix=((1, 0, 0), (0, 1, 0), (0, 0, 1))):
     """
     Create a supercell.
     
     Arguments:
         structure:
             pymatgen.core.structure Structure object.
         scaling_matrix:
             a matrix of transforming the lattice vectors. Defaults to the
             identity matrix. Has to be all integers. e.g.,
             [[2,1,0],[0,3,0],[0,0,1]] generates a new structure with
             lattice vectors a' = 2a + b, b' = 3b, c' = c where a, b, and c
             are the lattice vectors of the original structure. 
     """
     self._original_structure = structure
     old_lattice = structure.lattice
     scale_matrix = np.array(scaling_matrix)
     new_lattice = Lattice(np.dot(scale_matrix, old_lattice.matrix))
     new_species = []
     new_fcoords = []
     def range_vec(i):
         return range(max(scale_matrix[:][:, i]) - min(scale_matrix[:][:, i]))
     for site in structure.sites:
         for (i, j, k) in itertools.product(range_vec(0), range_vec(1), range_vec(2)):
             new_species.append(site.species_and_occu)
             fcoords = site.frac_coords
             coords = old_lattice.get_cartesian_coords(fcoords + np.array([i, j, k]))
             new_fcoords.append(new_lattice.get_fractional_coords(coords))
     self._modified_structure = Structure(new_lattice, new_species, new_fcoords, False)
예제 #5
0
 def test_get_vector_along_lattice_directions(self):
     lattice_mat = np.array([[0.5, 0.0, 0.0], [0.5,
                                               np.sqrt(3) / 2.0, 0.0],
                             [0.0, 0.0, 1.0]])
     lattice = Lattice(lattice_mat)
     cart_coord = np.array([0.5, np.sqrt(3) / 4.0, 0.5])
     latt_coord = np.array([0.25, 0.5, 0.5])
     from_direct = lattice.get_fractional_coords(
         cart_coord) * lattice.lengths
     self.assertArrayAlmostEqual(
         lattice.get_vector_along_lattice_directions(cart_coord),
         from_direct)
     self.assertArrayAlmostEqual(
         lattice.get_vector_along_lattice_directions(cart_coord),
         latt_coord)
     self.assertArrayEqual(
         lattice.get_vector_along_lattice_directions(cart_coord).shape,
         [
             3,
         ],
     )
     self.assertArrayEqual(
         lattice.get_vector_along_lattice_directions(
             cart_coord.reshape([1, 3])).shape,
         [1, 3],
     )
예제 #6
0
    def test_get_points_in_sphere(self):
        # This is a non-niggli representation of a cubic lattice
        latt = Lattice([[1, 5, 0], [0, 1, 0], [5, 0, 1]])
        # evenly spaced points array between 0 and 1
        pts = np.array(list(itertools.product(range(5), repeat=3))) / 5
        pts = latt.get_fractional_coords(pts)

        self.assertEqual(
            len(latt.get_points_in_sphere(pts, [0, 0, 0], 0.20001)), 7)
        self.assertEqual(
            len(latt.get_points_in_sphere(pts, [0.5, 0.5, 0.5], 1.0001)), 552)
예제 #7
0
    def test_get_points_in_sphere(self):
        # This is a non-niggli representation of a cubic lattice
        latt = Lattice([[1,5,0],[0,1,0],[5,0,1]])
        # evenly spaced points array between 0 and 1
        pts = np.array(list(itertools.product(range(5), repeat=3))) / 5
        pts = latt.get_fractional_coords(pts)

        self.assertEqual(len(latt.get_points_in_sphere(
            pts, [0, 0, 0], 0.20001)), 7)
        self.assertEqual(len(latt.get_points_in_sphere(
            pts, [0.5, 0.5, 0.5], 1.0001)), 552)
예제 #8
0
 def test_get_vector_along_lattice_directions(self):
    lattice_mat = np.array([[0.5, 0., 0.],
                            [0.5, np.sqrt(3) / 2., 0.],
                            [0., 0., 1.0]])
    lattice = Lattice(lattice_mat)
    cart_coord = np.array([0.5, np.sqrt(3)/4., 0.5])
    latt_coord = np.array([0.25, 0.5, 0.5])
    from_direct = lattice.get_fractional_coords(cart_coord) * lattice.lengths_and_angles[0]
    self.assertArrayAlmostEqual(lattice.get_vector_along_lattice_directions(cart_coord), from_direct)
    self.assertArrayAlmostEqual(lattice.get_vector_along_lattice_directions(cart_coord), latt_coord)
    self.assertArrayEqual(lattice.get_vector_along_lattice_directions(cart_coord).shape, [3,])
    self.assertArrayEqual(lattice.get_vector_along_lattice_directions(cart_coord.reshape([1,3])).shape, [1,3])
예제 #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
    def __init__(self,
                 structure,
                 scaling_matrix=((1, 0, 0), (0, 1, 0), (0, 0, 1))):
        """
        Create a supercell.

        Args:
            structure:
                pymatgen.core.structure Structure object.
            scaling_matrix:
                a matrix of transforming the lattice vectors. Defaults to the
                identity matrix. Has to be all integers. e.g.,
                [[2,1,0],[0,3,0],[0,0,1]] generates a new structure with
                lattice vectors a' = 2a + b, b' = 3b, c' = c where a, b, and c
                are the lattice vectors of the original structure.
        """
        self._original_structure = structure
        old_lattice = structure.lattice
        scale_matrix = np.array(scaling_matrix)
        new_lattice = Lattice(np.dot(scale_matrix, old_lattice.matrix))
        new_sites = []

        def range_vec(i):
            return range(
                max(scale_matrix[:][:, i]) - min(scale_matrix[:][:, i]) + 1)

        for site in structure.sites:
            for (i, j, k) in itertools.product(range_vec(0), range_vec(1),
                                               range_vec(2)):
                fcoords = site.frac_coords + np.array([i, j, k])
                coords = old_lattice.get_cartesian_coords(fcoords)
                new_coords = new_lattice.get_fractional_coords(coords)
                new_site = PeriodicSite(site.species_and_occu,
                                        new_coords,
                                        new_lattice,
                                        properties=site.properties)
                contains_site = False
                for s in new_sites:
                    if s.is_periodic_image(new_site):
                        contains_site = True
                        break
                if not contains_site:
                    new_sites.append(new_site)
        self._modified_structure = Structure.from_sites(new_sites)
예제 #11
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
예제 #12
0
    def test_get_points_in_sphere(self):
        # This is a non-niggli representation of a cubic lattice
        latt = Lattice([[1, 5, 0], [0, 1, 0], [5, 0, 1]])
        # evenly spaced points array between 0 and 1
        pts = np.array(list(itertools.product(range(5), repeat=3))) / 5
        pts = latt.get_fractional_coords(pts)

        # Test getting neighbors within 1 neighbor distance of the origin
        fcoords, dists, inds, images = latt.get_points_in_sphere(pts, [0, 0, 0], 0.20001, zip_results=False)
        self.assertEqual(len(fcoords), 7)  # There are 7 neighbors
        self.assertEqual(np.isclose(dists, 0.2).sum(), 6)  # 6 are at 0.2
        self.assertEqual(np.isclose(dists, 0).sum(), 1)  # 1 is at 0
        self.assertEqual(len(set(inds)), 7)  # They have unique indices
        self.assertArrayEqual(images[np.isclose(dists, 0)], [[0, 0, 0]])

        # More complicated case, using the zip output
        result = latt.get_points_in_sphere(pts, [0.5, 0.5, 0.5], 1.0001)
        self.assertEqual(len(result), 552)
        self.assertEqual(len(result[0]), 4)  # coords, dists, ind, supercell
예제 #13
0
    def test_get_points_in_sphere(self):
        # This is a non-niggli representation of a cubic lattice
        latt = Lattice([[1, 5, 0], [0, 1, 0], [5, 0, 1]])
        # evenly spaced points array between 0 and 1
        pts = np.array(list(itertools.product(range(5), repeat=3))) / 5
        pts = latt.get_fractional_coords(pts)

        # Test getting neighbors within 1 neighbor distance of the origin
        fcoords, dists, inds, images = latt.get_points_in_sphere(pts, [0, 0, 0], 0.20001,
                                                                 zip_results=False)
        self.assertEqual(len(fcoords), 7)  # There are 7 neighbors
        self.assertEqual(np.isclose(dists, 0.2).sum(), 6)  # 6 are at 0.2
        self.assertEqual(np.isclose(dists, 0).sum(), 1)  # 1 is at 0
        self.assertEqual(len(set(inds)), 7)  # They have unique indices
        self.assertArrayEqual(images[np.isclose(dists, 0)], [[0, 0, 0]])

        # More complicated case, using the zip output
        result = latt.get_points_in_sphere(pts, [0.5, 0.5, 0.5], 1.0001)
        self.assertEqual(len(result), 552)
        self.assertEqual(len(result[0]), 4)  # coords, dists, ind, supercell
예제 #14
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
예제 #15
0
class StructureEditor(StructureModifier):
    """
    Editor for adding, removing and changing sites from a structure
    """
    DISTANCE_TOLERANCE = 0.01

    def __init__(self, structure):
        """
        Args:
            structure:
                pymatgen.core.structure Structure object.
        """
        self._original_structure = structure
        self._lattice = structure.lattice
        self._sites = list(structure.sites)

    def add_site_property(self, property_name, values):
        """
        Adds a property to a site.
        
        Args:
            property_name:
                The name of the property to add.
            values: 
                A sequence of values. Must be same length as number of sites.
        """
        if len(values) != len(self._sites):
            raise ValueError("Values must be same length as sites.")
        for i in xrange(len(self._sites)):
            site = self._sites[i]
            props = site.properties
            if not props:
                props = {}
            props[property_name] = values[i]
            self._sites[i] = PeriodicSite(site.species_and_occu, site.frac_coords, self._lattice, properties=props)

    def replace_species(self, species_mapping):
        """
        Swap species in a structure.
        
        Args:
            species_mapping:
                dict of species to swap. Species can be elements too.
                e.g., {Element("Li"): Element("Na")} performs a Li for Na 
                substitution. The second species can be a sp_and_occu dict. 
                For example, a site with 0.5 Si that is passed the mapping 
                {Element('Si): {Element('Ge'):0.75, Element('C'):0.25} } will
                have .375 Ge and .125 C.
        """

        def mod_site(site):
            new_atom_occu = dict()
            for sp, amt in site.species_and_occu.items():
                if sp in species_mapping:
                    if species_mapping[sp].__class__.__name__ in ('Element', 'Specie'):
                        if species_mapping[sp] in new_atom_occu:
                            new_atom_occu[species_mapping[sp]] += amt
                        else:
                            new_atom_occu[species_mapping[sp]] = amt
                    elif species_mapping[sp].__class__.__name__ == 'dict':
                        for new_sp, new_amt in species_mapping[sp].items():
                            if new_sp in new_atom_occu:
                                new_atom_occu[new_sp] += amt * new_amt
                            else:
                                new_atom_occu[new_sp] = amt * new_amt
                else:
                    if sp in new_atom_occu:
                        new_atom_occu[sp] += amt
                    else:
                        new_atom_occu[sp] = amt
            return PeriodicSite(new_atom_occu, self._lattice.get_fractional_coords(site.coords), self._lattice)

        self._sites = map(mod_site, self._sites)

    def replace_site(self, index, species_n_occu):
        """
        Replace a single site. Takes either a species or a dict of occus
        
        Args:
            index:
                The index of the site in the _sites list
            species:
                A species object  
        """
        self._sites[index] = PeriodicSite(species_n_occu, self._lattice.get_fractional_coords(self._sites[index].coords), self._lattice)

    def remove_species(self, species):
        """
        Remove all occurrences of a species from a structure.
        
        Args:
            species:
                species to remove
        """
        new_sites = []
        for site in self._sites:
            new_sp_occu = {sp:amt for sp, amt in site.species_and_occu.items() if sp not in species}
            if len(new_sp_occu) > 0:
                new_sites.append(PeriodicSite(new_sp_occu, site.frac_coords, self._lattice))
        self._sites = new_sites

    def append_site(self, species, coords, coords_are_cartesian=False,
                    validate_proximity=True):
        """
        Append a site to the structure at the end.
        
        Args:
            species:
                species of inserted site
            coords:
                coordinates of inserted site
            fractional_coord:
                Whether coordinates are cartesian. Defaults to False.
            validate_proximity:
                Whether to check if inserted site is too close to an existing site. Defaults to True.
        
        """
        self.insert_site(len(self._sites), species, coords, coords_are_cartesian, validate_proximity)

    def insert_site(self, i, species, coords, coords_are_cartesian=False,
                    validate_proximity=True, properties=None):
        """
        Insert a site to the structure.
        
        Args:
            i:
                index to insert site
            species:
                species of inserted site
            coords:
                coordinates of inserted site
            coords_are_cartesian:
                Whether coordinates are cartesian. Defaults to False.
            validate_proximity:
                Whether to check if inserted site is too close to an existing site. Defaults to True.
        """
        if not coords_are_cartesian:
            new_site = PeriodicSite(species, coords, self._lattice, properties=properties)
        else:
            new_site = PeriodicSite(species, self._lattice.get_fractional_coords(coords), self._lattice, properties=properties)

        if validate_proximity:
            for site in self._sites:
                if site.distance(new_site) < self.DISTANCE_TOLERANCE:
                    raise ValueError("New site is too close to an existing site!")

        self._sites.insert(i, new_site)

    def delete_site(self, i):
        """
        Delete site at index i.
        
        Args:
            i:
                index of site to delete.
        """
        del(self._sites[i])

    def delete_sites(self, indices):
        """
        Delete sites with at indices.
        
        Args:
            indices:
                sequence of indices of sites to delete.
        """
        self._sites = [self._sites[i] for i in range(len(self._sites)) if i not in indices]

    def apply_operation(self, symmop):
        """
        Apply a symmetry operation to the structure and return the new structure.
        The lattice is operated by the rotation matrix only.
        Coords are operated in full and then transformed to the new lattice.
        
        Args:
            symmop:
                Symmetry operation to apply.
        """
        self._lattice = Lattice([symmop.apply_rotation_only(row) for row in self._lattice.matrix])
        def operate_site(site):
            new_cart = symmop.operate(site.coords)
            return PeriodicSite(site.species_and_occu, self._lattice.get_fractional_coords(new_cart), self._lattice)
        self._sites = map(operate_site, self._sites)

    def modify_lattice(self, new_lattice):
        """
        Modify the lattice of the structure.  Mainly used for changing the basis.
        
        Args:
            new_lattice:
                New lattice
        """
        self._lattice = new_lattice
        new_sites = []
        for site in self._sites:
            new_sites.append(PeriodicSite(site.species_and_occu, self._lattice.get_fractional_coords(site.coords), self._lattice))
        self._sites = new_sites

    def translate_sites(self, indices, vector, frac_coords=True):
        """
        Translate specific sites by some vector, keeping the sites within the
        unit cell.
        
        Args:
            sites:
                List of site indices on which to perform the translation.
            vector:
                Translation vector for sites.
            frac_coords:
                Boolean stating whether the vector corresponds to fractional or
                cartesian coordinates.
        """
        for i in indices:
            site = self._sites[i]
            if frac_coords:
                fcoords = site.frac_coords + vector
            else:
                fcoords = self._lattice.get_fractional_coords(site.coords + vector)
            new_site = PeriodicSite(site.species_and_occu, fcoords, self._lattice, to_unit_cell=True, coords_are_cartesian=False)
            self._sites[i] = new_site

    def perturb_structure(self, distance=0.1):
        '''
        performs a random perturbation of the sites in a structure to break symmetries
        
        Args:
            distance: distance by which to perturb each site
        '''
        for i in range(len(self._sites)):
            vector = np.random.rand(3)
            vector /= np.linalg.norm(vector) / distance
            self.translate_sites([i], vector, frac_coords=False)

    @property
    def original_structure(self):
        return self._original_structure

    @property
    def modified_structure(self):
        coords = [site.frac_coords for site in self._sites]
        species = [site.species_and_occu for site in self._sites]
        props = {}
        if self._sites[0].properties:
            for k in self._sites[0].properties.keys():
                props[k] = [site.properties[k] for site in self._sites]
        return Structure(self._lattice, species, coords, False,
                         site_properties=props)
예제 #16
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]))
예제 #17
0
class StructureEditor(StructureModifier):
    """
    Editor for adding, removing and changing sites from a structure
    """
    DISTANCE_TOLERANCE = 0.01

    def __init__(self, structure):
        """
        Args:
            structure:
                pymatgen.core.structure Structure object.
        """
        self._original_structure = structure
        self._lattice = structure.lattice
        self._sites = list(structure.sites)

    def add_site_property(self, property_name, values):
        """
        Adds a property to a site.

        Args:
            property_name:
                The name of the property to add.
            values:
                A sequence of values. Must be same length as number of sites.
        """
        if len(values) != len(self._sites):
            raise ValueError("Values must be same length as sites.")
        for i in xrange(len(self._sites)):
            site = self._sites[i]
            props = site.properties
            if not props:
                props = {}
            props[property_name] = values[i]
            self._sites[i] = PeriodicSite(site.species_and_occu,
                                          site.frac_coords,
                                          self._lattice,
                                          properties=props)

    def replace_species(self, species_mapping):
        """
        Swap species in a structure.

        Args:
            species_mapping:
                dict of species to swap. Species can be elements too.
                e.g., {Element("Li"): Element("Na")} performs a Li for Na
                substitution. The second species can be a sp_and_occu dict.
                For example, a site with 0.5 Si that is passed the mapping
                {Element('Si): {Element('Ge'):0.75, Element('C'):0.25} } will
                have .375 Ge and .125 C.
        """
        def mod_site(site):
            new_atom_occu = collections.defaultdict(int)
            for sp, amt in site.species_and_occu.items():
                if sp in species_mapping:
                    if isinstance(species_mapping[sp], (Element, Specie)):
                        new_atom_occu[species_mapping[sp]] += amt
                    elif isinstance(species_mapping[sp], dict):
                        for new_sp, new_amt in species_mapping[sp].items():
                            new_atom_occu[new_sp] += amt * new_amt
                else:
                    new_atom_occu[sp] += amt
            return PeriodicSite(new_atom_occu,
                                site.frac_coords,
                                self._lattice,
                                properties=site.properties)

        self._sites = map(mod_site, self._sites)

    def replace_site(self, index, species_n_occu):
        """
        Replace a single site. Takes either a species or a dict of species and
        occupations.

        Args:
            index: The index of the site in the _sites list.
            species: A species object.
        """
        self._sites[index] = PeriodicSite(
            species_n_occu,
            self._sites[index].frac_coords,
            self._lattice,
            properties=self._sites[index].properties)

    def remove_species(self, species):
        """
        Remove all occurrences of a species from a structure.

        Args:
            species:
                species to remove.
        """
        new_sites = []
        for site in self._sites:
            new_sp_occu = {
                sp: amt
                for sp, amt in site.species_and_occu.items()
                if sp not in species
            }
            if len(new_sp_occu) > 0:
                new_sites.append(
                    PeriodicSite(new_sp_occu,
                                 site.frac_coords,
                                 self._lattice,
                                 properties=site.properties))
        self._sites = new_sites

    def append_site(self,
                    species,
                    coords,
                    coords_are_cartesian=False,
                    validate_proximity=True):
        """
        Append a site to the structure at the end.

        Args:
            species:
                species of inserted site
            coords:
                coordinates of inserted site
            fractional_coord:
                Whether coordinates are cartesian. Defaults to False.
            validate_proximity:
                Whether to check if inserted site is too close to an existing
                site. Defaults to True.
        """
        self.insert_site(len(self._sites), species, coords,
                         coords_are_cartesian, validate_proximity)

    def insert_site(self,
                    i,
                    species,
                    coords,
                    coords_are_cartesian=False,
                    validate_proximity=True,
                    properties=None):
        """
        Insert a site to the structure.

        Args:
            i:
                index to insert site
            species:
                species of inserted site
            coords:
                coordinates of inserted site
            coords_are_cartesian:
                Whether coordinates are cartesian. Defaults to False.
            validate_proximity:
                Whether to check if inserted site is too close to an existing
                site. Defaults to True.
        """
        if not coords_are_cartesian:
            new_site = PeriodicSite(species,
                                    coords,
                                    self._lattice,
                                    properties=properties)
        else:
            frac_coords = self._lattice.get_fractional_coords(coords)
            new_site = PeriodicSite(species,
                                    frac_coords,
                                    self._lattice,
                                    properties=properties)

        if validate_proximity:
            for site in self._sites:
                if site.distance(new_site) < self.DISTANCE_TOLERANCE:
                    raise ValueError("New site is too close to an existing "
                                     "site!")

        self._sites.insert(i, new_site)

    def delete_site(self, i):
        """
        Delete site at index i.

        Args:
            i:
                index of site to delete.
        """
        del (self._sites[i])

    def delete_sites(self, indices):
        """
        Delete sites with at indices.

        Args:
            indices:
                sequence of indices of sites to delete.
        """
        self._sites = [
            self._sites[i] for i in range(len(self._sites)) if i not in indices
        ]

    def apply_operation(self, symmop):
        """
        Apply a symmetry operation to the structure and return the new
        structure. The lattice is operated by the rotation matrix only.
        Coords are operated in full and then transformed to the new lattice.

        Args:
            symmop:
                Symmetry operation to apply.
        """
        self._lattice = Lattice(
            [symmop.apply_rotation_only(row) for row in self._lattice.matrix])

        def operate_site(site):
            new_cart = symmop.operate(site.coords)
            new_frac = self._lattice.get_fractional_coords(new_cart)
            return PeriodicSite(site.species_and_occu,
                                new_frac,
                                self._lattice,
                                properties=site.properties)

        self._sites = map(operate_site, self._sites)

    def modify_lattice(self, new_lattice):
        """
        Modify the lattice of the structure.  Mainly used for changing the
        basis.

        Args:
            new_lattice:
                New lattice
        """
        self._lattice = new_lattice
        new_sites = []
        for site in self._sites:
            new_sites.append(
                PeriodicSite(site.species_and_occu,
                             site.frac_coords,
                             self._lattice,
                             properties=site.properties))
        self._sites = new_sites

    def apply_strain(self, strain):
        """
        Apply an isotropic strain to the lattice.

        Args:
            strain:
                Amount of strain to apply. E.g., 0.01 means all lattice
                vectors are increased by 1%. This is equivalent to
                calling modify_lattice with a lattice with lattice parameters
                that are 1% larger.
        """
        self.modify_lattice(Lattice(self._lattice.matrix * (1 + strain)))

    def translate_sites(self, indices, vector, frac_coords=True):
        """
        Translate specific sites by some vector, keeping the sites within the
        unit cell.

        Args:
            sites:
                List of site indices on which to perform the translation.
            vector:
                Translation vector for sites.
            frac_coords:
                Boolean stating whether the vector corresponds to fractional or
                cartesian coordinates.
        """
        for i in indices:
            site = self._sites[i]
            if frac_coords:
                fcoords = site.frac_coords + vector
            else:
                fcoords = self._lattice.get_fractional_coords(site.coords +
                                                              vector)
            new_site = PeriodicSite(site.species_and_occu,
                                    fcoords,
                                    self._lattice,
                                    to_unit_cell=True,
                                    coords_are_cartesian=False,
                                    properties=site.properties)
            self._sites[i] = new_site

    def perturb_structure(self, distance=0.1):
        """
        Performs a random perturbation of the sites in a structure to break
        symmetries.

        Args:
            distance:
                distance in angstroms by which to perturb each site.
        """
        def get_rand_vec():
            #deals with zero vectors.
            vector = np.random.randn(3)
            vnorm = np.linalg.norm(vector)
            return vector / vnorm * distance if vnorm != 0 else get_rand_vec()

        for i in range(len(self._sites)):
            self.translate_sites([i], get_rand_vec(), frac_coords=False)

    def add_oxidation_state_by_element(self, oxidation_states):
        """
        Add oxidation states to a structure.

        Args:
            structure:
                pymatgen.core.structure Structure object.
            oxidation_states:
                dict of oxidation states.
                E.g., {"Li":1, "Fe":2, "P":5, "O":-2}
        """
        try:
            for i, site in enumerate(self._sites):
                new_sp = {}
                for el, occu in site.species_and_occu.items():
                    sym = el.symbol
                    new_sp[Specie(sym, oxidation_states[sym])] = occu
                new_site = PeriodicSite(new_sp,
                                        site.frac_coords,
                                        self._lattice,
                                        coords_are_cartesian=False,
                                        properties=site.properties)
                self._sites[i] = new_site

        except KeyError:
            raise ValueError("Oxidation state of all elements must be "
                             "specified in the dictionary.")

    def add_oxidation_state_by_site(self, oxidation_states):
        """
        Add oxidation states to a structure by site.

        Args:
            oxidation_states:
                List of oxidation states.
                E.g., [1, 1, 1, 1, 2, 2, 2, 2, 5, 5, 5, 5, -2, -2, -2, -2]
        """
        try:
            for i, site in enumerate(self._sites):
                new_sp = {}
                for el, occu in site.species_and_occu.items():
                    sym = el.symbol
                    new_sp[Specie(sym, oxidation_states[i])] = occu
                new_site = PeriodicSite(new_sp,
                                        site.frac_coords,
                                        self._lattice,
                                        coords_are_cartesian=False,
                                        properties=site.properties)
                self._sites[i] = new_site

        except IndexError:
            raise ValueError("Oxidation state of all sites must be "
                             "specified in the dictionary.")

    def remove_oxidation_states(self):
        """
        Removes oxidation states from a structure.
        """
        for i, site in enumerate(self._sites):
            new_sp = collections.defaultdict(float)
            for el, occu in site.species_and_occu.items():
                sym = el.symbol
                new_sp[Element(sym)] += occu
            new_site = PeriodicSite(new_sp,
                                    site.frac_coords,
                                    self._lattice,
                                    coords_are_cartesian=False,
                                    properties=site.properties)
            self._sites[i] = new_site

    def to_unit_cell(self, tolerance=0.1):
        """
        Returns all the sites to their position inside the unit cell.
        If there is a site within the tolerance already there, the site is
        deleted instead of moved.
        """
        new_sites = []
        for site in self._sites:
            if not new_sites:
                new_sites.append(site)
                frac_coords = np.array([site.frac_coords])
                continue
            if len(
                    get_points_in_sphere_pbc(self._lattice, frac_coords,
                                             site.coords, tolerance)):
                continue
            frac_coords = np.append(frac_coords, [site.frac_coords % 1],
                                    axis=0)
            new_sites.append(site.to_unit_cell)
        self._sites = new_sites

    @property
    def original_structure(self):
        """
        The original structure.
        """
        return self._original_structure

    @property
    def modified_structure(self):
        coords = [site.frac_coords for site in self._sites]
        species = [site.species_and_occu for site in self._sites]
        props = {}
        if self._sites[0].properties:
            for k in self._sites[0].properties.keys():
                props[k] = [site.properties[k] for site in self._sites]
        return Structure(self._lattice,
                         species,
                         coords,
                         False,
                         site_properties=props)
예제 #18
0
class Topology(object):
    """
    """
    def __init__(self, name: str, slots: List[Fragment],
                 cell: Union[numpy.ndarray, Lattice]) -> None:
        """
        Parameters
        ----------
        name : str
            the name given to the topology (RCSR symbol in defaults)
        slots : List[Fragment]
            the list of Fragment objects describing the orientation and
            connectivity of slots in the topology.
        cell : numpy.ndarray
            The information on periodicity in matrix form (3x3)
        """
        self.name = name
        if isinstance(cell, Lattice):
            self.cell = cell
        else:
            self.cell = Lattice(cell)
        self.slots = numpy.array(slots, dtype=object)
        sizes = [len(fragment.atoms) for fragment in self.slots]
        self.sizes = numpy.array(sizes, dtype=numpy.int8)
        mappings = {}
        for slot_type in set(slots):
            mappings[slot_type] = [
                i for i, s in enumerate(slots) if s == slot_type
            ]
        self.mappings = mappings
        return None

    def __len__(self):
        return len(self.slots)

    def __repr__(self):
        return self.name

    def copy(self) -> Topology:
        """
        Provides a deep copy of the starting object

        Returns
        -------
        Topology
            the copy of the starting object
        """
        return copy.deepcopy(self)

    def get_compatible_slots(self,
                             candidate: Fragment) -> Dict[Fragment, List[int]]:
        """
        Returns a dictionary of the slot indices available for a candidate
        Fragment object, taking into account the symmetry elements common to
        it and the slots.

        Parameters
        ----------
        candidate : Fragment
            the query Fragment with which to test compatibility

        Returns
        -------
        Dict[Fragment, List[int]]
            A ictionary of available slot indices
        """
        available_slots = {}
        for slot in self.mappings:
            available_slots[slot] = []
            if slot.has_compatible_symmetry(candidate):
                available_slots[slot] += self.mappings[slot]
        return available_slots

    def scale_slots(
        self, scales: Tuple[float, float, float] = (1.0, 1.0, 1.0)) -> None:
        """
        Applies in-place a scaling along cell vectors of the slots contines in
        the topology.
        TODO: rename scales to three a, b, c parameters for clarity

        Parameters
        ----------
        scales : Tuple[float, float, float], optional
            the cell vector lengths to apply, by default (1.0, 1.0, 1.0)
        """
        alpha, beta, gamma = self.cell.angles
        a, b, c = scales
        scaled_cell = Lattice.from_parameters(a, b, c, alpha, beta, gamma)
        scaled_slots = []
        for slot in self.slots:
            scaled_slot = copy.deepcopy(slot)
            fract_coords = self.cell.get_fractional_coords(
                slot.atoms.cart_coords)
            scaled_coords = scaled_cell.get_cartesian_coords(fract_coords)
            scaled_slot.atoms = Molecule(
                slot.atoms.species,
                scaled_coords,
                site_properties=slot.atoms.site_properties)
            scaled_slots.append(scaled_slot)
        self.slots = scaled_slots
        self.cell = scaled_cell
        return None
예제 #19
0
class StructureEditor(StructureModifier):
    """
    Editor for adding, removing and changing sites from a structure
    """
    DISTANCE_TOLERANCE = 0.01

    def __init__(self, structure):
        """
        Args:
            structure:
                pymatgen.core.structure Structure object.
        """
        self._original_structure = structure
        self._lattice = structure.lattice
        self._sites = list(structure.sites)

    def add_site_property(self, property_name, values):
        """
        Adds a property to a site.

        Args:
            property_name:
                The name of the property to add.
            values:
                A sequence of values. Must be same length as number of sites.
        """
        if len(values) != len(self._sites):
            raise ValueError("Values must be same length as sites.")
        for i in xrange(len(self._sites)):
            site = self._sites[i]
            props = site.properties
            if not props:
                props = {}
            props[property_name] = values[i]
            self._sites[i] = PeriodicSite(site.species_and_occu,
                                          site.frac_coords, self._lattice,
                                          properties=props)

    def replace_species(self, species_mapping):
        """
        Swap species in a structure.

        Args:
            species_mapping:
                dict of species to swap. Species can be elements too.
                e.g., {Element("Li"): Element("Na")} performs a Li for Na
                substitution. The second species can be a sp_and_occu dict.
                For example, a site with 0.5 Si that is passed the mapping
                {Element('Si): {Element('Ge'):0.75, Element('C'):0.25} } will
                have .375 Ge and .125 C.
        """
        def mod_site(site):
            new_atom_occu = dict()
            for sp, amt in site.species_and_occu.items():
                if sp in species_mapping:
                    if isinstance(species_mapping[sp], (Element, Specie)):
                        if species_mapping[sp] in new_atom_occu:
                            new_atom_occu[species_mapping[sp]] += amt
                        else:
                            new_atom_occu[species_mapping[sp]] = amt
                    elif isinstance(species_mapping[sp], dict):
                        for new_sp, new_amt in species_mapping[sp].items():
                            if new_sp in new_atom_occu:
                                new_atom_occu[new_sp] += amt * new_amt
                            else:
                                new_atom_occu[new_sp] = amt * new_amt
                else:
                    if sp in new_atom_occu:
                        new_atom_occu[sp] += amt
                    else:
                        new_atom_occu[sp] = amt
            return PeriodicSite(new_atom_occu, site.frac_coords, self._lattice,
                                properties=site.properties)

        self._sites = map(mod_site, self._sites)

    def replace_site(self, index, species_n_occu):
        """
        Replace a single site. Takes either a species or a dict of species and
        occupations.

        Args:
            index:
                The index of the site in the _sites list.
            species:
                A species object.
        """
        self._sites[index] = PeriodicSite(species_n_occu,
                                          self._sites[index].frac_coords,
                                          self._lattice,
                                          properties=self._sites[index].
                                          properties)

    def remove_species(self, species):
        """
        Remove all occurrences of a species from a structure.

        Args:
            species:
                species to remove.
        """
        new_sites = []
        for site in self._sites:
            new_sp_occu = {sp: amt for sp, amt in site.species_and_occu.items()
                           if sp not in species}
            if len(new_sp_occu) > 0:
                new_sites.append(PeriodicSite(new_sp_occu, site.frac_coords,
                                              self._lattice,
                                              properties=site.properties))
        self._sites = new_sites

    def append_site(self, species, coords, coords_are_cartesian=False,
                    validate_proximity=True):
        """
        Append a site to the structure at the end.

        Args:
            species:
                species of inserted site
            coords:
                coordinates of inserted site
            fractional_coord:
                Whether coordinates are cartesian. Defaults to False.
            validate_proximity:
                Whether to check if inserted site is too close to an existing
                site. Defaults to True.
        """
        self.insert_site(len(self._sites), species, coords,
                         coords_are_cartesian, validate_proximity)

    def insert_site(self, i, species, coords, coords_are_cartesian=False,
                    validate_proximity=True, properties=None):
        """
        Insert a site to the structure.

        Args:
            i:
                index to insert site
            species:
                species of inserted site
            coords:
                coordinates of inserted site
            coords_are_cartesian:
                Whether coordinates are cartesian. Defaults to False.
            validate_proximity:
                Whether to check if inserted site is too close to an existing
                site. Defaults to True.
        """
        if not coords_are_cartesian:
            new_site = PeriodicSite(species, coords, self._lattice,
                                    properties=properties)
        else:
            frac_coords = self._lattice.get_fractional_coords(coords)
            new_site = PeriodicSite(species, frac_coords, self._lattice,
                                    properties=properties)

        if validate_proximity:
            for site in self._sites:
                if site.distance(new_site) < self.DISTANCE_TOLERANCE:
                    raise ValueError("New site is too close to an existing "
                                     "site!")

        self._sites.insert(i, new_site)

    def delete_site(self, i):
        """
        Delete site at index i.

        Args:
            i:
                index of site to delete.
        """
        del(self._sites[i])

    def delete_sites(self, indices):
        """
        Delete sites with at indices.

        Args:
            indices:
                sequence of indices of sites to delete.
        """
        self._sites = [self._sites[i] for i in range(len(self._sites))
                       if i not in indices]

    def apply_operation(self, symmop):
        """
        Apply a symmetry operation to the structure and return the new
        structure. The lattice is operated by the rotation matrix only.
        Coords are operated in full and then transformed to the new lattice.

        Args:
            symmop:
                Symmetry operation to apply.
        """
        self._lattice = Lattice([symmop.apply_rotation_only(row)
                                 for row in self._lattice.matrix])

        def operate_site(site):
            new_cart = symmop.operate(site.coords)
            new_frac = self._lattice.get_fractional_coords(new_cart)
            return PeriodicSite(site.species_and_occu, new_frac, self._lattice,
                                properties=site.properties)
        self._sites = map(operate_site, self._sites)

    def modify_lattice(self, new_lattice):
        """
        Modify the lattice of the structure.  Mainly used for changing the
        basis.

        Args:
            new_lattice:
                New lattice
        """
        self._lattice = new_lattice
        new_sites = []
        for site in self._sites:
            new_sites.append(PeriodicSite(site.species_and_occu,
                                          site.frac_coords,
                                          self._lattice,
                                          properties=site.properties))
        self._sites = new_sites

    def apply_strain(self, strain):
        """
        Apply an isotropic strain to the lattice.

        Args:
            strain:
                Amount of strain to apply. E.g., 0.01 means all lattice
                vectors are increased by 1%. This is equivalent to
                calling modify_lattice with a lattice with lattice parameters
                that are 1% larger.
        """
        self.modify_lattice(Lattice(self._lattice.matrix * (1 + strain)))

    def translate_sites(self, indices, vector, frac_coords=True):
        """
        Translate specific sites by some vector, keeping the sites within the
        unit cell.

        Args:
            sites:
                List of site indices on which to perform the translation.
            vector:
                Translation vector for sites.
            frac_coords:
                Boolean stating whether the vector corresponds to fractional or
                cartesian coordinates.
        """
        for i in indices:
            site = self._sites[i]
            if frac_coords:
                fcoords = site.frac_coords + vector
            else:
                fcoords = self._lattice.get_fractional_coords(site.coords
                                                              + vector)
            new_site = PeriodicSite(site.species_and_occu, fcoords,
                                    self._lattice, to_unit_cell=True,
                                    coords_are_cartesian=False,
                                    properties=site.properties)
            self._sites[i] = new_site

    def perturb_structure(self, distance=0.1):
        """
        Performs a random perturbation of the sites in a structure to break
        symmetries.

        Args:
            distance:
                distance in angstroms by which to perturb each site.
        """
        def get_rand_vec():
            #deals with zero vectors.
            vector = np.random.randn(3)
            vnorm = np.linalg.norm(vector)
            return vector / vnorm * distance if vnorm != 0 else get_rand_vec()

        for i in range(len(self._sites)):
            self.translate_sites([i], get_rand_vec(), frac_coords=False)

    def add_oxidation_state_by_element(self, oxidation_states):
        """
        Add oxidation states to a structure.

        Args:
            structure:
                pymatgen.core.structure Structure object.
            oxidation_states:
                dict of oxidation states.
                E.g., {"Li":1, "Fe":2, "P":5, "O":-2}
        """
        try:
            for i, site in enumerate(self._sites):
                new_sp = {}
                for el, occu in site.species_and_occu.items():
                    sym = el.symbol
                    new_sp[Specie(sym, oxidation_states[sym])] = occu
                new_site = PeriodicSite(new_sp, site.frac_coords,
                                        self._lattice,
                                        coords_are_cartesian=False,
                                        properties=site.properties)
                self._sites[i] = new_site

        except KeyError:
            raise ValueError("Oxidation state of all elements must be "
                             "specified in the dictionary.")

    def add_oxidation_state_by_site(self, oxidation_states):
        """
        Add oxidation states to a structure by site.

        Args:
            oxidation_states:
                List of oxidation states.
                E.g., [1, 1, 1, 1, 2, 2, 2, 2, 5, 5, 5, 5, -2, -2, -2, -2]
        """
        try:
            for i, site in enumerate(self._sites):
                new_sp = {}
                for el, occu in site.species_and_occu.items():
                    sym = el.symbol
                    new_sp[Specie(sym, oxidation_states[i])] = occu
                new_site = PeriodicSite(new_sp, site.frac_coords,
                                        self._lattice,
                                        coords_are_cartesian=False,
                                        properties=site.properties)
                self._sites[i] = new_site

        except IndexError:
            raise ValueError("Oxidation state of all sites must be "
                             "specified in the dictionary.")

    def remove_oxidation_states(self):
        """
        Removes oxidation states from a structure.
        """
        for i, site in enumerate(self._sites):
            new_sp = collections.defaultdict(float)
            for el, occu in site.species_and_occu.items():
                sym = el.symbol
                new_sp[Element(sym)] += occu
            new_site = PeriodicSite(new_sp, site.frac_coords,
                                    self._lattice,
                                    coords_are_cartesian=False,
                                    properties=site.properties)
            self._sites[i] = new_site

    def to_unit_cell(self, tolerance=0.1):
        """
        Returns all the sites to their position inside the unit cell.
        If there is a site within the tolerance already there, the site is
        deleted instead of moved.
        """
        new_sites = []
        for site in self._sites:
            if not new_sites:
                new_sites.append(site)
                frac_coords = np.array([site.frac_coords])
                continue
            if len(get_points_in_sphere_pbc(self._lattice, frac_coords,
                                            site.coords, tolerance)):
                continue
            frac_coords = np.append(frac_coords, [site.frac_coords % 1],
                                    axis=0)
            new_sites.append(site.to_unit_cell)
        self._sites = new_sites

    @property
    def original_structure(self):
        """
        The original structure.
        """
        return self._original_structure

    @property
    def modified_structure(self):
        coords = [site.frac_coords for site in self._sites]
        species = [site.species_and_occu for site in self._sites]
        props = {}
        if self._sites[0].properties:
            for k in self._sites[0].properties.keys():
                props[k] = [site.properties[k] for site in self._sites]
        return Structure(self._lattice, species, coords, False,
                         site_properties=props)
예제 #20
0
    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