Coverage for /builds/ase/ase/ase/build/ribbon.py: 63.75%

80 statements  

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

1# fmt: off 

2 

3from math import sqrt 

4 

5import numpy as np 

6 

7from ase.atoms import Atoms 

8 

9 

10def graphene_nanoribbon(n, m, type='zigzag', saturated=False, C_H=1.09, 

11 C_C=1.42, vacuum=None, magnetic=False, initial_mag=1.12, 

12 sheet=False, main_element='C', saturate_element='H'): 

13 """Create a graphene nanoribbon. 

14 

15 Creates a graphene nanoribbon in the x-z plane, with the nanoribbon 

16 running along the z axis. 

17 

18 Parameters: 

19 

20 n: int 

21 The width of the nanoribbon. For armchair nanoribbons, this 

22 n may be half-integer to repeat by half a cell. 

23 m: int 

24 The length of the nanoribbon. 

25 type: str 

26 The orientation of the ribbon. Must be either 'zigzag' 

27 or 'armchair'. 

28 saturated: bool 

29 If true, hydrogen atoms are placed along the edge. 

30 C_H: float 

31 Carbon-hydrogen bond length. Default: 1.09 Angstrom. 

32 C_C: float 

33 Carbon-carbon bond length. Default: 1.42 Angstrom. 

34 vacuum: None (default) or float 

35 Amount of vacuum added to non-periodic directions, if present. 

36 magnetic: bool 

37 Make the edges magnetic. 

38 initial_mag: float 

39 Magnitude of magnetic moment if magnetic. 

40 sheet: bool 

41 If true, make an infinite sheet instead of a ribbon (default: False) 

42 """ 

43 

44 if m % 1 != 0: 

45 raise ValueError('m must be integer') 

46 if type == 'zigzag' and n % 1 != 0: 

47 raise ValueError('n must be an integer for zigzag ribbons') 

48 

49 b = sqrt(3) * C_C / 4 

50 arm_unit = Atoms(main_element + '4', 

51 pbc=(1, 0, 1), 

52 cell=[4 * b, 0, 3 * C_C]) 

53 arm_unit.positions = [[0, 0, 0], 

54 [b * 2, 0, C_C / 2.], 

55 [b * 2, 0, 3 * C_C / 2.], 

56 [0, 0, 2 * C_C]] 

57 arm_unit_half = Atoms(main_element + '2', 

58 pbc=(1, 0, 1), 

59 cell=[2 * b, 0, 3 * C_C]) 

60 arm_unit_half.positions = [[b * 2, 0, C_C / 2.], 

61 [b * 2, 0, 3 * C_C / 2.]] 

62 zz_unit = Atoms(main_element + '2', 

63 pbc=(1, 0, 1), 

64 cell=[3 * C_C / 2.0, 0, b * 4]) 

65 zz_unit.positions = [[0, 0, 0], 

66 [C_C / 2.0, 0, b * 2]] 

67 atoms = Atoms() 

68 

69 if type == 'zigzag': 

70 edge_index0 = np.arange(m) * 2 

71 edge_index1 = (n - 1) * m * 2 + np.arange(m) * 2 + 1 

72 

73 if magnetic: 

74 mms = np.zeros(m * n * 2) 

75 for i in edge_index0: 

76 mms[i] = initial_mag 

77 for i in edge_index1: 

78 mms[i] = -initial_mag 

79 

80 for i in range(n): 

81 layer = zz_unit.repeat((1, 1, m)) 

82 layer.positions[:, 0] += 3 * C_C / 2 * i 

83 if i % 2 == 1: 

84 layer.positions[:, 2] += 2 * b 

85 layer[-1].position[2] -= b * 4 * m 

86 atoms += layer 

87 

88 xmin = atoms.positions[0, 0] 

89 

90 if magnetic: 

91 atoms.set_initial_magnetic_moments(mms) 

92 if saturated: 

93 H_atoms0 = Atoms(saturate_element + str(m)) 

94 H_atoms0.positions = atoms[edge_index0].positions 

95 H_atoms0.positions[:, 0] -= C_H 

96 H_atoms1 = Atoms(saturate_element + str(m)) 

97 H_atoms1.positions = atoms[edge_index1].positions 

98 H_atoms1.positions[:, 0] += C_H 

99 atoms += H_atoms0 + H_atoms1 

100 atoms.cell = [n * 3 * C_C / 2, 0, m * 4 * b] 

101 

102 elif type == 'armchair': 

103 n *= 2 

104 n_int = int(round(n)) 

105 if abs(n_int - n) > 1e-10: 

106 raise ValueError( 

107 'The argument n has to be half-integer for armchair ribbons.') 

108 n = n_int 

109 

110 for i in range(n // 2): 

111 layer = arm_unit.repeat((1, 1, m)) 

112 layer.positions[:, 0] -= 4 * b * i 

113 atoms += layer 

114 if n % 2: 

115 layer = arm_unit_half.repeat((1, 1, m)) 

116 layer.positions[:, 0] -= 4 * b * (n // 2) 

117 atoms += layer 

118 

119 xmin = atoms.positions[-1, 0] 

120 

121 if saturated: 

122 if n % 2: 

123 arm_right_saturation = Atoms(saturate_element + '2', 

124 pbc=(1, 0, 1), 

125 cell=[2 * b, 0, 3 * C_C]) 

126 arm_right_saturation.positions = [ 

127 [- sqrt(3) / 2 * C_H, 0, C_C / 2 - C_H * 0.5], 

128 [- sqrt(3) / 2 * C_H, 0, 3 * C_C / 2.0 + C_H * 0.5]] 

129 else: 

130 arm_right_saturation = Atoms(saturate_element + '2', 

131 pbc=(1, 0, 1), 

132 cell=[4 * b, 0, 3 * C_C]) 

133 arm_right_saturation.positions = [ 

134 [- sqrt(3) / 2 * C_H, 0, C_H * 0.5], 

135 [- sqrt(3) / 2 * C_H, 0, 2 * C_C - C_H * 0.5]] 

136 arm_left_saturation = Atoms(saturate_element + '2', pbc=(1, 0, 1), 

137 cell=[4 * b, 0, 3 * C_C]) 

138 arm_left_saturation.positions = [ 

139 [b * 2 + sqrt(3) / 2 * C_H, 0, C_C / 2 - C_H * 0.5], 

140 [b * 2 + sqrt(3) / 2 * C_H, 0, 3 * C_C / 2.0 + C_H * 0.5]] 

141 arm_right_saturation.positions[:, 0] -= 4 * b * (n / 2.0 - 1) 

142 

143 atoms += arm_right_saturation.repeat((1, 1, m)) 

144 atoms += arm_left_saturation.repeat((1, 1, m)) 

145 

146 atoms.cell = [b * 4 * n / 2.0, 0, 3 * C_C * m] 

147 

148 atoms.set_pbc([sheet, False, True]) 

149 

150 # The ribbon was 'built' from x=0 towards negative x. 

151 # Move the ribbon to positive x: 

152 atoms.positions[:, 0] -= xmin 

153 if not sheet: 

154 atoms.cell[0] = 0.0 

155 if vacuum is not None: 

156 atoms.center(vacuum, axis=1) 

157 if not sheet: 

158 atoms.center(vacuum, axis=0) 

159 return atoms