def setUp(self):
        latt = Lattice.cubic(4.17)
        species = ["Ni", "O"]
        coords = [[0, 0, 0], [0.5, 0.5, 0.5]]
        self.NiO = Structure.from_spacegroup(225, latt, species, coords)

        latt = Lattice([[2.085, 2.085, 0.0], [0.0, -2.085, -2.085], [-2.085, 2.085, -4.17]])
        species = ["Ni", "Ni", "O", "O"]
        coords = [[0.5, 0, 0.5], [0, 0, 0], [0.25, 0.5, 0.25], [0.75, 0.5, 0.75]]
        self.NiO_AFM_111 = Structure(latt, species, coords)
        self.NiO_AFM_111.add_spin_by_site([-5, 5, 0, 0])

        latt = Lattice([[2.085, 2.085, 0], [0, 0, -4.17], [-2.085, 2.085, 0]])
        species = ["Ni", "Ni", "O", "O"]
        coords = [[0.5, 0.5, 0.5], [0, 0, 0], [0, 0.5, 0], [0.5, 0, 0.5]]
        self.NiO_AFM_001 = Structure(latt, species, coords)
        self.NiO_AFM_001.add_spin_by_site([-5, 5, 0, 0])

        parser = CifParser(os.path.join(PymatgenTest.TEST_FILES_DIR, "Fe3O4.cif"))
        self.Fe3O4 = parser.get_structures()[0]
        trans = AutoOxiStateDecorationTransformation()
        self.Fe3O4_oxi = trans.apply_transformation(self.Fe3O4)

        parser = CifParser(os.path.join(PymatgenTest.TEST_FILES_DIR, "Li8Fe2NiCoO8.cif"))
        self.Li8Fe2NiCoO8 = parser.get_structures()[0]
        self.Li8Fe2NiCoO8.remove_oxidation_states()
        warnings.simplefilter("ignore")
Beispiel #2
0
    def substitution_structures(self, mp_struct, mpid):
        
        ''' Predicts substituted compounds from a given Materials Project structure (mp_struct) and ID (mpid) '''
        ''' flat_predictions: a list of predicted structures as pymatgen.alchemy.materials.TransformedStructure objects '''
        
        auto_oxi = AutoOxiStateDecorationTransformation() #initialize the automatic oxidation state transformation
        structures = []
        
        try:
            oxi_struct = auto_oxi.apply_transformation(mp_struct) # get oxidized reference structure
            subs = SubstitutionPredictor().composition_prediction(oxi_struct.composition) # from a given charge-balanced
            trial_subs = [list(sub['substitutions'].keys()) for sub in subs] # returns the potential substitutions
            sbr = Substitutor()
            
            for sub in trial_subs:
                run = True
                
                for specie in sub:
                    if str(specie.element) not in self.elements:
                        run = False

                if run == True:
                    structs = sbr.pred_from_structures(sub, [{'structure':oxi_struct, 'id':mpid}])
                    structures.append(structs)
           
        except:
            pass
        
        flat_predictions = [structure for substitution in structures for structure in substitution]
        
        return flat_predictions
Beispiel #3
0
    def __init__(self,
                 structure_templates,
                 requests,
                 crystals,
                 query=None,
                 **kwargs):
        """
        Predict structures given a list of elements and their oxidation states.

        Args:
            structure_templates (Store): store of template structures to predict from
            requests (Store): store of structure prediction requests
            crystals (Store): predicted crystal structures and their info (XRD, spacegroup, etc.)
        """

        self.structure_templates = structure_templates
        self.requests = requests
        self.crystals = crystals
        self.query = query if query else {}
        self.kwargs = kwargs
        self.auto_oxi = AutoOxiStateDecorationTransformation()

        super().__init__(
            sources=[structure_templates, requests],
            targets=[requests, crystals],
            **kwargs,
        )
Beispiel #4
0
    def _evaluate(self, symbol_values):

        s = symbol_values['s']

        trans = AutoOxiStateDecorationTransformation()
        s_oxi = trans.apply_transformation(s)

        return {'s_oxi': s_oxi}
Beispiel #5
0
 def test_apply_transformation(self):
     p = Poscar.from_file(os.path.join(PymatgenTest.TEST_FILES_DIR,
                                       "POSCAR.LiFePO4"),
                          check_for_POTCAR=False)
     t = AutoOxiStateDecorationTransformation()
     s = t.apply_transformation(p.structure)
     expected_oxi = {"Li": 1, "P": 5, "O": -2, "Fe": 2}
     for site in s:
         self.assertEqual(site.specie.oxi_state,
                          expected_oxi[site.specie.symbol])
Beispiel #6
0
def create_crystal(cifpath, primitive=False):
    """ Convert cif to pymatgen struture """
    structure = CifParser(cifpath).get_structures(primitive=primitive)[0]
    if structure.is_ordered:
        return structure
    order_transformer = OrderDisorderedStructureTransformation()
    oxid_transformer = AutoOxiStateDecorationTransformation()
    a = oxid_transformer.apply_transformation(structure)
    b = order_transformer.apply_transformation(a)
    return b
def get_Species_Substitution_Order(structure, dummies, species, to_ignore):

    els_removed = species.copy()
    for element in to_ignore:
        els_removed.remove(Element(element))

    subs = [list(p) for p in permutations(els_removed)]
    diff_coords = []
    struct_copies = []
    for sub in subs:
        copy = structure.copy()
        for dummy_ind in range(len(dummies)):
            copy[dummies[dummy_ind].symbol] = sub[dummy_ind]
        struct_copies.append(copy)
        coordination_numbers = []
        CN = CrystalNN(weighted_cn=True)
        for element_ind in range(len(copy.species)):
            cn = CN.get_cn(copy, element_ind, use_weights=True)
            coordination_numbers.append((copy.species[element_ind], cn))
        diff_coords.append(coordination_numbers)

    unique_cn_els = []
    for cn in diff_coords:
        cns = []
        for i in range(len(cn)):
            if cn[i] not in cns:
                cns.append(cn[i])
        unique_cn_els.append(cns)

    substitution_ind = None
    check_len = 300  # arbitrarily high number to start, used to get substitution scheme with lowest number of unique sites
    for unique_cn_el_ind in range(len(unique_cn_els)):
        if len(unique_cn_els[unique_cn_el_ind]) < check_len:
            substitution_ind = unique_cn_el_ind
            check_len = len(unique_cn_els[unique_cn_el_ind])
        else:
            continue

    ox_transform = AutoOxiStateDecorationTransformation()
    ox_species = ox_transform.apply_transformation(
        struct_copies[substitution_ind]).types_of_specie
    for specie in ox_species:
        for element in to_ignore:
            if specie.symbol == Element(element).symbol:
                ox_species.remove(specie)

    return subs[substitution_ind], ox_species
    def setUp(self):

        latt = Lattice.cubic(4.17)
        species = ["Ni", "O"]
        coords = [[0, 0, 0],
                  [0.5, 0.5, 0.5]]
        self.NiO = Structure.from_spacegroup(225, latt, species, coords)

        latt = Lattice([[2.085, 2.085, 0.0],
                        [0.0, -2.085, -2.085],
                        [-2.085, 2.085, -4.17]])
        species = ["Ni", "Ni", "O", "O"]
        coords = [[0.5, 0, 0.5],
                  [0, 0, 0],
                  [0.25, 0.5, 0.25],
                  [0.75, 0.5, 0.75]]
        self.NiO_AFM_111 = Structure(latt, species, coords)
        self.NiO_AFM_111.add_spin_by_site([-5, 5, 0, 0])

        latt = Lattice([[2.085, 2.085, 0],
                        [0, 0, -4.17],
                        [-2.085, 2.085, 0]])
        species = ["Ni", "Ni", "O", "O"]
        coords = [[0.5, 0.5, 0.5],
                  [0, 0, 0],
                  [0, 0.5, 0],
                  [0.5, 0, 0.5]]
        self.NiO_AFM_001 = Structure(latt, species, coords)
        self.NiO_AFM_001.add_spin_by_site([-5, 5, 0, 0])

        parser = CifParser(os.path.join(test_dir, 'Fe3O4.cif'))
        self.Fe3O4 = parser.get_structures()[0]
        trans = AutoOxiStateDecorationTransformation()
        self.Fe3O4_oxi = trans.apply_transformation(self.Fe3O4)

        parser = CifParser(os.path.join(test_dir, 'Li8Fe2NiCoO8.cif'))
        self.Li8Fe2NiCoO8 = parser.get_structures()[0]
        self.Li8Fe2NiCoO8.remove_oxidation_states()
        warnings.simplefilter("ignore")
Beispiel #9
0
# j = st.SiteCollection.get_distance(0,4)
# j = st.lattice_vectors
# print(j)
# list = pymatgen.structure_prediction.pred_from_structures(st)
# print(dir(sp))
# print(sp.__path__)
# print(sp.__doc__)
# print(dir(pymatgen.core.structure))
# print(dir(stt.SiteCollection))

# print(sp.__builtins__)
# print(dir(test))
# i = test.SubstitutionPredictor(st.composition)
Subs = substitutor.Substitutor()
# Subs._threshold = 0.001 # Any threshold beyond 0.001 brings it out of the default hence it doesn't work: has to figure out how to explicitly set the threshold probability
struc = AutoOxiStateDecorationTransformation().apply_transformation(st)
spgr = struc.get_spacegroup_info()
print(struc.get_spacegroup_info())

# print(struc)
cations = ['Co', 'Rh', 'Ir']
#cations = ['Li', 'Na', 'K', 'Rb', 'Cs']
# cations2 = ['C','Si','Ge','Sn', 'Pb']
anions = ['F', 'Cl', 'Br', 'I']
counter = 0
for cation in cations:
    # for cation2 in cations2:
    for anion in anions:
        try:
            # list = Subs.pred_from_structures(target_species=[Specie(cation, +1), Specie(cation2, +2), Specie(anion, -1)], structures_list=[{'structure': struc, 'id': 'TEST'}])
            list = Subs.pred_from_structures(
    def __init__(
        self,
        structure: Structure,
        overwrite_magmom_mode: Union[OverwriteMagmomMode, str] = "none",
        round_magmoms: bool = False,
        detect_valences: bool = False,
        make_primitive: bool = True,
        default_magmoms: bool = None,
        set_net_positive: bool = True,
        threshold: float = 0.1,
    ):
        """
        A class which provides a few helpful methods to analyze
        collinear magnetic structures.
        If magnetic moments are not defined, moments will be
        taken either from default_magmoms.yaml (similar to the
        default magmoms in MPRelaxSet, with a few extra definitions)
        or from a specie:magmom dict provided by the default_magmoms
        kwarg.
        Input magmoms can be replaced using the 'overwrite_magmom_mode'
        kwarg. This can be:
        * "none" to do nothing,
        * "respect_sign" which will overwrite existing magmoms with
          those from default_magmoms but will keep sites with positive magmoms
          positive, negative magmoms negative and zero magmoms zero,
        * "respect_zeros", which will give a ferromagnetic structure
          (all positive magmoms from default_magmoms) but still keep sites with
          zero magmoms as zero,
        * "replace_all" which will try to guess initial magmoms for
          all sites in the structure irrespective of input structure
          (this is most suitable for an initial DFT calculation),
        * "replace_all_if_undefined" is the same as "replace_all" but only if
          no magmoms are defined in input structure, otherwise it will respect
          existing magmoms.
        * "normalize" will normalize magmoms to unity, but will respect sign
          (used for comparing orderings), magmoms < theshold will be set to zero
        :param structure: Structure object
        :param overwrite_magmom_mode (str): default "none"
        :param round_magmoms (int or bool): will round input magmoms to
        specified number of decimal places if integer is supplied, if set
        to a float will try and group magmoms together using a kernel density
        estimator of provided width, and extracting peaks of the estimator
        :param detect_valences (bool): if True, will attempt to assign valences
        to input structure
        :param make_primitive (bool): if True, will transform to primitive
        magnetic cell
        :param default_magmoms (dict): (optional) dict specifying default magmoms
        :param set_net_positive (bool): if True, will change sign of magnetic
        moments such that the net magnetization is positive. Argument will be
        ignored if mode "respect_sign" is used.
        :param threshold (float): number (in Bohr magnetons) below which magmoms
        will be rounded to zero, default of 0.1 can probably be increased for many
        magnetic systems, depending on your application
        """

        if default_magmoms:
            self.default_magmoms = default_magmoms
        else:
            self.default_magmoms = DEFAULT_MAGMOMS

        structure = structure.copy()

        # check for disorder
        if not structure.is_ordered:
            raise NotImplementedError(
                "Not implemented for disordered structures, "
                "make ordered approximation first.")

        if detect_valences:
            trans = AutoOxiStateDecorationTransformation()
            bva = BVAnalyzer()
            try:
                structure = trans.apply_transformation(structure)
            except ValueError:
                warnings.warn("Could not assign valences "
                              "for {}".format(
                                  structure.composition.reduced_formula))

        # check to see if structure has magnetic moments
        # on site properties or species spin properties,
        # prioritize site properties

        has_magmoms = bool(structure.site_properties.get("magmom", False))

        has_spin = False
        for comp in structure.species_and_occu:
            for sp, occu in comp.items():
                if getattr(sp, "spin", False):
                    has_spin = True

        # perform input sanitation ...
        # rest of class will assume magnetic moments
        # are stored on site properties:
        # this is somewhat arbitrary, arguments can
        # be made for both approaches

        if has_magmoms and has_spin:
            raise ValueError("Structure contains magnetic moments on both "
                             "magmom site properties and spin species "
                             "properties. This is ambiguous. Remove one or "
                             "the other.")
        elif has_magmoms:
            if None in structure.site_properties["magmom"]:
                warnings.warn("Be careful with mixing types in your magmom "
                              "site properties. Any 'None' magmoms have been "
                              "replaced with zero.")
            magmoms = [
                m if m else 0 for m in structure.site_properties["magmom"]
            ]
        elif has_spin:
            magmoms = [getattr(sp, "spin", 0) for sp in structure.species]
            structure.remove_spin()
        else:
            # no magmoms present, add zero magmoms for now
            magmoms = [0] * len(structure)
            # and overwrite magmoms with default magmoms later unless otherwise stated
            if overwrite_magmom_mode == "replace_all_if_undefined":
                overwrite_magmom_mode = "replace_all"

        # test to see if input structure has collinear magmoms
        self.is_collinear = Magmom.are_collinear(magmoms)

        if not self.is_collinear:
            warnings.warn(
                "This class is not designed to be used with "
                "non-collinear structures. If your structure is "
                "only slightly non-collinear (e.g. canted) may still "
                "give useful results, but use with caution.")

        # this is for collinear structures only, make sure magmoms
        # are all floats
        magmoms = list(map(float, magmoms))

        # set properties that should be done /before/ we process input magmoms
        self.total_magmoms = sum(magmoms)
        self.magnetization = sum(magmoms) / structure.volume

        # round magmoms below threshold to zero
        magmoms = [m if abs(m) > threshold else 0 for m in magmoms]

        # overwrite existing magmoms with default_magmoms
        if overwrite_magmom_mode not in (
                "none",
                "respect_sign",
                "respect_zeros",
                "replace_all",
                "replace_all_if_undefined",
                "normalize",
        ):
            raise ValueError("Unsupported mode.")

        for idx, site in enumerate(structure):

            if site.species_string in self.default_magmoms:
                # look for species first, e.g. Fe2+
                default_magmom = self.default_magmoms[site.species_string]
            elif (isinstance(site.specie, Specie)
                  and str(site.specie.element) in self.default_magmoms):
                # look for element, e.g. Fe
                default_magmom = self.default_magmoms[str(site.specie.element)]
            else:
                default_magmom = 0

            # overwrite_magmom_mode = "respect_sign" will change magnitude of
            # existing moments only, and keep zero magmoms as
            # zero: it will keep the magnetic ordering intact

            if overwrite_magmom_mode == "respect_sign":
                set_net_positive = False
                if magmoms[idx] > 0:
                    magmoms[idx] = default_magmom
                elif magmoms[idx] < 0:
                    magmoms[idx] = -default_magmom

            # overwrite_magmom_mode = "respect_zeros" will give a ferromagnetic
            # structure but will keep zero magmoms as zero

            elif overwrite_magmom_mode == "respect_zeros":
                if magmoms[idx] != 0:
                    magmoms[idx] = default_magmom

            # overwrite_magmom_mode = "replace_all" will ignore input magmoms
            # and give a ferromagnetic structure with magnetic
            # moments on *all* atoms it thinks could be magnetic

            elif overwrite_magmom_mode == "replace_all":
                magmoms[idx] = default_magmom

            # overwrite_magmom_mode = "normalize" set magmoms magnitude to 1

            elif overwrite_magmom_mode == "normalize":
                if magmoms[idx] != 0:
                    magmoms[idx] = int(magmoms[idx] / abs(magmoms[idx]))

        # round magmoms, used to smooth out computational data
        magmoms = (self._round_magmoms(magmoms, round_magmoms)
                   if round_magmoms else magmoms)

        if set_net_positive:
            sign = np.sum(magmoms)
            if sign < 0:
                magmoms = -np.array(magmoms)

        structure.add_site_property("magmom", magmoms)

        if make_primitive:
            structure = structure.get_primitive_structure(use_site_props=True)

        self.structure = structure
Beispiel #11
0
    def __init__(self, structure,
                 overwrite_magmom_mode="none",
                 round_magmoms=False,
                 detect_valences=False,
                 make_primitive=True,
                 default_magmoms=None,
                 threshold=0.1):
        """
        A class which provides a few helpful methods to analyze
        collinear magnetic structures.

        If magnetic moments are not defined, moments will be
        taken either from default_magmoms.yaml (similar to the
        default magmoms in MPRelaxSet, with a few extra definitions)
        or from a specie:magmom dict provided by the default_magmoms
        kwarg.

        Input magmoms can be replaced using the 'overwrite_magmom_mode'
        kwarg. This can be:
        * "none" to do nothing,
        * "respect_sign" which will overwrite existing magmoms with
          those from default_magmoms but will keep sites with positive magmoms
          positive, negative magmoms negative and zero magmoms zero,
        * "respect_zeros", which will give a ferromagnetic structure
          (all positive magmoms from default_magmoms) but still keep sites with
          zero magmoms as zero,
        * "replace_all" which will try to guess initial magmoms for
          all sites in the structure irrespective of input structure
          (this is most suitable for an initial DFT calculation),
        * "replace_all_if_undefined" is the same as "replace_all" but only if
          no magmoms are defined in input structure, otherwise it will respect
          existing magmoms.

        :param structure: Structure object
        :param overwrite_magmom_mode (str): default "none"
        :param round_magmoms (int): will round input magmoms to
        specified number of decimal places, suggest value of 1 or False
        for typical DFT calculations depending on application
        :param detect_valences (bool): if True, will attempt to assign valences
        to input structure
        :param make_primitive (bool): if True, will transform to primitive
        magnetic cell
        :param default_magmoms (dict): (optional) dict specifying default magmoms
        :param threshold (float): number (in Bohr magnetons) below which magmoms
        will be rounded to zero, default of 0.1 can probably be increased for many
        magnetic systems, depending on your application
        """

        if default_magmoms:
            self.default_magmoms = default_magmoms
        else:
            self.default_magmoms = DEFAULT_MAGMOMS

        structure = structure.copy()

        # check for disorder
        if not structure.is_ordered:
            raise NotImplementedError("Not implemented for disordered structures, "
                                      "make ordered approximation first.")

        if detect_valences:
            trans = AutoOxiStateDecorationTransformation()
            bva = BVAnalyzer()
            try:
                structure = trans.apply_transformation(structure)
            except ValueError:
                warnings.warn("Could not assign valences "
                              "for {}".format(structure.composition.reduced_formula))

        # check to see if structure has magnetic moments
        # on site properties or species spin properties,
        # prioritize site properties

        has_magmoms = bool(structure.site_properties.get('magmom', False))

        has_spin = False
        for comp in structure.species_and_occu:
            for sp, occu in comp.items():
                if getattr(sp, 'spin', False):
                    has_spin = True

        # perform input sanitation ...
        # rest of class will assume magnetic moments
        # are stored on site properties:
        # this is somewhat arbitrary, arguments can
        # be made for both approaches

        if has_magmoms and has_spin:
            raise ValueError("Structure contains magnetic moments on both "
                             "magmom site properties and spin species "
                             "properties. This is ambiguous. Remove one or "
                             "the other.")
        elif has_magmoms:
            if None in structure.site_properties['magmom']:
                warnings.warn("Be careful with mixing types in your magmom "
                              "site properties. Any 'None' magmoms have been "
                              "replaced with zero.")
            magmoms = [m if m else 0 for m in structure.site_properties['magmom']]
        elif has_spin:
            magmoms = [getattr(sp, 'spin', 0) for sp
                       in structure.species]
            structure.remove_spin()
        else:
            # no magmoms present, add zero magmoms for now
            magmoms = [0]*len(structure)
            # and overwrite magmoms with default magmoms later unless otherwise stated
            if overwrite_magmom_mode == "replace_all_if_undefined":
                overwrite_magmom_mode = "replace_all"

        # test to see if input structure has collinear magmoms
        self.is_collinear = Magmom.are_collinear(magmoms)

        if not self.is_collinear:
            warnings.warn("This class is not designed to be used with "
                          "non-collinear structures. If your structure is "
                          "only slightly non-collinear (e.g. canted) may still "
                          "give useful results, but use with caution.")

        # this is for collinear structures only, make sure magmoms
        # are all floats
        magmoms = list(map(float, magmoms))

        # set properties that should be done /before/ we process input magmoms
        self.total_magmoms = sum(magmoms)
        self.magnetization = sum(magmoms)/structure.volume

        # round magmoms below threshold to zero
        magmoms = [m if abs(m) > threshold else 0 for m in magmoms]

        # overwrite existing magmoms with default_magmoms
        if overwrite_magmom_mode not in ("none", "respect_sign",
                                         "respect_zeros", "replace_all",
                                         "replace_all_if_undefined"):
            raise ValueError("Unsupported mode.")

        for idx, site in enumerate(structure):

            if site.species_string in self.default_magmoms:
                # look for species first, e.g. Fe2+
                default_magmom = self.default_magmoms[site.species_string]
            elif isinstance(site.specie, Specie) and \
                    str(site.specie.element) in self.default_magmoms:
                # look for element, e.g. Fe
                default_magmom = self.default_magmoms[str(site.specie.element)]
            else:
                default_magmom = 0

            # overwrite_magmom_mode = "respect_sign" will change magnitude of
            # existing moments only, and keep zero magmoms as
            # zero: it will keep the magnetic ordering intact

            if overwrite_magmom_mode == "respect_sign":
                if magmoms[idx] > 0:
                    magmoms[idx] = default_magmom
                elif magmoms[idx] < 0:
                    magmoms[idx] = -default_magmom

            # overwrite_magmom_mode = "respect_zeros" will give a ferromagnetic
            # structure but will keep zero magmoms as zero

            elif overwrite_magmom_mode == "respect_zeros":
                if magmoms[idx] != 0:
                    magmoms[idx] = default_magmom

            # overwrite_magmom_mode = "replace_all" will ignore input magmoms
            # and give a ferromagnetic structure with magnetic
            # moments on *all* atoms it thinks could be magnetic

            elif overwrite_magmom_mode == "replace_all":
                magmoms[idx] = default_magmom

        # round magmoms to specified number of
        # decimal places, used to smooth out
        # computational data
        # TODO: be a bit smarter about rounding magmoms!
        if round_magmoms:
            magmoms = np.around(structure.site_properties['magmom'],
                                decimals=round_magmoms)
            structure.add_site_property(magmoms)

        structure.add_site_property('magmom', magmoms)

        if make_primitive:
            structure = structure.get_primitive_structure(use_site_props=True)

        self.structure = structure
Beispiel #12
0
 def test_as_from_dict(self):
     t = AutoOxiStateDecorationTransformation()
     d = t.as_dict()
     t = AutoOxiStateDecorationTransformation.from_dict(d)
     self.assertEqual(t.analyzer.dist_scale_factor, 1.015)
Beispiel #13
0
    def _generate_radii(self):

        structure = self.structure_graph.structure

        # don't calculate radius if one is explicitly supplied
        if 'display_radius' in structure.site_properties:
            return

        if self.radius_strategy is 'bvanalyzer_ionic':

            trans = AutoOxiStateDecorationTransformation()
            try:
                structure = trans.apply_transformation(
                    self.structure_graph.structure)
            except:
                # if we can't assign valences use average ionic
                self.radius_strategy = 'average_ionic'

        radii = []
        for site_idx, site in enumerate(structure):

            site_radii = []

            for comp_idx, (sp,
                           occu) in enumerate(site.species_and_occu.items()):

                radius = None

                if self.radius_strategy not in self.available_radius_strategies:
                    raise ValueError(
                        "Unknown radius strategy {}, choose from: {}".format(
                            self.radius_strategy,
                            self.available_radius_strategies))

                if self.radius_strategy is 'atomic':
                    radius = sp.atomic_radius
                elif self.radius_strategy is 'bvanalyzer_ionic' and isinstance(
                        sp, Specie):
                    radius = sp.ionic_radius
                elif self.radius_strategy is 'average_ionic':
                    radius = sp.average_ionic_radius
                elif self.radius_strategy is 'covalent':
                    el = str(getattr(sp, 'element', sp))
                    radius = CovalentRadius.radius[el]
                elif self.radius_strategy is 'van_der_waals':
                    radius = sp.van_der_waals_radius
                elif self.radius_strategy is 'atomic_calculated':
                    radius = sp.atomic_radius_calculated

                if not radius:
                    warnings.warn('Radius unknown for {} and strategy {}, '
                                  'setting to 1.0.'.format(
                                      sp, self.radius_strategy))
                    radius = 1.0

                site_radii.append(radius)

            radii.append(site_radii)

        self.structure_graph.structure.add_site_property(
            'display_radius', radii)
Beispiel #14
0
class StructurePredictionBuilder(Builder):
    def __init__(self,
                 structure_templates,
                 requests,
                 crystals,
                 query=None,
                 **kwargs):
        """
        Predict structures given a list of elements and their oxidation states.

        Args:
            structure_templates (Store): store of template structures to predict from
            requests (Store): store of structure prediction requests
            crystals (Store): predicted crystal structures and their info (XRD, spacegroup, etc.)
        """

        self.structure_templates = structure_templates
        self.requests = requests
        self.crystals = crystals
        self.query = query if query else {}
        self.kwargs = kwargs
        self.auto_oxi = AutoOxiStateDecorationTransformation()

        super().__init__(
            sources=[structure_templates, requests],
            targets=[requests, crystals],
            **kwargs,
        )

    def get_items(self):
        """
        Gets all structure predictions ready to run

        Returns:
            Generator of request and relevant structure templates to run structure prediction tasks
        """
        self.logger.info("Structure Prediction Builder started")

        requests = self.requests.query(criteria={"state": "READY"})

        for request in requests:
            elements = request["elements"]
            oxi_states = request["element_oxidation_states"]
            threshold = request["threshold"]
            max_num_subs = request["max_num_subs"]

            original_species = [
                Specie(e, o) for e, o in zip(elements, map(int, oxi_states))
            ]

            request["original_species"] = original_species

            all_chemsys_to_consider = list(
                self.find_all_chemsys(original_species, threshold,
                                      max_num_subs))
            self.logger.info(
                f"Considering the following chemical systems: {all_chemsys_to_consider}"
            )

            templates = [
                struct for struct in self.structure_templates.query(
                    {"chemsys": {
                        "$in": all_chemsys_to_consider
                    }})
            ]

            self.logger.info(
                f"Acquired {len(templates)} structure templates for {original_species}"
            )

            yield {"request": request, "templates": templates}

    def process_item(self, item):
        """
        Finds all predicted structures for given item

        Args:
            item (dict): structure prediction request and relevant oxidation state-labeled structure templates

        Returns:
            (dict, dict): A tuple containing updated request doc and a list of crystal docs to update
        """
        request = item["request"]
        templates = item["templates"]

        self.logger.info(
            f"Labeling oxidation states for {len(templates)} structure templates"
        )

        oxi_labeled_templates = []
        for template in templates:
            struct = Structure.from_dict(template["structure"])
            try:
                oxi_labeled_templates.append({
                    "structure":
                    self.auto_oxi.apply_transformation(struct),
                    "id":
                    template[self.structure_templates.key],
                })
            except:
                continue  # if auto-oxidation fails, try next structure

        self.logger.info(
            f"Successfully labeled oxidation states for {len(oxi_labeled_templates)} structures"
        )
        self.logger.info("Substituting original species into structures")

        predicted_structs = Substitutor(
            threshold=request["threshold"]).pred_from_structures(
                request["original_species"],
                oxi_labeled_templates,
                remove_duplicates=True,
                remove_existing=True,
            )
        predicted_structs.sort(key=lambda s: s.other_parameters["proba"],
                               reverse=True)

        structure_prediction_id = request[self.requests.key]

        crystal_docs = []
        summaries = []

        self.logger.info(
            f"Found {len(predicted_structs)} predicted structures. Generating crystal docs (XRDs, CIFs, etc."
        )

        for number_id, struct in enumerate(predicted_structs):
            crystal = {}
            summary = {}
            xrd_dict = {}

            final_structure = struct.final_structure
            sga = SpacegroupAnalyzer(final_structure, symprec=0.1)

            for rad_source in ["CuKa", "AgKa", "MoKa", "FeKa"]:
                xrdc = XRDCalculator(wavelength=rad_source)
                pattern = xrdc.get_pattern(final_structure,
                                           two_theta_range=None)
                xrd_dict[rad_source] = pattern

            transformed_structure = struct.to_snl(
                f"{request['name']} <{request['email']}>",
                remarks=["Created by MP Structure Predictor"],
            )

            crystal[self.requests.key] = structure_prediction_id
            crystal[self.crystals.key] = number_id
            crystal["probability"] = struct.other_parameters["proba"]
            crystal["transformed_structure"] = transformed_structure
            crystal["xrd"] = xrd_dict
            crystal["space_group_info"] = {
                "symbol": sga.get_space_group_symbol(),
                "number": sga.get_space_group_number(),
                "hall": sga.get_hall(),
                "crystal_system": sga.get_crystal_system(),
            }

            summary[self.crystals.key] = number_id
            summary["probability"] = struct.other_parameters["proba"]
            summary[
                "pretty_formula"] = final_structure.composition.reduced_formula
            summary["nsites"] = len(final_structure)
            summary["space_group"] = sga.get_space_group_symbol()
            summary["cif"] = str(CifWriter(final_structure))

            crystal_docs.append(jsanitize(crystal, strict=True))
            summaries.append(jsanitize(summary, strict=True))

        self.logger.info(
            f"Successfully generated {len(crystal_docs)} crystal docs for request {request['original_species']}"
        )

        request.update({
            "state": "COMPLETE",
            "completed_at": datetime.utcnow(),
            "num_crystals": len(crystal_docs),
            "crystals": summaries,
        })

        return request, crystal_docs

    def update_targets(self, items):
        """
        Update the request doc and insert the predicted crystals into the collection

        Args:
            items [(dict, dict)]: A list containing tuples with request, crystal docs to update
        """
        request_docs = []
        crystal_docs = []

        for item in items:
            request_docs.append(item[0])
            crystal_docs.extend(item[1])

        if len(request_docs) > 0:
            self.logger.info(f"Updating {len(request_docs)} request docs")
            self.requests.update(request_docs)
        else:
            self.logger.info("No requests to update")

        if len(crystal_docs) > 0:
            self.logger.info(f"Updating {len(crystal_docs)} crystal docs")
            self.crystals.update(crystal_docs,
                                 key=[self.requests.key, self.crystals.key])
        else:
            self.logger.info("No crystals to update")

    @staticmethod
    def find_all_chemsys(original_species, threshold=0.0001, max_num_subs=5):
        """
        Determines chemical systems to consider via data-mined ionic substitution probabilities (see lambda.json)

        Args:
            original_species (list): a list of species, e.g. [Specie('Li',1), Specie('Ni',2), Specie('O',-2)]
            threshold (float): Probability threshold for generating ionic substitutions. Defaults to 0.0001
            max_num_subs (int): Limits maximum number of substitutions wanted. Defaults to 5

        Returns:
            (set): A set of all chemical systems to predict from
        """
        num_species = len(original_species)

        sub_elems = set()
        for specie in original_species:
            subs = SubstitutionPredictor(threshold=threshold).list_prediction(
                [specie])

            # sort and cap number of substitutions
            subs.sort(key=lambda x: x["probability"], reverse=True)
            subs = subs[0:max_num_subs]

            for sub in subs:
                for new_species in sub["substitutions"]:
                    sub_elems.add(new_species.element)

        all_chemsys = set()
        for chemsys in combinations(sub_elems, num_species):
            all_chemsys.add("-".join(sorted([str(el) for el in chemsys])))

        return all_chemsys
# j = st.SiteCollection.get_distance(0,4)
# j = st.lattice_vectors
# print(j)
# list = pymatgen.structure_prediction.pred_from_structures(st)
# print(dir(sp))
# print(sp.__path__)
# print(sp.__doc__)
# print(dir(pymatgen.core.structure))
# print(dir(stt.SiteCollection))

# print(sp.__builtins__)
# print(dir(test))
# i = test.SubstitutionPredictor(st.composition)
Subs = substitutor.Substitutor()
# Subs._threshold = 0.001 # Any threshold beyond 0.001 brings it out of the default hence it doesn't work: has to figure out how to explicitly set the threshold probability
struc = AutoOxiStateDecorationTransformation().apply_transformation(st)
spgr = struc.get_spacegroup_info()
print(struc.get_spacegroup_info())

# print(struc)
cations = ['Co', 'Rh', 'Ir']
#cations = ['Li', 'Na', 'K', 'Rb', 'Cs']
# cations2 = ['C','Si','Ge','Sn', 'Pb']
anions = ['F', 'Cl', 'Br', 'I']
counter = 0
for cation in cations:
    # for cation2 in cations2:
    for anion in anions:
        try:
            # list = Subs.pred_from_structures(target_species=[Specie(cation, +1), Specie(cation2, +2), Specie(anion, -1)], structures_list=[{'structure': struc, 'id': 'TEST'}])
            list = Subs.pred_from_structures(target_species=[Specie(cation, +3), Specie(anion, -1)], structures_list=[{'structure': struc, 'id': 'TEST'}])