module FirstAndLastPlaceValues

export ufp, ulp, uls, rre, ε⁻, ε⁺, eta

#=
References

Accurate Floating-Point Summation Part I: Faithful Rounding
Siegfried M. Rump, Takeshi Ogita and Shin'ichi Oishi
http://oishi.info.waseda.ac.jp/~oishi/papers/RuOgOi07I.pdf

Fast quadruple-double floating point format
Naoya Yamanaka and Shin’ichi Oishi
2014-Jan-01

Error Estimation of Floating-Point Summation and Dot Product
Siegfried M. Rump

On the definition of ulp(x)
Jean-Michel Muller

Computing predecessor and successor in rounding to nearest
Siegfried Rump, Paul Zimmermann, Sylvie Boldo, Guillaume Melquiond

On various ways to split a floating-point number
Claude-Pierre Jeannerod, Jean-Michel Muller, Paul Zimmermann
=#

two(::Type{T}) where {T<:Real} = one(T) + one(T)
two(::Type{Float64}) = 2.0
two(::Type{Float32}) = 2.0f0
two(::Type{Float16}) = Float16(2.0)

#=
  Nicolas Fabiano, Jean-Michel Muller, Joris Picot.
  Algorithms for triple-word arithmetic.
  IEEE Transactions on Computers, Institute of Electrical and Electronics Engineers, 2019, 68 (11), pp.1573-1583.
  10.1109/TC.2019.2918451. hal-01869009v2
  Sylvie Boldo, Mioara Joldes, Jean-Michel Muller, Valentina Popescu.
  Formal Verification of a FloatingPoint Expansion Renormalization Algorithm.
  8th International Conference on Interactive Theorem Proving (ITP’2017), Sep 2017, Brasilia, Brazil.
  hal-01512417f
  ACCURATE FLOATING-POINT SUMMATION PART I: FAITHFUL ROUNDING
  SIEGFRIED M. RUMP, TAKESHI OGITA, AND SHIN’ICHI OISHI
  in SIAM J. Sci. Comput. Volume 31, Issue 1, pp. 189-224 (2008)
=#

#=
  ufp(x)  unit in the first place
              the weight of the most significant bit
  ulp(x)  unit in the last place
              the weight of the least significant bit
  uls(x)  unit in the last significant place
              the weight of the rightmost nonzero bit
              the largest power of 2 that divides x
              the largest k s.t. x / 2^k is an integer

  reu(T)  roundoff error unit
             2^(-precision)
             ulp(1) / 2

  blp(x)  bit in the last place:              ulp(x) == 2.0^blp(x)
  bfp(x)  bit in the first place:             ufp(x) == 2.0^bfp(x)
  bls(x)  bit in the last significant place:  uls(x) == 2.0^bls(x)
=#
#=
julia> x = cbrt(17*pi)        #  3.765878158314341
julia> ufp(x),ulp(x),uls(x)   #  (2.0, 4.440892098500626e-16, 8.881784197001252e-16)
julia> bfp(x),blp(x),bls(x)   #  (1, -51, -50)
julia> x = 1/cbrt(17*pi)      #  0.26554231389355776
julia> ufp(x),ulp(x),uls(x)   #  (0.25, 5.551115123125783e-17, 4.440892098500626e-16)
julia> bfp(x),blp(x),bls(x)   #  (-2, -54, -51)
=#

#=
    The relative rounding error unit (reu) ,
    the distance from 1.0 to the next smaller floating-point number, is denoted by eps, and
    the underflow unit by eta, that is the smallest positive (subnormal) floating-point number.

For IEEE 754 double precision we have eps = 2^(−53) and eta = 2^(−1074).
      The smallest positive normalized floating-point number is ets = (1/2)(eps^(-1))(eta) = (1/2)(2^(53-1074))
=#
#=
const BitPrecision = 1 + trailing_ones(Base.significand_mask(Float64))  # 53 = 1 implicit bit + 52 significand bits

const reu = 1.0 - prevfloat(1.0)  # 2.0^(-53)          # 1.0 - prevfloat(1.0)
const eta = nextfloat(0.0)        # 2.0^(-1074)        # smallest positive subnormal == nextfloat(0.0)
const ets = floatmin(Float64)     # 2.0^(53-1074) / 2  # smallest positive normal    == floatmin(Float64)
=#

explicit_precision(::Type{T}) where {T} = trailing_ones(Base.significand_mask(T))
implicit_precision(::Type{T}) where {T} = one(T) + explicit_precision(T)

reu(::Type{T}) where {T} = one(T) - prevfloat(one(T))
eta(::Type{T}) where {T} = nextfloat(zero(T))
ets(::Type{T}) where {T} = floatmin(T)

ufp(x) = 2.0^floor(Int,log2(abs(x)))
ulp(x::T) where {T} = ufp(x) * 2.0^(-explicit_precision(T)+1)
uls(x) = ulp(x) * 2.0^(trailing_zeros(reinterpret(UInt64,x)))

bfp(x) = round(Int, log2(ufp(x)))
blp(x) = round(Int, log2(ulp(x)))
bls(x) = round(Int, log2(uls(x)))

# for x <= sqrt(floatmax(T)) !! bounds need testing
fast_ufp(x::Float64) = reinterpret(Float64, reinterpret(UInt64, x) & 0xfff0000000000000)
fast_ufp(x::Float32) = reinterpret(Float32, reinterpret(UInt32, x) & 0xfff0_0000)
fast_ufp(x::Float16) = reinterpret(Float16, reinterpret(UInt16, x) & 0xfff0)

#=
eta is the smallest positive (nonzero, denormal)
eta(T) = nextfloat(zero(T))
=#
eta(::Type{T}) where {T<:AbstractFloat} = nextfloat(zero(T))

eta(::Type{Float64}) = 5.0e-324
eta(::Type{Float32}) = 1.0f-45
eta(::Type{Float16}) = Float16(6.0e-8)

#=
  epsilon (ε)
  ε⁻(T) = (one(T) - prevfloat(one(T)))
  ε⁺(T) = (nextfloat(one(T)) - one(T))
=#
ε⁻(::Type{T}) where {T<:AbstractFloat} = (one(T) - prevfloat(one(T)))
ε⁺(::Type{T}) where {T<:AbstractFloat} = (nextfloat(one(T)) - one(T))

ε⁻(::Type{Float64}) = 1.1102230246251565e-16
ε⁻(::Type{Float32}) = 5.9604645f-8
ε⁻(::Type{Float16}) = Float16(0.0004883)

ε⁺(::Type{Float64}) = 2.220446049250313e-16
ε⁺(::Type{Float32}) = 1.1920929f-7
ε⁺(::Type{Float16}) = Float16(0.000977)

#=
  relative rounding error (rre)
  rre(T) = (nextfloat(one(T)) - one(T)) / (one(T) + one(T))
  rre(T) = eps(T) / 2
=#

rre(::Type{Float64}) = 1.1102230246251565e-16
rre(::Type{Float32}) = 1.1920929f-7
rre(::Type{Float16}) = Float16(0.000977)

rre(::Type{T}) where {T<:AbstractFloat} =
    (nextfloat(one(T)) - one(T)) / two(T)

#=
inv2rre(T)
=#
inv2rre(::Type{T}) where {T<:AbstractFloat} =
    inv(two(T))*rre(T) + one(T)

#=
ufp(x) unit in the first place of x

ufp(zero(T)) ≝ zero(T)

ufp(x::T) ≝ 2^floor(Int, log2(abs(x)))
          ≡ ldexp(one(T), floor(Int,log2(abs(x))))

the unsigned constants used in `ufp(x::T)`
     Float64: ((~UInt64(0)) >> 52) << 52
     Float32: ((~UInt32(0)) >> 23) << 23
     Float16: ((~UInt16(0)) >> 10) << 10

the shifts: Base.trailing_ones(Base.significand_mask)
=#

ulp(::Type{T}) where {T<:AbstractFloat} = two(T) * ε⁻(T)

ulp(::Type{Float64}) = 2.220446049250313e-16
ulp(::Type{Float32}) = 1.1920929f-7
ulp(::Type{Float16}) = Float16(0.000977)

ulp(x::T) where {T<:AbstractFloat} = two(T) * ε⁻(T) * ufp(x)
# ulp(x::T) where {T<:AbstractFloat} = ε⁺(T) * ufp(x)

ulp(x::Float64) = 2.220446049250313e-16 * ufp(x)
ulp(x::Float32) = 1.1920929f-7 * ufp(x)
ulp(x::Float16) = Float16(0.000977) * ufp(x)

end # FirstAndLastPlaceValues


module LogisticRegression

using DataModel
using StatsBase

export LogisticRegressionModel
"""
Datamodel for logistic regression

Fields
    - d: dimension
    - x: covariantes N x d
    - y: labels (±1)
    - priorPrec: prior precision (normal prior)

Constructors
    `LogisticRegressionModel(d,x,y,priorPrec;intercept=false, intercept_priorprec = 1.0)`
    `intercept = true` extend x to include an intercept term and adds
    `intercept_priorprec` to the prior precision matrix
"""
type LogisticRegressionModel <: AbstractDataModel
    d::Int64
    x::Array{Float64,2}
    y::Array{Float64}
    priorPrec::Array{Float64,2}
    true_weights::Nullable{Array{Float64,1}}
    function LogisticRegressionModel(x,y,priorPrec::Array{Float64,1};intercept=false, intercept_priorprec= 1.0)
        priorPrec = diagm(priorPrec)
        d = size(x)[2]
        N = size(x)[1]
        @assert N == length(y)
        @assert size(priorPrec)[1] == size(priorPrec)[2] == d
        @assert isposdef(priorPrec)
        if intercept
            d += 1
            x = hcat(ones(N),x)
            priorExtended = zeros(d,d)
            priorExtended[1,1] = intercept_priorprec
            priorExtended[2:end,2:end] = priorPrec
            priorPrec = priorExtended
        end
        new(d,x,y,priorPrec,Nullable{Array{Float64,1}}())
    end
    function LogisticRegressionModel(x,y,priorPrec::Array{Float64,2};intercept=false, intercept_priorprec= 1.0)
        d = size(x)[2]
        N = size(x)[1]
        @assert N == length(y)
        @assert size(priorPrec)[1] == size(priorPrec)[2] == d
        @assert isposdef(priorPrec)
        @assert intercept_priorprec > 0.
        if intercept
            d += 1
            x = hcat(ones(N),x)
            priorExtended = zeros(d,d)
            priorExtended[1,1] = intercept_priorprec
            priorExtended[2:end,2:end] = priorPrec
            priorPrec = priorExtended
        end
        new(d,x,y,priorPrec,Nullable{Array{Float64,1}}())
    end
    function LogisticRegressionModel(x,y,priorPrec,true_weights::Array{Float64,1};intercept=false,intercept_priorprec= 1.0)
        @assert size(x)[1] == length(y) == size(priorPrec)[1]
        d = size(x)[2]
        N = size(x)[1]
        @assert isposdef(priorPrec)
        @assert intercept_priorprec > 0.
        if intercept
            d += 1
            x = hcat(ones(N),x)
            precExtended = zeros(d,d)
            priorExtended[1,1] = intercept_priorprec
            priorExtended[2:end,2:end] = priorPrec
        end
        new(d,x,y,priorPrec,Nullable(vcat(0,true_weights)))
    end
end

#-------------------------
# Helper functions
#-------------------------
logit(z) = 1.0./(1.0.+exp(-z))
log_logit(z)= -log(1.0 .+ exp(-z))
grad_log_logit(z)=1.0-logit(z)
function ddlogit(x)
    return exp(-x)/(1+exp(-x))^2
end

"""

"""
function DataModel.getN(dm::LogisticRegressionModel)
  return length(dm.y)
end

"""
`DataModel.getllik(dm::LogisticRegressionModel;nobs=length(dm.y))`
returns a full loglikelihood function. The optional `nobs` keyword argument restrict the dataset to the first `nobs` data points.
"""
function DataModel.getllik(dm::LogisticRegressionModel;nobs::Int64=length(dm.y))
    function logdensity(beta)
        d=dm.d
        x=dm.x
        y=dm.y
        priorPrec=dm.priorPrec
        log_prior = -0.5 * dot(beta,priorPrec*beta)
        log_like= sum(log_logit((x[1:nobs,:]* beta).*y[1:nobs]))
        return log_prior+log_like
    end
    return logdensity
end

"""
`DataModel.getgrad(dm::LogisticRegressionModel,batchsize;nobs=length(dm.y))`
returns a stochastic gradient function using batches of size `batchsize`. The optional `nobs` keyword argument restrict the dataset to the first `nobs` data points.
"""
function DataModel.getgrad(dm::LogisticRegressionModel,batchsize::Int64;nobs::Int64=length(dm.y))
    function grad_log_posterior_sub(beta)
        d=dm.d
        x=dm.x
        y=dm.y
        priorPrec=dm.priorPrec
        chosen_indices=sample(1:nobs,batchsize,replace=false)
        log_prior_gradient= -priorPrec* beta

        #first step: compute y*grad_log_logit(y*beta*x)
        weights = y[chosen_indices].*grad_log_logit((x[chosen_indices,:] * beta ).*y[chosen_indices])
        #second ztep: compute y*grad_log_logit(y*beta*x)*x

        return log_prior_gradient+(1.0*nobs)/batchsize*(reshape(weights,1,batchsize)* x[chosen_indices,:])[:]
    end
    return grad_log_posterior_sub
end
"""
`getgrad_i(dm::LogisticRegressionModel,batchsize;nobs=length(dm.y))`
returns a stochastic gradient function where index can be specified in array
"""
function getgrad_i(dm::LogisticRegressionModel;nobs=length(dm.y))
    function grad_log_posterior_sub(beta::Array{Float64,1},chosen_indices)
        d=dm.d
        x=dm.x
        y=dm.y
        priorPrec=dm.priorPrec
        batchsize=length(chosen_indices)
        log_prior_gradient= -priorPrec* beta

        #first step: compute y*grad_log_logit(y*beta*x)
        weights = y[chosen_indices].*grad_log_logit((x[chosen_indices,:] * beta ).*y[chosen_indices])
        #second ztep: compute y*grad_log_logit(y*beta*x)*x

        return log_prior_gradient+(1.0*nobs)/batchsize*(reshape(weights,1,batchsize)* x[chosen_indices,:])[:]
    end
    return grad_log_posterior_sub
end

"""
`DataModel.getfullgrad(dm::LogisticRegressionModel;nobs=length(dm.y))`
returns the full gradient function. The optional `nobs` keyword argument restrict the dataset to the first `nobs` data points.
"""
function DataModel.getfullgrad(dm::LogisticRegressionModel;nobs=length(dm.y))

    function grad_log_posterior(beta)::Array{Float64,1}
        d=dm.d
        x=dm.x
      y=dm.y
        priorPrec=dm.priorPrec
        chosen_indices=vec(1:nobs)
        log_prior_gradient= -priorPrec* beta

        #first step: compute y*grad_log_logit(y*beta*x)
        weights = y[chosen_indices].*grad_log_logit((x[chosen_indices,:] * beta ).*y[chosen_indices])
        #second ztep: compute y*grad_log_logit(y*beta*x)*x

        return log_prior_gradient+(reshape(weights,1,nobs)* x[chosen_indices,:])[:]
    end
    return grad_log_posterior
end
"""
`DataModel.getfullhess(dm::LogisticRegressionModel;nobs=length(dm.y))`
returns the full hessian function. The optional `nobs` keyword argument restrict the dataset to the first `nobs` data points.
"""
function DataModel.getfullhess(dm::LogisticRegressionModel;nobs=length(dm.y))

    d=dm.d
    x=dm.x
    y=dm.y
    Cinv=dm.priorPrec
    function hessfun(beta_vec)
      hess=zeros(d,d)
      hess+=Cinv;
      for i=1:(nobs)
        hess+=ddlogit(y[i]*dot(x[i,:],beta_vec)[1])*y[i]^2*x[i,:]*x[i,:]';
      end
      hess+=Cinv;
      return hess
    end
    return hessfun;
end

"""
`artificial_logreg_dm(nObs::Int64,dim::Int64;seed::Int64 =1, diagonal_covariance::Bool = false, intercept::Bool = false)` creates an artificial dataset which samples `nObs` observations from the logistic regression model. The generating weights are saved in `dm.true_weights`.
"""
function artificial_logreg_dm(nObs::Int64,dim::Int64;seed::Int64 =1, diagonal_covariance::Bool = false, intercept::Bool = false)
    srand(seed)
    # output: (X,Y) [(NxD), (N,1)], observations and labels
    # .
    # . covariance (size dxd)
    P   = diagonal_covariance ? diagm(-1+2*rand(dim)):(-1+2*rand(dim,dim))
    cov = P * P'
    # . mean (size dx1), uniformly distributed on [0,1]
    mu = rand(dim)
    # . observations (size NxD)
    X = (repmat(mu,1,nObs)+P*randn(dim,nObs))'
    if intercept
        X = hcat(ones(nObs),X)
    end
    # . true parameters (size dx1)
    priorCov = 10*eye(dim)
    priorPrec = 0.1*eye(dim)
    C = copy(priorPrec)
    if intercept
        w = vcat(randn(1),chol(priorCov)'*randn(dim))
        dim += 1
        priorPrec= eye(dim)
        priorPrec[2:end,2:end] = C
    else
        w = chol(priorCov)'*randn(dim)
    end
    # . generate labels (size Nx1)
    y01      = rand(nObs).<logit(X*w)
    y        = y01*2.-1.
    dm = LogisticRegressionModel(X,y,priorPrec)
    dm.true_weights = Nullable(w)
    srand()
    dm
end
end


## Exercise 1-2
## Start the Julia REPL and use it as a calculator.

## 1. How many seconds are there in 42 minutes 42 seconds?
println("Ans 1:")

## 2. How many miles are there in 10 kilometers?
## TIP
## There are 1.61 kilometers in a mile.
println("Ans 2:")

## If you run a 10-kilometer race in 37 minutes 48 seconds, what is your average pace (time per mile in minutes and seconds)? What is your average speed in miles per hour?
println("Ans 3:")

println("End.")


using Base.Test

try
  using ZAdjoin
catch
  info("Testing as a module. Not installed as a package.")
  include("../src/ZAdjoin.jl")
  using ZAdjoin
end

# ZAdjoin.cyclotomic(n) is recursive, requiring cyclotomic
# polynomials for all proper divisors of n (including 1.)
@test ZAdjoin.cyclotomic(24)==[1,0,0,0,-1,0,0,0,1] # 1-x^4+x^8
@test ZAdjoin.cyclotomic(21)==[1,-1,0,1,-1,0,1,0,-1,1,0,-1,1] # 1-x+x^3-x^4+x^6-x^8+x^9-x^11+x^12

nothing


# This file is part of TaylorSeries.jl, MIT licensed
#
# Tests for TaylorSeries implementation
using TaylorSeries
using FactCheck
using Compat
import Compat.String

FactCheck.setstyle(:compact)
# FactCheck.onlystats(true)

facts("Tests for Taylor1 expansions") do
    ta(a) = Taylor1([a,one(a)],15)
    t = Taylor1(Int,15)
    tim = im*t
    zt = zero(t)
    ot = 1.0*one(t)
    tol1 = eps(1.0)

    @fact Taylor1([0,1,0,0]) == Taylor1(3)  --> true
    @fact get_coeff(Taylor1(Complex128,3),1) == complex(1.0,0.0) --> true
    @fact eltype(convert(Taylor1{Complex128},ot)) == Complex128  --> true
    @fact eltype(convert(Taylor1{Complex128},1)) == Complex128  --> true
    @fact convert(Taylor1{Complex{Int}},[0,2]) == (2+0im)*t  --> true
    @fact convert(Taylor1{BigFloat},[0.0, 1.0]) == ta(big(0.0))  --> true
    @fact promote(0,Taylor1(1.0,0)) == (zt,ot)  --> true
    @fact eltype(promote(ta(0.0),zeros(Int,2))[2]) == Float64  --> true
    @fact eltype(promote(0,Taylor1(ot))[1]) == Float64  --> true
    @fact eltype(promote(1.0+im, zt)[1]) == Complex{Float64}  --> true
    @fact eltype(TaylorSeries.fixshape(zt,ot)[1]) == Float64  --> true

    @fact length(Taylor1(10)) == 10  --> true
    @fact length(TaylorSeries.fixshape(zt,Taylor1([1.0]))[2]) == 15  --> true
    @fact eltype(TaylorSeries.fixshape(zt,Taylor1([1.0]))[1]) == Float64  --> true
    @fact TaylorSeries.firstnonzero(t) == 1  --> true
    @fact TaylorSeries.firstnonzero(zt) == zt.order+1  --> true

    @fact t == Taylor1(ta(0),15)  --> true
    @fact ot == 1  --> true
    @fact 0.0 == zt  --> true
    @fact get_coeff(tim,1) == complex(0,1)  --> true
    @fact zt+1.0 == ot  --> true
    @fact 1.0-ot == zt  --> true
    @fact t+t == 2t  --> true
    @fact t-t == zt  --> true
    @fact +t == -(-t)  --> true

    tsquare = Taylor1([0,0,1],15)
    @fact t * true == t  --> true
    @fact false * t == zero(t)  --> true
    @fact t^0 == t^0.0 == one(t)  --> true
    @fact t*t == tsquare  --> true
    @fact t*1 == t -->  true
    @fact 0*t == zt -->  true
    @fact (-t)^2 == tsquare  --> true
    @fact t^3 == tsquare*t  --> true
    @fact tsquare/t == t  --> true
    @fact t/(t*3) == (1/3)*ot  --> true
    @fact t/3im == -tim/3  --> true
    @fact 1/(1-t) == Taylor1(ones(t.order+1))  --> true
    @fact Taylor1([0,1,1])/t == t+1  --> true
    @fact (t+im)^2 == tsquare+2im*t-1  --> true
    @fact (t+im)^3 == Taylor1([-1im,-3,3im,1],15)  --> true
    @fact (t+im)^4 == Taylor1([1,-4im,-6,4im,1],15)  --> true
    @fact imag(tsquare+2im*t-1) == 2t  --> true
    @fact (Rational(1,2)*tsquare).coeffs[3] == 1//2  --> true
    @fact t^2/tsquare == ot  --> true
    @fact ((1+t)^(1/3)).coeffs[3]+1/9 <= tol1  --> true
    @fact 1-tsquare == (1+t)-t*(1+t)  --> true
    @fact (1-tsquare)^2 == (1+t)^2.0 * (1-t)^2.0  --> true
    @fact (sqrt(1+t)).coeffs[3] == -1/8  --> true
    @fact ((1-tsquare)^(1//2))^2 == 1-tsquare  --> true
    @fact ((1-t)^(1//4)).coeffs[15] == -4188908511//549755813888  --> true
    @fact abs(((1+t)^3.2).coeffs[14] + 5.4021062656e-5) < tol1  --> true

    @fact isapprox( rem(4.1 + t,4).coeffs[1], (0.1 + t).coeffs[1] )  --> true
    @fact isapprox( mod(4.1 + t,4).coeffs[1], (0.1 + t).coeffs[1] )  --> true
    @fact isapprox( mod2pi(2pi+0.1+t).coeffs[1],(0.1 + t).coeffs[1])  --> true

    @fact abs(ta(1))  --> ta(1)
    @fact abs(ta(-1.0))  --> -ta(-1.0)

    @fact log(exp(tsquare)) == tsquare  --> true
    @fact exp(log(1-tsquare)) == 1-tsquare  --> true
    @fact log((1-t)^2) == 2*log(1-t)  --> true
    @fact real(exp(tim)) == cos(t)  --> true
    @fact imag(exp(tim)) == sin(t)  --> true
    @fact exp(conj(tim)) == cos(t)-im*sin(t) == exp(tim')  --> true
    @fact (exp(t))^(2im) == cos(2t)+im*sin(2t)  --> true
    @fact (exp(t))^Taylor1([-5.2im]) == cos(5.2t)-im*sin(5.2t)  --> true
    @fact get_coeff(convert(Taylor1{Rational{Int}},cos(t)),8) ==
        1//factorial(8)  --> true
    @fact abs((tan(t)).coeffs[8]- 17/315) < tol1  --> true
    @fact abs((tan(t)).coeffs[14]- 21844/6081075) < tol1  --> true
    @fact evaluate(exp(Taylor1([0,1],17)),1.0) == 1.0*e  --> true
    @fact evaluate(exp(Taylor1([0,1],1))) == 1.0  --> true
    @fact evaluate(exp(t),t^2) == exp(t^2)  --> true

    @fact sin(asin(tsquare)) == tsquare --> true
    @fact tan(atan(tsquare)) == tsquare --> true
    @fact atan(tan(tsquare)) == tsquare --> true
    @fact asin(t) + acos(t) == pi/2 --> true
    @fact derivative(acos(t)) == - 1/sqrt(1-t^2) --> true

    @fact - sinh(t) + cosh(t) == exp(-t) --> true
    @fact  sinh(t) + cosh(t) == exp(t) --> true
    @fact evaluate(- sinh(t)^2 + cosh(t)^2 , rand()) == 1 --> true #works in an eps() difference, but ideally it should return a 1 + 𝒪(t¹⁶) Taylor.
    @fact tanh(t + 0im) == -1im * tan(t*1im) --> true
    @fact  evaluate(tanh(t/2),1.5) == evaluate(sinh(t) / (cosh(t) + 1),1.5) --> true
    @fact cosh(t) == real(cos(im*t)) --> true
    @fact sinh(t) == imag(sin(im*t)) --> true

    v = [sin(t), exp(-t)]
    @fact evaluate(v) == [0.0, 1.0]  --> true
    @fact evaluate(v, complex(0.0,0.2)) ==
        [complex(0.0,sinh(0.2)),complex(cos(0.2),sin(-0.2))]  --> true
    @fact derivative(5, exp(ta(1.0))) == exp(1.0)  --> true
    @fact derivative(3, exp(ta(1.0pi))) == exp(1.0pi)  --> true
    @fact isapprox(derivative(10, exp(ta(1.0pi))) , exp(1.0pi) )  --> true
    @fact integrate(derivative(exp(t)),1) == exp(t)  --> true
    @fact integrate(cos(t)) == sin(t)  --> true

    @fact promote(ta(0.0), t) == (ta(0.0),ta(0.0))  --> true

    @fact_throws ArgumentError abs(ta(big(0)))
    @fact_throws ArgumentError 1/t
    @fact_throws ArgumentError zt/zt
    @fact_throws ArgumentError t^1.5
    @fact_throws DomainError t^(-2)
    @fact_throws ArgumentError sqrt(t)
    @fact_throws ArgumentError log(t)
    @fact_throws ArgumentError cos(t)/sin(t)
    @fact_throws AssertionError derivative(30, exp(ta(1.0pi)))

    @fact string(ta(-3)) == " - 3 + 1 t + 𝒪(t¹⁶)"  --> true
    @fact TaylorSeries.pretty_print(ta(3im)) ==
        " ( 3 im )  + ( 1 ) t + 𝒪(t¹⁶)"  --> true
end

facts("Matrix multiplication for Taylor1") do
    order = 30
    n1 = 100
    k1 = 90

    order = max(n1,k1)
    B1 = randn(n1,order)
    Y1 = randn(k1,order)

    A1  = randn(k1,n1)

    for A in (A1,sparse(A1))
        # B and Y contain elements of different orders
        B  = Taylor1{Float64}[Taylor1(collect(B1[i,1:i]),i) for i=1:n1]
        Y  = Taylor1{Float64}[Taylor1(collect(Y1[k,1:k]),k) for k=1:k1]
        Bcopy = deepcopy(B)
        A_mul_B!(Y,A,B)

        # do we get the same result when using the `A*B` form?
        @fact A*B==Y -->true
        # Y should be extended after the multilpication
        @fact reduce(&, [y1.order for y1 in Y] .== Y[1].order) --> true
        # B should be unchanged
        @fact B==Bcopy --> true

        # is the result compatible with the matrix multiplication?  We
        # only check the zeroth order of the Taylor series.
        y1=sum(Y).coeffs[1]
        Y=A*B1[:,1]
        y2=sum(Y)

        # There is a small numerical error when comparing the generic
        # multiplication and the specialized version
        @fact abs(y1-y2) < n1*(eps(y1)+eps(y2)) --> true

        @fact_throws DimensionMismatch A_mul_B!(Y,A[:,1:end-1],B)
        @fact_throws DimensionMismatch A_mul_B!(Y,A[1:end-1,:],B)
        @fact_throws DimensionMismatch A_mul_B!(Y,A,B[1:end-1])
        @fact_throws DimensionMismatch A_mul_B!(Y[1:end-1],A,B)
    end
end

facts("Tests for HomogeneousPolynomial and TaylorN") do

    @fact eltype(set_variables(Int, "x", numvars=2, order=6))  --> TaylorN{Int}
    @fact eltype(set_variables("x", numvars=2, order=6))  --> TaylorN{Float64}
    @fact eltype(set_variables(BigInt, "x y", order=6))  --> TaylorN{BigInt}
    @fact eltype(set_variables("x y", order=6))  --> TaylorN{Float64}

    @fact TaylorSeries.coeff_table[2][1] == [1,0]  --> true
    @fact TaylorSeries.index_table[2][1] == 7  --> true
    @fact TaylorSeries.in_base(get_order(),[2,1]) == 15  --> true
    @fact TaylorSeries.pos_table[4][15] == 2  --> true

    @fact get_order() == 6  --> true
    @fact get_numvars() == 2  --> true

    x, y = set_variables("x y", order=6)
    @fact x.order == 6 --> true
    @fact TaylorSeries.set_variable_names(["x","y"]) == ["x", "y"]  --> true
    @fact TaylorSeries.get_variable_names() == ["x", "y"]  --> true

    set_variables("x", numvars=2, order=17)

    xH = HomogeneousPolynomial([1,0])
    yH = HomogeneousPolynomial([0,1],1)
    xT = TaylorN(xH,17)
    # yT = taylorN_variable(Int64, 2, 17)
    yT = TaylorN(Int64, 2, order=17)
    zeroT = zero( TaylorN([xH],1) )
    uT = one(convert(TaylorN{Float64},yT))
    @fact ones(xH,1) == [HomogeneousPolynomial(1), xH+yH]  --> true
    @fact ones(HomogeneousPolynomial{Complex{Int}},0) ==
        [HomogeneousPolynomial(1+0im)]  --> true
    @fact get_order(zeroT) == 1  --> true
    @fact get_coeff(xT,[1,0]) == 1  --> true
    @fact get_coeff(yH,[1,0]) == 0  --> true
    @fact convert(HomogeneousPolynomial{Int64},[1,1]) == xH+yH  --> true
    @fact convert(HomogeneousPolynomial{Float64},[2,-1]) == 2.0xH-yH  --> true
    @fact convert(TaylorN{Float64}, yH) == 1.0*yT  --> true
    @fact convert(TaylorN{Float64}, [xH,yH]) == xT+1.0*yT  --> true
    @fact convert(TaylorN{Int}, [xH,yH]) == xT+yT  --> true
    @fact promote(xH, [1,1])[2] == xH+yH  --> true
    @fact promote(xH, yT)[1] == xT  --> true
    @fact promote(xT, [xH,yH])[2] == xT+yT  --> true
    @fact typeof(promote(im*xT,[xH,yH])[2]) == TaylorN{Complex{Int64}}  --> true
    # @fact TaylorSeries.fixorder(taylorN_variable(1,1),17) == xT  --> true
    @fact TaylorSeries.fixorder(TaylorN(1, order=1),17) == xT  --> true
    @fact TaylorSeries.iszero(zeroT.coeffs[2])  -->  true

    @fact HomogeneousPolynomial(xH,1) == HomogeneousPolynomial(xH)  --> true
    @fact eltype(xH) == Int  --> true
    @fact length(xH) == 2  --> true
    @fact zero(xH) == 0*xH  --> true
    @fact one(yH) == xH+yH  --> true
    @fact xH * true == xH  --> true
    @fact false * yH == zero(yH)  --> true
    @fact get_order(yH) == 1  --> true
    @fact get_order(xT) == 17  --> true
    @fact xT * true == xT  --> true
    @fact false * yT == zero(yT)  --> true

    @fact xT == TaylorN([xH])  --> true
    @fact one(xT) == TaylorN(1,5)  --> true
    @fact TaylorN(zeroT,5) == 0  --> true
    @fact TaylorN(uT) == convert(TaylorN{Complex},1)  --> true
    @fact get_numvars() == 2  --> true
    @fact length(uT) == get_order()+1  --> true
    @fact eltype(convert(TaylorN{Complex128},1)) == Complex128  --> true

    @fact 1+xT+yT == TaylorN(1,1) + TaylorN([xH,yH],1)  --> true
    @fact xT-yT-1 == TaylorN([-1,xH-yH])  --> true
    @fact xT*yT == TaylorN([HomogeneousPolynomial([0,1,0],2)])  --> true
    @fact (1/(1-xT)).coeffs[4] == HomogeneousPolynomial(1.0,3)  --> true
    @fact xH^20 == HomogeneousPolynomial([0],get_order())  --> true
    @fact (yT/(1-xT)).coeffs[5] == xH^3 * yH  --> true
    @fact mod(1+xT,1) == +xT  --> true
    @fact (rem(1+xT,1)).coeffs[1] == 0  --> true
    @fact abs(1-xT)  --> 1-xT
    @fact abs(-1-xT)  --> 1+xT
    @fact derivative(mod2pi(2pi+yT^3),2) == derivative(yT^3,2)  --> true
    @fact derivative(yT) == zeroT  --> true
    @fact -xT/3im == im*xT/3  --> true
    @fact (xH/3im)' == im*xH/3  --> true

    @fact derivative(2xT*yT^2,1) == 2yT^2  --> true
    @fact xT*xT^3 == xT^4  --> true
    txy = 1.0 + xT*yT - 0.5*xT^2*yT + (1/3)*xT^3*yT + 0.5*xT^2*yT^2
    # @fact (1+taylorN_variable(1,4))^taylorN_variable(2,4) == txy  --> true
    @fact (1+TaylorN(1,order=4))^TaylorN(2,order=4) == txy  --> true
    @fact_throws DomainError yT^(-2)
    @fact_throws DomainError yT^(-2.0)
    @fact (1+xT)^(3//2) == ((1+xT)^0.5)^3  --> true
    @fact real(xH) == xH  --> true
    @fact imag(xH) == zero(xH)  --> true
    @fact conj(im*yH) == (im*yH)'  --> true
    @fact conj(im*yT) == (im*yT)'  --> true
    @fact real( exp(1im * xT)) == cos(xT)  --> true
    @fact get_coeff(convert(TaylorN{Rational{Int}},cos(xT)),[4,0]) ==
        1//factorial(4)  --> true
    cr = convert(TaylorN{Rational{Int}},cos(xT))
    @fact get_coeff(cr,[4,0]) == 1//factorial(4)  --> true
    @fact imag((exp(yT))^(-1im)') == sin(yT)  --> true
    exy = exp( xT+yT )
    @fact evaluate(exy) == 1  --> true
    @fact evaluate(exy,[0.1im,0.01im]) == exp(0.11im)  --> true
    @fact isapprox(evaluate(exy, [1,1]), e^2)  --> true
    txy = tan(xT+yT)
    @fact get_coeff(txy,[8,7]) == 929569/99225  --> true
    ptxy = xT + yT + (1/3)*( xT^3 + yT^3 ) + xT^2*yT + xT*yT^2
    # @fact tan(taylorN_variable(1,4)+taylorN_variable(2,4)) == ptxy  --> true
    @fact tan(TaylorN(1,order=4)+TaylorN(2,order=4)) == ptxy  --> true
    @fact evaluate(xH*yH,[1.0,2.0]) == 2.0  --> true

    g1(xT,yT) = xT^3 + 3yT^2 - 2xT^2 * yT - 7xT + 2
    g2(xT,yT) = yT + xT^2 - xT^4
    f1 = g1(xT,yT)
    f2 = g2(xT,yT)
    @fact gradient(f1) == [ 3*xT^2-4*xT*yT-TaylorN(7,0), 6*yT-2*xT^2 ]  --> true
    @fact ∇(f2) == [2*xT - 4*xT^3, TaylorN(1,0)]  --> true
    @fact jacobian([f1,f2], [2,1]) ==
        jacobian( [g1(xT+2,yT+1), g2(xT+2,yT+1)] )  --> true
    @fact [xT yT]*hessian(f1*f2)*[xT, yT] ==
        [ 2*TaylorN((f1*f2).coeffs[3]) ]  --> true
    @fact hessian(f1^2)/2 == [ [49,0] [0,12] ]  --> true
    @fact hessian(f1-f2-2*f1*f2) == (hessian(f1-f2-2*f1*f2))'  --> true
    @fact hessian(f1-f2,[1,-1]) == hessian(g1(xT+1,yT-1)-g2(xT+1,yT-1))  --> true

    @fact string(-xH) == " - 1 x₁"  --> true
    @fact string(xT^2) == " 1 x₁² + 𝒪(‖x‖¹⁸)"  --> true
    @fact string(1im*yT) == " ( 1 im ) x₂ + 𝒪(‖x‖¹⁸)"  --> true
    @fact string(xT-im*yT) == "  ( 1 ) x₁ - ( 1 im ) x₂ + 𝒪(‖x‖¹⁸)"  --> true

    @fact_throws ArgumentError abs(xT)
    @fact_throws AssertionError 1/x
    @fact_throws AssertionError zero(x)/zero(x)
    @fact_throws ArgumentError sqrt(x)
    @fact_throws AssertionError x^(-2)
    @fact_throws ArgumentError log(x)
    @fact_throws AssertionError cos(x)/sin(y)
end

facts("Testing an identity proved by Euler (8 variables)") do
    make_variable(name, index::Int) = string(name, TaylorSeries.subscriptify(index))

    @compat variable_names = String[make_variable("α", i) for i in 1:4]
    append!(variable_names, [make_variable("β", i) for i in 1:4])

    a1, a2, a3, a4, b1, b2, b3, b4 = set_variables(variable_names, order=4)

    lhs1 = a1^2 + a2^2 + a3^2 + a4^2
    lhs2 = b1^2 + b2^2 + b3^2 + b4^2
    lhs = lhs1 * lhs2

    rhs1 = (a1*b1 - a2*b2 - a3*b3 - a4*b4)^2
    rhs2 = (a1*b2 + a2*b1 + a3*b4 - a4*b3)^2
    rhs3 = (a1*b3 - a2*b4 + a3*b1 + a4*b2)^2
    rhs4 = (a1*b4 + a2*b3 - a3*b2 + a4*b1)^2

    rhs = rhs1 + rhs2 + rhs3 + rhs4

    @fact lhs == rhs  --> true
    v = randn(8)
    @fact evaluate( rhs, v) == evaluate( lhs, v)  --> true
end

facts("High order polynomials test inspired by Fateman (takes a few seconds))") do
    x, y, z, w = set_variables(Int128, "x", numvars=4, order=40)

    function fateman2(degree::Int)
        T = Int128
        oneH = HomogeneousPolynomial(one(T), 0)
        # s = 1 + x + y + z + w
        s = TaylorN(
         [oneH, HomogeneousPolynomial([one(T),one(T),one(T),one(T)],1)], degree)
        s = s^degree
        # s is converted to order 2*ndeg
        s = TaylorN(s, 2*degree)
        return s^2 + s
    end

    function fateman3(degree::Int)
        s = x + y + z + w + 1
        s = s^degree
        s * (s+1)
    end

    f2 = fateman2(20)
    f3 = fateman3(20)
    c = get_coeff(f2,[1,6,7,20])
    @fact c == 128358585324486316800 --> true
    @fact get_coeff(f2,[1,6,7,20]) == c --> true
end

exitstatus()


# ---
# title: 617. Merge Two Binary Trees
# id: problem617
# author: Tian Jun
# date: 2020-10-31
# difficulty: Easy
# categories: Tree
# link: <https://leetcode.com/problems/merge-two-binary-trees/description/>
# hidden: true
# ---
#
# Given two binary trees and imagine that when you put one of them to cover the
# other, some nodes of the two trees are overlapped while the others are not.
#
# You need to merge them into a new binary tree. The merge rule is that if two
# nodes overlap, then sum node values up as the new value of the merged node.
# Otherwise, the NOT null node will be used as the node of new tree.
#
# **Example 1:**
#
#
#
#     Input:
#     	Tree 1                     Tree 2
#               1                         2
#              / \                       / \
#             3   2                     1   3
#            /                           \   \
#           5                             4   7
#     Output:
#     Merged tree:
#     	     3
#     	    / \
#     	   4   5
#     	  / \   \
#     	 5   4   7
#
#
#
#
# **Note:** The merging process must start from the root nodes of both trees.
#
#
## @lc code=start
using LeetCode

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


using ASCIIPlots
using Base.Test

x = randn(20)
y = 21.0 * x + randn(20)
scatterplot(x, y)


using DiffEqBase, Test

function f_lin(du,u,p,t)
  du[1] = 0.2u[1] + p[1]*u[2]
  du[2] = 0.2u[1] - p[2]*u[2]
end
p = (0.0,1.0)
prob = LinearProblem(f_lin,ones(2))
prob = LinearProblem(rand(2,2),ones(2))

function f_nonlin(du,u,p)
  du[1] = 0.2u[1] + p[1]*u[2]
  du[2] = 0.2u[1] - p[2]*u[2]
end
p = (0.0,1.0)
prob = NonlinearProblem(f_nonlin,ones(2),p)

function f_quad(du,u,p)
  du[1] = 0.2u[1] + p[1]*u[2]
  du[2] = 0.2u[1] - p[2]*u[2]
end
p = (0.0,1.0)
prob = QuadratureProblem(f_quad,zeros(2),ones(2),p)

function f(du,u,p,t)
  du[1] = 0.2u[1]
  du[2] = 0.4u[2]
end
u0 = ones(2)
tspan = (0,1.0)

prob = ODEProblem(f,u0,tspan)
@test typeof(prob.tspan) == Tuple{Float64,Float64}
prob = ODEProblem{true}(f,u0,tspan)
@test typeof(prob.tspan) == Tuple{Float64,Float64}
prob = ODEProblem(ODEFunction{true}(f),u0,tspan)
@test typeof(prob.tspan) == Tuple{Float64,Float64}
@test isinplace(prob) == true
prob = ODEProblem{false}(f,u0,tspan)
@test isinplace(prob) == false

@test_broken @inferred ODEProblem{true}(f,u0,tspan)
@test_broken @inferred ODEProblem(f,u0,tspan)

function f(dv,u,v,p,t)
  dv .= 2.0 .* v
end
u0 = ones(2)
v0 = ones(2)
tspan = (0,1.0)
prob = SecondOrderODEProblem(f,u0,v0,tspan)

prob = SDEProblem((u,p,t) -> 1.01u,(u,p,t) -> 0.87u,1/2,(0.0,1.0))

function f(du,u,p,t)
  du[1] = 0.2u[1]
  du[2] = 0.4u[2]
end
function g(du,u,p,t)
  du[1] = 0.2u[1]
  du[2] = 0.4u[2]
end
u0 = ones(2)
tspan = (0,1.0)
prob = SDEProblem(f,g,u0,tspan)
prob = SDEProblem{true}(f,g,u0,tspan)

@test_broken @inferred SDEProblem(f,g,u0,tspan)
@test_broken @inferred SDEProblem{true}(f,g,u0,tspan)

f_1delay = function (du,u,h,p,t)
    du[1] = - h(t-1)[1]
end
prob =  DDEProblem(f_1delay,ones(1),t->zeros(1),(0.0, 10.0),constant_lags = ones(1))
prob =  DDEProblem{true}(f_1delay,ones(1),t->zeros(1),(0.0, 10.0),dependent_lags = ones(1))

@test_broken @inferred DDEProblem(f_1delay,ones(1),t->zeros(1),(0.0, 10.0),constant_lags = ones(1))
@test_broken @inferred DDEProblem{true}(f_1delay,ones(1),t->zeros(1),(0.0, 10.0),dependent_lags = ones(1))

function f(r, yp, y, p,tres)
    r[1]  = -0.04*y[1] + 1.0e4*y[2]*y[3]
    r[2]  = -r[1] - 3.0e7*y[2]*y[2] - yp[2]
    r[1] -=  yp[1]
    r[3]  =  y[1] + y[2] + y[3] - 1.0
end
u0 = [1.0, 0, 0]
du0 = [-0.04, 0.04, 0.0]
prob_dae_resrob = DAEProblem(f,du0,u0,(0.0,100000.0))
prob_dae_resrob = DAEProblem{true}(f,du0,u0,(0.0,100000.0))

@test_broken @inferred DAEProblem(f,du0,u0,(0.0,100000.0))
@test_broken @inferred DAEProblem{true}(f,du0,u0,(0.0,100000.0))

f(u,t,W) = 1.01u.+0.87u.*W
u0 = 1.00
tspan = (0.0,1.0)
prob = RODEProblem(f,u0,tspan)
prob = RODEProblem{false}(f,u0,tspan)

@test_broken @inferred RODEProblem(f,u0,tspan)
@test_broken @inferred RODEProblem{false}(f,u0,tspan)

DiscreteProblem(ones(1),tspan)
f(t,u) = 0.5
DiscreteProblem{false}(f,ones(1),tspan)

@test_broken @inferred DiscreteProblem(ones(1),tspan)
@test_broken @inferred DiscreteProblem{false}(f,ones(1),tspan)

function f(du,u,t)
  du[1] = 2 - 2u[1]
  du[2] = u[1] - 4u[2]
end
u0 = zeros(2)
prob = SteadyStateProblem(f,u0)

@test_broken @inferred SteadyStateProblem(f,u0)
@test SteadyStateProblem(ODEProblem(f,u0,tspan,:param)).p == :param


## source from: http://www.pjm.com/markets-and-operations/ops-analysis/historical-load-data.aspx
function load24_data(filename_Load24)
    temp_data = readcsv(filename_Load24,header=true)[1][:,3:end]
    raw_data = Array{Float64}(size(temp_data))
    normalized_data = Array{Float64}(size(temp_data))
    raw_data[:] = temp_data[:]
    N_days = size(raw_data,1)
    for k = 1:N_days
        normalized_data[k,:] = raw_data[k,:] / maximum(raw_data[k,:])
    end
    load24 = normalized_data
    return load24
end


using Test
using LinearAlgebraicRepresentation
Lar = LinearAlgebraicRepresentation

@testset "mini-FL Tests" begin
   @testset "comp" begin
		@test Lar.comp([acos,atan,tan,sin])(pi/4) * 4 - pi < 0.1e-14
		@test Lar.comp([acos,atan,tan,sin])(1)==(acos∘atan∘tan∘sin)(1)
		@test Lar.comp([atan,tan])(pi/4)*4 > 3.14159265358979
   end

   @testset "cons Tests" begin
		@test ((Lar.cons([acos,atan,tan,sin])(pi/4) .<=
		[0.667458,0.665774,1.0,0.707107]))==[true,true,true,true]
		@test (Lar.cons([sin,cos])(pi/2).>=[1.0,0.0])==[true,true]
		@test Lar.cons([+,-])([2,3])==[[2,3],[-2,-3]]
		@test typeof(Lar.cons([+,-])([2,3]))==Array{Array{Int64,1},1}
   end

   @testset "k Tests" begin
		@test Lar.k(10)(100)==10
		@test Lar.k(10)(sin)==10
		@test Lar.k(sin)(cos)==sin
		@test Lar.k(sin)(1000)==sin
		@test Lar.k([1,2,3])(100)==[1,2,3]
		@test Lar.k([1,2,3])(sin)==[1,2,3]
   end

   @testset "aa Tests" begin
		@test (Lar.aa(sin)([0.,pi/2])==[0.0,1.0])
		@test typeof(Lar.aa(sin)([0.,pi/6,pi/2]))==Array{Float64,1}
		@test Lar.aa(sum)([[1,2],[3,4],[5,6],[7,8]])==[3,7,11,15]
   end
end


### A Pluto.jl notebook ###
# v0.12.21

using Markdown
using InteractiveUtils

# ╔═╡ cbcca572-7614-11eb-2fe5-699acc8e2746
begin
	using Pkg
	Pkg.activate("MLJ_env", shared=true)

	# using MLJ
	using CSV
	using DataFrames
	using PlutoUI
	# using Random
	using Test
	using Printf
	using Plots
end

# ╔═╡ f1eefcb4-7614-11eb-0173-d5112c9036d7
include("./utils.jl")

# ╔═╡ 84a95dde-7614-11eb-20da-8191d2c5103e
md"""
## Predicting house prices using k-nearest neighbors regression

In this notebook, we will implement k-nearest neighbors regression. We will:
  - Find the k-nearest neighbors of a given query input
  - Predict the output for the query input using the k-nearest neighbors
  - Choose the best value of k using a validation set
"""

# ╔═╡ db51098e-7614-11eb-2dcd-57f4526747f6
md"""
### Load in house sales data (train, valid and test sets)
"""

# ╔═╡ f20a9c80-7614-11eb-1400-e55b390c3437
begin
  	train = CSV.File("../../ML_UW_Spec/C02/data/kc_house_data_small_train.csv";
		header=true) |> DataFrame;
  	valid = CSV.File("../../ML_UW_Spec/C02/data/kc_house_data_small_validation.csv";
		header=true) |> DataFrame;
	test = CSV.File("../../ML_UW_Spec/C02/data/kc_house_data_small_test.csv";
		header=true) |> DataFrame;

	size(train), size(valid), size(test)
end

# ╔═╡ e0ad9bee-7615-11eb-0226-617fef643e14
md"""
We need `get_data()` and `normalize_features()` from previous week. Let's import them from our utils module.
"""

# ╔═╡ f1d25e60-7614-11eb-2db3-69a5c37b2856
md"""
### Extract features and normalize

Using all of the numerical inputs listed in feature_list, transform the training, test, and validation DataFrames into Julia arrays:
"""

# ╔═╡ f1a1aa7c-7614-11eb-24b8-3183535eb4c1
md"""
In computing distances, it is crucial to normalize features. Otherwise, for example, the sqft_living feature (typically on the order of thousands) would exert a much larger influence on distance than the bedrooms feature (typically on the order of ones). We divide each column of the training feature matrix by its 2-norm, so that the transformed column has unit norm.

*IMPORTANT*: Make sure to store the norms of the features in the training set. The features in the test and validation sets must be divided by these same norms, so that the training, test, and validation sets are normalized consistently.

"""

# ╔═╡ f1b90668-7614-11eb-1313-0b2a3b91b243
begin
	feature_list = [:bedrooms, :bathrooms,
                :sqft_living, :sqft_lot,
                :floors, :waterfront,
                :view, :condition,
                :grade, :sqft_above,
                :sqft_basement, :yr_built,
                :yr_renovated, :lat, :long,
                :sqft_living15, :sqft_lot15]

	features_train, output_train = get_data(train, feature_list, :price)
	features_test, output_test = get_data(test, feature_list, :price)
	features_valid, output_valid = get_data(valid, feature_list, :price)

	## Normalize
	features_train, norms = normalize_features(features_train) # norm. train set features (columns)
	features_test = features_test ./ norms    # normalize test set ...
	features_valid = features_valid ./ norms  # ... and valid set with training set norms

	size(features_train), size(features_test), size(features_valid)
end

# ╔═╡ 0249baea-761a-11eb-09fa-efa1de647bb8
md"""
### Compute a single distance

To start, let's just explore computing the "distance" between two given houses. We will take our query house to be the first house of the test set and look at the distance between this house and the 10th house of the training set.

To see the features associated with the query house, print the first row (index 1) of the test feature matrix. You should get an 18-dimensional vector whose components (in absolute value) are between 0 and 1.

"""

# ╔═╡ 022e64ac-761a-11eb-1daf-d9ad5926c387
begin
	qry_house = features_test[1, :]

	@test size(qry_house)[1] == 18
	@test all(x -> 0. ≤ abs(x) ≤ 1., qry_house)
end

# ╔═╡ 020058f8-761a-11eb-0b03-1be1e9780d1c
md"""
**Quiz Question**

What is the Euclidean distance between the query house and the 10th house of the training set?

"""

# ╔═╡ f15192e4-7614-11eb-1fca-3b6fc4634159
begin
	function calc_euclidean_distance(row::Vector, row_ref::Vector)
  		√(sum((row - row_ref) .* (row - row_ref)))
	end

	function calc_euclidean_distance(rows::Matrix, row_ref::Vector)
		sqrt.(sum(((rows .- row_ref').^2), dims=2))
	end
end

# ╔═╡ f137c3e6-7614-11eb-190e-4d331f41b02e
begin
	d10th = calc_euclidean_distance(features_train[10, :], qry_house)

	with_terminal() do
		@printf("distance %1.8f / rounded: %1.3f\n", d10th, d10th)
	end
end

# ╔═╡ 25250e4c-761b-11eb-2c45-f54d7410184a
md"""
### Compute multiple distances

Of course, to do nearest neighbor regression, we need to compute the distance between our query house and all houses in the training set.

To visualize this nearest-neighbor search, let's first compute the distance from our query house (features_test[1]) to the first 10 houses of the training set (features_train[1:10]) and then search for the nearest neighbor within this small set of houses. Through restricting ourselves to a small set of houses to begin with, we can visually scan the list of 10 distances to verify that our code for finding the nearest neighbor is working.

Compute the Euclidean distance from the query house to each of the first 10 houses in the training set.
"""

# ╔═╡ 67931b0c-761b-11eb-3f3c-cfa948d07ebf
dist_10 = calc_euclidean_distance(features_train[1:10, :], qry_house)

# ╔═╡ e504f550-761f-11eb-1471-0b3ae40edef8
sort(collect(enumerate(dist_10[:,])),
	by=((_ix, x)=t) -> x, rev=false)[1:2]

# ╔═╡ 67441dd6-761b-11eb-1008-9f7fe69785b8
md"""
**Quiz Question**

Among the first 10 training houses, which house is the closest to the query house?
"""

# ╔═╡ 6699cfcc-761b-11eb-0c6a-89637d586437
## Resp.
begin
	ix_10 = argmin(dist_10)
	d_10 = dist_10[ix_10]
	(index=ix_10[1], distance=d_10)
end

# ╔═╡ 667cb82c-761b-11eb-1925-153041dc9a6a
md"""
### Perform k-nearest neighbor regression

For k-nearest neighbors, we need to find a set of k houses in the training set closest to a given query house. We then make predictions based on these k nearest neighbors.
Fetch k-nearest neighbors

Using the functions above, implement a function that takes in
  - the value of k;
  - the feature matrix for the training houses; and
  - the feature vector of the query house

and returns the indices of the k closest training houses. For instance, with 2-nearest neighbor, a return value of [5, 10] would indicate that the 5th and 10th training houses are closest to the query house.
"""

# ╔═╡ 6661ff46-761b-11eb-2817-0bf3e5c48275
function k_nearest(k::Int, feature_matrix::Matrix, feature_vect::Vector)
	@assert k ≥ 1
  	dist = calc_euclidean_distance(feature_matrix, feature_vect)
	sort(collect(enumerate(dist[:])),
		by=((_ix, x)=t) -> x, rev=false)[1:k] |>
		a -> map(t -> t[1], a)
end

# ╔═╡ 6645eb26-761b-11eb-2de2-212f9a229bef
md"""
**Quiz Question**

Take the query house to be third house of the test set (features_test[3]). What are the indices of the 4 training houses closest to the query house?
"""

# ╔═╡ 6628a162-761b-11eb-386d-5d6c4b9d8646
## for quiz, index answers are expected to be 0-based
## Julia however is 1-based index, thus removed 1 to this answers
k_nearest(4, features_train, features_test[3, :])

# ╔═╡ 660da98c-761b-11eb-34af-b3b35c66288c
md"""
### Make a single prediction by averaging k nearest neighbor outputs

Now that we know how to find the k-nearest neighbors, write a function that predicts the value of a given query house. For simplicity, take the average of the prices of the k nearest neighbors in the training set. The function should have the following parameters:

  - the value of k;
  - the feature matrix for the training houses;
  - the output values (prices) of the training houses; and
  - the feature vector of the query house, whose price we are predicting.

The function should return a predicted value of the query house.

"""

# ╔═╡ f7a44f48-7622-11eb-1d99-e33a5cbe3f29
function predict(k::Int, feature_matrix::Matrix, feature_vect::Vector,
		output::Vector)
  ixes = k_nearest(k, feature_matrix, feature_vect)
  sum(output[ixes]) / k
end

# ╔═╡ f7809ec2-7622-11eb-2d16-615ec1f7fb49
md"""
### Quiz Question

Again taking the query house to be third house of the test set (features_test[3]), predict the value of the query house using k-nearest neighbors with k=4 and the simple averaging method described and implemented above.
"""

# ╔═╡ f7637df6-7622-11eb-076f-0f6ab652bfd2
predict(4, features_train, features_test[3, :], output_train)
# cmp
# 413987.5 (k-nearest) vs 249000 (1-nearest)

# ╔═╡ f74b6982-7622-11eb-37d2-21ed0ec6551d
md"""
### Make multiple predictions

Write a function to predict the value of each and every house in a query set. (The query set can be any subset of the dataset, be it the test set or validation set.) The idea is to have a loop where we take each house in the query set as the query house and make a prediction for that specific house. The new function should take the following parameters:

  - the value of k;
  - the feature matrix for the training houses;
  - the output values (prices) of the training houses; and
  - the feature matrix for the query set.

The function should return a set of predicted values, one for each house in the query set.

"""

# ╔═╡ f728dac0-7622-11eb-094f-6782145366d8
function predict_all(k::Int, feature_matrix::Matrix, feature_matrix_qs::Matrix,
		output::Vector)
	n = size(feature_matrix_qs)[1]
	vresp = zeros(Float64, n)
	for ix ∈ 1:n
		vresp[ix] = predict(k, feature_matrix, feature_matrix_qs[ix, :], output)
	end
	vresp
end

# ╔═╡ 5b3578e0-7626-11eb-1c95-27a497721fd1
size(features_test), typeof(features_test)

# ╔═╡ c1c949fc-7625-11eb-212e-7316c164b585
size(features_test[1:10, :]), typeof(features_test[1:10, :])

# ╔═╡ a307a67c-7624-11eb-0021-09f621585464
size(features_test[1:10, :])[1]

# ╔═╡ c51c48a4-7628-11eb-07cd-97f29b9fb5fa
output_train

# ╔═╡ 65f46ecc-761b-11eb-1617-0fe428d5a1bd
md"""
**Quiz Question**

Make predictions for the first 10 houses in the test set using k-nearest neighbors with k=10.

  1. What is the index of the house in this query set that has the lowest predicted value?
  2. What is the predicted value of this house?

"""

# ╔═╡ d4f46ee6-7625-11eb-3b88-3d68b2e1c58b
features_test[2, :], size(features_test[2, :])

# ╔═╡ 65dba81a-761b-11eb-3bb8-47bf75fafa40
begin
	sol = predict_all(10, features_train, features_test[1:10, :], output_train)
	m_ix = argmin(sol)
	with_terminal() do
		println((solution=sol, index=m_ix[1], predicted_price=sol[m_ix]))
	end
end

# ╔═╡ 65bfee54-761b-11eb-16e8-4b4ca6decc9d
md"""
### Choosing the best value of k using a validation set

There remains a question of choosing the value of k to use in making predictions. Here, we use a validation set to choose this value. Write a loop that does the following:

```
  For k ∈ [1, 2, ..., 15]
    Makes predictions for each house in the validation set using the k-nearest neighbors from the training set.
    Computes the RSS for these predictions on the validation set
    Stores the RSS computed above in rss_all
  End
  Report which k produced the lowest RSS on validation set.
```

"""

# ╔═╡ 9cb7ace4-762a-11eb-3e80-f16cbf712a62
function rss_fn(preds, output)
	sum((preds .- output) .^ 2)
end

# ╔═╡ 726f0674-7629-11eb-2c27-7d634b6d1d4b
function find_best_k(features_matrix::Matrix, features_matrix_qs::Matrix,
		output::Vector, output_val::Vector; range=1:15)
 	rss_all = []
  	best_k, best_rss = nothing, nothing
  	for k ∈ range
    	preds = predict_all(k, features_matrix, features_matrix_qs, output)
    	rss = rss_fn(preds, output_val)
    	push!(rss_all, rss)
    	if isnothing(best_rss) || rss < best_rss
			best_rss, best_k = rss, k
		end
	end
  	(best_k, best_rss, rss_all)
end

# ╔═╡ 65a2c806-761b-11eb-2f1c-3773d2584857
(best_k, best_rss, rss_all) = find_best_k(features_train, features_valid, output_train, output_valid)

# ╔═╡ 6577f8ec-761b-11eb-01f5-67cb002a7070
md"""
To visualize the performance as a function of k, plot the RSS on the *validation* set for each considered k value.
"""

# ╔═╡ 377da4be-762a-11eb-31be-c1041f7c306b
plot(collect(1:15), rss_all, legend=false, color=[:lightblue], marker=".")

# ╔═╡ 754a565c-762a-11eb-0b81-9d30ddf27406
md"""
**Quiz Question**

What is the RSS on the test data using the value of k found above? To be clear, sum over all houses in the test set.

"""

# ╔═╡ 9210589a-762a-11eb-3439-f3130e570162
begin
	test_preds = predict_all(best_k, features_train, features_test, output_train)
	test_rss = rss_fn(test_preds, output_test)

	with_terminal() do
		@printf("test rss: %1.7e\n", test_rss)
	end
end

# ╔═╡ Cell order:
# ╟─84a95dde-7614-11eb-20da-8191d2c5103e
# ╠═cbcca572-7614-11eb-2fe5-699acc8e2746
# ╟─db51098e-7614-11eb-2dcd-57f4526747f6
# ╠═f20a9c80-7614-11eb-1400-e55b390c3437
# ╟─e0ad9bee-7615-11eb-0226-617fef643e14
# ╠═f1eefcb4-7614-11eb-0173-d5112c9036d7
# ╟─f1d25e60-7614-11eb-2db3-69a5c37b2856
# ╟─f1a1aa7c-7614-11eb-24b8-3183535eb4c1
# ╠═f1b90668-7614-11eb-1313-0b2a3b91b243
# ╟─0249baea-761a-11eb-09fa-efa1de647bb8
# ╠═022e64ac-761a-11eb-1daf-d9ad5926c387
# ╟─020058f8-761a-11eb-0b03-1be1e9780d1c
# ╠═f15192e4-7614-11eb-1fca-3b6fc4634159
# ╠═f137c3e6-7614-11eb-190e-4d331f41b02e
# ╟─25250e4c-761b-11eb-2c45-f54d7410184a
# ╠═67931b0c-761b-11eb-3f3c-cfa948d07ebf
# ╠═e504f550-761f-11eb-1471-0b3ae40edef8
# ╟─67441dd6-761b-11eb-1008-9f7fe69785b8
# ╠═6699cfcc-761b-11eb-0c6a-89637d586437
# ╟─667cb82c-761b-11eb-1925-153041dc9a6a
# ╠═6661ff46-761b-11eb-2817-0bf3e5c48275
# ╟─6645eb26-761b-11eb-2de2-212f9a229bef
# ╠═6628a162-761b-11eb-386d-5d6c4b9d8646
# ╟─660da98c-761b-11eb-34af-b3b35c66288c
# ╠═f7a44f48-7622-11eb-1d99-e33a5cbe3f29
# ╟─f7809ec2-7622-11eb-2d16-615ec1f7fb49
# ╠═f7637df6-7622-11eb-076f-0f6ab652bfd2
# ╟─f74b6982-7622-11eb-37d2-21ed0ec6551d
# ╠═f728dac0-7622-11eb-094f-6782145366d8
# ╠═5b3578e0-7626-11eb-1c95-27a497721fd1
# ╠═c1c949fc-7625-11eb-212e-7316c164b585
# ╠═a307a67c-7624-11eb-0021-09f621585464
# ╠═c51c48a4-7628-11eb-07cd-97f29b9fb5fa
# ╟─65f46ecc-761b-11eb-1617-0fe428d5a1bd
# ╠═d4f46ee6-7625-11eb-3b88-3d68b2e1c58b
# ╠═65dba81a-761b-11eb-3bb8-47bf75fafa40
# ╟─65bfee54-761b-11eb-16e8-4b4ca6decc9d
# ╠═9cb7ace4-762a-11eb-3e80-f16cbf712a62
# ╠═726f0674-7629-11eb-2c27-7d634b6d1d4b
# ╠═65a2c806-761b-11eb-2f1c-3773d2584857
# ╟─6577f8ec-761b-11eb-01f5-67cb002a7070
# ╠═377da4be-762a-11eb-31be-c1041f7c306b
# ╟─754a565c-762a-11eb-0b81-9d30ddf27406
# ╠═9210589a-762a-11eb-3439-f3130e570162


module VerticesTests

using FixedSizeArrays, Contour, Base.Test

# Setup test axes that will be shared among the tests

# Shift the axes so that they do not line up with
# integer values
Δ = 0.01
X = collect(0:Δ:4) + π
Y = collect(0:Δ:3) + φ

# TEST CASE 1
#
# f(x,y) = x^2 + y^2
#
Z = [(x^2 + y^2)::Float64 for x in X, y in Y]
h = rand()*(maximum(Z) - minimum(Z)) + minimum(Z)

contourlevels = Contour.contour(X,Y,Z, h)
for line in contourlevels.lines
    # Contour vertices lie on a circle around the origin
    for v in line.vertices
        @test_approx_eq_eps v[1]^2 + v[2]^2 h 0.1Δ
    end

    # coordinates() returns the correct values
    xs,ys = coordinates(line)
    xs .== [v[1] for v in line.vertices]
    ys .== [v[2] for v in line.vertices]
end

# TEST CASE 1.5
#
# Same as test case 1, but with a shifted center
#
h = 1
x0,y0 = (.5,-1.5)
Z = Float64[(x-x0)^2+(y-y0)^2 for x in X, y in Y]

contourlevels = Contour.contour(X, Y, Z, h)
for line in contourlevels.lines
    for v in line.vertices
        @test_approx_eq_eps ((v[1]-x0)^2 + (v[2]-y0)^2) h 0.01Δ
    end
end

# TEST CASE 2
#
# Check that ambigious cells (5, 10) are handled correctly
# Case 5: z_center > h
X = [1.0,2.0]
Y = [2.0,1.0]

Z = float([1 0;
           0 1])
h = 0.1

lines = Contour.contour(X, Y, Z, h).lines
@test length(lines) == 2

for line in lines
    @test length(line.vertices) == 2
    Δ = line.vertices[2] - line.vertices[1]
    @test_approx_eq Δ[2]/Δ[1] -1.0
end

# Case 5: z_center < h
Z = float([1 0;
           0 1])
h = 0.9

lines = Contour.contour(X, Y, Z, h).lines

@test length(lines) == 2

for line in lines
    @test length(line.vertices) == 2
    Δ = line.vertices[2] - line.vertices[1]
    @test_approx_eq Δ[2]/Δ[1] 1.0
end

# Case 10: z_center > h
Z = float([0 1;
           1 0])
h = 0.1

lines = Contour.contour(X, Y, Z, h).lines
@test length(lines) == 2

for line in lines
    @test length(line.vertices) == 2
    Δ = line.vertices[2] - line.vertices[1]
    @test_approx_eq Δ[2]/Δ[1] 1.0
end

# Case 10: z_center < h

Z = float([0 1;
           1 0])
h = 0.9

lines = Contour.contour(X, Y, Z, h).lines
@test length(lines) == 2

for line in lines
    @test length(line.vertices) == 2
    Δ = line.vertices[2] - line.vertices[1]
    @test_approx_eq Δ[2]/Δ[1] -1.0
end

# Test Known Bugs

# Issue #12
x = float(collect(1:3));
y = copy(x);
z = eye(3,3);
contours(x,y,z)

# Test handling of saddle points
#
# f(x,y) = x^2 - y^2
#
Δ = 0.01
X = collect(-3:Δ:3)
Y = collect(-3:Δ:3)

Z = [(x^2 - y^2)::Float64 for x in X, y in Y]
h = rand()*(maximum(Z) - minimum(Z)) + minimum(Z)

contourlevels = Contour.contour(X,Y,Z, h)
for line in contourlevels.lines
    # Contour vertices lie on a circle around the origin
    for v in line.vertices
        @test_approx_eq_eps v[1]^2 - v[2]^2 h 0.1Δ
    end

    # coordinates() returns the correct values
    xs,ys = coordinates(line)
    xs .== [v[1] for v in line.vertices]
    ys .== [v[2] for v in line.vertices]
end

# Test that closed contours are identified correctly
# when ambiguous cells are involved

Z = float([1 1 1 1 1 1
           1 0 0 1 1 1
           1 0 0 1 1 1
           1 1 1 0 0 1
           1 1 1 0 0 1
           1 1 1 1 1 1])

X = Y = collect(0:0.2:1)
h = 0.75
contourlevels = Contour.contour(X,Y,Z,h)

# There should be only one closed contour
@test length(contourlevels.lines) == 1

# Test contour location on a realistic dataset
include("testdata.jl")
Contour.contours(x,y,z)

end


# Generates samples of dense random matrices that are distributed according
# to the classical Gaussian random matrix ensembles
#
# The notation follows closely that in:
#
# Ioana Dumitriu and Alan Edelman, " Models for Beta s"
# Journal of Mathematical Physics, vol. 43 no. 11 (2002), pp. 5830--5547
# doi: 10.1063/1.1507823
# arXiv: math-ph/0206043
#
# References
#
# Alan Edelman, Per-Olof Persson and Brian D Sutton, "The fourfold way"
# http://www-math.mit.edu/~edelman/homepage/papers/ffw.pdf

export GaussianHermite, GaussianLaguerre, GaussianJacobi,
       Wigner, Wishart, MANOVA #Synonyms for Hermite, Laguerre and Jacobi

#A convenience function to define a chi scalar random variable
chi(df::Real) = sqrt(rand(Chisq(df)))

#####################
# Hermite ensemble #
#####################

type GaussianHermite <: ContinuousMatrixDistribution
  beta::Real
end
typealias Wigner  GaussianHermite

# Generates a NxN symmetric Wigner matrix
function rand(d::GaussianHermite, n::Integer)
  if d.beta == 1 #real
    A = randn(n, n)
  elseif d.beta == 2 #complex
    A = randn(n, n) + im*randn(n, n)
  elseif d.beta == 4 #quaternion
    #Employs 2x2 matrix representation of quaternions
    X = randn(n, n) + im*randn(n, n)
    Y = randn(n, n) + im*randn(n, n)
    A = [X Y; -conj(Y) conj(X)]
  else
    error(@sprintf("beta = %d is not implemented", d.beta))
  end
  normalization = sqrt(2*d.beta*n)
  return (A + A') / normalization
end

rand(d::GaussianHermite, dims::Dim2) = dims[1]==dims[2] ? rand(d, dims[1]) : error("Can only generate square matrices")

#Generates a NxN tridiagonal Wigner matrix
#The beta=infinity case is defined in Edelman, Persson and Sutton, 2012
function tridrand(d::GaussianHermite, n::Integer)
  if d.beta<=0 error("beta must be positive") end
  if d.beta==Inf return SymTridiagonal(zeros(n), [sqrt(x/2) for x=n-1:-1:1]) end
  Hdiag = randn(n)/sqrt(n)
  Hsup = [chi(d.beta*i)/sqrt(2*n) for i=n-1:-1:1]
  return SymTridiagonal(Hdiag, Hsup)
end

tridrand(d::GaussianHermite, dims::Dim2) = dims[1]==dims[2] ? tridrand(d, dims[1]) : error("Can only generate square matrices")

#Return n eigenvalues distributed according to the Hermite ensemble
eigvalrand(d::GaussianHermite, n::Integer) = eigvals(tridrand(d, n))

#Calculate Vandermonde determinant term
function VandermondeDeterminant{Eigenvalue<:Number}(lambda::Vector{Eigenvalue}, beta::Real)
  n = length(lambda)
  Vandermonde = 1.0
  for j=1:n
    for i=1:j-1
      Vandermonde *= abs(lambda[i] - lambda[j])^beta
    end
  end
  Vandermonde
end

function eigvaljpdf{Eigenvalue<:Number}(d::GaussianHermite, lambda::Vector{Eigenvalue})
  n = length(lambda)
  #Calculate normalization constant
  c = (2pi)^(-n/2)
  for j=1:n
    c *= gamma(1 + beta/2)/gamma(1 + beta*j/2)
  end
  Energy = sum(lambda.^2/2) #Calculate argument of exponential
  VandermondeDeterminant(lambda, beta) * exp(-Energy)
end

#####################
# Laguerre ensemble #
#####################

type GaussianLaguerre <: ContinuousMatrixDistribution
  beta::Real
  a::Real
end
typealias Wishart GaussianLaguerre

#Generates a NxN Hermitian Wishart matrix
#TODO Check - the eigenvalue distribution looks funky
#TODO The appropriate matrix size should be calculated from a and one matrix dimension
function rand(d::GaussianLaguerre, dims::Dim2)
  n = 2.0*a/d.beta
  if d.beta == 1 #real
    A = randn(dims)
  elseif d.beta == 2 #complex
    A = randn(dims) + im*randn(dims)
  elseif d.beta == 4 #quaternion
    #Employs 2x2 matrix representation of quaternions
    X = randn(dims) + im*randn(dims)
    Y = randn(dims) + im*randn(dims)
    A = [X Y; -conj(Y) conj(X)]
    error(@sprintf("beta = %d is not implemented", d.beta))
  end
  return (A * A') / dims[1]
end

#Generates a NxN bidiagonal Wishart matrix
function bidrand(d::GaussianLaguerre, m::Integer)
  if d.a <= d.beta*(m-1)/2.0
    error(@sprintf("Given your choice of m and beta, a must be at least %f (You said a = %f)", d.beta*(m-1)/2.0, d.a))
  end
  Bidiagonal([chi(2*d.a-i*d.beta) for i=0:m-1], [chi(d.beta*i) for i=m-1:-1:1], true)
end

#Generates a NxN tridiagonal Wishart matrix
function tridrand(d::GaussianLaguerre, m::Integer)
  B = bidrand(d, m)
  L = B * B'
  SymTridiagonal(diag(L)/sqrt(m), diag(L,1)/sqrt(m))
end

#Return n eigenvalues distributed according to the Laguerre ensemble
eigvalrand(d::GaussianLaguerre, m::Integer) = eigvals(tridrand(d, m))

#TODO Check m and ns
function eigvaljpdf{Eigenvalue<:Number}(d::GaussianLaguerre, lambda::Vector{Eigenvalue})
  m = length(lambda)
  #Laguerre parameters
  p = 0.5*d.beta*(m-1) + 1.0
  #Calculate normalization constant
  c = 2.0^-(m*d.a)
  z = (d.a - d.beta*(m)*0.5)
  for j=1:m
     z += 0.5*d.beta
     if z < 0 && (int(z) - z) < eps()
         #Pole of gamma function, there is no density here no matter what
         return 0.0
     end
     c *= gamma(1 + beta/2)/(gamma(1 + beta*j/2)*gamma(z))
  end

  Prod = prod(lambda.^(a-p)) #Calculate Laguerre product term
  Energy = sum(lambda)/2 #Calculate argument of exponential
  return c * VandermondeDeterminant(lambda, beta) * Prod * exp(-Energy)
end

###################
# Jacobi ensemble #
###################

#Generates a NxN self-dual MANOVA matrix
type GaussianJacobi <: ContinuousMatrixDistribution
  beta::Real
  a::Real
  b::Real
end
typealias MANOVA  GaussianJacobi

function rand(d::GaussianJacobi, m::Integer)
  w1 = Wishart(m, int(2.0*d.a/d.beta), d.beta)
  w2 = Wishart(m, int(2.0*d.b/d.beta), d.beta)
  return (w1 + w2) \ w1
end

# A helper function for Jacobi samples
function SampleCSValues(n::Integer, a::Real, b::Real, beta::Real)
  if beta == Inf
    c =sqrt((a+[1:n])./(a+b+2*[1:n]))
    s =sqrt((b+[1:n])./(a+b+2*[1:n]))
    cp=sqrt([1:n-1]./(a+b+1+2*[1:n-1]))
    sp=sqrt((a+b+1+[1:n-1])./(a+b+1+2*[1:n-1]))
  else
    #Generate cosine-squared values
    csq  = [rand(Beta(beta*(a+i)/2,beta*(b+i)/2)) for i=1:n]
    cpsq = [rand(Beta(beta*i/2,beta*(a+b+1+i)/2)) for i=1:n]
    #Cosine-sine pairs
    c , s  = sqrt(csq) , sqrt(1-csq)
    cp, sp = sqrt(cpsq), sqrt(1-cpsq)
  end
  return c, s, cp, sp
end

#Generates a 2Mx2M sparse MANOVA matrix
#Jacobi ensemble
#
# Reference:
#   A. Edelman and B. D. Sutton, "The beta-Jacobi matrix model, the CS decomposition,
#   and generalized singular value problems", Foundations of Computational Mathematics,
#   vol. 8 iss. 2 (2008), pp 259-285.
#TODO check normalization
function sprand(d::GaussianJacobi, n::Integer, a::Real, b::Real)
  CoordI = zeros(8n-4)
  CoordJ = zeros(8n-4)
  Values = zeros(8n-4)

  c, s, cp, sp = SampleCSValues(n, a, b, d.beta)

  #Diagonals of each block
  for i=1:n
    CoordI[i], CoordJ[i] = i, i
    Values[i] = i==1 ? c[n] : c[n+1-i] * sp[n+1-i]
  end
  for i=1:n
    CoordI[n+i], CoordJ[n+i] = i, n+i
    Values[n+i] = i==n ? s[1] : s[n+1-i] * sp[n-i]
  end
  for i=1:n
    CoordI[2n+i], CoordJ[2n+i] = n+i, i
    Values[2n+i] = i==1 ? -s[n] : -s[n+1-i] * sp[n+1-i]
  end
  for i=1:n
    CoordI[3n+i], CoordJ[3n+i] = n+i, n+i
    Values[3n+i] = i==n ? c[1] : c[n+1-i] * sp[n-i]
  end
  #Off-diagonals of each block
  for i=1:n+1
    CoordI[4n+i], CoordJ[4n+i] = i,i+1
    Values[4n+i] = -s[n+1-i]*cp[n-i]
  end
  for i=1:n+1
    CoordI[5n-1+i], CoordJ[5n-1+i] = i+1,n+i
    Values[5n-1+i] = c[n-i]*cp[n-i]
  end
  for i=1:n+1
    CoordI[6n-2+i], CoordJ[6n-2+i] = n+i,i+1
    Values[6n-2+i] = -c[n+1-i]*cp[n-i]
  end
  for i=1:n+1
    CoordI[7n-3+i], CoordJ[7n-3+i] = n+i,i+1
    Values[7n-3+i] = -s[n-i]*cp[n-i]
  end

  return sparse(CoordI, CoordJ, Values)
end

#Return n eigenvalues distributed according to the Jacobi ensemble
function eigvalrand(d::GaussianJacobi, n::Integer)
  #Generate just the upper left quadrant of the matrix
  c, s, cp, sp = SampleCSValues(n, d.a, d.b, d.beta)
  dv = [i==1 ? c[n] : c[n+1-i] * sp[n+1-i] for i=1:n]
  ev = [-s[n+1-i]*cp[n-i] for i=1:n-1]

  ##TODO: understand why dv and ev are returned as Array{Any,1}
  M = Bidiagonal(convert(Array{Float64,1},dv), convert(Array{Float64,1},ev), false)
  return svdvals(M)
end

#TODO Check m and ns
function eigvaljpdf{Eigenvalue<:Number}(d::GaussianJacobi, lambda::Vector{Eigenvalue})
  m = length(lambda)
  #Jacobi parameters
  a1, a2 = d.a, d.b
  p = 1.0 + d.beta*(m-1)/2.0
  #Calculate normalization constant
  c = 1.0
  for j=1:m
    z1 = (a1 - beta*(m-j)/2.0)
    if z1 < 0 && (int(z1) - z1) < eps()
      return 0.0 #Pole of gamma function, there is no density here no matter what
    end
    z2 = (a2 - beta*(m-j)/2.0)
    if z2 < 0 && (int(z2) - z2) < eps()
      return 0.0 #Pole of gamma function, there is no density here no matter what
    end
    c *= gamma(1 + beta/2)*gamma(a1+a2-beta*(m-j)/2)
    c /= gamma(1 + beta*j/2)*gamma(z1)*gamma(z2)
  end

  Prod = prod(lambda.^(a1-p))*prod((1-lambda).^(a2-p)) #Calculate Laguerre product term
  Energy = sum(lambda/2) #Calculate argument of exponential

  return c * VandermondeDeterminant(lambda, beta) * Prod * exp(-Energy)
end


# Pkg.add("Spirograph")
using Spirograph

spirograph(40,-11,18,linecolor=:red)
spirograph!(40,-11,16,linecolor=:blue)
spirograph!(40,-11,14,linecolor=:green)


module DualMatrixTools

using DualNumbers, LinearAlgebra, SparseArrays, SuiteSparse

# Personal add-on: Allow for multiplication by ε of a sparse matrix
import Base.*
"""
    *(d::Dual, M::SparseMatrixCSC)

Exactly multiplies a sparse matrix `M` by the dual number `d`.
This overload allows `ε * M` to be non-zero.
(This is to correct the effect of the `ε == 0` feature in DualNumbers.jl,
which silently removes non-zeros from sparse matrices when multiplied by `ε`.)
"""
function *(d::Dual, M::SparseMatrixCSC)
    i, j, v = findnz(M)
    m, n = size(M)
    return sparse(i, j, d .* v, m, n)
end
export *

"""
    DualFactors

Container type to work efficiently with backslash on dual-valued sparse matrices.

`factorize(M)` will create an instance containing
- `Af = factorize(realpart.(M))` — the factors of the real part
- `B = dualpart.(M)` — the dual part
for a dual-valued matrix `M`.

This is because only the factors of the real part are needed when solving a linear system of the type ``M x = b`` for a dual-valued matrix ``M = A + \\varepsilon B``.
In fact, the inverse of ``M`` is given by
``M^{-1} = (I - \\varepsilon A^{-1} B) A^{-1}``.
"""
mutable struct DualFactors{TAf,TB}
    Af::TAf # the factors of the real part
    B ::TB  # the ε part
end
export DualFactors

# Factorization functions
for f in (:lu, :qr, :cholesky, :factorize)
    @eval begin
        import LinearAlgebra: $f
        """
        $($f)(M::SparseMatrixCSC{<:Dual,<:Int})

        Invokes `$($f)` on just the real part of `M` and stores it along with the dual part into a `DualFactors` object.
        """
        $f(M::SparseMatrixCSC{<:Dual,<:Int}) = DualFactors($f(realpart.(M)), dualpart.(M))
        """
        $($f)(M::Array{<:Dual,2})

        Invokes `$($f)` on just the real part of `M` and stores it along with the dual part into a `DualFactors` object.
        """
        $f(M::Array{<:Dual,2}) = DualFactors($f(realpart.(M)), dualpart.(M))
        export $f
    end
end

# In-place factorization for the case where the real part is already stored
import LinearAlgebra: factorize
function factorize(Mf::DualFactors, M; update_factors = false)
    Mf.B = dualpart.(M)
    if update_factors
        Mf.Af = factorize(realpart.(M))
    end
    return Mf
end
export factorize

# Adjoint and transpose definitions for `DualFactors`
for f in (:adjoint, :transpose)
    @eval begin
        import Base: $f
        """
        $($f)(M::DualFactors)

        Invokes `$($f)` on both `M.Af` and `M.B` and returns them into a new `DualFactors` object.
        """
        $f(M::DualFactors) = DualFactors($f(M.Af), $f(M.B))
        export $f
    end
end

import Base.\
"""
    \\(M::DualFactors, y::AbstractVecOrMat{Float64})

Backsubstitution for `DualFactors`.
See `DualFactors` for details.
"""
function \(M::DualFactors, y::AbstractVecOrMat{Float64})
    A, B = M.Af, M.B
    A⁻¹y = A \ y
    return A⁻¹y - ε * (A \ (B * A⁻¹y))
end

"""
    \\(M::DualFactors, y::AbstractVecOrMat{Dual128})

Backsubstitution for `DualFactors`.
See `DualFactors` for details.
"""
function \(M::DualFactors, y::AbstractVecOrMat{Dual128})
    a, b = realpart.(y), dualpart.(y)
    A, B = M.Af, M.B
    A⁻¹a = A \ a
    return A⁻¹a + ε * (A \ (b - B * A⁻¹a))
end

"""
    \\(Af::Factorization{Float64}, y::AbstractVecOrMat{Dual128})

Backsubstitution for Dual-valued RHS.
"""
function \(Af::Factorization{Float64}, y::AbstractVecOrMat{Dual128})
    return (Af \ realpart.(y)) + ε * (Af \ dualpart.(y))
end

"""
    \\(M::AbstractArray{Dual128,2}, y::AbstractVecOrMat)

Backslash (factorization and backsubstitution) for Dual-valued matrix `M`.
"""
\(M::SparseMatrixCSC{<:Dual,<:Int}, y::AbstractVecOrMat) = factorize(M) \ y
\(M::Array{<:Dual,2}, y::AbstractVecOrMat) = factorize(M) \ y
export \

import Base.isapprox
function isapprox(x::AbstractVecOrMat{Dual128}, y::AbstractVecOrMat{Dual128})
    bigx = [realpart.(x) dualpart.(x)]
    bigy = [realpart.(y) dualpart.(y)]
    return isapprox(bigx, bigy)
end
isapprox(x::AbstractVecOrMat, y::AbstractVecOrMat{Dual128}) = isapprox(dual.(x), y)
isapprox(x::AbstractVecOrMat{Dual128}, y::AbstractVecOrMat) = isapprox(x, dual.(y))
function isapprox(x::Dual128, y::Dual128)
    bigx = [realpart(x) dualpart(x)]
    bigy = [realpart(y) dualpart(y)]
    return isapprox(bigx, bigy)
end
isapprox(x::Float64, y::Dual128) = isapprox(dual(x), y)
isapprox(x::Dual128, y::Float64) = isapprox(x, dual(y))

end # module


using LAR

include("test/merge.jl")
include("test/spacePartition.jl")

using PyCall
@pyimport triangle

function chainCoords(EW,rows)
	edges = hcat([EW[:,e] for e in rows]...)
	for k=1:length(edges)
		v1,v2 = find(edges .== edges[k])
		if (v1 + v2) % 2 == 0
			# swap column elements, for column that contains v2
			col = v2 ÷ 2
			if v2 % 2 != 0
				col +=1
			end
			edges[2,col],edges[1,col] = edges[1,col],edges[2,col]
		end
	end
	out = Int[]
	for k=1:size(edges,2)
		if edges[1,k] < edges[2,k]
			push!(out,1)
		else
			push!(out,-1)
		end
	end
	out
end

function boundary_2(EW,FW)
	I,J,V = Int[],Int[],Int[]
	FE = crossRelation(FW,EW)
	for (f,rows) in enumerate(FE)
		append!(I,rows)
		append!(J,[f for k=1:length(rows)])
		append!(V,chainCoords(EW,rows))
	end
	return sparse(I,J,V)
end

function edgeSlopeOrdering(VE,V,EV)
 	VE_sorted = Array{Array{Int64,1},1}()
 	forwardBackward = Tuple{Array{Int64,1},Dict{Int64,Int64},Dict{Int64,Int64}}[]
 	for (v,ve) in enumerate(VE)
 		ve_angle = []
 		if ve != []
 			for edge in ve
 				w = pop!(setdiff(Set(EV[:,edge]), Set(v)))
 				(x,y) = V[:,w] - V[:,v]
 				angle = atan2(y,x)
 				push!(ve_angle, 180*angle/pi)
 			end
 		end
 		pairs = sort(collect(zip(ve_angle,ve)))
 		sortedEdges = [pair[2] for pair in pairs]
 		push!(VE_sorted, sortedEdges)
 		theNexts,thePrevs = nextPrev(sortedEdges)
 		next = Dict(zip(sortedEdges,theNexts))
 		prev = Dict(zip(sortedEdges,thePrevs))
 		push!(forwardBackward, (sortedEdges,next,prev))
 	end
 	forwardBackward
end

function larTriangulate(W::Array{Float64,2},FW::Array{Int64,2},EW::Array{Int,2})
	FV = [face for face in FW]
	return larTriangulate(W,FV,EW)
end

function larTriangulate(W::Array{Float64,2},FW::Array{Array{Int64,1},1},EW::Array{Int,2})
	FE = crossRelation(FW,EW)
	TW = Array{Array{Int64,2},1}()
	for (f,face) in enumerate(FW)
		@show (f,face)," in larTriangulate"
		# submanifold mapping and 2D projection
		vs = hcat([W[:,v] for v in face]...)
		fv = [[v for v=1:length(face)]]
		ev = Array{Array{Int64,1},1}([])
		for e in FE[f]
			edge = Int[]
			push!( edge, find(face .== EW[1,e])[1] )
			push!( edge, find(face .== EW[2,e])[1] )
			push!(ev,edge)
		end
		ev = hcat(ev ...)
		M = submanifoldMapping(vs,fv,1)
		U = vcat(vs,ones((1,size(vs,2))))
		Y = M * U
		Z = Y[ 1:2, : ]
		# prepare data for triangle.triangulation
		polygon = Dict(
			#"holes" => Array{Float64,2}(),
			"segments" => Array{Int32,2}(ev-1)',
			#"vertex_attributes" => hcat([[1.0] for k=1:size(Z,2)]...)',
			"vertices" => Array{Float64,2}(Z'),
		)
		tria = triangle.triangulate(PyObject(polygon), PyObject("p"))
		triangles = [face[v+1] for v in tria["triangles"]]
		push!(TW,triangles)
	end
	return TW
end

function boundary_3(W,EW,FW)
	BND_2 = boundary_2(EW,FW)
	m = size(BND_2,1)
	n = size(BND_2,2)
	EF = crossRelation(EW,FW)

	#forwardBackward = edgeSlopeOrdering(VE,V,EV)

end

v,(vv,ev,fv,cv) = p.larCuboids((3,3,1),true)
V = hcat([Array{Float64,1}(v[k,:]) for k=1:size(v,1)]...)
FV = hcat([Array{Int64,1}(fv[k,:]+1) for k=1:size(fv,1)]...)
EV = hcat([Array{Int64,1}(ev[k,:]+1) for k=1:size(ev,1)]...)
model1 = Any[V,FV,EV]

W = hcat([V[:,k] + [.5;0.5;0.5] for k=1:size(V,2)]...)
#W = rotate([0,0,π/6],W)
FW = copy(FV)
EW = copy(EV)
model2 = Any[W,FW,EW]

models = [model1,model2]
X,FX,EX = cmerge(models)

viewexploded(X,FX)
viewexploded(X,EX)

V,FV,EV = deepcopy((X,FX,EX))
W,FW,EW = spacePartition(V,FV,EV)

viewexploded(W,EW)
viewexploded(W,FW)

#full(boundary_2(EW,FW))
#viewLarIndices(W,EW,FW,2)

TW = larTriangulate(W,FW,EW)
viewexploded(W,vcat(TW...)')


"""
    gettransmat(seq; relabel = false) -> (Dict, Matrix)

Return the transition matrix associated to the label sequence `seq`.
The labels must be positive integer.

**Arguments**
- `seq::Vector{<:Integer}`: positive label sequence.

**Keyword Arguments**
- `relabel::Bool = false`: if set to true the sequence
  will be made contiguous. E.g. `[7,7,9,9,1,1]` will become `[2,2,3,3,1,1]`.

**Output**
- `Dict{Integer,Integer}`: the mapping between the original and the new labels.
- `Matrix{Float64}`: the transition matrix.
"""
function gettransmat(seq::Vector{<:Integer}; relabel = false)
    @argcheck all(seq .>= 0)

    if relabel
        # /!\ Sort is important here, so that we don't relabel already contiguous states.
        mapping = Dict(old => new for (new, old) in enumerate(sort(unique(seq))))
    else
        mapping = Dict(old => old for old in unique(seq))
    end

    (length(mapping) == 0) && return mapping, Float64[]
    K = maximum(values(mapping))

    transmat = zeros(K, K)
    for i = 1:length(seq)-1
        transmat[mapping[seq[i]], mapping[seq[i+1]]] += 1
    end
    transmat = transmat ./ sum(transmat, dims = 2)
    transmat[isnan.(transmat)] .= 0.0

    mapping, transmat
end

"""
    randtransmat([rng,] prior) -> Matrix{Float64}

Generate a transition matrix where each row is sampled from `prior`.
The prior must be a multivariate probability distribution, such as a
Dirichlet distribution.

**Arguments**
- `prior::MultivariateDistribution`: distribution over the transition matrix rows.

**Example**
```julia
A = randtransmat(Dirichlet([0.1, 0.1, 0.1]))
```
"""
function randtransmat(rng::AbstractRNG, prior::MultivariateDistribution)
    K = length(prior)
    A = Matrix{Float64}(undef, K, K)
    for i in OneTo(K)
        A[i, :] = rand(rng, prior)
    end
    @check istransmat(A)
    A
end

randtransmat(prior::MultivariateDistribution) = randtransmat(GLOBAL_RNG, prior)

"""
    randtransmat([rng, ]K, α = 1.0) -> Matrix{Float64}

Generate a transition matrix where each row is sampled from
a Dirichlet distribution of dimension `K` and concentration
parameter `α`.

**Arguments**
- `K::Integer`: number of states.
- `α::Float64 = 1.0`: concentration parameter of the Dirichlet distribution.

**Example**
```julia
A = randtransmat(4)
```
"""
randtransmat(rng::AbstractRNG, K::Integer, α = 1.0) = randtransmat(rng, Dirichlet(K, α))

randtransmat(K::Integer, args...) = randtransmat(GLOBAL_RNG, K, args...)

"""
    remapseq(seq, ref) -> Vector{Integer}

Find the permutations of `seq` indices that maximize the overlap with `ref`.

**Arguments**
- `seq::Vector{Integer}`: sequence to be remapped.
- `ref::Vector{Integer}`: reference sequence.

**Example**
```julia
ref = [1,1,2,2,3,3]
seq = [2,2,3,3,1,1]
remapseq(seq, ref)
# [1,1,2,2,3,3]
```
"""
function remapseq(seq::Vector{<:Integer}, ref::Vector{<:Integer})
    seqlabels, reflabels = unique(seq), unique(ref)
    @argcheck all(seqlabels .> 0) && all(reflabels .> 0)

    # C[i,j]: cost of assigning seq. label `i` to ref. label `j`
    C = zeros(maximum(seqlabels), maximum(reflabels))
    for i in seqlabels, j in reflabels
        C[i, j] = -sum((seq .== i) .& (ref .== j))
    end

    # TODO: Own implementation of the hungarian alg.,
    # to avoid pulling another dependency ?
    assignment, _ = hungarian(C)
    [assignment[x] for x in seq]
end

# ~2x times faster than Base.maximum
# v = rand(25)
# @btime maximum(v)
# @btime vec_maximum(v)
#   63.909 ns (1 allocation: 16 bytes)
#   30.307 ns (1 allocation: 16 bytes)
function vec_maximum(v::AbstractVector)
    m = v[1]
    @inbounds for i in OneTo(length(v))
        if v[i] > m
            m = v[i]
        end
    end
    m
end


# Discrete univariate

convert(::Type{Binomial}, d::Bernoulli) = Binomial(1, d.p)

# Continuous univariate

convert(::Type{Gamma}, d::Exponential) = Gamma(1.0, d.θ)
convert(::Type{Gamma}, d::Erlang) = Gamma(d.α, d.θ)

convert(::Type{GeneralizedGamma}, d::Gamma) = GeneralizedGamma(d.θ, d.α, 1.0)
convert(::Type{GeneralizedGamma}, d::Exponential) = GeneralizedGamma(d.θ, 1.0, 1.0)
convert(::Type{GeneralizedGamma}, d::Weibull) = GeneralizedGamma(d.θ, d.α, d.α)


using Makie, Statistics

# Point3f0 is a 3-tuple of 32-bit floats for 3-dimensional space, and all Points are 3D.
Point = Point3f0

# a Face is defined by the points that are its vertices, in order.
Face = Vector{Point}

# an Edge is a line segment where the points are sorted
struct Edge
    p1::Point
    p2::Point
    Edge(a, b) = new(min(a, b), max(a, b))
end

edgemidpoint(edge) = (edge.p1  + edge.p2) / 2.0
facesforpoint(p, faces) = [f for f in faces if p in f]
facesforedge(e, faces) = [f for f in faces if (e.p1 in f) && (e.p2 in f)]
nexttohole(edge, faces) = length(facesforedge(edge, faces)) < 2

function newedgepoint(edge, faces)
    f = facesforedge(edge, faces)
    p1, p2, len = edge.p1, edge.p2, length(f)
    if len == 2
        return (p1 + p2 + mean(f[1]) + mean(f[2])) / 4.0
    elseif len == 1
        return (p1 + p2 + mean(f[1])) / 3.0
    end
    return (p1 + p2) / 2.0
end

function edgesforface(face)
    ret, indices = Vector{Edge}(), collect(1:length(face))
    for i in 1:length(face)-1
        push!(ret, Edge(face[indices[1]], face[indices[2]]))
        indices .= circshift(indices, 1)
    end
    ret
end

function edgesforpoint(p, faces)
    f = filter(x -> p in x, faces)
    return filter(e -> p == e.p1 || p == e.p2, mapreduce(edgesforface, vcat, f))
end

function adjacentpoints(point, face)
    a = indexin([point], face)
    if a[1] != nothing
        adjacent = (a[1] == 1) ? [face[end], face[2]] :
            a[1] == length(face) ? [face[end-1], face[1]] :
            [face[a[1] - 1], face[a[1] + 1]]
        return sort(adjacent)
    else
        throw("point $point not in face $face")
    end
end

adjacentedges(point, face) = [Edge(point, x) for x in adjacentpoints(point, face)]
facewrapped(face) = begin f = deepcopy(face); push!(f, f[1]); f end
drawface(face, colr) = lines(facewrapped(face); color=colr)
drawface!(face, colr) = lines!(facewrapped(face); color=colr)
drawfaces!(faces, colr) = for f in faces drawface!(f, colr) end
const colors = [:red, :green, :blue, :gold]

function drawfaces(faces, colr)
    scene = drawface(faces[1], colr)
    if length(faces) > 1
        for f in faces[2:end]
            drawface!(f, colr)
        end
    end
    scene
end

function catmullclarkstep(faces)
    d, E, dprime = Set(reduce(vcat, faces)), Dict{Vector, Point}(), Dict{Point, Point}()
    for face in faces, (i, p) in enumerate(face)
        edge = (p == face[end]) ? Edge(p, face[1]) : Edge(p, face[i + 1])
        E[[edge, face]] = newedgepoint(edge, faces)
    end
    for p in d
        F = mean([mean(face) for face in facesforpoint(p, faces)])
        pe = edgesforpoint(p, faces)
        R = mean(map(edgemidpoint, pe))
        n = length(pe)
        dprime[p] = (F + 2 * R + p * (n - 3)) / n
    end
    newfaces = Vector{Face}()
    for face in faces
        v = mean(face)
        for point in face
            fp1, fp2 = map(x -> E[[x, face]], adjacentedges(point, face))
            push!(newfaces, [fp1, dprime[point], fp2, v])
        end
    end
    return newfaces
end

"""
    catmullclark(faces, iters, scene)

Perform a multistep Catmull-Clark subdivision of a surface. See Wikipedia or page 53
of http://graphics.stanford.edu/courses/cs468-10-fall/LectureSlides/10_Subdivision.pdf
Plots the iterations, with colors for each iteration as set in the colors array.
Uses a Makie Scene of scene to plot the iters iterations.
"""
function catmullclark(faces, iters, scene)
    nextfaces = deepcopy(faces)
    for i in 1:iters
        nextfaces = catmullclarkstep(nextfaces)
        drawfaces!(nextfaces, colors[i])
        display(scene)
        sleep(1)
    end
end

const inputpoints = [
    [-1.0, -1.0, -1.0],
    [-1.0, -1.0, 1.0],
    [-1.0, 1.0, -1.0],
    [-1.0, 1.0, 1.0],
    [1.0, -1.0, -1.0],
    [1.0, -1.0, 1.0],
    [1.0, 1.0, -1.0],
    [1.0, 1.0, 1.0]
]

const inputfaces = [
    [0, 4, 5, 1],
    [4, 6, 7, 5],
    [6, 2, 3, 7],
    [2, 0, 1, 3],
    [1, 5, 7, 3],
    [0, 2, 6, 4]
]

const faces = [map(x -> Point3f0(inputpoints[x]), p .+ 1) for p in inputfaces]

scene = drawfaces(faces, :black)
display(scene)
sleep(1)

catmullclark(faces, 4, scene)

println("Press Enter to continue", readline())


#== # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# Description
#
#   Tests related to simplified dipole model of the Earth geomagnetic field.
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# References
#
#   [1] http://helios.fmi.fi/~juusolal/geomagnetism/Lectures/Chapter3_dipole.pdf
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # ==#

# File: ./src/earth/geomagnetic_field_models/dipole
# =================================================

# Function: geomag_dipole
# -----------------------

@testset "Function geomag_dipole" begin

    d2r = π/180

    # Test 1
    # --------------------------------------------------------------------------
    #
    # Position aligned with the dipole moment vector.

    year     = SatelliteToolbox._dipole_pole[32,1]
    pole_lat = SatelliteToolbox._dipole_pole[32,2]*d2r
    pole_lon = SatelliteToolbox._dipole_pole[32,3]*d2r
    m        = SatelliteToolbox._dipole_mag[32,2]*1e22

    # Distance from the Earth center.
    r = R0 + 196e3

    # Compute using the simplified equations [1] [nT].
    k0     = 1e-7m
    B_norm = 2k0/r^3*1e9

    # Rotate to ECEF.
    Deg = angle_to_dcm(-(π/2-pole_lat), -pole_lon, 0, :YZX)
    B_g = SVector{3}(0,0,-B_norm)
    B_e_expected = Deg*B_g

    # Compute using the function.
    r_g = SVector{3}(0,0,r)
    r_e = Deg*r_g
    B_e_result = geomag_dipole(r_e, year)

    @test B_e_expected ≈ B_e_result atol=1e-9

    # Test 2
    # --------------------------------------------------------------------------
    #
    # Position at the magnetic Equator, which must have half the magnitude of
    # that of the test 1.

    B_g = SVector{3}(0,0,B_norm/2)
    B_e_expected = Deg*B_g

    r_g = SVector{3}(r,0,0)
    r_e = Deg*r_g
    B_e_result = geomag_dipole(r_e, year)

    @test B_e_expected ≈ B_e_result atol=1e-9
end


function compute_dodge(x, i, n; width, space = 0.2)
    unique_x = unique(sort(x))
    width === automatic && (width = minimum(diff(unique_x))*(1-space))
    w = width/n
    x′ = x .+ i*w .- w*(n+1)/2
    return x′, w
end


mutable struct FVIntegrator{T1,mType,F,T2,tType}
  alg::T1
  mesh::mType
  Flux::F
  numvars::Int
  fluxes :: T2
  dt :: tType
  use_threads :: Bool
end

mutable struct FVDiffIntegrator{T1,mType,F,B,T2,tType}
  alg::T1
  mesh::mType
  Flux::F
  DiffMat::B
  M::Int
  fluxes::T2
  dt :: tType
  use_threads :: Bool
end

function compute_du!(du, fluxes, mesh, noscalar::Bool, ::Type{Val{true}})
    Threads.@threads for cell in cell_indices(mesh)
        @inbounds left = left_node_idx(cell, mesh)
        @inbounds right = right_node_idx(cell, mesh)
        if noscalar
            du[:,cell] = -(fluxes[:,right] - fluxes[:,left] ) / cell_volume(mesh.mesh, cell)
        else
            du[cell] = -(fluxes[right] - fluxes[left] ) / cell_volume(mesh.mesh, cell)
        end
    end
end

function compute_du!(du, fluxes, mesh, noscalar::Bool, ::Type{Val{false}})
    for cell in cell_indices(mesh)
        @inbounds left = left_node_idx(cell, mesh)
        @inbounds right = right_node_idx(cell, mesh)
        if noscalar
            du[:,cell] = -(fluxes[:,right] - fluxes[:,left] ) / cell_volume(mesh.mesh, cell)
        else
            du[cell] = -(fluxes[right] - fluxes[left] ) / cell_volume(mesh.mesh, cell)
        end
    end
end

function (fv::FVIntegrator)(du::AbstractArray{T}, u::AbstractArray{T}, p, t) where {T}
  mesh = fv.mesh; alg = fv.alg; Flux = fv.Flux; numvars = fv.numvars
  fluxes = fv.fluxes; dt = fv.dt; use_threads = fv.use_threads
  compute_fluxes!(fluxes, Flux, u, mesh, dt, alg, numvars > 1, Val{use_threads})
  apply_bc_in_fluxes!(fluxes, mesh)
  compute_du!(du, fluxes, mesh, numvars > 1, Val{use_threads})
  apply_bc_in_du!(du, mesh)
  nothing
end

function (fv::FVDiffIntegrator)(du::AbstractArray{T}, u::AbstractArray{T}, p, t) where {T}
  mesh = fv.mesh; alg = fv.alg; Flux = fv.Flux; M = fv.numvars
  fluxes = fv.fluxes; DiffMat = fv.DiffMat; dt = fv.dt; use_threads = fv.use_threads
  compute_Dfluxes!(fluxes, Flux, DiffMat, u, mesh, dt, alg, M>1, Val{use_threads})
  apply_bc_in_fluxes!(fluxes, mesh)
  compute_du!(du, fluxes, mesh, numvars > 1, Val{use_threads})
  apply_bc_in_du!(du, mesh)
  nothing
end


"""
    RootedSpanningForest{T<:LG.SimpleDiGraph{Int64}} <: AbstractGraphPointProcess{T}

Concrete type reprensenting a point process defined on the edges of a **connected** `graph` characterizing the uniform distribution on the directed [spanning forests](https://en.wikipedia.org/wiki/Spanning_tree) of `graph` rooted at `roots`.

It can be viewed as a the product distribution of the uniform distribution on the set of neighbors of each vertex conditioned on forming no cycles.

The object has two fields:

- `graph::LG.SimpleGraph{Int64}`
- `roots::Vector{Int64}`

**See also**

- Section 4.2 of [GuJeLi19](@cite).

# Example

A realization from a ``5\\times 5`` grid graph with `roots=[13]`.

![assets/rooted_spanning_tree.png](assets/rooted_spanning_tree.png)
"""
struct RootedSpanningForest{T<:LG.SimpleDiGraph{Int64}} <: AbstractGraphPointProcess{T}
    "Graph"
    graph::LG.SimpleGraph{Int64}
    "Roots"
    roots::Vector{Int64}
end

function Base.show(io::IO, pp::RootedSpanningForest{T}) where {T}
    print(io, "RootedSpanningForest{$T}\n- graph = $(pp.graph)\n- roots = $(pp.roots)")
end

"""
    RootedSpanningForest(
        graph::LG.SimpleGraph{T},
        roots::Union{Nothing,T,AbstractVector{T},AbstractSet{T}}=nothing
    ) where {T<:Int}

Construct a [`PRS.RootedSpanningForest`](@ref) model on a **connected** `graph`, rooted at `roots`.
If `roots === nothing`, a random vertex is selected uniformly at random among `LG.vertices(g)` and considered as `roots`.

```jldoctest; output = true
using PartialRejectionSampling
using LightGraphs; const LG = LightGraphs

g, roots = LG.grid([5, 5]), [1, 2, 3]

rsf = PRS.RootedSpanningForest(g, roots)

# output

RootedSpanningForest{LightGraphs.SimpleGraphs.SimpleDiGraph{Int64}}
- graph = {25, 40} undirected simple Int64 graph
- roots = [1, 2, 3]
```
"""
function RootedSpanningForest(
    graph::LG.SimpleGraph{T},
    roots::Union{Nothing,T,AbstractVector{T},AbstractSet{T}}=nothing
) where {T<:Int}
    roots_ = unique(isnothing(roots) ? rand(LG.vertices(graph)) : roots)
    if issubset(roots_, LG.vertices(graph))
        if LG.is_connected(graph)
            return RootedSpanningForest{LG.SimpleDiGraph{T}}(graph, roots_)
        else
            throw(DomainError(graph, "The graph must be connected"))
        end
    else
        throw(DomainError(roots, "roots must be contained in vertices(graph)"))
    end
end

"""
    generate_sample(
        [rng::Random.AbstractRNG,]
        pp::RootedSpanningForest
    )

Generate an exact sample from the [`PRS.RootedSpanningForest`](@ref).

Default sampler is [`PRS.generate_sample_prs`](@ref).
"""
function generate_sample(
    rng::Random.AbstractRNG,
    pp::RootedSpanningForest
)
    return generate_sample_prs(rng, pp)
end

# generate_sample(pp::RootedSpanningForest) = generate_sample(Random.default_rng(), pp)

"""
    generate_sample_prs(
        [rng::Random.AbstractRNG,]
        pp::RootedSpanningForest{T}
    )::T where {T<:LG.SimpleDiGraph{Int64}}

Generate a rooted spanning forest of `pp.`graph` with prescribed `pp.roots`, uniformly at random among all rooted spanning forests rooted at `pp.roots`, using Partial Rejection Sampling (PRS).

**See also**
- Section 4.2 of [GuJeLi19](@cite).

# Example

An illustration of the procedure on a ``5\\times 5`` grid graph, with `roots=[13]`

- red: variables involved in constraints violation (edges forming a cycle)
- orange: variables resampled (edges originating from a red/orange node)

![assets/rooted_spanning_tree_prs.gif](assets/rooted_spanning_tree_prs.gif)
"""
function generate_sample_prs(
    rng::Random.AbstractRNG,
    pp::RootedSpanningForest{T}
)::T where {T<:LG.SimpleDiGraph{Int64}}
    return _generate_sample_rooted_spanning_forest_prs(rng, pp.graph, pp.roots)
end

# generate_sample_prs(pp::RootedSpanningForest) = generate_sample_prs(Random.default_rng(), pp)

"""
    _generate_sample_rooted_spanning_forest_prs(
        rng::Random.AbstractRNG,
        graph::LG.SimpleGraph{T},
        roots
    )::LG.SimpleDiGraph{T} where {T}

Generate a rooted spanning forest from a **connected** `graph` with prescribed `roots`, uniformly at random among all rooted spanning forests rooted at `roots`, using Partial Rejection Sampling.

**See also**
- Section 4.2 of [GuJeLi19](@cite).
"""
function _generate_sample_rooted_spanning_forest_prs(
    rng::Random.AbstractRNG,
    graph::LG.SimpleGraph{T},
    roots
)::LG.SimpleDiGraph{T} where {T}
    g = random_neighbor_assignment(rng, graph, roots)
    while true
        vertices_in_cycles = Set(Iterators.flatten(LG.simplecycles(g)))
        isempty(vertices_in_cycles) && break
        # Resample the successor of vertices involved in cycles
        for v in vertices_in_cycles
            # Remove current edge (v, w)
            w = LG.neighbors(g, v)[1]
            LG.rem_edge!(g, v, w)
            # Resample the successor w of v and add edge (v, w)
            w = rand(rng, LG.neighbors(graph, v))
            LG.add_edge!(g, v, w)
        end
    end
    return g
end

function _generate_sample_rooted_spanning_forest_prs(
    graph::LG.SimpleGraph{T},
    roots
)::LG.SimpleDiGraph{T} where {T}
    return _generate_sample_rooted_spanning_forest_prs(Random.default_rng(), graph, roots)
end

@doc raw"""
    _generate_sample_rooted_spanning_forest_wilson(
        [rng::Random.AbstractRNG,]
        graph::LG.SimpleGraph{T},
        roots
    )

Generate a spanning forest of a **connected** `graph` with prescribed `roots`, uniformly at random among all rooted spanning forests rooted at `roots`, using [Wilson's algorithm](https://en.wikipedia.org/wiki/Loop-erased_random_walk#Uniform_spanning_tree).

**See also**
- [RandomForests.jl implementation](https://gricad-gitlab.univ-grenoble-alpes.fr/barthesi/RandomForests.jl/-/blob/master/src/random_spanning_tree.jl).
"""
function _generate_sample_rooted_spanning_forest_wilson(
    rng::Random.AbstractRNG,
    graph::LG.SimpleGraph{T},
    roots
)::LG.SimpleDiGraph{T} where {T}
    in_tree = falses(LG.nv(graph))
    for r in roots
        @inbounds in_tree[r] = true
    end

    successors = zeros(T, LG.nv(graph))
    for i in LG.vertices(graph)
        # Run a natural random walk on g from i until the walk hits a vertex in tree
        u = i
        while !in_tree[u]
            successors[u] = rand(rng, LG.neighbors(graph, u))
            u = successors[u]
        end
        # Erase loops formed during the walk
        u = i
        while !in_tree[u]
            in_tree[u] = true
            u = successors[u]
        end
    end

    return directed_forest_from_successors(successors)
end

function _generate_sample_rooted_spanning_forest_wilson(
    graph::LG.SimpleGraph{T},
    roots
)::LG.SimpleDiGraph{T} where {T}
    return _generate_sample_rooted_spanning_forest_wilson(Random.default_rng(), graph, roots)
end

"""
    directed_forest_from_successors(successors)::LG.SimpleDiGraph

Return a directed graph of size `length(successors)` with edges `i => successors[i]`.
"""
function directed_forest_from_successors(successors)::LG.SimpleDiGraph
    graph = LG.SimpleDiGraph(length(successors))
    for (i, j) in enumerate(successors)
        LG.add_edge!(graph, i, j)
    end
    return graph
end


# Copyright (c) 2021 Idiap Research Institute, http://www.idiap.ch/
#  Niccolò Antonello <nantonel@idiap.ch>

# weighted arc definition
W = ProbabilityWeight{Float32}
a1 = FiniteStateTransducers.Arc(1,1,W(0.5),2)
a2 = FiniteStateTransducers.Arc(1,1,W(0.5),2)
a3 = FiniteStateTransducers.Arc(1,1,W(0.8),2)
@test a1 == a2
@test (a1 == a3) == false
@test ==(a1,a3;check_weight=false) == true

# WFST constructor
isym = Dict(i=>i for i =1:5)
fst = WFST(isym; W = W)
add_states!(fst,4) # create 4 states
@test length(fst) == 4
initial!(fst,1)
@test 1 in get_initial(fst)
@test isinitial(fst,1) == true
final!(fst,4,0.5)
final!(fst,3)
@test 4 in keys(get_final(fst))
@test isfinal(fst,3) == true
initial!(fst,1,W(0.2))
### add arcs
ϵ = get_eps(Int)
add_arc!(fst, 1=>2, 5=>1)
add_arc!(fst, 1, 3, 1, 1, rand())
add_arc!(fst, 2, 4, 1, ϵ, rand())
add_arc!(fst, 2, 3, 1, ϵ, rand())
add_arc!(fst, 2, 3, 2, 2, rand())
add_arc!(fst, 3, 4, 3, 3, rand())
add_arc!(fst, 3, 3, 4, 4, rand())
@test_throws ErrorException add_arc!(fst, 3, 3, 6, 6, rand())
println(fst)
initial!(fst,2)
rm_initial!(fst,1)
@test get_initial(fst;single=true) == 2
rm_initial!(fst,2)
@test_throws ErrorException get_initial(fst)
rm_final!(fst,4)
println(fst)
rm_final!(fst,3)
@test_throws ErrorException get_final(fst)

## testing linear wfst
fst = linearfst([1,1,3,4],[1,2,3,4],rand(4),isym)
@test size(fst) == (5,4)
@test isinitial(fst,1)
@test isfinal(fst,5)

fst = linearfst([1,1,3,4],[:a,:b,:c,:d],rand(4))
@test length(get_isym(fst)) == 3
@test eltype(keys(get_isym(fst))) == Int
@test length(get_osym(fst)) == 4
@test eltype(keys(get_osym(fst))) == Symbol

### matrix2fsa
Ns,Nt=3,10
sym=Dict("a"=>1,"b"=>2,"c"=>3)
X = rand(Ns,Nt)
fst = matrix2wfst(sym,X)
#println(fst)
@test size(fst) == (Nt+1,Ns*Nt)
@test isinitial(fst,1)
@test isfinal(fst,Nt+1)

fst = matrix2wfst([:a,:b,:c],X)
@test eltype(keys(get_isym(fst))) == Symbol
@test length(get_isym(fst)) == 3
@test get_isym(fst) == get_osym(fst)

X[2,2] = Inf # this is a tropical zero so no arc should be added
fst = matrix2wfst(sym,X; W = TropicalWeight{Float32})
@test size(fst) == (Nt+1,Ns*Nt-1)
@test isinitial(fst,1)
@test isfinal(fst,Nt+1)

# transduce symbol seq
isym=Dict(Char(i)=>i for i=97:122)
osym=Dict(Char(i)=>i for i=97:122)
W = ProbabilityWeight{Float64}
A = WFST(isym,osym; W=W);
add_arc!(A,1=>2,'h'=>'w',1)
add_arc!(A,2=>3,'e'=>'o',1)
add_arc!(A,3=>4,'l'=>'r',1)
add_arc!(A,4=>5,'l'=>'l',1)
add_arc!(A,5=>6,'o'=>'d',1)
add_arc!(A,5=>6,'ϵ'=>'d',0.001)
add_arc!(A,6=>6,'o'=>'ϵ',0.5)
initial!(A,1)
final!(A,6)
#println(A)
out,w = A(['h', 'e', 'l', 'l', 'o'])
@test out == ['w', 'o', 'r', 'l', 'd']
@test w == one(W)
out,w = A(['e', 'l'])
@test isempty(out)
@test w == zero(W)
out,w = A(['h'])
@test out == ['w']
@test w == zero(W)
out,w = A(['h'])
@test out == ['w']
@test w == zero(W)
out,w = A(['h', 'e', 'l', 'l', 'o', 'o', 'o', 'o'])
@test out == ['w', 'o', 'r', 'l', 'd']
@test w == W(0.5^3) # 3 times in self loop
out,w = A(['h', 'e', 'l', 'l', 'ϵ', 'o', 'o', 'o'])
@test out == ['w', 'o', 'r', 'l', 'd']
@test w == W(0.5^2) # 2 times in self loop
out,w = A(['h', 'e', 'l', 'l'])
@test out == ['w', 'o', 'r', 'l', 'd']
@test w == W(0.001)

B = WFST(isym,osym; W=W);
add_arc!(B,1=>2,'h'=>'n',1)
add_arc!(B,2=>3,'e'=>'ϵ',1)
add_arc!(B,3=>4,'ϵ'=>'ϵ',1)
add_arc!(B,4=>5,'l'=>'ϵ',1)
add_arc!(B,5=>6,'l'=>'ϵ',1)
add_arc!(B,6=>7,'ϵ'=>'o',1)
add_arc!(B,7=>7,'l'=>'ϵ',0.5)
initial!(B,1)
final!(B,7)
out,w = B(['h', 'e', 'l', 'l'])
@test out == ['n', 'o']
@test w == one(W)
out,w = B(['h', 'e', 'l', 'l', 'l', 'l'])
@test out == ['n', 'o']
@test w == W(0.5^2)


"""Euclidean norm of segment"""
length{D, T}(line::Segment{D, T}) = norm(line.B - line.A)

"""Iterator over edges of the curve as Segment entities"""
function iter_segments{D, T}(curve::Curve{D, T})
    (Segment(curve.points[i0], curve.points[i1]) for (i0, i1) in curve.segments)
end

"""Total length of the curve"""
length{D, T}(curve::Curve{D, T}) = sum(imap(length, iter_segments(curve)))

"""Smallest segment of the curve"""
min_segment{D, T}(curve::Curve{D, T}) = minimum(imap(length, iter_segments(curve)))

"""Longest segment of the curve"""
max_segment{D, T}(curve::Curve{D, T}) = maximum(imap(length, iter_segments(curve)))

"""Length of branch refered to by segments indices in curve.segments"""
function branch_length(curve::Curve, segments::Vector{Int})
    l = 0.
    for segment in segments
        # index -> indices
        v0, v1 = curve.segments[segment]
        # Vertices as physical points
        l += norm(curve.points[v1]-curve.points[v0])
    end
    l
end

"""Produce an array of branch lengths in the order they appear in bmap"""
function branch_lengths(curve::Curve,  bmap::Dict{Tuple{Int, Int}, Vector{Vector{Int}}})
    lengths = Vector{Real}(num_branches(curve))
    for key in keys(bmap)
        for branch in bmap[key]
            push!(lengths, branch_length(branch))
        end
    end
    lengths
end

"""Length of the longest branch"""
function min_branch(curve::Curve,  bmap::Dict{Tuple{Int, Int}, Vector{Vector{Int}}})
    minimum(branch_lengths(curve, bmap))
end

"""Length of the shortest branch"""
function max_branch(curve::Curve,  bmap::Dict{Tuple{Int, Int}, Vector{Vector{Int}}})
    maximum(branch_lengths(curve, bmap))
end

# Generate those which compute the map
for foo in (:branch_lengths, :min_branch, :max_branch)
    @eval $(foo)(curve::Curve) = $(foo)(curve, branch_lengths(curve))
end

######################################################################
# Box counting and fractional dimension
######################################################################
include("fractal_dim.jl")

"""
Scale the curve (in place) such as the distances between points are
changed by factor a.
"""
function scale!{D, T}(c::Curve{D, T}, a::Number)
    o = BoundingBox(c).low

    points = c.points
    for i in 1:length(c.points)
        points[i] = a*(points[i] - o) + o
    end
end

"""
The distance vector between points is scaled by diagm(a). This allows for
sheer.
"""
function scale!{D, T}(c::Curve{D, T}, a::NTuple{D, T})
    o = BoundingBox(c).low

    X = diagm(SVector(a...))
    points = c.points
    for i in 1:length(c.points)
        points[i] = X*(points[i] - o) + o
    end
end

"""Translate! the curve by vector b"""
function translate!{D, T}(c::Curve{D, T}, b::SVector{D, T})
    points = c.points
    for i in 1:length(c.points)
        points[i] += b
    end
end

"""Scale! curve to rectangle b, i.e. the bbox of c should be b."""
function scale!{D, T}(c::Curve{D, T}, b::BoundingBox{D, T})
    target_low, target_high = b.low, b.high

    cframe = BoundingBox(c)
    c_low, c_high = cframe.low, cframe.high
    # Figure out the relative scaling
    scale = (target_high - target_low)./(c_high - c_low)

    X = diagm(scale)
    points = c.points
    for i in 1:length(c.points)
        # Scale and shift to next origin
        points[i] = X*(points[i] - c_low) + target_low
    end
end

# IDEA: fd in parallel
# TODO: tortuosity


import Base: linspace

# Multidimensional Linspace
function linspace{D, T}(start ::NTuple{D, T}, stop ::NTuple{D, T}, n=50)
  zip([linspace(start[d], stop[d], n+1)[1:n] for d in 1:D]...)
end

function flatten{T}(a :: Array{T})
  return reshape(a, prod(size(a)))
end

import Iterators

function linpath{D, T <: Number}(points ::Vector{NTuple{D, T}}, n=50)
  segments = []
  for ip in 2:length(points)
    push!(segments, linspace(points[ip-1], points[ip], n))
  end
  Iterators.chain(segments...)
end