# Lattices

We can use Bloqade to simulate the quantum evolution of information stored in neutral atoms. Present-day neutral-atom hardware permits the arrangement of atoms in a regular lattice structure and even in nearly arbitrary geometries in 1D, 2D, and 3D.
This makes neutral atom platform a natural playground for quantum simulation of statistical models and quantum matters. With Bloqade, we support several built-in lattice structures and also allow the users to specify atom positions by inputting coordinates.
Please refer to the [Rydberg Blockade](@ref blockade) page for recommendations on how to set the lattice constants for different lattices.

## Types of Lattices

A crystal lattice is completely determined by a set of Bravais lattice vectors (in unit of μm) plus the locations of atoms within a unit cell.
A [Bravais lattice](https://en.wikipedia.org/wiki/Bravais_lattice) is an infinite array of discrete points generated by a set of discrete translation operations described by
```math
\mathbf{R} = n_1 \mathbf{a}_1 + n_2 \mathbf{a}_2 + \ldots + n_d \mathbf{a}_d,
```
where ``d`` is the dimension of space, ``n_1, \ldots, n_d \in Z`` are integers.
The unit cell of a Bravais lattice is defined by specifying its lattice vectors ``(\mathbf{a}_1, \mathbf{a}_2, \ldots, \mathbf{a}_d)``.
To create a simple lattice, we first specify the locations of the atoms within a unit cell and then specify the lattice vectors of the Bravais lattice. For example, for a triangular lattice, we need just one site (atom) at the location `(0.0, 0.0)` in the unit cell and then lattice vectors `(1.0, 0.0)` and `(0.5, 0.5*sqrt(3))`:

```@repl quick-start
using Bloqade
triangular = GeneralLattice([(1.0, 0.0), (0.5, 0.5*sqrt(3))], [(0.0, 0.0)])
```

For composite lattices, one should provide multiple sites as the second argument to specify their locations in a unit cell. For example, the honeycomb lattice can be defined by:
```@repl quick-start
honeycomb = GeneralLattice([(1.0, 0.0), (0.5, 0.5*sqrt(3))],
    [(0.0, 0.0), (0.5, 0.5/sqrt(3))])
```


We provide a few shorthands for several useful lattices, including the [`ChainLattice`](@ref), [`SquareLattice`](@ref), [`HoneycombLattice`](@ref), [`TriangularLattice`](@ref), [`LiebLattice`](@ref), and [`KagomeLattice`](@ref) shown below. 
One can use [`lattice_vectors`](@ref) and [`lattice_sites`](@ref) to access the lattice vectors and site locations in a unit cell as described in the above section.

##### [`ChainLattice`](@ref)
```@example quick-start
using Bloqade
chain = ChainLattice()
```

```@example quick-start
# to make the plot look good in both light and dark backgrounds.
BloqadeLattices.DEFAULT_BACKGROUND_COLOR[] = "#FFFFFF"

# to show the lattice vectors (rescaled a bit to shrink the head).
unitvectors(lattice::AbstractLattice{2}) = [((0.0, 0.0), v) for v in lattice_vectors(lattice)]

Bloqade.plot(generate_sites(chain, 10); vectors=[((0.0, 0.0), (0.9, 0.0))], bond_linewidth=0.015)
```

!!! note
    You can see the above visualization in one of the following editors
    * a [VSCode](https://github.com/julia-vscode/julia-vscode) editor,
    * a [Jupyter](https://github.com/JunoLab/Juno.jl) notebook,
    * or a [Pluto](https://github.com/fonsp/Pluto.jl) notebook,
    
    but not in a Julia REPL which does not have a graphical display.
    

```@example quick-start
lattice_vectors(chain)
```

```@example quick-start
lattice_sites(chain)
```

Once we have defined certain lattice shapes (which have fixed lattice vectors and site positions in the unit cell), we can generate the atom positions by 
specifying the number of atoms and the scale size of the lattice. 
This is done by using the function [`generate_sites`](@ref) , which will return a [`AtomList`](@ref) instance containing the coordinates of each atom, e.g.:  

```@example quick-start
atoms = generate_sites(HoneycombLattice(), 3, 5; scale = 4.5)
```
where `scale` defines the unit distance in the unit μm of the lattice, and `3, 5` specifies the repetitions of unit cells in each lattice vector direction. The default `scale` is 1 μm.

Here are some examples of other lattices:

##### [`SquareLattice`](@ref)
```@example quick-start
square = SquareLattice()
Bloqade.plot(generate_sites(square, 10, 10); vectors=unitvectors(square), bond_linewidth=0.015)
```

Note that the indices showing on the sites are consistent with the indices of the qubits for performing computation. 
In other words, if we want to do measurement or apply operations on individual sites (qubits), we can refer to the numbering on the atoms for convenience. 
For more details on how to generate Hamiltonians by using the lattice as an argument, please see the section [Hamiltonians](@ref).

```@example quick-start
lattice_vectors(square)
```
```@example quick-start
lattice_sites(square)
```

##### [`HoneycombLattice`](@ref)
```@example quick-start
honeycomb = HoneycombLattice()
Bloqade.plot(generate_sites(honeycomb, 5, 5); vectors=unitvectors(honeycomb), bond_linewidth=0.015)
```



##### [`TriangularLattice`](@ref)
```@example quick-start
triangular = TriangularLattice()
Bloqade.plot(generate_sites(triangular, 8, 8); vectors=unitvectors(triangular), bond_linewidth=0.015)
```


##### [`LiebLattice`](@ref)
```@example quick-start
lieb = LiebLattice()
Bloqade.plot(generate_sites(lieb, 5, 5); vectors=unitvectors(lieb), bond_linewidth=0.015)
```


##### [`KagomeLattice`](@ref)
```@example quick-start
kagome = KagomeLattice()
Bloqade.plot(generate_sites(kagome, 5, 5); vectors=unitvectors(kagome), bond_linewidth=0.015)
```


## Sorting Sites and Other Operations on Lattices

We also support different operations on the generated lattices. For instance, one can apply some predefined filters, e.g. [`rescale_axes`](@ref), [`clip_axes`](@ref), [`offset_axes`](@ref), to manipulate atom locations:

```@example quick-start
atoms = generate_sites(HoneycombLattice(), 3, 5; scale = 4.5)
rescale_axes(atoms, 0.8)
```
where the above operation rescales the coordinates of the original `sites` by a factor of `0.8`. 

The code below restricts the atoms sitting in the window `(0.0, 5.0), (0.0, 6.0)` and throw away those outside this area: 

```@example quick-start
clip_axes(atoms, (0.0, 5.0), (0.0, 6.0))
```

Furthermore, we can shift the origin of the `atoms` by some vector `(5.0, 5.0)` simply by typing the code:

```@example quick-start
offset_axes(atoms, 5.0, 5.0)
```


To sort the atoms by their x-coordinates, one can convert these locations to a [`MaskedGrid`](@ref) representation of the atoms:
```@example quick-start
atoms_in_grid = make_grid(atoms)
```

Then one can get the sorted atoms by typing:
```@example quick-start
sorted_atoms = collect_atoms(atoms_in_grid)
```

Note that the sorting will change the index numbering of the atoms. 

You can also delete atoms given their index number:
```@example quick-start
deleteat!(atoms, 8, 9, 13, 14, 18, 20, 23)
atoms
```

Note that this permanently changes the contents of `atoms` and just like sorting atoms, will change the index numbering albeit to preserve the integer sequence without gaps.



## User-Defined Arbitrary Geometries

One can also generate atoms located at arbitrary positions by directly inputting the coordinates of the atoms:
```@repl quick-start
atom_coordinate = AtomList([(0.0, 0.0), (0, 5), (0, 8), (5, 2), (6, 7), (9, 6)])
```

## Query Neighbors

One can use [`make_kdtree`](@ref) to generate a [k-d tree](https://en.wikipedia.org/wiki/K-d_tree) data type for the efficient querying of neighborhoods in a low-dimensional space.
```@example quick-start
tree = make_kdtree(sorted_atoms)
```

The return value is a `KDTree` instance, which is defined in the package [`NearestNeigbors`](https://github.com/KristofferC/NearestNeighbors.jl). One can use it to query the neighbors of an atom: e.g. one can find the 20 nearest neighbors of the 5-th site by typing:
```@example quick-start
neighbors = grouped_nearest(tree, 5, 20)
```

The return value is a [`DistanceGroup`](@ref) instance, and the indices of the second nearest neighbors are:
```@example quick-start
neighbors[2]
```

One can select and display these atoms with the correct labeling by typing:
```@example quick-start
Bloqade.plot(sorted_atoms[neighbors[2]]; texts=string.(neighbors[2]))
```

It shows the correct second nearest neighbors of the site 5.
One can check the docstring of [`Bloqade.plot`](@ref) to know more about how to customize lattice visualization.

## References

```@docs
AbstractLattice
GeneralLattice
SquareLattice
RectangularLattice
HoneycombLattice
TriangularLattice
ChainLattice
LiebLattice
KagomeLattice

lattice_vectors
lattice_sites
BloqadeLattices.dimension
generate_sites
offset_axes
random_dropout
rescale_axes
clip_axes

MaskedGrid
AtomList
make_grid
collect_atoms

img_atoms
img_maskedgrid
ByDensity

AbstractRegion
Parallelepiped
Parallelepiped(vecs)
Parallelepiped(vecs::T) where {T<:Real}

distance
generate_sites_in_region

BoundedLattice
parallelepiped_region

two_body_interaction_matrix
rydberg_interaction_matrix

```

