%load_ext autoreload
%autoreload 2
Constructing Custom Lattices¶
pylattica
is used to simulate systems and models which represent some kind of lattice - i.e. a tiling of some basic shape that yields many identical sites (such as an atomic crystal structure, the square grids used in classical cellular automata, or the hexagonal grids used in lattice-gas automata). This guide illustrates how lattices of any shape and periodicity can be constructed using pylattica
.
from pylattica.core import Lattice
Defining a lattice¶
A lattice is defined by its lattice vectors. Its dimensionality is given by the number of vectors (which should equal their length) used to define it. Below, two lattice vectors are used to define a two-dimensional square lattice. They are the unit vectors in the two primary cartesian directions. These vectors do not have to have any particular length.
square_grid_2d = Lattice([
[1, 0],
[0, 1]
])
To construct an Oblique lattice, we use the same procedure, but with a different pair of vectors. Note that in this case, the first vector is not a unit vector.
oblique = Lattice([
[1, 1],
[1, 0]
])
Fractional and Cartesian Coordinates¶
Lattices provide methods for switching between cartesian and fractional coordinates, as shown below. We illustrate by using our oblique lattice.
cart_coord = (0.5, 0.25)
Converting to fractional coordinates means that we are performing a change of basis operation from the intuitive orthogonal Cartesian basis to the basis defined by the lattice vectors.
We can see that this point can be decomposed as 0.25 times our first lattice vector, and 0.25 times our second lattice vector. Recall that the lattice vectors are defined a few cells above.
frac_coord = oblique.get_fractional_coords(cart_coord)
print("Fractional coordinate is: ", frac_coord)
Fractional coordinate is: [0.25 0.25]
We can also convert back from the fractional representation of our coordinate to the Cartesian form of our coordinate. This is also a change of basis operation.
resolved_cart_coord = oblique.get_cartesian_coords(frac_coord)
print("Cartesian coordinate, again: ", resolved_cart_coord)
Cartesian coordinate, again: [0.5 0.25]
Note that this is the same value as our original point.
Periodicity¶
In pylattica, Lattices are by default periodic in all directions, but periodicity can be specified in any subset of directions. Periodicity just means that the methods for returning periodic points will always return the point inside the unit cell, according to the periodic boundary conditions. Lets illustrate with our square 2D grid, which is periodic in all directions.
cart_coord = (1.5, 0.5)
This point is outside the unit cell (because the lattice vectors are (1, 0) and (0, 1), bounding a square of side length 1). Since period boundary conditions apply in all directions by default, we can expect our Lattice to convert this to the point in the middle of the cell (0.5, 0.5):
periodized_pt = square_grid_2d.get_periodized_cartesian_coords(cart_coord)
print("Periodized point: ", periodized_pt)
Periodized point: [0.5 0.5]
Negative coordinates are also allowed. Here's a point that's totally outside the unit cell, in the negative direction:
cart_coord = (-1.3, -0.2)
periodized_pt = square_grid_2d.get_periodized_cartesian_coords(cart_coord)
print("Periodized point: ", periodized_pt)
Periodized point: [0.7 0.8]
Note that this point has come back in from the top right corner of the cell.
Partial Periodicity¶
You can specify that a lattice is not periodic when instantiating the lattice:
lattice_vecs = [
[1, 0],
[0, 1]
]
aperiodic_lat = Lattice(lattice_vecs, periodic=False)
Now, when we periodize a point as before, the coordinates remain unchanged.
cart_coords = (1.5, 1.2)
periodized_pt = aperiodic_lat.get_periodized_cartesian_coords(cart_coords)
print("Periodized point (same as cart_coords): ", periodized_pt)
Periodized point (same as cart_coords): [1.5 1.2]
We can also specify that our lattice is periodic in only some of it's dimensions, like so:
partially_periodic = Lattice(lattice_vecs, periodic=(True, False))
This lattice has periodic boundary conditions in the direction of the first lattice vector, but not in the direction of the other.
See here:
cart_coords = (1.5, 1.5)
periodized_pt = partially_periodic.get_periodized_cartesian_coords(cart_coords)
print("Periodized point: ", periodized_pt)
Periodized point: [0.5 1.5]
Distances under periodic boundary conditions¶
Under periodic boundary conditions, distances are given as the smallest possible distance between two points, given the periodic boundary conditions. In other words, the shortest line connecting the two points may cross one of the boundaries, as opposed to spanning the interior of the lattice unit cell.
A demonstration below shows this.
lattice_vecs = [
[1, 0],
[0, 1]
]
periodic_lattice = Lattice(lattice_vecs)
nonperiodic_lattice = Lattice(lattice_vecs, periodic=False)
pt1 = (0.1, 0.5)
pt2 = (0.9, 0.5)
print("Distance under periodic boundary conditions: ", periodic_lattice.cartesian_periodic_distance(pt1, pt2))
print("Distance under nonperiodic boundary conditions: ", nonperiodic_lattice.cartesian_periodic_distance(pt1, pt2))
Distance under periodic boundary conditions: 0.2 Distance under nonperiodic boundary conditions: 0.8
Scaling a Lattice¶
Each Lattice
object is used to determine positions inside a PeriodicStructure
. This means that the Lattice
should be as large as the simulation structure. When a PeriodicStructure
is created, the lattice is scaled to the desired size. Although this is very rarely done manually, it can be done like so:
unscaled_lattice = Lattice(lattice_vecs)
print("Lattice vector lengths: ", unscaled_lattice.vec_lengths)
Lattice vector lengths: [1.0, 1.0]
scaled_lattice = unscaled_lattice.get_scaled_lattice((2,2))
print("Scaled lattice vector lengths: ", scaled_lattice.vec_lengths)
Scaled lattice vector lengths: [2.0, 2.0]
Note that the periodization is different, and reflects the larger size of the scaled lattice:
point = (1.5, 1.5)
print("Point in unscaled lattice: ", unscaled_lattice.get_periodized_cartesian_coords(point))
print("Point in scaled lattice: ", scaled_lattice.get_periodized_cartesian_coords(point))
Point in unscaled lattice: [0.5 0.5] Point in scaled lattice: [1.5 1.5]