-
Notifications
You must be signed in to change notification settings - Fork 123
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Register: delete view * Register: refactor * change test * Utils: improve performance * Test: rename pack * Register: export new API nactive * Test: improve test
- Loading branch information
Showing
5 changed files
with
178 additions
and
298 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.