"""
Predicting potential dopants
"""
import warnings
import numpy as np
from pymatgen.analysis.structure_prediction.substitution_probability import (
SubstitutionPredictor,
)
from pymatgen.core.periodic_table import Element, Species
[docs]def get_dopants_from_substitution_probabilities(structure, num_dopants=5, threshold=0.001, match_oxi_sign=False):
"""
Get dopant suggestions based on substitution probabilities.
Args:
structure (Structure): A pymatgen structure decorated with
oxidation states.
num_dopants (int): The number of suggestions to return for
n- and p-type dopants.
threshold (float): Probability threshold for substitutions.
match_oxi_sign (bool): Whether to force the dopant and original species
to have the same sign of oxidation state. E.g. If the original site
is in a negative charge state, then only negative dopants will be
returned.
Returns:
(dict): Dopant suggestions, given as a dictionary with keys "n_type" and
"p_type". The suggestions for each doping type are given as a list of
dictionaries, each with they keys:
- "probability": The probability of substitution.
- "dopant_species": The dopant species.
- "original_species": The substituted species.
"""
els_have_oxi_states = [hasattr(s, "oxi_state") for s in structure.species]
if not all(els_have_oxi_states):
raise ValueError("All sites in structure must have oxidation states to " "predict dopants.")
sp = SubstitutionPredictor(threshold=threshold)
subs = [sp.list_prediction([s]) for s in set(structure.species)]
subs = [
{
"probability": pred["probability"],
"dopant_species": list(pred["substitutions"].keys())[0],
"original_species": list(pred["substitutions"].values())[0],
}
for species_preds in subs
for pred in species_preds
]
subs.sort(key=lambda x: x["probability"], reverse=True)
return _get_dopants(subs, num_dopants, match_oxi_sign)
[docs]def get_dopants_from_shannon_radii(bonded_structure, num_dopants=5, match_oxi_sign=False):
"""
Get dopant suggestions based on Shannon radii differences.
Args:
bonded_structure (StructureGraph): A pymatgen structure graph
decorated with oxidation states. For example, generated using the
CrystalNN.get_bonded_structure() method.
num_dopants (int): The nummber of suggestions to return for
n- and p-type dopants.
match_oxi_sign (bool): Whether to force the dopant and original species
to have the same sign of oxidation state. E.g. If the original site
is in a negative charge state, then only negative dopants will be
returned.
Returns:
(dict): Dopant suggestions, given as a dictionary with keys "n_type" and
"p_type". The suggestions for each doping type are given as a list of
dictionaries, each with they keys:
- "radii_diff": The difference between the Shannon radii of the species.
- "dopant_spcies": The dopant species.
- "original_species": The substituted species.
"""
# get a list of all Species for all elements in all their common oxid states
all_species = [Species(el, oxi) for el in Element for oxi in el.common_oxidation_states]
# get a series of tuples with (coordination number, specie)
cn_and_species = set(
(
bonded_structure.get_coordination_of_site(i),
bonded_structure.structure[i].specie,
)
for i in range(bonded_structure.structure.num_sites)
)
cn_to_radii_map = {}
possible_dopants = []
for cn, species in cn_and_species:
cn_roman = _int_to_roman(cn)
try:
species_radius = species.get_shannon_radius(cn_roman)
except KeyError:
warnings.warn(
"Shannon radius not found for {} with coordination " "number {}.\nSkipping...".format(species, cn)
)
continue
if cn not in cn_to_radii_map:
cn_to_radii_map[cn] = _shannon_radii_from_cn(all_species, cn_roman, radius_to_compare=species_radius)
shannon_radii = cn_to_radii_map[cn]
possible_dopants += [
{
"radii_diff": p["radii_diff"],
"dopant_species": p["species"],
"original_species": species,
}
for p in shannon_radii
]
possible_dopants.sort(key=lambda x: abs(x["radii_diff"]))
return _get_dopants(possible_dopants, num_dopants, match_oxi_sign)
def _get_dopants(substitutions, num_dopants, match_oxi_sign):
"""
Utility method to get n- and p-type dopants from a list of substitutions.
"""
n_type = [
pred
for pred in substitutions
if pred["dopant_species"].oxi_state > pred["original_species"].oxi_state
and (
not match_oxi_sign
or np.sign(pred["dopant_species"].oxi_state) == np.sign(pred["original_species"].oxi_state)
)
]
p_type = [
pred
for pred in substitutions
if pred["dopant_species"].oxi_state < pred["original_species"].oxi_state
and (
not match_oxi_sign
or np.sign(pred["dopant_species"].oxi_state) == np.sign(pred["original_species"].oxi_state)
)
]
return {"n_type": n_type[:num_dopants], "p_type": p_type[:num_dopants]}
def _shannon_radii_from_cn(species_list, cn_roman, radius_to_compare=0):
"""
Utility func to get Shannon radii for a particular coordination number.
As the Shannon radii depends on charge state and coordination number,
species without an entry for a particular coordination number will
be skipped.
Args:
species_list (list): A list of Species to get the Shannon radii for.
cn_roman (str): The coordination number as a roman numeral. See
Species.get_shannon_radius for more details.
radius_to_compare (float, optional): If set, the data will be returned
with a "radii_diff" key, containing the difference between the
shannon radii and this radius.
Returns:
(list of dict): The Shannon radii for all Species in species. Formatted
as a list of dictionaries, with the keys:
- "species": The species with charge state.
- "radius": The Shannon radius for the species.
- "radius_diff": The difference between the Shannon radius and the
radius_to_compare optional argument.
"""
shannon_radii = []
for s in species_list:
try:
radius = s.get_shannon_radius(cn_roman)
shannon_radii.append(
{
"species": s,
"radius": radius,
"radii_diff": radius - radius_to_compare,
}
)
except KeyError:
pass
return shannon_radii
def _int_to_roman(number):
"""Utility method to convert an int (less than 20) to a roman numeral."""
roman_conv = [(10, "X"), (9, "IX"), (5, "V"), (4, "IV"), (1, "I")]
result = []
for (arabic, roman) in roman_conv:
(factor, number) = divmod(number, arabic)
result.append(roman * factor)
if number == 0:
break
return "".join(result)