Coverage for ase / geometry / dimensionality / topology_scaling.py: 100.00%
43 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-30 08:22 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-30 08:22 +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
36 ----------
38 num_atoms: int The number of atoms in the unit cell.
39 n: int The number size of the (n, n, n) periodic supercell.
40 """
41 self.n = n
42 self.num_atoms = num_atoms
43 self.gsingle = DisjointSet(num_atoms)
44 self.gsuper = DisjointSet(num_atoms * n**3)
46 self.m = [1, n, n**2]
47 self.cells = np.array(list(itertools.product(range(n), repeat=3)))
48 self.offsets = num_atoms * np.dot(self.m, self.cells.T)
50 def insert_bond(self, i, j, offset):
51 """Inserts a bond into the component graph, both in the single cell and
52 each of the n^3 subcells of the supercell.
54 Parameters
55 ----------
57 i: int The index of the first atom.
58 n: int The index of the second atom.
59 offset: tuple The cell offset of the second atom.
60 """
61 nbr_cells = (self.cells + offset) % self.n
62 nbr_offsets = self.num_atoms * np.dot(self.m, nbr_cells.T)
64 self.gsingle.union(i, j)
65 for (a, b) in zip(self.offsets, nbr_offsets):
66 self.gsuper.union(a + i, b + j)
67 self.gsuper.union(b + i, a + j)
69 def _get_component_dimensionalities(self):
71 n = self.n
72 offsets = self.offsets
73 single_roots = np.unique(self.gsingle.find_all())
74 super_components = self.gsuper.find_all()
76 component_dim = {}
77 for i in single_roots:
79 num_clusters = len(np.unique(super_components[offsets + i]))
80 dim = {n**3: 0, n**2: 1, n: 2, 1: 3}[num_clusters]
81 component_dim[i] = dim
82 return component_dim
84 def check(self):
85 """Determines the dimensionality histogram.
87 Returns
88 -------
89 hist : tuple Dimensionality histogram.
90 """
91 cdim = self._get_component_dimensionalities()
92 hist = np.zeros(4).astype(int)
93 bc = np.bincount(list(cdim.values()))
94 hist[:len(bc)] = bc
95 return tuple(hist)
97 def get_components(self):
98 """Determines the dimensionality and constituent atoms of each
99 component.
101 Returns
102 -------
103 components: array The component ID every atom
104 """
105 relabelled_dim = {}
106 relabelled_components = self.gsingle.find_all(relabel=True)
107 cdim = self._get_component_dimensionalities()
108 for k, v in cdim.items():
109 relabelled_dim[relabelled_components[k]] = v
111 return relabelled_components, relabelled_dim