Matter and Coordinates

Atoms and Charges are the building blocks of Crystals and molecules in Xtals.jl. Each have coordinates in both Cartesian and Fractional space (associated with unit cell information, i.e., a Box).

Coordinates

We store coordinates as an abstract Coords type that has two subtypes: Cart and Frac for Cartesian and Fractional, respectively. See the Wikipedia page on fractional coordinates, which are defined in the context of a periodic system, e.g. within a crystal.

Construct coordinates of n particles by passing a n by 3 array:

# construct cartesian coordinates of a particle
coord = Cart([1.0, 2.0, 5.0])
coord.x

# output

3×1 Matrix{Float64}:
 1.0
 2.0
 5.0
# construct fractional coordinates of a particle
coord = Frac([0.1, 0.2, 0.5])
coord.xf

# output

3×1 Matrix{Float64}:
 0.1
 0.2
 0.5

The coordinates of multiple particles are stored column-wise:

# five particles at uniform random coordinates
coords = Cart([
    0.0 1.0 0.0 0.0 1.0
    0.0 0.0 1.0 0.0 1.0
    0.0 0.0 0.0 1.0 1.0
])

Many Array operations work on Coords, such as:

coords[2]                      # coordinate of 2nd particle
coords[2:3]                    # (slicing by index) coords of particles 2 and 3
coords[[1, 2, 5]]              # (slicing by index) coords of particles 1, 2, and 5
coords[rand(Bool, 5)]          # (boolean slicing) coords, selected at random
length(coords)                 # number of particles, (5)

Manipulating coordinates

Coords are immutable:

coords.x = rand(3, 5)

# output

ERROR: setfield!: immutable struct of type Cart cannot be changed

But we can manipulate the values of Array{Float64, 2} where coordinates (through coords.x or coords.xf) are stored:

coords.x[2, 3] = 100.0         # successful!
coords.x[:] = rand(3, 5)       # successful! (achieves the above, but need the [:] to say "overwrite all of the elements"

Fractional coordinates can be wrapped to be inside the unit cell box:

coords = Frac([1.2, -0.3, 0.9])
wrap!(coords)
coords.xf

# output

3×1 Matrix{Float64}:
 0.19999999999999996
 0.7
 0.9

We can translate coordinates by a vector dx:

dx = Cart([1.0, 2.0, 3.0])
coords = Cart([1.0, 0.0, 0.0])
translate_by!(coords, dx)
coords.x

# output

3×1 Matrix{Float64}:
 2.0
 2.0
 3.0

If dx::Frac and coords::Cart, translate_by! requires a Box to convert between Fractional and Cartesian, as the last argument:

dx = Frac([0.1, 0.2, 0.3])
box = unit_cube()
coords = Cart([1.0, 0.0, 0.0])
translate_by!(coords, dx, box)
coords.x

# output

3×1 Matrix{Float64}:
 1.1
 0.20000000000000004
 0.3

Atoms

An atom is specified by its coordinates and atomic species. We can construct a set of Atoms (perhaps comprising a molecule or crystal) as follows:

species = [:O, :H, :H]            # atomic species are represnted with Symbols
coords = Cart([
    0.0 0.757 -0.757  # coordinates of each
    0.0 0.586 0.586
    0.0 0.0 0.0
])
atoms = Atoms(species, coords)    # 3 atoms comprising water
atoms.n                           # number of atoms, 3
atoms.coords                      # coordinates; atoms.coords.x gives the array of coords
atoms.species                     # array of species
atoms::Atoms{Cart}                # successful type assertion, as opposed to atoms::Atoms{Frac}

The last line illustrates the two subtypes of Atoms, depending on whether the Coords are stored as Fractional or Cartesian.

We can slice Atoms, such as:

atoms[1]                         # 1st atom
atoms[2:3]                       # 2nd and 3rd atom

And combine them:

atoms_combined = atoms[1] + atoms[2:3]   # combine atoms 1, 2, and 3
isapprox(atoms, atoms_combined)

# output

true

Charges

Point Charges work analogously to Atoms, except instead of species, the values of the point charges are stored in an array q.

q = [-1.0, 0.5, 0.5]              # values of point charges, units: electrons
coords = Cart([
    0.0 0.757 -0.757  # coordinates of the point charges
    0.0 0.586 0.586
    0.0 0.0 0.0
])
charges = Charges(q, coords)      # 3 point charges
charges.n                         # number of charges, 3
charges.coords                    # retreive coords
charges.q                         # retreive q
charges::Charges{Cart}            # successful type assertion, as opposed to charges::Charges{Frac}

We can determine if the set of point charges comprise a charge-neutral system by:

net_charge(charges)

# output

0.0
neutral(charges)

# output

true

Detailed Docs

Xtals.FracType

fractional coordinates, a subtype of Coords.

construct by passing an Array{Float64, 2} whose columns are the coordinates.

generally, fractional coordinates should be in [0, 1] and are implicitly associated with a Box to represent a periodic coordinate system.

e.g.

f_coords = Frac(rand(3, 2))  # 2 particles
f_coords.xf                  # retreive fractional coords
source
Xtals.CartType

cartesian coordinates, a subtype of Coords.

construct by passing an Array{Float64, 2} whose columns are the coordinates.

e.g.

c_coords = Cart(rand(3, 2))  # 2 particles
c_coords.x                   # retreive cartesian coords
source
Xtals.AtomsType

used to represent a set of atoms in space (their atomic species and coordinates).

struct Atoms{T <: Coords} # enforce that the type specified is `Coords`
    n::Int # how many atoms?
    species::Array{Symbol, 1} # list of species
    coords::T # coordinates
end

here, T is Frac or Cart.

helper constructor (infers n):

species = [:H, :H]
coords = Cart(rand(3, 2))
atoms = Atoms(species, coords)
source
Xtals.ChargesType

used to represent a set of partial point charges in space (their charges and coordinates).

struct Charges{T <: Coords} # enforce that the type specified is `Coords`
    n::Int
    q::Array{Float64, 1}
    coords::T
end

here, T is Frac or Cart.

helper constructor (infers n):

q = [0.1, -0.1]
coords = Cart(rand(3, 2))
charges = Charges(q, coords)
source
Xtals.net_chargeFunction
nc = net_charge(charges)
nc = net_charge(crystal)
nc = net_charge(molecule)

find the sum of charges in charges::Charges or charges in crystal::Crystal or molecule::Molecule. (if there are no charges, the net charge is zero.)

source
Xtals.neutralFunction
neutral(charges, tol) # true or false. default tol = 1e-5
neutral(crystal, tol) # true or false. default tol = 1e-5

determine if a set of charges::Charges (charges.q) sum to an absolute value less than tol::Float64. if crystal::Crystal is passed, the function looks at the crystal.charges. i.e. determine the absolute value of the net charge is less than tol.

source
Xtals.translate_by!Function
translate_by!(coords, dx)
translate_by!(coords, dx, box)
translate_by!(molecule, dx)
translate_by!(molecule, dx, box)

translate coords by the vector dx. that is, add the vector dx.

this works for any combination of Frac and Cart coords.

modifies coordinates in place.

box is needed when mixing Frac and Cart coords.

note that periodic boundary conditions are not subsequently applied here.

if applied to a molecule::Molecule, the coords of atoms, charges, and center of mass are all translated.

source