Introduction to atomman: ase and phonopy conversions

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

Disclaimers

1. Introduction

The Atomic Simulation Environment, ase Python package, provides an alternative representation of atomic configurations in Python. It has many built-in tools for analyzing crystal structures as well as methods designed to integrate simulators into their environment. Atomic configurations are represented in ase by the ase.Atoms class.

The phonopy Python package is a common tool used to evaluate phonons for a given atomic configuration. Atomic configurations are represented in phonopy by the phonopy.Atoms class, which is based on the ase.Atoms class.

Atomman provides built-in conversions between these two packages to allow users the opportunity to take advantage of the unique features and calculation tools provided by the different packages. As the Atoms classes for the two external packages are comparable, the ‘ase_Atoms’ and ‘phonopy_Atoms’ conversion styles in atomman only differ by which package’s Atoms class is returned.

NOTE: Requires that the respective external package (ase or phonopy) be installed.

Library Imports

[1]:
# Standard Python libraries
import datetime

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

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.10
Notebook executed on 2023-07-28

Generate test system information (CsCl)

[2]:
# Generate box
alat = uc.set_in_units(3.2, 'angstrom')
box = am.Box(a=alat, b=alat, c=alat)

# Generate atoms with atype, pos, charge, and stress properties
atype = [1, 2]
pos = [[0,0,0], [0.5, 0.5, 0.5]]
charge = uc.set_in_units([1, -1], 'e')
stress = uc.set_in_units(np.zeros((2, 3, 3)), 'MPa')
atoms = am.Atoms(pos=pos, atype=atype, charge=charge, stress=stress)

# Build system from box and atoms, and scale atoms
system = am.System(atoms=atoms, box=box, scale=True, symbols=['Cs', 'Cl'])

# Print system information
print(system)
system.atoms_df()
avect =  [ 3.200,  0.000,  0.000]
bvect =  [ 0.000,  3.200,  0.000]
cvect =  [ 0.000,  0.000,  3.200]
origin = [ 0.000,  0.000,  0.000]
natoms = 2
natypes = 2
symbols = ('Cs', 'Cl')
pbc = [ True  True  True]
per-atom properties = ['atype', 'pos', 'charge', 'stress']
     id   atype  pos[0]  pos[1]  pos[2]
      0       1   0.000   0.000   0.000
      1       2   1.600   1.600   1.600
[2]:
atype pos[0] pos[1] pos[2] charge stress[0][0] stress[0][1] stress[0][2] stress[1][0] stress[1][1] stress[1][2] stress[2][0] stress[2][1] stress[2][2]
0 1 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
1 2 1.6 1.6 1.6 -1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0

2. System.dump(‘ase_Atoms’) and System.dump(‘phonopy_Atoms’)

Parameters

  • symbols (tuple, optional) List of the element symbols that correspond to the atom types. If not given, will use system.symbols if set, otherwise no element content will be included.

  • return_prop (bool, optional) Indicates if the extra per-atom properties are to be returned in a dictionary. Default value is False.

Returns

  • atoms/atoms (ase.Atoms/phonopy.Atoms) The external package’s representation of a collection of atoms.

  • prop (dict) Dictionary containing any extra per-atom properties to include. Returned if return_prop is True.

Convert the atomman.System to an ase.Atoms object

[3]:
aseatoms, prop = system.dump('ase_Atoms', return_prop=True)
print(type(aseatoms))
print(aseatoms)
<class 'ase.atoms.Atoms'>
Atoms(symbols='CsCl', pbc=True, cell=[3.2, 3.2, 3.2])

As the ase.Atoms object does not allow user-defined per-atom properties, the extra properties can be returned as a dictionary by setting return_prop = True

[4]:
print(prop)
{'charge': array([ 1., -1.]), 'stress': array([[[0., 0., 0.],
        [0., 0., 0.],
        [0., 0., 0.]],

       [[0., 0., 0.],
        [0., 0., 0.],
        [0., 0., 0.]]])}

System.dump(‘phonopy_Atoms’) behaves exactly the same except returns a phonopy.Atoms object instead.

[5]:
phonopyatoms, prop = system.dump('phonopy_Atoms', return_prop=True)
print(type(phonopyatoms))
print(phonopyatoms)
<class 'phonopy.structure.atoms.PhonopyAtoms'>
lattice:
- [     3.200000000000000,     0.000000000000000,     0.000000000000000 ] # a
- [     0.000000000000000,     3.200000000000000,     0.000000000000000 ] # b
- [     0.000000000000000,     0.000000000000000,     3.200000000000000 ] # c
points:
- symbol: Cs # 1
  coordinates: [  0.000000000000000,  0.000000000000000,  0.000000000000000 ]
  mass: 132.905452
- symbol: Cl # 2
  coordinates: [  0.500000000000000,  0.500000000000000,  0.500000000000000 ]
  mass: 35.453000

3. atomman.load(‘ase_Atoms’) and atomman.load(‘phonopy_Atoms’)

Parameters

  • atoms (ase.Atoms or phonopy.Atoms) The external package’s representation of a collection of atoms.

  • symbols (tuple, optional) Allows the list of element symbols to be assigned during loading. Useful if the symbols for the model differ from the standard element tags.

  • prop (dict, optional) Dictionary containing any extra per-atom properties to include.

Returns

  • system (atomman.System) An atomman representation of a system.

Load the ase.Atoms object along with the property dictionary.

[6]:
system = am.load('ase_Atoms', aseatoms, prop=prop)

print(system)
system.atoms_df()
avect =  [ 3.200,  0.000,  0.000]
bvect =  [ 0.000,  3.200,  0.000]
cvect =  [ 0.000,  0.000,  3.200]
origin = [ 0.000,  0.000,  0.000]
natoms = 2
natypes = 2
symbols = ('Cl', 'Cs')
pbc = [ True  True  True]
per-atom properties = ['atype', 'pos', 'charge', 'stress']
     id   atype  pos[0]  pos[1]  pos[2]
      0       2   0.000   0.000   0.000
      1       1   1.600   1.600   1.600
[6]:
atype pos[0] pos[1] pos[2] charge stress[0][0] stress[0][1] stress[0][2] stress[1][0] stress[1][1] stress[1][2] stress[2][0] stress[2][1] stress[2][2]
0 2 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
1 1 1.6 1.6 1.6 -1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0

Load the phonopy.Atoms object along with the property dictionary.

[7]:
system = am.load('phonopy_Atoms', phonopyatoms, prop=prop)

print(system)
system.atoms_df()
avect =  [ 3.200,  0.000,  0.000]
bvect =  [ 0.000,  3.200,  0.000]
cvect =  [ 0.000,  0.000,  3.200]
origin = [ 0.000,  0.000,  0.000]
natoms = 2
natypes = 2
symbols = ('Cl', 'Cs')
pbc = [ True  True  True]
per-atom properties = ['atype', 'pos', 'charge', 'stress']
     id   atype  pos[0]  pos[1]  pos[2]
      0       2   0.000   0.000   0.000
      1       1   1.600   1.600   1.600
[7]:
atype pos[0] pos[1] pos[2] charge stress[0][0] stress[0][1] stress[0][2] stress[1][0] stress[1][1] stress[1][2] stress[2][0] stress[2][1] stress[2][2]
0 2 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
1 1 1.6 1.6 1.6 -1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0