from pymatgen.symmetry.analyzer import SpacegroupAnalyzer, SpacegroupOperations, PointGroupAnalyzer
from pymatgen.util.coord import coord_list_mapping_pbc, coord_list_mapping
from pymatgen.core.lattice import Lattice
from pymatgen.core.structure import Molecule, Structure
from bsym import SpaceGroup, SymmetryOperation, ConfigurationSpace, PointGroup
from copy import copy
from functools import partial
import numpy as np
[docs]def structure_cartesian_coordinates_mapping( structure, symmop ):
"""
Maps the coordinates of pymatgen ``Structure`` according to a ``SymmOp`` symmetry operation.
Args:
structure (``Structure``): The pymatgen ``Structure``.
symmop (``SymmOp``): The pymatgen symmetry operation object.
Returns
(np.array): The mapped Cartesian coordinates.
"""
return structure.lattice.get_cartesian_coords( symmop.operate_multi( structure.frac_coords ) )
[docs]def molecule_cartesian_coordinates_mapping( molecule, symmop ):
"""
Maps the coordinates of pymatgen ``Molecule`` according to a ``SymmOp`` symmetry operation.
Args:
molecule (``Structure``): The pymatgen ``Molecule``.
symmop (``SymmOp``): The pymatgen symmetry operation object.
Returns
(np.array): The mapped Cartesian coordinates.
"""
return symmop.operate_multi( molecule.cart_coords )
[docs]def structure_mapping_list( new_structure, mapping_structure, atol ):
"""
Gives the index mapping between two pymatgen ``Structure`` objects.
Args:
new_structure (``Structure``):
mapping_structure (``Structure``):
Returns:
list of indices such that mapping_structure.sites[indices] == new_structure.sites
"""
return coord_list_mapping_pbc( new_structure.frac_coords, mapping_structure.frac_coords, atol=atol )
[docs]def molecule_mapping_list( new_molecule, mapping_molecule, atol ):
"""
Gives the index mapping between two pymatgen ``Molecule`` objects.
Args:
new_structure (``Molecule``):
mapping_structure (``Molecule``):
Returns:
list of indices such that mapping_molecule.sites[indices] == new_molecule.sites
"""
return coord_list_mapping( new_molecule.cart_coords, mapping_molecule.cart_coords, atol=atol )
[docs]def unique_symmetry_operations_as_vectors_from_structure( structure, verbose=False, subset=None, atol=1e-5 ):
"""
Uses `pymatgen`_ symmetry analysis to find the minimum complete set of symmetry operations for the space group of a structure.
Args:
structure (pymatgen ``Structure``): structure to be analysed.
subset (Optional [list]): list of atom indices to be used for generating the symmetry operations.
atol (Optional [float]): tolerance factor for the ``pymatgen`` `coordinate mapping`_ under each symmetry operation.
Returns:
(list[list]): a list of lists, containing the symmetry operations as vector mappings.
.. _pymatgen:
http://pymatgen.org
.. _coordinate mapping:
http://pymatgen.org/pymatgen.util.coord_utils.html#pymatgen.util.coord_utils.coord_list_mapping_pbc
"""
if isinstance( structure, Structure ):
instantiate_structure = partial( Structure, lattice=structure.lattice, coords_are_cartesian=True )
coord_mapping = structure_cartesian_coordinates_mapping
mapping_list = structure_mapping_list
symmetry_analyzer = SpacegroupAnalyzer( structure )
if verbose:
print( "The space group for this structure is {}".format( symmetry_analyzer.get_space_group_symbol()) )
elif isinstance( structure, Molecule ):
instantiate_structure = Molecule
coord_mapping = molecule_cartesian_coordinates_mapping
mapping_list = molecule_mapping_list
symmetry_analyzer = PointGroupAnalyzer( structure, tolerance=atol )
if verbose:
print( "The point group for this structure is {}".format( symmetry_analyzer.get_pointgroup()) )
else:
raise ValueError( 'structure argument should be a Structure or Molecule object' )
symmetry_operations = symmetry_analyzer.get_symmetry_operations()
mappings = []
if subset:
species_subset = [ spec for i,spec in enumerate( structure.species ) if i in subset ]
cart_coords_subset = [ coord for i, coord in enumerate( structure.cart_coords ) if i in subset ]
mapping_structure = instantiate_structure( species=species_subset, coords=cart_coords_subset )
else:
mapping_structure = structure
for symmop in symmetry_operations:
cart_coords = coord_mapping( mapping_structure, symmop )
new_structure = instantiate_structure( species=mapping_structure.species, coords=cart_coords )
new_mapping = [ x+1 for x in list( mapping_list( new_structure, mapping_structure, atol ) ) ]
if new_mapping not in mappings:
mappings.append( new_mapping )
return mappings
[docs]def space_group_symbol_from_structure( structure ):
"""
Returns the symbol for the space group defined by this structure.
Args:
structure (pymatgen ``Structure``): The input structure.
Returns:
(str): The space group symbol.
"""
symmetry_analyzer = SpacegroupAnalyzer( structure )
symbol = symmetry_analyzer.get_space_group_symbol()
return symbol
[docs]def space_group_from_structure( structure, subset=None, atol=1e-5 ):
"""
Generates a ``SpaceGroup`` object from a `pymatgen` ``Structure``.
Args:
structure (pymatgen ``Structure``): structure to be used to define the :any:`SpaceGroup`.
subset (Optional [list]): list of atom indices to be used for generating the symmetry operations.
atol (Optional [float]): tolerance factor for the ``pymatgen`` `coordinate mapping`_ under each symmetry operation.
Returns:
a new :any:`SpaceGroup` instance
.. _coordinate mapping:
http://pymatgen.org/pymatgen.util.coord_utils.html#pymatgen.util.coord_utils.coord_list_mapping_pbc
"""
mappings = unique_symmetry_operations_as_vectors_from_structure( structure, subset=subset, atol=atol )
symmetry_operations = [ SymmetryOperation.from_vector( m ) for m in mappings ]
return SpaceGroup( symmetry_operations=symmetry_operations )
[docs]def point_group_from_molecule( molecule, subset=None, atol=1e-5 ):
"""
Generates a ``PointGroup`` object from a `pymatgen` ``Molecule``.
Args:
molecule (pymatgen ``Molecule``): molecule to be used to define the :any:`PointGroup`.
subset (Optional [list]): list of atom indices to be used for generating the symmetry operations.
atol (Optional [float]): tolerance factor for the ``pymatgen`` `coordinate mapping`_ under each symmetry operation.
Returns:
a new :any:`PointGroup` instance
.. _coordinate mapping:
http://pymatgen.org/pymatgen.util.coord_utils.html#pymatgen.util.coord_utils.coord_list_mapping
"""
molecule = Molecule( molecule.species, molecule.cart_coords - molecule.center_of_mass )
mappings = unique_symmetry_operations_as_vectors_from_structure( molecule, subset=subset, atol=atol )
symmetry_operations = [ SymmetryOperation.from_vector( m ) for m in mappings ]
return PointGroup( symmetry_operations=symmetry_operations )
[docs]def configuration_space_from_structure( structure, subset=None, atol=1e-5 ):
"""
Generate a ``ConfigurationSpace`` object from a `pymatgen` ``Structure``.
Args:
structure (pymatgen ``Structure``): structure to be used to define the :any:`ConfigurationSpace`.
subset (Optional [list]): list of atom indices to be used for generating the configuration space.
atol (Optional [float]): tolerance factor for the ``pymatgen`` `coordinate mapping`_ under each symmetry operation.
Returns:
a new :any:`ConfigurationSpace` instance.
.. _coordinate mapping:
http://pymatgen.org/pymatgen.util.coord_utils.html#pymatgen.util.coord_utils.coord_list_mapping_pbc
"""
space_group = space_group_from_structure( structure, subset=subset, atol=atol )
if subset is None:
subset = list( range( 1, len( structure )+1 ) )
config_space = ConfigurationSpace( objects=subset, symmetry_group=space_group )
return config_space
[docs]def configuration_space_from_molecule( molecule, subset=None, atol=1e-5 ):
"""
Generate a ``ConfigurationSpace`` object from a `pymatgen` ``Molecule``.
Args:
molecule (pymatgen ``Molecule``): molecule to be used to define the :any:`ConfigurationSpace`.
subset (Optional [list]): list of atom indices to be used for generating the configuration space.
atol (Optional [float]): tolerance factor for the ``pymatgen`` `coordinate mapping`_ under each symmetry operation.
Returns:
a new :any:`ConfigurationSpace` instance.
.. _coordinate mapping:
http://pymatgen.org/pymatgen.util.coord_utils.html#pymatgen.util.coord_utils.coord_list_mapping
"""
molecule = Molecule( molecule.species, molecule.cart_coords - molecule.center_of_mass )
point_group = point_group_from_molecule( molecule, subset=subset, atol=atol )
if subset is None:
subset = list( range( 1, len( molecule )+1 ) )
config_space = ConfigurationSpace( objects=subset, symmetry_group=point_group )
return config_space
[docs]def unique_structure_substitutions( structure, to_substitute, site_distribution, verbose=False, atol=1e-5, show_progress=False ):
"""
Generate all symmetry-unique structures formed by substituting a set of sites in a `pymatgen` structure.
Args:
structure (pymatgen.Structure): The parent structure.
to_substitute (str): atom label for the sites to be substituted.
site_distribution (dict): A dictionary that defines the number of each substituting element.
verbose (bool): verbose output.
atol (Optional [float]): tolerance factor for the ``pymatgen`` `coordinate mapping`_ under each symmetry operation. Default=1e-5.
show_progress (opt:default=False): Show a progress bar.
Setting to `True` gives a simple progress bar.
Setting to `"notebook"` gives a Jupyter notebook compatible progress bar.
Returns:
(list[Structure]): A list of Structure objects for each unique substitution.
Notes:
The number of symmetry-equivalent configurations for each structure
is stored in the `number_of_equivalent_configurations` attribute.
If the parent structure was previously generated using this function
(as part of a sequence of substitutions) the full configuration
degeneracy of each symmetry inequivalent configuration is stored in
the `full_configuration_degeneracy` attribute. If the parent structure
is a standard Pymatgen Structure object, `number_of_equivalent_configurations`
and `full_configuration_degeneracy` will be equal.
.. _coordinate mapping:
http://pymatgen.org/pymatgen.util.coord_utils.html#pymatgen.util.coord_utils.coord_list_mapping_pbc
"""
site_substitution_index = list( structure.indices_from_symbol( to_substitute ) )
if len( site_substitution_index ) != sum( site_distribution.values() ):
raise ValueError( "Number of sites from index does not match number from site distribution" )
if isinstance( structure, Structure ):
config_space = configuration_space_from_structure( structure, subset=site_substitution_index, atol=atol )
elif isinstance( structure, Molecule ):
structure = Molecule( structure.species, structure.cart_coords - structure.center_of_mass )
config_space = configuration_space_from_molecule( structure, subset=site_substitution_index, atol=atol )
else:
raise ValueError( "pymatgen Structure or Molecule object expected" )
numeric_site_distribution, numeric_site_mapping = parse_site_distribution( site_distribution )
unique_configurations = config_space.unique_configurations( numeric_site_distribution, verbose=verbose, show_progress=show_progress )
new_structures = [ new_structure_from_substitution( structure, site_substitution_index, [ numeric_site_mapping[k] for k in c.tolist() ] ) for c in unique_configurations ]
if hasattr( structure, 'number_of_equivalent_configurations' ):
for s, c in zip( new_structures, unique_configurations ):
s.number_of_equivalent_configurations = c.count
s.full_configuration_degeneracy = c.count * structure.full_configuration_degeneracy
else:
for s, c in zip( new_structures, unique_configurations ):
s.number_of_equivalent_configurations = c.count
s.full_configuration_degeneracy = c.count
return new_structures
[docs]def parse_site_distribution( site_distribution ):
"""
Converts a site distribution using species labels into one using integer labels.
Args:
site_distribution (dict): e.g. `{ 'Mg': 1, 'Li': 3 }`
Returns:
numeric_site_distribution ( dict): e.g. `{ 1:1, 0:3 }`
numeric_site_mapping (dict): e.g. `{ 0:'Mg', 1:'Li' }`
"""
numeric_site_distribution = {}
numeric_site_mapping = {}
for i,k in enumerate( site_distribution.keys() ):
numeric_site_distribution[i] = site_distribution[k]
numeric_site_mapping[i] = k
return numeric_site_distribution, numeric_site_mapping
[docs]def new_structure_from_substitution( parent_structure, site_substitution_index, new_species_list ):
"""
Generate a new pymatgen ``Structure`` from site substitution parameters.
Args:
parent_structure (Structure): The parent pymatgen ``Struture`` object.
site_substitution_index (list[int]): The list of site indices to be substituted.
new_species_list (list[str]): A list of the replacement atomic species.
Returns:
(``Structure``): The new pymatgen ``Structure``.
Notes:
pymatgen ``Structure`` and ``Molecule`` classes both subclass ``SiteCollection``.
This function will also accept a parent ``Molecule`` object, and return a new
``Molecule``.
"""
if len( site_substitution_index ) != len( new_species_list ):
raise ValueError
if any( i >= len( parent_structure ) for i in site_substitution_index ):
raise ValueError
s = parent_structure.copy()
for i, spec in zip( site_substitution_index, new_species_list ):
s[i] = spec
return s