Introduction to atomman: Basic support and analysis tools

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

Disclaimers

1. Introduction

This Notebook outlines some of the other tools in atomman that provide basic support features and simple analysis of the atomistic systems.

Library Imports

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

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

# https://pandas.pydata.org/
import pandas as pd

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

Construct a demonstration 2x2x2 diamond cubic silicon system

[2]:
a = uc.set_in_units(5.431, 'angstrom')
ucell = am.load('prototype', 'A4--C--dc', a=a, symbols='Si')

system = ucell.supersize(2,2,2)

print(system.natoms)
64

2. Elastic constants

The full elastic constants tensor for a given crystal can be represented with the atomman.ElasticConstants class. The values in an ElasticConstants object can be set and retrieved in a variety of formats and transformed to other Cartesian coordinate systems.

See the 3.1. ElasticConstants class Jupyter Notebook for more details and a full description of all of the class methods.

[3]:
# Define an ElasticConstants object for diamond cubic silicon
# values taken from http://www.ioffe.ru/SVA/NSM/Semicond/Si/mechanic.html
C11 = uc.set_in_units(16.60 * 10**11, 'dyn/cm^2')
C12 = uc.set_in_units( 6.40 * 10**11, 'dyn/cm^2')
C44 = uc.set_in_units( 7.96 * 10**11, 'dyn/cm^2')

C = am.ElasticConstants(C11=C11, C12=C12, C44=C44)
[4]:
# Get 6x6 Cij Voigt representation of elastic constants in GPa
print('Cij (GPa) =')
print(uc.get_in_units(C.Cij, 'GPa'))
Cij (GPa) =
[[166.   64.   64.    0.    0.    0. ]
 [ 64.  166.   64.    0.    0.    0. ]
 [ 64.   64.  166.    0.    0.    0. ]
 [  0.    0.    0.   79.6   0.    0. ]
 [  0.    0.    0.    0.   79.6   0. ]
 [  0.    0.    0.    0.    0.   79.6]]

3. Relative distances between atoms

There are a few built-in tools for investigating the relative positions between atoms of the same and different systems.

3.1. System.dvect()

The System.dvect() method computes the shortest vector(s) between two points or list of points within the System taking into account the System’s periodic dimensions.

Parameters

  • pos_0 (numpy.ndarray or index) Absolute Cartesian vector position(s) to use as reference point(s). If the value can be used as an index, then self.atoms.pos[pos_0] is taken.

  • pos_1 (numpy.ndarray or index) Absolute Cartesian vector position(s) to find relative to pos_0. If the value can be used as an index, then self.atoms.pos[pos_1] is taken.

[5]:
# Calculate shortest vector between atoms 1 and 60
print(system.dvect(1, 60))
[ 4.07325  4.07325 -4.07325]

3.2. System.dmag()

The System dmag() method computes the magnitude of the shortest vector(s) between two points or list of points within the System taking into account the System’s periodic dimensions. This is identical to computing dvect above, then finding the magnitude of those vectors, but should be faster.

[6]:
# Calculate shortest distance between position [5., 5., 5.] and all atoms in system
dmags = system.dmag([5.0, 5.0, 5.0], range(system.natoms))
print(dmags)
[8.66025404 5.95297241 5.95297241 5.95297241 6.30856205 3.87088054
 3.87088054 3.87088054 7.08419092 6.33398788 6.33398788 3.25939281
 5.45266877 2.2175116  5.86626957 5.86626957 7.08419092 6.33398788
 3.25939281 6.33398788 5.45266877 5.86626957 2.2175116  5.86626957
 5.03701519 6.69334927 3.91218142 3.91218142 4.43455051 4.93424363
 4.93424363 7.33774633 7.08419092 3.25939281 6.33398788 6.33398788
 5.45266877 5.86626957 5.86626957 2.2175116  5.03701519 3.91218142
 6.69334927 3.91218142 4.43455051 4.93424363 7.33774633 4.93424363
 5.03701519 3.91218142 3.91218142 6.69334927 4.43455051 7.33774633
 4.93424363 4.93424363 0.7465139  4.4706471  4.4706471  4.4706471
 3.09820588 6.6163557  6.6163557  6.6163557 ]

3.3. displacement()

The atomman.displacement() function compares two systems with the same number of atoms and calculates the vector differences between all atoms with the same atomic id’s. The vectors returned are the shortest vectors after taking periodic boundaries in consideration, i.e. it uses dvect().

Parameters

  • system_0 (atomman.System) The initial system to calculate displacements from.

  • system_1 (atomman.System) The final system to calculate displacements to.

  • box_reference (str or None) Specifies which system’s boundary conditions to use.

    • ‘initial’ uses system_0’s box and pbc.

    • ‘final’ uses system_1’s box and pbc (Default).

    • None computes the straight difference between the positions without accounting for periodic boundaries.

[7]:
# Copy system and randomly displace atoms
system2 = deepcopy(system)
system2.atoms.pos += 3 * np.random.rand(system.natoms, 3)
system2.wrap()

# Show displacement between the two systems
print(am.displacement(system, system2))
[[1.99873321e+00 1.97898350e+00 2.77948386e+00]
 [1.90972476e+00 5.77147115e-01 2.55570199e+00]
 [1.58029646e+00 4.84773542e-04 7.36039618e-01]
 [7.95302419e-01 2.70387355e+00 1.39885788e+00]
 [2.94562565e+00 1.30116739e+00 2.56655853e+00]
 [2.09665097e+00 1.71789825e+00 2.78662765e+00]
 [2.36151843e+00 1.78235553e+00 6.98546182e-01]
 [2.53755912e+00 2.25709711e+00 2.92939053e+00]
 [5.67145253e-01 1.21639994e-01 1.78419551e+00]
 [1.50068234e+00 1.89098355e+00 2.98187915e+00]
 [2.22802317e+00 1.65158130e+00 2.51999543e+00]
 [1.32364938e+00 1.67047798e+00 6.18993231e-01]
 [7.14096872e-02 2.38261250e+00 5.03345805e-01]
 [1.20843369e+00 2.20662924e-01 1.49648073e+00]
 [1.63879986e+00 2.55225627e+00 3.41932853e-02]
 [2.37330304e+00 2.83899535e+00 1.47960165e+00]
 [2.59689604e+00 1.24117247e+00 3.53742081e-01]
 [2.34903493e+00 2.28917165e+00 5.14139099e-01]
 [1.04880124e+00 5.50959619e-03 1.52393079e+00]
 [2.84031852e+00 2.27313648e+00 1.00537794e+00]
 [2.06169040e+00 2.38547851e+00 1.15842202e+00]
 [1.91592906e+00 1.63365584e+00 1.38575059e+00]
 [1.50011379e+00 1.25333319e+00 2.52546652e-01]
 [1.24666348e+00 2.02047356e+00 9.63567008e-01]
 [9.03659624e-01 1.05819188e+00 2.46342761e+00]
 [6.90423162e-01 1.67144899e+00 1.54656630e+00]
 [1.16580952e+00 1.85150775e+00 1.58552636e-01]
 [1.02458137e+00 1.50905031e-01 6.81565393e-01]
 [2.24657698e+00 8.46435010e-01 2.98184750e-02]
 [4.44846705e-01 2.93111545e+00 6.68615946e-01]
 [1.58088692e+00 1.11089490e+00 1.25127449e-01]
 [2.64469401e+00 2.56430328e+00 3.12963273e-01]
 [8.19377012e-01 2.37057706e+00 1.51973519e+00]
 [4.44955488e-01 1.40937869e+00 1.38254015e+00]
 [1.15636167e-01 2.43469101e+00 1.17578765e-01]
 [2.13696686e+00 2.78639975e+00 1.80638729e+00]
 [6.47953792e-01 1.48171848e+00 4.13686755e-01]
 [7.47737525e-01 1.76757962e+00 3.93300215e-01]
 [2.87987561e+00 5.28062945e-01 2.05386128e+00]
 [1.27164768e+00 1.59292476e+00 4.43686364e-02]
 [2.03415621e-01 2.62232588e+00 2.42610479e+00]
 [1.32522337e+00 2.44744211e+00 1.09543001e+00]
 [2.26348913e+00 2.16825552e+00 5.40743833e-01]
 [6.22058932e-01 2.75364504e+00 3.87324391e-01]
 [1.45493914e+00 9.60899979e-01 2.36822341e+00]
 [2.19217088e+00 6.83546788e-01 2.36999581e+00]
 [1.34041340e+00 9.92652607e-01 1.99740562e+00]
 [2.50388855e+00 4.41028770e-01 8.87901250e-01]
 [1.87586344e+00 4.66748097e-01 1.96519442e+00]
 [1.75746183e+00 1.66774019e+00 1.70974022e+00]
 [2.22189288e+00 1.12859444e+00 2.08201276e+00]
 [1.23549346e+00 1.03486861e+00 1.00576957e+00]
 [2.03943614e+00 1.58683384e+00 2.77754679e+00]
 [1.09604889e+00 9.16170712e-01 2.90000024e+00]
 [1.87663454e+00 2.47017267e+00 2.33482038e+00]
 [7.15956215e-01 2.68605082e+00 1.64359450e+00]
 [1.37643382e+00 2.84110114e+00 2.35045575e+00]
 [2.02845094e+00 1.31646286e+00 2.06764359e+00]
 [2.89725261e+00 2.68678555e-01 2.39768789e-01]
 [2.47529750e+00 6.40922121e-01 1.47957635e+00]
 [2.38145834e+00 2.89932521e+00 2.20350347e+00]
 [1.09188850e+00 2.37434464e+00 1.46652077e+00]
 [1.11017249e+00 1.96497657e+00 2.10849494e+00]
 [5.30650376e-02 2.52180303e-01 2.89517774e+00]]

3.4. System.neighborlist()

A list of neighbor atoms within a cutoff can be constructed using the System.neighborlist() method. The list of neighbors is returned as an atomman.NeighborList object.

See the 3.2. NeighborList class Jupyter Notebook for more details on how the list is calculated and can be used.

Parameters

  • cutoff (float, optional) Radial cutoff distance for identifying neighbors. Must be given if model is not given.

  • model (str or file-like object, optional) Gives the file path or content to load. If given, no other parameters are allowed.

  • initialsize (int, optional) The number of neighbor positions to initially assign to each atom. Default value is 20.

  • deltasize (int, optional) Specifies the number of extra neighbor positions to allow each atom when the number of neighbors exceeds the underlying array size. Default value is 10.

Returns

  • (atomman.NeighborList) The compiled list of neighbors.

[8]:
# Identify neighbors within 3 angstroms
neighbors = system.neighborlist(cutoff=3)

The coordinataion numbers for the atoms can be retrieved with coord.

[9]:
# Show average atomic coordination
print('Average coordination =', neighbors.coord.mean())
Average coordination = 4.0

Index selection on the NeighborList object will return the indices of the neighbor atoms.

[10]:
# List neighbor atoms of atom 6
print('Neighbors of atom 6 =', neighbors[6])
Neighbors of atom 6 = [ 2 11 33 40]

The neighbor indices can then be used to filter other properties to only focus on an atom’s neighbors.

[11]:
# List the dmag values between atom 9 and its neighbors
print(system.dmag(9, neighbors[9]))
[2.35169198 2.35169198 2.35169198 2.35169198]

4. Region selectors

Added version 1.3.0

A number of geometric shape definitions are available in the atomman.region submodule to help identify regions in space above/below planes or inside/outside of regions. These are useful for constructing systems by slicing away atoms to create nanostructures, or for performing analysis on only select regions.

See the 3.3. Region selectors Jupyter Notebook for more details and a list of all available shapes.

[12]:
# Define a plane normal to the y axis and positioned halfway across system
plane = am.region.Plane([0,1,0], system.box.bvect / 2)

# Count number of atoms in system, and above/below plane
print(f'{system.natoms} atoms in system')

abovecount = np.sum(plane.above(system.atoms.pos))
print(f'{abovecount} atoms above plane')

belowcount = np.sum(plane.below(system.atoms.pos))
print(f'{belowcount} atoms below plane')

# Define a sphere centered at [0,0,0] with radius = 6
sphere = am.region.Sphere([0,0,0], 6)

# Count atoms inside sphere
insidecount = np.sum(sphere.inside(system.atoms.pos))
print(f'{insidecount} atoms inside sphere')
64 atoms in system
24 atoms above plane
40 atoms below plane
11 atoms inside sphere

5. Basic tools

This lists some of the other basic tools and features in atomman.

5.1. Atomic information

  • atomman.tools.atomic_number() returns the atomic number associated with an element’s atomic symbol.

  • atomman.tools.atomic_symbol() returns the elemental symbol associated with an given atomic number.

  • atomman.tools.atomic_mass() returns the atomic mass of an element or isotope. The atom can be identified with atomic number or atomic/isotope symbol.

[13]:
# Get atomic number for an atomic symbol
num = am.tools.atomic_number('Fe')
print(num)

# Get atomic symbol for an atomic number
symbol = am.tools.atomic_symbol(num)
print(symbol)

# Get atomic mass for an atomic symbol
mass = am.tools.atomic_mass(symbol)
print(mass)

# Get atomic mass for an atomic number
mass = am.tools.atomic_mass(num)
print(mass)
26
Fe
55.845
55.845
[14]:
# Get atomic mass for an isotope
mass = am.tools.atomic_mass('Al-26')
print(mass)
25.986891904

5.2. axes_check()

The axes_check() function is useful when working in Cartesian systems. Given a (3,3) array representing three 3D Cartesian vectors:

  • The three vectors are checked that they are orthogonal and right-handed.

  • The corresponding array of unit vectors are returned. This can then be used for crystal transformations.

[15]:
axes = [[-1, 0, 1],
        [ 1, 0, 1],
        [ 0, 1, 0]]
print(am.tools.axes_check(axes))
[[-0.70710678  0.          0.70710678]
 [ 0.70710678  0.          0.70710678]
 [ 0.          1.          0.        ]]

5.3. filltemplate()

The filltemplate() function takes a template and fills in values for delimited template variables.

[16]:
madlibs = "My friend <name> really likes to use templates to <verb>, says that they are <adjective>!"
s_delimiter = '<'
e_delimiter = '>'

terms = {}
terms['name'] = 'Charlie'
terms['verb'] = 'program'
terms['adjective'] = 'delicious'

print(am.tools.filltemplate(madlibs, terms, s_delimiter, e_delimiter))
My friend Charlie really likes to use templates to program, says that they are delicious!

5.4. indexstr()

Iterates through all indicies of an array with a given shape, returning both the numeric index and a string representation.

[17]:
for index, istr in am.tools.indexstr((3,2)):
    print('index ->', repr(index), ', istr ->', repr(istr))
index -> (0, 0) , istr -> '[0][0]'
index -> (0, 1) , istr -> '[0][1]'
index -> (1, 0) , istr -> '[1][0]'
index -> (1, 1) , istr -> '[1][1]'
index -> (2, 0) , istr -> '[2][0]'
index -> (2, 1) , istr -> '[2][1]'

5.5. uber_open_rmode

uber_open_rmode is a context manager that allows for similar reading of content from a file or from a string variable. It equivalently handles:

  • str path name to a file

  • str content

  • open file-like object

[18]:
# Define str and save to file
text = 'Here I am, read me!'
fname = 'text.txt'
with open(fname, 'w') as f:
    f.write(text)

# Use uber_open_rmode on text
with am.tools.uber_open_rmode(text) as f:
    print(f.read())

# Use uber_open_rmode on file path
with am.tools.uber_open_rmode(fname) as f:
    print(f.read())

# Use uber_open_rmode on file-like object
with open(fname, 'rb') as fobject:
    with am.tools.uber_open_rmode(fobject) as f:
        print(f.read())
b'Here I am, read me!'
b'Here I am, read me!'
b'Here I am, read me!'

5.6. vect_angle()

The vect_angle() function returns the angle between two vectors.

[19]:
vect1 = 2*np.random.rand(3)-1
vect2 = 2*np.random.rand(3)-1

print('Angle between', vect1, 'and', vect2, '=')
print(am.tools.vect_angle(vect1, vect2), 'degrees')
print(am.tools.vect_angle(vect1, vect2, 'radian'), 'radians')
Angle between [0.4216233  0.71458726 0.9039527 ] and [-0.17034854  0.29137372 -0.94224125] =
125.62681883431212 degrees
2.1926016174651712 radians

5.7. duplicates_allclose()

Determine duplicates in dataframe based on tolerances. The implementation first uses pandas.DataFrame.duplicated on the dcols argument with keep=False to keep all duplicates. The duplicate sub-dataframe is then sorted on both dcols and fcols. A diff between each row is then done on the sorted duplicates dataframe. The float values are then checked for their tolerances.

Note: False duplicates may be identified if tolerance ranges overlap. Consider dataframe with rows 1,2,3. If row 2 matches row 1 within the tolerances, and row 3 matches row 2 within the tolerances, both rows 2 and 3 will be labeled as tolerances even if row 3 does not match row 1 within the tolerances.

Parameters - dataframe (pandas.DataFrame) The dataframe to search for duplicates - dcols (list) The column names that are tested for exact duplicates. - fcols (dict) The column names (keys) that are tested using absolute tolerances (values).

Returns - (list of bool of length nrows) False for first occurrence of checked values, True for subsequent duplicates.

[20]:
# Generate test DataFrame
df = pd.DataFrame({'A':[1.00001, 1.00002, 3.0000, 1.000000], 'B':['Same', 'Diff', 'Same', 'Same']})
df
[20]:
A B
0 1.00001 Same
1 1.00002 Diff
2 3.00000 Same
3 1.00000 Same
[21]:
# Show unique values
df[~am.tools.duplicates_allclose(df, dcols=['B'], fcols={'A':1e-4})]
[21]:
A B
1 1.00002 Diff
2 3.00000 Same
3 1.00000 Same

5.8. Miller index conversions

  • atomman.tools.miller.vector3to4(indices) converts vectors from three-term Miller indices to four-term Miller-Bravais indices for hexagonal systems. Updated version 1.2.6: now returns vectors with magnitudes consistent withe given vectors rather than rescaling to the smallest integer representations.

  • atomman.tools.miller.vector4to3(indices) converts vectors from four-term Miller-Bravais indices to three-term Miller indices. Updated version 1.2.6: now returns vectors with magnitudes consistent withe given vectors rather than rescaling to the smallest integer representations.

  • atomman.tools.miller.plane3to4(indices) converts planes from three-term Miller indices to four-term Miller-Bravais indices for hexagonal systems. Added version 1.2.8

  • atomman.tools.miller.plane4to3(indices) converts planes from four-term Miller-Bravais indices to three-term Miller indices. Added version 1.2.8

  • atomman.tools.miller.vector_crystal_to_cartesian(indices, box) converts Miller and Miller-Bravais indices to Cartesian vectors based on a supplied box. Updated version 1.2.6 renamed from vectortocartesian for consistency.

  • atomman.tools.miller.plane_crystal_to_cartesian(indices, box) converts Miller and Miller-Bravais plane indices to Cartesian normal vectors based on a supplied box. The method uses the definition of the crystal planes to identify two in-plane crystal vectors, converts them to Cartesian, and obtains the plane normal as the cross product of the two vectors. Added version 1.3.2

  • atomman.tools.miller.vector_primitive_to_conventional(indices, setting) converts vectors relative to a primitive unit cell to a conventional unit cell in the given setting (p, a, b, c, i, f, t1 or t2). Added version 1.2.6 Updated version 1.4.10 to include t1 and t2 options.

  • atomman.tools.miller.vector_conventional_to_primitive(indices, setting) converts vectors relative to a conventional unit cell in the given setting (p, a, b, c, i, f, t1 or t2) to a primitive unit cell. Added version 1.2.6 Updated version 1.4.10 to include t1 and t2 options.

  • atomman.tools.miller.fromstring(str) reads a string representation of a Miller(-Bravais) vector/plane and converts it into a numpy array. Added version 1.4.10

[22]:
# Test single vector conversions
print(am.tools.miller.vector3to4(np.array([3,3,3])))
print(am.tools.miller.vector4to3(np.array([1,1,-2,0])))
[ 1.  1. -2.  3.]
[3. 3. 0.]
[23]:
# Generate random uvw crystal indices
indices = np.random.randint(-5,6, (3,3))
print(indices)
print()

# Convert to hexagonal uvtw's
indices = am.tools.miller.vector3to4(indices)
print(indices)
print()

# Convert back to uvw's and see that values are recovered
indices = am.tools.miller.vector4to3(indices)
print(indices)
[[-4 -2  0]
 [ 1 -5  4]
 [ 5 -1  5]]

[[-2.          0.          2.          0.        ]
 [ 2.33333333 -3.66666667  1.33333333  4.        ]
 [ 3.66666667 -2.33333333 -1.33333333  5.        ]]

[[-4. -2.  0.]
 [ 1. -5.  4.]
 [ 5. -1.  5.]]
[24]:
# Test single plane conversions
print(am.tools.miller.plane3to4(np.array([3,3,3])))
print(am.tools.miller.plane4to3(np.array([1,1,-2,0])))
[ 3.  3. -6.  3.]
[1. 1. 0.]
[25]:
# Generate random hkl crystal indices
indices = np.random.randint(-5,6, (3,3))
print(indices)
print()

# Convert to hexagonal hkil's
indices = am.tools.miller.plane3to4(indices)
print(indices)
print()

# Convert back to hkl's and see that values are recovered
indices = am.tools.miller.plane4to3(indices)
print(indices)
[[-3  4  2]
 [-2  2 -5]
 [ 2 -3  3]]

[[-3.  4. -1.  2.]
 [-2.  2. -0. -5.]
 [ 2. -3.  1.  3.]]

[[-3.  4.  2.]
 [-2.  2. -5.]
 [ 2. -3.  3.]]
[26]:
# Define a hexagonal box
a = uc.set_in_units(2.51, 'angstrom')
c = uc.set_in_units(4.07, 'angstrom')
box = am.Box(a=a, b=a, c=c, gamma=120)

# Pass Miller indices
indices = [[1,0,0],
           [0,1,0],
           [0,0,1]]
print(am.tools.miller.vector_crystal_to_cartesian(indices, box))
print()

# Pass equivalent Miller-Bravais indices
indices = [[ 2/3,-1/3,-1/3, 0],
           [-1/3, 2/3,-1/3, 0],
           [   0,   0,   0, 1]]
print(am.tools.miller.vector_crystal_to_cartesian(indices, box))
[[ 2.51        0.          0.        ]
 [-1.255       2.17372376  0.        ]
 [ 0.          0.          4.07      ]]

[[ 2.51        0.          0.        ]
 [-1.255       2.17372376  0.        ]
 [ 0.          0.          4.07      ]]
[27]:
# Define a hexagonal box
a = uc.set_in_units(2.51, 'angstrom')
c = uc.set_in_units(4.07, 'angstrom')
box = am.Box(a=a, b=a, c=c, gamma=120)

# Pass Miller plane indices
indices = [ 0, 0, 1]
print(am.tools.miller.plane_crystal_to_cartesian(indices, box))
indices = [ 1, 0, 0]
print(am.tools.miller.plane_crystal_to_cartesian(indices, box))
print()

# Pass equivalent Miller-Bravais indices
indices = [ 0, 0, 0, 1]
print(am.tools.miller.plane_crystal_to_cartesian(indices, box))
indices = [ 1, 0,-1, 0]
print(am.tools.miller.plane_crystal_to_cartesian(indices, box))
[ 0. -0.  1.]
[ 0.8660254  0.5       -0.       ]

[ 0. -0.  1.]
[ 0.8660254  0.5       -0.       ]
[28]:
# Define a primitive bcc unit cell box
a = uc.set_in_units(2.86, 'angstrom')
p_box = am.Box.trigonal(a * 3**0.5 / 2, alpha=109.466666667)
p_ucell = am.System(box=p_box)
print(p_ucell)
avect =  [ 2.477,  0.000,  0.000]
bvect =  [-0.825,  2.335,  0.000]
cvect =  [-0.825, -1.167,  2.023]
origin = [ 0.000,  0.000,  0.000]
natoms = 1
natypes = 1
symbols = (None,)
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
[29]:
# Convert conventional box vectors to primitive vectors
a_uvw = am.tools.miller.vector_conventional_to_primitive([1, 0, 0], setting='i')
b_uvw = am.tools.miller.vector_conventional_to_primitive([0, 1, 0], setting='i')
c_uvw = am.tools.miller.vector_conventional_to_primitive([0, 0, 1], setting='i')
p_uvws = np.array([a_uvw, b_uvw, c_uvw])
print('primitive uvws:')
print(p_uvws)

# Convert back to conventional just for consistency
print('conventional uvws:')
print(am.tools.miller.vector_primitive_to_conventional(p_uvws, setting='i'))
primitive uvws:
[[ 0. -1. -1.]
 [ 1.  1.  0.]
 [ 1.  0.  1.]]
conventional uvws:
[[1. 0. 0.]
 [0. 1. 0.]
 [0. 0. 1.]]
[30]:
# rotate system using p_uvws to get conventional unit cell
c_ucell = p_ucell.rotate(p_uvws)
print(c_ucell)
avect =  [ 2.860,  0.000,  0.000]
bvect =  [-0.000,  2.860,  0.000]
cvect =  [-0.000,  0.000,  2.860]
origin = [ 0.000,  0.000,  0.000]
natoms = 2
natypes = 1
symbols = (None,)
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   1.430   1.430   1.430
[39]:
# Read in string representations of the Miller(-Bravais) vectors and planes
miller_strings = [
    '1/2 [1 1 0]',
    '1/3 <1 1 -2 0>',
    '{1 1 1}',
    '(0 0 0 1)',
    '1 4 1'
]
for miller_str in miller_strings:
    print(f'{repr(miller_str):16} -> {repr(am.tools.miller.fromstring(miller_str))}')
'1/2 [1 1 0]'    -> array([0.5, 0.5, 0. ])
'1/3 <1 1 -2 0>' -> array([ 0.33333333,  0.33333333, -0.66666667,  0.        ])
'{1 1 1}'        -> array([1., 1., 1.])
'(0 0 0 1)'      -> array([0., 0., 0., 1.])
'1 4 1'          -> array([1., 4., 1.])

5.9. Crystal lattice identification

Note version 1.4.4 These have been added as methods of the Box class. These stand-alone functions are likely to be depreciated in the next major atomman version.

There are also a few tests for identifying if a supplied box is consistent with a standard representation of a crystal family unit cell.

  • atomman.tools.identifyfamily(box) returns str crystal family if box corresponds to a standard crystal representation. Otherwise, returns None.

  • atomman.tools.iscubic(box)) returns bool indicating if box is a standard cubic box.

  • atomman.tools.ishexagonal(box)) returns bool indicating if box is a standard hexagonal box.

  • atomman.tools.istetragonal(box)) returns bool indicating if box is a standard tetragonal box.

  • atomman.tools.isrhombohedral(box)) returns bool indicating if box is a standard rhombohedral box.

  • atomman.tools.isorthorhombic(box)) returns bool indicating if box is a standard orthorhombic box.

  • atomman.tools.ismonoclinic(box)) returns bool indicating if box is a standard monoclinic box.

  • atomman.tools.istriclinic(box)) returns bool indicating if box is a standard triclinic box.

All of these functions use the following standard representation criteria:

  • cubic:

    • \(a = b = c\)

    • \(\alpha = \beta = \gamma = 90\)

  • hexagonal:

    • \(a = b \ne c\)

    • \(\alpha = \beta = 90\)

    • \(\gamma = 120\)

  • tetragonal:

    • \(a = b \ne c\)

    • \(\alpha = \beta = \gamma = 90\)

  • rhombohedral:

    • \(a = b = c\)

    • \(\alpha = \beta = \gamma \ne 90\)

  • orthorhombic:

    • \(a \ne b \ne c\)

    • \(\alpha = \beta = \gamma = 90\)

  • monoclinic:

    • \(a \ne b \ne c\)

    • \(\alpha = \gamma = 90\)

    • \(\beta \ne 90\)

  • triclinic:

    • \(a \ne b \ne c\)

    • \(\alpha \ne \beta \ne \gamma\)

[31]:
# Define an orthogonal box
a = uc.set_in_units(2.51, 'angstrom')
b = uc.set_in_units(3.13, 'angstrom')
c = uc.set_in_units(4.07, 'angstrom')
box = am.Box(a=a, b=b, c=c)

print('identifyfamily =', am.tools.identifyfamily(box))
print('iscubic =       ', am.tools.iscubic(box))
print('ishexagonal =   ', am.tools.ishexagonal(box))
print('istetragonal =  ', am.tools.istetragonal(box))
print('isrhombohedral =', am.tools.isrhombohedral(box))
print('isorthorhombic =', am.tools.isorthorhombic(box))
print('ismonoclinic =  ', am.tools.ismonoclinic(box))
print('istriclinic =   ', am.tools.istriclinic(box))
identifyfamily = orthorhombic
iscubic =        False
ishexagonal =    False
istetragonal =   False
isrhombohedral = False
isorthorhombic = True
ismonoclinic =   False
istriclinic =    False
[32]:
# Define a non-standard tetragonal box with a=c!=b
box = am.Box(a=a, b=b, c=a)
print('identifyfamily =', am.tools.identifyfamily(box))
identifyfamily = None

5.10. compositionstr()

Added version 1.2.7

Takes a list of symbols and the counts for each and returns a reduced composition string. Used by System.composition.

[33]:
symbols = ['Si', 'Al', 'Si']
counts = [500, 1000, 2000]
print('Composition =', am.tools.compositionstr(symbols, counts))
Composition = Al2Si5

File Cleanup

[34]:
os.remove('text.txt')