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

1# fmt: off 

2 

3"""Implements the Topology-Scaling Algorithm (TSA) 

4 

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 

11 

12 

13A disjoint set is used here to allow insertion of bonds one at a time. 

14This permits k-interval analysis. 

15""" 

16 

17 

18import itertools 

19 

20import numpy as np 

21 

22from ase.geometry.dimensionality.disjoint_set import DisjointSet 

23 

24 

25class TSA: 

26 

27 def __init__(self, num_atoms, n=2): 

28 """Initializes the TSA class. 

29 

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. 

34 

35 Parameters 

36 ---------- 

37 

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) 

45 

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) 

49 

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. 

53 

54 Parameters 

55 ---------- 

56 

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) 

63 

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) 

68 

69 def _get_component_dimensionalities(self): 

70 

71 n = self.n 

72 offsets = self.offsets 

73 single_roots = np.unique(self.gsingle.find_all()) 

74 super_components = self.gsuper.find_all() 

75 

76 component_dim = {} 

77 for i in single_roots: 

78 

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 

83 

84 def check(self): 

85 """Determines the dimensionality histogram. 

86 

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) 

96 

97 def get_components(self): 

98 """Determines the dimensionality and constituent atoms of each 

99 component. 

100 

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 

110 

111 return relabelled_components, relabelled_dim