root / ase / gui / nanoparticle.py @ 4
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 |
|