Source code for crystal_torture.pymatgen_doping
"""Simple functions for manipulating and doping a pymatgen structure."""
import random
from pymatgen.core import Structure, Molecule, PeriodicSite
[docs]
def count_sites(structure: Structure,
species: set[str] | None = None,
labels: set[str] | None = None) -> int:
"""Count sites in structure by species and/or labels.
Given structure object and either specie string or label string, it counts and returns the
number of sites with that species or label (or both) in the structure.
Args:
structure: Pymatgen structure object.
species: Site species to count.
labels: Site labels to count.
Returns:
Number of sites occupied by species or label (or both) in structure.
Raises:
ValueError: If neither species nor labels are provided.
"""
if labels and species:
return len(
[
i
for i, site in enumerate(structure)
if ((site.label in labels) and (site.species_string in species))
]
)
elif species and not labels:
return len(
[i for i, site in enumerate(structure) if site.species_string in species]
)
elif labels and not species:
return len([i for i, site in enumerate(structure) if site.label in labels])
else:
raise ValueError("Need to supply either specie, or label to count_sites")
[docs]
def index_sites(structure: Structure,
species: set[str] | None = None,
labels: set[str] | None = None) -> list[int]:
"""Return site indices occupied by specie or label (or both).
Args:
structure: Pymatgen structure object.
species: Site species to find.
labels: Site labels to find.
Returns:
List with site indices occupied by species or label (or both) in structure.
Raises:
ValueError: If neither species nor labels are provided.
"""
if labels and species:
return [
i
for i, site in enumerate(structure)
if ((site.label in labels) and (site.species_string in species))
]
elif species and not labels:
return [i for i, site in enumerate(structure) if site.species_string in species]
elif labels and not species:
return [i for i, site in enumerate(structure) if site.label in labels]
else:
raise ValueError("Need to supply either specie, or label to index_sites")
[docs]
def sort_structure(structure: Structure,
order: list[str]) -> Structure:
"""Sort structure species so their indices sit side by side in given order.
Given a pymatgen structure object sort the species so that their indices
sit side by side in the structure, in given order - allows for POSCAR file to
be written in a readable way after doping.
Args:
structure: Pymatgen structure object.
order: List of species str in order to sort.
Returns:
Ordered pymatgen Structure object.
Raises:
ValueError: If order elements don't match structure elements.
"""
symbols = [species for species in structure.symbol_set]
if "X" in set(symbols):
symbols.remove("X")
symbols.append("X0+")
if set(symbols) == set(order):
structure_sorted = Structure(lattice=structure.lattice, species=[], coords=[])
for symbol in symbols:
for i, site in enumerate(structure.sites):
if site.species_string == symbol:
structure_sorted.append(
symbol,
site.coords,
coords_are_cartesian=True,
properties=site.properties,
)
else:
error_msg = "Error: sort structure elements in list passed in order does not match that found in POSCAR\n"
error_msg += "Passed: {}\n".format(order)
error_msg += "POSCAR: {}\n".format(symbols)
raise ValueError(error_msg)
return structure_sorted
[docs]
def dope_structure(
structure: Structure,
conc: float,
species_to_rem: str,
species_to_insert: list[str],
label_to_remove: str | None = None
) -> Structure:
"""Dope a pymatgen structure object to a particular concentration.
Removes conc * no(species_to_remove) from structure and inserts species to insert in
their place. Does so at random (excepting when label_to_remove is passed).
Args:
structure: Pymatgen structure object.
conc: Fractional percentage of sites to remove.
species_to_rem: The species to remove from structure.
species_to_insert: A list of species to equally distribute over sites that are removed.
label_to_remove: Label of sites to select for removal.
Returns:
The doped structure.
Raises:
ValueError: If species_to_rem is not in structure.
"""
if {species_to_rem}.issubset(structure.symbol_set):
no_sites = count_sites(
structure, species={species_to_rem}, labels={label_to_remove} if label_to_remove else None
)
site_indices = index_sites(
structure, species={species_to_rem}, labels={label_to_remove} if label_to_remove else None
)
no_dopants = int(round(conc * no_sites) / len(species_to_insert))
random.shuffle(site_indices)
for species in species_to_insert:
for dopant in range(no_dopants):
structure[site_indices.pop()] = species
structure = sort_structure(
structure=structure, order=[species for species in structure.symbol_set]
)
return structure
else:
raise ValueError("dope_structure: species_to_rem is not in structure")
[docs]
def dope_structure_by_no(
structure: Structure,
no_dopants: int,
species_to_rem: str,
species_to_insert: list[str],
label_to_remove: str | None = None
) -> Structure:
"""Dope a pymatgen structure object by swapping a specific number of sites.
Removes no_dopants of species_to_remove from structure and inserts species to insert in
their place. Does so at random (excepting when label_to_remove is passed).
Args:
structure: Pymatgen structure object.
no_dopants: Number of each type of dopant to insert.
species_to_rem: The species to remove from structure.
species_to_insert: A list of species to equally distribute over sites that are removed.
label_to_remove: Label of sites to select for removal.
Returns:
The doped structure.
Raises:
ValueError: If species_to_rem is not in structure.
"""
if {species_to_rem}.issubset(structure.symbol_set):
no_sites = count_sites(
structure, species={species_to_rem}, labels={label_to_remove} if label_to_remove else None
)
site_indices = index_sites(
structure, species={species_to_rem}, labels={label_to_remove} if label_to_remove else None
)
random.shuffle(site_indices)
for species in species_to_insert:
for dopant in range(no_dopants):
structure[site_indices.pop()] = species
structure = sort_structure(
structure=structure, order=[species for species in structure.symbol_set]
)
return structure
else:
raise ValueError("dope_struture_by_no: species_to_rem is not in structure")
[docs]
def set_site_labels(structure: Structure, labels: list[str]) -> None:
"""Set site labels using the built-in label property.
Args:
structure: Pymatgen structure object.
labels: List of labels to assign to sites.
Raises:
ValueError: If number of labels doesn't match number of sites.
"""
if len(labels) != len(structure.sites):
raise ValueError(f"Number of labels ({len(labels)}) must match number of sites ({len(structure.sites)})")
for site, label in zip(structure.sites, labels):
site.label = label