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

— Type`AbstractBlock{D}`

Abstract type for quantum circuit blocks. while `D`

is the number level in each qudit.

**Required Methods**

**Optional Methods**

`YaoAPI.PrimitiveBlock`

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

subtype for primitive block with parameter should implement `hash`

and `==`

method to enable key value cache.

**Required Methods**

`apply!`

`mat`

`print_block`

`Base.hash`

`Base.:(==)`

**Optional Methods**

`YaoAPI.CompositeBlock`

— Type`CompositeBlock{D} <: AbstractBlock{D}`

Abstract supertype which composite blocks will inherit from. Composite blocks are blocks composited from other `AbstractBlock`

s, thus it is a `AbstractBlock`

as well.

**Required Methods**

**Optional Methods**

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

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

`YaoBlocks.IdentityGate`

— Type`IdentityGate{D} <: TrivialGate{D}`

The identity gate.

`YaoBlocks.Measure`

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

`YaoBlocks.Measure`

— Method`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 ₍₂₎)```

`YaoBlocks.PhaseGate`

— Type`PhaseGate`

Global phase gate.

`YaoBlocks.Projector`

— Type`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|\]

`YaoBlocks.ReflectGate`

— Type`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|$.

`YaoBlocks.RotationGate`

— Type`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\]

`YaoBlocks.ShiftGate`

— Type`ShiftGate <: PrimitiveBlock`

Phase shift gate.

`YaoBlocks.TimeEvolution`

— Type`TimeEvolution{D, TT, GT} <: PrimitiveBlock{D}`

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

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

.

`YaoBlocks.ConstGate.@const_gate`

— Macro```
@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`

## Composite Blocks

Composite blocks are subtypes of `CompositeBlock`

, they are the composition of blocks.

We provide the following composite blocks:

`YaoBlocks.AbstractAdd`

— Type`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`

`YaoBlocks.Add`

— Type```
Add{D} <: AbstractAdd{D}
Add(blocks::AbstractBlock...) -> Add
```

Type for block addition.

```
julia> X + X
nqubits: 1
+
├─ X
└─ X
```

`YaoBlocks.CachedBlock`

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

`YaoBlocks.ChainBlock`

— Type`ChainBlock{D} <: CompositeBlock{D}`

`ChainBlock`

is a basic construct tool to create user defined blocks horizontically. It is a `Vector`

like composite type.

`YaoBlocks.ControlBlock`

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

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`

`YaoBlocks.Daggered`

— Type`Daggered{BT, D} <: TagBlock{BT,D}`

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

`YaoBlocks.Daggered`

— Method`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
```

`YaoBlocks.KronBlock`

— Type`KronBlock{D,M,MT<:NTuple{M,Any}} <: CompositeBlock{D}`

composite block that combine blocks by kronecker product.

`YaoBlocks.PSwap`

— Type```
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}\]

`YaoBlocks.PutBlock`

— Type`PutBlock{D,C,GT<:AbstractBlock} <: AbstractContainer{GT,D}`

Type for putting a block at given locations.

`YaoBlocks.RepeatedBlock`

— Type`RepeatedBlock{D,C,GT<:AbstractBlock} <: AbstractContainer{GT,D}`

Repeat the same block on given locations.

`YaoBlocks.Scale`

— Type```
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
```

`YaoBlocks.Subroutine`

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

`YaoBlocks.Swap`

— Type```
Swap = PutBlock{2,2,G} where {G<:ConstGate.SWAPGate}
Swap(n::Int, locs::Tuple{Int,Int})
```

Swap gate, which swaps two qubits.

`YaoBlocks.UnitaryChannel`

— Type```
UnitaryChannel{D, W<:AbstractVector} <: CompositeBlock{D}
UnitaryChannel(operators, probs)
```

Create a unitary channel, where `probs`

is a real vector that sum up to 1.

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

`YaoAPI.apply!`

— Function`apply!(register, block)`

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

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 ₍₂₎
```

`YaoBlocks.apply`

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

`YaoAPI.niparams`

— Function`niparam(block) -> Int`

Return number of intrinsic parameters in `block`

. See also `nparameters`

.

**Examples**

```
julia> niparams(Rx(0.1))
1
```

`YaoAPI.getiparams`

— Function`getiparams(block)`

Returns the intrinsic parameters of node `block`

, default is an empty tuple.

**Examples**

```
julia> getiparams(Rx(0.1))
0.1
```

`YaoAPI.render_params`

— Function`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
```

`YaoAPI.nparameters`

— Function`nparameters(block) -> Int`

Return number of parameters in `block`

. See also `niparams`

.

**Examples**

```
julia> nparameters(chain(Rx(0.1), Rz(0.2)))
2
```

`YaoAPI.occupied_locs`

— Function`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,)
```

`YaoAPI.print_block`

— Function`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)
```

`YaoAPI.apply_back!`

— Function`apply_back!((ψ, ∂L/∂ψ*), circuit::AbstractBlock, collector) -> AbstractRegister`

back propagate and calculate the gradient ∂L/∂θ = 2*Re(∂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!

`YaoAPI.mat_back!`

— Function`mat_back!(T, rb::AbstractBlock, adjy, collector)`

Back propagate the matrix gradients.

## 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::ComplexF32`

`ERROR: 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`

`Base.kron`

— Method`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 `1`

st qubit, and a `Y`

gate on the `3`

rd qubit.

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

`Base.kron`

— Method```
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
```

`Base.kron`

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

`Base.repeat`

— Method`repeat(x::AbstractBlock, locs)`

Lazy curried version of `repeat`

.

`Base.repeat`

— Method`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)
```

`LinearAlgebra.ishermitian`

— Method`ishermitian(op::AbstractBlock) -> Bool`

Returns true if `op`

is hermitian.

`YaoAPI.chcontent`

— Method`chcontent(x, blk)`

Create a similar block of `x`

and change its content to blk.

`YaoAPI.chsubblocks`

— Method`chsubblocks(composite_block, itr)`

Change the sub-blocks of a `CompositeBlock`

with given iterator `itr`

.

`YaoAPI.content`

— Method`content(x)`

Returns the content of `x`

.

`YaoAPI.dispatch!`

— Method`dispatch!(x::AbstractBlock, collection)`

Dispatch parameters in collection to block tree `x`

.

it will try to dispatch the parameters in collection first.

`YaoAPI.expect`

— Method```
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 `gψ`

the gradient of input state and `gparams`

the gradients of circuit parameters. For register input, the return value is a register.

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.

`YaoAPI.getiparams`

— Method`getiparams(block)`

Returns the intrinsic parameters of node `block`

, default is an empty tuple.

`YaoAPI.iparams_eltype`

— Method`iparams_eltype(block)`

Return the element type of `getiparams`

.

`YaoAPI.mat`

— Method`mat([T=ComplexF64], blk)`

Returns the matrix form of given block.

`YaoAPI.mat`

— Method`mat(A::GeneralMatrixBlock)`

Return the matrix of general matrix block.

Instead of converting it to the default data type `ComplexF64`

, this will return its contained matrix.

`YaoAPI.operator_fidelity`

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

`YaoAPI.parameters`

— Method`parameters(block)`

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

.

`YaoAPI.parameters_eltype`

— Method`parameters_eltype(x)`

Return the element type of `parameters`

.

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

.

`YaoAPI.subblocks`

— Method`subblocks(x)`

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

`YaoBlocks.Rx`

— Method`YaoBlocks.Ry`

— Method`YaoBlocks.Rz`

— Method`YaoBlocks.apply`

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

`YaoBlocks.applymatrix`

— Method`applymatrix(g::AbstractBlock) -> Matrix`

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

`YaoBlocks.bit_flip_channel`

— Method`YaoBlocks.cache`

— Function`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
```

`YaoBlocks.cache_key`

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

`YaoBlocks.cache_type`

— Method`cache_type(::Type) -> DataType`

Return the element type that a `CacheFragment`

will use.

`YaoBlocks.chain`

— Method`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
```

`YaoBlocks.chain`

— Method`chain()`

Return an lambda `n->chain(n)`

.

`YaoBlocks.chain`

— Method```
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
```

`YaoBlocks.chmeasureoperator`

— Method`chmeasureoperator(m::Measure, op::AbstractBlock)`

change the measuring `operator`

. It will also discard existing measuring results.

`YaoBlocks.cleanup`

— Method`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
```

`YaoBlocks.cnot`

— Method`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))
```

`YaoBlocks.collect_blocks`

— Method`collect_blocks(block_type, root)`

Return a `ChainBlock`

with all block of `block_type`

in root.

`YaoBlocks.control`

— Method`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))
```

`YaoBlocks.control`

— Method`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
```

`YaoBlocks.cunmat`

— Function`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.cz`

— Method`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
```

`YaoBlocks.decode_sign`

— Method`decode_sign(ctrls...)`

Decode signs into control sequence on control or inversed control.

`YaoBlocks.depolarizing_channel`

— Method`depolarizing_channel(n::Int; p::Real)`

Create a global depolarizing channel.

**Arguments**

`n`

: number of qubits.

**Keyword Arguments**

`p`

: probability of this error to occur.

**See also**

`single_qubit_depolarizing_channel`

and `two_qubit_depolarizing_channel`

for depolarizing channels acting on only one or two qubits.

`YaoBlocks.dispatch`

— Method`dispatch(x::AbstractBlock, collection)`

Dispatch parameters in collection to block tree `x`

, the generic non-inplace version.

it will try to dispatch the parameters in collection first.

`YaoBlocks.dump_gate`

— Function`dump_gate(blk::AbstractBlock) -> Expr`

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

`YaoBlocks.eigenbasis`

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

`YaoBlocks.gate_expr`

— Method`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.getcol`

— Method`getcol(csc::SDparseMatrixCSC, icol::Int) -> (View, View)`

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

`YaoBlocks.igate`

— Method`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)
```

`YaoBlocks.isclean`

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

`YaoBlocks.map_address`

— Function`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.matblock`

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

`YaoBlocks.num_nonzero`

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

`YaoBlocks.parameters!`

— Method`parameters!(out, block)`

Append all the parameters contained in block tree with given root `block`

to `out`

.

`YaoBlocks.parameters_range`

— Method`parameters_range(block)`

Return the range of real parameters present in `block`

.

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

`YaoBlocks.parse_block`

— Function`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.pauli_error_channel`

— Method`pauli_error_channel(; px::Real, py::Real=px, pz::Real=px)`

Create the Pauli error channel as a `UnitaryChannel`

\[ (1 - (p_x + p_y + p_z))⋅ρ + p_x⋅XρX + p_y⋅YρY + p_z⋅ZρZ\]

`YaoBlocks.phase`

— Method`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)
```

`YaoBlocks.phase_flip_channel`

— Method`YaoBlocks.popdispatch!`

— Method`popdispatch!(block, list)`

Pop the first `nparameters`

parameters of list, then dispatch them to the block tree `block`

. See also `dispatch!`

.

`YaoBlocks.popdispatch!`

— Method`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!`

.

`YaoBlocks.postwalk`

— Method`postwalk(f, src::AbstractBlock)`

Walk the tree and call `f`

after the children are visited.

`YaoBlocks.prewalk`

— Method`prewalk(f, src::AbstractBlock)`

Walk the tree and call `f`

once the node is visited.

`YaoBlocks.print_annotation`

— Method`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_prefix`

— Method`print_prefix(io, depth, charset, active_levels)`

print prefix of a tree node in a single line.

`YaoBlocks.print_subtypetree`

— Function`print_subtypetree(::Type[, level=1, indent=4])`

Print subtype tree, `level`

specify the depth of the tree.

`YaoBlocks.print_title`

— Method`print_title(io, block)`

Print the title of given `block`

of an `AbstractBlock`

.

`YaoBlocks.print_tree`

— Function`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`

`YaoBlocks.print_tree`

— Method`print_tree([io=stdout], root)`

Print the block tree.

`YaoBlocks.projection`

— Method`projection(y::AbstractMatrix, op::AbstractMatrix) -> typeof(y)`

Project `op`

to sparse matrix with same sparsity as `y`

.

`YaoBlocks.projector`

— Method```
projector(v::AbstractArrayReg) -> Projector
```

Create a `Projector`

with an quantum state vector `v`

.

**Example**

```
julia> projector(rand_state(3))
|s⟩⟨s|, nqudits = 3
```

`YaoBlocks.projector`

— Method`projector(x)`

Return projector on `0`

or projector on `1`

.

`YaoBlocks.pswap`

— Method```
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)
```

`YaoBlocks.put`

— Method`YaoBlocks.put`

— Method`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`

.

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

— Method`rand_hermitian([T=ComplexF64], N::Int) -> Matrix`

Create a random hermitian matrix.

```
julia> ishermitian(rand_hermitian(2))
true
```

`YaoBlocks.rand_unitary`

— Method`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})
```

`YaoBlocks.reflect`

— Function```
reflect(
v::AbstractArrayReg
) -> TimeEvolution{D, Irrational{:π}, TH} where {D, TH<:(Projector{D, T, AT} where {T, AT<:(AbstractArrayReg{D, T})})}
reflect(
v::AbstractArrayReg,
θ::Real
) -> TimeEvolution{_A, Tt, TH} where {_A, Tt<:Real, TH<:(Projector{_A, T, AT} where {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
```

`YaoBlocks.rmlines`

— Method`rmlines(ex)`

Remove `LineNumberNode`

from an `Expr`

.

`YaoBlocks.rot`

— Method`rot(U, theta)`

Return a `RotationGate`

on U axis.

`YaoBlocks.sandwich`

— Method`sandwich(bra::AbstractRegister, op::AbstractBlock, ket::AbstracRegister) -> Complex`

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

`YaoBlocks.setcol!`

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

set specific col of a CSC matrix

`YaoBlocks.setiparams`

— Function```
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`

.

`YaoBlocks.shift`

— Method`shift(θ)`

Create a `ShiftGate`

with phase `θ`

.

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

**Examples**

```
julia> shift(0.1)
shift(0.1)
```

`YaoBlocks.simple_commute_eachother`

— MethodReturn true if operators commute to each other.

`YaoBlocks.single_qubit_depolarizing_channel`

— Method`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)\]

`YaoBlocks.sprand_hermitian`

— Method`sprand_hermitian([T=ComplexF64], N, density)`

Create a sparse random hermitian matrix.

`YaoBlocks.sprand_unitary`

— Method`sprand_unitary([T=ComplexF64], N::Int, density) -> SparseMatrixCSC`

Create a random sparse unitary matrix.

`YaoBlocks.subroutine`

— Method`subroutine(block, locs) -> f(n)`

Lazy curried version of `subroutine`

.

`YaoBlocks.subroutine`

— Method`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
```

`YaoBlocks.swap`

— Method`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
```

`YaoBlocks.swap`

— Method`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))
```

`YaoBlocks.time_evolve`

— Method`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
```

`YaoBlocks.two_qubit_depolarizing_channel`

— Method`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))`

.

`YaoBlocks.u1ij!`

— Function`u1ij!(target, i, j, a, b, c, d)`

single u1 matrix into a target matrix.

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

`YaoBlocks.unitary_channel`

— Method`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
```

`YaoBlocks.unmat`

— Method`unmat(::Val{D}, nbit::Int, U::AbstractMatrix, locs::NTuple) -> AbstractMatrix`

Return the matrix representation of putting matrix at locs.

`YaoBlocks.@yao_str`

— Macro```
@yao_str
yao"..."
```

The mark up language for quantum circuit.