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 Yaojulia> reg = ArrayReg([0, 1, -1+0.0im, 0]) # a unnormalized Bell state |01⟩ - |10⟩ArrayReg{2, ComplexF64, Array...} active qubits: 2/2 nlevel: 2julia> 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.0imjulia> 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: 2julia> 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: 2julia> 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: 2julia> bit"110"[3] # the bit string is in little-endian format1julia> 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: 2julia> 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.0imjulia> 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: 3julia> 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: 3julia> 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: 2julia> 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.12514imjulia> reg_view = viewbatch(reg_batch, 1) # view the first state in the batchArrayReg{2, ComplexF64, SubArray...} active qubits: 3/3 nlevel: 2julia> 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: 2julia> nqudits(reg) # the total number of qudits3julia> nactive(reg) # the number of active qubits3julia> nremain(reg) # the number of remaining qubits0julia> nbatch(reg) # the batch size2julia> nlevel(reg) # the number of levels of each qudit4julia> 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: 2julia> nactive(reg) # the number of active qubits2julia> 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: 2julia> nactive(reg) # the number of active qubits3julia> reorder!(reg, (3,1,2)) # reorder the qubitsBatchedArrayReg{4, ComplexF64, Transpose...} active qudits: 3/3 nlevel: 4 nbatch: 2julia> reg1 = product_state(bit"111");julia> reg2 = ghz_state(3);julia> fidelity(reg1, reg2) # the fidelity between two states0.7071067811865476julia> 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: 2julia> reg2 = rand_state(3)ArrayReg{2, ComplexF64, Array...} active qubits: 3/3 nlevel: 2julia> reg3 = reg1 + reg2 # additionArrayReg{2, ComplexF64, Array...} active qubits: 3/3 nlevel: 2julia> normalize!(reg3) # normalize the stateArrayReg{2, ComplexF64, Array...} active qubits: 3/3 nlevel: 2julia> isnormalized(reg3) # check if the state is normalizedtruejulia> reg1 - reg2 # subtractionArrayReg{2, ComplexF64, Array...} active qubits: 3/3 nlevel: 2julia> reg1 * 2 # scalar multiplicationArrayReg{2, ComplexF64, Array...} active qubits: 3/3 nlevel: 2julia> reg1 / 2 # scalar divisionArrayReg{2, ComplexF64, Array...} active qubits: 3/3 nlevel: 2julia> 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: 2julia> append_qudits!(reg0, 2) # append 2 qubitsArrayReg{2, ComplexF64, Array...} active qubits: 5/5 nlevel: 2julia> 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: 2julia> instruct!(reg, Val(:H), (1,)) # apply a Hadamard gate on the first qubitArrayReg{2, ComplexF64, Array...} active qubits: 2/2 nlevel: 2julia> 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: 2julia> measure!(reg0)0000100 ₍₂₎julia> invorder!(reg0) # reverse the order of qubitsArrayReg{2, ComplexF64, Array...} active qubits: 7/7 nlevel: 2julia> measure!(reg0)0010000 ₍₂₎julia> measure!(RemoveMeasured(), reg0, 2:4) # remove the measured qubits000 ₍₂₎julia> reg0ArrayReg{2, ComplexF64, Array...} active qubits: 4/4 nlevel: 2julia> reg1 = ghz_state(3)ArrayReg{2, ComplexF64, Array...} active qubits: 3/3 nlevel: 2julia> select!(reg1, bit"111") # post-select the |111⟩ stateArrayReg{2, ComplexF64, Array...} active qubits: 0/0 nlevel: 2julia> isnormalized(reg1) # check if the state is normalizedfalse
Density matrices
julia> reg = rand_state(3)ArrayReg{2, ComplexF64, Array...} active qubits: 3/3 nlevel: 2julia> rho = density_matrix(reg) # the density matrix of the stateDensityMatrix{2, ComplexF64, Array...} active qubits: 3/3 nlevel: 2julia> rand_density_matrix(3) # a random density matrixDensityMatrix{2, ComplexF64, Array...} active qubits: 3/3 nlevel: 2julia> completely_mixed_state(3) # a completely mixed stateDensityMatrix{2, ComplexF64, Array...} active qubits: 3/3 nlevel: 2julia> partial_tr(rho, 1) # partial trace on the first qubitDensityMatrix{2, ComplexF64, Array...} active qubits: 2/2 nlevel: 2julia> purify(rho) # purify the stateArrayReg{2, ComplexF64, Array...} active qubits: 3/6 nlevel: 2julia> von_neumann_entropy(rho) # von Neumann entropy5.624513604281401e-14julia> mutual_information(rho, 1, 2) # mutual information between qubits 1 and 20.2659095304711605
API
The constructors and functions for quantum registers are listed below.
YaoAPI.AbstractRegister — TypeAbstractRegister{D}Abstract type for quantum registers. Type parameter D is the number of levels in each qudit. For qubits, D = 2.
Required methods
Optional methods
YaoArrayRegister.AbstractArrayReg — TypeAbstractArrayRegAbstract type for quantum registers that are represented by an array.
YaoArrayRegister.ArrayReg — TypeArrayReg{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.
ArrayReg constructor will not normalize the quantum state. If you need a normalized quantum state remember to use normalize!(register) on the register.
YaoArrayRegister.BatchedArrayReg — TypeBatchedArrayReg{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.
BatchedArrayReg constructor will not normalize the quantum state. If you need a normalized quantum state remember to use normalize!(register) on the register.
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 qubits: 4/4
nlevel: 2
julia> arrayreg(ComplexF32, bit"1010")
ArrayReg{2, ComplexF32, Array...}
active qubits: 4/4
nlevel: 2YaoArrayRegister.product_state — Functionproduct_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
trueYaoArrayRegister.zero_state — Functionzero_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: 3YaoArrayRegister.zero_state_like — Functionzero_state_like(register, n) -> AbstractRegisterCreate 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: 2YaoArrayRegister.rand_state — Functionrand_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: 2YaoArrayRegister.uniform_state — Functionuniform_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: 2YaoArrayRegister.ghz_state — Functionghz_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: 2YaoAPI.clone — Functionclone(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: 12The following functions are for querying the properties of a quantum register.
YaoAPI.nqudits — Functionnqudits(register) -> IntReturns the total number of qudits in register.
YaoAPI.nqubits — Functionnqubits(register) -> IntReturns the (total) number of qubits. See nactive, nremain for more details.
YaoAPI.nactive — Functionnactive(register) -> IntReturns the number of active qudits in register. Here, active qudits means the system qubits that operators can be applied on.
YaoAPI.nremain — Functionnremain(register) -> IntReturns the number of inactive qudits in register. It equals to subtracting nqudits and nactive.
YaoArrayRegister.nbatch — Functionnbatch(register) -> Union{Int,NoBatch()}Returns the number of batches.
YaoAPI.nlevel — Functionnlevel(x)Number of levels in each qudit.
Examples
julia> nlevel(X)
2YaoAPI.focus! — Functionfocus!(register, locs) -> register
focus!(locs...) -> f(register) -> registerSet 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.
YaoAPI.focus — Functionfocus(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: 2YaoAPI.relax! — Functionrelax!(register[, locs]; to_nactive=nqudits(register)) -> register
relax!(locs::Int...; to_nactive=nqudits(register)) -> f(register) -> registerInverse 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: 2YaoArrayRegister.exchange_sysenv — Functionexchange_sysenv(reg::AbstractArrayReg) -> AbstractRegisterExchange 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: 2The following functions are for querying the state of a quantum register.
YaoArrayRegister.state — Functionstate(register::AbstractArrayReg) -> MatrixReturns the raw array storage of register. See also statevec.
state(ρ::DensityMatrix) -> MatrixReturn the raw state of density matrix ρ.
BitBasis.basis — Functionbasis(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.
basis(register) -> UnitRangeReturns 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 ₍₂₎YaoArrayRegister.statevec — Functionstatevec(r::ArrayReg) -> arrayReturn a state matrix/vector by droping the last dimension of size 1 (i.e. nactive(r) = nqudits(r)). See also state.
statevec is not type stable. It may cause performance slow down.
YaoArrayRegister.relaxedvec — Functionrelaxedvec(r::AbstractArrayReg) -> AbstractArrayReturn a vector representation of state, with all qudits activated. See also state, statevec.
BitBasis.hypercubic — Functionhypercubic(A::Array) -> Arrayget the hypercubic representation for an array.
hypercubic(r::ArrayReg) -> AbstractArrayReturn the hypercubic representation (high dimensional tensor) of this register, only active qudits are considered. See also rank3 and state.
YaoArrayRegister.rank3 — Functionrank3(r::ArrayReg)Return the rank 3 tensor representation of state, the 3 dimensions are (activated space, remaining space, batch dimension). See also hypercubic and state.
YaoAPI.viewbatch — Functionviewbatch(register, i::Int) -> AbstractRegisterReturns 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 ₍₂₎YaoArrayRegister.transpose_storage — Functiontranspose_storage(register) -> registerTranspose the register storage. Sometimes transposed storage provides better performance for batched simulation.
The following functions are for arithmetic operations on quantum registers.
YaoArrayRegister.AdjointArrayReg — TypeAdjointArrayReg{D,T,MT} = AdjointRegister{D,<:AbstractArrayReg{D,T,MT}}Adjoint array register type, it is used to represent the bra in the Dirac notation.
We also have some faster inplace versions of arithematic operations
YaoArrayRegister.regadd! — Functionregadd!(target, source)Inplace version of + that accumulates source to target.
YaoArrayRegister.regsub! — Functionregsub!(target, source)Inplace version of - that subtract source from target.
YaoArrayRegister.regscale! — Functionregscale!(target, x)Inplace version of multiplying a scalar x to target.
We also define the following functions for state normalization, and distance measurement.
LinearAlgebra.normalize! — Functionnormalize!(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)
trueYaoArrayRegister.isnormalized — Functionisnormalized(r::ArrayReg) -> BoolReturns true if the register r is normalized.
YaoAPI.fidelity — Functionfidelity(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 gψ 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{ρ}})\]
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.35355339059327373References
- 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.
The original definition of fidelity $F$ was from "transition probability", defined by Jozsa in 1994, it is the square of what we use here.
YaoAPI.tracedist — Functiontracedist(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.9354143466934852References
- https://en.wikipedia.org/wiki/Trace_distance
The following functions are for adding and reordering qubits in a quantum register.
YaoAPI.insert_qudits! — Functioninsert_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 ₍₂₎YaoAPI.insert_qubits! — Functioninsert_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.
YaoAPI.append_qudits! — Functionappend_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.
YaoAPI.append_qubits! — Functionappend_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.
YaoAPI.reorder! — Functionreorder!(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.
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 ₍₂₎YaoAPI.invorder! — Functioninvorder!(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 ₍₂₎The instruct! function is for applying quantum operators on a quantum register.
YaoAPI.instruct! — Functioninstruct!([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
nlevelis the number of levels in each qudit,stateis a vector or matrix representing the quantum state, where the first dimension is the active qubit dimension, the second is the batch dimension.operatoris a quantum operator, which can beVal(GATE_SYMBOL)or a matrix.locs::Tupleis a tuple for specifying the locations this gate applied.control_locs::Tupleandcontrol_configsare tuples for specifying the control locations and control values.theta::Realis the parameter for the gate, e.g.Val(:Rx)gate takes a real number of its parameter.
The following functions are for measurement and post-selection.
YaoAPI.measure! — Functionmeasure!([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
postprocessis the postprocessing method, it can beNoPostProcess()(default).ResetTo(config), reset to result state toconfig. It can not be used ifoperatoris 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 theoperatorargument.
operator::AbstractBlockis the operator to measure.register::AbstractRegisteris the quantum state.locsis the qubits to performance the measurement. Iflocsis not provided, all current active qudits are measured (regarding to active qudits,
Keyword arguments
rngis 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: 2Measuring 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.0imHere, we measured the parity operator, as a result, the resulting state collapsed to the subspace with either even or odd parity.
YaoAPI.measure — Functionmeasure([, 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::AbstractBlockis the operator to measure.register::AbstractRegisteris the quantum state.locsis the qubits to performance the measurement. Iflocsis not provided, all current active qudits are measured (regarding to active qudits,
Keyword arguments
nshots::Intis the number of shots.rngis 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.0imYaoAPI.select! — Functionselect!(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.7071067811865476The 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.0imDevelopers 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.
YaoAPI.select — Functionselect(register, bits) -> AbstractRegisterThe non-inplace version of select!.
YaoAPI.collapseto! — Functioncollapseto!(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 ₍₂₎YaoAPI.probs — Functionprobs(register) -> VectorReturns 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.0YaoArrayRegister.most_probable — Functionmost_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 ₍₂₎The following functions are for density matrices.
YaoAPI.DensityMatrix — TypeDensityMatrix{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.
YaoAPI.density_matrix — Functiondensity_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.0imYaoArrayRegister.rand_density_matrix — Functionrand_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.
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.
YaoArrayRegister.completely_mixed_state — Functioncompletely_mixed_state([T=ComplexF64], n::Int; nlevel::Int=2)Generate the completely mixed state with density matrix I(n) ./ nlevel^n.
YaoAPI.partial_tr — Functionpartial_tr(ρ, locs) -> DensityMatrixReturn a density matrix which is the partial traced on locs.
YaoAPI.purify — Functionpurify(r::DensityMatrix; nbit_env::Int=nactive(r)) -> ArrayRegGet 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)
trueYaoArrayRegister.von_neumann_entropy — Functionvon_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.6931471805599612von_neumann_entropy(rho) -> RealReturn the von-Neumann entropy for the input density matrix:
\[-{\rm Tr}(\rho\ln\rho)\]
YaoArrayRegister.mutual_information — Functionmutual_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