"""
    adaptiveHRVFilter(signal::Vector{<:Real}; removeOutliers::Bool = true, replaceNonNormal::Bool = true, adaptiveControllingCoef::Real = 0.05, proportionalLimit::Real = 10/100, controllingFilterCoef::Real = 3, controllingBasicVariability::Real = 20, physiological_values::Tuple{Real, Real} = (200, 2000))

An adaptive filter for HRV data. Filters out nonphysiological beats in the RR series with the ability to replace them.

Based on: Wessel, N., Voss, A., Malberg, H., Ziehmann, Ch., Voss, H. U., Schirdewan, A., Meyerfeldt, U.,Kurths, J.: Nonlinear analysis of complex phenomena in cardiological data, Herzschr. Elektrophys., 11(3), 2000, 159-173, doi:10.1007/s003990070035.

# Input

* signal: HRV in ms

# Keyword Arguments

* removeOutliers: option if nonphysiological outliers shall be removed, defaults to true
* replaceNonNormal: if true, non normal HRV values are replaced with a random value sampled from an adaptive interval to not falsely decrease variability.
* physiological_values: Definition of the physiological value range, scheme: (min, max), defaults to (200, 2000)
* adaptiveControllingCoef: Controlling coefficient for the adaptive mean, ∈ [0,1] and defaults to 0.05
* proportionalLimit: Proportional limit for the exclusion rule, defaults to 10%
* controllingFilterCoef: Filter coefficient in the final controlling procedure , defaults to 3
* controllingBasicVariability: Basic variability in the controlling procedure to reduce filtering errors for time series with low variability, defaults to 20 ms

# Return
Return value depends on keyword args

```julia
julia> adaptiveHRVFilter(signal)
```
"""
function adaptiveHRVFilter(signal::Vector{<:Real}; removeOutliers::Bool = true, replaceNonNormal::Bool = true, adaptiveControllingCoef::Real = 0.05, proportionalLimit::Real = 10/100, controllingFilterCoef::Real = 3, controllingBasicVariability::Real = 20, physiological_values::Tuple{Real, Real} = (200, 2000))
    # some input checking
    0 < adaptiveControllingCoef < 1 || throw(DomainError("adaptiveControllingCoef has to be in range 0:1."))
    physiological_values[1] < physiological_values[2] || throw(DomainError("physiological_values is a tuple with minumum first and maximum second, is: $physiological_values."))
    # TODO

    # Remove values that are out of range of Physiological Heart beats
    if removeOutliers
        ok = (physiological_values[1] .<= signal .<= physiological_values[2])
        removed_nonphysiological_outliers = .!ok
        signal = signal[ok]
        @assert length(signal) >= 1 "Signal contains only outliers, recheck parameters."
    end
    # Calculate helper matrixes for the Binominal Filter
    bin_coeff = [1; 6; 15; 20; 15; 6; 1]
    coeff_sum = sum(bin_coeff)
    # normalize filter coefs
    bin_coeff =  bin_coeff .* (1/coeff_sum)
    coeff_count = length(bin_coeff)
    @assert (mod(coeff_count, 2) == 1) "Nono"

    # signalLen = length(signal)
    # filter_values = repeat(bin_coeff', signalLen)'
    # value_index_column = (1:coeff_count) .- Int(ceil(coeff_count/2))
    # hrv_ind_offset_val = repeat(value_index_column', signalLen)
    # hrv_ind_base_val = repeat(collect(1:signalLen)', coeff_count)'

    # #The first and last elements will be out of range work around by just (re-)using first and last elements...
    # hrv_idx_mat = hrv_ind_offset_val .+ hrv_ind_base_val
    # replace!(x -> x <=0 ? 1 : x, hrv_idx_mat)
    # replace!(x -> x > signalLen ? signalLen : x, hrv_idx_mat)

    # Calculate first filtered Signal (Through Binominal Filter)
    # filtered_signal = vec(sum(signal[hrv_idx_mat] .* filter_values', dims = 2))
    filtered_signal = filtfilt(bin_coeff, signal)

    # Apply "Adaptive Percent Filter" => fixed_hrv_timeseries
    adaptive_mean, adaptive_sigma = adaptive_moments(filtered_signal, coeff_count, adaptiveControllingCoef)
    adaptive_sigma_mean = mean(adaptive_sigma)

    last_good_value = signal[1] #Maybe filtered_signal(1)
    last_good_range = proportionalLimit * last_good_value + controllingFilterCoef * adaptive_sigma_mean

    hrv_diff = diff(signal)
    normal_values = repeat([true], length(filtered_signal))
    for i in 2: length(signal)
        current_diff = abs(hrv_diff[i - 1])
        current_max_range = proportionalLimit * signal[i-1] + controllingFilterCoef * adaptive_sigma_mean
        current_value_is_normal= current_diff <= current_max_range || current_diff <= last_good_range
        if current_value_is_normal
            last_good_value = signal[i]
            last_good_range = proportionalLimit * last_good_value + controllingFilterCoef * adaptive_sigma_mean
        end
        normal_values[i] = current_value_is_normal
    end
    # Just change reference, no copy
    fixed_signal = signal
    nonnormal_values = broadcast(!, normal_values)
    if eltype(signal) <: Int
        fixed_signal[nonnormal_values] = round.(Int, adaptive_mean[nonnormal_values] + (randn(sum(nonnormal_values)) .- 0.5) .* adaptive_sigma[nonnormal_values])
    else
        fixed_signal[nonnormal_values] = adaptive_mean[nonnormal_values] + (randn(sum(nonnormal_values)) .- 0.5) .* adaptive_sigma[nonnormal_values]
    end
    # Calculate second filtered Signal (Through Binominal Filter)
    #filtered_fixed_signal =  vec(sum(fixed_signal[hrv_idx_mat] .* filter_values', dims = 2))
    filtered_fixed_signal = filtfilt(bin_coeff, fixed_signal)

    # Apply "Adaptive Controlling Procedure" => fixed_fixed_signal
    adaptive_fixed_mean, adaptive_fixed_sigma = adaptive_moments(filtered_fixed_signal, coeff_count, adaptiveControllingCoef)

    normal_fixed_hrv_values = abs.(fixed_signal - adaptive_fixed_mean) .<= (controllingFilterCoef .* adaptive_fixed_sigma .+ controllingBasicVariability)

    fixed_fixed_signal = fixed_signal
    nonnormal_fixed_values = .!normal_fixed_hrv_values
    if eltype(signal) <: Int
        fixed_fixed_signal[nonnormal_fixed_values] = round.(Int, adaptive_fixed_mean[nonnormal_fixed_values] + (randn(sum(nonnormal_fixed_values)) .- 0.5) .* adaptive_fixed_sigma[nonnormal_fixed_values])
    else
        fixed_fixed_signal[nonnormal_fixed_values] = adaptive_fixed_mean[nonnormal_fixed_values] + (randn(sum(nonnormal_fixed_values)) .- 0.5) .* adaptive_fixed_sigma[nonnormal_fixed_values]
    end

    # Returns
    if replaceNonNormal
        if removeOutliers
            return fixed_fixed_signal, removed_nonphysiological_outliers, nonnormal_values
        else
            return fixed_fixed_signal, nonnormal_values
        end
    else
        if removeOutliers
            return removed_nonphysiological_outliers, nonnormal_values
        else
            return nonnormal_values
        end
    end
end

# Helper to calculate adaptive moments (mean, std)
function adaptive_moments(signal::Vector{<:Real}, init_length::Int, adaptiveControllingCoef::Real)
    adaptive_mean = Float64[]
    adaptive_variance = Float64[]
    push!(adaptive_mean, mean(signal[1:init_length]))
    push!(adaptive_variance, var(signal[1:init_length]))
    for i in 2:length(signal)
        push!(adaptive_mean, (adaptive_mean[end] - adaptiveControllingCoef * (adaptive_mean[end] - signal[i-1])))
        last_variance_item = (adaptive_mean[end-1] - signal[i-1]) ^ 2
        push!(adaptive_variance, adaptive_variance[end] - adaptiveControllingCoef * (adaptive_variance[end] - last_variance_item))
    end
   return adaptive_mean, sqrt.(adaptive_variance)
end


@testset "Machine learning" begin
    @testset "Machine learning: K-Means clustering" begin
        # poinst is a vector of 2D points.
        points = [[0, 0], [1, 1], [5.0, 5.0], [10.0, 10.0]]
        @test KMeans.cluster(1, -1, points) == [[4.0, 4.0]] # 1 cluster, undefined number of iterations
        @test KMeans.cluster(1, 10, points) == [[4.0, 4.0]] # 1 cluster, 10 iterations
        # If we are looking for 2 clusters, it is obvious that the first one will be between the first two
        # points ([0, 0] and [1, 1]), and the second one will be between the last two points ([5, 5] and [10, 10])
        @test KMeans.cluster(2, -1, points) == [[0.5, 0.5], [7.5, 7.5]]
        # When the number of clusters is equal to the number of points,
        # each cluster will be assigned to a single point
        @test KMeans.cluster(4, -1, points) == points
        # When there are not enough points to create k clusters,
        # each cluster will have its coordinates set to 0
        KMeans.cluster(5, -1, points) == [[0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0]]
    end
end


module WavePropagation

    using FFTW
    using FastChebInterp
    using FastChebInterp: ChebPoly
    using Memoize
    using ChainRulesCore: ProjectTo, NoTangent
    import ChainRulesCore.rrule

    export planned_fft, planned_ifft
    export convolve, convolveT, convolve!, convolveT!
    export incident_field, greens
    export get_model

    include("planned_fft.jl")
    include("fields.jl")
    include("convolve.jl")
    include("scattering.jl")

end


#-------------IFS.jl-----------------------------------------------------------#
#
# Purpose: quick test of iterated function system fractals
#
#------------------------------------------------------------------------------#

using Images

mutable struct Pixel
    color::Vector{Float64}
    val::Float64
end

# Functions for fractal flames
function linear(point::Array{Float64, 1}, c::Float64, color::Array{Float64, 1})
    ret_point = zeros(5)
    ret_point[1] = point[1] * c
    ret_point[2] = point[2] * c
    ret_point[3] = color[1]
    ret_point[4] = color[2]
    ret_point[5] = color[3]

    return ret_point
end

function heart(point::Array{Float64, 1}, color::Array{Float64, 1})
    ret_point = zeros(5)
    r = sqrt(point[1]^2 + point[2]^2)
    theta = atan2(point[2],point[1])
    ret_point[1] = r*(sin(r*theta))
    ret_point[2] = -r*(cos(r*theta))
    ret_point[3] = color[1]
    ret_point[4] = color[2]
    ret_point[5] = color[3]

    return ret_point
end

function sinusoidal(point::Array{Float64, 1}, color::Array{Float64, 1})
    ret_point = zeros(5)
    ret_point[1] = sin(point[1])
    ret_point[2] = cos(point[2])
    ret_point[3] = color[1]
    ret_point[4] = color[2]
    ret_point[5] = color[3]

    return ret_point
end

function swirl(point::Array{Float64, 1}, color::Array{Float64, 1})
    ret_point = zeros(5)
    r = sqrt(point[1]^2 + point[2]^2)
    ret_point[1] = point[1]*sin(r^2) - point[2]*cos(r^2)
    ret_point[2] = point[1]*cos(r^2) + point[2]*sin(r^2)
    ret_point[3] = color[1]
    ret_point[4] = color[2]
    ret_point[5] = color[3]

    return ret_point
end

function sierpensky_flame(point::Array{Float64, 1}, chosen_function::Int64)
    if (chosen_function == 1)
        point[1:2] *= 0.5
        point[3] = 0.5
        point[4] = 0.5
        point[5] = 0.0
    elseif(chosen_function == 2)
        point[1:2] = [(point[1] - 1)/2, (point[2])/2]
        point[3] = 0.0
        point[4] = 0.5
        point[5] = 0.5
    else
        point[1:2] = [(point[1])/2, (point[2]-1)/2]
        point[3] = 0.5
        point[4] = 0.0
        point[5] = 0.5
    end

    return point
end

function barnsley_flame(point::Array{Float64, 1}, chosen_function::Int64)
    if (chosen_function == 1)
        point[1:2] = [0 0; 0 0.16]*point[1:2]
        point[3] = 1.0
        point[4] = 1.0
        point[5] = 1.0
    elseif(chosen_function == 2)
        point[1:2] = [0.85 0.04; -0.04 0.85]*point[1:2] + [0, 1.60]
        point[3] = 1.0
        point[4] = 0.0
        point[5] = 0.0
    elseif(chosen_function == 3)
        point[1:2] = [0.2 -0.26; 0.23 0.22]*point[1:2] + [0, 1.60]
        point[3] = 0.0
        point[4] = 1.0
        point[5] = 0.0
    else
        point[1:2] = [-0.15 0.28; 0.26 0.24]*point[1:2] + [0, 0.44]
        point[3] = 0.0
        point[4] = 0.0
        point[5] = 1.0
    end

    return point

end

function write_image(p::Array{Pixel,2})
    a = Array{RGB{N0f8},2}(size(p,1), size(p,2))
    for i = 1:length(pixels)
        a[i] = RGB{N0f8}(pixels[i].color[1], pixels[i].color[2],
                         pixels[i].color[3])
    end
    save("fractal.png", a)
end

function barnsley_fern(n::Int64)
    point = [0.0, 0.0]
    f = open("barnsley.dat", "w")
    for i = 1:n
        rnd = rand()
        if (rnd <= 0.01)
            point = [0 0; 0 0.16]*point
        elseif(rnd > 0.01 && rnd <= 0.86)
            point = [0.85 0.04; -0.04 0.85]*point + [0, 1.60]
        elseif(rnd > 0.86 && rnd <= 0.93)
            point = [0.2 -0.26; 0.23 0.22]*point + [0, 1.60]
        else
            point = [-0.15 0.28; 0.26 0.24]*point + [0, 0.44]
        end

        println(f, point[1], '\t', point[2])
    end
    close(f)
end

function sierpensky(n::Int64)
    element_x = rand()*2 - 1
    element_y = rand()*2 - 1
    point = [element_x element_y]

    f = open("sierpensky.dat", "w")

    for i = 1:n
        if (i > 20)
            println(f, point[1], '\t', point[2])
        end
        rnd = rand()

        if (rnd <= 0.33)
            point = 0.5*point
        elseif(rnd > 0.33 && rnd <= 0.66)
            point[1] = (point[1] - 1)/2
            point[2] = (point[2])/2
        else
            point[1] = (point[1])/2
            point[2] = (point[2]-1)/2
        end
    end

    close(f)
end

function flame(n::Int64, resx::Int64, resy::Int64,
               scalex::Float64, scaley::Float64, gamma::Float64)
    element_x = rand()*scalex*0.5 - 1
    element_y = rand()*scaley*0.5 - 1
    point = [element_x, element_y, 0.0, 0.0, 0.0]

    pixels = [Pixel([0.0, 0.0, 0.0], 0.0) for i = 1:resx, j = 1:resy]

    pixel_file = open("pixels.dat","w")

    color = zeros(3)
    for i = 1:n
        rnd = rand()
        r = sqrt(point[1]^2 + point[2]^2)
        if (abs(r) < 0.0001)
            r = 0.0001
        end

#=
        if (rnd <= 0.01)
            point = barnsley_flame(point, 1)
        elseif(rnd > 0.01 && rnd <= 0.86)
            point = barnsley_flame(point, 2)
        elseif(rnd > 0.86 && rnd <= 0.93)
            point = barnsley_flame(point, 3)
        else
            point = barnsley_flame(point, 4)
        end
=#

        if (rnd <= 0.33)
            #point = sierpensky_flame(point, 1)
            point = sinusoidal(point, [0.0, 0.0, 1.0])
        elseif (rnd > 0.33 && rnd <= 0.66)
            #point = sierpensky_flame(point, 2)
            point = linear(point, 0.5, [1.0, 0.0, 0.0])
        else
            #point = sierpensky_flame(point, 3)
            point = swirl(point, [1.0, 1.0, 1.0])
        end

        # Final function attempts
        final_point = heart(point, [1.0, 0.0, 1.0])

        # blending the colors
        if (i > 20)
            xval = Int(floor((final_point[1]+scalex*0.5) / (scalex / resx)))
            yval = Int(floor((final_point[2]+scaley*0.5) / (scaley / resy)))
            if (xval < resx && yval < resy && xval > 0 && yval > 0)
                pixels[yval,xval].val += 1
                pixels[yval,xval].color = (color+point[3:5])*0.5
                color = pixels[yval,xval].color
                pixels[yval,xval].color = (color+final_point[3:5])*0.5
            end
        end

    end

    # find maximum in a dumb way
    max = 0;
    for pixel in pixels
        if (pixel.val > 0)
            pixel.val = log10(pixel.val)
        end
        if pixel.val > max
            max = pixel.val
        end
    end

    for i = 1:resy-1
        index_string = ""
        for j = 1:resx-1
            index_string *= string(log10(pixels[i,j].val), '\t')
        end
        println(pixel_file, index_string)
    end
    close(pixel_file)

    # Setting log plot and changing brightness
    for pixel in pixels
        pixel.val /= max
        if (pixel.val > 0.0000001)
            pixel.color = (pixel.color * pixel.val).^(1/gamma)
            for c in pixel.color
                if c > 1
                    c = 1
                end
            end
        end
    end

    return pixels

end

pixels = flame(100000000, 1000, 1000, 4., 4., 1.0)
#pixels = flame(1000000, 1000, 1000, 2., 2., 2.2)
write_image(pixels)
#sierpensky(1000000)
#barnsley_fern(100000)


import Compat.LinearAlgebra: eigvals

eigvals(A::QuMatrix, args...; kwargs...) =
    eigvals(A.data, args...; kwargs...)

function eigen(A::QuMatrix, args...; kwargs...)
    D,V = eigen(A.data, args...; kwargs...)
    return D, [Ket(V[:,i],dims(A)) for i = 1:size(V,2)]
end

function eigs(A::Operator, args...; kwargs...)
    D,V = eigs(A.data, args...; which=:SR, kwargs...)
    return D, [Ket(V[:,i],dims(A)) for i = 1:size(V,2)]
end


using JuMP, EAGO

                     m = Model()

                     EAGO.register_eago_operators!(m)

                     @variable(m, -1 <= x[i=1:2] <= 1)

                     @variable(m, -3.892086739821988 <= q <= 1.133112254487742)

                     add_NL_constraint(m, :(gelu(-0.9215225812660202 + -0.8659264200816277*gelu(0.9754315885766114 + 0.9182647481159649*$(x[1]) + 0.2963572993024828*$(x[2])) + -0.7655479829179535*gelu(0.7516798050016731 + -0.4187259277447679*$(x[1]) + -0.991762240929563*$(x[2]))) + gelu(0.9152212659843197 + -0.09622429015475253*gelu(0.9754315885766114 + 0.9182647481159649*$(x[1]) + 0.2963572993024828*$(x[2])) + 0.18727818139193309*gelu(0.7516798050016731 + -0.4187259277447679*$(x[1]) + -0.991762240929563*$(x[2]))) - $q <= 0.0))

                     @objective(m, Min, q)

                     return m


module PhysicalFESpacesTests

using Gridap.ReferenceFEs
using Gridap.Geometry
using Gridap.Arrays
using Gridap.Fields
using Gridap.FESpaces
using Gridap.Polynomials
using Gridap.CellData
using Gridap.TensorValues
using Test

# domain = (0,1)
# partition = (3,)
domain = (0,1,0,1)
partition = (2,2)
model = CartesianDiscreteModel(domain,partition)
order = 1

trian = get_triangulation(model)
quad = CellQuadrature(trian,order)

u(x) = x[1]
T = Float64

u(x) = VectorValue(x[1],x[2])
T = VectorValue{2,Float64}

# Juno.@run TestFESpace( reffe = :Lagrangian, conformity = :H1, valuetype=T, order = order, model = model, dirichlet_tags = [1,6], dof_space = :physical)
# Juno.@enter TestFESpace( reffe = :Lagrangian, conformity = :H1, valuetype=T, order = order, model = model, dirichlet_tags = [1,6], dof_space = :physical)
# Juno.@enter TestFESpace( reffe = :Lagrangian, conformity = :H1, valuetype=T, order = order, model = model, dirichlet_tags = [1,6])

Vp = TestFESpace(
  reffe = :Lagrangian,
  conformity = :H1,
  valuetype=T,
  order = order,
  model = model,
  dirichlet_tags = [1,6],
  dof_space = :physical)

V = TestFESpace(
  reffe = :Lagrangian,
  conformity = :H1,
  valuetype=VectorValue{2,Float64},
  order = order,
  model = model,
  dirichlet_tags = [1,6])

  V.cell_basis.ref_trait

# Vp = TestFESpace(
#   reffe = :RaviartThomas,
#   conformity = :Hdiv,
#   order = order,
#   model = model,
#   dirichlet_tags = [1,6],
#   dof_space = :physical)
#
# V = TestFESpace(
#   reffe = :RaviartThomas,
#   conformity = :Hdiv,
#   order = order,
#   model = model,
#   dirichlet_tags = [1,6])
# test_single_field_fe_space(V)

Up = TrialFESpace(Vp,u)
U = TrialFESpace(V,u)
U.cell_basis.ref_trait
U.space.cell_basis.ref_trait

# FEM = Gridap.FESpaces
# Juno.@enter TrialFESpace(Vp,u)
# dirichlet_values = FEM.compute_dirichlet_values_for_tags(V,u)

q = get_coordinates(quad)
r = evaluate(U.cell_basis,q)
rp = evaluate(Up.cell_basis,q)

@test all(collect(r) .≈ collect(rp))

@test RefStyle(Up.space.cell_dof_basis) == Val{false}()
@test RefStyle(U.space.cell_dof_basis) == Val{true}()

@test RefStyle(Up.space.cell_basis) == Val{false}()
@test RefStyle(U.space.cell_basis) == Val{true}()

@test RefStyle(Vp.cell_basis) == Val{false}()
@test RefStyle(V.cell_basis) == Val{true}()

uhf = interpolate(u,Up)
uhd = interpolate_dirichlet(u,Up)
uhdf = interpolate_everywhere(u,Up)
uhf.dirichlet_values
uhdf.dirichlet_values
uhd.dirichlet_values

uhf_r = interpolate(u,U)
uhd_r= interpolate_dirichlet(u,U)
uhdf_r = interpolate_everywhere(u,U)
uhd_r.dirichlet_values

@test uhf.free_values == uhf_r.free_values
@test uhf.dirichlet_values == uhf_r.dirichlet_values
@test uhd.dirichlet_values == uhd_r.dirichlet_values
@test uhdf.free_values == uhdf_r.free_values
@test uhdf.dirichlet_values == uhdf_r.dirichlet_values

@test uhf.free_values == uhdf.free_values
@test uhf.dirichlet_values == uhd.dirichlet_values
@test uhdf.free_values == uhf.free_values
@test uhdf.dirichlet_values == uhd.dirichlet_values
@test uhdf.dirichlet_values == uhdf_r.dirichlet_values

e = u - uhf_r

el2 = sqrt(sum(integrate(inner(e,e),trian,quad)))

@test el2 < 1.0e-10

e = u - uhf

el2 = sqrt(sum(integrate(inner(e,e),trian,quad)))

@test el2 < 1.0e-10

end #module


# This file is a part of Julia. License is MIT: http://julialang.org/license

# Inner produce of x and y
function inner( x, y )
    s = zero(eltype(x))
    @simd for i=1:length(x)
        @inbounds s += x[i]*y[i]
    end
    s
end

function flog_inner( m, x, y )
    s = zero(eltype(x))
    for j=1:m
        s += inner(x,y)
    end
    s
end

for t in [Float32,Float64]
    n = 1000
    x = rand(t,n)
    y = rand(t,n)
    bits = 8*sizeof(t)
    @timeit(flog_inner(100,x,y), "inner_$bits", "SIMD inner product for type $t", "SIMD")
end


struct Multivector{V<:MVector}<:GeometricAlgebraType
    coefs::V
end

Multivector(coefs::AbstractVector) = Multivector(MVector{length(coefs)}(coefs))

vectype(::Type{<:Multivector{V}}) where {V} = V

eltype(T::Type{<:Multivector}) = eltype(vectype(T))
eltype(::Type{Multivector}) = Any
eltype(mv::Multivector) = eltype(typeof(mv))

length(T::Type{<:Multivector}) = length(vectype(T))
length(mv::Multivector) = length(typeof(mv))

zero(::Type{<:Multivector{V}}) where {V} = Multivector(zeros(V))

grades(mv::Multivector) = unique(grade.(nonzero_blades(mv)))

setindex!(mv::Multivector, args...) = setindex!(mv.coefs, args...)

store!(mv::Multivector, b::Blade) = setindex!(mv, b.coef, b.index)

add!(mv::Multivector, b::Blade) = setindex!(mv, mv[b.index] + b.coef, b.index)

getindex(mv::Multivector, args...) = getindex(mv.coefs, args...)

index(::Multivector, i) = i
index(::Multivector, b::Blade) = b.index

broadcastable(mv::Multivector) = mv.coefs

(==)(x::Multivector, y::Multivector) = all(x.coefs .== y.coefs)
@commutative (==)(x::Blade, y::GeometricAlgebraType) = x.coef == value(y, x)

(≈)(x::Multivector, y::Multivector; kwargs...) = all(.≈(x.coefs, y.coefs; kwargs...))
(≈)(x::Blade, y::GeometricAlgebraType; kwargs...) = ≈(x.coef, value(y, x); kwargs...)
(≈)(y::GeometricAlgebraType, x::Blade; kwargs...) = ≈(x.coef, value(y, x); kwargs...)

function (==)(x::GeometricAlgebraType, y::GeometricAlgebraType)
    bx, by = collect(nonzero_blades(x)), collect(nonzero_blades(y))
    length(bx) == length(by) && all(bx .== by)
end

function (≈)(x::GeometricAlgebraType, y::GeometricAlgebraType; kwargs...)
    bx, by = collect(nonzero_blades(x)), collect(nonzero_blades(y))
    length(bx) == length(by) && all(.≈(bx, by; kwargs...))
end

indices(mv::Multivector) = 1:length(mv)

is_homogeneous(mv::Multivector) = length(grades(mv)) ≤ 1

function convert(T::Type{<:Multivector}, kvec::KVector)
    mv = zero(Multivector, eltype(T) == Any ? eltype(kvec) : promote_type(eltype(T), eltype(kvec)))
    mv.coefs[indices(kvec)] .= kvec.coefs
    mv
end

function kvectors end

function kvector end

function show(io::IO, @nospecialize mv::Multivector)
    bs = nonzero_blades(mv)
    if isempty(bs)
        print(io, string(scalar(zero(eltype(mv)))))
    else
        print(io, join(string.(collect(bs)), " + "))
    end
end


module acou_annulus_examples
using FinEtools
using FinEtools.AlgoAcoustModule

function acou_annulus_Q4_example_algo()
    println("""
    Annular region, pressure BC + rigid wall.
    This version uses the FinEtools algorithm module.
    Version: 08/21/2017
    """)

    t0 = time()

    rho = 1001*phun("kg/m^3");# mass density
    c  = 1500.0*phun("m/s");# sound speed
    bulk =  c^2*rho;
    omega =  2000*phun("rev/s");      # frequency of the piston
    rin =  1.0*phun("m");#internal radius

    rex =  2.0*phun("m"); #external radius
    nr = 20; nc = 120;
    Angle = 2*pi;
    thickness =  1.0*phun("m/s");
    tolerance = min(rin/nr,  rin/nc/2/pi)/10000;

    fens, fes = Q4annulus(rin, rex, nr, nc, Angle)
    fens, fes = mergenodes(fens,  fes,  tolerance);
    edge_fes = meshboundary(fes);

    # The pressure boundary condition
    l1 = selectelem(fens, edge_fes, box=[-1.1*rex -0.9*rex -0.5*rex 0.5*rex]);
    ebc1 = FDataDict("node_list"=>connectednodes(subset(edge_fes, l1)),
    "pressure"=>x -> cos(2*pi*x[2]/rin)+1im*sin(2*pi*x[2]/rin)) # entering the domain

    material = MatAcoustFluid(bulk, rho)
    femm = FEMMAcoust(IntegData(fes,  GaussRule(2, 2)),  material)
    region1 = FDataDict("femm"=>femm)

    # Make model data
    modeldata = FDataDict("fens"=>fens,
    "omega"=>omega,
    "regions"=>[region1], "essential_bcs"=>[ebc1]);

    # Call the solver
    modeldata = FinEtools.AlgoAcoustModule.steadystate(modeldata)
    geom=modeldata["geom"]
    P=modeldata["P"]
    println("Minimum/maximum pressure, real= $(minimum(real(P.values)))/$(maximum(real(P.values))))")
    println("Minimum/maximum pressure, imag= $(minimum(imag(P.values)))/$(maximum(imag(P.values))))")

    println("Total time elapsed = ",time() - t0,"s")

    # Postprocessing
    File = "acou_annulusmod.vtk"
    vtkexportmesh(File, fes.conn, geom.values, FinEtools.MeshExportModule.Q4; scalars=[("Pre", real(P.values)), ("Pim", imag(P.values))])
    @async run(`"paraview.exe" $File`)

end # acou_annulus_Q4_example_algo

function allrun()
    println("#####################################################")
    println("# acou_annulus_Q4_example_algo ")
    acou_annulus_Q4_example_algo()
    return true
end # function allrun

end # module acou_annulus_examples


using FeedbackParticleFilters, Distributions, LinearAlgebra, Random, PDMats, FillArrays

println("Testing BrownianMotionTorus.jl:")

@testset "BrownianMotionTorus.jl" begin
    @testset "$(d)d" for d in 1:3
        print("  inner constructor")
        model = BrownianMotionTorus(d)

        print("  method state_dim")
        print("-")
        @test state_dim(model) == d
        println("DONE")

        print("  method noise_dim")
        print("-")
        @test noise_dim(model) == d
        println("DONE")

        print("  method initialize")
        x0 = initialize(model)
        print("-")
        @test all(0 .<= x0 .<= 2π)
        println("DONE")

        print("  calling instance of BrownianMotionTorus")
        x0 = ones(d)
        x1 = model(x0, 0.01)
        print("-")
        @test x1 != x0
        print("-")
        @test all(0 .<= x0 .<= 2π)
        xx0 = hcat(ones(d), zeros(d))
        xx1 = model(xx0, 0.01)
        print("-")
        @test xx1 != xx0
        print("-")
        @test all(0 .<= x0 .<= 2π)
        println("DONE")

        print("  method propagate!")
        x0 = ones(d)
        x1 = copy(x0)
        propagate!(x1, model, 0.01)
        print("-")
        @test x1 != x0
        xx0 = hcat(ones(d), zeros(d))
        xx1 = copy(xx0)
        propagate!(xx1, model, 0.01)
        print("-")
        @test xx1 != xx0
        println("DONE")
    end
end; #BrownianMotionTorus.jl


using DifferentialEquations
using Optim

function init_guess(M, X, Y)
    #=
    Gives a reasonable guess for Pc, Tc, R, and L for a star of mass M
    =#
    yhalf = [Rsun * 10 ^ 0.062544715341505616, Lsun * 10 ^ -0.36630780815983022, 10 ^ 19.700356657315954, 10 ^ 7.2863105891013289]
    y2 = [Rsun * 10 ^ 0.36072469919806871, Lsun * 10 ^ 1.3021335874028637, 10 ^ 17.186879800538229, 10 ^ 7.3557279873577759]
    y5 = [Rsun * 10 ^ 0.59322648100945885, Lsun * 10 ^ 2.8743140385067893, 10 ^ 16.752833342638592, 10 ^ 7.4547058342916337]
    y4 = [Rsun * 10 ^ 0.52962615387538248, Lsun * 10 ^ 2.50275963393719, 10 ^ 16.855407680647915, 10 ^ 7.429616124439673]
    y10 = [Rsun * 10 ^ 0.68191099104456998, Lsun * 10 ^ 3.8447263619458569, 10 ^ 16.517451455410988, 10 ^ 7.5012272142698366]
    if M == Msun
        return [Rsun, Lsun, 2.4e17, 1.6e7]
    elseif M == 0.5 * Msun
        @info("init_guess: from MESA-Web")
        return yhalf
    elseif M == 2 * Msun
        @info("init_guess: from MESA-Web")
        return y2
    elseif (M >= 4 * Msun) & (M <= 5 * Msun)
        @info("init_guess: from MESA-Web")
        dm = M - 4 * Msun
        dydm = (y5 - y4) / Msun
        return y4 + dydm * dm
    elseif M == 10 * Msun
        @info("init_guess: from MESA-Web")
        return y10
    end
    mu = mu_from_composition(X, Y)

    # mass-radius relation
    if M / Msun > 1.3 # then CNO burning dominates
        z1 = 0.8
        z2 = 0.64
    else # then pp chain dominates
        z1 = 0.5
        z2 = 0.1
    end # if
    R = Rsun * (M / Msun) ^ z1 * (mu / 0.61) ^ z2
    # mass-luminosity relation
    L = Lsun * (M / Msun) ^ 3 * (mu / 0.61) ^ 4
    # Pc, Tc guesses, essentially dimensional analysis with fudges
    Pc = 2 * G * M ^ 2 / (pi * R ^ 4)
    Tc = 8 * 0.02 * mu * m_H * G * M / (k_B * R)
    return [R, L, Pc, Tc]
end # init_guess

function load1(m, Pc, Tc, X, Y)
    #=
    Subroutine for shootf.
    Loads the (r, l, P, T) center (m = 0) values for starting the outward
    integration.
    =#
    mu = mu_from_composition(X, Y)
    # m = 0.01 * Msun # needs tuning
    rhoc = rho_ideal(Pc, Tc, mu)
    rc = (3 * m / (4 * pi * rhoc)) ^ (1 / 3)
    lc = dldm(m, 0, 0, 0, Tc, rhoc, X, Y) * m
    # @debug("load1: center values = ", [rc, lc, Pc, Tc])
    return [rc, lc, Pc, Tc]
end # load1

function load2(M, Rs, Ls, X, Y; max_iterations=1000)
    #=
    Subroutine for shootf.
    Loads the (r, l, P, T) surface (m = M) values for starting the inward
    integration.
    =#
    mu = mu_from_composition(X, Y)
    converged = false
    rho_guess = 1e-8 # need a less arbitrary guess?
    Ts = try
        T_surface(Rs, Ls)
    catch
        @warn("load2: Rs = ", Rs)
        @warn("load2: Ls = ", Ls)
    end
    Ps = 0
    count = 1
    while ~converged & (count < 100)
        kappa = opacity(logkappa_spl, rho_guess, Ts)
        Ps = P_surface(M, Rs, kappa)
        rho = rho_ideal(Ps, Ts, mu)
        relerr = (rho - rho_guess) / rho
        if abs(relerr) < 1e-6 # need less arbitrary convergence condition
            converged = true
        else
            rho_guess = rho_guess + relerr * rho / 6
            count += 1
        end # if
    end # while
    if count >= max_iterations
        @warn("load2: Exceeded ", max_iterations, " iterations!")
    end # if
    # @debug("load2: surface values = ", [Rs, Ls, Ps, Ts])
    return [Rs, Ls, Ps, Ts]
end # load2

function profiles(m, M, Rs, Ls, Pc, Tc, X, Y, mf)
    #=
    Integrates outward from m to mf and inward from M to mf to construct
    r, l, P, and T profiles from the given initial conditions.
    =#
    yc = load1(m, Pc, Tc, X, Y)
    prob = ODEProblem(deriv!,yc,(0, mf), (X, Y))
    sol_out = solve(prob, Tsit5());
    sol_out_v = hcat(sol_out.u...)'

    ys = load2(M, Rs, Ls, X, Y)
    prob = ODEProblem(deriv!,ys,(M, mf), [X, Y])
    sol_in = solve(prob, Tsit5());
    sol_in_v = hcat(sol_in.u...)'
    return sol_out.t, sol_in.t, sol_out_v, sol_in_v
end

function profiles(star::Star, Rs, Ls, Pc, Tc; n=1000)
    return profiles(star.m, star.M, Rs, Ls, Pc, Tc, star.X, star.Y, star.mf)
end

function deriv!(du,u,p,m)
    r, l, P, T = u
    X, Y = p

    mu = mu_from_composition(X, Y)
    rho = rho_ideal(P, T, mu)

    kappa = opacity(logkappa_spl, rho, T)

    du[1] = drdm(m, r, l, P, T, rho, mu)
    du[2] = dldm(m, r, l, P, T, rho, X, Y)
    du[3] = dPdm(m, r, l, P, T)
    du[4] = dTdm(m, r, l, P, T, mu, kappa)
end

function score(m, M, Rs, Ls, Pc, Tc, X, Y, mf)
    #=
    Function to zero.
    =#
    ss = try
        sol_out_t, sol_in_t, sol_out_v, sol_in_v = profiles(m, M, Rs, Ls, Pc, Tc, X, Y, mf)
        (sol_out_v[end, :] - sol_in_v[end, :]) ./ sol_in_v[end, :]
    catch
        [NaN]
    end

    return (sum(ss.^2))
end # score

function shootf(m, M, X, Y; fixed_point=0.8)
    #=
    Shooting to a fixed point
    =#
    y0 = init_guess(M, X, Y)
    mf = fixed_point * M
    f(y) = score(m, M, y[1], y[2], y[3], y[4], X, Y, mf)

    res = optimize(f, y0,
                g_tol = 1e-15,
                iterations = 10000,
                allow_f_increases=true,
                store_trace = false,
                show_trace = false
    )
    return res
end # shootf

function shootf(star::Star)
    return shootf(star.m, star.M, star.X, star.Y)
end

function shootf!(star::Star)
    res = shootf(star)
    bc = Optim.minimizer(res)
    sol_out_t, sol_in_t, sol_out_v, sol_in_v = profiles(star, bc...)
    star.grid = vcat(sol_out_t, reverse(sol_in_t))
    star.r = vcat(sol_out_v[:,1], reverse(sol_in_v[:,1]))
    star.l = vcat(sol_out_v[:,2], reverse(sol_in_v[:,2]))
    star.P = vcat(sol_out_v[:,3], reverse(sol_in_v[:,3]))
    star.T = vcat(sol_out_v[:,4], reverse(sol_in_v[:,4]));
    return res
end


## DTU42380a2: Supply Networks and Inventory
## Edward J. Xu
## April 10, 2020
## Data and Functions

function get_data()
  demands = [
    640000.00	400000.00	320000.00	440000.00	700000.00	350000.00;
    1152000.00	720000.00	576000.00	792000.00	1260000.00	630000.00;
    2073600.00	1296000.00	1036800.00	1425600.00	2268000.00	1134000.00
    ]  # demands[s, v]

  # demands = [
  #   640000.00	400000.00	320000.00	440000.00	700000.00	350000.00;
  #   1216000.00	760000.00	608000.00	836000.00	1330000.00	665000.00;
  #   2310400.00	1444000.00	1155200.00	1588400.00	2527000.00	1263500.00
  #   ]  # demands[s, v]

  costs_ship = [
    0.500	0.625	0.875	1.000	1.250	1.375
    0.625	0.625	0.625	0.750	1.000	1.125
    0.875	0.875	0.625	0.625	0.750	0.875
    1.000	1.000	0.750	0.625	0.750	0.625
    1.125	1.250	0.750	0.875	0.625	1.000
    ]  # costs_fix[l, v]

  costs_fix = [
    300000.00 500000.00
    250000.00 420000.00
    220000.00 375000.00
    220000.00 375000.00
    240000.00 400000.00
    ]  # costs_fix[l, w]

  cost_var = 0.2

  q = [2 4] * 1000000  # q[w]

  e = [
    1 1 0;
    0 1 1;
    1 1 1;
    0 0 1;
    0 0 0
    ]  # e[p, s]

  return demands, costs_ship, costs_fix, cost_var, q, e
end

"Get the optimized value of decision vector."
function value_vec(vec_x::Union{Array{VariableRef,1}, VariableRef})
    if isa(vec_x, VariableRef)
        vec_value = value(vec_x)
    else
        vec_value = [value(vec_x[i]) for i = 1:length(vec_x)]
    end

    return vec_value
end

"Get the optimized value of decision matrix."
function value_mat(mat_x::Array{VariableRef,2})
  m, n = size(mat_x)
  mat_value = [value(mat_x[i, j]) for i=1:m, j=1:n]

  return mat_value
end

"Get the optimized value of a 3-D decision matrix."
function value_mat3(mat3_x::Array{VariableRef,3})
  m, n, o = size(mat3_x)
  mat3_value =
  mat3_value = [value(mat3_x[i, j, k]) for i=1:m, j=1:n, k=1:o]

  return mat3_value
end


using Gadfly, DataArrays, RDatasets

 plot(dataset("ggplot2", "diamonds"), x="Price", Geom.density)


@testset "PARSDMM" begin
#test PARSDMM
Random.seed!(123)

options=PARSDMM_options()
options.FL                    = Float64
options.parallel              = false
options.zero_ini_guess        = true

x=randn(100,201)

comp_grid = compgrid((1.0, 1.0),(100, 201))
x         = vec(x)
m2        = deepcopy(x)
x_ini2    = deepcopy(x)

#check if input returns if the input satisfies the constraints
  #bound constraints
  constraint = Vector{SetIntersectionProjection.set_definitions}()

  m_min     = minimum(vec(x))
  m_max     = maximum(vec(x))
  set_type  = "bounds"
  TD_OP     = "identity"
  app_mode  = ("matrix","")
  custom_TD_OP = ([],false)
  push!(constraint, set_definitions(set_type,TD_OP,m_min,m_max,app_mode,custom_TD_OP))

  (P_sub,TD_OP,set_Prop) = setup_constraints(constraint,comp_grid,options.FL)
  (TD_OP,AtA,l,y)        = PARSDMM_precompute_distribute(TD_OP,set_Prop,comp_grid,options)

  m=deepcopy(x)

  #default settings
  (x,log_PARSDMM) = PARSDMM(m,AtA,TD_OP,set_Prop,P_sub,comp_grid,options);
  @test x==m

#add a few convex constraints. The projected model should satisfy these constraints
#(a necessary but not sufficient test for the projection onto the intersection)

  constraint = Vector{SetIntersectionProjection.set_definitions}()

  #bound constraints
  m_min     = 0.5*minimum(vec(x))
  m_max     = 0.5*maximum(vec(x))
  set_type  = "bounds"
  TD_OP     = "identity"
  app_mode  = ("matrix","")
  custom_TD_OP = ([],false)
  push!(constraint, set_definitions(set_type,TD_OP,m_min,m_max,app_mode,custom_TD_OP))

  #transform domain bounds
  (TD_OP, AtA_diag, dense, TD_n)=get_TD_operator(comp_grid,"D_z",options.FL)
  m_min     = 0.5*minimum(TD_OP*x)
  m_max     = 0.5*maximum(TD_OP*x)
  set_type  = "bounds"
  TD_OP     = "D_z"
  app_mode  = ("matrix","")
  custom_TD_OP = ([],false)
  push!(constraint, set_definitions(set_type,TD_OP,m_min,m_max,app_mode,custom_TD_OP))

  #total variation
  (TD_OP, AtA_diag, dense, TD_n)=get_TD_operator(comp_grid,"TV",options.FL)
  m_min     = 0.0
  m_max     = 0.5*norm(TD_OP*x,1)
  set_type  = "l1"
  TD_OP     = "TV"
  app_mode  = ("matrix","")
  custom_TD_OP = ([],false)
  push!(constraint, set_definitions(set_type,TD_OP,m_min,m_max,app_mode,custom_TD_OP))

  (P_sub,TD_OP,set_Prop) = setup_constraints(constraint,comp_grid,options.FL)
  (TD_OP,AtA,l,y)        = PARSDMM_precompute_distribute(TD_OP,set_Prop,comp_grid,options)

  m=deepcopy(x)

  #(mostly) default settings
  options              = PARSDMM_options()
  options.FL           = Float64
  options.evol_rel_tol = 10*eps()
  options.maxit        = 5000

  (x,log_PARSDMM) = PARSDMM(m2,AtA,TD_OP,set_Prop,P_sub,comp_grid,options);

  result = deepcopy(x)
  for i=1:length(TD_OP)-1
    copy!(result,x)
    @test norm(P_sub[i](TD_OP[i]*result)-(TD_OP[i]*result))/norm((TD_OP[i]*result)) <= 1.5*options.feas_tol
  end

  #accurate setting
  options              = PARSDMM_options()
  options.FL           = Float64
  options.obj_tol      = 1e-12
  options.feas_tol     = 1e-12
  options.evol_rel_tol = 10*eps()
  options.maxit        = 5000

  (x,log_PARSDMM) = PARSDMM(m2,AtA,TD_OP,set_Prop,P_sub,comp_grid,options);
  result = deepcopy(x)

  for i=1:length(TD_OP)-1
    copy!(result,x)
    @test norm(P_sub[i](TD_OP[i]*result)-(TD_OP[i]*result))/norm((TD_OP[i]*result)) <= 1.5*options.feas_tol
  end

  #accurate without BLAS
  options               = PARSDMM_options()
  options.FL            = Float64
  options.Blas_active   = false
  options.obj_tol       = 1e-12
  options.feas_tol      = 1e-12
  options.evol_rel_tol  = 10*eps()
  options.maxit         = 5000

  (x,log_PARSDMM) = PARSDMM(m2,AtA,TD_OP,set_Prop,P_sub,comp_grid,options);
  result = deepcopy(x)
  for i=1:length(TD_OP)-1
    copy!(result,x)
    @test norm(P_sub[i](TD_OP[i]*result)-(TD_OP[i]*result))/norm((TD_OP[i]*result)) <= 1.5*options.feas_tol
  end

  #accurate with rho adjustment, without gamma adjustment
  options               = PARSDMM_options()
  options.FL            = Float64
  options.adjust_gamma  = false
  options.obj_tol       = 1e-12
  options.feas_tol      = 1e-12
  options.evol_rel_tol  = 10*eps()
  options.maxit         = 5000

  (x,log_PARSDMM) = PARSDMM(m2,AtA,TD_OP,set_Prop,P_sub,comp_grid,options);
  result = deepcopy(x)
 for i=1:length(TD_OP)-1
    copy!(result,x)
    @test norm(P_sub[i](TD_OP[i]*result)-(TD_OP[i]*result))/norm((TD_OP[i]*result)) <= 1.5*options.feas_tol
  end

  #accurate without rho adjustment, with gamma
  options               = PARSDMM_options()
  options.FL            = Float64
  options.adjust_gamma  = true
  options.adjust_rho    = false
  options.obj_tol       = 1e-6
  options.feas_tol      = 1e-6
  options.evol_rel_tol  = 10*eps()
  options.maxit         = 10000

  (x,log_PARSDMM) = PARSDMM(m2,AtA,TD_OP,set_Prop,P_sub,comp_grid,options);
  result = deepcopy(x)
 for i=1:length(TD_OP)-1
    copy!(result,x)
    @test norm(P_sub[i](TD_OP[i]*result)-(TD_OP[i]*result))/norm((TD_OP[i]*result)) <= 1.5*options.feas_tol
  end

  # without rho adjustment, without gamma
  options               = PARSDMM_options()
  options.FL            = Float64
  options.adjust_gamma  = false
  options.adjust_rho    = false
  options.obj_tol       = 1e-6
  options.feas_tol      = 1e-6
  options.evol_rel_tol  = 10*eps()
  options.maxit         = 25000

  (x,log_PARSDMM) = PARSDMM(m2,AtA,TD_OP,set_Prop,P_sub,comp_grid,options);
  result = deepcopy(x)
  for i=1:length(TD_OP)-1
    copy!(result,x)
    @test norm(P_sub[i](TD_OP[i]*result)-(TD_OP[i]*result))/norm((TD_OP[i]*result)) <= 1.5*options.feas_tol
  end

  #accurate with rho adjustment, with gamma and without rho based on feasibility
  options                        = PARSDMM_options()
  options.FL                     = Float64
  options.adjust_feasibility_rho = false
  options.adjust_gamma           = true
  options.adjust_rho             = true
  options.obj_tol                = 1e-12
  options.feas_tol               = 1e-12
  options.evol_rel_tol           = 10*eps()
  options.maxit                  = 5000

  (x,log_PARSDMM) = PARSDMM(m2,AtA,TD_OP,set_Prop,P_sub,comp_grid,options);
  result = deepcopy(x)
  for i=1:length(TD_OP)-1
    copy!(result,x)
    @test norm(P_sub[i](TD_OP[i]*result)-(TD_OP[i]*result))/norm((TD_OP[i]*result)) <= 1.5*options.feas_tol
  end

# some closed form solutions:

  #create a vector that has a certain nuclear norm as reference solution
  x         = randn(100,201)
  comp_grid = compgrid((1.0, 1.0),(100, 201))
  x         = vec(x)

  c_l_solution = deepcopy(x)
  tau          = 0.54321
  c_l_solution = reshape(c_l_solution,comp_grid.n)

  project_nuclear!(c_l_solution,tau,[])
  c_l_solution = vec(c_l_solution)

  constraint = Vector{SetIntersectionProjection.set_definitions}()

  #add nuclear norm constraint
    m_min     = 0.0
    m_max     = tau
    set_type  = "nuclear"
    TD_OP     = "identity"
    app_mode  = ("matrix","")
    custom_TD_OP = ([],false)
    push!(constraint, set_definitions(set_type,TD_OP,m_min,m_max,app_mode,custom_TD_OP))

  (P_sub,TD_OP,set_Prop) = setup_constraints(constraint,comp_grid,options.FL)
  (TD_OP,AtA,l,y)        = PARSDMM_precompute_distribute(TD_OP,set_Prop,comp_grid,options)

  #solve
  m = deepcopy(x)

  options                        = PARSDMM_options()
  options.FL                     = Float64
  options.adjust_feasibility_rho = true
  options.adjust_gamma           = true
  options.adjust_rho             = true
  options.obj_tol                = 10*eps()
  options.feas_tol               = 10*eps()
  options.evol_rel_tol           = 10*eps()
  options.maxit                  = 2500
  options.Blas_active            = false
  (x,log_PARSDMM) = PARSDMM(m,AtA,TD_OP,set_Prop,P_sub,comp_grid,options);

  result = deepcopy(x)
  for i=1:length(TD_OP)-1
    copy!(result,x)
    @test norm(P_sub[i](TD_OP[i]*result)-(TD_OP[i]*result))/norm((TD_OP[i]*result)) <= 2.0*options.feas_tol
  end
  result = deepcopy(x)
  @test norm(result-c_l_solution)/norm(c_l_solution) <= 1e-9

#test if the algorithm gives same output if we use many explicit BLAS calls
x         = randn(100,201)
comp_grid = compgrid((1.0, 1.0),(100, 201))
x         = vec(x)

constraint = Vector{SetIntersectionProjection.set_definitions}()

#add nuclear norm constraint
  m_min     = 0.0
  m_max     = 1.123
  set_type  = "nuclear"
  TD_OP     = "identity"
  app_mode  = ("matrix","")
  custom_TD_OP = ([],false)
  push!(constraint, set_definitions(set_type,TD_OP,m_min,m_max,app_mode,custom_TD_OP))

#add a few convex constraints
  #bound constraints
    m_min     = 1.0*minimum(x)
    m_max     = 0.50*maximum(x)
    set_type  = "bounds"
    TD_OP     = "identity"
    app_mode  = ("matrix","")
    custom_TD_OP = ([],false)
    push!(constraint, set_definitions(set_type,TD_OP,m_min,m_max,app_mode,custom_TD_OP))

  #transform domain bounds
    (TD_OP, AtA_diag, dense, TD_n)=get_TD_operator(comp_grid,"D_z",options.FL)
    m_min     = 0.9*minimum(TD_OP*vec(x))
    m_max     = 0.67*maximum(TD_OP*vec(x))
    set_type  = "bounds"
    TD_OP     = "D_z"
    app_mode  = ("matrix","")
    custom_TD_OP = ([],false)
    push!(constraint, set_definitions(set_type,TD_OP,m_min,m_max,app_mode,custom_TD_OP))

  #total variation
    (TD_OP, AtA_diag, dense, TD_n)=get_TD_operator(comp_grid,"TV",options.FL)
    m_min     = 0.0
    m_max     =  0.2*norm(TD_OP*vec(x),1)
    set_type  = "l1"
    TD_OP     = "TV"
    app_mode  = ("matrix","")
    custom_TD_OP = ([],false)
    push!(constraint, set_definitions(set_type,TD_OP,m_min,m_max,app_mode,custom_TD_OP))

  (P_sub,TD_OP,set_Prop) = setup_constraints(constraint,comp_grid,options.FL)
  (TD_OP,AtA,l,y)        = PARSDMM_precompute_distribute(TD_OP,set_Prop,comp_grid,options)

  #solve
  m = deepcopy(x)

  options                        = PARSDMM_options()
  options.FL                     = Float64
  options.adjust_feasibility_rho = true
  options.adjust_gamma           = true
  options.adjust_rho             = true
  options.obj_tol                = 1e-6
  options.feas_tol               = 1e-6
  options.evol_rel_tol           = 1e-6
  options.maxit                  = 2500

  options.Blas_active = false
  (x_noblas,log_PARSDMM) = PARSDMM(m,AtA,TD_OP,set_Prop,P_sub,comp_grid,options);

  m = deepcopy(x)
  options.Blas_active=true
  (x_blas,log_PARSDMM) = PARSDMM(m,AtA,TD_OP,set_Prop,P_sub,comp_grid,options);

 #put test here
 @test isapprox(x_blas,x_noblas,rtol=1e-12)

end


function ≒(a,b)
    if a ≈ b
        return true
    elseif abs(a-b) < 1e-10
        return true
    else
        return false
    end
end

@testset "BSplineBasis" begin

    Δt = 1.0e-8
    ε = 1.0e-8
    ∞ = Inf

    @testset "0th degree basis" begin
        Random.seed!(42)

        p = 0
        v = rand(10)
        k = Knots(v) + (p+1)*Knots(0, 1) + Knots(v[2], v[3])
        P = BSplineSpace(p, k)
        fP = FastBSplineSpace(p, k)
        n = dim(P)

        @test degree(P) == degree(fP) == 0

        # Check the values of B-spline basis funcitons
        for i in 1:n, t in rand(5)
            for t in rand(5)
                @test (bsplinebasis₊₀(P, i, t)
                     ≈ bsplinebasis₊₀(fP, i, t)
                     ≈ bsplinebasis₋₀(P, i, t)
                     ≈ bsplinebasis₋₀(fP, i, t)
                     ≈ bsplinebasis(P, i, t)
                     ≈ bsplinebasis(fP, i, t))
            end
            for t in k
                t₊₀ = nextfloat(t)
                t₋₀ = prevfloat(t)
                @test bsplinebasis₊₀(P, i, t) ≈ bsplinebasis₊₀(fP, i, t) ≒ bsplinebasis₊₀(P, i, t₊₀) ≒ bsplinebasis₊₀(fP, i, t₊₀)
                @test bsplinebasis₋₀(P, i, t) ≈ bsplinebasis₋₀(fP, i, t) ≒ bsplinebasis₋₀(P, i, t₋₀) ≒ bsplinebasis₋₀(fP, i, t₋₀)
                @test bsplinebasis(P, i, t) ≈ bsplinebasis(fP, i, t)
            end
        end

        # Check the values of the derivative of B-spline basis funcitons
        for i in 1:n
            for t in rand(5)
                @test (bsplinebasis′₊₀(P, i, t)
                     ≈ bsplinebasis′₊₀(fP, i, t)
                     ≈ bsplinebasis′₋₀(P, i, t)
                     ≈ bsplinebasis′₋₀(fP, i, t)
                     ≈ bsplinebasis′(P, i, t)
                     ≈ bsplinebasis′(fP, i, t))
            end
            for t in k
                t₊₀ = nextfloat(t)
                t₋₀ = prevfloat(t)
                @test bsplinebasis′₊₀(P, i, t) ≈ bsplinebasis′₊₀(fP, i, t) ≒ bsplinebasis′₊₀(P, i, t₊₀) ≒ bsplinebasis′₊₀(fP, i, t₊₀)
                @test bsplinebasis′₋₀(P, i, t) ≈ bsplinebasis′₋₀(fP, i, t) ≒ bsplinebasis′₋₀(P, i, t₋₀) ≒ bsplinebasis′₋₀(fP, i, t₋₀)
                @test bsplinebasis′(P, i, t) ≈ bsplinebasis′(fP, i, t)
            end
        end

        # Check the derivative
        @testset "derivative" begin
            Random.seed!(57)
            for i in 1:n, t in rand(5)
                b′₊₀ = bsplinebasis′₊₀(fP, i, t)
                b′₋₀ = bsplinebasis′₋₀(fP, i, t)
                b′   = bsplinebasis′(fP, i, t)
                a′₊₀ = (bsplinebasis₊₀(fP, i, t+Δt) - bsplinebasis₊₀(fP, i, t-Δt)) / 2Δt
                a′₋₀ = (bsplinebasis₋₀(fP, i, t+Δt) - bsplinebasis₋₀(fP, i, t-Δt)) / 2Δt
                a′   = (bsplinebasis(fP, i, t+Δt) - bsplinebasis(fP, i, t-Δt)) / 2Δt
                @test b′₊₀ ≈ b′₋₀ ≈ b′ ≈ a′₊₀ ≈ a′₋₀ ≈ a′
            end
        end
    end

    @testset "1st degree basis" begin
        Random.seed!(42)

        p = 1
        v = rand(10)
        k = Knots(v) + (p+1)*Knots(0, 1) + Knots(v[2], v[3])
        P = BSplineSpace(p, k)
        fP = FastBSplineSpace(p, k)
        n = dim(P)

        @test degree(P) == degree(fP) == 1

        # Check the values of B-spline basis funcitons
        for i in 1:n, t in rand(5)
            for t in rand(5)
                @test (bsplinebasis₊₀(P, i, t)
                     ≈ bsplinebasis₊₀(fP, i, t)
                     ≈ bsplinebasis₋₀(P, i, t)
                     ≈ bsplinebasis₋₀(fP, i, t)
                     ≈ bsplinebasis(P, i, t)
                     ≈ bsplinebasis(fP, i, t))
            end
            for t in k
                t₊₀ = nextfloat(t)
                t₋₀ = prevfloat(t)
                @test bsplinebasis₊₀(P, i, t) ≈ bsplinebasis₊₀(fP, i, t) ≒ bsplinebasis₊₀(P, i, t₊₀) ≒ bsplinebasis₊₀(fP, i, t₊₀)
                @test bsplinebasis₋₀(P, i, t) ≈ bsplinebasis₋₀(fP, i, t) ≒ bsplinebasis₋₀(P, i, t₋₀) ≒ bsplinebasis₋₀(fP, i, t₋₀)
                @test bsplinebasis(P, i, t) ≈ bsplinebasis(fP, i, t)
            end
        end

        # Check the values of the derivative of B-spline basis funcitons
        for i in 1:n
            for t in rand(5)
                @test (bsplinebasis′₊₀(P, i, t)
                     ≈ bsplinebasis′₊₀(fP, i, t)
                     ≈ bsplinebasis′₋₀(P, i, t)
                     ≈ bsplinebasis′₋₀(fP, i, t)
                     ≈ bsplinebasis′(P, i, t)
                     ≈ bsplinebasis′(fP, i, t))
            end
            for t in k
                t₊₀ = nextfloat(t)
                t₋₀ = prevfloat(t)
                @test bsplinebasis′₊₀(P, i, t) ≈ bsplinebasis′₊₀(fP, i, t) ≒ bsplinebasis′₊₀(P, i, t₊₀) ≒ bsplinebasis′₊₀(fP, i, t₊₀)
                @test bsplinebasis′₋₀(P, i, t) ≈ bsplinebasis′₋₀(fP, i, t) ≒ bsplinebasis′₋₀(P, i, t₋₀) ≒ bsplinebasis′₋₀(fP, i, t₋₀)
                @test bsplinebasis′(P, i, t) ≈ bsplinebasis′(fP, i, t)
            end
        end

        # Check the derivative
        @testset "derivative" begin
            Random.seed!(57)
            for i in 1:n, t in rand(5)
                b′₊₀ = bsplinebasis′₊₀(fP, i, t)
                b′₋₀ = bsplinebasis′₋₀(fP, i, t)
                b′   = bsplinebasis′(fP, i, t)
                a′₊₀ = (bsplinebasis₊₀(fP, i, t+Δt) - bsplinebasis₊₀(fP, i, t-Δt)) / 2Δt
                a′₋₀ = (bsplinebasis₋₀(fP, i, t+Δt) - bsplinebasis₋₀(fP, i, t-Δt)) / 2Δt
                a′   = (bsplinebasis(fP, i, t+Δt) - bsplinebasis(fP, i, t-Δt)) / 2Δt
                @test b′₊₀ ≈ b′₋₀ ≈ b′ ≈ a′₊₀ ≈ a′₋₀ ≈ a′
            end
        end
    end

    @testset "2nd degree basis" begin
        Random.seed!(42)

        p = 2
        v = rand(10)
        k = Knots(v) + (p+1)*Knots(0, 1) + Knots(v[2], v[3])
        P = BSplineSpace(p, k)
        fP = FastBSplineSpace(p, k)
        n = dim(P)

        @test degree(P) == degree(fP) == 2

        # Check the values of B-spline basis funcitons
        for i in 1:n, t in rand(5)
            for t in rand(5)
                @test (bsplinebasis₊₀(P, i, t)
                     ≈ bsplinebasis₊₀(fP, i, t)
                     ≈ bsplinebasis₋₀(P, i, t)
                     ≈ bsplinebasis₋₀(fP, i, t)
                     ≈ bsplinebasis(P, i, t)
                     ≈ bsplinebasis(fP, i, t))
            end
            for t in k
                t₊₀ = nextfloat(t)
                t₋₀ = prevfloat(t)
                @test bsplinebasis₊₀(P, i, t) ≈ bsplinebasis₊₀(fP, i, t) ≒ bsplinebasis₊₀(P, i, t₊₀) ≒ bsplinebasis₊₀(fP, i, t₊₀)
                @test bsplinebasis₋₀(P, i, t) ≈ bsplinebasis₋₀(fP, i, t) ≒ bsplinebasis₋₀(P, i, t₋₀) ≒ bsplinebasis₋₀(fP, i, t₋₀)
                @test bsplinebasis(P, i, t) ≈ bsplinebasis(fP, i, t)
            end
        end

        # Check the values of the derivative of B-spline basis funcitons
        for i in 1:n
            for t in rand(5)
                @test (bsplinebasis′₊₀(P, i, t)
                     ≈ bsplinebasis′₊₀(fP, i, t)
                     ≈ bsplinebasis′₋₀(P, i, t)
                     ≈ bsplinebasis′₋₀(fP, i, t)
                     ≈ bsplinebasis′(P, i, t)
                     ≈ bsplinebasis′(fP, i, t))
            end
            for t in k
                t₊₀ = nextfloat(t)
                t₋₀ = prevfloat(t)
                @test bsplinebasis′₊₀(P, i, t) ≈ bsplinebasis′₊₀(fP, i, t) ≒ bsplinebasis′₊₀(P, i, t₊₀) ≒ bsplinebasis′₊₀(fP, i, t₊₀)
                @test bsplinebasis′₋₀(P, i, t) ≈ bsplinebasis′₋₀(fP, i, t) ≒ bsplinebasis′₋₀(P, i, t₋₀) ≒ bsplinebasis′₋₀(fP, i, t₋₀)
                @test bsplinebasis′(P, i, t) ≈ bsplinebasis′(fP, i, t)
            end
        end

        # Check the derivative
        @testset "derivative" begin
            Random.seed!(57)
            for i in 1:n, t in rand(5)
                b′₊₀ = bsplinebasis′₊₀(fP, i, t)
                b′₋₀ = bsplinebasis′₋₀(fP, i, t)
                b′   = bsplinebasis′(fP, i, t)
                a′₊₀ = (bsplinebasis₊₀(fP, i, t+Δt) - bsplinebasis₊₀(fP, i, t-Δt)) / 2Δt
                a′₋₀ = (bsplinebasis₋₀(fP, i, t+Δt) - bsplinebasis₋₀(fP, i, t-Δt)) / 2Δt
                a′   = (bsplinebasis(fP, i, t+Δt) - bsplinebasis(fP, i, t-Δt)) / 2Δt
                @test b′₊₀ ≈ b′₋₀ ≈ b′ ≈ a′₊₀ ≈ a′₋₀ ≈ a′
            end
        end
    end

    @testset "3rd degree basis" begin
        Random.seed!(42)

        p = 3
        v = rand(10)
        k = Knots(v) + (p+1)*Knots(0, 1) + Knots(v[2], v[3])
        P = BSplineSpace(p, k)
        fP = FastBSplineSpace(p, k)
        n = dim(P)

        @test degree(P) == degree(fP) == 3

        # Check the values of B-spline basis funcitons
        for i in 1:n, t in rand(5)
            for t in rand(5)
                @test (bsplinebasis₊₀(P, i, t)
                     ≈ bsplinebasis₊₀(fP, i, t)
                     ≈ bsplinebasis₋₀(P, i, t)
                     ≈ bsplinebasis₋₀(fP, i, t)
                     ≈ bsplinebasis(P, i, t)
                     ≈ bsplinebasis(fP, i, t))
            end
            for t in k
                t₊₀ = nextfloat(t)
                t₋₀ = prevfloat(t)
                @test bsplinebasis₊₀(P, i, t) ≈ bsplinebasis₊₀(fP, i, t) ≒ bsplinebasis₊₀(P, i, t₊₀) ≒ bsplinebasis₊₀(fP, i, t₊₀)
                @test bsplinebasis₋₀(P, i, t) ≈ bsplinebasis₋₀(fP, i, t) ≒ bsplinebasis₋₀(P, i, t₋₀) ≒ bsplinebasis₋₀(fP, i, t₋₀)
                @test bsplinebasis(P, i, t) ≈ bsplinebasis(fP, i, t)
            end
        end

        # Check the values of the derivative of B-spline basis funcitons
        for i in 1:n
            for t in rand(5)
                @test (bsplinebasis′₊₀(P, i, t)
                     ≈ bsplinebasis′₊₀(fP, i, t)
                     ≈ bsplinebasis′₋₀(P, i, t)
                     ≈ bsplinebasis′₋₀(fP, i, t)
                     ≈ bsplinebasis′(P, i, t)
                     ≈ bsplinebasis′(fP, i, t))
            end
            for t in k
                t₊₀ = nextfloat(t)
                t₋₀ = prevfloat(t)
                @test bsplinebasis′₊₀(P, i, t) ≈ bsplinebasis′₊₀(fP, i, t) ≒ bsplinebasis′₊₀(P, i, t₊₀) ≒ bsplinebasis′₊₀(fP, i, t₊₀)
                @test bsplinebasis′₋₀(P, i, t) ≈ bsplinebasis′₋₀(fP, i, t) ≒ bsplinebasis′₋₀(P, i, t₋₀) ≒ bsplinebasis′₋₀(fP, i, t₋₀)
                @test bsplinebasis′(P, i, t) ≈ bsplinebasis′(fP, i, t)
            end
        end

        # Check the derivative
        @testset "derivative" begin
            Random.seed!(57)
            for i in 1:n, t in rand(5)
                b′₊₀ = bsplinebasis′₊₀(fP, i, t)
                b′₋₀ = bsplinebasis′₋₀(fP, i, t)
                b′   = bsplinebasis′(fP, i, t)
                a′₊₀ = (bsplinebasis₊₀(fP, i, t+Δt) - bsplinebasis₊₀(fP, i, t-Δt)) / 2Δt
                a′₋₀ = (bsplinebasis₋₀(fP, i, t+Δt) - bsplinebasis₋₀(fP, i, t-Δt)) / 2Δt
                a′   = (bsplinebasis(fP, i, t+Δt) - bsplinebasis(fP, i, t-Δt)) / 2Δt
                @test b′₊₀ ≈ b′₋₀ ≈ b′ ≈ a′₊₀ ≈ a′₋₀ ≈ a′
            end
        end
    end

    @testset "4th degree basis" begin
        Random.seed!(42)

        p = 4
        v = rand(10)
        k = Knots(v) + (p+1)*Knots(0, 1) + Knots(v[2], v[3])
        P = BSplineSpace(p, k)
        fP = FastBSplineSpace(p, k)
        n = dim(P)

        @test degree(P) == degree(fP) == 4

        # Check the values of B-spline basis funcitons
        for i in 1:n, t in rand(5)
            for t in rand(5)
                @test (bsplinebasis₊₀(P, i, t)
                     ≈ bsplinebasis₊₀(fP, i, t)
                     ≈ bsplinebasis₋₀(P, i, t)
                     ≈ bsplinebasis₋₀(fP, i, t)
                     ≈ bsplinebasis(P, i, t)
                     ≈ bsplinebasis(fP, i, t))
            end
            for t in k
                t₊₀ = nextfloat(t)
                t₋₀ = prevfloat(t)
                @test bsplinebasis₊₀(P, i, t) ≈ bsplinebasis₊₀(fP, i, t) ≒ bsplinebasis₊₀(P, i, t₊₀) ≒ bsplinebasis₊₀(fP, i, t₊₀)
                @test bsplinebasis₋₀(P, i, t) ≈ bsplinebasis₋₀(fP, i, t) ≒ bsplinebasis₋₀(P, i, t₋₀) ≒ bsplinebasis₋₀(fP, i, t₋₀)
                @test bsplinebasis(P, i, t) ≈ bsplinebasis(fP, i, t)
            end
        end

        # Check the values of the derivative of B-spline basis funcitons
        for i in 1:n
            for t in rand(5)
                @test (bsplinebasis′₊₀(P, i, t)
                     ≈ bsplinebasis′₊₀(fP, i, t)
                     ≈ bsplinebasis′₋₀(P, i, t)
                     ≈ bsplinebasis′₋₀(fP, i, t)
                     ≈ bsplinebasis′(P, i, t)
                     ≈ bsplinebasis′(fP, i, t))
            end
            for t in k
                t₊₀ = nextfloat(t)
                t₋₀ = prevfloat(t)
                @test bsplinebasis′₊₀(P, i, t) ≈ bsplinebasis′₊₀(fP, i, t) ≒ bsplinebasis′₊₀(P, i, t₊₀) ≒ bsplinebasis′₊₀(fP, i, t₊₀)
                @test bsplinebasis′₋₀(P, i, t) ≈ bsplinebasis′₋₀(fP, i, t) ≒ bsplinebasis′₋₀(P, i, t₋₀) ≒ bsplinebasis′₋₀(fP, i, t₋₀)
                @test bsplinebasis′(P, i, t) ≈ bsplinebasis′(fP, i, t)
            end
        end

        # Check the derivative
        @testset "derivative" begin
            Random.seed!(57)
            for i in 1:n, t in rand(5)
                b′₊₀ = bsplinebasis′₊₀(fP, i, t)
                b′₋₀ = bsplinebasis′₋₀(fP, i, t)
                b′   = bsplinebasis′(fP, i, t)
                a′₊₀ = (bsplinebasis₊₀(fP, i, t+Δt) - bsplinebasis₊₀(fP, i, t-Δt)) / 2Δt
                a′₋₀ = (bsplinebasis₋₀(fP, i, t+Δt) - bsplinebasis₋₀(fP, i, t-Δt)) / 2Δt
                a′   = (bsplinebasis(fP, i, t+Δt) - bsplinebasis(fP, i, t-Δt)) / 2Δt
                @test b′₊₀ ≈ b′₋₀ ≈ b′ ≈ a′₊₀ ≈ a′₋₀ ≈ a′
            end
        end
    end

    @testset "5th degree basis" begin
        Random.seed!(42)

        p = 5
        v = rand(10)
        k = Knots(v) + (p+1)*Knots(0, 1) + Knots(v[2], v[3])
        P = BSplineSpace(p, k)
        fP = FastBSplineSpace(p, k)
        n = dim(P)

        @test degree(P) == degree(fP) == 5

        # Check the values of B-spline basis funcitons
        for i in 1:n, t in rand(5)
            for t in rand(5)
                @test (bsplinebasis₊₀(P, i, t)
                     ≈ bsplinebasis₊₀(fP, i, t)
                     ≈ bsplinebasis₋₀(P, i, t)
                     ≈ bsplinebasis₋₀(fP, i, t)
                     ≈ bsplinebasis(P, i, t)
                     ≈ bsplinebasis(fP, i, t))
            end
            for t in k
                t₊₀ = nextfloat(t)
                t₋₀ = prevfloat(t)
                @test bsplinebasis₊₀(P, i, t) ≈ bsplinebasis₊₀(fP, i, t) ≒ bsplinebasis₊₀(P, i, t₊₀) ≒ bsplinebasis₊₀(fP, i, t₊₀)
                @test bsplinebasis₋₀(P, i, t) ≈ bsplinebasis₋₀(fP, i, t) ≒ bsplinebasis₋₀(P, i, t₋₀) ≒ bsplinebasis₋₀(fP, i, t₋₀)
                @test bsplinebasis(P, i, t) ≈ bsplinebasis(fP, i, t)
            end
        end

        # Check the values of the derivative of B-spline basis funcitons
        for i in 1:n
            for t in rand(5)
                @test (bsplinebasis′₊₀(P, i, t)
                     ≈ bsplinebasis′₊₀(fP, i, t)
                     ≈ bsplinebasis′₋₀(P, i, t)
                     ≈ bsplinebasis′₋₀(fP, i, t)
                     ≈ bsplinebasis′(P, i, t)
                     ≈ bsplinebasis′(fP, i, t))
            end
            for t in k
                t₊₀ = nextfloat(t)
                t₋₀ = prevfloat(t)
                @test bsplinebasis′₊₀(P, i, t) ≈ bsplinebasis′₊₀(fP, i, t) ≒ bsplinebasis′₊₀(P, i, t₊₀) ≒ bsplinebasis′₊₀(fP, i, t₊₀)
                @test bsplinebasis′₋₀(P, i, t) ≈ bsplinebasis′₋₀(fP, i, t) ≒ bsplinebasis′₋₀(P, i, t₋₀) ≒ bsplinebasis′₋₀(fP, i, t₋₀)
                @test bsplinebasis′(P, i, t) ≈ bsplinebasis′(fP, i, t)
            end
        end

        # Check the derivative
        @testset "derivative" begin
            Random.seed!(57)
            for i in 1:n, t in rand(5)
                b′₊₀ = bsplinebasis′₊₀(fP, i, t)
                b′₋₀ = bsplinebasis′₋₀(fP, i, t)
                b′   = bsplinebasis′(fP, i, t)
                a′₊₀ = (bsplinebasis₊₀(fP, i, t+Δt) - bsplinebasis₊₀(fP, i, t-Δt)) / 2Δt
                a′₋₀ = (bsplinebasis₋₀(fP, i, t+Δt) - bsplinebasis₋₀(fP, i, t-Δt)) / 2Δt
                a′   = (bsplinebasis(fP, i, t+Δt) - bsplinebasis(fP, i, t-Δt)) / 2Δt
                @test b′₊₀ ≈ b′₋₀ ≈ b′ ≈ a′₊₀ ≈ a′₋₀ ≈ a′
            end
        end
    end

    @testset "_bsplinebasis" begin
        Random.seed!(42)

        k = Knots(rand(10).-1) + Knots(rand(10)) + Knots(rand(10).+1)
        ts = rand(10)

        for p in 0:BasicBSpline.MAX_DEGREE
            fP = FastBSplineSpace(p,k)
            P = BSplineSpace(p,k)
            for t in ts
                j = BasicBSpline._knotindex(fP,t)
                _B = BasicBSpline._bsplinebasis(fP,t,j)
                # _B′ = BasicBSpline._bsplinebasis′(fP,t,j)
                B = [bsplinebasis(fP,i,t) for i in j-p:j]
                B′ = [bsplinebasis′(fP,i,t) for i in j-p:j]
                @test norm(collect(_B) - B) < ε

                _B = bsplinebasis(P,t)[j-p:j]
                @test norm(_B - B) < ε

                _B = bsplinebasis₊₀(P,t)[j-p:j]
                @test norm(_B - B) < ε

                _B = bsplinebasis₋₀(P,t)[j-p:j]
                @test norm(_B - B) < ε

                _B′ = bsplinebasis′(P,t)[j-p:j]
                @test norm(_B′ - B′) < ε

                _B′ = bsplinebasis′₊₀(P,t)[j-p:j]
                @test norm(_B′ - B′) < ε

                _B′ = bsplinebasis′₋₀(P,t)[j-p:j]
                @test norm(_B′ - B′) < ε
            end
        end
    end
end


function parse_paths(wires)
    paths = []

    for w in wires
        coord = (0, 0)
        path = []
        for p in w
            dir = if p[1] == 'U'
                (0, 1)
            elseif p[1] == 'R'
                (1, 0)
            elseif p[1] == 'D'
                (0, -1)
            elseif p[1] == 'L'
                (-1, 0)
            else
                throw(ErrorException("Invalid direction: " * p[1]))
            end
            for i = 1:(parse(Int, p[2:end]))
                coord = coord .+ dir
                push!(path, coord)
            end
        end
        push!(paths, path)
    end

    return paths
end

function shortest_distance(wires)::Int
    paths = parse_paths(wires)

    return minimum(sum.((x -> abs.(x)).(intersect(paths[1], paths[2]))))
end

function shortest_delay(wires)::Int
    paths = parse_paths(wires)
    intersections = intersect(paths[1], paths[2])

    delays = []
    for i in intersections
        s1 = findfirst(x -> x==i, paths[1])
        s2 = findfirst(x -> x==i, paths[2])
        push!(delays, s1+s2)
    end

    return minimum(delays)
end

wires = open("wires.csv") do f
    split.(readlines(f), ",")
end
println(shortest_distance(wires))
println(shortest_delay(wires))


module TLOU

import RecipesBase
using StaticArrays: SVector

"""
Information from the higher level decision: TLOU pricing structure
"""
struct Pricing{N1,N2}
    K::Float64
    C_L::SVector{N1,Float64}
    C_H::SVector{N2,Float64}
    piL::SVector{N1,Float64}
    piH::SVector{N2,Float64}
    function Pricing(K::Real,C_L::SVector{N1,Float64},C_H::SVector{N2,Float64},piL::SVector{N1,Float64},piH::SVector{N2,Float64}) where {N1,N2}
        new{N1,N2}(K, C_L, C_H, piL, piH)
    end
end

"""
Construct Pricing from NTuples
"""
function Pricing(K::Real,C_L::NTuple{N1,T},C_H::NTuple{N2,T},piL::NTuple{N1,T},piH::NTuple{N2,T}) where {N1,N2,T<:Real}
    return Pricing(K,SVector(C_L),SVector(C_H),SVector(piL),SVector(piH))
end

"""
Calling a pricing directly on a capacity value and consumption returns the
corresponding cost of energy.
A capacity of 0 always corresponds the base price 1.0
"""
function (p::Pricing{N1,N2})(c, x) where {N1,N2}
    if x <= c
        idx = price_segment(p, c, Val{:low}())
        price = idx > 0 ? p.piL[idx] : 1.0
        p.K*c + price * x
    else
        idx = price_segment(p, c, Val{:high}())
        price = idx > 0 ? p.piH[idx] : 1.0
        p.K*c + price * x
    end
end

"""
Returns the price value corresponding to a price_segment
Allowed symbols are:
* :low for lower price
* :high for higher price
* :both to get a tuple (low_price, high_price)
"""
function price_value(p::Pricing{N1,N2}, c, s::Symbol) where {N1,N2}
    price_value(p, c, Val(s))
end

function price_value(p::Pricing{N1,N2}, c, ::Val{:low}) where {N1,N2}
    ps = price_segment(p, c, Val{:low}())
    ps == 0 ? 1.0 : p.piL[ps]
end

function price_value(p::Pricing{N1,N2}, c, ::Val{:high}) where {N1,N2}
    ps = price_segment(p, c, Val{:high}())
    ps == 0 ? 1.0 : p.piH[ps]
end

function price_value(p::Pricing{N1,N2}, c, ::Val{:both}) where {N1,N2}
    (psL, psH) = price_segment(p, c, Val{:both}())
    (psL == 0 ? 1.0 : p.piL[psL], psH == 0 ? 1.0 : p.piH[psH])
end

"""
Returns the segment in πL/πH correesponding to a capacity.
Segment 0 corresponds to the baseline, others to an index
in the corresponding price vector.
Allowed symbols are:
* :low for lower price
* :high for higher price
* :both to get a tuple (low_idx, high_idx)
"""
function price_segment(p::Pricing{N1,N2}, c, s::Symbol) where {N1,N2}
    price_segment(p, c, Val(s))
end

function price_segment(p::Pricing{N1,N2}, c, ::Val{:low}) where {N1,N2}
    idxlow = 1
    while idxlow <= N1
        if p.C_L[idxlow] > c
            break
        end
        idxlow += 1
    end
    return idxlow - 1
end

function price_segment(p::Pricing{N1,N2}, c, ::Val{:high}) where {N1,N2}
    idxhigh = 1
    while idxhigh <= N2
        if p.C_H[idxhigh] > c
            break
        end
        idxhigh += 1
    end
    return idxhigh - 1
end

function price_segment(p::Pricing{N1,N2}, c, ::Val{:both}) where {N1,N2}
    (price_segment(p, c, Val{:low}()), price_segment(p, c, Val{:high}()))
end

"""
Represent a TLOU pricing graphically.
mcoeff adjusts how far the curves are represented after last jump point
"""
function RecipesBase.plot(pr::Pricing{N1,N2}; mcoeff = 1.1) where {N1,N2}
    m = max(maximum(pr.C_L),maximum(pr.C_H)) * mcoeff ## slightly larger than last
    p = RecipesBase.plot(
        [0.0,m], [0.0,m*pr.K],
        label = "Booking fee", color = :orange, alpha = 0.5
    )
    RecipesBase.plot!(p,
        [0.0,max(first(pr.C_L),first(pr.C_H))], [1.0, 1.0],
        color = :black, line = :dot
    )
    RecipesBase.plot!(p,
        [first(pr.C_L),first(pr.C_L)], [1.0, first(pr.piL)],
        color = :blue, line = :dot
    )
    RecipesBase.plot!(p,
        [first(pr.C_H),first(pr.C_H)], [1.0, first(pr.piH)],
        color = :red, line = :dot
    )
    for idx in Base.OneTo(N1-1)
        RecipesBase.plot!(p,
            pr.C_L[idx:idx+1], [pr.piL[idx],pr.piL[idx]],
            color = :blue, line = :dot
        )
        RecipesBase.plot!(p,
            [pr.C_L[idx+1],pr.C_L[idx+1]], [pr.piL[idx:idx+1]],
            color = :blue, line = :dot
        )
    end
    for idx in Base.OneTo(N2-1)
        RecipesBase.plot!(p,
            pr.C_H[idx:idx+1], [pr.piH[idx],pr.piH[idx]],
            color = :red, line = :dot
        )
        RecipesBase.plot!(p,
            [pr.C_H[idx+1],pr.C_H[idx+1]], [pr.piH[idx:idx+1]],
            color = :red, line = :dot
        )
    end
    RecipesBase.plot!(p,
        [last(pr.C_L), m], [last(pr.piL),last(pr.piL)],
        color = :blue, line = :dot
    )
    RecipesBase.plot!(p,
        [last(pr.C_H), m], [last(pr.piH),last(pr.piH)],
        color = :red, line = :dot
    )
    return p
end

"""
Captures both higher and lower level decisions of the TLOU
"""
struct Decision{N1,N2}
    p::Pricing{N1,N2}
    c::Float64
    Decision(p::Pricing{N1,N2},c::Real) where {N1,N2} = new{N1,N2}(p,c)
end

end # module


## Julia 1.1.0

# Initialize
using Plots
plotlyjs() # Backend PlotlyJS

x = range(-4 * pi, stop = 4 * pi, length = 1000)

plot(x, sin.(x))
savefig("./png/sin.png")


"""
`ODE`: Ordinary Differential Equation

Defines an initial value problem
```math
\\dot{q} (t) = f(t, q(t)) , \\qquad q(t_{0}) = q_{0} ,
```
with vector field ``f``, initial condition ``q_{0}`` and the solution
``q`` taking values in ``\\mathbb{R}^{d}``.

### Fields

* `d`: dimension of dynamical variable ``q`` and the vector field ``f``
* `f`: function computing the vector field
* `t₀`: initial time
* `q₀`: initial condition

The function `f` providing the vector field must have the interface
```julia
    function f(t, q, fq)
        fq[1] = ...
        fq[2] = ...
        ...
    end
```
where `t` is the current time, `q` is the current solution vector, and
`fq` is the vector which holds the result of evaluating the vector field ``f``
on `t` and `q`.

"""
immutable ODE{T,F} <: Equation{T}
    d::Int
    n::Int
    f::F
    t₀::T
    q₀::Array{T,2}

    function ODE(d, n, f, t₀, q₀)
        @assert d == size(q₀,1)
        @assert n == size(q₀,2)
        @assert T == eltype(q₀)
        @assert ndims(q₀) ∈ (1,2)

        if ndims(q₀) == 1
            q₀ = reshape(q₀, d, n)
        end

        new(d, n, f, t₀, q₀)
    end
end

function ODE{T,F}(f::F, t₀::Real, q₀::DenseArray{T})
    ODE{T,F}(size(q₀, 1), size(q₀, 2), f, t₀, q₀)
end

function ODE(f, q₀)
    ODE(f, 0, q₀)
end

Base.hash(ode::ODE, h::UInt) = hash(ode.d, hash(ode.n, hash(ode.f, hash(ode.t₀, hash(ode.q₀, h)))))
Base.:(==){T1, T2, F1, F2}(ode1::ODE{T1,F1}, ode2::ODE{T2,F2}) = (
                                ode1.d == ode2.d
                             && ode1.n == ode2.n
                             && ode1.f == ode2.f
                             && ode1.t₀ == ode2.t₀
                             && ode1.q₀ == ode2.q₀)


########### Curve Section
import Base.insert!
insert!(dic::Dict, k::Number, val::Number) = dic[k] = val;
return nothing;
using Dictionaries
#const FinMCDict=Dict
const FinMCDict = Dictionaries.Dictionary

const Curve = FinMCDict{num1, num2} where {num1 <: Number, num2 <: Number}

function keys_(x::Curve{num1, num2}) where {num1 <: Number, num2 <: Number}
    return sort(collect(keys(x)))
end

function values_(x::Curve{num1, num2}) where {num1 <: Number, num2 <: Number}
    T_d = collect(keys(x))
    idx_ = sortperm(T_d)
    return collect(values(x))[idx_]
end

function Curve(r_::Array{num1}, T::num2) where {num1 <: Number, num2 <: Number}
    r = FinMCDict{num2, num1}()
    Nstep = length(r_) - 1
    dt = T / Nstep
    for i = 1:length(r_)
        insert!(r, (i - 1) * dt, r_[i])
    end
    return r
end
function Curve(r_::Array{num1}, T::Array{num2}) where {num1 <: Number, num2 <: Number}
    @assert length(r_) == length(T)
    @assert T == sort(T)
    r = FinMCDict{num2, num1}()
    Nstep = length(r_) - 1
    for i = 1:length(r_)
        insert!(r, T[i], r_[i])
    end
    return r
end
function ImpliedCurve(r_::Array{num1}, T::Array{num2}) where {num1 <: Number, num2 <: Number}
    @assert length(r_) == length(T)
    @assert length(r_) >= 1
    @assert T == sort(T)

    r = FinMCDict{num2, num1}()
    Nstep = length(r_) - 1
    insert!(r, 0.0, 0.0)
    insert!(r, T[1], r_[1] * 2.0)
    prec_r = r_[1] * 2.0
    for i = 2:length(r_)
        tmp_r = (r_[i] * T[i] - r_[i-1] * T[i-1]) * 2 / (T[i] - T[i-1]) - prec_r
        insert!(r, T[i], tmp_r)
        prec_r = tmp_r
    end
    return r
end
function ImpliedCurve(r_::Array{num1}, T::num2) where {num1 <: Number, num2 <: Number}
    N = length(r_)
    dt_ = T / (N)
    t_ = collect(dt_:dt_:T)
    return ImpliedCurve(r_, t_)
end
function (x::Curve)(t::Number, dt::Number)
    T = collect(keys_(x))
    r = collect(values_(x))
    return intgral_2(t + dt, T, r) - intgral_2(t, T, r)
end

using Interpolations
function intgral_2(x::num, T::Array{num1}, r::Array{num2}) where {num <: Number, num1 <: Number, num2 <: Number}
    @assert length(T) == length(r)
    if (x == 0.0)
        return 0.0
    end
    @assert x >= 0.0
    idx_ = findlast(y -> y < x, T)
    out = sum([(r[i] + r[i+1]) * 0.5 * (T[i+1] - T[i]) for i = 1:(idx_-1)])
    if x <= T[end]
        itp = LinearInterpolation([T[idx_], T[idx_+1]], [r[idx_], r[idx_+1]], extrapolation_bc = Flat())
        out = out + (r[idx_] + itp(x)) * 0.5 * (x - T[idx_])
    else
        #continuation
        out = out + (r[idx_] + r[idx_-1]) * 0.5 * (x - T[idx_])
    end
    return out
end

function integral(r::FinMCDict{num1, num2}, t::Number) where {num1 <: Number, num2 <: Number}
    T = collect(keys_(r))
    r = collect(values_(r))
    return intgral_2(t, T, r)
end
integral(x::num1, t::num2) where {num1 <: Number, num2 <: Number} = x * t;

### end curve section