Example #1
0
 def test_get_neighbors_of_site_with_index(self):
     self.assertEqual(
         len(get_neighbors_of_site_with_index(self.diamond, 0)), 4)
     self.assertEqual(len(get_neighbors_of_site_with_index(self.nacl, 0)),
                      6)
     self.assertEqual(len(get_neighbors_of_site_with_index(self.cscl, 0)),
                      8)
     self.assertEqual(
         len(get_neighbors_of_site_with_index(self.diamond, 0, delta=0.01)),
         4)
     self.assertEqual(
         len(get_neighbors_of_site_with_index(self.diamond, 0, cutoff=6)),
         4)
     self.assertEqual(
         len(
             get_neighbors_of_site_with_index(self.diamond,
                                              0,
                                              approach="voronoi")), 4)
     self.assertEqual(
         len(
             get_neighbors_of_site_with_index(self.diamond,
                                              0,
                                              approach="min_OKeeffe")), 4)
     self.assertEqual(
         len(
             get_neighbors_of_site_with_index(self.diamond,
                                              0,
                                              approach="min_VIRE")), 4)
Example #2
0
    def __init__(self, crystal, L=6):
        # all power spectrum values up to maximum degree L
        ls = np.arange(2, L + 1, 1)
        power_spectrum = self._populate_dicts(ls)
        '''Loop over l to calculate each power spectrum parameter'''
        for l in ls:
            parameter_index = str(l)
            for index, site in enumerate(crystal):
                # get all nearest neighbors
                neighbors = get_neighbors_of_site_with_index(
                    crystal, index, approach='voronoi')
                # generate free integer paramters
                # calculate power spectrum pl see Pl method
                power_spectrum['p' + parameter_index].append(
                    self.Pl(site, neighbors, l))

        spectrum_values = list(power_spectrum.values())
        '''
        If the list is 1 dimensional simply take the mean of the list
        If the list is 2 dimensional take the mean over the rows of the list'''
        try:  # 2D case
            self.Power_spectrum = np.apply_along_axis(np.mean, 1,
                                                      spectrum_values)
        except:  # 1D case
            self.Power_spectrum = np.mean(spectrum_values)
Example #3
0
    def __init__(self, crystal, L=22):
        # all bond order parameters up to maximum degree L
        Ls = np.arange(4, L + 1, 2)
        '''populate a dictionary of empty lists with keys corresponding
           to each bond order parameter up to L'''
        bond_order_params = self._populate_dicts(Ls)
        '''loop over l to calculate each bond order parameter
           and store it in the dictionary'''
        for l in Ls:
            # to index the dictionary
            parameter_index = str(l)
            # iterate over each periodic site in the pymatgen crystal structure
            for index, site in enumerate(crystal):
                # get all nearest neighbors
                neighbors = get_neighbors_of_site_with_index(
                    crystal, index, approach='voronoi')
                # generate free integer parameters
                mvals = self._mvalues(l)
                # complex vector of all qlms generated by the free integer parameters
                qlms = _qlm(site, neighbors, l, mvals)
                # scalar product of qlm with itself
                dot = _scalar_product(qlms, qlms)
                # calculate ql see _ql method
                bond_order_params['q' + parameter_index] += [_ql(dot, l)]
                '''
                Here we use a mapping from the free integer parameter set [-l,l]
                to the set [0, 2l] in order to index the complex vector for the
                computation of the wl bond order parameter

                It is more convenient to use the set [0, 2l] to index qlms as
                [0, 2l] is the set of indeces for the array elements

                When m1 + m2 + m3 belonging to the set [-l,l] is 0;
                     m1 + m2 + m3 belonging to the set [0, 2l] is exactly 3 * l
                '''
                # the set [0, 2l]
                iterator = np.arange(0, 2 * l + 1, 1)
                # initiate wli as a float
                wli = 0.
                # equivalent to a triple for loop
                for m1, m2, m3 in product(iterator, repeat=3):
                    '''
                    This case corresponds to when m1 + m2 + m3 in the set [-l,l]
                    is zero
                    '''
                    if m1 + m2 + m3 == 3 * l:
                        # add wli to the bond order parameter
                        wli += _wli(qlms, l, m1, m2, m3)
                # normalize by the scalar product of the complex vector
                bond_order_params['w' +
                                  parameter_index] += [wli / (dot**(3 / 2))]

        # call the values in the dictionary and insert them into a list
        parameters = np.array(list(bond_order_params.values()))

        parameters[np.isnan(parameters)] = 0
        self.params = parameters.T
Example #4
0
 def test_get_neighbors_of_site_with_index(self):
     self.assertEqual(len(get_neighbors_of_site_with_index(
             self.diamond, 0)), 4)
     self.assertEqual(len(get_neighbors_of_site_with_index(
             self.nacl, 0)), 6)
     self.assertEqual(len(get_neighbors_of_site_with_index(
             self.cscl, 0)), 8)
     self.assertEqual(len(get_neighbors_of_site_with_index(
             self.diamond, 0, delta=0.01)), 4)
     self.assertEqual(len(get_neighbors_of_site_with_index(
             self.diamond, 0, cutoff=6)), 4)
     self.assertEqual(len(get_neighbors_of_site_with_index(
             self.diamond, 0, approach="voronoi")), 4)
     self.assertEqual(len(get_neighbors_of_site_with_index(
             self.diamond, 0, approach="min_OKeeffe")), 4)
     self.assertEqual(len(get_neighbors_of_site_with_index(
             self.diamond, 0, approach="min_VIRE")), 4)
Example #5
0
    def get_analysis_and_structure(self,
                                   structure,
                                   calculate_valences=True,
                                   guesstimate_spin=False,
                                   op_threshold=0.1):
        """
        Obtain an analysis of a given structure and if it may be Jahn-Teller
        active or not. This is a heuristic, and may give false positives and
        false negatives (false positives are preferred).

        :param structure: input structure
        :param calculate_valences (bool): whether to attempt to calculate valences or not, structure
            should have oxidation states to perform analysis
        :param guesstimate_spin (bool): whether to guesstimate spin state from magnetic moments
            or not, use with caution
        :param op_threshold (float): threshold for order parameter above which to consider site
            to match an octahedral or tetrahedral motif, since Jahn-Teller structures can often be
            quite distorted, this threshold is smaller than one might expect
        :return (dict): analysis of structure, with key 'strength' which may be 'none', 'strong',
            'weak', or 'unknown'
        """

        structure = structure.get_primitive_structure()

        if calculate_valences:
            bva = BVAnalyzer()
            structure = bva.get_oxi_state_decorated_structure(structure)

        # no point testing multiple equivalent sites, doesn't make any difference to analysis
        # but makes returned
        symmetrized_structure = SpacegroupAnalyzer(
            structure).get_symmetrized_structure()

        # to detect structural motifs of a given site
        op = LocalStructOrderParams(['oct', 'tet'])

        # dict of site index to the Jahn-Teller analysis of that site
        jt_sites = []
        non_jt_sites = []

        for indices in symmetrized_structure.equivalent_indices:

            idx = indices[0]
            site = symmetrized_structure[idx]

            # only interested in sites with oxidation states
            if isinstance(site.specie,
                          Specie) and site.specie.element.is_transition_metal:

                # get motif around site
                order_params = op.get_order_parameters(symmetrized_structure,
                                                       idx)

                if order_params[0] > order_params[1] and order_params[
                        0] > op_threshold:
                    motif = 'oct'
                    motif_order_parameter = order_params[0]
                elif order_params[1] > op_threshold:
                    motif = 'tet'
                    motif_order_parameter = order_params[1]
                else:
                    motif = 'unknown'
                    motif_order_parameter = None

                if motif == "oct" or motif == "tet":

                    # guess spin of metal ion
                    if guesstimate_spin and 'magmom' in site.properties:

                        # estimate if high spin or low spin
                        magmom = site.properties['magmom']
                        spin_state = self._estimate_spin_state(
                            site.specie, motif, magmom)
                    else:
                        spin_state = "unknown"

                    magnitude = self.get_magnitude_of_effect_from_species(
                        site.specie, spin_state, motif)

                    if magnitude != "none":

                        ligands = get_neighbors_of_site_with_index(
                            structure, idx, approach="min_dist", delta=0.15)
                        ligand_bond_lengths = [
                            ligand.distance(structure[idx])
                            for ligand in ligands
                        ]
                        ligands_species = list(
                            set([str(ligand.specie) for ligand in ligands]))
                        ligand_bond_length_spread = max(ligand_bond_lengths) - \
                                                    min(ligand_bond_lengths)

                        def trim(f):
                            # avoid storing to unreasonable precision, hurts readability
                            return float("{:.4f}".format(f))

                        # to be Jahn-Teller active, all ligands have to be the same
                        if len(ligands_species) == 1:
                            jt_sites.append({
                                'strength':
                                magnitude,
                                'motif':
                                motif,
                                'motif_order_parameter':
                                trim(motif_order_parameter),
                                'spin_state':
                                spin_state,
                                'species':
                                str(site.specie),
                                'ligand':
                                ligands_species[0],
                                'ligand_bond_lengths': [
                                    trim(length)
                                    for length in ligand_bond_lengths
                                ],
                                'ligand_bond_length_spread':
                                trim(ligand_bond_length_spread),
                                'site_indices':
                                indices
                            })

                    # store reasons for not being J-T active
                    else:
                        non_jt_sites.append({
                            'site_indices':
                            indices,
                            'strength':
                            "none",
                            'reason':
                            "Not Jahn-Teller active for this "
                            "electronic configuration."
                        })
                else:
                    non_jt_sites.append({
                        'site_indices': indices,
                        'strength': "none",
                        'reason': "motif is {}".format(motif)
                    })

        # perform aggregation of all sites
        if jt_sites:
            analysis = {'active': True}
            # if any site could exhibit 'strong' Jahn-Teller effect
            # then mark whole structure as strong
            strong_magnitudes = [
                site['strength'] == "strong" for site in jt_sites
            ]
            if any(strong_magnitudes):
                analysis['strength'] = "strong"
            else:
                analysis['strength'] = "weak"
            analysis['sites'] = jt_sites
            return analysis, structure
        else:
            return {'active': False, 'sites': non_jt_sites}, structure
Example #6
0
    def get_analysis_and_structure(self,
                                   structure,
                                   calculate_valences=True,
                                   guesstimate_spin=False,
                                   op_threshold=0.1):
        """
        Obtain an analysis of a given structure and if it may be Jahn-Teller
        active or not. This is a heuristic, and may give false positives and
        false negatives (false positives are preferred).

        :param structure: input structure
        :param calculate_valences (bool): whether to attempt to calculate valences or not, structure
        should have oxidation states to perform analysis
        :param guesstimate_spin (bool): whether to guesstimate spin state from magnetic moments
        or not, use with caution
        :param op_threshold (float): threshold for order parameter above which to consider site
        to match an octahedral or tetrahedral motif, since Jahn-Teller structures can often be
        quite distorted, this threshold is smaller than one might expect
        :return (dict): analysis of structure, with key 'strength' which may be 'none', 'strong',
        'weak', or 'unknown'
        """

        structure = structure.get_primitive_structure()

        if calculate_valences:
            bva = BVAnalyzer()
            structure = bva.get_oxi_state_decorated_structure(structure)

        # no point testing multiple equivalent sites, doesn't make any difference to analysis
        # but makes returned
        symmetrized_structure = SpacegroupAnalyzer(structure).get_symmetrized_structure()

        # to detect structural motifs of a given site
        op = LocalStructOrderParams(['oct', 'tet'])

        # dict of site index to the Jahn-Teller analysis of that site
        jt_sites = []
        non_jt_sites = []

        for indices in symmetrized_structure.equivalent_indices:

            idx = indices[0]
            site = symmetrized_structure[idx]

            # only interested in sites with oxidation states
            if isinstance(site.specie, Specie) and site.specie.element.is_transition_metal:

                # get motif around site
                order_params = op.get_order_parameters(symmetrized_structure, idx)

                if order_params[0] > order_params[1] and order_params[0] > op_threshold:
                    motif = 'oct'
                    motif_order_parameter = order_params[0]
                elif order_params[1] > op_threshold:
                    motif = 'tet'
                    motif_order_parameter = order_params[1]
                else:
                    motif = 'unknown'
                    motif_order_parameter = None

                if motif == "oct" or motif == "tet":

                    # guess spin of metal ion
                    if guesstimate_spin and 'magmom' in site.properties:
                        # estimate if high spin or low spin
                        magmom = site.properties['magmom']
                        spin_state = self._estimate_spin_state(site.specie, motif, magmom)
                    else:
                        spin_state = "unknown"

                    magnitude = self.get_magnitude_of_effect_from_species(site.specie,
                                                                          spin_state,
                                                                          motif)

                    if magnitude != "none":

                        ligands = get_neighbors_of_site_with_index(structure, idx,
                                                                   approach="min_dist",
                                                                   delta=0.15)
                        ligand_bond_lengths = [ligand.distance(structure[idx])
                                               for ligand in ligands]
                        ligands_species = list(set([str(ligand.specie) for ligand in ligands]))
                        ligand_bond_length_spread = max(ligand_bond_lengths) - \
                                                    min(ligand_bond_lengths)

                        def trim(f):
                            # avoid storing to unreasonable precision, hurts readability
                            return float("{:.4f}".format(f))

                        # to be Jahn-Teller active, all ligands have to be the same
                        if len(ligands_species) == 1:

                            jt_sites.append({'strength': magnitude,
                                             'motif': motif,
                                             'motif_order_parameter': trim(motif_order_parameter),
                                             'spin_state': spin_state,
                                             'species': str(site.specie),
                                             'ligand': ligands_species[0],
                                             'ligand_bond_lengths': [trim(length) for length in
                                                                     ligand_bond_lengths],
                                             'ligand_bond_length_spread':
                                                 trim(ligand_bond_length_spread),
                                             'site_indices': indices})

            # store reasons for not being J-T active
                    else:
                        non_jt_sites.append({'site_indices': indices,
                                             'strength': "none",
                                             'reason': "Not Jahn-Teller active for this "
                                                       "electronic configuration."})
                else:
                    non_jt_sites.append({'site_indices': indices,
                                         'strength': "none",
                                         'reason': "motif is {}".format(motif)})

        # perform aggregation of all sites
        if jt_sites:
            analysis = {'active': True}
            # if any site could exhibit 'strong' Jahn-Teller effect
            # then mark whole structure as strong
            strong_magnitudes = [site['strength'] == "strong" for site in jt_sites]
            if any(strong_magnitudes):
                analysis['strength'] = "strong"
            else:
                analysis['strength'] = "weak"
            analysis['sites'] = jt_sites
            return analysis, structure
        else:
            return {'active': False, 'sites': non_jt_sites}, structure
Example #7
0
if thresh is None:
    thresh = {
        "qtet": 0.5,
        "qoct": 0.5,
        "qbcc": 0.5,
        "q6": 0.4,
        "qtribipyr": 0.8,
        "qsqpyr": 0.8
    }

ops = LocalStructOrderParams(
    ["cn", "tet", "oct", "bcc", "q6", "sq_pyr", "tri_bipyr"])

neighs_cent = get_neighbors_of_site_with_index(struct,
                                               n,
                                               approach=approach,
                                               delta=delta,
                                               cutoff=cutoff)

neighs_cent.append(struct.sites[n])
opvals = ops.get_order_parameters(
    neighs_cent,
    len(neighs_cent) - 1,
    indices_neighs=[i for i in range(len(neighs_cent) - 1)])
cn = int(opvals[0] + 0.5)
motif_type = "unrecognized"
nmotif = 0

if cn == 4 and opvals[1] > thresh["qtet"]:
    motif_type = "tetrahedral"
    nmotif += 1
Example #8
0
    def get_analysis_and_structure(
        self,
        structure: Structure,
        calculate_valences: bool = True,
        guesstimate_spin: bool = False,
        op_threshold: float = 0.1,
    ) -> Tuple[Dict, Structure]:
        """Obtain an analysis of a given structure and if it may be Jahn-Teller
        active or not. This is a heuristic, and may give false positives and
        false negatives (false positives are preferred).

        Args:
            structure: input structure
            calculate_valences: whether to attempt to calculate valences or not, structure
                should have oxidation states to perform analysis (Default value = True)
            guesstimate_spin: whether to guesstimate spin state from magnetic moments
                or not, use with caution (Default value = False)
            op_threshold: threshold for order parameter above which to consider site
                to match an octahedral or tetrahedral motif, since Jahn-Teller structures
                can often be
                quite distorted, this threshold is smaller than one might expect

        Returns:
            analysis of structure, with key 'strength' which may be 'none', 'strong',
            'weak', or 'unknown' (Default value = 0.1) and decorated structure

        """

        structure = structure.get_primitive_structure()

        if calculate_valences:
            bva = BVAnalyzer()
            structure = bva.get_oxi_state_decorated_structure(structure)

        # no point testing multiple equivalent sites, doesn't make any difference to analysis
        # but makes returned
        symmetrized_structure = SpacegroupAnalyzer(
            structure).get_symmetrized_structure()

        # to detect structural motifs of a given site
        op = LocalStructOrderParams(["oct", "tet"])

        # dict of site index to the Jahn-Teller analysis of that site
        jt_sites = []
        non_jt_sites = []

        for indices in symmetrized_structure.equivalent_indices:

            idx = indices[0]
            site = symmetrized_structure[idx]

            # only interested in sites with oxidation states
            if isinstance(site.specie,
                          Species) and site.specie.element.is_transition_metal:

                # get motif around site
                order_params = op.get_order_parameters(symmetrized_structure,
                                                       idx)

                if order_params[0] > order_params[1] and order_params[
                        0] > op_threshold:
                    motif = "oct"
                    motif_order_parameter = order_params[0]
                elif order_params[1] > op_threshold:
                    motif = "tet"
                    motif_order_parameter = order_params[1]
                else:
                    motif = "unknown"
                    motif_order_parameter = None

                if motif in ["oct", "tet"]:

                    motif = cast(Literal["oct", "tet"],
                                 motif)  # mypy needs help

                    # guess spin of metal ion
                    if guesstimate_spin and "magmom" in site.properties:

                        # estimate if high spin or low spin
                        magmom = site.properties["magmom"]
                        spin_state = self._estimate_spin_state(
                            site.specie, motif, magmom)
                    else:
                        spin_state = "unknown"

                    magnitude = self.get_magnitude_of_effect_from_species(
                        site.specie, spin_state, motif)

                    if magnitude != "none":

                        ligands = get_neighbors_of_site_with_index(
                            structure, idx, approach="min_dist", delta=0.15)
                        ligand_bond_lengths = [
                            ligand.distance(structure[idx])
                            for ligand in ligands
                        ]
                        ligands_species = list(
                            {str(ligand.specie)
                             for ligand in ligands})
                        ligand_bond_length_spread = max(
                            ligand_bond_lengths) - min(ligand_bond_lengths)

                        def trim(f):
                            """
                            Avoid storing to unreasonable precision, hurts readability.
                            """
                            return float(f"{f:.4f}")

                        # to be Jahn-Teller active, all ligands have to be the same
                        if len(ligands_species) == 1:
                            jt_sites.append({
                                "strength":
                                magnitude,
                                "motif":
                                motif,
                                "motif_order_parameter":
                                trim(motif_order_parameter),
                                "spin_state":
                                spin_state,
                                "species":
                                str(site.specie),
                                "ligand":
                                ligands_species[0],
                                "ligand_bond_lengths": [
                                    trim(length)
                                    for length in ligand_bond_lengths
                                ],
                                "ligand_bond_length_spread":
                                trim(ligand_bond_length_spread),
                                "site_indices":
                                indices,
                            })

                    # store reasons for not being J-T active
                    else:
                        non_jt_sites.append({
                            "site_indices":
                            indices,
                            "strength":
                            "none",
                            "reason":
                            "Not Jahn-Teller active for this electronic configuration.",
                        })
                else:
                    non_jt_sites.append({
                        "site_indices": indices,
                        "strength": "none",
                        "reason": f"motif is {motif}",
                    })

        # perform aggregation of all sites
        if jt_sites:
            analysis = {"active": True}  # type: Dict[str, Any]
            # if any site could exhibit 'strong' Jahn-Teller effect
            # then mark whole structure as strong
            strong_magnitudes = [
                site["strength"] == "strong" for site in jt_sites
            ]
            if any(strong_magnitudes):
                analysis["strength"] = "strong"
            else:
                analysis["strength"] = "weak"
            analysis["sites"] = jt_sites
            return analysis, structure
        return {"active": False, "sites": non_jt_sites}, structure
def site_is_of_motif_type(struct,
                          n,
                          neighbors_list=None,
                          approach="min_dist",
                          delta=0.1,
                          cutoff=10.0,
                          thresh=None):
    """
    Returns the motif type of the site with index n in structure struct;
    currently featuring "tetrahedral", "octahedral", "bcc", and "cp"
    (close-packed: fcc and hcp) as well as "square pyramidal" and
    "trigonal bipyramidal".  If the site is not recognized,
    "unrecognized" is returned.  If a site should be assigned to two
    different motifs, "multiple assignments" is returned.

    Args:
        struct (Structure): input structure.
        n (int): index of site in Structure object for which motif type
                is to be determined.
        approach (str): type of neighbor-finding approach, where
              "min_dist" will use the MinimumDistanceNN class,
              "voronoi" the VoronoiNN class, "min_OKeeffe" the
              MinimumOKeeffe class, and "min_VIRE" the MinimumVIRENN class.
        delta (float): tolerance involved in neighbor finding.
        cutoff (float): (large) radius to find tentative neighbors.
        thresh (dict): thresholds for motif criteria (currently, required
                keys and their default values are "qtet": 0.5,
                "qoct": 0.5, "qbcc": 0.5, "q6": 0.4).

    Returns: motif type (str).
    """
    # | - site_is_of_motif_type
    # print("THIS IS MY CUSTOM METHOD, NOT NATIVE PYMATGEN")

    if thresh is None:
        thresh = {
            "qtet": 0.5,
            "qoct": 0.5,
            "qbcc": 0.5,
            "q6": 0.4,
            "qtribipyr": 0.8,
            "qsqpyr": 0.8
        }

    ops = LocalStructOrderParams(
        ["cn", "tet", "oct", "bcc", "q6", "sq_pyr", "tri_bipyr"])

    # #########################################################################
    if neighbors_list is None:
        neighs_cent = get_neighbors_of_site_with_index(struct,
                                                       n,
                                                       approach=approach,
                                                       delta=delta,
                                                       cutoff=cutoff)
    else:
        neighs_cent = neighbors_list

    neighs_cent.append(struct.sites[n])

    opvals = ops.get_order_parameters(
        neighs_cent,
        len(neighs_cent) - 1,
        indices_neighs=[i for i in range(len(neighs_cent) - 1)])
    cn = int(opvals[0] + 0.5)
    motif_type = "unrecognized"
    nmotif = 0

    if cn == 4 and opvals[1] > thresh["qtet"]:
        motif_type = "tetrahedral"
        nmotif += 1
    if cn == 5 and opvals[5] > thresh["qsqpyr"]:
        motif_type = "square pyramidal"
        nmotif += 1
    if cn == 5 and opvals[6] > thresh["qtribipyr"]:
        motif_type = "trigonal bipyramidal"
        nmotif += 1
    if cn == 6 and opvals[2] > thresh["qoct"]:
        motif_type = "octahedral"
        nmotif += 1
    if cn == 8 and (opvals[3] > thresh["qbcc"] and opvals[1] < thresh["qtet"]):
        motif_type = "bcc"
        nmotif += 1
    if cn == 12 and (opvals[4] > thresh["q6"] and opvals[1] < thresh["q6"] and
                     opvals[2] < thresh["q6"] and opvals[3] < thresh["q6"]):
        motif_type = "cp"
        nmotif += 1

    if nmotif > 1:
        motif_type = "multiple assignments"

    return motif_type