Closed Roger-luo closed 6 years ago
In general, this package will use a block tree to store the structure of a quantum circuit. Each block is a quantum gate with index.
abstract type AbstractGate{N} end
size(gate)
Base
sparse(gate)
stdlib
full(gate)
stdlib
function apply(gate, state, pos) end
N
qubits gate on the qubits start from pos
. function apply(gate::AbstractGate{N}, state) where N end
N
qubit (equals to pos=1
)function update!(gate, params) end
apply
. We should follow: explicit is better than implicitfunction (::AbstractGate)(state, pos) end
function (::AbstractGate)(state) end
Blocks are subtypes of AbstractGate
with additional information about its position in a quantum circuit, it is a container of subtypes of AbstractGate
. The final structure will looks similar to Julia's own AST tree.
abstract type AbstractBlock{N} <: AbstractGate{N} end
apply(block, state)
apply
method will be changed to the above form.Generic Block will not be type stable since this will store any possible instance of the subtypes of AbstractBlock
.
mutable struct BasicBlock{N} <: AbstractBlock{N}
heads::Vector{Int}
lists::Vector{AbstractBlock}
end
function apply(gate::BasicBlock, state) end
apply
method will reshape the state (a tensor with N
legs) to contract with blocks/gates inside this BasicBlock
The structure is known for a rotation block:
-- [Z] -- [X] -- [Z] --
...
-- [Z] -- [X] -- [Z] --
we don't actually need to store what's inside there
struct RotationBlock{N} <: AbstractBlock{N}
end
and we assume a rotation block is contiguous on quantum register, and for rotation operations like
-- [Z] -- [X] -- [Z] --
...
-- [Z] -- [X] -- [Z] --
-----------------------
-- [Z] -- [X] -- [Z] --
...
-- [Z] -- [X] -- [Z] --
Then we will have two rotation blocks.
What will be included in our first version (v0.0.1) is listed below.
Blocks are conbination of multiple gates on multiple qubits.
List some reference and resources here:
circuit = chain(
kron(Hardmard for i=1:4),
focus(4, 1:2),
PauliX,
focus(4, 1:4)
control(
4, PauliX,
1, 2, 3,
),
focus(4, 1:2),
PauliX,
# 1:3 active now
focus(4, 1:3),
kron(chain(Hardmard, PauliX) for i=1:3),
control(
3, PauliZ,
1, 2,
)
kron(chain(PauliX, Hardmard) for i=1:3),
)
With its matrix form cached blocks, the process of evaluation can be accelerated with directly calculation with its matrix form.
# A four qubit quantum circuit born machine
rotations = [rotation(4), rotation(4)]
circuit = sequence(
cache(rotations[1], level=3),
cache(
kron(
control(
2, PauliX,
1,
),
control(
2, PauliX,
1,
),
),
level=1
),
focus(4, 1:3),
control(
1, PauliX,
2, 3,
)
focus(4, 1:4),
cache(rotations[2], level=3),
measure(1:4, n=1000)
)
register = Register(4)
step = 1e-2
# force cache all cacheable block in the beginning
cache!(circuit, force=true)
# this trains the first rotation block
for i in 1:1000
update!(rotations[1], (i%4, -pi/2)) # update will add parameters
cache!(circuit) # will not re-cache next time
negative = circuit(register)
update!(rotations[1], (i%4, pi/2))
cache!(circuit)
positive = circuit(register)
grad = grad_MMD_loss(negative, positive)
update!(rotations[1], (i%4, grad * step))
end
# A four qubit quantum circuit born machine
rotations = [rotation(4), rotation(4)]
pre_circuit = chain(
cache(rotations[1], level=3),
cache(
kron(
control(
2, PauliX,
1,
),
control(
2, PauliX,
1,
),
),
level=1
),
focus(4, 1:3),
control(
1, PauliX,
2, 3,
)
focus(4, 1:4),
cache(rotations[2], level=3)
)
post_circuit = sequence(
pre_circuit,
remove!(4, 1:2), # this create a `RemoveBlock` (a subtype of `AbstractMeasure`)
another_smaller_circuit,
)
sequence(blocks...)
: make a Sequence
block, this is a naive wrapper of a list of blocks. (no shape check)chain(blocks...)
: chain several blocks together, it returns a ChainBlock
and requires all its ninput
and noutput
is equal. See ChainBlock
for more info.kron(blocks...)
or kron(block_with_pos::Tuple{Int, Block}...)
: combine serveal block together by kronecker product. See KronBlock
for more info.cache(block, level=1)
: cache its argument with a cache level. See CacheBlock
for more info.control(head, block, control_qubits...)
: create a controled blockNOTE: All constructors of blocks will not be exported.
Abstract block supertype which blocks inherit from.
abstract type AbstractBlock{N} end
ninput
: number of input qubits (default is N
)noutput
: number of output qubits (default is N
)isunitary
: check whether this block type is a unitary (default is false
)iscacheable
: check whether this block type is cacheable (default is false
)cache_type
: what kind of cache block (with which level) should be used for this block (default is CacheBlock
)ispure
: check whether this block type is a pure block (default is false
)get_cache(block)->list
: get all cache blocks in block
apply!(register, block)->reg
: apply this block to a register, it will only have side-effect on this register.update!(block, params...)->block
: scatter a set of parameters to each block, it will only have side-effect on this block (and its children).cache!(block, level=1, force=false)->block
: update cache blocks with cache level less than input cache level, if there is a cached instance of block, unless force=true
, cache will not be updated.Abstract block supertype whose subtype has a square matrix form.
abstract type PureBlock{N} <: AbstractBlock{N} end
ispure
: check whether this block type is a pure block (default is true
)full
: get the dense matrix form of given instancesparse
: get the sparse matrix form of given instanceBlock that concentrate given lines together.
struct Concentrator{N, M} <: AbstractBlock{N}
line_orders::NTuple{M, Int}
end
M
: number of active qubits.
ninput
: get the number of input qubits (N
)noutput
: get the number of output qubits (M
)line_orders
: get what is concentrated togetherfocus(nqubits, orders...)
: get a instance of Concentrator
a naive wrapper of a sequence of blocks.
struct Sequence{N} <: AbstractBlock{N}
list::Vector
end
Exported
sequence(blocks...)
: create an instance of Sequence
.Abstract block supertype which measurement block will inherit from.
abstract type AbstractMeasure{N, M} <: AbstractBlock{N} end
abstract supertype which cache blocks will inherit from
abstract type CacheBlock{N} <: PureBlock{N} end
cache!(block, level=1, force=false)
cache this block with given cache level.cache(block)
create a cache block according to this block type's cache_type
trait.PureBlock
abstract supertype which composite blocks will inherit from.
abstract type CompositeBlock{N} <: PureBlock{N} end
ChainBlock
: chain a list of blocks together, will check its input and output shape.
struct ChainBlock{N, T <: AbstractBlock{N}}
list::Vector{T}
end
Exported bindings
chain(blocks...)
: create an instance of ChainBlock
and check the shape of each block. (ChainBlock
's constructor won't check the shape)KronBlock
: fuse a list of blocks together with kronecker product
struct KronBlock{N, T <: PureBlock} <: AbstractBlock{N}
heads::Vector{Int}
list::Vector{T}
end
exported bindings
kron(blocks)
and kron(blocks_with_pos::Tuple...)
create an instance of KronBlock
abstract supertype which all primitive blocks will inherit from.
abstract type PrimitiveBlock{N} <: PureBlock{N} end
iscacheable
: default is true
isunitary
: default is true
Gate
: block for simple gate type (GT
) without parameters. Their matrix form is a constant matrix. Therefore, there will only be one matrix allocation no matter how many instances are created.
struct Gate{GT, N} <: PrimitiveBlock{N} end
Exported bindings:
PauliX, PauliY, PauliZ
Hardmard
CNOT
PhiGate
: block for phase gates
mutable struct PhiGate{T} <: PrimitiveBlock{1}
theta::T
end
Exported bindings
phase(theta)
Rotation
: a primitive block for rotation gates (not RotationBlock
)
mutable struct Rotation{GT, T} <: PrimitiveBlock{1}
theta::T
end
Exported bindings
rotation(::Type{GT}, ::AbstractFloat) where {GT <: Union{X, Y, Z}}
RotationBlock
: a primitive block for arbitrary rotation: Rz Rx Rz
mutable struct RotationBlock{N, T} <: PrimitiveBlock{N}
thetas::Vector{T} # 3N thetas
end
Exported bindings
rotation(::Int)
RotationBlock is not PrimitiveBlock, it is consist of single qubit arbitrary rotation blocks.
Then we can just use a KronBlock
with a ChainBlock
. But if there is any specific optimization for this we can always define a PrimitiveBlock
for this architecture.
kron( chain(Rz(), Rx(), Rz()) for i = 1:N )
or
chain(
kron(Rz() for i = 1:N),
kron(Rx() for i = 1:N),
kron(Rz() for i = 1:N),
)
This issue is for listing what shoud be support in the first version.