"""Functions for creating defects within structures."""fromcollections.abcimportIterablefromitertoolsimportcombinationsfromitertoolsimportpermutationsfromaseimportAtomsfrompymatgen.analysis.structure_matcherimportStructureMatcherfrompymatgen.io.aseimportAseAtomsAdaptor
[docs]def_convert_symbols(*,structure:Atoms,sites:list[int|str])->list[int]:"""Convert chemical symbols into structure indices."""symbols:dict[str,list[int]]={}foratominstructure:ifatom.symbolnotinsymbols:symbols[atom.symbol]=[]symbols[atom.symbol].append(atom.index)integer_sites:set[int]=set()forsiteinsites:ifisinstance(site,int):integer_sites.add(site)else:integer_sites.update(symbols[site])returnlist(integer_sites)
[docs]def_validate_occupancies(*,sites:list[int],occupancies:list[tuple[str,int]])->bool:"""Ensure that occupancies do not exceed sites. Args: sites: The indices of sites to permute. occupancies: A list of 2-tuples, whose first entry indicates the chemical symbol of a dopant and whose second entry indicates the number of sites to fill with that dopant. Returns: True if the number of occupanies do not exceed the number sites. False, otherwise. """num_sites=len(sites)num_sites_to_replace=sum(xfor_,xinoccupancies)returnnum_sites_to_replace<=num_sites
[docs]def_convert_occupancies_to_occupants(*,occupancies:list[tuple[str,int]])->list[str]:"""Convert occupancies and counts into a list of chemical symbols."""occupants:list[str]=[]foroccupant,occupant_countinoccupancies:occupants.extend([occupant]*occupant_count)returnoccupants
[docs]def_filter_for_uniqueness(structures:list[Atoms])->list[Atoms]:"""Filter out duplicate structures."""unique_structures:list[Atoms]=[]duplicates:list[int]=[]fori,structure1inenumerate(structures):match=iinduplicatesifnotmatch:forjinrange(i+1,len(structures)):matcher=StructureMatcher()ifmatcher.fit(struct1=AseAtomsAdaptor.get_structure(structure1),struct2=AseAtomsAdaptor.get_structure(structures[j]),):match=Trueduplicates.append(j)unique_structures.append(structure1)returnunique_structures
[docs]defpermute(*,structure:Atoms,sites:Iterable[int|str]|None=None,occupancies:Iterable[tuple[str,int]]|None=None,)->list[Atoms]:"""Create permutations of a structure considering a number of sites. Args: structure: an `ase.atoms.Atoms` object to permutate. sites: an optional list of integers and strings representing the sites to permute. Integers are interpreted as structure indices. Strings are interpreted as all sites of a given element. Defaults to all the indices of the structure. occupancies: an optional list of 2-tuples whose first elements are strings representing chemical symbols and whose second elements are integers indicating the number of sites to fill with the given element. Empty strings can be passed to denote vacancy defects. Defaults to the occupancies of the sites defined in ``sites``. Example: Permute all atoms in a structure >>> from ase import Atoms >>> from ase.build import bulk >>> from ccu.structure.defects import permute >>> structure = bulk("NiO", "rocksalt", a=0.352) * (2, 1, 1) >>> permuted_nios = permute(structure=structure) >>> len(permuted_nios) 2 Example: Create Ni-M bimetallics >>> from ase import Atoms >>> from ase.build import bulk >>> from ccu.structure.defects import permute >>> ni = bulk("Ni", "fcc", a=0.352) * (2, 1, 1) >>> metals = ["Co", "Cr", "Cu", "Fe", "Ti"] >>> bimetallics = [ni] >>> for metal in metals: ... for atom in ni: ... bimetallics.extend( ... permute(structure=ni, occupancies=[(metal, atom.index + 1)]) ... ) >>> bimetallics[1].get_chemical_formula() 'CoNi' >>> len(bimetallics) 11 """ifsitesisNone:sites=list(range(len(structure)))sites=list(sites)sites=_convert_symbols(structure=structure,sites=sites)ifoccupanciesisNone:symbols=[structure.symbols[i]foriinsites]occupancies=[(symbol,symbols.count(symbol))forsymbolinset(symbols)]occupancies=list(occupancies)ifnot_validate_occupancies(sites=sites,occupancies=occupancies):msg=("Number of sites to replace specified via ``occupancies`` exceeds ""the number of sites specified via ``sites``.")raiseValueError(msg)occupants=_convert_occupancies_to_occupants(occupancies=occupancies)occupant_orderings=set(permutations(occupants))permuted_structures:list[Atoms]=[]forsite_subsetincombinations(sites,r=len(occupants)):foroccupant_orderinginoccupant_orderings:new_structure=Atoms(structure)forsite,occupantinzip(site_subset,occupant_ordering,strict=False):ifoccupant:new_structure.symbols[site]=occupantelse:delnew_structure[site]permuted_structures.append(new_structure)# Find unique structure permutationsunique_structures=_filter_for_uniqueness(permuted_structures)returnunique_structures