Skip to content

Commit

Permalink
Merge branch 'master' into synthesis
Browse files Browse the repository at this point in the history
  • Loading branch information
shyuep committed Nov 9, 2018
2 parents 8864e4b + 1bb876a commit 97aa2cb
Show file tree
Hide file tree
Showing 5 changed files with 294 additions and 11 deletions.
186 changes: 186 additions & 0 deletions pymatgen/analysis/structure_prediction/dopant_predictor.py
@@ -0,0 +1,186 @@
import warnings
import numpy as np

from pymatgen.analysis.structure_prediction.substitution_probability import \
SubstitutionPredictor

from pymatgen.core.periodic_table import Specie, Element


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)


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)
@@ -0,0 +1,88 @@
# coding: utf-8
# Copyright (c) Pymatgen Development Team.
# Distributed under the terms of the MIT License.

from __future__ import unicode_literals

import unittest

from pymatgen.core.periodic_table import Specie
from pymatgen.core.structure import Structure
from pymatgen.analysis.local_env import CrystalNN

from pymatgen.analysis.structure_prediction.dopant_predictor import \
get_dopants_from_shannon_radii, get_dopants_from_substitution_probabilities


class DopantPredictionTest(unittest.TestCase):

def setUp(self):
self.tin_dioxide = Structure(
[3.24, 0, 0, 0, 4.83, 0, 0, 0, 4.84],
['O', 'O', 'O', 'O', 'Sn', 'Sn'],
[[0.5, 0.19, 0.80], [0.5, 0.80, 0.19], [0, 0.30, 0.30],
[0, 0.69, 0.69], [0.5, 0.50, 0.50], [0, 0, 0]]
)
self.tin_dioxide.add_oxidation_state_by_element(
{'Sn': 4, 'O': -2})

def test_dopants_from_substitution_probabilities(self):
dopants = get_dopants_from_substitution_probabilities(
self.tin_dioxide, num_dopants=5)

self.assertTrue("n_type" in dopants)
self.assertTrue("p_type" in dopants)

self.assertTrue(len(dopants['n_type']) <= 5)

self.assertTrue(len(dopants['p_type']) <= 5)

self.assertAlmostEqual(dopants['n_type'][0]['probability'],
0.06692682583342474)
self.assertEqual(dopants['n_type'][0]['dopant_species'],
Specie('F', -1))
self.assertEqual(dopants['n_type'][0]['original_species'],
Specie('O', -2))

self.assertAlmostEqual(dopants['p_type'][0]['probability'],
0.023398867249112935)
self.assertEqual(dopants['p_type'][0]['dopant_species'],
Specie('Co', 2))
self.assertEqual(dopants['p_type'][0]['original_species'],
Specie('Sn', 4))

# test oxidation sign matching
dopants = get_dopants_from_substitution_probabilities(
self.tin_dioxide, num_dopants=15, match_oxi_sign=False)
self.assertEqual(dopants['n_type'][14]['dopant_species'],
Specie('Li', 1))
self.assertEqual(dopants['n_type'][14]['original_species'],
Specie('O', -2))

dopants = get_dopants_from_substitution_probabilities(
self.tin_dioxide, num_dopants=15, match_oxi_sign=True)
self.assertNotEqual(dopants['n_type'][14]['dopant_species'],
Specie('Li', 1))

def test_dopants_from_shannon_radii(self):
bonded_structure = CrystalNN().get_bonded_structure(self.tin_dioxide)

dopants = get_dopants_from_shannon_radii(bonded_structure,
num_dopants=5)

self.assertTrue("n_type" in dopants)
self.assertTrue("p_type" in dopants)

self.assertTrue(len(dopants['n_type']) <= 5)
self.assertTrue(len(dopants['p_type']) <= 5)

self.assertAlmostEqual(dopants['n_type'][0]['radii_diff'], 0.04)
self.assertEqual(dopants['n_type'][0]['dopant_species'], Specie('U', 6))
self.assertEqual(dopants['n_type'][0]['original_species'],
Specie('Sn', 4))

self.assertAlmostEqual(dopants['p_type'][0]['radii_diff'], 0.)
self.assertEqual(dopants['p_type'][0]['dopant_species'],
Specie('Ni', 2))
self.assertEqual(dopants['p_type'][0]['original_species'],
Specie('Sn', 4))
19 changes: 14 additions & 5 deletions pymatgen/core/structure.py
Expand Up @@ -366,9 +366,12 @@ def __init__(self, lattice, species, coords, charge=None,
coords (Nx3 array): list of fractional/cartesian coordinates of
each species.
charge (int): overall charge of the structure. Defaults to behavior
in SiteCollection where total charge is the sum of the oxidation states
in SiteCollection where total charge is the sum of the oxidation
states.
validate_proximity (bool): Whether to check if there are sites
that are less than 0.01 Ang apart. Defaults to False.
to_unit_cell (bool): Whether to map all sites into the unit cell,
i.e., fractional coords between 0 and 1. Defaults to False.
coords_are_cartesian (bool): Set to True if you are providing
coordinates in cartesian coordinates. Defaults to False.
site_properties (dict): Properties associated with the sites as a
Expand Down Expand Up @@ -2358,9 +2361,9 @@ class Structure(IStructure, collections.MutableSequence):
"""
__hash__ = None

def __init__(self, lattice, species, coords, charge=None, validate_proximity=False,
to_unit_cell=False, coords_are_cartesian=False,
site_properties=None):
def __init__(self, lattice, species, coords, charge=None,
validate_proximity=False, to_unit_cell=False,
coords_are_cartesian=False, site_properties=None):
"""
Create a periodic structure.
Expand All @@ -2379,9 +2382,15 @@ def __init__(self, lattice, species, coords, charge=None, validate_proximity=Fal
ii. List of dict of elements/species and occupancies, e.g.,
[{"Fe" : 0.5, "Mn":0.5}, ...]. This allows the setup of
disordered structures.
fractional_coords: list of fractional coordinates of each species.
coords (Nx3 array): list of fractional/cartesian coordinates of
each species.
charge (int): overall charge of the structure. Defaults to behavior
in SiteCollection where total charge is the sum of the oxidation
states.
validate_proximity (bool): Whether to check if there are sites
that are less than 0.01 Ang apart. Defaults to False.
to_unit_cell (bool): Whether to map all sites into the unit cell,
i.e., fractional coords between 0 and 1. Defaults to False.
coords_are_cartesian (bool): Set to True if you are providing
coordinates in cartesian coordinates. Defaults to False.
site_properties (dict): Properties associated with the sites as a
Expand Down
6 changes: 3 additions & 3 deletions requirements-optional.txt
@@ -1,5 +1,5 @@
pybtex==0.21
tqdm==4.26.0
tqdm==4.28.1
nose==1.3.7
nose-timer==0.7.3
coverage==4.5.1
Expand All @@ -9,8 +9,8 @@ chemview==0.6
netCDF4==1.3.1
fdint==2.0.2
phonopy==1.11.12.121
networkx==2.1
networkx==2.2
pygraphviz==1.3.1
anytree==2.4.3
h5py==2.7.0
h5py==2.7.1
#BoltzTraP2==18.9.1
6 changes: 3 additions & 3 deletions requirements.txt
@@ -1,7 +1,7 @@
numpy==1.15.3
numpy==1.15.4
pydispatcher==2.0.5
sympy==1.3
requests==2.19.1
requests==2.20.0
monty==1.0.3
ruamel.yaml==0.15.61
six==1.11.0
Expand All @@ -12,4 +12,4 @@ matplotlib==3.0.1
palettable==3.1.1
spglib==1.10.4.11
pandas==0.23.4
networkx==2.1
networkx==2.2

0 comments on commit 97aa2cb

Please sign in to comment.