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.Box
— Type.
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
a,b,c::Float64
: unit cell dimensions (units: Angstroms)α,β,γ::Float64
: unit cell angles (units: radians)Ω::Float64
: volume of the unit cell (units: cubic Angtroms)f_to_c::Array{Float64,2}
: the 3x3 transformation matrix used to map fractional
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
c_to_f::Array{Float64,2}
: the 3x3 transformation matrix used to map Cartesian
coordinates to fractional coordinates. units: inverse Angstrom
reciprocal_lattice::Array{Float64, 2}
: the rows are the reciprocal lattice vectors.
This choice was made (instead of columns) for speed of Ewald Sums.
#
PorousMaterials.replicate
— Function.
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
original_box::Box
: The box that you want to replicaterepfactors::Tuple{Int, Int, Int}
: The factor you want to replicate the box by
Returns
box::Box
: Fully formed Box object
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
framework::Framework
: The framework to replicaterepfactors::Tuple{Int, Int, Int}
: The factors by which to replicate the crystal structure in each direction.
Returns
replicated_frame::Framework
: Replicated framework
#
PorousMaterials.UnitCube
— Function.
unit_cube = UnitCube()
This function generates a unit cube, each side is 1.0 Angstrom long, and all the corners are right angles.
#
PorousMaterials.write_vtk
— Function.
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
box::Box
: a Bravais latticefilename::AbstractString
: filename of the .vtk file output (absolute path)framework::Framework
: A framework containing the crystal structure informationcenter_at_origin::Bool
: center box at origin if true. if false, the origin is the corner of the box.
#
PorousMaterials.inside
— Function.
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]
.
Crystals#
#
PorousMaterials.Framework
— Type.
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
filename::AbstractString
: the name of the crystal structure file (include ".cif" or ".cssr") read fromPATH_TO_CRYSTALS
.check_charge_neutrality::Bool
: check for charge neutralitynet_charge_tol::Float64
: when checking for charge neutrality, throw an error if the absolute value of the net charge is larger than this value.check_atom_and_charge_overlap::Bool
: throw an error if overlapping atoms are detected.remove_overlap::Bool
: remove identical atoms automatically. Identical atoms are the same element atoms which overlap.
Returns
framework::Framework
: A framework containing the crystal structure information
Attributes
name::AbstractString
: name of crystal structurebox::Box
: unit cell (Bravais Lattice)atoms::Atoms
: list of Atoms in crystal unit cellcharges::Charges
: list of point charges in crystal unit cell
#
PorousMaterials.remove_overlapping_atoms_and_charges
— Function.
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
framework::Framework
: The framework containing the crystal structure informationatom_overlap_tol::Float64
: The minimum distance between two atoms that is toleratedcharge_overlap_tol::Float64
: The minimum distance between two charges that is tolerated
Returns
new_framework::Framework
: A new framework where identical atoms have been removed.
#
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
framework::Framework
: The framework containing the crystal structure information
#
PorousMaterials.chemical_formula
— Function.
formula = chemical_formula(framework, verbose=false)
Find the irreducible chemical formula of a crystal structure.
Arguments
framework::Framework
: The framework containing the crystal structure informationverbose::Bool
: Iftrue
, will print the chemical formula as well
Returns
formula::Dict{Symbol, Int}
: A dictionary with the irreducible chemical formula of a crystal structure
#
PorousMaterials.molecular_weight
— Function.
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
framework::Framework
: The framework containing the crystal structure information
Returns
mass_of_framework::Float64
: The molecular weight of a unit cell of the framework in amu
#
PorousMaterials.crystal_density
— Function.
ρ = crystal_density(framework) # kg/m²
Compute the crystal density of a framework. Pulls atomic masses from read_atomic_masses
.
Arguments
framework::Framework
: The framework containing the crystal structure information
Returns
ρ::Float64
: The crystal density of a framework in kg/m³
#
PorousMaterials.replicate
— Method.
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
framework::Framework
: The framework to replicaterepfactors::Tuple{Int, Int, Int}
: The factors by which to replicate the crystal structure in each direction.
Returns
replicated_frame::Framework
: Replicated framework
#
PorousMaterials.charged
— Method.
charged_flag = charged(framework, verbose=false) # true or false
Determine if a framework has point charges
#
PorousMaterials.write_cif
— Function.
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.
#
PorousMaterials.assign_charges
— Function.
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
framework::Framework
: the framework to which we should add charges (not modified in
this function)
charges::Union{Dict{Symbol, Float64}, Array{Float64, 1}}
: a dictionary that returns the
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).
net_charge_tol::Float64
: the net charge tolerated when asserting charge neutrality of
the resulting framework
Returns
new_framework::Framework
: a new framework identical to the one passed except charges
are assigned.
Grids#
#
PorousMaterials.Grid
— Type.
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
box::Box
: describes Bravais lattice over which a grid of points is super-imposed. grid points on all faces are included.n_pts::Tuple{Int, Int, Int}
: number of grid points in x, y, z directions. 0 and 1 fractional coordinates are included.data::Array{T, 3}
: three dimensional array conaining data associated with each grid point.units::Symbol
: the units associated with each data point.origin::Array{Float64, 1}
: the origin of the grid.
#
PorousMaterials.xf_to_id
— Function.
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
n_pts::Tuple{Int, Int, Int}
: The number of points for each axis in theGrid
xf::Array{Float64, 1}
: The fractional coordinates to be converted to an id
Returns
id::Array{Int, 1}
: The array indices for storing this point in space
#
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
grid::Grid
: the grid to be updatedmolecules::Array{Molecule, 1}
: An array of molecules whose positions will be added to the gridspecies::Symbol
: The species of atom that can be added to this density grid
#
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
molecule::Molecule
: A molecule we're interested in seeing if its' center of mass falls withinsimulation_box
#
PorousMaterials.write_cube
— Function.
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
grid::Grid
: grid with associated data at each grid point.filename::AbstractString
: name of .cube file to which we write the grid; this is relative toPATH_TO_GRIDS
.verbose::Bool
: print name of file after writing.
#
PorousMaterials.read_cube
— Function.
grid = read_cube(filename)
Read a .cube file and return a populated Grid
data structure.
Arguments
filename::AbstractString
: name of .cube file to which we write the grid; this is relative toPATH_TO_GRIDS
Returns
grid::Grid
: A grid data structure
#
PorousMaterials.energy_grid
— Function.
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
framework::Framework
: crystal in which we seek to compute an energy grid for a molecule.grid.box
will beframework.box
.molecule::Molecule
: molecule for which we seek an energy gridljforcefield::LJForceField
: molecular model for computing molecule-framework interactionsn_pts::Tuple{Int, Int, Int}=(50,50,50)
: number of grid points in each fractional coordinate dimension, including endpoints (0, 1)n_rotations::Int
: number of random rotations to conduct in a Monte Carlo simulation for finding the free energy of a molecule centered at a given grid point.
This is only relevant for molecules that are comprised of more than one Lennard Jones sphere.
temperature::Float64
: the temperature at which to compute the free energy for molecules where rotations are required. Lower temperatures overemphasize the minimum potential energy rotational conformation at that point.units::Symbol
: either:K
or:kJ_mol
, the units in which the energy should be stored in the returnedGrid
.center::Bool
: shift coords of grid so that the origin is the center of the unit cellframework.box
.verbose::Bool=true
: print some information.
Returns
grid::Grid
: A grid data structure containing the potential energy of the system