%load_ext autoreload
%autoreload 2
The autoreload extension is already loaded. To reload it, use: %reload_ext autoreload
from pylattica.core import PeriodicStructure, Lattice
Periodic Structures¶
Although assigning geometric locations to the sites in your simulation is not strictly necessary, lattice simulations are defined in part by the geometry of the sites (the namesake lattice). In pylattica
, these locations are given instances of the PeriodicStructure
class. Though the name suggests that these structures are periodic, this is not a requirement. The periodicity of the structure is given by the periodicity of the Lattice
it is built on (see the Constructing Lattices
guide for more details).
In this guide, we will illustrate the behavior of PeriodicStructure
s by building two and three dimensional square grid structures.
Defining a Structure using a Lattice and a Motif¶
A PeriodicStructure
is defined by a Lattice
object, and a motif. The Lattice
defines the shape, dimensionality, and periodicity of the bounding box of the simulation (see Constructing Lattices
for details), and the motif defines the position of simulation sites within that bounding box. In the parlance of materials science and chemistry, the Lattice
gives a unit cell, and the motif defines the atomic basis.
We will use a two dimensional square grid lattice for this demonstration:
lattice_vecs = [
[1, 0],
[0, 1]
]
lattice = Lattice(lattice_vecs)
This lattice defines a unit cell for a two-dimensional square grid. We will need to define a motif in order to put sites in the structure. The motif specifies the location of sites in each unit cell. The simplest type of motif is given as a list of positions.
motif = [
[0.5, 0.75],
[0.5, 0.25]
]
This motif is a list of coordinate pairs. Each item in the list specifies the position of a site in the unit cell. Let's build a structure from this motif.
structure = PeriodicStructure.build_from(
lattice,
num_cells=((3,3)),
site_motif=motif
)
This code creates a structure from the lattice and motif we defined by tiling the unit cell three times along each of the lattice vectors. As a result, we expect our structure to be a 3 by 3 cell square grid. We can see this by investigating the way the structure transforms coordinates, and by retrieving sites from the structure.
The num_cells
parameter specifies the extent of the structure along the directions of the lattice vectors. In this case, (3,3) indicates tiling the original lattice by three units in each direction.
A new lattice has been created to support this structure. It's larger than the original one we used by a factor of 3 in each dimension.
structure.lattice.vecs
array([[3, 0], [0, 3]])
Note that because of the larger extent of this lattice, coordinates that would have fallen outside the original lattice now fall inside of this one:
coords = (1.5, 1.5)
print("Original periodized (1.5, 1.5): ", lattice.get_periodized_cartesian_coords(coords))
print("New periodized (1.5, 1.5): ", structure.lattice.get_periodized_cartesian_coords(coords))
Original periodized (1.5, 1.5): [0.5 0.5] New periodized (1.5, 1.5): [1.5 1.5]
Now let's look at the structure itself. Our motif specified that there are two sites in each unit cell, so we expect 18 sites in the structure we generated (2 sites/cell * 9 cells = 18 sites).
print("Number of sites in structure: ", len(structure.sites()))
Number of sites in structure: 18
Each site in the structure has three attributes:
- a location
- an ID
- a class
You can refer to specific sites in the structure by either their location or their ID. The IDs are positive integers. Let's look at an example site. We know from our motif that there should be a site at [0.5, 0.75] (we can also expect sites at [1.5, 0.75], [1.5,1.75], etc).
site_location = (0.5, 0.75)
site = structure.site_at(site_location)
print(site)
{'_site_class': 'A', '_location': (0.5, 0.75), '_site_id': 0}
You can see the location of this site, and it's ID (0) in this dictionary. We'll discuss the _site_class
attribute in a moment.
Note that because this structure is periodic in all three dimensions, we can retrieve that same site by refering to a periodic image of it:
periodic_img_loc = (3.5, 3.75)
same_site = structure.site_at(periodic_img_loc)
print(same_site)
{'_site_class': 'A', '_location': (0.5, 0.75), '_site_id': 0}
As you can see, the site returned at those coordinates has the same ID (and is in fact the same site) as the one returned before.
We can also retrieve sites by their ID:
site_by_id = structure.get_site(0)
print(site_by_id)
{'_site_class': 'A', '_location': (0.5, 0.75), '_site_id': 0}
Site Classes¶
As you saw above, each site also has a _site_class
attribute assigned to it. This attribute is used to distinguish types of sites that might exist in your structure. For instance, in crystallography, we frequently refer to sites by their coordination environment (e.g. octahedral or tetrahedral). Further, in surface catalysis, sites are distinguished by well known names: bridge, hollow, on-top. If this type of distinction is important to your simulation, it can be expressed using this attribute.
If you specify a motif using a list of positions, like we did before, every site will have the same _site_class
value, and will be of little use.
If you want to distinguish between sites in your structure by class, we can use a more complex form of motif to achieve this, as shown below.
class_motif = {
"A": [
(0.5, 0.25)
],
"B": [
(0.5, 0.75)
]
}
In this new motif, the keys of the dictionary are the site classes, and the values are the lists of positions where sites with each class belong. Let's make a new structure using the same lattice as before, but this motif.
multi_class_struct = PeriodicStructure.build_from(
lattice,
num_cells=(3,3),
site_motif=class_motif
)
This structure has sites in all the same locations as the one we built before, but now half of them have class A and the other have class B.
print("Total number of sites: ", len(multi_class_struct.site_ids))
print("Number of A sites: ", len(multi_class_struct.sites(site_class="A")))
print("Number of B sites: ", len(multi_class_struct.sites(site_class="B")))
Total number of sites: 18 Number of A sites: 9 Number of B sites: 9
We can also see that sites with class A and B are in the locations we would expect.
print("We expect site A here: ", multi_class_struct.site_at((0.5, 0.25)))
print("And here: ", multi_class_struct.site_at((1.5, 0.25)))
print("And we expect site B here: ", multi_class_struct.site_at((0.5, 0.75)))
print("And here: ", multi_class_struct.site_at((0.5, 1.75)))
We expect site A here: {'_site_class': 'A', '_location': (0.5, 0.25), '_site_id': 0} And here: {'_site_class': 'A', '_location': (1.5, 0.25), '_site_id': 6} And we expect site B here: {'_site_class': 'B', '_location': (0.5, 0.75), '_site_id': 1} And here: {'_site_class': 'B', '_location': (0.5, 1.75), '_site_id': 3}