Lattice Tutorial

The following notebook provides a thorough walkthrough to using the Lattice class to build up crystal systems.

Lattice Functionality

  • Variable-dimension crystal structures
    • Lattice can support the dimensionality of mBuild, which means that the systems can be in 1D, 2D, or 3D. Replace the necessary vector components with 0 to emulate the dimensionality of interest.
  • Multicomponent crystals
    • Lattice can support an indefinite amount of lattice points in its data structure.
    • The 'repeat' cell can be as large as the user defines useful.
    • The components that occupy the lattice points are mbuild.Compound's.
      • This allows the user to build any system since compounds are only a representation of matter in this design.
      • Molecular crystals, coarse grained, atomic, even alloy crystal structures are all supported
  • Triclinic Lattices

    • With full support for triclinic lattices, any crystal structure can technically be developed.
    • Either the user can provide the lattice parameters, or if they know the vectors that span the unit cell, that can also be provided.
  • IN PROGRESS Generation of lattice structure from crystallographic index file (CIF)

    • Although this feature is not currently implemented, this functionality can be extended.
  • IN PROGRESS Template recipes to generate common crystal structures (FCC, BCC, HEX, etc)
    • This is currently being developed and will be released relatively shortly
    • To generate these structures currently, the user needs to know the lattice parameters or lattice vectors that define these units.

Lattice Data Structure Introduction

Below we will explore the relevant data structures that are attributes of the Lattice class. This information will be essential to build desired crystal structures.

To begin, we will call the python help() method to observe the parameters and attributes of the Lattice class.


In [1]:
import mbuild
help(mbuild.Lattice)


Help on class Lattice in module mbuild.lattice:

class Lattice(builtins.object)
 |  Develop crystal structure from user defined inputs.
 |  
 |  Lattice, the abstract building block of a crystal cell.
 |  Once defined by the user, the lattice can then be populated with
 |  Compounds and replicated as many cell lengths desired in 3D space.
 |  
 |  A Lattice is defined through the Bravais lattice definitions. With edge
 |  vectors a1, a2, a3; lattice spacing a,b,c; and lattice points at unique
 |  fractional positions between 0-1 in 3 dimensions. This encapsulates
 |  distance, area, volume, depending on the parameters defined.
 |  
 |  
 |  Parameters
 |  ----------
 |  lattice_spacing : numpy array, shape=(3,), required, dtype=float
 |      Array of lattice spacings a,b,c for the cell.
 |  lattice_vectors : numpy array, shape=(3, 3), optional
 |                    default=[[1,0,0], [0,1,0], [0,0,1]]
 |      Vectors that encase the unit cell corresponding to dimension. Will
 |      only default to these values if no angles were defined as well.
 |  lattice_points : dictionary, shape={'id': [[nested list of positions]]
 |      optional, default={'default': [[0.,0.,0.]]}
 |      Locations of all lattice points in cell using fractional coordinates.
 |  angles : numpy array, shape=(3,), optional, dtype=float
 |      Array of inter-planar Bravais angles
 |  
 |  Attributes
 |  ----------
 |  dimension : int, 3
 |      Default dimensionality within mBuild. If choosing a lower dimension,
 |      pad the relevant arrays with zeroes.
 |  lattice_spacing : numpy array, shape=(3,), required, dtype=float
 |      Array of lattice spacings a,b,c for the cell.
 |  lattice_vectors : numpy array, shape=(3, 3), optional
 |                    default=[[1,0,0], [0,1,0], [0,0,1]]
 |      Vectors that encase the unit cell corresponding to dimension. Will
 |      only default to these values if no angles were defined as well.
 |  lattice_points : dictionary, shape={'id': [[nested list of positions]]
 |      optional, default={'default': [[0.,0.,0.]]}
 |      Locations of all lattice points in cell using fractional coordinates.
 |  angles : numpy array, shape=(3,), optional, dtype=float
 |      Array of inter-planar Bravais angles
 |  
 |  Examples
 |  --------
 |  Generating a triclinic lattice for cholesterol.
 |  
 |  >>> import mbuild as mb
 |  >>> from mbuild.utils.io import get_fn
 |  >>> # reading in the lattice parameters for crystalline cholesterol
 |  >>> angle_values = [94.64, 90.67, 96.32]
 |  >>> spacing = [1.4172, 3.4209, 1.0481]
 |  >>> basis = {'cholesterol':[[0., 0., 0.]]}
 |  >>> cholesterol_lattice = mb.Lattice(spacing,
 |  ...                                  angles=angle_values,
 |  ...                                  lattice_points=basis)
 |  
 |  >>> # The lattice based on the bravais lattice parameters of crystalline
 |  >>> # cholesterol was generated.
 |  
 |  >>> # Replicating the triclinic unit cell out 3 replications
 |  >>> # in x,y,z directions.
 |  
 |  >>> cholesterol_unit = mb.Compound()
 |  >>> cholesterol_unit = mb.load(get_fn('cholesterol.pdb'))
 |  >>> # associate basis vector with id 'cholesterol' to cholesterol Compound
 |  >>> basis_dictionary = {'cholesterol' : cholesterol_unit}
 |  >>> expanded_cell = cholesterol_lattice.populate(x=3, y=3, z=3,
 |  ...                              compound_dict=basis_dictionary)
 |  
 |  The unit cell of cholesterol was associated with a Compound that contains
 |  the connectivity data and spatial arrangements of a cholesterol molecule.
 |  The unit cell was then expanded out in x,y,z directions and cholesterol
 |  Compounds were populated.
 |  
 |  
 |  Generating BCC CsCl crystal structure
 |  
 |  >>> import mbuild as mb
 |  >>> chlorine = mb.Compound(name='Cl')
 |  >>> # angles not needed, when not provided, defaults to 90,90,90
 |  >>> cesium = mb.Compound(name='Cs')
 |  >>> spacing = [.4123, .4123, .4123]
 |  >>> basis = {'Cl' : [[0., 0., 0.]], 'Cs' : [[.5, .5, .5]]}
 |  >>> cscl_lattice = mb.Lattice(spacing, lattice_points=basis)
 |  
 |  >>> # Now associate id with Compounds for lattice points and replicate 3x
 |  
 |  >>> cscl_dict = {'Cl' : chlorine, 'Cs' : cesium}
 |  >>> cscl_compound = cscl_lattice.populate(x=3, y=3, z=3,
 |  ...                                       compound_dict=cscl_dict)
 |  
 |  A multi-Compound basis was created and replicated. For each unique basis
 |  atom position, a separate entry must be completed for the basis_atom
 |  input.
 |  
 |  Generating FCC Copper cell with lattice_vectors instead of angles
 |  
 |  >>> import mbuild as mb
 |  >>> copper = mb.Compound(name='Cu')
 |  >>> lattice_vector = [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
 |  >>> spacing = [.36149, .36149, .36149]
 |  >>> copper_locations = [[0., 0., 0.], [.5, .5, 0.],
 |  ...                     [.5, 0., .5], [0., .5, .5]]
 |  >>> basis = {'Cu' : copper_locations}
 |  >>> copper_lattice = mb.Lattice(lattice_spacing = spacing,
 |  ...                             lattice_vectors=lattice_vector,
 |  ...                             lattice_points=basis)
 |  >>> copper_dict = {'Cu' : copper}
 |  >>> copper_pillar = copper_lattice.populate(x=3, y=3, z=20,
 |  ...                                       compound_dict=copper_dict)
 |  
 |  Generating the 2d Structure Graphene carbon backbone
 |  
 |  >>> import mbuild as mb
 |  >>> carbon = mb.Compound(name='C')
 |  >>> angles = [90, 90, 120]
 |  >>> carbon_locations = [[0, 0, 0], [2/3, 1/3, 0]]
 |  >>> basis = {'C' : carbon_locations}
 |  >>> graphene = mb.Lattice(lattice_spacing=[.2456, .2456, 0],
 |  ...                        angles=angles, lattice_points=basis)
 |  >>> carbon_dict = {'C' : carbon}
 |  >>> graphene_cell = graphene.populate(compound_dict=carbon_dict,
 |  ...                                   x=3, y=3, z=1)
 |  
 |  Methods defined here:
 |  
 |  __init__(self, lattice_spacing=None, lattice_vectors=None, lattice_points=None, angles=None)
 |      Initialize self.  See help(type(self)) for accurate signature.
 |  
 |  populate(self, compound_dict=None, x=1, y=1, z=1)
 |      Expand lattice and create compound from lattice.
 |      
 |      populate will expand lattice based on user input. The user must also
 |      pass in a dictionary that contains the keys that exist in the
 |      basis_dict. The corresponding Compound will be the full lattice
 |      returned to the user.
 |      
 |      If no dictionary is passed to the user, Dummy Compounds will be used.
 |      
 |      Parameters
 |      ----------
 |      x : int, optional, default=1
 |          How many iterations in the x direction.
 |      y : int, optional, default=1
 |          How many iterations in the y direction.
 |      z : int, optional, default=1
 |          How many iterations in the z direction.
 |      compound_dict : dictionary, optional, default=None
 |          Link between basis_dict and Compounds.
 |      
 |      Exceptions Raised
 |      -----------------
 |      ValueError : incorrect x,y, or z values.
 |      TypeError : incorrect type for basis vector
 |      
 |      Call Restrictions
 |      -----------------
 |      Called after constructor by user.
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)

As we can see, there are quite a few attributes and parameters that make up this class. There are also a lot of inline examples as well. If you ever get stuck, remember to use the python built-in help() method!

  • Lattice.lattice_spacing

    This data structure is a (3,) array that details the lengths of the repeat cell for the crystal. You can either use a numpy array object, or simply pass in a list and Lattice will handle the rest. Remember that mBuild's units of length are in nanometers [nm]. You must pass in all three lengths, even if they are all equivalent. These are the lattice parameters $a, b, c$ when viewing crystallographic information.

    For Example:

    lattice_spacing = [.5, .5, .5]
    
  • Lattice.lattice_vectors

    lattice_vectors is a 3x3 array that defines the vectors that encapsulate the repeat cell. This is an optional value that the user can pass in to define the cell. Either this must be passed in, or the 3 Bravais angles of the cell from the lattice parameters must be provided. If neither is passed in, the default value are the vectors that encase a cubic lattice.

    Note, most users will not have to use these to build their lattice structure of interest. It will usually be easier for the users to provide the 3 Bravais angles instead. If the user then wants the vectors, the Lattice object will calculate them for the user.

    For example: Cubic Cell

    lattice_vectors = [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
    
  • Lattice.angles

    angles is a (3,) array that defines the three Bravais angles of the lattice. Commonly referred to as $\alpha, \beta, \gamma$ in the definition of the lattice parameters.

    For example: Cubic Cell

    angles = [90, 90, 90]
    
  • Lattice.lattice_points

    lattice_points can be the most common source of confusion when creating a crystal structure. In crystallographic terms, this is the minimum basis set of points in space that define where the points in the lattice exist. This requires that the user does not over define the system.

    The other tricky issue that can come up is the data structure itself. lattice_points is a dictionary where the dict.key items are the string id's for each basis point. The dict.values items are a nested list of fractional coordinates of the unique lattice points in the cell. If you have the same Compound at multiple lattice_points, it is easier to put all those coordinates in a nested list under the same key value. Two examples will be given below, both FCC unit cells, one with all the same id, and one with unique ids for each lattice_point.

    For Example: FCC All Unique

    lattice_points = {'A' : [[0, 0, 0]], 'B' : [[0.5, 0.5, 0]], 'C' : [[0.5, 0, 0.5]], 'D' : [[0, 0.5, 0.5]]}
    

    For Example: FCC All Same

    lattice_points = {'A' : [[0, 0, 0], [0.5, 0.5, 0], [0.5, 0, 0.5], [0, 0.5, 0.5]] }
    

Lattice Public Methods

The Lattice class also contains methods that are responsible for applying Compounds to the lattice points, with user defined cell replications in the x, y, and z directions.

  • Lattice.populate(compound_dict=None, x=1, y=1, z=1)

    This method uses the Lattice object to place Compounds at the specified lattice_points. There are 4 optional inputs for this class.

    • compound_dict This input is another dictionary that defines a relationship between the lattice_points and the Compounds that the user wants to populate the lattice with. The dict.keys of this dictionary must be the same as the keys in the lattice_points dictionary. However, for the dict.items in this case, the Compound that the user wants to place at that lattice point(s) will be used. An example will use the FCC examples from above. They have been copied below:

      For Example: FCC All Unique

      lattice_points = {'A' : [[0, 0, 0]], 'B' : [[0.5, 0.5, 0]], 'C' : [[0.5, 0, 0.5]], 'D' : [[0, 0.5, 0.5]]}
      
        # compound dictionary
        a = mbuild.Compound(name='A')
        b = mbuild.Compound(name='B')
        c = mbuild.Compound(name='C')
        d = mbuild.Compound(name='D')
      
        compound_dict = {'A' : a, 'B' : b, 'C' : c, 'D' : d}
      

      For Example: FCC All Same

      lattice_points = {'A' : [[0, 0, 0], [0.5, 0.5, 0], [0.5, 0, 0.5], [0, 0.5, 0.5]] }
      
        # compound dictionary
        a = mbuild.Compound(name='A')
        compound_dict = {'A' : a}
      

Putting it all together

Below contains some examples of homogeneous and heterogeneous 2D and 3D lattice structures using the Lattice class.

Example Lattice Systems

Simple Cubic (SC)

  • Polonium

In [ ]:
import mbuild as mb
import numpy as np
import nglview as nv

# define all necessary lattice parameters
spacings = [0.3359, 0.3359, 0.3359]
angles = [90, 90, 90]
points = [[0, 0, 0]]

# define lattice object
sc_lattice = mb.Lattice(lattice_spacing=spacings, angles=angles, lattice_points={'Po' : points})

# define Polonium Compound
po = mb.Compound(name='Po')

# populate lattice with compounds
po_lattice = sc_lattice.populate(compound_dict={'Po' : po}, x=2, y=2, z=2)

# visualize
nv.show_parmed(po_lattice.to_parmed())

Body-centered Cubic (BCC)

  • CsCl

In [ ]:
import mbuild as mb
import numpy as np
import nglview as nv

# define all necessary lattice parameters
spacings = [0.4123, 0.4123, 0.4123]
angles = [90, 90, 90]
point1 = [[0, 0, 0]]
point2 = [[0.5, 0.5, 0.5]]

# define lattice object
bcc_lattice = mb.Lattice(lattice_spacing=spacings, angles=angles, lattice_points={'A' : point1, 'B' : point2})

# define Compounds
cl = mb.Compound(name='Cl')
cs = mb.Compound(name='Cs')

# populate lattice with compounds
cscl_lattice = bcc_lattice.populate(compound_dict={'A' : cl, 'B' : cs}, x=2, y=2, z=2)

# visualize
nv.show_parmed(cscl_lattice.to_parmed())

Face-centered Cubic (FCC)

  • Cu

In [ ]:
import mbuild as mb
import numpy as np
import nglview as nv

# define all necessary lattice parameters
spacings = [0.36149, 0.36149, 0.36149]
angles = [90, 90, 90]
points = [[0, 0, 0], [0.5, 0.5, 0], [0.5, 0, 0.5], [0, 0.5, 0.5]]

# define lattice object
fcc_lattice = mb.Lattice(lattice_spacing=spacings, angles=angles, lattice_points={'A' : points})

# define Compound
cu = mb.Compound(name='Cu')

# populate lattice with compounds
cu_lattice = fcc_lattice.populate(compound_dict={'A' : cu}, x=2, y=2, z=2)

# visualize
nv.show_parmed(cu_lattice.to_parmed())

Diamond (Cubic)

  • Si

In [ ]:
import mbuild as mb
import numpy as np
import nglview as nv

# define all necessary lattice parameters
spacings = [0.54309, 0.54309, 0.54309]
angles = [90, 90, 90]
points = [[0, 0, 0], [0.5, 0.5, 0], [0.5, 0, 0.5], [0, 0.5, 0.5],
          [0.25, 0.25, 0.75], [0.25, 0.75, 0.25], [0.75, 0.25, 0.25], [0.75, 0.75, 0.75]]

# define lattice object
diamond_lattice = mb.Lattice(lattice_spacing=spacings, angles=angles, lattice_points={'A' : points})

# define Compound
si = mb.Compound(name='Si')

# populate lattice with compounds
si_lattice = diamond_lattice.populate(compound_dict={'A' : si}, x=2, y=2, z=2)

# visualize
nv.show_parmed(si_lattice.to_parmed())

Graphene (2D)

  • C

In [ ]:
import mbuild as mb
import numpy as np
import nglview as nv

# define all necessary lattice parameters
spacings = [0.246, 0.246, 0]
angles = [90, 90, 120]
points = [[0, 0, 0], [1/3, 2/3, 0]]

# define lattice object
graphene_lattice = mb.Lattice(lattice_spacing=spacings, angles=angles, lattice_points={'A' : points})

# define Compound
c = mb.Compound(name='C')

# populate lattice with compounds
graphene = graphene_lattice.populate(compound_dict={'A' : c}, x=5, y=5, z=1)

# visualize
nv.show_parmed(graphene.to_parmed())