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 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 is an infinite array of discrete points generated by a set of discrete translation operations described by

\[\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)):

julia> using Bloqade
julia> triangular = GeneralLattice([(1.0, 0.0), (0.5, 0.5*sqrt(3))], [(0.0, 0.0)])GeneralLattice{2, 1, Float64}(((1.0, 0.0), (0.5, 0.8660254037844386)), ((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:

julia> honeycomb = GeneralLattice([(1.0, 0.0), (0.5, 0.5*sqrt(3))],
           [(0.0, 0.0), (0.5, 0.5/sqrt(3))])GeneralLattice{2, 2, Float64}(((1.0, 0.0), (0.5, 0.8660254037844386)), ((0.0, 0.0), (0.5, 0.2886751345948129)))

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

ChainLattice
using Bloqade
chain = ChainLattice()
ChainLattice()
# to make the plot look good in both light and dark backgrounds.
BloqadeLattices.DEFAULT_LINE_COLOR[] = "#0085FF"

# to show the lattice vectors (rescaled a bit to shrink the head).
unitvectors(lattice::AbstractLattice{2}, scale=0.9) = [((0.0, 0.0), v .* scale) 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)
0.0μm 2.25μm 4.5μm 6.75μm 9.0μm 0.0μm 1 2 3 4 5 6 7 8 9 10
Note

You can see the above visualization in one of the following editors

but not in a Julia REPL which does not have a graphical display.

lattice_vectors(chain)
((1.0,),)
lattice_sites(chain)
((0.0,),)

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

atoms = generate_sites(HoneycombLattice(), 3, 5; scale = 4.5)
0.0μm 5.06μm 10.12μm 15.19μm 20.25μm 0.0μm 4.22μm 8.44μm 12.67μm 16.89μm 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

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
square = SquareLattice()
Bloqade.plot(generate_sites(square, 10, 10); vectors=unitvectors(square), bond_linewidth=0.015)
0.0μm 2.25μm 4.5μm 6.75μm 9.0μm 0.0μm 2.25μm 4.5μm 6.75μm 9.0μm 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100

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.

lattice_vectors(square)
((1.0, 0.0), (0.0, 1.0))
lattice_sites(square)
((0.0, 0.0),)
HoneycombLattice
honeycomb = HoneycombLattice()
Bloqade.plot(generate_sites(honeycomb, 5, 5); vectors=unitvectors(honeycomb), bond_linewidth=0.015)
0.0μm 1.62μm 3.25μm 4.88μm 6.5μm 0.0μm 0.94μm 1.88μm 2.81μm 3.75μm 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
TriangularLattice
triangular = TriangularLattice()
Bloqade.plot(generate_sites(triangular, 8, 8); vectors=unitvectors(triangular), bond_linewidth=0.015)
0.0μm 2.62μm 5.25μm 7.88μm 10.5μm 0.0μm 1.52μm 3.03μm 4.55μm 6.06μm 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
LiebLattice
lieb = LiebLattice()
Bloqade.plot(generate_sites(lieb, 5, 5); vectors=unitvectors(lieb), bond_linewidth=0.015)
0.0μm 1.12μm 2.25μm 3.38μm 4.5μm 0.0μm 1.12μm 2.25μm 3.38μm 4.5μm 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
KagomeLattice
kagome = KagomeLattice()
Bloqade.plot(generate_sites(kagome, 5, 5); vectors=unitvectors(kagome), bond_linewidth=0.015)
0.0μm 1.69μm 3.38μm 5.06μm 6.75μm 0.0μm 0.97μm 1.95μm 2.92μm 3.9μm 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75

Sort 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, clip_axes, offset_axes, to manipulate atom locations:

atoms = generate_sites(HoneycombLattice(), 3, 5; scale = 4.5)
rescale_axes(atoms, 0.8)
0.0μm 4.05μm 8.1μm 12.15μm 16.2μm 0.0μm 3.38μm 6.75μm 10.13μm 13.51μm 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

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:

clip_axes(atoms, (0.0, 5.0), (0.0, 6.0))
0.0μm 4.5μm 0.0μm 2.6μm 5.2μm 1 2 3 4 5

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

offset_axes(atoms, 5.0, 5.0)
5.0μm 10.06μm 15.12μm 20.19μm 25.25μm 5.0μm 9.22μm 13.44μm 17.67μm 21.89μm 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

To sort the atoms by their x-coordinates, one can convert these locations to a MaskedGrid representation of the atoms:

atoms_in_grid = make_grid(atoms)
0.0μm 5.06μm 10.12μm 15.19μm 20.25μm 0.0μm 4.22μm 8.44μm 12.67μm 16.89μm 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

Then one can get the sorted atoms by typing:

sorted_atoms = collect_atoms(atoms_in_grid)
0.0μm 5.06μm 10.12μm 15.19μm 20.25μm 0.0μm 4.22μm 8.44μm 12.67μm 16.89μm 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

Note that the sorting has changed the index numbering of the atoms.

User-Defined Arbitrary Geometries

One can also generate atoms located at arbitrary positions by directly inputting the coordinates of the atoms:

julia> atom_coordinate = AtomList([(0.0, 0.0), (0, 5), (0, 8), (5, 2), (6, 7), (9, 6)])6-element AtomList{2, Real}:
 (0.0, 0.0)
 (0, 5)
 (0, 8)
 (5, 2)
 (6, 7)
 (9, 6)

Query Neighbors

One can use make_kdtree to generate a k-d tree data type for the efficient querying of neighborhoods in a low-dimensional space.

tree = make_kdtree(sorted_atoms)
NearestNeighbors.KDTree{StaticArrays.SVector{2, Float64}, Distances.Euclidean, Float64}
  Number of points: 30
  Dimensions: 2
  Metric: Distances.Euclidean(0.0)
  Reordered: true

The return value is a KDTree instance, which is defined in the package NearestNeigbors. 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:

neighbors = grouped_nearest(tree, 5, 20)
DistanceGroup([5, 8, 2, 3, 10, 6, 11, 4, 7, 9, 14, 13, 1, 16, 12, 17, 15, 19, 20, 22], [1, 2, 5, 9, 11, 14, 16, 17, 18, 19, 20, 21])

The return value is a DistanceGroup instance, and the indices of the second nearest neighbors are:

neighbors[2]
4-element Vector{Int64}:
 10
  6
 11
  4

One can select and display these atoms with the correct labeling by typing:

Bloqade.plot(sorted_atoms[neighbors[2]]; texts=string.(neighbors[2]))
2.25μm 6.75μm 11.25μm 3.25μm 10 6 11 4

It shows the correct second nearest neigbors of the site 5. One can check the docstring of Bloqade.plot to know more about how to customize lattice visualization.

References

BloqadeLattices.AtomListType
AtomList{D, T} <: AbstractVector{NTuple{D, T}}
AtomList(atoms::Vector{<:NTuple})

A list of atoms in D dimensional space.

source
BloqadeLattices.ByDensityMethod
ByDensity(values; colormap="Grays", vmin=minimum(values), vmax=maximum(values))

For specifying the colors for density plots, where values are densities.

Keyword arguments

  • colormap is a string for specifying the color map, check the documentation of [Colors] package for the detailed description.
  • vmin and vmax are the color range.
source
BloqadeLattices.ChainLatticeType
BloqadeLattices.ChainLattice <: AbstractLattice{1}
BloqadeLattices.ChainLattice()

BloqadeLattices.ChainLattice is a 1 dimensional lattice with:

  • Lattice vectors = ((1.0,),)
  • Lattice sites = ((0.0,),)
source
BloqadeLattices.GeneralLatticeType
GeneralLattice{D,K,T} <: AbstractLattice{D}
GeneralLattice(vectors, sites)

The general lattice type for tiling the space. Type parameter D is the dimension, K is the number of sites in a unit cell and T is the data type for coordinates, e.g. Float64. Input arguments are

  • vectors is a vector/tuple of D-tuple. Its length is D, it specifies the Bravais lattice vectors.
  • sites is a vector/tuple of D-tuple. Its length is K, it specifies the sites inside a Bravais cell.
source
BloqadeLattices.HoneycombLatticeType
BloqadeLattices.HoneycombLattice <: AbstractLattice{2}
BloqadeLattices.HoneycombLattice()

BloqadeLattices.HoneycombLattice is a 2 dimensional lattice with:

  • Lattice vectors = ((1.0, 0.0), (0.5, 0.8660254037844386))
  • Lattice sites = ((0.0, 0.0), (0.5, 0.2886751345948129))
source
BloqadeLattices.KagomeLatticeType
BloqadeLattices.KagomeLattice <: AbstractLattice{2}
BloqadeLattices.KagomeLattice()

BloqadeLattices.KagomeLattice is a 2 dimensional lattice with:

  • Lattice vectors = ((1.0, 0.0), (0.5, 0.8660254037844386))
  • Lattice sites = ((0.0, 0.0), (0.25, 0.4330127018922193), (0.75, 0.4330127018922193))
source
BloqadeLattices.LiebLatticeType
BloqadeLattices.LiebLattice <: AbstractLattice{2}
BloqadeLattices.LiebLattice()

BloqadeLattices.LiebLattice is a 2 dimensional lattice with:

  • Lattice vectors = ((1.0, 0.0), (0.0, 1.0))
  • Lattice sites = ((0.0, 0.0), (0.5, 0.0), (0.0, 0.5))
source
BloqadeLattices.MaskedGridType
MaskedGrid{T}
MaskedGrid(xs, ys, mask)

Masked square lattice contains 3 fields, the x-coordinates, y-coordinates and a mask. e.g. MaskedGrid([0.0, 1.0, 3.0], [0.0, 2.0,6.0], Bool[1 0 0; 0 1 1; 0 1 0]) specifies the following lattice:

     y₁   y₂        y₃
     ↓    ↓         ↓
x₁ → ●    ⋅         ●
x₂ → ⋅    ●         ●

x₃ → ⋅    ●         ⋅
source
BloqadeLattices.RectangularLatticeType
RectangularLattice <: AbstractLattice{2}
RectangularLattice(aspect_ratio::Real)

RectangularLattice is a 2 dimensional lattice with:

  • Lattice vectors = ((1.0, 0.0), (0.0, aspect_ratio)
  • Lattice sites = ((0.0, 0.0),)
source
BloqadeLattices.SquareLatticeType
BloqadeLattices.SquareLattice <: AbstractLattice{2}
BloqadeLattices.SquareLattice()

BloqadeLattices.SquareLattice is a 2 dimensional lattice with:

  • Lattice vectors = ((1.0, 0.0), (0.0, 1.0))
  • Lattice sites = ((0.0, 0.0),)
source
BloqadeLattices.TriangularLatticeType
BloqadeLattices.TriangularLattice <: AbstractLattice{2}
BloqadeLattices.TriangularLattice()

BloqadeLattices.TriangularLattice is a 2 dimensional lattice with:

  • Lattice vectors = ((1.0, 0.0), (0.5, 0.8660254037844386))
  • Lattice sites = ((0.0, 0.0),)
source
BloqadeLattices.clip_axesMethod
clip_axes(sites::AtomList{D, T}, bounds::Vararg{Tuple{T,T},D}) where {D, T}
clip_axes(bounds...)

Remove sites out of bounds, where bounds is specified by D D-tuples.

julia> sites = AtomList([(1.0, 2.0), (10.0, 3.0), (1.0, 12.0), (3.0, 5.0)])
4-element AtomList{2, Float64}:
 (1.0, 2.0)
 (10.0, 3.0)
 (1.0, 12.0)
 (3.0, 5.0)

julia> clip_axes(sites, (-5.0, 5.0), (-5.0, 5.0))
2-element AtomList{2, Float64}:
 (1.0, 2.0)
 (3.0, 5.0)
source
BloqadeLattices.generate_sitesMethod
generate_sites(lattice::AbstractLattice{D}, repeats::Vararg{Int,D}; scale=1.0)

Returns an AtomList instance by tiling the specified lattice. The tiling repeat the sites of the lattice m times along the first dimension, n times along the second dimension, and so on. scale is a real number that re-scales the lattice constant and atom locations.

source
BloqadeLattices.grouped_nearestMethod
grouped_nearest(tree::KDTree, siteindex::Int, nsites::Int; atol=1e-8)

Find the nsites closest vertices to siteindex, and group them by distance. Difference of the distances smaller than the atol (default is 1e-8) are treated as the same Returns a DistanceGroup instance.

julia> atoms = generate_sites(HoneycombLattice(), 5, 5);

julia> tree = make_kdtree(atoms)
NearestNeighbors.KDTree{StaticArrays.SVector{2, Float64}, Distances.Euclidean, Float64}
  Number of points: 50
  Dimensions: 2
  Metric: Distances.Euclidean(0.0)
  Reordered: true

julia> gn = grouped_nearest(tree, 23, 20)
DistanceGroup([23, 14, 22, 24, 15, 13, 21, 25, 33, 31, 12, 16, 32, 4, 6, 34, 26, 17, 5, 41], [1, 2, 5, 11, 14, 18, 21])

julia> gn[0]  # the 0-th nearest neighbor is defined by vertex itself
1-element Vector{Int64}:
 23

julia> gn[1]  # nearest neighbors
3-element Vector{Int64}:
 14
 22
 24

julia> gn[2]  # second nearest neighbors
6-element Vector{Int64}:
 15
 13
 21
 25
 33
 31
source
BloqadeLattices.img_atomsMethod
img_atoms(atoms::AtomList;
    colors=[DEFAULT_LINE_COLOR[], ...],
    blockade_radius=0,
    texts=["1", "2", ...],
    vectors = [],
    format=SVG,
    io=nothing,
    kwargs...
    )

Plots atoms with colors specified by colors and texts specified by texts. Extra vectors can be specified by the vectors keyword argument, which is a vector of (startloc, endloc) pair. You will need a VSCode, Pluto notebook or Jupyter notebook to show the image. If you want to write this image to the disk without displaying it in a frontend, please try

julia> using Compose

julia> open("test.png", "w") do f
            img_atoms(generate_sites(SquareLattice(), 5, 5); io=f, format=Compose.PNG)
       end

The format keyword argument can also be Compose.SVG or Compose.PDF. Atoms within blockade_radius will be connected by bonds.

Other Keyword Arguments

# overall scaling
scale::Float64 = 1.0

# padding space
pad::Float64 = 1.5 

# axes
axes_text_color::String = DEFAULT_LINE_COLOR[]
axes_text_fontsize::Float64 = 11.0
axes_num_of_xticks = 5
axes_num_of_yticks = 5
axes_x_offset::Float64 = 0.1
axes_y_offset::Float64 = 0.06
axes_unit::String = "μm"

# node
node_text_fontsize::Float64 = 5.0
node_text_color::String = DEFAULT_LINE_COLOR[]
node_stroke_color = DEFAULT_LINE_COLOR[]
node_stroke_linewidth = 0.03
node_fill_color = "white"
# bond
bond_color::String = DEFAULT_LINE_COLOR[]
bond_linewidth::Float64 = 0.03
# blockade
blockade_style::String = "none"
blockade_stroke_color::String = DEFAULT_LINE_COLOR[]
blockade_fill_color::String = "transparent"
blockade_fill_opacity::Float64 = 0.5
blockade_stroke_linewidth = 0.03
# image size in cm
image_size::Float64 = 12
source
BloqadeLattices.img_maskedgridMethod
img_maskedgrid(maskedgrid::MaskedGrid;
    format=SVG,
    io=nothing,
    colors=[DEFAULT_LINE_COLOR[], ...],
    texts=["1", "2", ...],
    vectors=[],
    blockade_radius = 0,
    kwargs...
    )

Draw a maskedgrid with colors specified by colors and texts specified by texts. You will need a VSCode, Pluto notebook or Jupyter notebook to show the image.

See also the docstring of img_atoms for explanations of other keyword arguments.

source
BloqadeLattices.lattice_sitesMethod
lattice_sites(lattice::AbstractLattice)

Returns sites in a Bravais lattice unit cell as a Tuple of D-Tuple, where D is the space dimension.

source
BloqadeLattices.make_gridMethod
make_grid(sites::AtomList; atol=...)

Create a MaskedGrid from the sites. It is required by lattice preparation of Rydberg array. Because the grid will sort the sites by rows, we need atol (default value is 10 time sit data precision) determines up to what level of round off error, two atoms belong to the same row.

source
BloqadeLattices.offset_axesMethod
offset_axes(sites::AtomList{D, T}, offsets::Vararg{T,D}) where {D, T}
offset_axes(offsets...)

Offset the sites by distance specified by offsets.

julia> sites = AtomList([(1.0, 2.0), (10.0, 3.0), (1.0, 12.0), (3.0, 5.0)])
4-element AtomList{2, Float64}:
 (1.0, 2.0)
 (10.0, 3.0)
 (1.0, 12.0)
 (3.0, 5.0)

julia> offset_axes(sites, 1.0, 3.0)
4-element AtomList{2, Float64}:
 (2.0, 5.0)
 (11.0, 6.0)
 (2.0, 15.0)
 (4.0, 8.0)
source
BloqadeLattices.random_dropoutMethod
random_dropout(sites::AtomList{D, T}, ratio::Real) where {D, T}
random_dropout(ratio)

Randomly drop out ratio * number of sites atoms from sites, where ratio ∈ [0, 1].

source
BloqadeLattices.rescale_axesMethod
rescale_axes(sites::AtomList{D, T}, scale::Real) where {D, T}
rescale_axes(scale)

Rescale the sites by a constant scale.

julia> sites = AtomList([(1.0, 2.0), (10.0, 3.0), (1.0, 12.0), (3.0, 5.0)])
4-element AtomList{2, Float64}:
 (1.0, 2.0)
 (10.0, 3.0)
 (1.0, 12.0)
 (3.0, 5.0)

julia> rescale_axes(sites, 2.0)
4-element AtomList{2, Float64}:
 (2.0, 4.0)
 (20.0, 6.0)
 (2.0, 24.0)
 (6.0, 10.0)
source