# coding: utf-8
# Copyright (c) Pymatgen Development Team.
# Distributed under the terms of the MIT License.
from __future__ import division, unicode_literals
"""
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.misc import factorial
import itertools
import abc
from monty.json import MSONable, MontyDecoder
import json
import os
from six import with_metaclass
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(with_metaclass(abc.ABCMeta, MSONable)):
"""
Class used to define a Chemenv strategy for the neighbors and coordination environment to be applied to a
StructureEnvironments object
"""
def __init__(self, algorithm_type):
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 self._algorithm_type
@abc.abstractmethod
def __str__(self):
return
[docs]class ExplicitPermutationsAlgorithm(AbstractChemenvAlgorithm):
def __init__(self, permutations):
"""
Initializes a separation plane for a given perfect coordination geometry
"""
super(ExplicitPermutationsAlgorithm, self).__init__(
algorithm_type=EXPLICIT_PERMUTATIONS)
self._permutations = permutations
def __str__(self):
return self.algorithm_type
@property
def permutations(self):
return self._permutations
@property
def as_dict(self):
return {"@module": self.__class__.__module__,
"@class": self.__class__.__name__,
"permutations": self._permutations}
[docs] @classmethod
def from_dict(cls, dd):
return cls(dd['permutations'])
[docs]class SeparationPlane(AbstractChemenvAlgorithm):
def __init__(self, plane_points, mirror_plane=False, ordered_plane=False,
point_groups=None,
ordered_point_groups=None, # include_inverted_plane=False,
point_groups_permutations=None,
# 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
:param 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)
:param ordered_plane : True if the order of the points in the plane can be taken into account to reduce the
number of permutations
:param 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)
:param point_groups: The two groups of points separated by the plane
:param plane_type: can be "MIRROR", if the plane is a mirror plane going through the central site,
'BASAL_THROUGH_CENTER', if the plane is a basal plane (no point on the "left" side) going through the central
site, 'BASAL', if the is a basal plane not going through the central site, 'UNEQUILIBRATED_THROUGH_CENTER', if
the plane cuts the geometry in two groups of points with different numbers of points on each side, and is going
through the centre, 'UNEQUILIBRATED', if the plane cuts the geometry in two groups of points with different
numbers of points on each side, and is not going through the centre, 'EQUILIBRATED_THROUGH_CENTER', if the
plane cuts the geometry in two groups of points of the same size, is going through the centre but is not a
mirror plane, 'EQUILIBRATED', if the plane cuts the geometry in two groups of points of the same size, is not
going through the centre but is not a mirror plane.
"""
super(SeparationPlane, self).__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._point_groups_permutations = point_groups_permutations
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))
@property
def ordered_indices(self):
return self._ordered_indices
@property
def inv_ordered_indices(self):
return self._inv_ordered_indices
@property
def permutations(self):
return self._permutations
@property
def ref_separation_perm(self):
return self._ref_separation_perm
@property
def argsorted_ref_separation_perm(self):
return self._argsorted_ref_separation_perm
[docs] def safe_plane_permutations(self, ordered_plane=False,
ordered_point_groups=None):
ordered_point_groups = [False,
False] if ordered_point_groups is None else ordered_point_groups
rotate = lambda s, n: s[-n:] + s[:-n]
if ordered_plane and self.ordered_plane:
plane_perms = [rotate(self.plane_points, ii) for ii in
range(len(self.plane_points))]
invplanepoints = self.plane_points[::-1]
plane_perms.extend([rotate(invplanepoints, ii) for ii in
range(len(self.plane_points) - 1, -1, -1)])
else:
plane_perms = list(itertools.permutations(self.plane_points))
if ordered_point_groups[0] and self.ordered_point_groups[0]:
s0_perms = [rotate(self.point_groups[0], ii) for ii in
range(len(self.point_groups[0]))]
invpg0 = self.point_groups[0][::-1]
s0_perms.extend([rotate(invpg0, ii) for ii in range(len(invpg0))])
else:
s0_perms = list(itertools.permutations(self.point_groups[0]))
if ordered_point_groups[1] and self.ordered_point_groups[1]:
s2_perms = [rotate(self.point_groups[1], ii) for ii in
range(len(self.point_groups[1]))]
invpg2 = self.point_groups[1][::-1]
s2_perms.extend([rotate(invpg2, ii) for ii in range(len(invpg2))])
else:
s2_perms = list(itertools.permutations(self.point_groups[1]))
add_opposite = False
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 s2_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
[docs] def safe_separation_permutations(self, ordered_plane=False,
ordered_point_groups=None,
add_opposite=False):
s0 = range(len(self.point_groups[0]))
plane = range(len(self.point_groups[0]),
len(self.point_groups[0]) + len(self.plane_points))
s1 = 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
rotate = lambda s, n: 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 {"@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,
"point_groups_permutations": self._point_groups_permutations,
"explicit_permutations": self.explicit_permutations,
"explicit_optimized_permutations": self.explicit_optimized_permutations,
"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):
eop = dd[
'explicit_optimized_permutations'] if 'explicit_optimized_permutations' in dd 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'],
point_groups_permutations=dd['point_groups_permutations'],
explicit_permutations=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(object):
"""
Class used to store the ideal representation of a chemical environment or "coordination geometry"
"""
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,
plane_ordering_override=True, deactivate=False, faces=None,
edges=None,
plane_safe_permutations=False, 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].
:param mp_symbol: Symbol used internally for the coordination geometry.
:param name: Name of the coordination geometry.
:param alternative_names: Alternative names for this coordination geometry.
:param IUPAC_symbol: The IUPAC symbol of this coordination geometry.
:param IUCr_symbol: The IUCr symbol of this coordination geometry.
:param coordination: The coordination number of this coordination geometry (number of neighboring atoms).
:param central_site: The coordinates of the central site of this coordination geometry.
:param points: The list of the coordinates of all the points of this coordination geometry.
:param separation_planes: List of separation facets to help set up the permutations
:param permutation_safe_override: Computes all the permutations if set to True (overrides the plane separation
algorithms or any other algorithm, for testing purposes)
:param plane_ordering_override: Computes all the permutations of the plane separation algorithm if set to False
otherwise, uses the anticlockwise ordering of the separation facets (for testing purposes)
:param deactivate: deactivates this coordination geometry in the search
:param faces : list of the faces with their vertices given in a clockwise or anticlockwise order, for drawing
purposes
:param : list of edges, for drawing purposes
"""
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_ordering_override = plane_ordering_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
[docs] def as_dict(self):
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):
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 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):
self.permutations_safe_override = permutations_safe_override
# self.setup_permutations()
@property
def distfactor_max(self):
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 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):
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] def get_pmeshes_test(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(permutation[face[ii]])
out += '{:d}\n'.format(
permutation[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(permutation[face_vertex])
out += '{:d}\n'.format(permutation[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
:param permutations_safe_override:
:param only_symbols:
"""
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)
def __getitem__(self, key):
return self.get_geometry_from_mp_symbol(key)
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.
:param coordination: The coordination number of which the list of coordination geometries are returned.
"""
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):
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):
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.
:param coordination: The coordination number of which the list of implemented coordination geometries
are returned.
"""
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.
:param coordination: The coordination number of which the list of implemented coordination geometries
are returned.
"""
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.
:param 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.
:param 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.
:param 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.
:param 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.
:param IUPAC_symbol:
:param IUCr_symbol:
:param name:
:param cn:
:param mp_symbol: The mp_symbol 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):
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