from cosymlib import shape, tools
from cosymlib.symmetry import Symmetry
from cosymlib.tools import generate_connectivity_from_geometry
from functools import wraps
from pointgroup import PointGroup
import numpy as np
def set_parameters(func):
@wraps(func)
def wrapper(*args, **kwargs):
args[0]._symmetry.set_parameters(kwargs)
return func(*args, **kwargs)
return wrapper
[docs]class Geometry:
"""
This class contains the methods related to shape and geometric symmetry calculations
:param positions: Cartesian coordinates
:type positions: list
:param symbols: Atomic elements symbols
:type symbols: list
:param name: Geometry name
:type name: str
:param connectivity: Connectivity list
:type connectivity: list
:param connectivity_thresh: Connectivity threshold
:type connectivity_thresh: float
:example:
.. code-block:: python
water = Geometry(positions=[[0.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 1.0],
symbols=['O', 'H', 'H'],
name='water molecule',
connectivity=[[1, 2], [1, 3]])
"""
def __init__(self,
positions,
symbols=(),
name='',
connectivity=None,
connectivity_thresh=1.2):
# self._central_atom = None
self._symbols = []
self._positions = []
self._atom_groups = list(symbols)
self._name = name
# TODO: This is a mess!
for symbol in symbols:
try:
int(symbol)
self._symbols.append(tools.atomic_number_to_element(int(symbol)))
except (ValueError, TypeError):
self._symbols.append(symbol.capitalize())
for ida, a in enumerate(symbol):
try:
int(a)
self._symbols[-1] = self._symbols[-1][:ida]
break
except (ValueError, TypeError, IndexError):
pass
try:
float(positions[0])
for symbol in positions:
self._positions.append(float(symbol))
self._positions = list(chunks(self._positions, 3))
except (ValueError, TypeError, IndexError):
for symbol in positions:
self._positions.append([float(j) for j in symbol])
self._positions = np.array(self._positions)
if connectivity is None:
self._connectivity = generate_connectivity_from_geometry(self, thresh=connectivity_thresh)
else:
self._connectivity = connectivity
self._shape = shape.Shape(self)
self._symmetry = Symmetry(self)
def __len__(self):
return len(self._positions)
def __str__(self):
txt = '{}\n\n'.format(len(self._positions))
for s, p in zip(self._symbols, self._positions):
txt += '{:3}'.format(s) + '{:10.5f} {:10.5f} {:10.5f}\n'.format(*p)
return txt
@property
def name(self):
return self._name
# TODO: 'symmetry' and 'shape' properties should be removed. Methods inside these
# TODO: classes should be mirrored in geometry/molecule class. See get_symmetry_measure()
@property
def symmetry(self):
return self._symmetry
@property
def shape(self):
return self._shape
def set_name(self, name):
self._name = name
[docs] def get_connectivity(self):
"""
Get connectivity as a list of pairs of connected atoms
:return: The connectivity
:rtype: list
"""
return self._connectivity
def set_connectivity(self, connectivity):
self._connectivity = connectivity
self._symmetry._connectivity = connectivity
def set_symbols(self, symbols):
self._symbols = symbols
self._symmetry._symbols = symbols
def set_positions(self, central_atom=0):
atom, self._positions = self._positions[central_atom], np.delete(self._positions, central_atom, 0)
self._positions = np.insert(self._positions, len(self._positions), atom, axis=0)
self._symmetry._positions= self._positions
self._shape._positions= self._positions
[docs] def get_positions(self):
"""
Get the positions in Cartesian coordinates
:return: the coordinates
:rtype: list
"""
return self._positions
[docs] def get_n_atoms(self):
"""
Get the number of atoms
:return: number of atoms
:rtype: int
"""
return len(self.get_positions())
[docs] def get_symbols(self):
"""
Get the atomic elements symbols
:return: the symbols
:rtype: list
"""
return self._symbols
def generate_connectivity(self, thresh=1.2):
self._connectivity = generate_connectivity_from_geometry(self, thresh=thresh)
# Shape methods
[docs] def get_shape_measure(self, shape_label, central_atom=0, fix_permutation=False):
"""
Get the Shape measure
:param shape_label: Reference shape label
:type shape_label: str
:param central_atom: the central atom position
:type central_atom: int
:param fix_permutation: Do not permute atoms
:type fix_permutation: bool
:return: The measure
:rtype: float
"""
return self._shape.measure(shape_label, central_atom=central_atom, fix_permutation=fix_permutation)
def get_shape_structure(self, shape_label, central_atom=0, fix_permutation=False):
from cosymlib.molecule.geometry import Geometry
structure_coordinates = self._shape.structure(shape_label, central_atom=central_atom, fix_permutation=fix_permutation)
return Geometry(symbols=self.get_symbols(),
positions=structure_coordinates,
name=self.name + '_structure')
def get_path_deviation(self, shape_label1, shape_label2, central_atom=0):
return self._shape.path_deviation(shape_label1, shape_label2, central_atom)
def get_generalized_coordinate(self, shape_label1, shape_label2, central_atom=0):
return self._shape.generalized_coordinate(shape_label1, shape_label2, central_atom)
# Symmetry methods
[docs] @set_parameters
def get_symmetry_measure(self, label, central_atom=0, center=None, multi=1, permutation=None):
"""
Get the symmetry measure
:param label: Symmetry point group
:type label: str
:param central_atom: central atom position (0 if no central atom)
:type central_atom: int
:param center: center of the measure in Cartesian coordinates
:type center: list
:param permutation: define permutation
:type permutation: list, tuple
:return: The symmetry measure
:rtype: float
"""
return self._symmetry.measure(label)
[docs] @set_parameters
def get_symmetry_permutation(self, label, central_atom=0, center=None, multi=1, permutation=None):
"""
Get the optimum atoms permutation for geometrical symmetry measures
:param label: Symmetry point group
:type label: str
:param central_atom: central atom position (0 if no central atom)
:type central_atom: int
:param center: center of the measure in Cartesian coordinates
:type center: list
:param permutation: define permutation
:type permutation: list, tuple
:return: The symmetry measure
:rtype: float
"""
return self._symmetry.optimum_permutation(label)
[docs] @set_parameters
def get_symmetry_nearest_structure(self, label, central_atom=0, center=None, multi=1, permutation=None):
"""
Returns the nearest ideal structure
:param label: symmetry point group
:type label: str
:param central_atom: central atom position (0 if no central atom)
:type central_atom: int
:param center: center of the measure in Cartesian coordinates
:type center: list
:param permutation: Define permutation
:type permutation: list, tuple
:return: The structure
:rtype: Geometry
"""
return Geometry(symbols=self.get_symbols(),
positions=self._symmetry.nearest_structure(label),
name=self.name + '_nearest')
# return self._symmetry.nearest_structure(label)
@set_parameters
def get_symmetry_optimum_axis(self, label, central_atom=0, permutation=None, center=None):
return self._symmetry.optimum_axis(label)
[docs] def get_pointgroup(self, tol=0.01):
"""
Get the symmetry point group
:param tol: The tolerance
:type tol: float
:return: The point group
:rtype: str
"""
return PointGroup(self._positions, self._symbols, tolerance_eig=tol).get_point_group()
def chunks(l, n):
for i in range(0, len(l), n):
yield l[i:i+n]