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
« prev ^ index » next coverage.py v7.5.3, created at 2025-08-02 00:12 +0000
1# fmt: off
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
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.
18 If the structure matches the experimental crystal structure, you can
19look up the lattice constant, otherwise you have to specify it
20yourself.""")
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)]
35structures, crystal, orthogonal, functions = zip(*surfaces)
37py_template = """
38from ase.build import {func}
40atoms = {func}(symbol='{symbol}', size={size},
41 a={a}, {c}vacuum={vacuum}, orthogonal={ortho})
42"""
45class SetupSurfaceSlab:
46 '''Window for setting up a surface.'''
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('')
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)])
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()
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()
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()
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']
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()
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
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()
164 ortho_warn_even = _('Please enter an even value for orthogonal cell')
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
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)
196 if vacuum is not None:
197 vacuumtext = _(' Vacuum: {} Å.').format(vacuum)
198 else:
199 vacuumtext = ''
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)
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)
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
228 def ok(self, *args):
229 if self.apply():
230 self.win.close()