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.

YaoAPI.PrimitiveBlockType
PrimitiveBlock{D} <: AbstractBlock{D}

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.

Required Methods

Optional Methods

source

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{D, MT} <: PrimitiveBlock{D}
GeneralMatrixBlock{D}(m, n, A, tag="matblock(...)")
GeneralMatrixBlock(A; nlevel=2, tag="matblock(...)")

General matrix gate wraps a matrix operator to quantum gates. This is the most general form of a quantum gate.

Arguments

  • m and n are the number of dits in row and column.
  • A is a matrix.
  • tag is the printed information.
  • D and nlevel are the number of levels in each qudit.
source
YaoBlocks.MeasureType
Measure{D,K, OT, LT, PT, RNG} <: PrimitiveBlock{D}
Measure(n::Int; rng=Random.GLOBAL_RNG, operator=ComputationalBasis(), locs=1:n, resetto=nothing, remove=false, nlevel=2)

Measure operator, currently only qudits are supported.

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

Create a Measure block with number of qudits n.

Examples

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

julia> Measure(4)
Measure(4)

Or you could specify which qudits you are going to measure

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

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

julia> r = normalize!(arrayreg(bit"000") + arrayreg(bit"111"))
ArrayReg{2, ComplexF64, Array...}
    active qubits: 3/3
    nlevel: 2

julia> state(r)
8×1 Matrix{ComplexF64}:
 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)
ArrayReg{2, ComplexF64, Array...}
    active qubits: 3/3
    nlevel: 2

julia> state(r)
8×1 Matrix{ComplexF64}:
 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
 1.0 + 0.0im

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

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

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

source
YaoBlocks.ProjectorType
struct Projector{D, T, AT<:(AbstractArrayReg{D, T})} <: PrimitiveBlock{D}

Projection operator to target state psi.

Definition

projector(s) defines the following operator.

\[|s⟩ → |s⟩⟨s|\]

source
YaoBlocks.ReflectGateType
ReflectGate{D, T, Tt, AT<:AbstractArrayReg{D, T}} = TimeEvolution{D,Tt,Projector{D,T,AT}}

Let |v⟩ be a quantum state vector, a reflection gate is a unitary operator that defined as the following operation.

\[|v⟩ → 1 - (1-exp(-iθ)) |v⟩⟨v|\]

When $θ = π$, it defines a standard reflection gate $1-2|v⟩⟨v|$.

source
YaoBlocks.RotationGateType
RotationGate{D,T,GT<:AbstractBlock{D}} <: PrimitiveBlock{D}

RotationGate, with GT both hermitian and isreflexive.

Definition

Expression rot(G, θ) defines the following gate

\[\cos \frac{\theta}{2}I - i \sin \frac{\theta}{2} G\]

source
YaoBlocks.TimeEvolutionType
TimeEvolution{D, TT, GT} <: PrimitiveBlock{D}

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.

source
YaoBlocks.ConstGate.@const_gateMacro
@const_gate <gate name> = <expr>
@const_gate <gate name>::<type> = <expr>
@const_gate <gate>::<type>

This macro simplify the definition of a constant gate. It will automatically bind the matrix form to a constant which will reduce memory allocation in the runtime.

Examples

@const_gate X = ComplexF64[0 1;1 0]

or

@const_gate X::ComplexF64 = [0 1;1 0]

You can bind new element types by simply re-declare with a type annotation.

@const_gate X::ComplexF32
source

Composite Blocks

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

We provide the following composite blocks:

YaoBlocks.AbstractAddType
AbstractAdd{D} <: CompositeBlock{D}

The abstract add interface, aimed to support Hamiltonian types.

Required Interfaces

  • chsubblocks
  • subblocks

Provides

  • unsafe_apply! and its backward
  • mat and its backward
  • adjoint
  • occupied_locs
  • getindex over dit strings
  • ishermitian
source
YaoBlocks.AddType
Add{D} <: AbstractAdd{D}
Add(blocks::AbstractBlock...) -> Add

Type for block addition.

julia> X + X
nqubits: 1
+
├─ X
└─ X
source
YaoBlocks.CachedBlockType
CachedBlock{ST, BT, D} <: TagBlock{BT, D}

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.

source
YaoBlocks.ChainBlockType
ChainBlock{D} <: CompositeBlock{D}

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

source
YaoBlocks.ControlBlockType

A control block is a composite block that applies a block when the control qubits are all ones.

Note

If control qubit index is negative, it means the inverse control, i.e., the block is applied when the control qubit is zero.

Fields

  • n::Int64

  • ctrl_locs::Tuple{Vararg{Int64, C}} where C

  • ctrl_config::Tuple{Vararg{Int64, C}} where C

  • content::AbstractBlock

  • locs::Tuple{Vararg{Int64, M}} where M

source
YaoBlocks.DaggeredType
Daggered{BT, D} <: TagBlock{BT,D}

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

source
YaoBlocks.DaggeredMethod
Daggered(block)

Create a Daggered block. Let $G$ be a input block, G' or Daggered(block) in code represents $G^\dagger$.

Examples

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 <: PrimitiveBlock{2} n::Int end

julia> YaoBlocks.nqudits(q::QFT) = q.n


julia> circuit(q::QFT) = qft(nqubits(q));

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

julia> QFT(2)'
 [†]QFT
source
YaoBlocks.KronBlockType
KronBlock{D,M,MT<:NTuple{M,Any}} <: CompositeBlock{D}

composite block that combine blocks by kronecker product.

source
YaoBlocks.PSwapType
PSwap = PutBlock{2,2,RotationGate{2,T,G}} where {G<:ConstGate.SWAPGate}
PSwap(n::Int, locs::Tuple{Int,Int}, θ::Real)

Parametrized swap gate that swaps two qubits with a phase, defined as

\[{\rm SWAP}(θ) = e^{-iθ{\rm SWAP}/2}\]

source
YaoBlocks.PutBlockType
PutBlock{D,C,GT<:AbstractBlock} <: AbstractContainer{GT,D}

Type for putting a block at given locations.

source
YaoBlocks.ScaleType
Scale{S <: Union{Number, Val}, D, BT <: AbstractBlock{D}} <: TagBlock{BT, D}
Scale(factor, block)

Multiply a block with a scalar factor, which can be a number or a Val. If the factor is a number, it is regarded as a parameter that can be changed dynamically. If the factor is a Val, it is regarded as a constant.

Examples

julia> 2 * X
[scale: 2] X

julia> im * Z
[+im] Z

julia> -im * Z
[-im] Z

julia> -Z
[-] Z
source
YaoBlocks.SubroutineType
Subroutine{D, BT <: AbstractBlock, C} <: AbstractContainer{BT, D}

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

source
YaoBlocks.SwapType
Swap = PutBlock{2,2,G} where {G<:ConstGate.SWAPGate}
Swap(n::Int, locs::Tuple{Int,Int})

Swap gate, which swaps two qubits.

source
YaoBlocks.UnitaryChannelType
UnitaryChannel{D, W<:AbstractVector} <: CompositeBlock{D}
UnitaryChannel(operators, probs)

Create a unitary channel, where probs is a real vector that sum up to 1.

source

Operations on Blocks

YaoAPI.unsafe_apply!Function
unsafe_apply!(r, block)

Similar to apply!, but will not check the size of the register and block, this is mainly used for overloading new blocks, use at your own risk.

source
YaoAPI.apply!Function
apply!(register, block)

Apply a block (of quantum circuit) to a quantum register.

Note

to overload apply! for a new block, please overload the unsafe_apply! function with same interface. Then the apply! interface will do the size checks on inputs automatically.

Examples

julia> r = zero_state(2)
ArrayReg{2, ComplexF64, Array...}
    active qubits: 2/2
    nlevel: 2

julia> apply!(r, put(2, 1=>X))
ArrayReg{2, ComplexF64, Array...}
    active qubits: 2/2
    nlevel: 2

julia> measure(r;nshots=10)
10-element Vector{DitStr{2, 2, Int64}}:
 01 ₍₂₎
 01 ₍₂₎
 01 ₍₂₎
 01 ₍₂₎
 01 ₍₂₎
 01 ₍₂₎
 01 ₍₂₎
 01 ₍₂₎
 01 ₍₂₎
 01 ₍₂₎
source
YaoBlocks.applyFunction
apply(register, block)

The non-inplace version of applying a block (of quantum circuit) to a quantum register. Check apply! for the faster inplace version.

source
YaoAPI.getiparamsFunction
getiparams(block)

Returns the intrinsic parameters of node block, default is an empty tuple.

Examples

julia> getiparams(Rx(0.1))
0.1
source
YaoAPI.render_paramsFunction
render_params(r::AbstractBlock, params)

This function renders the input parameter to a consumable type to r. params can be a number or a symbol like :zero and :random.

Examples

julia> collect(render_params(Rx(0.1), :zero))
1-element Vector{Float64}:
 0.0
source
YaoAPI.nparametersFunction
nparameters(block) -> Int

Return number of parameters in block. See also niparams.

Examples

julia> nparameters(chain(Rx(0.1), Rz(0.2)))
2
source
YaoAPI.occupied_locsFunction
occupied_locs(x)

Return a tuple of occupied locations of x.

Examples

julia> occupied_locs(kron(5, 1=>X, 3=>X))
(1, 3)

julia> occupied_locs(kron(5, 1=>X, 3=>I2))
(1,)
source
YaoAPI.print_blockFunction
print_block(io, block)

Define how blocks are printed as text in one line.

Examples

julia> print_block(stdout, X)
X

julia> print_block(stdout, put(2, 1=>X))
put on (1)
source
YaoAPI.apply_back!Function
apply_back!((ψ, ∂L/∂ψ*), circuit::AbstractBlock, collector) -> AbstractRegister

back propagate and calculate the gradient ∂L/∂θ = 2Re(∂L/∂ψ⋅∂ψ/∂θ), given ∂L/∂ψ. ψ is the output register, ∂L/∂ψ* should also be register type.

Note: gradients are stored in Diff blocks, it can be access by either diffblock.grad or gradient(circuit). Note2: now apply_back! returns the inversed gradient!

source
YaoAPI.mat_back!Function
mat_back!(T, rb::AbstractBlock, adjy, collector)

Back propagate the matrix gradients.

source

Error and Exceptions

Extending Blocks

Blocks are defined as a sub-type system inside Julia, you could extend it by defining new Julia types by subtyping abstract types we provide. But we also provide some handy tools to help you create your own blocks.

Define Custom Constant Blocks

Constant blocks are used quite often and in numerical simulation we would expect it to be a real constant in the program, which means it won't allocate new memory when we try to get its matrix for several times, and it won't change with parameters.

In Yao, you can simply define a constant block with @const_gate, with the corresponding matrix:


julia> @const_gate Rand = rand(ComplexF64, 4, 4)

This will automatically create a type RandGate{T} and a constant binding Rand to the instance of RandGate{ComplexF64}, and it will also bind a Julia constant for the given matrix, so when you call mat(Rand), no allocation will happen.

julia> @allocated mat(Rand)ERROR: UndefVarError: `mat` not defined

If you want to use other data type like ComplexF32, you could directly call Rand(ComplexF32), which will create a new instance with data type ComplexF32.

julia> Rand(ComplexF32)ERROR: UndefVarError: `Rand` not defined

But remember this won't bind the matrix, it only binds the matrix you give

julia> @allocated mat(Rand(ComplexF32))ERROR: UndefVarError: `Rand` not defined

so if you want to make the matrix call mat for ComplexF32 to have zero allocation as well, you need to do it explicitly.

julia> @const_gate Rand::ComplexF32ERROR: LoadError: UndefVarError: `@const_gate` not defined
in expression starting at REPL[1]:1

Define Custom Blocks

Primitive blocks are the most basic block to build a quantum circuit, if a primitive block has a certain structure, like containing tweakable parameters, it cannot be defined as a constant, thus create a new type by subtyping PrimitiveBlock is necessary

using YaoBlocks

mutable struct PhaseGate{T <: Real} <: PrimitiveBlock{1}
    theta::T
end

If your insterested block is a composition of other blocks, you should define a CompositeBlock, e.g

struct ChainBlock{N} <: CompositeBlock{N}
    blocks::Vector{AbstractBlock{N}}
end

Besides types, there are several interfaces you could define for a block, but don't worry, they should just error if it doesn't work.

Define the matrix

The matrix form of a block is the minimal requirement to make a custom block functional, defining it is super simple, e.g for phase gate:

mat(::Type{T}, gate::PhaseGate) where T = exp(T(im * gate.theta)) * Matrix{Complex{T}}(I, 2, 2)

Or for composite blocks, you could just calculate the matrix by call mat on its subblocks.

mat(::Type{T}, c::ChainBlock) where T = prod(x->mat(T, x), reverse(c.blocks))

The rest will just work, but might be slow since you didn't define any specification for this certain block.

Define how blocks are applied to registers

Although, having its matrix is already enough for applying a block to register, we could improve the performance or dispatch to other actions by overloading apply! interface, e.g we can use specialized instruction to make X gate (a builtin gate defined @const_gate) faster:

function apply!(r::ArrayReg, x::XGate)
    nactive(r) == 1 || throw(QubitMismatchError("register size $(nactive(r)) mismatch with block size $N"))
    instruct!(matvec(r.state), Val(:X), (1, ))
    return r
end

In Yao, this interface allows us to provide more aggressive specialization on different patterns of quantum circuits to accelerate the simulation etc.

Define Parameters

If you want to use some member of the block to be parameters, you need to declare them explicitly

niparams(::Type{<:PhaseGate}) = 1
getiparams(x::PhaseGate) = x.theta
setiparams!(r::PhaseGate, param::Real) = (r.theta = param; r)

Define Adjoint

Since blocks are actually quantum operators, it makes sense to call their adjoint as well. We provide Daggered for general purpose, but some blocks may have more specific transformation rules for adjoints, e.g

Base.adjoint(x::PhaseGate) = PhaseGate(-x.theta)

Define Cache Keys

To enable cache, you should define cache_key, e.g for phase gate, we only cares about its phase, instead of the whole instance

cache_key(gate::PhaseGate) = gate.theta

APIs

Base.:|>Method
|>(register, circuit) -> register

Apply a quantum circuits to register, which modifies the register directly.

Example

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

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

source
Base.kronMethod
kron(n, locs_and_blocks::Pair{<:Any, <:AbstractBlock}...) -> KronBlock

Returns a n-qudit KronBlock. The inputs contains a list of location-block pairs, where a location can be an integer or a range. It is conceptually a chain of put block without address conflicts, but it has a richer type information that can be useful for various purposes such as more efficient mat function.

Let $I$ be a $2\times 2$ identity matrix, $G$ and $H$ be two $2\times 2$ matrix, the matrix representation of kron(n, i=>G, j=>H) (assume $j > i$) is defined as

\[I^{\otimes n-j} \otimes H \otimes I^{\otimes j-i-1} \otimes G \otimes I^{i-1}\]

For multiple locations, the expression can be complicated.

Examples

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

Examples

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
source
Base.kronMethod
kron(blocks...) -> f(n)
kron(itr) -> f(n)

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

Examples

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)...))
source
Base.repeatMethod
repeat(n, subblock::AbstractBlock[, locs]) -> RepeatedBlock{n}

Create a n-qudit RepeatedBlock block, which is conceptually a [kron] block with all gates being the same. If locs is provided, repeat on locs, otherwise repeat on all locations. Let $G$ be a $2\times 2$ matrix, the matrix representation of repeat(n, X) is

\[X^{\otimes n}\]

The RepeatedBlock can be used to accelerate repeated applying certain gate types: X, Y, Z, S, T, Sdag, and Tdag.

Examples

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
YaoAPI.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
YaoAPI.expectMethod
expect(op::AbstractBlock, reg) -> Real
expect(op::AbstractBlock, reg => circuit) -> Real
expect(op::AbstractBlock, density_matrix) -> Real

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
YaoAPI.getiparamsMethod
getiparams(block)

Returns the intrinsic parameters of node block, default is an empty tuple.

source
YaoAPI.matMethod
mat([T=ComplexF64], blk)

Returns the matrix form of given block.

source
YaoAPI.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.

source
YaoAPI.operator_fidelityMethod
operator_fidelity(b1::AbstractBlock, b2::AbstractBlock) -> Number

Operator fidelity defined as

\[F^2 = \frac{1}{d}\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
YaoAPI.parametersMethod
parameters(block)

Returns all the parameters contained in block tree with given root block.

source
YaoAPI.setiparams!Function
setiparams!([f], block, itr)
setiparams!([f], block, params...)

Set the parameters of block. When f is provided, set parameters of block to the value in collection mapped by f. iter can be an iterator or a symbol, the symbol can be :zero, :random.

source
YaoAPI.subblocksMethod
subblocks(x)

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

source
YaoBlocks.applyMethod
apply(register, block)

The non-inplace version of applying a block (of quantum circuit) to a quantum register. Check apply! for the faster inplace version.

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

Examples

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
source
YaoBlocks.chainMethod
chain(n)

Return an empty ChainBlock which can be used like a list of blocks.

Examples

julia> chain(2)
nqubits: 2
chain


julia> chain(2; nlevel=3)
nqudits: 2
chain

source
YaoBlocks.chainMethod
chain(blocks...) -> ChainBlock
chain(n) -> ChainBlock

Return a ChainBlock which chains a list of blocks with the same number of qudits. Let $G_i$ be a sequence of n-qudit blocks, the matrix representation of block chain(G_1, G_2, ..., G_m) is

\[G_m G_{m-1}\ldots G_1\]

It is almost equivalent to matrix multiplication except the order is reversed. We make its order different from regular matrix multiplication because quantum circuits can be represented more naturally in this form.

Examples

julia> chain(X, Y, Z)
nqubits: 1
chain
├─ X
├─ Y
└─ Z

julia> chain(2, put(1=>X), put(2=>Y), cnot(2, 1))
nqubits: 2
chain
├─ put on (1)
│  └─ X
├─ put on (2)
│  └─ Y
└─ control(2)
   └─ (1,) X
source
YaoBlocks.chmeasureoperatorMethod
chmeasureoperator(m::Measure, op::AbstractBlock)

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

source
YaoBlocks.cleanupMethod
cleanup(entries::EntryTable; zero_threshold=0.0)

Clean up the entry table by 1) sort entries, 2) merge items and 3) clean up zeros. Any value with amplitude ≤ zero_threshold will be regarded as zero.

julia> et = EntryTable([bit"000",bit"011",bit"101",bit"101",bit"011",bit"110",bit"110",bit"011",], [1.0 + 0.0im,-1, 1,1,1,-1,1,1,-1])
EntryTable{DitStr{2, 3, Int64}, ComplexF64}:
  000 ₍₂₎   1.0 + 0.0im
  011 ₍₂₎   -1.0 + 0.0im
  101 ₍₂₎   1.0 + 0.0im
  101 ₍₂₎   1.0 + 0.0im
  011 ₍₂₎   1.0 + 0.0im
  110 ₍₂₎   -1.0 + 0.0im
  110 ₍₂₎   1.0 + 0.0im
  011 ₍₂₎   1.0 + 0.0im


julia> cleanup(et)
EntryTable{DitStr{2, 3, Int64}, ComplexF64}:
  000 ₍₂₎   1.0 + 0.0im
  011 ₍₂₎   1.0 + 0.0im
  101 ₍₂₎   2.0 + 0.0im
source
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.

Examples

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

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

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

Examples

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

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

Return a n-qubit ControlBlock, where the control locations ctrl_locs and the subblock locations in the third argument can be an integer, a tuple or a range, and the size of the subblock should match the length of locations. Let $I$ be the $2 \times 2$ identity matrix, $G$ be a $2 \times 2$ subblock, $P_0=|0\rangle\langle 0|$ and $P_1=|1\rangle\langle 1|$ be two single qubit projection operators to subspace $|0\rangle$ and $|1\rangle$, $i$ and $j$ be two integers that $i>j$. The matrix representation of control(n, i, j=>G) is

\[\begin{align} &I^{\otimes n-i} P_0 \otimes I^{\otimes i-j-1} \otimes I\otimes I^{\otimes j-1} +\\ & I^{\otimes n-i} P_1 \otimes I^{\otimes i-j-1} \otimes G\otimes I^{\otimes j-1} \end{align}\]

The multi-controlled multi-qubit controlled block is more complicated, it means apply the gate when control qubits are all ones. Each control location can take a negative sign to represent the inverse control, meaning only when this qubit is 0, the controlled gate is applied.

Examples

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
source
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

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

Examples

julia> cz(2, 1, 2)
nqubits: 2
control(1)
└─ (2,) Z
source
YaoBlocks.dispatchMethod
dispatch(x::AbstractBlock, collection)

Dispatch parameters in collection to block tree x, the generic non-inplace version.

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

source
YaoBlocks.eigenbasisMethod
eigenbasis(op::AbstractBlock)

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.

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

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

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

source
YaoBlocks.igateMethod
igate(n::Int; nlevel=2)

The constructor for IdentityGate. Let $I_d$ be a $d \times d$ identity matrix, igate(n; nlevel=d) is defined as $I_d^{\otimes n}$.

Examples

julia> igate(2)
igate(2)

julia> igate(2; nlevel=3)
igate(2;nlevel=3)
source
YaoBlocks.iscleanMethod
isclean(entries::EntryTable; zero_threshold=0.0)

Return true if the entries are ordered, unique and amplitudes are nonzero. Any value with amplitude ≤ zero_threshold will be regarded as zero.

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
source
YaoBlocks.matblockMethod
matblock(mat_or_block; nlevel=2, tag="matblock(...)")

Create a GeneralMatrixBlock with a matrix m.

Examples

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`.
source
YaoBlocks.num_nonzeroFunction
num_nonzero(nbits, nctrls, U, [N])

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.

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

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

source
YaoBlocks.parameters_rangeMethod
parameters_range(block)

Return the range of real parameters present in block.

Note

It may not be the case that length(parameters_range(block)) == nparameters(block).

Example

julia> YaoBlocks.parameters_range(RotationGate(X, 0.1))
1-element Vector{Tuple{Float64, Float64}}:
 (0.0, 6.283185307179586)
source
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.

source
YaoBlocks.phaseMethod
phase(theta)

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

\[e^{iθ} I\]

Examples

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

julia> phase(0.1)
phase(0.1)
source
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).

source
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 YaoBlocks.BlockTreeCharSet.
  • title: control whether to print the title, true or false, default is true
source
YaoBlocks.projectionMethod
projection(y::AbstractMatrix, op::AbstractMatrix) -> typeof(y)

Project op to sparse matrix with same sparsity as y.

source
YaoBlocks.projectorMethod
projector(v::AbstractArrayReg) -> Projector

Create a Projector with an quantum state vector v.

Example

julia> projector(rand_state(3))
|s⟩⟨s|, nqudits = 3
source
YaoBlocks.pswapMethod
pswap(n::Int, i::Int, j::Int, α::Real)
pswap(i::Int, j::Int, α::Real) -> f(n)

parametrized swap gate.

Examples

julia> pswap(2, 1, 2, 0.1)
nqubits: 2
put on (1, 2)
└─ rot(SWAP, 0.1)
source
YaoBlocks.putMethod
put(pair) -> f(n)

Lazy curried version of put.

Examples

julia> put(1=>X)
(n -> put(n, 1 => X))
source
YaoBlocks.putMethod
put(n::Int, locations => subblock) => PutBlock

Create a n-qudit PutBlock. The second argument is a pair of locations and subblock, where the locations can be a tuple, an integer or a range and the subblock size should match the length of locations. Let $I$ be a $2\times 2$ identity matrix and $G$ be a $2\times 2$ matrix, the matrix representation of put(n, i=>G) is defined as

\[I^{\otimes n-i} \otimes G \otimes I^{\otimes i-1}\]

For multiple locations, the expression can be complicated, which corresponds to the matrix representation of multi-qubit gate applied on n-qubit space in quantum computing.

Examples

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.

source
YaoBlocks.rand_hermitianMethod
rand_hermitian([T=ComplexF64], N::Int) -> Matrix

Create a random hermitian matrix.

julia> ishermitian(rand_hermitian(2))
true
source
YaoBlocks.rand_unitaryMethod
rand_unitary([T=ComplexF64], N::Int) -> Matrix

Create a random unitary matrix.

Examples

julia> isunitary(rand_unitary(2))
true

julia> eltype(rand_unitary(ComplexF32, 2))
ComplexF32 (alias for Complex{Float32})
source
YaoBlocks.reflectFunction
reflect(
    v::AbstractArrayReg
) -> ReflectGate{D, T, Irrational{:π}, AT} where {D, T, AT<:(AbstractArrayReg{D, T})}
reflect(
    v::AbstractArrayReg,
    θ::Real
) -> ReflectGate{_A, T, Tt, AT} where {_A, Tt<:Real, T, AT<:(AbstractArrayReg{_A, T})}

Create a ReflectGate with respect to an quantum state vector v.

Example

julia> reflect(rand_state(3))
Time Evolution Δt = π, tol = 1.0e-7
|s⟩⟨s|, nqudits = 3
source
YaoBlocks.sandwichMethod
sandwich(bra::AbstractRegister, op::AbstractBlock, ket::AbstracRegister) -> Complex

Compute the sandwich function ⟨bra|op|ket⟩.

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

set specific col of a CSC matrix

source
YaoBlocks.setiparamsFunction
setiparams([f], block, itr)
setiparams([f], block, params...)

Set the parameters of block, the non-inplace version. When f is provided, set parameters of block to the value in collection mapped by f. iter can be an iterator or a symbol, the symbol can be :zero, :random.

source
YaoBlocks.shiftMethod
shift(θ)

Create a ShiftGate with phase θ.

\[\begin{pmatrix} 1 & 0\\ 0 & e^{i\theta} \end{pmatrix}\]

Examples

julia> shift(0.1)
shift(0.1)
source
YaoBlocks.single_qubit_depolarizing_channelMethod
single_qubit_depolarizing_channel(p::Real)

Create a single-qubit depolarizing channel.

The factor of 3/4 in front of p ensures that single_qubit_depolarizing_channel(p) == depolarizing_channel(1, p)

\[(1 - 3p/4 ⋅ρ) + p/4⋅(XρX + YρY + ZρZ)\]

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

Create a n-qudit Subroutine block, where the subblock is a subprogram of size m, and locs is a tuple or range of length m. It runs a quantum subprogram with smaller size on a subset of locations. While its mathematical definition is the same as the put block, while it is more suited for running a larger chunk of circuit.

Examples

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{2, ComplexF64, Array...}
    active qubits: 3/3
    nlevel: 2

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{2, ComplexF64, Array...}
    active qubits: 4/4
    nlevel: 2

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
source
YaoBlocks.swapMethod
swap(n, loc1, loc2)

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

Examples

julia> swap(4, 1, 2)
nqubits: 4
put on (1, 2)
└─ SWAP
source
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.

Examples

julia> swap(1, 2)
(n -> swap(n, 1, 2))
source
YaoBlocks.time_evolveMethod
time_evolve(H, dt[; tol=1e-7, check_hermicity=true])

Create a TimeEvolution block with Hamiltonian H and time step dt. The TimeEvolution block will use Krylove based expv to calculate time propagation. TimeEvolution block can also be used for imaginary time evolution if dt is complex. Let $H$ be a hamiltonian and $t$ be a time, the matrix representation of time_evolve(H, t) is $e^{-iHt}$.

Arguments

  • H the hamiltonian represented as an AbstractBlock.
  • dt: the evolution duration (start time is zero).

Keyword Arguments

  • tol::Real=1e-7: error tolerance.
  • check_hermicity=true: check hermicity or not.

Examples

julia> time_evolve(kron(2, 1=>X, 2=>X), 0.1)
Time Evolution Δt = 0.1, tol = 1.0e-7
kron
├─ 1=>X
└─ 2=>X
source
YaoBlocks.two_qubit_depolarizing_channelMethod
two_qubit_depolarizing_channel(p::Real)

Create a two-qubit depolarizing channel. Note that this is not the same as kron(single_qubit_depolarizing_channel(p), single_qubit_depolarizing_channel(p)).

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

source
YaoBlocks.unitary_channelMethod
unitary_channel(operators, probs) -> UnitaryChannel

Returns a UnitaryChannel instance, where `operators is a list of operators, probs is a real vector that sum up to 1. The unitary channel is defined as below

\[\phi(\rho) = \sum_i p_i U_i ρ U_i^\dagger,\]

where $\rho$ in a DensityMatrix as the register to apply on, $p_i$ is the i-th element in probs, U_i is the i-th operator in operators.

Examples

julia> unitary_channel([X, Y, Z], [0.1, 0.2, 0.7])
nqubits: 1
unitary_channel
├─ [0.1] X
├─ [0.2] Y
└─ [0.7] Z
source
YaoBlocks.unmatMethod
unmat(::Val{D}, nbit::Int, U::AbstractMatrix, locs::NTuple) -> AbstractMatrix

Return the matrix representation of putting matrix at locs.

source