Statistiques
| Révision :

root / ase / gui / nanoparticle.py @ 15

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