Skip to content

Commit

Permalink
Merge pull request #136 from JiwooChloeLee/doper
Browse files Browse the repository at this point in the history
doper.py remove duplicates
  • Loading branch information
AntObi committed Jul 17, 2023
2 parents 1636b30 + ebdf5ff commit e15fe56
Showing 1 changed file with 97 additions and 85 deletions.
182 changes: 97 additions & 85 deletions smact/dopant_prediction/doper.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,3 @@
### This Jupyter notebook creates ntype ptype possiblie dopants for input species
### using SMACT structure prediction
### Working with Kieth from SCIML and Anthony

###Doper ver 2

# Now 'Doper' can generate possible n-type p-type dopants for multicomponent materials (i.e. Ternary, Quaternary etc).
# Can plot the result of doping search within a single step
# """ex) test= Doper(('Cu1+','Zn2+','Ge4+','S2-'))
# test.get_dopants(num_dopants = 10, plot_heatmap = True)"""


from typing import List, Tuple

from pymatgen.util import plotting
Expand All @@ -26,13 +14,15 @@ class Doper:
Attributes:
original_species: A tuple which describes the constituent species of a material. For example:
>>> test= Doper(('Cu1+','Zn2+','Ge4+','S2-'))
>>> test= Doper(("Zn2+","S2-"))
>>> test.original_species
('Cu1+','Zn2+','Ge4+','S2-')
('Zn2+','S2-')
"""

def __init__(self, original_species: Tuple[str, ...]):
def __init__(
self, original_species: Tuple[str, ...], filepath: str = None
):
"""
Intialise the `Doper` class with a tuple of species
Expand All @@ -41,6 +31,7 @@ def __init__(self, original_species: Tuple[str, ...]):
"""
self.original_species = original_species
self._get_dopants(filepath)

def _get_cation_dopants(
self, element_objects: List[smact.Element], cations: List[str]
Expand All @@ -54,15 +45,14 @@ def _get_cation_dopants(
el_symbol = element.symbol
for state in oxi_state:
for cation in cations:
ele = utilities.unparse_spec((el_symbol, state))
_, charge = utilities.parse_spec(cation)
if state > charge:
poss_n_type_cat.append(
utilities.unparse_spec((el_symbol, state))
)
if ele not in poss_n_type_cat:
poss_n_type_cat.append(ele)
elif state < charge and state > 0:
poss_p_type_cat.append(
utilities.unparse_spec((el_symbol, state))
)
if ele not in poss_p_type_cat:
poss_p_type_cat.append(ele)

return poss_n_type_cat, poss_p_type_cat

Expand All @@ -77,57 +67,17 @@ def _get_anion_dopants(
el_symbol = element.symbol
for state in oxi_state:
for anion in anions:
ele = utilities.unparse_spec((el_symbol, state))
_, charge = utilities.parse_spec(anion)
if state > charge and state < 0:
poss_n_type_an.append(
utilities.unparse_spec((el_symbol, state))
)
if ele not in poss_n_type_an:
poss_n_type_an.append(ele)
elif state < charge:
poss_p_type_an.append(
utilities.unparse_spec((el_symbol, state))
)
if ele not in poss_p_type_an:
poss_p_type_an.append(ele)
return poss_n_type_an, poss_p_type_an

def _plot_dopants(self, results: dict):
"""
Uses pymatgen plotting utilities to plot the results of doping search
"""
for key, val in results.items():
dict_results = {utilities.parse_spec(x)[0]: y for x, y in val}
plotting.periodic_table_heatmap(
elemental_data=dict_results,
cmap="rainbow",
blank_color="gainsboro",
edge_color="white",
)

def get_dopants(
self,
num_dopants: int = 5,
plot_heatmap: bool = False,
) -> dict:
"""
Args:
num_dopants (int): The number of suggestions to return for n- and p-type dopants.
plot_heatmap (bool): If True, the results of the doping search are plotted as heatmaps
Returns:
(dict): Dopant suggestions, given as a dictionary with keys
"n_type_cation", "p_type_cation", "n_type_anion", "p_type_anion".
Examples:
>>> test = Doper(('Ti4+','O2-'))
>>> print(test.get_dopants(num_dopants=2))
{'n-type cation substitutions': [('Ta5+', 8.790371775858281e-05),
('Nb5+', 7.830035204694342e-05)],
'p-type cation substitutions': [('Na1+', 0.00010060400812977031),
('Zn2+', 8.56373996146833e-05)],
'n-type anion substitutions': [('F1-', 0.01508116810515677),
('Cl1-', 0.004737202729901607)],
'p-type anion substitutions': [('N3-', 0.0014663800608945628),
('C4-', 9.31310255126729e-08)]}
"""

def _get_dopants(self, filepath: str):
cations = []
anions = []
try:
Expand All @@ -140,7 +90,7 @@ def get_dopants(
except Exception as e:
print(e, "charge is not defined")

CM = mutation.CationMutator.from_json()
CM = mutation.CationMutator.from_json(filepath)

# call all elements
element_objects = list(smact.element_dictionary().values())
Expand All @@ -155,32 +105,94 @@ def get_dopants(
n_type_cat, p_type_cat, n_type_an, p_type_an = [], [], [], []
for cation in cations:
for n_specie, p_specie in zip(poss_n_type_cat, poss_p_type_cat):
n_type_cat.append((n_specie, CM.sub_prob(cation, n_specie)))
p_type_cat.append((p_specie, CM.sub_prob(cation, p_specie)))
if cation == n_specie or cation == p_specie:
continue
n_type_cat.append(
(n_specie, cation, CM.sub_prob(cation, n_specie))
)
p_type_cat.append(
(p_specie, cation, CM.sub_prob(cation, p_specie))
)

for anion in anions:
for n_specie, p_specie in zip(poss_n_type_an, poss_p_type_an):
n_type_an.append((n_specie, CM.sub_prob(anion, n_specie)))
p_type_an.append((p_specie, CM.sub_prob(anion, p_specie)))
if anion == n_specie or cation == p_specie:
continue
n_type_an.append(
(n_specie, anion, CM.sub_prob(anion, n_specie))
)
p_type_an.append(
(p_specie, anion, CM.sub_prob(anion, p_specie))
)

# [('B3+', 0.003), ('C4+', 0.001), (), (), ...] : list(tuple(str, float))
# sort by probability
n_type_cat.sort(key=lambda x: x[1], reverse=True)
p_type_cat.sort(key=lambda x: x[1], reverse=True)
n_type_an.sort(key=lambda x: x[1], reverse=True)
p_type_an.sort(key=lambda x: x[1], reverse=True)
n_type_cat.sort(key=lambda x: x[-1], reverse=True)
p_type_cat.sort(key=lambda x: x[-1], reverse=True)
n_type_an.sort(key=lambda x: x[-1], reverse=True)
p_type_an.sort(key=lambda x: x[-1], reverse=True)

results = {
"n-type cation substitutions": n_type_cat[:num_dopants],
"p-type cation substitutions": p_type_cat[:num_dopants],
"n-type anion substitutions": n_type_an[:num_dopants],
"p-type anion substitutions": p_type_an[:num_dopants],
}
self.n_type_cat = n_type_cat
self.p_type_cat = p_type_cat
self.n_type_an = n_type_an
self.p_type_an = p_type_an

# plot heatmap
def get_dopants(
self,
num_dopants: int = 5,
) -> dict:
"""
Args:
num_dopants (int): The number of suggestions to return for n- and p-type dopants.
Returns:
(dict): Dopant suggestions, given as a dictionary with keys
"n_type_cation", "p_type_cation", "n_type_anion", "p_type_anion".
if plot_heatmap:
self._plot_dopants(results)
Examples:
>>> test = Doper(('Ti4+','O2-'))
>>> print(test.get_dopants(num_dopants=2))
{'n-type cation substitutions': [('Ta5+', 8.790371775858281e-05),
('Nb5+', 7.830035204694342e-05)],
'p-type cation substitutions': [('Na1+', 0.00010060400812977031),
('Zn2+', 8.56373996146833e-05)],
'n-type anion substitutions': [('F1-', 0.01508116810515677),
('Cl1-', 0.004737202729901607)],
'p-type anion substitutions': [('N3-', 0.0014663800608945628),
('C4-', 9.31310255126729e-08)]}
"""

results = {
"n-type cation substitutions": self.n_type_cat[:num_dopants],
"p-type cation substitutions": self.p_type_cat[:num_dopants],
"n-type anion substitutions": self.n_type_an[:num_dopants],
"p-type anion substitutions": self.p_type_an[:num_dopants],
}
# return the top (num_dopants) results for each case
return results

def plot_dopants(
self,
num_dopants: int = 5,
) -> None:
"""
Uses pymatgen plotting utilities to plot the results of doping search
Args:
num_dopants (int): The number of suggestions to return for n- and p-type dopants.
Returns:
None
"""
results = {
"n-type cation substitutions": self.n_type_cat[:num_dopants],
"p-type cation substitutions": self.p_type_cat[:num_dopants],
"n-type anion substitutions": self.n_type_an[:num_dopants],
"p-type anion substitutions": self.p_type_an[:num_dopants],
}
for key, val in results.items():
dict_results = {utilities.parse_spec(x)[0]: y for x, y in val}
plotting.periodic_table_heatmap(
elemental_data=dict_results,
cmap="rainbow",
blank_color="gainsboro",
edge_color="white",
show_plot=True,
)

0 comments on commit e15fe56

Please sign in to comment.