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

80 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-03-30 08:22 +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 

21 n: int 

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

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

24 m: int 

25 The length of the nanoribbon. 

26 type: str 

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

28 or 'armchair'. 

29 saturated: bool 

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

31 C_H: float 

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

33 C_C: float 

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

35 vacuum: None (default) or float 

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

37 magnetic: bool 

38 Make the edges magnetic. 

39 initial_mag: float 

40 Magnitude of magnetic moment if magnetic. 

41 sheet: bool 

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

43 """ 

44 

45 if m % 1 != 0: 

46 raise ValueError('m must be integer') 

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

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

49 

50 b = sqrt(3) * C_C / 4 

51 arm_unit = Atoms(main_element + '4', 

52 pbc=(1, 0, 1), 

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

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

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

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

57 [0, 0, 2 * C_C]] 

58 arm_unit_half = Atoms(main_element + '2', 

59 pbc=(1, 0, 1), 

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

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

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

63 zz_unit = Atoms(main_element + '2', 

64 pbc=(1, 0, 1), 

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

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

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

68 atoms = Atoms() 

69 

70 if type == 'zigzag': 

71 edge_index0 = np.arange(m) * 2 

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

73 

74 if magnetic: 

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

76 for i in edge_index0: 

77 mms[i] = initial_mag 

78 for i in edge_index1: 

79 mms[i] = -initial_mag 

80 

81 for i in range(n): 

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

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

84 if i % 2 == 1: 

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

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

87 atoms += layer 

88 

89 xmin = atoms.positions[0, 0] 

90 

91 if magnetic: 

92 atoms.set_initial_magnetic_moments(mms) 

93 if saturated: 

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

95 H_atoms0.positions = atoms[edge_index0].positions 

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

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

98 H_atoms1.positions = atoms[edge_index1].positions 

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

100 atoms += H_atoms0 + H_atoms1 

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

102 

103 elif type == 'armchair': 

104 n *= 2 

105 n_int = int(round(n)) 

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

107 raise ValueError( 

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

109 n = n_int 

110 

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

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

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

114 atoms += layer 

115 if n % 2: 

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

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

118 atoms += layer 

119 

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

121 

122 if saturated: 

123 if n % 2: 

124 arm_right_saturation = Atoms(saturate_element + '2', 

125 pbc=(1, 0, 1), 

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

127 arm_right_saturation.positions = [ 

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

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

130 else: 

131 arm_right_saturation = Atoms(saturate_element + '2', 

132 pbc=(1, 0, 1), 

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

134 arm_right_saturation.positions = [ 

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

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

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

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

139 arm_left_saturation.positions = [ 

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

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

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

143 

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

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

146 

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

148 

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

150 

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

152 # Move the ribbon to positive x: 

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

154 if not sheet: 

155 atoms.cell[0] = 0.0 

156 if vacuum is not None: 

157 atoms.center(vacuum, axis=1) 

158 if not sheet: 

159 atoms.center(vacuum, axis=0) 

160 return atoms