Example #1
0
class SetupNanoparticle:
    "Window for setting up a nanoparticle."
    # Structures:  Abbreviation, name,
    # 4-index (boolean), two lattice const (bool), factory
    structure_data = (('fcc', _('Face centered cubic (fcc)'), False, False,
                       FaceCenteredCubic),
                      ('bcc', _('Body centered cubic (bcc)'), False, False,
                       BodyCenteredCubic), ('sc', _('Simple cubic (sc)'),
                                            False, False, SimpleCubic),
                      ('hcp', _('Hexagonal closed-packed (hcp)'), True, True,
                       HexagonalClosedPacked), ('graphite', _('Graphite'),
                                                True, True, Graphite))
    # NB:  HCP is broken!

    # A list of import statements for the Python window.
    import_names = {
        'fcc': 'from ase.cluster.cubic import FaceCenteredCubic',
        'bcc': 'from ase.cluster.cubic import BodyCenteredCubic',
        'sc': 'from ase.cluster.cubic import SimpleCubic',
        'hcp': 'from ase.cluster.hexagonal import HexagonalClosedPacked',
        'graphite': 'from ase.cluster.hexagonal import Graphite'
    }

    # Default layer specifications for the different structures.
    default_layers = {
        'fcc': [((1, 0, 0), 6), ((1, 1, 0), 9), ((1, 1, 1), 5)],
        'bcc': [((1, 0, 0), 6), ((1, 1, 0), 9), ((1, 1, 1), 5)],
        'sc': [((1, 0, 0), 6), ((1, 1, 0), 9), ((1, 1, 1), 5)],
        'hcp': [((0, 0, 0, 1), 5), ((1, 0, -1, 0), 5)],
        'graphite': [((0, 0, 0, 1), 5), ((1, 0, -1, 0), 5)]
    }

    def __init__(self, gui):
        self.atoms = None
        self.no_update = True
        self.old_structure = 'undefined'

        win = self.win = ui.Window(_('Nanoparticle'))
        win.add(ui.Text(introtext))

        self.element = Element('', self.apply)
        lattice_button = ui.Button(_('Get structure'), self.set_structure_data)
        self.elementinfo = ui.Label(' ')
        win.add(self.element)
        win.add(self.elementinfo)
        win.add(lattice_button)

        # The structure and lattice constant
        labels = []
        values = []
        self.needs_4index = {}
        self.needs_2lat = {}
        self.factory = {}
        for abbrev, name, n4, c, factory in self.structure_data:
            labels.append(name)
            values.append(abbrev)
            self.needs_4index[abbrev] = n4
            self.needs_2lat[abbrev] = c
            self.factory[abbrev] = factory
        self.structure = ui.ComboBox(labels, values, self.update_structure)
        win.add([_('Structure:'), self.structure])
        self.fourindex = self.needs_4index[values[0]]

        self.a = ui.SpinBox(3.0, 0.0, 1000.0, 0.01, self.update)
        self.c = ui.SpinBox(3.0, 0.0, 1000.0, 0.01, self.update)
        win.add([_('Lattice constant:  a ='), self.a, ' c =', self.c])

        # Choose specification method
        self.method = ui.ComboBox(
            [_('Layer specification'),
             _('Wulff construction')], ['layers', 'wulff'],
            self.update_gui_method)
        win.add([_('Method: '), self.method])

        self.layerlabel = ui.Label('Missing text')  # Filled in later
        win.add(self.layerlabel)
        self.direction_table_rows = ui.Rows()
        win.add(self.direction_table_rows)
        self.default_direction_table()

        win.add(_('Add new direction:'))
        self.new_direction_and_size_rows = ui.Rows()
        win.add(self.new_direction_and_size_rows)
        self.update_new_direction_and_size_stuff()

        # Information
        win.add(_('Information about the created cluster:'))
        self.info = [
            _('Number of atoms: '),
            ui.Label('-'),
            _('   Approx. diameter: '),
            ui.Label('-')
        ]
        win.add(self.info)

        # Finalize setup
        self.update_structure('fcc')
        self.update_gui_method()
        self.no_update = False

        self.auto = ui.CheckButton(_('Automatic Apply'))
        win.add(self.auto)

        win.add([
            pybutton(_('Creating a nanoparticle.'), self.makeatoms),
            ui.helpbutton(helptext),
            ui.Button(_('Apply'), self.apply),
            ui.Button(_('OK'), self.ok)
        ])

        self.gui = gui
        self.smaller_button = None
        self.largeer_button = None

        self.element.grab_focus()

    def default_direction_table(self):
        'Set default directions and values for the current crystal structure.'
        self.direction_table = []
        struct = self.structure.value
        for direction, layers in self.default_layers[struct]:
            self.direction_table.append((direction, layers, 1.0))

    def update_direction_table(self):
        self.direction_table_rows.clear()
        for direction, layers, energy in self.direction_table:
            self.add_direction(direction, layers, energy)
        self.update()

    def add_direction(self, direction, layers, energy):
        i = len(self.direction_table_rows)

        if self.method.value == 'wulff':
            spin = ui.SpinBox(energy, 0.0, 1000.0, 0.1, self.update)
        else:
            spin = ui.SpinBox(layers, 1, 100, 1, self.update)

        up = ui.Button(_('Up'), self.row_swap_next, i - 1)
        down = ui.Button(_('Down'), self.row_swap_next, i)
        delete = ui.Button(_('Delete'), self.row_delete, i)

        self.direction_table_rows.add(
            [str(direction) + ':', spin, up, down, delete])
        up.active = i > 0
        down.active = False
        delete.active = i > 0

        if i > 0:
            down, delete = self.direction_table_rows[-2][3:]
            down.active = True
            delete.active = True

    def update_new_direction_and_size_stuff(self):
        if self.needs_4index[self.structure.value]:
            n = 4
        else:
            n = 3

        rows = self.new_direction_and_size_rows

        rows.clear()

        self.new_direction = row = ['(']
        for i in range(n):
            if i > 0:
                row.append(',')
            row.append(ui.SpinBox(0, -100, 100, 1))
        row.append('):')

        if self.method.value == 'wulff':
            row.append(ui.SpinBox(1.0, 0.0, 1000.0, 0.1))
        else:
            row.append(ui.SpinBox(5, 1, 100, 1))

        row.append(ui.Button(_('Add'), self.row_add))

        rows.add(row)

        if self.method.value == 'wulff':
            # Extra widgets for the Wulff construction
            self.size_radio = ui.RadioButtons(
                [_('Number of atoms'), _('Diameter')], ['natoms', 'diameter'],
                self.update_gui_size)
            self.size_natoms = ui.SpinBox(100, 1, 100000, 1,
                                          self.update_size_natoms)
            self.size_diameter = ui.SpinBox(5.0, 0, 100.0, 0.1,
                                            self.update_size_diameter)
            self.round_radio = ui.RadioButtons(
                [_('above  '), _('below  '),
                 _('closest  ')], ['above', 'below', 'closest'],
                callback=self.update)
            self.smaller_button = ui.Button(_('Smaller'), self.wulff_smaller)
            self.larger_button = ui.Button(_('Larger'), self.wulff_larger)
            rows.add(_('Choose size using:'))
            rows.add(self.size_radio)
            rows.add(
                [_('atoms'), self.size_natoms,
                 _(u'ų'), self.size_diameter])
            rows.add(
                _('Rounding: If exact size is not possible, choose the size:'))
            rows.add(self.round_radio)
            rows.add([self.smaller_button, self.larger_button])
            self.update_gui_size()
        else:
            self.smaller_button = None
            self.larger_button = None

    def update_structure(self, s):
        'Called when the user changes the structure.'
        # s = self.structure.value
        if s != self.old_structure:
            old4 = self.fourindex
            self.fourindex = self.needs_4index[s]
            if self.fourindex != old4:
                # The table of directions is invalid.
                self.default_direction_table()
            self.old_structure = s
            self.c.active = self.needs_2lat[s]

        self.update()

    def update_gui_method(self, *args):
        'Switch between layer specification and Wulff construction.'
        self.update_direction_table()
        self.update_new_direction_and_size_stuff()
        if self.method.value == 'wulff':
            self.layerlabel.text = _(
                'Surface energies (as energy/area, NOT per atom):')
        else:
            self.layerlabel.text = _('Number of layers:')

        self.update()

    def wulff_smaller(self, widget=None):
        'Make a smaller Wulff construction.'
        n = len(self.atoms)
        self.size_radio.value = 'natoms'
        self.size_natoms.value = n - 1
        self.round_radio.value = 'below'
        self.apply()

    def wulff_larger(self, widget=None):
        'Make a larger Wulff construction.'
        n = len(self.atoms)
        self.size_radio.value = 'natoms'
        self.size_natoms.value = n + 1
        self.round_radio.value = 'above'
        self.apply()

    def row_add(self, widget=None):
        'Add a row to the list of directions.'
        if self.fourindex:
            n = 4
        else:
            n = 3
        idx = tuple(a.value for a in self.new_direction[1:1 + 2 * n:2])
        if not any(idx):
            ui.error(_('At least one index must be non-zero'), '')
            return
        if n == 4 and sum(idx) != 0:
            ui.error(
                _('Invalid hexagonal indices',
                  'The sum of the first three numbers must be zero'))
            return
        new = [idx, 5, 1.0]
        if self.method.value == 'wulff':
            new[1] = self.new_direction[-2].value
        else:
            new[2] = self.new_direction[-2].value
        self.direction_table.append(new)
        self.add_direction(*new)
        self.update()

    def row_delete(self, row):
        del self.direction_table[row]
        self.update_direction_table()

    def row_swap_next(self, row):
        dt = self.direction_table
        dt[row], dt[row + 1] = dt[row + 1], dt[row]
        self.update_direction_table()

    def update_gui_size(self, widget=None):
        'Update gui when the cluster size specification changes.'
        self.size_natoms.active = self.size_radio.value == 'natoms'
        self.size_diameter.active = self.size_radio.value == 'diameter'

    def update_size_natoms(self, widget=None):
        at_vol = self.get_atomic_volume()
        dia = 2.0 * (3 * self.size_natoms.value * at_vol /
                     (4 * np.pi))**(1 / 3)
        self.size_diameter.value = dia
        self.update()

    def update_size_diameter(self, widget=None, update=True):
        if self.size_diameter.active:
            at_vol = self.get_atomic_volume()
            n = round(np.pi / 6 * self.size_diameter.value**3 / at_vol)
            self.size_natoms.value = int(n)
            if update:
                self.update()

    def update(self, *args):
        if self.no_update:
            return
        self.element.Z  # Check
        if self.auto.value:
            self.makeatoms()
            if self.atoms is not None:
                self.gui.new_atoms(self.atoms)
        else:
            self.clearatoms()
        self.makeinfo()

    def set_structure_data(self, *args):
        'Called when the user presses [Get structure].'
        z = self.element.Z
        if z is None:
            return
        ref = ase.data.reference_states[z]
        if ref is None:
            structure = None
        else:
            structure = ref['symmetry']

        if ref is None or structure not in [s[0] for s in self.structure_data]:
            ui.error(
                _('Unsupported or unknown structure'),
                _('Element = {0}, structure = {1}').format(
                    self.element.symbol, structure))
            return

        self.structure.value = structure

        a = ref['a']
        self.a.value = a
        self.fourindex = self.needs_4index[structure]
        if self.fourindex:
            try:
                c = ref['c']
            except KeyError:
                c = ref['c/a'] * a
            self.c.value = c

    def makeatoms(self, *args):
        'Make the atoms according to the current specification.'
        symbol = self.element.symbol
        if symbol is None:
            self.clearatoms()
            self.makeinfo()
            return False
        struct = self.structure.value
        if self.needs_2lat[struct]:
            # a and c lattice constants
            lc = {'a': self.a.value, 'c': self.c.value}
            lc_str = str(lc)
        else:
            lc = self.a.value
            lc_str = '%.5f' % (lc, )
        if self.method.value == 'wulff':
            # Wulff construction
            surfaces = [x[0] for x in self.direction_table]
            surfaceenergies = [
                x[1].value for x in self.direction_table_rows.rows
            ]
            self.update_size_diameter(update=False)
            rounding = self.round_radio.value
            self.atoms = wulff_construction(symbol, surfaces, surfaceenergies,
                                            self.size_natoms.value,
                                            self.factory[struct], rounding, lc)
            python = py_template_wulff % {
                'element': symbol,
                'surfaces': str(surfaces),
                'energies': str(surfaceenergies),
                'latconst': lc_str,
                'natoms': self.size_natoms.value,
                'structure': struct,
                'rounding': rounding
            }
        else:
            # Layer-by-layer specification
            surfaces = [x[0] for x in self.direction_table]
            layers = [x[1].value for x in self.direction_table_rows.rows]
            self.atoms = self.factory[struct](symbol,
                                              copy(surfaces),
                                              layers,
                                              latticeconstant=lc)
            imp = self.import_names[struct]
            python = py_template_layers % {
                'import': imp,
                'element': symbol,
                'surfaces': str(surfaces),
                'layers': str(layers),
                'latconst': lc_str,
                'factory': imp.split()[-1]
            }
        self.makeinfo()

        return python

    def clearatoms(self):
        self.atoms = None

    def get_atomic_volume(self):
        s = self.structure.value
        a = self.a.value
        c = self.c.value
        if s == 'fcc':
            return a**3 / 4
        elif s == 'bcc':
            return a**3 / 2
        elif s == 'sc':
            return a**3
        elif s == 'hcp':
            return np.sqrt(3.0) / 2 * a * a * c / 2
        elif s == 'graphite':
            return np.sqrt(3.0) / 2 * a * a * c / 4

    def makeinfo(self):
        """Fill in information field about the atoms.

        Also turns the Wulff construction buttons [Larger] and
        [Smaller] on and off.
        """
        if self.atoms is None:
            self.info[1].text = '-'
            self.info[3].text = '-'
        else:
            at_vol = self.get_atomic_volume()
            dia = 2 * (3 * len(self.atoms) * at_vol / (4 * np.pi))**(1 / 3)
            self.info[1].text = str(len(self.atoms))
            self.info[3].text = u'{0:.1f} Å'.format(dia)

        if self.method.value == 'wulff':
            if self.smaller_button is not None:
                self.smaller_button.active = self.atoms is not None
                self.larger_button.active = self.atoms is not None

    def apply(self, callbackarg=None):
        self.makeatoms()
        if self.atoms is not None:
            self.gui.new_atoms(self.atoms)
            return True
        else:
            ui.error(
                _('No valid atoms.'),
                _('You have not (yet) specified a consistent set of '
                  'parameters.'))
            return False

    def ok(self):
        if self.apply():
            self.win.close()
Example #2
0
class SetupSurfaceSlab:
    '''Window for setting up a surface.'''
    def __init__(self, gui):
        self.element = Element('', self.apply)
        self.structure = ui.ComboBox(structures, structures,
                                     self.structure_changed)
        self.structure_warn = ui.Label('', 'red')
        self.orthogonal = ui.CheckButton('', True, self.make)
        self.lattice_a = ui.SpinBox(3.2, 0.0, 10.0, 0.001, self.make)
        self.retrieve = ui.Button(_('Get from database'),
                                  self.structure_changed)
        self.lattice_c = ui.SpinBox(None, 0.0, 10.0, 0.001, self.make)
        self.x = ui.SpinBox(1, 1, 30, 1, self.make)
        self.x_warn = ui.Label('', 'red')
        self.y = ui.SpinBox(1, 1, 30, 1, self.make)
        self.y_warn = ui.Label('', 'red')
        self.z = ui.SpinBox(1, 1, 30, 1, self.make)
        self.vacuum_check = ui.CheckButton('', False, self.vacuum_checked)
        self.vacuum = ui.SpinBox(5, 0, 40, 0.01, self.make)
        self.description = ui.Label('')

        win = self.win = ui.Window(_('Surface'))
        win.add(ui.Text(introtext))
        win.add(self.element)
        win.add([_('Structure:'), self.structure, self.structure_warn])
        win.add([_('Orthogonal cell:'), self.orthogonal])
        win.add([_('Lattice constant:')])
        win.add([_('\ta'), self.lattice_a, (u'Å'), self.retrieve])
        win.add([_('\tc'), self.lattice_c, (u'Å')])
        win.add([_('Size:')])
        win.add([_('\tx: '), self.x, _(' unit cells'), self.x_warn])
        win.add([_('\ty: '), self.y, _(' unit cells'), self.y_warn])
        win.add([_('\tz: '), self.z, _(' unit cells')])
        win.add([_('Vacuum: '), self.vacuum_check, self.vacuum, (u'Å')])
        win.add(self.description)
        # TRANSLATORS: This is a title of a window.
        win.add([
            pybutton(_('Creating a surface.'), self.make),
            ui.Button(_('Apply'), self.apply),
            ui.Button(_('OK'), self.ok)
        ])

        self.element.grab_focus()
        self.gui = gui
        self.atoms = None
        self.lattice_c.active = False
        self.vacuum.active = False
        self.structure_changed()

    def vacuum_checked(self, *args):
        if self.vacuum_check.var.get():
            self.vacuum.active = True
        else:
            self.vacuum.active = False
        self.make()

    def get_lattice(self, *args):
        if self.element.symbol is None:
            return
        ref = reference_states[self.element.Z]
        symmetry = "unknown"
        for struct in surfaces:
            if struct[0] == self.structure.value:
                symmetry = struct[1]
        if ref['symmetry'] != symmetry:
            # TRANSLATORS: E.g. "... assume fcc crystal structure for Au"
            self.structure_warn.text = (_('Error: Reference values assume {} '
                                          'crystal structure for {}!').format(
                                              ref['symmetry'],
                                              self.element.symbol))
        else:
            if symmetry == 'fcc' or symmetry == 'bcc' or symmetry == 'diamond':
                self.lattice_a.value = ref['a']
            elif symmetry == 'hcp':
                self.lattice_a.value = ref['a']
                self.lattice_c.value = ref['a'] * ref['c/a']
        self.make()

    def structure_changed(self, *args):
        for surface in surfaces:
            if surface[0] == self.structure.value:
                if surface[2] == 'ortho':
                    self.orthogonal.var.set(True)
                    self.orthogonal.check['state'] = ['disabled']
                elif surface[2] == 'non-ortho':
                    self.orthogonal.var.set(False)
                    self.orthogonal.check['state'] = ['disabled']
                else:
                    self.orthogonal.check['state'] = ['normal']

                if surface[1] == _('hcp'):
                    self.lattice_c.active = True
                    self.lattice_c.value = round(
                        self.lattice_a.value * ((8.0 / 3.0)**(0.5)), 3)
                else:
                    self.lattice_c.active = False
                    self.lattice_c.value = 'None'
        self.get_lattice()

    def make(self, *args):
        symbol = self.element.symbol
        self.atoms = None
        self.description.text = ''
        self.python = None
        self.x_warn.text = ''
        self.y_warn.text = ''
        if symbol is None:
            return

        x = self.x.value
        y = self.y.value
        z = self.z.value
        size = (x, y, z)
        a = self.lattice_a.value
        c = self.lattice_c.value
        vacuum = self.vacuum.value
        if not self.vacuum_check.var.get():
            vacuum = None
        ortho = self.orthogonal.var.get()

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

        struct = self.structure.value
        if struct == _('BCC(111)') and (not (y % 2 == 0) and ortho):
            self.y_warn.text = ortho_warn_even
            return
        if struct == _('BCC(110)') and (not (y % 2 == 0) and ortho):
            self.y_warn.text = ortho_warn_even
            return
        if struct == _('FCC(111)') and (not (y % 2 == 0) and ortho):
            self.y_warn.text = ortho_warn_even
            return
        if struct == _('FCC(211)') and (not (x % 3 == 0) and ortho):
            self.x_warn.text = _('Please enter a value divisible by 3'
                                 ' for orthogonal cell')
            return
        if struct == _('HCP(0001)') and (not (y % 2 == 0) and ortho):
            self.y_warn.text = ortho_warn_even
            return
        if struct == _('HCP(10-10)') and (not (y % 2 == 0) and ortho):
            self.y_warn.text = ortho_warn_even
            return

        for surface in surfaces:
            if surface[0] == struct:
                c_py = ""
                if surface[1] == _('hcp'):
                    self.atoms = surface[3](symbol, size, a, c, vacuum, ortho)
                    c_py = "{}, ".format(c)
                else:
                    self.atoms = surface[3](symbol, size, a, vacuum, ortho)

                if vacuum is not None:
                    vacuumtext = _(' Vacuum: {} Å.').format(vacuum)
                else:
                    vacuumtext = ''

                natoms = len(self.atoms)
                label = ngettext(
                    # TRANSLATORS: e.g. "Au fcc100 surface with 2 atoms."
                    # or "Au fcc100 surface with 2 atoms. Vacuum: 5 Å."
                    '{symbol} {surf} surface with one atom.{vacuum}',
                    '{symbol} {surf} surface with {natoms} atoms.{vacuum}',
                    natoms).format(symbol=symbol,
                                   surf=surface[3].__name__,
                                   natoms=natoms,
                                   vacuum=vacuumtext)

                self.description.text = label
                return py_template.format(func=surface[3].__name__,
                                          a=a,
                                          c=c_py,
                                          symbol=symbol,
                                          size=size,
                                          ortho=ortho,
                                          vacuum=vacuum)

    def apply(self, *args):
        self.make()
        if self.atoms is not None:
            self.gui.new_atoms(self.atoms)
            return True
        else:
            ui.error(
                _('No valid atoms.'),
                _('You have not (yet) specified a consistent '
                  'set of parameters.'))
            return False

    def ok(self, *args):
        if self.apply():
            self.win.close()