Coverage for /builds/ase/ase/ase/geometry/dimensionality/topology_scaling.py: 100.00%
43 statements
« prev ^ index » next coverage.py v7.5.3, created at 2025-08-02 00:12 +0000
« prev ^ index » next coverage.py v7.5.3, created at 2025-08-02 00:12 +0000
1# fmt: off
3"""Implements the Topology-Scaling Algorithm (TSA)
5Method is described in:
6Topology-Scaling Identification of Layered Solids and Stable Exfoliated
72D Materials
8M. Ashton, J. Paul, S.B. Sinnott, and R.G. Hennig
9Phys. Rev. Lett. 118, 106101
102017
13A disjoint set is used here to allow insertion of bonds one at a time.
14This permits k-interval analysis.
15"""
18import itertools
20import numpy as np
22from ase.geometry.dimensionality.disjoint_set import DisjointSet
25class TSA:
27 def __init__(self, num_atoms, n=2):
28 """Initializes the TSA class.
30 A disjoint set is maintained for the single cell and for the supercell.
31 For some materials, such as interpenetrating networks,
32 the dimensionality classification is dependent on the size of the
33 initial cell.
35 Parameters:
37 num_atoms: int The number of atoms in the unit cell.
38 n: int The number size of the (n, n, n) periodic supercell.
39 """
40 self.n = n
41 self.num_atoms = num_atoms
42 self.gsingle = DisjointSet(num_atoms)
43 self.gsuper = DisjointSet(num_atoms * n**3)
45 self.m = [1, n, n**2]
46 self.cells = np.array(list(itertools.product(range(n), repeat=3)))
47 self.offsets = num_atoms * np.dot(self.m, self.cells.T)
49 def insert_bond(self, i, j, offset):
50 """Inserts a bond into the component graph, both in the single cell and
51 each of the n^3 subcells of the supercell.
53 Parameters:
55 i: int The index of the first atom.
56 n: int The index of the second atom.
57 offset: tuple The cell offset of the second atom.
58 """
59 nbr_cells = (self.cells + offset) % self.n
60 nbr_offsets = self.num_atoms * np.dot(self.m, nbr_cells.T)
62 self.gsingle.union(i, j)
63 for (a, b) in zip(self.offsets, nbr_offsets):
64 self.gsuper.union(a + i, b + j)
65 self.gsuper.union(b + i, a + j)
67 def _get_component_dimensionalities(self):
69 n = self.n
70 offsets = self.offsets
71 single_roots = np.unique(self.gsingle.find_all())
72 super_components = self.gsuper.find_all()
74 component_dim = {}
75 for i in single_roots:
77 num_clusters = len(np.unique(super_components[offsets + i]))
78 dim = {n**3: 0, n**2: 1, n: 2, 1: 3}[num_clusters]
79 component_dim[i] = dim
80 return component_dim
82 def check(self):
83 """Determines the dimensionality histogram.
85 Returns:
86 hist : tuple Dimensionality histogram.
87 """
88 cdim = self._get_component_dimensionalities()
89 hist = np.zeros(4).astype(int)
90 bc = np.bincount(list(cdim.values()))
91 hist[:len(bc)] = bc
92 return tuple(hist)
94 def get_components(self):
95 """Determines the dimensionality and constituent atoms of each
96 component.
98 Returns:
99 components: array The component ID every atom
100 """
101 relabelled_dim = {}
102 relabelled_components = self.gsingle.find_all(relabel=True)
103 cdim = self._get_component_dimensionalities()
104 for k, v in cdim.items():
105 relabelled_dim[relabelled_components[k]] = v
107 return relabelled_components, relabelled_dim