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
« prev ^ index » next coverage.py v7.5.3, created at 2025-08-02 00:12 +0000
1# fmt: off
3from math import sqrt
5import numpy as np
7from ase.atoms import Atoms
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.
15 Creates a graphene nanoribbon in the x-z plane, with the nanoribbon
16 running along the z axis.
18 Parameters:
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 """
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')
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()
69 if type == 'zigzag':
70 edge_index0 = np.arange(m) * 2
71 edge_index1 = (n - 1) * m * 2 + np.arange(m) * 2 + 1
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
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
88 xmin = atoms.positions[0, 0]
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]
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
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
119 xmin = atoms.positions[-1, 0]
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)
143 atoms += arm_right_saturation.repeat((1, 1, m))
144 atoms += arm_left_saturation.repeat((1, 1, m))
146 atoms.cell = [b * 4 * n / 2.0, 0, 3 * C_C * m]
148 atoms.set_pbc([sheet, False, True])
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