{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import pybinding as pb\n",
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"\n",
"pb.pltutils.use_style()\n",
"%matplotlib inline"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Lattice specification\n",
"\n",
"This section covers a few extra features of the [`Lattice`](http://docs.pybinding.site/page/advanced/../_api/pybinding.Lattice.html#pybinding.Lattice) class. It is assumed that you are already familiar with the [Tutorial](http://docs.pybinding.site/page/advanced/../tutorial/index.html).\n",
"\n",
"[Download this page as a Jupyter notebook](http://docs.pybinding.site/page/_downloads/6929512cfca862e43886b583720c2b1e/lattice.ipynb)\n",
"\n",
"First, we set a few constants which are going to be needed in the following examples:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from math import sqrt, pi\n",
"\n",
"a = 0.24595 # [nm] unit cell length\n",
"a_cc = 0.142 # [nm] carbon-carbon distance\n",
"t = -2.8 # [eV] nearest neighbour hopping\n",
"\n",
"Gamma = [0, 0]\n",
"K1 = [-4*pi / (3*sqrt(3)*a_cc), 0]\n",
"M = [0, 2*pi / (3*a_cc)]\n",
"K2 = [2*pi / (3*sqrt(3)*a_cc), 2*pi / (3*a_cc)]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Intrinsic onsite energy\n",
"\n",
"During the construction of a [`Lattice`](http://docs.pybinding.site/page/advanced/../_api/pybinding.Lattice.html#pybinding.Lattice) object, the full signature of a sublattice is (`name`, `offset`, `onsite_energy=0.0`), where the last argument is optional. The `name` and `offset` arguments were already explained in the basic tutorial. The `onsite_energy` is applied as an intrinsic part of the sublattice site. As an example, we’ll add this term to monolayer graphene:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def monolayer_graphene(onsite_energy=[0, 0]):\n",
" lat = pb.Lattice(a1=[a, 0], a2=[a/2, a/2 * sqrt(3)])\n",
" lat.add_sublattices(('A', [0, -a_cc/2], onsite_energy[0]),\n",
" ('B', [0, a_cc/2], onsite_energy[1]))\n",
" lat.add_hoppings(([0, 0], 'A', 'B', t),\n",
" ([1, -1], 'A', 'B', t),\n",
" ([0, -1], 'A', 'B', t))\n",
" return lat\n",
"\n",
"lattice = monolayer_graphene()\n",
"lattice.plot()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"> See [`Lattice.add_one_sublattice()`](http://docs.pybinding.site/page/advanced/../_api/pybinding.Lattice.html#pybinding.Lattice.add_one_sublattice) and [`Lattice.add_sublattices()`](http://docs.pybinding.site/page/advanced/../_api/pybinding.Lattice.html#pybinding.Lattice.add_sublattices).\n",
"\n",
"The effect of the onsite energy becomes apparent if we set opposite values for the A and B sublattices. This opens a band gap in graphene:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"model = pb.Model(\n",
" monolayer_graphene(onsite_energy=[-1, 1]), # eV\n",
" pb.translational_symmetry()\n",
")\n",
"solver = pb.solver.lapack(model)\n",
"bands = solver.calc_bands(K1, Gamma, M, K2)\n",
"bands.plot(point_labels=['K', r'$\\Gamma$', 'M', 'K'])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"An alternative way of doing this was covered in the [Opening a band gap](http://docs.pybinding.site/page/advanced/../tutorial/fields.html#graphene-mass-term) section of the basic tutorial. There, an [`@onsite_energy_modifier`](http://docs.pybinding.site/page/advanced/../_api/pybinding.onsite_energy_modifier.html#pybinding.onsite_energy_modifier) was used to produce the same effect. The modifier is applied only after the system is constructed so it can depend on the final (x, y, z) coordinates. Conversely, when the onsite energy is specified directly in a [`Lattice`](http://docs.pybinding.site/page/advanced/../_api/pybinding.Lattice.html#pybinding.Lattice) object, it models an intrinsic part of the lattice and cannot depend on position. If both the intrinsic energy and the modifier are specified, the values are added up.\n",
"\n",
"## Constructing a supercell\n",
"\n",
"A primitive cell is the smallest unit cell of a crystal. For graphene, this is the usual 2-atom cell. It’s translated in space to construct a larger system. Sometimes it can be convenient to use a larger unit cell instead, i.e. a supercell consisting of multiple primitive cells. This allows us to slightly adjust the geometry of the lattice. For example, the 2-atom primitive cell of graphene has vectors at an acute angle with regard to each other. On the other hand, a 4-atom supercell is rectangular which makes certain model geometries easier to create. It also makes it possible to realize armchair edges, as shown in [Nanoribbons](http://docs.pybinding.site/page/advanced/../tutorial/shape_symmetry.html#graphene-nanoribbons) section of the basic tutorial.\n",
"\n",
"We can create a 4-atom cell by adding two more sublattice to the [`Lattice`](http://docs.pybinding.site/page/advanced/../_api/pybinding.Lattice.html#pybinding.Lattice) specification:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def monolayer_graphene_4atom():\n",
" lat = pb.Lattice(a1=[a, 0], a2=[0, 3*a_cc])\n",
" lat.add_sublattices(('A', [ 0, -a_cc/2]),\n",
" ('B', [ 0, a_cc/2]),\n",
" ('A2', [a/2, a_cc]),\n",
" ('B2', [a/2, 2*a_cc]))\n",
" lat.add_hoppings(\n",
" # inside the unit sell\n",
" ([0, 0], 'A', 'B', t),\n",
" ([0, 0], 'B', 'A2', t),\n",
" ([0, 0], 'A2', 'B2', t),\n",
" # between neighbouring unit cells\n",
" ([-1, -1], 'A', 'B2', t),\n",
" ([ 0, -1], 'A', 'B2', t),\n",
" ([-1, 0], 'B', 'A2', t),\n",
" )\n",
" return lat\n",
"\n",
"lattice = monolayer_graphene_4atom()\n",
"plt.figure(figsize=(5, 5))\n",
"lattice.plot()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Note the additional sublattices A2 and B2, shown in green and red in the figure. As defined above, these are interpreted as new and distinct lattice sites. However, we would like to have sublattices A2 and B2 be equivalent to A and B. [`Lattice.add_aliases()`](http://docs.pybinding.site/page/advanced/../_api/pybinding.Lattice.html#pybinding.Lattice.add_aliases) does exactly that:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def monolayer_graphene_4atom():\n",
" lat = pb.Lattice(a1=[a, 0], a2=[0, 3*a_cc])\n",
" lat.add_sublattices(('A', [ 0, -a_cc/2]),\n",
" ('B', [ 0, a_cc/2]))\n",
" lat.add_aliases(('A2', 'A', [a/2, a_cc]),\n",
" ('B2', 'B', [a/2, 2*a_cc]))\n",
" lat.add_hoppings(\n",
" # inside the unit sell\n",
" ([0, 0], 'A', 'B', t),\n",
" ([0, 0], 'B', 'A2', t),\n",
" ([0, 0], 'A2', 'B2', t),\n",
" # between neighbouring unit cells\n",
" ([-1, -1], 'A', 'B2', t),\n",
" ([ 0, -1], 'A', 'B2', t),\n",
" ([-1, 0], 'B', 'A2', t),\n",
" )\n",
" return lat\n",
"\n",
"lattice = monolayer_graphene_4atom()\n",
"plt.figure(figsize=(5, 5))\n",
"lattice.plot()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now we have a supercell with only two unique sublattices: A and B. The 4-atom graphene unit cell is rectangular which makes it a more convenient building block than the oblique 2-atom cell.\n",
"\n",
"## Removing dangling bonds\n",
"\n",
"When a finite-sized graphene system is constructed, it’s possible that it will contain a few dangling bonds on the edge of the system. These are usually not desired and can be removed easily by setting the [`Lattice.min_neighbors`](http://docs.pybinding.site/page/advanced/../_api/pybinding.Lattice.html#pybinding.Lattice.min_neighbors) attribute:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"plt.figure(figsize=(8, 3))\n",
"lattice = monolayer_graphene()\n",
"shape = pb.rectangle(x=1.4, y=1.1)\n",
"\n",
"plt.subplot(121, title=\"min_neighbors == 1 -> dangling bonds\")\n",
"model = pb.Model(lattice, shape)\n",
"model.plot()\n",
"\n",
"plt.subplot(122, title=\"min_neighbors == 2\", ylim=[-0.6, 0.6])\n",
"model = pb.Model(lattice.with_min_neighbors(2), shape)\n",
"model.plot()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The dangling atoms on the edges have only one neighbor which makes them unique. When we use the [`Lattice.with_min_neighbors()`](http://docs.pybinding.site/page/advanced/../_api/pybinding.Lattice.html#pybinding.Lattice.with_min_neighbors) method, the model is required to remove any atoms which have less than the specified minimum number of neighbors. Note that setting [`min_neighbors`](http://docs.pybinding.site/page/advanced/../_api/pybinding.Lattice.html#pybinding.Lattice.min_neighbors) to 3 would produce an empty system since it is impossible for all atoms to have at least 3 neighbors.\n",
"\n",
"## Global lattice offset\n",
"\n",
"When we defined `monolayer_graphene()` at the start of this section, we set the positions of the sublattices as $[x, y] = [0, \\pm a_{cc}]$, i.e. the coordinate system origin is at the midpoint between A and B atoms. It can sometimes be convenient to choose a different origin position such as the center of a hexagon formed by the carbon atoms. Rather than define an entirely new lattice with different positions for A and B, we can simply offset the entire lattice by setting the [`Lattice.offset`](http://docs.pybinding.site/page/advanced/../_api/pybinding.Lattice.html#pybinding.Lattice.offset) attribute:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"plt.figure(figsize=(8, 3))\n",
"shape = pb.regular_polygon(num_sides=6, radius=0.55)\n",
"\n",
"plt.subplot(121, title=\"Origin between A and B atoms\")\n",
"model = pb.Model(monolayer_graphene(), shape)\n",
"model.plot()\n",
"model.shape.plot()\n",
"\n",
"plt.subplot(122, title=\"Origin in the center of a hexagon\")\n",
"model = pb.Model(monolayer_graphene().with_offset([a/2, 0]), shape)\n",
"model.plot()\n",
"model.shape.plot()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Note that the shape remains unchanged, only the lattice shifts position. We could have achieved the same result by only moving the shape, but then the center of the shape would not match the origin of the coordinate system. The [`Lattice.with_offset()`](http://docs.pybinding.site/page/advanced/../_api/pybinding.Lattice.html#pybinding.Lattice.with_offset) makes it easy to position the lattice as needed. Note that the given offset must be within half the length of a primitive lattice vector (positive or negative). Beyond that length the lattice repeats periodically, so it doesn’t make sense to shift it any father."
]
}
],
"metadata": {},
"nbformat": 4,
"nbformat_minor": 4
}