Register Basics

# Register Basics

• Construction and Storage
• Basics Arithmatics
• Fidelity and DensityMatrix
• Batched Registers
using Yao
using LinearAlgebra

## Construction and Storage

AbstractRegister{B, T} is abstract type that registers will subtype from. B is the batch size, T is the data type. Normally, we use a matrix as the state (with columns the batch and environment dimension) of a register, which is called DefaultRegister{B, T}.

To initialize a quantum register, all you need is

• register(vec),
• zero_state(nbit),
• rand_state(nbit), both real and imaginary parts are random normal distributions,
• product_state(nbit, val=0), where val is an Integer as bitstring, e.g. 0b10011 or 19,
• uniform_state(nbit), evenly distributed state, i.e. H|0>.

e.g.

ψ1 = zero_state(5)
@show ψ1
@show nqubits(ψ1)
@show nactive(ψ1)   # number of activated qubits
@show nremain(ψ1)   # number of remaining qubits

ψ2 = ψ1 |> focus!(3,2,4)   # set activated qubits
@show ψ2
@show nqubits(ψ2)
@show nactive(ψ2)
@show nremain(ψ2)

@assert relax!(ψ2, (3,2,4)) == ψ1
ψ1 = DefaultRegister{1, Array{Complex{Float64},2}}
active qubits: 5/5
nqubits(ψ1) = 5
nactive(ψ1) = 5
nremain(ψ1) = 0
ψ2 = DefaultRegister{1, Array{Complex{Float64},2}}
active qubits: 3/5
nqubits(ψ2) = 5
nactive(ψ2) = 3
nremain(ψ2) = 2

The total number of qubits here is 5, they are all acitve by default. active qubits are also called system qubits that are visible to operations, remaining qubits are the environment. nremain == nqubits-nactive always holds.

focus! & relax! focus!(reg, (3,2,4)) is equivalent to reg |> focus!(3,2,4), which changes focused bits to (3,2,4). Here from ψ1 -> ψ2, qubit line numbers change as (active)(remaining): (1,2,3,4,5)() -> (3,2,4)(1,5)

focus! uses relative positions, which means it sees only active qubits and does not memorize original qubits positions. We take this convension to support modulized design. For example, if we want to insert a QFT blocks into some parent module, both the QFT and its parent do not need to know original position, which provides flexibility.

relax! is the inverse process of focus!, relax!(reg, (3,2,4)) will cancel the above operation. Here we have a second parameter since a register does not memorize original positions. This annoying feature can be circumvented using focus!(reg, (3,2,4)) do ... end, which will automatically restore your focus operation, see an example here.

Please also notice APIs for changing lines order

• reorder!(reg, order), change lines order
• reg |> invorder!, inverse lines order

and

• reg |> oneto(n), return a register view, with first n bits focused.

Extending Registers We can extend registers by either joining two registers or adding bits.

@assert product_state(3, 0b110) ⊗ product_state(3, 0b001) == product_state(6, 0b110001)
reg = product_state(5, 0b11100)
@assert addbit!(copy(reg), 2) == product_state(7, 0b0011100) == zero_state(2) ⊗ reg

Storage Let's dive into the storage of a register, there are three types representations

• reg |> state, matrix format, size = (2^nactive, 2^nremain * nbatch)
• reg |> rank3, rank 3 tensor format, size = (2^nactive, 2^nremain, nbatch)
• reg |> hypercubic, hypercubic format, size = (2, 2, 2, ..., nbatch)

Here, we add a dimension nbatch to support parallism among registers. They are all different views of same memory. Please also check statevec and relaxedvec format, which prefer vectors whenever possible.

@show ψ1 |> state |> size
@show ψ1 |> rank3 |> size
@show ψ1 |> hypercubic |> size
@show ψ1 |> statevec |> size
@show ψ1 |> relaxedvec |> size;
(ψ1 |> state) |> size = (32, 1)
(ψ1 |> rank3) |> size = (32, 1, 1)
(ψ1 |> hypercubic) |> size = (2, 2, 2, 2, 2, 1)
(ψ1 |> statevec) |> size = (32,)
(ψ1 |> relaxedvec) |> size = (32,)

### Example

multiply |0> by a random unitary operator on qubits (3, 1, 5) (relax the register afterwards).

using Yao.Intrinsics: rand_unitary

reg = zero_state(5)
focus!(reg, [3,1,5]) do r
r.state = rand_unitary(8) * r.state
r
end
@show reg.state;
reg.state = Complex{Float64}[-0.302894-0.1309im; -0.230708-0.317541im; 0.0+0.0im; 0.0+0.0im; 0.340651-0.147317im; 0.157962+0.385621im; 0.0+0.0im; 0.0+0.0im; 0.0+0.0im; 0.0+0.0im; 0.0+0.0im; 0.0+0.0im; 0.0+0.0im; 0.0+0.0im; 0.0+0.0im; 0.0+0.0im; 0.125243+0.0986739im; -0.0595983-0.145877im; 0.0+0.0im; 0.0+0.0im; 0.507097-0.268703im; 0.160987-0.141923im; 0.0+0.0im; 0.0+0.0im; 0.0+0.0im; 0.0+0.0im; 0.0+0.0im; 0.0+0.0im; 0.0+0.0im; 0.0+0.0im; 0.0+0.0im; 0.0+0.0im]

## Basic Arithmatics

+, -, *, /, ⊗, ' are implemented.

The adjoint of a register is also called bra, it can be used in calculating state overlap

ψ1 = rand_state(5)
ψ2 = rand_state(5)
DefaultRegister{1, Array{Complex{Float64},2}}
active qubits: 5/5

arithmatics

@show ψ1
@show ψ2
@show ψ3 = (0.3ψ1 + 2ψ2)/2 ⊗ ψ1
@assert ψ3 ≈ 0.15ψ1 ⊗ ψ1 + ψ2 ⊗ ψ1
ψ1 = DefaultRegister{1, Array{Complex{Float64},2}}
active qubits: 5/5
ψ2 = DefaultRegister{1, Array{Complex{Float64},2}}
active qubits: 5/5
ψ3 = ((0.3ψ1 + 2ψ2) / 2) ⊗ ψ1 = DefaultRegister{1, Array{Complex{Float64},2}}
active qubits: 10/10

normalize ψ3

@assert ψ1 |> isnormalized && ψ2 |> isnormalized
@assert ψ3 |> isnormalized == false
@show ψ3 |> normalize! |> isnormalized

@show ψ3' * ψ3;
(ψ3 |> normalize!) |> isnormalized = true
ψ3' * ψ3 = 1.0 + 0.0im

## Measure

• measure(reg; nshot=1), measure without collapsing state,
• measure!(reg), measure and collapse,
• measure_remove!(reg), measure focused bits and remove them,
• measure_reset!(reg, val=0), measure focused bits and reset them to some value,
• reset!(reg), collapse to specific value directly.
• select(reg, x), select subspace projected on specific basis, i.e. $|\phi\rangle = |x\rangle\langle x|\psi\rangle$.

measure

@show product_state(5, 0b11001) |> measure  # please notice binary number 0b11001 is equivalent to 25!
reg = rand_state(7)
@show measure(reg; nshot=5);          # measure multiple times
product_state(5, 0x19) |> measure = 
measure(reg; nshot=5) = [5, 41, 60, 92, 7]

measure!

reg = rand_state(7)
@show [measure!(reg) for i=1:5];  # measure! will collapse state
[measure!(reg) for i = 1:5] = Array{Int64,1}[, , , , ]

measure_reset!

reg = rand_state(7)
@show [measure_reset!(reg, val=i*10) for i=1:5];   # measure_reset! will reset the measured bit to target state (default is 0)
[measure_reset!(reg, val=i * 10) for i = 1:5] = Array{Int64,1}[, , , , ]

measure_remove!

reg = rand_state(7)
@show measure_remove!(reg)
@show reg;

reg = rand_state(7)
@show measure_remove!(reg |> focus!(2,3))
@show reg;
measure_remove!(reg) = 
reg = DefaultRegister{1, Array{Complex{Float64},2}}
active qubits: 0/0
measure_remove!(reg |> focus!(2, 3)) = 
reg = DefaultRegister{1, Array{Complex{Float64},2}}
active qubits: 0/5

select

select will allow you to get the disired measurement result, and collapse to that state. It is equivalent to calculating $|\phi\rangle = |x\rangle\langle x|\psi\rangle$.

reg = rand_state(9) |> focus!(1, 2, 3, 4)
@show ψ = select(reg, 0b1110)
@show ψ |> relax!;

# Fidelity and Density Matrix
ψ1 = rand_state(6)
ψ2 = rand_state(6)
@show fidelity(ψ1, ψ2)
@show tracedist(ψ1, ψ2)
@show ψ1 |> ρ
@show tracedist(ψ1 |> ρ, ψ2|> ρ);  # calculate trace distance using density matrix
@assert ψ1 |> probs ≈ dropdims(ψ1 |> ρ |> probs, dims=2)
ψ = select(reg, 0x0e) = DefaultRegister{1, Array{Complex{Float64},2}}
active qubits: 0/5
ψ |> relax! = DefaultRegister{1, Array{Complex{Float64},2}}
active qubits: 5/5
fidelity(ψ1, ψ2) = [0.0542046]
tracedist(ψ1, ψ2) = [0.99853]
ψ1 |> ρ = DensityMatrix{1, Complex{Float64}}
nqubits: 6
tracedist(ψ1 |> ρ, ψ2 |> ρ) = [0.99853]

## Batched Registers

Most operations support batched register, which means running multiple registers in parallel.

ψ = rand_state(6, 3)
@show ψ
@show nbatch(ψ)
@show viewbatch(ψ, 2)  # this is a view of register at 2nd column of the batch dimension
@show repeat(ψ, 3);    # repeat registers in batch dimension
ψ = DefaultRegister{3, Array{Complex{Float64},2}}
active qubits: 6/6
nbatch(ψ) = 3
viewbatch(ψ, 2) = DefaultRegister{1, SubArray{Complex{Float64},2,Array{Complex{Float64},3},Tuple{Base.Slice{Base.OneTo{Int64}},Base.Slice{Base.OneTo{Int64}},Int64},true}}
active qubits: 6/6
repeat(ψ, 3) = DefaultRegister{9, Array{Complex{Float64},2}}
active qubits: 6/6

@. ψ * 5 - 4 * ψ ≈ ψ
3-element BitArray{1}:
true
true
true
X2 = put(5, 2=>X)       # X operator on 2nd bit, with total number of bit 5.
direct = copy(ψ) |> X2  # applying X2 directly
map(reg->reg |> X2, ψ)  # applying X2 using broadcasting, here X2 operator is applied inplace!
ψ .≈ direct
3-element BitArray{1}:
true
true
true