@testset "fourier_diff" begin
    @test fourier_diff(5, order=1) ≈ [0.0 0.8506508083520398 -0.5257311121191336 0.5257311121191336 -0.8506508083520399; -0.8506508083520399 0.0 0.8506508083520398 -0.5257311121191336 0.5257311121191336; 0.5257311121191336 -0.8506508083520399 0.0 0.8506508083520398 -0.5257311121191336; -0.5257311121191336 0.5257311121191336 -0.8506508083520399 0.0 0.8506508083520398; 0.8506508083520398 -0.5257311121191336 0.5257311121191336 -0.8506508083520399 0.0]
    @test fourier_diff(6, order=1) ≈ [0.0 0.8660254037844387 -0.2886751345948127 -3.061616997868383e-17 0.288675134594813 -0.8660254037844387; -0.8660254037844387 0.0 0.8660254037844387 -0.2886751345948127 -3.061616997868383e-17 0.288675134594813; 0.288675134594813 -0.8660254037844387 0.0 0.8660254037844387 -0.2886751345948127 -3.061616997868383e-17; -3.061616997868383e-17 0.288675134594813 -0.8660254037844387 0.0 0.8660254037844387 -0.2886751345948127; -0.2886751345948127 -3.061616997868383e-17 0.288675134594813 -0.8660254037844387 0.0 0.8660254037844387; 0.8660254037844387 -0.2886751345948127 -3.061616997868383e-17 0.288675134594813 -0.8660254037844387 0.0]
    @test fourier_diff(BigFloat, 5, order=1) ≈ [BigFloat("0.0") BigFloat("0.850650808352039932181540497063011072240401403764816881836740242377884047363955") BigFloat("-0.5257311121191336060256690848478766072854979322433417815289355232412111464032185") BigFloat("0.5257311121191336060256690848478766072854979322433417815289355232412111464032185") BigFloat("-0.8506508083520399321815404970630110722404014037648168818367402423778840473639636"); BigFloat("-0.8506508083520399321815404970630110722404014037648168818367402423778840473639636") BigFloat("0.0") BigFloat("0.850650808352039932181540497063011072240401403764816881836740242377884047363955") BigFloat("-0.5257311121191336060256690848478766072854979322433417815289355232412111464032185") BigFloat("0.5257311121191336060256690848478766072854979322433417815289355232412111464032185"); BigFloat("0.5257311121191336060256690848478766072854979322433417815289355232412111464032185") BigFloat("-0.8506508083520399321815404970630110722404014037648168818367402423778840473639636") BigFloat("0.0") BigFloat("0.850650808352039932181540497063011072240401403764816881836740242377884047363955") BigFloat("-0.5257311121191336060256690848478766072854979322433417815289355232412111464032185"); BigFloat("-0.5257311121191336060256690848478766072854979322433417815289355232412111464032185") BigFloat("0.5257311121191336060256690848478766072854979322433417815289355232412111464032185") BigFloat("-0.8506508083520399321815404970630110722404014037648168818367402423778840473639636") BigFloat("0.0") BigFloat("0.850650808352039932181540497063011072240401403764816881836740242377884047363955"); BigFloat("0.850650808352039932181540497063011072240401403764816881836740242377884047363955") BigFloat("-0.5257311121191336060256690848478766072854979322433417815289355232412111464032185") BigFloat("0.5257311121191336060256690848478766072854979322433417815289355232412111464032185") BigFloat("-0.8506508083520399321815404970630110722404014037648168818367402423778840473639636") BigFloat("0.0")]
    @test fourier_diff(BigFloat, 6, order=1) ≈ [BigFloat("0.0") BigFloat("0.8660254037844386467637231707529361834714026269051903140279034897259665084543383") BigFloat("-0.2886751345948128822545743902509787278238008756350634380093011632419888361514749") BigFloat("-2.742293602448380191855326565989245052626895591271719877947514292480356721283385e-78") BigFloat("0.2886751345948128822545743902509787278238008756350634380093011632419888361514619") BigFloat("-0.8660254037844386467637231707529361834714026269051903140279034897259665084543902"); BigFloat("-0.8660254037844386467637231707529361834714026269051903140279034897259665084543902") BigFloat("0.0") BigFloat("0.8660254037844386467637231707529361834714026269051903140279034897259665084543383") BigFloat("-0.2886751345948128822545743902509787278238008756350634380093011632419888361514749") BigFloat("-2.742293602448380191855326565989245052626895591271719877947514292480356721283385e-78") BigFloat("0.2886751345948128822545743902509787278238008756350634380093011632419888361514619"); BigFloat("0.2886751345948128822545743902509787278238008756350634380093011632419888361514619") BigFloat("-0.8660254037844386467637231707529361834714026269051903140279034897259665084543902") BigFloat("0.0") BigFloat("0.8660254037844386467637231707529361834714026269051903140279034897259665084543383") BigFloat("-0.2886751345948128822545743902509787278238008756350634380093011632419888361514749") BigFloat("-2.742293602448380191855326565989245052626895591271719877947514292480356721283385e-78"); BigFloat("-2.742293602448380191855326565989245052626895591271719877947514292480356721283385e-78") BigFloat("0.2886751345948128822545743902509787278238008756350634380093011632419888361514619") BigFloat("-0.8660254037844386467637231707529361834714026269051903140279034897259665084543902") BigFloat("0.0") BigFloat("0.8660254037844386467637231707529361834714026269051903140279034897259665084543383") BigFloat("-0.2886751345948128822545743902509787278238008756350634380093011632419888361514749"); BigFloat("-0.2886751345948128822545743902509787278238008756350634380093011632419888361514749") BigFloat("-2.742293602448380191855326565989245052626895591271719877947514292480356721283385e-78") BigFloat("0.2886751345948128822545743902509787278238008756350634380093011632419888361514619") BigFloat("-0.8660254037844386467637231707529361834714026269051903140279034897259665084543902") BigFloat("0.0") BigFloat("0.8660254037844386467637231707529361834714026269051903140279034897259665084543383"); BigFloat("0.8660254037844386467637231707529361834714026269051903140279034897259665084543383") BigFloat("-0.2886751345948128822545743902509787278238008756350634380093011632419888361514749") BigFloat("-2.742293602448380191855326565989245052626895591271719877947514292480356721283385e-78") BigFloat("0.2886751345948128822545743902509787278238008756350634380093011632419888361514619") BigFloat("-0.8660254037844386467637231707529361834714026269051903140279034897259665084543902") BigFloat("0.0")]

    @test fourier_diff(5, order=2) ≈ [-2.0 1.1708203932499366 -0.1708203932499369 -0.17082039324993695 1.170820393249937; 1.170820393249937 -2.0 1.1708203932499366 -0.1708203932499369 -0.17082039324993695; -0.17082039324993695 1.170820393249937 -2.0 1.1708203932499366 -0.1708203932499369; -0.1708203932499369 -0.17082039324993695 1.170820393249937 -2.0 1.1708203932499366; 1.1708203932499366 -0.1708203932499369 -0.17082039324993695 1.170820393249937 -2.0]
    @test fourier_diff(6, order=2) ≈ [-3.1666666666666665 2.000000000000001 -0.6666666666666666 0.5 -0.6666666666666669 2.000000000000001; 2.000000000000001 -3.1666666666666665 2.000000000000001 -0.6666666666666666 0.5 -0.6666666666666669; -0.6666666666666669 2.000000000000001 -3.1666666666666665 2.000000000000001 -0.6666666666666666 0.5; 0.5 -0.6666666666666669 2.000000000000001 -3.1666666666666665 2.000000000000001 -0.6666666666666666; -0.6666666666666666 0.5 -0.6666666666666669 2.000000000000001 -3.1666666666666665 2.000000000000001; 2.000000000000001 -0.6666666666666666 0.5 -0.6666666666666669 2.000000000000001 -3.1666666666666665]
    @test fourier_diff(BigFloat, 5, order=2) ≈ [BigFloat("-1.999999999999999999999999999999999999999999999999999999999999999999999999999983") BigFloat("1.170820393249936908922752100619382870632185507883457717281269173623156277691329") BigFloat("-0.170820393249936908922752100619382870632185507883457717281269173623156277691348") BigFloat("-0.1708203932499369089227521006193828706321855078834577172812691736231562776913437") BigFloat("1.170820393249936908922752100619382870632185507883457717281269173623156277691346"); BigFloat("1.170820393249936908922752100619382870632185507883457717281269173623156277691346") BigFloat("-1.999999999999999999999999999999999999999999999999999999999999999999999999999983") BigFloat("1.170820393249936908922752100619382870632185507883457717281269173623156277691329") BigFloat("-0.170820393249936908922752100619382870632185507883457717281269173623156277691348") BigFloat("-0.1708203932499369089227521006193828706321855078834577172812691736231562776913437"); BigFloat("-0.1708203932499369089227521006193828706321855078834577172812691736231562776913437") BigFloat("1.170820393249936908922752100619382870632185507883457717281269173623156277691346") BigFloat("-1.999999999999999999999999999999999999999999999999999999999999999999999999999983") BigFloat("1.170820393249936908922752100619382870632185507883457717281269173623156277691329") BigFloat("-0.170820393249936908922752100619382870632185507883457717281269173623156277691348"); BigFloat("-0.170820393249936908922752100619382870632185507883457717281269173623156277691348") BigFloat("-0.1708203932499369089227521006193828706321855078834577172812691736231562776913437") BigFloat("1.170820393249936908922752100619382870632185507883457717281269173623156277691346") BigFloat("-1.999999999999999999999999999999999999999999999999999999999999999999999999999983") BigFloat("1.170820393249936908922752100619382870632185507883457717281269173623156277691329"); BigFloat("1.170820393249936908922752100619382870632185507883457717281269173623156277691329") BigFloat("-0.170820393249936908922752100619382870632185507883457717281269173623156277691348") BigFloat("-0.1708203932499369089227521006193828706321855078834577172812691736231562776913437") BigFloat("1.170820393249936908922752100619382870632185507883457717281269173623156277691346") BigFloat("-1.999999999999999999999999999999999999999999999999999999999999999999999999999983")]
    @test fourier_diff(BigFloat, 6, order=2) ≈ [BigFloat("-3.166666666666666666666666666666666666666666666666666666666666666666666666666678") BigFloat("1.999999999999999999999999999999999999999999999999999999999999999999999999999793") BigFloat("-0.6666666666666666666666666666666666666666666666666666666666666666666666666666868") BigFloat("0.50") BigFloat("-0.6666666666666666666666666666666666666666666666666666666666666666666666666666609") BigFloat("1.999999999999999999999999999999999999999999999999999999999999999999999999999965"); BigFloat("1.999999999999999999999999999999999999999999999999999999999999999999999999999965") BigFloat("-3.166666666666666666666666666666666666666666666666666666666666666666666666666678") BigFloat("1.999999999999999999999999999999999999999999999999999999999999999999999999999793") BigFloat("-0.6666666666666666666666666666666666666666666666666666666666666666666666666666868") BigFloat("0.50") BigFloat("-0.6666666666666666666666666666666666666666666666666666666666666666666666666666609"); BigFloat("-0.6666666666666666666666666666666666666666666666666666666666666666666666666666609") BigFloat("1.999999999999999999999999999999999999999999999999999999999999999999999999999965") BigFloat("-3.166666666666666666666666666666666666666666666666666666666666666666666666666678") BigFloat("1.999999999999999999999999999999999999999999999999999999999999999999999999999793") BigFloat("-0.6666666666666666666666666666666666666666666666666666666666666666666666666666868") BigFloat("0.50"); BigFloat("0.50") BigFloat("-0.6666666666666666666666666666666666666666666666666666666666666666666666666666609") BigFloat("1.999999999999999999999999999999999999999999999999999999999999999999999999999965") BigFloat("-3.166666666666666666666666666666666666666666666666666666666666666666666666666678") BigFloat("1.999999999999999999999999999999999999999999999999999999999999999999999999999793") BigFloat("-0.6666666666666666666666666666666666666666666666666666666666666666666666666666868"); BigFloat("-0.6666666666666666666666666666666666666666666666666666666666666666666666666666868") BigFloat("0.50") BigFloat("-0.6666666666666666666666666666666666666666666666666666666666666666666666666666609") BigFloat("1.999999999999999999999999999999999999999999999999999999999999999999999999999965") BigFloat("-3.166666666666666666666666666666666666666666666666666666666666666666666666666678") BigFloat("1.999999999999999999999999999999999999999999999999999999999999999999999999999793"); BigFloat("1.999999999999999999999999999999999999999999999999999999999999999999999999999793") BigFloat("-0.6666666666666666666666666666666666666666666666666666666666666666666666666666868") BigFloat("0.50") BigFloat("-0.6666666666666666666666666666666666666666666666666666666666666666666666666666609") BigFloat("1.999999999999999999999999999999999999999999999999999999999999999999999999999965") BigFloat("-3.166666666666666666666666666666666666666666666666666666666666666666666666666678")]

    @test fourier_diff(5) == fourier_diff(5, order=1)
end


# The four adjacent digits in the 1000-digit number that have the greatest
# product are 9 × 9 × 8 × 9 = 5832.
#
# 73167176531330624919225119674426574742355349194934
# 96983520312774506326239578318016984801869478851843
# 85861560789112949495459501737958331952853208805511
# 12540698747158523863050715693290963295227443043557
# 66896648950445244523161731856403098711121722383113
# 62229893423380308135336276614282806444486645238749
# 30358907296290491560440772390713810515859307960866
# 70172427121883998797908792274921901699720888093776
# 65727333001053367881220235421809751254540594752243
# 52584907711670556013604839586446706324415722155397
# 53697817977846174064955149290862569321978468622482
# 83972241375657056057490261407972968652414535100474
# 82166370484403199890008895243450658541227588666881
# 16427171479924442928230863465674813919123162824586
# 17866458359124566529476545682848912883142607690042
# 24219022671055626321111109370544217506941658960408
# 07198403850962455444362981230987879927244284909188
# 84580156166097919133875499200524063689912560717606
# 05886116467109405077541002256983155200055935729725
# 71636269561882670428252483600823257530420752963450
#
# Find the thirteen adjacent digits in the 1000-digit number that have the
# greatest product. What is the value of this product?

using ProjectEulerSolutions

# Use Julia built-ins to convert string into individual numbers then multiply
# every adjacent chunck.
function p008solution(number::String="23", adjacent::Integer=1)::Integer
    array = map(x->parse(Int8, x), split(number, ""))
    # Below, another way to split, using digits function, not quite as fast
    # array = digits(Int8, parse(BigInt, input))
    products = map(x -> prod(array[x:x+adjacent-1]), 1:(length(array)-adjacent+1))
    return maximum(products)
end

p008 = Problems.Problem(p008solution)

input = "7316717653133062491922511967442657474235534919493496983520312774506326239578318016984801869478851843858615607891129494954595017379583319528532088055111254069874715852386305071569329096329522744304355766896648950445244523161731856403098711121722383113622298934233803081353362766142828064444866452387493035890729629049156044077239071381051585930796086670172427121883998797908792274921901699720888093776657273330010533678812202354218097512545405947522435258490771167055601360483958644670632441572215539753697817977846174064955149290862569321978468622482839722413756570560574902614079729686524145351004748216637048440319989000889524345065854122758866688116427171479924442928230863465674813919123162824586178664583591245665294765456828489128831426076900422421902267105562632111110937054421750694165896040807198403850962455444362981230987879927244284909188845801561660979191338754992005240636899125607176060588611646710940507754100225698315520005593572972571636269561882670428252483600823257530420752963450"

Problems.benchmark(p008, input, 13)


# Composite vector spaces
#-------------------------
# Obtained as (some subspace of) the tensor product elementary vector spaces
abstract CompositeSpace{S<:ElementarySpace} <: VectorSpace

Base.eltype{S<:ElementarySpace}(V::CompositeSpace{S}) = eltype(S)
Base.eltype{S<:ElementarySpace}(::Type{CompositeSpace{S}}) = eltype(S)
Base.eltype{S<:CompositeSpace}(::Type{S}) = eltype(super(S))

# Composite spaces with finite number of elements N, defining spaces of tensors
abstract TensorSpace{S<:ElementarySpace,N} <: CompositeSpace{S}
Base.isfinite{S<:ElementarySpace}(::TensorSpace{S,:Inf}) = false
Base.isfinite(::TensorSpace) = true

# Explicit realizations:
#------------------------
# ProductSpace: type and methods for tensor products of ElementarySpace objects
include("spaces/productspace.jl")

# InvariantSpace: invariant subspace of tensor product of UnitaryRepresentationSpace objects
include("spaces/invariantspace.jl")

# Other examples might include:
# braidedspace and fermionspace
# symmetric and antisymmetric subspace of a tensor product of identical vector spaces
# ...


using Distributions
using Base.Test

## Fisher's noncentral hypergeometric distribution
ns = 80
nf = 60
n = 100

# http://en.wikipedia.org/wiki/Fisher's_noncentral_hypergeometric_distribution
ω = 10.0
d = FisherNoncentralHypergeometric(ns, nf, n, ω)

@test_approx_eq_eps mean(d) 71.95759 1e-5
@test mode(d) == 72
@test quantile(d, 0.05) == 68
@test quantile(d, 0.95) == 75
@test_approx_eq_eps logpdf(d, 75) -2.600957 1e-6
@test_approx_eq_eps pdf(d, 75) 0.07420252 1e-7
@test_approx_eq_eps cdf(d, 75) 0.9516117 1e-7

ω = 0.361
d = FisherNoncentralHypergeometric(ns, nf, n, ω)

@test_approx_eq_eps mean(d) 50.42299 1e-5
@test mode(d) == 50
@test quantile(d, 0.05) == 47
@test quantile(d, 0.95) == 54
@test_approx_eq_eps logpdf(d, 50) -1.820071 1e-6
@test_approx_eq_eps pdf(d, 50) 0.1620142 1e-7
@test_approx_eq_eps cdf(d, 50) 0.5203835 1e-7

# distributions are both equal to the (central) hypergeometric distribution when the odds ratio is 1.
ω = 1.0
d = FisherNoncentralHypergeometric(ns, nf, n, ω)

ref = Hypergeometric(ns,nf,n)
@test_approx_eq_eps mean(d) mean(ref) 1e-5
@test_approx_eq_eps logpdf(d, 51) logpdf(ref, 51) 1e-7
@test_approx_eq_eps pdf(d, 51) pdf(ref, 51) 1e-7
@test_approx_eq_eps cdf(d, 51) cdf(ref, 51) 1e-7
@test_approx_eq_eps quantile(d, 0.05) quantile(ref, 0.05) 1e-7
@test_approx_eq_eps quantile(d, 0.95) quantile(ref, 0.95) 1e-7
@test mode(d) == mode(ref)

## Wallenius' noncentral hypergeometric distribution
ns = 80
nf = 60
n = 100

# http://en.wikipedia.org/wiki/Fisher's_noncentral_hypergeometric_distribution
ω = 10.0
d = WalleniusNoncentralHypergeometric(ns, nf, n, ω)

@test_approx_eq_eps mean(d) 78.82945 1e-5
@test mode(d) == 80
@test quantile(d, 0.05) == 77
@test quantile(d, 0.95) == 80
@test_approx_eq_eps logpdf(d, 75) -4.750073 1e-6
@test_approx_eq_eps pdf(d, 75) 0.00865106 1e-7
@test_approx_eq_eps cdf(d, 75) 0.01133378 1e-7

ω = 0.361
d = WalleniusNoncentralHypergeometric(ns, nf, n, ω)

@test_approx_eq_eps mean(d) 45.75323 1e-5
@test mode(d) == 45
@test quantile(d, 0.05) == 42
@test quantile(d, 0.95) == 49
@test_approx_eq_eps logpdf(d, 50) -3.624478 1e-6
@test_approx_eq_eps pdf(d, 50) 0.026663 1e-7
@test_approx_eq_eps cdf(d, 50) 0.983674 1e-7

# distributions are both equal to the (central) hypergeometric distribution when the odds ratio is 1.
ω = 1.0
d = WalleniusNoncentralHypergeometric(ns, nf, n, ω)

ref = Hypergeometric(ns,nf,n)
@test_approx_eq_eps mean(d) mean(ref) 1e-5
@test_approx_eq_eps logpdf(d, 51) logpdf(ref, 51) 1e-7
@test_approx_eq_eps pdf(d, 51) pdf(ref, 51) 1e-7
@test_approx_eq_eps cdf(d, 51) cdf(ref, 51) 1e-7
@test_approx_eq_eps quantile(d, 0.05) quantile(ref, 0.05) 1e-7
@test_approx_eq_eps quantile(d, 0.95) quantile(ref, 0.95) 1e-7
@test mode(d) == mode(ref)


export
    create,
    annihilate,
    displace,
    squeeze

############
# a† and a #
############
"""
    QuantumStateBase.Creation(T; dim::Integer)

Construct a creation operator in matrix form.

``\\hat{a}^{\\dagger}``
"""
Creation(T; dim::Integer) = .√(diagm(-1 => T.(Base.OneTo(dim-1))))

"""
    create(s::AbstractArray{T})

Apply creation operator on a quantum state.

## Arguments

* `s`: `s` can be a quantum state vector or a density matrix of a quantum state.

## Examples

```jldoctest
julia> state = VacuumState(dim=5)
5-element Vector{Float64}:
 1.0
 0.0
 0.0
 0.0
 0.0

julia> create(state)
5-element Vector{Float64}:
 0.0
 1.0
 0.0
 0.0
 0.0
```

```jldoctest
julia> state = VacuumState(Matrix, dim=5)
5×5 Matrix{Float64}:
 1.0  0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0

julia> create(state)
5×5 Matrix{Float64}:
 0.0  0.0  0.0  0.0  0.0
 0.0  1.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0
```
"""
function create(v::AbstractVector{T}) where {T}
    dim = length(v)

    return Creation(T, dim=dim) * v
end

function create(ρ::AbstractMatrix{T}) where {T}
    dim = size(ρ, 1)
    c = Creation(T, dim=dim)

    return c * ρ * c'
end

"""
    QuantumStateBase.Annihilation(T; dim::Integer)

Construct an annihilation operator in matrix form.

``\\hat{a}``
"""
Annihilation(T; dim::Integer) = .√(diagm(1 => T.(Base.OneTo(dim-1))))

"""
    annihilate(s::AbstractArray{T})

Apply annihilation operator on a quantum state.

## Arguments

* `s`: `s` can be a quantum state vector or a density matrix of a quantum state.

## Examples

```jldoctest
julia> state = SinglePhotonState(dim=5)
5-element Vector{Float64}:
 0.0
 1.0
 0.0
 0.0
 0.0

julia> annihilate(state)
5-element Vector{Float64}:
 1.0
 0.0
 0.0
 0.0
 0.0
```

```jldoctest
julia> state = SinglePhotonState(Matrix, dim=5)
5×5 Matrix{Float64}:
 0.0  0.0  0.0  0.0  0.0
 0.0  1.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0

julia> annihilate(state)
5×5 Matrix{Float64}:
 1.0  0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0
```
"""
function annihilate(v::AbstractVector{T}) where {T}
    dim = length(v)

    return Annihilation(T, dim=dim) * v
end

function annihilate(ρ::AbstractMatrix{T}) where {T}
    dim = size(ρ, 1)
    a = Annihilation(T, dim=dim)

    return a * ρ * a'
end

###########
# α and ξ #
###########

struct ComplexVec{T<:Real}
    r::T
    θ::T
end

"""
    QuantumStateBase.ComplexVec(r::T, θ::T)

Construct a vector in polar coordinate for complex plane.

``v = re^{-i\\theta}``

## Arguments

* `r`: Radius of the complex number.
* `θ`: Argument of the complex number.
"""
ComplexVec(r::T, θ::T) where {T} = ComplexVec{T}(r, θ)

function Base.show(io::IO, complexvec::ComplexVec{T}) where {T}
    print(io, "ComplexVec{$T}($(complexvec.r)exp(-$(complexvec.θ)im))")
end

"""
    QuantumStateBase.z(complexvec::ComplexVec)

Evaluate the complex vector.
"""
z(complexvec::ComplexVec) = complexvec.r * exp(-im * complexvec.θ)

################
# displacement #
################
"""
    QuantumStateBase.Displacement(T::Type{<:Complex}, r::Real, θ::Real; dim::Integer)

Construct a displacement operator in matrix form.

``\\hat{D}(\\alpha) = exp(\\alpha \\hat{a}^{\\dagger} - \\alpha^{*} \\hat{a})``
"""
function Displacement(T::Type{<:Complex}, r::Real, θ::Real; dim)
    U = real(T)
    α = ComplexVec(U(r), U(θ))

    return exp(
        z(α) * Creation(U, dim=dim) - z(α)' * Annihilation(U, dim=dim)
    )
end

"""
    displace(s::AbstractArray{T}, r, θ)

Apply displacement operator on a quantum state.

## Arguments

* `s`: `s` can be a quantum state vector or a density matrix of a quantum state.
* `r`: Radius of displacement.
* `θ`: Phase of displacement.

## Examples

```julia-repl
julia> state = VacuumState(dim=5)
5-element Vector{Float64}:
 1.0
 0.0
 0.0
 0.0
 0.0

julia> displace(state, 2, π/4)
5-element Vector{ComplexF64}:
   0.14864196494651452 - 5.551115123125783e-17im
   0.15309939720606983 - 0.15309939720606955im
 8.326672684688674e-17 - 0.5201876236174409im
   -0.1359447277638306 - 0.13594472776383068im
   -0.7896009204837534 - 3.0531133177191805e-16im
```

```julia-repl
julia> state = VacuumState(Matrix, dim=5)
5×5 Matrix{Float64}:
 1.0  0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0

julia> displace(state, 2, π/4)
5×5 Matrix{ComplexF64}:
   0.0220944+0.0im              0.022757+0.022757im   4.12531e-17+0.0773217im    -0.0202071+0.0202071im    -0.117368+8.92137e-17im
    0.022757-0.022757im        0.0468789+0.0im          0.0796404+0.0796404im  -2.42861e-17+0.0416261im    -0.120887+0.120887im
 4.12531e-17-0.0773217im       0.0796404-0.0796404im     0.270595+0.0im           0.0707168+0.0707168im  9.30717e-17+0.410741im
  -0.0202071-0.0202071im    -2.42861e-17-0.0416261im    0.0707168-0.0707168im     0.0369619+0.0im           0.107342+0.107342im
   -0.117368-8.92137e-17im     -0.120887-0.120887im   9.30717e-17-0.410741im       0.107342-0.107342im       0.62347+0.0im
```
"""
function displace(v::AbstractVector{T}, r, θ) where {T}
    dim = length(v)

    return Displacement(complex(T), r, θ, dim=dim) * v
end

function displace(ρ::AbstractMatrix{T}, r, θ) where {T}
    dim = size(ρ, 1)
    d = Displacement(complex(T), r, θ, dim=dim)

    return d * ρ * d'
end

#############
# squeezing #
#############

"""
    QuantumStateBase.Squeezing(T::Type{<:Complex}, r::Real, θ::Real; dim::Integer)

Construct a squeezing operator in matrix form.

## Arguments

* `r`: Squeezing level.
* `θ`: Squeezing phase.

``\\hat{S}(\\xi) = exp(\\frac{1}{2}(\\xi^{*} \\hat{a}^{2} - \\xi \\hat{a}^{\\dagger 2}))``
"""
function Squeezing(T::Type{<:Complex}, r::Real, θ::Real; dim)
    U = real(T)
    ξ = ComplexVec(U(r), U(θ))

    return exp(
        (z(ξ)' * Annihilation(U, dim=dim)^2)/2 - (z(ξ) * Creation(U, dim=dim)^2)/2
    )
end

"""
    squeeze(s::AbstractArray{T})

Apply squeezing operator on a quantum state.

## Arguments

* `s`: `s` can be a quantum state vector or a density matrix of a quantum state.
* `r`: Squeezing level.
* `θ`: Squeezing phase.

## Examples

```julia-repl
julia> state = VacuumState(dim=5)
5-element Vector{Float64}:
 1.0
 0.0
 0.0
 0.0
 0.0

julia> squeeze(state, 0.5, π/4)
5-element Vector{ComplexF64}:
     0.9419264274916698 + 0.0im
                    0.0 + 0.0im
   -0.21510388914370943 + 0.21510388914370937im
                    0.0 + 0.0im
 3.0228557826221164e-17 - 0.1422506201859301im
```

```julia-repl
julia> state = VacuumState(Matrix, dim=5)
5×5 Matrix{Float64}:
 1.0  0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0

julia> squeeze(state, 0.5, π/4)
5×5 Matrix{ComplexF64}:
    0.887225+0.0im       0.0+0.0im   -0.202612-0.202612im   0.0+0.0im  2.84731e-17+0.13399im
         0.0+0.0im       0.0+0.0im         0.0+0.0im        0.0+0.0im          0.0+0.0im
   -0.202612+0.202612im  0.0+0.0im   0.0925394+0.0im        0.0+0.0im   -0.0305987-0.0305987im
         0.0+0.0im       0.0+0.0im         0.0+0.0im        0.0+0.0im          0.0+0.0im
 2.84731e-17-0.13399im   0.0+0.0im  -0.0305987+0.0305987im  0.0+0.0im    0.0202352+0.0im
```
"""
function squeeze(v::AbstractVector{T}, r, θ) where {T}
    dim = length(v)

    return Squeezing(complex(T), r, θ, dim=dim) * v
end

function squeeze(ρ::AbstractMatrix{T}, r, θ) where {T}
    dim = size(ρ, 1)
    s = Squeezing(complex(T), r, θ, dim=dim)

    return s * ρ * s'
end


module TestBasisPursuit
using Test
using LinearAlgebra
using SparseArrays
using CompressedSensing: bp, bp_candes, bp_ard, bpd, bpd_candes, bpd_ard, sparse_data, perturb

n, m = 32, 48
k = 3
A, x, b = sparse_data(n = n, m = m, k = k, rescaled = true)
δ = 1e-2
y = perturb(b, δ/2)

@testset "Basis Pursuit" begin
    # equality constrained l1 minimization
    xl = bp(A, b)
    @test xl.nzind == x.nzind

    xc = bp_candes(A, b)
    @test xc.nzind == x.nzind

    xard = bp_ard(A, b)
    @test xard.nzind == x.nzind
end

@testset "Basis Pursuit Denoising" begin
    xl = bpd(A, y, δ)
    droptol!(xl, 1e-2) # sometimes has spurious coefficients above perturbation level
    @test xl.nzind == x.nzind

    xc = bpd_candes(A, y, δ, maxiter = 3)
    droptol!(xc, 1e-6)
    @test xc.nzind == x.nzind

    xard = bpd_ard(A, y, δ, maxiter = 16)
    droptol!(xard, 1e-6)
    @test xard.nzind == x.nzind
end

using CompressedSensing: ista, fista
@testset "ISTA" begin
    λ = δ/10
    xista = ista(A, y, λ, maxiter = 1024, stepsize = 1e-1)
    # droptol!(xista, δ)
    # @test xista.nzind == x.nzind
    # @test xista.nzind ⊆ x.nzind
    @test norm(A*xista - y) < δ
    # TODO: FISTA
end

end


function ifeast!(A::AbstractMatrix, X₀::AbstractMatrix, nodes::Integer, iter::Integer;
                c=complex(0.0,0.0), r=1.0, debug=false, ϵ=0.05)
    N, m₀ = size(X₀)
    if size(A, 1) != size(A, 2)
        error("Incorrect dimensions of A, must be square")
    elseif size(A,1) != N
        error("Incorrect dimensions of X₀, must match A")
    end
    X = deepcopy(X₀)
    Λ = zeros(m₀)
    res = zeros(m₀)
    resvecs = zeros(ComplexF64, N, m₀)
    temp = complex.(X)
    Aq, Bq, Xq = zeros(ComplexF64, m₀, m₀), zeros(ComplexF64, m₀, m₀), zeros(ComplexF64, m₀, m₀)
    θ = LinRange(π/nodes, 2*π-π/nodes, nodes)
    Q = similar(X, ComplexF64)
    ZmA = similar(A, ComplexF64)
    for nit=1:iter
        Q .= 0.0
        for i=1:nodes
            ZmA .= 0.0
                for j=1:N
                    ZmA[j,j] = r*exp(θ[i]*im)+c
                end
            ZmA .-= A
            for m=1:m₀
                temp[:, m] .= bicgstabl(ZmA, X[:, m])
            end
            Q .+= temp .* exp(θ[i]*im)./nodes
        end
        # Q .= Matrix(qr(Q).Q)
        mul!(resvecs, A, Q) ## why does this one allocate?
        mul!(Aq, Q', resvecs) ### Aq = Q' * A * Q
        mul!(Bq, Q', Q) ### Bq = Q' * Q
        F = eigen!(Aq, Bq)
        Λ = F.values
        Xq = F.vectors
        # Λ, Xq = eigen!(Aq, Bq) ### eigen! allocates
        # for i=1:m₀
        #     Xq[:,i] = Xq[:,i] / vecnorm(Xq)
        # end
        mul!(X, Q, Xq) ### X = Q*Xq
        for i=1:m₀
            vn = norm(@view X[:,i]) ## allocates
            for j=1:N
                X[j,i] /= vn
            end
            # res[i] = vecnorm(A*X[:,i]-X[:,i]*Λ[i])/vecnorm(X[:,i]*Λ[i])
        end
        mul!(resvecs, A, X) ## allocates
        mul!(temp, X, Diagonal(Λ)) ## Diagonal allocates
        resvecs .-= temp ### resvecs = A*X-X*Diagonal(Λ)
        for i in 1:m₀
            res[i]=norm(@view resvecs[:,i])
        end
        if debug println(nit) end
    end
    Λ, X, res
end


module TestBootstrap
    using StreamStats
    using Distributions
    using Base.Test

    # CI's for mean of uniform draws
    for n in rand(1:10_000, 10)
        xs = rand(n)
        inner_stat = StreamStats.Mean()
        stat1 = StreamStats.BernoulliBootstrap(inner_stat)
        stat2 = StreamStats.PoissonBootstrap(inner_stat)
        for x in xs
            update!(stat1, x)
            update!(stat2, x)
        end

        # test using quantiles to generate bootstrap CIs
        lower1, upper1 = StreamStats.ci(stat1, 0.05)
        lower2, upper2 = StreamStats.ci(stat2, 0.05)
        m, sem = mean(xs), std(xs) / sqrt(n)
        @test m - 3 * sem <= lower1 <= m - 1 * sem
        @test m + 1 * sem <= upper1 <= m + 3 * sem
        @test m - 3 * sem <= lower2 <= m - 1 * sem
        @test m + 1 * sem <= upper2 <= m + 3 * sem

        # test normal approximation of the bootstrap CIs
        lower1, upper1 = StreamStats.ci(stat1, 0.05, :normal)
        lower2, upper2 = StreamStats.ci(stat2, 0.05, :normal)
        m, sem = mean(xs), std(xs) / sqrt(n)
        @test m - 3 * sem <= lower1 <= m - 1 * sem
        @test m + 1 * sem <= upper1 <= m + 3 * sem
        @test m - 3 * sem <= lower2 <= m - 1 * sem
        @test m + 1 * sem <= upper2 <= m + 3 * sem
    end

    # CI's for variance of uniform draws
    for n in rand(1:10_000, 10)
        xs = rand(n)
        inner_stat = StreamStats.Variance()
        stat1 = StreamStats.BernoulliBootstrap(inner_stat)
        stat2 = StreamStats.PoissonBootstrap(inner_stat)
        for x in xs
            update!(stat1, x)
            update!(stat2, x)
        end
        lower1, upper1 = StreamStats.ci(stat1, 0.05, :quantile)
        lower2, upper2 = StreamStats.ci(stat2, 0.05, :quantile)
        @test lower1 <= var(xs) <= upper1
        @test lower2 <= var(xs) <= upper2

        n_lower1, n_upper1 = StreamStats.ci(stat1, 0.05, :normal)
        n_lower2, n_upper2 = StreamStats.ci(stat2 , 0.05, :normal)
        @test n_lower1 <= var(xs) <= n_upper1
        @test n_lower2 <= var(xs) <= n_upper2
    end

    # test rand() method
    n = 1_000
    b1 = StreamStats.BernoulliBootstrap(StreamStats.Mean(), 10_000)
    b2 = StreamStats.BernoulliBootstrap(StreamStats.Mean(), 10_000)
    xs = rand(n)
    for i in xs
        update!(b1, i)
        update!(b2, i / 2.0)
    end
    xb = [rand(b1) for i in 1:1_000]

    # test -() method
    # TODO: rename Boostrap.cached_state() to replicates(), so that
    # below can simply be mean(replicates(b2-b1))
    # @test mean(b2 - b1) < 0

end


@safetestset gray_code_symmetry = "Gray code symmetry" begin
using BijectiveHilbert
# for i in 0:10
#     println(lpad(i, 3, " "), " ", lpad(string(BijectiveHilbert.brgc(i), base = 2), 8, "0"))
# end
# Here's a test of the gray code.
n = 5
for i in 0x0:(0x1 << n - 0x1)
    # println(BijectiveHilbert.brgc(1<<n - 1 - i), " ", BijectiveHilbert.brgc(i) ⊻ (1 << (n-1)))
    @test(BijectiveHilbert.brgc(1<<n - 1 - i) == BijectiveHilbert.brgc(i) ⊻ (1 << (n - 1)))
end
end

@safetestset gray_code_single = "Gray code changes one bit" begin
using BijectiveHilbert: brgc, is_power_of_two
for i in 0x1:0x1000
    @test is_power_of_two(brgc(i) ⊻ brgc(i + 1))
end
end

@safetestset brgc_own_inverse = "Gray code is own inverse" begin
using BijectiveHilbert
for i in 0x0:0x1000
    @test(BijectiveHilbert.brgc_inv(BijectiveHilbert.brgc(i)) == i)
end
end

@safetestset brgc_equals_naive = "Gray code matches paper description" begin
using BijectiveHilbert: brgc_inv
using Random
rng = MersenneTwister(9719742)
for T in [UInt8, UInt16, UInt32, UInt64]
    for trial in 1:10000
        v = rand(rng, T)
        n = brgc_inv(Int128(v))  # The paper version is used for integers.
        i = brgc_inv(v)
        @test n == i
    end
end
end

@safetestset brgc_ranks_equal = "the rank calculation is the same as the paper" begin
    using BijectiveHilbert: brgc_rank, brgc_rank2
    using Random
    rng = MersenneTwister(974073242)
    for trial in 1:1000
        n = rand(rng, 2:7)
        w = UInt8(rand(rng, 0:(1<<n - 1)))
        mask = UInt8(rand(rng, 0:(1<<n - 1)))
        a = brgc_rank(mask, w, n)
        b = brgc_rank2(mask, w, n)
        @test a == b
    end
end


module Tutorial1
using FEniCS

mesh = UnitSquareMesh(8,8)
V = FunctionSpace(mesh,"P",1)
u_D = Expression("1+x[0]*x[0]+2*x[1]*x[1]", degree=2)
u = TrialFunction(V)
bc1 = DirichletBC(V,u_D, "on_boundary")
v = TestFunction(V)
f = Constant(-6.0)
a = dot(grad(u),grad(v))*dx
L = f*v*dx
U=FeFunction(V)
lvsolve(a,L,U,bc1) #linear variational solver
#lvsolve(a,L,U,bc1,solver_parameters=Dict("linear_solver"=>"lu"),form_compiler_parameters=Dict("optimize"=>true))
error_L2= errornorm(u_D, U, norm="L2")

#get_array(L) #this returns an array for the stiffness matrix
#get_array(U) #this returns an array for the solution values
#vtkfile = File("poisson/solution.pvd")
#vtkfile << U.pyobject #exports the solution to a vtkfile
File("poisson/solutionnew.pvd",U)
vertex_values_u_D = compute_vertex_values(u_D,mesh)
vertex_values_u = compute_vertex_values(U,mesh)
error_max = maximum(abs.(vertex_values_u_D-vertex_values_u))
end#module


# http://stronglyconvex.com/blog/accelerated-gradient-descent.html
# TODO: Need to specify alphamax on each iteration
# Flip notation relative to Duckworth
# Start with x_{0}
# y_{t} = x_{t - 1} - alpha g(x_{t - 1})
# If converged, return y_{t}
# x_{t} = y_{t} + (t - 1.0) / (t + 2.0) * (y_{t} - y_{t - 1})

# L should be function or any other callable
immutable AcceleratedGradientDescent{L} <: Optimizer
    linesearch!::L
end

Base.summary(::AcceleratedGradientDescent) = "Accelerated Gradient Descent"

function AcceleratedGradientDescent(; linesearch = LineSearches.HagerZhang())
    AcceleratedGradientDescent(linesearch)
end

type AcceleratedGradientDescentState{T,N}
    x::Array{T,N}
    x_previous::Array{T,N}
    f_x_previous::T
    iteration::Int
    y::Array{T,N}
    y_previous::Array{T,N}
    s::Array{T,N}
    @add_linesearch_fields()
end

function initial_state{T}(method::AcceleratedGradientDescent, options, d, initial_x::Array{T})
    value_gradient!(d, initial_x)
    AcceleratedGradientDescentState(copy(initial_x), # Maintain current state in state.x
                         copy(initial_x), # Maintain previous state in state.x_previous
                         T(NaN), # Store previous f in state.f_x_previous
                         0, # Iteration
                         copy(initial_x), # Maintain intermediary current state in state.y
                         similar(initial_x), # Maintain intermediary state in state.y_previous
                         similar(initial_x), # Maintain current search direction in state.s
                         @initial_linesearch()...) # Maintain a cache for line search results in state.lsr
end

function update_state!{T}(d, state::AcceleratedGradientDescentState{T}, method::AcceleratedGradientDescent)
    n = length(state.x)
    state.iteration += 1
    # Search direction is always the negative gradient
    @simd for i in 1:n
        @inbounds state.s[i] = -gradient(d, i)
    end

    # Determine the distance of movement along the search line
    lssuccess = perform_linesearch!(state, method, d)

    # Record previous state
    copy!(state.x_previous, state.x)

    # Make one move in the direction of the gradient
    copy!(state.y_previous, state.y)
    @simd for i in 1:n
        @inbounds state.y[i] = state.x[i] + state.alpha * state.s[i]
    end

    # Update current position with Nesterov correction
    scaling = (state.iteration - 1) / (state.iteration + 2)
    @simd for i in 1:n
        @inbounds state.x[i] = state.y[i] + scaling * (state.y[i] - state.y_previous[i])
    end

    lssuccess == false # break on linesearch error
end


@testset "Brownian Bridge" begin

using DiffEqNoiseProcess, DiffEqBase, Test, Random, DiffEqBase.EnsembleAnalysis

Random.seed!(100)
W = BrownianBridge(0.0,1.0,0.0,1.0,0.0,0.0)
prob = NoiseProblem(W,(0.0,1.0))
ensemble_prob = EnsembleProblem(prob)
@time sol = solve(ensemble_prob,dt=0.1,trajectories=100000)

# Spot check the mean and the variance
qs = 0:0.1:1
for i in 2:10
  q = qs[i]
  @test ≈(timestep_mean(sol,i),q,atol=1e-2)
  @test ≈(timestep_meanvar(sol,i)[2],(1-q)*q,atol=1e-2)
end
@test ≈(timestep_mean(sol,1)[1],0.0,atol=1e-16)
@test ≈(timestep_meanvar(sol,1)[2],0.0,atol=1e-16)
@test ≈(timestep_mean(sol,11)[1],1.0,atol=1e-16)
@test ≈(timestep_meanvar(sol,11)[2],0.0,atol=1e-16)

μ = 1.2
σ = 2.2
W = GeometricBrownianBridge(μ,σ,0.0,1.0,0.0,1.0,0.0,0.0)
prob = NoiseProblem(W,(0.0,1.0))
ensemble_prob = EnsembleProblem(prob)
@time sol = solve(ensemble_prob,dt=0.1,trajectories=100)

Random.seed!(100)
r = 100 # should be independent of the rate, so make it crazy
rate(u,p,t) = r
W = CompoundPoissonBridge(rate,0.0,1.0,0.0,1.0)
prob = NoiseProblem(W,(0.0,1.0))
ensemble_prob = EnsembleProblem(prob)
@time sol = solve(ensemble_prob,dt=0.1,trajectories=100000)

# Spot check the mean and the variance
qs = 0:0.1:1
for i in 2:10
  q = qs[i]
  # Mean and variance of binomial matches that of the Brownian bridge!
  @test ≈(timestep_mean(sol,i),q,atol=1e-2)
  @test ≈(timestep_meanvar(sol,i)[2],(1-q)*q,atol=1e-2)
end
@test ≈(timestep_mean(sol,1)[1],0.0,atol=1e-16)
@test ≈(timestep_meanvar(sol,1)[2],0.0,atol=1e-16)
@test ≈(timestep_mean(sol,11)[1],1.0,atol=1e-16)
@test ≈(timestep_meanvar(sol,11)[2],0.0,atol=1e-16)

# check VBT distributional properties

W = VirtualBrownianTree(0.0,0.0; Wend=1.0,tree_depth=3)
prob = NoiseProblem(W,(0.0,1.0))
function prob_func(prob,i,repeat)
  # to re-instantiate PRNG
  Wtmp = VirtualBrownianTree(0.0,0.0; Wend=1.0,tree_depth=3)
  remake(prob, noise=Wtmp)
end
ensemble_prob = EnsembleProblem(prob, prob_func=prob_func)
@time sol = solve(ensemble_prob,dt=0.125,trajectories=100000)

# Spot check the mean and the variance
qs = 0:0.125:1
for i in 2:8
  q = qs[i]
  @test ≈(timestep_mean(sol,i),q,atol=1e-2)
  @test ≈(timestep_meanvar(sol,i)[2],(1-q)*q,atol=1e-2)
end
@test ≈(timestep_mean(sol,1)[1],0.0,atol=1e-16)
@test ≈(timestep_meanvar(sol,1)[2],0.0,atol=1e-16)
@test ≈(timestep_mean(sol,Int(2^(W.tree_depth)+1))[1],W.W[end],atol=1e-16)
@test ≈(timestep_meanvar(sol,Int(2^(W.tree_depth)+1))[2],0.0,atol=1e-16)

end


@testset "math" begin
    x = collect(1:100)
    y = collect(6:9)

    @test ATools.rxy(1,1) ≈ sqrt(2.)
    @test ATools.phixy(1,1) ≈ π/4

    @test ATools.dxyz([1,1,1], [2,2,2]) ≈ sqrt(3.)

    m, s = ATools.mean_std(x, 5, 10)
    @test m ≈ mean(y)
    @test s ≈ std(y)

    norm_weights = Int.(round.(1000 * exp.(-(x .- 50).^2 / 18)))
    m, s = ATools.mean_std(x, 1, 90, w=norm_weights)
    @test m ≈ 50.0
    @test isapprox(s, 3.0, atol=0.003)

    fxy = ATools.gline2p(0, 10., 1., 2.)
    @test fxy(0) == 10.0
    @test fxy(1) == 2.0
    @test fxy(0.5) == 6.0
end


function gencond( kappa, m, n )
  A = randn(m,n)
  (U,S,V) = svd(A)
  S = logspace(0, -log10(kappa), n)
  return U*diagm(S)*V'
end


# 1-D Calculations
"""
    mass_matrix(b::Basis)

    Calculate the mass-matrix for the basis `b` with Gauss quadrature.
"""
function mass_matrix(basis::BSplineBasis{dirichlet})
    x, w = gausslegendre(basis.k + 10)
    a = basis.interval.left
    b = basis.interval.right
    N = length(basis.B)
    Δx = (b-a)/basis.N

    M = zeros(N, N)
    # Do Quadrature over every element -> exact
    for el in 1:basis.N
        b = a + Δx
        xx = a .+ (b - a) .*(x .+ 1) ./ 2

        # Otherwise sometimes the last element is wrong
        if xx[end] > basis.interval.right
            xx[end] = basis.interval.right
        end

        for i in 1:N
            for j in 1:i
                M[i, j] = M[j, i] += 2/Δx * dot(basis[xx, i] .* basis[xx, j], w)
            end
        end
        a = b
    end

    return  M
end

# Todo more precisely, defined on the same domain
function mass_matrix(basis1::BSplineBasis{T}, basis2::BSplineBasis{T}) where {T}
    x, w = gausslegendre(basis1.k + 2)
    a1 = basis1.interval.left
    b1 = basis1.interval.right
    N1 = length(basis1.B)
    Δx1 = (b1-a1)/basis1.N

    a2 = basis2.interval.left
    b2 = basis2.interval.right
    N2 = length(basis2.B)
    Δx2 = (b2-a2)/basis2.N

    M = zeros(N1, N2)
    # Do Quadrature over every element -> exact
    for el1 in 1:basis1.N
        b1 = a1 + Δx1
        xx = a1 .+ (b1 - a1) .*(x .+ 1) ./ 2

        if xx[end] > basis1.interval.right
            xx[end] = basis1.interval.right
        end

        for i in 1:N1
            for j in 1:N2
                M[i, j] += 2/(b1 - a1) .* dot(basis1[xx, i] .* basis2[xx, j], w)
            end
        end
        a1 = b1
    end

    return  M
end

# For the stiffness_matrix of basis b, call stiffness_matrix(b')
# Or should we take b as an input?
# Care for the factor in the integral, the derived basis functions, i.e. b', have already 2/Δx as an factor!
"""
    stiffness_matrix(b'::Basis)

    Calculate the stiffnes-matrix for the basis `b` with Gauss quadrature. Caution: We directly input b' here as a basis.
"""
function stiffness_matrix(basis::BSplineBasis{dirichlet})
    x, w = gausslegendre(basis.k + 1)
    a = basis.interval.left
    b = basis.interval.right
    N = length(basis.B)
    Δx = (b-a)/basis.N

    M = zeros(N+1, N+1)
    # The derivative of the basis functions is the known formula for BSplines
    # B'_i(x) = D_i(x) - D_{i+1}(x), where D_i are the basisfunctions of b'
    for el in 1:basis.N
        b = a + Δx
        xx = a .+ (b - a) .*(x .+ 1) ./ 2

        if xx[end] > basis.interval.right
            xx[end] = basis.interval.right
        end

        M[1, 1] += 2/(b - a) .* dot((-basis[xx, 1]) .* (-basis[xx, 1] ), w)

        for j in 1:N-1
            M[1, j+1] = M[j+1, 1] += 2/(b - a) .* dot((-basis[xx, 1]) .* (basis[xx, j] - basis[xx, j+1] ), w)
        end

        for i in 1:N-1
            for j in 1:i

                M[i+1, j+1] = M[j+1, i+1] += 2/(b - a) .* dot((basis[xx, i] .- basis[xx, i+1]) .* (basis[xx, j] .- basis[xx, j+1]), w)
            end
        end

        for j in 1:N-1
            M[N+1, j+1] = M[j+1, N+1] += 2/(b - a) .* dot((basis[xx, N]) .* (basis[xx, j] - basis[xx, j+1] ), w)
        end

        M[N+1, N+1] += 2/(b - a) .* dot((basis[xx, N]) .* (basis[xx, N] ), w)

        a = b
    end

    return  M
end

# For periodic boundary conditions on the basis use the circulant methods
function mass_matrix(basis::BSplineBasis{periodic})
    x, w = gausslegendre(basis.k+1)
    a = basis.interval.left
    b = basis.interval.right
    N = length(basis.B)
    Δx = (b-a)/basis.N
    c = zeros(N)

    for el in 1:basis.N
        b = a + Δx
        xx = a .+ (b - a) .*(x .+ 1) ./ 2

        for i in 1:N
            c[i] += 2/(b - a) .* dot(basis[xx, i] .* basis[xx, 1], w)
        end
        a = b
    end

    return Circulant(c)
end

# Input derivative basis
function stiffness_matrix(basis::BSplineBasis{periodic})
    x, w = gausslegendre(basis.k+1)
    a = basis.interval.left
    b = basis.interval.right
    N = length(basis.B)
    Δx = (b-a)/basis.N
    c = zeros(N)

    for el in 1:basis.N
        b = a + Δx
        xx = a .+ (b - a) .*(x .+ 1) ./ 2

        for i in 1:N-1
            c[i] += 2/(b - a) .* dot((basis[xx, i] .- basis[xx, i+1]) .* (basis[xx, 1] .- basis[xx, 2]), w)
        end
        c[N] += 2/(b - a) .* dot((basis[xx, N] .- basis[xx, 1]) .* (basis[xx, 1] .- basis[xx, 2]), w)
        a = b
    end

    return Circulant(c)
end

# Since for now we only have a periodic basis, f has to be as well
function L2_prod(f::Function, basis::Basis, order::Int64=basis.k+1)
    # what degree do we want here?
    x, w = gausslegendre(order+1)
    a = basis.interval.left
    b = basis.interval.right
    N = length(basis.B)
    Δx = (b-a)/basis.N
    fh = zeros(N)

    for el in 1:basis.N
        b = a + Δx
        xx = a .+ (b - a) .*(x .+ 1) ./ 2

        if xx[end] > basis.interval.right
            xx[end] = basis.interval.right
        end

        for i in 1:N
            fh[i] += 2/(b - a) .* dot(basis[xx, i] .* f.(xx), w)
        end
        a = b
    end

    return fh
end

#maybe find a better name
function col_mat(basis::Basis, m::Int)
    n = length(basis.B)
    x = range(basis.interval.left+1e-14, basis.interval.right-1e-14, length = m)
    M = zeros(m, n)

    for i in 1:n
        M[:,i] = basis[x, i]
    end

    return x, M
end

#n-D Calculations
function mass_matrix(basis::TensorProductBasis{N}) where N
    matrices = []
    for i in 1:N
        push!(matrices, mass_matrix(basis[i]))
    end

    return ⊗(matrices...)
end

function stiffness_matrix(basis::TensorProductBasis{N}) where N
    matrices = []
    for i in 1:N
        push!(matrices,  stiffness_matrix(basis[i]))
    end

    return ⊗(matrices...)
end

#Todo: Generalize the following functions and make them more efficient
function L2_prod(f::Function, basis::BSplineTensorProductBasis{2, T}) where T
    # what degree do we want here?
    X,Y = basis.B

    x1, w1 = gausslegendre(X.k + 1)
    x2, w2 = gausslegendre(Y.k + 1)

    a1 = X.interval.left
    b1 = X.interval.right
    N1 = length(X.B)
    Δx1 = (b1-a1)/X.N

    a2 = Y.interval.left
    b2 = Y.interval.right
    N2 = length(Y.B)
    Δx2 = (b2-a2)/Y.N

    vectors = zeros(N1, N2)

    #loop over basis functions
    for i in 1:N1
        for j in 1:N2
            #loop over elements in x-direction
            for el_x in 1:X.N
                b1 = a1 + Δx1
                xx1 = a1 .+ (b1 - a1) .*(x1 .+ 1) ./ 2

                if xx1[end] > X.interval.right
                    xx1[end] = X.interval.right
                end

                val_y = zeros(length(xx1))
                # at every quadrature point in x-direction, do a full quadrature in y-direction
                for xn in 1:length(xx1)
                    val = 0

                    for el_y in 1:Y.N
                        b2 = a2 + Δx2
                        xx2 = a2 .+ (b2 - a2) .*(x2 .+ 1) ./ 2
                        if xx2[end] > Y.interval.right
                            xx2[end] = Y.interval.right
                        end

                        val += 2/(b2 - a2) .* dot(Y[xx2, j] .* f.(xx1[xn], xx2), w2)

                        a2 = b2
                    end

                    val_y[xn] = val
                    a2 = Y.interval.left
                end

                vectors[i, j] += 2/(b1 - a1) .* dot(X[xx1, i] .* val_y, w1)

                a1 = b1
            end

            a1 = X.interval.left
        end

    end

    return reshape(permutedims(vectors, [2 ,1]), N1*N2)
end

function L2_prod(f::Function, basis::BSplineTensorProductBasis{3, T}) where T
    X, Y, Z = basis.B
    N = (length(X.B), length(Y.B), length(Z.B))

    x1, w1 = gausslegendre(X.k + 1)
    x2, w2 = gausslegendre(Y.k + 1)
    x3, w3 = gausslegendre(Z.k + 1)

    a1 = X.interval.left
    b1 = X.interval.right
    N1 = length(X.B)
    Δx1 = (b1-a1)/X.N

    a2 = Y.interval.left
    b2 = Y.interval.right
    N2 = length(Y.B)
    Δx2 = (b2-a2)/Y.N

    a3 = Z.interval.left
    b3 = Z.interval.right
    N3 = length(Z.B)
    Δx3 = (b3-a3)/Z.N

    vectors = zeros(Float64, N1, N2, N3)

    xx1 = zeros(Float64, length(x1))
    xx2 = zeros(Float64, length(x2))
    xx3 = zeros(Float64, length(x3))
    #loop over basis functions
    for i in 1:N1
        for j in 1:N2
            for l in 1:N3
                #loop over elements in x-direction
                for el_x in 1:X.N
                    b1 = a1 + Δx1
                    xx1 = a1 .+ (b1 - a1)/2 * (x1 .+ 1)

                    if xx1[end] > X.interval.right
                        xx1[end] = X.interval.right
                    end

                    val_y = zeros(Float64, length(xx1))
                    # at every quadrature point in x-direction, do a full quadrature in y-direction
                    for xn in 1:length(xx1)
                        val2 = 0.0

                        for el_y in 1:Y.N
                            b2 = a2 + Δx2
                            xx2 = a2 .+ (b2 - a2)/2 * (x2 .+ 1)
                            if xx2[end] > Y.interval.right
                                xx2[end] = Y.interval.right
                            end
                                val_z = zeros(Float64, length(xx2))
                                # at every quadrature point in x-direction, do a full quadrature in y-direction
                                for yn in 1:length(xx2)
                                    val3 = 0.0

                                    for el_z in 1:Z.N
                                        b3 = a3 + Δx3
                                        xx3 = a3 .+ (b3 - a3)/2 * (x3 .+ 1)
                                        if xx3[end] > Z.interval.right
                                            xx3[end] = Z.interval.right
                                        end

                                        val3 += 2/(b3 - a3) * dot(Z[xx3, l] .* f.(xx1[xn], xx2[yn], xx3), w3)

                                        a3 = b3
                                    end

                                    val_z[yn] = val3
                                    a3 = Z.interval.left
                                end

                            val2 += 2/(b2 - a2) * dot(Y[xx2, j] .* val_z, w2)

                            a2 = b2
                        end

                        val_y[xn] = val2
                        a2 = Y.interval.left
                    end
                    vectors[i, j, l] += 2/(b1 - a1) * dot(X[xx1, i] .* val_y, w1)

                    a1 = b1
                end

                a1 = X.interval.left
            end
        end

    end

    return reshape(permutedims(vectors, [3, 2 ,1]), N1*N2*N3)
end

#x = (x, y)  1-d interval discretizations
function col_mat(basis::TensorProductBasis{2}, mm::Int)
    X,Y = basis.B
    N = (length(X.B), length(Y.B))

    x = range(X.interval.left+1e-14, X.interval.right-1e-14, length = mm)
    y = range(Y.interval.left+1e-14, Y.interval.right-1e-14, length = mm)
    xy = [(a,b) for a in x for b in y]

    M1 = zeros(mm, N[1])
    M2 = zeros(mm, N[2])

    for i in 1:N[1]
        M1[:, i] = X[x, i]
    end

    for i in 1:N[2]
        M2[:, i] = Y[y, i]
    end

    return xy, M1 ⊗ M2
end

function col_mat(basis::BSplineTensorProductBasis{3, T}, mm::Int) where T
    X, Y, Z = basis.B
    N = (length(X.B), length(Y.B), length(Z.B))

    x = range(X.interval.left+1e-14, X.interval.right-1e-14, length = mm)
    y = range(Y.interval.left+1e-14, Y.interval.right-1e-14, length = mm)
    z = range(Z.interval.left+1e-14, Z.interval.right-1e-14, length = mm)
    xyz = [(a, b, c) for a in x for b in y for c in z]

    M1 = zeros(mm, N[1])
    M2 = zeros(mm, N[2])
    M3 = zeros(mm, N[3])

    for i in 1:N[1]
        M1[:, i] = X[x, i]
    end

    for i in 1:N[2]
        M2[:, i] = Y[y, i]
    end

    for i in 1:N[3]
        M3[:, i] = Z[z, i]
    end

    return xyz, M1 ⊗ M2 ⊗ M3
end

function L2_prod(f::Function, basis::SArray{Tuple{3}, BSplineTensorProductBasis{3, T}}) where {T}
    fh = []

    for i in 1:3
        push!(fh, L2_prod((x,y,z) -> f(x,y,z)[i], basis[i]))
    end

    return SVector(fh...)
end

function L2_prod(f::Function, basis::SArray{Tuple{2}, BSplineTensorProductBasis{2, T}}) where {T}
    fh = []

    for i in 1:2
        push!(fh, L2_prod((x,y) -> f(x,y)[i], basis[i]))
    end

    return SVector(fh...)
end

function L2_proj(f::Function, basis::SArray{Tuple{3}, BSplineTensorProductBasis{3, T}}) where {T}
    fh = []
    M = mass_matrix.(basis)

    for i in 1:3
        push!(fh, M[i]\L2_prod((x,y,z) -> f(x,y,z)[i], basis[i]))
    end

    return SVector(fh...)
end

function L2_proj(f::Function, basis::BSplineTensorProductBasis{3, T}) where {T}
    M = mass_matrix(basis)

    fh = M\L2_prod(f, basis)
    return SVector{length(fh)}(fh)
end

function L2_proj(f::Function, basis::SArray{Tuple{2}, BSplineTensorProductBasis{2, T}}) where {T}
    fh = []
    M = mass_matrix.(basis)

    for i in 1:2
        push!(fh, M[i]\L2_prod((x,y) -> f(x,y)[i], basis[i]))
    end

    return SVector(fh...)
end

function L2_proj(f::Function, basis::BSplineTensorProductBasis{2, T}) where {T}
    M = mass_matrix(basis)
    fh = M\L2_prod(f, basis)
    return SVector{length(fh)}(fh)
end

function L2_proj(f::Function, basis::BSplineBasis{T}) where {T}
    M = mass_matrix(basis)
    fh = M\L2_prod(f, basis)
    return SVector{length(fh)}(fh)
end

##weighted mass matrices
function weighted_mass_matrix(basis::Basis, ω::Function)
    x, w = gausslegendre(basis.k + 1)
    a = basis.interval.left
    b = basis.interval.right
    N = length(basis.B)
    Δx = (b-a)/basis.N

    M = zeros(N, N)

    for el in 1:basis.N
        b = a + Δx
        xx = a .+ (b - a) .*(x .+ 1) ./ 2
        if xx[end] > basis.interval.right
            xx[end] = basis.interval.right
        end

        for i in 1:N
            for j in 1:N
                M[i, j] += 2/(b - a) .* dot(basis[xx, i] .* basis[xx, j] .* ω.(xx), w)
            end
        end
        a = b
    end

    return  M
end

#ω(x,y)
function weighted_mass_matrix(basis::BSplineTensorProductBasis{2, T}, ω::Function) where {T}
    X,Y = basis.B

    #maybe we need higher order
    x1, w1 = gausslegendre(X.k + 1)
    x2, w2 = gausslegendre(Y.k + 1)

    a1 = X.interval.left
    b1 = X.interval.right
    N1 = length(X.B)
    Δx1 = (b1-a1)/X.N

    a2 = Y.interval.left
    b2 = Y.interval.right
    N2 = length(Y.B)
    Δx2 = (b2-a2)/Y.N

    M = zeros(N1*N2, N1*N2)

    vectors = zeros(N1, N2)
    for k in 1:N1
        for l in 1:N2

            vectors = zeros(N1, N2)
            for i in 1:N1
                for j in 1:N2
                    #loop over elements in x-direction
                    for el_x in 1:X.N
                        b1 = a1 + Δx1
                        xx1 = a1 .+ (b1 - a1) .*(x1 .+ 1) ./ 2
                        if xx1[end] > X.interval.right
                            xx1[end] = X.interval.right
                        end

                        val_y = zeros(length(xx1))
                        # at every quadrature point in x-direction, do a full quadrature in y-direction
                        for xn in 1:length(xx1)
                            val = 0

                            for el_y in 1:Y.N
                                b2 = a2 + Δx2
                                xx2 = a2 .+ (b2 - a2) .*(x2 .+ 1) ./ 2
                                if xx2[end] > Y.interval.right
                                    xx2[end] = Y.interval.right
                                end
                                val += 2/(b2 - a2) .* dot(Y[xx2, j] .* Y[xx2, l] .* ω.(xx1[xn], xx2), w2)

                                a2 = b2
                            end

                            val_y[xn] = val
                            a2 = Y.interval.left
                        end
                        vectors[i, j] += 2/(b1 - a1) .* dot(X[xx1, i] .* X[xx1, k] .* val_y, w1)

                        a1 = b1
                    end
                    a1 = X.interval.left
                end

            end
            M[:, N2*(k-1) + l ] = reshape(permutedims(vectors, [2 ,1]), N1*N2)

        end
    end

    return  M
end

#special structure because of V1
#w(x, y)
function interior_product_matrix(basis::SArray{Tuple{2}, BSplineTensorProductBasis{2, T}}, ω::Function) where T
    M = weighted_mass_matrix(basis[1], basis[2], ω)

    return SArray(M, M')
end

#ω(x,y)
#V1 = SVector(X' ⊗ Y, X ⊗ Y')
function weighted_mass_matrix(basis1::BSplineTensorProductBasis{2, periodic}, basis2::BSplineTensorProductBasis{2, periodic}, ω::Function)
    X, Y = basis1.B
    X2, Y2 = basis2.B

    x1, w1 = gausslegendre(X.k + 1)
    x2, w2 = gausslegendre(Y.k + 1)

    # Assume both component bases are defined on the same domain
    a1 = X.interval.left
    b1 = X.interval.right
    N1 = length(X.B)
    Δx1 = (b1-a1)/X.N

    a2 = Y.interval.left
    b2 = Y.interval.right
    N2 = length(Y.B)
    Δx2 = (b2-a2)/Y.N

    M = zeros(N1*N2, N1*N2)

    vectors = zeros(N1, N2)
    for k in 1:length(X2.B) # build the full matrix
        for l in 1:length(Y2.B)

            vectors = zeros(N1, N2)
            for i in 1:N1
                for j in 1:N2
                    #loop over elements in x-direction
                    for el_x in 1:X.N
                        b1 = a1 + Δx1
                        xx1 = a1 .+ (b1 - a1) .*(x1 .+ 1) ./ 2
                        if xx1[end] > X.interval.right
                            xx1[end] = X.interval.right
                        end
                        val_y = zeros(length(xx1))
                        # at every quadrature point in x-direction, do a full quadrature in y-direction
                        for xn in 1:length(xx1)
                            val = 0

                            for el_y in 1:Y.N
                                b2 = a2 + Δx2
                                xx2 = a2 .+ (b2 - a2) .*(x2 .+ 1) ./ 2
                                if xx2[end] > Y.interval.right
                                    xx2[end] = Y.interval.right
                                end
                                val += 2/(b2 - a2) .* dot(Y[xx2, j] .* Y2[xx2, l] .* ω.(xx1[xn], xx2), w2)

                                a2 = b2
                            end

                            val_y[xn] = val
                            a2 = Y.interval.left
                        end
                        vectors[i, j] += 2/(b1 - a1) .* dot(X[xx1, i] .* X2[xx1, k] .* val_y, w1)

                        a1 = b1
                    end
                    a1 = X.interval.left
                end

            end
            M[:, N2*(k-1) + l ] = reshape(permutedims(vectors, [2 ,1]), N1*N2)

        end
    end

    return  M
end

function interior_product_matrix(basis::SArray{Tuple{3}, BSplineTensorProductBasis{3, T}}, ω::SArray{Tuple{3}, Function}) where T

    M1 = weighted_mass_matrix(V1[1], V1[2], ω[3])
    M2 = weighted_mass_matrix(V1[1], V1[3], ω[2])
    M3 = weighted_mass_matrix(V1[2], V1[3], ω[1])

    #M = [0 -M1 M2;
    #     M1 0 -M3;
    #     -M2 M3 0]

    #we could take it as a curl matrix
    return SVector(M1, M2, M3)
end

function L2_penalty(uh::T, basis::BSplineBasis, ϵ) where T

    M = mass_matrix(basis)
    M0 = copy(M)

    M0[1, 1] *= (1 + 1/ϵ)
    M0[end, end] *= (1 + 1/ϵ)

    return M0\(M*uh)

end

#returns invertible matrix, to get coefficients from point values
function col_mat_proj(basis::BSplineTensorProductBasis{2, periodic})
    X,Y = basis.B
    N = (length(X.B), length(Y.B))
    k = (X.k, Y.k)

    #periodic
    x = X.t[k[1]+2:N[1]+k[1]+1]
    y = Y.t[k[2]+2:N[2]+k[2]+1]

    xy = [(a,b) for a in x for b in y]

    M1 = zeros(N[1], N[1])
    M2 = zeros(N[2], N[2])

    for i in 1:N[1]
        M1[:, i] = X[x, i]
    end

    for i in 1:N[2]
        M2[:, i] = Y[y, i]
    end

    return xy, M1 ⊗ M2
end

#returns invertible matrix, to get coefficients from point values
function col_mat_proj(basis::BSplineTensorProductBasis{2, dirichlet})
    X,Y = basis.B
    N = (length(X.B), length(Y.B))
    k = (X.k, Y.k)

    x = range(X.interval.left+1e-14, X.interval.right-1e-14, length= N[1])
    y = range(Y.interval.left+1e-14, Y.interval.right-1e-14, length= N[2])
    xy = [(a,b) for a in x for b in y]

    M1 = zeros(N[1], N[1])
    M2 = zeros(N[2], N[2])

    for i in 1:N[1]
        M1[:, i] = X[x, i]
    end

    for i in 1:N[2]
        M2[:, i] = Y[y, i]
    end

    return xy, M1 ⊗ M2
end

#returns invertible matrix, to get coefficients from point values
function col_mat_proj(basis::BSplineBasis{dirichlet})
    N = length(basis.B)
    #k = basis.k

    x = range(basis.interval.left+1e-14, basis.interval.right-1e-14, length= N)

    M1 = zeros(N, N)

    for i in 1:N
        M1[:, i] = basis[x, i]
    end

    return x, M1
end


@testset "Quadrature testing" begin
    ref_tet_vol(dim) = 1 / factorial(dim)
    ref_square_vol(dim) = 2^dim

    function integrate(qr::QuadratureRule, f::Function)
        I = 0.0
        for (w, x) in zip(getweights(qr), getpoints(qr))
            I += w * f(x)
        end
        return I
    end

    # Cube
    for dim = (1,2,3)
        for order in (1,2,3,4)
            f = (x, p) -> sum([x[i]^p for i in 1:length(x)])
            # Legendre
            qr = QuadratureRule{dim, RefCube}(:legendre, order)
            @test integrate(qr, (x) -> f(x, 2*order-1)) < 1e-14
            @test sum(qr.weights) ≈ ref_square_vol(dim)
            @test sum(getweights(qr)) ≈ ref_square_vol(dim)
            # Lobatto
            if order > 1
                qr = QuadratureRule{dim, RefCube}(:lobatto, order)
                @test integrate(qr, (x) -> f(x, 2*order-1)) < 1e-14
                @test sum(qr.weights) ≈ ref_square_vol(dim)
                @test sum(getweights(qr)) ≈ ref_square_vol(dim)
            end
        end
    end
    @test_throws ArgumentError QuadratureRule{1, RefCube}(:einstein, 2)

    # Tetrahedron
    g = (x) -> sqrt(sum(x))
    dim = 2
    for order in (1,2,3,4,5,6,7,8)
        qr = QuadratureRule{dim, RefTetrahedron}(:legendre, order)
        # http://www.wolframalpha.com/input/?i=integrate+sqrt(x%2By)+from+x+%3D+0+to+1,+y+%3D+0+to+1-x
        @test integrate(qr, g) - 0.4 < 0.01
        @test sum(qr.weights) ≈ ref_tet_vol(dim)
    end
    @test_throws ArgumentError QuadratureRule{dim, RefTetrahedron}(:einstein, 2)
    @test_throws ArgumentError QuadratureRule{dim, RefTetrahedron}(0)

    dim = 3
    for order in (1, 2, 3)
        qr = QuadratureRule{dim, RefTetrahedron}(:legendre, order)
        # Table 1:
        # http://www.m-hikari.com/ijma/ijma-2011/ijma-1-4-2011/venkateshIJMA1-4-2011.pdf
        @test integrate(qr, g) - 0.14 < 0.01
        @test sum(qr.weights) ≈ ref_tet_vol(dim)
    end
    @test_throws ArgumentError QuadratureRule{dim, RefTetrahedron}(:einstein, 2)
    @test_throws ArgumentError QuadratureRule{dim, RefTetrahedron}(0)

end