Building and Manipulating Atoms

Overview

Teaching: 25 min
Exercises: 25 min
Questions
  • How can I build molecules and bulk structures more efficiently?

  • How can I create supercells and point defects?

Objectives
  • Build a molecule using the built-in database

  • Build a crystal using built-in crystal structure types

  • Build (optimal) supercell expansions

  • Remove, add or swap atom(s) to create point defects

Code connection

In this episode we explore the ase.build module, which contains tools for building structures using parameters rather than detailed lists of positions.

A set of simple molecules are pre-defined in ASE

import ase.build
from ase.visualize import view

g2_n2 = ase.build.molecule('N2')
view(g2_n2, viewer='ngl')

image of N2

type(g2_n2)
ase.atoms.Atoms
view(ase.build.molecule('C60'), viewer='ngl')

image of buckyball

There are pre-defined lattice parameters and crystal types to create bulk systems

view(ase.build.bulk('Cu', cubic=True), viewer='ngl')

image of Cu

view(
    ase.build.bulk('ZnS',
                   crystalstructure='zincblende',
                   a=5.387,
                   cubic=True), 
    viewer='ngl'
)

image of ZnS structure

A compact notation can be used to create a supercell

si = ase.build.bulk('Si', cubic=True)
view(si, viewer='ngl')

image of Si unit cell

view(si * 4, viewer='ngl')

image of Si supercell

view(si * [2, 4, 1], viewer='ngl')

image of Si supercell

Cell parameters can be inspected using the Atoms.cell attribute

si_prime = ase.build.bulk('Si')
view(si_prime, viewer='ngl')

image of primitive cell of Si

si_prime.cell
Cell([[0.0, 2.715, 2.715], [2.715, 0.0, 2.715], [2.715, 2.715, 0.0]])

ASE can search for the matrix which gives the most cubic supercell

Academic background

This algorithm is described in more detail in the ASE docs, and was developed to support supercell doping calculations

optimal_array = ase.build.find_optimal_cell_shape(si_prime.cell, 4, 'sc', verbose=True)
target metric (h_target):
[[1. 0. 0.]
 [0. 1. 0.]
 [0. 0. 1.]]
normalization factor (Q): 0.184162
idealized transformation matrix:
[[-1.  1.  1.]
 [ 1. -1.  1.]
 [ 1.  1. -1.]]
closest integer transformation matrix (P_0):
[[-1  1  1]
 [ 1 -1  1]
 [ 1  1 -1]]
smallest score (|Q P h_p - h_target|_2): 0.000000
optimal transformation matrix (P_opt):
[[-1  1  1]
 [ 1 -1  1]
 [ 1  1 -1]]
supercell metric:
[[5.43 0.   0.  ]
 [0.   5.43 0.  ]
 [0.   0.   5.43]]
determinant of optimal transformation matrix: 4
cubic_Si_expansion = ase.build.make_supercell(si_prime, optimal_array)
view(cubic_Si_expansion, viewer='ngl')

image of unit cell of Si

cubic_Si_expansion.cell
Cell([5.43, 5.43, 5.43])
si = ase.build.bulk('Si', cubic=True)
si.cell
Cell([5.43, 5.43, 5.43])

An Atoms object can be treated as an array of Atom objects

crystal = ase.build.bulk('ZnS',
                   crystalstructure='zincblende',
                   a=5.387,
                   cubic=True)

for atom in crystal:
    print(atom.symbol, atom.position, atom.mass)
Zn [0. 0. 0.] 65.38
S [1.34675 1.34675 1.34675] 32.06
Zn [0.     2.6935 2.6935] 65.38
S [1.34675 4.04025 4.04025] 32.06
Zn [2.6935 0.     2.6935] 65.38
S [4.04025 1.34675 4.04025] 32.06
Zn [2.6935 2.6935 0.    ] 65.38
S [4.04025 4.04025 1.34675] 32.06
zinc_indices = [i for i, atom in enumerate(crystal) if atom.symbol == 'Zn']
zinc_sublattice = crystal[zinc_indices]
view(zinc_sublattice, viewer='ngl')

image of Zn sub lattice

type(zinc_sublattice)
ase.atoms.Atoms

Methods and operations can be used to create point defects

from ase import Atom
composite = zinc_sublattice.copy()
composite.append(Atom('He', position=(1.34675, 4.04025, 4.04025)))
view(composite, viewer='ngl')

image of ZnS with H interstitial

zinc_vacancy = crystal.copy()
del zinc_vacancy[0]
view(zinc_vacancy, viewer='ngl')

image of ZnS with a Zn vacancy

antisite = crystal.copy()
antisite.positions[[0, 1]] = antisite.positions[[1, 0]]
view(antisite, viewer='ngl')

image of ZnS with antisite disorder

Exercise: Distorted sphalerite

It is possible to combine entire Atoms with +. In this case, the first Atoms takes precedence to determine the cell parameters. Use this to create a distorted sphalerite cell, with the S sublattice translated along the x-coordinate relative to the Zn sublattice

Solution

sulfur_indices = [i for i, atom in enumerate(crystal) if atom.symbol == 'S']
sulfur_sublattice = crystal[sulfur_indices]
sulfur_sublattice.translate([.3, 0., 0.])
view(zinc_sublattice + sulfur_sublattice, viewer='ngl')

Exercise: Water animation

Create an animation of a water molecule being wrapped in a C60 cage (or something even cooler!)

Hints:

  • The GIF animation will need to be generated with a list of Atoms objects
  • Molecules can be combined with +
  • To get the wrapping effect we need to keep adding atoms that are near to the atoms already added
  • To avoid writing too much repetitive code, use Python’s looping tools

Solution

water = ase.build.molecule('H2O')
water.center()

bucky = ase.build.molecule('C60')
bucky.center()

start_atom = 36
distances = bucky.get_all_distances()[start_atom]
sorted_bucky_indices = sorted(enumerate(distances),
                              key = (lambda x: x[1]))
sorted_bucky_indices = [i for i, _ in sorted_bucky_indices]
sorted_bucky = bucky[sorted_bucky_indices]

frames = [water.copy()]

for i in range(len(sorted_bucky)):
    frames.append(water + sorted_bucky[:i + 1])

from ase.io.animation import write_gif
_ = write_gif('wrapped_molecule.gif', frames)

Key Points

  • A set of simple molecules are pre-defined in ASE

  • There are pre-defined lattice parameters and crystal types to create bulk systems

  • A compact notation can be used to create a supercell

  • Cell parameters can be inspected using the Atoms.cell attribute

  • ASE can search for the matrix which gives the most cubic supercell

  • An Atoms object can be treated as an array of Atom objects

  • Methods and operations can be used to create point defects