Registers and Observables

Bloqade follows the register interface in Yao. It uses a register to represent a device and its internal quantum state. For Bloqade, the most commonly used register types are ArrayReg and SubspaceArrayReg. They both use a dense array to store the corresponding quantum state. The only difference is that SubspaceArrayReg also stores a subspace object. In this section, we will only cover how to create registers and perform operations on them in the full Hilbert space. For subspace operations, please refer to the subspace page. We will also discuss a few convenient wrappers on commonly used observables for Rydberg systems.

In the analog mode, we use the states $|g\rangle$ (ground state) and $|r\rangle$ (Rydberg state) to encode a qubit. To be consistent with the standard language of qubits, we refer the states $|g\rangle$ and $|r\rangle$ as $|0\rangle$ and $|1\rangle$ here.

Basic Interface

To create a register with all atoms being in the ground state $| 00..00 \rangle$, we can use the function zero_state by specifying the number of qubits:

julia> using Bloqade
julia> zero_state(5) # creates a 5-qubit registerArrayReg{2, ComplexF64, Array...} active qubits: 5/5 nlevel: 2

To create a more general product state in the computational basis, one can use the product_state function by inputting its bitstring:

julia> product_state(bit"10011")ArrayReg{2, ComplexF64, Array...}
    active qubits: 5/5
    nlevel: 2

where bit"10011" is a special Julia string literal defined for bitstrings.

One can also construct the ArrayReg or SubspaceArrayReg directly from arrays, e.g.:

julia> ArrayReg(rand(ComplexF64, 2^5))ArrayReg{2, ComplexF64, Array...}
    active qubits: 5/5
    nlevel: 2

For a subspace register, one can create in the following way:

julia> space = Subspace(5, [0, 2, 3, 7])5-qubits 4-elements Subspace{Vector{Int64}}:
──┬──
 1│ 0
 2│ 2
 3│ 3
 4│ 7
julia> state = rand(ComplexF64, length(space))4-element Vector{ComplexF64}: 0.19003474468804527 + 0.9934601979826159im 0.9600586895279266 + 0.8130497158020606im 0.4105301227138456 + 0.1633900195329373im 0.12133745950396868 + 0.7794653644269897im
julia> reg = SubspaceArrayReg(state, space)SubspaceArrayReg{Vector{ComplexF64}, Subspace{Vector{Int64}}}(5, ComplexF64[0.19003474468804527 + 0.9934601979826159im, 0.9600586895279266 + 0.8130497158020606im, 0.4105301227138456 + 0.1633900195329373im, 0.12133745950396868 + 0.7794653644269897im], Subspace{Vector{Int64}}(5, Dict(0 => 1, 7 => 4, 2 => 2, 3 => 3), [0, 2, 3, 7]))

Here, $[0, 2, 3, 7]$ are base-10 integer representations of the corresponding states in bitstrings. For a more detailed guide on how to work in the subspace, please see subspace.

Operations on Registers

You can perform various operations on registers via the standard Yao register interface. This includes, e.g.,

  • applying operators on quantum states by using apply!,
  • measuring bitstrings with a projection on the quantum state by using measure!,
  • calculating the expectation value of certain observables by using expect,
  • inspecting the internal state of the register by using statevec.
julia> reg = rand_state(3)ArrayReg{2, ComplexF64, Array...}
    active qubits: 3/3
    nlevel: 2
julia> measure(reg; nshots=5)5-element Vector{BitBasis.BitStr64{3}}: 101 ₍₂₎ 101 ₍₂₎ 111 ₍₂₎ 110 ₍₂₎ 101 ₍₂₎
julia> expect(put(3,1=>X), reg)-0.014626310294176395 + 0.0im
julia> statevec(reg)8-element Vector{ComplexF64}: -0.12795344922154667 - 0.08457005293516812im -0.08744131172565585 - 0.016004999784909796im -0.0412800452020803 - 0.29921787459597277im 0.09554759889657362 - 0.09522824853250164im 0.045022292959879084 - 0.206850837143405im -0.24585230747505457 - 0.6238142804968493im 0.16279764308163583 + 0.48659713340621297im -0.13484077504825678 - 0.28857714670560924im
julia> apply!(reg, put(1=>X))ArrayReg{2, ComplexF64, Array...} active qubits: 3/3 nlevel: 2

For a more detailed introduction of the register interface, please refer to Yao:Registers.

Convenient Wrappers

Bloqade also provides a few convenient wrappers on some commonly used observables for Rydberg systems, including the Rydberg density and two-point correlation functions:

Bloqade.rydberg_densityFunction
rydberg_density(reg, i::Int) -> Real

Calculates the rydberg density at site i.

\[\langle n_i \rangle\]

source
rydberg_density(reg) -> Vector

Return the rydberg density at each site.

source

For example, if we want to measure the Rydberg density at each site or at a specific site, we can use the code below:

julia> reg = rand_state(10)ArrayReg{2, ComplexF64, Array...}
    active qubits: 10/10
    nlevel: 2
julia> n_each = rydberg_density(reg)10-element Vector{Float64}: 0.49542377949785943 0.492975412926783 0.5038798806195967 0.4748910463069969 0.5100344675717324 0.48553824589087424 0.5090129740163696 0.5080893070172974 0.49899769866721133 0.525692681048678
julia> n_2 = rydberg_density(reg, 2)0.492975412926783

To access the two-point correlation functions, we can use the rydberg_corr function below:

Bloqade.rydberg_corrFunction
rydberg_corr([op=Op.n], reg) -> Matrix

Calculates the rydberg correlation matrix.

\[\langle \text{op}_i \text{op}_j \rangle\]

here op can be Op.n, X or Y.

Arguments

  • op: the correlation function, default is Op.n.
  • reg: required, the register object.
source

This function will output a matrix that stores the correlation function for each pair of sites:

julia> nn_corr = rydberg_corr(Op.n, reg)10×10 Matrix{ComplexF64}:
 0.495424+0.0im  0.249148+0.0im  …   0.24381+0.0im  0.267926+0.0im
 0.249148+0.0im  0.492975+0.0im     0.242645+0.0im  0.251374+0.0im
 0.239581+0.0im   0.24587+0.0im     0.235012+0.0im  0.266382+0.0im
 0.245121+0.0im  0.233426+0.0im     0.240494+0.0im  0.243561+0.0im
 0.245145+0.0im  0.258432+0.0im     0.248234+0.0im  0.275111+0.0im
 0.256351+0.0im  0.236644+0.0im  …  0.249293+0.0im   0.26052+0.0im
  0.25573+0.0im  0.249327+0.0im     0.245111+0.0im  0.267824+0.0im
 0.252525+0.0im  0.244574+0.0im     0.259633+0.0im   0.26148+0.0im
  0.24381+0.0im  0.242645+0.0im     0.498998+0.0im  0.268522+0.0im
 0.267926+0.0im  0.251374+0.0im     0.268522+0.0im  0.525693+0.0im

It is worth mentioning that besides Op.n, other single-site operators including the Pauli operators X, Y and Z can also be used.

Create General Observables using Operator Expressions

Bloqade makes use of Yao's block system to represent operator expressions. For example, one can construct the Rydberg correlation operator as:

julia> corr(n, i, j) = chain(n, put(i=>Op.n), put(j=>Op.n))corr (generic function with 1 method)

You can thus create any kinds of quantum operators in this way and use it with the expect or measure function, e.g.:

julia> reg = rand_state(10)ArrayReg{2, ComplexF64, Array...}
    active qubits: 10/10
    nlevel: 2
julia> corr_XY = chain(10, put(2=>Op.X), put(4=>Op.Y))nqubits: 10 chain ├─ put on (2) │ └─ X └─ put on (4) └─ Y
julia> expect(corr_XY, reg)0.03067597209141073 + 6.911788849595091e-19im

Because the Hamiltonian is also an operator expression, it can be used as an observable too:

julia> r = rand_state(5)ArrayReg{2, ComplexF64, Array...}
    active qubits: 5/5
    nlevel: 2
julia> pos = [(i, ) for i in 1:5]5-element Vector{Tuple{Int64}}: (1,) (2,) (3,) (4,) (5,)
julia> h = rydberg_h(pos; Ω=2π*0.1)nqubits: 5 + ├─ [+] ∑ 2π ⋅ 8.627e6.0/|r_i-r_j|^6 n_i n_j └─ [+] 2π ⋅ 0.05 ⋅ ∑ σ^x_i
julia> expect(h, r)5.281770633673039e6 + 5.82076470356796e-11im

Please refer to the Hamiltonians to see other operators that are supported in building the Hamiltonian.

References

YaoArrayRegister.arrayregFunction
arrayreg(state; nbatch::Union{Integer,NoBatch}=NoBatch(), nlevel::Integer=2)

Create an array register, if nbatch is a integer, it will return a BatchedArrayReg.

arrayreg([T=ComplexF64], bit_str; nbatch=NoBatch())

Construct an array register from bit string literal. For bit string literal please read @bit_str.

Examples

julia> arrayreg(bit"1010")
ArrayReg{2, ComplexF64, Array...}
    active qudits: 4/4
    nlevel: 2

julia> arrayreg(ComplexF32, bit"1010")
ArrayReg{2, ComplexF32, Array...}
    active qudits: 4/4
    nlevel: 2
YaoAPI.apply!Function
apply!(register, block)

Apply a block (of quantum circuit) to a quantum register.

YaoAPI.measure!Function
measure!([postprocess,] [operator, ]register[, locs]; rng=Random.GLOBAL_RNG)

Measure current active qudits or qudits at locs. After measure and collapse,

* do nothing if postprocess is `NoPostProcess`
* reset to result state to `postprocess.config` if `postprocess` is `ResetTo`.
* remove the qubit if `postprocess` is `RemoveMeasured`
YaoAPI.measureFunction
measure([, operator], register[, locs]; nshots=1, rng=Random.GLOBAL_RNG) -> Vector{Int}

Return measurement results of qudits in locs. If locs is not provided, all current active qudits are measured (regarding to active qudits, see focus! and relax!).

YaoAPI.expectFunction
expect(op::AbstractBlock, reg) -> Vector
expect(op::AbstractBlock, reg => circuit) -> Vector
expect(op::AbstractBlock, density_matrix) -> Vector

Get the expectation value of an operator, the second parameter can be a register reg or a pair of input register and circuit reg => circuit.

expect'(op::AbstractBlock, reg=>circuit) -> Pair expect'(op::AbstractBlock, reg) -> AbstracRegister

Obtain the gradient with respect to registers and circuit parameters. For pair input, the second return value is a pair of gψ=>gparams, with the gradient of input state and gparams the gradients of circuit parameters. For register input, the return value is a register.

Note

For batched register, expect(op, reg=>circuit) returns a vector of size number of batch as output. However, one can not differentiate over a vector loss, so expect'(op, reg=>circuit) accumulates the gradient over batch, rather than returning a batched gradient of parameters.

YaoArrayRegister.statevecFunction
statevec(r::ArrayReg) -> array

Return a state matrix/vector by droping the last dimension of size 1. See also state.

Warning

statevec is not type stable. It may cause performance slow down.

YaoArrayRegister.zero_stateFunction
zero_state([T=ComplexF64], n::Int; nbatch::Int=1)

Create an AbstractArrayReg with total number of bits n. See also product_state, rand_state, uniform_state.

Examples

julia> zero_state(4)
ArrayReg{2, ComplexF64, Array...}
    active qudits: 4/4
    nlevel: 2

julia> zero_state(ComplexF32, 4)
ArrayReg{2, ComplexF32, Array...}
    active qudits: 4/4
    nlevel: 2

julia> zero_state(ComplexF32, 4; nbatch=3)
BatchedArrayReg{2, ComplexF32, Transpose...}
    active qudits: 4/4
    nlevel: 2
    nbatch: 3
zero_state([T=ComplexF64], n::Int, subspace)

Create a SubspaceArrayReg in zero state in given subspace.

Arguments

  • T: optional, element type, default is ComplexF64.
  • n: required, number of atoms (qubits).
  • subspace: required, the subspace of rydberg state.
source
YaoArrayRegister.rand_stateFunction
rand_state([T=ComplexF64], n::Int; nbatch=1, no_transpose_storage=false)

Create a random AbstractArrayReg with total number of qudits n.

Examples

julia> rand_state(4)
ArrayReg{2, ComplexF64, Array...}
    active qudits: 4/4
    nlevel: 2

julia> rand_state(ComplexF64, 4)
ArrayReg{2, ComplexF64, Array...}
    active qudits: 4/4
    nlevel: 2

julia> rand_state(ComplexF64, 4; nbatch=2)
BatchedArrayReg{2, ComplexF64, Transpose...}
    active qudits: 4/4
    nlevel: 2
    nbatch: 2
rand_state(subspace)

Create a random state in the given subspace.

source
YaoArrayRegister.product_stateFunction
product_state([T=ComplexF64], bit_str; nbatch=NoBatch())

Create an ArrayReg with bit string literal defined with @bit_str. See also zero_state, rand_state, uniform_state.

Examples

julia> product_state(bit"100"; nbatch=2)
BatchedArrayReg{2, ComplexF64, Transpose...}
    active qudits: 3/3
    nlevel: 2
    nbatch: 2

julia> r1 = product_state(ComplexF32, bit"100"; nbatch=2)
BatchedArrayReg{2, ComplexF32, Transpose...}
    active qudits: 3/3
    nlevel: 2
    nbatch: 2

julia> r2 = product_state(ComplexF32, [0, 0, 1]; nbatch=2)
BatchedArrayReg{2, ComplexF32, Transpose...}
    active qudits: 3/3
    nlevel: 2
    nbatch: 2

julia> r1 ≈ r2   # because we read bit strings from right to left, vectors from left to right.
true
product_state([T=ComplexF64], total::Int, bit_config::Integer; nbatch=1, no_transpose_storage=false)

Create an ArrayReg with bit configuration bit_config, total number of bits total. See also zero_state, rand_state, uniform_state.

Examples

julia> product_state(4, 3; nbatch=2)
BatchedArrayReg{2, ComplexF64, Transpose...}
    active qudits: 4/4
    nlevel: 2
    nbatch: 2

julia> product_state(4, 0b1001; nbatch=2)
BatchedArrayReg{2, ComplexF64, Transpose...}
    active qudits: 4/4
    nlevel: 2
    nbatch: 2

julia> product_state(ComplexF32, 4, 0b101)
ArrayReg{2, ComplexF32, Array...}
    active qudits: 4/4
    nlevel: 2
Warning

This interface will not check whether the number of required digits for the bit configuration matches the total number of bits.

product_state(config, subspace)

Create a product state of given config from subspace.

source