```@meta
CurrentModule = Bloqade
```

# Hamiltonians

Quantum Hamiltonians encode the essential physical properties of a quantum system. For the analog mode of neutral-atom quantum computers, the quantum dynamics is governed by the Rydberg Hamiltonian ``\hat{\mathcal{H}}``:

```math
i \hbar \dfrac{\partial}{\partial t} | \psi \rangle = \hat{\mathcal{H}}(t) | \psi \rangle,  \\

\frac{\mathcal{H}(t)}{\hbar} = \sum_j \frac{\Omega_j(t)}{2} \left( e^{i \phi_j(t) } | g_j \rangle  \langle r_j | + e^{-i \phi_j(t) } | r_j \rangle  \langle g_j | \right) - \sum_j \Delta_j(t) \hat{n}_j + \sum_{j < k} V_{jk} \hat{n}_j \hat{n}_k,
```

where ``\Omega_j``, ``\phi_j``, and ``\Delta_j``  denote the Rabi frequency, laser phase, and the detuning of the driving laser field on atom (qubit) ``j`` coupling the two states  ``| g_j \rangle `` (ground state) and `` | r_j \rangle `` (Rydberg state); ``\hat{n}_j = |r_j\rangle \langle r_j|`` is the number operator, and ``V_{jk} = C_6/|\mathbf{x}_j - \mathbf{x}_k|^6`` describes the Rydberg interaction (van der Waals interaction) between atoms ``j`` and ``k`` where ``\mathbf{x}_j`` denotes the position of the atom ``j``; ``C_6`` is the Rydberg interaction constant that depends on the particular Rydberg state used. For Bloqade, the default ``C_6 = 862690 \times 2\pi \text{ MHz μm}^6`` for ``|r \rangle = \lvert 70S_{1/2} \rangle`` of the ``^{87}``Rb atoms; ``\hbar`` is the reduced Planck's constant.

One can use the Rydberg Hamiltonian to 
understand the ground state properties of the corresponding system and to generate interesting quantum dynamics. 
The Rydberg Hamiltonian is generally specified by atom positions ``\mathbf{x}_j``, Rabi frequencies ``\Omega_j``, laser phase ``\phi_j``, and detunings ``\Delta_j``. In Bloqade, 
we can easily create a Hamiltonian by inputting these variable parameters into the function [`rydberg_h`](@ref). Furthermore, by inputting waveforms for the Rabi frequency and 
detuning, we can easily generate time-dependent Hamiltonians. 

## Building Time-Independent Hamiltonians

To specify the Hamiltonian, we first need to specify the atom positions, which determine the Rydberg interactions strengths ``V_{jk}`` between pairs of atoms. Here, we generate a square lattice by using the code below:

```@repl hamiltonian
using Bloqade
atoms = generate_sites(SquareLattice(), 3, 3, scale=6.3)
```

Please refer to the [Lattices](@ref) page for more details on how to generate lattices and the relevant operations. 

Then, the Hamiltonian can be simply built by inputting the generated atom positions `atoms` and by specifying the strength of the detuning `Δ`, Rabi frequency `Ω`, and laser phase `ϕ`:

```@repl hamiltonian
h0 = rydberg_h(atoms; Δ=1.2*2π, Ω=1.1*2π, ϕ=2.1)
```

Note that the default value for the Rydberg interaction constant is ``C_6 = 2\pi \times 862690 \text{ MHz μm}^6`` to match the default unit used on the hardware. For more information about units, please 
refer to [Bloqade](@ref). Instead of using the default value for ``C_6``, the users are free to set their own values. For instance, if the users would like to have a chain lattice with nearest-neighbor atoms separated by 1 μm, and interaction strength to be a particular value, say, ``2\pi * 10.0^6 \text{ MHz μm}^6``, it can be done with the following code:

```@repl hamiltonian
atoms = generate_sites(ChainLattice(), 9, scale=1.0)
h0 = rydberg_h(atoms; C=2π*10.0^6, Δ=1.2*2π, Ω=1.1*2π, ϕ=2.1)
```


## Building Time-Dependent Hamiltonians

One can also directly use waveforms (instead of constant values of detuning, Rabi frequency, and laser phase) to build a time-dependent Hamiltonian. 
First, let us again use the  [`generate_sites`](@ref) to create a list of atom coordinates: 

```@repl hamiltonian
atoms = generate_sites(ChainLattice(), 5, scale=5.72)
```

Then, we generate the time-dependent pulses for ``\Omega`` and ``\Delta`` by using 
[`piecewise_linear`](@ref). For details on how to create waveforms and the built-in functions, please refer to the page [Waveforms](@ref). 

```@repl hamiltonian
Ω1 = piecewise_linear(clocks=[0.0, 0.1, 2.1, 2.2], values=2π*[0.0, 6.0, 6.0, 0]);
Δ1 = piecewise_linear(clocks=[0.0, 0.6, 2.1, 2.2], values=2π*[-10.1, -10.1, 10.1, 10.1]);
```

The time-dependent Hamiltonian can then be easily generated by inputting the waveforms into the function [`rydberg_h`](@ref):

```@repl hamiltonian
h1 = rydberg_h(atoms; Δ=Δ1, Ω=Ω1)
```

By specifying the time of `h1`, we can access the Hamiltonian at a particular time, e.g.: 

```@repl hamiltonian
ht= h1 |> attime(0.5)
```

## Building Hamiltonians with Site-Dependent Waveforms

In certain cases, the user may want to build a Hamiltonian that has site-dependent ``\Omega_j``, ``\phi_j``, and ``\Delta_j``, which may or may not have time dependence.

For the time-independent Hamiltonian, one can for example build a Hamiltonian like: 

```@repl hamiltonian
h0 = rydberg_h(atoms; Δ=1.2*2π*rand(length(atoms)), Ω=1.1*2π*rand(length(atoms)), ϕ=2.1)
```

For time-dependent Hamiltonians, here is an example:

```@repl hamiltonian
atoms = generate_sites(ChainLattice(), 5, scale=5.72)
Δ1 = map(1:length(atoms)) do idx
    Waveform(t-> idx*sin(2π*t), duration = 2)
end
h =rydberg_h(atoms; Δ=Δ1)
```


## Hamiltonian Expressions

Bloqade uses "block"s from [Yao](https://yaoquantum.org/) to build symbolic hamiltonian
expressions. This gives users the flexibility to define various  
kinds of Hamiltonians by simply writing down the expression.

Please refer to the References section below for the types of operators supported by Bloqade.

As an example, we can explicitly add up some Hamiltonian terms to compose a new Hamiltonian, e.g.:

```@repl hamiltonian
using Bloqade
h = 2π*1.1*SumOfX(5, 1.0) + 2π*1.2*SumOfZ(5, 1.0)
```

## Convert Hamiltonians to Matrices

An Hamiltonian expression can be converted to a matrix via the [`mat`](https://docs.yaoquantum.org/dev/man/blocks.html#YaoAPI.mat-Tuple{AbstractBlock})
interface from Yao:

```@docs
mat
```

This method will return the most compact matrix representation of the operator,
e.g.:

```@repl hamiltonian
mat(X) # will return a PermMatrix
mat(ht) # will return a SparseMatrixCSC
```

The Hamiltonian matrix can also be created in a subspace, such as the
blockade subspace (see also [Working with Subspace](@ref subspace)). This will allow one to simulate larger system sizes because of the smaller truncated Hilbert space.

For the Rydberg Hamiltonian, we can create a subspace via the [`blockade_subspace`](@ref)
method, e.g.:

```@repl hamiltonian
space = blockade_subspace(atoms, 7.5)
```

The above code means that the blockade subspace only includes states where there is only one Rydberg excitation 
within the distance of 7.5 μm, which we call the subspace radius ``R_s``. If we have a chain of atoms separated by 5.72 μm, the blockade subspace 
does not contain states with nearest-neighbor atoms being simultaneously excited to the Rydberg state `` | r \rangle ``.

Once we have defined the space, we can convert the Hamiltonian to a matrix in a subspace basis via the codes below:

```@repl hamiltonian
h_m = mat(ht, space)
```

We can see that the size of the Hamiltonian matrix in the blockade subspace is much smaller than that in the full Hilbert space.

## Diagonalization of the Hamiltonian

Bloqade doesn't provide any built-in diagonalization tool, as there are many existing 
tools in the Julia ecosystem. Here, we demonstrate how to use the [KrylovKit](https://github.com/Jutho/KrylovKit.jl) package for this purpose as follows:

```@repl hamiltonian
using KrylovKit
vals, vecs, info = KrylovKit.eigsolve(h_m,  1, :SR)
```

where the `vals` and `vecs` store the calculated eigenvalues and eigenvectors respectively. 

## Low-Level Representation of the Hamiltonian

Besides the symbolic representation, in order to achieve the best possible performance, we use a lower-level representation of the Hamiltonian in Bloqade,
which is the `Hamiltonian` and `StepHamiltonian` type:

```@docs
BloqadeExpr.Hamiltonian
BloqadeExpr.StepHamiltonian
```

The `Hamiltonian` type represents the following Hamiltonian expression

```math
f_1(t) H_1 + f_2(t) H_2 + \cdots + f_n(t) H_n + H_c,
```

where ``f_i(t)`` are time-dependent parameters of the Hamiltonian,
``H_i`` are time-independent local terms of the Hamiltonian as linear operators
(in Julia, this means objects that support `LinearAlgebra.mul!` interface), and
``H_c`` is the constant component of the Hamiltonian.

A `Hamiltonian` object supports callable methods, which will produce a
`StepHamiltonian` that is time-independent, e.g.:

```@repl hamiltonian
using BloqadeExpr
h = BloqadeExpr.Hamiltonian(Float64, SumOfX(5, sin) + SumOfZ(5, cos))
h(0.1)
```

Here, we see that the Hamiltonian expression written as Yao blocks are automatically analyzed into time-dependent terms and constant terms. A more complicated example can be [`SumOfXPhase`](@ref):

```@repl hamiltonian
using BloqadeExpr
h = BloqadeExpr.Hamiltonian(Float64, SumOfXPhase(5, sin, cos) + SumOfZ(5, cos))
h(0.1)
```

## References

```@docs
rydberg_h
```

Except the standard operators from Yao, the following operators are also supported
by Bloqade:

```@docs
RydInteract
SumOfX
SumOfXPhase
SumOfZ
SumOfN
XPhase
```