# [test/interpd_tests.jl]
using Test

@testset "Interpd Tests" begin

    using JLD2
    import ODEHybrid: interp1, interpd

    function generate_random_times(matrix_size = 1)
        """
            Convenience function used to generate ind and dep variables,
                as well as interpolation points
        """
        start = round(10.0 * rand())
        step  = round(2.0 * rand(), digits = 1)
        if abs(step) < 0.1
            step = 0.1
        end
        fin   = start + round(15.0 * rand())

        x = Array(start:step:fin) # Convert to an array for MATLAB
        y = zeros(length(x), matrix_size)
        for i = 1:matrix_size
            dx = 5.0 * rand()
            y_int = 10.0 * rand()
            y[:, i] .= dx .* x .+ y_int
        end

        if matrix_size == 1
            y = y[:, 1]
        end

        xq = Array(start:step:fin)
        xq = round.(Array(xq .+ randn(length(xq))), digits = 2)
        xq[ xq .< x[1] ] .= x[1]
        xq[ xq .> x[end]] .= x[end]

        return x, y, xq
    end;

    # Run a bunch of random interpolations to see if they match
    @testset "Interp1" begin # verbose = true
        @testset "Vector dependent variable" begin
            # data = []
            # for i = 1:50
            #     push!(data, generate_random_times())
            # end

            # r_mats = []
            # for i = 1:50
            #     x, y, xq = data[i]
            #     if length(x) < 3
            #         push!(r_mats, [])
            #         continue
            #     end
            #     r = mat"interp1($x, $y, $xq)";
            #     push!(r_mats, r)
            # end

            @load "solutions/interp1_vec_dep.jld2" data  r_mats

            for i = 1:50
                x, y, xq = data[i]
                if length(x) < 3
                    continue
                end

                # r_og = mat"interp1($x, $y, $xq)"
                r_jl = interp1((x,), y, xq)

                @test r_jl == r_mats[i]
            end
        end

        # Test for a non-column vector dependent variable
        @testset "Matrix dependent variable" begin
            # data = []
            # for i = 1:50
            #     N = Int(round(5.0 * rand())) + 1;
            #     if N ≤ 1
            #         N = 2
            #     end
            #     push!(data, generate_random_times(N))
            # end

            # r_mats = []
            # for i = 1:50
            #     x, y, xq = data[i]
            #     if length(x) < 3
            #         push!(r_mats, [])
            #         continue
            #     end
            #     r = mat"interp1($x, $y, $xq)";
            #     push!(r_mats, r)
            # end

            @load "solutions/interp1_mat_dep.jld2" data r_mats

            for i = 1:50
                x, y, xq = data[i]
                if length(x) < 3
                    continue
                end

                r_jl = interp1((x,), y, xq)

                @test r_jl == r_mats[i]
            end
        end
    end;

    # Run the full Interpd in a variety of cases to verify the julia and matlab implementations match
    @testset "Interpd" begin

        # Test when there are doubled values
        @testset "Doubled values" begin
            x = [1.0, 2, 3, 3, 3, 4, 5, 5, 6, 6, 7, 8, 10]
            y = 5.0 .* x .- 16
            xi = [1.5, 2.75, 5.5, 8.9]
            # og_ans = mat"interpd($x, $y, $xi)"
            @load "solutions/interpd_doubled_values_1.jld2" og_ans;
            jl_ans = interpd(x, y, xi)
            @test og_ans == jl_ans

            x = [5.0, 5.0, 6.0, 10.0, 10.0, 15.0, 15.0, 15.0, 15.0]
            y = 2.0 .* x .+ 1.0
            xi = [5.5, 6.5, 14.14, 14.14, 14.5 ]
            # og_ans = mat"interpd($x, $y, $xi)"
            @load "solutions/interpd_doubled_values_2.jld2" og_ans
            jl_ans = interpd(x, y, xi)
            @test og_ans == jl_ans
        end

        # Test with an unequal step size
        @testset "Unequal step size" begin
            # data = []
            # for i = 1:25
            #     x = sort(10.0 * round.(rand(10), digits = 2));
            #     dist = x[end] - x[1];
            #     y = 5.0 * rand() .* x .+ 5.0 * rand();
            #     xq = Array(range(x[1], x[end], step = dist/10));

            #     push!(data, [x, y, xq])
            # end

            # r_mats = []
            # for i = 1:25
            #     x, y, xq = data[i]
            #     rp = mat"""interpd($x, $y, $xq, "+")""";
            #     rn = mat"""interpd($x, $y, $xq, "-")""";
            #     push!(r_mats, (rp, rn))
            # end

            @load "solutions/interpd_unequal_step.jld2" data r_mats

            for i = 1:25
                x, y, xq = data[i]
                rp_mat, rn_mat = r_mats[i]

                mode = "-";
                jl_ans = interpd(x, y, xq, mode);
                @test jl_ans == rn_mat

                mode = "+"
                jl_ans = interpd(x, y, xq, mode)
                @test rp_mat == jl_ans
            end
        end

        # Test with an [n × m] dependent variable (doubled, mode, step_size)
        @testset "Doubled with matrix dependent" begin
            x = [5.0, 5.0, 6.0, 10.0, 10.0, 15.0, 15.0, 15.0, 15.0]
            y1 = 2.0 .* x .+ 1.0
            y2 = 5.5 .* x .- 7.2
            y3 = -14.2 .* x .+ 17.8
            y = [y1 y2 y3]
            xq = [5.5, 6.5, 14.14, 14.14, 14.5 ]

            mode = "+";
            # og_ans = mat"interpd($x, $y, $xq, $mode)";
            @load "solutions/interpd_doubled_with_matrix_1.jld2" og_ans
            jl_ans = interpd(x, y, xq, mode);
            @test og_ans == jl_ans

            mode = "-";
            # og_ans = mat"interpd($x, $y, $xq, $mode)";
            @load "solutions/interpd_doubled_with_matrix_2.jld2" og_ans
            jl_ans = interpd(x, y, xq, mode);
            @test og_ans == jl_ans
        end

    end;

    @testset "Interpolating Instantaneous Jump" begin

        # mat"""
        #     t = [2.76, 2.91, 3,   3,   3.12].';
        #     x = [0.2,  0.3,  0.4, 1.1, 1.2].';

        #     ti = (2.8:0.1:3.1).';
        #     $xi_p_mat = interpd(t, x, ti, '+');
        #     $xi_m_mat = interpd(t, x, ti, '-');
        # """

        t = [2.76; 2.91; 3; 3; 3.12];
        x = [0.2;  0.3;  0.4; 1.1; 1.2];
        ti = (2.8:0.1:3.1);
        xi_p_jul = interpd(t, x, ti, "+");
        xi_m_jul = interpd(t, x, ti, "-");

        @load "solutions/interpd_instant_jump.jld2" xi_p_mat xi_m_mat

        @test xi_p_mat ≈ xi_p_jul
        @test xi_m_mat ≈ xi_m_jul
    end

end;


# ---
# title: 213. House Robber II
# id: problem213
# author: Tian Jun
# date: 2020-10-31
# difficulty: Medium
# categories: Dynamic Programming
# link: <https://leetcode.com/problems/house-robber-ii/description/>
# hidden: true
# ---
#
# You are a professional robber planning to rob houses along a street. Each
# house has a certain amount of money stashed. All houses at this place are
# **arranged in a circle.** That means the first house is the neighbor of the
# last one. Meanwhile, adjacent houses have a security system connected, and
# **it will automatically contact the police if two adjacent houses were broken
# into on the same night**.
#
# Given a list of non-negative integers `nums` representing the amount of money
# of each house, return _the maximum amount of money you can rob tonight
# **without alerting the police**_.
#
#
#
# **Example 1:**
#
#
#
#     Input: nums = [2,3,2]
#     Output: 3
#     Explanation: You cannot rob house 1 (money = 2) and then rob house 3 (money = 2), because they are adjacent houses.
#
#
# **Example 2:**
#
#
#
#     Input: nums = [1,2,3,1]
#     Output: 4
#     Explanation: Rob house 1 (money = 1) and then rob house 3 (money = 3).
#     Total amount you can rob = 1 + 3 = 4.
#
#
# **Example 3:**
#
#
#
#     Input: nums = [0]
#     Output: 0
#
#
#
#
# **Constraints:**
#
#   * `1 <= nums.length <= 100`
#   * `0 <= nums[i] <= 1000`
#
#
## @lc code=start
using LeetCode

## add your code here:
## @lc code=end


using MAT
using LinearAlgebra
using BlockArrays

file = matopen("./examples/MultiscaleLowRank/MultiscaleLowRankRandom.mat")
X_decom = read(file, "X_decom")
close(file)

x1 = X_decom[:,:,1]
x2 = X_decom[:,:,2]
x3 = X_decom[:,:,3]
x4 = X_decom[:,:,4]
m, n = size(x1)

# first component
n = 64
kr1 = count(x->x!=0, x1)
x1 = vec(x1)

# second component
bn2 = 4; l = floor(Int, n/bn2)
Xb2 = BlockArray(x2, repeat([bn2], l), repeat([bn2], l))
idx2 = CartesianIndices((l, l))
kr2 = map(i->rank(Xb2[Block(i[1], i[2])]), idx2)
kr2 = vec(kr2)
x2 = vec(x2)

# third component
bn3 = 16; l = floor(Int, n/bn3)
Xb3 = BlockArray(x3, repeat([bn3], l), repeat([bn3], l))
idx3 = CartesianIndices((l, l))
kr3 = map(i->rank(Xb3[Block(i[1], i[2])]), idx3)
kr3 = vec(kr3)
x3 = vec(x3)

# fourth component
kr4 = rank(x4)
x4 = vec(x4)

# observation
b = x1 + x2 + x3 + x4

# write to file
f = open("/home/zhenan/Github/AtomicOpt.jl/examples/MultiscaleLowRank/MultiscaleData.txt", "w")
l = length(b)
@printf(f, "x1 = ")
for i in 1:l
    @printf(f, "%.3f ", x1[i])
end
println(f, "\n")
@printf(f, "x2 = ")
for i in 1:l
    @printf(f, "%.3f ", x2[i])
end
println(f, "\n")
@printf(f, "x3 = ")
for i in 1:l
    @printf(f, "%.3f ", x3[i])
end
println(f, "\n")
@printf(f, "x4 = ")
for i in 1:l
    @printf(f, "%.3f ", x4[i])
end
println(f, "\n")
@printf(f, "b = ")
for i in 1:l
    @printf(f, "%.3f ", b[i])
end
println(f, "\n")
@printf(f, "kr1 = %d\n", kr1)
@printf(f, "kr2 = ")
for t in kr2
    @printf(f, "%d ", t)
end
println(f, "\n")
@printf(f, "kr3 = ")
for t in kr3
    @printf(f, "%d ", t)
end
println(f, "\n")
@printf(f, "kr4 = %d\n", kr4)
@printf(f, "m = %d\n", m)
@printf(f, "n = %d\n", n)
close(f)


export HalfSpace

struct HalfSpace{K} <: AbstractSimpleShape{K}
    x₀::SFloat{K}  # point on boundary
    n̂::SFloat{K}  # outward direction normal
    HalfSpace{K}(x₀,n̂) where {K} = new(x₀,n̂)  # suppress default outer constructor
end

HalfSpace(x₀::AbsVecReal, n::AbsVecReal) = HalfSpace{length(x₀)}(x₀, normalize(n))

Base.:(==)(s1::HalfSpace, s2::HalfSpace) = s1.x₀==s2.x₀ && s1.n̂==s2.n̂
Base.isapprox(s1::HalfSpace, s2::HalfSpace) = s1.x₀≈s2.x₀ && s1.n̂≈s2.n̂
Base.hash(s::HalfSpace, h::UInt) = hash(s.x₀, hash(s.n̂, hash(:HalfSpace, h)))

level(x::SReal{K}, s::HalfSpace{K}) where {K} = s.n̂ ⋅ (x-s.x₀)

# The following functions cannot be implemented for HalfSpace.
# center()
# bounds()


# Copyright (c) 2017: Miles Lubin and contributors
# Copyright (c) 2017: Google Inc.
#
# Use of this source code is governed by an MIT-style license that can be found
# in the LICENSE.md file or at https://opensource.org/licenses/MIT.

"""
    abstract type FlipSignBridge{T,S1,S2} <: SetMapBridge{T,S2,S1} end

An abstract type that simplifies the creation of other bridges.
"""
abstract type FlipSignBridge{T,S1<:MOI.AbstractSet,S2<:MOI.AbstractSet} <:
              SetMapBridge{T,S2,S1} end

function MOI.Bridges.map_function(::Type{<:FlipSignBridge{T}}, func) where {T}
    return MOI.Utilities.operate(-, T, func)
end

# The map is an involution
function MOI.Bridges.inverse_map_function(BT::Type{<:FlipSignBridge}, func)
    return MOI.Bridges.map_function(BT, func)
end

# The map is symmetric
function MOI.Bridges.adjoint_map_function(BT::Type{<:FlipSignBridge}, func)
    return MOI.Bridges.map_function(BT, func)
end

# The map is a symmetric involution
function MOI.Bridges.inverse_adjoint_map_function(
    BT::Type{<:FlipSignBridge},
    func,
)
    return MOI.Bridges.map_function(BT, func)
end

function MOI.delete(
    model::MOI.ModelLike,
    bridge::FlipSignBridge,
    i::MOI.Bridges.IndexInVector,
)
    MOI.delete(model, bridge.variables[i.value])
    deleteat!(bridge.variables, i.value)
    return
end

"""
    NonposToNonnegBridge{T} <: Bridges.Variable.AbstractBridge

`NonposToNonnegBridge` implements the following reformulation:

* ``x \\in \\mathbb{R}_-`` into ``y \\in \\mathbb{R}_+`` with the substitution
  rule ``x = -y``,

where `T` is the coefficient type of `-y`.

## Source node

`NonposToNonnegBridge` supports:

* [`MOI.VectorOfVariables`](@ref) in [`MOI.Nonpositives`](@ref)

## Target nodes

`NonposToNonnegBridge` creates:

* One variable node: [`MOI.VectorOfVariables`](@ref) in [`MOI.Nonnegatives`](@ref),
"""
struct NonposToNonnegBridge{T} <:
       FlipSignBridge{T,MOI.Nonpositives,MOI.Nonnegatives}
    variables::Vector{MOI.VariableIndex}
    constraint::MOI.ConstraintIndex{MOI.VectorOfVariables,MOI.Nonnegatives}
end

const NonposToNonneg{T,OT<:MOI.ModelLike} =
    SingleBridgeOptimizer{NonposToNonnegBridge{T},OT}

function MOI.Bridges.map_set(
    ::Type{<:NonposToNonnegBridge},
    set::MOI.Nonnegatives,
)
    return MOI.Nonpositives(set.dimension)
end

function MOI.Bridges.inverse_map_set(
    ::Type{<:NonposToNonnegBridge},
    set::MOI.Nonpositives,
)
    return MOI.Nonnegatives(set.dimension)
end


"""
    AbstractGate

Abtract unitary quantum gate. `N` is the number of "wires" the gate acts on.
"""
abstract type AbstractGate end

"""
    Base.adjoint(::AbstractGate)

returns adjoint of AbstractGate objects

# Examples
```jldoctest
julia> X ≈ X
true

julia> Y ≈ Z
false

julia> RxGate(0.3) ≈ RxGate(0.3)
true

julia> RxGate(0.3) ≈ RxGate(0.2)
false
```
"""
Base.adjoint(::AbstractGate) = error("Not implemented")

"""
    num_wires(::AbstractGate)

returns number of wires affected by AbstractGate object.

# Examples
```jldoctest
julia> num_wires(X)
1

julia> num_wires(SwapGate())
2
```
"""
num_wires(::AbstractGate) = error("Not implemented")

"""
    matrix(::AbstractGate)

returns dense matrix representation of [`AbstractGate`](@ref) objects

# Examples
```jldoctest
julia> matrix(X)
2×2 Array{Complex{Float64},2}:
 0.0+0.0im  1.0+0.0im
 1.0+0.0im  0.0+0.0im
```
"""
matrix(::AbstractGate) = error("Not implemented")

"""
    sparse_matrix(::AbstractGate)

returns sparse matrix representation of [`AbstractGate`](@ref) objects

# Examples
```jldoctest
julia> sparse_matrix(X)
2×2 SparseArrays.SparseMatrixCSC{Complex{Float64},Int64} with 2 stored entries:
  [2, 1]  =  1.0+0.0im
  [1, 2]  =  1.0+0.0im
```
"""
sparse_matrix(::AbstractGate) = error("Not implemented")

"""
    adjoint(::AbstractGate)

returns adjoint of [`AbstractGate`](@ref)

# Examples
```jldoctest
julia> adjoint(X)
XGate()

julia> adjoint(RxGate(0.3))
RxGate(-0.3)
```
"""

"""
    Pauli X gate

``X = \\begin{pmatrix} 0 & 1 \\\\ 1 & 0 \\end{pmatrix}``
"""
struct XGate <: AbstractGate end

"""
    Pauli Y gate

``Y = \\begin{pmatrix} 0 & -i \\\\ i & 0 \\end{pmatrix}``
"""
struct YGate <: AbstractGate end

"""
    Pauli Z gate

``Z = \\begin{pmatrix} 1 & 0 \\\\ 0 & -1 \\end{pmatrix}``
"""
struct ZGate <: AbstractGate end

matrix(::XGate) = ComplexF64[0  1 ; 1  0]
matrix(::YGate) = ComplexF64[0 -im; im 0]
matrix(::ZGate) = ComplexF64[1  0 ; 0 -1]

sparse_matrix(::XGate) = sparse(matrix(XGate()))
sparse_matrix(::YGate) = sparse(matrix(YGate()))
sparse_matrix(::ZGate) = sparse(matrix(ZGate()))

LinearAlgebra.ishermitian(::XGate) = true
LinearAlgebra.ishermitian(::YGate) = true
LinearAlgebra.ishermitian(::ZGate) = true

# Pauli matrices are Hermitian
Base.adjoint(::XGate) = X
Base.adjoint(::YGate) = Y
Base.adjoint(::ZGate) = Z

# corresponding instances
X = XGate()
Y = YGate()
Z = ZGate()

# wires
num_wires(::XGate)::Int = 1
num_wires(::YGate)::Int = 1
num_wires(::ZGate)::Int = 1

"""
    Hadamard gate

``H = \\frac{1}{\\sqrt{2}} \\begin{pmatrix} 1 & 1 \\\\ 1 & 1 \\end{pmatrix}``
"""
struct HadamardGate <: AbstractGate end

sparse_matrix(::HadamardGate) = sparse([1,1,2,2],[1,2,1,2], ComplexF64[1, 1, 1, -1] / sqrt(2))
matrix(::HadamardGate) = ComplexF64[1  1; 1 -1] / sqrt(2)

LinearAlgebra.ishermitian(::HadamardGate) = true
# Hadamard gate is Hermitian
Base.adjoint(H::HadamardGate) = H

# wires
num_wires(::HadamardGate)::Int = 1

"""
    S gate

``S = \\begin{pmatrix} 1 & 0 \\\\ 0 & i \\end{pmatrix}``
"""
struct SGate <: AbstractGate end

"""
    T gate

``T = \\begin{pmatrix} 1 & 0 \\\\ 0 & e^{\\frac{iπ}{4}} \\end{pmatrix}``
"""
struct TGate <: AbstractGate end

"""
    S^{†} gate

``S^{†} = \\begin{pmatrix} 1 & 0 \\\\ 0 & -i \\end{pmatrix}``
"""
struct SdagGate <: AbstractGate end

"""
    T^{†} gate

``T^{†} = \\begin{pmatrix} 1 & 0 \\\\ 0 & e^{-\\frac{iπ}{4}} \\end{pmatrix}``
"""
struct TdagGate <: AbstractGate end

matrix(::SGate)    = ComplexF64[1 0; 0 im]
matrix(::SdagGate) = ComplexF64[1 0; 0 -im]
matrix(::TGate)    = ComplexF64[1 0; 0 Base.exp(im * π / 4)]
matrix(::TdagGate) = ComplexF64[1 0; 0 Base.exp(-im * π / 4)]

sparse_matrix(::SGate)    = sparse(matrix(SGate()))
sparse_matrix(::TGate)    = sparse(matrix(TGate()))
sparse_matrix(::SdagGate) = sparse(matrix(SdagGate()))
sparse_matrix(::TdagGate) = sparse(matrix(TdagGate()))

LinearAlgebra.ishermitian(::SGate) = false
LinearAlgebra.ishermitian(::TGate) = false

LinearAlgebra.ishermitian(::SdagGate) = false
LinearAlgebra.ishermitian(::TdagGate) = false

Base.adjoint(::SGate) = SdagGate()
Base.adjoint(::TGate) = TdagGate()

Base.adjoint(::SdagGate) = SGate()
Base.adjoint(::TdagGate) = TGate()

# wires
num_wires(::SGate)::Int = 1
num_wires(::TGate)::Int = 1
num_wires(::SdagGate)::Int = 1
num_wires(::TdagGate)::Int = 1

"""
    Rotation-X gate

``R_{x}(\\theta) = \\begin{pmatrix} \\cos(\\frac{\\theta}{2}) & -i\\sin(\\frac{\\theta}{2}) \\\\ -i\\sin(\\frac{\\theta}{2}) & \\cos(\\frac{\\theta}{2}) \\end{pmatrix}``
"""
struct RxGate <: AbstractGate
    # use a reference type (array with 1 entry) for compatibility with Flux
    θ::Vector{Float64}

    function RxGate(θ::Real)
        new([θ])
    end
end

function matrix(g::RxGate)
    c = cos(g.θ[] / 2.0)
    s = sin(g.θ[] / 2.0)
    ComplexF64[c -im * s; -im * s c]
end

sparse_matrix(g::RxGate) = sparse(matrix(g))

LinearAlgebra.ishermitian(g::RxGate) = abs(sin(g.θ[] / 2)) < 4 * eps()

# wires
num_wires(::RxGate)::Int = 1

"""
    Rotation-Y gate

``R_{y}(\\theta) = \\begin{pmatrix} \\cos(\\frac{\\theta}{2}) & -\\sin(\\frac{\\theta}{2}) \\\\ \\sin(\\frac{\\theta}{2}) & \\cos(\\frac{\\theta}{2}) \\end{pmatrix}``
"""
struct RyGate <: AbstractGate
    # use a reference type (array with 1 entry) for compatibility with Flux
    θ::Vector{Float64}

    function RyGate(θ::Real)
        new([θ])
    end
end

function matrix(g::RyGate)
    c = cos(g.θ[] / 2.0)
    s = sin(g.θ[] / 2.0)
    ComplexF64[c -s; s c]
end

sparse_matrix(g::RyGate) = sparse(matrix(g))

LinearAlgebra.ishermitian(g::RyGate) = abs(sin(g.θ[] / 2)) < 4 * eps()

# wires
num_wires(::RyGate)::Int = 1

"""
    Rotation-Z gate

``R_{z}(\\theta) = \\begin{pmatrix} e^{\\frac{-i\\theta}{2}} & 0 \\\\ 0 & e^{\\frac{i\\theta}{2}} \\end{pmatrix}``
"""
struct RzGate <: AbstractGate
    # use a reference type (array with 1 entry) for compatibility with Flux
    θ::Vector{Float64}
    function RzGate(θ::Real)
        new([θ])
    end
end

function matrix(g::RzGate)
    eθ = exp(im * g.θ[] / 2)
    ComplexF64[conj(eθ) 0; 0 eθ]
end

sparse_matrix(g::RzGate) = sparse(matrix(g))

LinearAlgebra.ishermitian(g::RzGate) = abs(sin(g.θ[] / 2)) < 4 * eps()

Base.adjoint(g::RxGate) = RxGate(-g.θ[])
Base.adjoint(g::RyGate) = RyGate(-g.θ[])
Base.adjoint(g::RzGate) = RzGate(-g.θ[])

# wires
num_wires(::RzGate)::Int = 1

"""
    General rotation operator gate: rotation by angle `θ` around unit vector `n`.

``R_{\\vec v}(\\theta) = \\cos(\\frac{\\theta}{2})I - i\\sin(\\frac{\\theta}{2})\\vec v \\sigma, \\\\ \\sigma = [X, Y, Z]``
"""
struct RotationGate <: AbstractGate
    nθ::Vector{Float64}

    function RotationGate(nθ::Vector{<:Real})
        length(nθ) == 3 || error("Rotation axis vector must have length 3.")
        new(nθ)
    end

    function RotationGate(θ::Real, n::Vector{<:Real})
        length(n) == 3 || error("Rotation axis vector must have length 3.")
        norm(n) ≈ 1 || error("Norm of rotation axis vector must be 1.")
        new(n * θ)
    end
end

function matrix(g::RotationGate)
    θ = norm(g.nθ)
    if θ == 0
        return Matrix{ComplexF64}(I, 2, 2)
    end
    n = g.nθ / θ
    return cos(θ/2)*I - im*sin(θ/2)*pauli_vector(n...)
end

sparse_matrix(g::RotationGate) = sparse(matrix(g))

LinearAlgebra.ishermitian(g::RotationGate) = abs(sin(norm(g.nθ) / 2)) < 8 * eps()

Base.adjoint(g::RotationGate) = RotationGate(-g.nθ)

# wires
num_wires(::RotationGate)::Int = 1

"""
    Phase shift gate

``P(\\phi) = \\begin{pmatrix} 1 & 0 \\\\ 0 & e^{i\\phi} \\end{pmatrix}``
"""
struct PhaseShiftGate <: AbstractGate
    # use a reference type (array with 1 entry) for compatibility with Flux
    ϕ::Vector{Float64}

    function PhaseShiftGate(ϕ::Real)
        new([ϕ])
    end
end

matrix(g::PhaseShiftGate) = ComplexF64[1 0; 0 Base.exp(im * g.ϕ[])]

sparse_matrix(g::PhaseShiftGate) = sparse(matrix(g))

LinearAlgebra.ishermitian(g::PhaseShiftGate) = abs(sin(g.ϕ[])) < 4 * eps()

Base.adjoint(g::PhaseShiftGate) = PhaseShiftGate(-g.ϕ[])

# wires
num_wires(::PhaseShiftGate)::Int = 1

"""
    Swap gate

``SWAP = \\begin{pmatrix} 1 & 0 & 0 & 0 \\\\ 0 & 0 & 1 & 0 \\\\ 0 & 1 & 0 & 0 \\\\ 0 & 0 & 0 & 1 \\end{pmatrix}``
"""
struct SwapGate <: AbstractGate end

matrix(::SwapGate,) = ComplexF64[1 0 0 0; 0 0 1 0; 0 1 0 0; 0 0 0 1]

sparse_matrix(::SwapGate,) = sparse([1, 3, 2, 4], [1, 2, 3, 4], ComplexF64[1, 1, 1, 1])

# swap gate is Hermitian
LinearAlgebra.ishermitian(::SwapGate) = true

Base.adjoint(s::SwapGate) = s

# wires
num_wires(::SwapGate)::Int = 2

"""
    Entanglement-XX gate

``G_{x}(\\theta) = e^{-i \\theta X \\otimes X / 2}``

Reference:\n
    B. Kraus and J. I. Cirac\n
    Optimal creation of entanglement using a two-qubit gate\n
    Phys. Rev. A 63, 062309 (2001)
"""
struct EntanglementXXGate <: AbstractGate
    # use a reference type (array with 1 entry) for compatibility with Flux
    θ::Vector{Float64}
    function EntanglementXXGate(θ::Real)
        new([θ])
    end
end

function matrix(g::EntanglementXXGate)
    c = cos(g.θ[] / 2)
    s = sin(g.θ[] / 2)
    ComplexF64[c  0  0  -im*s; 0  c  -im*s  0; 0  -im*s  c  0; -im*s  0  0  c]
end

function sparse_matrix(g::EntanglementXXGate)
    c = cos(g.θ[] / 2)
    s = sin(g.θ[] / 2)
    sparse([1, 4, 2, 3, 2, 3, 1, 4], [1, 1, 2, 2, 3, 3, 4, 4], ComplexF64[c, -im*s, c, -im*s, -im*s, c, -im*s, c])
end

LinearAlgebra.ishermitian(g::EntanglementXXGate) = abs(sin(g.θ[] / 2)) < 4 * eps()

# wires
num_wires(::EntanglementXXGate)::Int = 2

"""
    Entanglement-YY gate

``G_{y}(\\theta) = e^{-i \\theta Y \\otimes Y / 2}``

Reference:\n
    B. Kraus and J. I. Cirac\n
    Optimal creation of entanglement using a two-qubit gate\n
    Phys. Rev. A 63, 062309 (2001)
"""
struct EntanglementYYGate <: AbstractGate
    # use a reference type (array with 1 entry) for compatibility with Flux
    θ::Vector{Float64}
    function EntanglementYYGate(θ::Real)
        new([θ])
    end
end

function matrix(g::EntanglementYYGate)
    c = cos(g.θ[] / 2)
    s = sin(g.θ[] / 2)
    ComplexF64[c  0  0  im*s; 0  c  -im*s  0; 0  -im*s  c  0; im*s  0  0  c]
end

function sparse_matrix(g::EntanglementYYGate)
    c = cos(g.θ[] / 2)
    s = sin(g.θ[] / 2)
    sparse([1, 4, 2, 3, 2, 3, 1, 4], [1, 1, 2, 2, 3, 3, 4, 4], ComplexF64[c, im*s, c, -im*s, -im*s, c, im*s, c])
end

LinearAlgebra.ishermitian(g::EntanglementYYGate) = abs(sin(g.θ[] / 2)) < 4 * eps()

# wires
num_wires(::EntanglementYYGate)::Int = 2

"""
    Entanglement-ZZ gate

``G_{z}(\\theta) = e^{-i \\theta Z \\otimes Z / 2}``

Reference:\n
    B. Kraus and J. I. Cirac\n
    Optimal creation of entanglement using a two-qubit gate\n
    Phys. Rev. A 63, 062309 (2001)
"""
struct EntanglementZZGate <: AbstractGate
    # use a reference type (array with 1 entry) for compatibility with Flux
    θ::Vector{Float64}
    function EntanglementZZGate(θ::Real)
        new([θ])
    end
end

function matrix(g::EntanglementZZGate)
    eθ = exp(im * g.θ[] / 2)
    ComplexF64[conj(eθ) 0 0 0; 0 eθ 0 0; 0 0 eθ 0; 0 0 0 conj(eθ)]
end

function sparse_matrix(g::EntanglementZZGate)
    eθ = exp(im * g.θ[] / 2)
    sparse([1, 2, 3, 4], [1, 2, 3, 4], ComplexF64[conj(eθ), eθ, eθ, conj(eθ)])
end

LinearAlgebra.ishermitian(g::EntanglementZZGate) = abs(sin(g.θ[] / 2)) < 4 * eps()

# wires
num_wires(::EntanglementZZGate)::Int = 2

Base.adjoint(g::EntanglementXXGate) = EntanglementXXGate(-g.θ[])
Base.adjoint(g::EntanglementYYGate) = EntanglementYYGate(-g.θ[])
Base.adjoint(g::EntanglementZZGate) = EntanglementZZGate(-g.θ[])

matrix(::UniformScaling{Bool}) = ComplexF64[1 0; 0 1]
sparse_matrix(::UniformScaling{Bool}) = sparse((1.0+0.0im)*I, 2, 2)

"""
    kron(m::Union{UniformScaling{Bool},G}...) where {G<:AbstractGate}

returns sparse matrix representation of kronecker product of AbstractGate objects
"""
Base.kron(m::Union{UniformScaling{Bool},G}...) where {G<:AbstractGate} = Base.kron(sparse_matrix.(m)...)

"""
    ControlledGate{G}(U::G, M::Int) where {G<:AbstractGate}

returns ControlledGate object. Controls [`AbstractGate`](@ref) of type `G` with `M` control wires
"""
struct ControlledGate{G} <: AbstractGate
    "target gate"
    U::G
    "number of control wires"
    M::Int
end

# wires
num_wires(g::ControlledGate)::Int = g.M + num_wires(g.U)

"""
    target_wires(g::ControlledGate)

return target wires for a [`ControlledGate`](@ref)

# Examples
```jldocttest
# controlled gate with 1 target wire
julia> cnot = ControlledGate(X, 1);
julia> target_wires(cnot)
1
```
"""
target_wires(g::ControlledGate) = num_wires(g.U)

"""
    control_wires(g::ControlledGate)

return control wires for a [`ControlledGate`](@ref)

# Examples
```jldocttest
# controlled gate with 2 control wires
julia> ccnot = ControlledGate(X, 2);
julia> target_wires(ccnot)
2
```
"""
control_wires(g::ControlledGate) = g.M

function matrix(g::ControlledGate{G}) where {G <:AbstractGate}
    N = num_wires(g)
    Umat = matrix(g.U)
    CU = Matrix{ComplexF64}(I, 2^N, 2^N)
    # Note: target qubit(s) corresponds to fastest varying index
    CU[end-size(Umat, 1)+1:end, end-size(Umat, 2)+1:end] = Umat
    return CU
end

function sparse_matrix(g::ControlledGate{G}) where {G <:AbstractGate}
    N = num_wires(g)
    Umat = sparse_matrix(g.U)
    CU = sparse(one(ComplexF64)*I, 2^N, 2^N)
    # Note: target qubit(s) corresponds to fastest varying index
    CU[end-size(Umat, 1)+1:end, end-size(Umat, 2)+1:end] = Umat
    return dropzeros!(CU)
end

LinearAlgebra.ishermitian(g::ControlledGate) = LinearAlgebra.ishermitian(g.U)

Base.adjoint(g::ControlledGate) = ControlledGate(Base.adjoint(g.U), g.M)

controlled_not() = ControlledGate(X, 1)

isunitary(m::AbstractMatrix) = (m * Base.adjoint(m) ≈ I)

"""
    MatrixGate

general gate constructed from an unitary matrix
"""
struct MatrixGate <: AbstractGate
    matrix::SparseMatrixCSC{ComplexF64,Int}
    function MatrixGate(m::AbstractMatrix)
        @assert size(m, 1) == size(m, 2)
        isunitary(m) || error("Quantum gate must be unitary")
        return new(sparse(m))
    end
end

# wires
num_wires(g::MatrixGate)::Int = Int(log(2, size(g.matrix, 1)))

matrix(g::MatrixGate) = Matrix(g.matrix)

sparse_matrix(g::MatrixGate) = g.matrix

Base.adjoint(g::MatrixGate) = MatrixGate(Base.adjoint(g.matrix))

LinearAlgebra.ishermitian(g::MatrixGate) = LinearAlgebra.ishermitian(g.matrix)

"""
    Base.isapprox(g1::G, g2::G) where {G <: AbstractGate}

returns true if input AbstractGates are of same type with same paremeters

# Examples
```jldoctest
julia> X ≈ X
true

julia> X ≈ Y
false

julia> RxGate(0.1) ≈ RxGate(0.2)
false
```
"""
function Base.isapprox(g1::G, g2::G) where {G <: AbstractGate}
    for name in fieldnames(G)
        if !(getfield(g1, name) ≈ getfield(g2, name))
            return false
        end
    end
    return true
end

# handle different gate types or dimensions
Base.isapprox(::AbstractGate, ::AbstractGate) = false

# Define "addition" and "scaling" of parametric gates (required for gradient accumulation)

Base.:+(g1::RxGate, g2::RxGate) = RxGate(g1.θ[] + g2.θ[])
Base.:+(g1::RyGate, g2::RyGate) = RyGate(g1.θ[] + g2.θ[])
Base.:+(g1::RzGate, g2::RzGate) = RzGate(g1.θ[] + g2.θ[])

Base.:+(g1::RotationGate, g2::RotationGate) = RotationGate(g1.nθ + g2.nθ)

Base.:+(g1::PhaseShiftGate, g2::PhaseShiftGate) = PhaseShiftGate(g1.ϕ[] + g2.ϕ[])

Base.:+(g1::EntanglementXXGate, g2::EntanglementXXGate) = EntanglementXXGate(g1.θ[] + g2.θ[])
Base.:+(g1::EntanglementYYGate, g2::EntanglementYYGate) = EntanglementYYGate(g1.θ[] + g2.θ[])
Base.:+(g1::EntanglementZZGate, g2::EntanglementZZGate) = EntanglementZZGate(g1.θ[] + g2.θ[])

Base.:*(α::Real, g::RxGate) = RxGate(α * g.θ[])
Base.:*(α::Real, g::RyGate) = RyGate(α * g.θ[])
Base.:*(α::Real, g::RzGate) = RzGate(α * g.θ[])

Base.:*(α::Real, g::RotationGate) = RotationGate(α * g.nθ)

Base.:*(α::Real, g::PhaseShiftGate) = PhaseShiftGate(α * g.ϕ[])

Base.:*(α::Real, g::EntanglementXXGate) = EntanglementXXGate(α * g.θ[])
Base.:*(α::Real, g::EntanglementYYGate) = EntanglementYYGate(α * g.θ[])
Base.:*(α::Real, g::EntanglementZZGate) = EntanglementZZGate(α * g.θ[])


### A Pluto.jl notebook ###
# v0.17.1

using Markdown
using InteractiveUtils

# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error).
macro bind(def, element)
    quote
        local iv = try Base.loaded_modules[Base.PkgId(Base.UUID("6e696c72-6542-2067-7265-42206c756150"), "AbstractPlutoDingetjes")].Bonds.initial_value catch; b -> missing; end
        local el = $(esc(element))
        global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : iv(el)
        el
    end
end

# ╔═╡ c102e87a-b570-4d86-b087-3506396fc065
begin
	using PlutoUI
	import Triangulate
	import TetGen
	using SimplexGridFactory
	using ExtendableGrids
    using PlutoVista
end

# ╔═╡ 93ca4fd0-8f61-4174-b459-55f5395c0f56
md"""
# Test Notebook for [PlutoVista](https://github.com/j-fu/PlutoVista.jl)
"""

# ╔═╡ 53f55db7-225f-4717-ab62-e024211e98a2
md"""
## 1D Data

1D Data are plotted using the [plotly.js](https://plotly.com/javascript/) javascript plotting library as backend.

### plot
"""

# ╔═╡ b8a976e3-7fef-4527-ae6a-4da31c93a04f
X=0:0.01:10

# ╔═╡ 133d0c31-eea9-4200-88d2-4afdc61a16bd
md"""
The simplest way to plot 1D data is to just plot:
"""

# ╔═╡ 2e92c42c-2923-4f35-9d43-6e38eee3f8f6
plot(X,cos.(X))

# ╔═╡ 09cfb361-b897-40b1-8792-23b00151b995
md"""
More complicated plots can be combined.
"""

# ╔═╡ f44deb76-e715-477c-9e8a-dcf5cd68577f
md"""
xscale $(@bind xscale Slider(1:0.1:10,show_value=true))

yscale: $(@bind yscale Slider(1:0.1:10,show_value=true)),
"""

# ╔═╡ 60dcfcf5-391e-418f-8e7c-3a0fe94f1e0d
let
    p=PlutoVistaPlot(resolution=(500,300),titlefontsize=20)
	plot!(p,X,yscale*sin.(X*xscale);label="sin",color=:red,linestyle=:dashdot)
	plot!(p,X,cos.(X*xscale);label="cos",color=:green,linewidth=1,markertype=:star5)
	plot!(p,X,X./X[end];color=:blue,linestyle=:dash,title="test",legend=:rt,xlabel="x")
end

# ╔═╡ 76d7a50d-40da-4367-a84f-3e2324e0c78d
md"""
For changing data, we can create the plot first and fill it afterwards.
"""

# ╔═╡ c3b719cd-5134-4395-8f36-4daab6ee3bf0
p1=plot(resolution=(600,300),axisfontsize=20)

# ╔═╡ bdde2452-cf28-4d56-935c-693e6a1ca8a9
md"""
 xscale1: $(@bind xscale1 Slider(1:0.1:10,show_value=true))
"""

# ╔═╡ cf9f4d1a-ebcd-4b59-bb0c-cc0d6b3239ca
begin
	plot!(p1,X,sin.(X*xscale1);label="sin",color=:red,linestyle=:dashdot,clear=true)
	plot!(p1,X,cos.(X*xscale1);label="cos",color=:green,linestyle=:dot,clear=false,show=true,xlabel="x",ylabel="y")
end

# ╔═╡ d5240960-7759-48b8-94fd-3436f7b1573d
md"""
### Discontinuous function
"""

# ╔═╡ b4afef75-450c-45a9-af8b-87224c3591e9
begin
	Xm=collect(-1:0.01:0)
	Xp=collect(0:0.01:1)
	Xpm=vcat(Xp,[NaN],Xm)
	F=vcat(map(x->sin(4π*x),Xm),[NaN],map(x->cos(4π*x),Xp))

end

# ╔═╡ 0c8bc23a-cee1-4d6b-a63e-8f2391fc3f3c
PlutoVista.plot(Xpm,F,markertype=:star5,markercount=20)

# ╔═╡ d0ea4e21-9d8c-416e-bf68-d7522e2ece20
md"""
## 2D Data

For 2D data, the package uses  [vtk.js](https://kitware.github.io/vtk-js/index.html)
as a backend and ths us uses GPU acceleration via the WebGL interface.
"""

# ╔═╡ f15a91a4-a03b-4125-ba58-e44d9792a7e3
md"""
Let us make a triangulation using the Triangulate.jl package, plot it, define a piecewise linear function on it and plot this function.
"""

# ╔═╡ ab232244-4fe2-4ab0-a0bf-d1d9510802d2
function maketriangulation(maxarea)
     triin=Triangulate.TriangulateIO()
    triin.pointlist=Matrix{Cdouble}([-1.0 -1.0 ; 1.0 -1.0 ; 1.0 2 ; -1.0 1.0]')
    triin.segmentlist=Matrix{Cint}([1 2 ; 2 3 ; 3 4 ; 4 1; 4 2 ]')
    triin.segmentmarkerlist=Vector{Int32}([1, 2, 3, 4, 6])
    triin.regionlist=Matrix{Cdouble}([-0.9 0.9; -0.9 0.9; 1 2 ; 2.0*maxarea maxarea])
    (triout, vorout)=Triangulate.triangulate("paADQ", triin)
    triout.pointlist, triout.trianglelist,Int.(vec(triout.triangleattributelist)),triout.segmentlist,triout.segmentmarkerlist
end;

# ╔═╡ 724495e1-d501-4a03-be88-16b644938afd
md"""
Change grid resolution: $(@bind resolution Slider(10:1:100))
"""

# ╔═╡ d3ad0d4f-859d-44ac-a387-aac8d465cc6d
(pts,tris,markers,edges,edgemarkers)=maketriangulation(1/resolution^2);

# ╔═╡ 83c7bffd-16c6-4cc7-8a68-87cbd739f3f4
md"""
The grid has $(size(pts,2)) points and $(size(tris,2))  triangles.
"""

# ╔═╡ da3bdabb-b81c-4c05-90cd-aee7b209e605
md"""
### trimesh
"""

# ╔═╡ 83be4a71-4f01-4f70-9cbe-f4e9b9222428
trimesh(pts,tris;markers=markers,edges=edges,edgemarkers=edgemarkers)

# ╔═╡ 8186bd23-5727-4d87-805c-5e5c6a092535
md"""
### tricontour
"""

# ╔═╡ ee9a6fb2-3978-40f4-803b-7cb8d50b4fac
func=0.5*[sin(10*pts[1,i])*cos(10*pts[2,i]) for i=1:size(pts,2)];

# ╔═╡ c6d700ec-91a1-4ef7-a104-8574cc162b9a
tricontour(pts,tris,func;colormap=:viridis),
tricontour(pts,tris,func;colormap=:summer,levels=3)

# ╔═╡ 8b25e922-12db-4fae-8f28-65fe4faf40f3
tricontour(pts,tris,func;colormap=:hot,levels=-0.5:0.2:0.5)

# ╔═╡ dce20465-d227-4273-82b7-c6a4621942b9
md"""
Above, we have shown  three ways to specify isolines:
- to have no isolines (default)
- a fixed number of automaticaly generated isolines
- a fixed range of isolines
Colormaps can be chosen from [ColorSchemes.jl](https://juliagraphics.github.io/ColorSchemes.jl/stable/basics/#Pre-defined-schemes)
"""

# ╔═╡ 20a88415-f2b4-4806-9ffc-6be979d12d0a
data(pts,t)=0.5*[sin(10*pts[1,i]-t)*cos(10*pts[2,i]-t)+t for i=1:size(pts,2)];

# ╔═╡ 39412087-3ee3-41fb-818f-23e5396abba3
p2=tricontour(resolution=(200,200))

# ╔═╡ 68cba79c-2577-4250-b045-08e954bde4e5
md"""
shift: $(@bind shift1 Slider(-8:0.1:8,show_value=true))
"""

# ╔═╡ d3055a1a-5b55-4c43-ac09-7704186e714a
tricontour!(p2,pts,tris,data(pts,shift1),colormap=:hot,levels=5)

# ╔═╡ 387bbdee-0175-4a25-9a97-49fdb8afb7fc
md"""
Alternatively, we can use the Plotly backend:
"""

# ╔═╡ 06fb9e66-c7c0-4028-80d9-2a7e36a6626d
md"""
### contour
A contour plot lives on a rectangular grid.
"""

# ╔═╡ 732b8e6a-68e0-4229-a2f9-52abe3ee5a40
md"""
shift: $(@bind shift Slider(-8:0.1:8,show_value=true))
"""

# ╔═╡ fe7fdb00-88a1-4d24-aedd-cedb6e50120b
f(x,y)=sqrt((x-shift)^2+(y-5)^2)*cos(x)*cos(y)

# ╔═╡ 5745f449-abc7-4f9a-a439-645698b781ea
X1=-10:0.1:10

# ╔═╡ 595539f8-f14e-418f-90d6-b6040292a9b6
Y1=0:0.1:10

# ╔═╡ 2eadced9-9e4a-4fa3-aea4-5f163933cb02
contour(X1,Y1,[f(x,y) for x∈X1, y∈Y1],resolution=(600,300),levels=-20:5:20 )

# ╔═╡ d1e13f8c-7cb8-4cb5-9dda-1a9b5d6142b6
md"""
## 3D Data
"""

# ╔═╡ a2bb8861-493d-4003-8a4e-e0ea051fbb72
md"""
### tetmesh
"""

# ╔═╡ 411d8975-f67a-4236-b827-c030675db91c
function sphere_with_hole()

    builder=SimplexGridBuilder(Generator=TetGen)
    facetregion!(builder,7)
    cellregion!(builder,1)
    maxvolume!(builder,0.001)
    regionpoint!(builder,(0,0,1.5))
    sphere!(builder,(0,0,0),2.0,nref=3)

    rect3d!(builder,[-0.5,-0.5,-0.5],[0.5,0.5,0.5], facetregions=[1,2,3,4,5,6])
    holepoint!(builder,(0,0,0))

    simplexgrid(builder)
end

# ╔═╡ b1f255ac-7347-4cf0-8ca1-491713fcb6b0
g3=sphere_with_hole()

# ╔═╡ 0a363d31-5a48-49ad-aba2-bc0058ce1225
p3dgrid=tetmesh(resolution=(300,300))

# ╔═╡ da36cf26-8105-4569-9d09-6f16383000a0
md"""
x: $(@bind gxplane Slider(0:0.01:1,show_value=true,default=0.45))
y: $(@bind gyplane Slider(0:0.01:1,show_value=true,default=0.45))
z: $(@bind gzplane Slider(0:0.01:1,show_value=true,default=0.45))
"""

# ╔═╡ d009c4cb-9ef6-45bd-960f-0213880f662a
 tetmesh!(p3dgrid,g3[Coordinates],g3[CellNodes];
		markers=g3[CellRegions],
	faces=g3[BFaceNodes],
	facemarkers=g3[BFaceRegions],
	xplanes=[gxplane],yplanes=[gyplane],zplanes=[gzplane])

# ╔═╡ b33a9e6b-7c93-475c-96f4-7259b30c2c47
md"""
### tetcontour
"""

# ╔═╡ aded5964-8229-423c-b9f3-c80358b95fcc
f3=map(g3) do x,y,z
	sinpi(x/2)*cospi(y/2)*cospi(z/2)
end

# ╔═╡ 6af0b5d7-5324-43b5-8f99-6f5d35d5deba
TableOfContents()

# ╔═╡ 809ceb74-8201-4cc1-8cdc-656dc070e020
p3d=tetcontour(resolution=(300,300))

# ╔═╡ c222b16b-0ddc-4287-a029-779bdd77dd7b
md"""
f: $(@bind flevel Slider(0:0.01:1,show_value=true,default=0.45))

x: $(@bind xplane Slider(-2:0.01:2,show_value=true,default=-1.1))
y: $(@bind yplane Slider(-2:0.01:2,show_value=true,default=-1.1))
z: $(@bind zplane Slider(-2:0.01:2,show_value=true,default=-1.1))
"""

# ╔═╡ 36e48e9c-9452-4b07-bce4-c1cfe3c19409
tetcontour!(p3d,g3[Coordinates],g3[CellNodes],f3;levels=[flevel],
	xplanes=[xplane],yplanes=[yplane],zplanes=[zplane],levelalpha=0.7)

# ╔═╡ b074a389-bd07-4548-a75c-efa8c7663b15
html"""<hr>"""

# ╔═╡ fdf728b9-ade9-46f3-8aaf-cf22aaaa55d2
md"""
    begin
     using Pkg
     Pkg.activate(".testenv")
     Pkg.add("Revise")
     using Revise
      Pkg.add(["PlutoUI","Triangulate",
	   "ExtendableGrids","SimplexGridFactory","TetGen"])
     Pkg.develop("PlutoVista")
    end
""";

# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
ExtendableGrids = "cfc395e8-590f-11e8-1f13-43a2532b2fa8"
PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
PlutoVista = "646e1f28-b900-46d7-9d87-d554eb38a413"
SimplexGridFactory = "57bfcd06-606e-45d6-baf4-4ba06da0efd5"
TetGen = "c5d3f3f7-f850-59f6-8a2e-ffc6dc1317ea"
Triangulate = "f7e6ffb2-c36d-4f8f-a77e-16e897189344"

[compat]
ExtendableGrids = "~0.8.8"
PlutoUI = "~0.7.18"
PlutoVista = "~0.8.9"
SimplexGridFactory = "~0.5.10"
TetGen = "~1.3.0"
Triangulate = "~2.1.0"
"""

# ╔═╡ 00000000-0000-0000-0000-000000000002
PLUTO_MANIFEST_TOML_CONTENTS = """
# This file is machine-generated - editing it directly is not advised

julia_version = "1.6.3"
manifest_format = "2.0"

[[deps.AbstractPlutoDingetjes]]
deps = ["Pkg"]
git-tree-sha1 = "0ec322186e078db08ea3e7da5b8b2885c099b393"
uuid = "6e696c72-6542-2067-7265-42206c756150"
version = "1.1.0"

[[deps.AbstractTrees]]
git-tree-sha1 = "03e0550477d86222521d254b741d470ba17ea0b5"
uuid = "1520ce14-60c1-5f80-bbc7-55ef81b5835c"
version = "0.3.4"

[[deps.Adapt]]
deps = ["LinearAlgebra"]
git-tree-sha1 = "84918055d15b3114ede17ac6a7182f68870c16f7"
uuid = "79e6a3ab-5dfb-504d-930d-738a2a938a0e"
version = "3.3.1"

[[deps.ArgTools]]
uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f"

[[deps.Artifacts]]
uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"

[[deps.Base64]]
uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"

[[deps.ColorSchemes]]
deps = ["ColorTypes", "Colors", "FixedPointNumbers", "Random"]
git-tree-sha1 = "a851fec56cb73cfdf43762999ec72eff5b86882a"
uuid = "35d6a980-a343-548e-a6ea-1d62b119f2f4"
version = "3.15.0"

[[deps.ColorTypes]]
deps = ["FixedPointNumbers", "Random"]
git-tree-sha1 = "024fe24d83e4a5bf5fc80501a314ce0d1aa35597"
uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f"
version = "0.11.0"

[[deps.Colors]]
deps = ["ColorTypes", "FixedPointNumbers", "Reexport"]
git-tree-sha1 = "417b0ed7b8b838aa6ca0a87aadf1bb9eb111ce40"
uuid = "5ae59095-9a9b-59fe-a467-6f913c188581"
version = "0.12.8"

[[deps.DataAPI]]
git-tree-sha1 = "cc70b17275652eb47bc9e5f81635981f13cea5c8"
uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a"
version = "1.9.0"

[[deps.DataValueInterfaces]]
git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6"
uuid = "e2d170a0-9d28-54be-80f0-106bbe20a464"
version = "1.0.0"

[[deps.Dates]]
deps = ["Printf"]
uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"

[[deps.DocStringExtensions]]
deps = ["LibGit2"]
git-tree-sha1 = "b19534d1895d702889b219c382a6e18010797f0b"
uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
version = "0.8.6"

[[deps.Downloads]]
deps = ["ArgTools", "LibCURL", "NetworkOptions"]
uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6"

[[deps.EarCut_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "3f3a2501fa7236e9b911e0f7a588c657e822bb6d"
uuid = "5ae413db-bbd1-5e63-b57d-d24a61df00f5"
version = "2.2.3+0"

[[deps.ElasticArrays]]
deps = ["Adapt"]
git-tree-sha1 = "a0fcc1bb3c9ceaf07e1d0529c9806ce94be6adf9"
uuid = "fdbdab4c-e67f-52f5-8c3f-e7b388dad3d4"
version = "1.2.9"

[[deps.ExtendableGrids]]
deps = ["AbstractTrees", "Dates", "DocStringExtensions", "ElasticArrays", "InteractiveUtils", "LinearAlgebra", "Printf", "Random", "SparseArrays", "Test"]
git-tree-sha1 = "e03c32179da71e9022381e1224c32a7a89febc10"
uuid = "cfc395e8-590f-11e8-1f13-43a2532b2fa8"
version = "0.8.8"

[[deps.FixedPointNumbers]]
deps = ["Statistics"]
git-tree-sha1 = "335bfdceacc84c5cdf16aadc768aa5ddfc5383cc"
uuid = "53c48c17-4a7d-5ca2-90c5-79b7896eea93"
version = "0.8.4"

[[deps.GeometryBasics]]
deps = ["EarCut_jll", "IterTools", "LinearAlgebra", "StaticArrays", "StructArrays", "Tables"]
git-tree-sha1 = "58bcdf5ebc057b085e58d95c138725628dd7453c"
uuid = "5c1252a2-5f33-56bf-86c9-59e7332b4326"
version = "0.4.1"

[[deps.GridVisualize]]
deps = ["ColorSchemes", "Colors", "DocStringExtensions", "ElasticArrays", "ExtendableGrids", "GeometryBasics", "HypertextLiteral", "LinearAlgebra", "Observables", "OrderedCollections", "PkgVersion", "Printf", "StaticArrays"]
git-tree-sha1 = "80232407bd1fd4683429e289f6051b730e58cfa5"
uuid = "5eed8a63-0fb0-45eb-886d-8d5a387d12b8"
version = "0.4.1"

[[deps.Hyperscript]]
deps = ["Test"]
git-tree-sha1 = "8d511d5b81240fc8e6802386302675bdf47737b9"
uuid = "47d2ed2b-36de-50cf-bf87-49c2cf4b8b91"
version = "0.0.4"

[[deps.HypertextLiteral]]
git-tree-sha1 = "5efcf53d798efede8fee5b2c8b09284be359bf24"
uuid = "ac1192a8-f4b3-4bfe-ba22-af5b92cd3ab2"
version = "0.9.2"

[[deps.IOCapture]]
deps = ["Logging", "Random"]
git-tree-sha1 = "f7be53659ab06ddc986428d3a9dcc95f6fa6705a"
uuid = "b5f81e59-6552-4d32-b1f0-c071b021bf89"
version = "0.2.2"

[[deps.InteractiveUtils]]
deps = ["Markdown"]
uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"

[[deps.IterTools]]
git-tree-sha1 = "05110a2ab1fc5f932622ffea2a003221f4782c18"
uuid = "c8e1da08-722c-5040-9ed9-7db0dc04731e"
version = "1.3.0"

[[deps.IteratorInterfaceExtensions]]
git-tree-sha1 = "a3f24677c21f5bbe9d2a714f95dcd58337fb2856"
uuid = "82899510-4779-5014-852e-03e436cf321d"
version = "1.0.0"

[[deps.JLLWrappers]]
deps = ["Preferences"]
git-tree-sha1 = "642a199af8b68253517b80bd3bfd17eb4e84df6e"
uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210"
version = "1.3.0"

[[deps.JSON]]
deps = ["Dates", "Mmap", "Parsers", "Unicode"]
git-tree-sha1 = "8076680b162ada2a031f707ac7b4953e30667a37"
uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
version = "0.21.2"

[[deps.LibCURL]]
deps = ["LibCURL_jll", "MozillaCACerts_jll"]
uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21"

[[deps.LibCURL_jll]]
deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"]
uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0"

[[deps.LibGit2]]
deps = ["Base64", "NetworkOptions", "Printf", "SHA"]
uuid = "76f85450-5226-5b5a-8eaa-529ad045b433"

[[deps.LibSSH2_jll]]
deps = ["Artifacts", "Libdl", "MbedTLS_jll"]
uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8"

[[deps.Libdl]]
uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"

[[deps.LinearAlgebra]]
deps = ["Libdl"]
uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"

[[deps.Logging]]
uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"

[[deps.Markdown]]
deps = ["Base64"]
uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"

[[deps.MbedTLS_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1"

[[deps.Mmap]]
uuid = "a63ad114-7e13-5084-954f-fe012c677804"

[[deps.MozillaCACerts_jll]]
uuid = "14a3606d-f60d-562e-9121-12d972cd8159"

[[deps.NetworkOptions]]
uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"

[[deps.Observables]]
git-tree-sha1 = "fe29afdef3d0c4a8286128d4e45cc50621b1e43d"
uuid = "510215fc-4207-5dde-b226-833fc4488ee2"
version = "0.4.0"

[[deps.OrderedCollections]]
git-tree-sha1 = "85f8e6578bf1f9ee0d11e7bb1b1456435479d47c"
uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
version = "1.4.1"

[[deps.Parsers]]
deps = ["Dates"]
git-tree-sha1 = "ae4bbcadb2906ccc085cf52ac286dc1377dceccc"
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
version = "2.1.2"

[[deps.Pkg]]
deps = ["Artifacts", "Dates", "Downloads", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"]
uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"

[[deps.PkgVersion]]
deps = ["Pkg"]
git-tree-sha1 = "a7a7e1a88853564e551e4eba8650f8c38df79b37"
uuid = "eebad327-c553-4316-9ea0-9fa01ccd7688"
version = "0.1.1"

[[deps.PlutoUI]]
deps = ["AbstractPlutoDingetjes", "Base64", "Dates", "Hyperscript", "HypertextLiteral", "IOCapture", "InteractiveUtils", "JSON", "Logging", "Markdown", "Random", "Reexport", "UUIDs"]
git-tree-sha1 = "57312c7ecad39566319ccf5aa717a20788eb8c1f"
uuid = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
version = "0.7.18"

[[deps.PlutoVista]]
deps = ["ColorSchemes", "Colors", "DocStringExtensions", "GridVisualize", "UUIDs"]
git-tree-sha1 = "74a871aabc16ad6bcfc119f24581015ae8efa17a"
uuid = "646e1f28-b900-46d7-9d87-d554eb38a413"
version = "0.8.9"

[[deps.Preferences]]
deps = ["TOML"]
git-tree-sha1 = "00cfd92944ca9c760982747e9a1d0d5d86ab1e5a"
uuid = "21216c6a-2e73-6563-6e65-726566657250"
version = "1.2.2"

[[deps.Printf]]
deps = ["Unicode"]
uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"

[[deps.REPL]]
deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"]
uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"

[[deps.Random]]
deps = ["Serialization"]
uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"

[[deps.Reexport]]
git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b"
uuid = "189a3867-3050-52da-a836-e630ba90ab69"
version = "1.2.2"

[[deps.SHA]]
uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"

[[deps.Serialization]]
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"

[[deps.SimplexGridFactory]]
deps = ["DocStringExtensions", "ElasticArrays", "ExtendableGrids", "GridVisualize", "LinearAlgebra", "Printf", "Test"]
git-tree-sha1 = "84e5e7f53ab95bea59d04168ccefb77e2368393c"
uuid = "57bfcd06-606e-45d6-baf4-4ba06da0efd5"
version = "0.5.10"

[[deps.Sockets]]
uuid = "6462fe0b-24de-5631-8697-dd941f90decc"

[[deps.SparseArrays]]
deps = ["LinearAlgebra", "Random"]
uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"

[[deps.StaticArrays]]
deps = ["LinearAlgebra", "Random", "Statistics"]
git-tree-sha1 = "3c76dde64d03699e074ac02eb2e8ba8254d428da"
uuid = "90137ffa-7385-5640-81b9-e52037218182"
version = "1.2.13"

[[deps.Statistics]]
deps = ["LinearAlgebra", "SparseArrays"]
uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"

[[deps.StructArrays]]
deps = ["Adapt", "DataAPI", "StaticArrays", "Tables"]
git-tree-sha1 = "2ce41e0d042c60ecd131e9fb7154a3bfadbf50d3"
uuid = "09ab397b-f2b6-538f-b94a-2f83cf4a842a"
version = "0.6.3"

[[deps.TOML]]
deps = ["Dates"]
uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76"

[[deps.TableTraits]]
deps = ["IteratorInterfaceExtensions"]
git-tree-sha1 = "c06b2f539df1c6efa794486abfb6ed2022561a39"
uuid = "3783bdb8-4a98-5b6b-af9a-565f29a5fe9c"
version = "1.0.1"

[[deps.Tables]]
deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "LinearAlgebra", "TableTraits", "Test"]
git-tree-sha1 = "fed34d0e71b91734bf0a7e10eb1bb05296ddbcd0"
uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
version = "1.6.0"

[[deps.Tar]]
deps = ["ArgTools", "SHA"]
uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e"

[[deps.Test]]
deps = ["InteractiveUtils", "Logging", "Random", "Serialization"]
uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[[deps.TetGen]]
deps = ["DocStringExtensions", "GeometryBasics", "LinearAlgebra", "Printf", "StaticArrays", "TetGen_jll"]
git-tree-sha1 = "2f1d87ccacd2a7faf9e0bade918946ec4d90bfdf"
uuid = "c5d3f3f7-f850-59f6-8a2e-ffc6dc1317ea"
version = "1.3.0"

[[deps.TetGen_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "bc67a0d0b799fe248b1f199a5c893ccf316f0e60"
uuid = "b47fdcd6-d2c1-58e9-bbba-c1cee8d8c179"
version = "1.5.2+0"

[[deps.Triangle_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "bfdd9ef1004eb9d407af935a6f36a4e0af711369"
uuid = "5639c1d2-226c-5e70-8d55-b3095415a16a"
version = "1.6.1+0"

[[deps.Triangulate]]
deps = ["DocStringExtensions", "Libdl", "Printf", "Test", "Triangle_jll"]
git-tree-sha1 = "2b4f716b192c0c615d96d541ee029e85666388cb"
uuid = "f7e6ffb2-c36d-4f8f-a77e-16e897189344"
version = "2.1.0"

[[deps.UUIDs]]
deps = ["Random", "SHA"]
uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"

[[deps.Unicode]]
uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"

[[deps.Zlib_jll]]
deps = ["Libdl"]
uuid = "83775a58-1f1d-513f-b197-d71354ab007a"

[[deps.nghttp2_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d"

[[deps.p7zip_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0"
"""

# ╔═╡ Cell order:
# ╟─93ca4fd0-8f61-4174-b459-55f5395c0f56
# ╠═c102e87a-b570-4d86-b087-3506396fc065
# ╟─53f55db7-225f-4717-ab62-e024211e98a2
# ╟─b8a976e3-7fef-4527-ae6a-4da31c93a04f
# ╟─133d0c31-eea9-4200-88d2-4afdc61a16bd
# ╠═2e92c42c-2923-4f35-9d43-6e38eee3f8f6
# ╟─09cfb361-b897-40b1-8792-23b00151b995
# ╠═60dcfcf5-391e-418f-8e7c-3a0fe94f1e0d
# ╟─f44deb76-e715-477c-9e8a-dcf5cd68577f
# ╟─76d7a50d-40da-4367-a84f-3e2324e0c78d
# ╠═c3b719cd-5134-4395-8f36-4daab6ee3bf0
# ╠═cf9f4d1a-ebcd-4b59-bb0c-cc0d6b3239ca
# ╟─bdde2452-cf28-4d56-935c-693e6a1ca8a9
# ╟─d5240960-7759-48b8-94fd-3436f7b1573d
# ╠═b4afef75-450c-45a9-af8b-87224c3591e9
# ╠═0c8bc23a-cee1-4d6b-a63e-8f2391fc3f3c
# ╟─d0ea4e21-9d8c-416e-bf68-d7522e2ece20
# ╟─f15a91a4-a03b-4125-ba58-e44d9792a7e3
# ╠═ab232244-4fe2-4ab0-a0bf-d1d9510802d2
# ╠═d3ad0d4f-859d-44ac-a387-aac8d465cc6d
# ╟─83c7bffd-16c6-4cc7-8a68-87cbd739f3f4
# ╟─724495e1-d501-4a03-be88-16b644938afd
# ╟─da3bdabb-b81c-4c05-90cd-aee7b209e605
# ╠═83be4a71-4f01-4f70-9cbe-f4e9b9222428
# ╟─8186bd23-5727-4d87-805c-5e5c6a092535
# ╠═ee9a6fb2-3978-40f4-803b-7cb8d50b4fac
# ╠═c6d700ec-91a1-4ef7-a104-8574cc162b9a
# ╠═8b25e922-12db-4fae-8f28-65fe4faf40f3
# ╟─dce20465-d227-4273-82b7-c6a4621942b9
# ╠═20a88415-f2b4-4806-9ffc-6be979d12d0a
# ╠═39412087-3ee3-41fb-818f-23e5396abba3
# ╟─68cba79c-2577-4250-b045-08e954bde4e5
# ╠═d3055a1a-5b55-4c43-ac09-7704186e714a
# ╟─387bbdee-0175-4a25-9a97-49fdb8afb7fc
# ╟─06fb9e66-c7c0-4028-80d9-2a7e36a6626d
# ╟─732b8e6a-68e0-4229-a2f9-52abe3ee5a40
# ╠═2eadced9-9e4a-4fa3-aea4-5f163933cb02
# ╠═fe7fdb00-88a1-4d24-aedd-cedb6e50120b
# ╠═5745f449-abc7-4f9a-a439-645698b781ea
# ╠═595539f8-f14e-418f-90d6-b6040292a9b6
# ╟─d1e13f8c-7cb8-4cb5-9dda-1a9b5d6142b6
# ╟─a2bb8861-493d-4003-8a4e-e0ea051fbb72
# ╠═411d8975-f67a-4236-b827-c030675db91c
# ╠═b1f255ac-7347-4cf0-8ca1-491713fcb6b0
# ╠═0a363d31-5a48-49ad-aba2-bc0058ce1225
# ╟─da36cf26-8105-4569-9d09-6f16383000a0
# ╠═d009c4cb-9ef6-45bd-960f-0213880f662a
# ╟─b33a9e6b-7c93-475c-96f4-7259b30c2c47
# ╠═aded5964-8229-423c-b9f3-c80358b95fcc
# ╟─6af0b5d7-5324-43b5-8f99-6f5d35d5deba
# ╠═809ceb74-8201-4cc1-8cdc-656dc070e020
# ╟─c222b16b-0ddc-4287-a029-779bdd77dd7b
# ╠═36e48e9c-9452-4b07-bce4-c1cfe3c19409
# ╟─b074a389-bd07-4548-a75c-efa8c7663b15
# ╠═fdf728b9-ade9-46f3-8aaf-cf22aaaa55d2
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002


using BandedMatrices, LinearAlgebra, LazyArrays, Random, Test
    import BandedMatrices: MemoryLayout, SymmetricLayout, HermitianLayout, BandedColumnMajor

@testset "Symmetric" begin
    A = Symmetric(brand(10,10,1,2))
    @test isbanded(A)
    @test BandedMatrix(A) == A
    @test bandwidth(A) == bandwidth(A,1) == bandwidth(A,2) ==  2
    @test bandwidths(A) == bandwidths(BandedMatrix(A)) == (2,2)
    @test MemoryLayout(A) == SymmetricLayout(BandedColumnMajor(), 'U')

    @test A[1,2] == A[2,1]
    @test A[1,4] == 0
    x=rand(10)
    @test A*x ≈ Matrix(A)*x
    @test all(A*x .=== (similar(x) .= Mul(A,x)) .=== (similar(x) .= 1.0.*Mul(A,x) .+ 0.0.*similar(x)) .===
                BLAS.sbmv!('U', 2, 1.0, parent(A).data, x, 0.0, similar(x)))

    A = Symmetric(brand(10,10,1,2),:L)
    @test isbanded(A)
    @test BandedMatrix(A) == A
    @test bandwidth(A) == bandwidth(A,1) == bandwidth(A,2) ==  1
    @test bandwidths(A) == bandwidths(BandedMatrix(A)) == (1,1)
    @test MemoryLayout(A) == SymmetricLayout(BandedColumnMajor(), 'L')

    @test A[1,2] == A[2,1]
    @test A[1,3] == 0
    x=rand(10)
    @test A*x ≈ Matrix(A)*x

    @test all(A*x .=== (similar(x) .= Mul(A,x)) .=== (similar(x) .= 1.0.*Mul(A,x) .+ 0.0.*similar(x)) .===
                BLAS.sbmv!('L', 1, 1.0, view(parent(A).data,3:4,:), x, 0.0, similar(x)))

    # eigvals
    Random.seed!(0)

    A = Symmetric(brand(Float64, 100, 100, 0, 4))
    @test eigvals(A) ≈ eigvals(Symmetric(Matrix(A)))

    # generalized eigvals

    function An(::Type{T}, N::Int) where {T}
        A = Symmetric(BandedMatrix(Zeros{T}(N,N), (0, 2)))
        for n = 0:N-1
            parent(A).data[3,n+1] = T((n+1)*(n+2))
        end
        A
    end

    function Bn(::Type{T}, N::Int) where {T}
        B = Symmetric(BandedMatrix(Zeros{T}(N,N), (0,2)))
        for n = 0:N-1
            parent(B).data[3,n+1] = T(2*(n+1)*(n+2))/T((2n+1)*(2n+5))
        end
        for n = 0:N-3
            parent(B).data[1,n+3] = -sqrt(T((n+1)*(n+2)*(n+3)*(n+4))/T((2n+3)*(2n+5)*(2n+5)*(2n+7)))
        end
        B
    end

    A = An(Float64, 100)
    B = Bn(Float64, 100)

    λ = eigvals(A, B)
    @test λ ≈ eigvals(Symmetric(Matrix(A)), Symmetric(Matrix(B)))

    err = λ*(2/π)^2 ./ (1:length(λ)).^2 .- 1

    @test norm(err[1:40]) < 100eps(Float64)

    A = An(Float32, 100)
    B = Bn(Float32, 100)

    λ = eigvals(A, B)

    @test λ ≈ eigvals(Symmetric(Matrix(A)), Symmetric(Matrix(B)))

    err = λ*(2.f0/π)^2 ./ (1:length(λ)).^2 .- 1

    @test norm(err[1:40]) < 100eps(Float32)
end

@testset "Hermitian" begin
    T = ComplexF64
    A = Hermitian(brand(T,10,10,1,2))
    @test isbanded(A)
    @test BandedMatrix(A) == A
    @test bandwidth(A) == bandwidth(A,1) == bandwidth(A,2) ==  2
    @test bandwidths(A) == bandwidths(BandedMatrix(A)) == (2,2)
    @test MemoryLayout(A) == HermitianLayout(BandedColumnMajor(), 'U')

    @test A[1,2] == conj(A[2,1])
    @test A[1,4] == 0
    x=rand(T,10)
    @test A*x ≈ Matrix(A)*x
    @test all(A*x .=== (similar(x) .= Mul(A,x)) .=== (similar(x) .= T(1.0).*Mul(A,x) .+ T(0.0).*similar(x)) .===
                BLAS.hbmv!('U', 2, T(1.0), parent(A).data, x, T(0.0), similar(x)))

    A = Hermitian(brand(T,10,10,1,2),:L)
    @test isbanded(A)
    @test BandedMatrix(A) == A
    @test bandwidth(A) == bandwidth(A,1) == bandwidth(A,2) ==  1
    @test bandwidths(A) == bandwidths(BandedMatrix(A)) == (1,1)
    @test MemoryLayout(A) == HermitianLayout(BandedColumnMajor(), 'L')

    @test A[1,2] == conj(A[2,1])
    @test A[1,3] == 0
    x=rand(T, 10)
    @test A*x ≈ Matrix(A)*x

    @test all(A*x .=== (similar(x) .= Mul(A,x)) .=== (similar(x) .= one(T).*Mul(A,x) .+ zero(T).*similar(x)) .===
                BLAS.hbmv!('L', 1, one(T), view(parent(A).data,3:4,:), x, zero(T), similar(x)))
end


# Test of reduction on scalar backend

# Unit testing for element-wise expressions on scalar backend

import Devectorize
import Devectorize.@devec
import Devectorize.@inspect_devec
import Devectorize.dump_devec
import Devectorize.sqr

using Base.Test

# data

a = [3., 4., 5., 6., 8., 7., 6., 5.]
b = [9., 8., 7., 6., 4., 2., 3., 1.]
c = [1., 2., 4., 3., 5., 7., 6., 8.]
abc = [a b c]

#################################################
#
#   full reduction
#
#################################################

r = zeros(1)
@devec r = sum(a)
@test isequal(r, sum(a))

@devec r = sum(a[:,:])
@test isequal(r, sum(a))

@devec r = sum(abc[:,:])
@test isequal(r, sum(abc))

@devec r = max(a)
@test isequal(r, max(a))

@devec r = max(c)
@test isequal(r, max(c))

@devec r = min(a)
@test isequal(r, min(a))

@devec r = min(c)
@test isequal(r, min(c))

@devec r = mean(a)
@test isequal(r, mean(a))

@devec r = mean(abc[:,:])
@test isequal(r, mean(abc))

@devec r = dot(a, b)
@test isequal(r, dot(a, b))

@devec r = dot(a[:,:], b[:,:])
@test isequal(r, dot(a, b))

@devec r = dot(abc[:,:], abc)
@test isequal(r, dot(abc[:], abc[:]))

#################################################
#
#   partial reduction
#
#################################################

@devec r = sum(abc, 1)
@test isequal(r, sum(abc, 1))

@devec r = sum(abc, 2)
@test isequal(r, sum(abc, 2))

r = zeros(size(abc, 2))
r0 = r
@devec r[:] = sum(abc, 1)
@test r === r0
@test isequal(r, vec(sum(abc, 1)))

r = zeros(size(abc, 1))
r0 = r
@devec r[:] = sum(abc, 2)
@test r === r0
@test isequal(r, vec(sum(abc, 2)))

@devec r = mean(abc, 1)
@test isequal(r, sum(abc, 1) / size(abc, 1))

@devec r = mean(abc, 2)
@test isequal(r, sum(abc, 2) / size(abc, 2))

@devec r = max(abc, (), 1)
@test isequal(r, max(abc, (), 1))

@devec r = max(abc, (), 2)
@test isequal(r, max(abc, (), 2))

@devec r = min(abc, (), 1)
@test isequal(r, min(abc, (), 1))

@devec r = min(abc, (), 2)
@test isequal(r, min(abc, (), 2))

@devec r = sum(sqr(abc), 1)
@test isequal(r, sum(abc .* abc, 1))

@devec r = sum(sqr(abc), 2)
@test isequal(r, sum(abc .* abc, 2))


"""

    lad(setting; starting_betas = nothing)

Perform Least Absolute Deviations regression for a given regression setting.

# Arguments
- `setting::RegressionSetting`: RegressionSetting object with a formula and dataset.
- `starting_betas::Array{Float64,1}`: Starting values of parameter estimations that fed to local search optimizer.

# Examples
```julia-repl
julia> reg0001 = createRegressionSetting(@formula(calls ~ year), phones);
julia> lad(reg0001)
Dict{Any,Any} with 2 entries:
  "betas"     => [-57.3269, 1.19155]
  "residuals" => [2.14958, 1.25803, 0.0664872, 0.0749413, -0.416605, -0.90815, -1.2997, -1.79124,…

```

"""
function lad(setting::RegressionSetting; starting_betas=nothing)
    X, y = @extractRegressionSetting setting
    return lad(X, y, starting_betas=starting_betas)
end

"""

    lad(X, y; starting_betas = nothing)

Perform Least Absolute Deviations regression for a given regression setting.

# Arguments
- `X::Array{Float64, 2}`: Design matrix of the linear model.
- `y::Array{Float64, 1}`: Response vector of the linear model.
- `starting_betas::Array{Float64,1}`: Starting values of parameter estimations that fed to local search optimizer.

"""
function lad(X::Array{Float64,2}, y::Array{Float64,1}; starting_betas=nothing)
    n, p = size(X)

    if starting_betas isa Nothing
        starting_betas = zeros(Float64, p)
    end

    function goal(betas::Array{Float64,1})::Float64
        sum(abs.(y .- X * betas))
    end

    optim_result = optimize(goal, starting_betas, NelderMead())
    betas = optim_result.minimizer
    residuals = y .- X * betas

    result = Dict()
    result["betas"] = optim_result.minimizer
    result["residuals"] = residuals

    return result
end


using Test
using GLM
using Statistics
using DataFrames
using Calculus

@testset "HWunconstrained.jl" begin

@testset "basics" begin

	@testset "Test Data Construction" begin

		# create data
		d = makeData(18)

		# test dataframe has 18 rows

		# test generated y vector has a mean less than 1
	end

	@testset "Test Return value of likelihood" begin

	end

	@testset "Test return value of gradient is nothing" begin
		# gradient should not return anything,
		# but modify a vector in place.

	end

	@testset "gradient vs finite difference" begin
	end
end

@testset "test maximization results" begin

	ttol = 2e-1

	@testset "maximize returns approximate result" begin
		m = HWunconstrained.maximize_like();
		d = makeData();
		@test maximum(abs,m.minimizer .- d["beta"]) < ttol
	end

	@testset "maximize_grad returns accurate result" begin
		m = HWunconstrained.maximize_like_grad();
		d = makeData();
		@test maximum(abs,m.minimizer .- d["beta"]) < ttol
	end

	@testset "maximize_grad_hess returns accurate result" begin
		m = HWunconstrained.maximize_like_grad_hess();
		d = makeData();
		@test maximum(abs,m.minimizer .- d["beta"]) < ttol
	end

	@testset "gradient is close to zero at max like estimate" begin
		m = HWunconstrained.maximize_like_grad();
		d = makeData()
		gradvec = ones(length(d["beta"]))
		r = HWunconstrained.grad!(gradvec,m.minimizer,d)

		@test r == nothing

		@test maximum(abs,gradvec) < 1e-6

	end

end

@testset "test against GLM" begin

	@testset "estimates vs GLM" begin

	end

	@testset "standard errors vs GLM" begin

	end

end

end


module geometry

export chain, triangle, rectangle, square, hexagonal, box, cube

"""
    geometry.chain(a, N)

Positions of spins on a chain in x-direction.

The chain starts at the origin and continues into positive x-direction.

# Arguments
* `a`: Spin-spin distance.
* `N`: Number of spins
"""
chain(a::Real, N::Int) = [i*Float64[a, 0, 0] for i=0:N-1]

"""
    geometry.triangle(a)

Positions of spins on a equilateral triangle in the xy-plane with edge length `a`.

The positions are: (0,0,0), (a,0,0), (a/2, h, 0)
"""
triangle(a::Real) = Vector{Float64}[[0, 0, 0], [a, 0, 0], [a/2, a*sqrt(3)/2, 0]]

"""
    geometry.rectangle(a, b; Nx=2, Ny=2)

Positions of spins on a rectangular lattice in the xy-plane.

The lattice starts at the origin and continues into positive x and y direction.

# Arguments
* `a`: Spin-spin distance in x-direction.
* `b`: Spin-spin distance in y-direction.
* `Nx=2`: Number of spins into x direction.
* `Ny=2`: Number of spins into y direction.
"""
rectangle(a::Real, b::Real; Nx::Int=2, Ny::Int=2) = vec([i*Float64[a,0.,0.]+j*Float64[0.,b,0.] for i=0:Nx-1, j=0:Ny-1])

"""
    geometry.square(a; Nx=2, Ny=2)

Positions of spins on a square lattice in the xy-plane with distance `a`.

The lattice starts at the origin and continues into positive x and y direction.
"""
square(a::Real; Nx::Int=2, Ny::Int=2) = rectangle(a, a; Nx=Nx, Ny=Ny)

"""
    geometry.hexagonal(a; Nr=1)

Positions of spins on a hexagonal lattice in the xy-plane.

The hexagonal lattice consists of `Nr` rings.
"""
function hexagonal(a::Real; Nr::Int=1)
    positions = Vector{Float64}[]
    ax = sqrt(3.0/4)*a
    for ix=-Nr:Nr
        if isodd(ix)
            Ny = div(2*Nr+1-abs(ix),2)
            for iy=-Ny:Ny-1
                push!(positions, [ax*ix, (0.5+iy)*a, 0])
            end
        else
            Ny = div(2*Nr-abs(ix),2)
            for iy=-Ny:Ny
                push!(positions, [ax*ix, iy*a, 0])
            end
        end
    end
    return positions
end

"""
    geometry.box(a, b, c; Nx=2, Ny=2, Nz=2)

Positions of spins on a orthorhombic lattice.

The lattice starts at the origin and continues into
positive x, y and z direction.
"""
box(a::Real, b::Real, c::Real; Nx::Int=2, Ny::Int=2, Nz::Int=2) = vec([i*Float64[a,0.,0.] + j*Float64[0.,b,0.] + k*Float64[0.,0.,c] for i=0:Nx-1, j=0:Ny-1, k=0:Nz-1])

"""
    geometry.cube(a; Nx=2, Ny=2, Nz=2)

Positions of spins on a cubic lattice with edge length `a`.

The lattice starts at the origin and continues into
positive x, y and z direction.
"""
cube(a::Real; Nx::Int=2, Ny::Int=2, Nz::Int=2) = box(a, a, a, Nx=Nx, Ny=Ny, Nz=Nz)

"""
    geometry.ring(a, N; distance = false)

Ring of N particles with radius a.
If distance is set to true, then `a` gives the distance between particles
and the radius is determined accordingly.
"""
function ring(a::Real, N::Int; distance = false)
    x = distance ? 0.5a/sin(pi/N) : a
    return [[x*cos(2*pi*j/N), x*sin(2*pi*j/N), 0] for j=0:(N-1)]
end

end # module


# Powermethod using Tpetra
# Adapted from https://trilinos.org/docs/dev/packages/tpetra/doc/html/Tpetra_Lesson03.html

using MPI
using Trilinos

function powermethod(A, niters::Number, tolerance::Number)
  q = Tpetra.Vector(Tpetra.getDomainMap(A))
  z = Tpetra.Vector(Tpetra.getRangeMap(A))
  resid = Tpetra.Vector(Tpetra.getRangeMap(A))

  Tpetra.randomize(z)

  lambda = 0.0
  normz = 0.0
  residual = 0.0

  report_frequency = 10

  for iter in 1:niters
    normz = Tpetra.norm2(z)         # Compute the 2-norm of z
    Tpetra.scale(q, 1.0 / normz, z) # q := z / normz
    Tpetra.apply(A, q, z)           # z := A * q
    lambda = dot(q,z)               # Approx. max eigenvalue
    # Compute and report the residual norm every report_frequency
    # iterations, or if we've reached the maximum iteration count.
    if iter % report_frequency == 0 || iter == 1
      Tpetra.update(resid, 1.0, z, -lambda, q, 0.0) # z := A*q - lambda*q
      residual = Tpetra.norm2(resid)                     # 2-norm of the residual vector

      println("Iteration $iter:")
      println("- lambda = $lambda")
      println("- ||A*q - lambda*q||_2 = $residual")
    end
    if residual < tolerance
      println("Converged after $iter iterations")
      break
    elseif (iter == niters)
      println("Failed to converge after $niters iterations")
      break
    end
  end

  return lambda
end

# MPI setup
MPI.Init()
comm = Teuchos.MpiComm(MPI.CComm(MPI.COMM_WORLD))

# Communication map
const num_global_indices = 50
const indexbase = 0
map = Tpetra.Map(num_global_indices, indexbase, comm)

# Number of local elements
my_rank = Teuchos.getRank(comm)
if my_rank != 0
  redirect_stdout(open("/dev/null", "w"))
  redirect_stderr(open("/dev/null", "w"))
end

num_my_elements = Int(Tpetra.getNodeNumElements(map)) # UInt otherwise
println("Number of elements for rank $my_rank is $num_my_elements")

# Construct the matrix
A = Tpetra.CrsMatrix(map, 0)
for local_row in 0:num_my_elements-1
  global_row = Tpetra.getGlobalElement(map, local_row)
  if global_row == 0
    Tpetra.insertGlobalValues(A, global_row, [global_row, global_row+1], [2.0, -1.0])
  elseif global_row == num_my_elements-1
    Tpetra.insertGlobalValues(A, global_row, [global_row-1, global_row], [-1.0, 2.0])
  else
    Tpetra.insertGlobalValues(A, global_row, [global_row-1, global_row, global_row+1], [-1.0, 2.0, -1.0])
  end
end

Tpetra.fillComplete(A)

# Number of iterations
niters = 500
# Target tolerance
tolerance = 1e-2

lambda = powermethod(A, niters, tolerance)
println("\nMaximal estimated eigenvalue: $lambda\n")

println("Increasing magnitude of A(0,0), solving again")

Tpetra.resumeFill(A)

if Tpetra.isNodeGlobalElement(Tpetra.getRowMap(A), 0)
  first_row_id = 0
  num_entries_in_row = Tpetra.getNumEntriesInGlobalRow(A, first_row_id)

  row_values = Vector{Float64}(num_entries_in_row)
  row_indices = Vector{Int}(num_entries_in_row)

  num_entries_returned = Ref(UInt(0))

  # The last 3 parameters here are output parameters. The ArrayView wraps the provided Julia array, which must be at least as large as the number of entries stored
  # num_entries_returned contains the actual number of items that is read
  Tpetra.getGlobalRowCopy(A, first_row_id, Teuchos.ArrayView(row_indices), Teuchos.ArrayView(row_values), num_entries_returned)

  for i in 1:num_entries_returned[]
    if row_indices[i] == first_row_id
      row_values[i] *= 10.0
    end
  end

  # Conversion to ArrayView is implicit here, because the arrays are read-only
  Tpetra.replaceGlobalValues(A, first_row_id, row_indices, row_values)
end

Tpetra.fillComplete(A)

lambda = powermethod(A, niters, tolerance)
println("\nMaximal estimated eigenvalue: $lambda\n")

MPI.Finalize()


################################################
# Unconstrained
################################################
function fitnessToMass(fitness::Vector{Float64})
    m = minimum(fitness)

    if m < 0
        fitness = 2 * abs(m) .+ fitness
    end

    fitness = 2 * maximum(fitness) .- fitness

    return fitness
end

"""
    getMass(U, max_fitness, epsilon)
    get the mass in a vector `m[i] = f[i] + 2*max_fitness*sum_vio[i] `, tolerance
    in equality constraints are given by `epsilon`
"""
getMass(U::Array{xf_indiv,1}) = fitnessToMass(fvals(U))

function getMass(U::Array{xfgh_indiv,1})
    fitness = fvals(U)
    fitnessToMass(fitness + 2maximum(abs.(fitness))*sum_violations.(U))
end

function center(U, mass)
    d = length(U[1].x)

    c = zeros(Float64, d)

    for i = 1:length(mass)
        c += mass[i] .* U[i].x
    end

    return c / sum(mass)
end

function center(U::Array)
    n = length(U)

    mass = getMass(U)

    return center(U, mass),
    argworst(U), # worst
    argbest(U)  # best
end

function getU(P::Array, K::Int, I::Vector{Int}, i::Int, N::Int)
    if i <= N - K
        U_ids = I[i:K+i]
    else
        j = (i:K+i) .% N
        U_ids = I[j.+1]
    end

    return P[U_ids]
end

function getU_ids(K::Int, I::Vector{Int}, i::Int, N::Int)
    if i <= N - K
        U_ids = I[i:K+i]
    else
        j = (i:K+i) .% N
        U_ids = I[j.+1]
    end

    return U_ids
end

function getU_ids(K::Int, I::Vector{Int}, i::Int, N::Int, feasible_solutions)
    # at least half of the population is feasible to generate random centers
    if length(feasible_solutions) >= 0.5N || length(feasible_solutions) == 0
        return getU_ids(K, I, i, N)
    end

    # center of mass is generated with at least one feasible solution
    K -= 1
    if i <= N - K
        U_ids = I[i:K+i]
    else
        j = (i:K+i) .% N
        U_ids = I[j.+1]
    end

    push!(U_ids, rand(feasible_solutions))

    return U_ids
end

function crossover(
    x::Vector{Float64},
    y::Vector{Float64},
    p_cr::Vector{Float64},
)
    D = length(x)
    tmp2 = zeros(D)
    for j = 1:D
        if rand() < p_cr[j]
            y[j] = x[j]
            tmp2[j] += 1
        end
    end

    return y, tmp2
end

"""
    ECA_operator(population, K, η_max)

Compute a solution using ECA variation operator, `K` is the number of solutions used to
calculate the center of mass and `η_max` is the maximum stepsize.
"""
function ECA_operator(
        # population::AbstractArray{xf_indiv}, K, η_max;
        population, K, η_max;
        i = rand(1:length(population)),
        U = rand(population, K),
        bounds = nothing
    )

    x = get_position(population[i])

    # generate center of mass
    c, u_worst, u_best = center(U)

    # stepsize
    η = η_max * rand()

    # u: worst element in U
    u = get_position(U[u_worst])

    y = x .+ η .* (c .- u)
    if isnothing(bounds)
        return y
    end

    !isempty(bounds) && evo_boundary_repairer!(y, c, bounds)

    return y
end


simple_model = @stochastic_model begin
    @stage 1 begin
        @variable(model, x₁ >= 40)
        @variable(model, x₂ >= 20)
        @objective(model, Min, 100*x₁ + 150*x₂)
        @constraint(model, x₁ + x₂ <= 120)
    end
    @stage 2 begin
        @decision x₁ x₂
        @uncertain q₁ q₂ d₁ d₂
        @variable(model, 0 <= y₁ <= d₁)
        @variable(model, 0 <= y₂ <= d₂)
        @objective(model, Min, q₁*y₁ + q₂*y₂)
        @constraint(model, 6*y₁ + 10*y₂ <= 60*x₁)
        @constraint(model, 8*y₁ + 5*y₂ <= 80*x₂)
    end
end

s₁ = Scenario(q₁ = -24.0, q₂ = -28.0, d₁ = 500.0, d₂ = 100.0, probability = 0.4)
s₂ = Scenario(q₁ = -28.0, q₂ = -32.0, d₁ = 300.0, d₂ = 300.0, probability = 0.6)
simple = instantiate(simple_model, [s₁,s₂])

push!(problems, (simple, "Simple"))


using Test, DualNumbers, FinancialMonteCarlo
@show "VarianceGammaProcess"
S0 = 100.0;
K = 100.0;
r = 0.02;
T = 1.0;
d = 0.01;
D = 90.0;

Nsim = 10000;
Nstep = 30;
sigma = dual(0.2, 1.0);
theta1 = 0.01;
k1 = 0.03;
sigma1 = 0.02;
mc = MonteCarloConfiguration(Nsim, Nstep);
mc1 = MonteCarloConfiguration(Nsim, Nstep, FinancialMonteCarlo.AntitheticMC());
toll = 0.8;

rfCurve = ZeroRate(r);

FwdData = Forward(T)
EUData = EuropeanOption(T, K)
AMData = AmericanOption(T, K)
BarrierData = BarrierOptionDownOut(T, K, D)
AsianData = AsianFloatingStrikeOption(T)
Model = VarianceGammaProcess(sigma, theta1, k1, Underlying(S0, d));

@show FwdPrice = pricer(Model, rfCurve, mc, FwdData);
@show EuPrice = pricer(Model, rfCurve, mc, EUData);
@show AmPrice = pricer(Model, rfCurve, mc, AMData);
@show BarrierPrice = pricer(Model, rfCurve, mc, BarrierData);
@show AsianPrice = pricer(Model, rfCurve, mc, AsianData);

@test abs(FwdPrice - 97.94751460264095) < toll
@test abs(EuPrice - 7.738298817933206) < toll
@test abs(BarrierPrice - 6.886023820038332) < toll
@test abs(AsianPrice - 4.414948846776423) < toll

@show FwdPrice = pricer(Model, rfCurve, mc1, FwdData);
@show EuPrice = pricer(Model, rfCurve, mc1, EUData);
@show AmPrice = pricer(Model, rfCurve, mc1, AMData);
@show BarrierPrice = pricer(Model, rfCurve, mc1, BarrierData);
@show AsianPrice = pricer(Model, rfCurve, mc1, AsianData);

@test abs(FwdPrice - 97.94751460264095) < toll
@test abs(EuPrice - 7.738298817933206) < toll
@test abs(BarrierPrice - 6.886023820038332) < toll
@test abs(AsianPrice - 4.414948846776423) < toll


using Pkg
Pkg.activate(".")
using Printf, StatsBase, UnicodePlots
push!(LOAD_PATH,"../src/")
using QuantumComputer

# configurable runner is below this function
function shor_beauregard(n, a, sample_size)
    n_qubit_count = convert(Int64, ceil(log2(n)))
    @printf("factoring %d using fixed a = %d (generalized %d-qubit circuit)\n", n, fixed_a, 2 * n_qubit_count + 3)
    raw_qubits = [1 0]
    for i in 2:n_qubit_count
      raw_qubits = vcat(raw_qubits, [1 0])
    end
    # here we set x to 1
    raw_qubits = vcat(raw_qubits, [0 1])
    for i in 1:(n_qubit_count + 2)
      raw_qubits = vcat(raw_qubits, [1 0])
    end

    qubits = convert(Array{Complex{Float64},2}, raw_qubits)
    classical_register = QuantumComputer.ClassicalRegister(2 * n_qubit_count)
    println("building period finding circuit (this can take a few minutes the first time)...")
    shor = QuantumComputer.Circuits.shor2n3_period_finding(n, a, false)

    @printf("sampling circuit (%d samples)...\n", sample_size)
    samples = Array{Int64,1}(undef, sample_size)
    for i in 1:sample_size
        print(".")
        superposition = QuantumComputer.Superposition(qubits)
        QuantumComputer.apply_circuit_to_superposition!(superposition, shor, classical_register)
        samples[i] = classical_register.value
    end
    println()

    labels = Array{String, 1}(undef, 0);
    vals = Array{Integer, 1}(undef, 0);

    counts = countmap(samples)
    for value in sort([key for key in keys(counts)])
        push!(labels, string(string(value), " |", string(value, base=2, pad=2*n_qubit_count), ">"))
        push!(vals, counts[value])
    end

    println(barplot(labels, vals, xlabel = "samples"))
    println()

    filtered = samples
    if length([k for k in keys(counts)]) > 6
        z = median([count for count in values(counts)]) + 1
        z = √(mean([count for count in values(counts)]))
        @printf("eliminating outliers with counts <= %f\n", z)
        filtered = [sample for sample in samples if counts[sample] > z]
        length(filtered) == 0 && throw(ErrorException("found zero non-trivial factors"))
    end

    println("converting samples to phases and determining probable denominators")
    denominators = [denominator(rationalize(value/(2^(2*n_qubit_count)), tol=1/(2^(2*n_qubit_count-1)))) for value in filtered]

    println("removing odd solutions")
    filtered = [denominator for denominator in denominators if denominator % 2 == 0]
    length(filtered) == 0 && throw(ErrorException("found zero non-trivial factors"))

    labels = Array{String, 1}(undef, 0);
    vals = Array{Integer, 1}(undef, 0);

    counts = countmap(filtered)
    for value in sort([key for key in keys(counts)])
        push!(labels, string(value))
        push!(vals, counts[value])
    end
    println(barplot(labels, vals, xlabel = "samples", ylabel = "r"))
    println()

    t(v) = counts[v]
    candidates = sort([v for v in keys(counts)], by = t, rev = true)

    @printf("trying up to %d candidates\n", n_qubit_count)
    firstn(a, n) = a[intersect(eachindex(a), 1:n)]
    for r in firstn(candidates, n_qubit_count)
        @printf("r = %d\n", r)
        raised_a = convert(BigInt, a)^(r >> 1)
        potential_factors = [gcd(raised_a - 1, n), gcd(raised_a + 1, n)]
        factors = [x for x in potential_factors if !(x in [1, n])]

        length(factors) == 0 && continue
        return first(factors)
    end
    throw(ErrorException("found zero non-trivial factors"))
end

# this one is probably fine. play with n and fixed_a
sample_size = 128

# change these (but beware adding another bit to n will increase circuit complexity and memory required)
n = length(ARGS) > 1 ? parse(Int64, ARGS[1]) : 15
fixed_a = length(ARGS) > 1 ? parse(Int64, ARGS[2]) : 7

if length(ARGS) > 2
    QuantumComputer.gate_set_cache_base(ARGS[3])
end

try
    println()
    x = shor_beauregard(n, fixed_a, sample_size)
    y = convert(Int64, n / x)

    @printf("factored %d into [%d, %d]!\n", n, x, y)
catch
    @printf("zero non-trivial factors of %d found\n", n)
end


# PREAMBLE

# PKG_SETUP

# ## Setup

using LinearAlgebra
using Plots
using DirectTrajectoryOptimization

# ## horizon
T = 101

# ## cartpole
num_state = 4
num_action = 1
num_parameter = 0

function cartpole(x, u, w)
    mc = 1.0
    mp = 0.2
    l = 0.5
    g = 9.81

    q = x[1:2]
    qd = x[3:4]

    s = sin(q[2])
    c = cos(q[2])

    H = [mc+mp mp*l*c; mp*l*c mp*l^2]
    Hinv = 1.0 / (H[1, 1] * H[2, 2] - H[1, 2] * H[2, 1]) * [H[2, 2] -H[1, 2]; -H[2, 1] H[1, 1]]

    C = [0 -mp*qd[2]*l*s; 0 0]
    G = [0, mp*g*l*s]
    B = [1, 0]

    qdd = -Hinv * (C*qd + G - B*u[1])

    return [qd; qdd]
end

function rk3_explicit(x, u, w)
    h = 0.05 # timestep

    k1 = h * cartpole(x, u, w)
    k2 = h * cartpole(x + 0.5 * k1, u, w)
    k3 = h * cartpole(x - k1 + 2.0 * k2, u, w)

    return x + (k1 + 4.0 * k2 + k3) / 6.0
end

function rk3_implicit(y, x, u, w)
    return y - rk3_explicit(x, u, w)
end

# ## model
dt = Dynamics(rk3_implicit, num_state, num_state, num_action,
    num_parameter=num_parameter)
dyn = [dt for t = 1:T-1]

# ## initialization
x1 = [0.0; 0.0; 0.0; 0.0]
xT = [0.0; π; 0.0; 0.0]

# ## objective
Q = 1.0e-2
R = 1.0e-1
Qf = 1.0e2

ot = (x, u, w) -> 0.5 * Q * dot(x - xT, x - xT) + 0.5 * R * dot(u, u)
oT = (x, u, w) -> 0.5 * Qf * dot(x - xT, x - xT)
ct = Cost(ot, num_state, num_action,
    num_parameter=num_parameter)
cT = Cost(oT, num_state, 0,
    num_parameter=num_parameter)
obj = [[ct for t = 1:T-1]..., cT]

# ## constraints
u_bnd = 3.0
bnd1 = Bound(num_state, num_action,
    action_lower=[-u_bnd],
    action_upper=[u_bnd])
bndt = Bound(num_state, num_action,
    action_lower=[-u_bnd],
    action_upper=[u_bnd])
bndT = Bound(num_state, 0)
bounds = [bnd1, [bndt for t = 2:T-1]..., bndT]

cons = [
            Constraint((x, u, w) -> x - x1, num_state, num_action),
            [Constraint() for t = 2:T-1]...,
            Constraint((x, u, w) -> x - xT, num_state, 0)
       ]

# ## problem
p = Solver(dyn, obj, cons, bounds,
    options=Options{Float64}())

# ## initialize
u_guess = [0.01 * ones(num_action) for t = 1:T-1]
x_rollout = [x1]
for t = 1:T-1
    push!(x_rollout, rk3_explicit(x_rollout[end], u_guess[t], zeros(num_parameter)))
end

initialize_states!(p, x_rollout)
initialize_controls!(p, u_guess)

# ## solve
@time solve!(p)

# ## solution
x_sol, u_sol = get_trajectory(p)

@show x_sol[1]
@show x_sol[T]

# ## state
plot(hcat(x_sol...)')

# ## control
plot(hcat(u_sol[1:end-1]..., u_sol[end-1])', linetype = :steppost)


""" Vertical circulation associated with horizontal velocities u, v. """
@inline Γᶠᶠᵃ(i, j, k, grid, u, v) = δxᶠᵃᵃ(i, j, k, grid, Δy_vᶜᶠᵃ, v) - δyᵃᶠᵃ(i, j, k, grid, Δx_uᶠᶜᵃ, u)

""" Vertical vorticity associated with horizontal velocities u, v. """
@inline ζ₃ᶠᶠᵃ(i, j, k, grid, u, v) = Γᶠᶠᵃ(i, j, k, grid, u, v) / Azᶠᶠᵃ(i, j, k, grid)


farmer_model = @stochastic_model begin
    @stage 1 begin
        @parameters begin
            Crops = [:wheat, :corn, :beets]
            Cost = Dict(:wheat=>150, :corn=>230, :beets=>260)
            Budget = 500
        end
        @variable(model, x[c = Crops] >= 0)
        @objective(model, Min, sum(Cost[c]*x[c] for c in Crops))
        @constraint(model, sum(x[c] for c in Crops) <= Budget)
    end
    @stage 2 begin
        @decision x
        @parameters begin
            Purchased  = [:wheat, :corn]
            Sold = [:wheat, :corn, :bquota, :bextra]
            Required = Dict(:wheat=>200, :corn=>240, :beets=>0)
            PurchasePrice = Dict(:wheat=>238, :corn=>210)
            SellPrice = Dict(:wheat=>170, :corn=>150, :bquota=>36, :bextra=>10)
        end
        @uncertain ξ::YieldScenario = begin
            wheat::Float64
            corn::Float64
            beets::Float64
        end
        @variable(model, y[p = Purchased] >= 0)
        @variable(model, w[s = Sold] >= 0)
        @objective(model, Min, sum( PurchasePrice[p] * y[p] for p = Purchased) - sum( SellPrice[s] * w[s] for s in Sold))

        @constraint(model, const_minreq[p=Purchased],
            ξ[p] * x[p] + y[p] - w[p] >= Required[p])
        @constraint(model, const_minreq_beets,
            ξ[:beets] * x[:beets] - w[:bquota] - w[:bextra] >= Required[:beets])
        @constraint(model, const_aux, w[:bquota] <= 6000)
    end
end

ξ₁ = YieldScenario(3.0, 3.6, 24.0, probability = 1/3)
ξ₂ = YieldScenario(2.5, 3.0, 20.0, probability = 1/3)
ξ₃ = YieldScenario(2.0, 2.4, 16.0, probability = 1/3)

farmer = instantiate(farmer_model, [ξ₁,ξ₂,ξ₃])

push!(problems, (farmer, "Farmer"))


using Distributions
using Test
using LinearAlgebra
model = Dynamics.Quadrotor2()
x,u = rand(model)

w0 = MvNormal(Diagonal(ones(6))*0)
model0 = Dynamics.NoisyRB(model, w0)
@test dynamics(model, x, u) ≈ dynamics(model0,x,u)

w1 = MvNormal(Diagonal(ones(6))*1)
model1 = Dynamics.NoisyRB(model, w1)
@test !(dynamics(model, x, u) ≈ dynamics(model1,x,u))


export ruleSPMomentConstraintOutG

"""
Evaluate the expectation ∫q(x; η)g(x)dx for a given eta
"""
function expectedValue(eta::Float64, m_bw::Float64, V_bw::Float64, g::Function)
    Z_tilde = gaussianQuadrature(x->exp(eta*g(x)),              m=m_bw, v=V_bw) # Obtain normalizing constant for constrained q(x)
    G_tilde = gaussianQuadrature(x->exp(eta*g(x))*g(x)/Z_tilde, m=m_bw, v=V_bw) # Evaluate the expectation of g(x) w.r.t. the constrained q(x)

    return G_tilde
end

"""
Obtain the first and second moment of the constrained q(x; η) for a given η
"""
function constrainedMoments(eta::Float64, m_bw::Float64, V_bw::Float64, g::Function)
    Z_tilde    = gaussianQuadrature(x->exp(eta*g(x)),             m=m_bw, v=V_bw) # Obtain normalizing constant for constrained q(x)
    m_tilde    = gaussianQuadrature(x->exp(eta*g(x))*x/Z_tilde,   m=m_bw, v=V_bw) # Compute mean
    V_tilde_nc = gaussianQuadrature(x->exp(eta*g(x))*x^2/Z_tilde, m=m_bw, v=V_bw) # Compute non-central variance
    V_tilde    = V_tilde_nc - m_tilde^2 # Compute central variance

    return (m_tilde, V_tilde)
end

#-------------
# Update rules
#-------------

function ruleSPMomentConstraintOutG(msg_out::Message{<:Gaussian, Univariate}, g::Function, G::Float64, eta_init::Float64)
    (m_bw, V_bw) = unsafeMeanCov(msg_out.dist)
    (xi_bw, W_bw) = unsafeWeightedMeanPrecision(msg_out.dist)

    eta_star = find_zero(eta->expectedValue(eta, m_bw, V_bw, g)-G, eta_init) # Find optimal eta
    (m_tilde, V_tilde) = constrainedMoments(eta_star, m_bw, V_bw, g) # Compute moments of constrained q(x) with optimal eta

    # Convert approximated marginal to canonical form
    W_tilde = inv(V_tilde)
    xi_tilde = W_tilde*m_tilde

    return Message(Univariate, GaussianWeightedMeanPrecision, xi=xi_tilde-xi_bw, w=W_tilde-W_bw) # Compute forward message
end

#---------------------------
# Custom inbounds collectors
#---------------------------

function collectSumProductNodeInbounds(node::MomentConstraint, entry::ScheduleEntry)
    inbounds = Any[]

    interface_to_schedule_entry = current_inference_algorithm.interface_to_schedule_entry
    for node_interface in node.interfaces
        inbound_interface = ultimatePartner(node_interface)
        if isa(inbound_interface.node, Clamp)
            # Hard-code outbound message of constant node in schedule
            push!(inbounds, assembleClamp!(inbound_interface.node, Message))
        else
            # Collect message from previous result
            push!(inbounds, interface_to_schedule_entry[inbound_interface])
        end
    end

    # Push function and value to calling signature; these need to be defined in the scope of the user
    push!(inbounds, Dict{Symbol, Any}(:g => node.g,
                                      :keyword => false))
    push!(inbounds, Dict{Symbol, Any}(:G => node.G,
                                      :keyword => false))
    push!(inbounds, Dict{Symbol, Any}(:eta_init => node.eta_init,
                                      :keyword => false))

    return inbounds
end


const func_to_jamsd = Dict(
:ceil => (1,1),
:floor => (2,1),
:round => (3,1),
:fmod => (4,1),
:trunc => (5,1),
:sign => (6,1),
:min => (7,2),
:max => (8,2),
# :sqr => (9,1),
:exp => (10,1),
:log => (11,1),
:log10 => (12,1),
:sqrt => (13,1),
:abs => (14,1),
:cos => (15,1),
:sin => (16,1),
:atan => (17,1),
:erf => (18,1),
# (dunfm),
# (dnorm),
:^ => (21,2), #power
# (jdate),
# (jtime),
# (jstart),
# (jnow),
# (error),
# (gyear),
# (gmonth),
# (gday),
# (gdow),
# (gleap),
# (ghour),
# (gminute),
# (gsecond),
# (curseed),
# (timest),
# (timeco),
# (timeex),
# (timecl),
:frac => (40,1),
# (errorl),
# (heaps),
# (fact),
# (unfmi),
# (pi),
# (ncpf),
# (ncpcm),
# (entropy),
# (sigmoid),
:log2 => (50,1),
# (boolnot),
# (booland),
# (boolor),
# (boolxor),
# (boolimp),
# (booleqv),
:(==)   => (57,2),  # (relopeq),
:(>)    => (58,2),  # (relopgt),
:(>=)   => (59,2),  # (relopge),
:(<)    => (60,2),  # (reloplt),
:(<=)   => (61,2),  # (relople),
:(!=)   => (62,2),  # (relopne),
:ifelse => (63,3),  # (ifthen),
#   &_rpower => (64,1),
# (edist),
# (div),
# (div0),
# (sllog10),
# (sqlog10),
# (slexp),
# (sqexp),
# (slrec),
# (sqrec),
# (cvpower),
# (vcpower),
# (centropy),
# (gmillisec),
# (maxerror),
# (timeel),
# (gamma),
# (loggamma),
# (beta),
# (logbeta),
# (gammareg),
# (betareg),
:sinh => (86,1),
:cosh => (87,1),
:tanh => (88,1),
# (mathlastrc),
# (mathlastec),
# (mathoval),
# (signpower),
# (handle),
# (ncpvusin),
# (ncpvupow),
# (binomial),
# (rehandle),
# (gamsver),
# (delhandle),
:tan => (100,1),
:acos => (101,1),
:asin => (102,1),
:atan2 => (103,1),
# (sleep),
# (heapf),
# (cohandle),
# (gamsrel),
# (poly),
# (licensestatus),
# (licenselevel),
# (heaplimit),
# (dummy),
)

const sense_to_jamsd = Dict(
:Min => 0,
:Max => 1,
)

const relation_to_jamsd = Dict(
:multiple => -1,
:<=       => 2,
:>=       => 1,
:(==)     => 0,
'='       => 0,
)

const quad_relation_sense = Dict(
'=' => '=',
'<' => :<=,
'>' => :>=
)

const arithm_ops = Dict(
:+ => 2,
:sum => 2,
:- => 3,
:* => 4,
:/ => 5,
)

const reverse_relation = Dict(
:0 => 0,
:1 => 2,
:2 => 1,
:4 => 4,
)

const nary_functions = Set{Symbol}([
:min,
:max,
# :count,
# :numberof,
# :numberofs,
# :and_n,
# :or_n,
# :alldiff,
])


using Revise
using ADCME
using AdFem
using PyPlot
using SparseArrays
using MAT
using Random
Random.seed!(1234)

close("all")

# grid setup
m = 20
n = 20
h = 1/n
bc = bcedge("all", m, n, h)
bd = bcnode("all", m, n, h)
bd_2d = [bd; (m+1)*(n+1) .+ bd]

# time step setup
t = 0;
t_final = 0.01;
NT = 20;
dt = t_final/NT;

# physical constants
ρ = 1.0
μ = Variable(10.0)
ν = μ / ρ

# pre-compute constant matrices
mass_mat = constant(compute_fem_mass_matrix1(m, n, h))
mass_mat_2d = constant(compute_fem_mass_matrix(m, n, h))
mass_mat_2d_bdry, _ = fem_impose_Dirichlet_boundary_condition(mass_mat_2d, bd, m, n, h) # 2D boundary condition
laplace_mat = constant(compute_fem_laplace_matrix1(m, n, h))
laplace_mat_2d = constant(compute_fem_laplace_matrix(m, n, h))
interact_mat = constant(compute_interaction_matrix(m, n, h))
tpfa_mat_bdry, _ = compute_fvm_tpfa_matrix(ones(m*n), bc, zeros(size(bc,1)), m, n, h)
tpfa_mat_bdry = constant(tpfa_mat_bdry)

# exact solutions
function u_exact(x,y,t)
    cos(2*pi*x) * sin(2*pi*y) * exp(-8*pi*pi*ν*t)
    # cos(x) * sin(y) * exp(-2*ν*t)
end

function v_exact(x,y,t)
    -sin(2*pi*x) * cos(2*pi*y) * exp(-8*pi*pi*ν*t)
    # -sin(x) * cos(y) * exp(-2*ν*t)
end

function p_exact(x, y, t, ρ)
    -ρ/4 * (cos(4*pi*x) + cos(4*pi*y)) * exp(-16*pi*pi*ν*t)
    # -ρ/4 * (cos(2*x1) + cos(2*x2)) * exp(-4*ν*t)
end

function step1(U, p0, Source = missing)
    Source = coalesce(Source, zeros(2*(m+1)*(n+1)))
    u0 = U[1:(m+1)*(n+1)]
    v0 = U[(m+1)*(n+1)+1:end]
    u0_gauss = fem_to_gauss_points(u0, m, n, h)
    v0_gauss = fem_to_gauss_points(v0, m, n, h)

    gradu = eval_grad_on_gauss_pts1(u0, m, n, h) # du/dx = gradu[:,1], du/dy = gradu[:,2]
    gradv = eval_grad_on_gauss_pts1(v0, m, n, h) # dv/dx = gradv[:,1], dv/dy = gradv[:,2]

    M1 = mass_mat
    M2 = compute_fem_mass_matrix1(gradu[:,1], m, n, h)
    M3 = compute_fem_advection_matrix1(u0_gauss, v0_gauss, m, n, h)
    M4 = laplace_mat
    A11 = 1/dt * M1 + M2 + M3 + ν * M4

    A12 = compute_fem_mass_matrix1(gradu[:,2], m, n, h)

    A21 = compute_fem_mass_matrix1(gradv[:,1], m, n, h)

    M2 = compute_fem_mass_matrix1(gradv[:,2], m, n, h)
    A22 = 1/dt * M1 + M2 + M3 + ν * M4    # M1, M3, M4 are same as A11

    A = [A11 A12
        A21 A22]

    grad_p = compute_interaction_term(p0, m, n, h) # weak form of [dp/dx; dp/dy] on fem points

    s1 = u0_gauss .* gradu[:,1] + v0_gauss .* gradu[:,2]
    s2 = u0_gauss .* gradv[:,1] + v0_gauss .* gradv[:,2]
    b3 = compute_fem_source_term(s1, s2, m, n, h)

    F = Source + 1/ρ * grad_p - ν * laplace_mat_2d * [u0;v0] - b3

    A, _ = fem_impose_Dirichlet_boundary_condition(A, bd, m, n, h)
    F = scatter_update(F, bd_2d, zeros(length(bd_2d)))

    sol = A \ F
    return sol
end

function step2(U_int)
    rhs = ρ / dt * interact_mat * U_int
    sol = tpfa_mat_bdry \ rhs
    return sol
end

function step3(U_int, dp)
    grad_dp = - compute_interaction_term(dp, m, n, h)
    rhs = mass_mat_2d * U_int - dt/ρ * grad_dp
    rhs = scatter_update(rhs, bd_2d, zeros(length(bd_2d)))
    sol = mass_mat_2d_bdry \ rhs
    return sol
end

# input: U length 2(m+1)(n+1)
# input: p length mn
function solve_ns_one_step(U, p)
    dU = step1(U, p)
    U_int = U + dU
    dp = step2(U_int)
    p_new = p + dp
    U_new = step3(U_int, dp)
    return U_new, p_new
end

function condition(i, velo_arr, p_arr)
    i <= NT + 1
end

function body(i, velo_arr, p_arr)
    velo = read(velo_arr, i-1)
    pres = read(p_arr, i-1)
    # op = tf.print("i=",i)
    # i = bind(i, op)
    velo_new, pres_new = solve_ns_one_step(velo, pres)
    velo_arr = write(velo_arr, i, velo_new)
    p_arr = write(p_arr, i, pres_new)
    return i+1, velo_arr, p_arr
end

# fem nodes
xy = fem_nodes(m, n, h)
x, y = xy[:,1], xy[:,2]
u0 = @.  u_exact(x, y, 0.0)
u0=stack(u0)
v0 = @.  v_exact(x, y, 0.0)
v0=stack(v0)
velo_arr = TensorArray(NT+1)
velo_arr = write(velo_arr, 1, [u0; v0])

# fvm nodes
xy = fvm_nodes(m, n, h)
x, y = xy[:,1], xy[:,2]
p0 = @.  p_exact(x, y, 0.0, ρ)
p0=stack(p0)
p_arr = TensorArray(NT+1)
p_arr = write(p_arr, 1, p0)

i = constant(2, dtype=Int32)

_, velo, p = while_loop(condition, body, [i, velo_arr, p_arr])
velo = set_shape(stack(velo), (NT+1, 2*(m+1)*(n+1)))
p = set_shape(stack(p), (NT+1, m*n))

velo_data = matread("ns_implicit_param_data.mat")["V"]
sample_size=20
idx = rand(1:(m+1)*(n+1), sample_size)
idx = [idx; idx .+ (m+1)*(n+1)] # observe velocity in y direction at the same location
observed_data = velo_data[:,idx]

noise = true
noise_level = 0.1
if noise
    noise_ratio = (1 - noise_level) .+ 2 * noise_level * rand(Float64, size(observed_data)) # uniform on (1-noise_level, 1+noise_level)
    observed_data = observed_data .* noise_ratio
end

loss = mean((velo[:,idx] - observed_data)^2)

max_iter = 100
sess = Session(); init(sess)
loss_ = BFGS!(sess, loss, max_iter)
figure(); semilogy(loss_); title("loss vs. iteration")
savefig(string("loss_ns_viscosity",string(noise_level),".png"))


using EnhancedGJK
using MeshCat
using Polyhedra
using StaticArrays: SVector
using GeometryTypes: HyperSphere, Point

function visualize_simplex(vis::Visualizer, simplex)
    p = polyhedron(vrep(simplex))
    setobject!(vis[:simplex], Polyhedra.Mesh(p))
    for (i, point) in enumerate(simplex)
        setobject!(vis["p$i"], HyperSphere(Point(point), 0.03))
    end
end

vis = Visualizer()
open(vis)
simplex = SVector{4, SVector{3, Float64}}([-3.33728783796428, 0.3321305518800686, 0.11004228580261734], [-3.33728783796428, -0.6678694481199314, 0.11004228580261734], [1.6627121620357201, -0.6678694481199314, 0.11004228580261734], [1.6627121620357201, 0.3321305518800686, 0.11004228580261734])
visualize_simplex(vis, simplex)
@show EnhancedGJK.projection_weights(simplex)


function DiffusionDefinition.diffusion_message(::Val{:Prokaryote})
    println("* * * * * * * * * * ")
    println("Prokaryote diffusion")
    println("* * * * * * * * * * ")
    println("Description")
    println("------------")
    println("Chemical Langevin equation for a simple system describing ")
    println("production of a protein that is repressing its own production.")
    println("It is represented by a 4-dimensional diffusion driven by an ")
    println("8-dimensional Wiener process:")
    println("\ndXₜ=S[θ .× h(Xₜ)]dt + S ⊙ √. ( θ .× h(X_t) ) dWₜ,\n")
    println("where the custom operation ⊙:ℝᵈˣᵏ→ℝᵈˣᵏ is defined via:")
    println("\n(M ⊙ μ)ᵢⱼ = Mᵢⱼμⱼ, i=1,…,d; j=1,…,k,\n")
    println("S is the stoichiometry matrix:")
    println("""S=[
    0   0   1   0   0   0  -1   0
    0   0   0   1  -2   2   0  -1
   -1   1   0   0   1  -1   0   0
   -1   1   0   0   0   0   0   0
]"""
)
    println("and the function h is given by:")
    println("\nh(x) = (x₃x₄, K-x₄, x₄, x₁, x₂(x₂-1)/2, x₃, x₁, x₂)ᵀ.\n")
    println("The underlying process X represents:")
    println("X = (RNA, P, P₂, DNA)")
    println("The model has eight parameters:")
    println("\tc1 c2 c3 c4 c5 c6 c7 c8 K")
    println("The first eight parameters represent the rate of chemical reactions")
    println("The last represent the total number of the DNA strands")
    println("\nConstructors")
    println("------------")
    println("You may initialize the process either by passing the parameters")
    println("in the order above, as in:")
    println("c₁, c₂, c₃, c₄, c₅, c₆, c₇, c₈, K = ...")
    println("P = Prokaryote(c₁, c₂, c₃, c₄, c₅, c₆, c₇, c₈, K)")
    println("or by keyword arguments")
    println("p = (c1=..., c2=..., c3=..., c4=..., c5=..., c6=..., c7=..., c8=..., K=...)")
    println("P = Prokaryote(;p...)")
    println()
end

@diffusion_process Prokaryote{T} begin
    :dimensions
    process --> 4
    wiener --> 8

    :parameters
    c --> (8, T)
    K --> Float64

    :additional
    statespace --> LowerBoundedStateSpace((1,2,3,4), (0.0,1.0,0.0,0.0))
end

function bound_satisfied(P::Prokaryote, x)
    DiffusionDefinition._bound_satisfied(P, x) && (P.K > x[4])
end

@inline _aux₁(x, P::Prokaryote) = P.c5*x[2]*(x[2]-1)
@inline _aux₂(x, P::Prokaryote) = P.c6*x[3]
@inline _aux₃(x, P::Prokaryote) = P.c2*(P.K - x[4])
@inline _aux₄(x, P::Prokaryote) = P.c1*x[3]*x[4]
@inline _aux₁₂(x, P::Prokaryote) = 2.0*_aux₂(x, P) - _aux₁(x, P)
@inline _aux₃₄(x, P::Prokaryote) = _aux₃(x, P) - _aux₄(x, P)

function DiffusionDefinition.b(t, x, P::Prokaryote)
    k1 = _aux₁₂(x, P)
    k2 = _aux₃₄(x, P)
    @SVector [
        P.c3*x[4] - P.c7*x[1],
        P.c4*x[1] + k1 - P.c8*x[2],
        k2 - 0.5 * k1,
        k2
    ]
end

function _σ_prokaryote(x, P)
    k₁ = sqrt(0.5*_aux₁(x, P))
    k₂ = sqrt(_aux₂(x, P))
    k₃ = sqrt(_aux₃(x, P))
    k₄ = sqrt(_aux₄(x, P))
    _O = zero(x[1]) # check if this breaks things for gradients

    @SMatrix [
        _O  _O  sqrt(P.c3*x[4]) _O              _O      _O      -sqrt(P.c7*x[1])    _O;
        _O  _O  _O              sqrt(P.c4*x[1]) -2.0*k₁ 2.0*k₂  _O                  -sqrt(P.c8*x[2]);
        -k₄ k₃  _O              _O              k₁      -k₂     _O                  _O;
        -k₄ k₃  _O              _O              _O      _O      _O                  _O
    ]
end

function _a_prokaryote(x, P)
    k₁ = _aux₁(x, P) + 2.0*_aux₂(x, P)
    k₂ = _aux₃(x, P) + _aux₄(x, P)
    s₁ = P.c3*x[4]+P.c7*x[1]
    s₂ = P.c4*x[1]+P.c8*x[2]
    _O = zero(x[1]) # check if this breaks things for gradients

    @SMatrix [
        s₁  _O      _O          _O;
        _O  s₂+2*k₁ -k₁         _O;
        _O  -k₁     0.5*k₁+k₂   k₂;
        _O  _O      k₂          k₂
    ]
end

DiffusionDefinition.σ(t, x, P::Prokaryote) = _σ_prokaryote(x, P)
DiffusionDefinition.a(t, x, P::Prokaryote) = _a_prokaryote(x, P)


## Define the model
## parameters
γ₁, β₁, λ₁, θ₁, κ = 11.0, 1.0, 12.48, 1.6, 2.6
γ₂, β₂, λ₂, θ₂, xstar = 22.0, 1.0, 16.25, 1.0, 1.6

## state space is {11,10,01,00}
T = [
    -(γ₁ + γ₂) γ₂ γ₁ 0;
    β₂ -(γ₁ + β₂) 0 γ₁;
    β₁ 0 -(γ₂ + β₁) γ₂;
    0 β₁ β₂ -(β₂ + β₂);
    ]

C = [λ₁ - θ₁; λ₁ - θ₁; -θ₁; -θ₁]

r₁₁(x) = (λ₂ - κ) * (x .== 0) +
    (λ₂ - θ₂) * ((x .> 0) .& (x .<= xstar)) +
    λ₂ * (x .> xstar)
r₁₀(x) = -κ * (x .== 0) +
    -θ₂ * ((x .> 0) .& (x .<= xstar))
r₀₁(x) = (λ₂ - κ) * (x .== 0) +
    (λ₂ - θ₂) * ((x .> 0) .& (x .<= xstar)) +
    λ₂ * (x .> xstar)
r₀₀(x) = -κ * (x .== 0) +
    -θ₂ * ((x .> 0) .& (x .<= xstar))

R₁₁(x) = x .* (λ₂ - θ₂) .* ((x .> 0) .& (x .< xstar)) .+
    ((x .- xstar) .* λ₂ .+ xstar .* (λ₂ - θ₂)) .* (x .>= xstar)
R₁₀(x) = x .* -θ₂ * ((x .> 0) .& (x .< xstar)) + xstar .* -θ₂ * (x .>= xstar)
R₀₁(x) = x .* (λ₂ - θ₂) .* ((x .> 0) .& (x .< xstar)) .+
    ((x .- xstar) .* λ₂ .+ xstar .* (λ₂ - θ₂)) .* (x .>= xstar)
R₀₀(x) = x .* -θ₂ * ((x .> 0) .& (x .< xstar)) + xstar .* -θ₂ * (x .>= xstar)

r = (
    r = function (x)
        [r₁₁(x) r₁₀(x) r₀₁(x) r₀₀(x)]
    end,
    R = function (x)
        [R₁₁(x) R₁₀(x) R₀₁(x) R₀₀(x)]
    end,
)

## make models
simBounds = [0 Inf; -Inf Inf] # bounds for simulation only
simModel = SFFM.Model( T, C, r, Bounds = simBounds)
println("created simModel with no upper bound")
println("")

approxBounds = [0 48; -Inf Inf] # bounds for approximation only
approxModel = SFFM.Model( T, C, r, Bounds = approxBounds)
println("Created approxModel with upper bound at x=", approxModel.Bounds[1,end])
println("")


"""
    transform(::YeoJohnson, 𝐱)

Transform an array using Yeo-Johnson method.  The power parameter λ is derived
from maximizing a log-likelihood estimator.
"""
function transform(method::YeoJohnson, 𝐱; optim_args...)
    λ, details = lambda(method, 𝐱; optim_args...)
    @debug "YeoJohnson: estimated lambda = $λ"
    return transform(method, 𝐱, λ)
end

"""
    transform(::YeoJohnson, 𝐱, λ)

Transform an array using Yeo-Johnson method with the provided power parameter λ.
"""
function transform(::YeoJohnson, 𝐱, λ)
    𝐱′ = similar(𝐱, Float64)
    for (i, x) in enumerate(𝐱)
        if x >= 0
            𝐱′[i] = λ ≈ 0 ? log(x + 1) : ((x + 1)^λ - 1)/λ
        else
            𝐱′[i] = λ ≈ 2 ? -log(-x + 1) : -((-x + 1)^(2 - λ) - 1) / (2 - λ)
        end
    end
    return 𝐱′
end

"""
    lambda(::YeoJohnson, 𝐱; interval = (-2.0, 2.0), optim_args...)

Calculate lambda from an array using a log-likelihood estimator.
Keyword arguments:
- interval: search interval
- optim_args: keyword arguments accepted by Optim.optimize function
See also: [`log_likelihood`](@ref)
"""
function lambda(method::YeoJohnson, 𝐱; interval = (-2.0, 2.0), optim_args...)
    i1, i2 = interval
    res = optimize(λ -> -log_likelihood(method, 𝐱, λ), i1, i2; optim_args...)
    return (value=minimizer(res), details=res)
end

"""
    log_likelihood(::YeoJohnson, 𝐱, λ)

Return log-likelihood for the given array and lambda.
"""
function log_likelihood(method::YeoJohnson, 𝐱, λ)
    N = length(𝐱)
    𝐲 = transform(method, float.(𝐱), λ)
    σ² = var(𝐲, corrected = false)
    c = sum(sign.(𝐱) .* log.(abs.(𝐱) .+ 1))
    llf = -N / 2.0 * log(σ²) + (λ - 1) * c
    @debug "YeoJohnson: λ = $λ => σ²=$σ², c=$c, llf=$llf"
    return llf
end


using LinearAlgebra
include("Pauli.jl")
include("BlochRotation.jl")

Hadammard = [ 1  1 ; 1 -1 ];
CNOT12= [1 0 0 0; 0 1 0 0; 0 0 0 1; 0 0 1 0];