# 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
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