using DelaunayMaps
using Test

@testset "DelaunayMaps.jl" begin
    @testset "Tests for triangulation" begin
    @testset "Test function is_closer" begin
        points = [
            0  0
            1  0
            1  1
        ]
        @test DelaunayMaps.is_closer(points, 1, 2, 3) == true
        @test DelaunayMaps.is_closer(points, 1, 3, 2) == false
    end
    @testset "Test function is_aligned" begin
        points = [
            0  0
            1  0
            1  1
            2  0
        ]
        @test DelaunayMaps.is_aligned(points, 1, 2, 3) == false
        @test DelaunayMaps.is_aligned(points, 1, 2, 4) == true
    end
    @testset "Test function is_left_of" begin
        points = [
             0  0
             1  0
             1  1
             1 -1
            -1  1
            -1 -1
             2  0
        ]
        @test DelaunayMaps.is_left_of(points, 1, 2, 3) == true
        @test DelaunayMaps.is_left_of(points, 1, 2, 4) == false
        @test DelaunayMaps.is_left_of(points, 1, 2, 5) == true
        @test DelaunayMaps.is_left_of(points, 1, 2, 6) == false
        @test DelaunayMaps.is_left_of(points, 1, 2, 7) == true
    end
    @testset "Test function is_strictly_left_of" begin
        points = [
             0  0
             1  0
             1  1
             1 -1
            -1  1
            -1 -1
             2  0
        ]
        @test DelaunayMaps.is_strictly_left_of(points, 1, 2, 3) == true
        @test DelaunayMaps.is_strictly_left_of(points, 1, 2, 4) == false
        @test DelaunayMaps.is_strictly_left_of(points, 1, 2, 5) == true
        @test DelaunayMaps.is_strictly_left_of(points, 1, 2, 6) == false
        @test DelaunayMaps.is_strictly_left_of(points, 1, 2, 7) == false
    end
    @testset "Test function is_right_of" begin
        points = [
             0  0
             1  0
             1  1
             1 -1
            -1  1
            -1 -1
             2  0
        ]
        @test DelaunayMaps.is_right_of(points, 1, 2, 3) == false
        @test DelaunayMaps.is_right_of(points, 1, 2, 4) == true
        @test DelaunayMaps.is_right_of(points, 1, 2, 5) == false
        @test DelaunayMaps.is_right_of(points, 1, 2, 6) == true
        @test DelaunayMaps.is_right_of(points, 1, 2, 7) == true
    end
    @testset "Test function is_strictly_right_of" begin
        points = [
             0  0
             1  0
             1  1
             1 -1
            -1  1
            -1 -1
             2  0
        ]
        @test DelaunayMaps.is_strictly_right_of(points, 1, 2, 3) == false
        @test DelaunayMaps.is_strictly_right_of(points, 1, 2, 4) == true
        @test DelaunayMaps.is_strictly_right_of(points, 1, 2, 5) == false
        @test DelaunayMaps.is_strictly_right_of(points, 1, 2, 6) == true
        @test DelaunayMaps.is_strictly_right_of(points, 1, 2, 7) == false
    end

    @testset "Test of Triangle constructors" begin
        points = [
             0  0
             1  0
             1  1
        ]
        @testset "For two vertices" begin
            t = DelaunayMaps.Triangle(1, 2)
            @test t.v3 == 0
            @test t.t1 == t.t2 == t.t3
            next = t.t3
            @test next.v3 == 0
            @test next.v1 == t.v2
            @test next.v2 == t.v1
            @test next.t1 == next.t2 == next.t3 == t
        end
        @testset "For three vertices" begin
            t = DelaunayMaps.Triangle(points, 1, 2, 3)
            # v1 -> v2 -> v3 should be clockwise
            @test t.v1 == 1
            @test t.v2 == 3
            @test t.v3 == 2
            @test DelaunayMaps.is_left_of(points, t.v1, t.v3, t.v2)
            @test t.v1 == 1

            @test t.t1.t3 == t.t2.t3 == t.t3.t3 == t
            @test t.t1.t1 == t.t3.t2 == t.t2
            @test t.t1.t2 == t.t2.t1 == t.t3
            @test t.t2.t2 == t.t3.t1 == t.t1

            @test t.t2.v2 == t.t3.v1 == t.v1
            @test t.t1.v1 == t.t3.v2 == t.v2
            @test t.t1.v2 == t.t2.v1 == t.v3
        end
    end
    @testset "Test is_hull" begin
        points = [
             0  0
             1  0
             1  1
        ]
        t = DelaunayMaps.Triangle(points, 1, 2, 3)
        @test DelaunayMaps.is_hull(t.t1)
        @test !DelaunayMaps.is_hull(t)
    end
    @testset "Test hull navigation function" begin
        points = [
             0  0
             1  0
             1  1
        ]
        t = DelaunayMaps.Triangle(points, 1, 2, 3)
        @test DelaunayMaps.next(t.t1) == t.t2
        @test DelaunayMaps.prev(t.t1) == t.t3
    end
    @testset "Test find_base_tangente" begin
        points = [
             0  0
             1  0
             1  1
             2  0
             3  0
             3  1
        ]
        t1 = DelaunayMaps.Triangle(points, 1, 2, 3)
        t2 = DelaunayMaps.Triangle(points, 4, 5, 6)
        t3 = DelaunayMaps.Triangle(2, 3)
        t4 = DelaunayMaps.Triangle(4, 5)

        t_left, t_right = DelaunayMaps.find_base_tangente(points, t1.t1, t2.t2)
        @test (t_left.v1, t_right.v1) == (2, 4)
    end
    @testset "Test _is_above_base" begin
        @testset "Test _is_above_base_left" begin
            @testset "For two edges triangles" begin
                points = [
                    0 0
                    0 1
                    1 0
                ]
                t = DelaunayMaps.Triangle(1, 2)
                @test DelaunayMaps._is_above_base_left(t, points, 1, 3) == true
                points = [
                    0 -1
                    0 0
                    1 0
                ]
                t = DelaunayMaps.Triangle(2, 1)
                @test DelaunayMaps._is_above_base_left(t, points, 2, 3) == false
            end
            @testset "For classic triangles" begin
                points = [
                    0 0
                    1 2
                    2 1
                    3 0
                ]
                t = DelaunayMaps.Triangle(points, 1, 2, 3)
                @test DelaunayMaps._is_above_base_left(t, points, 1, 4) == true
                points = [
                    0 0
                    1 -1
                    1 -2
                    3 0
                ]
                t = DelaunayMaps.Triangle(points, 1, 2, 3)
                @test DelaunayMaps._is_above_base_left(t, points, 1, 4) == false
                points = [
                    0 1
                    1 0
                    2 0
                    3 0
                ]
                t = DelaunayMaps.Triangle(points, 1, 2, 3)
                @test DelaunayMaps._is_above_base_left(t, points, 3, 4) == true
                points = [
                    0 -1
                    1 0
                    2 0
                    3 0
                ]
                t = DelaunayMaps.Triangle(points, 1, 2, 3)
                @test DelaunayMaps._is_above_base_left(t, points, 3, 4) == false
                points = [
                    0 1
                    1 1
                    2 0
                    3 0
                ]
                t = DelaunayMaps.Triangle(points, 1, 2, 3)
                @test DelaunayMaps._is_above_base_left(t, points, 3, 4) == true
                points = [
                    0 -1
                    1 1
                    2 0
                    3 0
                ]
                t = DelaunayMaps.Triangle(points, 1, 2, 3)
                @test DelaunayMaps._is_above_base_left(t, points, 3, 4) == false
            end
        end
        @testset "Test _is_above_base_right" begin
            @testset "For two edges triangles" begin
                points = [
                    0 0
                    1 0
                    1 1
                ]
                t = DelaunayMaps.Triangle(2, 3)
                @test DelaunayMaps._is_above_base_right(t, points, 1, 2) == true
                points = [
                    0 0
                    1 0
                    1 -1
                ]
                t = DelaunayMaps.Triangle(3, 2)
                @test DelaunayMaps._is_above_base_right(t, points, 1, 2) == false
            end
            @testset "For classic triangles" begin
                points = [
                    0 0
                    1 3
                    2 1
                    3 0
                ]
                t = DelaunayMaps.Triangle(points, 2, 3, 4)
                @test DelaunayMaps._is_above_base_right(t, points, 1, 4) == true
                points = [
                    0 0
                    1 -3
                    2 -2
                    3 0
                ]
                t = DelaunayMaps.Triangle(points, 2, 3, 4)
                @test DelaunayMaps._is_above_base_right(t, points, 1, 4) == false
                points = [
                    0 0
                    1 1
                    2 0
                    3 1
                ]
                t = DelaunayMaps.Triangle(points, 2, 3, 4)
                @test DelaunayMaps._is_above_base_right(t, points, 1, 3) == true
                points = [
                    0 0
                    1 -1
                    2 0
                    3 -1
                ]
                t = DelaunayMaps.Triangle(points, 2, 3, 4)
                @test DelaunayMaps._is_above_base_right(t, points, 1, 3) == false
                points = [
                    0 0
                    1 0
                    2 1
                    3 1
                ]
                t = DelaunayMaps.Triangle(points, 2, 3, 4)
                @test DelaunayMaps._is_above_base_right(t, points, 1, 2) == true
                points = [
                    0 0
                    1 0
                    2 -1
                    3 -1
                ]
                t = DelaunayMaps.Triangle(points, 2, 3, 4)
                @test DelaunayMaps._is_above_base_right(t, points, 1, 2) == false
            end
        end
    end
    @testset "Test _next" begin
        @testset "For two edges triangles" begin
            t = DelaunayMaps.Triangle(1, 2)
            @test DelaunayMaps._next_left(t, 1) == t.t3
            @test DelaunayMaps._next_left(DelaunayMaps._next_left(t, 1), 1) == t
            @test DelaunayMaps._next_right(t, 1) == t.t3
            @test DelaunayMaps._next_right(DelaunayMaps._next_right(t, 1), 1) == t
        end
        @testset "For classic triangles" begin
            points = [
                0 0
                1 0
                1 1
            ]
            t = DelaunayMaps.Triangle(points, 1, 2, 3)
            @test DelaunayMaps._next_left(t, 1) == t.t3
            @test DelaunayMaps._next_left(t, 3) == t.t1
            @test DelaunayMaps._next_left(t, 2) == t.t2
            @test DelaunayMaps._next_right(t, 1) == t.t2
            @test DelaunayMaps._next_right(t, 3) == t.t3
            @test DelaunayMaps._next_right(t, 2) == t.t1
        end
    end

    @testset "I tested every corner case I could think of, but just in case, let's generate 100 graphs and test if they are delaunay 🙈" begin
        for i in 1:100
            m = Map(20, 20, 10)
            delaunay = DelaunayMaps.is_delaunay(m)
            @test delaunay == true
            if !delaunay
                break
            end
        end
    end
end
end


"""
# Summary
    mutable struct ZNorm <: Normalizer
# Fields
    γ        :: VarOrNil                        # scaling params
    β        :: VarOrNil                        # shifting params
    μ        :: Union{AbstractArray,Nothing}    # running average
    σ        :: Union{AbstractArray,Nothing}    # running variance otherwise standard deviation
    views    :: NTuple                          # views to collect elements for mean and var
    training :: Bool                            # if traning or not
    epsilion :: AbstractFloat                   # prevent dividing by zero, 1e-10 for default
    momentum :: AbstractFloat                   # smoothing const, or called historical inertia coefficient

Applies mean and scaling normalization over a N-dimensional input, like BatchNorm LayerNorm and InstanceNorm.

"""
mutable struct ZNorm <: Normalizer
    γ::VarOrNil                        # scaling params
    β::VarOrNil                        # shifting params
    μ::Union{AbstractArray,Nothing}    # running average
    σ::Union{AbstractArray,Nothing}    # running variance otherwise standard deviation
    views::NTuple                      # views to collect elements for mean and var
    training::Bool                     # if traning or not
    epsilion::AbstractFloat            # prevent dividing by zero, 1e-10 for default
    momentum::AbstractFloat            # inertia coefficient
    function ZNorm(;ndims::Int,        # how many dimentions the input data has
                   keptdims::Union{Tuple,Int},     # must be unique and sorted and positive
                   keptsize::Union{Tuple,Int},     # must be positive
                   epsilion::AbstractFloat=1e-10,  # stability const
                   momentum::AbstractFloat=0.900,  # smoothing const or historical inertia
                   type::Type=Array{Float32})

        shape, views = ShapeAndViews(ndims, keptdims, keptsize);
        γ = Variable{type}( Ones(type, shape), true, true, true);
        β = Variable{type}(Zeros(type, shape), true, true, true);
        μ = Zeros(type, shape);
        σ =  Ones(type, shape);
        new(γ, β, μ, σ, views, true, epsilion, momentum)
    end
    function ZNorm(views, training, epsilion, momentum)
        new(nothing, nothing, nothing, nothing, views, training, epsilion, momentum)
    end
end

function clone(this::ZNorm; type::Type=Array{Float32})
    cloned = ZNorm(this.views, this.training, this.epsilion, this.momentum)
    cloned.γ = clone(this.γ, type=type)
    cloned.β = clone(this.β, type=type)
    cloned.μ = type(this.μ)
    cloned.σ = type(this.σ)
    return cloned
end

function Base.show(io::IO, m::ZNorm)
    SIZE = size(m.β.value)
    TYPE = typeof(m.β.value)
    print(io, "ZNorm(statistic vars' size=$SIZE; type=$TYPE)")
end

function paramsof(m::ZNorm)
    params = Vector{Variable}(undef,2)
    params[1] = m.γ
    params[2] = m.β
    return params
end

function xparamsof(m::ZNorm)
    xparams = Vector{XVariable}(undef,2)
    xparams[1] = ('w', m.γ)
    xparams[2] = ('b', m.β)
    return xparams
end

function nparamsof(model::ZNorm)
    return 4*length(model.β)
end

function bytesof(model::ZNorm, unit::String="MB")
    n = nparamsof(model) * sizeof(eltype(model.β))
    return blocksize(n, uppercase(unit))
end

function forward(b::ZNorm, x::Variable{T}) where T
    γ = b.γ
    β = b.β
    ϵ = b.epsilion
    ρ = b.momentum
    v = b.views
    μ = mean(ᵛ(x), dims=v)
    σ =  std(ᵛ(x), dims=v, mean=μ, corrected=false)
    𝐗 = (ᵛ(x) .- μ) ./ (σ .+ ϵ)
    y = Variable{T}(𝐗 .* ᵛ(γ) .+ ᵛ(β), x.backprop)

    if y.backprop
        Σ = σ .* σ
        @. b.μ = ρ * b.μ + (1 - ρ) * μ    # running mean
        @. b.σ = ρ * b.σ + (1 - ρ) * Σ    # running var
        function ZNormBackward()
            if need2computeδ!(x)
                n     = length(σ)/length(x)
                σ¯¹   = 1 ./ (σ .+ ϵ)
                σ¯³   = (σ¯¹).^3
                Δ     = ᵛ(x) .- μ
                ∂𝐋∂𝐗  = δ(y) .* ᵛ(γ)
                Δ∂𝐋∂𝐗 = Δ .* ∂𝐋∂𝐗
                SumΔ∂𝐋∂𝐗  = sum(Δ∂𝐋∂𝐗, dims=v)

                δ(x) .+= σ¯¹ .* ∂𝐋∂𝐗
                δ(x) .-= σ¯³ .* n   .* SumΔ∂𝐋∂𝐗 .* Δ
                δ(x) .+= σ¯³ .* n^2 .* SumΔ∂𝐋∂𝐗 .* sum(Δ, dims=v) .- σ¯¹ .* n .* sum(∂𝐋∂𝐗, dims=v)

                if need2computeδ!(γ) δ(γ) .+= sum(δ(y) .* 𝐗, dims=v) end
                if need2computeδ!(β) δ(β) .+= sum(δ(y),      dims=v) end
            end
            ifNotKeepδThenFreeδ!(y);
        end
        push!(graph.backward, ZNormBackward)
    end
    return y
end

function predict(b::ZNorm, x::AbstractArray)
    ϵ = b.epsilion
    γ = ᵛ(b.γ)
    β = ᵛ(b.β)
    μ = b.μ
    σ = b.σ
    return @. (x - μ) / sqrt(σ + ϵ) * γ + β
end

function BatchNorm1d(nchannels::Int;
            epsilion::AbstractFloat=1e-10,
            momentum::AbstractFloat=0.900,
            type::Type=Array{Float32})
    return ZNorm(ndims=3,
                 keptdims=1,
                 keptsize=nchannels,
                 epsilion=epsilion,
                 momentum=momentum,
                 type=type)
end

function BatchNorm0d(nchannels::Int;
            epsilion::AbstractFloat=1e-10,
            momentum::AbstractFloat=0.900,
            type::Type=Array{Float32})
    return ZNorm(ndims=2,
                 keptdims=1,
                 keptsize=nchannels,
                 epsilion=epsilion,
                 momentum=momentum,
                 type=type)
end


using ConvexTests, ECOS

@info "Starting ECOS tests"

do_tests("ECOS", () -> ECOS.Optimizer(verbose=0); exclude = [r"mip", r"sdp"])


using DSP

"""
    sos = butter2sos(order, Fc, Fs, type=:lowpass)

Z-domain IIR Butterworth design of Lowpass/Highpass/Allpass filters.
`Fc` indicates the corner "-3 dB" natural frequency point, whereas `Fs` indicates
the discrete sample rate and `order` specifies the cascade filter order. The returned
`sos` SecondOrderSection type is compatible with DSP.jl.
"""
function butter2sos(order::Integer, Fc::Real, Fs::Real, type::Symbol=:lowpass)
    (Fc > 0 && Fs > 0) || throw(ArgumentError("Fc and/or Fs must be greater than 0 Hz (DC)."))
    (Fc <= Fs/2) || throw(ArgumentError("Upperband `Fhi` cannot exceed Nyquist."))
    (type ∈ [:lowpass, :highpass, :allpass]) || throw(ArgumentError("type must indicate ':lowpass', ':highpass', or ':allpass.'"))
    Nstages = ceil(Int, order/2)
    matptr = Vector{Float64}(undef, Nstages*6)
    if (type == :lowpass)
        ftype = Int(0)
    elseif (type == :highpass)
        ftype = Int(1)
    else
        ftype = Int(2)
    end
    ccall((:jl_butter, "butterlib"), Cvoid, (Cint, Cdouble, Cdouble, Cint, Ptr{Float64}), order, Fc, Fs, ftype, matptr)
    sosmat = Matrix{Float64}(transpose(reshape(matptr, (6, Nstages))))
    Vb = Vector{Biquad{:z, Float64}}(undef, Nstages)
    K = prod(sosmat[:,1]) # normalize by lead coefficient and extract gain, (whichever ordering.)
    kstg = 1
    for iStg = 1:Nstages
        kstg = sosmat[iStg,1]
        sosmat[iStg,1:3] ./= kstg
        Vb[iStg] = Biquad{:z, Float64}(sosmat[iStg,1], sosmat[iStg,2], sosmat[iStg,3], sosmat[iStg,5], sosmat[iStg,6])
    end
    !any(isnan, sosmat) || error("Filter specs ($(order), $(Fc), $(Fs), $(type)) produced a gain of zero.")
    SecondOrderSections(Vb, K)
end

"""
    sos = butterband(order, Fl, Fh, Fs, type=:bandpass)

Z-domain IIR Butterworth design of Bandpass/Bandstop filters.
`Fl` and `Fh` indicate the lower and upper natural frequency edges,
with `Fs` indicating the discrete sample rate, and `order` specifies
the cascaded Biquads filter order.
"""
function butterband(order::Integer, Fl::Real, Fh::Real, Fs::Real, type::Symbol=:bandpass)
    (Fs > 0) || throw(ArgumentError("Fs sampling rate must be greater than 0 Hz (DC)."))
    (order > 0) || throw(ArgumentError("Filter order must be greater than zero."))
    (type ∈ [:bandpass, :bandstop]) || throw(ArgumentError("type must indicate ':bandpass' or ':bandstop'."))
    if (Fl > Fh)
        Fl, Fh = Fh, Fl
    end
    ((Fl > 0 && Fl < Fs/2) && (Fh > 0 && Fh < Fs/2)) || throw(ArgumentError("Upper/lower bounds must be in range (0, Fs/2]."))
    matptr = Vector{Float64}(undef, order*6)
    ftype = (type == :bandpass) ? Int(0) : Int(1)
    ccall((:jl_butterband, "butterlib"), Cvoid, (Cint, Cdouble, Cdouble, Cdouble, Cint, Ptr{Float64}), order, Fl, Fh, Fs, ftype, matptr)
    sosmat = Matrix{Float64}(transpose(reshape(matptr, (6, order))))
    Vb = Vector{Biquad{:z, Float64}}(undef, order)
    kstg = 1
    K = prod(sosmat[:,1])
    for iStg = 1:order
        kstg = sosmat[iStg,1]
        sosmat[iStg,1:3] ./= kstg
        Vb[iStg] = Biquad{:z, Float64}(sosmat[iStg,1], sosmat[iStg,2], sosmat[iStg,3], sosmat[iStg,5], sosmat[iStg,6])
    end
    !any(isnan, sosmat) || error("Filter specs ($(order), $(Fl), $(Fh), $(Fs), $(type)) produced a gain of zero.")
    SecondOrderSections(Vb,K)
end


""" Category of (possibly infinite) sets and functions.

This module defines generic types for the category of sets ([`SetOb`](@ref),
[`SetFunction`](@ref)), as well as a few basic concrete types, such as a wrapper
type to view Julia types as sets ([`TypeSet`](@ref)). Extensive support for
finite sets is provided by another module, [`FinSets`](@ref).
"""
module Sets
export SetOb, TypeSet, PredicatedSet, SetFunction, ConstantFunction

using AutoHashEquals
using FunctionWrappers: FunctionWrapper

using ...GAT, ..FreeDiagrams, ..Limits
using ...Theories: Category
import ...Theories: dom, codom, id, compose, ⋅, ∘
import ..Limits: limit, universal

# Data types
############

""" Abstract type for object in the category **Set**.

The type parameter `T` is the element type of the set.

Note: This type is more abstract than the built-in Julia types `AbstractSet` and
`Set`, which are intended for data structures for finite sets. Those are
encompassed by the subtype [`FinSet`](@ref).
"""
abstract type SetOb{T} end

Base.eltype(::Type{<:SetOb{T}}) where T = T

""" A Julia data type regarded as a set.
"""
struct TypeSet{T} <: SetOb{T} end

TypeSet(T::Type) = TypeSet{T}()

Base.show(io::IO, ::TypeSet{T}) where T = print(io, "TypeSet($T)")

""" Abstract type for morphism in the category **Set**.

Every instance of `SetFunction{<:SetOb{T},<:SetOb{T′}}` is callable with
elements of type `T`, returning an element of type `T′`.

Note: This type would be better called simply `Function` but that name is
already taken by the base Julia type.
"""
abstract type SetFunction{Dom <: SetOb, Codom <: SetOb} end

SetFunction(f::Function, args...) = SetFunctionCallable(f, args...)
SetFunction(::typeof(identity), args...) = SetFunctionIdentity(args...)

show_type(io::IO, ::Type{<:SetFunction}) = print(io, "SetFunction")

""" Function in **Set** defined by a callable Julia object.
"""
@auto_hash_equals struct SetFunctionCallable{
    T,T′,Dom<:SetOb{T},Codom<:SetOb{T′}} <: SetFunction{Dom,Codom}
  # Field `func` is usually a `Function` but can be any Julia callable.
  func::FunctionWrapper{T′,Tuple{T}}
  dom::Dom
  codom::Codom

  function SetFunctionCallable(f, dom::Dom, codom::Codom) where
      {T,T′,Dom<:SetOb{T},Codom<:SetOb{T′}}
    new{T,T′,Dom,Codom}(FunctionWrapper{T′,Tuple{T}}(f), dom, codom)
  end
end

function (f::SetFunctionCallable{T,T′})(x::T)::T′ where {T,T′}
  f.func(x)
end

function Base.show(io::IO, f::F) where F <: SetFunctionCallable
  func = f.func.obj[] # Deference FunctionWrapper
  show_type(io, F)
  print(io, "($(nameof(func)), $(f.dom), $(f.codom))")
end

""" Identity function in **Set**.
"""
@auto_hash_equals struct SetFunctionIdentity{Dom} <: SetFunction{Dom,Dom}
  dom::Dom
end

function SetFunctionIdentity(dom::SetOb, codom::SetOb)
  dom == codom || error("Domain mismatch in identity function: $dom != $codom")
  SetFunctionIdentity(dom)
end

codom(f::SetFunctionIdentity) = f.dom

(f::SetFunctionIdentity)(x) = x

function Base.show(io::IO, f::F) where F <: SetFunctionIdentity
  show_type(io, F)
  print(io, "(identity, $(f.dom))")
end

""" Function in **Set** taking a constant value.
"""
@auto_hash_equals struct ConstantFunction{T,Value<:T,Dom,Codom<:SetOb{T}} <:
    SetFunction{Dom,Codom}
  value::Value
  dom::Dom
  codom::Codom
end

ConstantFunction(value::T, dom::SetOb) where T =
  ConstantFunction(value, dom, TypeSet{T}())

(f::ConstantFunction)(x) = f.value

# Predicated sets
#################

""" Set defined by a predicate (boolean-valued function) on a Julia data type.
"""
struct PredicatedSet{T} <: SetOb{T}
  predicate::FunctionWrapper{Bool,Tuple{T}}

  PredicatedSet{T}(f) where T = new{T}(FunctionWrapper{Bool,Tuple{T}}(f))
end

PredicatedSet(T::Type, f) = PredicatedSet{T}(f)

function (s::PredicatedSet{T})(x::T)::Bool where {T}
  s.predicate(x)
end

function Base.show(io::IO, s::PredicatedSet{T}) where T
  func = s.predicate.obj[] # Deference FunctionWrapper
  print(io, "PredicatedSet($T, $(nameof(func)))")
end

const PredicatedFunction{T,T′} =
  SetFunctionCallable{T,T′,PredicatedSet{T},PredicatedSet{T′}}

function (f::PredicatedFunction{T,T′})(x::T)::T′ where {T,T′}
  dom(f)(x) || error("Predicate not satisfied on input: $x")
  y = f.func(x)
  codom(f)(y) || error("Predicate not satisfied on output: $y")
  y
end

# Category of sets
##################

""" Category of sets and functions.
"""
@instance Category{SetOb, SetFunction} begin
  dom(f::SetFunction) = f.dom
  codom(f::SetFunction) = f.codom

  id(A::SetOb) = SetFunction(identity, A, A)

  function compose(f::SetFunction, g::SetFunction)
    codom(f) == dom(g) ||
      error("Domain mismatch in composition: $(codom(f)) != $(dom(g))")
    compose_id(f, g)
  end
end

@inline compose_id(f::SetFunction, g::SetFunction) = do_compose(f, g)
@inline compose_id(f::SetFunction, ::SetFunctionIdentity) = f
@inline compose_id(::SetFunctionIdentity, g::SetFunction) = g
@inline compose_id(f::SetFunctionIdentity, ::SetFunctionIdentity) = f

do_compose(f::SetFunction, g::SetFunction) = SetFunction(g∘f, dom(f), codom(g))
do_compose(f::SetFunction, c::ConstantFunction) =
  ConstantFunction(c.value, dom(f), codom(c))
do_compose(c::ConstantFunction, f::SetFunction) =
  ConstantFunction(f(c.value), dom(c), codom(f))
do_compose(c::ConstantFunction, d::ConstantFunction) =
  ConstantFunction(d.value, dom(c), codom(d))

@cartesian_monoidal_instance SetOb SetFunction

# Limits
########

limit(Xs::EmptyDiagram{<:TypeSet}) =
  Limit(Xs, SMultispan{0}(TypeSet(Nothing)))

universal(lim::Terminal{TypeSet{Nothing}}, span::SMultispan{0,<:SetOb}) =
  ConstantFunction(nothing, apex(span), ob(lim))

function limit(Xs::ObjectPair{<:TypeSet})
  X1, X2 = Xs
  X = TypeSet(Tuple{eltype(X1),eltype(X2)})
  π1, π2 = SetFunction(first, X, X1), SetFunction(last, X, X2)
  Limit(Xs, Span(π1, π2))
end

function universal(lim::BinaryProduct{<:TypeSet}, span::Span{<:SetOb})
  f, g = span
  SetFunction(x -> (f(x),g(x)), apex(span), ob(lim))
end

function limit(Xs::DiscreteDiagram{<:TypeSet})
  X = TypeSet(Tuple{map(eltype, Xs)...})
  πs = [ SetFunction(x -> getindex(x, i), X, Xi) for (i, Xi) in enumerate(Xs) ]
  Limit(Xs, Multispan(X, πs))
end

function universal(lim::Product{<:TypeSet}, span::Multispan{<:SetOb})
  @assert length(cone(lim)) == length(span)
  fs = Tuple(legs(span))
  SetFunction(x -> map(f -> f(x), fs), apex(span), ob(lim))
end

function limit(cospan::Multicospan{<:TypeSet})
  eltype(apex(cospan)) == Nothing ? product(feet(cospan)) :
    error("Pullbacks of TypeSets that are not products are not supported")
end

end


#
#      S1 - The manifold of the 1-dimensional sphere represented by angles
#
# Manopt.jl, R. Bergmann, 2018-06-26
import Base: exp, log, show

export Circle, S1Point, S1TVector
export distance, dot, embed, exp, log, manifoldDimension, norm, opposite
export parallelTransport
export parallelTransport, randomMPoint, randomTVector, typeofMPoint, typeofTVector
export zeroTVector
export show, getValue
export validateMPoint, validateTVector

export symRem
# Types
# ---
@doc doc"""
    Circle <: Manifold

The one-dimensional manifold $\mathbb S^1$ represented by angles.
Note that one can also use the $n$-dimensional sphere with $n=1$ to obtain the
same manifold represented by unit vectors in $\mathbb R^2$.

# Abbreviation

`S1`

# Constructor

    Circle()

construct a circle
"""
struct Circle <: Manifold
  name::String
  dimension::Int
  abbreviation::String
  Circle() = new("1-Sphere as angles",1,"S1")
end
@doc doc"""
    S1Point <: MPoint

a point $x\in\mathbb S^1$ represented by an angle `getValue(x)`$\in[-\pi,\pi)$,
usually referred to as “cyclic“ or “phase” data.
"""
struct S1Point <: MPoint
  value::Float64
  S1Point(value::Float64) = new(value)
end
getValue(x::S1Point) = x.value

@doc doc"""
    S1TVector <: TVector

a tangent vector $\xi\in\mathbb S^1$ represented by a real valiue
`getValue(ξ)`$\in\mathbb R$.
"""
struct S1TVector <: TVector
  value::Float64
  S1TVector(value::Float64) = new(value)
end
getValue(ξ::S1TVector) = ξ.value
# Traits
# ---
#(a) S1 is a matrix manifold
@traitimpl IsMatrixM{Circle}
@traitimpl IsMatrixP{S1Point}
@traitimpl IsMatrixTV{S1TVector}

# Functions
# ---

@doc doc"""
    distance(M,x,y)

return the distance two cyclic data items, which is given by
$\lvert (x-y)_{2\pi} \rvert $,
where $(\cdot)_{2\pi}$ is the symmetric remainder modulo $2\pi$,
see [`symRem`](@ref).
"""
distance(M::Circle, x::S1Point,y::S1Point) = abs( symRem(getValue(y) - getValue(x)) )
@doc doc"""
    dot(M,x,ξ,ν)

compute the inner product of two [`S1TVector`](@ref)s in the tangent space $T_x\mathbb S^1$
of the [`S1Point`](@ref) `x`. Since the values are angles, we
obtain $\langle \xi,\nu\rangle_x = \xi\nu$.
"""
dot(M::Circle, x::S1Point, ξ::S1TVector, ν::S1TVector) = getValue(ξ)*getValue(ν)

@doc doc"""
    embed(M,x)

embed the [`Circle`](@ref)` `[`Manifold`](@ref), i.e. turn the [`S1Point`](@ref)
into an [`SnPoint`](@ref) on the manifold [`Sphere`](@ref)`(1)` embedded in
$\mathbb R^2$.
"""
embed(M::Circle,x::S1Point) = SnPoint([cos(getValue(x)),sin(getValue(x))])

@doc doc"""
    exp(M,x,ξ,[t=1.0])

compute the exponential map on the [`Circle`](@ref) $\mathbb S^1$ with
respect to the [`S1Point`](@ref) `x` and the [`S1TVector`](@ref) `ξ`, which can
be shortened with `t` to `tξ`. The formula reads
```math
y = (x+\xi)_{2\pi}
```
where $(\cdot)_{2\pi}$ is the symmetric remainder modulo $2\pi$,
see [`symRem`](@ref).
"""
exp(M::Circle, x::S1Point,ξ::S1TVector,t::Float64=1.0) = S1Point( symRem(getValue(x) + t*getValue(ξ)) )

@doc doc"""
    log(M,x,y)

compute the logarithmic map on the [`Circle`](@ref) $\mathbb S^1$,
i.e., the [`S1TVector`](@ref) `ξ` whose corresponding
[`geodesic`](@ref) starting from [`S1Point`](@ref) `x` reaches the
[`S1Point`](@ref)` y` after time 1. The formula reads

```math
\xi = (y-x)_{2\pi},
```

where $(\cdot)_{2\pi}$ is the symmetric remainder modulo $2\pi$,
see [`symRem`](@ref).
"""
log(M::Circle, x::S1Point,y::S1Point)::S1TVector = S1TVector(symRem( getValue(y) - getValue(x) ))

"""
    manifoldDimension(x)

return the dimension of the manifold the [`S1Point`](@ref) `x` belongs to, i.e.
of the [`Circle`](@ref), which is 1.
"""
manifoldDimension(x::S1Point) = 1

"""
    manifoldDimension(M)

return the dimension of the [`Circle`](@ref) manifold, i.e., 1.
"""
manifoldDimension(M::Circle) = 1

@doc doc"""
    norm(M,x,ξ)

compute the norm of the [`S1TVector`](@ref) `ξ` in the tangent space
$T_x\mathcal M$ at [`S1Point`](@ref) `x` of the
[`Circle`](@ref) $\mathbb S^1$, which is just its absolute value $\lvert\xi\rvert$.
"""
norm(M::Circle, x::S1Point, ξ::S1TVector)::Float64 = abs( getValue(ξ) )

@doc doc"""
    opposite(M,x)

return the antipodal [`S1Point`](@ref) of `x` on the [`Circle`](@ref) `M`,
i.e. $y = (x+\pi)_{2\pi}$.
"""
opposite(M::Circle, x::S1Point) = S1Point( symRem(getValue(x)+π) )
@doc doc"""
    parallelTransport(M,x,y,ξ)

compute the parallel transport of the [`S1TVector`](@ref) `ξ` from the tangent
space $T_x\mathbb S^1$ at the [`S1Point`](@ref) `x` to $T_y\mathbb S^1$
at the [`S1Point`](@ref)` y`.
Since the [`Sphere`](@ref) `M` is represented in angles this is the identity.
"""
parallelTransport(M::Circle, x::S1Point, y::S1Point, ξ::S1TVector) = ξ
@doc doc"""
    randomMPoint(M,:Uniform)

return a random [`S1Point`](@ref) on the [`Circle`](@ref) $\mathbb S^1$ by
picking a random element from $[-\pi,\pi)$ uniformly.
"""
randomMPoint(M::Circle, ::Val{:Uniform}) = S1Point((rand()-0.5)*2*π)
randomMPoint(M::Circle) = randomMPoint(M,Val(:Uniform)) # introduce different default

@doc doc"""
    randomTVector(M,x [,Gaussian,σ=1.0])

returns a random tangent vector from the tangent space of the [`S1Point`](@ref)
 `x` on the [`Circle`](@ref) $\mathbb S^1$ by using a normal distribution with
mean 0 and standard deviation 1.
"""
randomTVector(M::Circle, x::S1Point, ::Val{:Gaussian}, σ::Real=1.0) = S1TVector(σ*randn())

@doc doc"""
    (Ξ,κ) = tangentONB(M,x,ξ)

compute an ONB within the tangent space $T_x\mathcal M$ such that $\xi$ is the
first vector and compute the eigenvalues of the curvature tensor
$R(\Xi,\dot g)\dot g$, where $g=g_{x,\xi}$ is the geodesic with $g(0)=x$,
$\dot g(0) = \xi$, i.e. $\kappa_1$ corresponding to $\Xi_1=\xi$ is zero.

# See also
 [`jacobiField`](@ref), [`adjointJacobiField`](@ref).
"""
tangentONB(M::Circle,x::S1Point,ξ::S1TVector) = [S1TVector(sign(getValue(ξ))==0 ? 1.0 : sign(getValue(ξ)))], [0.]

@doc doc"""
    (Ξ,κ) = tangentONB(M,x,y)

compute an ONB within the tangent space $T_x\mathcal M$ such that $\xi=\log_xy$ is the
first vector and compute the eigenvalues of the curvature tensor
$R(\Xi,\dot g)\dot g$, where $g=g_{x,\xi}$ is the geodesic with $g(0)=x$,
$\dot g(0) = \xi$, i.e. $\kappa_1$ corresponding to $\Xi_1=\xi$ is zero.

# See also
 [`jacobiField`](@ref), [`adjointJacobiField`](@ref).
"""
tangentONB(M::Circle, x::S1Point, y::S1Point) = tangentONB(M,x,log(M,x,y))

typeofTVector(::Type{S1Point}) = S1TVector
typeofMPoint(::Type{S1TVector}) = S1Point

"""
    typicalDistance(M)

returns the typical distance on the [`Circle`](@ref) `M`: π.
"""
typicalDistance(M::Circle) = π/2;

@doc doc"""
    ξ = zeroTVector(M,x)

returns a zero vector in the tangent space $T_x\mathcal M$ of the
[`S1Point`](@ref) $x\in\mathbb S^1$ on the [`Circle`](@ref)` S1`.
"""
zeroTVector(M::Circle, x::S1Point) = S1TVector(  zero( getValue(x) )  );
# Display
# ---
show(io::IO, M::Circle) = print(io, "The manifold S1 consisting of angles");
show(io::IO, x::S1Point) = print(io, "S1($( getValue(x) ))");
show(io::IO, ξ::S1TVector) = print(io, "S1T($( getValue(ξ) ))");
# little Helpers
# ---
@doc doc"""
    symRem(x,[T=π])

symmetric remainder of `x` with respect to the interall 2*`T`, i.e.
`(x+T)%2T`, where the default for `T` is $\pi$
"""
function symRem(x::Float64, T::Float64=Float64(π))::Float64
  return rem(x, 2*T,RoundNearest)
end
@doc doc"""
    validateMPoint(M,x)

validate, that a [`S1Point`](@ref) `x` has a valid value for a point on the
[`Circle`](@ref) `M`$=\mathbb S^1$, i.e. is within $[-\pi,\pi)$.
"""
function validateMPoint(M::Circle, x::S1Point)
    if (getValue(x) < - π) || (getValue(x) >= π) # out of range
        throw( ErrorException(
            "The Point $x is out of range for the Circle represented by anfgles in radians [-π,π),"
        ))
    end
    return true
end
@doc doc"""
    validateTVector(M,x,ξ)

validate, that the [`S1TVector`](@ref) `ξ` is a valid tangent vector in the
tangent space of the [`S1Point`](@ref) `x` ont the [`Circle`](@ref) `M`$=\mathbb S^1$,
though this is always the case since all real values are valid.
"""
function validateTVector(M::Circle,x::S1Point,ξ::S1TVector)
    return true
end


using Plots

function PlotReflectivity()

    ε_0, e, ħ = 8.85418782e-12, 1.60217662e-19, 1.05457e-34

    σ = 3.6e7 # Ω⋅m

    # 減衰小さい場合
    ε(ω, ω_p) = 1 - ω_p^2 / ω^2
    # 減衰大きい場合
    ε_damped(ω) = 1 - (σ) / (im * ω * ε_0)

    ε_actual(ω, ω_p, σ) = 1 - ω_p^2 / (ω^2 + im * ω * ε_0 * ω_p^2 / σ)

    Reflectivity(ε) = abs2((√complex(ε) - 1) / (√complex(ε) + 1))

    x = range(0.01, stop = 2, step = 0.002)

    ħωp_eV = 15.2
    omega_p = ħωp_eV * e / ħ

    println(omega_p)

    ω_range_max = omega_p * 2
    x2 = range(omega_p / 100, stop = ω_range_max, length = 500)

    function reflectivity_nodecay(ω::Float64, ω_p::Float64)
        if ω < ω_p
            1.0
        else
            (2ω^2 - ω_p^2 - 2ω * √(ω^2 - ω_p^2)) / (2ω^2 - ω_p^2 + 2ω * √(ω^2 - ω_p^2))
        end
    end

    reflectivity_largedecay(ω) = (ω^2 * ε_0^2 / σ^2) * (√complex(1 + σ / (im * ω * ε_0)) - 1)^2 * (√complex(1 - σ / (im * ω * ε_0)) - 1)^2

    Plots.display(plot(x, Reflectivity.(ε.(x, 1.0)), label = "Reflectivity, σ = ∞ (no decay)", ylims = (0, 1.5)))
    # Plots.display(plot!(x, reflectivity_nodecay.(x, 1.0), label = "Reflectivity, σ = ∞ (no decay); after simplified"))
    Plots.display(plot!(x2 ./ omega_p, Reflectivity.(ε_damped.(x2)), xlabel = "ω/ω_p", label = "Reflectivity, σ → 0 (large decay)"))
    # Plots.display(plot!(x2 ./ omega_p, reflectivity_largedecay.(x2), label = "Reflectivity, σ → 0 (large decay); after simplified"))

    Plots.display(plot!(x2 ./ omega_p, Reflectivity.(ε_actual.(x2, omega_p, σ)), label = "Reflectivity, theoretical (without approximation)"))
    gui()
    readline()
    savefig("Reflectivity-of-Free-Electron.svg")
    savefig("Reflectivity-of-Free-Electron.png")
end

PlotReflectivity()


#=
Resolution of singularities
=#

using Makie

s = Scene()
θs = -π / 2:0.05:π / 2
r = 2
lines!(zeros(length(θs)), zeros(length(θs)), θs, linewidth = 10)
for θ in θs
    lines!(s, r * [-cos(θ), cos(θ)], r * [-sin(θ), sin(θ)], [θ, θ])
end

x = 0.001:0.001:1.5
y = x.^(3 / 2)
z = y ./ x
lines!(s, x, y, fill(-π / 2, length(x)), linewidth = 5.)
lines!(s, x, y, z, linewidth = 5., color = :blue)

x = 0.001:0.001:1.5
y = -x.^(3 / 2)
z = y ./ x
lines!(s, x, y, fill(-π / 2, length(x)), linewidth = 5.)
lines!(s, x, y, z, linewidth = 5., color = :blue)


using FameSVD
using LinearAlgebra
using Test

@testset "fsvd test for $T" for T in (Float32, Float64)
  A = randn(T, 100, 100)
  x = fsvd(A)

  tol = T == Float32 ? 1e-5 : 1e-10
  @test (norm(A - (x.U * diagm(0 => x.S) * x.V'), 2) / norm(A, 2)) <= tol
end


using Test
using Catlab.Graphs
using Catlab.CategoricalAlgebra
using Catlab.Present
using Catlab.Theories
using Catlab.CategoricalAlgebra.CSetDataStructures: struct_acset

include(joinpath(@__DIR__, "../src/CSetAutomorphisms.jl"))

# Helper functions for writing tests
function xs(x::Int)::Symbol
  return Symbol("x$x")
end
function xs(xx::AbstractVector{Int})::Vector{Symbol}
  return [Symbol("x$x") for x in xx]
end
function es(x::Int)::Symbol
  return Symbol("e$x")
end
function es(xx::AbstractVector{Int})::Vector{Symbol}
  [Symbol("e$x") for x in xx]
end

"""
Create a CSet type specified by a graph
Vertices are x₁,x₂,..., edges are e₁, e₂,...
all edges are indexed
"""
function graph_to_cset(grph::StructACSet, name::Symbol)::StructACSet
  pres = Presentation(FreeSchema)
  xobs = [Ob(FreeSchema,xs(i)) for i in 1:nv(grph)]
  for x in xobs
    add_generator!(pres, x)
  end
  for (i,(src, tgt)) in enumerate(zip(grph[:src], grph[:tgt]))
    add_generator!(pres, Hom(es(i), xobs[src], xobs[tgt]))
  end

  expr = struct_acset(name, StructACSet, pres, index=es(1:ne(grph)))
  eval(expr)
  csettype = eval(name)
  return Base.invokelatest(csettype)
end

"""Create n copies of a CSet based on a graph schema"""
function init_graphs(name::Symbol, schema::StructACSet, consts::Vector{Int},
                     n::Int=2)::Vector{StructACSet}
  cset = graph_to_cset(schema, name)
  for (i, con) in enumerate(consts)
    add_parts!(cset, Symbol("x$i"), con)
  end
  return [deepcopy(cset) for _ in 1:n]
end

"""Confirm canonical hash tracks with whether two ACSets are iso"""
function test_iso(a::StructACSet,b::StructACSet, eq::Bool=true)::Test.Pass
  tst = a -> eq ? a : !a
  @test tst(is_isomorphic(a,b))
  @test a != b  # confirm they're not literally equal
  @test tst(canonical_hash(a) == canonical_hash(b))
end

G,H = Graph(4), Graph(4);
add_edges!(G,[1,2,4,4,3],[2,4,3,3,2]);
add_edges!(H,[2,3,1,4,4],[1,1,4,3,3]);
test_iso(G,H) # 196 automorphisms

Triangle = Graph(3) # f;g = h
add_edges!(Triangle, [1,1,2], [2,3,3]) # f,h,g

G,H = init_graphs(:Tri, Triangle,[2,2,2])
for i in 1:3 set_subpart!(G, Symbol("e$i"), [1,1]) end
for i in 1:3 set_subpart!(H, Symbol("e$i"), [2,2]) end
test_iso(G, H)

Loop = Graph(1)
add_edge!(Loop, 1, 1)
G, H = init_graphs(:Loo, Loop, [3])
set_subpart!(G, Symbol("e1"), [3,2,1])
set_subpart!(H, Symbol("e1"), [1,3,2])
test_iso(G, H)

cyclel, cycler = Graph(3), Graph(3)
add_edges!(cyclel,[1,2,3],[2,3,1])
add_edges!(cycler,[3,2,1],[2,1,3])
test_iso(cyclel, cycler)

# For performance reasons, this no longer is viable.
# Directed example from Hartke and Radcliffe exposition of Nauty
# G, H = Graph(9), Graph(9)
# add_edges!(G,[1,1,2,2,3,3,4,4,5,6,7,8],
#              [7,8,5,6,6,8,5,7,9,9,9,9])
# add_edges!(H,[1,1,3,3,7,7,9,9,2,4,6,8],
#              [2,4,8,6,6,2,4,8,5,5,5,5])
# test_iso(G, H)

Loop2 = Graph(1)
add_edges!(Loop2, [1,1],[1,1])

G,H = init_graphs(:Loo2, Loop2, [2])
set_subpart!(G, :e1, [2,1])
set_subpart!(G, :e2, [2,1])
set_subpart!(H, :e1, [1,1])
set_subpart!(H, :e2, [2,2])
test_iso(G, H, false)

# ACSet tests
@present TheoryDecGraph(FreeSchema) begin
  E::Ob
  V::Ob
  src::Hom(E,V)
  tgt::Hom(E,V)

  X::AttrType
  dec::Attr(E,X)
end

@acset_type Labeled(TheoryDecGraph)

G = @acset Labeled{String} begin
  V = 4
  E = 4
  src = [1,2,3,4]
  tgt = [2,3,4,1]
  dec = ["a","b","c","d"]
end;

H = @acset Labeled{String} begin
  V = 4
  E = 4
  src = [1,3,2,4]
  tgt = [3,2,4,1]
  dec = ["a","b","c","d"]
end;

test_iso(G,H) # vertices permuted

I = @acset Labeled{String} begin
V = 4
E = 4
src = [1,2,3,4]
tgt = [2,3,4,1]
dec = ["b","c","d","a"]
end;

test_iso(G,I) # labels permuted

N = @acset Labeled{String} begin
  V = 4
  E = 4
  src = [1,2,3,4]
  tgt = [2,3,4,1]
  dec = ["a","a","b","c"]
end;

test_iso(G,N, false) # label mismatch

K = @acset Labeled{String} begin
  V = 4
  E = 4
  src = [1,3,2,4]
  tgt = [2,3,4,1]
  dec = ["a","d","b","c"]
end;

test_iso(G,K,false) # vertex mismatch

G1 = @acset Labeled{String} begin
  V = 1
  E = 1
  src = [1]
  tgt = [1]
  dec = ["a"]
end;

H1 = @acset Labeled{String} begin
  V = 1
  E = 1
  src = [1]
  tgt = [1]
  dec = ["b"]
end;

test_iso(G1, H1, false) # label values different


using SyntaxTree, Reduce
using Test
@force using Reduce.Algebra

# write your own tests here
@test showerror(stdout,ReduceError("A Portable General-Purpose Computer Algebra System")) == nothing
@test rcall(:((1+pi)^2)) == convert(Expr,RExpr(rcall("(1+pi)**2")))
@test try; "1/0" |> rcall; false; catch; true; end
@test Reduce.Reset() == nothing
@test display(RExpr("(x+i)^3")) == nothing; print('\n')
@test Reduce._syme(Reduce.r_to_jl) |> typeof == String
@test R"x+2" == R"2+x-1+1"
@test :((x+1+π)^2; int(1/(1+x^3),x)) |> RExpr |> Reduce.parse |> typeof == Expr
@test !Base.process_exited(Reduce.rs)
@test string(R"x+1") |> typeof == String
@test RExpr(:x) == R"x"
@test RExpr(:x)*R"x" == R"x^2"
@test convert(RExpr,R"x").str == convert(Array{String,1},R"x")
@test load_package([:rlfi]) == load_package(:rlfi,:rlfi)
@test show(stdout, R"") == nothing
@test show(stdout,"text/latex",R"int(sinh(e**i*z),z)") == nothing
@test Base.write(Reduce.rs, R"") |> typeof == Int; rcall(1)
@test (x = :(x^2+2x+1); rcall(x,off=[:factor]) == x)
@test rcall("x + 1","factor") == "x + 1"
@test Expr(:function,:fun,:(return begin; x = 700; y = x; end)) |> RExpr |> Reduce.parse |> typeof == Expr
@test Expr(:for,:(i=2:34),:(product(i))) |> rcall |> eval |> typeof == BigInt
@test try; Expr(:type,false,:x) |> RExpr; false; catch; true; end
@test try; :(@time f(x)) |> RExpr; false; catch; true; end
@test (x = Expr(:function,:fun,:(return y=a^3+3*a^2*b+3*a*b^2+b^3)); x==x |> Reduce.factor |> expand)
@test try; Expr(:for,:(i=2:34),:(product(i))) |> RExpr |> Reduce.parse; false; catch; true; end
@test R"begin; 1:2; end" |> Reduce.parse |> RExpr |> string == "1:2 "
@test latex(:(x+1)) |> typeof == String
@test length(:(x+y)) |> typeof == Int
@test log(:(ℯ^x)) == :x
@test nextprime(100) == 101
@test ceiling(1.2) == 2
@test impart(:(1+2*im)) == 2
@test impart(2+1.7im) == 17/10
@test bernoulli(2) == 1/6
@test Reduce.parsegen(:parsetest,:expr) |> typeof == Expr
@test Reduce.parsegen(:calctest,:args) |> typeof == Expr
@test Reduce.parsegen(:switchtest,:switch) |> typeof == Expr
@test Reduce.parsegen(:unarytest,:unary) |> typeof == Expr
Sys.islinux() && @test Reduce.RSymReplace("!#03a9; *x**2 + !#03a9;") |> typeof == String
@test :((x+im+π)^2; int(1/(1+x^3),x)) |> RExpr |> rcall |> Reduce.parse |> typeof == Expr
@test :(int(sin(im*x+pi)^2-1,x)) |> rcall |> typeof == Expr
@test int(:(x^2+y),:x) |> RExpr == int("x^2+y","x") |> RExpr
@test R"/(2,begin 2; +(7,4); return +(4,*(2,7))+9 end)" |> Reduce.parse |> typeof == Expr
@test df(Expr(:function,:fun,:(return begin; zn = z^2+c; nz = z^3-1; end))|>RExpr,:z) |> typeof == RExpr
@test :([1 2; 3 4]) |> RExpr |> Reduce.parse |> RExpr == [1 2; 3 4] |> RExpr
println()
#@test Reduce.repl_init(Base.active_repl)==nothing

@test nextprime("3") == "5"
@test expand("(x-2)^2") |> RExpr == R"(x-2)^2"
@test nat("x+1") |> RExpr == "\nx + 1\n" |> RExpr
@test macroexpand(@__MODULE__(),@factor(:(x^2+2x+1))) == :((x+1)^2)
@test :x^2 == :(x^2)
@test NaN//NaN |> isnan
@test join(split(R"x+1;x+2"))|> string == "x+1;\nx+2"
@test sub(:x=>7,:x+7) == sub([:x=>7,:z=>21],:z-:x)
@test SyntaxTree.sub(Float64,prod((:x-:n)^:n,:n,1,7)|>horner) |> typeof == Expr
@test squash(Expr(:function,:(fun(x)),:(z=3;z+=:x))).args[2] == squash(:(y=:x;y+=3))
@test squash(:(sqrt(x)^2)) == :x
@test Expr(:block,:(x+1)) |> RExpr == R"1+x"
@test limit((1-1/:n)^-:n,:n,Inf) == ℯ
@test log(exp(:pi)) == π
@test 2//Inf == 0
@test Inf//2 == Inf

@test (rcall("x"); Algebra.ws() == :x)
@test (operator(:x); operator(:x); clear(:x); true)
@test try det([:x :y]) catch; true end
@test join([R"1",R"1"]) == R"1;1"
@test list([R"1",R"x"]) == list((1,:x))
@test Reduce.lister(:x) == R"x"
@test !latex(false)
@test (@rounded @factor x^2-2x+1) == :((x-1)^2)
@test (@rounded @off_factor @rcall x^2) == :(x^2)
@test det([:a :b; :c :d]) == :(a*d-b*c)
@test tp([:a;:b]) == [:a :b;]
@test transpose(:x) == :x
@test adjoint(:x) == :(repart(x)-impart(x)*im)
operator(:cbrt)
@test (rlet(:(cbrt(~x))=>:(x^(1/3))); true)
@test (rlet([:(cbrt(~x))=>:(x^(1/3))]); true)
@test (rlet(Dict(:(cbrt(~x))=>:(x^(1/3)))); true)
@test 1+R"x" == :(x+1)
@test inv([:a :b; :c :d]) |> typeof <: Array
@test inv(1) == 1.0
@test [1 2; 3 4]\[1,2] == [0.0,0.5]
@test 1.0//1.0 == 1
@test [:a :b; :c :d]/2 |> typeof <: Array
@test [:a :b; :c :d]+1 == 1+[:a :b; :c :d]
@test [:x] + 1 == 1 + [:x]
@test [:x,:y]' + 1 == 1 + [:x,:y]'
@test solve(:(x-1),:x) == solve((:(x-1),),:x)
@test (order(nothing); korder(nothing); true)
@test (Reduce.@subtype FakeReal <: Real; FakeReal(:(x+1)) + FakeReal(:y) == FakeReal(:(x+1+y)))


"""
    ovlp = overlap(distsq, s, w, dirdot)

Calculates the unnormalized overlap between two Gaussian distributions with width `s`,
weight `w', and squared distance `distsq`, and geometric scaling factor `dirdot`.
"""
function overlap(distsq::Real, s::Real, w::Real, dirdot::Real)
    return w * 0.5*(1+dirdot) * exp(-distsq / (2*s)) # / (sqrt2pi * sqrt(s))^ndims
    # Note, the normalization term for the Gaussians is left out, since it is not required that the total "volume" of each Gaussian
    # is equal to 1 (e.g. satisfying the requirements for a probability distribution)
end

"""
    ovlp = overlap(dist, σx, σy, ϕx, ϕy, dirdot)

Calculates the unnormalized overlap between two Gaussian distributions with variances
`σx` and `σy`, weights `ϕx` and `ϕy`, and means separated by distance `dist`, scaled
by the dot product obtained from geometric constraints `dirdot`.
"""
function overlap(dist::Real, σx::Real, σy::Real, ϕx::Real, ϕy::Real, dirdot::Real)
    return overlap(dist^2, σx^2 + σy^2, ϕx*ϕy, dirdot)
end

"""
    ovlp = overlap(x::IsotropicGaussian, y::IsotropicGaussian, xtform=identity)

Calculates the unnormalized overlap between two `IsotropicGaussian` objects.
"""
function overlap(x::AbstractIsotropicGaussian, y::AbstractIsotropicGaussian, s=nothing, w=nothing)
    if isnothing(s)
        s = x.σ^2 + y.σ^2
    end
    if isnothing(w)
        w = x.ϕ*y.ϕ
    end
    if length(x.dirs) == 0 || length(y.dirs) == 0
        cosγ = 1.
    else
        cosγ = -1
        for xdir in x.dirs
            for ydir in y.dirs
                cosγ = max(cosγ, dot(xdir, ydir))
            end
        end
    end
    return overlap(sum(abs2, x.μ.-y.μ), s, w, cosγ)
end

"""
    ovlp = overlap(x::AbstractSingleGMM, y::AbstractSingleGMM, xtform=identity)

Calculates the unnormalized overlap between two `AbstractSingleGMM` objects.
"""
function overlap(x::AbstractSingleGMM, y::AbstractSingleGMM, pσ=nothing, pϕ=nothing)
    # prepare pairwise widths and weights, if not provided
    if isnothing(pσ) || isnothing(pϕ)
        pσ, pϕ = pairwise_consts(x, y)
    end

    # sum overlaps for all pairwise combinations of Gaussians between x and y
    ovlp = zero(promote_type(numbertype(x),numbertype(y)))
    for (i,gx) in enumerate(x.gaussians)
        for (j,gy) in enumerate(y.gaussians)
            ovlp += overlap(gx, gy, pσ[i,j], pϕ[i,j])
        end
    end
    return ovlp
end

"""
    ovlp = overlap(x::AbstractMultiGMM, y::AbstractMultiGMM, xtform=identity)

Calculates the unnormalized overlap between two `AbstractMultiGMM` objects.
"""
function overlap(x::AbstractMultiGMM, y::AbstractMultiGMM, mpσ=nothing, mpϕ=nothing)
    # prepare pairwise widths and weights, if not provided
    if isnothing(mpσ) || isnothing(mpϕ)
        mpσ, mpϕ = pairwise_consts(x, y)
    end

    # sum overlaps from each keyed pairs of GMM
    ovlp = zero(promote_type(numbertype(x),numbertype(y)))
    for k in keys(x.gmms) ∩ keys(y.gmms)
        ovlp += overlap(x.gmms[k], y.gmms[k], mpσ[k], mpϕ[k])
    end
    return ovlp
end

"""
l2dist = distance(x, y)

Calculates the L2 distance between two GMMs made up of spherical Gaussian distributions.
"""
function distance(x::AbstractGMM, y::AbstractGMM)
    return overlap(x,x) + overlap(y,y) - 2*overlap(x,y)
end

"""
tani = tanimoto(x, y)

Calculates the tanimoto distance based on Gaussian overlap between two GMMs.
"""
function tanimoto(x::AbstractGMM, y::AbstractGMM)
    o = overlap(x,y)
    return o / (overlap(x,x) + overlap(y,y) - o)
end


using Ipopt, ForwardDiff

#=
using ReverseDiff:
    GradientTape,
    GradientConfig,
    gradient,
    gradient!,
    JacobianTape,
    JacobianConfig,
    jacobian,
    jacobian!,
    hessian,
    hessian!,
    HessianTape,
    HessianConfig,
    compile,
    DiffResults
=#
# hs071
# min x1 * x4 * (x1 + x2 + x3) + x3
# st  x1 * x2 * x3 * x4 >= 25
#     x1^2 + x2^2 + x3^2 + x4^2 = 40
#     1 <= x1, x2, x3, x4 <= 5
# Start at (1,5,5,1)
# End at (1.000..., 4.743..., 3.821..., 1.379...)
function F(x)
    x[1] * x[4] * (x[1] + x[2] + x[3]) + x[3]
end

 xzero = [1., 2., 3., 4.]
 FgradTape = GradientTape(F, xzero)
 compFgradTape = compile(FgradTape)
function gradF(x)
    return gradient!(similar(xzero), compFgradTape, x)
end

function G(x)
    [x[1] * x[2] * x[3] * x[4],
     x[1]^2 + x[2]^2 + x[3]^2 + x[4]^2]
end

 GgradTape = [GradientTape(x -> G(x)[1], xzero), GradientTape(x -> G(x)[2], xzero)]
 compGgradTape = [compile(GgradTape[1]), compile(GgradTape[2])]
function jacG(x)
    results = similar(xzero, m, n)
    results[1,:] = gradient!(similar(xzero), compGgradTape[1], x)
    results[2,:] = gradient!(similar(xzero), compGgradTape[2], x)
    return results
end

FhessTape = HessianTape(F, xzero)
compFhessTape = compile(FhessTape)
function hessF(x)
    return hessian!(similar(xzero, n, n), compFhessTape, x)
end

GhessTape = [HessianTape(x -> G(x)[1], xzero), HessianTape(x -> G(x)[2], xzero)]
compGhessTape = [compile(GhessTape[1]), compile(GhessTape[2])]
function vHessG(x)
    return [hessian!(similar(xzero, n, n), compGhessTape[1], x),
            hessian!(similar(xzero, n, n), compGhessTape[2], x)]
end
#we now define the callback function for ipopt
function eval_f(x)
    F(x)
end

function eval_g(x, g)
  # Bad: g    = zeros(2)  # Allocates new array
  # OK:  g[:] = zeros(2)  # Modifies 'in place'
    g[1] = G(x)[1]
    g[2] = G(x)[2]
end

function eval_grad_f(x, grad_f)
  # Bad: grad_f    = zeros(4)  # Allocates new array
  # OK:  grad_f[:] = zeros(4)  # Modifies 'in place'
  grad_f[1] = gradF(x)[1]
  grad_f[2] = gradF(x)[2]
  grad_f[3] = gradF(x)[3]
  grad_f[4] = gradF(x)[4]
end

function eval_jac_g(x, mode, rows, cols, values)
  if mode == :Structure
    # Constraint (row) 1
    rows[1] = 1; cols[1] = 1
    rows[2] = 1; cols[2] = 2
    rows[3] = 1; cols[3] = 3
    rows[4] = 1; cols[4] = 4
    # Constraint (row) 2
    rows[5] = 2; cols[5] = 1
    rows[6] = 2; cols[6] = 2
    rows[7] = 2; cols[7] = 3
    rows[8] = 2; cols[8] = 4
  else
    # Constraint (row) 1
#    values[1:n] = jacG(x)[1,:]
#    values[1] = jacG(x)[1, 1]
#    values[2] = jacG(x)[1, 2]
#    values[3] = jacG(x)[1, 3]
#    values[4] = jacG(x)[1, 4]
    # Constraint (row) 2
#     values[n+1:2n] = jacG(x)[2,:]
#    values[5] = 2*x[1]  # 2,1
#    values[6] = 2*x[2]  # 2,2
#    values[7] = 2*x[3]  # 2,3
#    values[8] = 2*x[4]  # 2,4
      values[:] = vec(transpose(jacG(x)))
  end
end

function eval_h(x, mode, rows, cols, obj_factor, lambda, values)
  if mode == :Structure
    # Symmetric matrix, fill the lower left triangle only
    idx = 1
    for row = 1:4
      for col = 1:row
        rows[idx] = row
        cols[idx] = col
        idx += 1
      end
    end
  else
    # Again, only lower left triangle
    # Objective
    iter1 = 1
    for i in 1:n
        for j in 1:i
            values[iter1] = obj_factor * hessF(x)[i, j]
            iter1 += 1
        end
    end

#    values[:] = vec(hessF(x)[1:])
#    values[1] = obj_factor * (2*x[4])  # 1,1
#    values[2] = obj_factor * (  x[4])  # 2,1
#    values[3] = 0                      # 2,2
#    values[4] = obj_factor * (  x[4])  # 3,1
#    values[5] = 0                      # 3,2
#    values[6] = 0                      # 3,3
#    values[7] = obj_factor * (2*x[1] + x[2] + x[3])  # 4,1
#    values[8] = obj_factor * (  x[1])  # 4,2
#    values[9] = obj_factor * (  x[1])  # 4,3
#    values[10] = 0                     # 4,4
#
    # Both constraints
    iter1 = 1
    for i in 1:n
        for j in 1:i
            values[iter1] += lambda[1] * vHessG(x)[1][i, j] + lambda[2] * vHessG(x)[2][i, j]
            iter1 += 1
        end
    end
    # First constraint
#    values[2] += lambda[1] * (x[3] * x[4])  # 2,1
#    values[4] += lambda[1] * (x[2] * x[4])  # 3,1
#    values[5] += lambda[1] * (x[1] * x[4])  # 3,2
#    values[7] += lambda[1] * (x[2] * x[3])  # 4,1
#    values[8] += lambda[1] * (x[1] * x[3])  # 4,2
#    values[9] += lambda[1] * (x[1] * x[2])  # 4,3
#    for i in 1:n
#        for j in 1:i
#            values[iter1] += lambda[2] * vHessG(x)[i + n, j]
#            iter1 += 1
#        end
#    end

    # Second constraint
#    values[1]  += lambda[2] * 2  # 1,1
#    values[3]  += lambda[2] * 2  # 2,2
#    values[6]  += lambda[2] * 2  # 3,3
#    values[10] += lambda[2] * 2  # 4,4
  end
end

n = 4
x_L = [1.0, 1.0, 1.0, 1.0]
x_U = [5.0, 5.0, 5.0, 5.0]

m = 2
g_L = [25.0, 40.0]
g_U = [2.0e19, 40.0]

function probcall()
prob = createProblem(n, x_L, x_U, m, g_L, g_U, 8, 10,
                     eval_f, eval_g, eval_grad_f, eval_jac_g, eval_h);
prob.x = [1.0, 5.0, 5.0, 1.0];
status = solveProblem(prob);
println(Ipopt.ApplicationReturnStatus[status])
println(prob.x)
println(prob.obj_val)
end
#if @isdefined(obj)
#else
#    obj = Float64[]
#end
#if length(obj) == 1
#else
#   println(obj[length(obj)-1])
#end


# Please run from the `basic_gppp` directory.
using Pkg
Pkg.activate(@__DIR__)
Pkg.instantiate()

using Random, Plots, Stheno
using Stheno: @model, EQ

###########################  Define our model  ###########################

# Define a distribution over f₁, f₂, and f₃, where f₃(x) = f₁(x) + f₂(x).
@model function model()
    f₁ = GP(randn(), EQ())
    f₂ = GP(EQ())
    f₃ = f₁ + f₂
    return f₁, f₂, f₃
end

# Construct the processes in the model.
f₁, f₂, f₃ = model()

# Randomly sample `N₁` and `N₃` locations at which to observe `f₁` and `f₃` respectively.
rng, N₁, N₃ = MersenneTwister(123546), 10, 11
x₁, x₃ = sort(rand(rng, N₁) * 10), sort(rand(rng, N₃) * 10)

# Generate some toy of `f₁` and `f₃`, `y₁` and `y₃` respectively.
y₁, y₃ = rand(rng, [f₁(x₁), f₃(x₃)])

# Compute the posterior processes.
(f₁_post, f₂_post, f₃_post) = (f₁, f₂, f₃) | (f₁(x₁)←y₁, f₃(x₃)←y₃)

# Define some plotting stuff.
Np, S = 500, 25;
xp = range(-2.5, stop=12.5, length=Np);

# Sample jointly from the posterior over each process.
f₁_post_xp, f₂_post_xp, f₃_post_xp =
    rand(rng, [f₁_post(xp, 1e-9), f₂_post(xp, 1e-9), f₃_post(xp, 1e-9)], S);

# Compute posterior marginals.
ms₁ = marginals(f₁_post(xp));
ms₂ = marginals(f₂_post(xp));
ms₃ = marginals(f₃_post(xp));

μf₁′, σf₁′ = mean.(ms₁), std.(ms₁);
μf₂′, σf₂′ = mean.(ms₂), std.(ms₂);
μf₃′, σf₃′ = mean.(ms₃), std.(ms₃);

###########################  Plot results  ###########################

gr();
posterior_plot = plot();

# Plot posterior marginal variances
plot!(posterior_plot, xp, [μf₁′ μf₁′];
    linewidth=0.0,
    fillrange=[μf₁′ .- 3 .* σf₁′, μf₁′ .+ 3 * σf₁′],
    fillalpha=0.3,
    fillcolor=:red,
    label="",
);
plot!(posterior_plot, xp, [μf₂′ μf₂′];
    linewidth=0.0,
    fillrange=[μf₂′ .- 3 .* σf₂′, μf₂′ .+ 3 * σf₂′],
    fillalpha=0.3,
    fillcolor=:green,
    label="",
);
plot!(posterior_plot, xp, [μf₃′ μf₃′];
    linewidth=0.0,
    fillrange=[μf₃′ .- 3 .* σf₃′, μf₃′ .+ 3 * σf₃′],
    fillalpha=0.3,
    fillcolor=:blue,
    label="",
);

# Plot joint posterior samples
plot!(posterior_plot, xp, f₁_post_xp,
    linecolor=:red,
    linealpha=0.2,
    label="",
);
plot!(posterior_plot, xp, f₂_post_xp,
    linecolor=:green,
    linealpha=0.2,
    label="",
);
plot!(posterior_plot, xp, f₃_post_xp,
    linecolor=:blue,
    linealpha=0.2,
    label="",
);

# Plot posterior means
plot!(posterior_plot, xp, μf₁′;
    linecolor=:red,
    linewidth=2.0,
    label="f1",
);
plot!(posterior_plot, xp, μf₂′;
    linecolor=:green,
    linewidth=2.0,
    label="f2",
);
plot!(posterior_plot, xp, μf₃′;
    linecolor=:blue,
    linewidth=2.0,
    label="f3",
);

# Plot observations
scatter!(posterior_plot, x₁, y₁;
    markercolor=:red,
    markershape=:circle,
    markerstrokewidth=0.0,
    markersize=4,
    markeralpha=0.7,
    label="",
);
scatter!(posterior_plot, x₃, y₃;
    markercolor=:blue,
    markershape=:circle,
    markerstrokewidth=0.0,
    markersize=4,
    markeralpha=0.7,
    label="",
);

display(posterior_plot);


include("C:/Users/stravaglino3/Downloads/julia_code/julia_code/model_fit_global.jl")
include("C:/Users/stravaglino3/Downloads/julia_code/julia_code/models.jl")
include("C:/Users/stravaglino3/Downloads/julia_code/julia_code/helper_functions.jl")

using DataFrames

CD3_data = CSV.read("C:/Users/stravaglino3/Downloads/julia_code/CD3_data/dissociations.csv",DataFrame)

delete_missing(x) = [collect(skipmissing(x[:,i])) for i in 1:ncol(x)]

ge = delete_missing(CD3_data[:,2:3])
de = delete_missing(CD3_data[:,8:9])
mix = delete_missing(CD3_data[:,14:15])
gethrml = delete_missing(CD3_data[:,5:5])

scatter(ge[1],ge[2])
scatter(de[1],de[2])
scatter(mix[1],mix[2])

# for each experiment: [forces, lifetimes]

function bin_dissociations(data, n_bins; bin_type = "force")
    F = data[1]
    t = data[2]
    # even force bins
    if bin_type == "force"
        f_min = minimum(F)
        f_max = maximum(F)

        edges = range(f_min,stop=f_max,length=n_bins+1)
        # h = fit(Histogram, F)
        bins,bin_ids = partition(F,edges)
        binned_t = [similar(t,0) for i in bins]
        for (i,bin) in enumerate(bin_ids)
            push!(binned_t[bin],t[i])
        end

    end
    return bins, bin_ids,binned_t
end

function partition(data, intervals)
    ranges = intervals[1:end-1] .=> intervals[2:end]
    bins = [similar(data, 0) for _ in 1:length(ranges)]
    ids = similar(data,0)
    for x in data
        for (i, (a, b)) in pairs(ranges)
            if a <= x < b
                push!(bins[i], x)
                push!(ids,i)
                break
            end
        end
    end
    return bins, Int64.(ids)
end

f_binned, bin_ids, binned_t = bin_dissociations(ge,8)
using StatsBase
t_avg = mean.(binned_t)
f_avg = mean.(f_binned)
plot!(f_avg,t_avg)
histogram(binned_t[5])
    # even events number bins

binned_t_sorted = sort.(binned_t)
sort_id = sortperm.(binned_t)

[f_binned[i][sort_id[i]] for i in 1:length(sort_id)]

survival_p(t) = collect((length(t) :-1:1) / length(t))

ps = (survival_p.(binned_t_sorted))

p2lnp(p) = map((x) -> log.(x), p)
# lnps = map((x) -> log.(x), ps)
plot(binned_t_sorted,lnps)

gethrml_sorted = sort(gethrml)
p_gethrml = survival_p.(gethrml_sorted)
lnp_gethrml = p2lnp(p_gethrml)
plot(gethrml_sorted,lnp_gethrml)

using StatsBase, LinearAlgebra
x = randn(1000)*10
h = fit(Histogram, x)
h.edges
h.weights

normalize(h, mode=:pdf)

plot(h)


using ApproxFun, Test, DualNumbers
    import ApproxFun: eigs

@testset "Extras" begin
    @testset "Dual numbers" begin
        @test dual(1.5,1) ∈  Segment(dual(1.0,1),dual(2.0))

        f=Fun(exp,Segment(dual(1.0,1),dual(2.0)),20)
        @test Fun(h->Fun(exp,Segment(1.0+h,2.0)).coefficients[1],0..1)'(0.) ≈ DualNumbers.epsilon(f.coefficients[1])

        ud=let d= Segment(dual(0.0,1.0),1.0)
            B = ldirichlet(d)
            D = Derivative(d)
            a = Fun(exp,d)
            u = [B;D+dual(1.0,4.0)*a] \ [dual(1.0,2.0),0.0]
            u(0.5)
        end

        u0=let d= Segment(0.0,1.0)
            B = ldirichlet(d)
            D = Derivative(d)
            a = Fun(exp,d)
            u = [B;D+a] \ [1.0,0.0]
            u(0.5)
        end
        h=0.00001
        uh=let d= Segment(h,1.0)
            B = ldirichlet(d)
            D = Derivative(d)
            a = Fun(exp,d)
            u = [B;D+(1+4h)*a] \ [1.0+2h,0.0]
            u(0.5)
        end

        @test absdual(ud - dual(u0,(uh-u0)/h)) ≤ h

        let d=Segment(0.0,1.0)
            B = ldirichlet(d)
            D = Derivative(d)
            a = Fun(exp,d)
            u = [B;D+a] \ [dual(1.0,2.0),0.0]
            ur = [B;D+a] \ [1.0,0.0]
            ud = [B;D+a] \ [2.0,0.0]
            @test absdual(u(0.5)  - dual(ur(0.5),ud(0.5))) < 10eps()
        end
    end

    @testset "Eig test #336" begin
        d = 0..π
        A=Derivative(d)^2
        λ=eigvals(Dirichlet(d),A,100)
        @test sort(λ)[end-5:end] ≈ -(-6:-1).^2
        λ=eigvals(Dirichlet(), A, 100)
        @test sort(λ)[end-5:end] ≈ -(-6:-1).^2

        F = x->x^8
        d = Interval(0.0,1.0)
        f = Fun(F,d)
        ginf = Fun(x->exp(-x),d)
        gp = ginf'
        Af = Fun(x->x+f(x),d)
        transport_ = Fun(x-> x - 1,d)
        damping = Fun(x-> 1 - f(x),d)
        A = transport_*Derivative(d) + damping
        P = -DefiniteIntegral(Chebyshev(d))[LowRankFun((x,y)->gp(x)*(y+f(y)),d^2)];
        λ,V = eigs(A,100)
        @test norm(sort(real(filter(x->isreal(x),λ)))[1:5]-(0:4)) ≤ 100000eps()

        λ,V = eigs(A+P,100)
        @test sort(real(filter(x->isreal(x),λ)))[5] ≈ 3.93759261234502 atol=1E-3
    end

    @testset "findmin/max" begin
        f = Fun(x -> exp(0.25x) + sin(x) + 0.5cos(10x), -4..4)
        @test [findmax(f)...] ≈ [3.0531164509549584, 1.886754631165656]
        @test [findmin(f)...] ≈ [-0.825047261209411, -1.5741041425422948]
    end

    @testset "Newton iteration" begin
        x=Fun(Chebyshev(0..1))

        # Test two coupled systems of nonlinear ODEs with nonlinear BCs
        u1 = 0.5*one(x)
        u2 = 0.5*one(x)

        N_dep = (u1,u2) -> [
                u1(0)*u1(0)*u1(0) - 0.125;
                u2(0) - 1;
                u1' - u1*u1;
                u2' + u1*u1*u2;
            ]
        u1,u2 = newton(N_dep, [u1,u2])

        u1_exact = -1 / (x - 2)
        u2_exact = exp(1 / (x - 2) + 1/2)
        @test norm(u2 - u2_exact) < 1e-15
        @test norm(u1 - u1_exact) < 1e-15

        # Test two independent systems of nonlinear ODEs with nonlinear BCs
        u1 = 0.5*one(x)
        u2 = 0.5*one(x)

        N_ind = (u1,u2) -> [
                u1(0)*u1(0)*u1(0) - 0.125;
                u2(0)*u2(0)*u2(0) + 0.125;
                u1' - u1*u1;
                u2' - u2*u2;
            ]

        # note takes a few more iterations to converge to accuracy
        u1,u2 = newton(N_ind, [u1,u2], maxiterations=25)

        u1_exact = -1 / (x - 2)
        u2_exact = -1 / (x + 2)
        @test norm(u2 - u2_exact) < 1e-15
        @test norm(u1 - u1_exact) < 1e-15

        # Compare to original newton for one equation and verify all solutions correct
        N_single = u -> [u(0)*u(0)*u(0) - 0.125;
                        u' - u*u]

        u = 0.5*one(x)
        u = newton(N_single, [u])
        @test norm(u - u1) < 1e-15

    end
end


# kernel and functions for the 𝐡𝐚𝐫𝐦𝐨𝐧𝐢𝐜 function exp(i ω x)
struct HarmonicIntegral{T, Tpar} <: OscillatoryIntegral{T, Tpar, 1}
    ω::Tpar
end

"""
    HarmonicIntegral{T}(ω)

This constructs a spherical Bessel integration problem which solves
```math
I = \\int_a^b f(x) e^{i \\omega x} \\, dx
```
and produces output of type `{T}`.
"""
HarmonicIntegral{T}(ω::Tpar) where {T, Tpar} = HarmonicIntegral{T,Tpar}(ω)
osc_kernel(oi::HarmonicIntegral{T, Tpar}, x) where {T, Tpar} = 1im * oi.ω
osc_funcs(oi::HarmonicIntegral{T, Tpar}, x) where {T, Tpar} =  # the vector v(x)
    exp(1im * oi.ω * x)

# M=1 case, where the Levin ODE is one-dimensional, like e^iωg(x)
function levintegrate(oi::OscillatoryIntegral{T, Tpar, 1}, f::F, a, b, alg=Vern9();
                      ode_kwargs...) where {T, Tpar, F}
    levin_static(u, p_oi, t) = f(t) - osc_kernel(p_oi, t) * u  # Levin94 eq 2.6
    va = zero(T)  # arbitrary, Levin prescribes no BC
    prob = ODEProblem{false}(levin_static, va, (a, b), oi)  # set up ODE from a to b
    sol = solve(prob, alg, save_start=false, save_everystep=false; ode_kwargs...)
    vb = last(sol)
    return osc_funcs(oi, b) * vb # - osc_funcs(oi, a) * va  # Levin94 eq 2.9, va is 0
end


"""

    Interval unions sets of defined by unions of disjoint intervals.
    This file includes constructors, arithmetic (including intervals and scalars)
    and complement functions

    Empty sets and intersecting intervals are appropriately handled in the constructor:

    julia> a = interval(0,2) ∪ interval(3,4)
    [0, 2] ∪ [3, 4]

    julia> b = interval(1,2) ∪ interval(4,5) ∪ ∅
    [1, 2] ∪ [4, 5]

    julia> c = a * b
    [0, 10] ∪ [12, 20]

    julia> complement(c)
    [-∞, 0] ∪ [10, 12] ∪ [20, ∞]

"""

###
#   IntervalUnion constructor. Consists of a vector of intervals
###
struct IntervalU{T<:Real} <: IntervalUnion{T}
    v :: Array{Interval{T}}
end

###
#   Outer constructors
###
function intervalU(x)
    x = IntervalU(x)
    sort!(x.v)
    x = remove_empties(x)
    x = condense(x)
    closeGaps!(x)
    return x
end

intervalU(num :: Real) = IntervalU([interval(num)])

intervalU(lo :: Real, hi :: Real) = IntervalU([interval(lo,hi)])
IntervalU(lo :: Real, hi :: Real) = IntervalU([interval(lo,hi)])

intervalU(x :: Interval) = IntervalU([x])
∪(x :: Interval) = intervalU(x)

∪(x :: Interval, y :: Interval) = intervalU([x; y])
∪(x :: Array{Interval{T}}) where T <:Real = intervalU(x)

intervalU(x :: Interval, y :: IntervalU) = intervalU([x; y.v])
∪(x :: Interval, y :: IntervalU) = intervalU(x,y)

intervalU(x :: IntervalU, y :: Interval) = intervalU([x.v; y])
∪(x :: IntervalU, y :: Interval) = intervalU(x,y)

intervalM(x :: IntervalU, y :: IntervalU) = intervalU([x.v; y.v])
∪(x :: IntervalU, y :: IntervalU) = intervalU(x,y)

# MultiInterval can act like a vector
getindex(x :: IntervalU, ind :: Integer) = getindex(x.v,ind)
getindex(x :: IntervalU, ind :: Array{ <: Integer}) = getindex(x.v,ind)

# Remove ∅ from IntervalUnion
function remove_empties(x :: IntervalU)
    v = x.v
    Vnew = v[v .!= ∅]
    return IntervalU(Vnew)
end

function closeGaps!(x :: IntervalU, maxInts = MAXINTS[1])

    while length(x.v) > maxInts     # Global

        # Complement code
        v = sort(x.v)
        vLo = left.(v)
        vHi = right.(v)
        vLo[1] == -∞ ? popfirst!(vLo) : pushfirst!(vHi, -∞)
        vHi[end] == ∞ ? pop!(vHi) : push!(vLo, ∞)
        xc = interval.(vHi,vLo)

        # Close smallest width of complement
        widths = diam.(xc)
        d, i = findmin(widths)
        merge = hull(x[i-1], x[i])
        popat!(x.v, i)
        popat!(x.v, i-1)
        push!(x.v, merge)
        sort!(x.v)
    end
    return x
end

# Recursively envolpe intervals which intersect.
function condense(x :: IntervalU)

    if iscondensed(x); return x; end

    v = sort(x.v)
    v = unique(v)

    Vnew = Interval{Float64}[]
    for i =1:length(v)

        intersects = intersect.(v[i],v )
        these = findall( intersects .!= ∅)

        push!(Vnew, hull(v[these]))
    end
    return condense( intervalU(Vnew) )
end

function iscondensed(x :: IntervalU)
    v = sort(x.v)
    for i=1:length(v)
        intersects = findall( intersect.(v[i],v[1:end .!= i]) .!= ∅)
        if !isempty(intersects); return false; end
    end
    return true
end

# Recursively envolpe intervals which intersect, except those which touch
function condense_weak(x :: IntervalU)

    if iscondensed(x); return x; end

    v = sort(x.v)
    v = unique(v)

    Vnew = Interval{Float64}[]
    for i =1:length(v)

        intersects = intersect.(v[i],v )

        notempty = intersects .!= ∅
        isItThin = isthin.(intersects)    # Don't hull intervals which touch

        notEmptyOrThin = notempty .* (1 .- isItThin)
        them = findall(notEmptyOrThin .== 1)

        push!(Vnew, hull(v[them]))
    end
    return condense( intervalU(Vnew) )
end

function iscondensed_weak(x :: IntervalU)
    v = sort(x.v)
    for i=1:length(v)
        intersects = intersect.(v[i],v[1:end .!= i])

        notempty = intersects .!= ∅
        isItThin =  isthin.(intersects)

        notEmptyOrThin = notempty .* (1 .- isItThin)

        if sum(notEmptyOrThin) != 0; return false; end
    end
    return true
end


using Test
using SymDiff

@test differentiate(:(1 + x), :x) == :(0 + 1)
@test differentiate(:(x - 1), :x) == :(1 - 0)


## dcf.jl  Tests related to decision cost functions

using Test
import CSV
import GZip

x = GZip.open("ru.2009.table.gz") do fd
    CSV.read(fd, delim='\t', truestrings=["TRUE"], falsestrings=["FALSE"])
end
tnt = TNT(x)
tar, non = tnt.tar, tnt.non
ruc = roc(tnt, collapse=false)
r = roc(tnt)

## scalar
d = DCF(0.01, 1, 10)            # nist 2008
@test oeff(d) ≈ 1/9.9
@test peff(d) ≈ 1/10.9
@test plo(d) ≈ -log(9.9)
## print
show(stdout, MIME"text/plain"(), d)
println([d,d])

lo = plo(d)
b = 0.03291547867387523
@test ber(x, lo) ≈ b
@test ber(tnt, lo) ≈ b
@test ber(tar, non, lo) ≈ b
@test ber(ruc, lo) ≈ b

m1 = 0.03173675448189074
@test minber(x, lo) ≈ m1
@test minber(tnt, lo) ≈ m1
@test minber(tar, non, lo) ≈ m1
@test minber(r, lo) ≈ m1

## set a global DCF as well
setdcf(cmiss=10)                # evalita 2009

for norm in (false, true)
    c = 0.035877871754524004 * 10^norm
    @test dcf(x, d=d, norm=norm) ≈ c
    @test dcf(tnt, d=d, norm=norm) ≈ c
    @test dcf(tar, non, d=d, norm=norm) ≈ c
    @test dcf(ruc, d=d, norm=norm) ≈ c

    m2 = 0.034593062385260914 * 10^norm
    @test mindcf(x, d=d, norm=norm) ≈ m2
    @test mindcf(tnt, d=d, norm=norm) ≈ m2
    @test mindcf(tar, non, d=d, norm=norm) ≈ m2
    @test mindcf(r, d=d, norm=norm) ≈ m2

    ## global DCF
    c = 0.23094942466561763084 * 2^norm
    @test dcf(x, norm=norm) ≈ c
    @test dcf(tnt, norm=norm) ≈ c
    @test dcf(tar, non, norm=norm) ≈ c
    @test dcf(ruc, norm=norm) ≈ c

    m3 = 0.21843024685287174003 * 2^norm
    @test mindcf(x, norm=norm) ≈ m3
    @test mindcf(tnt, norm=norm) ≈ m3
    @test mindcf(tar, non,norm=norm) ≈ m3
    @test mindcf(ruc, norm=norm) ≈ m3
end

## array
lo = collect(-7:0.01:7)
dc = DCF(sigmoid.(lo), 1, 1)

@test ber(x, lo) ≈ ber(ruc, lo) atol=1e-5
@test ber(tnt, lo) == ber(tar, non, lo)

@test minber(x, lo) == minber(tnt, lo) == minber(tar, non, lo) == minber(r, lo)
@test all(minber(r, lo) .≤ ber(ruc, lo))

for norm in (false, true)
    @test dcf(x, d=dc, norm=norm) ≈ dcf(ruc, d=dc, norm=norm)
    @test dcf(x, d=dc, norm=norm) == dcf(tnt, d=dc, norm=norm) == dcf(tar, non, d=dc, norm=norm)
    @test mindcf(x, d=dc, norm=norm) == mindcf(tnt, d=dc, norm=norm) == mindcf(tar, non, d=dc, norm=norm) == mindcf(r, d=dc, norm=norm)
    @test all(mindcf(r, d=dc, norm=norm) .≤ dcf(ruc, d=dc, norm=norm))
end