Block Basics

Block Basics

Table of Contents

using Yao, Yao.Blocks
using LinearAlgebra

Construction and Matrix Representation

Blocks are operations on registers, we call those with matrix representation (linear) MatrixBlock.

A MatrixBlock can be

@show X
@show X |> typeof
@show isunitary(X)
@show ishermitian(X)
@show isreflexive(X);
X = X gate
X |> typeof = XGate{Complex{Float64}}
isunitary(X) = true
ishermitian(X) = true
isreflexive(X) = true

matrix representation

mat(X)
2×2 LuxurySparse.PermMatrix{Complex{Float64},Int64,Array{Complex{Float64},1},Array{Int64,1}}:
    0       1.0+0.0im
 1.0+0.0im     0

composite gates Embeding an X gate into larger Hilbert space, the first parameter of most non-primitive constructors are always qubit numbers

@show X2 = put(3, 2=>X)
@show isunitary(X2)
@show ishermitian(X2)
@show isreflexive(X2);
X2 = put(3, 2 => X) = Total: 3, DataType: Complex{Float64}
put on (2)
└─ X gate

isunitary(X2) = true
ishermitian(X2) = true
isreflexive(X2) = true
mat(X2)
8×8 LuxurySparse.PermMatrix{Complex{Float64},Int64,Array{Complex{Float64},1},Array{Int64,1}}:
    0          0       1.0+0.0im  …     0          0          0
    0          0          0             0          0          0
 1.0+0.0im     0          0             0          0          0
    0       1.0+0.0im     0             0          0          0
    0          0          0             0       1.0+0.0im     0
    0          0          0       …     0          0       1.0+0.0im
    0          0          0             0          0          0
    0          0          0          1.0+0.0im     0          0
@show cx = control(3, 3, 1=>X)
@show isunitary(cx)
@show ishermitian(cx)
@show isreflexive(cx);
cx = control(3, 3, 1 => X) = Total: 3, DataType: Complex{Float64}
control(3)
└─ (1,)=>X gate

isunitary(cx) = true
ishermitian(cx) = true
isreflexive(cx) = true
mat(cx)
8×8 LuxurySparse.PermMatrix{Complex{Float64},Int64,Array{Complex{Float64},1},Array{Int64,1}}:
 1.0+0.0im     0          0       …     0          0          0
    0       1.0+0.0im     0             0          0          0
    0          0       1.0+0.0im        0          0          0
    0          0          0             0          0          0
    0          0          0          1.0+0.0im     0          0
    0          0          0       …     0          0          0
    0          0          0             0          0       1.0+0.0im
    0          0          0             0       1.0+0.0im     0

hermitian and reflexive blocks can be used to construct rotation gates

@show rx = rot(X, π/4)
@show isunitary(rx)
@show ishermitian(rx)
@show isreflexive(rx);
rx = rot(X, π / 4) = Rot X gate: 0.7853981633974483
isunitary(rx) = true
ishermitian(rx) = false
isreflexive(rx) = false
mat(rx)
2×2 Array{Complex{Float64},2}:
 0.92388+0.0im           0.0-0.382683im
     0.0-0.382683im  0.92388+0.0im

now let's build a random circuit for following demos

using Yao.Intrinsics: rand_unitary
circuit = chain(5, control(5, 3=>Rx(0.25π)), put(5, (2,3)=>matrixgate(rand_unitary(4))), swap(5, 3, 4), repeat(5, H, 2:5), put(5, 2=>Ry(0.6)))
Total: 5, DataType: Complex{Float64}
chain
├─ control(5)
│  └─ (3,)=>Rot X gate: 0.7853981633974483
├─ put on (2, 3)
│  └─ GeneralMatrixGate(2^2 × 2^2; Array{Complex{Float64},2})
├─ swap(3, 4)
├─ repeat on (2, 3, 4, 5)
│  └─ H gate
└─ put on (2)
   └─ Rot Y gate: 0.6

to apply it on some register, we can use

reg = zero_state(10)
focus!(reg, 1:5) do reg_focused
    apply!(reg_focused, circuit)
end
@show reg ≈ zero_state(10);   # reg is changed!
reg ≈ zero_state(10) = false

then we reverse the process and check the correctness

focus!(reg, 1:5) do reg_focused
    reg_focused |> circuit'
end
@show reg ≈ zero_state(10);   # reg is restored!
reg ≈ zero_state(10) = true

Here, we have used the pip "eye candy" reg |> block to represent applying a block on register, which is equivalent to apply!(reg, block)

Type Tree To see a full list of block types

using InteractiveUtils: subtypes
function subtypetree(t, level=1, indent=4)
   level == 1 && println(t)
   for s in subtypes(t)
     println(join(fill(" ", level * indent)) * string(s))
     subtypetree(s, level+1, indent)
   end
end

subtypetree(Yao.Blocks.AbstractBlock);
AbstractBlock
    AbstractMeasure
        Measure
        MeasureAndRemove
        MeasureAndReset
    FunctionBlock
    MatrixBlock
        AbstractContainer
            Concentrator
            ControlBlock
            PutBlock
            RepeatedBlock
            TagBlock
                AbstractDiff
                    BPDiff
                    QDiff
                AbstractScale
                    Scale
                    StaticScale
                CachedBlock
                Daggered
        CompositeBlock
            AddBlock
            ChainBlock
            KronBlock
            PauliString
            Roller
        PrimitiveBlock
            ConstantGate
                CNOTGate
                HGate
                I2Gate
                P0Gate
                P1Gate
                PdGate
                PuGate
                SGate
                SWAPGate
                SdagGate
                TGate
                TdagGate
                ToffoliGate
                XGate
                YGate
                ZGate
            GeneralMatrixGate
            MathBlock
            PhaseGate
            ReflectBlock
            RotationGate
            ShiftGate
            Swap
            TimeEvolution
    Sequential

In the top level, we have

Block Tree Architecture

A block tree is specified the following two APIs

crx = circuit[1]
@show crx
@show subblocks(crx)
@show chsubblocks(crx, (Y,));
crx = Total: 5, DataType: Complex{Float64}
control(5)
└─ (3,)=>Rot X gate: 0.7853981633974483

subblocks(crx) = (Rot X gate: 0.7853981633974483,)
chsubblocks(crx, (Y,)) = Total: 5, DataType: Complex{Float64}
control(5)
└─ (3,)=>Y gate

if we want to define a function that travals over the tree in depth first order, we can write something like

function print_block_tree(root, depth=0)
    println("  "^depth * "- $(typeof(root).name)")
    print_block_tree.(root |> subblocks, depth+1)
end
print_block_tree(circuit);
- ChainBlock
  - ControlBlock
    - RotationGate
  - PutBlock
    - GeneralMatrixGate
  - Swap
  - RepeatedBlock
    - HGate
  - PutBlock
    - RotationGate

there are some functions defined using this strategy, like collect(circuit, block_type), it can filter out any type of blocks

rg = collect(circuit, RotationGate)
Sequence
├─ Rot X gate: 0.7853981633974483
└─ Rot Y gate: 0.6

Tagging System

We proudly introduced our tag system here. In previous sections, we have introduced the magic operation circuit' to get the dagger a circuit, its realization is closely related to the tagging mechanism of Yao.

@show X'    # hermitian gate
@show Pu'   # special gate
@show Rx(0.5)';   # rotation gate
X' = X gate
Pu' = Pd gate
(Rx(0.5))' = Rot X gate: -0.5

The dagger of above gates can be translated to other gates easily. but some blocks has no predefined dagger operations, then we put a tag for it as a default behavior, e.g.

daggered_gate = matrixgate(randn(4, 4))'
@show daggered_gate |> typeof
daggered_gate
daggered_gate |> typeof = Daggered{GeneralMatrixGate{2,2,Float64,Array{Float64,2}},2,Float64}
Total: 2, DataType: Float64
GeneralMatrixGate(2^2 × 2^2; Array{Float64,2}) [†]

Here, Daggered is a subtype of TagBlock.

Other tag blocks include

Scale, static scaling

2X
Total: 1, DataType: Complex{Float64}
[2] X gate

CachedBlock, get the matrix representation of a block when applying it on registers, and cache it in memory (or CacheServer more precisely). This matrix can be useful in future calculation, like boosting time evolution.

put(5, 2=>X) |> cache
Total: 5, DataType: Complex{Float64}
[↺] put on (2)
└─ X gate

AbstactDiff, marks a block as differentiable, either in classical back propagation mode (with extra memory cost to store intermediate data)

put(5, 2=>Rx(0.3)) |> autodiff(:BP)
Total: 5, DataType: Complex{Float64}
[∂] put on (2)
└─ Rot X gate: 0.3

or non-cheating quantum circuit simulation

put(5, 2=>Rx(0.3)) |> autodiff(:QC)
Total: 5, DataType: Complex{Float64}
put on (2)
└─ [̂∂] Rot X gate: 0.3

Parameter System

using the depth first searching strategy, we can find all parameters in a tree or subtree. Two relevant APIs are

@show parameters(circuit)
dispatch!(circuit, [0.1, 0.9])
@show parameters(circuit)
dispatch!(+, circuit, [0.1, 0.1])
@show parameters(circuit)
dispatch!(circuit, :zero)
@show parameters(circuit)
dispatch!(circuit, :random)
@show parameters(circuit);
parameters(circuit) = [0.785398, 0.6]
parameters(circuit) = [0.1, 0.9]
parameters(circuit) = [0.2, 1.0]
parameters(circuit) = [0.0, 0.0]
parameters(circuit) = [5.40807, 1.96619]

Intrinsic parameters

Intrinsic parameters are block's net contribution to total paramters, normally, we define these two APIs for subtyping blocks

@show iparameters(Rx(0.3))
@show setiparameters!(Rx(0.3), 1.2)
@show chain(Rx(0.3), Ry(0.5)) |> iparameters;
iparameters(Rx(0.3)) = 0.3
setiparameters!(Rx(0.3), 1.2) = Rot X gate: 1.2
chain(Rx(0.3), Ry(0.5)) |> iparameters = ()

Differentiable Blocks

see the independant chapter Automatic Differentiation

Time Evolution and Hamiltonian

docs are under preparation

This page was generated using Literate.jl.