Loading in Crystal Structure Files#

Place .cif and .cssr crystal structure files in PorousMaterials.PATH_TO_CRYSTALS. PorousMaterials.jl currently takes crystals in P1 symmetry only. From here you can start julia and do the following to load a framework and start working with it.

using PorousMaterials

f = Framework("SBMOF-1.cif")

PorousMaterials will then output information about the framework you just loaded:

Name: SBMOF-1.cif
Bravais unit cell of a crystal.
        Unit cell angles α = 90.000000 deg. β = 100.897000 deg. γ = 90.000000 deg.
        Unit cell dimensions a = 11.619300 Å. b = 5.566700 Å, c = 22.931200 Å
        Volume of unit cell: 1456.472102 ų

Number of atoms = 120
Number of charges = 0
Chemical formula: Dict(:H=>8,:S=>1,:Ca=>1,:O=>6,:C=>14)

Building Blocks of PorousMaterials: Bravais lattice#

We later apply periodic boundary conditions to mimic a crystal of infinite extent. A Box describes a Bravais lattice.

To make a 10 by 10 by 10 Å Bravais lattice with right angles:

box = Box(10.0, 10.0, 10.0, π/2, π/2, π/2)

box.a, box.b, box.c # unit cell dimensions (10.0 Å)
box.α, box.β, box.γ # unit cell angles (1.57... radians)
box.Ω # volume (1000.0 ų)
box.f_to_c # fractional to Cartesian coordinate transformation matrix
box.c_to_f # Cartesian to fractional coordinate transformation matrix
box.reciprocal_lattice # rows are reciprocal lattice vectors

Replicate a box as follows:

box = replicate(box, (2, 2, 2)) # new box replicated 2 by 2 by 2
box.a # 20 Å

Building Blocks of PorousMaterials: Porous Crystals#

using PorousMaterials

# read in xtal structure file
framework = Framework("SBMOF-1.cif")

# access unit cell box
framework.box

# access Lennard-Jones spheres and point charges comprising the crystal
framework.atoms
framework.charges

# remove annoying numbers on the atom labels
strip_numbers_from_atom_labels!(framework)

# compute crystal density
ρ = crystal_density(framework) # kg/m3

# compute the chemical formula
cf = chemical_formula(framework)

# assign charges according to atom type
charges = Dict(:Ca => 3.0, :O => 2.0, :C => -1.0, :S => 7.0, :H => -1.0)
charged_framework = assign_charges(framework, charges)

# replicate & visualize
framework = replicate(framework, (3, 3, 3))
write_to_xyz(framework, "SBMOF-1.xyz")

Demo of Potential Energy Grid#

Superimpose a grid of points about the unit cell of SBMOF-1. Compute the potential energy of xenon at each point and store as a grid.

using PorousMaterials

framework = Framework("SBMOF-1.cif")
molecule = Molecule("Xe")
forcefield = LJForceField("UFF.csv")

grid = energy_grid(framework, molecule, forcefield,
    n_pts=(50, 50, 50), units=:kJ_mol) # Grid data structure

Write to a .cube volume file to visualize the potential energy contours.

write_cube(grid, "CH4_in_SBMOF1.cube")

Boxes#

# PorousMaterials.BoxType.

box = Box(a, b, c, α, β, γ, volume, f_to_c, c_to_f, reciprocal_lattice)
box = Box(a, b, c, α, β, γ)
box = Box(f_to_c)

Data structure to describe a unit cell box (Bravais lattice) and convert between fractional and Cartesian coordinates.

Attributes

coordinates to cartesian coordinates. The columns of this matrix define the unit cell axes. Columns are the vectors defining the unit cell box. units: Angstrom

coordinates to fractional coordinates. units: inverse Angstrom

This choice was made (instead of columns) for speed of Ewald Sums.

source

# PorousMaterials.replicateFunction.

new_box = replicate(original_box, repfactors)

Replicates a Box in positive directions to construct a new Box representing a supercell. The original_box is replicated according to the factors in repfactors. Note replicate(original_box, repfactors=(1, 1, 1)) returns same Box. The new fractional coordinates as described by f_to_c and c_to_f still ∈ [0, 1].

Arguments

Returns

source

replicated_frame = replicate(framework, repfactors)

Replicates the atoms and charges in a Framework in positive directions to construct a new Framework. Note replicate(framework, (1, 1, 1)) returns the same Framework.

Arguments

Returns

source

# PorousMaterials.UnitCubeFunction.

unit_cube = UnitCube()

This function generates a unit cube, each side is 1.0 Angstrom long, and all the corners are right angles.

source

# PorousMaterials.write_vtkFunction.

write_vtk(box, filename; verbose=true, center_at_origin=false)
write_vtk(framework)

Write a Box to a .vtk file for visualizing e.g. the unit cell boundary of a crystal. If a Framework is passed, the Box of that framework is written to a file that is the same as the crystal structure filename but with a .vtk extension.

Appends ".vtk" extension to filename automatically if not passed.

Arguments

source

# PorousMaterials.insideFunction.

inside_box = inside(x, box) # true or false

Determine whether a Cartesian vector x lays inside a Box. This works by computing the fractional coordinates of vector x and ensuring each lie within the interval [0, 1].

source

Crystals#

# PorousMaterials.FrameworkType.

framework = Framework(filename, check_charge_neutrality=true,
                      net_charge_tol=0.001, check_atom_and_charge_overlap=true,
                      remove_overlap=false)
framework = Framework(name, box, atoms, charges)

Read a crystal structure file (.cif or .cssr) and populate a Framework data structure, or construct a Framework data structure directly.

Arguments

Returns

Attributes

source

# PorousMaterials.remove_overlapping_atoms_and_chargesFunction.

new_framework = remove_overlapping_atoms_and_charges(framework, overlap_tol=0.1, verbose=true)

Takes in a framework and returns a new framework with where overlapping atoms and overlapping charges were removed. i.e. if there is an overlapping pair, one in the pair is removed. For any atoms or charges to be removed, the species and charge, respectively, must be identical.

Arguments

Returns

source

# PorousMaterials.strip_numbers_from_atom_labels!Function.

strip_numbers_from_atom_labels!(framework)

Strip numbers from labels for framework.atoms. Precisely, for atom in framework.atoms, find the first number that appears in atom. Remove this number and all following characters from atom. e.g. C12 –> C Ba12A_3 –> Ba

Arguments

source

# PorousMaterials.chemical_formulaFunction.

formula = chemical_formula(framework, verbose=false)

Find the irreducible chemical formula of a crystal structure.

Arguments

Returns

source

# PorousMaterials.molecular_weightFunction.

mass_of_framework = molecular_weight(framework)

Calculates the molecular weight of a unit cell of the framework in amu using information stored in data/atomicmasses.csv.

Arguments

Returns

source

# PorousMaterials.crystal_densityFunction.

ρ = crystal_density(framework) # kg/m²

Compute the crystal density of a framework. Pulls atomic masses from read_atomic_masses.

Arguments

Returns

source

# PorousMaterials.replicateMethod.

replicated_frame = replicate(framework, repfactors)

Replicates the atoms and charges in a Framework in positive directions to construct a new Framework. Note replicate(framework, (1, 1, 1)) returns the same Framework.

Arguments

Returns

source

# PorousMaterials.chargedMethod.

charged_flag = charged(framework, verbose=false) # true or false

Determine if a framework has point charges

source

# PorousMaterials.write_cifFunction.

write_cif(framework, filename)

Write a framework::Framework to a .cif file with filename::AbstractString. If filename does not include the .cif extension, it will automatically be added.

source

# PorousMaterials.assign_chargesFunction.

new_framework = assign_charges(framework, charges, net_charge_tol=1e-5)

Assign charges to the atoms present in the framework. Pass a dictionary of charges that place charges according to the species of the atoms or pass an array of charges to assign to each atom, with the order of the array consistent with the order of framework.atoms.

If the framework already has charges, the charges are removed and new charges are added accordingly so that framework.atoms.n_atoms == framework.charges.n_charges.

Examples

charges = Dict(:Ca => 2.0, :C => 1.0, :H => -1.0)
new_framework = assign_charges(framework, charges)
charges = [4.0, 2.0, -6.0] # framework.atoms is length 3
new_framework = assign_charges(framework, charges)

Arguments

this function)

charge assigned to the species of atom or an array of charges to assign, with order consistent with the order in framework.atoms (units: electrons).

the resulting framework

Returns

are assigned.

source

Grids#

# PorousMaterials.GridType.

Data structure for a regular [equal spacing between points in each coordinate] grid of points superimposed on a unit cell box (Box). Each grid point has data, data, associated with it, of type T, stored in a 3D array.

Attributes

source

# PorousMaterials.xf_to_idFunction.

voxel_id = xf_to_id(n_pts, xf)

Returns the indices of the voxel in which it falls when a unit cube is partitioned into a regular grid of n_pts[1] by n_pts[2] by n_pts[3] voxels. Periodic boundary conditions are applied.

Arguments

Returns

source

# PorousMaterials.update_density!Function.

update_density!(grid, molecule, species)

updates the density grid based on an array of molecules. If a molecule doesn't match the specified species it won't be added to the density grid. This function doesn't calculate the actual densities, it will need a ./ = num_snapshots at the end of the GCMC simulation.

Arguments

source

# PorousMaterials.apply_periodic_boundary_condition!Function.

apply_periodic_boundary_condition!(molecule)

Check if the center_of_mass of a Molecule is outside of a Box. If so, apply periodic boundary conditions and translate the center of mass of the Molecule (and its atoms and point charges) so that it is inside of the Box.

Arguments

source

# PorousMaterials.write_cubeFunction.

write_cube(grid, filename, verbose=true)

Write grid to a .cube file format. This format is described here: http://paulbourke.net/dataformats/cube/ The atoms of the unit cell are not printed in the .cube. Instead, use .xyz files to also visualize atoms.

Arguments

source

# PorousMaterials.read_cubeFunction.

grid = read_cube(filename)

Read a .cube file and return a populated Grid data structure.

Arguments

Returns

source

# PorousMaterials.energy_gridFunction.

grid = energy_grid(framework, molecule, ljforcefield; n_pts=(50, 50, 50), temperature=298.0, n_rotations=750)

Superimposes a regular grid of points (regularly spaced in fractional coordinates of the framework.box) over the unit cell of a crystal, with n_gridpts dictating the number of grid points in the a, b, c directions (including 0 and 1 fractional coords). The fractional coordinates 0 and 1 are included in the grid, although they are redundant. Then, at each grid point, calculate the ensemble average potential energy of the molecule when its mass is centered at that point. The average is taken over Boltzmann-weighted rotations.

The ensemble average is a Boltzmann average over rotations: - R T log ⟨e⁻ᵇᵁ⟩

Arguments

This is only relevant for molecules that are comprised of more than one Lennard Jones sphere.

Returns

source