Coverage for /builds/ase/ase/ase/build/surfaces_with_termination.py: 98.73%

79 statements  

« prev     ^ index     » next       coverage.py v7.5.3, created at 2025-08-02 00:12 +0000

1# fmt: off 

2 

3import numpy as np 

4 

5from ase.build.general_surface import surface 

6from ase.geometry import get_layers 

7from ase.symbols import string2symbols 

8 

9 

10def surfaces_with_termination(lattice, indices, layers, vacuum=None, tol=1e-10, 

11 termination=None, return_all=False, 

12 verbose=False): 

13 """Create surface from a given lattice and Miller indices with a given 

14 termination 

15 

16 Parameters 

17 ========== 

18 lattice: Atoms object or str 

19 Bulk lattice structure of alloy or pure metal. Note that the 

20 unit-cell must be the conventional cell - not the primitive cell. 

21 One can also give the chemical symbol as a string, in which case the 

22 correct bulk lattice will be generated automatically. 

23 indices: sequence of three int 

24 Surface normal in Miller indices (h,k,l). 

25 layers: int 

26 Number of equivalent layers of the slab. (not the same as the layers 

27 you choose from for terminations) 

28 vacuum: float 

29 Amount of vacuum added on both sides of the slab. 

30 termination: str 

31 the atoms you wish to be in the top layer. There may be many such 

32 terminations, this function returns all terminations with the same 

33 atomic composition. 

34 e.g. 'O' will return oxygen terminated surfaces. 

35 e.g.'TiO' returns surfaces terminated with layers containing both 

36 O and Ti 

37 Returns: 

38 return_surfs: List 

39 a list of surfaces that match the specifications given 

40 

41 """ 

42 lats = translate_lattice(lattice, indices) 

43 return_surfs = [] 

44 check = [] 

45 check2 = [] 

46 for item in lats: 

47 too_similar = False 

48 surf = surface(item, indices, layers, vacuum=vacuum, tol=tol) 

49 surf.wrap(pbc=[True] * 3) # standardize slabs 

50 

51 positions = surf.get_scaled_positions().flatten() 

52 for i, value in enumerate(positions): 

53 if value >= 1 - tol: # move things closer to zero within tol 

54 positions[i] -= 1 

55 surf.set_scaled_positions(np.reshape(positions, (len(surf), 3))) 

56 # rep = find_z_layers(surf) 

57 z_layers, _hs = get_layers(surf, (0, 0, 1)) # just z layers matter 

58 # get the indicies of the atoms in the highest layer 

59 top_layer = [ 

60 i for i, val in enumerate( 

61 z_layers == max(z_layers)) if val] 

62 

63 if termination is not None: 

64 comp = [surf.get_chemical_symbols()[a] for a in top_layer] 

65 term = string2symbols(termination) 

66 # list atoms in top layer and not in requested termination 

67 check = [a for a in comp if a not in term] 

68 # list of atoms in requested termination and not in top layer 

69 check2 = [a for a in term if a not in comp] 

70 if len(return_surfs) > 0: 

71 pos_diff = [a.get_positions() - surf.get_positions() 

72 for a in return_surfs] 

73 for i, su in enumerate(pos_diff): 

74 similarity_test = su.flatten() < tol * 1000 

75 if similarity_test.all(): 

76 # checks if surface is too similar to another surface 

77 too_similar = True 

78 if too_similar: 

79 continue 

80 if return_all is True: 

81 pass 

82 elif check != [] or check2 != []: 

83 continue 

84 return_surfs.append(surf) 

85 return return_surfs 

86 

87 

88def translate_lattice(lattice, indices, tol=10**-3): 

89 """translates a bulk unit cell along a normal vector given by the a set of 

90 miller indices to the next symetric position. This is used to control the 

91 termination of the surface in the smart_surface command 

92 Parameters: 

93 ========== 

94 lattice: Atoms object 

95 atoms object of the bulk unit cell 

96 indices: 1x3 list,tuple, or numpy array 

97 the miller indices you wish to cut along. 

98 returns: 

99 lattice_list: list of Atoms objects 

100 a list of all the different translations of the unit cell that will 

101 yield different terminations of a surface cut along the miller 

102 indices provided. 

103 """ 

104 lattice_list = [] 

105 cell = lattice.get_cell() 

106 pt = [0, 0, 0] 

107 h, k, l = indices # noqa (E741 ambiguous name 'l') 

108 millers = list(indices) 

109 for index, item in enumerate(millers): 

110 if item == 0: 

111 millers[index] = 10**9 # make zeros large numbers 

112 elif pt == [0, 0, 0]: # for numerical stability 

113 pt = list(cell[index] / float(item) / np.linalg.norm(cell[index])) 

114 h1, k1, l1 = millers 

115 N = np.array(cell[0] / h1 + cell[1] / k1 + cell[2] / l1) 

116 n = N / np.linalg.norm(N) # making a unit vector normal to cut plane 

117 # finding distance from cut plan vector 

118 d = [np.round(np.dot(n, (a - pt)) * n, 5) for 

119 a in lattice.get_scaled_positions()] 

120 duplicates = [] 

121 for i, item in enumerate(d): 

122 g = [True for a in d[i + 1:] if np.linalg.norm(a - item) < tol] 

123 if g != []: 

124 duplicates.append(i) 

125 duplicates.reverse() 

126 for i in duplicates: 

127 del d[i] 

128 # put distance to the plane at the end of the array 

129 for i, item in enumerate(d): 

130 d[i] = np.append(item, 

131 np.dot(n, (lattice.get_scaled_positions()[i] - pt))) 

132 d = np.array(d) 

133 d = d[d[:, 3].argsort()] # sort by distance to the plane 

134 d = [a[:3] for a in d] # remove distance 

135 d = list(d) # make it a list again 

136 for i in d: 

137 """ 

138 The above method gives you the boundries of between terminations that 

139 will allow you to build a complete set of terminations. However, it 

140 does not return all the boundries. Thus you must check both above and 

141 below the boundary, and not stray too far from the boundary. If you move 

142 too far away, you risk hitting another boundary you did not find. 

143 """ 

144 lattice1 = lattice.copy() 

145 displacement = (h * cell[0] + k * cell[1] + l * cell[2]) \ 

146 * (i + 10 ** -8) 

147 lattice1.positions -= displacement 

148 lattice_list.append(lattice1) 

149 lattice1 = lattice.copy() 

150 displacement = (h * cell[0] + k * cell[1] + l * cell[2]) \ 

151 * (i - 10 ** -8) 

152 lattice1.positions -= displacement 

153 lattice_list.append(lattice1) 

154 return lattice_list