Quantum Registers

Constructing quantum states

A quantum register is a quantum state or a batch of quantum states. Qubits in a Yao register can be active or inactive. Only active qubits are visible to quantum operators, which enables applying quantum operators on a subset of qubits. For example, Suppose we want to run a quantum Fourier transformation circuit of size 4 on qubits (1, 3, 5, 7) with the focus! function, we first set these qubits to active qubits the rest to inactive, then we apply the circuit on the active qubits, and finally we switch back to the original configuration with the relax! function.

Yao provides two types of quantum registers ArrayReg and BatchedArrayReg. Both use matrices as the storage. For example, for a quantum register with $a$ active qubits, $r$ remaining qubits and batch size $b$, the storage is as follows.

The first dimension of size $2^a$ is for active qubits, only this subset of qubits are allowed to interact with quantum operators. Since we reshaped the state vector into a matrix, applying a quantum operator can be conceptually represented as a matrix-matrix multiplication.

Various quantum states can be created with the following functions.

julia> using Yao
julia> reg = ArrayReg([0, 1, -1+0.0im, 0]) # a unnormalized Bell state |01⟩ - |10⟩ArrayReg{2, ComplexF64, Array...} active qubits: 2/2 nlevel: 2
julia> statevec(reg) # a quantum state is represented as a vector4-element Vector{ComplexF64}: 0.0 + 0.0im 1.0 + 0.0im -1.0 + 0.0im 0.0 + 0.0im
julia> print_table(reg)00 ₍₂₎ 0.0 + 0.0im 01 ₍₂₎ 1.0 + 0.0im 10 ₍₂₎ -1.0 + 0.0im 11 ₍₂₎ 0.0 + 0.0im
julia> reg_zero = zero_state(3)  # create a zero state |000⟩ArrayReg{2, ComplexF64, Array...}
    active qubits: 3/3
    nlevel: 2
julia> print_table(reg_zero)000 ₍₂₎ 1.0 + 0.0im 001 ₍₂₎ 0.0 + 0.0im 010 ₍₂₎ 0.0 + 0.0im 011 ₍₂₎ 0.0 + 0.0im 100 ₍₂₎ 0.0 + 0.0im 101 ₍₂₎ 0.0 + 0.0im 110 ₍₂₎ 0.0 + 0.0im 111 ₍₂₎ 0.0 + 0.0im
julia> reg_rand = rand_state(ComplexF32, 3)  # a random stateArrayReg{2, ComplexF32, Array...}
    active qubits: 3/3
    nlevel: 2
julia> reg_uniform = uniform_state(ComplexF32, 3)  # a uniform stateArrayReg{2, ComplexF32, Array...}
    active qubits: 3/3
    nlevel: 2
julia> print_table(reg_uniform)000 ₍₂₎ 0.35355f0 + 0.0f0im 001 ₍₂₎ 0.35355f0 + 0.0f0im 010 ₍₂₎ 0.35355f0 + 0.0f0im 011 ₍₂₎ 0.35355f0 + 0.0f0im 100 ₍₂₎ 0.35355f0 + 0.0f0im 101 ₍₂₎ 0.35355f0 + 0.0f0im 110 ₍₂₎ 0.35355f0 + 0.0f0im 111 ₍₂₎ 0.35355f0 + 0.0f0im
julia> reg_prod = product_state(bit"110")  # a product stateArrayReg{2, ComplexF64, Array...}
    active qubits: 3/3
    nlevel: 2
julia> bit"110"[3] # the bit string is in little-endian format1
julia> print_table(reg_prod)000 ₍₂₎ 0.0 + 0.0im 001 ₍₂₎ 0.0 + 0.0im 010 ₍₂₎ 0.0 + 0.0im 011 ₍₂₎ 0.0 + 0.0im 100 ₍₂₎ 0.0 + 0.0im 101 ₍₂₎ 0.0 + 0.0im 110 ₍₂₎ 1.0 + 0.0im 111 ₍₂₎ 0.0 + 0.0im
julia> reg_ghz = ghz_state(3)  # a GHZ stateArrayReg{2, ComplexF64, Array...}
    active qubits: 3/3
    nlevel: 2
julia> print_table(reg_ghz)000 ₍₂₎ 0.70711 - 0.0im 001 ₍₂₎ 0.0 + 0.0im 010 ₍₂₎ 0.0 + 0.0im 011 ₍₂₎ 0.0 + 0.0im 100 ₍₂₎ 0.0 + 0.0im 101 ₍₂₎ 0.0 + 0.0im 110 ₍₂₎ 0.0 + 0.0im 111 ₍₂₎ 0.70711 - 0.0im
julia> von_neumann_entropy(reg_ghz, (1, 3)) / log(2) # entanglement entropy between qubits (1, 3) and (2,)1.0000000000000229
julia> reg_rand3 = rand_state(3, nlevel=3)  # a random qutrit stateArrayReg{3, ComplexF64, Array...}
    active qudits: 3/3
    nlevel: 3
julia> reg_prod3 = product_state(dit"120;3") # a qudit product state, what follows ";" symbol denotes the number of levelsArrayReg{3, ComplexF64, Array...} active qudits: 3/3 nlevel: 3
julia> print_table(reg_prod3)000 ₍₃₎ 0.0 + 0.0im 001 ₍₃₎ 0.0 + 0.0im 002 ₍₃₎ 0.0 + 0.0im 010 ₍₃₎ 0.0 + 0.0im 011 ₍₃₎ 0.0 + 0.0im 012 ₍₃₎ 0.0 + 0.0im 020 ₍₃₎ 0.0 + 0.0im 021 ₍₃₎ 0.0 + 0.0im 022 ₍₃₎ 0.0 + 0.0im 100 ₍₃₎ 0.0 + 0.0im 101 ₍₃₎ 0.0 + 0.0im 102 ₍₃₎ 0.0 + 0.0im 110 ₍₃₎ 0.0 + 0.0im 111 ₍₃₎ 0.0 + 0.0im 112 ₍₃₎ 0.0 + 0.0im 120 ₍₃₎ 1.0 + 0.0im 121 ₍₃₎ 0.0 + 0.0im 122 ₍₃₎ 0.0 + 0.0im 200 ₍₃₎ 0.0 + 0.0im 201 ₍₃₎ 0.0 + 0.0im 202 ₍₃₎ 0.0 + 0.0im 210 ₍₃₎ 0.0 + 0.0im 211 ₍₃₎ 0.0 + 0.0im 212 ₍₃₎ 0.0 + 0.0im 220 ₍₃₎ 0.0 + 0.0im 221 ₍₃₎ 0.0 + 0.0im 222 ₍₃₎ 0.0 + 0.0im
julia> reg_batch = rand_state(3; nbatch=2)  # a batch of 2 random qubit statesBatchedArrayReg{2, ComplexF64, Transpose...}
    active qubits: 3/3
    nlevel: 2
    nbatch: 2
julia> print_table(reg_batch)000 ₍₂₎ -0.00131 - 0.2684im; 0.16813 + 0.14023im 001 ₍₂₎ 0.30648 - 0.29432im; 0.03173 - 0.23842im 010 ₍₂₎ 0.12485 - 0.09021im; -0.64855 + 0.18629im 011 ₍₂₎ 0.13148 - 0.35264im; 0.16267 - 0.47im 100 ₍₂₎ 0.16153 + 0.35565im; 0.02691 + 0.33801im 101 ₍₂₎ -0.20673 - 0.41949im; 0.06515 + 0.05127im 110 ₍₂₎ 0.2543 - 0.1087im; 0.16238 + 0.15649im 111 ₍₂₎ -0.17558 + 0.3216im; 0.05642 - 0.12514im
julia> reg_view = viewbatch(reg_batch, 1) # view the first state in the batchArrayReg{2, ComplexF64, SubArray...} active qubits: 3/3 nlevel: 2
julia> print_table(reg_view)000 ₍₂₎ -0.00131 - 0.2684im 001 ₍₂₎ 0.30648 - 0.29432im 010 ₍₂₎ 0.12485 - 0.09021im 011 ₍₂₎ 0.13148 - 0.35264im 100 ₍₂₎ 0.16153 + 0.35565im 101 ₍₂₎ -0.20673 - 0.41949im 110 ₍₂₎ 0.2543 - 0.1087im 111 ₍₂₎ -0.17558 + 0.3216im
julia> reg = rand_state(3; nlevel=4, nbatch=2)BatchedArrayReg{4, ComplexF64, Transpose...}
    active qudits: 3/3
    nlevel: 4
    nbatch: 2
julia> nqudits(reg) # the total number of qudits3
julia> nactive(reg) # the number of active qubits3
julia> nremain(reg) # the number of remaining qubits0
julia> nbatch(reg) # the batch size2
julia> nlevel(reg) # the number of levels of each qudit4
julia> basis(reg) # the basis of the register000 ₍₄₎:333 ₍₄₎
julia> focus!(reg, 1:2) # set on the first two qubits as activeBatchedArrayReg{4, ComplexF64, Transpose...} active qudits: 2/3 nlevel: 4 nbatch: 2
julia> nactive(reg) # the number of active qubits2
julia> basis(reg) # the basis of the register00 ₍₄₎:33 ₍₄₎
julia> relax!(reg) # set all qubits as activeBatchedArrayReg{4, ComplexF64, Transpose...} active qudits: 3/3 nlevel: 4 nbatch: 2
julia> nactive(reg) # the number of active qubits3
julia> reorder!(reg, (3,1,2)) # reorder the qubitsBatchedArrayReg{4, ComplexF64, Transpose...} active qudits: 3/3 nlevel: 4 nbatch: 2
julia> reg1 = product_state(bit"111");
julia> reg2 = ghz_state(3);
julia> fidelity(reg1, reg2) # the fidelity between two states0.7071067811865476
julia> tracedist(reg1, reg2) # the trace distance between two states0.7071067811865477

Arithmetic operations

The list of arithmetic operations for ArrayReg include

  • +
  • -
  • *
  • / (scalar)
  • adjoint
julia> reg1 = rand_state(3)ArrayReg{2, ComplexF64, Array...}
    active qubits: 3/3
    nlevel: 2
julia> reg2 = rand_state(3)ArrayReg{2, ComplexF64, Array...} active qubits: 3/3 nlevel: 2
julia> reg3 = reg1 + reg2 # additionArrayReg{2, ComplexF64, Array...} active qubits: 3/3 nlevel: 2
julia> normalize!(reg3) # normalize the stateArrayReg{2, ComplexF64, Array...} active qubits: 3/3 nlevel: 2
julia> isnormalized(reg3) # check if the state is normalizedtrue
julia> reg1 - reg2 # subtractionArrayReg{2, ComplexF64, Array...} active qubits: 3/3 nlevel: 2
julia> reg1 * 2 # scalar multiplicationArrayReg{2, ComplexF64, Array...} active qubits: 3/3 nlevel: 2
julia> reg1 / 2 # scalar divisionArrayReg{2, ComplexF64, Array...} active qubits: 3/3 nlevel: 2
julia> reg1' # adjointAdjointRegister{2, ArrayReg{2, ComplexF64, Matrix{ComplexF64}}}(ArrayReg{2, ComplexF64, Array...} active qubits: 3/3 nlevel: 2)
julia> reg1' * reg1 # inner product1.0 + 0.0im

Register operations

julia> reg0 = rand_state(3)ArrayReg{2, ComplexF64, Array...}
    active qubits: 3/3
    nlevel: 2
julia> append_qudits!(reg0, 2) # append 2 qubitsArrayReg{2, ComplexF64, Array...} active qubits: 5/5 nlevel: 2
julia> insert_qudits!(reg0, 2, 2) # insert 2 qubits at the 2nd positionArrayReg{2, ComplexF64, Array...} active qubits: 7/7 nlevel: 2

Comparing with using matrix multiplication for quantum simulation, using specialized instructions are much faster and memory efficient. These instructions are specified with the instruct! function.

julia> reg = zero_state(2)ArrayReg{2, ComplexF64, Array...}
    active qubits: 2/2
    nlevel: 2
julia> instruct!(reg, Val(:H), (1,)) # apply a Hadamard gate on the first qubitArrayReg{2, ComplexF64, Array...} active qubits: 2/2 nlevel: 2
julia> print_table(reg)00 ₍₂₎ 0.70711 + 0.0im 01 ₍₂₎ 0.70711 + 0.0im 10 ₍₂₎ 0.0 + 0.0im 11 ₍₂₎ 0.0 + 0.0im

Measurement

We use the measure! function returns the measurement outcome and collapses the state after the measurement. We also have some "cheating" version measure that does not collapse states to facilitate classical simulation.

julia> measure!(reg0, 1)  # measure the qubit, the state collapses0 ₍₂₎
julia> measure!(reg0) # measure all qubits0010000 ₍₂₎
julia> measure(reg0, 3) # measure the qubit at location 3, the state does not collapse (hacky)1-element Vector{DitStr{2, 1, Int64}}: 0 ₍₂₎
julia> reorder!(reg0, 7:-1:1) # reorder the qubitsArrayReg{2, ComplexF64, Array...} active qubits: 7/7 nlevel: 2
julia> measure!(reg0)0000100 ₍₂₎
julia> invorder!(reg0) # reverse the order of qubitsArrayReg{2, ComplexF64, Array...} active qubits: 7/7 nlevel: 2
julia> measure!(reg0)0010000 ₍₂₎
julia> measure!(RemoveMeasured(), reg0, 2:4) # remove the measured qubits000 ₍₂₎
julia> reg0ArrayReg{2, ComplexF64, Array...} active qubits: 4/4 nlevel: 2
julia> reg1 = ghz_state(3)ArrayReg{2, ComplexF64, Array...} active qubits: 3/3 nlevel: 2
julia> select!(reg1, bit"111") # post-select the |111⟩ stateArrayReg{2, ComplexF64, Array...} active qubits: 0/0 nlevel: 2
julia> isnormalized(reg1) # check if the state is normalizedfalse

Density matrices

julia> reg = rand_state(3)ArrayReg{2, ComplexF64, Array...}
    active qubits: 3/3
    nlevel: 2
julia> rho = density_matrix(reg) # the density matrix of the stateDensityMatrix{2, ComplexF64, Array...} active qubits: 3/3 nlevel: 2
julia> rand_density_matrix(3) # a random density matrixDensityMatrix{2, ComplexF64, Array...} active qubits: 3/3 nlevel: 2
julia> completely_mixed_state(3) # a completely mixed stateDensityMatrix{2, ComplexF64, Array...} active qubits: 3/3 nlevel: 2
julia> partial_tr(rho, 1) # partial trace on the first qubitDensityMatrix{2, ComplexF64, Array...} active qubits: 2/2 nlevel: 2
julia> purify(rho) # purify the stateArrayReg{2, ComplexF64, Array...} active qubits: 3/6 nlevel: 2
julia> von_neumann_entropy(rho) # von Neumann entropy5.624513604281401e-14
julia> mutual_information(rho, 1, 2) # mutual information between qubits 1 and 20.2659095304711605

API

The constructors and functions for quantum registers are listed below.

YaoArrayRegister.ArrayRegType
ArrayReg{D,T,MT<:AbstractMatrix{T}} <: AbstractArrayRegister{D}
ArrayReg{D}(raw)
ArrayReg(raw::AbstractVecOrMat; nlevel=2)
ArrayReg(r::ArrayReg)

Simulated full amplitude register type, it uses an array to represent corresponding one or a batch of quantum states. T is the numerical type for each amplitude, it is ComplexF64 by default.

Warning

ArrayReg constructor will not normalize the quantum state. If you need a normalized quantum state remember to use normalize!(register) on the register.

source
YaoArrayRegister.BatchedArrayRegType
BatchedArrayReg{D,T,MT<:AbstractMatrix{T}} <: AbstractArrayReg{D}
BatchedArrayReg(raw, nbatch; nlevel=2)
BatchedArrayReg{D}(raw, nbatch)

Simulated batched full amplitude register type, it uses an array to represent corresponding one or a batch of quantum states. T is the numerical type for each amplitude, it is ComplexF64 by default.

Warning

BatchedArrayReg constructor will not normalize the quantum state. If you need a normalized quantum state remember to use normalize!(register) on the register.

source
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.

source
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 qubits: 4/4
    nlevel: 2

julia> arrayreg(ComplexF32, bit"1010")
ArrayReg{2, ComplexF32, Array...}
    active qubits: 4/4
    nlevel: 2
source
YaoArrayRegister.product_stateFunction
product_state([T=ComplexF64], dit_str; nbatch=NoBatch(), no_transpose_storage=false)
product_state([T=ComplexF64], nbits::Int, val::Int; nbatch=NoBatch(), nlevel=2, no_transpose_storage=false)
product_state([T=ComplexF64], vector; nbatch=NoBatch(), nlevel=2, no_transpose_storage=false)

Create an ArrayReg of product state. The configuration can be specified with a dit string, which can be defined with @bit_str or @dit_str. Or equivalently, it can be specified explicitly with nbits, val and nlevel. See also zero_state, rand_state, uniform_state.

Examples

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

julia> measure(reg)
1×2 Matrix{BitBasis.DitStr64{3, 3}}:
 120 ₍₃₎  120 ₍₃₎

julia> product_state(bit"100"; nbatch=2);

julia> r1 = product_state(ComplexF32, bit"001"; nbatch=2);

julia> r2 = product_state(ComplexF32, [1, 0, 0]; nbatch=2);

julia> r3 = product_state(ComplexF32, 3, 0b001; nbatch=2);

julia> r1 ≈ r2   # because we read bit strings from right to left, vectors from left to right.
true

julia> r1 ≈ r3
true
source
YaoArrayRegister.zero_stateFunction
zero_state([T=ComplexF64], n::Int; nbatch::Int=NoBatch())

Create an AbstractArrayReg that initialized to state $|0\rangle^{\otimes n}$. See also product_state, rand_state, uniform_state and ghz_state.

Examples

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

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

julia> zero_state(ComplexF32, 4; nbatch=3)
BatchedArrayReg{2, ComplexF32, Transpose...}
    active qubits: 4/4
    nlevel: 2
    nbatch: 3
source
YaoArrayRegister.zero_state_likeFunction
zero_state_like(register, n) -> AbstractRegister

Create a register initialized to zero from an existing one.

Examples

julia> reg = rand_state(3; nbatch=2);

julia> zero_state_like(reg, 2)
BatchedArrayReg{2, ComplexF64, Array...}
    active qubits: 2/2
    nlevel: 2
    nbatch: 2
source
YaoArrayRegister.rand_stateFunction
rand_state([T=ComplexF64], n::Int; nbatch=NoBatch(), no_transpose_storage=false)

Create a random AbstractArrayReg with total number of qudits n.

Examples

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

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

julia> rand_state(ComplexF64, 4; nbatch=2)
BatchedArrayReg{2, ComplexF64, Transpose...}
    active qubits: 4/4
    nlevel: 2
    nbatch: 2
source
YaoArrayRegister.uniform_stateFunction
uniform_state([T=ComplexF64], n; nbatch=NoBatch(), no_transpose_storage=false)

Create a uniform state:

\[\frac{1}{\sqrt{2^n}} \sum_{k=0}^{2^{n}-1} |k\rangle.\]

This state can also be created by applying H (Hadmard gate) on $|00⋯00⟩$ state.

Example

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

julia> uniform_state(ComplexF32, 4; nbatch=2)
BatchedArrayReg{2, ComplexF32, Transpose...}
    active qubits: 4/4
    nlevel: 2
    nbatch: 2
source
YaoArrayRegister.ghz_stateFunction
ghz_state([T=ComplexF64], n::Int; nbatch::Int=NoBatch())

Create a GHZ state (or a cat state) that defined as

\[\frac{|0\rangle^{\otimes n} + |1\rangle^{\otimes n}}{\sqrt{2}}.\]

Examples

julia> ghz_state(4)
ArrayReg{2, ComplexF64, Array...}
    active qubits: 4/4
    nlevel: 2
source
YaoAPI.cloneFunction
clone(register, n)

Create an ArrayReg by cloning the original register for n times on batch dimension. This function is only for emulation.

Example

julia> clone(arrayreg(bit"101"; nbatch=3), 4)
BatchedArrayReg{2, ComplexF64, Array...}
    active qubits: 3/3
    nlevel: 2
    nbatch: 12
source

The following functions are for querying the properties of a quantum register.

YaoAPI.nquditsFunction
nqudits(register) -> Int

Returns the total number of qudits in register.

source
YaoAPI.nactiveFunction
nactive(register) -> Int

Returns the number of active qudits in register. Here, active qudits means the system qubits that operators can be applied on.

source
YaoAPI.nlevelFunction
nlevel(x)

Number of levels in each qudit.

Examples

julia> nlevel(X)
2
source
YaoAPI.focus!Function
focus!(register, locs) -> register
focus!(locs...) -> f(register) -> register

Set the active qubits to focused locations, usually used to execute a subroutine. If register is not provided, returns a lambda that takes a register as input.

Examples

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

julia> focus!(reg, (1,3,4))
ArrayReg{2, ComplexF64, Array...}
    active qubits: 3/5
    nlevel: 2

julia> measure(reg; nshots=3)
3-element Vector{DitStr{2, 3, Int64}}:
 111 ₍₂₎
 111 ₍₂₎
 111 ₍₂₎

julia> measure(apply(reg, put(3, 2=>X)); nshots=3)
3-element Vector{DitStr{2, 3, Int64}}:
 101 ₍₂₎
 101 ₍₂₎
 101 ₍₂₎

Here, we prepare a product state and only look at the qubits 1, 3 and 4. The measurement results are all ones. With the focued register, we can apply a block of size 3 on it, even though the number of qubits is 5.

source
YaoAPI.focusFunction
focus(f, register, locs)

Call a callable f under the context of focus. See also focus!.

Examples

To print the focused register

julia> r = arrayreg(bit"101100")
ArrayReg{2, ComplexF64, Array...}
    active qubits: 6/6
    nlevel: 2

julia> focus(x->(println(x);x), r, (1, 2));
ArrayReg{2, ComplexF64, Array...}
    active qubits: 2/6
    nlevel: 2
source
YaoAPI.relax!Function
relax!(register[, locs]; to_nactive=nqudits(register)) -> register
relax!(locs::Int...; to_nactive=nqudits(register)) -> f(register) -> register

Inverse transformation of focus!, where to_nactive is the number of active bits for target register. If the register is not provided, returns a lambda function that takes a register as input.

Examples

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

julia> focus!(reg, (1,3,4))
ArrayReg{2, ComplexF64, Array...}
    active qubits: 3/5
    nlevel: 2

julia> relax!(reg, (1,3,4))
ArrayReg{2, ComplexF64, Array...}
    active qubits: 5/5
    nlevel: 2
source
YaoArrayRegister.exchange_sysenvFunction
exchange_sysenv(reg::AbstractArrayReg) -> AbstractRegister

Exchange system (focused qubits) and environment (remaining qubits).

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

julia> focus!(reg, (2,4))
ArrayReg{2, ComplexF64, Array...}
    active qubits: 2/5
    nlevel: 2

julia> exchange_sysenv(reg)
ArrayReg{2, ComplexF64, Adjoint...}
    active qubits: 3/5
    nlevel: 2
source

The following functions are for querying the state of a quantum register.

YaoArrayRegister.stateFunction
state(register::AbstractArrayReg) -> Matrix

Returns the raw array storage of register. See also statevec.

source
state(ρ::DensityMatrix) -> Matrix

Return the raw state of density matrix ρ.

source
BitBasis.basisFunction
basis(ditstr) -> UnitRange{DitStr{D,N,T}}
basis(DitStr{D,N,T}) -> UnitRange{DitStr{D,N,T}}

Returns the UnitRange for basis in Hilbert Space of qudits.

source
basis(register) -> UnitRange

Returns an UnitRange of the all the bits in the Hilbert space of given register.

julia> collect(basis(rand_state(3)))
8-element Vector{DitStr{2, 3, Int64}}:
 000 ₍₂₎
 001 ₍₂₎
 010 ₍₂₎
 011 ₍₂₎
 100 ₍₂₎
 101 ₍₂₎
 110 ₍₂₎
 111 ₍₂₎
source
YaoArrayRegister.statevecFunction
statevec(r::ArrayReg) -> array

Return a state matrix/vector by droping the last dimension of size 1 (i.e. nactive(r) = nqudits(r)). See also state.

Warning

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

source
BitBasis.hypercubicFunction
hypercubic(A::Array) -> Array

get the hypercubic representation for an array.

source
hypercubic(r::ArrayReg) -> AbstractArray

Return the hypercubic representation (high dimensional tensor) of this register, only active qudits are considered. See also rank3 and state.

source
YaoAPI.viewbatchFunction
viewbatch(register, i::Int) -> AbstractRegister

Returns the i-th single register of a batched register. The returned instance is a view of the original register, i.e. inplace operation changes the original register directly.

Examples

julia> reg = zero_state(5; nbatch=2);

julia> apply!(viewbatch(reg, 2), put(5, 2=>X));

julia> measure(reg; nshots=3)
3×2 Matrix{DitStr{2, 5, Int64}}:
 00000 ₍₂₎  00010 ₍₂₎
 00000 ₍₂₎  00010 ₍₂₎
 00000 ₍₂₎  00010 ₍₂₎
source
YaoArrayRegister.transpose_storageFunction
transpose_storage(register) -> register

Transpose the register storage. Sometimes transposed storage provides better performance for batched simulation.

source

The following functions are for arithmetic operations on quantum registers.

YaoArrayRegister.AdjointArrayRegType
AdjointArrayReg{D,T,MT} = AdjointRegister{D,<:AbstractArrayReg{D,T,MT}}

Adjoint array register type, it is used to represent the bra in the Dirac notation.

source

We also have some faster inplace versions of arithematic operations

We also define the following functions for state normalization, and distance measurement.

LinearAlgebra.normalize!Function
normalize!(r::AbstractArrayReg)

Normalize the register r by its 2-norm. It changes the register directly.

Examples

The following code creates a normalized GHZ state.

julia> reg = product_state(bit"000") + product_state(bit"111");

julia> norm(reg)
1.4142135623730951

julia> isnormalized(reg)
false

julia> normalize!(reg);

julia> isnormalized(reg)
true
source
YaoAPI.fidelityFunction
fidelity(register1, register2) -> Real/Vector{<:Real}
fidelity'(pair_or_reg1, pair_or_reg2) -> (g1, g2)

Return the fidelity between two states. Calcuate the fidelity between r1 and r2, if r1 or r2 is not pure state (nactive(r) != nqudits(r)), the fidelity is calcuated by purification. See also: http://iopscience.iop.org/article/10.1088/1367-2630/aa6a4b/meta

Obtain the gradient with respect to registers and circuit parameters. For pair input ψ=>circuit, the returned gradient 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.

Definition

The fidelity of two quantum state for qudits is defined as:

\[F(ρ, σ) = tr(\sqrt{\sqrt{ρ}σ\sqrt{ρ}})\]

Note

This definition is different from the one in Wiki by a square.

Examples

julia> reg1 = uniform_state(3);

julia> reg2 = zero_state(3);

julia> fidelity(reg1, reg2)
0.35355339059327373

References

  • Jozsa R. Fidelity for mixed quantum states[J]. Journal of modern optics, 1994, 41(12): 2315-2323.
  • Nielsen M A, Chuang I. Quantum computation and quantum information[J]. 2002.
Note

The original definition of fidelity $F$ was from "transition probability", defined by Jozsa in 1994, it is the square of what we use here.

source
YaoAPI.tracedistFunction
tracedist(register1, register2)

Return the trace distance of register1 and register2.

Definition

Trace distance is defined as following:

\[\frac{1}{2} || A - B ||_{\rm tr}\]

It takes values between 0 and 1.

Examples

julia> reg1 = uniform_state(3);

julia> reg2 = zero_state(3);

julia> tracedist(reg1, reg2)
0.9354143466934852

References

  • https://en.wikipedia.org/wiki/Trace_distance
source

The following functions are for adding and reordering qubits in a quantum register.

YaoAPI.insert_qudits!Function
insert_qudits!(register, loc::Int, nqudits::Int) -> register
insert_qudits!(loc::Int, nqudits::Int) -> λ(register)

Insert qudits to given register in state |0>. i.e. |psi> -> join(|psi>, |0...>, |psi>), increased bits have higher indices.

Examples

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

julia> insert_qudits!(reg, 2, 2)
ArrayReg{2, ComplexF64, Array...}
    active qubits: 7/7
    nlevel: 2

julia> measure(reg; nshots=3)
3-element Vector{DitStr{2, 7, Int64}}:
 0110001 ₍₂₎
 0110001 ₍₂₎
 0110001 ₍₂₎
source
YaoAPI.insert_qubits!Function
insert_qubits!(register, loc::Int, nqubits::Int=1) -> register
insert_qubits!(loc::Int, nqubits::Int=1) -> λ(register)

Insert n qubits to given register in state |0>. It is an alias of insert_qudits! function.

source
YaoAPI.append_qudits!Function
append_qudits!(register, n::Int) -> register
append_qudits!(n::Int) -> λ(register)

Add n qudits to given register in state |0>. i.e. |psi> -> |000> ⊗ |psi>, increased bits have higher indices.

If only an integer is provided, then returns a lambda function.

Examples

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

julia> append_qudits!(reg, 2)
ArrayReg{2, ComplexF64, Array...}
    active qubits: 7/7
    nlevel: 2

julia> measure(reg; nshots=3)
3-element Vector{DitStr{2, 7, Int64}}:
 0001101 ₍₂₎
 0001101 ₍₂₎
 0001101 ₍₂₎

Note here, we read the bit string from right to left.

source
YaoAPI.append_qubits!Function
append_qubits!(register, n::Int) -> register
append_qubits!(n::Int) -> λ(register)

Add n qudits to given register in state |0>. It is an alias of append_qudits! function.

source
YaoAPI.reorder!Function
reorder!(reigster, orders)

Reorder the locations of register by input orders. For a 3-qubit register, an order (i, j, k) specifies the following reordering of qubits

  • move the first qubit go to i,
  • move the second qubit go to j,
  • move the third qubit go to k.
Note

The convention of reorder! is different from the permutedims function, one can use the sortperm function to relate the permutation order and the order in this function.

Examples

julia> reg = product_state(bit"010101");

julia> reorder!(reg, (1,4,2,5,3,6));

julia> measure(reg)
1-element Vector{DitStr{2, 6, Int64}}:
 000111 ₍₂₎
source
YaoAPI.invorder!Function
invorder!(register)

Inverse the locations of the register.

Examples

julia> reg = product_state(bit"010101")
ArrayReg{2, ComplexF64, Array...}
    active qubits: 6/6
    nlevel: 2

julia> measure(invorder!(reg); nshots=3)
3-element Vector{DitStr{2, 6, Int64}}:
 101010 ₍₂₎
 101010 ₍₂₎
 101010 ₍₂₎
source

The instruct! function is for applying quantum operators on a quantum register.

YaoAPI.instruct!Function
instruct!([nlevel=Val(2), ]state, operator, locs[, control_locs, control_configs, theta])

Unified interface for applying an operator to a quantum state. It modifies the state directly.

Arguments

  • nlevel is the number of levels in each qudit,
  • state is a vector or matrix representing the quantum state, where the first dimension is the active qubit dimension, the second is the batch dimension.
  • operator is a quantum operator, which can be Val(GATE_SYMBOL) or a matrix.
  • locs::Tuple is a tuple for specifying the locations this gate applied.
  • control_locs::Tuple and control_configs are tuples for specifying the control locations and control values.
  • theta::Real is the parameter for the gate, e.g. Val(:Rx) gate takes a real number of its parameter.
source

The following functions are for measurement and post-selection.

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

Measure current active qudits or qudits at locs. If the operator is not provided, it will measure on the computational basis and collapse to a product state. Otherwise, the quantum state collapse to the subspace corresponds to the resulting eigenvalue of the observable.

Arguments

  • postprocess is the postprocessing method, it can be
    • NoPostProcess() (default).
    • ResetTo(config), reset to result state to config. It can not be used if operator is provided, because measuring an operator in general does not return a product state.
    • RemoveMeasured(), remove the measured qudits from the register. It is also incompatible with the operator argument.
  • operator::AbstractBlock is the operator to measure.
  • register::AbstractRegister is the quantum state.
  • locs is the qubits to performance the measurement. If locs is not provided, all current active qudits are measured (regarding to active qudits,

see focus! and relax!).

Keyword arguments

  • rng is the random number generator.

Examples

The following example measures a random state on the computational basis and reset it to a certain bitstring value.

julia> reg = rand_state(3);

julia> measure!(ResetTo(bit"011"), reg)
110 ₍₂₎

julia> measure(reg; nshots=3)
3-element Vector{DitStr{2, 3, Int64}}:
 011 ₍₂₎
 011 ₍₂₎
 011 ₍₂₎

julia> measure!(RemoveMeasured(), reg, (1,2))
11 ₍₂₎

julia> reg  # removed qubits are not usable anymore
ArrayReg{2, ComplexF64, Array...}
    active qubits: 1/1
    nlevel: 2

Measuring an operator will project the state to the subspace associated with the returned eigenvalue.

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

julia> print_table(reg)
000 ₍₂₎   0.35355 + 0.0im
001 ₍₂₎   0.35355 + 0.0im
010 ₍₂₎   0.35355 + 0.0im
011 ₍₂₎   0.35355 + 0.0im
100 ₍₂₎   0.35355 + 0.0im
101 ₍₂₎   0.35355 + 0.0im
110 ₍₂₎   0.35355 + 0.0im
111 ₍₂₎   0.35355 + 0.0im

julia> measure!(repeat(3, Z, 1:3), reg)
-1.0 + 0.0im

julia> print_table(reg)
000 ₍₂₎   0.0 + 0.0im
001 ₍₂₎   0.5 + 0.0im
010 ₍₂₎   0.5 + 0.0im
011 ₍₂₎   0.0 + 0.0im
100 ₍₂₎   0.5 + 0.0im
101 ₍₂₎   0.0 + 0.0im
110 ₍₂₎   0.0 + 0.0im
111 ₍₂₎   0.5 + 0.0im

Here, we measured the parity operator, as a result, the resulting state collapsed to the subspace with either even or odd parity.

source
YaoAPI.measureFunction
measure([, operator], register[, locs]; nshots=1, rng=Random.GLOBAL_RNG) -> Vector{Int}

Measure a quantum state and return measurement results of qudits. This measurement function a cheating version of measure! that does not collapse the input state. It also does not need to recompute the quantum state for performing multiple shots measurement.

Arguments

  • operator::AbstractBlock is the operator to measure.
  • register::AbstractRegister is the quantum state.
  • locs is the qubits to performance the measurement. If locs is not provided, all current active qudits are measured (regarding to active qudits,

see focus! and relax!).

Keyword arguments

  • nshots::Int is the number of shots.
  • rng is the random number generator.

Examples

julia> reg = product_state(bit"110")
ArrayReg{2, ComplexF64, Array...}
    active qubits: 3/3
    nlevel: 2

julia> measure(reg; nshots=3)
3-element Vector{DitStr{2, 3, Int64}}:
 110 ₍₂₎
 110 ₍₂₎
 110 ₍₂₎

julia> measure(reg, (2,3); nshots=3)
3-element Vector{DitStr{2, 2, Int64}}:
 11 ₍₂₎
 11 ₍₂₎
 11 ₍₂₎

The following example switches to the X basis for measurement.

julia> reg = apply!(product_state(bit"100"), repeat(3, H, 1:3))
ArrayReg{2, ComplexF64, Array...}
    active qubits: 3/3
    nlevel: 2

julia> measure(repeat(3, X, 1:3), reg; nshots=3)
3-element Vector{ComplexF64}:
 -1.0 + 0.0im
 -1.0 + 0.0im
 -1.0 + 0.0im

julia> reg = apply!(product_state(bit"101"), repeat(3, H, 1:3))
ArrayReg{2, ComplexF64, Array...}
    active qubits: 3/3
    nlevel: 2

julia> measure(repeat(3, X, 1:3), reg; nshots=3)
3-element Vector{ComplexF64}:
 1.0 - 0.0im
 1.0 - 0.0im
 1.0 - 0.0im
source
YaoAPI.select!Function
select!(dest::AbstractRegister, src::AbstractRegister, bits::Integer...) -> AbstractRegister
select!(register::AbstractRegister, bits::Integer...) -> register
select!(b::Integer) -> f(register)

select a subspace of given quantum state based on input eigen state bits. See also select for the non-inplace version. If the register is not provided, it returns a lambda expression that takes a register as the input.

Examples

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

julia> select!(reg, bit"111")
ArrayReg{2, ComplexF64, Array...}
    active qubits: 0/0
    nlevel: 2

julia> norm(reg)
0.7071067811865476

The selection only works on the activated qubits, for example

julia> reg = focus!(ghz_state(3), (1, 2))
ArrayReg{2, ComplexF64, Array...}
    active qubits: 2/3
    nlevel: 2

julia> select!(reg, bit"11")
ArrayReg{2, ComplexF64, Array...}
    active qubits: 0/1
    nlevel: 2

julia> statevec(reg)
1×2 Matrix{ComplexF64}:
 0.0+0.0im  0.707107+0.0im
Tip

Developers should overload select!(r::RegisterType, bits::NTuple{N, <:Integer}) and do not assume bits has specific number of bits (e.g Int64), or it will restrict the its maximum available number of qudits.

source
YaoAPI.collapseto!Function
collapseto!(register, config)

Set the register to bit string literal bit_str (or an equivalent integer). About bit string literal, see more in @bit_str. This interface is only for emulation.

Examples

The following code collapse a random state to a certain state.

julia> measure(collapseto!(rand_state(3), bit"001"); nshots=3)
3-element Vector{DitStr{2, 3, Int64}}:
 001 ₍₂₎
 001 ₍₂₎
 001 ₍₂₎
source
YaoAPI.probsFunction
probs(register) -> Vector

Returns the probability distribution of computation basis, aka $|<x|ψ>|^2$.

Examples

julia> reg = product_state(bit"101");

julia> reg |> probs
8-element Vector{Float64}:
 0.0
 0.0
 0.0
 0.0
 0.0
 1.0
 0.0
 0.0
source
YaoArrayRegister.most_probableFunction
most_probable(reg::ArrayReg{D, T} where T, n::Int64) -> Any

Find n most probable qubit configurations in a quantum register and return these configurations as a vector of DitStr instances.

Example

julia> most_probable(ghz_state(3), 2)
2-element Vector{DitStr{2, 3, Int64}}:
 000 ₍₂₎
 111 ₍₂₎
source

The following functions are for density matrices.

YaoAPI.DensityMatrixType
DensityMatrix{D,T,MT<:AbstractMatrix{T}} <: AbstractRegister{D}
DensityMatrix{D}(state::AbstractMatrix)
DensityMatrix(state::AbstractMatrix; nlevel=2)

Density matrix type, where state is a matrix. Type parameter D is the number of levels, it can also be specified by a keyword argument nlevel.

source
YaoAPI.density_matrixFunction
density_matrix(register_or_rho[, locations])

Returns the reduced density matrix for qubits at locations (default: all qubits).

Examples

The following code gets the single site reduce density matrix for the GHZ state.

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

julia> density_matrix(reg, (2,)).state
2×2 Matrix{ComplexF64}:
 0.5+0.0im  0.0+0.0im
 0.0-0.0im  0.5+0.0im
source
YaoArrayRegister.rand_density_matrixFunction
rand_density_matrix([T=ComplexF64], n::Int; nlevel::Int=2, pure::Bool=false)

Generate a random density matrix by partial tracing half of the pure state.

Note

The generated density matrix is not strict hermitian due to rounding error. If you need to check hermicity, do not use ishermitian consider using isapprox(dm.state, dm.state') or explicit mark it as Hermitian.

source
YaoAPI.partial_trFunction
partial_tr(ρ, locs) -> DensityMatrix

Return a density matrix which is the partial traced on locs.

source
YaoAPI.purifyFunction
purify(r::DensityMatrix; nbit_env::Int=nactive(r)) -> ArrayReg

Get a purification of target density matrix.

Examples

The following example shows how to measure a local operator on the register, reduced density matrix and the purified register. Their results should be consistent.

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

julia> r = density_matrix(reg, (2,));

julia> preg = purify(r)
ArrayReg{2, ComplexF64, Array...}
    active qubits: 1/2
    nlevel: 2

julia> isapprox(expect(Z + Y, preg), 0.0; atol=1e-10)
true

julia> isapprox(expect(Z + Y, r), 0.0; atol=1e-10)
true

julia> isapprox(expect(put(3, 2=>(Z + Y)), reg), 0.0; atol=1e-10)
true
source
YaoArrayRegister.von_neumann_entropyFunction
von_neumann_entropy(reg::AbstractArrayReg, part)
von_neumann_entropy(ρ::DensityMatrix)

The entanglement entropy between part and the rest part in quantum state reg. If the input is a density matrix, it returns the entropy of a mixed state.

Example

The Von Neumann entropy of any segment of GHZ state is $\log 2$.

julia> von_neumann_entropy(ghz_state(3), (1,2))
0.6931471805599612
source
von_neumann_entropy(rho) -> Real

Return the von-Neumann entropy for the input density matrix:

\[-{\rm Tr}(\rho\ln\rho)\]

source
YaoArrayRegister.mutual_informationFunction
mutual_information(register_or_rho, part1, part2)

Returns the mutual information between subsystems part1 and part2 of the input quantum register or density matrix:

\[S(\rho_A) + S(\rho_B) - S(\rho_{AB})\]

Example

The mutual information of a GHZ state of any two disjoint parts is always equal to $\log 2$.

julia> mutual_information(ghz_state(4), (1,), (3,4))
0.6931471805599132
source