Пример #1
0
 def test_class(self):
     # Tests entire class as single working unit
     sm = StructureMatcher()
     # Test group_structures and find_indices
     out = sm.group_structures(self.struct_list)
     self.assertEqual(list(map(len, out)), [4, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1])
     self.assertEqual(sum(map(len, out)), len(self.struct_list))
     for s in self.struct_list[::2]:
         s.replace_species({'Ti': 'Zr', 'O': 'Ti'})
     out = sm.group_structures(self.struct_list, anonymous=True)
     self.assertEqual(list(map(len, out)), [4, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1])
Пример #2
0
 def test_class(self):
     # Tests entire class as single working unit
     sm = StructureMatcher()
     # Test group_structures and find_indices
     out = sm.group_structures(self.struct_list)
     self.assertEqual(list(map(len, out)), [4, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1])
     self.assertEqual(sum(map(len, out)), len(self.struct_list))
     for s in self.struct_list[::2]:
         s.replace_species({'Ti': 'Zr', 'O':'Ti'})
     out = sm.group_structures(self.struct_list, anonymous=True)
     self.assertEqual(list(map(len, out)), [4, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1])
def compare_structures(strt_list_to_compare):
    matcher = StructureMatcher()
    strt_holder = []
    for grp in matcher.group_structures(strt_list_to_compare):
        strt_holder.append(grp[0])

    return strt_holder
Пример #4
0
    def test_structure(self):
        pep = self.peptide
        structure = pep.structure
        self.assertEqual(type(structure).__name__, 'Structure')
        self.assertEqual(len(structure.composition.elements), 21)
        self.assertEqual(structure.num_sites, 2004)
        self.assertEqual(structure.formula, "H1320 C30 S1 N6 O647")
        structure.remove_oxidation_states()
        self.assertEqual(len(structure.composition.elements), 5)

        ethane = self.ethane
        structure = ethane.structure
        self.assertEqual(len(structure.composition.elements), 2)
        self.assertEqual(structure.num_sites, 8)
        self.assertEqual(structure.formula, "H6 C2")

        quartz = self.quartz
        structure = quartz.structure
        self.assertEqual(len(structure.composition.elements), 2)
        self.assertEqual(structure.num_sites, 9)
        self.assertEqual(structure.formula, "Si3 O6")

        # test tilt structure
        tilt_str = Structure.from_file(os.path.join(test_dir, "POSCAR_tilt"))
        lmp_tilt_data = structure_2_lmpdata(tilt_str)
        s = StructureMatcher()
        groups = s.group_structures([lmp_tilt_data.structure, tilt_str])
        self.assertEqual(len(groups), 1)
Пример #5
0
    def test_previous_reconstructions(self):

        # Test to see if we generated all reconstruction
        # types correctly and nothing changes

        m = StructureMatcher()
        for n in self.rec_archive.keys():
            if "base_reconstruction" in self.rec_archive[n].keys():
                arch = self.rec_archive[
                    self.rec_archive[n]["base_reconstruction"]]
                sg = arch["spacegroup"]["symbol"]
            else:
                sg = self.rec_archive[n]["spacegroup"]["symbol"]
            if sg == "Fm-3m":
                rec = ReconstructionGenerator(self.Ni, 20, 20, n)
                el = self.Ni[0].species_string
            elif sg == "Im-3m":
                rec = ReconstructionGenerator(self.Fe, 20, 20, n)
                el = self.Fe[0].species_string
            elif sg == "Fd-3m":
                rec = ReconstructionGenerator(self.Si, 20, 20, n)
                el = self.Si[0].species_string

            slabs = rec.build_slabs()
            s = Structure.from_file(get_path(os.path.join("reconstructions",
                                                          el + "_" + n + ".cif")))
            self.assertTrue(any(
                [len(m.group_structures([s, slab])) == 1 for slab in slabs]))
Пример #6
0
def compare_structures(options):
    """Inspired to a similar function in pmg_structure."""
    paths = options.paths
    if len(paths) < 2:
        print("You need more than one structure to compare!")
        return 1

    try:
        structures = [abilab.Structure.from_file(p) for p in paths]
    except Exception as ex:
        print("Error reading structures from files. Are they in the right format?")
        print(str(ex))
        return 1

    from pymatgen.analysis.structure_matcher import StructureMatcher, ElementComparator
    compareby = "species" if options.anonymous else "element"
    m = StructureMatcher() if compareby == "species" else StructureMatcher(comparator=ElementComparator())
    print("Grouping %s structures by `%s` with `anonymous: %s`" % (len(structures), compareby, options.anonymous))

    for i, grp in enumerate(m.group_structures(structures, anonymous=options.anonymous)):
        print("Group {}: ".format(i))
        for s in grp:
            spg_symbol, international_number = s.get_space_group_info()
            print("\t- {} ({}), vol: {:.2f} A^3, {} ({})".format(
                  paths[structures.index(s)], s.formula, s.volume, spg_symbol, international_number))
        print()

    if options.verbose:
        pprint(m.as_dict())
Пример #7
0
 def test_class(self):
     # Tests entire class as single working unit
     sm = StructureMatcher()
     # Test group_structures and find_indices
     out = sm.group_structures(self.struct_list)
     self.assertEqual(map(len, out), [4, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1])
     self.assertEqual(sum(map(len, out)), len(self.struct_list))
Пример #8
0
    def test_previous_reconstructions(self):

        # Test to see if we generated all reconstruction
        # types correctly and nothing changes

        m = StructureMatcher()
        for n in self.rec_archive.keys():
            if "base_reconstruction" in self.rec_archive[n].keys():
                arch = self.rec_archive[self.rec_archive[n]
                                        ["base_reconstruction"]]
                sg = arch["spacegroup"]["symbol"]
            else:
                sg = self.rec_archive[n]["spacegroup"]["symbol"]
            if sg == "Fm-3m":
                rec = ReconstructionGenerator(self.Ni, 20, 20, n)
                el = self.Ni[0].species_string
            elif sg == "Im-3m":
                rec = ReconstructionGenerator(self.Fe, 20, 20, n)
                el = self.Fe[0].species_string
            elif sg == "Fd-3m":
                rec = ReconstructionGenerator(self.Si, 20, 20, n)
                el = self.Si[0].species_string

            slabs = rec.build_slabs()
            s = Structure.from_file(
                get_path(os.path.join("reconstructions",
                                      el + "_" + n + ".cif")))
            self.assertTrue(
                any([
                    len(m.group_structures([s, slab])) == 1 for slab in slabs
                ]))
Пример #9
0
    def test_class(self):
        # Tests entire class as single working unit
        sm = StructureMatcher()
        # Test group_structures and find_indices
        out = sm.group_structures(self.struct_list)

        self.assertEqual(sm.find_indexes(self.struct_list, out), [0, 0, 0, 1, 2, 3, 4, 0, 5, 6, 7, 8, 8, 9, 9, 10])
Пример #10
0
    def test_ignore_species(self):
        s1 = Structure.from_file(os.path.join(test_dir, "LiFePO4.cif"))
        s2 = Structure.from_file(os.path.join(test_dir, "POSCAR"))
        m = StructureMatcher(ignored_species=["Li"],
                             primitive_cell=False,
                             attempt_supercell=True)
        self.assertTrue(m.fit(s1, s2))
        self.assertTrue(m.fit_anonymous(s1, s2))
        groups = m.group_structures([s1, s2])
        self.assertEqual(len(groups), 1)
        s2.make_supercell((2, 1, 1))
        ss1 = m.get_s2_like_s1(s2, s1, include_ignored_species=True)
        self.assertAlmostEqual(ss1.lattice.a, 20.820740000000001)
        self.assertEqual(ss1.composition.reduced_formula, "LiFePO4")

        self.assertEqual(
            {
                k.symbol: v.symbol
                for k, v in m.get_best_electronegativity_anonymous_mapping(
                    s1, s2).items()
            }, {
                "Fe": "Fe",
                "P": "P",
                "O": "O"
            })
Пример #11
0
def compare_structures(args):
    """
    Compare structures in files for similarity using structure matcher.

    Args:
        args (dict): Args from argparse.
    """
    filenames = args.filenames
    if len(filenames) < 2:
        print("You need more than one structure to compare!")
        sys.exit(-1)
    try:
        structures = [Structure.from_file(fn) for fn in filenames]
    except Exception as ex:
        print("Error converting file. Are they in the right format?")
        print(str(ex))
        sys.exit(-1)

    m = StructureMatcher() if args.group == "species" else StructureMatcher(
        comparator=ElementComparator())
    for idx, grp in enumerate(m.group_structures(structures)):
        print(f"Group {idx}: ")
        for s in grp:
            print(f"- {filenames[structures.index(s)]} ({s.formula})")
        print()
Пример #12
0
    def filter_and_group_tasks(self, tasks):
        """
        Groups tasks by structure matching
        """

        filtered_tasks = [t for t in tasks if task_type(
            t['input']['incar']) in self.allowed_tasks]

        structures = [Structure.from_dict(
            t["output"]['structure']) for t in filtered_tasks]

        if self.separate_mag_orderings:
            for structure in structures:
                if has(structure.site_properties,"magmom"):
                    structure.add_spin_by_site(structure.site_properties['magmom'])
                    structure.remove_site_property('magmom')

        for idx, s in enumerate(structures):
            s.index = idx

        sm = StructureMatcher(ltol=self.ltol, stol=self.stol, angle_tol=self.angle_tol,
                              primitive_cell=True, scale=True,
                              attempt_supercell=False, allow_subset=False,
                              comparator=ElementComparator())

        grouped_structures = sm.group_structures(structures)

        grouped_tasks = [[filtered_tasks[struc.index]
                          for struc in group] for group in grouped_structures]

        return grouped_tasks
Пример #13
0
def site_weighted_spectrum(xas_list: list[XAS], num_samples: int = 500) -> XAS:
    """
    Obtain site-weighted XAS object based on site multiplicity for each
    absorbing index and its corresponding site-wise spectrum.

    Args:
        xas_list([XAS]): List of XAS object to be weighted
        num_samples(int): Number of samples for interpolation

    Returns:
        XAS object: The site-weighted spectrum
    """
    m = StructureMatcher()
    groups = m.group_structures([i.structure for i in xas_list])
    if len(groups) > 1:
        raise ValueError("The input structures mismatch")
    if not len({i.absorbing_element for i in xas_list}) == len({i.edge for i in xas_list}) == 1:
        raise ValueError(
            "Can only perform site-weighting for spectra with same absorbing element and same absorbing edge."
        )
    if len({i.absorbing_index for i in xas_list}) == 1 or None in {i.absorbing_index for i in xas_list}:
        raise ValueError("Need at least two site-wise spectra to perform site-weighting")

    sa = SpacegroupAnalyzer(groups[0][0])
    ss = sa.get_symmetrized_structure()
    maxes, mines = [], []
    fs = []
    multiplicities = []

    for xas in xas_list:
        multiplicity = len(ss.find_equivalent_sites(ss[xas.absorbing_index]))
        multiplicities.append(multiplicity)
        maxes.append(max(xas.x))
        mines.append(min(xas.x))
        # use 3rd-order spline interpolation for mu (idx 3) vs energy (idx 0).
        f = interp1d(
            np.asarray(xas.x),
            np.asarray(xas.y),
            bounds_error=False,
            fill_value=0,
            kind="cubic",
        )
        fs.append(f)
    # Interpolation within the intersection of x-axis ranges.
    x_axis = np.linspace(max(mines), min(maxes), num=num_samples)
    weighted_spectrum = np.zeros(num_samples)
    sum_multiplicities = sum(multiplicities)

    for i, j in enumerate(multiplicities):
        weighted_spectrum += (j * fs[i](x_axis)) / sum_multiplicities

    return XAS(
        x_axis,
        weighted_spectrum,
        ss,
        xas.absorbing_element,
        xas.edge,
        xas.spectrum_type,
    )
Пример #14
0
    def test_class(self):
        # Tests entire class as single working unit
        sm = StructureMatcher()
        # Test group_structures and find_indices
        out = sm.group_structures(self.struct_list)

        self.assertEqual(sm.find_indexes(self.struct_list, out),
                         [0, 0, 0, 1, 2, 3, 4, 0, 5, 6, 7, 8, 8, 9, 9, 10])
Пример #15
0
    def apply_transformation(self, structure, return_ranked_list=False):
        #Make a mutable structure first
        mods = Structure.from_sites(structure)
        for sp, spin in self.mag_species_spin.items():
            sp = get_el_sp(sp)
            oxi_state = getattr(sp, "oxi_state", 0)
            if spin:
                up = Specie(sp.symbol, oxi_state, {"spin": abs(spin)})
                down = Specie(sp.symbol, oxi_state, {"spin": -abs(spin)})
                mods.replace_species(
                    {sp: Composition({up: self.order_parameter,
                                      down: 1 - self.order_parameter})})
            else:
                mods.replace_species(
                    {sp: Specie(sp.symbol, oxi_state, {"spin": spin})})

        if mods.is_ordered:
            return [mods] if return_ranked_list > 1 else mods

        enum_args = self.enum_kwargs

        enum_args["min_cell_size"] = max(int(
            MagOrderingTransformation.determine_min_cell(
                structure, self.mag_species_spin,
                self.order_parameter)),
            enum_args.get("min_cell_size"))

        max_cell = self.enum_kwargs.get('max_cell_size')
        if max_cell:
            if enum_args["min_cell_size"] > max_cell:
                raise ValueError('Specified max cell size is smaller'
                                 ' than the minimum enumerable cell size')
        else:
            enum_args["max_cell_size"] = enum_args["min_cell_size"]

        t = EnumerateStructureTransformation(**enum_args)

        alls = t.apply_transformation(mods,
                                      return_ranked_list=return_ranked_list)

        try:
            num_to_return = int(return_ranked_list)
        except ValueError:
            num_to_return = 1

        if num_to_return == 1 or not return_ranked_list:
            return alls[0]["structure"] if num_to_return else alls

        m = StructureMatcher(comparator=SpinComparator())

        grouped = m.group_structures([d["structure"] for d in alls])

        alls = [{"structure": g[0], "energy": self.emodel.get_energy(g[0])}
                for g in grouped]

        self._all_structures = sorted(alls, key=lambda d: d["energy"])

        return self._all_structures[0:num_to_return]
Пример #16
0
def group_structures(
    structures,
    ltol=LTOL,
    stol=STOL,
    angle_tol=ANGLE_TOL,
    symprec=SYMPREC,
    separate_mag_orderings=False,
):
    """
    Groups structures according to space group and structure matching

    Args:
        structures ([Structure]): list of structures to group
        ltol (float): StructureMatcher tuning parameter for matching tasks to materials
        stol (float): StructureMatcher tuning parameter for matching tasks to materials
        angle_tol (float): StructureMatcher tuning parameter for matching tasks to materials
        symprec (float): symmetry tolerance for space group finding
        separate_mag_orderings (bool): Separate magnetic orderings into different materials
    """

    sm = StructureMatcher(
        ltol=ltol,
        stol=stol,
        angle_tol=angle_tol,
        primitive_cell=True,
        scale=True,
        attempt_supercell=False,
        allow_subset=False,
        comparator=ElementComparator(),
    )

    def get_sg(struc):
        # helper function to get spacegroup with a loose tolerance
        try:
            sg = struc.get_space_group_info(symprec=SYMPREC)[1]
        except:
            sg = -1

        return sg

    def get_mag_ordering(struc):
        # helperd function to get a label of the magnetic ordering type
        return np.around(np.abs(struc.total_magnetization) / struc.volume,
                         decimals=1)

    # First group by spacegroup number then by structure matching
    for sg, pregroup in groupby(sorted(structures, key=get_sg), key=get_sg):
        for group in sm.group_structures(list(pregroup)):

            # Match magnetic orderings here
            if separate_mag_orderings:
                for _, mag_group in groupby(sorted(group,
                                                   key=get_mag_ordering),
                                            key=get_mag_ordering):
                    yield list(mag_group)
            else:
                yield group
Пример #17
0
def group_structures(s_l):
    """group_structures
    Applies a structure grouping algorithm to a list of structures.
    :param s_l: List of Structure objects.
    :return: List of lists of grouped structures.
    """
    sm = StructureMatcher(scale=True,
                          attempt_supercell=True,
                          comparator=FrameworkComparator())
    return sm.group_structures(s_l)
Пример #18
0
def test_coloring_with_fixed_species():
    lattice = Lattice(3.945 * np.eye(3))
    species = ["Sr", "Ti", "O", "O", "O"]
    frac_coords = np.array([[0, 0, 0], [0.5, 0.5, 0.5], [0.0, 0.5, 0.5],
                            [0.5, 0.0, 0.5], [0.5, 0.5, 0.0]])
    aristo = Structure(lattice, species, frac_coords)
    base_structure = aristo.copy()
    base_structure.remove_species(["Sr", "Ti"])
    additional_species = species[:2]
    additional_frac_coords = frac_coords[:2]

    mapping_color_species = [DummySpecie("X"), "O"]
    num_types = len(mapping_color_species)
    index = 2

    se = StructureEnumerator(
        base_structure,
        index,
        num_types,
        mapping_color_species=mapping_color_species,
        color_exchange=False,
        leave_superperiodic=False,
        use_all_colors=False,
    )
    list_dstructs = se.generate(additional_species=additional_species,
                                additional_frac_coords=additional_frac_coords)

    # with base_site_constraints
    mapping_color_species2 = [DummySpecie("X"), "O", "Sr", "Ti"]
    num_types2 = len(mapping_color_species2)
    base_site_constraints = [
        [2],  # Sr site
        [3],  # Cu site
        [0, 1],  # O or V
        [0, 1],  # O or V
        [0, 1],  # O or V
    ]
    se2 = StructureEnumerator(
        aristo,
        index,
        num_types2,
        mapping_color_species=mapping_color_species2,
        base_site_constraints=base_site_constraints,
        color_exchange=False,
        leave_superperiodic=False,
        use_all_colors=False,
    )
    list_dstructs2 = se2.generate()

    # check uniqueness by StructureMatcher
    stm = StructureMatcher(ltol=1e-4, stol=1e-4)
    grouped = stm.group_structures(list_dstructs + list_dstructs2)
    assert len(grouped) == len(list_dstructs)
    assert all([(len(matched) == 2) for matched in grouped])
Пример #19
0
 def test_mix(self):
     structures = [self.get_structure("Li2O"), self.get_structure("Li2O2"), self.get_structure("LiFePO4")]
     for fname in ["POSCAR.Li2O", "POSCAR.LiFePO4"]:
         structures.append(Structure.from_file(os.path.join(test_dir, fname)))
     sm = StructureMatcher(comparator=ElementComparator())
     groups = sm.group_structures(structures)
     for g in groups:
         formula = g[0].composition.reduced_formula
         if formula in ["Li2O", "LiFePO4"]:
             self.assertEqual(len(g), 2)
         else:
             self.assertEqual(len(g), 1)
Пример #20
0
 def test_mix(self):
     structures = []
     for fname in ["POSCAR.Li2O", "Li2O.cif", "Li2O2.cif", "LiFePO4.cif",
                   "POSCAR.LiFePO4"]:
         structures.append(read_structure(os.path.join(test_dir, fname)))
     sm = StructureMatcher(comparator=ElementComparator())
     groups = sm.group_structures(structures)
     for g in groups:
         formula = g[0].composition.reduced_formula
         if formula in ["Li2O", "LiFePO4"]:
             self.assertEqual(len(g), 2)
         else:
             self.assertEqual(len(g), 1)
Пример #21
0
def test_coloring_with_fixed_species(sto_perovskite: Structure):
    base_structure = sto_perovskite.copy()
    base_structure.remove_species(["Sr", "Ti"])
    additional_species = sto_perovskite.species[:2]
    additional_frac_coords = sto_perovskite.frac_coords[:2]

    mapping_color_species = [DummySpecie("X"), "O"]
    num_types = len(mapping_color_species)
    index = 2

    se = StructureEnumerator(
        base_structure,
        index,
        num_types,
        mapping_color_species=mapping_color_species,
        color_exchange=False,
        remove_superperiodic=True,
        remove_incomplete=False,
    )
    list_dstructs = se.generate(additional_species=additional_species,
                                additional_frac_coords=additional_frac_coords)

    # with base_site_constraints
    mapping_color_species2 = [DummySpecie("X"), "O", "Sr", "Ti"]
    num_types2 = len(mapping_color_species2)
    base_site_constraints = [
        [2],  # Sr site
        [3],  # Cu site
        [0, 1],  # O or V
        [0, 1],  # O or V
        [0, 1],  # O or V
    ]
    se2 = StructureEnumerator(
        sto_perovskite,
        index,
        num_types2,
        mapping_color_species=mapping_color_species2,
        base_site_constraints=base_site_constraints,
        color_exchange=False,
        remove_superperiodic=True,
        remove_incomplete=False,
    )
    list_dstructs2 = se2.generate()

    # check uniqueness by StructureMatcher
    stm = StructureMatcher(ltol=1e-4, stol=1e-4)
    grouped = stm.group_structures(list_dstructs +
                                   list_dstructs2)  # type: ignore
    assert len(grouped) == len(list_dstructs)
    assert all([(len(matched) == 2) for matched in grouped])
Пример #22
0
def check(base_structure, num_type, indices, species, name):
    for index in indices:
        se = StructureEnumerator(
            base_structure,
            index,
            num_type,
            species,
            color_exchange=True,
            leave_superperiodic=False,
        )
        list_ds = se.generate()

        stm = StructureMatcher(ltol=1e-4, stol=1e-4)
        grouped = stm.group_structures(list_ds)
        assert len(grouped) == len(list_ds)
Пример #23
0
def group_structures(structures,
                     ltol=0.2,
                     stol=0.3,
                     angle_tol=5,
                     symprec=0.1,
                     separate_mag_orderings=False):
    """
    Groups structures according to space group and structure matching

    Args:
        structures ([Structure]): list of structures to group
        ltol (float): StructureMatcher tuning parameter for matching tasks to materials
        stol (float): StructureMatcher tuning parameter for matching tasks to materials
        angle_tol (float): StructureMatcher tuning parameter for matching tasks to materials
        symprec (float): symmetry tolerance for space group finding
        separate_mag_orderings (bool): Separate magnetic orderings into different materials
    """

    sm = StructureMatcher(ltol=ltol,
                          stol=stol,
                          angle_tol=angle_tol,
                          primitive_cell=True,
                          scale=True,
                          attempt_supercell=False,
                          allow_subset=False,
                          comparator=ElementComparator())

    def get_sg(struc):
        # helper function to get spacegroup with a loose tolerance
        return struc.get_space_group_info(symprec=symprec)[1]

    def get_mag_ordering(struc):
        # helperd function to get a label of the magnetic ordering type
        return CollinearMagneticStructureAnalyzer(struc).ordering.value

    # First group by spacegroup number then by structure matching
    for sg, pregroup in groupby(sorted(structures, key=get_sg), key=get_sg):
        for group in sm.group_structures(list(pregroup)):

            # Match magnetic orderings here
            if separate_mag_orderings:
                for _, mag_group in groupby(sorted(group,
                                                   key=get_mag_ordering),
                                            key=get_mag_ordering):
                    yield list(mag_group)
            else:
                yield group
Пример #24
0
    def get_slabs(self, bonds=None, tol=0.1, max_broken_bonds=0):
        """
        This method returns a list of slabs that are generated using the list of
        shift values from the method, _calculate_possible_shifts(). Before the
        shifts are used to create the slabs however, if the user decides to take
        into account whether or not a termination will break any polyhedral
        structure (bonds != None), this method will filter out any shift values
        that do so.

        Args:
            bonds ({(specie1, specie2): max_bond_dist}: bonds are
                specified as a dict of tuples: float of specie1, specie2
                and the max bonding distance. For example, PO4 groups may be
                defined as {("P", "O"): 3}.
            tol (float): Threshold parameter in fcluster in order to check
                if two atoms are lying on the same plane. Default thresh set
                to 0.1 Angstrom in the direction of the surface normal.
            max_broken_bonds (int): Maximum number of allowable broken bonds
                for the slab. Use this to limit # of slabs (some structures
                may have a lot of slabs). Defaults to zero, which means no
                defined bonds must be broken.

        Returns:
            ([Slab]) List of all possible terminations of a particular surface.
            Slabs are sorted by the # of bonds broken.
        """
        c_ranges = set() if bonds is None else self._get_c_ranges(bonds)

        slabs = []
        for shift in self._calculate_possible_shifts(tol=tol):
            bonds_broken = 0
            for r in c_ranges:
                if r[0] <= shift <= r[1]:
                    bonds_broken += 1
            if bonds_broken <= max_broken_bonds:
                # For now, set the energy to be equal to no. of broken bonds
                # per unit cell.
                slab = self.get_slab(shift, tol=tol, energy=bonds_broken)
                slabs.append(slab)

        # Further filters out any surfaces made that might be the same
        m = StructureMatcher(ltol=tol,
                             stol=tol,
                             primitive_cell=False,
                             scale=False)
        slabs = [g[0] for g in m.group_structures(slabs)]
        return sorted(slabs, key=lambda s: s.energy)
Пример #25
0
    def test_ignore_species(self):
        s1 = Structure.from_file(os.path.join(test_dir, "LiFePO4.cif"))
        s2 = Structure.from_file(os.path.join(test_dir, "POSCAR"))
        m = StructureMatcher(ignored_species=["Li"], primitive_cell=False, attempt_supercell=True)
        self.assertTrue(m.fit(s1, s2))
        self.assertTrue(m.fit_anonymous(s1, s2))
        groups = m.group_structures([s1, s2])
        self.assertEqual(len(groups), 1)
        s2.make_supercell((2, 1, 1))
        ss1 = m.get_s2_like_s1(s2, s1, include_ignored_species=True)
        self.assertAlmostEqual(ss1.lattice.a, 20.820740000000001)
        self.assertEqual(ss1.composition.reduced_formula, "LiFePO4")

        self.assertEqual(
            {k.symbol: v.symbol for k, v in m.get_best_electronegativity_anonymous_mapping(s1, s2).items()},
            {"Fe": "Fe", "P": "P", "O": "O"},
        )
Пример #26
0
    def get_slabs(self, bonds=None, tol=0.1, max_broken_bonds=0):
        """
        This method returns a list of slabs that are generated using the list of
        shift values from the method, _calculate_possible_shifts(). Before the
        shifts are used to create the slabs however, if the user decides to take
        into account whether or not a termination will break any polyhedral
        structure (bonds != None), this method will filter out any shift values
        that do so.

        Args:
            bonds ({(specie1, specie2): max_bond_dist}: bonds are
                specified as a dict of tuples: float of specie1, specie2
                and the max bonding distance. For example, PO4 groups may be
                defined as {("P", "O"): 3}.
            tol (float): Threshold parameter in fcluster in order to check
                if two atoms are lying on the same plane. Default thresh set
                to 0.1 Angstrom in the direction of the surface normal.
            max_broken_bonds (int): Maximum number of allowable broken bonds
                for the slab. Use this to limit # of slabs (some structures
                may have a lot of slabs). Defaults to zero, which means no
                defined bonds must be broken.

        Returns:
            ([Slab]) List of all possible terminations of a particular surface.
            Slabs are sorted by the # of bonds broken.
        """
        c_ranges = set() if bonds is None else self._get_c_ranges(bonds)

        slabs = []
        for shift in self._calculate_possible_shifts(tol=tol):
            bonds_broken = 0
            for r in c_ranges:
                if r[0] <= shift <= r[1]:
                    bonds_broken += 1
            if bonds_broken <= max_broken_bonds:
                # For now, set the energy to be equal to no. of broken bonds
                # per unit cell.
                slab = self.get_slab(shift, tol=tol, energy=bonds_broken)
                slabs.append(slab)

        # Further filters out any surfaces made that might be the same
        m = StructureMatcher(ltol=tol, stol=tol, primitive_cell=False,
                             scale=False)
        slabs = [g[0] for g in m.group_structures(slabs)]
        return sorted(slabs, key=lambda s: s.energy)
Пример #27
0
def test_basic_structure(kind, index):
    base_structure = get_lattice(kind)
    num_type = 2
    species = ["Cu", "Au"]

    se = StructureEnumerator(
        base_structure,
        index,
        num_type,
        species,
        color_exchange=True,
        remove_superperiodic=True,
    )
    list_ds = se.generate()

    stm = StructureMatcher(ltol=1e-4, stol=1e-4)
    grouped = stm.group_structures(list_ds)
    assert len(grouped) == len(list_ds)
Пример #28
0
class RemoveDuplicatesFilterTest(unittest.TestCase):
    def setUp(self):
        with open(os.path.join(test_dir, "TiO2_entries.json"), 'r') as fp:
            entries = json.load(fp, cls=PMGJSONDecoder)
        self._struct_list = [e.structure for e in entries]
        self._sm = StructureMatcher()

    def test_filter(self):
        transmuter = StandardTransmuter.from_structures(self._struct_list)
        fil = RemoveDuplicatesFilter()
        transmuter.apply_filter(fil)
        out = self._sm.group_structures(transmuter.transformed_structures)
        self.assertEqual(len(transmuter.transformed_structures), 11)

    def test_to_from_dict(self):
        fil = RemoveDuplicatesFilter()
        d = fil.to_dict
        self.assertIsInstance(RemoveDuplicatesFilter().from_dict(d),
                              RemoveDuplicatesFilter)
Пример #29
0
def compare_structures(args):
    filenames = args.filenames
    if len(filenames) < 2:
        print "You need more than one structure to compare!"
        sys.exit(-1)
    try:
        structures = map(read_structure, filenames)
    except Exception as ex:
        print "Error converting file. Are they in the right format?"
        print str(ex)
        sys.exit(-1)

    from pymatgen.analysis.structure_matcher import StructureMatcher, ElementComparator

    m = StructureMatcher() if args.oxi else StructureMatcher(comparator=ElementComparator())
    for i, grp in enumerate(m.group_structures(structures)):
        print "Group {}: ".format(i)
        for s in grp:
            print "- {} ({})".format(filenames[structures.index(s)], s.formula)
        print
Пример #30
0
def compare_structures(args):
    filenames = args.filenames
    if len(filenames) < 2:
        print("You need more than one structure to compare!")
        sys.exit(-1)
    try:
        structures = [Structure.from_file(fn) for fn in filenames]
    except Exception as ex:
        print("Error converting file. Are they in the right format?")
        print(str(ex))
        sys.exit(-1)

    m = StructureMatcher() if args.group == "species" \
        else StructureMatcher(comparator=ElementComparator())
    for i, grp in enumerate(m.group_structures(structures)):
        print("Group {}: ".format(i))
        for s in grp:
            print("- {} ({})".format(filenames[structures.index(s)],
                                     s.formula))
        print()
Пример #31
0
def test_enumerated_structures(kind, num_types, composition_constraints,
                               site_constraints):
    structure = get_lattice(kind)

    for index in range(1, 5):
        # TODO: when remove_superperiodic = remove_incomplete = False, this test is failed.
        zse = ZddStructureEnumerator(
            structure,
            index,
            num_types,
            composition_constraints=composition_constraints,
            base_site_constraints=site_constraints,
            remove_superperiodic=True,
            remove_incomplete=True,
        )
        list_dstructs = zse.generate()

        stm = StructureMatcher(ltol=1e-4, stol=1e-4)
        grouped = stm.group_structures(list_dstructs)
        assert len(grouped) == len(list_dstructs)
Пример #32
0
class RemoveDuplicatesFilterTest(unittest.TestCase):
    def setUp(self):
        with open(os.path.join(test_dir, "TiO2_entries.json"), "rb") as fp:
            entries = json.load(fp, cls=PMGJSONDecoder)
        self._struct_list = [e.structure for e in entries]
        self._sm = StructureMatcher()

    def test_filter(self):
        transmuter = StandardTransmuter.from_structures(self._struct_list)
        fil = RemoveDuplicatesFilter()
        transmuter.apply_filter(fil)
        out = self._sm.group_structures(transmuter.transformed_structures)
        self.assertEqual(
            self._sm.find_indexes(transmuter.transformed_structures, out), [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
        )

    def test_to_from_dict(self):
        fil = RemoveDuplicatesFilter()
        d = fil.to_dict
        self.assertIsInstance(RemoveDuplicatesFilter().from_dict(d), RemoveDuplicatesFilter)
Пример #33
0
    def test_structure(self):
        structure = self.lammps_data.structure
        self.assertEqual(len(structure.composition.elements), 2)
        self.assertEqual(structure.num_sites, 3)
        self.assertEqual(structure.formula, "Li2 O1")

        lammps_data = LammpsData.from_file(os.path.join(test_dir, "nvt.data"),
                                           'full')
        self.assertEqual(type(lammps_data.structure).__name__, 'Structure')
        self.assertEqual(len(lammps_data.structure.composition.elements), 2)
        self.assertEqual(lammps_data.structure.num_sites, 648)
        self.assertEqual(lammps_data.structure.symbol_set[0], 'O')
        self.assertEqual(lammps_data.structure.symbol_set[1], 'H')
        self.assertEqual(lammps_data.structure.volume, 27000)

        # test tilt structure
        tilt_str = mg.Structure.from_file(os.path.join(test_dir, "POSCAR"))
        lmp_tilt_data = LammpsData.from_structure(tilt_str)
        s = StructureMatcher()
        groups = s.group_structures([lmp_tilt_data.structure, tilt_str])
        self.assertEqual(len(groups), 1)
Пример #34
0
def compare_structures(args):
    filenames = args.filenames
    if len(filenames) < 2:
        print "You need more than one structure to compare!"
        sys.exit(-1)
    try:
        structures = map(read_structure, filenames)
    except Exception as ex:
        print "Error converting file. Are they in the right format?"
        print str(ex)
        sys.exit(-1)

    from pymatgen.analysis.structure_matcher import StructureMatcher, \
        ElementComparator
    m = StructureMatcher() if args.oxi \
        else StructureMatcher(comparator=ElementComparator())
    for i, grp in enumerate(m.group_structures(structures)):
        print "Group {}: ".format(i)
        for s in grp:
            print "- {} ({})".format(filenames[structures.index(s)], s.formula)
        print
Пример #35
0
def group_structures(structures,
                     ltol=0.2,
                     stol=0.3,
                     angle_tol=5,
                     separate_mag_orderings=False):
    """
    Groups structures according to space group and structure matching

    Args:
        structures ([Structure]): list of structures to group
        ltol (float): StructureMatcher tuning parameter for matching tasks to materials
        stol (float): StructureMatcher tuning parameter for matching tasks to materials
        angle_tol (float): StructureMatcher tuning parameter for matching tasks to materials
        separate_mag_orderings (bool): Separate magnetic orderings into different materials
    """
    if separate_mag_orderings:
        for structure in structures:
            if has(structure.site_properties, "magmom"):
                structure.add_spin_by_site(structure.site_properties['magmom'])
                structure.remove_site_property('magmom')

    sm = StructureMatcher(ltol=ltol,
                          stol=stol,
                          angle_tol=angle_tol,
                          primitive_cell=True,
                          scale=True,
                          attempt_supercell=False,
                          allow_subset=False,
                          comparator=ElementComparator())

    def get_sg(struc):
        return struc.get_space_group_info(symprec=0.1)[1]

    # First group by spacegroup number then by structure matching
    for _, pregroup in groupby(sorted(structures, key=get_sg), key=get_sg):
        for group in sm.group_structures(sorted(pregroup, key=get_sg)):
            yield group
Пример #36
0
def group_structures(
    structures: List[Structure],
    ltol: float = SETTINGS.LTOL,
    stol: float = SETTINGS.STOL,
    angle_tol: float = SETTINGS.ANGLE_TOL,
    symprec: float = SETTINGS.SYMPREC,
) -> Iterator[List[Structure]]:
    """
    Groups structures according to space group and structure matching

    Args:
        structures ([Structure]): list of structures to group
        ltol (float): StructureMatcher tuning parameter for matching tasks to materials
        stol (float): StructureMatcher tuning parameter for matching tasks to materials
        angle_tol (float): StructureMatcher tuning parameter for matching tasks to materials
        symprec (float): symmetry tolerance for space group finding
    """

    sm = StructureMatcher(
        ltol=ltol,
        stol=stol,
        angle_tol=angle_tol,
        primitive_cell=True,
        scale=True,
        attempt_supercell=False,
        allow_subset=False,
        comparator=ElementComparator(),
    )

    def _get_sg(struc):
        return get_sg(struc, symprec=symprec)

    # First group by spacegroup number then by structure matching
    for sg, pregroup in groupby(sorted(structures, key=_get_sg), key=_get_sg):
        for group in sm.group_structures(list(pregroup)):
            yield group
Пример #37
0
    def apply_transformation(self, structure, return_ranked_list=False):
        """
        Apply MagOrderTransformation to an input structure.
        :param structure: Any ordered structure.
        :param return_ranked_list: As in other Transformations.
        :return:
        """

        if not structure.is_ordered:
            raise ValueError("Create an ordered approximation of "
                             "your  input structure first.")

        # retrieve order parameters
        order_parameters = [MagOrderParameterConstraint.from_dict(op_dict)
                            for op_dict in self.order_parameter]
        # add dummy species on which to perform enumeration
        structure = self._add_dummy_species(structure, order_parameters)

        # trivial case
        if structure.is_ordered:
            structure = self._remove_dummy_species(structure)
            return [structure] if return_ranked_list > 1 else structure

        enum_kwargs = self.enum_kwargs.copy()

        enum_kwargs["min_cell_size"] = max(
            int(self.determine_min_cell(structure)),
            enum_kwargs.get("min_cell_size", 1)
        )

        if enum_kwargs.get("max_cell_size", None):
            if enum_kwargs["min_cell_size"] > enum_kwargs["max_cell_size"]:
                warnings.warn("Specified max cell size ({}) is smaller "
                              "than the minimum enumerable cell size ({}), "
                              "changing max cell size to {}".format(enum_kwargs["max_cell_size"],
                                                                    enum_kwargs["min_cell_size"],
                                                                    enum_kwargs["min_cell_size"]))
                enum_kwargs["max_cell_size"] = enum_kwargs["min_cell_size"]
        else:
            enum_kwargs["max_cell_size"] = enum_kwargs["min_cell_size"]

        t = EnumerateStructureTransformation(**enum_kwargs)

        alls = t.apply_transformation(structure,
                                      return_ranked_list=return_ranked_list)

        # handle the fact that EnumerateStructureTransformation can either
        # return a single Structure or a list
        if isinstance(alls, Structure):
            # remove dummy species and replace Spin.up or Spin.down
            # with spin magnitudes given in mag_species_spin arg
            alls = self._remove_dummy_species(alls)
            alls = self._add_spin_magnitudes(alls)
        else:
            for idx, _ in enumerate(alls):
                alls[idx]["structure"] = self._remove_dummy_species(alls[idx]["structure"])
                alls[idx]["structure"] = self._add_spin_magnitudes(alls[idx]["structure"])

        try:
            num_to_return = int(return_ranked_list)
        except ValueError:
            num_to_return = 1

        if num_to_return == 1 or not return_ranked_list:
            return alls[0]["structure"] if num_to_return else alls

        # remove duplicate structures and group according to energy model
        m = StructureMatcher(comparator=SpinComparator())
        key = lambda x: SpacegroupAnalyzer(x, 0.1).get_space_group_number()
        out = []
        for _, g in groupby(sorted([d["structure"] for d in alls],
                                   key=key), key):
            g = list(g)
            grouped = m.group_structures(g)
            out.extend([{"structure": g[0],
                         "energy": self.energy_model.get_energy(g[0])}
                        for g in grouped])

        self._all_structures = sorted(out, key=lambda d: d["energy"])

        return self._all_structures[0:num_to_return]
Пример #38
0
    def get_slabs(self, bonds=None, tol=0.1, max_broken_bonds=0):
        """
        This method returns a list of slabs that are generated using the list of
        shift values from the method, _calculate_possible_shifts(). Before the
        shifts are used to create the slabs however, if the user decides to take
        into account whether or not a termination will break any polyhedral
        structure (bonds != None), this method will filter out any shift values
        that do so.

        Args:
            bonds ({(specie1, specie2): max_bond_dist}: bonds are
                specified as a dict of tuples: float of specie1, specie2
                and the max bonding distance. For example, PO4 groups may be
                defined as {("P", "O"): 3}.
            tol (float): Threshold parameter in fcluster in order to check
                if two atoms are lying on the same plane. Default thresh set
                to 0.1 Angstrom in the direction of the surface normal.
            max_broken_bonds (int): Maximum number of allowable broken bonds
                for the slab. Use this to limit # of slabs (some structures
                may have a lot of slabs). Defaults to zero, which means no
                defined bonds must be broken.

        Returns:
            ([Slab]) List of all possible terminations of a particular surface.
            Slabs are sorted by the # of bonds broken.
        """
        def get_c_ranges(site1, site2, bond_dist):
            lattice = site1.lattice
            f1 = site1.frac_coords
            c_ranges = []
            for dist, image in lattice.get_all_distance_and_image(
                    f1, site2.frac_coords):
                if dist < bond_dist:
                    f2 = site2.frac_coords + image
                    # Checks if the distance between the two species
                    # is less then the user input bond distance
                    min_c = f1[2]
                    max_c = f2[2]
                    c_range = sorted([min_c, max_c])
                    if c_range[1] > 1:
                        # Takes care of PBC when c coordinate of site
                        # goes beyond the upper boundary of the cell
                        c_ranges.append((c_range[0], 1))
                        c_ranges.append((0, c_range[1] - 1))
                    elif c_range[0] < 0:
                        # Takes care of PBC when c coordinate of site
                        # is below the lower boundary of the unit cell
                        c_ranges.append((0, c_range[1]))
                        c_ranges.append((c_range[0] + 1, 1))
                    else:
                        c_ranges.append(c_range)
            return c_ranges

        bond_c_ranges = []
        if bonds is not None:
            #Convert to species first
            bonds = {(get_el_sp(s1), get_el_sp(s2)): dist for (s1, s2), dist in
                     bonds.items()}
            for s1, s2 in itertools.combinations(self.oriented_unit_cell, 2):
                # Iterates through every possible pair of species in the
                # oriented unit cell
                all_sp = set(s1.species_and_occu.keys())
                all_sp.update(s2.species_and_occu.keys())
                for species, bond_dist in bonds.items():
                    # Checks if elements in species is in all_sp
                    if all_sp.issuperset(species):
                        bond_c_ranges.extend(get_c_ranges(s1, s2, bond_dist))

        slabs = []
        for shift in self._calculate_possible_shifts(tol=tol):
            bonds_broken = 0
            for r in bond_c_ranges:
                if r[0] <= shift <= r[1]:
                    bonds_broken += 1
            if bonds_broken <= max_broken_bonds:
                # For now, set the energy to be equal to no. of broken bonds
                # per unit cell.
                slab = self.get_slab(shift, tol=tol, energy=bonds_broken)
                slabs.append(slab)

        # Further filters out any surfaces made that might be the same
        m = StructureMatcher(ltol=tol, stol=tol, primitive_cell=False,
                             scale=False)
        slabs = [g[0] for g in m.group_structures(slabs)]
        return sorted(slabs, key=lambda s: s.energy)
Пример #39
0
    def generate_substitution_structures(self,
                                         atom,
                                         target_species=[],
                                         sub_both_sides=False,
                                         range_tol=1e-2,
                                         dist_from_surf=0):
        """
        Function that performs substitution-type doping on the surface and
            returns all possible configurations where one dopant is substituted
            per surface. Can substitute one surface or both.

        Args:
            atom (str): atom corresponding to substitutional dopant
            sub_both_sides (bool): If true, substitute an equivalent
                site on the other surface
            target_species (list): List of specific species to substitute
            range_tol (float): Find viable substitution sites at a specific
                distance from the surface +- this tolerance
            dist_from_surf (float): Distance from the surface to find viable
                substitution sites, defaults to 0 to substitute at the surface
        """

        # Get symmetrized structure in case we want to substitue both sides
        sym_slab = SpacegroupAnalyzer(self.slab).get_symmetrized_structure()

        # Define a function for substituting a site
        def substitute(site, i):
            slab = self.slab.copy()
            props = self.slab.site_properties
            if sub_both_sides:
                # Find an equivalent site on the other surface
                eq_indices = [
                    indices for indices in sym_slab.equivalent_indices
                    if i in indices
                ][0]
                for ii in eq_indices:
                    if "%.6f" % (sym_slab[ii].frac_coords[2]) != \
                                    "%.6f" % (site.frac_coords[2]):
                        props["surface_properties"][ii] = "substitute"
                        slab.replace(ii, atom)
                        break

            props["surface_properties"][i] = "substitute"
            slab.replace(i, atom)
            slab.add_site_property("surface_properties",
                                   props["surface_properties"])
            return slab

        # Get all possible substitution sites
        substituted_slabs = []
        # Sort sites so that we can define a range relative to the position of the
        # surface atoms, i.e. search for sites above (below) the bottom (top) surface
        sorted_sites = sorted(sym_slab, key=lambda site: site.frac_coords[2])
        if sorted_sites[0].surface_properties == "surface":
            d = sorted_sites[0].frac_coords[2] + dist_from_surf
        else:
            d = sorted_sites[-1].frac_coords[2] - dist_from_surf

        for i, site in enumerate(sym_slab):
            if d - range_tol < site.frac_coords[2] < d + range_tol:
                if target_species and site.species_string in target_species:
                    substituted_slabs.append(substitute(site, i))
                elif not target_species:
                    substituted_slabs.append(substitute(site, i))

        matcher = StructureMatcher()
        return [s[0] for s in matcher.group_structures(substituted_slabs)]
Пример #40
0
    def get_tasker2_slabs(self, tol=0.01, same_species_only=True):
        """
        Get a list of slabs that have been Tasker 2 corrected.

        Args:
            tol (float): Tolerance to determine if atoms are within same plane.
                This is a fractional tolerance, not an absolute one.
            same_species_only (bool): If True, only that are of the exact same
                species as the atom at the outermost surface are considered for
                moving. Otherwise, all atoms regardless of species that is
                within tol are considered for moving. Default is True (usually
                the desired behavior).

        Returns:
            ([Slab]) List of tasker 2 corrected slabs.
        """
        sites = list(self.sites)
        slabs = []

        sortedcsites = sorted(sites, key=lambda site: site.c)

        # Determine what fraction the slab is of the total cell size in the
        # c direction. Round to nearest rational number.
        nlayers_total = int(round(self.lattice.c /
                                  self.oriented_unit_cell.lattice.c))
        nlayers_slab = int(round((sortedcsites[-1].c - sortedcsites[0].c)
                                 * nlayers_total))
        slab_ratio = nlayers_slab / nlayers_total

        a = SpacegroupAnalyzer(self)
        symm_structure = a.get_symmetrized_structure()

        def equi_index(site):
            for i, equi_sites in enumerate(symm_structure.equivalent_sites):
                if site in equi_sites:
                    return i
            raise ValueError("Cannot determine equi index!")

        for surface_site, shift in [(sortedcsites[0], slab_ratio),
                                    (sortedcsites[-1], -slab_ratio)]:
            tomove = []
            fixed = []
            for site in sites:
                if abs(site.c - surface_site.c) < tol and (
                        (not same_species_only) or
                        site.species_and_occu == surface_site.species_and_occu):
                    tomove.append(site)
                else:
                    fixed.append(site)

            # Sort and group the sites by the species and symmetry equivalence
            tomove = sorted(tomove, key=lambda s: equi_index(s))

            grouped = [list(sites) for k, sites in itertools.groupby(
                tomove, key=lambda s: equi_index(s))]

            if len(tomove) == 0 or any([len(g) % 2 != 0 for g in grouped]):
                warnings.warn("Odd number of sites to divide! Try changing "
                              "the tolerance to ensure even division of "
                              "sites or create supercells in a or b directions "
                              "to allow for atoms to be moved!")
                continue
            combinations = []
            for g in grouped:
                combinations.append(
                    [c for c in itertools.combinations(g, int(len(g) / 2))])

            for selection in itertools.product(*combinations):
                species = [site.species_and_occu for site in fixed]
                fcoords = [site.frac_coords for site in fixed]

                for s in tomove:
                    species.append(s.species_and_occu)
                    for group in selection:
                        if s in group:
                            fcoords.append(s.frac_coords)
                            break
                    else:
                        # Move unselected atom to the opposite surface.
                        fcoords.append(s.frac_coords + [0, 0, shift])

                # sort by species to put all similar species together.
                sp_fcoord = sorted(zip(species, fcoords), key=lambda x: x[0])
                species = [x[0] for x in sp_fcoord]
                fcoords = [x[1] for x in sp_fcoord]
                slab = Slab(self.lattice, species, fcoords, self.miller_index,
                            self.oriented_unit_cell, self.shift,
                            self.scale_factor, energy=self.energy)
                slabs.append(slab)
        s = StructureMatcher()
        unique = [ss[0] for ss in s.group_structures(slabs)]
        return unique
Пример #41
0
    def apply_transformation(self, structure, return_ranked_list=False):
        """
        Apply MagOrderTransformation to an input structure.
        :param structure: Any ordered structure.
        :param return_ranked_list: As in other Transformations.
        :return:
        """

        if not structure.is_ordered:
            raise ValueError("Create an ordered approximation of "
                             "your  input structure first.")

        # retrieve order parameters
        order_parameters = [
            MagOrderParameterConstraint.from_dict(op_dict)
            for op_dict in self.order_parameter
        ]
        # add dummy species on which to perform enumeration
        structure = self._add_dummy_species(structure, order_parameters)

        # trivial case
        if structure.is_ordered:
            structure = self._remove_dummy_species(structure)
            return [structure] if return_ranked_list > 1 else structure

        enum_kwargs = self.enum_kwargs.copy()

        enum_kwargs["min_cell_size"] = max(
            int(self.determine_min_cell(structure)),
            enum_kwargs.get("min_cell_size", 1))

        max_cell = enum_kwargs.get('max_cell_size')
        if max_cell:
            if enum_kwargs["min_cell_size"] > max_cell:
                raise ValueError('Specified max cell size is smaller'
                                 ' than the minimum enumerable cell size')
        else:
            enum_kwargs["max_cell_size"] = enum_kwargs["min_cell_size"]

        t = EnumerateStructureTransformation(**enum_kwargs)

        alls = t.apply_transformation(structure,
                                      return_ranked_list=return_ranked_list)

        # handle the fact that EnumerateStructureTransformation can either
        # return a single Structure or a list
        if isinstance(alls, Structure):
            # remove dummy species and replace Spin.up or Spin.down
            # with spin magnitudes given in mag_species_spin arg
            alls = self._remove_dummy_species(alls)
            alls = self._add_spin_magnitudes(alls)
        else:
            for idx, _ in enumerate(alls):
                alls[idx]["structure"] = self._remove_dummy_species(
                    alls[idx]["structure"])
                alls[idx]["structure"] = self._add_spin_magnitudes(
                    alls[idx]["structure"])

        try:
            num_to_return = int(return_ranked_list)
        except ValueError:
            num_to_return = 1

        if num_to_return == 1 or not return_ranked_list:
            return alls[0]["structure"] if num_to_return else alls

        # remove duplicate structures and group according to energy model
        m = StructureMatcher(comparator=SpinComparator())
        key = lambda x: SpacegroupAnalyzer(x, 0.1).get_space_group_number()
        out = []
        for _, g in groupby(sorted([d["structure"] for d in alls], key=key),
                            key):
            g = list(g)
            grouped = m.group_structures(g)
            out.extend([{
                "structure": g[0],
                "energy": self.energy_model.get_energy(g[0])
            } for g in grouped])

        self._all_structures = sorted(out, key=lambda d: d["energy"])

        return self._all_structures[0:num_to_return]
Пример #42
0
    def analyze(self,
                structures=None,
                structure_ids=None,
                against_icsd=False,
                energies=None):
        """
        One encounter of a given structure will be labeled as True, its
        remaining matching structures as False.

        Args:
            structures (list): a list of structures to be compared.
            structure_ids (list): uids of structures, optional.
            against_icsd (bool): whether a comparison to icsd is also made.
            energies (list): list of energies (per atom) corresponding
                to structures. If given, the lowest energy instance of a
                given structure will be return as the unique one. Otherwise,
                there is no such guarantee. (optional)

        Returns:
            ([bool]) list of bools corresponding to the given list of
                structures corresponding to uniqueness
        """
        self.structures = structures
        self.structure_ids = structure_ids
        self.against_icsd = against_icsd
        self.energies = energies

        smatch = StructureMatcher()
        self.groups = smatch.group_structures(structures)
        self.structure_is_unique = []

        if self.energies:
            for i in range(len(self.groups)):
                self.groups[i] = [
                    x for _, x in sorted(
                        zip(
                            [
                                self.energies[self.structures.index(s)]
                                for s in self.groups[i]
                            ],
                            self.groups[i],
                        ))
                ]

        self._unique_structures = [i[0] for i in self.groups]
        for s in structures:
            if s in self._unique_structures:
                self.structure_is_unique.append(True)
            else:
                self.structure_is_unique.append(False)
        self._not_duplicate = self.structure_is_unique

        if self.against_icsd:
            structure_file = "oqmd1.2_exp_based_entries_structures.json"
            cache_matrio_data(structure_file)
            with open(os.path.join(CAMD_CACHE, structure_file), "r") as f:
                icsd_structures = json.load(f)
            chemsys = set()
            for s in self._unique_structures:
                chemsys = chemsys.union(set(s.composition.as_dict().keys()))

            self.icsd_structs_inchemsys = []
            for k, v in icsd_structures.items():
                try:
                    s = Structure.from_dict(v)
                    elems = set(s.composition.as_dict().keys())
                    if elems == chemsys:
                        self.icsd_structs_inchemsys.append(s)
                # TODO: can we make this exception more specific,
                #  do we have an example where this fails?
                except Exception as e:
                    warnings.warn("Unable to process structure {}".format(k))
                    warnings.warn("Error: {}".format(e))

            self.matching_icsd_strs = []
            for i in range(len(structures)):
                if self.structure_is_unique[i]:
                    match = None
                    for s2 in self.icsd_structs_inchemsys:
                        if smatch.fit(self.structures[i], s2):
                            match = s2
                            break
                    self.matching_icsd_strs.append(
                        match)  # store the matching ICSD structures.
                else:
                    self.matching_icsd_strs.append(None)

            # Flip matching bools, and create a filter
            self._icsd_filter = [not i for i in self.matching_icsd_strs]
            self.structure_is_unique = (np.array(self.structure_is_unique) *
                                        np.array(self._icsd_filter)).tolist()
            self.unique_structures = list(
                itertools.compress(self.structures, self.structure_is_unique))
        else:
            self.unique_structures = self._unique_structures

        # We store the final list of unique structures as unique_structures.
        # We return a corresponding list of bool to the initial structure
        # list provided.
        return self.structure_is_unique
Пример #43
0
    def generate_substitution_structures(self, atom, target_species=[],
                                         sub_both_sides=False, range_tol=1e-2,
                                         dist_from_surf=0):
        """
        Function that performs substitution-type doping on the surface and
            returns all possible configurations where one dopant is substituted
            per surface. Can substitute one surface or both.

        Args:
            atom (str): atom corresponding to substitutional dopant
            sub_both_sides (bool): If true, substitute an equivalent
                site on the other surface
            target_species (list): List of specific species to substitute
            range_tol (float): Find viable substitution sites at a specific
                distance from the surface +- this tolerance
            dist_from_surf (float): Distance from the surface to find viable
                substitution sites, defaults to 0 to substitute at the surface
        """

        # Get symmetrized structure in case we want to substitue both sides
        sym_slab = SpacegroupAnalyzer(self.slab).get_symmetrized_structure()

        # Define a function for substituting a site
        def substitute(site, i):
            slab = self.slab.copy()
            props = self.slab.site_properties
            if sub_both_sides:
                # Find an equivalent site on the other surface
                eq_indices = [indices for indices in
                              sym_slab.equivalent_indices if i in indices][0]
                for ii in eq_indices:
                    if "%.6f" % (sym_slab[ii].frac_coords[2]) != \
                                    "%.6f" % (site.frac_coords[2]):
                        props["surface_properties"][ii] = "substitute"
                        slab.replace(ii, atom)
                        break

            props["surface_properties"][i] = "substitute"
            slab.replace(i, atom)
            slab.add_site_property("surface_properties",
                                   props["surface_properties"])
            return slab

        # Get all possible substitution sites
        substituted_slabs = []
        # Sort sites so that we can define a range relative to the position of the
        # surface atoms, i.e. search for sites above (below) the bottom (top) surface
        sorted_sites = sorted(sym_slab, key=lambda site: site.frac_coords[2])
        if sorted_sites[0].surface_properties == "surface":
            d = sorted_sites[0].frac_coords[2] + dist_from_surf
        else:
            d = sorted_sites[-1].frac_coords[2] - dist_from_surf

        for i, site in enumerate(sym_slab):
            if d - range_tol < site.frac_coords[2] < d + range_tol:
                if target_species and site.species_string in target_species:
                    substituted_slabs.append(substitute(site, i))
                elif not target_species:
                    substituted_slabs.append(substitute(site, i))

        matcher = StructureMatcher()
        return [s[0] for s in matcher.group_structures(substituted_slabs)]
Пример #44
0
    def adsorb_both_surfaces(self, molecule, repeat=None, min_lw=5.0,
                             reorient=True, find_args={}, ltol=0.1,
                             stol=0.1, angle_tol=0.01):

        """
        Function that generates all adsorption structures for a given
        molecular adsorbate on both surfaces of a slab.

        Args:
            molecule (Molecule): molecule corresponding to adsorbate
            repeat (3-tuple or list): repeat argument for supercell generation
            min_lw (float): minimum length and width of the slab, only used
                if repeat is None
            reorient (bool): flag on whether or not to reorient adsorbate
                along the miller index
            find_args (dict): dictionary of arguments to be passed to the
                call to self.find_adsorption_sites, e.g. {"distance":2.0}
            ltol (float): Fractional length tolerance. Default is 0.2.
            stol (float): Site tolerance. Defined as the fraction of the
                average free length per atom := ( V / Nsites ) ** (1/3)
                Default is 0.3.
            angle_tol (float): Angle tolerance in degrees. Default is 5 degrees.
        """

        # First get all possible adsorption configurations for this surface
        adslabs = self.generate_adsorption_structures(molecule, repeat=repeat, min_lw=min_lw,
                                                      reorient=reorient, find_args=find_args)

        # Now we need to sort the sites by their position along
        # c as well as whether or not they are adsorbate
        single_ads = []
        for i, slab in enumerate(adslabs):
            sorted_sites = sorted(slab, key=lambda site: site.frac_coords[2])
            ads_indices = [site_index for site_index, site in enumerate(sorted_sites) \
                           if site.surface_properties == "adsorbate"]
            non_ads_indices = [site_index for site_index, site in enumerate(sorted_sites) \
                               if site.surface_properties != "adsorbate"]

            species, fcoords, props = [], [], {"surface_properties": []}
            for site in sorted_sites:
                species.append(site.specie)
                fcoords.append(site.frac_coords)
                props["surface_properties"].append(site.surface_properties)

            slab_other_side = Structure(slab.lattice, species,
                                        fcoords, site_properties=props)

            # For each adsorbate, get its distance from the surface and move it
            # to the other side with the same distance from the other surface
            for ads_index in ads_indices:
                props["surface_properties"].append("adsorbate")
                adsite = sorted_sites[ads_index]
                diff = abs(adsite.frac_coords[2] - \
                           sorted_sites[non_ads_indices[-1]].frac_coords[2])
                slab_other_side.append(adsite.specie, [adsite.frac_coords[0],
                                                       adsite.frac_coords[1],
                                                       sorted_sites[0].frac_coords[2] - diff],
                                       properties={"surface_properties": "adsorbate"})
                # slab_other_side[-1].add

            # Remove the adsorbates on the original side of the slab
            # to create a slab with one adsorbate on the other side
            slab_other_side.remove_sites(ads_indices)

            # Put both slabs with adsorption on one side
            # and the other in a list of slabs for grouping
            single_ads.extend([slab, slab_other_side])

        # Now group the slabs.
        matcher = StructureMatcher(ltol=ltol, stol=stol,
                                   angle_tol=angle_tol)
        groups = matcher.group_structures(single_ads)

        # Each group should be a pair with adsorbate on one side and the other.
        # If a slab has no equivalent adsorbed slab on the other side, skip.
        adsorb_both_sides = []
        for i, group in enumerate(groups):
            if len(group) != 2:
                continue
            ads1 = [site for site in group[0] if \
                    site.surface_properties == "adsorbate"][0]
            group[1].append(ads1.specie, ads1.frac_coords,
                            properties={"surface_properties": "adsorbate"})
            # Build the slab object
            species, fcoords, props = [], [], {"surface_properties": []}
            for site in group[1]:
                species.append(site.specie)
                fcoords.append(site.frac_coords)
                props["surface_properties"].append(site.surface_properties)
            s = Structure(group[1].lattice, species, fcoords, site_properties=props)
            adsorb_both_sides.append(s)

        return adsorb_both_sides
Пример #45
0
def adsorb_both_surfaces(slab, molecule, selective_dynamics=False,
                         height=0.9, mi_vec=None, repeat=None,
                         min_lw=5.0, reorient=True, find_args={}):
    """
    Function that generates all adsorption structures for a given
    molecular adsorbate on both surfaces of a slab.

    Args:
        slab (Slab): slab object for which to find adsorbate sites
        selective_dynamics (bool): flag for whether to assign
            non-surface sites as fixed for selective dynamics
        molecule (Molecule): molecule corresponding to adsorbate
        selective_dynamics (bool): flag for whether to assign
            non-surface sites as fixed for selective dynamics
        height (float): height criteria for selection of surface sites
        mi_vec (3-D array-like): vector corresponding to the vector
            concurrent with the miller index, this enables use with
            slabs that have been reoriented, but the miller vector
            must be supplied manually
        repeat (3-tuple or list): repeat argument for supercell generation
        min_lw (float): minimum length and width of the slab, only used
            if repeat is None
        reorient (bool): flag on whether or not to reorient adsorbate
            along the miller index
        find_args (dict): dictionary of arguments to be passed to the
            call to self.find_adsorption_sites, e.g. {"distance":2.0}
    """

    matcher = StructureMatcher()

    # Get adsorption on top
    adsgen_top = AdsorbateSiteFinder(slab, selective_dynamics=selective_dynamics,
                                     height=height, mi_vec=mi_vec, top_surface=True)
    structs = adsgen_top.generate_adsorption_structures(molecule, repeat=repeat,
                                                        min_lw=min_lw, reorient=reorient,
                                                        find_args=find_args)
    adslabs = [g[0] for g in matcher.group_structures(structs)]
    # Get adsorption on bottom
    adsgen_bottom = AdsorbateSiteFinder(slab, selective_dynamics=selective_dynamics,
                                        height=height, mi_vec=mi_vec, top_surface=False)
    structs = adsgen_bottom.generate_adsorption_structures(molecule, repeat=repeat,
                                                           min_lw=min_lw, reorient=reorient,
                                                           find_args=find_args)
    adslabs.extend([g[0] for g in matcher.group_structures(structs)])

    # Group symmetrically similar slabs
    adsorbed_slabs = []
    for group in matcher.group_structures(adslabs):
        # Further group each group by which surface adsorbed
        top_ads, bottom_ads = [], []
        for s in group:
            sites = sorted(s, key=lambda site: site.frac_coords[2])
            if sites[0].surface_properties == "adsorbate":
                bottom_ads.append(s)
            else:
                top_ads.append(s)
        if not top_ads or not bottom_ads:
            warnings.warn("There are not enough sites at the bottom or "
                          "top to generate a symmetric adsorbed slab")
            continue

        # Combine the adsorbates of both top and bottom slabs
        # into one slab with one adsorbate on each side
        coords = list(top_ads[0].frac_coords)
        species = top_ads[0].species
        lattice = top_ads[0].lattice
        coords.extend([site.frac_coords for site in bottom_ads[0]
                       if site.surface_properties == "adsorbate"])
        species.extend([site.specie for site in bottom_ads[0]
                        if site.surface_properties == "adsorbate"])

        slab = Slab(lattice, species, coords, slab.miller_index,
                    slab.oriented_unit_cell, slab.shift, slab.scale_factor)
        adsorbed_slabs.append(slab)

    return adsorbed_slabs