Source code for pymatgen.analysis.chemenv.coordination_environments.coordination_geometries

# coding: utf-8
# Copyright (c) Pymatgen Development Team.
# Distributed under the terms of the MIT License.


"""
This module contains the class describing the coordination geometries that can exist in a given structure. These
"model" coordination geometries are described in the following articles :
 - Pure Appl. Chem., Vol. 79, No. 10, pp. 1779--1799, 2007.
 - Acta Cryst. A, Vol. 46, No. 1, pp. 1--11, 1990.
The module also contains descriptors of part of these geometries (plane of separation, ...) that are used in the
identification algorithms.
"""

__author__ = "David Waroquiers"
__copyright__ = "Copyright 2012, The Materials Project"
__credits__ = "Geoffroy Hautier"
__version__ = "2.0"
__maintainer__ = "David Waroquiers"
__email__ = "david.waroquiers@gmail.com"
__date__ = "Feb 20, 2016"

import numpy as np
from scipy.special import factorial
import itertools
import abc
from monty.json import MSONable, MontyDecoder
import json
import os

module_dir = os.path.dirname(os.path.abspath(__file__))

UNKNOWN_ENVIRONMENT_SYMBOL = 'UNKNOWN'
UNCLEAR_ENVIRONMENT_SYMBOL = 'UNCLEAR'
EXPLICIT_PERMUTATIONS = 'EXPLICIT_PERMUTATIONS'
SEPARATION_PLANE = 'SEPARATION_PLANE'


[docs]class AbstractChemenvAlgorithm(MSONable, metaclass=abc.ABCMeta): """ Base class used to define a Chemenv algorithm used to identify the correct permutation for the computation of the Continuous Symmetry Measure. """ def __init__(self, algorithm_type): """ Base constructor for ChemenvAlgorithm. Args: algorithm_type (str): Type of algorithm. """ self._algorithm_type = algorithm_type
[docs] @abc.abstractmethod def as_dict(self): """ A JSON serializable dict representation of the algorithm """ pass
@property def algorithm_type(self): """ Return the type of algorithm. Returns: Type of the algorithm """ return self._algorithm_type @abc.abstractmethod def __str__(self): return
[docs]class ExplicitPermutationsAlgorithm(AbstractChemenvAlgorithm): """ Class representing the algorithm doing the explicit permutations for the calculation of the Continuous Symmetry Measure. """ def __init__(self, permutations): """ Initializes a separation plane for a given perfect coordination geometry. Args: permutations: Permutations used for this algorithm. """ super().__init__( algorithm_type=EXPLICIT_PERMUTATIONS) self._permutations = permutations def __str__(self): return self.algorithm_type @property def permutations(self): """ Return the permutations to be performed for this algorithm. Returns: Permutations to be performed. """ return self._permutations @property def as_dict(self): """ Return the JSON serializable dict representation of this ExplicitPermutationsAlgorithm algorithm. Returns: a JSON serializable dict representation of this ExplicitPermutationsAlgorithm algorithm. """ return {"@module": self.__class__.__module__, "@class": self.__class__.__name__, "permutations": self._permutations}
[docs] @classmethod def from_dict(cls, dd): """ Reconstructs the ExplicitPermutationsAlgorithm algorithm from its JSON serializable dict representation. Args: dd: a JSON serializable dict representation of an ExplicitPermutationsAlgorithm algorithm. Returns: an ExplicitPermutationsAlgorithm algorithm. """ return cls(dd['permutations'])
[docs]class SeparationPlane(AbstractChemenvAlgorithm): """ Class representing the algorithm using separation planes for the calculation of the Continuous Symmetry Measure. """ def __init__(self, plane_points, mirror_plane=False, ordered_plane=False, point_groups=None, ordered_point_groups=None, # include_inverted_plane=False, # do_inverse_pt_gp_permutations=False, plane_type='MIRROR', explicit_permutations=None, minimum_number_of_points=None, explicit_optimized_permutations=None, multiplicity=None, other_plane_points=None): # , plane_safe_permutations=False): """ Initializes a separation plane for a given perfect coordination geometry Args: plane_points: Indices of the points that are in the plane in the perfect structure (and should be found in the defective one as well). mirror_plane: True if the separation plane is a mirror plane, in which case there is a correspondence of the points in each point_group (can reduce the number of permutations). ordered_plane: True if the order of the points in the plane can be taken into account to reduce the number of permutations. point_groups: Indices of the points in the two groups of points separated by the plane. ordered_point_groups: Whether the order of the points in each group of points can be taken into account to reduce the number of permutations. explicit_permutations: Explicit permutations to be performed in this separation plane algorithm. minimum_number_of_points: Minimum number of points needed to initialize a separation plane for this algorithm. explicit_optimized_permutations: Optimized set of explicit permutations to be performed in this separation plane algorithm. multiplicity: Number of such planes in the model geometry. other_plane_points: Indices of the points that are in the plane in the perfect structure for the other planes. The multiplicity should be equal to the length of this list + 1 ("main" separation plane + the other ones). """ super().__init__(algorithm_type=SEPARATION_PLANE) self.mirror_plane = mirror_plane self.plane_points = plane_points self.point_groups = point_groups if len(point_groups[0]) > len(point_groups[1]): raise RuntimeError( "The number of points in the first group should be\n" "less than or equal to the number of points in the second group") self._hash = 10000 * len(plane_points) + 100 * len( point_groups[0]) + len(point_groups[1]) self.ordered_plane = ordered_plane self.ordered_point_groups = [False, False] if ordered_point_groups is None else ordered_point_groups # self._ordered_indices = list(point_groups[0]) # self._ordered_indices.extend(plane_points) # self._ordered_indices.extend(point_groups[1]) # self._inv_ordered_indices = np.argsort(self._ordered_indices) self.explicit_permutations = explicit_permutations self.explicit_optimized_permutations = explicit_optimized_permutations self._safe_permutations = None if self.explicit_optimized_permutations is not None: self._permutations = self.explicit_optimized_permutations elif self.explicit_permutations is not None: self._permutations = self.explicit_permutations self.multiplicity = multiplicity self.other_plane_points = other_plane_points self.minimum_number_of_points = minimum_number_of_points self.maximum_number_of_points = len(self.plane_points) self._ref_separation_perm = list(self.point_groups[0]) self._ref_separation_perm.extend(list(self.plane_points)) self._ref_separation_perm.extend(list(self.point_groups[1])) self._argsorted_ref_separation_perm = list( np.argsort(self._ref_separation_perm)) self.separation = (len(point_groups[0]), len(plane_points), len(point_groups[1])) # @property # def ordered_indices(self): # """ # Ordered indices of the separation plane. # # Examples: # For a separation plane of type 2|4|3, with plane_points indices [0, 3, 5, 8] and # point_groups indices [1, 4] and [2, 7, 6], the list of ordered indices is : # [0, 3, 5, 8, 1, 4, 2, 7, 6]. # # Returns: list of ordered indices of this separation plane. # """ # return self._ordered_indices # # @property # def inv_ordered_indices(self): # return self._inv_ordered_indices @property def permutations(self): """ Permutations used for this separation plane algorithm. Returns: List of permutations to be performed. """ return self._permutations @property def ref_separation_perm(self): """ Ordered indices of the separation plane. Examples: For a separation plane of type 2|4|3, with plane_points indices [0, 3, 5, 8] and point_groups indices [1, 4] and [2, 7, 6], the list of ordered indices is : [0, 3, 5, 8, 1, 4, 2, 7, 6]. Returns: list of ordered indices of this separation plane. """ return self._ref_separation_perm @property def argsorted_ref_separation_perm(self): """ "Arg sorted" ordered indices of the separation plane. This is used in the identification of the final permutation to be used. Returns: list of the "arg sorted" ordered indices of the separation plane. """ return self._argsorted_ref_separation_perm
[docs] def safe_separation_permutations(self, ordered_plane=False, ordered_point_groups=None, add_opposite=False): """ Simple and safe permutations for this separation plane. This is not meant to be used in production. Default configuration for ChemEnv does not use this method. Args: ordered_plane: Whether the order of the points in the plane can be used to reduce the number of permutations. ordered_point_groups: Whether the order of the points in each point group can be used to reduce the number of permutations. add_opposite: Whether to add the permutations from the second group before the first group as well. Returns: List of safe permutations. """ s0 = list(range(len(self.point_groups[0]))) plane = list(range(len(self.point_groups[0]), len(self.point_groups[0]) + len(self.plane_points))) s1 = list(range(len(self.point_groups[0]) + len(self.plane_points), len(self.point_groups[0]) + len(self.plane_points) + len( self.point_groups[1]))) ordered_point_groups = [False, False] if ordered_point_groups is None else ordered_point_groups def rotate(s, n): return s[-n:] + s[:-n] if ordered_plane and self.ordered_plane: plane_perms = [rotate(plane, ii) for ii in range(len(plane))] inv_plane = plane[::-1] plane_perms.extend( [rotate(inv_plane, ii) for ii in range(len(inv_plane))]) else: plane_perms = list(itertools.permutations(plane)) if ordered_point_groups[0] and self.ordered_point_groups[0]: s0_perms = [rotate(s0, ii) for ii in range(len(s0))] inv_s0 = s0[::-1] s0_perms.extend([rotate(inv_s0, ii) for ii in range(len(inv_s0))]) else: s0_perms = list(itertools.permutations(s0)) if ordered_point_groups[1] and self.ordered_point_groups[1]: s1_perms = [rotate(s1, ii) for ii in range(len(s1))] inv_s1 = s1[::-1] s1_perms.extend([rotate(inv_s1, ii) for ii in range(len(inv_s1))]) else: s1_perms = list(itertools.permutations(s1)) if self._safe_permutations is None: self._safe_permutations = [] for perm_side1 in s0_perms: for perm_sep_plane in plane_perms: for perm_side2 in s1_perms: perm = list(perm_side1) perm.extend(list(perm_sep_plane)) perm.extend(list(perm_side2)) self._safe_permutations.append(perm) if add_opposite: perm = list(perm_side2) perm.extend(list(perm_sep_plane)) perm.extend(list(perm_side1)) self._safe_permutations.append(perm) return self._safe_permutations
@property def as_dict(self): """ Return the JSON serializable dict representation of this SeparationPlane algorithm. Returns: a JSON serializable dict representation of this SeparationPlane algorithm. """ return {"@module": self.__class__.__module__, "@class": self.__class__.__name__, "plane_points": self.plane_points, "mirror_plane": self.mirror_plane, "ordered_plane": self.ordered_plane, "point_groups": self.point_groups, "ordered_point_groups": self.ordered_point_groups, "explicit_permutations": [eperm.tolist() for eperm in self.explicit_permutations] if self.explicit_permutations is not None else None, "explicit_optimized_permutations": [eoperm.tolist() for eoperm in self.explicit_optimized_permutations] if self.explicit_optimized_permutations is not None else None, "multiplicity": self.multiplicity, "other_plane_points": self.other_plane_points, "minimum_number_of_points": self.minimum_number_of_points}
[docs] @classmethod def from_dict(cls, dd): """ Reconstructs the SeparationPlane algorithm from its JSON serializable dict representation. Args: dd: a JSON serializable dict representation of an SeparationPlane algorithm. Returns: a SeparationPlane algorithm. """ eop = [np.array(eoperm) for eoperm in dd[ 'explicit_optimized_permutations']] if ('explicit_optimized_permutations' in dd and dd['explicit_optimized_permutations'] is not None) else None return cls(plane_points=dd['plane_points'], mirror_plane=dd['mirror_plane'], ordered_plane=dd['ordered_plane'], point_groups=dd['point_groups'], ordered_point_groups=dd['ordered_point_groups'], explicit_permutations=[np.array(eperm) for eperm in dd['explicit_permutations']], explicit_optimized_permutations=eop, multiplicity=dd[ 'multiplicity'] if 'multiplicity' in dd else None, other_plane_points=dd[ 'other_plane_points'] if 'other_plane_points' in dd else None, minimum_number_of_points=dd['minimum_number_of_points'])
def __str__(self): out = 'Separation plane algorithm with the following reference separation :\n' out += '[{}] | [{}] | [{}]'.format( '-'.join(str(pp) for pp in [self.point_groups[0]]), '-'.join(str(pp) for pp in [self.plane_points]), '-'.join(str(pp) for pp in [self.point_groups[1]]), ) return out
[docs]class CoordinationGeometry: """ Class used to store the ideal representation of a chemical environment or "coordination geometry". """ # Default value of continuous symmetry measure beyond which no further # search is performed for the separation plane algorithms CSM_SKIP_SEPARATION_PLANE_ALGO = 10.0
[docs] class NeighborsSetsHints: """ Class used to describe neighbors sets hints. This allows to possibly get a lower coordination from a capped-like model polyhedron. """ ALLOWED_HINTS_TYPES = ['single_cap', 'double_cap', 'triple_cap'] def __init__(self, hints_type, options): """ Constructor for this NeighborsSetsHints. Args: hints_type: type of hint (single, double or triple cap) options: options for the "hinting", e.g. the maximum csm value beyond which no additional neighbors set could be found from a "cap hint". """ if hints_type not in self.ALLOWED_HINTS_TYPES: raise ValueError('Type "{}" for NeighborsSetsHints is not allowed'.format(type)) self.hints_type = hints_type self.options = options
[docs] def hints(self, hints_info): """ Return hints for an additional neighbors set, i.e. the voronoi indices that constitute this new neighbors set. Args: hints_info: Info needed to build new "hinted" neighbors set. Returns: Voronoi indices of the new "hinted" neighbors set. """ if hints_info['csm'] > self.options['csm_max']: return [] return object.__getattribute__(self, '{}_hints'.format(self.hints_type))(hints_info)
[docs] def single_cap_hints(self, hints_info): """ Return hints for an additional neighbors set, i.e. the voronoi indices that constitute this new neighbors set, in case of a "Single cap" hint. Args: hints_info: Info needed to build new "hinted" neighbors set. Returns: Voronoi indices of the new "hinted" neighbors set. """ cap_index_perfect = self.options['cap_index'] nb_set = hints_info['nb_set'] permutation = hints_info['permutation'] nb_set_voronoi_indices_perfect_aligned = nb_set.get_neighb_voronoi_indices(permutation=permutation) cap_voronoi_index = nb_set_voronoi_indices_perfect_aligned[cap_index_perfect] new_site_voronoi_indices = list(nb_set.site_voronoi_indices) new_site_voronoi_indices.remove(cap_voronoi_index) return [new_site_voronoi_indices]
[docs] def double_cap_hints(self, hints_info): """ Return hints for an additional neighbors set, i.e. the voronoi indices that constitute this new neighbors set, in case of a "Double cap" hint. Args: hints_info: Info needed to build new "hinted" neighbors set. Returns: Voronoi indices of the new "hinted" neighbors set. """ first_cap_index_perfect = self.options['first_cap_index'] second_cap_index_perfect = self.options['second_cap_index'] nb_set = hints_info['nb_set'] permutation = hints_info['permutation'] nb_set_voronoi_indices_perfect_aligned = nb_set.get_neighb_voronoi_indices(permutation=permutation) first_cap_voronoi_index = nb_set_voronoi_indices_perfect_aligned[first_cap_index_perfect] second_cap_voronoi_index = nb_set_voronoi_indices_perfect_aligned[second_cap_index_perfect] new_site_voronoi_indices1 = list(nb_set.site_voronoi_indices) new_site_voronoi_indices2 = list(nb_set.site_voronoi_indices) new_site_voronoi_indices3 = list(nb_set.site_voronoi_indices) new_site_voronoi_indices1.remove(first_cap_voronoi_index) new_site_voronoi_indices2.remove(second_cap_voronoi_index) new_site_voronoi_indices3.remove(first_cap_voronoi_index) new_site_voronoi_indices3.remove(second_cap_voronoi_index) return [new_site_voronoi_indices1, new_site_voronoi_indices2, new_site_voronoi_indices3]
[docs] def triple_cap_hints(self, hints_info): """ Return hints for an additional neighbors set, i.e. the voronoi indices that constitute this new neighbors set, in case of a "Triple cap" hint. Args: hints_info: Info needed to build new "hinted" neighbors set. Returns: Voronoi indices of the new "hinted" neighbors set. """ first_cap_index_perfect = self.options['first_cap_index'] second_cap_index_perfect = self.options['second_cap_index'] third_cap_index_perfect = self.options['third_cap_index'] nb_set = hints_info['nb_set'] permutation = hints_info['permutation'] nb_set_voronoi_indices_perfect_aligned = nb_set.get_neighb_voronoi_indices(permutation=permutation) first_cap_voronoi_index = nb_set_voronoi_indices_perfect_aligned[first_cap_index_perfect] second_cap_voronoi_index = nb_set_voronoi_indices_perfect_aligned[second_cap_index_perfect] third_cap_voronoi_index = nb_set_voronoi_indices_perfect_aligned[third_cap_index_perfect] new_site_voronoi_indices1 = list(nb_set.site_voronoi_indices) new_site_voronoi_indices2 = list(nb_set.site_voronoi_indices) new_site_voronoi_indices3 = list(nb_set.site_voronoi_indices) new_site_voronoi_indices4 = list(nb_set.site_voronoi_indices) new_site_voronoi_indices5 = list(nb_set.site_voronoi_indices) new_site_voronoi_indices6 = list(nb_set.site_voronoi_indices) new_site_voronoi_indices7 = list(nb_set.site_voronoi_indices) new_site_voronoi_indices1.remove(first_cap_voronoi_index) new_site_voronoi_indices2.remove(second_cap_voronoi_index) new_site_voronoi_indices3.remove(third_cap_voronoi_index) new_site_voronoi_indices4.remove(second_cap_voronoi_index) new_site_voronoi_indices4.remove(third_cap_voronoi_index) new_site_voronoi_indices5.remove(first_cap_voronoi_index) new_site_voronoi_indices5.remove(third_cap_voronoi_index) new_site_voronoi_indices6.remove(first_cap_voronoi_index) new_site_voronoi_indices6.remove(second_cap_voronoi_index) new_site_voronoi_indices7.remove(first_cap_voronoi_index) new_site_voronoi_indices7.remove(second_cap_voronoi_index) new_site_voronoi_indices7.remove(third_cap_voronoi_index) return [new_site_voronoi_indices1, new_site_voronoi_indices2, new_site_voronoi_indices3, new_site_voronoi_indices4, new_site_voronoi_indices5, new_site_voronoi_indices6, new_site_voronoi_indices7]
[docs] def as_dict(self): """ A JSON serializable dict representation of this NeighborsSetsHints """ return {'hints_type': self.hints_type, 'options': self.options}
[docs] @classmethod def from_dict(cls, dd): """ Reconstructs the NeighborsSetsHints from its JSON serializable dict representation. Args: dd: a JSON serializable dict representation of a NeighborsSetsHints. Returns: a NeighborsSetsHints. """ return cls(hints_type=dd['hints_type'], options=dd['options'])
def __init__(self, mp_symbol, name, alternative_names=None, IUPAC_symbol=None, IUCr_symbol=None, coordination=None, central_site=np.zeros(3), points=None, solid_angles=None, permutations_safe_override=False, deactivate=False, faces=None, edges=None, algorithms=None, equivalent_indices=None, neighbors_sets_hints=None): """ Initializes one "coordination geometry" according to [Pure Appl. Chem., Vol. 79, No. 10, pp. 1779--1799, 2007] and [Acta Cryst. A, Vol. 46, No. 1, pp. 1--11, 1990]. Args: mp_symbol: Symbol used internally for the coordination geometry. name: Name of the coordination geometry. alternative_names: Alternative names for this coordination geometry. IUPAC_symbol: The IUPAC symbol of this coordination geometry. IUCr_symbol: The IUCr symbol of this coordination geometry. coordination: The coordination number of this coordination geometry (number of neighboring atoms). central_site: The coordinates of the central site of this coordination geometry. points: The list of the coordinates of all the points of this coordination geometry. solid_angles: The list of solid angles for each neighbor in this coordination geometry. permutations_safe_override: Computes all the permutations if set to True (overrides the plane separation algorithms or any other algorithm, for testing purposes) deactivate: Whether to deactivate this coordination geometry faces: List of the faces with their vertices given in a clockwise or anticlockwise order, for drawing purposes. edges: List of edges, for drawing purposes. algorithms: Algorithms used to identify this coordination geometry. equivalent_indices: The equivalent sets of indices in this coordination geometry (can be used to skip equivalent permutations that have already been performed). neighbors_sets_hints: Neighors sets hints for this coordination geometry. """ self._mp_symbol = mp_symbol self.name = name self.alternative_names = alternative_names if alternative_names is not None else [] self.IUPACsymbol = IUPAC_symbol self.IUCrsymbol = IUCr_symbol self.coordination = coordination self.central_site = np.array(central_site) self.points = points self._solid_angles = solid_angles self.permutations_safe_override = permutations_safe_override # self.plane_safe_permutations = plane_safe_permutations # self.setup_permutations(permutations) self.deactivate = deactivate self._faces = faces self._edges = edges self._algorithms = algorithms if points is not None: self.centroid = np.mean(np.array(points), axis=0) else: self.centroid = None self.equivalent_indices = equivalent_indices self.neighbors_sets_hints = neighbors_sets_hints self._pauling_stability_ratio = None
[docs] def as_dict(self): """ A JSON serializable dict representation of this CoordinationGeometry. """ return {'mp_symbol': self._mp_symbol, 'name': self.name, 'alternative_names': self.alternative_names, 'IUPAC_symbol': self.IUPACsymbol, 'IUCr_symbol': self.IUCrsymbol, 'coordination': self.coordination, 'central_site': [float(xx) for xx in self.central_site], 'points': [[float(xx) for xx in pp] for pp in self.points] if self.points is not None else None, 'solid_angles': [float(ang) for ang in self._solid_angles] if self._solid_angles is not None else None, 'deactivate': self.deactivate, '_faces': self._faces, '_edges': self._edges, '_algorithms': [algo.as_dict for algo in self._algorithms] if self._algorithms is not None else None, 'equivalent_indices': self.equivalent_indices, 'neighbors_sets_hints': [nbsh.as_dict() for nbsh in self.neighbors_sets_hints] if self.neighbors_sets_hints is not None else None}
[docs] @classmethod def from_dict(cls, dd): """ Reconstructs the CoordinationGeometry from its JSON serializable dict representation. Args: dd: a JSON serializable dict representation of a CoordinationGeometry. Returns: a CoordinationGeometry. """ dec = MontyDecoder() return cls(mp_symbol=dd['mp_symbol'], name=dd['name'], alternative_names=dd['alternative_names'], IUPAC_symbol=dd['IUPAC_symbol'], IUCr_symbol=dd['IUCr_symbol'], coordination=dd['coordination'], central_site=dd['central_site'], points=dd['points'], solid_angles=(dd['solid_angles'] if 'solid_angles' in dd else [4.0 * np.pi / dd['coordination']] * dd[ 'coordination']), deactivate=dd['deactivate'], faces=dd['_faces'], edges=dd['_edges'], algorithms=[dec.process_decoded(algo_d) for algo_d in dd['_algorithms']] if dd['_algorithms'] is not None else None, equivalent_indices=dd[ 'equivalent_indices'] if 'equivalent_indices' in dd else None, neighbors_sets_hints=[cls.NeighborsSetsHints.from_dict(nbshd) for nbshd in dd['neighbors_sets_hints']] if ('neighbors_sets_hints' in dd and dd['neighbors_sets_hints'] is not None) else None)
def __str__(self): symbol = '' if self.IUPAC_symbol is not None: symbol += ' (IUPAC: {s}'.format(s=self.IUPAC_symbol) if self.IUCr_symbol is not None: symbol += ' || IUCr: {s})'.format(s=self.IUCr_symbol) else: symbol += ')' elif self.IUCr_symbol is not None: symbol += ' (IUCr: {s})'.format(s=self.IUCr_symbol) outs = ['Coordination geometry type : {n}{s}\n'.format(n=self.name, s=symbol), ' - coordination number : {c}'.format(c=self.coordination)] if self.points is None: outs.append('... not yet implemented') else: outs.append(' - list of points :') for pp in self.points: outs.append(' - {p}'.format(p=pp)) outs.append( '------------------------------------------------------------') outs.append('') return '\n'.join(outs) def __repr__(self): symbol = '' if self.IUPAC_symbol is not None: symbol += ' (IUPAC: {s}'.format(s=self.IUPAC_symbol) if self.IUCr_symbol is not None: symbol += ' || IUCr: {s})'.format(s=self.IUCr_symbol) else: symbol += ')' elif self.IUCr_symbol is not None: symbol += ' (IUCr: {s})'.format(s=self.IUCr_symbol) outs = ['Coordination geometry type : {n}{s}\n'.format(n=self.name, s=symbol), ' - coordination number : {c}'.format(c=self.coordination)] outs.append( '------------------------------------------------------------') outs.append('') return '\n'.join(outs) def __len__(self): return self.coordination
[docs] def set_permutations_safe_override(self, permutations_safe_override): """ Setup ChemEnv so that a safe set of permutations are used. Args: permutations_safe_override: Whether to use safe permutations. """ self.permutations_safe_override = permutations_safe_override
# self.setup_permutations() # @property # def csm_skip_algo(self): # return self.CSM_SKIP_SEPARATION_PLANE_ALGO @property def distfactor_max(self): """ The maximum distfactor for the perfect CoordinationGeometry. Returns: Maximum distfactor for the perfect CoordinationGeometry (usually 1.0 for symmetric polyhedrons). """ dists = [np.linalg.norm(pp - self.central_site) for pp in self.points] return np.max(dists) / np.min(dists) @property def coordination_number(self): """ Returns the coordination number of this coordination geometry. """ return self.coordination @property def pauling_stability_ratio(self): """ Returns the theoretical Pauling stability ratio (rC/rA) for this environment. """ if self._pauling_stability_ratio is None: if self.ce_symbol in ['S:1', 'L:2']: self._pauling_stability_ratio = 0.0 else: mindist_anions = 1000000.0 mindist_cation_anion = 1000000.0 for ipt1 in range(len(self.points)): pt1 = np.array(self.points[ipt1]) mindist_cation_anion = min(mindist_cation_anion, np.linalg.norm(pt1 - self.central_site)) for ipt2 in range(ipt1 + 1, len(self.points)): pt2 = np.array(self.points[ipt2]) mindist_anions = min(mindist_anions, np.linalg.norm(pt1 - pt2)) anion_radius = mindist_anions / 2.0 cation_radius = mindist_cation_anion - anion_radius self._pauling_stability_ratio = cation_radius / anion_radius return self._pauling_stability_ratio @property def mp_symbol(self): """ Returns the MP symbol of this coordination geometry. """ return self._mp_symbol @property def ce_symbol(self): """ Returns the symbol of this coordination geometry. """ return self._mp_symbol
[docs] def get_coordination_number(self): """ Returns the coordination number of this coordination geometry. """ return self.coordination
[docs] def is_implemented(self): """ Returns True if this coordination geometry is implemented. """ return bool(self.points)
[docs] def get_name(self): """ Returns the name of this coordination geometry. """ return self.name
@property def IUPAC_symbol(self): """ Returns the IUPAC symbol of this coordination geometry. """ return self.IUPACsymbol @property def IUPAC_symbol_str(self): """ Returns a string representation of the IUPAC symbol of this coordination geometry. """ return str(self.IUPACsymbol) @property def IUCr_symbol(self): """ Returns the IUCr symbol of this coordination geometry. """ return self.IUCrsymbol @property def IUCr_symbol_str(self): """ Returns a string representation of the IUCr symbol of this coordination geometry. """ return str(self.IUCrsymbol) @property def number_of_permutations(self): """ Returns the number of permutations of this coordination geometry. """ if self.permutations_safe_override: return factorial(self.coordination) elif self.permutations is None: return factorial(self.coordination) return len(self.permutations)
[docs] def ref_permutation(self, permutation): """ Returns the reference permutation for a set of equivalent permutations. Can be useful to skip permutations that have already been performed. Args: permutation: Current permutation Returns: Reference permutation of the perfect CoordinationGeometry. """ perms = [] for eqv_indices in self.equivalent_indices: perms.append(tuple([permutation[ii] for ii in eqv_indices])) perms.sort() return perms[0]
@property def algorithms(self): """ Returns the list of algorithms that are used to identify this coordination geometry. """ return self._algorithms
[docs] def get_central_site(self): """ Returns the central site of this coordination geometry. """ return self.central_site
[docs] def faces(self, sites, permutation=None): """ Returns the list of faces of this coordination geometry. Each face is given as a list of its vertices coordinates. """ if permutation is None: coords = [site.coords for site in sites] else: coords = [sites[ii].coords for ii in permutation] return [[coords[ii] for ii in f] for f in self._faces]
[docs] def edges(self, sites, permutation=None, input='sites'): """ Returns the list of edges of this coordination geometry. Each edge is given as a list of its end vertices coordinates. """ if input == 'sites': coords = [site.coords for site in sites] elif input == 'coords': coords = sites # if permutation is None: # coords = [site.coords for site in sites] # else: # coords = [sites[ii].coords for ii in permutation] if permutation is not None: coords = [coords[ii] for ii in permutation] return [[coords[ii] for ii in e] for e in self._edges]
[docs] def solid_angles(self, permutation=None): """ Returns the list of "perfect" solid angles Each edge is given as a list of its end vertices coordinates. """ if permutation is None: return self._solid_angles else: return [self._solid_angles[ii] for ii in permutation]
[docs] def get_pmeshes(self, sites, permutation=None): """ Returns the pmesh strings used for jmol to show this geometry. """ pmeshes = [] # _vertices = [site.coords for site in sites] if permutation is None: _vertices = [site.coords for site in sites] else: _vertices = [sites[ii].coords for ii in permutation] _face_centers = [] number_of_faces = 0 for face in self._faces: if len(face) in [3, 4]: number_of_faces += 1 else: number_of_faces += len(face) _face_centers.append(np.array([np.mean([_vertices[face_vertex][ii] for face_vertex in face]) for ii in range(3)])) out = '{}\n'.format(len(_vertices) + len(_face_centers)) for vv in _vertices: out += '{:15.8f} {:15.8f} {:15.8f}\n'.format(vv[0], vv[1], vv[2]) for fc in _face_centers: out += '{:15.8f} {:15.8f} {:15.8f}\n'.format(fc[0], fc[1], fc[2]) out += '{:d}\n'.format(number_of_faces) for iface, face in enumerate(self._faces): if len(face) == 3: out += '4\n' elif len(face) == 4: out += '5\n' else: for ii in range(len(face)): out += '4\n' out += '{:d}\n'.format(len(_vertices) + iface) out += '{:d}\n'.format(face[ii]) out += '{:d}\n'.format(face[np.mod(ii + 1, len(face))]) out += '{:d}\n'.format(len(_vertices) + iface) if len(face) in [3, 4]: for face_vertex in face: out += '{:d}\n'.format(face_vertex) out += '{:d}\n'.format(face[0]) pmeshes.append({"pmesh_string": out}) return pmeshes
[docs]class AllCoordinationGeometries(dict): """ Class used to store all the reference "coordination geometries" (list with instances of the CoordinationGeometry classes) """ def __init__(self, permutations_safe_override=False, only_symbols=None): """ Initializes the list of Coordination Geometries. Args: permutations_safe_override: Whether to use safe permutations. only_symbols: Whether to restrict the list of environments to be identified. """ dict.__init__(self) self.cg_list = list() if only_symbols is None: f = open( '{}/coordination_geometries_files/allcg.txt'.format(module_dir), 'r') data = f.readlines() f.close() for line in data: cg_file = '{}/{}'.format(module_dir, line.strip()) f = open(cg_file, 'r') dd = json.load(f) f.close() self.cg_list.append(CoordinationGeometry.from_dict(dd)) else: for symbol in only_symbols: fsymbol = symbol.replace(':', '#') cg_file = '{}/coordination_geometries_files/{}.json'.format( module_dir, fsymbol) f = open(cg_file, 'r') dd = json.load(f) f.close() self.cg_list.append(CoordinationGeometry.from_dict(dd)) self.cg_list.append(CoordinationGeometry(UNKNOWN_ENVIRONMENT_SYMBOL, "Unknown environment", deactivate=True)) self.cg_list.append(CoordinationGeometry(UNCLEAR_ENVIRONMENT_SYMBOL, "Unclear environment", deactivate=True)) if permutations_safe_override: for cg in self.cg_list: cg.set_permutations_safe_override(True) self.minpoints = {} self.maxpoints = {} self.separations_cg = {} for cn in range(6, 21): for cg in self.get_implemented_geometries(coordination=cn): if only_symbols is not None and cg.ce_symbol not in only_symbols: continue if cn not in self.separations_cg: self.minpoints[cn] = 1000 self.maxpoints[cn] = 0 self.separations_cg[cn] = {} for algo in cg.algorithms: sep = (len(algo.point_groups[0]), len(algo.plane_points), len(algo.point_groups[1])) if sep not in self.separations_cg[cn]: self.separations_cg[cn][sep] = [] self.separations_cg[cn][sep].append(cg.mp_symbol) self.minpoints[cn] = min(self.minpoints[cn], algo.minimum_number_of_points) self.maxpoints[cn] = max(self.maxpoints[cn], algo.maximum_number_of_points) self.maxpoints_inplane = {cn: max([sep[1] for sep in seps.keys()]) for cn, seps in self.separations_cg.items()} def __getitem__(self, key): return self.get_geometry_from_mp_symbol(key) def __contains__(self, item): try: self[item] return True except LookupError: return False def __repr__(self): """ Returns a string with the list of coordination geometries. """ outs = ['', '#=================================#', '# List of coordination geometries #', '#=================================#', ''] for cg in self.cg_list: outs.append(repr(cg)) return '\n'.join(outs) def __str__(self): """ Returns a string with the list of coordination geometries that are implemented. """ outs = ['', '#=======================================================#', '# List of coordination geometries currently implemented #', '#=======================================================#', ''] for cg in self.cg_list: if cg.is_implemented(): outs.append(str(cg)) return '\n'.join(outs)
[docs] def get_geometries(self, coordination=None, returned='cg'): """ Returns a list of coordination geometries with the given coordination number. Args: coordination: The coordination number of which the list of coordination geometries are returned. returned: Type of objects in the list. """ geom = list() if coordination is None: for gg in self.cg_list: if returned == 'cg': geom.append(gg) elif returned == 'mp_symbol': geom.append(gg.mp_symbol) else: for gg in self.cg_list: if gg.get_coordination_number() == coordination: if returned == 'cg': geom.append(gg) elif returned == 'mp_symbol': geom.append(gg.mp_symbol) return geom
[docs] def get_symbol_name_mapping(self, coordination=None): """ Return a dictionary mapping the symbol of a CoordinationGeometry to its name. Args: coordination: Whether to restrict the dictionary to a given coordination. Returns: Dictionary mapping the symbol of a CoordinationGeometry to its name. """ geom = {} if coordination is None: for gg in self.cg_list: geom[gg.mp_symbol] = gg.name else: for gg in self.cg_list: if gg.get_coordination_number() == coordination: geom[gg.mp_symbol] = gg.name return geom
[docs] def get_symbol_cn_mapping(self, coordination=None): """ Return a dictionary mapping the symbol of a CoordinationGeometry to its coordination. Args: coordination: Whether to restrict the dictionary to a given coordination. Returns: Dictionary mapping the symbol of a CoordinationGeometry to its coordination. """ geom = {} if coordination is None: for gg in self.cg_list: geom[gg.mp_symbol] = gg.coordination_number else: for gg in self.cg_list: if gg.get_coordination_number() == coordination: geom[gg.mp_symbol] = gg.coordination_number return geom
[docs] def get_implemented_geometries(self, coordination=None, returned='cg', include_deactivated=False): """ Returns a list of the implemented coordination geometries with the given coordination number. Args: coordination: The coordination number of which the list of implemented coordination geometries are returned. returned: Type of objects in the list. include_deactivated: Whether to include CoordinationGeometry that are deactivated. """ geom = list() if coordination is None: for gg in self.cg_list: if gg.points is not None and ( (not gg.deactivate) or include_deactivated): if returned == 'cg': geom.append(gg) elif returned == 'mp_symbol': geom.append(gg.mp_symbol) else: for gg in self.cg_list: if gg.get_coordination_number() == coordination and gg.points is not None and \ ((not gg.deactivate) or include_deactivated): if returned == 'cg': geom.append(gg) elif returned == 'mp_symbol': geom.append(gg.mp_symbol) return geom
[docs] def get_not_implemented_geometries(self, coordination=None, returned='mp_symbol'): """ Returns a list of the implemented coordination geometries with the given coordination number. Args: coordination: The coordination number of which the list of implemented coordination geometries are returned. returned: Type of objects in the list. """ geom = list() if coordination is None: for gg in self.cg_list: if gg.points is None: if returned == 'cg': geom.append(gg) elif returned == 'mp_symbol': geom.append(gg.mp_symbol) else: for gg in self.cg_list: if gg.get_coordination_number() == coordination and gg.points is None: if returned == 'cg': geom.append(gg) elif returned == 'mp_symbol': geom.append(gg.mp_symbol) return geom
[docs] def get_geometry_from_name(self, name): """ Returns the coordination geometry of the given name. Args: name: The name of the coordination geometry. """ for gg in self.cg_list: if gg.name == name or name in gg.alternative_names: return gg raise LookupError( 'No coordination geometry found with name "{name}"'.format( name=name))
[docs] def get_geometry_from_IUPAC_symbol(self, IUPAC_symbol): """ Returns the coordination geometry of the given IUPAC symbol. Args: IUPAC_symbol: The IUPAC symbol of the coordination geometry. """ for gg in self.cg_list: if gg.IUPAC_symbol == IUPAC_symbol: return gg raise LookupError( 'No coordination geometry found with IUPAC symbol "{symbol}"'.format( symbol=IUPAC_symbol))
[docs] def get_geometry_from_IUCr_symbol(self, IUCr_symbol): """ Returns the coordination geometry of the given IUCr symbol. Args: IUCr_symbol: The IUCr symbol of the coordination geometry. """ for gg in self.cg_list: if gg.IUCr_symbol == IUCr_symbol: return gg raise LookupError( 'No coordination geometry found with IUCr symbol "{symbol}"'.format( symbol=IUCr_symbol))
[docs] def get_geometry_from_mp_symbol(self, mp_symbol): """ Returns the coordination geometry of the given mp_symbol. Args: mp_symbol: The mp_symbol of the coordination geometry. """ for gg in self.cg_list: if gg.mp_symbol == mp_symbol: return gg raise LookupError( 'No coordination geometry found with mp_symbol "{symbol}"'.format( symbol=mp_symbol))
[docs] def is_a_valid_coordination_geometry(self, mp_symbol=None, IUPAC_symbol=None, IUCr_symbol=None, name=None, cn=None): """ Checks whether a given coordination geometry is valid (exists) and whether the parameters are coherent with each other. Args: mp_symbol: The mp_symbol of the coordination geometry. IUPAC_symbol: The IUPAC_symbol of the coordination geometry. IUCr_symbol: The IUCr_symbol of the coordination geometry. name: The name of the coordination geometry. cn: The coordination of the coordination geometry. """ if name is not None: raise NotImplementedError( 'is_a_valid_coordination_geometry not implemented for the name') if mp_symbol is None and IUPAC_symbol is None and IUCr_symbol is None: raise SyntaxError( 'missing argument for is_a_valid_coordination_geometry : at least one of mp_symbol, ' 'IUPAC_symbol and IUCr_symbol must be passed to the function') if mp_symbol is not None: try: cg = self.get_geometry_from_mp_symbol(mp_symbol) if IUPAC_symbol is not None: if IUPAC_symbol != cg.IUPAC_symbol: return False if IUCr_symbol is not None: if IUCr_symbol != cg.IUCr_symbol: return False if cn is not None: if int(cn) != int(cg.coordination_number): return False return True except LookupError: return False elif IUPAC_symbol is not None: try: cg = self.get_geometry_from_IUPAC_symbol(IUPAC_symbol) if IUCr_symbol is not None: if IUCr_symbol != cg.IUCr_symbol: return False if cn is not None: if cn != cg.coordination_number: return False return True except LookupError: return False elif IUCr_symbol is not None: try: cg = self.get_geometry_from_IUCr_symbol(IUCr_symbol) if cn is not None: if cn != cg.coordination_number: return False return True except LookupError: return True raise Exception('Should not be here !')
[docs] def pretty_print(self, type='implemented_geometries', maxcn=8, additional_info=None): """ Return a string with a list of the Coordination Geometries. Args: type: Type of string to be returned (all_geometries, all_geometries_latex_images, all_geometries_latex, implemented_geometries). maxcn: Maximum coordination. additional_info: Whether to add some additional info for each coordination geometry. Returns: String describing the list of coordination geometries. """ if type == 'all_geometries_latex_images': mystring = '' for cn in range(1, maxcn + 1): mystring += '\\section*{{Coordination {cn}}}\n\n'.format(cn=cn) for cg in self.get_implemented_geometries(coordination=cn, returned='cg'): mystring += '\\subsubsection*{{{mp} : {name}}}\n\n'.format( mp=cg.mp_symbol, name=cg.get_name()) mystring += 'IUPAC : {iupac}\n\nIUCr : {iucr}\n\n'.format( iupac=cg.IUPAC_symbol, iucr=cg.IUCr_symbol) mystring += '\\begin{center}\n' mystring += '\\includegraphics[scale=0.15]{{images/{let}_{cif}.png}}\n'.format( let=cg.mp_symbol.split(':')[0], cif=cg.mp_symbol.split(':')[1]) mystring += '\\end{center}\n\n' for cg in self.get_not_implemented_geometries(cn, returned='cg'): mystring += '\\subsubsection*{{{mp} : {name}}}\n\n'.format( mp=cg.mp_symbol, name=cg.get_name()) mystring += 'IUPAC : {iupac}\n\nIUCr : {iucr}\n\n'.format( iupac=cg.IUPAC_symbol, iucr=cg.IUCr_symbol) elif type == 'all_geometries_latex': mystring = '' for cn in range(1, maxcn + 1): mystring += '\\subsection*{{Coordination {cn}}}\n\n'.format( cn=cn) mystring += '\\begin{itemize}\n' for cg in self.get_implemented_geometries(coordination=cn, returned='cg'): mystring += '\\item {mp} $\\rightarrow$ {name} '.format( mp=cg.mp_symbol.replace('_', '\\_'), name=cg.get_name()) mystring += '(IUPAC : {iupac} - IUCr : {iucr})\n'.format( iupac=cg.IUPAC_symbol_str, iucr=cg.IUCr_symbol_str.replace('[', '$[$').replace(']', '$]$')) for cg in self.get_not_implemented_geometries(cn, returned='cg'): mystring += '\\item {mp} $\\rightarrow$ {name} '.format( mp=cg.mp_symbol.replace('_', '\\_'), name=cg.get_name()) mystring += '(IUPAC : {iupac} - IUCr : {iucr})\n'.format( iupac=cg.IUPAC_symbol_str, iucr=cg.IUCr_symbol_str.replace('[', '$[$').replace(']', '$]$')) mystring += '\\end{itemize}\n\n' else: mystring = '+-------------------------+\n| Coordination geometries |\n+-------------------------+\n\n' for cn in range(1, maxcn + 1): mystring += '==>> CN = {cn} <<==\n'.format(cn=cn) if type == 'implemented_geometries': for cg in self.get_implemented_geometries(coordination=cn): if additional_info is not None: if 'nb_hints' in additional_info: if cg.neighbors_sets_hints is not None: addinfo = ' *' else: addinfo = '' else: addinfo = '' else: addinfo = '' mystring += ' - {mp} : {name}{addinfo}\n'.format(mp=cg.mp_symbol, name=cg.get_name(), addinfo=addinfo) elif type == 'all_geometries': for cg in self.get_geometries(coordination=cn): mystring += ' - {mp} : {name}\n'.format(mp=cg.mp_symbol, name=cg.get_name()) mystring += '\n' return mystring