Blocks

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:

GeneralMatrixBlock{M, N, T, MT} <: PrimitiveBlock{N, T}

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

Measure{N, K, OT} <: PrimitiveBlock{N, Bool}
Measure(n::Int; operator=ComputationalBasis(), locs=1:n, collapseto=nothing, remove=false)

Measure operator.

PhiGate

Global phase gate.

PrimitiveBlock{N, T} <: AbstractBlock{N, T}

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.

ReflectGate{N, T, Tr} <: PrimitiveBlock{N, T}

Reflection operator to target state psi.

Definition

\[|ψ⟩ → 2 |s⟩⟨s| - 1\]
ReflectGate(r::AbstractVector)

Create a ReflectGate with a quantum state vector v.

ReflectGate(r::ArrayReg{1})

Create a ReflectGate with a quantum register r.

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

RotationGate, with GT both hermitian and isreflexive.

ShiftGate <: PrimitiveBlock

Phase shift gate.

TimeEvolution{N, TT, GT} <: PrimitiveBlock{N, ComplexF64}

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.

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.

Composite Blocks

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

We provide the following composite blocks:

AbstractContainer{BT, N, T} <: CompositeBlock{N, T}

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

CachedBlock{ST, BT, N, T} <: TagBlock{BT, N, T}

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.

ChainBlock{N, T} <: CompositeBlock{N, T}

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

CompositeBlock{N, T} <: AbstractBlock{N, T}

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

Concentrator{N, T, BT <: AbstractBlock} <: AbstractContainer{BT, N, T}

concentrates serveral lines together in the circuit, and expose it to other blocks.

Daggered{N, T, BT} <: TagBlock{N, T}

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

KronBlock{N, T, MT<:AbstractBlock} <: CompositeBlock{N, T}

composite block that combine blocks by kronecker product.

PauliString(list::Vector)

Create a PauliString from a list of Pauli gates.

Example

julia> PauliString([X, Y, Z])
nqubits: 3, datatype: Complex{Float64}
PauliString
├─ X gate
├─ Y gate
└─ Z gate
PauliString(xs::PauliGate...)

Create a PauliString from some Pauli gates.

Example

julia> PauliString(X, Y, Z)
nqubits: 3, datatype: Complex{Float64}
PauliString
├─ X gate
├─ Y gate
└─ Z gate
PutBlock <: AbstractContainer

Type for putting a block at given locations.

RepeatedBlock <: AbstractContainer

Repeat the same block on given locations.

Roller{N, T, BT <: Tuple} <: CompositeBlock{N, T}

Roller block.

TagBlock{BT, N, T} <: AbstractContainer{BT, N, T}

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

APIs

Base.kronMethod.
kron(n, blocks::Pair{Int, <: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, datatype: Complex{Float64}
kron
├─ 1=>X gate
└─ 3=>Y gate
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, datatype: Complex{Float64}
kron
├─ 1=>X gate
├─ 2=>Y gate
├─ 3=>Z gate
└─ 4=>Z gate
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 gate)), (n  ->  put(n, 1 => X gate))))

julia> kron(X for _ in 1:2)
nqubits: 2, datatype: Complex{Float64}
kron
├─ 1=>X gate
└─ 2=>X gate

julia> kron(1=>X, 3=>Y)
(n -> kron(n, 1 => X gate, 3 => Y gate))
Base.repeatMethod.
repeat(x::AbstractBlock, locs)

Lazy curried version of repeat.

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, datatype: Complex{Float64}
repeat on (1, 2, 3, 4)
└─ X gate

You can also specify the location

julia> repeat(4, X, (1, 2))
nqubits: 4, datatype: Complex{Float64}
repeat on (1, 2)
└─ X gate

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, datatype: Complex{Float64}
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, datatype: Complex{Float64}
repeat on (1, 2, 3, 4)
└─ phase(0.2)
YaoBlocks.RxMethod.
Rx(theta)

Return a RotationGate on X axis.

YaoBlocks.RyMethod.
Ry(theta)

Return a RotationGate on Y axis.

YaoBlocks.RzMethod.
Rz(theta)

Return a RotationGate on Z axis.

YaoBlocks.apply!Method.
apply!(register, block)

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

source
applymatrix(g::AbstractBlock) -> Matrix

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

source
cache_key(block)

Returns the key that identify the matrix cache of this block. By default, we use the returns of parameters as its key.

source
cache_type(::Type) -> DataType

Return the element type that a CacheFragment will use.

source
YaoBlocks.chainMethod.
chain([T=ComplexF64], n)

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

YaoBlocks.chainMethod.
chain()

Return an lambda n->chain(n).

YaoBlocks.chainMethod.
chain(blocks...)

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

chcontent(x, blk)

Create a similar block of x and change its content to blk.

source
chsubblocks(composite_block, itr)

Change the sub-blocks of a CompositeBlock with given iterator itr.

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.

Example

julia> cnot(3, (2, 3), 1)
nqubits: 3, datatype: Complex{Float64}
control(2, 3)
└─ (1,) X gate

julia> cnot(2, 1)
(n -> cnot(n, 2, 1))
collect_blocks(block_type, root)

Return a ChainBlock with all block of block_type in root.

source
concentrate(block, locs) -> f(n)

Lazy curried version of concentrate.

concentrate(n, block, locs)

Create a Concentrator 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

Concentrator 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), concentrate(X, 1)) ≈ apply!(copy(r), put(1=>X))
true

It works for in-contigious locations as well

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

julia> cc = concentrate(4, kron(X, Y), (1, 3))
nqubits: 4, datatype: Complex{Float64}
Concentrator: (1, 3)
└─ kron
   ├─ 1=>X gate
   └─ 2=>Y gate

julia> pp = chain(4, put(1=>X), put(3=>Y))
nqubits: 4, datatype: Complex{Float64}
chain
├─ put on (1)
│  └─ X gate
└─ put on (3)
   └─ Y gate

julia> apply!(copy(r), cc) ≈ apply!(copy(r), pp)
true
YaoBlocks.contentMethod.
content(x)

Returns the content of x.

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.

Example

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

julia> control(2, 1=>X)
(n -> control(n, 2, 1 => X gate))
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, datatype: Complex{Float64}
control(1, 2)
└─ (3,) X gate

julia> control(4, 1, 3=>X)
nqubits: 4, datatype: Complex{Float64}
control(1)
└─ (3,) X gate
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 gate))

julia> control((2, 3) => ConstGate.CNOT)
(ctrl_locs -> control(ctrl_locs, (2, 3) => CNOT gate))
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))
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.expectMethod.
expect(op::AbstractBlock, reg::AbstractRegister{B}) -> Vector
expect(op::AbstractBlock, dm::DensityMatrix{B}) -> Vector

expectation value of an operator.

source
getiparams(block)

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

source
iparams_eltype(block)

Return the element type of getiparams.

source
YaoBlocks.matMethod.
mat(blk)

Returns the matrix form of given block.

source
YaoBlocks.matblockMethod.
matblock(m::AbstractMatrix)

Create a GeneralMatrixBlock with a matrix m.

Example

YaoBlocks.matblockMethod.
matblock(m::AbstractMatrix)

Create a GeneralMatrixBlock with a matrix m.

YaoBlocks.mathgateMethod.
mathgate(f; nbits[, bview=BitBasis.bint])

Create a MathGate with a math function f and number of bits. You can select different kinds of view which this MathGate will be applied on. Possible values are BitBasis.bint, BitBasis.bint_r, BitBasis.bfloat, BitBasis.bfloat_r.

mathgate(f; bview=BitBasis.bint) -> f(n)

Lazy curried version of mathgate.

Example

We can make a classical toffoli gate on quantum register.

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

julia> function toffli(b::BitStr)
           t = @inbounds b[1] ⊻ (b[3] & b[2])
           return @inbounds bit_literal(t, b[2], b[3])
       end
toffli (generic function with 1 method)

julia> g = mathgate(toffli; nbits=3)
mathgate(toffli; nbits=3, bview=bint)

julia> apply!(r, g) == ArrayReg(bit"111")
true
YaoBlocks.niparamsMethod.
nparameters(block) -> Int

Return number of parameters in block. See also nparameters.

source
occupied_locs(x)

Return an iterator of occupied locations of x.

source
parameters!(out, block)

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

source
parameters(block)

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

source
parameters_eltype(x)

Return the element type of parameters.

source
YaoBlocks.phaseMethod.
phase(theta)

Returns a global phase gate.

popdispatch!(block, list)

Pop the first nparameters parameters of list, then dispatch them to the block tree block. See also dispatch!.

source
popdispatch!(f, block, list)

Pop the first nparameters parameters of list, map them with a function f, then dispatch them to the block tree block. See also dispatch!.

source
YaoBlocks.postwalkMethod.
postwalk(f, src::AbstractBlock)

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

source
YaoBlocks.prewalkMethod.
prewalk(f, src::AbstractBlock)

Walk the tree and call f once the node is visited.

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 BlockTreeCharSet.
  • title: control whether to print the title, true or false, default is true
print_tree([io=stdout], root)

Print the block tree.

projector(x)

Return projector on 0 or projector on 1.

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

Lazy curried version of put.

Example

julia> put(1=>X)
(n -> put(n, 1 => X gate))
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, datatype: Complex{Float64}
put on (1)
└─ X gate

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, datatype: Complex{Float64}
put on (1, 3)
└─ kron
   ├─ 1=>X gate
   └─ 2=>Y gate

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 concentrate 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.reflectMethod.
reflect(v::AbstractVector{<:Complex})

Create a ReflectGate with an quantum state vector v.

YaoBlocks.reflectMethod.
reflect(r::ArrayReg)

Create a ReflectGate with an ArrayReg.

YaoBlocks.rollMethod.
roll(n, blocks...)

Return a Roller with total number of active qubits.

YaoBlocks.rotMethod.
rot(U, theta)

Return a RotationGate on U axis.

setiparams!(block, itr)
setiparams!(block, params...)

Set the parameters of block.

source
setiparams(f, block, collection)

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

setiparams(f, block, symbol)

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

YaoBlocks.shiftMethod.
shift(θ)

Returns a shift gate.

YaoBlocks.simplifyMethod.
simplify(block[; rules=__default_simplification_rules__])

Simplify a block tree accroding to given rules, default to use __default_simplification_rules__.

subblocks(x)

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

source
YaoBlocks.swapMethod.
swap([T=ComplexF64], n, loc1, loc2)

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

YaoBlocks.swapMethod.
swap(loc1, loc2) -> f(n)

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

@mathgate f <nbits> <bview=bint>

Create a MathGate with a math function f and number of bits nbits, binary view bview. Unlike mathgate, f will be automatically converted to a more legible form.

Example

julia> @mathgate x->x + 0b11 nbits=4
mathgate((x -> x + 0x03); nbits=4, bview=bint)
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.

LinearAlgebra.opnormFunction.
opnorm(A::BlockMap, p::Real=2)

opnorm for quantum circuit blocks.

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

decode_sign(ctrls...)

Decode signs into control sequence on control or inversed control.

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

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

print_annotation(io, root, node, child, k)

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

print_prefix(io, depth, charset, active_levels)

print prefix of a tree node in a single line.

print_title(io, block)

Print the title of given block of an AbstractBlock.

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.