Blocks

Blocks are the basic building blocks of a quantum circuit in Yao. It simply means a quantum operator, thus, all the blocks have matrices in principal and one can get its matrix by mat. The basic blocks required to build an arbitrary quantum circuit is defined in the component package YaoBlocks.

Block Tree serves as an intermediate representation for Yao to analysis, optimize the circuit, then it will be lowered to instructions like for simulations, blocks will be lowered to instruct! calls.

The structure of blocks is the same with a small type system, it consists of two basic kinds of blocks: CompositeBlock (like composite types), and PrimitiveBlock (like primitive types). By combining these two kinds of blocks together, we'll be able to construct a quantum circuit and represent it in a tree data structure.

Primitive Blocks

Primitive blocks are subtypes of PrimitiveBlock, they are the leaf nodes in a block tree, thus primitive types do not have subtypes.

We provide the following primitive blocks:

YaoBlocks.GeneralMatrixBlockType
GeneralMatrixBlock{M, N, MT} <: PrimitiveBlock{N}

General matrix gate wraps a matrix operator to quantum gates. This is the most general form of a quantum gate. M is the hilbert dimension (first dimension), N is the hilbert dimension (second dimension) of current quantum state. For most quantum gates, we have $M = N$.

YaoBlocks.MeasureType
Measure{N, K, OT, LT, PT, RNG} <: PrimitiveBlock{N}
Measure(n::Int; rng=Random.GLOBAL_RNG, operator=ComputationalBasis(), locs=1:n, resetto=nothing, remove=false)

Measure operator.

YaoBlocks.MeasureMethod
Measure(n::Int; rng=Random.GLOBAL_RNG, operator=ComputationalBasis(), locs=AllLocs(), resetto=nothing, remove=false)

Create a Measure block with number of qubits n.

Example

You can create a Measure block on given basis (default is the computational basis).

julia> Measure(4)
Measure(4;postprocess=NoPostProcess())

Or you could specify which qubits you are going to measure

julia> Measure(4; locs=1:3)
Measure(4;locs=(1, 2, 3), postprocess=NoPostProcess())

by default this will collapse the current register to measure results.

julia> r = normalize!(ArrayReg(bit"000") + ArrayReg(bit"111"))
ArrayReg{1, Complex{Float64}, Array...}
    active qubits: 3/3

julia> state(r)
8×1 Array{Complex{Float64},2}:
 0.7071067811865475 + 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.7071067811865475 + 0.0im

julia> r |> Measure(3)
Measure(3;postprocess=NoPostProcess())

julia> state(r)
1×1 Array{Complex{Float64},2}:
 1.0 + 0.0im

But you can also specify the target bit configuration you want to collapse to with keyword resetto.

```jldoctest; setup=:(using YaoBlocks; using BitBasis) julia> m = Measure(4; resetto=bit"0101") Measure(4;postprocess=ResetTo{BitStr{4,Int64}}(0101 ₍₂₎))

julia> m.postprocess ResetTo{BitStr{4,Int64}}(0101 ₍₂₎)```

YaoBlocks.PrimitiveBlockType
PrimitiveBlock{N} <: AbstractBlock{N}

Abstract type that all primitive block will subtype from. A primitive block is a concrete block who can not be decomposed into other blocks. All composite block can be decomposed into several primitive blocks.

Note

subtype for primitive block with parameter should implement hash and == method to enable key value cache.

YaoBlocks.RotationGateType
RotationGate{N, T, GT <: AbstractBlock{N, Complex{T}}} <: PrimitiveBlock{N, Complex{T}}

RotationGate, with GT both hermitian and isreflexive.

Definition

\[\mathbf{I} cos(θ / 2) - im sin(θ / 2) * mat(U)\]
YaoBlocks.ShiftGateType
ShiftGate <: PrimitiveBlock

Phase shift gate.

Definition

\[\begin{pmatrix} 1 & 0\ 0 & e^(im θ) \end{pmatrix}\]
YaoBlocks.TimeEvolutionType
TimeEvolution{N, TT, GT} <: PrimitiveBlock{N}

TimeEvolution, where GT is block type. input matrix should be hermitian.

!!!note: TimeEvolution contructor check hermicity of the input block by default, but sometimes it can be slow. Turn off the check manually by specifying optional parameter check_hermicity = false.

Composite Blocks

Composite blocks are subtypes of CompositeBlock, they are the composition of blocks.

We provide the following composite blocks:

YaoBlocks.AbstractContainerType
AbstractContainer{BT, N} <: CompositeBlock{N}

Abstract type for container block. Container blocks are blocks contain a single block. Container block should have a

YaoBlocks.AddType
Add{N} <: CompositeBlock{N}
Add{N}(iterable) -> Add
Add(blocks::AbstractBlock{N}...) -> Add
YaoBlocks.CachedBlockType
CachedBlock{ST, BT, N} <: TagBlock{BT, N}

A label type that tags an instance of type BT. It forwards every methods of the block it contains, except mat and apply!, it will cache the matrix form whenever the program has.

YaoBlocks.ChainBlockType
ChainBlock{N} <: CompositeBlock{N}

ChainBlock is a basic construct tool to create user defined blocks horizontically. It is a Vector like composite type.

YaoBlocks.CompositeBlockType
CompositeBlock{N} <: AbstractBlock{N}

Abstract supertype which composite blocks will inherit from. Composite blocks are blocks composited from other AbstractBlocks, thus it is a AbstractBlock as well.

YaoBlocks.DaggeredType
Daggered{N, BT} <: TagBlock{N}

Wrapper block allowing to execute the inverse of a block of quantum circuit.

YaoBlocks.DaggeredMethod
Daggered(x)

Create a Daggered block with given block x.

Example

The inverse QFT is not hermitian, thus it will be tagged with a Daggered block.

julia> A(i, j) = control(i, j=>shift(2π/(1<<(i-j+1))));

julia> B(n, i) = chain(n, i==j ? put(i=>H) : A(j, i) for j in i:n);

julia> qft(n) = chain(B(n, i) for i in 1:n);

julia> struct QFT{N} <: PrimitiveBlock{N} end

julia> QFT(n) = QFT{n}();

julia> circuit(::QFT{N}) where N = qft(N);

julia> YaoBlocks.mat(x::QFT) = mat(circuit(x));

julia> QFT(2)'
 [†]QFT{2}
YaoBlocks.KronBlockType
KronBlock{N, T, MT<:AbstractBlock} <: CompositeBlock{N, T}

composite block that combine blocks by kronecker product.

YaoBlocks.PutBlockType
PutBlock <: AbstractContainer

Type for putting a block at given locations.

YaoBlocks.ScaleType
Scale{S <: Union{Number, Val}, N, BT <: AbstractBlock{N}} <: TagBlock{BT, N}

Scale a block with scalar. it can be either a Number or a compile time Val.

Example

julia> 2 * X
[scale: 2] X

julia> im * Z
[+im] Z

julia> -im * Z
[-im] Z

julia> -Z
[-] Z
YaoBlocks.SubroutineType
Subroutine{N, T, BT <: AbstractBlock} <: AbstractContainer{BT, N, T}

Subroutine node on given locations. This allows you to shoehorn a smaller circuit to a larger one.

YaoBlocks.TagBlockType
TagBlock{BT, N} <: AbstractContainer{BT, N}

TagBlock is a special kind of Container block, it forwards most of the methods but tag the block with some extra information.

YaoBlocks.UnitaryChannelType
UnitaryChannel(operators[, weights])

Create a unitary channel, optionally weighted from an list of weights. The unitary channel is defined as below in Kraus representation

\[ϕ(ρ) = \sum_i U_i ρ U_i^†\]
Note

Unitary channel will only normalize the weights when calculating the matrix form, thus you should be careful when you need this condition for other purpose.

Note

when applying a UnitaryChannel on the register, a unitary will be sampled uniformly or optionally from given weights, then this unitary will be applied to the register.

Example

julia> UnitaryChannel([X, Y, Z])
nqubits: 1
unitary_channel
├─ [1.0] X
├─ [1.0] Y
└─ [1.0] Z

Or with weights

julia> UnitaryChannel([X, Y, Z], [0.1, 0.2, 0.7])
nqubits: 1
unitary_channel
├─ [0.1] X
├─ [0.2] Y
└─ [0.7] Z

APIs

Base.kronMethod
kron(n, blocks::Pair{<:Any, <:AbstractBlock}...)

Return a KronBlock, with total number of qubits n and pairs of blocks.

Example

Use kron to construct a KronBlock, it will put an X gate on the 1st qubit, and a Y gate on the 3rd qubit.

julia> kron(4, 1=>X, 3=>Y)
nqubits: 4
kron
├─ 1=>X
└─ 3=>Y
Base.kronMethod
kron(blocks::AbstractBlock...)
kron(n, itr)

Return a KronBlock, with total number of qubits n, and blocks should use all the locations on n wires in quantum circuits.

Example

You can use kronecker product to composite small blocks to a large blocks.

julia> kron(X, Y, Z, Z)
nqubits: 4
kron
├─ 1=>X
├─ 2=>Y
├─ 3=>Z
└─ 4=>Z
Base.kronMethod
kron(blocks...) -> f(n)
kron(itr) -> f(n)

Return a lambda, which will take the total number of qubits as input.

Example

If you don't know the number of qubit yet, or you are just too lazy, it is fine.

julia> kron(put(1=>X) for _ in 1:2)
(n -> kron(n, (n  ->  put(n, 1 => X)), (n  ->  put(n, 1 => X))))

julia> kron(X for _ in 1:2)
nqubits: 2
kron
├─ 1=>X
└─ 2=>X

julia> kron(1=>X, 3=>Y)
(n -> kron(n, 1 => X, 3 => Y))
Base.repeatMethod
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)
YaoBlocks.applymatrixMethod
applymatrix(g::AbstractBlock) -> Matrix

Transform the apply! function of specific block to dense matrix.

source
YaoBlocks.cacheFunction
cache(x[, level=1; recursive=false])

Create a CachedBlock with given block x, which will cache the matrix of x for the first time it calls mat, and use the cached matrix in the following calculations.

Example

julia> cache(control(3, 1, 2=>X))
nqubits: 3
[cached] control(1)
   └─ (2,) X


julia> chain(cache(control(3, 1, 2=>X)), repeat(H))
nqubits: 3
chain
├─ [cached] control(1)
│     └─ (2,) X
└─ repeat on (1, 2, 3)
   └─ H
YaoBlocks.chainMethod
chain(blocks...)

Return a ChainBlock which chains a list of blocks with same nqubits. If there is lazy evaluated block in blocks, chain can infer the number of qubits and create an instance itself.

YaoBlocks.chmeasureoperatorMethod
chmeasureoperator(m::Measure, op::AbstractBlock)

change the measuring operator. It will also discard existing measuring results.

YaoBlocks.cnotMethod
cnot([n, ]ctrl_locs, location)

Return a speical ControlBlock, aka CNOT gate with number of active qubits n and locs of control qubits ctrl_locs, and location of X gate.

Example

julia> cnot(3, (2, 3), 1)
nqubits: 3
control(2, 3)
└─ (1,) X

julia> cnot(2, 1)
(n -> cnot(n, 2, 1))
YaoBlocks.controlMethod
control(ctrl_locs, target) -> f(n)

Return a lambda that takes the number of total active qubits as input. See also control.

Example

julia> control((2, 3), 1=>X)
(n -> control(n, (2, 3), 1 => X))

julia> control(2, 1=>X)
(n -> control(n, 2, 1 => X))
YaoBlocks.controlMethod
control(n, ctrl_locs, target)

Return a ControlBlock with number of active qubits n and control locs ctrl_locs, and control target in Pair.

Example

julia> control(4, (1, 2), 3=>X)
nqubits: 4
control(1, 2)
└─ (3,) X

julia> control(4, 1, 3=>X)
nqubits: 4
control(1)
└─ (3,) X
YaoBlocks.controlMethod
control(target) -> f(ctrl_locs)

Return a lambda that takes a Tuple of control qubits locs as input. See also control.

Example

julia> control(1=>X)
(ctrl_locs -> control(ctrl_locs, 1 => X))

julia> control((2, 3) => YaoBlocks.ConstGate.CNOT)
(ctrl_locs -> control(ctrl_locs, (2, 3) => CNOT))
YaoBlocks.controlMethod
control(ctrl_locs::Int...) -> f(target)

Return a lambda that takes a Pair of control target as input. See also control.

Example

julia> control(1, 2)
(target -> control((1, 2), target))
YaoBlocks.czMethod
cz([n, ]ctrl_locs, location)

Return a speical ControlBlock, aka CZ gate with number of active qubits n and locs of control qubits ctrl_locs, and location of Z gate. See also cnot.

YaoBlocks.dispatch!Method
dispatch!(x::AbstractBlock, collection)

Dispatch parameters in collection to block tree x.

Note

it will try to dispatch the parameters in collection first.

source
YaoBlocks.dump_gateFunction
dump_gate(blk::AbstractBlock) -> Expr

convert a gate to a YaoScript expression for serization. The fallback is GateTypeName(fields...)

YaoBlocks.expectMethod
expect(op::AbstractBlock, reg) -> Vector
expect(op::AbstractBlock, reg => circuit) -> Vector
expect(op::AbstractBlock, density_matrix) -> Vector

Get the expectation value of an operator, the second parameter can be a register reg or a pair of input register and circuit reg => circuit.

expect'(op::AbstractBlock, reg=>circuit) -> Pair expect'(op::AbstractBlock, reg) -> AbstracRegister

Obtain the gradient with respect to registers and circuit parameters. For pair input, the second return value 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.

Note

For batched register, expect(op, reg=>circuit) returns a vector of size number of batch as output. However, one can not differentiate over a vector loss, so expect'(op, reg=>circuit) accumulates the gradient over batch, rather than returning a batched gradient of parameters.

source
YaoBlocks.map_addressFunction
map_address(block::AbstractBlock, info::AddressInfo) -> AbstractBlock

map the locations in block to target locations.

Example

map_address can be used to embed a sub-circuit to a larger one.

julia> c = chain(5, repeat(H, 1:5), put(2=>X), kron(1=>X, 3=>Y))
nqubits: 5
chain
├─ repeat on (1, 2, 3, 4, 5)
│  └─ H
├─ put on (2)
│  └─ X
└─ kron
   ├─ 1=>X
   └─ 3=>Y


julia> map_address(c, AddressInfo(10, [6,7,8,9,10]))
nqubits: 10
chain
├─ repeat on (6, 7, 8, 9, 10)
│  └─ H
├─ put on (7)
│  └─ X
└─ kron
   ├─ 6=>X
   └─ 8=>Y
YaoBlocks.matMethod
mat(A::GeneralMatrixBlock)

Return the matrix of general matrix block.

!!!warn

Instead of converting it to the default data type `ComplexF64`,
this will return its contained matrix.
YaoBlocks.matblockMethod
matblock(m::AbstractMatrix)

Create a GeneralMatrixBlock with a matrix m.

Example

julia> matblock(ComplexF64[0 1;1 0])
matblock(...)

!!!warn

Instead of converting it to the default data type `ComplexF64`,
this will return its contained matrix when calling `mat`.
YaoBlocks.operator_fidelityMethod
operator_fidelity(b1::AbstractBlock, b2::AbstractBlock) -> Number

Operator fidelity defined as

\[F^2 = \frac{1}{d^2}\left[{\rm Tr}(b1^\dagger b2)\right]\]

Here, d is the size of the Hilbert space. Note this quantity is independant to global phase. See arXiv: 0803.2940v2, Equation (2) for reference.

source
YaoBlocks.parameters!Method
parameters!(out, block)

Append all the parameters contained in block tree with given root block to out.

source
YaoBlocks.phaseMethod
phase(theta)

Returns a global phase gate. Defined with following matrix form:

\[exp(iθ) \mathbf{I}\]

Example

You can create a global phase gate with a phase (a real number).

julia> phase(0.1)
phase(0.1)
YaoBlocks.postwalkMethod
postwalk(f, src::AbstractBlock)

Walk the tree and call f after the children are visited.

source
YaoBlocks.print_annotationMethod
print_annotation(io, root, node, child, k)

Print the annotation of k-th child of node, aka the k-th element of subblocks(node).

YaoBlocks.print_prefixMethod
print_prefix(io, depth, charset, active_levels)

print prefix of a tree node in a single line.

YaoBlocks.print_treeFunction
print_tree(io, root, node[, depth=1, active_levels=()]; kwargs...)

Print the block tree.

Keywords

  • maxdepth: max tree depth to print
  • charset: default is ('├','└','│','─'). See also BlockTreeCharSet.
  • title: control whether to print the title, true or false, default is true
YaoBlocks.pswapMethod
pswap(n::Int, i::Int, j::Int, α::Real)
pswap(i::Int, j::Int, α::Real) -> f(n)

parametrized swap gate.

YaoBlocks.putMethod
put(pair) -> f(n)

Lazy curried version of put.

Example

julia> put(1=>X)
(n -> put(n, 1 => X))
YaoBlocks.putMethod
put(total::Int, pair)

Create a PutBlock with total number of active qubits, and a pair of location and block to put on.

Example

julia> put(4, 1=>X)
nqubits: 4
put on (1)
└─ X

If you want to put a multi-qubit gate on specific locations, you need to write down all possible locations.

julia> put(4, (1, 3)=>kron(X, Y))
nqubits: 4
put on (1, 3)
└─ kron
   ├─ 1=>X
   └─ 2=>Y

The outter locations creates a scope which make it seems to be a contiguous two qubits for the block inside PutBlock.

Tips

It is better to use subroutine instead of put for large blocks, since put will use the matrix of its contents directly instead of making use of what's in it. put is more efficient for small blocks.

YaoBlocks.setiparams!Method
setiparams(f, block, collection)

Set parameters of block to the value in collection mapped by f.

YaoBlocks.setiparams!Method
setiparams(f, block, symbol)

Set the parameters to a given symbol, which can be :zero, :random.

YaoBlocks.subblocksMethod
subblocks(x)

Returns an iterator of the sub-blocks of a composite block. Default is empty.

source
YaoBlocks.subroutineMethod
subroutine(n, block, locs)

Create a Subroutine block with total number of current active qubits n, which concentrates given wire location together to length(locs) active qubits, and relax the concentration afterwards.

Example

Subroutine is equivalent to put a block on given position mathematically, but more efficient and convenient for large blocks.

julia> r = rand_state(3)
ArrayReg{1, Complex{Float64}, Array...}
    active qubits: 3/3

julia> apply!(copy(r), subroutine(X, 1)) ≈ apply!(copy(r), put(1=>X))
true

It works for in-contigious locs as well

julia> r = rand_state(4)
ArrayReg{1, Complex{Float64}, Array...}
    active qubits: 4/4

julia> cc = subroutine(4, kron(X, Y), (1, 3))
nqubits: 4
Subroutine: (1, 3)
└─ kron
   ├─ 1=>X
   └─ 2=>Y

julia> pp = chain(4, put(1=>X), put(3=>Y))
nqubits: 4
chain
├─ put on (1)
│  └─ X
└─ put on (3)
   └─ Y

julia> apply!(copy(r), cc) ≈ apply!(copy(r), pp)
true
YaoBlocks.swapMethod
swap(n, loc1, loc2)

Create a n-qubit Swap gate which swap loc1 and loc2.

Example

julia> swap(4, 1, 2)
nqubits: 4
put on (1, 2)
└─ SWAP
YaoBlocks.swapMethod
swap(loc1, loc2) -> f(n)

Create a lambda that takes the total number of active qubits as input. Lazy curried version of swap(n, loc1, loc2). See also Swap.

Example

julia> swap(1, 2)
(n -> swap(n, 1, 2))
YaoBlocks.time_evolveMethod
TimeEvolution(H, dt[; tol::Real=1e-7])

Create a TimeEvolution block with Hamiltonian H and time step dt. The TimeEvolution block will use Krylove based expv to calculate time propagation.

Optional keywords are tolerance tol (default is 1e-7) TimeEvolution block can also be used for imaginary time evolution if dt is complex.

Base.:|>Method
|>(register, blk)

Pipe operator for quantum circuits.

Example

julia> ArrayReg(bit"0") |> X |> Y
Warning

|> is equivalent to apply!, which means it has side effects. You need to copy original register, if you do not want to change it in-place.

YaoBlocks.cunmatFunction
cunmat(nbit::Int, cbits::NTuple{C, Int}, cvals::NTuple{C, Int}, U0::AbstractMatrix, locs::NTuple{M, Int}) where {C, M} -> AbstractMatrix

control-unitary matrix

YaoBlocks.decode_signMethod
decode_sign(ctrls...)

Decode signs into control sequence on control or inversed control.

YaoBlocks.eigenbasisMethod
eigenbasis(op::AbstractBlock{N})

Return the eigenvalue and eigenvectors of target operator. By applying eigenvector' to target state, one can swith the basis to the eigenbasis of this operator. However, eigenvalues does not have a specific form.

YaoBlocks.gate_exprMethod
gate_expr(::Val{G}, args, info)

Obtain the gate constructior from its YaoScript expression. G is a symbol for the gate type, the default constructor is G(args...). info contains the informations about the number of qubit and Yao version.

YaoBlocks.getcolMethod
getcol(csc::SDparseMatrixCSC, icol::Int) -> (View, View)

get specific col of a CSC matrix, returns a slice of (rowval, nzval)

YaoBlocks.num_nonzeroFunction
num_nonzero(nbits, nctrls, U)

Return number of nonzero entries of the matrix form of control-U gate. nbits is the number of qubits, and nctrls is the number of control qubits.

YaoBlocks.parse_blockFunction
parse_block(n, ex)

This function parse the julia object ex to a quantum block, it defines the syntax of high level interfaces. ex can be a function takes number of qubits n as input or it can be a pair.

YaoBlocks.print_subtypetreeFunction
print_subtypetree(::Type[, level=1, indent=4])

Print subtype tree, level specify the depth of the tree.

YaoBlocks.setcol!Method
setcol!(csc::SparseMatrixCSC, icol::Int, rowval::AbstractVector, nzval) -> SparseMatrixCSC

set specific col of a CSC matrix

YaoBlocks.u1ij!Function
u1ij!(target, i, j, a, b, c, d)

single u1 matrix into a target matrix.

Note

For coo, we take an additional parameter * ptr: starting position to store new data.

YaoBlocks.unmatMethod
unmat(nbit::Int, U::AbstractMatrix, locs::NTuple) -> AbstractMatrix

Return the matrix representation of putting matrix at locs.