# 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.ArrayReg`

— Type`ArrayReg{B, T, MT <: AbstractMatrix{T}} <: AbstractRegister{B}`

Simulated full amplitude register type, it uses an array to represent corresponding one or a batch of quantum states. `B`

is the batch size, `T`

is the numerical type for each amplitude, it is `ComplexF64`

by default.

We define some shortcuts to create simulated quantum states easier:

`YaoArrayRegister.product_state`

— Function`product_state([T=ComplexF64], bit_str; nbatch=1)`

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)
ArrayReg{2, Complex{Float64}, Array...}
active qubits: 3/3
julia> r1 = product_state(ComplexF32, bit"100"; nbatch=2)
ArrayReg{2, Complex{Float32}, Array...}
active qubits: 3/3
julia> r2 = product_state(ComplexF32, [0, 0, 1]; nbatch=2)
ArrayReg{2, Complex{Float32}, Array...}
active qubits: 3/3
julia> r1 ≈ r2 # because we read bit strings from right to left, vectors from left to right.
true
```

`product_state([T=ComplexF64], total::Int, bit_config::Integer; nbatch=1, no_transpose_storage=false)`

Create an `ArrayReg`

with bit configuration `bit_config`

, total number of bits `total`

. See also `zero_state`

, `rand_state`

, `uniform_state`

.

**Examples**

```
julia> product_state(4, 3; nbatch=2)
ArrayReg{2, Complex{Float64}, Array...}
active qubits: 4/4
julia> product_state(4, 0b1001; nbatch=2)
ArrayReg{2, Complex{Float64}, Array...}
active qubits: 4/4
julia> product_state(ComplexF32, 4, 0b101)
ArrayReg{1, Complex{Float32}, Array...}
active qubits: 4/4
```

This interface will not check whether the number of required digits for the bit configuration matches the total number of bits.

`YaoArrayRegister.zero_state`

— Function`zero_state([T=ComplexF64], n::Int; nbatch::Int=1)`

Create an `ArrayReg`

with total number of bits `n`

. See also `product_state`

, `rand_state`

, `uniform_state`

.

**Examples**

```
julia> zero_state(4)
ArrayReg{1, Complex{Float64}, Array...}
active qubits: 4/4
julia> zero_state(ComplexF32, 4)
ArrayReg{1, Complex{Float32}, Array...}
active qubits: 4/4
julia> zero_state(ComplexF32, 4; nbatch=3)
ArrayReg{3, Complex{Float32}, Array...}
active qubits: 4/4
```

`YaoArrayRegister.rand_state`

— Function`rand_state([T=ComplexF64], n::Int; nbatch=1, no_transpose_storage=false)`

Create a random `ArrayReg`

with total number of qubits `n`

.

**Examples**

```
julia> rand_state(4)
ArrayReg{1, Complex{Float64}, Array...}
active qubits: 4/4
julia> rand_state(ComplexF64, 4)
ArrayReg{1, Complex{Float64}, Array...}
active qubits: 4/4
julia> rand_state(ComplexF64, 4; nbatch=2)
ArrayReg{2, Complex{Float64}, Array...}
active qubits: 4/4
```

`YaoArrayRegister.uniform_state`

— Function`uniform_state([T=ComplexF64], n; nbatch=1, no_transpose_storage=false)`

Create a uniform state: $\frac{1}{2^n} \sum_k |k⟩$. This state can also be created by applying `H`

(Hadmard gate) on $|00⋯00⟩$ state.

**Example**

```
julia> uniform_state(4; nbatch=2)
ArrayReg{2, Complex{Float64}, Array...}
active qubits: 4/4
julia> uniform_state(ComplexF32, 4; nbatch=2)
ArrayReg{2, Complex{Float64}, Array...}
active qubits: 4/4
```

`YaoArrayRegister.oneto`

— Function`oneto(r::ArrayReg, n::Int=nqubits(r))`

Returns an `ArrayReg`

with `1:n`

qubits activated.

`oneto(n::Int) -> f(register)`

Like `oneto(register, n)`

, but the input `register`

is delayed.

`Base.repeat`

— Function`repeat(register, n)`

Create an `ArrayReg`

by copying the original `register`

for `n`

times on batch dimension.

**Example**

```
julia> repeat(ArrayReg{3}(bit"101"), 4)
ArrayReg{12, Complex{Float64}, Array...}
active qubits: 3/3
```

`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(x::AbstractBlock, locs)`

Lazy curried version of `repeat`

.

## Properties

You can access the storage of an `ArrayReg`

with:

`YaoArrayRegister.state`

— Function`state(register::ArrayReg) -> raw array`

Returns the raw array storage of `register`

. See also `statevec`

.

`state(ρ::DensityMatrix)`

Return the raw state of density matrix `ρ`

.

`YaoArrayRegister.statevec`

— Function`statevec(r::ArrayReg) -> array`

Return a state matrix/vector by droping the last dimension of size 1. See also `state`

.

`statevec`

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

`YaoArrayRegister.relaxedvec`

— Function`relaxedvec(r::ArrayReg) -> AbstractArray`

Return a matrix (vector) for B>1 (B=1) as a vector representation of state, with all qubits activated. See also `state`

, `statevec`

.

`BitBasis.hypercubic`

— Function`hypercubic(A::Array) -> Array`

get the hypercubic representation for an array.

`hypercubic(r::ArrayReg) -> AbstractArray`

Return the hypercubic form (high dimensional tensor) of this register, only active qubits are considered. See also `rank3`

.

`YaoArrayRegister.rank3`

— Function`rank3(r::ArrayReg)`

Return the rank 3 tensor representation of state, the 3 dimensions are (activated space, remaining space, batch dimension). See also `rank3`

.

## 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::ArrayReg)`

Normalize the register `r`

in-place by its `2`

-norm.

`YaoArrayRegister.isnormalized`

— Function`isnormalized(r::ArrayReg) -> Bool`

Check if the register is normalized.

## 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))`

.

## Others

`Base.join`

— Method`join(regs...)`

concat a list of registers `regs`

to a larger register, each register should have the same batch size. See also `repeat`

.

`Base.repeat`

— Method`repeat(register, n)`

Create an `ArrayReg`

by copying the original `register`

for `n`

times on batch dimension.

**Example**

```
julia> repeat(ArrayReg{3}(bit"101"), 4)
ArrayReg{12, Complex{Float64}, Array...}
active qubits: 3/3
```

`BitBasis.hypercubic`

— Method`hypercubic(r::ArrayReg) -> AbstractArray`

Return the hypercubic form (high dimensional tensor) of this register, only active qubits are considered. See also `rank3`

.

`LinearAlgebra.normalize!`

— Method`normalize!(r::ArrayReg)`

Normalize the register `r`

in-place by its `2`

-norm.

`YaoAPI.fidelity`

— Method`fidelity(r1::ArrayReg, r2::ArrayReg)`

Calcuate the fidelity between `r1`

and `r2`

, if `r1`

or `r2`

is not pure state (`nactive(r) != nqubits(r)`

), the fidelity is calcuated by purification. See also `pure_state_fidelity`

, `purification_fidelity`

.

`fidelity'(pair_or_reg1, pair_or_reg2) -> (g1, g2)`

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.

`YaoAPI.probs`

— Method`probs(ρ)`

Returns the probability distribution from a density matrix `ρ`

.

`YaoArrayRegister.contiguous_shape_orders`

— Method`contiguous_shape_orders(shape, orders)`

Merge the shape and orders if the orders are contiguous. Returns the new merged shape and order.

**Example**

```
julia> YaoArrayRegister.contiguous_shape_orders((2, 3, 4), (1, 2, 3))
([24], [1])
```

`YaoArrayRegister.datatype`

— Method`datatype(register) -> Int`

Returns the numerical data type used by register.

`datatype`

is not the same with `eltype`

, since `AbstractRegister`

family is not exactly the same with `AbstractArray`

, it is an iterator of several registers.

`YaoArrayRegister.exchange_sysenv`

— Method`exchange_sysenv(reg::ArrayReg) -> ArrayReg`

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

`YaoArrayRegister.is_order_same`

— Method`is_order_same(locs) -> Bool`

Check if the order specified by `locs`

is the same as current order.

`YaoArrayRegister.isnormalized`

— Method`isnormalized(r::ArrayReg) -> Bool`

Check if the register is normalized.

`YaoArrayRegister.matvec`

— Function`matvec(x::VecOrMat) -> MatOrVec`

Return vector if a matrix is a column vector, else untouched.

`YaoArrayRegister.move_ahead`

— Method`move_ahead(collection, orders)`

Move `orders`

to the beginning of `collection`

.

`YaoArrayRegister.mulcol!`

— Function`mulcol!(v::AbstractVector, i::Int, f) -> VecOrMat`

multiply col i of v by f inplace.

`YaoArrayRegister.mulrow!`

— Function`mulrow!(v::AbstractVector, i::Int, f) -> VecOrMat`

multiply row i of v by f inplace.

`YaoArrayRegister.oneto`

— Method`oneto(n::Int) -> f(register)`

Like `oneto(register, n)`

, but the input `register`

is delayed.

`YaoArrayRegister.oneto`

— Method`oneto(r::ArrayReg, n::Int=nqubits(r))`

Returns an `ArrayReg`

with `1:n`

qubits activated.

`YaoArrayRegister.product_state`

— Method`product_state([T=ComplexF64], bit_str; nbatch=1)`

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)
ArrayReg{2, Complex{Float64}, Array...}
active qubits: 3/3
julia> r1 = product_state(ComplexF32, bit"100"; nbatch=2)
ArrayReg{2, Complex{Float32}, Array...}
active qubits: 3/3
julia> r2 = product_state(ComplexF32, [0, 0, 1]; nbatch=2)
ArrayReg{2, Complex{Float32}, Array...}
active qubits: 3/3
julia> r1 ≈ r2 # because we read bit strings from right to left, vectors from left to right.
true
```

`YaoArrayRegister.product_state`

— Method`product_state([T=ComplexF64], total::Int, bit_config::Integer; nbatch=1, no_transpose_storage=false)`

Create an `ArrayReg`

with bit configuration `bit_config`

, total number of bits `total`

. See also `zero_state`

, `rand_state`

, `uniform_state`

.

**Examples**

```
julia> product_state(4, 3; nbatch=2)
ArrayReg{2, Complex{Float64}, Array...}
active qubits: 4/4
julia> product_state(4, 0b1001; nbatch=2)
ArrayReg{2, Complex{Float64}, Array...}
active qubits: 4/4
julia> product_state(ComplexF32, 4, 0b101)
ArrayReg{1, Complex{Float32}, Array...}
active qubits: 4/4
```

This interface will not check whether the number of required digits for the bit configuration matches the total number of bits.

`YaoArrayRegister.rand_state`

— Method`rand_state([T=ComplexF64], n::Int; nbatch=1, no_transpose_storage=false)`

Create a random `ArrayReg`

with total number of qubits `n`

.

**Examples**

```
julia> rand_state(4)
ArrayReg{1, Complex{Float64}, Array...}
active qubits: 4/4
julia> rand_state(ComplexF64, 4)
ArrayReg{1, Complex{Float64}, Array...}
active qubits: 4/4
julia> rand_state(ComplexF64, 4; nbatch=2)
ArrayReg{2, Complex{Float64}, Array...}
active qubits: 4/4
```

`YaoArrayRegister.rank3`

— Method`rank3(r::ArrayReg)`

Return the rank 3 tensor representation of state, the 3 dimensions are (activated space, remaining space, batch dimension). See also `rank3`

.

`YaoArrayRegister.relaxedvec`

— Method`relaxedvec(r::ArrayReg) -> AbstractArray`

Return a matrix (vector) for B>1 (B=1) as a vector representation of state, with all qubits activated. See also `state`

, `statevec`

.

`YaoArrayRegister.sort_unitary`

— Method`sort_unitary(U, locations::NTuple{N, Int}) -> U`

Return an sorted unitary operator according to the locations.

`YaoArrayRegister.state`

— Method`state(register::ArrayReg) -> raw array`

Returns the raw array storage of `register`

. See also `statevec`

.

`YaoArrayRegister.state`

— Method`state(ρ::DensityMatrix)`

Return the raw state of density matrix `ρ`

.

`YaoArrayRegister.statevec`

— Method`statevec(r::ArrayReg) -> array`

Return a state matrix/vector by droping the last dimension of size 1. See also `state`

.

`statevec`

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

`YaoArrayRegister.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).

`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).

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

`YaoArrayRegister.uniform_state`

— Method`uniform_state([T=ComplexF64], n; nbatch=1, no_transpose_storage=false)`

Create a uniform state: $\frac{1}{2^n} \sum_k |k⟩$. This state can also be created by applying `H`

(Hadmard gate) on $|00⋯00⟩$ state.

**Example**

```
julia> uniform_state(4; nbatch=2)
ArrayReg{2, Complex{Float64}, Array...}
active qubits: 4/4
julia> uniform_state(ComplexF32, 4; nbatch=2)
ArrayReg{2, Complex{Float64}, Array...}
active qubits: 4/4
```

`YaoArrayRegister.zero_state`

— Method`zero_state([T=ComplexF64], n::Int; nbatch::Int=1)`

Create an `ArrayReg`

with total number of bits `n`

. See also `product_state`

, `rand_state`

, `uniform_state`

.

**Examples**

```
julia> zero_state(4)
ArrayReg{1, Complex{Float64}, Array...}
active qubits: 4/4
julia> zero_state(ComplexF32, 4)
ArrayReg{1, Complex{Float32}, Array...}
active qubits: 4/4
julia> zero_state(ComplexF32, 4; nbatch=3)
ArrayReg{3, Complex{Float32}, Array...}
active qubits: 4/4
```