root / ase / gui / nanoparticle.py @ 3
Historique | Voir | Annoter | Télécharger (17,65 ko)
1 |
# encoding: utf-8
|
---|---|
2 |
"""nanoparticle.py - Window for setting up crystalline nanoparticles.
|
3 |
"""
|
4 |
|
5 |
import gtk |
6 |
from ase.gui.widgets import pack, cancel_apply_ok, oops |
7 |
from ase.gui.setupwindow import SetupWindow |
8 |
from ase.gui.pybutton import PyButton |
9 |
import ase |
10 |
import numpy as np |
11 |
# Delayed imports:
|
12 |
# ase.cluster.data
|
13 |
|
14 |
introtext = """\
|
15 |
You specify the size of the particle by specifying the number of atomic layers
|
16 |
in the different low-index crystal directions. Often, the number of layers is
|
17 |
specified for a family of directions, but they can be given individually.
|
18 |
|
19 |
When the particle is created, the actual numbers of layers are printed, they
|
20 |
may be less than specified if a surface is cut of by other surfaces."""
|
21 |
|
22 |
py_template = """
|
23 |
from ase.cluster import Cluster
|
24 |
import ase
|
25 |
|
26 |
layers = %(layers)s
|
27 |
atoms = Cluster(symbol='%(element)s', layers=layers, latticeconstant=%(a).5f,
|
28 |
symmetry='%(structure)s')
|
29 |
|
30 |
# OPTIONAL: Cast to ase.Atoms object, discarding extra information:
|
31 |
# atoms = ase.Atoms(atoms)
|
32 |
"""
|
33 |
|
34 |
class attribute_collection: |
35 |
pass
|
36 |
|
37 |
class SetupNanoparticle(SetupWindow): |
38 |
"Window for setting up a nanoparticle."
|
39 |
families = {'fcc': [(0,0,1), (0,1,1), (1,1,1)]} |
40 |
defaults = {'fcc': [6, 9, 5]} |
41 |
|
42 |
def __init__(self, gui): |
43 |
SetupWindow.__init__(self)
|
44 |
self.set_title("Nanoparticle") |
45 |
self.atoms = None |
46 |
import ase.cluster.data |
47 |
self.data_module = ase.cluster.data
|
48 |
import ase.cluster |
49 |
self.Cluster = ase.cluster.Cluster
|
50 |
self.wulffconstruction = ase.cluster.wulff_construction
|
51 |
self.no_update = True |
52 |
|
53 |
vbox = gtk.VBox() |
54 |
|
55 |
# Intoductory text
|
56 |
self.packtext(vbox, introtext)
|
57 |
|
58 |
# Choose the element
|
59 |
label = gtk.Label("Element: ")
|
60 |
label.set_alignment(0.0, 0.2) |
61 |
element = gtk.Entry(max=3)
|
62 |
self.element = element
|
63 |
lattice_button = gtk.Button("Get structure")
|
64 |
lattice_button.connect('clicked', self.get_structure) |
65 |
self.elementinfo = gtk.Label(" ") |
66 |
pack(vbox, [label, element, self.elementinfo, lattice_button], end=True) |
67 |
self.element.connect('activate', self.update) |
68 |
self.legal_element = False |
69 |
|
70 |
# The structure and lattice constant
|
71 |
label = gtk.Label("Structure: ")
|
72 |
self.structure = gtk.combo_box_new_text()
|
73 |
self.allowed_structures = ('fcc',) |
74 |
for struct in self.allowed_structures: |
75 |
self.structure.append_text(struct)
|
76 |
self.structure.set_active(0) |
77 |
self.structure.connect('changed', self.update) |
78 |
|
79 |
label2 = gtk.Label(" Lattice constant: ")
|
80 |
self.lattice_const = gtk.Adjustment(3.0, 0.0, 1000.0, 0.01) |
81 |
lattice_box = gtk.SpinButton(self.lattice_const, 10.0, 3) |
82 |
lattice_box.numeric = True
|
83 |
pack(vbox, [label, self.structure, label2, lattice_box])
|
84 |
self.lattice_const.connect('value-changed', self.update) |
85 |
|
86 |
# Choose specification method
|
87 |
label = gtk.Label("Method: ")
|
88 |
self.method = gtk.combo_box_new_text()
|
89 |
for meth in ("Layer specification", "Wulff construction"): |
90 |
self.method.append_text(meth)
|
91 |
self.method.set_active(0) |
92 |
self.method.connect('changed', self.update_gui) |
93 |
pack(vbox, [label, self.method])
|
94 |
pack(vbox, gtk.Label(""))
|
95 |
|
96 |
# The number of layers
|
97 |
self.layerbox = gtk.VBox()
|
98 |
self.layerdata = attribute_collection()
|
99 |
pack(vbox, self.layerbox)
|
100 |
self.make_layer_gui(self.layerbox, self.layerdata, 0) |
101 |
|
102 |
# The Wulff construction
|
103 |
self.wulffbox = gtk.VBox()
|
104 |
self.wulffdata = attribute_collection()
|
105 |
pack(vbox, self.wulffbox)
|
106 |
self.make_layer_gui(self.wulffbox, self.wulffdata, 1) |
107 |
label = gtk.Label("Particle size: ")
|
108 |
self.size_n_radio = gtk.RadioButton(None, "Number of atoms: ") |
109 |
self.size_n_radio.set_active(True) |
110 |
self.size_n_adj = gtk.Adjustment(100, 1, 100000, 1) |
111 |
self.size_n_spin = gtk.SpinButton(self.size_n_adj, 0, 0) |
112 |
self.size_dia_radio = gtk.RadioButton(self.size_n_radio, |
113 |
"Volume: ")
|
114 |
self.size_dia_adj = gtk.Adjustment(1.0, 0, 100.0, 0.1) |
115 |
self.size_dia_spin = gtk.SpinButton(self.size_dia_adj, 10.0, 2) |
116 |
pack(self.wulffbox, [label, self.size_n_radio, self.size_n_spin, |
117 |
gtk.Label(" "), self.size_dia_radio, self.size_dia_spin, |
118 |
gtk.Label("ų")])
|
119 |
self.size_n_radio.connect("toggled", self.update_gui) |
120 |
self.size_dia_radio.connect("toggled", self.update_gui) |
121 |
self.size_n_adj.connect("value-changed", self.update_size_n) |
122 |
self.size_dia_adj.connect("value-changed", self.update_size_dia) |
123 |
self.update_size_n()
|
124 |
label = gtk.Label( |
125 |
"Rounding: If exact size is not possible, choose the size")
|
126 |
pack(self.wulffbox, [label])
|
127 |
self.round_above = gtk.RadioButton(None, "above ") |
128 |
self.round_below = gtk.RadioButton(self.round_above, "below ") |
129 |
self.round_closest = gtk.RadioButton(self.round_above, "closest ") |
130 |
self.round_closest.set_active(True) |
131 |
buts = [self.round_above, self.round_below, self.round_closest] |
132 |
pack(self.wulffbox, buts)
|
133 |
for b in buts: |
134 |
b.connect("toggled", self.update) |
135 |
|
136 |
# Information
|
137 |
pack(vbox, gtk.Label(""))
|
138 |
infobox = gtk.VBox() |
139 |
label1 = gtk.Label("Number of atoms: ")
|
140 |
self.natoms_label = gtk.Label("-") |
141 |
label2 = gtk.Label(" Approx. diameter: ")
|
142 |
self.dia1_label = gtk.Label("-") |
143 |
pack(infobox, [label1, self.natoms_label, label2, self.dia1_label]) |
144 |
pack(infobox, gtk.Label(""))
|
145 |
infoframe = gtk.Frame("Information about the created cluster:")
|
146 |
infoframe.add(infobox) |
147 |
infobox.show() |
148 |
pack(vbox, infoframe) |
149 |
|
150 |
# Buttons
|
151 |
self.pybut = PyButton("Creating a nanoparticle.") |
152 |
self.pybut.connect('clicked', self.makeatoms) |
153 |
buts = cancel_apply_ok(cancel=lambda widget: self.destroy(), |
154 |
apply=self.apply,
|
155 |
ok=self.ok)
|
156 |
pack(vbox, [self.pybut, buts], end=True, bottom=True) |
157 |
self.auto = gtk.CheckButton("Automatic Apply") |
158 |
fr = gtk.Frame() |
159 |
fr.add(self.auto)
|
160 |
fr.show_all() |
161 |
pack(vbox, [fr], end=True, bottom=True) |
162 |
|
163 |
# Finalize setup
|
164 |
self.update_gui()
|
165 |
self.add(vbox)
|
166 |
vbox.show() |
167 |
self.show()
|
168 |
self.gui = gui
|
169 |
self.no_update = False |
170 |
|
171 |
def update_gui(self, widget=None): |
172 |
method = self.method.get_active()
|
173 |
if method == 0: |
174 |
self.wulffbox.hide()
|
175 |
self.layerbox.show()
|
176 |
elif method == 1: |
177 |
self.layerbox.hide()
|
178 |
self.wulffbox.show()
|
179 |
self.size_n_spin.set_sensitive(self.size_n_radio.get_active()) |
180 |
self.size_dia_spin.set_sensitive(self.size_dia_radio.get_active()) |
181 |
|
182 |
def update_size_n(self, widget=None): |
183 |
if not self.size_n_radio.get_active(): |
184 |
return
|
185 |
at_vol = self.get_atomic_volume()
|
186 |
dia = 2.0 * (3 * self.size_n_adj.value * at_vol / (4 * np.pi))**(1.0/3) |
187 |
self.size_dia_adj.value = dia
|
188 |
self.update()
|
189 |
|
190 |
def update_size_dia(self, widget=None): |
191 |
if not self.size_dia_radio.get_active(): |
192 |
return
|
193 |
at_vol = self.get_atomic_volume()
|
194 |
n = round(np.pi / 6 * self.size_dia_adj.value**3 / at_vol) |
195 |
self.size_n_adj.value = n
|
196 |
self.update()
|
197 |
|
198 |
def update(self, *args): |
199 |
if self.no_update: |
200 |
return
|
201 |
self.update_gui()
|
202 |
self.update_element()
|
203 |
if self.auto.get_active(): |
204 |
self.makeatoms()
|
205 |
if self.atoms is not None: |
206 |
self.gui.new_atoms(self.atoms) |
207 |
else:
|
208 |
self.clearatoms()
|
209 |
self.makeinfo()
|
210 |
|
211 |
def get_structure(self, *args): |
212 |
if not self.update_element(): |
213 |
oops("Invalid element.")
|
214 |
return
|
215 |
z = ase.atomic_numbers[self.legal_element]
|
216 |
ref = ase.data.reference_states[z] |
217 |
if ref is None: |
218 |
structure = None
|
219 |
else:
|
220 |
structure = ref['symmetry'].lower()
|
221 |
|
222 |
if ref is None or not structure in self.allowed_structures: |
223 |
oops("Unsupported or unknown structure",
|
224 |
"Element = %s, structure = %s" % (self.legal_element, |
225 |
structure)) |
226 |
return
|
227 |
for i, s in enumerate(self.allowed_structures): |
228 |
if structure == s:
|
229 |
self.structure.set_active(i)
|
230 |
a = ref['a']
|
231 |
self.lattice_const.set_value(a)
|
232 |
|
233 |
def make_layer_gui(self, box, data, method): |
234 |
"Make the part of the gui specifying the layers of the particle"
|
235 |
assert method in (0,1) |
236 |
|
237 |
# Clear the box
|
238 |
children = box.get_children() |
239 |
for c in children: |
240 |
box.remove(c) |
241 |
del children
|
242 |
|
243 |
# Make the label
|
244 |
if method == 0: |
245 |
txt = "Number of layers:"
|
246 |
else:
|
247 |
txt = "Surface energies (unit: energy/area, i.e. J/m<sup>2</sup> or eV/nm<sup>2</sup>, <i>not</i> eV/atom):"
|
248 |
label = gtk.Label() |
249 |
label.set_markup(txt) |
250 |
pack(box, [label]) |
251 |
|
252 |
# Get the crystal structure
|
253 |
struct = self.structure.get_active_text()
|
254 |
# Get the surfaces in the order the ase.cluster module expects
|
255 |
surfaces = self.data_module.lattice[struct]['surface_names'] |
256 |
# Get the surface families
|
257 |
families = self.families[struct]
|
258 |
if method == 0: |
259 |
defaults = self.defaults[struct]
|
260 |
else:
|
261 |
defaults = [1.0] * len(self.defaults[struct]) |
262 |
|
263 |
# Empty array for the gtk.Adjustments for the layer numbers
|
264 |
data.layers = [None] * len(surfaces) |
265 |
data.layer_lbl = [None] * len(surfaces) |
266 |
data.layer_spin = [None] * len(surfaces) |
267 |
data.layer_owner = [None] * len(surfaces) |
268 |
data.layer_label = [None] * len(surfaces) |
269 |
data.famlayers = [None] * len(families) |
270 |
data.infamily = [None] * len(families) |
271 |
data.family_label = [None] * len(families) |
272 |
|
273 |
# Now, make a box for each family of surfaces
|
274 |
frames = [] |
275 |
for i in range(len(families)): |
276 |
family = families[i] |
277 |
default = defaults[i] |
278 |
frames.append(self.make_layer_family(data, i, family, surfaces,
|
279 |
default, method)) |
280 |
for a in data.layers: |
281 |
assert a is not None |
282 |
|
283 |
pack(box, frames) |
284 |
box.show_all() |
285 |
|
286 |
def make_layer_family(self, data, n, family, surfaces, default, method): |
287 |
"""Make a frame box for a single family of surfaces.
|
288 |
|
289 |
The layout is a frame containing a table. For example
|
290 |
|
291 |
{0,0,1}, SPIN, EMPTY, EMPTY
|
292 |
-- empty line --
|
293 |
(0,0,1), SPIN, Label(actual), Checkbox
|
294 |
...
|
295 |
"""
|
296 |
tbl = gtk.Table(2, 4) |
297 |
lbl = gtk.Label("{%i,%i,%i}: " % family)
|
298 |
lbl.set_alignment(1, 0.5) |
299 |
tbl.attach(lbl, 0, 1, 0, 1) |
300 |
if method == 0: |
301 |
famlayers = gtk.Adjustment(default, 1, 100, 1) |
302 |
sbut = gtk.SpinButton(famlayers, 0, 0) |
303 |
else:
|
304 |
flimit = 1000.0
|
305 |
famlayers = gtk.Adjustment(default, 0.0, flimit, 0.1) |
306 |
sbut = gtk.SpinButton(famlayers, 10.0, 3) |
307 |
tbl.attach(sbut, 2, 3, 0, 1) |
308 |
tbl.attach(gtk.Label(" "), 0, 1, 1, 2) |
309 |
assert data.famlayers[n] is None |
310 |
data.famlayers[n] = famlayers |
311 |
data.infamily[n] = [] |
312 |
data.family_label[n] = gtk.Label("")
|
313 |
tbl.attach(data.family_label[n], 1, 2, 0, 1) |
314 |
row = 2
|
315 |
myspin = [] |
316 |
for i, s in enumerate(surfaces): |
317 |
s2 = [abs(x) for x in s] |
318 |
s2.sort() |
319 |
if tuple(s2) == family: |
320 |
data.infamily[n].append(i) |
321 |
tbl.resize(row+1, 4) |
322 |
lbl = gtk.Label("(%i,%i,%i): " % s)
|
323 |
lbl.set_alignment(1, 0.5) |
324 |
tbl.attach(lbl, 0, 1, row, row+1) |
325 |
label = gtk.Label(" ")
|
326 |
tbl.attach(label, 1, 2, row, row+1) |
327 |
data.layer_label[i] = label |
328 |
if method == 0: |
329 |
lay = gtk.Adjustment(default, -100, 100, 1) |
330 |
spin = gtk.SpinButton(lay, 0, 0) |
331 |
else:
|
332 |
lay = gtk.Adjustment(default, -flimit, flimit, 0.1)
|
333 |
spin = gtk.SpinButton(lay, 10.0, 3) |
334 |
lay.connect('value-changed', self.update) |
335 |
spin.set_sensitive(False)
|
336 |
tbl.attach(spin, 2, 3, row, row+1) |
337 |
assert data.layers[i] is None |
338 |
data.layers[i] = lay |
339 |
data.layer_lbl[i] = lbl |
340 |
data.layer_spin[i] = spin |
341 |
data.layer_owner[i] = n |
342 |
myspin.append(spin) |
343 |
chkbut = gtk.CheckButton() |
344 |
tbl.attach(chkbut, 3, 4, row, row+1) |
345 |
chkbut.connect("toggled", self.toggle_surface, i) |
346 |
row += 1
|
347 |
famlayers.connect('value-changed', self.changed_family_layers, myspin) |
348 |
vbox = gtk.VBox() |
349 |
vbox.pack_start(tbl, False, False, 0) |
350 |
fr = gtk.Frame() |
351 |
fr.add(vbox) |
352 |
fr.show_all() |
353 |
return fr
|
354 |
|
355 |
def toggle_surface(self, widget, number): |
356 |
"Toggle whether a layer in a family can be specified."
|
357 |
active = widget.get_active() |
358 |
data = self.get_data()
|
359 |
data.layer_spin[number].set_sensitive(active) |
360 |
if not active: |
361 |
data.layers[number].value = \ |
362 |
data.famlayers[data.layer_owner[number]].value |
363 |
|
364 |
def changed_family_layers(self, widget, myspin): |
365 |
"Change the number of layers in inactive members of a family."
|
366 |
self.no_update = True |
367 |
x = widget.value |
368 |
for s in myspin: |
369 |
if s.state == gtk.STATE_INSENSITIVE:
|
370 |
adj = s.get_adjustment() |
371 |
if adj.value != x:
|
372 |
adj.value = x |
373 |
self.no_update = False |
374 |
self.update()
|
375 |
|
376 |
def get_data(self): |
377 |
return self.layerdata |
378 |
|
379 |
def makeatoms(self, *args): |
380 |
"Make the atoms according to the current specification."
|
381 |
if not self.update_element(): |
382 |
self.clearatoms()
|
383 |
self.makeinfo()
|
384 |
return False |
385 |
assert self.legal_element is not None |
386 |
struct = self.structure.get_active_text()
|
387 |
lc = self.lattice_const.value
|
388 |
if self.method.get_active() == 0: |
389 |
# Layer-by-layer specification
|
390 |
layers = [int(x.value) for x in self.layerdata.layers] |
391 |
self.atoms = self.Cluster(self.legal_element, layers=layers, |
392 |
latticeconstant=lc, symmetry=struct) |
393 |
self.pybut.python = py_template % {'element': self.legal_element, |
394 |
'layers': str(layers), |
395 |
'structure': struct,
|
396 |
'a': lc}
|
397 |
else:
|
398 |
# Wulff construction
|
399 |
assert self.method.get_active() == 1 |
400 |
surfaceenergies = [x.value for x in self.wulffdata.layers] |
401 |
self.update_size_dia()
|
402 |
if self.round_above.get_active(): |
403 |
rounding = "above"
|
404 |
elif self.round_below.get_active(): |
405 |
rounding = "below"
|
406 |
elif self.round_closest.get_active(): |
407 |
rounding = "closest"
|
408 |
else:
|
409 |
raise RuntimeError("No rounding!") |
410 |
self.atoms = self.wulffconstruction(self.legal_element, |
411 |
surfaceenergies, |
412 |
self.size_n_adj.value,
|
413 |
rounding, struct, lc) |
414 |
|
415 |
self.makeinfo()
|
416 |
|
417 |
def clearatoms(self): |
418 |
self.atoms = None |
419 |
self.pybut.python = None |
420 |
|
421 |
def get_atomic_volume(self): |
422 |
s = self.structure.get_active_text()
|
423 |
a = self.lattice_const.value
|
424 |
if s == 'fcc': |
425 |
return a**3 / 4 |
426 |
else:
|
427 |
raise RuntimeError("Unknown structure: "+s) |
428 |
|
429 |
def makeinfo(self): |
430 |
"Fill in information field about the atoms."
|
431 |
#data = self.get_data()
|
432 |
if self.atoms is None: |
433 |
self.natoms_label.set_label("-") |
434 |
self.dia1_label.set_label("-") |
435 |
for d in (self.layerdata, self.wulffdata): |
436 |
for label in d.layer_label+d.family_label: |
437 |
label.set_text(" ")
|
438 |
else:
|
439 |
self.natoms_label.set_label(str(len(self.atoms))) |
440 |
at_vol = self.get_atomic_volume()
|
441 |
dia = 2 * (3 * len(self.atoms) * at_vol / (4 * np.pi))**(1.0/3.0) |
442 |
self.dia1_label.set_label("%.1f Å" % (dia,)) |
443 |
actual = self.atoms.get_layers()
|
444 |
for i, a in enumerate(actual): |
445 |
self.layerdata.layer_label[i].set_text("%2i " % (a,)) |
446 |
self.wulffdata.layer_label[i].set_text("%2i " % (a,)) |
447 |
for d in (self.layerdata, self.wulffdata): |
448 |
for i, label in enumerate(d.family_label): |
449 |
relevant = actual[d.infamily[i]] |
450 |
if relevant.min() == relevant.max():
|
451 |
label.set_text("%2i " % (relevant[0])) |
452 |
else:
|
453 |
label.set_text("-- ")
|
454 |
|
455 |
def apply(self, *args): |
456 |
self.makeatoms()
|
457 |
if self.atoms is not None: |
458 |
self.gui.new_atoms(self.atoms) |
459 |
return True |
460 |
else:
|
461 |
oops("No valid atoms.",
|
462 |
"You have not (yet) specified a consistent set of parameters.")
|
463 |
return False |
464 |
|
465 |
def ok(self, *args): |
466 |
if self.apply(): |
467 |
self.destroy()
|
468 |
|