"""
Predicting potential dopants
"""
import warnings
import numpy as np
from pymatgen.analysis.structure_prediction.substitution_probability import \
SubstitutionPredictor
from pymatgen.core.periodic_table import Specie, Element
[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 Specie for all elements in all their common oxid states
all_species = [Specie(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
Specie.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)