Introduction to atomman: Defining atomic systems
Lucas M. Hale, lucas.hale@nist.gov, Materials Science and Engineering Division, NIST.
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.