コード例 #1
0
ファイル: surface.py プロジェクト: adozier/pymatgen
 def _get_c_ranges(self, bonds):
     c_ranges = set()
     bonds = {(get_el_sp(s1), get_el_sp(s2)): dist for (s1, s2), dist in
              bonds.items()}
     for (sp1, sp2), bond_dist in bonds.items():
         for site in self.oriented_unit_cell:
             if sp1 in site.species_and_occu:
                 for nn, d in self.oriented_unit_cell.get_neighbors(
                         site, bond_dist):
                     if sp2 in nn.species_and_occu:
                         c_range = tuple(sorted([site.frac_coords[2],
                                                 nn.frac_coords[2]]))
                         if c_range[1] > 1:
                             # Takes care of PBC when c coordinate of site
                             # goes beyond the upper boundary of the cell
                             c_ranges.add((c_range[0], 1))
                             c_ranges.add((0, c_range[1] - 1))
                         elif c_range[0] < 0:
                             # Takes care of PBC when c coordinate of site
                             # is below the lower boundary of the unit cell
                             c_ranges.add((0, c_range[1]))
                             c_ranges.add((c_range[0] + 1, 1))
                         elif c_range[0] != c_range[1]:
                             c_ranges.add(c_range)
     return c_ranges
コード例 #2
0
ファイル: bonds.py プロジェクト: bocklund/pymatgen
def get_bond_length(sp1, sp2, bond_order=1):
    """
    Get the bond length between two species.

    Args:
        sp1 (Specie): First specie.
        sp2 (Specie): Second specie.
        bond_order: For species with different possible bond orders,
            this allows one to obtain the bond length for a particular bond
            order. For example, to get the C=C bond length instead of the
            C-C bond length, this should be set to 2. Defaults to 1.

    Returns:
        Bond length in Angstrom. If no data is available, the sum of the atomic
        radii is used.
    """
    sp1 = get_el_sp(sp1)
    sp2 = get_el_sp(sp2)
    syms = tuple(sorted([sp1.symbol, sp2.symbol]))
    if syms in bond_lengths:
        all_lengths = bond_lengths[syms]
        if bond_order:
            return all_lengths.get(bond_order)
        else:
            return all_lengths.get(1)
    warnings.warn("No bond lengths for %s-%s found in database. Returning sum"
                  "of atomic radius." % (sp1, sp2))
    return sp1.atomic_radius + sp2.atomic_radius
コード例 #3
0
ファイル: sites.py プロジェクト: mathematicus/pymatgen
    def __init__(self, atoms_n_occu, coords, properties=None):
        """
        Create a *non-periodic* site.

        Args:
            atoms_n_occu: Species on the site. Can be:

                i.  A sequence of element / specie specified either as string
                    symbols, e.g. ["Li", "Fe2+", "P", ...] or atomic numbers,
                    e.g., (3, 56, ...) or actual Element or Specie objects.
                ii. List of dict of elements/species and occupancies, e.g.,
                    [{"Fe" : 0.5, "Mn":0.5}, ...]. This allows the setup of
                    disordered structures.
            coords: Cartesian coordinates of site.
            properties: Properties associated with the site as a dict, e.g.
                {"magmom": 5}. Defaults to None.
        """
        if issubclass(atoms_n_occu.__class__, collections.Mapping):
            self._species = Composition({get_el_sp(k): v
                                         for k, v in atoms_n_occu.items()})
            totaloccu = self._species.num_atoms
            if totaloccu > 1 + Composition.amount_tolerance:
                raise ValueError("Species occupancies sum to more than 1!")
            self._is_ordered = (totaloccu == 1 and len(self._species) == 1)
        else:
            self._species = Composition(
                {get_el_sp(atoms_n_occu): 1})
            self._is_ordered = True

        self._coords = coords
        self._properties = properties if properties else {}
コード例 #4
0
ファイル: bonds.py プロジェクト: ATNDiaye/pymatgen
def get_bond_length(sp1, sp2, bond_order=1):
    """
    Get the bond length between two species.

    Args:
        sp1 (Specie): First specie.
        sp2 (Specie): Second specie.
        bond_order: For species with different possible bond orders,
            this allows one to obtain the bond length for a particular bond
            order. For example, to get the C=C bond length instead of the
            C-C bond length, this should be set to 2. Defaults to 1.

    Returns:
        Bond length in Angstrom. None if no data is available.
    """

    syms = tuple(sorted([get_el_sp(sp1).symbol,
                         get_el_sp(sp2).symbol]))
    if syms in bond_lengths:
        all_lengths = bond_lengths[syms]
        if bond_order:
            return all_lengths.get(bond_order)
        else:
            return all_lengths.get(1)
    return None
コード例 #5
0
    def __init__(
        self,
        sp,
        num=None,
        bond_lengths=None,
        interior_only=False,
        midpoints=True,
        min_solid_angle=0,
        site_distance_tol=0.1,
    ):
        """
        Args:
            sp:
                specie to add
            num:
                number to add (will end up with a disordered structure)
            bond_lengths:
                dictionary of bond lengths. For example, if {'O' : 1} is
                used, all points will be scaled from their voronoi locations
                to be exactly 1 angstrom from the oxygen (if the oxygen is
                at the center of the voronoi region). In the case of a point
                neighboring 2 oxygen atoms, 2 points will be added - one for
                each oxygen
            interior_only:
                only uses voronoi points that are inside the tetrahedra
                formed by their 4 nearest neighbors. This is useful because
                when this is not true, there is another point accessible
                without going through a bottleneck that is strictly larger.
                Currently this doesn't work well with bond lengths because 
                finding the correct association between sites and voronoi 
                points is difficult. Doesn't affect the delaunay midpoints
            midpoints:
                whether to add the points at the center of the voronoi
                faces (midpoints of the delaunay triangulation)
            min_solid_angle:
                Threshold for creating points at the delaunay midpoints. The
                solid angle is a metric for how much two sites are neighbors of
                each other (the solid angle swept out by the voronoi face 
                corresponding to the two sites). Typical values are 0 to Pi, with
                Pi corresponding to a tetrahedrally coordinated site.
            site_distance_tol:
                Tolerance passed to to_unit_cell(). When there are multiple
                sites within this tolerance, only one is used
        """
        self._sp = get_el_sp(sp)
        self._n = num
        self._interior = interior_only
        self._midpoints = midpoints
        self._msa = min_solid_angle
        self._sdt = site_distance_tol

        # transform keys to species
        if bond_lengths:
            self._bl = dict(zip(map(lambda x: get_el_sp(x), bond_lengths.keys()), bond_lengths.values()))
        else:
            self._bl = {}
コード例 #6
0
 def apply_transformation(self, structure):
     species_map = {}
     for k, v in self._species_map.items():
         if isinstance(v, dict):
             value = {get_el_sp(x): y for x, y in v.items()}
         else:
             value = get_el_sp(v)
         species_map[get_el_sp(k)] = value
     s = Structure.from_sites(structure.sites)
     s.replace_species(species_map)
     return s
コード例 #7
0
ファイル: composition.py プロジェクト: ExpHP/pymatgen
def reduce_formula(sym_amt, iupac_ordering=False):
    """
    Helper method to reduce a sym_amt dict to a reduced formula and factor.

    Args:
        sym_amt (dict): {symbol: amount}.
        iupac_ordering (bool, optional): Whether to order the
            formula by the iupac "electronegativity" series, defined in
            Table VI of "Nomenclature of Inorganic Chemistry (IUPAC
            Recommendations 2005)". This ordering effectively follows
            the groups and rows of the periodic table, except the
            Lanthanides, Actanides and hydrogen. Note that polyanions
            will still be determined based on the true electronegativity of
            the elements.

    Returns:
        (reduced_formula, factor).
    """
    syms = sorted(sym_amt.keys(), key=lambda x: [get_el_sp(x).X, x])

    syms = list(filter(
        lambda x: abs(sym_amt[x]) > Composition.amount_tolerance, syms))

    factor = 1
    # Enforce integers for doing gcd.
    if all((int(i) == i for i in sym_amt.values())):
        factor = abs(gcd(*(int(i) for i in sym_amt.values())))

    polyanion = []
    # if the composition contains a poly anion
    if len(syms) >= 3 and get_el_sp(syms[-1]).X - get_el_sp(syms[-2]).X < 1.65:
        poly_sym_amt = {syms[i]: sym_amt[syms[i]] / factor
                        for i in [-2, -1]}
        (poly_form, poly_factor) = reduce_formula(
            poly_sym_amt, iupac_ordering=iupac_ordering)

        if poly_factor != 1:
            polyanion.append("({}){}".format(poly_form, int(poly_factor)))

    syms = syms[:len(syms) - 2 if polyanion else len(syms)]

    if iupac_ordering:
        syms = sorted(syms,
                      key=lambda x: [get_el_sp(x).iupac_ordering, x])

    reduced_form = []
    for s in syms:
        normamt = sym_amt[s] * 1.0 / factor
        reduced_form.append(s)
        reduced_form.append(formula_double_format(normamt))

    reduced_form = "".join(reduced_form + polyanion)
    return reduced_form, factor
コード例 #8
0
def get_conversion_factor(structure, species, temperature):
    """
    Conversion factor to convert between cm^2/s diffusivity measurements and
    mS/cm conductivity measurements based on number of atoms of diffusing
    species. Note that the charge is based on the oxidation state of the
    species (where available), or else the number of valence electrons
    (usually a good guess, esp for main group ions).

    Args:
        structure (Structure): Input structure.
        species (Element/Specie): Diffusing species.
        temperature (float): Temperature of the diffusion run in Kelvin.

    Returns:
        Conversion factor.
        Conductivity (in mS/cm) = Conversion Factor * Diffusivity (in cm^2/s)
    """
    df_sp = get_el_sp(species)
    if hasattr(df_sp, "oxi_state"):
        z = df_sp.oxi_state
    else:
        z = df_sp.full_electronic_structure[-1][2]

    n = structure.composition[species]

    vol = structure.volume * 1e-24  # units cm^3
    return 1000 * n / (vol * const.N_A) * z ** 2 * (const.N_A * const.e) ** 2 \
        / (const.R * temperature)
コード例 #9
0
ファイル: pdmaker.py プロジェクト: artemcpp/pymatgen
    def __init__(self, entries, chempots, elements=None):
        """
        Standard constructor for grand potential phase diagram.

        Args:
            entries ([PDEntry]): A list of PDEntry-like objects having an
                energy, energy_per_atom and composition.
            chempots {Element: float}: Specify the chemical potentials
                of the open elements.
            elements ([Element]): Optional list of elements in the phase
                diagram. If set to None, the elements are determined from
                the the entries themselves.
        """
        if elements is None:
            elements = set()
            map(elements.update, [entry.composition.elements
                                  for entry in entries])
        self.chempots = {get_el_sp(el): u for el, u in chempots.items()}
        elements = set(elements).difference(self.chempots.keys())
        all_entries = [GrandPotPDEntry(e, self.chempots)
                       for e in entries
                       if (not e.is_element) or
                       e.composition.elements[0] in elements]

        super(GrandPotentialPhaseDiagram, self).__init__(all_entries, elements)
コード例 #10
0
ファイル: jahnteller.py プロジェクト: navnidhirajput/pymatgen
    def get_magnitude_of_effect_from_species(self, species, spin_state, motif):
        """
        Get magnitude of Jahn-Teller effect from provided species, spin state and motife.

        :param species: e.g. Fe2+
        :param spin_state (str): "high" or "low"
        :param motif (str): "oct" or "tet"

        :return (str):
        """

        magnitude = "none"

        sp = get_el_sp(species)

        # has to be Specie; we need to know the oxidation state
        if isinstance(sp, Specie) and sp.element.is_transition_metal:

            d_electrons = self._get_number_of_d_electrons(sp)

            if motif in self.spin_configs:
                if spin_state not in self.spin_configs[motif][d_electrons]:
                    spin_state = self.spin_configs[motif][d_electrons]['default']
                spin_config = self.spin_configs[motif][d_electrons][spin_state]
                magnitude = JahnTellerAnalyzer.get_magnitude_of_effect_from_spin_config(motif,
                                                                                        spin_config)
        else:
            warnings.warn("No data for this species.")

        return magnitude
コード例 #11
0
ファイル: composition.py プロジェクト: adozier/pymatgen
 def __contains__(self, item):
     try:
         sp = get_el_sp(item)
         return sp in self._data
     except ValueError:
         raise TypeError("Invalid key {}, {} for Composition\n"
                         "ValueError exception:\n{}".format(item, type(item), ex))
コード例 #12
0
ファイル: composition.py プロジェクト: zacharygibbs/pymatgen
    def __init__(self, *args, **kwargs):
        """
        Very flexible Composition construction, similar to the built-in Python
        dict(). Also extended to allow simple string init.

        Args:
            Any form supported by the Python built-in dict() function.

            1. A dict of either {Element/Specie: amount},

               {string symbol:amount}, or {atomic number:amount} or any mixture
               of these. E.g., {Element("Li"):2 ,Element("O"):1},
               {"Li":2, "O":1}, {3:2, 8:1} all result in a Li2O composition.
            2. Keyword arg initialization, similar to a dict, e.g.,

               Compostion(Li = 2, O = 1)

            In addition, the Composition constructor also allows a single
            string as an input formula. E.g., Composition("Li2O").
        """
        if len(args) == 1 and isinstance(args[0], basestring):
            elmap = self._parse_formula(args[0])
        else:
            elmap = dict(*args, **kwargs)
        for k, v in elmap.items():
            if v < -Composition.amount_tolerance:
                raise CompositionError("Amounts in Composition cannot be "
                                       "negative!")
            elif v < 0:
                del elmap[k]
        self._elmap = {get_el_sp(k): v for k, v in elmap.items()}
        self._natoms = sum(self._elmap.values())
コード例 #13
0
    def apply_transformation(self, structure, return_ranked_list=False):
        """
        Apply the transformation.

        Args:
            structure: input structure
            return_ranked_list (bool/int): Boolean stating whether or not
                multiple structures are returned. If return_ranked_list is
                an int, that number of structures is returned.

        Returns:
            Depending on returned_ranked list, either a transformed structure
            or a list of dictionaries, where each dictionary is of the form
            {"structure" = .... , "other_arguments"}
            the key "transformation" is reserved for the transformation that
            was actually applied to the structure.
            This transformation is parsed by the alchemy classes for generating
            a more specific transformation history. Any other information will
            be stored in the transformation_parameters dictionary in the
            transmuted structure class.
        """
        sp = get_el_sp(self._specie)
        specie_indices = [i for i in range(len(structure))
                          if structure[i].species_and_occu ==
                          Composition({sp: 1})]
        trans = PartialRemoveSitesTransformation([specie_indices],
                                                 [self._frac], algo=self._algo)
        return trans.apply_transformation(structure, return_ranked_list)
コード例 #14
0
ファイル: sites.py プロジェクト: blondegeek/pymatgen
    def __init__(self, atoms_n_occu, coords, properties=None):
        """
        Create a *non-periodic* site.

        Args:
            atoms_n_occu: Species on the site. Can be:
                i.  A Composition object (preferred)
                ii. An  element / specie specified either as a string
                    symbols, e.g. "Li", "Fe2+", "P" or atomic numbers,
                    e.g., 3, 56, or actual Element or Specie objects.
                iii.Dict of elements/species and occupancies, e.g.,
                    {"Fe" : 0.5, "Mn":0.5}. This allows the setup of
                    disordered structures.
            coords: Cartesian coordinates of site.
            properties: Properties associated with the site as a dict, e.g.
                {"magmom": 5}. Defaults to None.
        """
        if isinstance(atoms_n_occu, Composition):
            # Compositions are immutable, so don't need to copy (much faster)
            species = atoms_n_occu
        else:
            try:
                species = Composition({get_el_sp(atoms_n_occu): 1})
            except TypeError:
                species = Composition(atoms_n_occu)
        totaloccu = species.num_atoms
        if totaloccu > 1 + Composition.amount_tolerance:
            raise ValueError("Species occupancies sum to more than 1!")
        self._species = species
        self.coords = np.array(coords)
        self.properties = properties or {}
コード例 #15
0
ファイル: composition.py プロジェクト: adozier/pymatgen
 def __getitem__(self, item):
     try:
         sp = get_el_sp(item)
         return self._data.get(sp, 0)
     except ValueError as ex:
         raise TypeError("Invalid key {}, {} for Composition\n"
                         "ValueError exception:\n{}".format(item, type(item), ex))
コード例 #16
0
ファイル: dos.py プロジェクト: materialsproject/pymatgen
    def get_element_spd_dos(self, el):
        """
        Get element and spd projected Dos


        Args:
            el: Element in Structure.composition associated with LobsterCompleteDos

        Returns:
            dict of {Element: {"S": densities, "P": densities, "D": densities}}
        """
        el = get_el_sp(el)
        el_dos = {}
        for site, atom_dos in self.pdos.items():
            if site.specie == el:
                for orb, pdos in atom_dos.items():
                    orbital_type = _get_orb_type_lobster(orb)
                    if orbital_type not in el_dos:
                        el_dos[orbital_type] = pdos
                    else:
                        el_dos[orbital_type] = \
                            add_densities(el_dos[orbital_type], pdos)

        return {orb: Dos(self.efermi, self.energies, densities)
                for orb, densities in el_dos.items()}
コード例 #17
0
ファイル: composition.py プロジェクト: adozier/pymatgen
def reduce_formula(sym_amt):
    """
    Helper method to reduce a sym_amt dict to a reduced formula and factor.

    Args:
        sym_amt (dict): {symbol: amount}.

    Returns:
        (reduced_formula, factor).
    """
    syms = sorted(sym_amt.keys(),
                  key=lambda s: get_el_sp(s).X)

    syms = list(filter(lambda s: abs(sym_amt[s]) >
                       Composition.amount_tolerance, syms))
    num_el = len(syms)
    contains_polyanion = (num_el >= 3 and
                          get_el_sp(syms[num_el - 1]).X
                          - get_el_sp(syms[num_el - 2]).X < 1.65)

    factor = 1
    # Enforce integers for doing gcd.
    if all((int(i) == i for i in sym_amt.values())):
        factor = abs(gcd(*(int(i) for i in sym_amt.values())))

    reduced_form = []
    n = num_el - 2 if contains_polyanion else num_el
    for i in range(0, n):
        s = syms[i]
        normamt = sym_amt[s] * 1.0 / factor
        reduced_form.append(s)
        reduced_form.append(formula_double_format(normamt))

    if contains_polyanion:
        poly_sym_amt = {syms[i]: sym_amt[syms[i]] / factor
                        for i in range(n, num_el)}
        (poly_form, poly_factor) = reduce_formula(poly_sym_amt)

        if poly_factor != 1:
            reduced_form.append("({}){}".format(poly_form, int(poly_factor)))
        else:
            reduced_form.append(poly_form)

    reduced_form = "".join(reduced_form)

    return reduced_form, factor
コード例 #18
0
    def apply_transformation(self, structure, return_ranked_list=False):
        #Make a mutable structure first
        mods = Structure.from_sites(structure)
        for sp, spin in self.mag_species_spin.items():
            sp = get_el_sp(sp)
            oxi_state = getattr(sp, "oxi_state", 0)
            if spin:
                up = Specie(sp.symbol, oxi_state, {"spin": abs(spin)})
                down = Specie(sp.symbol, oxi_state, {"spin": -abs(spin)})
                mods.replace_species(
                    {sp: Composition({up: self.order_parameter,
                                      down: 1 - self.order_parameter})})
            else:
                mods.replace_species(
                    {sp: Specie(sp.symbol, oxi_state, {"spin": spin})})

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

        enum_args = self.enum_kwargs

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

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

        t = EnumerateStructureTransformation(**enum_args)

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

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

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

        m = StructureMatcher(comparator=SpinComparator())

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

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

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

        return self._all_structures[0:num_to_return]
コード例 #19
0
ファイル: composition.py プロジェクト: georgeyumnam/pymatgen
 def formula(self):
     """
     Returns a formula string, with elements sorted by electronegativity,
     e.g., Li4 Fe4 P4 O16.
     """
     sym_amt = self.get_el_amt_dict()
     syms = sorted(sym_amt.keys(), key=lambda sym: get_el_sp(sym).X)
     formula = [s + formula_double_format(sym_amt[s], False) for s in syms]
     return " ".join(formula)
コード例 #20
0
ファイル: composition.py プロジェクト: montoyjh/pymatgen
 def __init__(self, *args, **kwargs):
     """
     Args:
         *args, **kwargs: any valid dict init arguments
     """
     d = dict(*args, **kwargs)
     super(ChemicalPotential, self).__init__((get_el_sp(k), v) for k, v in d.items())
     if len(d) != len(self):
         raise ValueError("Duplicate potential specified")
コード例 #21
0
    def list_prediction(self, species, to_this_composition=True):
        """
        Args:
            species:
                list of species
            to_this_composition:
                If true, substitutions with this as a final composition
                will be found. If false, substitutions with this as a
                starting composition will be found (these are slightly
                different)
        Returns:
            List of predictions in the form of dictionaries.
            If to_this_composition is true, the values of the dictionary
            will be from the list species. If false, the keys will be
            from that list.
        """
        for sp in species:
            if get_el_sp(sp) not in self.p.species:
                raise ValueError("the species {} is not allowed for the"
                                 "probability model you are using".format(sp))
        max_probabilities = []
        for s1 in species:
            if to_this_composition:
                max_p = max([self.p.cond_prob(s2, s1) for s2 in self.p.species])
            else:
                max_p = max([self.p.cond_prob(s1, s2) for s2 in self.p.species])
            max_probabilities.append(max_p)

        output = []

        def _recurse(output_prob, output_species):
            best_case_prob = list(max_probabilities)
            best_case_prob[:len(output_prob)] = output_prob
            if six.moves.reduce(mul, best_case_prob) > self.threshold:
                if len(output_species) == len(species):
                    odict = {
                        'probability': six.moves.reduce(mul, best_case_prob)}
                    if to_this_composition:
                        odict['substitutions'] = dict(
                            zip(output_species, species))
                    else:
                        odict['substitutions'] = dict(
                            zip(species, output_species))
                    if len(output_species) == len(set(output_species)):
                        output.append(odict)
                    return
                for sp in self.p.species:
                    i = len(output_prob)
                    if to_this_composition:
                        prob = self.p.cond_prob(sp, species[i])
                    else:
                        prob = self.p.cond_prob(species[i], sp)
                    _recurse(output_prob + [prob], output_species + [sp])

        _recurse([], [])
        logging.info('{} substitutions found'.format(len(output)))
        return output
コード例 #22
0
ファイル: composition.py プロジェクト: georgeyumnam/pymatgen
 def __add__(self, other):
     """
     Adds two compositions. For example, an Fe2O3 composition + an FeO
     composition gives a Fe3O4 composition.
     """
     new_el_map = collections.defaultdict(float)
     new_el_map.update(self)
     for k, v in other.items():
         new_el_map[get_el_sp(k)] += v
     return Composition(new_el_map, allow_negative=self.allow_negative)
コード例 #23
0
ファイル: sites.py プロジェクト: blondegeek/pymatgen
 def species(self, species):
     if not isinstance(species, Composition):
         try:
             species = Composition({get_el_sp(species): 1})
         except TypeError:
             species = Composition(species)
     totaloccu = species.num_atoms
     if totaloccu > 1 + Composition.amount_tolerance:
         raise ValueError("Species occupancies sum to more than 1!")
     self._species = species
コード例 #24
0
 def test_get_el_sp(self):
     self.assertEqual(get_el_sp("Fe2+"), Specie("Fe", 2))
     self.assertEqual(get_el_sp("3"), Element.Li)
     self.assertEqual(get_el_sp("3.0"), Element.Li)
     self.assertEqual(get_el_sp("U"), Element.U)
     self.assertEqual(get_el_sp("X2+"), DummySpecie("X", 2))
     self.assertEqual(get_el_sp("Mn3+"), Specie("Mn", 3))
     self.assertEqual(get_el_sp(["Li+", "Mn3+"]),
                      [Specie("Li", 1), Specie("Mn", 3)])
コード例 #25
0
ファイル: substitutor.py プロジェクト: ExpHP/pymatgen
    def pred_from_list(self, species_list):
        """
        There are an exceptionally large number of substitutions to
        look at (260^n), where n is the number of species in the
        list. We need a more efficient than brute force way of going
        through these possibilities. The brute force method would be::

            output = []
            for p in itertools.product(self._sp.species_list
                                       , repeat = len(species_list)):
                if self._sp.conditional_probability_list(p, species_list)
                                       > self._threshold:
                    output.append(dict(zip(species_list,p)))
            return output

        Instead of that we do a branch and bound.

        Args:
            species_list:
                list of species in the starting structure

        Returns:
            list of dictionaries, each including a substitutions
            dictionary, and a probability value
        """
        species_list = get_el_sp(species_list)
        # calculate the highest probabilities to help us stop the recursion
        max_probabilities = []
        for s2 in species_list:
            max_p = 0
            for s1 in self._sp.species:
                max_p = max([self._sp.cond_prob(s1, s2), max_p])
            max_probabilities.append(max_p)
        output = []

        def _recurse(output_prob, output_species):
            best_case_prob = list(max_probabilities)
            best_case_prob[:len(output_prob)] = output_prob
            if functools.reduce(mul, best_case_prob) > self._threshold:
                if len(output_species) == len(species_list):
                    odict = {
                        'substitutions':
                            dict(zip(species_list, output_species)),
                        'probability': functools.reduce(mul, best_case_prob)}
                    output.append(odict)
                    return
                for sp in self._sp.species:
                    i = len(output_prob)
                    prob = self._sp.cond_prob(sp, species_list[i])
                    _recurse(output_prob + [prob], output_species + [sp])

        _recurse([], [])
        logging.info('{} substitutions found'.format(len(output)))
        return output
コード例 #26
0
ファイル: composition.py プロジェクト: georgeyumnam/pymatgen
    def get_wt_fraction(self, el):
        """
        Calculate weight fraction of an Element or Specie.

        Args:
            el (Element/Specie): Element or Specie to get fraction for.

        Returns:
            Weight fraction for element el in Composition
        """
        return get_el_sp(el).atomic_mass * abs(self[el]) / self.weight
コード例 #27
0
ファイル: composition.py プロジェクト: zacharygibbs/pymatgen
 def __add__(self, other):
     """
     Adds two compositions. For example, an Fe2O3 composition + an FeO
     composition gives a Fe3O4 composition.
     """
     new_el_map = collections.defaultdict(float)
     new_el_map.update(self)
     for k in other.keys():
         el = get_el_sp(k)
         new_el_map[el] += other[k]
     return Composition(new_el_map)
コード例 #28
0
    def __init__(self, symm_tol=0.1, max_radius=4, max_permutations=100000,
                 distance_scale_factor=1.015,
                 charge_neutrality_tolerance=CHARGE_NEUTRALITY_TOLERANCE,
                 forbidden_species=None):
        """
        Initializes the BV analyzer, with useful defaults.

        Args:
            symm_tol:
                Symmetry tolerance used to determine which sites are
                symmetrically equivalent. Set to 0 to turn off symmetry.
            max_radius:
                Maximum radius in Angstrom used to find nearest neighbors.
            max_permutations:
                The maximum number of permutations of oxidation states to test.
            distance_scale_factor:
                A scale factor to be applied. This is useful for scaling
                distances, esp in the case of calculation-relaxed structures
                which may tend to under (GGA) or over bind (LDA). The default
                of 1.015 works for GGA. For experimental structure, set this to
                1.
            charge_neutrality_tolerance:
                Tolerance on the charge neutrality when unordered structures
                are at stake.
            forbidden_species:
                List of species that are forbidden (example : ["O-"] cannot be
                used) It is used when e.g. someone knows that some oxidation
                state cannot occur for some atom in a structure or list of
                structures.
        """
        self.symm_tol = symm_tol
        self.max_radius = max_radius
        self.max_permutations = max_permutations
        self.dist_scale_factor = distance_scale_factor
        self.charge_neutrality_tolerance = charge_neutrality_tolerance
        forbidden_species = [get_el_sp(sp) for sp in forbidden_species] if \
            forbidden_species else []
        self.icsd_bv_data = {get_el_sp(specie): data
                             for specie, data in ICSD_BV_DATA.items()
                             if not specie in forbidden_species} \
            if len(forbidden_species) > 0 else ICSD_BV_DATA
コード例 #29
0
ファイル: sites.py プロジェクト: blondegeek/pymatgen
    def __init__(self, atoms_n_occu, coords, lattice, to_unit_cell=False,
                 coords_are_cartesian=False, properties=None):
        """
        Create a periodic site.

        Args:
            atoms_n_occu: Species on the site. Can be:

                i.  A sequence of element / specie specified either as string
                    symbols, e.g. ["Li", "Fe2+", "P", ...] or atomic numbers,
                    e.g., (3, 56, ...) or actual Element or Specie objects.
                ii. List of dict of elements/species and occupancies, e.g.,
                    [{"Fe" : 0.5, "Mn":0.5}, ...]. This allows the setup of
                    disordered structures.
            coords (3x1 array or sequence): Coordinates of site as fractional
                or cartesian coordinates.
            lattice: Lattice associated with the site
            to_unit_cell (bool): Translates fractional coordinate to the
                basic unit cell, i.e. all fractional coordinates satisfy 0
                <= a < 1. Defaults to False.
            coords_are_cartesian (bool): Set to True if you are providing
                cartesian coordinates. Defaults to False.
            properties (dict): Properties associated with the PeriodicSite,
                e.g., {"magmom":5}. Defaults to None.
        """
        if coords_are_cartesian:
            frac_coords = lattice.get_fractional_coords(coords)
            cart_coords = coords
        else:
            frac_coords = np.array(coords)
            cart_coords = lattice.get_cartesian_coords(coords)

        if to_unit_cell:
            frac_coords = np.mod(frac_coords, 1)
            cart_coords = lattice.get_cartesian_coords(frac_coords)
        if isinstance(atoms_n_occu, Composition):
            # Compositions are immutable, so don't need to copy (much faster)
            species = atoms_n_occu
        else:
            try:
                species = Composition({get_el_sp(atoms_n_occu): 1})
            except TypeError:
                species = Composition(atoms_n_occu)

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

        self._lattice = lattice
        self._frac_coords = frac_coords
        self._species = species
        self._coords = np.array(cart_coords)
        self.properties = properties or {}
コード例 #30
0
 def test_get_el_sp(self):
     self.assertEqual(get_el_sp("Fe2+"), Specie("Fe", 2))
     self.assertEqual(get_el_sp("3"), Element("Li"))
     self.assertEqual(get_el_sp("3.0"), Element("Li"))
     self.assertEqual(get_el_sp("U"), Element("U"))
     self.assertEqual(get_el_sp("X2+"), DummySpecie("X", 2))
     self.assertEqual(get_el_sp("Mn3+"), Specie("Mn", 3))
コード例 #31
0
def get_eleName(i_ele, eleOrd):
    """
    Get the element name

    Parameter
        i_ele: int (>0)
            The number of the elements show in the period table, from left to right and from top to bottom
               e.g. In the full period table, H: i_ele=1, He: i_ele=2, Hf: i_ele=58
        eleOrd: list[int]
            The list of the element order shown int the table (from left to right, from top to bottom)
    Return
        eleName: str
            The symbol of the element
    """
    ele_trueInd = eleOrd[i_ele - 1]
    elemap = {
        104: "Rf",
        105: "Db",
        106: "Sg",
        107: "Bh",
        108: "Hs",
        109: "Mt",
        110: "Ds",
        111: "Rg",
        112: "Cn",
        113: "Uut",
        114: "Fl",
        115: "Uup",
        116: "Lv",
        117: "Uus",
        118: "Uuo"
    }
    if ele_trueInd < 104:
        eleName = get_el_sp(ele_trueInd)
    else:
        eleName = elemap[ele_trueInd]
    return eleName
コード例 #32
0
    def get_projections_on_elements_and_orbitals(self, el_orb_spec):
        """
        Method returning a dictionary of projections on elements and specific
        orbitals

        Args:
            el_orb_spec: A dictionary of Elements and Orbitals for which we want
                to have projections on. It is given as: {Element:[orbitals]},
                e.g., {'Cu':['d','s']}

        Returns:
            A dictionary of projections on elements in the
            {Spin.up:[][{Element:{orb:values}}],
            Spin.down:[][{Element:{orb:values}}]} format
            if there is no projections in the band structure returns an empty
            dict.
        """
        result = {}
        structure = self.structure
        el_orb_spec = {get_el_sp(el): orbs for el, orbs in el_orb_spec.items()}
        for spin, v in self.projections.items():
            result[spin] = [[{str(e): collections.defaultdict(float)
                              for e in el_orb_spec}
                             for i in range(len(self.kpoints))]
                            for j in range(self.nb_bands)]

            for i, j, k in itertools.product(
                    range(self.nb_bands), range(len(self.kpoints)),
                    range(structure.num_sites)):
                sp = structure[k].specie
                for orb_i in range(len(v[i][j])):
                    o = Orbital(orb_i).name[0]
                    if sp in el_orb_spec:
                        if o in el_orb_spec[sp]:
                            result[spin][i][j][str(sp)][o] += v[i][j][
                                orb_i][k]
        return result
コード例 #33
0
 def __init__(self, dopant, ionic_radius_tol=float("inf"), min_length=10,
              alio_tol=0, codopant=False, max_structures_per_enum=100,
              allowed_doping_species=None, **kwargs):
     """
     Args:
         dopant (Specie-like): E.g., Al3+. Must have oxidation state.
         ionic_radius_tol (float): E.g., Fractional allowable ionic radii
             mismatch for dopant to fit into a site. Default of inf means
             that any dopant with the right oxidation state is allowed.
         min_Length (float): Min. lattice parameter between periodic
             images of dopant. Defaults to 10A for now.
         alio_tol (int): If this is not 0, attempt will be made to dope
             sites with oxidation_states +- alio_tol of the dopant. E.g.,
             1 means that the ions like Ca2+ and Ti4+ are considered as
             potential doping sites for Al3+.
         codopant (bool): If True, doping will be carried out with a
             codopant to maintain charge neutrality. Otherwise, vacancies
             will be used.
         max_structures_per_enum (float): Maximum number of structures to
             return per enumeration. Note that there can be more than one
             candidate doping site, and each site enumeration will return at
             max max_structures_per_enum structures. Defaults to 100.
         allowed_doping_species (list): Species that are allowed to be
             doping sites. This is an inclusionary list. If specified,
             any sites which are not
         \\*\\*kwargs:
             Same keyword args as :class:`EnumerateStructureTransformation`,
             i.e., min_cell_size, etc.
     """
     self.dopant = get_el_sp(dopant)
     self.ionic_radius_tol = ionic_radius_tol
     self.min_length = min_length
     self.alio_tol = alio_tol
     self.codopant = codopant
     self.max_structures_per_enum = max_structures_per_enum
     self.allowed_doping_species = allowed_doping_species
     self.kwargs = kwargs
コード例 #34
0
ファイル: jahnteller.py プロジェクト: squiton/pymatgen
    def get_magnitude_of_effect_from_species(self, species: Union[str,
                                                                  Species],
                                             spin_state: str,
                                             motif: str) -> str:
        """
        Get magnitude of Jahn-Teller effect from provided species, spin state and motif.

        Args:
          species: e.g. Fe2+
          spin_state: "high" or "low"
          motif: "oct" or "tet"

        Returns: "none", "weak" or "strong

        """

        magnitude = "none"

        sp = get_el_sp(species)

        # has to be Species; we need to know the oxidation state
        if isinstance(sp, Species) and sp.element.is_transition_metal:

            d_electrons = self._get_number_of_d_electrons(sp)

            if motif in self.spin_configs:
                if spin_state not in self.spin_configs[motif][d_electrons]:
                    spin_state = self.spin_configs[motif][d_electrons][
                        "default"]
                spin_config = self.spin_configs[motif][d_electrons][spin_state]
                magnitude = JahnTellerAnalyzer.get_magnitude_of_effect_from_spin_config(
                    motif, spin_config)
        else:
            warnings.warn("No data for this species.")

        return magnitude
コード例 #35
0
ファイル: composition.py プロジェクト: zacharygibbs/pymatgen
    def __sub__(self, other):
        """
        Subtracts two compositions. For example, an Fe2O3 composition - an FeO
        composition gives an FeO2 composition.

        Raises:
            CompositionError if the subtracted composition is greater than the
            original composition in any of its elements.
        """
        new_el_map = {el: self[el] for el in self}
        for k in other.keys():
            el = get_el_sp(k)
            if el in self and other[k] <= self[el]:
                new_el_map[el] -= other[k]
            else:
                raise CompositionError(
                    "All elements in subtracted composition must exist in "
                    "original composition in equal or lesser amount!")

            new_el_map = {
                sp: amt
                for sp, amt in new_el_map.items() if amt != 0
            }
        return Composition(new_el_map)
コード例 #36
0
ファイル: sites.py プロジェクト: zizai/pymatgen
    def __init__(self,
                 species: Union[str, Element, Specie, DummySpecie, Dict, Composition],
                 coords: Union[Tuple, List, np.ndarray],
                 properties: dict = None,
                 skip_checks: bool = False):
        """
        Creates a non-periodic Site.

        :param species: Species on the site. Can be:
            i.  A Composition-type object (preferred)
            ii. An  element / specie specified either as a string
                symbols, e.g. "Li", "Fe2+", "P" or atomic numbers,
                e.g., 3, 56, or actual Element or Specie objects.
            iii.Dict of elements/species and occupancies, e.g.,
                {"Fe" : 0.5, "Mn":0.5}. This allows the setup of
                disordered structures.
        :param coords: Cartesian coordinates of site.
        :param properties: Properties associated with the site as a dict, e.g.
            {"magmom": 5}. Defaults to None.
        :param skip_checks: Whether to ignore all the usual checks and just
            create the site. Use this if the Site is created in a controlled
            manner and speed is desired.
        """
        if not skip_checks:
            if not isinstance(species, Composition):
                try:
                    species = Composition({get_el_sp(species): 1})
                except TypeError:
                    species = Composition(species)
            totaloccu = species.num_atoms
            if totaloccu > 1 + Composition.amount_tolerance:
                raise ValueError("Species occupancies sum to more than 1!")
            coords = np.array(coords)
        self._species = species
        self.coords = coords
        self.properties = properties or {}
コード例 #37
0
    def get_element_spd_dos(self, el):
        """
        Get element and spd projected Dos

        Args:
            el: Element in Structure.composition associated with CompleteDos

        Returns:
            dict of {Element: {"S": densities, "P": densities, "D": densities}}
        """
        el = get_el_sp(el)
        el_dos = {}
        for site, atom_dos in self.pdos.items():
            if site.specie == el:
                for orb, pdos in atom_dos.items():
                    orbital_type = _get_orb_type(orb)
                    if orbital_type not in el_dos:
                        el_dos[orbital_type] = pdos
                    else:
                        el_dos[orbital_type] = \
                            add_densities(el_dos[orbital_type], pdos)

        return {orb: Dos(self.efermi, self.energies, densities)
                for orb, densities in el_dos.items()}
コード例 #38
0
ファイル: atat.py プロジェクト: mhsiron/pymatgen
    def structure_from_string(data):
        """
        Parses a rndstr.in, lat.in or bestsqs.out file into pymatgen's
        Structure format.

        :param data: contents of a rndstr.in, lat.in or bestsqs.out file
        :return: Structure object
        """

        data = data.splitlines()
        data = [x.split() for x in data if x]  # remove empty lines

        # following specification/terminology given in manual
        if len(data[0]) == 6:  # lattice parameters
            a, b, c, alpha, beta, gamma = map(float, data[0])
            coord_system = Lattice.from_parameters(a, b, c, alpha, beta,
                                                   gamma).matrix
            lattice_vecs = np.array(
                [
                    [data[1][0], data[1][1], data[1][2]],
                    [data[2][0], data[2][1], data[2][2]],
                    [data[3][0], data[3][1], data[3][2]],
                ],
                dtype=float,
            )
            first_species_line = 4
        else:
            coord_system = np.array(
                [
                    [data[0][0], data[0][1], data[0][2]],
                    [data[1][0], data[1][1], data[1][2]],
                    [data[2][0], data[2][1], data[2][2]],
                ],
                dtype=float,
            )
            lattice_vecs = np.array(
                [
                    [data[3][0], data[3][1], data[3][2]],
                    [data[4][0], data[4][1], data[4][2]],
                    [data[5][0], data[5][1], data[5][2]],
                ],
                dtype=float,
            )
            first_species_line = 6

        scaled_matrix = np.matmul(lattice_vecs, coord_system)
        lattice = Lattice(scaled_matrix)

        all_coords = []
        all_species = []
        for l in data[first_species_line:]:

            coords = np.array([l[0], l[1], l[2]], dtype=float)
            scaled_coords = np.matmul(coords, np.linalg.inv(lattice_vecs))
            all_coords.append(scaled_coords)

            species_strs = "".join(
                l[3:])  # join multiple strings back together
            species_strs = species_strs.replace(" ",
                                                "")  # trim any white space
            species_strs = species_strs.split(",")  # comma-delimited

            species = {}

            for species_occ in species_strs:

                # gets a species, occupancy pair
                species_occ = species_occ.split("=")

                if len(species_occ) == 1:
                    # assume occupancy is 1.0
                    species_occ = [species_occ[0], 1.0]

                if "_" in species_occ[0]:
                    # see to_string() method in this file, since , and = are not valid
                    # species names in AT-AT we replace "," with "__" and "=" with "___",
                    # for pymatgen to parse these back correctly we have to replace them back
                    species_occ[0] = (species_occ[0].replace("___",
                                                             "=").replace(
                                                                 "__", ","))

                species[get_el_sp(species_occ[0])] = float(species_occ[1])

            all_species.append(species)

        return Structure(lattice, all_species, all_coords)
コード例 #39
0
ファイル: _site.py プロジェクト: zhenming-xu/maml
    def transform_one(self, structure: Structure) -> pd.DataFrame:
        """
        Args:
            structure (Structure): Pymatgen Structure object.
        """
        elements = sorted(structure.symbol_set,
                          key=lambda sym: get_el_sp(sym).X)
        all_neighbors = structure.get_all_neighbors(self.cutoff)
        data = []

        for atom_idx, neighbors in enumerate(all_neighbors):
            center = structure[atom_idx].coords
            site_symmfuncs: List[np.ndarray] = []
            sorted_neighbors = sorted(
                neighbors, key=lambda neighbor: neighbor.species_string)
            temp_dict = {
                element:
                [(neighbor.coords, neighbor.nn_distance) for neighbor in group]
                for element, group in itertools.groupby(
                    sorted_neighbors,
                    key=lambda neighbor: neighbor.species_string)
            }
            for specie in elements:
                distances = np.array([
                    nn_distance for _, nn_distance in temp_dict[specie]
                ])[:, None, None]
                g2 = np.sum(np.exp(-self.r_etas *
                                   (distances - self.r_shift)**2) *
                            self._fc(distances),
                            axis=0)
                site_symmfuncs.extend(g2.ravel().tolist())

            for specie1, specie2 in itertools.combinations_with_replacement(
                    elements, 2):

                group1, group2 = temp_dict[specie1], temp_dict[specie2]

                c = itertools.combinations(range(len(group1)), 2) if specie1 == specie2 \
                    else itertools.product(range(len(group1)), range(len(group2)))
                index_combination = np.array(list(c)).T.tolist()

                coords_group1 = np.array([coords for coords, _ in group1])
                coords_group2 = np.array([coords for coords, _ in group2])
                distances_group1 = np.array(
                    [nn_distance for _, nn_distance in group1])
                distances_group2 = np.array(
                    [nn_distance for _, nn_distance in group2])

                v1 = coords_group1[index_combination[0]]
                v2 = coords_group2[index_combination[1]]
                d1 = distances_group1[index_combination[0]]
                d2 = distances_group2[index_combination[1]]
                d3 = np.linalg.norm(v1 - v2, axis=1)
                cosines = np.sum(
                    (v1 - center) * (v2 - center), axis=1) / (d1 * d2)
                cosines = cosines[:, None, None, None]
                distances = np.stack((d1, d2, d3))
                cutoffs = np.prod(self._fc(distances), axis=0)
                cutoffs = np.atleast_1d(cutoffs)[:, None, None, None]
                powers = np.sum(distances**2, axis=0)[:, None, None, None]
                g4 = np.sum((1 + self.lambdas * cosines)**self.zetas *
                            np.exp(-self.a_etas * powers) * cutoffs *
                            2.0**(1 - self.zetas),
                            axis=0)
                site_symmfuncs.extend(g4.ravel().tolist())

            data.append(site_symmfuncs)
        df = pd.DataFrame(data)
        return df
コード例 #40
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)
            else:
                m = re.findall(r"w?[A-Z][a-z]*", sym)
                if m and m != "?":
                    return m[0]
                return ""

        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) if \
                            symbol in special_symbols else 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)
                if coord not in coord_to_species:
                    coord_to_species[coord] = {el: occu}
                else:
                    coord_to_species[coord][el] = occu

        coord_to_species = {
            k: Composition(v)
            for k, v in coord_to_species.items()
        }
        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 species in allspecies:
                totaloccu = sum(species.values())
                if 1 < totaloccu <= self._occupancy_tolerance:
                    for key, value in six.iteritems(species):
                        species[key] = value / 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
コード例 #41
0
 def __getitem__(self, item: SpeciesLike):
     try:
         sp = get_el_sp(item)
         return self._data.get(sp, 0)
     except ValueError as ex:
         raise TypeError(f"Invalid key {item}, {type(item)} for Composition\nValueError exception:\n{ex}")
コード例 #42
0
    def apply_transformation(self, structure, return_ranked_list=False):
        # Make a mutable structure first
        mods = Structure.from_sites(structure)
        for sp, spin in self.mag_species_spin.items():
            sp = get_el_sp(sp)
            oxi_state = getattr(sp, "oxi_state", 0)
            if spin:
                up = Specie(sp.symbol, oxi_state, {"spin": abs(spin)})
                down = Specie(sp.symbol, oxi_state, {"spin": -abs(spin)})
                mods.replace_species({
                    sp:
                    Composition({
                        up: self.order_parameter,
                        down: 1 - self.order_parameter
                    })
                })
            else:
                mods.replace_species(
                    {sp: Specie(sp.symbol, oxi_state, {"spin": spin})})

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

        enum_args = self.kwargs

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

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

        t = EnumerateStructureTransformation(**enum_args)

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

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

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

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

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

        return self._all_structures[0:num_to_return]
コード例 #43
0
def write_wannier(skp, nbnd, nq, flg_phonon = False, flg_wan90 = False, flg_respack = True):
    #
    # Lattice information
    #
    avec = skp["primitive_lattice"]
    bvec = skp["reciprocal_primitive_lattice"]
    pos = skp["primitive_positions"]
    nat = len(skp["primitive_types"])
    atom = [str(get_el_sp(iat)) for iat in skp["primitive_types"]]
    #
    # band.gp : Gnuplot script
    #
    if not os.path.isfile("band.gp"):
        with open("band.gp", 'w') as f:
            print("#set terminal pdf color enhanced \\", file=f)
            print("#dashed dl 0.5 size 8.0cm, 6.0cm", file=f)
            print("#set output \"band.pdf\"", file=f)
            print("#", file=f)
            print("EF = ", file=f)
            print("Emin = ", file=f)
            print("Emax = ", file=f)
            print("#", file=f)
            n_sym_points = 1
            final = 0
            x0 = numpy.linalg.norm(avec[0, :]) * 0.5 / numpy.pi
            print("x%d = %f" % (n_sym_points, x0*skp["explicit_kpoints_linearcoord"][final]), file=f)
            for ipath in range(len(skp["path"])):
                start = skp["explicit_segments"][ipath][0]
                if start != final:
                    n_sym_points += 1
                    print("x%d = %f" % (n_sym_points, x0*skp["explicit_kpoints_linearcoord"][start]), file=f)
                n_sym_points += 1
                final = skp["explicit_segments"][ipath][1] - 1
                print("x%d = %f" % (n_sym_points, x0*skp["explicit_kpoints_linearcoord"][final]), file=f)
            print("#", file=f)
            print("set border lw 2", file=f)
            print("#", file=f)
            print("set style line 1 lt 1 lw 2 lc 0 dashtype 2", file=f)
            print("set style line 2 lt 1 lw 2 lc 0", file=f)
            print("set style line 3 lt 1 lw 1 lc 1", file=f)
            print("set style line 4 lt 1 lw 1 lc 2", file=f)
            print("set style line 5 lt 1 lw 1 lc 3", file=f)
            print("set style line 6 lt 1 lw 1 lc 4", file=f)
            print("#", file=f)
            print("set ytics scale 3.0, -0.5 1.0 font \'Cmr10,18\'", file=f)
            print("set xtics( \\", file=f)
            n_sym_points = 1
            final = 0
            label_f = skp["explicit_kpoints_labels"][final]
            if label_f == "GAMMA":
                label_f = "\\241"
            print("\"%s\" x%d" % (label_f, n_sym_points), end="", file=f)
            for ipath in range(len(skp["path"])):
                start = skp["explicit_segments"][ipath][0]
                label_s = skp["explicit_kpoints_labels"][start]
                if label_s == "GAMMA":
                    label_s = "\\241"
                label_f = skp["explicit_kpoints_labels"][final]
                if label_f == "GAMMA":
                    label_f = "\\241"
                if start != final:
                    n_sym_points += 1
                    print(", \\\n\"%s%s\" x%d" % (label_f, label_s, n_sym_points), end="", file=f)
                n_sym_points += 1
                final = skp["explicit_segments"][ipath][1] - 1
                label_f = skp["explicit_kpoints_labels"][final]
                if label_f == "GAMMA":
                    label_f = "\\241"
                print(", \\\n\"%s\" x%d" % (label_f, n_sym_points), end="", file=f)
            print(") \\\noffset 0.0, 0.0 font \'Cmr10,18\'", file=f)
            print("#", file=f)
            for ii in range(n_sym_points):
                print("set arrow from x%d, Emin to x%d, Emax nohead ls 2 front" % (ii+1, ii+1), file=f)
            print("#", file=f)
            print("unset key", file=f)
            print("#", file=f)
            print("set xzeroaxis ls 1", file=f)
            print("#", file=f)
            print("set ylabel \"Energy from {/Cmmi10 E}_F [eV]\" offset - 0.5, 0.0 font \'Cmr10,18\'", file=f)
            print("#", file=f)
            n_sym_points = 1
            final = 0
            for ipath in range(len(skp["path"])):
                start = skp["explicit_segments"][ipath][0]
                if start == final:
                    n_sym_points += 1
                    final = skp["explicit_segments"][ipath][1] - 1
                else:
                    break
            print("plot[:][Emin:Emax] \\", file=f)
            print("        \"bands.out.gnu\" u 1:($2-EF) w p ls 3, \\", file=f)
            print("        \"wannier_band.dat\" u ($1/%f):($2-EF) w p ls 3, \\" % x0, file=f)
            print("        \"dir-wan/dat.iband\" u ($1*x%d):($2-EF) w l ls 4" % n_sym_points, file=f)
            print("pause -1", file=f)
    #
    # wannier.win : wannier90 input
    #
    if flg_wan90 is True:
        if not os.path.isfile("wannier.win"):
            with open("wannier.win", 'w') as f:
                print("num_bands = %d" % nbnd, file=f)
                print(" num_wann = ", file=f)
                print("", file=f)
                print(" dis_win_min = ", file=f)
                print(" dis_win_max = ", file=f)
                print("dis_froz_min = ", file=f)
                print("dis_froz_max = ", file=f)
                print("", file=f)
                print("begin projections", file=f)
                print("end projections", file=f)
                print("!site_symmetry = .true.", file=f)
                print("", file=f)
                print("write_hr = .true.", file=f)
                print("bands_plot = .true.", file=f)
                print("wannier_plot = .true.", file=f)
                print("", file=f)
                print("wannier_plot_supercell = 3", file=f)
                print("begin kpoint_path", file=f)
                for ipath in range(len(skp["path"])):
                    start = skp["explicit_segments"][ipath][0]
                    final = skp["explicit_segments"][ipath][1] - 1
                    print("%s %f %f %f %s %f %f %f" % (
                        skp["explicit_kpoints_labels"][start],
                        skp["explicit_kpoints_rel"][start][0],
                        skp["explicit_kpoints_rel"][start][1],
                        skp["explicit_kpoints_rel"][start][2],
                        skp["explicit_kpoints_labels"][final],
                        skp["explicit_kpoints_rel"][final][0],
                        skp["explicit_kpoints_rel"][final][1],
                        skp["explicit_kpoints_rel"][final][2]),
                          file=f)
                print("end kpoint_path", file=f)
                print("", file=f)
                print("mp_grid = %d %d %d" % (nq[0], nq[1], nq[2]), file=f)
                print("", file=f)
                print("begin unit_cell_cart", file=f)
                print("Ang", file=f)
                for ii in range(3):
                    print(" %f %f %f" % (avec[ii, 0], avec[ii, 1], avec[ii, 2]), file=f)
                print("end unit_cell_cart", file=f)
                print("", file=f)
                print("begin atoms_frac", file=f)
                for iat in range(nat):
                    print(" %s %f %f %f" % (
                        atom[iat], pos[iat][0], pos[iat][1], pos[iat][2]), file=f)
                print("end atoms_frac", file=f)
                print("", file=f)
                print("begin kpoints", file=f)
                for i0 in range(nq[0]):
                    for i1 in range(nq[1]):
                        for i2 in range(nq[2]):
                            print(" %f %f %f" % (
                                    float(i0)/float(nq[0]),
                                    float(i1)/float(nq[1]),
                                    float(i2)/float(nq[2])
                            ), file=f)
                print("end kpoints", file=f)
    #
    # respack.in : Input file for RESPACK
    #
    if not os.path.isfile("respack.in"):
        with open("respack.in", 'w') as f:
            print("&PARAM_CHIQW", file=f)
            print("          Num_freq_grid = 1", file=f)
            print("!          Ecut_for_eps = ", file=f)
            print("               flg_cRPA = 1", file=f)
            print("! MPI_num_proc_per_qcomm = 1", file=f)
            print("!          MPI_num_qcomm = 1", file=f)
            print("!          flg_calc_type = 2", file=f)
            print("!               n_calc_q = 1", file=f)
            print("/", file=f)
            print("&PARAM_WANNIER", file=f)
            print("           N_wannier = ", file=f)
            print("     N_initial_guess = ", file=f)
            print(" Lower_energy_window = ", file=f)
            print(" Upper_energy_window = ", file=f)
            print("!flg_initial_guess_direc = 1", file=f)
            print("!   set_inner_window =.true.", file=f)
            print("! Lower_inner_window = ", file=f)
            print("! Upper_inner_window = ", file=f)
            print("/", file=f)
            for iat in range(nat):
                for prj in "s", "px", "py", "pz", "dxy", "dyz", "dzx", "dx2", "dz2":
                    print("%s 0.2 %f %f %f !%s%d" %
                          (prj, pos[iat][0], pos[iat][1], pos[iat][2],
                           atom[iat], iat+1), file=f)
            print("&PARAM_INTERPOLATION", file=f)
            n_sym_points = 1
            final = 0
            for ipath in range(len(skp["path"])):
                start = skp["explicit_segments"][ipath][0]
                if start == final:
                    n_sym_points += 1
                    final = skp["explicit_segments"][ipath][1] - 1
                else:
                    break
            print(" N_sym_points = %d" % n_sym_points, file=f)
            print("!       dense = %d, %d, %d" % (nq[0]*4, nq[1]*4, nq[2]*4), file=f)
            print("/", file=f)
            final = 0
            print("%f %f %f" % (
                skp["explicit_kpoints_rel"][final][0],
                skp["explicit_kpoints_rel"][final][1],
                skp["explicit_kpoints_rel"][final][2]),
                  file=f)
            for ipath in range(len(skp["path"])):
                start = skp["explicit_segments"][ipath][0]
                if start == final:
                    final = skp["explicit_segments"][ipath][1] - 1
                    print("%f %f %f" % (
                        skp["explicit_kpoints_rel"][final][0],
                        skp["explicit_kpoints_rel"][final][1],
                        skp["explicit_kpoints_rel"][final][2]),
                        file=f)
                else:
                    break
            print("&PARAM_VISUALIZATION", file=f)
            print("! flg_vis_wannier = 1,", file=f)
            print("       ix_vis_min = -1,", file=f)
            print("       ix_vis_max = 2,", file=f)
            print("       iy_vis_min = -1,", file=f)
            print("       iy_vis_max = 2,", file=f)
            print("       iz_vis_min = -1,", file=f)
            print("       iz_vis_max = 2", file=f)
            print("/", file=f)
            print("&PARAM_CALC_INT", file=f)
            print("  calc_ifreq = 1", file=f)
            print(" ix_intJ_min = 0", file=f)
            print(" ix_intJ_max = 0", file=f)
            print(" iy_intJ_min = 0", file=f)
            print(" iy_intJ_max = 0", file=f)
            print(" iz_intJ_min = 0", file=f)
            print(" iz_intJ_max = 0", file=f)
            print("/", file=f)
    #
    # disp.in : Phonon dispersion
    #
    if flg_phonon is True:
        if not os.path.isfile("disp.in"):
            with open("disp.in", 'w') as f:
                print("&INPUT", file=f)
                print(" flfrc = \'ifc.dat\'", file=f)
                print(" fldos = \' \'", file=f)
                print(" flfrq = \'matdyn.freq\'", file=f)
                print(" flvec = \' \'", file=f)
                print(" fleig = \' \'", file=f)
                print(" fldyn = \' \'", file=f)
                print(" fltau = \' \'", file=f)
                print("   la2f = .true.", file=f)
                print("   q_in_cryst_coord = .true.", file=f)
                print("   asr = \'crystal\'", file=f)
                print("/", file=f)
                print(len(skp["explicit_kpoints_rel"]), file=f)
                for ik in range(len(skp["explicit_kpoints_rel"])):
                    print(" %f %f %f 1.0" % (
                        skp["explicit_kpoints_rel"][ik][0],
                        skp["explicit_kpoints_rel"][ik][1],
                        skp["explicit_kpoints_rel"][ik][2]),
                          file=f)
    #
    # respack.in : Input file for RESPACK
    #
    if flg_respack is True:
        if not os.path.isfile("dcore.ini"):
            with open("dcore.ini", 'w') as f:
                print("[model]", file=f)
                print("lattice = wannier90", file=f)
                print("ncor = ", file=f)
                print("nelec = ", file=f)
                print("norb = ", file=f)
                print("seedname = wannier", file=f)
                print("equiv = None", file=f)
                print("bvec = [(%f, %f, %f)," % (bvec[0][0], bvec[0][1], bvec[0][2]), file=f)
                print("        (%f, %f, %f)," % (bvec[1][0], bvec[1][1], bvec[1][2]), file=f)
                print("        (%f, %f, %f)]" % (bvec[2][0], bvec[2][1], bvec[2][2]), file=f)
                print("spin_orbit = False", file=f)
                print("interaction = respack", file=f)
                print("density_density = False", file=f)
                print("kanamori = None", file=f)
                print("slater_f = None", file=f)
                print("slater_uj = None", file=f)
                print("non_colinear = False", file=f)
                print("", file=f)
                print("[system]", file=f)
                print("beta = 40.0", file=f)
                print("n_iw = 2048", file=f)
                print("n_tau = 10000", file=f)
                print("fix_mu = False", file=f)
                print("mu = 0.0", file=f)
                print("nk0 = %d" % (nq[0]*4), file=f)
                print("nk1 = %d" % (nq[1]*4), file=f)
                print("nk2 = %d" % (nq[2]*4), file=f)
                print("prec_mu = 0.0001", file=f)
                print("with_dc = True", file=f)
                print("perform_tail_fit = False", file=f)
                print("fit_max_moment = 2", file=f)
                print("fit_min_w = 5.0", file=f)
                print("fit_max_w = 10.0", file=f)
                print("n_l = 0", file=f)
                print("", file=f)
                print("[impurity_solver]", file=f)
                print("verbosity{int} = 10", file=f)
                print("#name = TRIQS/hubbard-I", file=f)
                print("#name = TRIQS/cthyb", file=f)
                print("#n_cycles{int} = 5000", file=f)
                print("#n_warmup_cycles{int} = 5000", file=f)
                print("#length_cycle{int} = 50", file=f)
                print("name = ALPS/cthyb", file=f)
                print("thermalization_time{int} = 60", file=f)
                print("max_time{int} = 120", file=f)
                print("", file=f)
                print("[control]", file=f)
                print("max_step = 100", file=f)
                print("sigma_mix = 0.5", file=f)
                print("restart = False", file=f)
                print("", file=f)
                print("[tool]", file=f)
                print("nk_line = 20", file=f)
                final = 0
                n_sym_points = 1
                print("knode = [(%s, %f, %f, %f)" % (
                    skp["explicit_kpoints_labels"][final],
                    skp["explicit_kpoints_rel"][final][0],
                    skp["explicit_kpoints_rel"][final][1],
                    skp["explicit_kpoints_rel"][final][2]),
                      file=f, end="")
                for ipath in range(len(skp["path"])):
                    start = skp["explicit_segments"][ipath][0]
                    if start == final:
                        n_sym_points += 1
                        final = skp["explicit_segments"][ipath][1] - 1
                        print(",\n         (%s, %f, %f, %f)" % (
                            skp["explicit_kpoints_labels"][final],
                            skp["explicit_kpoints_rel"][final][0],
                            skp["explicit_kpoints_rel"][final][1],
                            skp["explicit_kpoints_rel"][final][2]),
                            file=f, end="")
                    else:
                        break
                print("]", file=f)
                print("nnode = %d" % n_sym_points, file=f)
                print("omega_min = -1", file=f)
                print("omega_max = 1", file=f)
                print("Nomega = 100", file=f)
                print("broadening = 0.1", file=f)
                print("eta = 0.0", file=f)
                print("omega_pade = 5.0", file=f)
                print("omega_check = 5.0", file=f)
コード例 #44
0
def get_pdos(dos, lm_orbitals=None, atoms=None, elements=None):
    """Extract the projected density of states from a CompleteDos object.

    Args:
        dos (:obj:`~pymatgen.electronic_structure.dos.CompleteDos`): The
            density of states.
        elements (:obj:`dict`, optional): The elements and orbitals to extract
            from the projected density of states. Should be provided as a
            :obj:`dict` with the keys as the element names and corresponding
            values as a :obj:`tuple` of orbitals. For example, the following
            would extract the Bi s, px, py and d orbitals::

                {'Bi': ('s', 'px', 'py', 'd')}

            If an element is included with an empty :obj:`tuple`, all orbitals
            for that species will be extracted. If ``elements`` is not set or
            set to ``None``, all elements for all species will be extracted.
        lm_orbitals (:obj:`dict`, optional): The orbitals to decompose into
            their lm contributions (e.g. p -> px, py, pz). Should be provided
            as a :obj:`dict`, with the elements names as keys and a
            :obj:`tuple` of orbitals as the corresponding values. For example,
            the following would be used to decompose the oxygen p and d
            orbitals::

                {'O': ('p', 'd')}

        atoms (:obj:`dict`, optional): Which atomic sites to use when
            calculating the projected density of states. Should be provided as
            a :obj:`dict`, with the element names as keys and a :obj:`tuple` of
            :obj:`int` specifying the atomic indices as the corresponding
            values. The elemental projected density of states will be summed
            only over the atom indices specified. If an element is included
            with an empty :obj:`tuple`, then all sites for that element will
            be included. The indices are 0 based for each element specified in
            the POSCAR. For example, the following will calculate the density
            of states for the first 4 Sn atoms and all O atoms in the
            structure::

                {'Sn': (1, 2, 3, 4), 'O': (, )}

            If ``atoms`` is not set or set to ``None`` then all atomic sites
            for all elements will be considered.

    Returns:
        dict: The projected density of states. Formatted as a :obj:`dict` of
        :obj:`dict` mapping the elements and their orbitals to
        :obj:`~pymatgen.electronic_structure.dos.Dos` objects. For example::

            {
                'Bi': {'s': Dos, 'p': Dos ... },
                'S': {'s': Dos}
            }
    """
    if not elements:
        symbols = dos.structure.symbol_set
        elements = dict(zip(symbols, [None] * len(symbols)))
    pdos = {}
    for el in elements:
        if atoms and el not in atoms:
            continue

        # select which sites to consider, if no sites were specified then
        # select all. Make a list of the sites of particular elements first
        # due to the dosplot atoms list specification (e.g. starts at 0 for
        # each element
        element_sites = [
            site for site in dos.structure.sites
            if site.specie == get_el_sp(el)
        ]
        sites = [
            site for i, site in enumerate(element_sites)
            if not atoms or (el in atoms and i in atoms[el])
        ]
        lm = lm_orbitals[el] if (lm_orbitals and el in lm_orbitals) else None
        orbitals = elements[el] if elements and el in elements else None

        pdos[el] = get_element_pdos(dos, el, sites, lm, orbitals)
    return pdos
コード例 #45
0
 def apply_transformation(self, structure):
     s = Structure.from_sites(structure.sites)
     map(s.remove_species, [[get_el_sp(sp)] for sp in self._species])
     return s
コード例 #46
0
 def get_lambda(self, s1, s2):
     k = frozenset([get_el_sp(s1), get_el_sp(s2)])
     return self._l.get(k, self.alpha)
コード例 #47
0
ファイル: substitutor.py プロジェクト: zizai/pymatgen
    def pred_from_structures(self,
                             target_species,
                             structures_list,
                             remove_duplicates=True,
                             remove_existing=False):
        """
        performs a structure prediction targeting compounds containing all of
        the target_species, based on a list of structure (those structures
        can for instance come from a database like the ICSD). It will return
        all the structures formed by ionic substitutions with a probability
        higher than the threshold

        Notes:
        If the default probability model is used, input structures must
        be oxidation state decorated. See AutoOxiStateDecorationTransformation

        This method does not change the number of species in a structure. i.e
        if the number of target species is 3, only input structures containing
        3 species will be considered.

        Args:
            target_species:
                a list of species with oxidation states
                e.g., [Specie('Li',1),Specie('Ni',2), Specie('O',-2)]

            structures_list:
                a list of dictionnary of the form {'structure':Structure object
                ,'id':some id where it comes from}
                the id can for instance refer to an ICSD id.

            remove_duplicates:
                if True, the duplicates in the predicted structures will
                be removed

            remove_existing:
                if True, the predicted structures that already exist in the
                structures_list will be removed

        Returns:
            a list of TransformedStructure objects.
        """
        target_species = get_el_sp(target_species)
        result = []
        transmuter = StandardTransmuter([])
        if len(list(set(target_species) & set(self.get_allowed_species()))) \
                != len(target_species):
            raise ValueError("the species in target_species are not allowed " +
                             "for the probability model you are using")

        for permut in itertools.permutations(target_species):
            for s in structures_list:
                # check if: species are in the domain,
                # and the probability of subst. is above the threshold
                els = s['structure'].composition.elements
                if len(els) == len(permut) and len(list(set(els) & set(self.get_allowed_species()))) == \
                        len(els) and self._sp.cond_prob_list(permut, els) > self._threshold:

                    clean_subst = {
                        els[i]: permut[i]
                        for i in range(0, len(els)) if els[i] != permut[i]
                    }

                    if len(clean_subst) == 0:
                        continue

                    transf = SubstitutionTransformation(clean_subst)

                    if Substitutor._is_charge_balanced(
                            transf.apply_transformation(s['structure'])):
                        ts = TransformedStructure(s['structure'], [transf],
                                                  history=[{
                                                      "source": s['id']
                                                  }],
                                                  other_parameters={
                                                      'type':
                                                      'structure_prediction',
                                                      'proba':
                                                      self._sp.cond_prob_list(
                                                          permut, els)
                                                  })
                        result.append(ts)
                        transmuter.append_transformed_structures([ts])

        if remove_duplicates:
            transmuter.apply_filter(
                RemoveDuplicatesFilter(symprec=self._symprec))
        if remove_existing:
            # Make the list of structures from structures_list that corresponds to the
            # target species
            chemsys = list(set([sp.symbol for sp in target_species]))
            structures_list_target = [
                st['structure'] for st in structures_list
                if Substitutor._is_from_chemical_system(
                    chemsys, st['structure'])
            ]
            transmuter.apply_filter(
                RemoveExistingFilter(structures_list_target,
                                     symprec=self._symprec))
        return transmuter.transformed_structures
コード例 #48
0
 def order_elements(el):
     if self.use_iupac_formula:
         return [get_el_sp(el).X, el]
     else:
         return [get_el_sp(el).iupac_ordering, el]
コード例 #49
0
    def _calculate_equivalent_sites(
        self,
        likeness_tol: float = 0.001,
        bond_dist_tol: float = 0.01,
        bond_angle_tol: float = 0.1,
    ) -> List[int]:
        """Determines the indices of the structurally inequivalent sites.

        Args:
            likeness_tol: The tolerance used to determine if two likeness
                parameters are the same.
            bond_dist_tol: The tolerance used to determine if two bond lengths
                are the same.
            bond_angle_tol: The tolerance used to determine if two bond angles
                are the same.

        Two sites are considered equivalent if they are the same element, and
        have the same geometry and (next) nearest neighbors.

        Returns:
            A :obj:`list` of indices mapping each site in the structure to a
            structurally equivalent site. For example, if the first two sites
            are equivalent and the last two are both inequivalent, the data will
            be formatted as::

                [0, 0, 2, 3]

        """
        # TODO: Use site fingerprint rather than geometry type.
        inequiv_sites = {}
        equivalent_sites = []

        for site_index, site in enumerate(self.bonded_structure.structure):
            element = get_el_sp(site.specie)
            geometry = self.get_site_geometry(site_index)
            nn_sites = self.get_nearest_neighbors(
                site_index, inc_inequivalent_site_index=False)
            nnn_sites = self.get_next_nearest_neighbors(
                site_index, inc_inequivalent_site_index=False)

            matched = False
            for inequiv_index, inequiv_site in inequiv_sites.items():
                elem_match = element == inequiv_site["element"]
                geom_match = geometries_match(geometry,
                                              inequiv_site["geometry"],
                                              likeness_tol=likeness_tol)
                nn_match = nn_summaries_match(nn_sites,
                                              inequiv_site["nn_sites"],
                                              bond_dist_tol=bond_dist_tol)
                nnn_match = nnn_summaries_match(nnn_sites,
                                                inequiv_site["nnn_sites"],
                                                bond_angle_tol=bond_angle_tol)

                if elem_match and geom_match and nn_match and nnn_match:
                    equivalent_sites.append(inequiv_index)
                    matched = True
                    break

            if not matched:
                # no matches therefore store original site index
                equivalent_sites.append(site_index)
                site_data = {
                    "element": element,
                    "geometry": geometry,
                    "nn_sites": nn_sites,
                    "nnn_sites": nnn_sites,
                }
                inequiv_sites[site_index] = site_data

        return equivalent_sites
コード例 #50
0
ファイル: composition.py プロジェクト: HiPeter/pymatgen
 def __getitem__(self, el):
     """
     Get the amount for element.
     """
     return self._elmap.get(get_el_sp(el), 0)
コード例 #51
0
    def _get_structure(self, data, primitive):
        """
        Generate structure from part of the cif.
        """
        def parse_symbol(sym):
            # Common representations for elements/water in cif files
            # TODO: fix inconsistent handling of water
            special = {
                "D": "D",
                "Hw": "H",
                "Ow": "O",
                "Wat": "O",
                "wat": "O",
                "OH": "",
                "OH2": ""
            }
            m = re.findall(r"w?[A-Z][a-z]*", sym)
            if m and m != "?":
                if sym in special:
                    v = special[sym]
                else:
                    v = special.get(m[0], m[0])
                if len(m) > 1 or (m[0] in special):
                    warnings.warn("{} parsed as {}".format(sym, v))
                return v

        lattice = self.get_lattice(data)

        # if magCIF, get magnetic symmetry moments and magmoms
        # else standard CIF, and use empty magmom dict
        if self.feature_flags["magcif_incommensurate"]:
            raise NotImplementedError(
                "Incommensurate structures not currently supported.")
        elif self.feature_flags["magcif"]:
            self.symmetry_operations = self.get_magsymops(data)
            magmoms = self.parse_magmoms(data, lattice=lattice)
        else:
            self.symmetry_operations = self.get_symops(data)
            magmoms = {}

        oxi_states = self.parse_oxi_states(data)

        coord_to_species = OrderedDict()
        coord_to_magmoms = OrderedDict()

        def get_matching_coord(coord):
            keys = list(coord_to_species.keys())
            coords = np.array(keys)
            for op in self.symmetry_operations:
                c = op.operate(coord)
                inds = find_in_coord_list_pbc(coords,
                                              c,
                                              atol=self._site_tolerance)
                # cant use if inds, because python is dumb and np.array([0]) evaluates
                # to False
                if len(inds):
                    return keys[inds[0]]
            return False

        for i in range(len(data["_atom_site_label"])):
            try:
                # If site type symbol exists, use it. Otherwise, we use the
                # label.
                symbol = parse_symbol(data["_atom_site_type_symbol"][i])
            except KeyError:
                symbol = parse_symbol(data["_atom_site_label"][i])
            if not symbol:
                continue

            if oxi_states is not None:
                o_s = oxi_states.get(symbol, 0)
                # use _atom_site_type_symbol if possible for oxidation state
                if "_atom_site_type_symbol" in data.data.keys():
                    oxi_symbol = data["_atom_site_type_symbol"][i]
                    o_s = oxi_states.get(oxi_symbol, o_s)
                try:
                    el = Specie(symbol, o_s)
                except:
                    el = DummySpecie(symbol, o_s)
            else:
                el = get_el_sp(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])
            magmom = magmoms.get(data["_atom_site_label"][i], Magmom(0))

            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})
                    coord_to_magmoms[coord] = magmom
                else:
                    coord_to_species[match] += {el: occu}
                    coord_to_magmoms[
                        match] = None  # disordered magnetic not currently supported

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

        allspecies = []
        allcoords = []
        allmagmoms = []

        # check to see if magCIF file is disordered
        if self.feature_flags["magcif"]:
            for k, v in coord_to_magmoms.items():
                if v is None:
                    # Proposed solution to this is to instead store magnetic moments
                    # as Specie 'spin' property, instead of site property, but this
                    # introduces ambiguities for end user (such as unintended use of
                    # `spin` and Specie will have fictious oxidation state).
                    raise NotImplementedError(
                        'Disordered magnetic structures not currently supported.'
                    )

        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]
                tmp_magmom = [
                    coord_to_magmoms[tmp_coord] for tmp_coord in tmp_coords
                ]

                if self.feature_flags["magcif"]:
                    coords, magmoms = self._unique_coords(
                        tmp_coords, tmp_magmom)
                else:
                    coords, magmoms = self._unique_coords(tmp_coords)

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

            # 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) and len(
                allspecies) == len(allmagmoms):

            if self.feature_flags["magcif"]:
                struct = Structure(lattice,
                                   allspecies,
                                   allcoords,
                                   site_properties={"magmom": allmagmoms})
            else:
                struct = Structure(lattice, allspecies, allcoords)

            struct = struct.get_sorted_structure()

            if primitive:
                struct = struct.get_primitive_structure()
                struct = struct.get_reduced_structure()
            return struct
コード例 #52
0
ファイル: composition.py プロジェクト: HiPeter/pymatgen
 def __contains__(self, el):
     return get_el_sp(el) in self._elmap
コード例 #53
0
ファイル: string.py プロジェクト: JMPriest/test_matminer
def disordered_formula(disordered_struct, symbols=('x', 'y', 'z'), fmt='plain'):
    """
    Returns a formula of a form like AxB1-x (x=0.5)
    for disordered structures. Will only return a
    formula for disordered structures with one
    kind of disordered site at present.

    Args:
        disordered_struct: a disordered structure
        symbols: a tuple of characters to use for
        subscripts, by default this is ('x', 'y', 'z')
        but if you have more than three disordered
        species more symbols will need to be added
        fmt (str): 'plain', 'HTML' or 'LaTeX'

    Returns (str): a disordered formula string
    """

    # this is in string utils and not in
    # Composition because we need to have access
    # to site occupancies to calculate this, so
    # have to pass the full structure as an argument
    # (alternatively this could be made a method on
    # Structure)
    from pymatgen.core.composition import Composition
    from pymatgen.core.periodic_table import get_el_sp

    if disordered_struct.is_ordered:
        raise ValueError("Structure is not disordered, "
                         "so disordered formula not defined.")

    disordered_site_compositions = {site.species_and_occu
                                    for site in disordered_struct if not site.is_ordered}

    if len(disordered_site_compositions) > 1:
        # this probably won't happen too often
        raise ValueError("Ambiguous how to define disordered "
                         "formula when more than one type of disordered "
                         "site is present.")
    disordered_site_composition = disordered_site_compositions.pop()

    disordered_species = {str(sp) for sp, occu in disordered_site_composition.items()}

    if len(disordered_species) > len(symbols):
        # this probably won't happen too often either
        raise ValueError("Not enough symbols to describe disordered composition: "
                         "{}".format(symbols))
    symbols = list(symbols)[0:len(disordered_species) - 1]

    comp = disordered_struct.composition.get_el_amt_dict().items()
    # sort by electronegativity, as per composition
    comp = sorted(comp, key=lambda x: get_el_sp(x[0]).X)

    disordered_comp = []
    variable_map = {}

    total_disordered_occu = sum([occu for sp, occu in comp
                                 if str(sp) in disordered_species])

    # composition to get common factor
    factor_comp = disordered_struct.composition.as_dict()
    factor_comp['X'] = total_disordered_occu
    for sp in disordered_species:
        del factor_comp[str(sp)]
    factor_comp = Composition.from_dict(factor_comp)
    factor = factor_comp.get_reduced_formula_and_factor()[1]

    total_disordered_occu /= factor
    remainder = "{}-{}".format(formula_double_format(total_disordered_occu, ignore_ones=False),
                               '-'.join(symbols))

    for sp, occu in comp:
        sp = str(sp)
        if sp not in disordered_species:
            disordered_comp.append((sp, formula_double_format(occu/factor)))
        else:
            if len(symbols) > 0:
                symbol = symbols.pop(0)
                disordered_comp.append((sp, symbol))
                variable_map[symbol] = occu / total_disordered_occu / factor
            else:
                disordered_comp.append((sp, remainder))

    if fmt == 'LaTeX':
        sub_start = "_{"
        sub_end = "}"
    elif fmt == 'HTML':
        sub_start = "<sub>"
        sub_end = "</sub>"
    elif fmt != 'plain':
        raise ValueError("Unsupported output format, "
                         "choose from: LaTeX, HTML, plain")

    disordered_formula = []
    for sp, occu in disordered_comp:
        disordered_formula.append(sp)
        if occu:  # can be empty string if 1
            if fmt != 'plain':
                disordered_formula.append(sub_start)
            disordered_formula.append(occu)
            if fmt != 'plain':
                disordered_formula.append(sub_end)
    disordered_formula.append(" ")
    disordered_formula += ["{}={} ".format(k, formula_double_format(v))
                           for k, v in variable_map.items()]

    comp = disordered_struct.composition

    return "".join(map(str, disordered_formula))[0:-1]
コード例 #54
0
 def apply_transformation(self, structure):
     s = structure.copy()
     for sp in self.species_to_remove:
         s.remove_species([get_el_sp(sp)])
     return s
コード例 #55
0
 def get_px(self, sp):
     return self._px[get_el_sp(sp)]
コード例 #56
0
def structure2input(structure, prefix, dk_path, dq_grid, pseudo_kind,
                    pseudo_dir, queue, rel):

    if pseudo_kind == "sg15":
        if rel:
            from sg15_rel import pseudo_dict, ecutwfc_dict, ecutrho_dict, valence_dict, atomwfc_dict
        else:
            from sg15 import pseudo_dict, ecutwfc_dict, ecutrho_dict, valence_dict, atomwfc_dict
    elif pseudo_kind == "pslibrary":
        if rel:
            from pslibrary_rel import pseudo_dict, ecutwfc_dict, ecutrho_dict, valence_dict, atomwfc_dict
        else:
            from pslibrary import pseudo_dict, ecutwfc_dict, ecutrho_dict, valence_dict, atomwfc_dict
    else:
        from sssp import pseudo_dict, ecutwfc_dict, ecutrho_dict, valence_dict, atomwfc_dict
    #
    # Band path and primitive lattice
    #
    frac_coord2 = numpy.array(structure.frac_coords)
    for ipos in range(len(frac_coord2)):
        for iaxis in range(3):
            coord3 = frac_coord2[ipos, iaxis] * 6.0
            if abs(round(coord3) - coord3) < 0.001:
                frac_coord2[ipos, iaxis] = float(round(coord3)) / 6.0
    #
    skp = seekpath.get_explicit_k_path(
        (structure.lattice.matrix, frac_coord2,
         [pymatgen.Element(str(spc)).number for spc in structure.species]),
        reference_distance=dk_path)
    #
    # Lattice information
    #
    bvec = skp["reciprocal_primitive_lattice"]
    atom = [str(get_el_sp(iat)) for iat in skp["primitive_types"]]
    typ = set(atom)
    #
    # WFC and Rho cutoff
    #
    ecutwfc = 0.0
    ecutrho = 0.0
    for ityp in typ:
        if ecutwfc < ecutwfc_dict[str(ityp)]:
            ecutwfc = ecutwfc_dict[str(ityp)]
        if ecutrho < ecutrho_dict[str(ityp)]:
            ecutrho = ecutrho_dict[str(ityp)]
    #
    # k and q grid
    #
    nq = numpy.zeros(3, numpy.int_)
    for ii in range(3):
        norm = numpy.sqrt(numpy.dot(bvec[ii][:], bvec[ii][:]))
        nq[ii] = round(norm / dq_grid)
        print(norm)
    print("Coarse grid : ", nq[0], nq[1], nq[2])
    #
    # Band path
    #
    print("Band path")
    for ipath in range(len(skp["path"])):
        start = skp["explicit_segments"][ipath][0]
        final = skp["explicit_segments"][ipath][1] - 1
        print("%5d %8s %10.5f %10.5f %10.5f %8s %10.5f %10.5f %10.5f" %
              (final - start + 1, skp["explicit_kpoints_labels"][start],
               skp["explicit_kpoints_rel"][start][0],
               skp["explicit_kpoints_rel"][start][1],
               skp["explicit_kpoints_rel"][start][2],
               skp["explicit_kpoints_labels"][final],
               skp["explicit_kpoints_rel"][final][0],
               skp["explicit_kpoints_rel"][final][1],
               skp["explicit_kpoints_rel"][final][2]))
    #
    # Number of electrons
    #
    nbnd = 0
    for iat in atom:
        nbnd += valence_dict[iat]
    if rel:
        nbnd *= 2
    #
    # Shell scripts
    #
    structure2 = pymatgen.Structure(skp["primitive_lattice"],
                                    skp["primitive_types"],
                                    skp["primitive_positions"])
    spg_analysis = SpacegroupAnalyzer(structure2)
    middle = spg_analysis.get_ir_reciprocal_mesh(mesh=(nq[0] * 2, nq[1] * 2,
                                                       nq[2] * 2),
                                                 is_shift=(0, 0, 0))
    dense = spg_analysis.get_ir_reciprocal_mesh(mesh=(nq[0] * 4, nq[1] * 4,
                                                      nq[2] * 4),
                                                is_shift=(0, 0, 0))
    print("Number of irreducible k : ", len(middle), len(dense))
    write_sh(len(middle), len(dense), len(skp["explicit_kpoints_rel"]), atom,
             prefix, atomwfc_dict, queue)
    #
    # rx.in, scf.in, nscf.in, band.in , nscf_w.in, nscf_r.in
    #
    write_pwx(prefix, skp, pseudo_dir, ecutwfc, ecutrho, pseudo_dict, nq, nbnd,
              rel)
    #
    # ph.in, elph.in, epmat.in, phdos.in, rpa.in, scdft.in
    #
    write_ph(prefix, nq, ecutwfc, nbnd)
    #
    # bands.in, pp.in, proj.in, pw2wan.in, q2r.in
    #
    write_pp(prefix)
    #
    # band.gp, {prefix}.win, respack.in, disp.in
    #
    write_wannier(prefix, skp, nbnd, nq)
    #
    # openmx.in : Input file for openmx
    #
    if not os.path.isfile("openmx.in"):
        write_openmx(prefix, skp, nq, rel)
コード例 #57
0
def get_material(query_info):
	try:
		#We copy the internals of the get_bandstructure_by_material_id method
		#to handle missing material ids
		with MPRester() as mp:
			if query_info["has_bandstructure"] == False:
				raise NoMaterialAtIDError

			#check if material is stable
			if query_info["e_above_hull"] > 0:
				return

			formula_numbers = query_info["anonymous_formula"]
			first_elem_num = formula_numbers["A"] if "A" in formula_numbers else 0
			second_elem_num = formula_numbers["B"] if "B" in formula_numbers else 0
			third_elem_num = formula_numbers["C"] if "C" in formula_numbers else 0 
			if (first_elem_num > SINGLE_SUBSCRIPT or second_elem_num > SINGLE_SUBSCRIPT 
				or third_elem_num > SINGLE_SUBSCRIPT or first_elem_num + second_elem_num + third_elem_num > TOTAL_SUBSCRIPT):
				return

			name = query_info["pretty_formula"]
			if ('Cd' in name) or ('Hg' in name) or ('Pb' in name) or ('Cr' in name): #check if material has Cd
				if PRINT_DEBUG:
					print "Material with index " + query_info["material_id"] + " contains prohibited material!"
				return

			banddata = mp.get_data(query_info['material_id'], prop="bandstructure")
			bandstruct = banddata[0]["bandstructure"]
				
			bandinfo = bandstruct.get_band_gap()
			bandgap = CORRECTION_SLOPE*bandinfo["energy"]+CORRECTION_OFFSET
			
			if PRINT_INFO:
				print "For " + name + ", we have " + ("a direct" if bandinfo["direct"] else "an indirect") + " band gap of " + str(bandgap) + " eV."

			if (bandstruct.is_metal()):
				return

			# Save information
			oxidizedstates = query_info['bv_structure'].get_primitive_structure()
			total_e = 0
			if(type(oxidizedstates.species[0]) is not periodic_table.Specie):
				return

			#TODO: Investigate how specie_and_occu differs?
			#For now, assume we don't don't
			for specie in oxidizedstates.species:
				if specie.Z > MAX_ALLOWED_ELEMENT:
					return
				num_electrons = specie.Z+specie.oxi_state
				#skip if beyond known elements. This actually works for ions with
				#no electrons (ie H^+ or He^2+)
				if(num_electrons <= 0 or num_electrons > MAX_DEFINED_ELEMENT):
					continue
				#This is a hack of sorts. We get the element that has the same number 
				#of electrons as our ion, and use that as the electronic structure
				orbitals = periodic_table.get_el_sp(int(num_electrons)).full_electronic_structure
				if len(orbitals) <= 1:
					continue
				index = -1
				max_orb_num = orbitals[index][0]
				#This will exclude d and higher electrons because they will never be the top
				#principal quantum number. We could also grab this directly to be safe
				while orbitals[index][0] == max_orb_num:
					total_e += orbitals[index][2]
					index -= 1

			N_VB = total_e/4
			N_CB = total_e/8
			# Calculate branch point energy (ebp) and correct
			ebp = calcEBP(bandstruct, name, N_VB, N_CB, PRINT_DEBUG)
			return {'name':name, 'vbm': -ebp, 'cbm': bandgap-ebp, 'bandinfo': bandinfo}

	except NoMaterialAtIDError:
		if PRINT_DEBUG:
			print "Material with index " + query_info["material_id"] + " does not exist!"

	except MPRestError:
		if PRINT_DEBUG:
			print "No band struct or density of states at index " + query_info["material_id"] + "!"
コード例 #58
0
ファイル: data.py プロジェクト: aaggarw99/matminer
    def get_property(self, comp, property_name):
        """
        Get descriptor data for elements in a compound from pymatgen.

        Args:
            comp (str/Composition): Either pymatgen Composition object or string formula,
                eg: "NaCl", "Na+1Cl-1", "Fe2+3O3-2" or "Fe2 +3 O3 -2"
                Notes:
                     - For 'ionic_radii' property, the Composition object must be made of oxidation
                        state decorated Specie objects not the plain Element objects.
                        eg.  fe2o3 = Composition({Specie("Fe", 3): 2, Specie("O", -2): 3})
                     - For string formula, the oxidation state sign(+ or -) must be specified explicitly.
                        eg.  "Fe2+3O3-2"

            property_name (str): pymatgen element attribute name, as defined in the Element class at
                http://pymatgen.org/_modules/pymatgen/core/periodic_table.html

        Returns:
            (list) of values containing descriptor floats for each atom in the compound(sorted by the
                electronegativity of the contituent atoms)

        """
        eldata = []
        # what are these named tuples for? not used or returned! -KM
        eldata_tup_lst = []
        eldata_tup = namedtuple('eldata_tup', 'element propname propvalue propunit amt')

        oxidation_states = {}
        if isinstance(comp, Composition):
            # check whether the composition is composed of oxidation state decorated species (not
            # just plain Elements)
            if hasattr(comp.elements[0], "oxi_state"):
                oxidation_states = dict(
                    [(str(sp.element), sp.oxi_state) for sp in comp.elements])
            el_amt_dict = comp.get_el_amt_dict()
        # string
        else:
            comp, oxidation_states = self.get_composition_oxidation_state(comp)
            el_amt_dict = comp.get_el_amt_dict()

        symbols = sorted(el_amt_dict.keys(), key=lambda sym: get_el_sp(sym).X)

        for el_sym in symbols:

            element = Element(el_sym)
            property_value = None
            property_units = None

            try:
                p = getattr(element, property_name)
            except AttributeError:
                print("{} attribute missing".format(property_name))
                raise

            if p is not None:
                if property_name in ['ionic_radii']:
                    if oxidation_states:
                        property_value = element.ionic_radii[oxidation_states[el_sym]]
                        property_units = Unit("ang")
                    else:
                        raise ValueError(
                            "oxidation state not given for {}; It does not yield a unique "
                            "number per Element".format(property_name))
                else:
                    property_value = float(p)

                # units are None for these pymatgen descriptors
                # todo: there seem to be a lot more unitless descriptors which are not listed here... -Alex D
                if property_name not in ['X', 'Z', 'group', 'row', 'number', 'mendeleev_no',
                                         'ionic_radii']:
                    property_units = p.unit

            # Make a named tuple out of all the available information
            eldata_tup_lst.append(
                eldata_tup(element=el_sym, propname=property_name, propvalue=property_value,
                           propunit=property_units, amt=el_amt_dict[el_sym]))

            # Add descriptor values, one for each atom in the compound
            for i in range(int(el_amt_dict[el_sym])):
                eldata.append(property_value)

        return eldata
コード例 #59
0
 def __contains__(self, item):
     try:
         sp = get_el_sp(item)
         return sp in self._data
     except ValueError as ex:
         raise TypeError(f"Invalid key {item}, {type(item)} for Composition\nValueError exception:\n{ex}")
コード例 #60
0
    def apply_transformation(self, structure, return_ranked_list=False):
        """
        Args:
            structure (Structure): Input structure to dope

        Returns:
            [{"structure": Structure, "energy": float}]
        """
        comp = structure.composition
        logger.info("Composition: %s" % comp)

        for sp in comp:
            try:
                sp.oxi_state
            except AttributeError:
                analyzer = BVAnalyzer()
                structure = analyzer.get_oxi_state_decorated_structure(
                    structure)
                comp = structure.composition
                break

        ox = self.dopant.oxi_state
        radius = self.dopant.ionic_radius

        compatible_species = [
            sp for sp in comp
            if sp.oxi_state == ox and abs(sp.ionic_radius / radius -
                                          1) < self.ionic_radius_tol
        ]

        if (not compatible_species) and self.alio_tol:
            # We only consider aliovalent doping if there are no compatible
            # isovalent species.
            compatible_species = [
                sp for sp in comp if abs(sp.oxi_state - ox) <= self.alio_tol
                and abs(sp.ionic_radius / radius -
                        1) < self.ionic_radius_tol and sp.oxi_state * ox >= 0
            ]

        if self.allowed_doping_species is not None:
            # Only keep allowed doping species.
            compatible_species = [
                sp for sp in compatible_species
                if sp in [get_el_sp(s) for s in self.allowed_doping_species]
            ]

        logger.info("Compatible species: %s" % compatible_species)

        lengths = structure.lattice.abc
        scaling = [
            max(1, int(round(math.ceil(self.min_length / x)))) for x in lengths
        ]
        logger.info("Lengths are %s" % str(lengths))
        logger.info("Scaling = %s" % str(scaling))

        all_structures = []
        t = EnumerateStructureTransformation(**self.kwargs)

        for sp in compatible_species:
            supercell = structure * scaling
            nsp = supercell.composition[sp]
            if sp.oxi_state == ox:
                supercell.replace_species(
                    {sp: {
                        sp: (nsp - 1) / nsp,
                        self.dopant: 1 / nsp
                    }})
                logger.info("Doping %s for %s at level %.3f" %
                            (sp, self.dopant, 1 / nsp))
            elif self.codopant:
                codopant = _find_codopant(sp, 2 * sp.oxi_state - ox)
                supercell.replace_species({
                    sp: {
                        sp: (nsp - 2) / nsp,
                        self.dopant: 1 / nsp,
                        codopant: 1 / nsp
                    }
                })
                logger.info("Doping %s for %s + %s at level %.3f" %
                            (sp, self.dopant, codopant, 1 / nsp))
            elif abs(sp.oxi_state) < abs(ox):
                # Strategy: replace the target species with a
                # combination of dopant and vacancy.
                # We will choose the lowest oxidation state species as a
                # vacancy compensation species as it is likely to be lower in
                # energy
                sp_to_remove = min([s for s in comp if s.oxi_state * ox > 0],
                                   key=lambda ss: abs(ss.oxi_state))

                if sp_to_remove == sp:
                    common_charge = lcm(int(abs(sp.oxi_state)), int(abs(ox)))
                    ndopant = common_charge / abs(ox)
                    nsp_to_remove = common_charge / abs(sp.oxi_state)
                    logger.info("Doping %d %s with %d %s." %
                                (nsp_to_remove, sp, ndopant, self.dopant))
                    supercell.replace_species({
                        sp: {
                            sp: (nsp - nsp_to_remove) / nsp,
                            self.dopant: ndopant / nsp
                        }
                    })
                else:
                    ox_diff = int(abs(round(sp.oxi_state - ox)))
                    vac_ox = int(abs(sp_to_remove.oxi_state))
                    common_charge = lcm(vac_ox, ox_diff)
                    ndopant = common_charge / ox_diff
                    nx_to_remove = common_charge / vac_ox
                    nx = supercell.composition[sp_to_remove]
                    logger.info(
                        "Doping %d %s with %s and removing %d %s." %
                        (ndopant, sp, self.dopant, nx_to_remove, sp_to_remove))
                    supercell.replace_species({
                        sp: {
                            sp: (nsp - ndopant) / nsp,
                            self.dopant: ndopant / nsp
                        },
                        sp_to_remove: {
                            sp_to_remove: (nx - nx_to_remove) / nx
                        }
                    })
            elif abs(sp.oxi_state) > abs(ox):
                # Strategy: replace the target species with dopant and also
                # remove some opposite charged species for charge neutrality
                if ox > 0:
                    sp_to_remove = max(supercell.composition.keys(),
                                       key=lambda el: el.X)
                else:
                    sp_to_remove = min(supercell.composition.keys(),
                                       key=lambda el: el.X)
                # Confirm species are of opposite oxidation states.
                assert sp_to_remove.oxi_state * sp.oxi_state < 0

                ox_diff = int(abs(round(sp.oxi_state - ox)))
                anion_ox = int(abs(sp_to_remove.oxi_state))
                nx = supercell.composition[sp_to_remove]
                common_charge = lcm(anion_ox, ox_diff)
                ndopant = common_charge / ox_diff
                nx_to_remove = common_charge / anion_ox
                logger.info(
                    "Doping %d %s with %s and removing %d %s." %
                    (ndopant, sp, self.dopant, nx_to_remove, sp_to_remove))
                supercell.replace_species({
                    sp: {
                        sp: (nsp - ndopant) / nsp,
                        self.dopant: ndopant / nsp
                    },
                    sp_to_remove: {
                        sp_to_remove: (nx - nx_to_remove) / nx
                    }
                })

            ss = t.apply_transformation(
                supercell, return_ranked_list=self.max_structures_per_enum)
            logger.info("%s distinct structures" % len(ss))
            all_structures.extend(ss)

        logger.info("Total %s doped structures" % len(all_structures))
        if return_ranked_list:
            return all_structures[:return_ranked_list]

        return all_structures[0]["structure"]