Source code for pymatgen.analysis.chemenv.connectivity.environment_nodes
__author__ = 'waroquiers'
import abc
from monty.json import MSONable
[docs]class AbstractEnvironmentNode(MSONable):
"""
Abstract class used to define an environment as a node in a graph.
"""
COORDINATION_ENVIRONMENT = 0
NUMBER_OF_NEIGHBORING_COORDINATION_ENVIRONMENTS = 1
NUMBER_OF_NEIGHBORING_CES = NUMBER_OF_NEIGHBORING_COORDINATION_ENVIRONMENTS
NEIGHBORING_COORDINATION_ENVIRONMENTS = 2
NEIGHBORING_CES = NEIGHBORING_COORDINATION_ENVIRONMENTS
NUMBER_OF_LIGANDS_FOR_EACH_NEIGHBORING_COORDINATION_ENVIRONMENT = 3
NUMBER_OF_LIGANDS_FOR_EACH_NEIGHBORING_CE = NUMBER_OF_LIGANDS_FOR_EACH_NEIGHBORING_COORDINATION_ENVIRONMENT
LIGANDS_ARRANGEMENT = 4
NEIGHBORS_LIGANDS_ARRANGEMENT = 5
ATOM = 6
CE_NNBCES_NBCES_LIGANDS = -1
DEFAULT_EXTENSIONS = (ATOM, COORDINATION_ENVIRONMENT)
def __init__(self, central_site, i_central_site):
"""
Constructor for the AbstractEnvironmentNode object.
Args:
central_site (Site or subclass of Site): central site as a pymatgen Site or
subclass of Site (e.g. PeriodicSite, ...).
i_central_site (int): Index of the central site in the structure.
"""
self.central_site = central_site
self.i_central_site = i_central_site
@property
def isite(self):
"""Index of the central site."""
return self.i_central_site
def __hash__(self):
"""Simple hash function based on the hash function of the central site."""
return self.central_site.__hash__()
def __eq__(self, other):
# When relabelling nodes from a str or int to an EnvironmentNode in-place in a graph (e.g. in a
# ConnectedComponent), the comparison should return False when comparing the already relabelled nodes (e.g. as
# an EnvironmentNode) with those not yet relabelled (e.g. a str representing the isite). This is useful for
# serialization as a json/bson object.
return self.__class__ == other.__class__ and self.isite == other.isite
def __lt__(self, other):
# This simple "Less Than" operator allows to strictly sort environment nodes in a graph.
# This is useful (and actually neeeded) in the definition of the cycles but does not have
# any real meaning of a "lower value" environment node.
return self.isite < other.isite
[docs] def everything_equal(self, other):
"""Checks equality with respect to another AbstractEnvironmentNode using the index of the central site
as well as the central site itself."""
return self.__eq__(other) and self.central_site == other.central_site
@property
@abc.abstractmethod
def coordination_environment(self):
return
# def number_of_neighboring_coordination_environments(self, environments_subgraph):
# # One cannot use the MultiGraph.neighbors(self) method because for self-loops,
# # it yields the neighbor only once
# incident_edges = environments_subgraph.edges(self)
# count = 0
# for edge in incident_edges:
# count += 1
# if edge[0] == edge[1]:
# count += 1
# return str(count)
#
# def neighboring_coordination_environments(self, environments_subgraph):
# pass
# # neighboring_environments_nodes = environments_subgraph.neighbors(self)
# # return str(len(neighboring_environments_nodes))
#
# def descriptor(self, extensions=DEFAULT_EXTENSIONS, **kwargs):
# return self.ce_extended(extensions, **kwargs)
#
# def ce_extended(self, extensions=DEFAULT_EXTENSIONS, **kwargs):
# npoints = max(extensions)
# res = ['']*(npoints + 1)
# for extension in extensions:
# res[extension] = self.get_descriptor(extension, **kwargs)
# return '.'.join(res)
#
# def get_descriptor(self, extension, **kwargs):
# if extension == AbstractEnvironmentNode.COORDINATION_ENVIRONMENT:
# return self.coordination_environment
# elif extension == AbstractEnvironmentNode.NUMBER_OF_NEIGHBORING_CES:
# env_subgraph = kwargs['environments_subgraph']
# return self.number_of_neighboring_coordination_environments(environments_subgraph=env_subgraph)
# elif extension == AbstractEnvironmentNode.CE_NNBCES_NBCES_LIGANDS:
# descr = str(self.coordination_environment)
# descr += '.'
# env_subgraph = kwargs['environments_subgraph']
# neighboring_ces_nodes = env_subgraph.neighbors(self)
# if len(list(neighboring_ces_nodes)) == 0:
# descr += '0-..'
# else:
# my_neighboring_nodes = []
# for ces_node in neighboring_ces_nodes:
# for iedge in env_subgraph[self][ces_node]:
# my_neighboring_nodes.append({'neighbor': ces_node,
# 'edge_data': env_subgraph[self][ces_node][iedge]})
# # Special case for self-loops :
# if ces_node == self:
# opposed_data = dict(env_subgraph[self][ces_node][iedge])
# opposed_data['delta'] = tuple([-ii for ii in opposed_data['delta']])
# my_neighboring_nodes.append({'neighbor': ces_node,
# 'edge_data': opposed_data})
# my_neighboring_nodes.sort(key=lambda x: len(x['edge_data']['ligands']))
# descr += str(len(my_neighboring_nodes))
# descr += '-'
# descr += ','.join([str(len(nn['edge_data']['ligands'])) for nn in my_neighboring_nodes])
# descr += '.'
# descr += ','.join([str(nn['neighbor'].coordination_environment) for nn in my_neighboring_nodes])
# #TODO: check sorting according to the nomenclature !
# return descr
# elif extension == AbstractEnvironmentNode.ATOM:
# return self.atom_symbol
# else:
# return 'NULL'
@property
def ce(self):
return self.coordination_environment
@property
def mp_symbol(self):
return self.coordination_environment
@property
def ce_symbol(self):
return self.coordination_environment
@property
def atom_symbol(self):
"""Symbol of the atom on the central site."""
return self.central_site.specie.symbol
def __str__(self):
"""String representation of the AbstractEnvironmentNode."""
return 'Node #{:d} {} ({})'.format(self.isite, self.atom_symbol, self.coordination_environment)
[docs]class EnvironmentNode(AbstractEnvironmentNode):
"""
Class used to define an environment as a node in a graph.
"""
def __init__(self, central_site, i_central_site, ce_symbol):
"""
Constructor for the EnvironmentNode object.
Args:
central_site (Site or subclass of Site): central site as a pymatgen Site or
subclass of Site (e.g. PeriodicSite, ...).
i_central_site (int): Index of the central site in the structure.
ce_symbol (str): Symbol of the identified environment.
"""
AbstractEnvironmentNode.__init__(self, central_site, i_central_site)
self._ce_symbol = ce_symbol
@property
def coordination_environment(self):
return self._ce_symbol
[docs] def everything_equal(self, other):
return (super().everything_equal(other) and
self.coordination_environment == other.coordination_environment)
# Keep these as they might come in handy later on if we decide to implement specific descriptors
# for specific environments
# class OctahedralEnvironmentNode(AbstractEnvironmentNode):
#
# CG = AllCoordinationGeometries().get_geometry_from_mp_symbol('O:6')
#
# def __init__(self, central_site, i_central_site):
# AbstractEnvironmentNode.__init__(self, central_site, i_central_site)
#
# @property
# def coordination_environment(self):
# return self.CG.mp_symbol
#
#
# class TetrahedralEnvironmentNode(AbstractEnvironmentNode):
#
# CG = AllCoordinationGeometries().get_geometry_from_mp_symbol('T:4')
#
# def __init__(self, central_site, i_central_site):
# AbstractEnvironmentNode.__init__(self, central_site, i_central_site)
#
# @property
# def coordination_environment(self):
# return self.CG.mp_symbol
#
#
# allowed_environment_nodes = {'O:6': OctahedralEnvironmentNode,
# 'T:4': TetrahedralEnvironmentNode}
[docs]def get_environment_node(central_site, i_central_site, ce_symbol):
"""
Get the EnvironmentNode class or subclass for the given site and symbol.
Args:
central_site (Site or subclass of Site): Central site of the environment.
i_central_site (int): Index of the central site in the structure.
ce_symbol: Symbol of the environment.
Returns:
An EnvironmentNode object.
"""
return EnvironmentNode(central_site=central_site, i_central_site=i_central_site, ce_symbol=ce_symbol)