"""
This module provides the OrbitList class.
"""
from collections import Counter
from typing import Any
import numpy as np
from _icet import _OrbitList
from icet.core.orbit import Orbit # noqa
from ase import Atoms
from icet.core.local_orbit_list_generator import LocalOrbitListGenerator
from icet.core.neighbor_list import get_neighbor_lists
from icet.core.matrix_of_equivalent_positions import \
_get_lattice_site_matrix_of_equivalent_positions, \
matrix_of_equivalent_positions_from_structure
from icet.core.structure import atoms_from_structure_data, structure_to_arrays
from icet.tools.geometry import (chemical_symbols_to_numbers,
atomic_number_to_chemical_symbol)
from icet.input_output.logging_tools import logger
from icet.input_output.repr_tools import html_representation, text_representation
logger = logger.getChild('orbit_list')
def _primitive_structure(self):
"""
A copy of the primitive structure to which the lattice sites in
the orbits are referenced to.
"""
return atoms_from_structure_data(self._get_structure_data())
def _get_rows(self) -> list[tuple[str, Any]]:
"""Returns the label and value pairs from which the string and HTML
representations are built.
Each orbit is summarized on one line, since the orbit list of a cluster
space holds hundreds of them.
Print an individual orbit to see the sites of its representative cluster.
"""
rows = [('number of orbits', len(self))]
for k, orbit in enumerate(self.orbits):
rows += [(f'orbit {k}', f'order {orbit.order}, multiplicity {len(orbit)}, '
f'radius {orbit.radius:.4f}, sites {orbit.sites}')]
return rows
def __str__(self) -> str:
""" String representation. """
return text_representation(title='Orbit List', rows=self._get_rows(),
width=80, label_width=18)
def _repr_html_(self) -> str:
""" HTML representation. Used, e.g., in jupyter notebooks. """
return html_representation(title='Orbit List', rows=self._get_rows())
def __repr__(self) -> str:
"""Representation.
The name is written out instead of being taken from the type, since the
orbit lists that the C++ core returns are instances of the bound base
class.
"""
return f'OrbitList(number_of_orbits={len(self)})'
def __getitem__(self, ind: int):
return self.get_orbit(ind)
def get_supercell_orbit_list(self,
structure: Atoms,
fractional_position_tolerance: float) -> _OrbitList:
"""
Returns the orbit list for a supercell structure.
For the interface see :class:`OrbitList <icet.core.orbit_list.OrbitList>`.
Parameters
----------
structure
Atomic structure.
fractional_position_tolerance
Tolerance applied when comparing positions in fractional coordinates.
"""
lolg = LocalOrbitListGenerator(
self,
**structure_to_arrays(structure),
fractional_position_tolerance=fractional_position_tolerance)
supercell_orbit_list = lolg.generate_full_orbit_list()
return supercell_orbit_list
def get_cluster_counts(self,
structure: Atoms,
fractional_position_tolerance: float,
orbit_indices: list[int] | None = None) -> dict[int, Counter]:
"""
Counts all clusters in a structure by finding their local orbit list.
Parameters
----------
structure
Structure for which to count clusters.
This structure needs to be commensurate with the structure this orbit
list is based on.
fractional_position_tolerance
Tolerance applied when comparing positions in fractional coordinates.
orbit_indices
Indices of the orbits for which counts are requested.
By default all orbits are counted.
Returns
-------
Dictionary, the keys of which are orbit indices and the values cluster
counts.
The latter are themselves dicts, with tuples of chemical symbols as
keys and the number of such clusters as values.
"""
supercell_orbit_list = self.get_supercell_orbit_list(
structure=structure,
fractional_position_tolerance=fractional_position_tolerance)
# Collect counts for all orbit_indices
if orbit_indices is None:
orbit_indices = range(len(self))
occupations = structure.get_atomic_numbers()
cluster_counts_full = {}
for i in orbit_indices:
orbit = supercell_orbit_list.get_orbit(i)
counts = orbit.get_cluster_counts(occupations)
sorted_counts = Counter()
for atomic_numbers, count in counts.items():
symbols = atomic_number_to_chemical_symbol(atomic_numbers)
sorted_symbols = tuple(sorted(symbols))
sorted_counts[sorted_symbols] += count
cluster_counts_full[i] = sorted_counts
return cluster_counts_full
# These are attached to the class that pybind11 exports rather than to the
# OrbitList subclass below, because pruning and merging return a new
# _OrbitList, and such an orbit list has to behave exactly like the one built
# from a structure.
_OrbitList.primitive_structure = property(_primitive_structure)
_OrbitList._get_rows = _get_rows
_OrbitList.__str__ = __str__
_OrbitList._repr_html_ = _repr_html_
_OrbitList.__repr__ = __repr__
_OrbitList.__getitem__ = __getitem__
_OrbitList.get_supercell_orbit_list = get_supercell_orbit_list
_OrbitList.get_cluster_counts = get_cluster_counts
[docs]
class OrbitList(_OrbitList):
"""
The orbit list object handles an internal list of orbits.
An orbit has a list of equivalent sites with the restriction
that at least one site is in the cell of the primitive structure.
An orbit list is immutable once constructed.
The operations that edit it, :func:`without_orbits`,
:func:`without_inactive_orbits`, and :func:`with_merged_orbits`, each
return a new orbit list.
Note
----
As a user you will usually not interact directly with objects of this type.
Parameters
----------
structure
This structure will be used to construct a primitive
structure on which all the lattice sites in the orbits
are based.
cutoffs
The `i`-th element of this list is the cutoff for orbits with
order `i+2`.
chemical_symbols
List of chemical symbols, each of which must map to an element
of the periodic table.
The outer list must be the same length as the `structure` object
and :attr:`chemical_symbols[i]` will correspond to the allowed species
on lattice site ``i``.
symprec
Tolerance imposed when analyzing the symmetry using spglib.
position_tolerance
Tolerance applied when comparing positions in Cartesian coordinates.
fractional_position_tolerance
Tolerance applied when comparing positions in fractional coordinates.
"""
def __init__(self,
structure: Atoms,
cutoffs: list[float],
chemical_symbols: list[list[str]],
symprec: float,
position_tolerance: float,
fractional_position_tolerance: float) -> None:
max_cutoff = np.max(cutoffs)
# Set up a permutation matrix
matrix_of_equivalent_positions, prim_structure, _ \
= matrix_of_equivalent_positions_from_structure(structure=structure,
cutoff=max_cutoff,
position_tolerance=position_tolerance,
find_primitive=False,
symprec=symprec)
allowed_atomic_numbers = [chemical_symbols_to_numbers(syms)
for syms in chemical_symbols]
logger.info('Done getting matrix_of_equivalent_positions.')
# Get a list of neighbor lists
neighbor_lists = get_neighbor_lists(structure=prim_structure, cutoffs=cutoffs,
position_tolerance=position_tolerance)
logger.info('Done getting neighbor lists.')
# Transform matrix_of_equivalent_positions to be in lattice site format
pm_lattice_sites = _get_lattice_site_matrix_of_equivalent_positions(
structure=prim_structure,
matrix_of_equivalent_positions=matrix_of_equivalent_positions,
fractional_position_tolerance=fractional_position_tolerance,
prune=True)
logger.info('Transformation of matrix of equivalent positions'
' to lattice neighbor format completed.')
_OrbitList.__init__(self,
**structure_to_arrays(prim_structure),
allowed_atomic_numbers=allowed_atomic_numbers,
matrix_of_equivalent_sites=pm_lattice_sites,
neighbor_lists=neighbor_lists,
position_tolerance=position_tolerance)
logger.info('Finished construction of orbit list.')