Coverage for /builds/ase/ase/ase/gui/surfaceslab.py: 70.75%

147 statements  

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

1# fmt: off 

2 

3'''surfaceslab.py - Window for setting up surfaces 

4''' 

5import ase.build as build 

6import ase.gui.ui as ui 

7from ase.data import reference_states 

8from ase.gui.i18n import _, ngettext 

9from ase.gui.widgets import Element, pybutton 

10 

11introtext = _("""\ 

12 Use this dialog to create surface slabs. Select the element by 

13writing the chemical symbol or the atomic number in the box. Then 

14select the desired surface structure. Note that some structures can 

15be created with an othogonal or a non-orthogonal unit cell, in these 

16cases the non-orthogonal unit cell will contain fewer atoms. 

17 

18 If the structure matches the experimental crystal structure, you can 

19look up the lattice constant, otherwise you have to specify it 

20yourself.""") 

21 

22# Name, structure, orthogonal, function 

23surfaces = [(_('FCC(100)'), _('fcc'), 'ortho', build.fcc100), 

24 (_('FCC(110)'), _('fcc'), 'ortho', build.fcc110), 

25 (_('FCC(111)'), _('fcc'), 'both', build.fcc111), 

26 (_('FCC(211)'), _('fcc'), 'ortho', build.fcc211), 

27 (_('BCC(100)'), _('bcc'), 'ortho', build.bcc100), 

28 (_('BCC(110)'), _('bcc'), 'both', build.bcc110), 

29 (_('BCC(111)'), _('bcc'), 'both', build.bcc111), 

30 (_('HCP(0001)'), _('hcp'), 'both', build.hcp0001), 

31 (_('HCP(10-10)'), _('hcp'), 'ortho', build.hcp10m10), 

32 (_('DIAMOND(100)'), _('diamond'), 'ortho', build.diamond100), 

33 (_('DIAMOND(111)'), _('diamond'), 'non-ortho', build.diamond111)] 

34 

35structures, crystal, orthogonal, functions = zip(*surfaces) 

36 

37py_template = """ 

38from ase.build import {func} 

39 

40atoms = {func}(symbol='{symbol}', size={size}, 

41 a={a}, {c}vacuum={vacuum}, orthogonal={ortho}) 

42""" 

43 

44 

45class SetupSurfaceSlab: 

46 '''Window for setting up a surface.''' 

47 

48 def __init__(self, gui): 

49 self.element = Element('', self.apply) 

50 self.structure = ui.ComboBox(structures, structures, 

51 self.structure_changed) 

52 self.structure_warn = ui.Label('', 'red') 

53 self.orthogonal = ui.CheckButton('', True, self.make) 

54 self.lattice_a = ui.SpinBox(3.2, 0.0, 10.0, 0.001, self.make) 

55 self.retrieve = ui.Button(_('Get from database'), 

56 self.structure_changed) 

57 self.lattice_c = ui.SpinBox(None, 0.0, 10.0, 0.001, self.make) 

58 self.x = ui.SpinBox(1, 1, 30, 1, self.make) 

59 self.x_warn = ui.Label('', 'red') 

60 self.y = ui.SpinBox(1, 1, 30, 1, self.make) 

61 self.y_warn = ui.Label('', 'red') 

62 self.z = ui.SpinBox(1, 1, 30, 1, self.make) 

63 self.vacuum_check = ui.CheckButton('', False, self.vacuum_checked) 

64 self.vacuum = ui.SpinBox(5, 0, 40, 0.01, self.make) 

65 self.description = ui.Label('') 

66 

67 win = self.win = ui.Window(_('Surface'), wmtype='utility') 

68 win.add(ui.Text(introtext)) 

69 win.add(self.element) 

70 win.add([_('Structure:'), self.structure, self.structure_warn]) 

71 win.add([_('Orthogonal cell:'), self.orthogonal]) 

72 win.add([_('Lattice constant:')]) 

73 win.add([_('\ta'), self.lattice_a, ('Å'), self.retrieve]) 

74 win.add([_('\tc'), self.lattice_c, ('Å')]) 

75 win.add([_('Size:')]) 

76 win.add([_('\tx: '), self.x, _(' unit cells'), self.x_warn]) 

77 win.add([_('\ty: '), self.y, _(' unit cells'), self.y_warn]) 

78 win.add([_('\tz: '), self.z, _(' unit cells')]) 

79 win.add([_('Vacuum: '), self.vacuum_check, self.vacuum, ('Å')]) 

80 win.add(self.description) 

81 # TRANSLATORS: This is a title of a window. 

82 win.add([pybutton(_('Creating a surface.'), self.make), 

83 ui.Button(_('Apply'), self.apply), 

84 ui.Button(_('OK'), self.ok)]) 

85 

86 self.element.grab_focus() 

87 self.gui = gui 

88 self.atoms = None 

89 self.lattice_c.active = False 

90 self.vacuum.active = False 

91 self.structure_changed() 

92 

93 def vacuum_checked(self, *args): 

94 if self.vacuum_check.var.get(): 

95 self.vacuum.active = True 

96 else: 

97 self.vacuum.active = False 

98 self.make() 

99 

100 def get_lattice(self, *args): 

101 if self.element.symbol is None: 

102 return 

103 ref = reference_states[self.element.Z] 

104 symmetry = "unknown" 

105 for struct in surfaces: 

106 if struct[0] == self.structure.value: 

107 symmetry = struct[1] 

108 if ref['symmetry'] != symmetry: 

109 # TRANSLATORS: E.g. "... assume fcc crystal structure for Au" 

110 self.structure_warn.text = (_('Error: Reference values assume {} ' 

111 'crystal structure for {}!'). 

112 format(ref['symmetry'], 

113 self.element.symbol)) 

114 else: 

115 if symmetry == 'fcc' or symmetry == 'bcc' or symmetry == 'diamond': 

116 self.lattice_a.value = ref['a'] 

117 elif symmetry == 'hcp': 

118 self.lattice_a.value = ref['a'] 

119 self.lattice_c.value = ref['a'] * ref['c/a'] 

120 self.make() 

121 

122 def structure_changed(self, *args): 

123 for surface in surfaces: 

124 if surface[0] == self.structure.value: 

125 if surface[2] == 'ortho': 

126 self.orthogonal.var.set(True) 

127 self.orthogonal.check['state'] = ['disabled'] 

128 elif surface[2] == 'non-ortho': 

129 self.orthogonal.var.set(False) 

130 self.orthogonal.check['state'] = ['disabled'] 

131 else: 

132 self.orthogonal.check['state'] = ['normal'] 

133 

134 if surface[1] == _('hcp'): 

135 self.lattice_c.active = True 

136 self.lattice_c.value = round(self.lattice_a.value * 

137 ((8.0 / 3.0) ** (0.5)), 3) 

138 else: 

139 self.lattice_c.active = False 

140 self.lattice_c.value = 'None' 

141 self.get_lattice() 

142 

143 def make(self, *args): 

144 symbol = self.element.symbol 

145 self.atoms = None 

146 self.description.text = '' 

147 self.python = None 

148 self.x_warn.text = '' 

149 self.y_warn.text = '' 

150 if symbol is None: 

151 return None 

152 

153 x = self.x.value 

154 y = self.y.value 

155 z = self.z.value 

156 size = (x, y, z) 

157 a = self.lattice_a.value 

158 c = self.lattice_c.value 

159 vacuum = self.vacuum.value 

160 if not self.vacuum_check.var.get(): 

161 vacuum = None 

162 ortho = self.orthogonal.var.get() 

163 

164 ortho_warn_even = _('Please enter an even value for orthogonal cell') 

165 

166 struct = self.structure.value 

167 if struct == _('BCC(111)') and y % 2 != 0 and ortho: 

168 self.y_warn.text = ortho_warn_even 

169 return None 

170 if struct == _('BCC(110)') and y % 2 != 0 and ortho: 

171 self.y_warn.text = ortho_warn_even 

172 return None 

173 if struct == _('FCC(111)') and y % 2 != 0 and ortho: 

174 self.y_warn.text = ortho_warn_even 

175 return None 

176 if struct == _('FCC(211)') and x % 3 != 0 and ortho: 

177 self.x_warn.text = _('Please enter a value divisible by 3' 

178 ' for orthogonal cell') 

179 return None 

180 if struct == _('HCP(0001)') and y % 2 != 0 and ortho: 

181 self.y_warn.text = ortho_warn_even 

182 return None 

183 if struct == _('HCP(10-10)') and y % 2 != 0 and ortho: 

184 self.y_warn.text = ortho_warn_even 

185 return None 

186 

187 for surface in surfaces: 

188 if surface[0] == struct: 

189 c_py = "" 

190 if surface[1] == _('hcp'): 

191 self.atoms = surface[3](symbol, size, a, c, vacuum, ortho) 

192 c_py = f"{c}, " 

193 else: 

194 self.atoms = surface[3](symbol, size, a, vacuum, ortho) 

195 

196 if vacuum is not None: 

197 vacuumtext = _(' Vacuum: {} Å.').format(vacuum) 

198 else: 

199 vacuumtext = '' 

200 

201 natoms = len(self.atoms) 

202 label = ngettext( 

203 # TRANSLATORS: e.g. "Au fcc100 surface with 2 atoms." 

204 # or "Au fcc100 surface with 2 atoms. Vacuum: 5 Å." 

205 '{symbol} {surf} surface with one atom.{vacuum}', 

206 '{symbol} {surf} surface with {natoms} atoms.{vacuum}', 

207 natoms).format(symbol=symbol, 

208 surf=surface[3].__name__, 

209 natoms=natoms, 

210 vacuum=vacuumtext) 

211 

212 self.description.text = label 

213 return py_template.format(func=surface[3].__name__, a=a, 

214 c=c_py, symbol=symbol, size=size, 

215 ortho=ortho, vacuum=vacuum) 

216 

217 def apply(self, *args): 

218 self.make() 

219 if self.atoms is not None: 

220 self.gui.new_atoms(self.atoms) 

221 return True 

222 else: 

223 ui.error(_('No valid atoms.'), 

224 _('You have not (yet) specified a consistent ' 

225 'set of parameters.')) 

226 return False 

227 

228 def ok(self, *args): 

229 if self.apply(): 

230 self.win.close()