Registers

Quantum circuits process quantum states. A quantum state being processing by a quantum circuit will be stored on a quantum register. In Yao we provide several types for registers. The default type for registers is the ArrayReg which is defined in YaoArrayRegister.jl.

The registers can be extended by subtyping AbstractRegister and define correspinding register interfaces defined in YaoAPI.jl, which includes:

Minimal Required Interfaces

The following interfaces are the minial required interfaces to make a register's printing work and be able to accept certain gates/blocks.

But if you don't want to work with our default printing, you could define your custom printing with Base.show.

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

you can define instruct!, to provide specialized instructions for the registers from plain storage types.

Qubit Management Interfaces

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{BitBasis.BitStr64{7}}:
 0001101 ₍₂₎
 0001101 ₍₂₎
 0001101 ₍₂₎

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

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{BitBasis.BitStr64{6}}:
 000111 ₍₂₎
source

Qubit Scope Management Interfaces

LDT format

Concepturely, a wave function $|\psi\rangle$ can be represented in a low dimentional tensor (LDT) format of order-3, L(f, r, b).

  • f: focused (i.e. operational) dimensions
  • r: remaining dimensions
  • b: batch dimension.

For simplicity, let's ignore batch dimension for the now, we have

\[|\psi\rangle = \sum\limits_{x,y} L(x, y, .) |j\rangle|i\rangle\]

Given a configuration x (in operational space), we want get the i-th bit using (x<<i) & 0x1, which means putting the small end the qubit with smaller index. In this representation L(x) will get return $\langle x|\psi\rangle$.

Note

Why not the other convension: Using the convention of putting 1st bit on the big end will need to know the total number of qubits n in order to know such positional information.

HDT format

Julia storage is column major, if we reshape the wave function to a shape of $2\times2\times ... \times2$ and get the HDT (high dimensional tensor) format representation H, we can use H($x_1, x_2, ..., x_3$) to get $\langle x|\psi\rangle$.

Array Registers

We provide ArrayReg as built in register type for simulations. It is a simple wrapper of a Julia array, e.g on CPU, we use Array by default and on CUDA devices we could use CuArray. You don't have to define your custom array type if the storage is array based.

Constructors

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 or normalize the input raw array with normalize or batched_normalize!.

source

We define some shortcuts to create simulated quantum states easier:

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 qubits: 3/3
    nlevel: 2
    nbatch: 2

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

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

julia> r1 ≈ r2   # because we read bit strings from right to left, vectors from left to right.
true
source
product_state([T=ComplexF64], total::Int, bit_config::Integer; nbatch=NoBatch(), 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 qubits: 4/4
    nlevel: 2
    nbatch: 2

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

julia> product_state(ComplexF32, 4, 0b101)
ArrayReg{2, ComplexF32, Array...}
    active qubits: 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.

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.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.onetoFunction
oneto(n::Int) -> f(register)
oneto(r::AbstractArrayReg, n::Int=nqudits(r))

Returns an register with 1:n qudits activated, which is faster than the general purposed focus function.

source
Base.repeatFunction
repeat(n, x::AbstractBlock[, locs]) -> RepeatedBlock{n}

Create a RepeatedBlock with total number of qubits n and the block to repeat on given location or on all the locations.

Example

This will create a repeat block which puts 4 X gates on each location.

julia> repeat(4, X)
nqubits: 4
repeat on (1, 2, 3, 4)
└─ X

You can also specify the location

julia> repeat(4, X, (1, 2))
nqubits: 4
repeat on (1, 2)
└─ X

But repeat won't copy the gate, thus, if it is a gate with parameter, e.g a phase(0.1), the parameter will change simultaneously.

julia> g = repeat(4, phase(0.1))
nqubits: 4
repeat on (1, 2, 3, 4)
└─ phase(0.1)

julia> g.content
phase(0.1)

julia> g.content.theta = 0.2
0.2

julia> g
nqubits: 4
repeat on (1, 2, 3, 4)
└─ phase(0.2)

Repeat over certain gates will provide speed up.

julia> reg = rand_state(20);

julia> @time apply!(reg, repeat(20, X));
  0.002252 seconds (5 allocations: 656 bytes)

julia> @time apply!(reg, chain([put(20, i=>X) for i=1:20]));
  0.049362 seconds (82.48 k allocations: 4.694 MiB, 47.11% compilation time)
source
repeat(x::AbstractBlock, locs)

Lazy curried version of repeat.

source

Properties

You can access the storage of an ArrayReg with:

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
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(r::ArrayReg) -> AbstractArray

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

source
hypercubic(A::Array) -> Array

get the hypercubic representation for an array.

Operations

We defined basic arithmatics for ArrayReg, besides since we do not garantee normalization for some operations on ArrayReg for simulation, normalize! and isnormalized is provided to check and normalize the simulated register.

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

Specialized Instructions

We define some specialized instruction by specializing instruct! to improve the performance for simulation and dispatch them with multiple dispatch.

Implemented instruct! is listed below:

Measurement

Simulation of measurement is mainly achieved by sampling and projection.

Sample

Suppose we want to measure operational subspace, we can first get

\[p(x) = \|\langle x|\psi\rangle\|^2 = \sum\limits_{y} \|L(x, y, .)\|^2.\]

Then we sample an $a\sim p(x)$. If we just sample and don't really measure (change wave function), its over.

Projection

\[|\psi\rangle' = \sum_y L(a, y, .)/\sqrt{p(a)} |a\rangle |y\rangle\]

Good! then we can just remove the operational qubit space since x and y spaces are totally decoupled and x is known as in state a, then we get

\[|\psi\rangle'_r = \sum_y l(0, y, .) |y\rangle\]

where l = L(a:a, :, :)/sqrt(p(a)).

References

Base.adjointMethod
adjoint(register) -> register

Lazy adjoint for quantum registers.

source
Base.joinMethod
join(regs...)

concatenate a list of registers regs to a larger register, each register should have the same batch size. See also clone.

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

julia> measure(reg; nshots=3)
3-element Vector{BitBasis.BitStr64{6}}:
 111000 ₍₂₎
 111000 ₍₂₎
 111000 ₍₂₎
source
BitBasis.basisMethod
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{BitBasis.BitStr64{3}}:
 000 ₍₂₎
 001 ₍₂₎
 010 ₍₂₎
 011 ₍₂₎
 100 ₍₂₎
 101 ₍₂₎
 110 ₍₂₎
 111 ₍₂₎
source
BitBasis.hypercubicMethod
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
LinearAlgebra.normalize!Method
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.probsMethod
probs(ρ) -> Vector

Returns the probability distribution from a density matrix ρ.

source
YaoArrayRegister.arrayregMethod
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
YaoArrayRegister.arrayregMethod
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.datatypeMethod
datatype(register) -> Int

Returns the numerical data type used by register.

Note

datatype is not the same with eltype, since AbstractRegister family is not exactly the same with AbstractArray, it is an iterator of several registers.

source
YaoArrayRegister.density_fidelityMethod
density_fidelity(ρ1, ρ2)

General fidelity (including mixed states) between two density matrix for qudits.

Definition

\[F(ρ, σ)^2 = tr(ρσ) + 2 \sqrt{det(ρ)det(σ)}\]

source
YaoArrayRegister.exchange_sysenvMethod
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
YaoArrayRegister.general_controlled_gatesMethod
general_controlled_gates(num_bit::Int, projectors::Vector{Tp}, cbits::Vector{Int}, gates::Vector{AbstractMatrix}, locs::Vector{Int}) -> AbstractMatrix

Return general multi-controlled gates in hilbert space of num_bit qudits,

  • projectors are often chosen as P0 and P1 for inverse-Control and Control at specific position.
  • cbits should have the same length as projectors, specifing the controling positions.
  • gates are a list of controlled single qubit gates.
  • locs should have the same length as gates, specifing the gates positions.
source
YaoArrayRegister.ghz_stateMethod
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
YaoArrayRegister.hilbertkronMethod
hilbertkron(num_bit::Int, gates::Vector{AbstractMatrix}, locs::Vector{Int}; nlevel=2) -> AbstractMatrix

Return general kronecher product form of gates in Hilbert space of num_bit qudits.

  • gates are a list of matrices.
  • start_locs should have the same length as gates, specifing the gates starting positions.
source
YaoArrayRegister.linop2denseMethod
linop2dense([T=ComplexF64], linear_map!::Function, n::Int; nlevel=2) -> Matrix

Returns the dense matrix representation given linear map function.

source
YaoArrayRegister.most_probableMethod
most_probable(reg::AbstractArrayReg{2}, n::Int)

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

Example

julia> most_probable(ghz_state(3), 2)
2-element Vector{BitBasis.BitStr64{3}}:
 000 ₍₂₎
 111 ₍₂₎
source
YaoArrayRegister.mutual_informationMethod
mutual_information(reg::AbstractArrayReg, part1, part2)

Compute the mutual information between locations part1 and locations part2 in a quantum state reg.

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
YaoArrayRegister.onetoMethod
oneto(n::Int) -> f(register)
oneto(r::AbstractArrayReg, n::Int=nqudits(r))

Returns an register with 1:n qudits activated, which is faster than the general purposed focus function.

source
YaoArrayRegister.product_stateMethod
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 qubits: 3/3
    nlevel: 2
    nbatch: 2

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

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

julia> r1 ≈ r2   # because we read bit strings from right to left, vectors from left to right.
true
source
YaoArrayRegister.product_stateMethod
product_state([T=ComplexF64], total::Int, bit_config::Integer; nbatch=NoBatch(), 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 qubits: 4/4
    nlevel: 2
    nbatch: 2

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

julia> product_state(ComplexF32, 4, 0b101)
ArrayReg{2, ComplexF32, Array...}
    active qubits: 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.

source
YaoArrayRegister.rand_stateMethod
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.statevecMethod
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
YaoArrayRegister.swapcols!Function
swapcols!(v::VecOrMat, i::Int, j::Int[, f1, f2]) -> VecOrMat

swap col i and col j of v inplace, with f1, f2 factors applied on i and j (before swap).

source
YaoArrayRegister.swaprows!Function
swaprows!(v::VecOrMat, i::Int, j::Int[, f1, f2]) -> VecOrMat

swap row i and row j of v inplace, with f1, f2 factors applied on i and j (before swap).

source
YaoArrayRegister.transpose_storageMethod
transpose_storage(register) -> register

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

source
YaoArrayRegister.u1rows!Function
u1rows!(state::VecOrMat, i::Int, j::Int, a, b, c, d) -> VecOrMat

apply u1 on row i and row j of state inplace.

source
YaoArrayRegister.uniform_stateMethod
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.von_neumann_entropyMethod
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
YaoArrayRegister.zero_stateMethod
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_likeMethod
zero_state_like(register, n) -> AbstractRegister

Create a register initialized to zero from an existing one.

Examples

julia> reg = rand_state(3; nbatch=2)
BatchedArrayReg{2, ComplexF64, Transpose...}
    active qubits: 3/3
    nlevel: 2
    nbatch: 2
source