Skip to content

Commit

Permalink
Register: Refactor (#17)
Browse files Browse the repository at this point in the history
* Register: delete view

* Register: refactor

* change test

* Utils: improve performance

* Test: rename pack

* Register: export new API nactive

* Test: improve test
  • Loading branch information
Roger-luo authored Apr 28, 2018
1 parent 46dee17 commit 1a7d01b
Show file tree
Hide file tree
Showing 5 changed files with 178 additions and 298 deletions.
60 changes: 17 additions & 43 deletions src/MathUtils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,51 +2,25 @@
# Ref: https://stackoverflow.com/questions/21442088
export log2i

function bit_length(x)
n = 0
while x!=0
n += 1
x >>= 1
end
return n
end

"""
log2i(x)
logrithm for integers
logrithm for integer pow of 2
"""
function log2i(x::T)::T where {T <: Integer}
log2i(unsigned(x))
end

function log2i(x::UInt8)::UInt8
t::UInt8 = UInt8(x > 0xf) << 2; x >>= t
s::UInt8 = UInt8(x > 0x3) << 1; x >>= s; t |= s;
(t | (x >> 1))
end

function log2i(x::UInt16)::UInt16
t::UInt16 = UInt16(x > 0xff) << 3; x >>= t
s::UInt16 = UInt16(x > 0xf) << 2; x >>= s; t |= s;
s = (x > 0x3) << 1; x >>= s; t |= s;
(t | (x >> 1))
end

function log2i(x::UInt32)::UInt32
t::UInt32 = UInt32(x > 0xffff) << 4; x >>= t
s::UInt32 = UInt32(x > 0xff) << 3; x >>= s; t |= s;
s = UInt32(x > 0xf) << 2; x >>= s; t |= s;
s = UInt32(x > 0x3) << 1; x >>= s; t |= s;
(t | (x >> 1))
end

function log2i(x::UInt64)::UInt64
t::UInt64 = UInt64(x > 0xffff_ffff) << 5; x >>= t
s::UInt64 = UInt64(x > 0xffff) << 4; x >>= s; t |= s;
s = UInt64(x > 0xff) << 3; x >>= s; t |= s;
s = UInt64(x > 0xf) << 2; x >>= s; t |= s;
s = UInt64(x > 0x3) << 1; x >>= s; t |= s;
(t | (x >> 1))
end

function log2i(x::UInt128)::UInt128
t::UInt128 = UInt128(x > 0xffff_ffff_ffff_ffff) << 6; x >>= t
s::UInt128 = UInt128(x > 0xffff_ffff) << 5; x >>= s; t |= s;
s = UInt128(x > 0xffff) << 4; x >>= s; t |= s;
s = UInt128(x > 0xff) << 3; x >>= s; t |= s;
s = UInt128(x > 0xf) << 2; x >>= s; t |= s;
s = UInt128(x > 0x3) << 1; x >>= s; t |= s;
(t | (x >> 1))
function log2i(x::T)::T where T
n::T = 0
while x&0x1!=1
n += 1
x >>= 1
end
return n
end
264 changes: 103 additions & 161 deletions src/Register.jl
Original file line number Diff line number Diff line change
@@ -1,206 +1,148 @@
"""
AbstractRegister{M, B, T, N} <: AbstractArray{T, N}
AbstractRegister{M, B, T}
Abstract type for quantum registers, all quantum registers supports
the interface of julia arrays.
Abstract type for quantum registers, all quantum registers contains a
subtype of `AbstractArray` as member `state`.
## Parameters
- `M` is the number of qubits
- `N` is the number of qubits
- `B` is the batch size
- `N` is the actual dimension (number of packed legs, batch dimension is the last dimension if batch size is not 0).
- `T` eltype
"""
abstract type AbstractRegister{M, B, T, N} <: AbstractArray{T, N} end
abstract type AbstractRegister{N, B, T} end

export nqubit, nbatch, line_orders, state, nactive
# register properties
nqubit(reg::AbstractRegister{M}) where M = M
nbatch(reg::AbstractRegister{M, B}) where {M, B} = B
# We assume each register has a member named data and ids
nqubit(reg::AbstractRegister{N}) where N = N
nbatch(reg::AbstractRegister{N, B}) where {N, B} = B
nactive(reg::AbstractRegister) = log2i(size(state(reg), 1))
# We assume each register has a member named state and orders
# overload this if there is not
qubits(reg::AbstractRegister{M}) where M = reg.ids
data(reg::AbstractRegister) = reg.data

# provide view method if data type supports
export view_batch
import Base: view
view_batch(reg::AbstractRegister{M, B, T, N}, ibatch::Int) where {M, B, T, N} =
view(data(reg), ntuple(x->:, Val{N-1})..., ibatch)
view_batch(reg::AbstractRegister{M, 1, T, N}, ibatch::Int) where {M, T, N} =
view(data(reg), ntuple(x->:, Val{N})...)
view(reg::AbstractRegister, dims...) = view(data(reg), dims...)


# use array interface
# TODO: use @forward instead
import Base: eltype, length, ndims, size, eachindex,
getindex, setindex!, stride, strides, copy
import Compat: axes

eltype(x::AbstractRegister{M, B, T, N}) where {M, B, T, N} = T
length(x::AbstractRegister) = length(data(x))
ndims(x::AbstractRegister) = ndims(data(x))
size(x::AbstractRegister) = size(data(x))
size(x::AbstractRegister, n::Integer) = size(data(x), n)
axes(x::AbstractRegister) = axes(data(x))
axes(x::AbstractRegister, d::Integer) = axes(data(x), d)
eachindex(x::AbstractRegister) = eachindex(data(x))
stride(x::AbstractRegister, k::Integer) = stride(data(x), k)
strides(x::AbstractRegister) = strides(data(x))
getindex(x::AbstractRegister, index::Integer...) = getindex(data(x), index...)
getindex(x::AbstractRegister, index::NTuple{N, T}) where {N, T <: Integer} = getindex(data(x), index...)
setindex!(x::AbstractRegister, val, index::Integer...) = (setindex!(data(x), val, index...); x)
setindex!(x::AbstractRegister, val, index::NTuple{N, T}) where {N, T <: Integer} = (setindex!(data(x), val, index...); x)
copy(x::AbstractRegister{M, B, T, N}) where {M, B, T, N} = Register{M, B, T, N}(copy(data(x)), copy(qubits(x)))

#################
# Batch Iterator
#################

export batch

# batch iterator
struct BatchIter{R <: AbstractRegister}
reg::R
end

batch(x::AbstractRegister) = BatchIter(x)

import Base: start, next, done, length, eltype
line_orders(reg::AbstractRegister{N}) where N = reg.line_orders
state(reg::AbstractRegister) = reg.state

start(x::BatchIter) = 1
next(x::BatchIter, state) =
view_batch(x.reg, state), state+1
done(x::BatchIter, state) = state > length(x)
length(x::BatchIter{R}) where R = nbatch(x.reg)
import Base: eltype, copy
eltype(::AbstractRegister{N, B, T}) where {N, B, T} = T
function copy(reg::AbstractRegister) end

export pack!
# permutation and concentration of qubits

"""
pack!(dst, src, ids)
pack_orders!(reg, orders)
pack `ids` together to the first k-dimensions.
pack `orders` together to the first k-dimensions.
"""
function pack!(dst, src, ids) end
pack!(reg::AbstractRegister, ids::NTuple) = pack!(reg, reg, ids)
pack!(reg::AbstractRegister, ids::Integer...) = pack!(reg, ids)
function pack_orders! end


export focus
export focus!

"""
focus(register, ids...)
focus!(register, orders)
pack tensor legs with ids together and reshape the register to
(exposed, remain, batch) or (exposed, remain) depending on register
type (with or without batch).
pack tensor legs with given orders and reshape the register state to
(exposed, remain * batch). `orders` can be a `Vector` or a `Tuple`. It
should contain either a `UnitRange` or `Int`. `UnitRange` will be
considered as a contiguous quantum memory and `Int` will be considered
as an in-contiguous order.
"""
focus(reg::AbstractRegister, ids...) = focus(reg, ids)
function focus! end

export Register
"""
Register{M, B, T, N} <: AbstractRegister{M, B, T, N}
Register{N, B, T} <: AbstractRegister{N, B, T}
default register type. This register use a builtin array
to store the quantum state.
to store the quantum state. The elements inside an instance
of `Register` will be related to a certain memory address,
but since it is not immutable (we need to change its shape),
be careful not to change its state, though the behaviour is
the same, but allocation should be avoided. Therefore, no
shallow copy method is provided.
"""
struct Register{M, B, T, N} <: AbstractRegister{M, B, T, N}
data::Array{T, N}
ids::Vector{Int}
mutable struct Register{N, B, T} <: AbstractRegister{N, B, T}
state::Array{T, 2}
line_orders::Vector{Int}
end

# We store the state with a M-dimentional tensor by default
# This will reduce memory allocation by allowing us use
# permutedims! rather than permutedims.

# Type Inference
Register(nqubit::Int, nbatch::Int, data::Array{T, N}, ids::Vector{Int}) where {T, N} =
Register{nqubit, nbatch, T, N}(data, ids)
# type conversion
Register(nqubit::Integer, nbatch::Integer, data::Array, ids) =
Register(Int(nqubit), Int(nbatch), data, Int[ids...])

Register(nqubit::Integer, nbatch::Integer, data::Array) =
Register(nqubit, nbatch, data, 1:nqubit)

# calculate number of qubits from data array
function Register(nbatch::Integer, data::Array)
len = length(data) ÷ nbatch
ispow2(len) || throw(Compat.InexactError(:Register, Register, data))
Register(log2i(len), nbatch, data)
function Register(state::Array{T, 2}) where T
len, nbatch = size(state)
ispow2(len) || throw(Compat.InexactError(:Register, Register, state))
N = log2i(len)
Register{N, nbatch, T}(state, collect(1:N))
end

# no batch
Register(data::Array) = Register(1, data)

#########################
# Default Initialization
#########################

# NOTE: we store a rank-n tensor for n qubits by default,
# we can always optimize this by add other kind of
# register for specific tasks
Register(::Type{T}, nqubit::Integer, nbatch::Integer) where T =
Register(nqubit, nbatch, zeros(T, ntuple(x->2, Val{nqubit})..., nbatch), 1:nqubit)
Register(::Type{T}, nqubit::Integer) where T =
Register(nqubit, 1, zeros(T, ntuple(x->2, Val{nqubit})...), 1:nqubit)
Register(state::Array{T, 1}) where T = Register(reshape(state, length(state), 1))

# We use Compelx128 by default
Register(nqubit::Integer, nbatch::Integer) =
Register(Complex128, nqubit, nbatch)
Register(nqubit::Integer) =
Register(Complex128, nqubit)
export zero_state, rand_state
zero_state(::Type{T}, nqubit::Int, nbatch::Int=1) where T =
(state = zeros(T, 1<<nqubit, nbatch); state[1, :] = 1; Register(state))
# TODO: support different RNG?, default is a MT19937
rand_state(::Type{T}, nqubit::Int, nbatch::Int=1) where T = Register(rand(T, 1<<nqubit, nbatch))

# set default type
zero_state(nqubit::Int, nbatch::Int=1) = zero_state(Complex128, nqubit, nbatch)
rand_state(nqubit::Int, nbatch::Int=1) = rand_state(Complex128, nqubit, nbatch)

## Dimension Permutation & Reshape
import Base: reshape
copy(reg::Register{N, B, T}) where {N, B, T} = Register{N, B, T}(copy(reg.state), copy(reg.line_orders))

reshape(reg::Register{M, B, T, N}, dims::Dims) where {M, B, T, N} =
Register(M, B, reshape(reg.data, dims), reg.ids)

function pack!(dst::Register{M, B, T, N}, src::Register{M, B, T, N}, ids::NTuple{K, Int}) where {M, B, T, N, K}
@assert N == M+1 "register shape mismatch"

ids = sort!([ids...])
inds = findin(src.ids, ids)
perm = copy(src.ids)
## Dimension Permutation
# in-contiguous orders
function pack_orders!(reg::Register{N, B}, orders::T) where {N, B, T <: Union{Int, Vector{Int}}}
tensor = reshape(state(reg), ntuple(x->2, Val{N})..., B)
inds = findin(reg.line_orders, orders)
perm = reg.line_orders
deleteat!(perm, inds)
prepend!(perm, ids)
dst.ids .= perm
append!(perm, M+1)
permutedims!(dst.data, src.data, perm)
dst
prepend!(perm, orders)
permutedims!(tensor, tensor, (perm..., N+1))
reg
end

function pack!(dst::Register{M, 1, T, M}, src::Register{M, 1, T, M}, ids::NTuple{K, Int}) where {M, T, K}
ids = sort!([ids...])
inds = findin(src.ids, ids)
perm = copy(src.ids)
deleteat!(perm, inds)
prepend!(perm, ids)
dst.ids .= perm
permutedims!(dst.data, src.data, perm)
dst
# contiguous orders
function pack_orders!(reg::Register{N, B}, orders::UnitRange{Int}) where {N, B}
start_order = first(orders)
# return ASAP when target order at the beginning
start_order == first(line_orders(reg)) && return reg

pack_size = 1<<length(orders)
start_ind, = findin(line_orders(reg), start_order)

if start_ind+length(orders)-1 == N
src = reshape(state(reg), :, pack_size)
dst = reshape(state(reg), pack_size, :)
transpose!(dst, src)
else
src = reshape(state(reg), 1<<start_ind, pack_size, :)
dst = reshape(state(reg), pack_size, 1<<start_ind, :)
permutedims!(dst, src, [2, 1, 3])
end
deleteat!(reg.line_orders, start_ind:(start_ind + length(orders) - 1))
prepend!(reg.line_orders, collect(orders))
reg
end

function focus(src::Register{M, B, T, N}, ids::NTuple{K, Int}) where {M, N, B, T, K}
N == M+1 || (src = reshape(src, ntuple(x->2, Val{M})..., B))

pack!(src, ids)
exposed_size = 2^K
remained_size = 2^(M-K)
data = reshape(src.data, exposed_size, remained_size, B)
Register(M, B, data, src.ids)
# mixed
function pack_orders!(reg::Register, orders)
for each in reverse(orders)
pack_orders!(reg, each)
end
reg
end

function focus(src::Register{M, 1, T, M}, ids::NTuple{K, Int}) where {M, T, K}
pack!(src, ids)
exposed_size = 2^K
remained_size = 2^(M-K)
data = reshape(src.data, exposed_size, remained_size)
Register(M, 1, data, src.ids)
nexposed(orders::NTuple{K, Int}) where K = 1<<K
nexposed(orders::Vector{Int}) = 1<<length(orders)
nexposed(orders::UnitRange{Int}) = 1<<length(orders)
# mixed
function nexposed(orders)
total = 0
for each in orders
total += length(each)
end
1<<total
end

# focus(src::Register{M, B}, ids::NTuple) where {M, B} =
# focus(reshape(src, ntuple(x->2, Val{M})..., B), ids)
focus(src::Register{M, 1}, ids::NTuple) where M =
focus(reshape(src, ntuple(x->2, Val{M})), ids)
function focus!(reg::Register{N, B}, orders) where {N, B}
pack_orders!(reg, orders)
reg.state = reshape(state(reg), (nexposed(orders), :))
reg
end
3 changes: 2 additions & 1 deletion test/MathUtils.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import QuCircuit: log2i
import QuCircuit: log2i, bit_length
using Compat.Test

@testset "log2i" begin
Expand All @@ -11,4 +11,5 @@ using Compat.Test
@test typeof(log2i(itype(2^5))) == itype
end

@test bit_length(8) == 4
end
Loading

0 comments on commit 1a7d01b

Please sign in to comment.