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 register
ArrayReg{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_density
— Functionrydberg_density(reg, i::Int) -> Real
Calculates the rydberg density at site i
.
\[\langle n_i \rangle\]
rydberg_density(reg) -> Vector
Return the rydberg density at each site.
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_corr
— Functionrydberg_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 isOp.n
.reg
: required, the register object.
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.arrayreg
— Functionarrayreg(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!
— Functionapply!(register, block)
Apply a block (of quantum circuit) to a quantum register.
YaoAPI.measure!
— Functionmeasure!([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.measure
— FunctionYaoAPI.expect
— Functionexpect(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 gψ
the gradient of input state and gparams
the gradients of circuit parameters. For register input, the return value is a register.
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.statevec
— Functionstatevec(r::ArrayReg) -> array
Return a state matrix/vector by droping the last dimension of size 1. See also state
.
statevec
is not type stable. It may cause performance slow down.
YaoArrayRegister.zero_state
— Functionzero_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 isComplexF64
.n
: required, number of atoms (qubits).subspace
: required, the subspace of rydberg state.
YaoArrayRegister.rand_state
— Functionrand_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.
YaoArrayRegister.product_state
— Functionproduct_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
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
.
YaoSubspaceArrayReg.SubspaceArrayReg
— TypeSubspaceArrayReg <: AbstractRegister{2}
SubspaceArrayReg(state, subspace)
Type for registers in a subspace. The subspace must be a Subspace
.
YaoSubspaceArrayReg.set_zero_state!
— Functionset_zero_state!(register)
Set the given register to |00...00⟩.
YaoBlocks.ConstGate.X
— ConstantX
XGate <: ConstantGate{1,2}
Pauli X gate. X
is the instance of XGate
.
YaoBlocks.ConstGate.Y
— ConstantY
YGate <: ConstantGate{1,2}
Pauli Y gate. Y
is the instance of YGate
.
YaoBlocks.ConstGate.Z
— ConstantZ
ZGate <: ConstantGate{1,2}
Pauli Z gate. Z
is the instance of YGate
.