Bloqade

Welcome to the documentation page for Bloqade, a   Julia Language   package for quantum computation and quantum dynamics based on neutral-atom architectures.

Neutral-atom quantum computers have two major modes of computation: the first mode is a "digital mode" to do universal, digital quantum computation that uses two ground states $|0\rangle$ and $|1\rangle$ to encode the qubit, which has long coherence time, and one Rydberg state $|r\rangle$ to entangle the qubits; the second mode is an "analog mode" as a programmable quantum simulator that uses one ground state $|g\rangle$ and one Rydberg state $|r\rangle$, where the quantum dynamics is governed by a Rydberg Hamiltonian $\hat{\mathcal{H}}$ described below.

Currently, Bloqade enables the easy design and fast execution of quantum dynamics in the analog mode, based on the neutral-atom quantum computing architecture. Besides fast full Hilbert-space simulation on CPUs, the main features include the design of arbitrary-layout quantum registers (Lattices), easy waveform generation (Waveforms), simulation in subspace constrained by the Rydberg blockade (Working with Subspace), faster GPU-accelerated simulation (GPU Acceleration), and more.

Installation

To install Bloqade, please open Julia's interactive session (known as REPL), press ] key in the REPL to use the package mode, and then add the QuEra Julia registry via:

For the stable release, type:

pkg> add Bloqade

Or for the current master:

pkg> add Bloqade#master

For a more advanced installation guide, please see the Installation page.

What does Bloqade Do?

In the analog mode, Bloqade simulates the time evolution of a quantum state under the Schrödinger equation where the Hamiltonian is the interacting Rydberg Hamiltonian $\hat{\mathcal{H}}$,

\[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.\]

Following the atomic physics nomenclature, $\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/|\overrightarrow{\mathbf{r}_j} - \overrightarrow{\mathbf{r}_k}|^6$ describes the Rydberg interaction (van der Waals interaction) between atoms $j$ and $k$ where $\overrightarrow{\mathbf{r}_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. Sometimes, we also refer the states $|g\rangle$ and $|r\rangle$ as $|0\rangle$ and $|1\rangle$ as well in the analog mode.

Starting from an initial quantum state $| \psi_{\text{ini}} \rangle$, Bloqade simulates its time evolution under the Hamiltonian $\hat{\mathcal{H}}(t)$, given the qubit positions and the time-dependent profiles for $\Omega_j$, $\phi_j$, and $\Delta_j$. Bloqade then outputs the real-time-evolved state $| \psi(t) \rangle$, which can then be used for measuring different observables.

More specifically, here are the steps to program neutral-atom quantum computers using Bloqade:

The default units for various quantities are:

QuantityDefault Unit
Lengthμm
Timeμs
$\Omega$2π * MHz
$\phi$rad
$\Delta$2π * MHz

A Simple Example

Let's try a simple example of simulating quantum many-body dynamics governed by the Rydberg Hamiltonian.

We start by loading the Bloqade Module:

julia> using Bloqade

As one can see from the Rydberg Hamiltonian, the interactions between Rydberg atoms depend on their positions. Bloqade provides several built-in Lattices structures for specifying the atom positions. For instance, we can use the following codes to quickly generate a chain of 10 atoms in 1D:

julia> nsites = 10;
julia> atoms = generate_sites(ChainLattice(), nsites, scale = 5.74)10-element AtomList{1, Float64}: (0.0,) (5.74,) (11.48,) (17.22,) (22.96,) (28.700000000000003,) (34.44,) (40.18,) (45.92,) (51.660000000000004,)

We have set the distance between nearest-neighbor atoms to be 5.74 μm. Note that the default unit of length is μm as shown in the table above.

Let's set both $\Omega$ and $\Delta$ to be constants (and $\phi = 0$). Since all the variable parameters in the Hamiltonian are specified, we can now create an interacting Rydberg Hamiltonian by using rydberg_h:

julia> h = rydberg_h(atoms; Ω = 4 * 2π, Δ = 0)nqubits: 10
+
├─ [+] ∑ 2π ⋅ 8.627e5.0/|r_i-r_j|^6 n_i n_j
├─ [+] 2π ⋅ 2.0 ⋅ ∑ σ^x_i
└─ [-] 2π ⋅ 0.0 ⋅ ∑ n_i

To create more complicated waveforms for $\Omega$ and $\Delta$ and find the supported utilities, please refer to the Waveforms page.

Let's create an initial state with all the atoms in the ground state by using zero_state.

julia> reg = zero_state(10)ArrayReg{2, ComplexF64, Array...}
    active qubits: 10/10
    nlevel: 2

We are interested in measuring observables of the final quantum state of the Rydberg system starting from the initial state and evolving under the Rydberg Hamiltonian over some time duration. We can first create the problem and then directly simulate the time evolution.

julia> prob = SchrodingerProblem(reg, 1.6, h)SchrodingerProblem:
  register info:
    type: ArrayReg{2, ComplexF64, Matrix{ComplexF64}}
    storage size: 8 bytes

  time span (μs): (0.0, 1.6)

  equation:
    storage size: 183.836 KiB
    expression:
nqubits: 10
+
├─ [+] ∑ 2π ⋅ 8.627e5.0/|r_i-r_j|^6 n_i n_j
├─ [+] 2π ⋅ 2.0 ⋅ ∑ σ^x_i
└─ [-] 2π ⋅ 0.0 ⋅ ∑ n_i


    algorithm: CompositeAlgorithm{Tuple{Vern9, Rodas5{0, false, Nothing, typeof(OrdinaryDiffEq.DEFAULT_PRECS), Val{:forward}, true, nothing}}, AutoSwitch{Vern9, Rodas5{0, false, Nothing, typeof(OrdinaryDiffEq.DEFAULT_PRECS), Val{:forward}, true, nothing}, Rational{Int64}, Int64}}((Vern9(true), Rodas5{0, false, Nothing, typeof(OrdinaryDiffEq.DEFAULT_PRECS), Val{:forward}, true, nothing}(nothing, OrdinaryDiffEq.DEFAULT_PRECS)), AutoSwitch{Vern9, Rodas5{0, false, Nothing, typeof(OrdinaryDiffEq.DEFAULT_PRECS), Val{:forward}, true, nothing}, Rational{Int64}, Int64}(Vern9(true), Rodas5{0, false, Nothing, typeof(OrdinaryDiffEq.DEFAULT_PRECS), Val{:forward}, true, nothing}(nothing, OrdinaryDiffEq.DEFAULT_PRECS), 10, 3, 9//10, 9//10, 2, false, 5))
  options:
    save_everystep: false
    save_start: false
    save_on: false
    dense: false
julia> integrator = init(prob, Vern8());
julia> emulate!(prob);

Here, we have chosen the ODE-based solver (Vern8()) by using SchrodingerProblem and set the total evolution time to be 1.6 μs.

After simulating the time evolution and get the final state, we can measure the Rydberg population at each site for the final state:

julia> rydberg_populations = map(1:nsites) do i
           rydberg_density(prob.reg, i)
       end10-element Vector{Float64}:
 0.4219218363122977
 0.17593447277832852
 0.28413947232948267
 0.20000108257741664
 0.3057048351222039
 0.30570483512220425
 0.20000108257741708
 0.2841394723294831
 0.17593447277832852
 0.42192183631229796

prob.reg is the register storing the final state after the time evolution.

Benchmarks

Have Suggestions or Interested in Contributing?