Introduction to atomman: Defining atomic systems

Lucas M. Hale, lucas.hale@nist.gov, Materials Science and Engineering Division, NIST.

Disclaimers

1. Introduction

This Notebook provides an introduction to creating and manipulating atomistic systems using atomman.

Library Imports

[1]:
# Standard Python libraries
import os
from copy import deepcopy
import datetime

# http://www.numpy.org/
import numpy as np

# https://github.com/usnistgov/atomman
import atomman as am
import atomman.unitconvert as uc

# Show atomman version
print('atomman version =', am.__version__)

# Show date of Notebook execution
print('Notebook executed on', datetime.date.today())
atomman version = 1.4.11
Notebook executed on 2024-04-29

2. Create a System

Atomic systems are represented with three Python objects: Box, Atoms, and System.

2.1. Box

The Box class defines a generic parallelepiped for the system’s boundaries.

See the 1.1. Box class Jupyter Notebook for more detailed information and options.

2.1.1. Create Box

Boxes can be defined using a variety of formats. Here, we’ll define a box using lattice parameters.

[2]:
# a = 4.05 angstroms (Al fcc lattice constant)
a = uc.set_in_units(4.05, 'angstrom')

# Create cubic box
box = am.Box.cubic(a=a)

2.1.2. Access Box properties

The string of Box shows four Cartesian vectors: the three box vectors (avect, bvect, and cvect) and the origin position.

[3]:
print(box)
avect =  [ 4.050,  0.000,  0.000]
bvect =  [ 0.000,  4.050,  0.000]
cvect =  [ 0.000,  0.000,  4.050]
origin = [ 0.000,  0.000,  0.000]

The parameters of the Box can also be retrieved in a variety of formats. Some examples are shown here.

[4]:
print('a =', box.a)
print('b =', box.b)
print('c =', box.c)
print('alpha =', box.alpha)
print('beta  =', box.beta)
print('gamma =', box.gamma)
a = 4.05
b = 4.05
c = 4.05
alpha = 90.0
beta  = 90.0
gamma = 90.0

2.2. Atoms

The Atoms class collects per-atom properties that can be freely assigned. The only limitations are that the number of atoms is constant, and that values for the per-atom properties must be given for all atoms.

See the 1.2. Atoms class Jupyter Notebook for more detailed information and options.

2.2.1. Create Atoms

When initializing an Atoms object, all keyword arguments (except natoms and prop) will be interpreted as property names. Values for each property must be given for each atom, or only one value given in which case all atoms will be assigned the same value.

By default, each Atoms instance has two per-atom properties:

  • atype is an integer atomic type. If not given, atype will be set to 1 for all atoms.

  • pos is the 3D vector position. If not given, pos will be set to [0,0,0] for all atoms.

[5]:
# Create Atoms for an fcc unit cell
atype = 1
pos = [[0.0, 0.0, 0.0],
       [0.5, 0.5, 0.0],
       [0.5, 0.0, 0.5],
       [0.0, 0.5, 0.5]]

atoms = am.Atoms(atype=atype, pos=pos)

2.2.2. Accessing Atoms properties

The string of Atoms shows id, atype, and pos for all atoms.

[6]:
print(atoms)
per-atom properties = ['atype', 'pos']
     id   atype  pos[0]  pos[1]  pos[2]
      0       1   0.000   0.000   0.000
      1       1   0.500   0.500   0.000
      2       1   0.500   0.000   0.500
      3       1   0.000   0.500   0.500

The per-atom properties can be accessed as attributes of the object.

[7]:
print('atoms.atype ->', atoms.atype)
print('atoms.pos[2] ->', atoms.pos[2])
atoms.atype -> [1 1 1 1]
atoms.pos[2] -> [0.5 0.  0.5]

The per-atom properties can also be converted into a flat pandas.DataFrame with the df() method.

[8]:
atoms.df()
[8]:
atype pos[0] pos[1] pos[2]
0 1 0.0 0.0 0.0
1 1 0.5 0.5 0.0
2 1 0.5 0.0 0.5
3 1 0.0 0.5 0.5

2.3. System

The System class gives a full representation of an atomic configuration by combining an atoms instance, a box instance, periodic boundary condition settings, and a list of element symbols.

See the 1.3. System class Jupyter Notebook for more detailed information and options.

2.3.1. Create system

A System is created by combining its components:

  • atoms is an Atoms object.

  • box is a Box object.

  • pbc is a list of three bools, where True indicates the box is periodic along the corresponding box vector.

  • symbols is a list of element model symbols for each unique atype value.

  • scale is a bool that indicates if atoms.pos are to be scaled relative to the box vectors.

[9]:
system = am.System(atoms=atoms, box=box, pbc=[True, False, True], symbols='Al', scale=True)
print(system)
avect =  [ 4.050,  0.000,  0.000]
bvect =  [ 0.000,  4.050,  0.000]
cvect =  [ 0.000,  0.000,  4.050]
origin = [ 0.000,  0.000,  0.000]
natoms = 4
natypes = 1
symbols = ('Al',)
pbc = [ True False  True]
per-atom properties = ['atype', 'pos']
     id   atype  pos[0]  pos[1]  pos[2]
      0       1   0.000   0.000   0.000
      1       1   2.025   2.025   0.000
      2       1   2.025   0.000   2.025
      3       1   0.000   2.025   2.025

2.3.2. View system

Preliminary feature added 1.4.10

You can view a system from within a Jupyter environment. This is useful for taking a quick peak at smaller structures to check that they are correct.

Caution: plotting methods are still preliminary and the method calls are likely to change!

[10]:
am.plot.py3Dmol.view_3d(system, width=400, height=400)

You appear to be running in JupyterLab (or JavaScript failed to load for some other reason). You need to install the 3dmol extension:
jupyter labextension install jupyterlab_3dmol

3. Load a System

Systems can also be created by loading in atomic configurations from either

  • files of many different formats,

  • representations of configurations used by other Python packages, or

  • records stored in the potentials database.

See the 1.4. Load and dump conversions Jupyter Notebook for more detailed information on all the different available styles and options.

3.1. Load from POSCAR

Create content in POSCAR format

[11]:
fcc_poscar = """POSCAR for fcc Al standard unit cell
1.0
4.05 0.00 0.00
0.00 4.05 0.00
0.00 0.00 4.05
Al
4
direct
0.00 0.00 0.00
0.50 0.50 0.00
0.50 0.00 0.50
0.00 0.50 0.50"""

Load content. The load() function parameters are:

  • style (str) indicates the format of the content being loaded.

  • content (any) the content to be loaded. For text formats, can be a str of the content, a file path or a file-like object.

  • **kwargs (any) any extra style-specific keyword arguments.

[12]:
new_system = am.load('poscar', fcc_poscar)
print(new_system)
avect =  [ 4.050,  0.000,  0.000]
bvect =  [ 0.000,  4.050,  0.000]
cvect =  [ 0.000,  0.000,  4.050]
origin = [ 0.000,  0.000,  0.000]
natoms = 4
natypes = 1
symbols = ('Al',)
pbc = [ True  True  True]
per-atom properties = ['atype', 'pos']
     id   atype  pos[0]  pos[1]  pos[2]
      0       1   0.000   0.000   0.000
      1       1   2.025   2.025   0.000
      2       1   2.025   0.000   2.025
      3       1   0.000   2.025   2.025

3.2. Load a database prototype

The potentials database stores structure information for many crystal prototypes, i.e. space group + wykoff sites without elemental information. Crystals can easily be created from this by loading the prototype and specifying the element(s) and the necessary lattice constants.

[13]:
potdb = am.library.Database()
[14]:
print(potdb.local_database)
database style local at /home/lmh1/library
[15]:
new_system = am.load('prototype', name='A1--Cu--fcc', a=4.05, symbols='Al')
print(new_system)
avect =  [ 4.050,  0.000,  0.000]
bvect =  [ 0.000,  4.050,  0.000]
cvect =  [ 0.000,  0.000,  4.050]
origin = [ 0.000,  0.000,  0.000]
natoms = 4
natypes = 1
symbols = ('Al',)
pbc = [ True  True  True]
per-atom properties = ['atype', 'pos']
     id   atype  pos[0]  pos[1]  pos[2]
      0       1   0.000   0.000   0.000
      1       1   0.000   2.025   2.025
      2       1   2.025   0.000   2.025
      3       1   2.025   2.025   0.000

3.3. Load a database crystal

The potentials database also contains relaxed crystal structures for the hosted interatomic potentials. If you know which potential you want to use, then you can load the appropriate relaxed structure.

[16]:
new_system = am.load('crystal', family='A1--Cu--fcc', potential_LAMMPS_id='2003--Zope-R-R--Al--LAMMPS--ipr1')
print(new_system)
avect =  [ 4.050,  0.000,  0.000]
bvect =  [ 0.000,  4.050,  0.000]
cvect =  [ 0.000,  0.000,  4.050]
origin = [ 0.000,  0.000,  0.000]
natoms = 4
natypes = 1
symbols = ('Al',)
pbc = [ True  True  True]
per-atom properties = ['atype', 'pos']
     id   atype  pos[0]  pos[1]  pos[2]
      0       1   0.000   0.000   0.000
      1       1   0.000   2.025   2.025
      2       1   2.025   0.000   2.025
      3       1   2.025   2.025   0.000

4. System Manipulations

The System class was defined to make it easy for users to manipulate systems and atomic properties. Additionally, there are a few System class methods that assist with some common manipulations.

See the 1.3. System class Jupyter Notebook for more detailed information and options.

4.1. System.rotate()

The rotate() method transforms the system such that the new box vectors correspond to three integer [uvw] crystal vectors with respect to the current box vectors. This ensures that atomic compatibilities across all periodic boundaries.

[17]:
# Rotate system to crystal vectors [110], [-110], [001]
uvws = [[ 1, 1, 0],
        [-1, 1, 0],
        [ 0, 0, 1]]
system = system.rotate(uvws)

# Show system is transformed and expanded
print(system)
avect =  [ 5.728,  0.000,  0.000]
bvect =  [ 0.000,  5.728,  0.000]
cvect =  [ 0.000,  0.000,  4.050]
origin = [ 0.000,  0.000,  0.000]
natoms = 8
natypes = 1
symbols = ('Al',)
pbc = [ True  True  True]
per-atom properties = ['atype', 'pos']
     id   atype  pos[0]  pos[1]  pos[2]
      0       1   5.728   2.864   4.050
      1       1   0.000   0.000   4.050
      2       1   2.864   5.728   4.050
      3       1   1.432   1.432   2.025
      4       1   1.432   4.296   2.025
      5       1   2.864   2.864   4.050
      6       1   4.296   1.432   2.025
      7       1   4.296   4.296   2.025

4.2. System.supersize()

A larger System (i.e. supercell) can be generated using the supersize() method

[18]:
# Make system a 2x2x2 supercell of itself
system = system.supersize(2, 2, 2)

print(system)
avect =  [11.455,  0.000,  0.000]
bvect =  [ 0.000, 11.455,  0.000]
cvect =  [ 0.000,  0.000,  8.100]
origin = [ 0.000,  0.000,  0.000]
natoms = 64
natypes = 1
symbols = ('Al',)
pbc = [ True  True  True]
per-atom properties = ['atype', 'pos']
     id   atype  pos[0]  pos[1]  pos[2]
      0       1   5.728   2.864   4.050
      1       1   0.000   0.000   4.050
      2       1   2.864   5.728   4.050
      3       1   1.432   1.432   2.025
      4       1   1.432   4.296   2.025
      5       1   2.864   2.864   4.050
      6       1   4.296   1.432   2.025
      7       1   4.296   4.296   2.025
      8       1  11.455   2.864   4.050
      9       1   5.728   0.000   4.050
     10       1   8.591   5.728   4.050
     11       1   7.159   1.432   2.025
     12       1   7.159   4.296   2.025
     13       1   8.591   2.864   4.050
     14       1  10.023   1.432   2.025
     15       1  10.023   4.296   2.025
     16       1   5.728   8.591   4.050
     17       1   0.000   5.728   4.050
     18       1   2.864  11.455   4.050
     19       1   1.432   7.159   2.025
     20       1   1.432  10.023   2.025
     21       1   2.864   8.591   4.050
     22       1   4.296   7.159   2.025
     23       1   4.296  10.023   2.025
     24       1  11.455   8.591   4.050
     25       1   5.728   5.728   4.050
     26       1   8.591  11.455   4.050
     27       1   7.159   7.159   2.025
     28       1   7.159  10.023   2.025
     29       1   8.591   8.591   4.050
     30       1  10.023   7.159   2.025
     31       1  10.023  10.023   2.025
     32       1   5.728   2.864   8.100
     33       1   0.000   0.000   8.100
     34       1   2.864   5.728   8.100
     35       1   1.432   1.432   6.075
     36       1   1.432   4.296   6.075
     37       1   2.864   2.864   8.100
     38       1   4.296   1.432   6.075
     39       1   4.296   4.296   6.075
     40       1  11.455   2.864   8.100
     41       1   5.728   0.000   8.100
     42       1   8.591   5.728   8.100
     43       1   7.159   1.432   6.075
     44       1   7.159   4.296   6.075
     45       1   8.591   2.864   8.100
     46       1  10.023   1.432   6.075
     47       1  10.023   4.296   6.075
     48       1   5.728   8.591   8.100
     49       1   0.000   5.728   8.100
     50       1   2.864  11.455   8.100
     51       1   1.432   7.159   6.075
     52       1   1.432  10.023   6.075
     53       1   2.864   8.591   8.100
     54       1   4.296   7.159   6.075
     55       1   4.296  10.023   6.075
     56       1  11.455   8.591   8.100
     57       1   5.728   5.728   8.100
     58       1   8.591  11.455   8.100
     59       1   7.159   7.159   6.075
     60       1   7.159  10.023   6.075
     61       1   8.591   8.591   8.100
     62       1  10.023   7.159   6.075
     63       1  10.023  10.023   6.075
[19]:
am.plot.py3Dmol.view_3d(system, width=400, height=400)

You appear to be running in JupyterLab (or JavaScript failed to load for some other reason). You need to install the 3dmol extension:
jupyter labextension install jupyterlab_3dmol

4.3. Other Methods

Other methods include:

  • wrap() wraps atoms around periodic boundaries and expands non-periodic boundaries to ensure all atom positions are within the box.

  • normalize() transforms the system such that box vectors and atomic positions are compatible with simulation codes, such as LAMMPS.