Пример #1
0
 def get_charges(self, defect_type, site_specie, sub_specie=None):
     """
     Based on the type of defect, site and substitution (if any) species
     the defect charge states are generated.
     Args:
         defect_type (str): Options are vacancy, antisite, substitution,
                            and interstitial
         site_specie (str): Specie on the host lattice site
                      Use this for interstitials as well
         sub_specie (str): Specie that is replacing the site specie.
                     At present used for substitution and antisite defects
     """
     if site_specie not in self.min_max_oxi.keys():
         max_oxi = max(Element(site_specie).common_oxidation_states)
         min_oxi = min(Element(site_specie).common_oxidation_states)
         self.min_max_oxi[site_specie] = [min_oxi, max_oxi]
     if sub_specie:
         if sub_specie not in self.min_max_oxi.keys():
             max_oxi = max(Element(sub_specie).common_oxidation_states)
             min_oxi = min(Element(sub_specie).common_oxidation_states)
             self.min_max_oxi[sub_specie] = [min_oxi, max_oxi]
     if defect_type == 'vacancy':
         site_oxi = self.oxi_states[site_specie]
         if site_oxi:
             return list(range(-abs(site_oxi), abs(site_oxi) + 1))
         else:
             min_oxi = self.min_max_oxi[site_specie][0]
             max_oxi = self.min_max_oxi[site_specie][1]
             if abs(min_oxi) < abs(max_oxi):
                 return list(range(-abs(max_oxi), abs(max_oxi) + 1))
             else:
                 return list(range(-abs(min_oxi), abs(min_oxi) + 1))
     elif defect_type == 'antisite':
         return list(
             range(self.min_max_oxi_bulk[0], self.min_max_oxi_bulk[1] - 1))
     elif defect_type == 'substitution':
         oxi_sub_set = Element(sub_specie).common_oxidation_states
         oxi_site = self.oxi_states[site_specie]
         min_max_oxi_bulk_sub = [
             min(min(oxi_sub_set) - oxi_site, -1),
             max(max(oxi_sub_set) - oxi_site, 1)
         ]
         if (min_max_oxi_bulk_sub[1] - min_max_oxi_bulk_sub[0]) > 2:
             if min_max_oxi_bulk_sub[1] > 2:
                 return list(
                     range(min_max_oxi_bulk_sub[0],
                           min_max_oxi_bulk_sub[1] - 2)
                 )  # if range exists, less likely to be a higher charge
             else:
                 return list(
                     range(min_max_oxi_bulk_sub[0], 2)
                 )  # extend to 2 if upper range does not already include this
         else:
             return list(
                 range(min_max_oxi_bulk_sub[0] - 1,
                       min_max_oxi_bulk_sub[1] +
                       2))  #likely upper bound is 0, so extend to 2
     elif defect_type == 'interstitial':
         if self.min_max_oxi[site_specie][0] > 0:
             min_oxi = 0
         else:
             min_oxi = self.min_max_oxi[site_specie][0]
         if self.min_max_oxi[site_specie][1] < 0:
             max_oxi = 0
         else:
             max_oxi = self.min_max_oxi[site_specie][1]
         return list(range(min_oxi, max_oxi + 1))
     else:
         raise ValueError("Defect type not understood")
Пример #2
0
 def setUp(self):
     self.entries = EntrySet.from_csv(str(module_dir / "pdentries_test.csv"))
     self.pd = GrandPotentialPhaseDiagram(self.entries, {Element("O"): -5})
     self.pd6 = GrandPotentialPhaseDiagram(self.entries, {Element("O"): -6})
Пример #3
0
    def __init__(self, entry1, entry2, working_ion_entry):
        # initialize some internal variables
        working_element = working_ion_entry.composition.elements[0]

        entry_charge = entry1
        entry_discharge = entry2
        if entry_charge.composition.get_atomic_fraction(working_element) \
                > entry2.composition.get_atomic_fraction(working_element):
            (entry_charge, entry_discharge) = (entry_discharge, entry_charge)

        comp_charge = entry_charge.composition
        comp_discharge = entry_discharge.composition

        ion_sym = working_element.symbol

        frame_charge_comp = Composition({
            el: comp_charge[el]
            for el in comp_charge if el.symbol != ion_sym
        })
        frame_discharge_comp = Composition({
            el: comp_discharge[el]
            for el in comp_discharge if el.symbol != ion_sym
        })

        # Data validation

        # check that the ion is just a single element
        if not working_ion_entry.composition.is_element:
            raise ValueError("VoltagePair: The working ion specified must be "
                             "an element")

        # check that at least one of the entries contains the working element
        if not comp_charge.get_atomic_fraction(working_element) > 0 and \
                not comp_discharge.get_atomic_fraction(working_element) > 0:
            raise ValueError("VoltagePair: The working ion must be present in "
                             "one of the entries")

        # check that the entries do not contain the same amount of the workin
        # element
        if comp_charge.get_atomic_fraction(working_element) == \
                comp_discharge.get_atomic_fraction(working_element):
            raise ValueError("VoltagePair: The working ion atomic percentage "
                             "cannot be the same in both the entries")

        # check that the frameworks of the entries are equivalent
        if not frame_charge_comp.reduced_formula == \
                frame_discharge_comp.reduced_formula:
            raise ValueError("VoltagePair: the specified entries must have the"
                             " same compositional framework")

        # Initialize normalization factors, charged and discharged entries

        valence_list = Element(ion_sym).oxidation_states
        working_ion_valence = abs(max(valence_list))

        (self.framework,
         norm_charge) = frame_charge_comp.get_reduced_composition_and_factor()
        norm_discharge = \
            frame_discharge_comp.get_reduced_composition_and_factor()[1]

        self._working_ion_entry = working_ion_entry

        # Initialize normalized properties
        self._vol_charge = entry_charge.structure.volume / norm_charge
        self._vol_discharge = entry_discharge.structure.volume / norm_discharge

        comp_charge = entry_charge.composition
        comp_discharge = entry_discharge.composition

        self._mass_charge = comp_charge.weight / norm_charge
        self._mass_discharge = comp_discharge.weight / norm_discharge

        self._num_ions_transferred = \
            (comp_discharge[working_element] / norm_discharge) \
            - (comp_charge[working_element] / norm_charge)

        self._voltage = \
            (((entry_charge.energy / norm_charge) -
              (entry_discharge.energy / norm_discharge)) / \
             self._num_ions_transferred + working_ion_entry.energy_per_atom) / working_ion_valence
        self._mAh = self._num_ions_transferred * Charge(1, "e").to("C") * \
                    Time(1, "s").to("h") * N_A * 1000 * working_ion_valence

        # Step 4: add (optional) hull and muO2 data
        self.decomp_e_charge = \
            entry_charge.data.get("decomposition_energy", None)
        self.decomp_e_discharge = \
            entry_discharge.data.get("decomposition_energy", None)

        self.muO2_charge = entry_charge.data.get("muO2", None)
        self.muO2_discharge = entry_discharge.data.get("muO2", None)

        self.entry_charge = entry_charge
        self.entry_discharge = entry_discharge
        self.normalization_charge = norm_charge
        self.normalization_discharge = norm_discharge
        self._frac_charge = comp_charge.get_atomic_fraction(working_element)
        self._frac_discharge = \
            comp_discharge.get_atomic_fraction(working_element)
Пример #4
0
 def test_get_defectsite_coordinated_elements(self):
     struct_el = self._mgo_uc.composition.elements
     for i in range(self._mgo_interstitial.defectsite_count()):
         for el in self._mgo_interstitial.get_coordinated_elements(i):
             self.assertTrue(
                 Element(el) in struct_el, "Coordinated elements are wrong")
Пример #5
0
 def test_getmu_range_stability_phase(self):
     results = self.pd.get_chempot_range_stability_phase(Composition("LiFeO2"), Element("O"))
     self.assertAlmostEqual(results[Element("O")][1], -4.4501812249999997)
     self.assertAlmostEqual(results[Element("Fe")][0], -6.5961470999999996)
     self.assertAlmostEqual(results[Element("Li")][0], -3.6250022625000007)
Пример #6
0
    def _get_oxid_state_guesses(self, all_oxi_states, max_sites,
                                oxi_states_override, target_charge):
        """
        Utility operation for guessing oxidation states.

        See `oxi_state_guesses` for full details. This operation does the
        calculation of the most likely oxidation states

        Args:
            oxi_states_override (dict): dict of str->list to override an
                element's common oxidation states, e.g. {"V": [2,3,4,5]}
            target_charge (int): the desired total charge on the structure.
                Default is 0 signifying charge balance.
            all_oxi_states (bool): if True, an element defaults to
                all oxidation states in pymatgen Element.icsd_oxidation_states.
                Otherwise, default is Element.common_oxidation_states. Note
                that the full oxidation state list is *very* inclusive and
                can produce nonsensical results.
            max_sites (int): if possible, will reduce Compositions to at most
                this many sites to speed up oxidation state guesses. If the
                composition cannot be reduced to this many sites a ValueError
                will be raised. Set to -1 to just reduce fully. If set to a
                number less than -1, the formula will be fully reduced but a
                ValueError will be thrown if the number of atoms in the reduced
                formula is greater than abs(max_sites).

        Returns:
            A list of dicts - each dict reports an element symbol and average
                oxidation state across all sites in that composition. If the
                composition is not charge balanced, an empty list is returned.
            A list of dicts - each dict maps the element symbol to a list of
                oxidation states for each site of that element. For example, Fe3O4 could
                return a list of [2,2,2,3,3,3] for the oxidation states of If the composition
                is

            """
        comp = self.copy()
        # reduce Composition if necessary
        if max_sites and max_sites < 0:
            comp = self.reduced_composition

            if max_sites < -1 and comp.num_atoms > abs(max_sites):
                raise ValueError("Composition {} cannot accommodate max_sites "
                                 "setting!".format(comp))

        elif max_sites and comp.num_atoms > max_sites:
            reduced_comp, reduced_factor = self. \
                get_reduced_composition_and_factor()
            if reduced_factor > 1:
                reduced_comp *= max(1, int(max_sites / reduced_comp.num_atoms))
                comp = reduced_comp  # as close to max_sites as possible
            if comp.num_atoms > max_sites:
                raise ValueError("Composition {} cannot accommodate max_sites "
                                 "setting!".format(comp))

        # Load prior probabilities of oxidation states, used to rank solutions
        if not Composition.oxi_prob:
            module_dir = os.path.join(
                os.path.dirname(os.path.abspath(__file__)))
            all_data = loadfn(
                os.path.join(module_dir, "..", "analysis", "icsd_bv.yaml"))
            Composition.oxi_prob = {
                Specie.from_string(sp): data
                for sp, data in all_data["occurrence"].items()
            }
        oxi_states_override = oxi_states_override or {}
        # assert: Composition only has integer amounts
        if not all(amt == int(amt) for amt in comp.values()):
            raise ValueError("Charge balance analysis requires integer "
                             "values in Composition!")

        # for each element, determine all possible sum of oxidations
        # (taking into account nsites for that particular element)
        el_amt = comp.get_el_amt_dict()
        els = el_amt.keys()
        el_sums = []  # matrix: dim1= el_idx, dim2=possible sums
        el_sum_scores = defaultdict(set)  # dict of el_idx, sum -> score
        el_best_oxid_combo = {
        }  # dict of el_idx, sum -> oxid combo with best score
        for idx, el in enumerate(els):
            el_sum_scores[idx] = {}
            el_best_oxid_combo[idx] = {}
            el_sums.append([])
            if oxi_states_override.get(el):
                oxids = oxi_states_override[el]
            elif all_oxi_states:
                oxids = Element(el).oxidation_states
            else:
                oxids = Element(el).icsd_oxidation_states or \
                        Element(el).oxidation_states

            # get all possible combinations of oxidation states
            # and sum each combination
            for oxid_combo in combinations_with_replacement(
                    oxids, int(el_amt[el])):

                # List this sum as a possible option
                oxid_sum = sum(oxid_combo)
                if oxid_sum not in el_sums[idx]:
                    el_sums[idx].append(oxid_sum)

                # Determine how probable is this combo?
                score = sum([
                    Composition.oxi_prob.get(Specie(el, o), 0)
                    for o in oxid_combo
                ])

                # If it is the most probable combo for a certain sum,
                #   store the combination
                if oxid_sum not in el_sum_scores[
                        idx] or score > el_sum_scores[idx].get(oxid_sum, 0):
                    el_sum_scores[idx][oxid_sum] = score
                    el_best_oxid_combo[idx][oxid_sum] = oxid_combo

        # Determine which combination of oxidation states for each element
        #    is the most probable
        all_sols = []  # will contain all solutions
        all_oxid_combo = [
        ]  # will contain the best combination of oxidation states for each site
        all_scores = []  # will contain a score for each solution
        for x in product(*el_sums):
            # each x is a trial of one possible oxidation sum for each element
            if sum(x) == target_charge:  # charge balance condition
                el_sum_sol = dict(zip(els, x))  # element->oxid_sum
                # normalize oxid_sum by amount to get avg oxid state
                sol = {el: v / el_amt[el] for el, v in el_sum_sol.items()}
                all_sols.append(
                    sol)  # add the solution to the list of solutions

                # determine the score for this solution
                score = 0
                for idx, v in enumerate(x):
                    score += el_sum_scores[idx][v]
                all_scores.append(score)

                # collect the combination of oxidation states for each site
                all_oxid_combo.append(
                    dict((e, el_best_oxid_combo[idx][v])
                         for idx, (e, v) in enumerate(zip(els, x))))

        # sort the solutions by highest to lowest score
        if len(all_scores) > 0:
            all_sols, all_oxid_combo = zip(
                *[(y, x)
                  for (z, y,
                       x) in sorted(zip(all_scores, all_sols, all_oxid_combo),
                                    key=lambda pair: pair[0],
                                    reverse=True)])
        return all_sols, all_oxid_combo
Пример #7
0
    def __str__(self):
        out = []
        site_descriptions = {}

        if self.pseudo is not None:
            site_descriptions = self.pseudo
        else:
            c = 1
            for site in self.structure:
                name = None
                for k, v in site_descriptions.items():
                    if site.properties == v:
                        name = k

                if name is None:
                    name = site.specie.symbol + str(c)
                    site_descriptions[name] = site.properties
                    c += 1

        def to_str(v):
            if isinstance(v, str):
                return "'%s'" % v
            if isinstance(v, float):
                return "%s" % str(v).replace("e", "d")
            if isinstance(v, bool):
                if v:
                    return ".TRUE."
                return ".FALSE."
            return v

        for k1 in ["control", "system", "electrons", "ions", "cell"]:
            v1 = self.sections[k1]
            out.append("&%s" % k1.upper())
            sub = []
            for k2 in sorted(v1.keys()):
                if isinstance(v1[k2], list):
                    n = 1
                    for l in v1[k2][: len(site_descriptions)]:
                        sub.append("  %s(%d) = %s" % (k2, n, to_str(v1[k2][n - 1])))
                        n += 1
                else:
                    sub.append("  %s = %s" % (k2, to_str(v1[k2])))
            if k1 == "system":
                if "ibrav" not in self.sections[k1]:
                    sub.append("  ibrav = 0")
                if "nat" not in self.sections[k1]:
                    sub.append("  nat = %d" % len(self.structure))
                if "ntyp" not in self.sections[k1]:
                    sub.append("  ntyp = %d" % len(site_descriptions))
            sub.append("/")
            out.append(",\n".join(sub))

        out.append("ATOMIC_SPECIES")
        for k, v in sorted(site_descriptions.items(), key=lambda i: i[0]):
            e = re.match(r"[A-Z][a-z]?", k).group(0)
            if self.pseudo is not None:
                p = v
            else:
                p = v["pseudo"]
            out.append("  %s  %.4f %s" % (k, Element(e).atomic_mass, p))

        out.append("ATOMIC_POSITIONS crystal")
        if self.pseudo is not None:
            for site in self.structure:
                out.append(
                    "  %s %.6f %.6f %.6f" % (site.specie.symbol, site.a, site.b, site.c)
                )
        else:
            for site in self.structure:
                name = None
                for k, v in sorted(site_descriptions.items(), key=lambda i: i[0]):
                    if v == site.properties:
                        name = k
                out.append("  %s %.6f %.6f %.6f" % (name, site.a, site.b, site.c))

        out.append("K_POINTS %s" % self.kpoints_mode)
        kpt_str = ["%s" % i for i in self.kpoints_grid]
        kpt_str.extend(["%s" % i for i in self.kpoints_shift])
        out.append("  %s" % " ".join(kpt_str))
        out.append("CELL_PARAMETERS angstrom")
        for vec in self.structure.lattice.matrix:
            out.append("  %f %f %f" % (vec[0], vec[1], vec[2]))
        return "\n".join(out)
Пример #8
0
    def generate_basis(self, symbol):
        """
    Author: "Kittithat (Mick) Krongchon" <*****@*****.**> and Lucas K. Wagner
    Returns a string containing the basis section.  It is modified according to a simple recipe:
    1) The occupied atomic orbitals are kept, with exponents less than 'cutoff' removed.
    2) These atomic orbitals are augmented with uncontracted orbitals according to the formula 
        e_i = params[0]*params[2]**i, where i goes from 0 to params[1]
        These uncontracted orbitals are added for every occupied atomic orbital (s,p for most elements and s,p,d for transition metals)

    Args:
        symbol (str): The symbol of the element to be specified in the
            D12 file.

    Returns:
        str: The pseudopotential and basis section.


    Uses the following member variables:
        xml_name (str): The name of the XML pseudopotential and basis
            set database.
        cutoff: smallest allowed exponent
        params: parameters for generating the augmenting uncontracted orbitals
        initial_charges    
    """

        maxorb = 3
        basis_name = "vtz"
        nangular = {"s": 1, "p": 1, "d": 1, "f": 1, "g": 0}
        maxcharge = {"s": 2, "p": 6, "d": 10, "f": 15}
        basis_index = {"s": 0, "p": 2, "d": 3, "f": 4}
        transition_metals = [
            "Sc", "Ti", "V", "Cr", "Mn", "Fe", "Co", "Ni", "Cu", "Zn"
        ]
        if symbol in transition_metals:
            maxorb = 4
            nangular['s'] = 2

        tree = ElementTree()
        tree.parse(self.xml_name)
        element = tree.find('./Pseudopotential[@symbol="{}"]'.format(symbol))
        atom_charge = int(element.find('./Effective_core_charge').text)
        if symbol in self.initial_charges.keys():
            atom_charge -= self.initial_charges[symbol]
        basis_path = './Basis-set[@name="{}"]/Contraction'.format(basis_name)
        found_orbitals = []
        totcharge = 0
        ret = []
        ncontract = 0
        for contraction in element.findall(basis_path):
            angular = contraction.get('Angular_momentum')
            if found_orbitals.count(angular) >= nangular[angular]:
                continue

            #Figure out which coefficients to print out based on the minimal exponent
            nterms = 0
            basis_part = []
            for basis_term in contraction.findall('./Basis-term'):
                exp = basis_term.get('Exp')
                coeff = basis_term.get('Coeff')
                if float(exp) > self.cutoff:
                    basis_part += ['  {} {}'.format(exp, coeff)]
                    nterms += 1
            #now write the header
            if nterms > 0:
                found_orbitals.append(angular)
                charge = min(atom_charge - totcharge, maxcharge[angular])
                #put in a special case for transition metals:
                #depopulate the 4s if the atom is charged
                if symbol in transition_metals and symbol in self.initial_charges.keys() \
                        and self.initial_charges[symbol] > 0 and found_orbitals.count(angular) > 1 \
                        and angular=="s":
                    charge = 0
                totcharge += charge
                ret += [
                    "0 %i %i %g 1" % (basis_index[angular], nterms, charge)
                ] + basis_part
                ncontract += 1

        #Add in the uncontracted basis elements
        angular_uncontracted = ['s', 'p']
        if symbol in transition_metals:
            angular_uncontracted.append('d')

        for angular in angular_uncontracted:
            for i in range(0, self.basis_params[1]):
                exp = self.basis_params[0] * self.basis_params[2]**i
                line = '{} {}'.format(exp, 1.0)
                ret += ["0 %i %i %g 1" % (basis_index[angular], 1, 0.0), line]
                ncontract += 1

        return ["%i %i"%(Element(symbol).number+200,ncontract)] +\
                self.pseudopotential_section(symbol) +\
                ret
Пример #9
0
    def from_steps(step1, step2, normalization_els):
        """
        Creates a ConversionVoltagePair from two steps in the element profile
        from a PD analysis.

        Args:
            step1:
                Starting step
            step2:
                Ending step
            normalization_els:
                Elements to normalize the reaction by. To ensure correct
                capacities.
        """
        working_ion_entry = step1["element_reference"]
        working_ion = working_ion_entry.composition.elements[0].symbol
        voltage = -step1["chempot"] + working_ion_entry.energy_per_atom
        mAh = (step2["evolution"] - step1["evolution"]) \
            * ELECTRON_TO_AMPERE_HOURS * 1000
        licomp = Composition.from_formula(working_ion)
        prev_rxn = step1["reaction"]
        reactants = {
            comp: abs(prev_rxn.get_coeff(comp))
            for comp in prev_rxn.products if comp != licomp
        }
        curr_rxn = step2["reaction"]
        products = {
            comp: abs(curr_rxn.get_coeff(comp))
            for comp in curr_rxn.products if comp != licomp
        }
        reactants[licomp] = (step2["evolution"] - step1["evolution"])

        rxn = BalancedReaction(reactants, products)

        for el, amt in normalization_els.items():
            if rxn.get_el_amount(el) != 0:
                rxn.normalize_to_element(el, amt)
                break

        prev_mass_dischg = sum([
            prev_rxn.all_comp[i].weight * abs(prev_rxn.coeffs[i])
            for i in xrange(len(prev_rxn.all_comp))
        ]) / 2
        vol_charge = sum([
            abs(prev_rxn.get_coeff(e.composition)) * e.structure.volume
            for e in step1["entries"]
            if e.composition.reduced_formula != working_ion
        ])
        mass_discharge = sum([
            curr_rxn.all_comp[i].weight * abs(curr_rxn.coeffs[i])
            for i in xrange(len(curr_rxn.all_comp))
        ]) / 2
        mass_charge = prev_mass_dischg
        mass_discharge = mass_discharge
        vol_discharge = sum([
            abs(curr_rxn.get_coeff(e.composition)) * e.structure.volume
            for e in step2["entries"]
            if e.composition.reduced_formula != working_ion
        ])

        totalcomp = Composition({})
        for comp in prev_rxn.products:
            if comp.reduced_formula != working_ion:
                totalcomp += comp * abs(prev_rxn.get_coeff(comp))
        frac_charge = totalcomp.get_atomic_fraction(Element(working_ion))

        totalcomp = Composition({})
        for comp in curr_rxn.products:
            if comp.reduced_formula != working_ion:
                totalcomp += comp * abs(curr_rxn.get_coeff(comp))
        frac_discharge = totalcomp.get_atomic_fraction(Element(working_ion))

        rxn = rxn
        entries_charge = step2["entries"]
        entries_discharge = step1["entries"]

        return ConversionVoltagePair(rxn, voltage, mAh, vol_charge,
                                     vol_discharge, mass_charge,
                                     mass_discharge, frac_charge,
                                     frac_discharge, entries_charge,
                                     entries_discharge, working_ion_entry)
Пример #10
0
    def plot_element_spd(self, elements, ax, order=['s', 'p', 'd'], scale_factor=5, color_dict=None, legend=True, linewidth=0.75, band_color='black'):
        """
        This function plots the projected band structure on the s, p, and d orbitals for each specified element in the calculated structure.

        Parameters:
            ax (matplotlib.pyplot.axis): Axis to plot the data on
            elements (list): List of element symbols to project onto
            order (list): This determines the order in which the points are plotted on the
                graph. This is an option because sometimes certain orbitals can be hidden
                under other orbitals because they have a larger weight. For example, if the
                signitures of the d orbitals are greater than that of the s orbitals, it
                might be smart to choose ['d', 'p', 's'] as the order so the s orbitals are
                plotted over the d orbitals.
            scale_factor (float): Factor to scale weights. This changes the size of the
                points in the scatter plot
            color_dict (dict[str][str]): This option allow the colors of the s, p, and d
                orbitals to be specified. Should be in the form of:
                {'s': <s color>, 'p': <p color>, 'd': <d color>}
            legend (bool): Determines if the legend should be included or not.
            linewidth (float): Line width of the plain band structure plotted in the background
            band_color (string): Color of the plain band structure
        """
        scale_factor = scale_factor ** 1.5

        self.plot_plain(ax=ax, linewidth=linewidth, color=band_color)

        element_dict = self._sum_elements(
            elements=elements, orbitals=True, spd=True)

        if color_dict is None:
            color_dict = {
                's': self.color_dict[0],
                'p': self.color_dict[1],
                'd': self.color_dict[2],
                'f': self.color_dict[4],
            }

        plot_element = {element: pd.DataFrame() for element in elements}

        if self.forbitals and 'f' not in order:
            order.append('f')

        plot_band = []
        plot_wave_vec = []

        for band in element_dict:
            plot_band.extend(self.bands_dict[band])
            plot_wave_vec.extend(range(len(self.bands_dict[band])))
            for element in elements:
                plot_element[element] = plot_element[element].append(
                    element_dict[band][element])

        for (i, element) in enumerate(elements):
            if self.forbitals:
                electronic_structure = Element(
                    element).full_electronic_structure
                if not np.isin('f', electronic_structure):
                    order = order.remove('f')
            for orbital in order:
                ax.scatter(
                    plot_wave_vec,
                    plot_band,
                    c=color_dict[orbital],
                    s=scale_factor * plot_element[element][orbital],
                    zorder=1,
                )

        if legend:
            legend_lines = []
            legend_labels = []
            for element in elements:
                for orbital in order:
                    legend_lines.append(plt.Line2D(
                        [0],
                        [0],
                        marker='o',
                        markersize=2,
                        linestyle='',
                        color=color_dict[orbital])
                    )
                    legend_labels.append(
                        f'{element}(${orbital}$)'
                    )

            leg = ax.get_legend()

            if leg is None:
                handles = legend_lines
                labels = legend_labels
            else:
                handles = [l._legmarker for l in leg.legendHandles]
                labels = [text._text for text in leg.texts]
                handles.extend(legend_lines)
                labels.extend(legend_labels)

            ax.legend(
                handles,
                labels,
                ncol=1,
                loc='upper left',
                fontsize=5,
                bbox_to_anchor=(1, 1),
                borderaxespad=0,
                frameon=False,
                handletextpad=0.1,
            )
Пример #11
0
 def get_ionic_radius(element, oxidationNo):
     if element == 'Mn' and oxidationNo == 4:
         radius = 0.67
     else:
         radius = Element(element).ionic_radii[oxidationNo]
     return radius
Пример #12
0
    def compute_corrections(self, exp_entries: list, calc_entries: dict) -> dict:
        """
        Computes the corrections and fills in correction, corrections_std_error, and corrections_dict.

        Args:
            exp_entries: list of dictionary objects with the following keys/values:
                    {"formula": chemical formula, "exp energy": formation energy in eV/formula unit,
                    "uncertainty": uncertainty in formation energy}
            calc_entries: dictionary of computed entries, of the form {chemical formula: ComputedEntry}

        Raises:
            ValueError: calc_compounds is missing an entry
        """

        self.exp_compounds = exp_entries
        self.calc_compounds = calc_entries

        self.names: List[str] = []
        self.diffs: List[float] = []
        self.coeff_mat: List[List[float]] = []
        self.exp_uncer: List[float] = []

        # remove any corrections in calc_compounds
        for entry in self.calc_compounds.values():
            entry.correction = 0

        for cmpd_info in self.exp_compounds:

            # to get consistent element ordering in formula
            name = Composition(cmpd_info["formula"]).reduced_formula

            allow = True

            compound = self.calc_compounds.get(name, None)
            if not compound:
                warnings.warn(
                    "Compound {} is not found in provided computed entries and is excluded from the fit".format(name)
                )
                continue

            # filter out compounds with large uncertainties
            relative_uncertainty = abs(cmpd_info["uncertainty"] / cmpd_info["exp energy"])
            if relative_uncertainty > self.max_error:
                allow = False
                warnings.warn(
                    "Compound {} is excluded from the fit due to high experimental uncertainty ({}%)".format(
                        name, relative_uncertainty
                    )
                )

            # filter out compounds containing certain polyanions
            for anion in self.exclude_polyanions:
                if anion in name or anion in cmpd_info["formula"]:
                    allow = False
                    warnings.warn(
                        "Compound {} contains the polyanion {} and is excluded from the fit".format(name, anion)
                    )
                    break

            # filter out compounds that are unstable
            if isinstance(self.allow_unstable, float):
                try:
                    eah = compound.data["e_above_hull"]
                except KeyError:
                    raise ValueError("Missing e above hull data")
                if eah > self.allow_unstable:
                    allow = False
                    warnings.warn(
                        "Compound {} is unstable and excluded from the fit (e_above_hull = {})".format(name, eah)
                    )

            if allow:
                comp = Composition(name)
                elems = list(comp.as_dict())

                reactants = []
                for elem in elems:
                    try:
                        elem_name = Composition(elem).reduced_formula
                        reactants.append(self.calc_compounds[elem_name])
                    except KeyError:
                        raise ValueError("Computed entries missing " + elem)

                rxn = ComputedReaction(reactants, [compound])
                rxn.normalize_to(comp)
                energy = rxn.calculated_reaction_energy

                coeff = []
                for specie in self.species:
                    if specie == "oxide":
                        if compound.data["oxide_type"] == "oxide":
                            coeff.append(comp["O"])
                            self.oxides.append(name)
                        else:
                            coeff.append(0)
                    elif specie == "peroxide":
                        if compound.data["oxide_type"] == "peroxide":
                            coeff.append(comp["O"])
                            self.peroxides.append(name)
                        else:
                            coeff.append(0)
                    elif specie == "superoxide":
                        if compound.data["oxide_type"] == "superoxide":
                            coeff.append(comp["O"])
                            self.superoxides.append(name)
                        else:
                            coeff.append(0)
                    elif specie == "S":
                        if Element("S") in comp:
                            sf_type = "sulfide"
                            if compound.data.get("sulfide_type"):
                                sf_type = compound.data["sulfide_type"]
                            elif hasattr(compound, "structure"):
                                sf_type = sulfide_type(compound.structure)
                            if sf_type == "sulfide":
                                coeff.append(comp["S"])
                                self.sulfides.append(name)
                            else:
                                coeff.append(0)
                        else:
                            coeff.append(0)
                    else:
                        try:
                            coeff.append(comp[specie])
                        except ValueError:
                            raise ValueError("We can't detect this specie: {}".format(specie))

                self.names.append(name)
                self.diffs.append((cmpd_info["exp energy"] - energy) / comp.num_atoms)
                self.coeff_mat.append([i / comp.num_atoms for i in coeff])
                self.exp_uncer.append((cmpd_info["uncertainty"]) / comp.num_atoms)

        # for any exp entries with no uncertainty value, assign average uncertainty value
        sigma = np.array(self.exp_uncer)
        sigma[sigma == 0] = np.nan

        with warnings.catch_warnings():
            warnings.simplefilter(
                "ignore", category=RuntimeWarning
            )  # numpy raises warning if the entire array is nan values
            mean_uncer = np.nanmean(sigma)

        sigma = np.where(np.isnan(sigma), mean_uncer, sigma)

        if np.isnan(mean_uncer):
            # no uncertainty values for any compounds, don't try to weight
            popt, self.pcov = curve_fit(_func, self.coeff_mat, self.diffs, p0=np.ones(len(self.species)))
        else:
            popt, self.pcov = curve_fit(
                _func,
                self.coeff_mat,
                self.diffs,
                p0=np.ones(len(self.species)),
                sigma=sigma,
                absolute_sigma=True,
            )
        self.corrections = popt.tolist()
        self.corrections_std_error = np.sqrt(np.diag(self.pcov)).tolist()
        for i in range(len(self.species)):
            self.corrections_dict[self.species[i]] = (
                round(self.corrections[i], 3),
                round(self.corrections_std_error[i], 4),
            )
        return self.corrections_dict
Пример #13
0
    def _get_structure(self, data, primitive, substitution_dictionary=None):
        """
        Generate structure from part of the cif.
        """
        # Symbols often representing
        # common representations for elements/water in cif files
        special_symbols = {"D": "D", "Hw": "H", "Ow": "O", "Wat": "O",
                           "wat": "O"}
        elements = [el.symbol for el in Element]

        lattice = self.get_lattice(data)
        self.symmetry_operations = self.get_symops(data)
        oxi_states = self.parse_oxi_states(data)

        coord_to_species = OrderedDict()

        def parse_symbol(sym):

            if substitution_dictionary:
                return substitution_dictionary.get(sym)
            elif sym in ['OH', 'OH2']:
                warnings.warn("Symbol '{}' not recognized".format(sym))
                return ""
            else:
                m = re.findall(r"w?[A-Z][a-z]*", sym)
                if m and m != "?":
                    return m[0]
                return ""

        def get_matching_coord(coord):
            for op in self.symmetry_operations:
                c = op.operate(coord)
                for k in coord_to_species.keys():
                    if np.allclose(pbc_diff(c, k), (0, 0, 0),
                                   atol=self._site_tolerance):
                        return tuple(k)
            return False

        ############################################################
        """
        This part of the code deals with handling formats of data as found in
        CIF files extracted from the Springer Materials/Pauling File
        databases, and that are different from standard ICSD formats.
        """

        # Check to see if "_atom_site_type_symbol" exists, as some test CIFs do
        # not contain this key.
        if "_atom_site_type_symbol" in data.data.keys():

            # Keep a track of which data row needs to be removed.
            # Example of a row: Nb,Zr '0.8Nb + 0.2Zr' .2a .m-3m 0 0 0 1 14
            # 'rhombic dodecahedron, Nb<sub>14</sub>'
            # Without this code, the above row in a structure would be parsed
            # as an ordered site with only Nb (since
            # CifParser would try to parse the first two characters of the
            # label "Nb,Zr") and occupancy=1.
            # However, this site is meant to be a disordered site with 0.8 of
            # Nb and 0.2 of Zr.
            idxs_to_remove = []

            for idx, el_row in enumerate(data["_atom_site_label"]):

                # CIF files from the Springer Materials/Pauling File have
                # switched the label and symbol. Thus, in the
                # above shown example row, '0.8Nb + 0.2Zr' is the symbol.
                # Below, we split the strings on ' + ' to
                # check if the length (or number of elements) in the label and
                # symbol are equal.
                if len(data["_atom_site_type_symbol"][idx].split(' + ')) > \
                        len(data["_atom_site_label"][idx].split(' + ')):

                    # Dictionary to hold extracted elements and occupancies
                    els_occu = {}

                    # parse symbol to get element names and occupancy and store
                    # in "els_occu"
                    symbol_str = data["_atom_site_type_symbol"][idx]
                    symbol_str_lst = symbol_str.split(' + ')
                    for elocc_idx in range(len(symbol_str_lst)):
                        # Remove any bracketed items in the string
                        symbol_str_lst[elocc_idx] = re.sub(
                            '\([0-9]*\)', '', symbol_str_lst[elocc_idx].strip())

                        # Extract element name and its occupancy from the
                        # string, and store it as a
                        # key-value pair in "els_occ".
                        els_occu[str(re.findall('\D+', symbol_str_lst[
                            elocc_idx].strip())[1]).replace('<sup>', '')] = \
                            float('0' + re.findall('\.?\d+', symbol_str_lst[
                                elocc_idx].strip())[1])

                    x = str2float(data["_atom_site_fract_x"][idx])
                    y = str2float(data["_atom_site_fract_y"][idx])
                    z = str2float(data["_atom_site_fract_z"][idx])

                    coord = (x, y, z)
                    # Add each partially occupied element on the site coordinate
                    for et in els_occu:
                        match = get_matching_coord(coord)
                        if not match:
                            coord_to_species[coord] = Composition(
                                {parse_symbol(et): els_occu[parse_symbol(et)]})
                        else:
                            coord_to_species[match] += {
                                parse_symbol(et): els_occu[parse_symbol(et)]}
                    idxs_to_remove.append(idx)

            # Remove the original row by iterating over all keys in the CIF
            # data looking for lists, which indicates
            # multiple data items, one for each row, and remove items from the
            # list that corresponds to the removed row,
            # so that it's not processed by the rest of this function (which
            # would result in an error).
            for cif_key in data.data:
                if type(data.data[cif_key]) == list:
                    for id in sorted(idxs_to_remove, reverse=True):
                        del data.data[cif_key][id]

        ############################################################

        for i in range(len(data["_atom_site_label"])):
            symbol = parse_symbol(data["_atom_site_label"][i])

            if symbol:
                if symbol not in elements and symbol not in special_symbols:
                    symbol = symbol[:2]
            else:
                continue
            # make sure symbol was properly parsed from _atom_site_label
            # otherwise get it from _atom_site_type_symbol
            try:
                if symbol in special_symbols:
                    get_el_sp(special_symbols.get(symbol))
                else:
                    Element(symbol)
            except (KeyError, ValueError):
                # sometimes the site doesn't have the type_symbol.
                # we then hope the type_symbol can be parsed from the label
                if "_atom_site_type_symbol" in data.data.keys():
                    symbol = data["_atom_site_type_symbol"][i]

            if oxi_states is not None:
                if symbol in special_symbols:
                    el = get_el_sp(special_symbols.get(symbol) +
                                   str(oxi_states[symbol]))
                else:
                    el = Specie(symbol, oxi_states.get(symbol, 0))
            else:

                el = get_el_sp(special_symbols.get(symbol, symbol))

            x = str2float(data["_atom_site_fract_x"][i])
            y = str2float(data["_atom_site_fract_y"][i])
            z = str2float(data["_atom_site_fract_z"][i])
            try:
                occu = str2float(data["_atom_site_occupancy"][i])
            except (KeyError, ValueError):
                occu = 1

            if occu > 0:
                coord = (x, y, z)
                match = get_matching_coord(coord)
                if not match:
                    coord_to_species[coord] = Composition({el: occu})
                else:
                    coord_to_species[match] += {el: occu}

        if any([sum(c.values()) > 1 for c in coord_to_species.values()]):
            warnings.warn("Some occupancies sum to > 1! If they are within "
                          "the tolerance, they will be rescaled.")

        allspecies = []
        allcoords = []

        if coord_to_species.items():
            for species, group in groupby(
                    sorted(list(coord_to_species.items()), key=lambda x: x[1]),
                    key=lambda x: x[1]):
                tmp_coords = [site[0] for site in group]

                coords = self._unique_coords(tmp_coords)

                allcoords.extend(coords)
                allspecies.extend(len(coords) * [species])

            # rescale occupancies if necessary
            for i, species in enumerate(allspecies):
                totaloccu = sum(species.values())
                if 1 < totaloccu <= self._occupancy_tolerance:
                    allspecies[i] = species / totaloccu

        if allspecies and len(allspecies) == len(allcoords):
            struct = Structure(lattice, allspecies, allcoords)
            struct = struct.get_sorted_structure()

            if primitive:
                struct = struct.get_primitive_structure()
                struct = struct.get_reduced_structure()
            return struct
Пример #14
0
    def test_get_data(self):
        props = {
            "energy",
            "energy_per_atom",
            "formation_energy_per_atom",
            "nsites",
            "unit_cell_formula",
            "pretty_formula",
            "is_hubbard",
            "elements",
            "nelements",
            "e_above_hull",
            "hubbards",
            "is_compatible",
            "task_ids",
            "density",
            "icsd_ids",
            "total_magnetization",
        }
        mpid = "mp-1143"
        vals = requests.get(
            f"http://www.materialsproject.org/materials/{mpid}/json/")
        expected_vals = vals.json()

        for prop in props:
            if prop not in [
                    "hubbards",
                    "unit_cell_formula",
                    "elements",
                    "icsd_ids",
                    "task_ids",
            ]:
                val = self.rester.get_data(mpid, prop=prop)[0][prop]
                if prop in ["energy", "energy_per_atom"]:
                    prop = "final_" + prop
                self.assertAlmostEqual(expected_vals[prop], val, 2,
                                       f"Failed with property {prop}")
            elif prop in ["elements", "icsd_ids", "task_ids"]:
                upstream_vals = set(
                    self.rester.get_data(mpid, prop=prop)[0][prop])
                self.assertLessEqual(set(expected_vals[prop]), upstream_vals)
            else:
                self.assertEqual(
                    expected_vals[prop],
                    self.rester.get_data(mpid, prop=prop)[0][prop],
                )

        props = ["structure", "initial_structure", "final_structure", "entry"]
        for prop in props:
            obj = self.rester.get_data(mpid, prop=prop)[0][prop]
            if prop.endswith("structure"):
                self.assertIsInstance(obj, Structure)
            elif prop == "entry":
                obj = self.rester.get_data(mpid, prop=prop)[0][prop]
                self.assertIsInstance(obj, ComputedEntry)

        # Test chemsys search
        data = self.rester.get_data("Fe-Li-O", prop="unit_cell_formula")
        self.assertTrue(len(data) > 1)
        elements = {Element("Li"), Element("Fe"), Element("O")}
        for d in data:
            self.assertTrue(
                set(Composition(
                    d["unit_cell_formula"]).elements).issubset(elements))

        self.assertRaises(MPRestError, self.rester.get_data, "Fe2O3",
                          "badmethod")
Пример #15
0
 def test_is_metal(self):
     for metal in ["Fe", "Eu", "Li", "Ca", "In"]:
         self.assertTrue(Element(metal).is_metal)
     for non_metal in ["Ge", "Si", "O", "He"]:
         self.assertFalse(Element(non_metal).is_metal)
Пример #16
0
def get_structure_type(structure, write_poscar_from_cluster=False):
    """
    This is a topology-scaling algorithm used to describe the
    periodicity of bonded clusters in a bulk structure.

    Args:
        structure (structure): Pymatgen structure object to classify.
        write_poscar_from_cluster (bool): Set to True to write a
            POSCAR from the sites in the cluster.

    Returns:
        string. 'molecular' (0D), 'chain', 'layered', 'heterogeneous'
            (intercalated 3D), or 'conventional' (3D)
    """

    # The conventional standard structure is much easier to work
    # with.

    structure = SpacegroupAnalyzer(
        structure).get_conventional_standard_structure()

    # Noble gases don't have well-defined bonding radii.
    if not len([
            e for e in structure.composition
            if e.symbol in ['He', 'Ne', 'Ar', 'Kr', 'Xe']
    ]) == 0:
        type = 'noble gas'
    else:
        if len(structure.sites) < 45:
            structure.make_supercell(2)

        # Create a dict of sites as keys and lists of their
        # bonded neighbors as values.
        sites = structure.sites
        bonds = {}
        for site in sites:
            bonds[site] = []

        for i in range(len(sites)):
            site_1 = sites[i]
            for site_2 in sites[i + 1:]:
                if (site_1.distance(site_2) < float(
                        Element(site_1.specie).atomic_radius +
                        Element(site_2.specie).atomic_radius) * 1.1):
                    bonds[site_1].append(site_2)
                    bonds[site_2].append(site_1)

        # Assimilate all bonded atoms in a cluster; terminate
        # when it stops growing.
        cluster_terminated = False
        while not cluster_terminated:
            original_cluster_size = len(bonds[sites[0]])
            for site in bonds[sites[0]]:
                bonds[sites[0]] += [
                    s for s in bonds[site] if s not in bonds[sites[0]]
                ]
            if len(bonds[sites[0]]) == original_cluster_size:
                cluster_terminated = True

        original_cluster = bonds[sites[0]]

        if len(bonds[sites[0]]) == 0:  # i.e. the cluster is a single atom.
            type = 'molecular'
        elif len(bonds[sites[0]]) == len(sites):  # i.e. all atoms are bonded.
            type = 'conventional'
        else:
            # If the cluster's composition is not equal to the
            # structure's overall composition, it is a heterogeneous
            # compound.
            cluster_composition_dict = {}
            for site in bonds[sites[0]]:
                if Element(site.specie) in cluster_composition_dict:
                    cluster_composition_dict[Element(site.specie)] += 1
                else:
                    cluster_composition_dict[Element(site.specie)] = 1
            uniform = True
            if len(cluster_composition_dict):
                cmp = Composition.from_dict(cluster_composition_dict)
                if cmp.reduced_formula != structure.composition.reduced_formula:
                    uniform = False
            if not uniform:
                type = 'heterogeneous'
            else:
                # Make a 2x2x2 supercell and recalculate the
                # cluster's new size. If the new cluster size is
                # the same as the old size, it is a non-periodic
                # molecule. If it is 2x as big, it's a 1D chain.
                # If it's 4x as big, it is a layered material.
                old_cluster_size = len(bonds[sites[0]])
                structure.make_supercell(2)
                sites = structure.sites
                bonds = {}
                for site in sites:
                    bonds[site] = []

                for i in range(len(sites)):
                    site_1 = sites[i]
                    for site_2 in sites[i + 1:]:
                        if (site_1.distance(site_2) < float(
                                Element(site_1.specie).atomic_radius +
                                Element(site_2.specie).atomic_radius) * 1.1):
                            bonds[site_1].append(site_2)
                            bonds[site_2].append(site_1)

                cluster_terminated = False
                while not cluster_terminated:
                    original_cluster_size = len(bonds[sites[0]])
                    for site in bonds[sites[0]]:
                        bonds[sites[0]] += [
                            s for s in bonds[site] if s not in bonds[sites[0]]
                        ]
                    if len(bonds[sites[0]]) == original_cluster_size:
                        cluster_terminated = True

                if len(bonds[sites[0]]) != 4 * old_cluster_size:
                    type = 'molecular'
                else:
                    type = 'layered'

    if write_poscar_from_cluster:
        Structure.from_sites(original_cluster).to('POSCAR', 'POSCAR')

    return type
Пример #17
0
def predict_k_g_list(material_id_list, api_key=API_KEY, query_engine=None):
    """
    Predict bulk (K) and shear (G) moduli for a list of materials.
    :param material_id_list: list of material-ID strings
    :param api_key: The API key used by pymatgen.matproj.rest.MPRester to connect to Materials Project
    :param query_engine: (Optional) QueryEngine object used to query materials instead of MPRester
 
    :return: (matid_list, predicted_k_list, predicted_g_list, caveats_list)
    Note that len(matid_list) may be less than len(material_id_list),
    if any requested material-IDs are not found.
    """

    if len(material_id_list) == 0 or not isinstance(material_id_list, list):
        return (None, None, None, None
                )  # material_id_list not properly specified

    lvpa_list = []
    cepa_list = []
    rowH1A_list = []
    rowHn3A_list = []
    xH4A_list = []
    xHn4A_list = []
    matid_list = []
    k_list = []
    g_list = []
    caveats_list = []
    aiab_problem_list = []

    # TODO: figure out if closing the query engine (using 'with' ctx mgr) is an issue
    # If it is a problem then try manually doing a session.close() for MPRester, but ignore for qe

    mpr = _get_mp_query(api_key, query_engine)
    for entry in mpr.query(criteria={"task_id": {
            "$in": material_id_list
    }},
                           properties=[
                               "material_id", "pretty_formula", "nsites",
                               "volume", "energy_per_atom", "is_hubbard"
                           ]):

        caveats_str = ''
        aiab_flag = False
        f_block_flag = False
        weight_list = []
        energy_list = []
        row_list = []
        x_list = []

        # Construct per-element lists for this material
        composition = Composition(str(entry["pretty_formula"]))
        for element_key, amount in composition.get_el_amt_dict().iteritems():
            element = Element(element_key)
            weight_list.append(composition.get_atomic_fraction(element))
            aiab_energy = get_element_aiab_energy(
                element_key)  # aiab = atom-in-a-box
            if aiab_energy is None:
                aiab_flag = True
                break
            energy_list.append(aiab_energy)
            if element.block == 'f':
                f_block_flag = True
            row_list.append(element.row)
            x_list.append(element.X)

        # On error, add material to aiab_problem_list and continue with next material
        if aiab_flag:
            aiab_problem_list.append(str(entry["material_id"]))
            continue

        # Check caveats
        if bool(entry["is_hubbard"]):
            if len(caveats_str) > 0: caveats_str += " "
            caveats_str += CAVEAT_HUBBARD
        if f_block_flag:
            if len(caveats_str) > 0: caveats_str += " "
            caveats_str += CAVEAT_F_BLOCK

        # Calculate intermediate weighted averages (WA) for this material
        ewa = np.average(energy_list,
                         weights=weight_list)  # atom-in-a-box energy WA

        print str(entry["material_id"])

        # Append descriptors for this material to descriptor lists
        lvpa_list.append(
            math.log10(float(entry["volume"]) / float(entry["nsites"])))
        cepa_list.append(float(entry["energy_per_atom"]) - ewa)
        rowH1A_list.append(holder_mean(row_list, 1.0, weights=weight_list))
        rowHn3A_list.append(holder_mean(row_list, -3.0, weights=weight_list))
        xH4A_list.append(holder_mean(x_list, 4.0, weights=weight_list))
        xHn4A_list.append(holder_mean(x_list, -4.0, weights=weight_list))
        matid_list.append(str(entry["material_id"]))
        caveats_list.append(caveats_str)

    if isinstance(mpr, MPRester):
        mpr.session.close()

    # Check that at least one valid material was provided
    num_predictions = len(matid_list)
    if num_predictions > 0:
        # Construct descriptor arrays
        if (len(lvpa_list) != num_predictions
                or len(cepa_list) != num_predictions
                or len(rowH1A_list) != num_predictions
                or len(rowHn3A_list) != num_predictions
                or len(xH4A_list) != num_predictions
                or len(xHn4A_list) != num_predictions):
            return (None, None, None, None)
        k_descriptors = np.ascontiguousarray(
            [lvpa_list, rowH1A_list, cepa_list, xHn4A_list], dtype=float)
        g_descriptors = np.ascontiguousarray(
            [cepa_list, lvpa_list, rowHn3A_list, xH4A_list], dtype=float)

        # Allocate prediction arrays
        k_predictions = np.empty(num_predictions)
        g_predictions = np.empty(num_predictions)

        # Make predictions
        k_filename = os.path.join(os.path.dirname(__file__), DATAFILE_K)
        g_filename = os.path.join(os.path.dirname(__file__), DATAFILE_G)
        gbml.core.predict(k_filename, num_predictions, k_descriptors,
                          k_predictions)
        gbml.core.predict(g_filename, num_predictions, g_descriptors,
                          g_predictions)

        k_list = np.power(10.0, k_predictions).tolist()
        g_list = np.power(10.0, g_predictions).tolist()

    # Append aiab problem cases
    for entry in aiab_problem_list:
        matid_list.append(entry)
        k_list.append(None)
        g_list.append(None)
        caveats_list.append(CAVEAT_AIAB)

    if len(matid_list) == 0:
        return (None, None, None, None)
    else:
        return (matid_list, k_list, g_list, caveats_list)
Пример #18
0
    def get_correction(self, entry) -> float:
        """
        :param entry: A ComputedEntry/ComputedStructureEntry
        :return: Correction.
        """
        comp = entry.composition
        if len(comp) == 1:  # Skip element entry
            return 0

        correction = 0
        # Check for sulfide corrections
        if Element("S") in comp:
            sf_type = "sulfide"
            if entry.data.get("sulfide_type"):
                sf_type = entry.data["sulfide_type"]
            elif hasattr(entry, "structure"):
                sf_type = sulfide_type(entry.structure)
            if sf_type in self.sulfide_correction:
                correction += self.sulfide_correction[sf_type] * comp["S"]

        # Check for oxide, peroxide, superoxide, and ozonide corrections.
        if Element("O") in comp:
            if self.correct_peroxide:
                if entry.data.get("oxide_type"):
                    if entry.data["oxide_type"] in self.oxide_correction:
                        ox_corr = self.oxide_correction[
                            entry.data["oxide_type"]]
                        correction += ox_corr * comp["O"]
                    if entry.data["oxide_type"] == "hydroxide":
                        ox_corr = self.oxide_correction["oxide"]
                        correction += ox_corr * comp["O"]

                elif hasattr(entry, "structure"):
                    ox_type, nbonds = oxide_type(entry.structure,
                                                 1.05,
                                                 return_nbonds=True)
                    if ox_type in self.oxide_correction:
                        correction += self.oxide_correction[ox_type] * \
                                      nbonds
                    elif ox_type == "hydroxide":
                        correction += self.oxide_correction["oxide"] * \
                                      comp["O"]
                else:
                    warnings.warn(
                        "No structure or oxide_type parameter present. Note "
                        "that peroxide/superoxide corrections are not as "
                        "reliable and relies only on detection of special"
                        "formulas, e.g., Li2O2.")
                    rform = entry.composition.reduced_formula
                    if rform in UCorrection.common_peroxides:
                        correction += self.oxide_correction["peroxide"] * \
                                      comp["O"]
                    elif rform in UCorrection.common_superoxides:
                        correction += self.oxide_correction["superoxide"] * \
                                      comp["O"]
                    elif rform in UCorrection.ozonides:
                        correction += self.oxide_correction["ozonide"] * \
                                      comp["O"]
                    elif Element("O") in comp.elements and len(comp.elements) \
                            > 1:
                        correction += self.oxide_correction['oxide'] * \
                                      comp["O"]
            else:
                correction += self.oxide_correction['oxide'] * comp["O"]

        return correction
Пример #19
0
 def test_init(self):
     d = {'Fe': 1, Element('Fe'): 1}
     self.assertRaises(ValueError, ChemicalPotential, d)
     for k in ChemicalPotential(Fe=1).keys():
         self.assertIsInstance(k, Element)
Пример #20
0
    def write_data_file(self, organism, job_dir_path, composition_space):
        """
        Writes the file (called in.data) containing the structure that LAMMPS
        reads.

        Args:
            organism: the Organism whose structure to write

            job_dir_path: the path the job directory (as a string) where the
                file will be written

            composition_space: the CompositionSpace of the search
        """

        # get xhi, yhi and zhi from the lattice vectors
        lattice_coords = organism.cell.lattice.matrix
        xhi = lattice_coords[0][0]
        yhi = lattice_coords[1][1]
        zhi = lattice_coords[2][2]
        box_size = [[0.0, xhi], [0.0, yhi], [0.0, zhi]]

        # get xy, xz and yz from the lattice vectors
        xy = lattice_coords[1][0]
        xz = lattice_coords[2][0]
        yz = lattice_coords[2][1]

        # get a list of the elements from the lammps input script to
        # preserve their order of appearance
        # TODO: not all formats give the element symbols at the end of the line
        #       containing the pair_coeff keyword. Find a better way.
        num_elements = len(composition_space.get_all_elements())
        if num_elements == 1:
            all_elements = composition_space.get_all_elements()
        else:
            with open(self.input_script, 'r') as f:
                lines = f.readlines()
                for line in lines:
                    if 'pair_coeff' in line:
                        element_symbols = line.split()[-1 * num_elements:]
                all_elements = []
                for symbol in element_symbols:
                    all_elements.append(Element(symbol))

        # get the dictionary of atomic masses - set the atom types to the order
        # of their appearance in the lammps input script
        atomic_masses_dict = {}
        for i in range(len(all_elements)):
            atomic_masses_dict[all_elements[i].symbol] = [
                i + 1, float(all_elements[i].atomic_mass)
            ]

        # get the atoms data
        atoms_data = LammpsData.get_atoms_data(organism.cell,
                                               atomic_masses_dict,
                                               set_charge=True)

        # make a LammpsData object
        lammps_data = LammpsData(box_size, atomic_masses_dict.values(),
                                 atoms_data)

        # write the data to a file
        # This method doesn't write the tilts, so we have to add those.
        lammps_data.write_file(job_dir_path + '/in.data')

        # read the in.data file as a list of strings
        with open(job_dir_path + '/in.data', 'r') as f:
            lines = f.readlines()

        # find the location to insert the tilts
        insertion_index = 0
        for line in lines:
            if 'zhi' in line:
                insertion_index = lines.index(line) + 1

        # build the string containing the tilts and add it
        tilts_string = str(xy) + ' ' + str(xz) + ' ' + str(yz) + ' xy xz yz\n'
        lines.insert(insertion_index, tilts_string)

        # overwrite the in.data file with the new contents, including the tilts
        with open(job_dir_path + '/in.data', 'w') as f:
            for line in lines:
                f.write('%s' % line)
Пример #21
0
    def from_entries(cls, entry1, entry2, working_ion_entry):
        """
        Args:
            entry1: Entry corresponding to one of the entries in the voltage step.
            entry2: Entry corresponding to the other entry in the voltage step.
            working_ion_entry: A single ComputedEntry or PDEntry representing
                the element that carries charge across the battery, e.g. Li.
        """
        # initialize some internal variables
        working_element = working_ion_entry.composition.elements[0]

        entry_charge = entry1
        entry_discharge = entry2
        if entry_charge.composition.get_atomic_fraction(working_element) > entry2.composition.get_atomic_fraction(
            working_element
        ):
            (entry_charge, entry_discharge) = (entry_discharge, entry_charge)

        comp_charge = entry_charge.composition
        comp_discharge = entry_discharge.composition

        ion_sym = working_element.symbol

        frame_charge_comp = Composition({el: comp_charge[el] for el in comp_charge if el.symbol != ion_sym})
        frame_discharge_comp = Composition({el: comp_discharge[el] for el in comp_discharge if el.symbol != ion_sym})

        # Data validation

        # check that the ion is just a single element
        if not working_ion_entry.composition.is_element:
            raise ValueError("VoltagePair: The working ion specified must be " "an element")

        # check that at least one of the entries contains the working element
        if (
            not comp_charge.get_atomic_fraction(working_element) > 0
            and not comp_discharge.get_atomic_fraction(working_element) > 0
        ):
            raise ValueError("VoltagePair: The working ion must be present in " "one of the entries")

        # check that the entries do not contain the same amount of the workin
        # element
        if comp_charge.get_atomic_fraction(working_element) == comp_discharge.get_atomic_fraction(working_element):
            raise ValueError("VoltagePair: The working ion atomic percentage " "cannot be the same in both the entries")

        # check that the frameworks of the entries are equivalent
        if not frame_charge_comp.reduced_formula == frame_discharge_comp.reduced_formula:
            raise ValueError("VoltagePair: the specified entries must have the" " same compositional framework")

        # Initialize normalization factors, charged and discharged entries

        valence_list = Element(ion_sym).oxidation_states
        working_ion_valence = abs(max(valence_list))

        (
            framework,
            norm_charge,
        ) = frame_charge_comp.get_reduced_composition_and_factor()
        norm_discharge = frame_discharge_comp.get_reduced_composition_and_factor()[1]

        # Initialize normalized properties
        if hasattr(entry_charge, "structure"):
            _vol_charge = entry_charge.structure.volume / norm_charge
        else:
            _vol_charge = entry_charge.data.get("volume")

        if hasattr(entry_discharge, "structure"):
            _vol_discharge = entry_discharge.structure.volume / norm_discharge
        else:
            _vol_discharge = entry_discharge.data.get("volume")

        comp_charge = entry_charge.composition
        comp_discharge = entry_discharge.composition

        _mass_charge = comp_charge.weight / norm_charge
        _mass_discharge = comp_discharge.weight / norm_discharge

        _num_ions_transferred = (comp_discharge[working_element] / norm_discharge) - (
            comp_charge[working_element] / norm_charge
        )

        _voltage = (
            ((entry_charge.energy / norm_charge) - (entry_discharge.energy / norm_discharge)) / _num_ions_transferred
            + working_ion_entry.energy_per_atom
        ) / working_ion_valence
        _mAh = _num_ions_transferred * Charge(1, "e").to("C") * Time(1, "s").to("h") * N_A * 1000 * working_ion_valence

        _frac_charge = comp_charge.get_atomic_fraction(working_element)
        _frac_discharge = comp_discharge.get_atomic_fraction(working_element)

        vpair = cls(
            voltage=_voltage,
            mAh=_mAh,
            mass_charge=_mass_charge,
            mass_discharge=_mass_discharge,
            vol_charge=_vol_charge,
            vol_discharge=_vol_discharge,
            frac_charge=_frac_charge,
            frac_discharge=_frac_discharge,
            working_ion_entry=working_ion_entry,
            entry_charge=entry_charge,
            entry_discharge=entry_discharge,
            _framework_formula=framework.reduced_formula,
        )

        # Step 4: add (optional) hull and muO2 data
        vpair.decomp_e_charge = entry_charge.data.get("decomposition_energy", None)
        vpair.decomp_e_discharge = entry_discharge.data.get("decomposition_energy", None)

        vpair.muO2_charge = entry_charge.data.get("muO2", None)
        vpair.muO2_discharge = entry_discharge.data.get("muO2", None)

        return vpair
Пример #22
0
    def get_relaxed_cell(self, atom_dump_path, data_in_path, element_symbols):
        """
        Parses the relaxed cell from the dump.atom file.

        Returns the relaxed cell as a Cell object.

        Args:
            atom_dump_path: the path (as a string) to the dump.atom file

            in_data_path: the path (as a string) to the in.data file

            element_symbols: a tuple containing the set of chemical symbols of
                all the elements in the compositions space
        """

        # read the dump.atom file as a list of strings
        with open(atom_dump_path, 'r') as atom_dump:
            lines = atom_dump.readlines()

        # get the lattice vectors
        a_data = lines[5].split()
        b_data = lines[6].split()
        c_data = lines[7].split()

        # parse the tilt factors
        xy = float(a_data[2])
        xz = float(b_data[2])
        yz = float(c_data[2])

        # parse the bounds
        xlo_bound = float(a_data[0])
        xhi_bound = float(a_data[1])
        ylo_bound = float(b_data[0])
        yhi_bound = float(b_data[1])
        zlo_bound = float(c_data[0])
        zhi_bound = float(c_data[1])

        # compute xlo, xhi, ylo, yhi, zlo and zhi according to the conversion
        # given by LAMMPS
        # http://lammps.sandia.gov/doc/Section_howto.html#howto-12
        xlo = xlo_bound - min([0.0, xy, xz, xy + xz])
        xhi = xhi_bound - max([0.0, xy, xz, xy + xz])
        ylo = ylo_bound - min(0.0, yz)
        yhi = yhi_bound - max([0.0, yz])
        zlo = zlo_bound
        zhi = zhi_bound

        # construct a Lattice object from the lo's and hi's and tilts
        a = [xhi - xlo, 0.0, 0.0]
        b = [xy, yhi - ylo, 0.0]
        c = [xz, yz, zhi - zlo]
        relaxed_lattice = Lattice([a, b, c])

        # get the number of atoms
        num_atoms = int(lines[3])

        # get the atom types and their Cartesian coordinates
        types = []
        relaxed_cart_coords = []
        for i in range(num_atoms):
            atom_info = lines[9 + i].split()
            types.append(int(atom_info[1]))
            relaxed_cart_coords.append([
                float(atom_info[2]) - xlo,
                float(atom_info[3]) - ylo,
                float(atom_info[4]) - zlo
            ])

        # read the atom types and corresponding atomic masses from in.data
        with open(data_in_path, 'r') as data_in:
            lines = data_in.readlines()
        types_masses = {}
        for i in range(len(lines)):
            if 'Masses' in lines[i]:
                for j in range(len(element_symbols)):
                    types_masses[int(lines[i + j + 2].split()[0])] = float(
                        lines[i + j + 2].split()[1])

        # map the atom types to chemical symbols
        types_symbols = {}
        for symbol in element_symbols:
            for atom_type in types_masses:
                # round the atomic masses to one decimal point for comparison
                if format(float(Element(symbol).atomic_mass),
                          '.1f') == format(types_masses[atom_type], '.1f'):
                    types_symbols[atom_type] = symbol

        # make a list of chemical symbols (one for each site)
        relaxed_symbols = []
        for atom_type in types:
            relaxed_symbols.append(types_symbols[atom_type])

        return Cell(relaxed_lattice,
                    relaxed_symbols,
                    relaxed_cart_coords,
                    coords_are_cartesian=True)
Пример #23
0
    def test_get_data(self):
        props = [
            "energy", "energy_per_atom", "formation_energy_per_atom", "nsites",
            "unit_cell_formula", "pretty_formula", "is_hubbard", "elements",
            "nelements", "e_above_hull", "hubbards", "is_compatible",
            "task_ids", "density", "icsd_ids", "total_magnetization"
        ]
        # unicode literals have been reintroduced in py>3.2
        expected_vals = [
            -191.33812137, -6.833504334642858, -2.551358929370749, 28,
            {k: v
             for k, v in {
                 'P': 4,
                 'Fe': 4,
                 'O': 16,
                 'Li': 4
             }.items()}, "LiFePO4", True, ['Li', 'O', 'P', 'Fe'], 4, 0.0,
            {
                k: v
                for k, v in {
                    'Fe': 5.3,
                    'Li': 0.0,
                    'O': 0.0,
                    'P': 0.0
                }.items()
            }, True,
            [
                u'mp-601412', u'mp-19017', u'mp-796535', u'mp-797820',
                u'mp-540081', u'mp-797269'
            ], 3.4662026991351147,
            [
                159107, 154117, 160776, 99860, 181272, 166815, 260571, 92198,
                165000, 155580, 38209, 161479, 153699, 260569, 260570, 200155,
                260572, 181341, 181342, 72545, 56291, 97764, 162282, 155635
            ], 16.0002716
        ]

        for (i, prop) in enumerate(props):
            if prop not in [
                    'hubbards', 'unit_cell_formula', 'elements', 'icsd_ids',
                    'task_ids'
            ]:
                val = self.rester.get_data("mp-19017", prop=prop)[0][prop]
                self.assertAlmostEqual(expected_vals[i], val)
            elif prop in ["elements", "icsd_ids", "task_ids"]:
                self.assertEqual(
                    set(expected_vals[i]),
                    set(self.rester.get_data("mp-19017", prop=prop)[0][prop]))
            else:
                self.assertEqual(
                    expected_vals[i],
                    self.rester.get_data("mp-19017", prop=prop)[0][prop])

        props = ['structure', 'initial_structure', 'final_structure', 'entry']
        for prop in props:
            obj = self.rester.get_data("mp-19017", prop=prop)[0][prop]
            if prop.endswith("structure"):
                self.assertIsInstance(obj, Structure)
            elif prop == "entry":
                obj = self.rester.get_data("mp-19017", prop=prop)[0][prop]
                self.assertIsInstance(obj, ComputedEntry)

        #Test chemsys search
        data = self.rester.get_data('Fe-Li-O', prop='unit_cell_formula')
        self.assertTrue(len(data) > 1)
        elements = {Element("Li"), Element("Fe"), Element("O")}
        for d in data:
            self.assertTrue(
                set(Composition(
                    d['unit_cell_formula']).elements).issubset(elements))

        self.assertRaises(MPRestError, self.rester.get_data, "Fe2O3",
                          "badmethod")
Пример #24
0
    def parse_oxide(self):
        """
        Determines if an oxide is a peroxide/superoxide/ozonide/normal oxide.

        Returns:
            oxide_type (str): Type of oxide
            ozonide/peroxide/superoxide/hydroxide/None.
            nbonds (int): Number of peroxide/superoxide/hydroxide bonds in
            structure.
        """
        structure = self.structure
        relative_cutoff = self.relative_cutoff
        o_sites_frac_coords = []
        h_sites_frac_coords = []
        lattice = structure.lattice

        if isinstance(structure.composition.elements[0], Element):
            comp = structure.composition
        elif isinstance(structure.composition.elements[0], Species):
            elmap = collections.defaultdict(float)
            for site in structure:
                for species, occu in site.species.items():
                    elmap[species.element] += occu
            comp = Composition(elmap)
        if Element("O") not in comp or comp.is_element:
            return "None", 0

        for site in structure:
            syms = [sp.symbol for sp in site.species.keys()]
            if "O" in syms:
                o_sites_frac_coords.append(site.frac_coords)
            if "H" in syms:
                h_sites_frac_coords.append(site.frac_coords)

        if h_sites_frac_coords:
            dist_matrix = lattice.get_all_distances(o_sites_frac_coords,
                                                    h_sites_frac_coords)
            if np.any(dist_matrix < relative_cutoff * 0.93):
                return (
                    "hydroxide",
                    len(np.where(dist_matrix < relative_cutoff * 0.93)[0]) /
                    2.0,
                )
        dist_matrix = lattice.get_all_distances(o_sites_frac_coords,
                                                o_sites_frac_coords)
        np.fill_diagonal(dist_matrix, 1000)
        is_superoxide = False
        is_peroxide = False
        is_ozonide = False
        if np.any(dist_matrix < relative_cutoff * 1.35):
            bond_atoms = np.where(dist_matrix < relative_cutoff * 1.35)[0]
            is_superoxide = True
        elif np.any(dist_matrix < relative_cutoff * 1.49):
            is_peroxide = True
            bond_atoms = np.where(dist_matrix < relative_cutoff * 1.49)[0]
        if is_superoxide:
            if len(bond_atoms) > len(set(bond_atoms)):
                is_superoxide = False
                is_ozonide = True
        try:
            nbonds = len(set(bond_atoms))
        except UnboundLocalError:
            nbonds = 0.0
        if is_ozonide:
            str_oxide = "ozonide"
        elif is_superoxide:
            str_oxide = "superoxide"
        elif is_peroxide:
            str_oxide = "peroxide"
        else:
            str_oxide = "oxide"
        if str_oxide == "oxide":
            nbonds = comp["O"]
        return str_oxide, nbonds
Пример #25
0
    def test_get_critical_compositions(self):
        c1 = Composition("Fe2O3")
        c2 = Composition("Li3FeO4")
        c3 = Composition("Li2O")

        comps = self.pd.get_critical_compositions(c1, c2)
        expected = [
            Composition("Fe2O3"),
            Composition("Li0.3243244Fe0.1621621O0.51351349") * 7.4,
            Composition("Li3FeO4"),
        ]
        for crit, exp in zip(comps, expected):
            self.assertTrue(crit.almost_equals(exp, rtol=0, atol=1e-5))

        comps = self.pd.get_critical_compositions(c1, c3)
        expected = [
            Composition("Fe2O3"),
            Composition("LiFeO2"),
            Composition("Li5FeO4") / 3,
            Composition("Li2O"),
        ]
        for crit, exp in zip(comps, expected):
            self.assertTrue(crit.almost_equals(exp, rtol=0, atol=1e-5))

        # Don't fail silently if input compositions aren't in phase diagram
        # Can be very confusing if you're working with a GrandPotentialPD
        self.assertRaises(
            ValueError,
            self.pd.get_critical_compositions,
            Composition("Xe"),
            Composition("Mn"),
        )

        # For the moment, should also fail even if compositions are in the gppd
        # because it isn't handled properly
        gppd = GrandPotentialPhaseDiagram(self.pd.all_entries, {"Xe": 1}, self.pd.elements + [Element("Xe")])
        self.assertRaises(
            ValueError,
            gppd.get_critical_compositions,
            Composition("Fe2O3"),
            Composition("Li3FeO4Xe"),
        )

        # check that the function still works though
        comps = gppd.get_critical_compositions(c1, c2)
        expected = [
            Composition("Fe2O3"),
            Composition("Li0.3243244Fe0.1621621O0.51351349") * 7.4,
            Composition("Li3FeO4"),
        ]
        for crit, exp in zip(comps, expected):
            self.assertTrue(crit.almost_equals(exp, rtol=0, atol=1e-5))

        # case where the endpoints are identical
        self.assertEqual(self.pd.get_critical_compositions(c1, c1 * 2), [c1, c1 * 2])
Пример #26
0
    def test_attributes(self):
        is_true = {
            ("Xe", "Kr"): "is_noble_gas",
            ("Fe", "Ni"): "is_transition_metal",
            ("Li", "Cs"): "is_alkali",
            ("Ca", "Mg"): "is_alkaline",
            ("F", "Br", "I"): "is_halogen",
            ("La",): "is_lanthanoid",
            ("U", "Pu"): "is_actinoid",
            ("Si", "Ge"): "is_metalloid",
            ("O", "Te"): "is_chalcogen",
        }

        for k, v in is_true.items():
            for sym in k:
                self.assertTrue(getattr(Element(sym), v), sym + " is false")

        keys = [
            "mendeleev_no",
            "atomic_mass",
            "electronic_structure",
            "atomic_radius",
            "min_oxidation_state",
            "max_oxidation_state",
            "electrical_resistivity",
            "velocity_of_sound",
            "reflectivity",
            "refractive_index",
            "poissons_ratio",
            "molar_volume",
            "thermal_conductivity",
            "melting_point",
            "boiling_point",
            "liquid_range",
            "critical_temperature",
            "superconduction_temperature",
            "bulk_modulus",
            "youngs_modulus",
            "brinell_hardness",
            "rigidity_modulus",
            "mineral_hardness",
            "vickers_hardness",
            "density_of_solid",
            "atomic_orbitals",
            "coefficient_of_linear_thermal_expansion",
            "oxidation_states",
            "common_oxidation_states",
            "average_ionic_radius",
            "average_cationic_radius",
            "average_anionic_radius",
            "ionic_radii",
            "long_name",
            "metallic_radius",
            "iupac_ordering",
        ]

        # Test all elements up to Uranium
        for i in range(1, 104):
            el = Element.from_Z(i)
            d = el.data
            for k in keys:
                k_str = k.capitalize().replace("_", " ")
                if k_str in d and (not str(d[k_str]).startswith("no data")):
                    self.assertIsNotNone(getattr(el, k))
                elif k == "long_name":
                    self.assertEqual(getattr(el, "long_name"), d["Name"])
                elif k == "iupac_ordering":
                    self.assertTrue("IUPAC ordering" in d)
                    self.assertIsNotNone(getattr(el, k))
            el = Element.from_Z(i)
            if len(el.oxidation_states) > 0:
                self.assertEqual(max(el.oxidation_states), el.max_oxidation_state)
                self.assertEqual(min(el.oxidation_states), el.min_oxidation_state)

            if el.symbol not in ["He", "Ne", "Ar"]:
                self.assertTrue(el.X > 0, "No electroneg for %s" % el)

        self.assertRaises(ValueError, Element.from_Z, 1000)
Пример #27
0
def eval_vasp_xml(file="vasprun.xml", recip=False, norm_fermi=True, print_out=False):
    dft = pymatgen.io.vasp.outputs.Vasprun(file, parse_projected_eigen=False)
    orbital_energy = pd.read_csv("element_orbital_energy.csv").set_index("element")

    lattice = jnp.asarray(dft.get_trajectory().as_dict()['lattice']).squeeze()
    lattice_normed = lattice / jnp.linalg.norm(lattice, axis=1, keepdims=True)
    lattice_recip = jnp.asarray(Lattice(lattice).reciprocal_lattice.matrix)  # wrong!

    positions_base = dft.get_trajectory().as_dict()['base_positions']
    positions = jnp.dot(positions_base, lattice)

    k_points = jnp.asarray(dft.actual_kpoints)

    weights = jnp.asarray(dft.actual_kpoints_weights)  # how to use ?

    species_dict = {}
    species_arr = np.asarray(dft.atomic_symbols)
    count = 0
    print(species_arr)
    for key in dict.fromkeys(set(dft.atomic_symbols), {}):
        species_dict["species_" + Element(key).long_name] = {"symbol": key,
                                                             "number": count,
                                                             "Es": orbital_energy.loc["C", "E_s"],
                                                             "Ep": orbital_energy.loc["C", "E_p"],
                                                             "Ed": orbital_energy.loc["C", "E_d"],
                                                             }
        species_arr[species_arr == key] = count  # cycles through elements but returns correct one anyway
        count += 1
    species_arr = jnp.asarray(species_arr.astype(int))

    for key in dft.eigenvalues.keys():
        key_last = key
    true_inp = np.zeros(
        (dft.eigenvalues[key_last][:, :, 0].shape[0], dft.eigenvalues[key_last][:, :, 0].shape[1], len(dft.eigenvalues.keys())))
    count = 0
    if len(dft.eigenvalues.keys()) != 1:
        print("only one spin direction supported but", len(dft.eigenvalues.keys()), "where given")
    for key in dft.eigenvalues.keys():  # OrderedDictionary might be nice
        true_inp[:, :, count] = dft.eigenvalues[key][:, :, 0]  # what is [:, :, 0] ???????????????????
        occupied = np.max(jnp.nonzero(dft.eigenvalues[key][:, :, 1])[1]) + 1
        fermi = find_fermi(true_inp, occupied)
        count += 1
    if norm_fermi:
        true_inp -= fermi
        print("E fermi calculated normed", find_fermi(true_inp, occupied, plot=False))
    if print_out:
        print("Lattice", type(lattice), lattice.shape, "\n", lattice)
        print("Lattice Normed", type(lattice_normed), lattice_normed.shape, lattice_normed)
        print("Lattice recip", type(lattice_recip), lattice_recip.shape, "\n", lattice_recip)
        print("Positions", type(positions_base), positions_base.shape, positions_base)
        print("Positions dot", type(positions), positions.shape, "\n", positions)
        print("kpts", k_points.shape, k_points)
        print("weights", weights.shape, weights)
        print("True shape", true_inp.shape, true_inp)
        print("species", species_arr.shape, species_arr, "\n", species_dict)
        # print("true", dft.eigenvalues[:].shape, "\n", dft.eigenvalues[dft.eigenvalues.keys()[0]][0, :, 0], "\n",
        #       dft.eigenvalues[dft.eigenvalues.keys()[0]][0, :, 1])
        print("E fermi vasp", dft.efermi)
        print("Highest occupied", occupied)
        print("E fermi calculated", fermi)
    if recip:
        return k_points, weights, lattice_recip, positions, species_arr, species_dict, true_inp, occupied
    else:
        return k_points, weights, lattice, positions, species_arr, species_dict, true_inp, occupied
Пример #28
0
 def test_is(self):
     self.assertTrue(Element("Bi").is_post_transition_metal, True)
Пример #29
0
 def setUp(self):
     comp = Composition("LiFeO2")
     self.entry = PDEntry(comp, 53)
     self.gpentry = GrandPotPDEntry(self.entry, {Element('O'): 1.5})
Пример #30
0
def van_arkel_triangle(list_of_materials, annotate=True):
    """
    A static method that generates a binary van Arkel-Ketelaar triangle to
        quantify the ionic, metallic and covalent character of a compound
        by plotting the electronegativity difference (y) vs average (x).
        See:
            A.E. van Arkel, Molecules and Crystals in Inorganic Chemistry,
                Interscience, New York (1956)
        and
            J.A.A Ketelaar, Chemical Constitution (2nd edition), An Introduction
                to the Theory of the Chemical Bond, Elsevier, New York (1958)

    Args:
         list_of_materials (list): A list of computed entries of binary
            materials or a list of lists containing two elements (str).
         annotate (bool): Whether or not to label the points on the
            triangle with reduced formula (if list of entries) or pair
            of elements (if list of list of str).
    """

    # F-Fr has the largest X difference. We set this
    # as our top corner of the triangle (most ionic)
    pt1 = np.array([(Element("F").X + Element("Fr").X) / 2,
                    abs(Element("F").X - Element("Fr").X)])
    # Cs-Fr has the lowest average X. We set this as our
    # bottom left corner of the triangle (most metallic)
    pt2 = np.array([
        (Element("Cs").X + Element("Fr").X) / 2,
        abs(Element("Cs").X - Element("Fr").X),
    ])
    # O-F has the highest average X. We set this as our
    # bottom right corner of the triangle (most covalent)
    pt3 = np.array([(Element("O").X + Element("F").X) / 2,
                    abs(Element("O").X - Element("F").X)])

    # get the parameters for the lines of the triangle
    d = np.array(pt1) - np.array(pt2)
    slope1 = d[1] / d[0]
    b1 = pt1[1] - slope1 * pt1[0]
    d = pt3 - pt1
    slope2 = d[1] / d[0]
    b2 = pt3[1] - slope2 * pt3[0]

    # Initialize the plt object
    import matplotlib.pyplot as plt

    # set labels and appropriate limits for plot
    plt.xlim(pt2[0] - 0.45, -b2 / slope2 + 0.45)
    plt.ylim(-0.45, pt1[1] + 0.45)
    plt.annotate("Ionic", xy=[pt1[0] - 0.3, pt1[1] + 0.05], fontsize=20)
    plt.annotate("Covalent", xy=[-b2 / slope2 - 0.65, -0.4], fontsize=20)
    plt.annotate("Metallic", xy=[pt2[0] - 0.4, -0.4], fontsize=20)
    plt.xlabel(r"$\frac{\chi_{A}+\chi_{B}}{2}$", fontsize=25)
    plt.ylabel(r"$|\chi_{A}-\chi_{B}|$", fontsize=25)

    # Set the lines of the triangle
    chi_list = [el.X for el in Element]
    plt.plot(
        [min(chi_list), pt1[0]],
        [slope1 * min(chi_list) + b1, pt1[1]],
        "k-",
        linewidth=3,
    )
    plt.plot([pt1[0], -b2 / slope2], [pt1[1], 0], "k-", linewidth=3)
    plt.plot([min(chi_list), -b2 / slope2], [0, 0], "k-", linewidth=3)
    plt.xticks(fontsize=15)
    plt.yticks(fontsize=15)

    # Shade with appropriate colors corresponding to ionic, metallci and covalent
    ax = plt.gca()
    # ionic filling
    ax.fill_between(
        [min(chi_list), pt1[0]],
        [slope1 * min(chi_list) + b1, pt1[1]],
        facecolor=[1, 1, 0],
        zorder=-5,
        edgecolor=[1, 1, 0],
    )
    ax.fill_between(
        [pt1[0], -b2 / slope2],
        [pt1[1], slope2 * min(chi_list) - b1],
        facecolor=[1, 1, 0],
        zorder=-5,
        edgecolor=[1, 1, 0],
    )
    # metal filling
    XPt = Element("Pt").X
    ax.fill_between(
        [min(chi_list), (XPt + min(chi_list)) / 2],
        [0, slope1 * (XPt + min(chi_list)) / 2 + b1],
        facecolor=[1, 0, 0],
        zorder=-3,
        alpha=0.8,
    )
    ax.fill_between(
        [(XPt + min(chi_list)) / 2, XPt],
        [slope1 * ((XPt + min(chi_list)) / 2) + b1, 0],
        facecolor=[1, 0, 0],
        zorder=-3,
        alpha=0.8,
    )
    # covalent filling
    ax.fill_between(
        [(XPt + min(chi_list)) / 2,
         ((XPt + min(chi_list)) / 2 + -b2 / slope2) / 2],
        [0, slope2 * (((XPt + min(chi_list)) / 2 + -b2 / slope2) / 2) + b2],
        facecolor=[0, 1, 0],
        zorder=-4,
        alpha=0.8,
    )
    ax.fill_between(
        [((XPt + min(chi_list)) / 2 + -b2 / slope2) / 2, -b2 / slope2],
        [slope2 * (((XPt + min(chi_list)) / 2 + -b2 / slope2) / 2) + b2, 0],
        facecolor=[0, 1, 0],
        zorder=-4,
        alpha=0.8,
    )

    # Label the triangle with datapoints
    for entry in list_of_materials:
        if type(entry).__name__ not in [
                "ComputedEntry", "ComputedStructureEntry"
        ]:
            X_pair = [Element(el).X for el in entry]
            el_1, el_2 = entry
            formatted_formula = f"{el_1}-{el_2}"
        else:
            X_pair = [
                Element(el).X for el in entry.composition.as_dict().keys()
            ]
            formatted_formula = format_formula(
                entry.composition.reduced_formula)
        plt.scatter(np.mean(X_pair), abs(X_pair[0] - X_pair[1]), c="b", s=100)
        if annotate:
            plt.annotate(
                formatted_formula,
                fontsize=15,
                xy=[np.mean(X_pair) + 0.005,
                    abs(X_pair[0] - X_pair[1])],
            )

    plt.tight_layout()
    return plt