Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Register: Refactor #17

Merged
merged 7 commits into from
Apr 28, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made a mistake, now I think rank-3 tensor makes more sense, because in this way, the array shape contains all informations about qubit and batch. otherwise, we can not distinguish remaining dimension and batch dimension without query the register.

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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this realization of log2i is much better: https://groups.google.com/forum/#!topic/julia-users/YaACmwePGxM

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