Source code for smact.oxidation_states

"""
smact.oxidation_states: Module for predicting the likelihood of species
coexisting in a compound based on statistical analysis of oxidation states.
It is possible to use the values obtained in the publication Materials
Discovery by Chemical Analogy: Role of Oxidation States in Structure
Prediction - DOI: 10.1039/C8FD00032H.
"""

import json
from os import path
from typing import Dict, Optional, Tuple

from numpy import mean
from pymatgen.core import Structure
from pymatgen.core.periodic_table import Species as pmgSpecies
from pymatgen.core.periodic_table import get_el_sp

from smact import Element, Species, data_directory


[docs]class Oxidation_state_probability_finder: """ Uses the model developed in the Faraday Discussions Paper (DOI:10.1039/C8FD00032H) to compute the likelihood of metal species existing in solids in the presence of certain anions. """ def __init__( self, probability_table: Optional[Dict[Tuple[str, str], float]] = None ): """ Args: probability_table (dict): Lookup table to get probabilities for anion-cation pairs. Must be of the format {(anion,cation): probability, ...} e.g. {('F-1', 'Li1'): 1.0,...}. If none, the default table is loaded from the data directory. """ if probability_table == None: with open( path.join( data_directory, "oxidation_state_probability_table.json" ) ) as f: probability_data = json.load(f) # Put data into the required format probability_table = {} for i in probability_data: probability_table[(i[0], i[1])] = i[2] self._probability_table = probability_table # Define set of species for which we have data included_anions = {i[0] for i in self._probability_table.keys()} included_cations = {i[1] for i in self._probability_table.keys()} included_species = list(included_anions) + list(included_cations) self._included_species = included_species self._included_cations = included_cations self._included_anions = included_anions def _generate_lookup_key(self, species1: Species, species2: Species): """ Internal function to generate keys to lookup table. Args: species1 (smact.Species): Species species2 (smact.Species): Species Returns: table_key (tuple): For looking up probability in the form (an_key, cat_key). """ # Check that there is one cation and one anion if (species1.oxidation > 0) and (species2.oxidation < 0): cation = species1 anion = species2 elif (species1.oxidation < 0) and (species2.oxidation > 0): anion = species1 cation = species2 else: raise ValueError("One cation and one anion required.") # Generate keys for lookup table cat_key = "".join([cation.symbol, str(int(cation.oxidation))]) an_key = "".join([anion.symbol, str(int(anion.oxidation))]) # Check that both the species are included in the probability table if not all( elem in self._included_species for elem in [an_key, cat_key] ): raise NameError( f"One or both of [{cat_key}, {an_key}] are not in the probability table." ) table_key = (an_key, cat_key) return table_key
[docs] def pair_probability(self, species1: Species, species2: Species) -> float: """ Get the anion-cation oxidation state probability for a provided pair of smact Species. i.e. :math:`P_{SA}=\\frac{N_{SX}}{N_{MX}}` in the original paper (DOI:10.1039/C8FD00032H). Args: species1 (smact.Species): Cation or anion species species2 (smact.Species): Cation or anion species Returns: prob (float): Species-anion probability """ # Generate lookup table key and use it to look up probability probability_table_key = self._generate_lookup_key(species1, species2) prob = self._probability_table[probability_table_key] return prob
[docs] def get_included_species(self): """ Returns a list of species for which there exists data in the probability table used. """ return self._included_species
[docs] def compound_probability( self, structure: Structure, ignore_stoichiometry: bool = True ) -> float: """ calculate overall probability for structure or composition. Args: structure (pymatgen.Structure): Compound for which the probability score will be generated. Can also be a list of pymatgen or SMACT Species. ignore_stoichiometry (bool): Whether to weight probabilities by stoichiometry. Defaults to false as decribed in the original paper. Returns: compound_prob (float): Compound probability """ # Convert input to list of SMACT Species if type(structure) == list: if all(isinstance(i, Species) for i in structure): pass elif all(isinstance(i, pmgSpecies) for i in structure): structure = [Species(i.symbol, i.oxi_state) for i in structure] else: raise TypeError( "Input requires a list of SMACT or Pymatgen species." ) elif type(structure) == Structure: species = structure.species if not all(isinstance(i, pmgSpecies) for i in species): raise TypeError("Structure must have oxidation states.") else: structure = [ Species( get_el_sp(i.species_string).symbol, get_el_sp(i.species_string).oxi_state, ) for i in structure ] else: raise TypeError( "Input requires a list of SMACT or Pymatgen Species or a Structure." ) # Put most electonegative element last in list by sorting by electroneg structure.sort(key=lambda x: x.pauling_eneg) # Define necessary species pairs anion = structure[-1] cations = [i for i in structure if i.oxidation > 0] species_pairs = [(anion, cation) for cation in cations] # Reduce down to unique pairs if ignoring stoichiometry if ignore_stoichiometry: species_pairs = list(set(species_pairs)) # Do the maths pair_probs = [ self.pair_probability(pair[0], pair[1]) for pair in species_pairs ] compound_prob = mean(pair_probs) return compound_prob