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

Basic framework for QuArrays and basis types #5

Merged
merged 1 commit into from
Jan 7, 2015
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
4 changes: 4 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
language: julia
julia:
- release
- nightly
1 change: 1 addition & 0 deletions REQUIRE
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
julia 0.3-
102 changes: 58 additions & 44 deletions src/QuBase.jl
Original file line number Diff line number Diff line change
@@ -1,51 +1,65 @@
module QuBase

#############
# Constants #
#############
const lang = "\u27E8"
const rang = "\u27E9"
const otimes = "\u2297"
const vdots ="\u205E"

import Base: kron

####################
# String Constants #
####################
const lang = "\u27E8"
const rang = "\u27E9"
const otimes = "\u2297"
const vdots ="\u205E"

##################
# Abstract Types #
##################
abstract AbstractStructure
abstract AbstractQuantum{S<:AbstractStructure}
abstract AbstractState{S<:AbstractStructure} <: AbstractQuantum{S}
abstract AbstractOperator{S<:AbstractStructure} <: AbstractQuantum{S}
abstract AbstractBasis{S<:AbstractStructure} <: AbstractQuantum{S}
abstract QuantumScalar <: Number
##################
# Abstract Types #
##################
abstract AbstractStructure
abstract Orthonormal <: AbstractStructure
abstract AbstractQuantum{S<:AbstractStructure}

#############
# Functions #
#############
structure{S}(::AbstractQuantum{S}) = S
# Various constructor methods in this repo allow an argument
# of type Type{BypassFlag} to be passed in in order to
# circumvent value precalculation/checking. This is useful for
# conversion methods and the like, where you know the input
# has already been vetted elsewhere. Don't use this unless
# you're sure of what you're doing, and don't export this.
abstract BypassFlag

for T=(:AbstractQuantum, :AbstractState, :AbstractOperator, :AbstractBasis)
@eval begin
structure{S}(::Type{($T){S}}) = S
structure(::Type{($T)}) = AbstractStructure
end
end
#############
# Functions #
#############

######################
# Include Statements #
######################
include("statelabel.jl")
include("diracstates.jl")
include("diracoperators.jl")
include("scalar.jl")
include("quarray.jl")
# This should be the only `structure`
# method that needs to be defined for
# type *instances*
structure{S}(::AbstractQuantum{S}) = S
structure(::DataType) = AbstractStructure

export AbstractStructure,
AbstractQuantum,
AbstractState,
AbstractOperator,
AbstractBasis,
QuantumScalar,
structure

# ...and all relevant singleton types
# should have it defined as well:
structure{S}(::Type{AbstractQuantum{S}}) = S

# an n-arity form of the tensor
# product, reduction is done via
# the binary definition of tensor()
# defined in the files included above.
tensor(s...) = reduce(tensor, s)

# For the sake of convenience, kron()
# is defined to be equivalent to
# tensor() for quantum objects
kron(a::AbstractQuantum, b::AbstractQuantum) = tensor(a, b)

######################
# Include Statements #
######################
include("bases/bases.jl")
include("arrays/quarray.jl")

export AbstractStructure,
AbstractQuantum,
Orthonormal,
structure,
tensor
end
68 changes: 68 additions & 0 deletions src/arrays/ladderops.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
###########################
# Ladder Operator Methods #
###########################
# n specifies which particle (a.k.a tensor product
# factor) the operator acts on
raisematrix(lens, n=1) = laddermatrix(lens, RaiseOpFlag, n)
lowermatrix(lens, n=1) = laddermatrix(lens, LowerOpFlag, n)

raiseop(b::AbstractBasis, n=1) = QuArray(raisematrix(size(b), n), b, b)
raiseop(lens::Tuple, n=1) = raiseop(FiniteBasis(lens), n)

lowerop(b::AbstractBasis, n=1) = QuArray(lowermatrix(size(b), n), b, b)
lowerop(lens::Tuple, n=1) = lowerop(FiniteBasis(lens), n)

##########################
# Helper Functions/Types #
##########################
abstract LadderOpFlag
abstract RaiseOpFlag <: LadderOpFlag
abstract LowerOpFlag <: LadderOpFlag

ladder_inds(n, ::Type{RaiseOpFlag}) = ([1:n-1], [2:n])
ladder_inds(n, ::Type{LowerOpFlag}) = ([2:n], [1:n-1])
laddercoeffs(n) = sqrt(linspace(1, n, n))

function fill_op_arr!(arr::AbstractMatrix, ladderflag)
if size(arr, 1) == size(arr, 2)
len = size(arr, 1)
inds = ladder_inds(len, ladderflag)
coeffs = laddercoeffs(len)
for i=1:len-1
arr[inds[1][i], inds[2][i]] = coeffs[i]
end
else
error("Cannot generate ladder coefficients for non-square matrix")
end
return arr
end

# returns a coefficient matrix
# for a ladder operator for a
# single particle fock basis
gen_op_mat(len, ladderflag) = fill_op_arr!(spzeros(len, len), ladderflag)

# this could/should be further optimized,
# it uses the naive approach of taking the
# kronecker product of identity matrices
# and the relevant ladder operator matrix
function laddermatrix(lens, ladderflag, n=1)
if n==1
arr = gen_op_mat(lens[1], ladderflag)
else
arr = speye(lens[1])
for i=2:n-1
arr = kron(speye(lens[i]), arr)
end
arr = kron(gen_op_mat(lens[n], ladderflag), arr)
end
for i=n+1:length(lens)
arr = kron(speye(lens[i]), arr)
end
return arr
end

export raisematrix,
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need/want to export raisematrix and lowermatrix?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's a good point - we're surely going to end up implementing a lot of operations that act perfectly validly on general AbstractArray types outside of QuArray. Do we want to provide that functionality to the world? I don't see any reason why not, it only makes the package more generally useful, and - at least in this case - doesn't expose anything obviously harmful.

Copy link
Contributor

Choose a reason for hiding this comment

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

Personally, I think we should export as few things as possible - at least in the beginning. And in particular I would try not to export abstract types. Maybe we should have a separate issue on export policy?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it merits it does indeed merit its own issue. I don't have time tonight to write one up but if you would like to, I'd be appreciative. If not, I can post one sometime this week.

lowermatrix,
raiseop,
lowerop
141 changes: 141 additions & 0 deletions src/arrays/quarray.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import Base: size,
length,
getindex,
similar,
in,
ctranspose,
transpose,
summary,
zeros,
eye,
#TODO: Implement the below operations
*,.*,
/,./,
+,.+,
-,.-,
kron

abstract AbstractQuArray{B<:AbstractBasis,T,N} <: AbstractArray{T,N}

###########
# QuArray #
###########
# Using an NTuple allows us to have a basis for each dimension,
# and gives us a less ambiguous way to determine if a QuArray will act
# like a vector, matrix, or tensor using the dimension parameter N.
type QuArray{B<:AbstractBasis,T,N,A} <: AbstractQuArray{B,T,N}
coeffs::A
bases::NTuple{N,B}
function QuArray(coeffs::AbstractArray{T}, bases::NTuple{N,B})
if checkbases(coeffs, bases)
new(coeffs, bases)
else
error("Coefficient array does not conform to input bases")
end
end
end

typealias QuVector{B<:AbstractBasis,T,A} QuArray{B,T,1,A}
typealias QuMatrix{B<:AbstractBasis,T,A} QuArray{B,T,2,A}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So now that we know about NTuples, how do folks feel about changing QuArray to this:

abstract AbstractQuArray{B<:AbstractBasis, T, N} <: AbstractArray{T,N}

type QuArray{B<:AbstractBasis, T, N, A} <: AbstractQuArray{B, T, N}
    coeffs::A
    bases::NTuple{N, B}
    function QuArray(coeffs::AbstractArray{T}, bases::NTuple{N, B}) 
        if checkbases(coeffs, bases) 
            new(coeffs, bases)
        else 
            error("Coefficient array does not conform to input bases")
        end
    end
end

I believe the above to be much, much cleaner. There are two significant changes with this approach:

  1. There can only be a single basis type for the entire array. I don't really see any detriment to
    this - when would you ever want different bases types along different dimensions? If anything, the
    restriction makes the whole object easier to reason about.
  2. We define the number of dimensions of a QuArray to be equivalent to how many bases its storing.
    This way, QuArray{..., 1} really does act like a vector, QuArray{..., 2} really does act like an
    operator, and QuArray{..., 3+} really does act like a tensor. This approach allows us to sidestep
    some of the issues with the conflation of vectors and matrices. I was already heading down this road
    with the QuVector and QuMatrix type aliases, but this is much more clearly laid out.

Copy link
Contributor

Choose a reason for hiding this comment

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

Nice! I guess I would have asked you about NTuples and QuArrays anyways :-) I also think that this change makes the definition of QuArrays much cleaner and nicer. The only objection I can think of is your point 1), because it "restricts" us to cartesian tensors only (ie we don't distinguish between raised and lower indices anymore). However, I would say that this is a reasonable compromise, since it will be the most common use case(?).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree. I've implemented this now, let me know what you think. If there aren't any other issues (besides exportation, which we now have a separate issue for), I'm ready for this to be actually merged.

QuArray{T,N,B<:AbstractBasis}(coeffs::AbstractArray{T}, bases::NTuple{N,B}) = QuArray{B,T,N,typeof(coeffs)}(coeffs, bases)
QuArray(coeffs, bases::AbstractBasis...) = QuArray(coeffs, bases)
QuArray(coeffs) = QuArray(coeffs, basesfordims(size(coeffs)))

############################
# Convenience Constructors #
############################
statevec(i::Int, fb::FiniteBasis) = QuArray(single_coeff(i, length(fb)), fb)
statevec(i::Int, lens::Int...=i) = statevec(i, FiniteBasis(lens))

zeros(qa::QuArray) = QuArray(zeros(qa.coeffs), qa.bases)
eye(qa::QuArray) = QuArray(eye(qa.coeffs), qa.bases)

######################
# Property Functions #
######################
bases(qa::QuArray) = qa.bases
coeffs(qa::QuArray) = qa.coeffs
size(qa::QuArray, i...) = size(qa.coeffs, i...)

########################
# Array-like functions #
########################
similar{B,T}(qa::QuArray{B,T}, element_type=T) = QuArray(similar(qa.coeffs, T), qa.bases)
# Is there a way to properly define the below for
# any arbitrary basis? Obviously doesn't make sense
# for B<:AbstractInfiniteBasis, and I'm reluctant to
# enforce that every B<:AbstractFiniteBasis will have a
# constructor B(::Int), which is how the below is constructing
# instances of FiniteBasis.
function similar{B<:FiniteBasis,T}(qa::QuArray{B,T}, element_type, dims)
return QuArray(similar(qa.coeffs, T, dims), basesfordims(dims, B))
end
getindex(qa::QuArray, i::AbstractArray) = getindex(qa.coeffs, i)
getindex(qa::QuArray, i::Real) = getindex(qa.coeffs, i)
getindex(qa::QuArray, i) = getindex(qa.coeffs, i)
getindex(qa::QuArray, i...) = getindex(qa.coeffs, i...)

in(c, qa::QuArray) = in(c, qa.coeffs)

ctranspose(qa::QuVector) = QuArray(ctranspose(qa.coeffs), qa.bases)
ctranspose(qa::QuMatrix) = QuArray(ctranspose(qa.coeffs), reverse(qa.bases))
Copy link
Contributor

Choose a reason for hiding this comment

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

I like the fact that a transposed QuVector stays a QuVector and doesn't become a matrix like for arrays. But we might want to have some way to indicate that a QuVector or a QuMatrix was transposed. I am thinking of something like JuliaLang/julia#6837, i.e. introducing a dual/transposed type. This would make it easier to define inner products and such. We don't have to do this now. Maybe once this is merged we can have an issue as a reminder. cc: @Jutho

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, the transpose distinction is going to need to exist at some point. I have a DualType for DiracArrays in QuDirac, but the way I'm using it doesn't extend to anything beyond vectors and I'd rather have the distinction happen in QuBase anyway (or even better, in Julia base, but who knows how long that will take). It's a tough nut to crack. I think giving it an issue is the right way to go (hopefully one that links to all the relevant issues elsewhere so we don't repeat discussion).

transpose(qa::QuVector) = QuArray(transpose(qa.coeffs), qa.bases)
transpose(qa::QuMatrix) = QuArray(transpose(qa.coeffs), reverse(qa.bases))

######################
# Printing Functions #
######################
function summary{B,T,N,A}(qa::QuArray{B,T,N,A})
return "$(sizenotation(size(qa))) QuArray\n" *
"...bases: $B,\n" *
"...coeff: $A"
end

######################
# Include Statements #
######################
include("ladderops.jl")

####################
# Helper Functions #
####################
sizenotation(tup::(Int,)) = "$(first(tup))-element"
sizenotation(tup::(Int...)) = reduce(*, map(s->"$(s)x", tup))[1:end-1]

# checkbases() is overloaded for a single basis
# to handle the fact that row vectors are
# N=2
function checkbases(coeffs, bases::NTuple{1, AbstractBasis})
if ndims(coeffs) <= 2
if size(coeffs, 2) == 1
return checkcoeffs(coeffs, 1, first(bases))
elseif size(coeffs, 1) == 1
return checkcoeffs(coeffs, 2, first(bases))
end
end
return false
end

function checkbases{N}(coeffs, bases::NTuple{N, AbstractBasis})
if ndims(coeffs) == length(bases)
return reduce(&, [checkcoeffs(coeffs, i, bases[i]) for i=1:N])
end
return false
end

# Assumes that every basis type passed in
# has a constructor B(::eltype(lens))
function basesfordims(lens::Tuple, B=ntuple(length(lens), x->FiniteBasis))
return ntuple(length(lens), n->B[n](lens[n]))
end
one_at_ind!(arr, i) = setindex!(arr, one(eltype(arr)), i)
single_coeff(i, lens...) = one_at_ind!(zeros(lens), i)

export AbstractQuArray,
QuArray,
QuVector,
QuMatrix,
bases,
coeffs,
statevec
55 changes: 55 additions & 0 deletions src/bases/bases.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
####################
# Type Definitions #
####################
abstract AbstractBasis{S<:AbstractStructure} <: AbstractQuantum{S}
abstract AbstractFiniteBasis{S<:AbstractStructure} <: AbstractBasis{S}
abstract AbstractInfiniteBasis{S<:AbstractStructure} <: AbstractBasis{S}

#############
# Functions #
#############
# Note: All B<:AbstractBasis types should implement the following:
#
# checkcoeffs(coeffs, dim, basis::B) -> checks whether a coefficient
# array is valid for use with
# the given basis. This is used
# by QuArray to ensure that the
# coefficient array is not malformed
# with respect to the input bases.
# The second argument, `dim`, specifies
# the dimension of the coefficient
# array which corresponds to the provided
# basis.
#
# nfactors(basis::B) -> the number of factor bases for `basis`; this is `N`
# in `FiniteBasis{S,N}`
#
# tensor(a::B, b::B) -> Take the tensor product of these two bases. This
# function should optimally return a basis of same
# type as the input bases. We can and should also
# implement promote_rules/conversion methods between
# bases.
#
# structure{S}(::Type{MyBasisType{S}}) -> returns S<:AbstractStructure for
# the provided basis type.

checkcoeffs(coeffs::AbstractArray, dim::Int, basis::AbstractBasis) = error("checkcoeffs(coeffs, dim, ::$B) must be defined!")

for basis=(:AbstractBasis, :AbstractFiniteBasis, :AbstractInfiniteBasis)
@eval begin
structure{S}(::Type{($basis){S}}) = S
end
end

######################
# Include Statements #
######################
include("finitebasis.jl")

export AbstractBasis,
AbstractFiniteBasis,
AbstractInfiniteBasis,
checkcoeffs,
structure


Loading