# 4. Finite size¶

This section introduces the concept of shapes with classes `Polygon`

and
`FreeformShape`

which are used to model systems of finite size. The sparse
eigensolver `arpack()`

is also introduced as a good tool for exactly solving
larger Hamiltonian matrices.

`Download this page as a Jupyter notebook`

## 4.1. Primitive¶

The simplest finite-sized system is just the unit cell of the crystal lattice.

```
from pybinding.repository import graphene
model = pb.Model(graphene.monolayer())
model.plot()
```

The unit cell can also be replicated a number of times to create a bigger system.

```
model = pb.Model(
graphene.monolayer(),
pb.primitive(a1=5, a2=3)
)
model.plot()
model.lattice.plot_vectors(position=[0.6, -0.25])
```

The `primitive()`

parameter tells the model to replicate the unit cell 5 times in the
\(a_1\) vector direction and 3 times in the \(a_2\) direction. However, to model
realistic systems we need proper shapes.

## 4.2. Polygon¶

The easiest way to create a 2D shape is with the `Polygon`

class. For example,
a simple rectangle:

```
def rectangle(width, height):
x0 = width / 2
y0 = height / 2
return pb.Polygon([[x0, y0], [x0, -y0], [-x0, -y0], [-x0, y0]])
shape = rectangle(1.6, 1.2)
shape.plot()
```

A `Polygon`

is initialized with a list of vertices which should be given in clockwise or
counterclockwise order. When added to a `Model`

the lattice will expand to fill the shape.

```
model = pb.Model(
graphene.monolayer(),
rectangle(width=1.6, height=1.2)
)
model.plot()
```

To help visualize the shape and the expanded lattice, the polygon outline can be plotted on top of the system by calling both plot methods one after another.

```
def trapezoid(a, b, h):
return pb.Polygon([[-a/2, 0], [-b/2, h], [b/2, h], [a/2, 0]])
model = pb.Model(
graphene.monolayer(),
trapezoid(a=3.2, b=1.4, h=1.5)
)
model.plot()
model.shape.plot()
```

In general, a shape does not depend on a specific material, so it can be easily reused. Here, we
shall switch to a `graphene.bilayer()`

lattice, but we’ll keep
the same `trapezoid`

shape as defined earlier:

```
model = pb.Model(
graphene.bilayer(),
trapezoid(a=3.2, b=1.4, h=1.5)
)
model.plot()
```

## 4.3. Freeform shape¶

Unlike a `Polygon`

which is defined by a list of vertices, a `FreeformShape`

is
defined by a `contains`

function which determines if a lattice site is inside the desired shape.

```
def circle(radius):
def contains(x, y, z):
return np.sqrt(x**2 + y**2) < radius
return pb.FreeformShape(contains, width=[2*radius, 2*radius])
model = pb.Model(
graphene.monolayer(),
circle(radius=2.5)
)
model.plot()
```

The `width`

parameter of `FreeformShape`

specifies the bounding box width. Only sites
inside the bounding box will be considered for the shape. It’s like carving a sculpture from a
block of stone. The bounding box can be thought of as the stone block, while the `contains`

function is the carving tool that can give the fine detail of the shape.

As with `Polygon`

, we can visualize the shape with the `FreeformShape.plot()`

method.

```
def ring(inner_radius, outer_radius):
def contains(x, y, z):
r = np.sqrt(x**2 + y**2)
return np.logical_and(inner_radius < r, r < outer_radius)
return pb.FreeformShape(contains, width=[2*outer_radius, 2*outer_radius])
shape = ring(inner_radius=1.4, outer_radius=2)
shape.plot()
```

The shaded area indicates the shape as determined by the `contains`

function. Creating a model
will cause the lattice to fill in the shape.

```
model = pb.Model(
graphene.monolayer(),
ring(inner_radius=1.4, outer_radius=2)
)
model.plot()
model.shape.plot()
```

Note that the `ring`

example uses `np.logical_and`

instead of the plain `and`

keyword. This is
because the `x, y, z`

positions are not given as scalar numbers but as numpy arrays. Array
comparisons return boolean arrays:

```
>>> x = np.array([7, 2, 3, 5, 1])
>>> x < 5
[False, True, True, False, True]
>>> 2 < x and x < 5
ValueError: ...
>>> np.logical_and(2 < x, x < 5)
[False, False, True, False, False]
```

The `and`

keyword can only operate on scalar values, but `np.logical_and`

can consider arrays.
Likewise, `math.sqrt`

does not work with arrays, but `np.sqrt`

does.

## 4.4. Composite shape¶

Complicated system geometry can also be produced by composing multiple simple shapes. The following example gives a quick taste of how it works. For a full overview of this functionality, see the Composite shapes section.

```
# Simple shapes
rectangle = pb.rectangle(x=6, y=1)
hexagon = pb.regular_polygon(num_sides=6, radius=1.92, angle=np.pi/6)
circle = pb.circle(radius=0.6)
# Compose them naturally
shape = rectangle + hexagon - circle
model = pb.Model(graphene.monolayer(), shape)
model.shape.plot()
model.plot()
```

## 4.5. Spatial LDOS¶

Now that we have a ring structure, we can exactly diagonalize its `model.hamiltonian`

using a
`Solver`

. We previously used the `lapack()`

solver to find all the eigenvalues and
eigenvectors, but this is not efficient for larger systems. The sparse `arpack()`

solver can
calculate a targeted subset of the eigenvalues, which is usually desired and much faster. In this
case, we are interested only in the 20 lowest energy states.

```
model = pb.Model(
graphene.monolayer(),
ring(inner_radius=1.4, outer_radius=2)
)
solver = pb.solver.arpack(model, k=20) # only the 20 lowest eigenstates
ldos = solver.calc_spatial_ldos(energy=0, broadening=0.05) # eV
ldos.plot(site_radius=(0.03, 0.12))
pb.pltutils.colorbar(label="LDOS")
```

The convenient `Solver.calc_spatial_ldos()`

method calculates the local density of states
(LDOS) at every site for the given energy with a Gaussian broadening. The returned object is a
`StructureMap`

which holds the LDOS data. The `StructureMap.plot()`

method will
produce a figure similar to `Model.plot()`

, but with a colormap indicating the LDOS value
at each lattice site. In addition, the `site_radius`

argument specifies a range of sizes which
will cause the low intensity sites to appear as small circles while high intensity ones become
large. The states with a high LDOS are clearly visible on the outer and inner edges of the
graphene ring structure.

## 4.6. Further reading¶

For more finite-sized systems check out the examples section.

## 4.7. Example¶

```
"""Model a graphene ring structure and calculate the local density of states"""
import pybinding as pb
import numpy as np
import matplotlib.pyplot as plt
from pybinding.repository import graphene
pb.pltutils.use_style()
def ring(inner_radius, outer_radius):
"""A simple ring shape"""
def contains(x, y, z):
r = np.sqrt(x**2 + y**2)
return np.logical_and(inner_radius < r, r < outer_radius)
return pb.FreeformShape(contains, width=[2 * outer_radius, 2 * outer_radius])
model = pb.Model(
graphene.monolayer(),
ring(inner_radius=1.4, outer_radius=2) # length in nanometers
)
model.plot()
plt.show()
```

```
# only solve for the 20 lowest energy eigenvalues
solver = pb.solver.arpack(model, k=20)
ldos = solver.calc_spatial_ldos(energy=0, broadening=0.05) # LDOS around 0 eV
ldos.plot(site_radius=(0.03, 0.12))
pb.pltutils.colorbar(label="LDOS")
plt.show()
```