Coverage for /builds/kinetik161/ase/ase/geometry/dimensionality/topology_scaling.py: 100.00%

43 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-12-10 11:04 +0000

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

2 

3Method is described in: 

4Topology-Scaling Identification of Layered Solids and Stable Exfoliated 

52D Materials 

6M. Ashton, J. Paul, S.B. Sinnott, and R.G. Hennig 

7Phys. Rev. Lett. 118, 106101 

82017 

9 

10 

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

12This permits k-interval analysis. 

13""" 

14 

15 

16import itertools 

17 

18import numpy as np 

19 

20from ase.geometry.dimensionality.disjoint_set import DisjointSet 

21 

22 

23class TSA: 

24 

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

26 """Initializes the TSA class. 

27 

28 A disjoint set is maintained for the single cell and for the supercell. 

29 For some materials, such as interpenetrating networks, 

30 the dimensionality classification is dependent on the size of the 

31 initial cell. 

32 

33 Parameters: 

34 

35 num_atoms: int The number of atoms in the unit cell. 

36 n: int The number size of the (n, n, n) periodic supercell. 

37 """ 

38 self.n = n 

39 self.num_atoms = num_atoms 

40 self.gsingle = DisjointSet(num_atoms) 

41 self.gsuper = DisjointSet(num_atoms * n**3) 

42 

43 self.m = [1, n, n**2] 

44 self.cells = np.array(list(itertools.product(range(n), repeat=3))) 

45 self.offsets = num_atoms * np.dot(self.m, self.cells.T) 

46 

47 def insert_bond(self, i, j, offset): 

48 """Inserts a bond into the component graph, both in the single cell and 

49 each of the n^3 subcells of the supercell. 

50 

51 Parameters: 

52 

53 i: int The index of the first atom. 

54 n: int The index of the second atom. 

55 offset: tuple The cell offset of the second atom. 

56 """ 

57 nbr_cells = (self.cells + offset) % self.n 

58 nbr_offsets = self.num_atoms * np.dot(self.m, nbr_cells.T) 

59 

60 self.gsingle.union(i, j) 

61 for (a, b) in zip(self.offsets, nbr_offsets): 

62 self.gsuper.union(a + i, b + j) 

63 self.gsuper.union(b + i, a + j) 

64 

65 def _get_component_dimensionalities(self): 

66 

67 n = self.n 

68 offsets = self.offsets 

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

70 super_components = self.gsuper.find_all() 

71 

72 component_dim = {} 

73 for i in single_roots: 

74 

75 num_clusters = len(np.unique(super_components[offsets + i])) 

76 dim = {n**3: 0, n**2: 1, n: 2, 1: 3}[num_clusters] 

77 component_dim[i] = dim 

78 return component_dim 

79 

80 def check(self): 

81 """Determines the dimensionality histogram. 

82 

83 Returns: 

84 hist : tuple Dimensionality histogram. 

85 """ 

86 cdim = self._get_component_dimensionalities() 

87 hist = np.zeros(4).astype(int) 

88 bc = np.bincount(list(cdim.values())) 

89 hist[:len(bc)] = bc 

90 return tuple(hist) 

91 

92 def get_components(self): 

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

94 component. 

95 

96 Returns: 

97 components: array The component ID every atom 

98 """ 

99 relabelled_dim = {} 

100 relabelled_components = self.gsingle.find_all(relabel=True) 

101 cdim = self._get_component_dimensionalities() 

102 for k, v in cdim.items(): 

103 relabelled_dim[relabelled_components[k]] = v 

104 

105 return relabelled_components, relabelled_dim