# An implementation of the Golub-Kahan version of Craig's method
# for the solution of the consistent (under/over-determined or square)
# linear system
#
#  Ax = b.
#
# The method seeks to solve the minimum-norm problem
#
#  min ‖x‖²  s.t. Ax = b,
#
# and is equivalent to applying the conjugate gradient method
# to the linear system
#
#  AA'y = b.
#
# This method, sometimes known under the name CRAIG, is the
# Golub-Kahan implementation of CGNE, and is described in
#
# C. C. Paige and M. A. Saunders, LSQR: An Algorithm for Sparse
# Linear Equations and Sparse Least Squares, ACM Transactions on
# Mathematical Software, Vol 8, No. 1, pp. 43-71, 1982.
#
# and
#
# M. A. Saunders, Solutions of Sparse Rectangular Systems Using
# LSQR and CRAIG, BIT, No. 35, pp. 588-604, 1995.
#
# Dominique Orban, <dominique.orban@gerad.ca>
# Montréal, QC, April 2015.
#
# This implementation is strongly inspired from Mike Saunders's.

export craig

"""Find the least-norm solution of the consistent linear system

  Ax + √λs = b

using the Golub-Kahan implementation of Craig's method, where λ ≥ 0 is a
regularization parameter. This method is equivalent to CGNE but is more
stable.

For a system in the form Ax = b, Craig's method is equivalent to applying
CG to AA'y = b and recovering x = A'y. Note that y are the Lagrange
multipliers of the least-norm problem

  minimize ‖x‖  subject to Ax = b.

In this implementation, both the x and y-parts of the solution are returned.
"""
function craig(A :: AbstractLinearOperator, b :: AbstractVector{T};
               λ :: Float64=0.0, atol :: Float64=1.0e-8, rtol :: Float64=1.0e-6,
               conlim :: Float64=1.0e+8, itmax :: Int=0, verbose :: Bool=false) where T <: Number

  m, n = size(A);
  size(b, 1) == m || error("Inconsistent problem size");
  verbose && @printf("CRAIG: system of %d equations in %d variables\n", m, n);

  x = zeros(T, n);
  β₁ = @knrm2(m, b)   # Marginally faster than norm(b);
  β₁ == 0 && return x, zeros(m), SimpleStats(true, false, [0.0], Float64[], "x = 0 is a zero-residual solution");
  β = β₁;
  θ = β₁;   # θ will differ from β when there is regularization (λ > 0).
  ξ = -1;   # Most recent component of x in Range(V).
  δ = λ;
  ρ_prev = 1;

  # β₁ u₁ = b.
  u = copy(b);
  @kscal!(m, 1.0/β₁, u)

  v = zeros(T, n)
  w = zeros(T, m)  # Used to update y.

  y = zeros(T, m)
  λ > 0.0 && (w2 = zeros(T, n))

  Anorm² = 0.0;  # Estimate of ‖A‖²_F.
  Dnorm  = 0.0;  # Estimate of ‖(A'A)⁻¹‖.
  xNorm² = 0.0;  # Estimate of ‖x‖².

  iter = 0;
  itmax == 0 && (itmax = m + n);

  rNorm  = β₁;
  rNorms = [rNorm;];
  ɛ_c = atol + rtol * rNorm;  # Stopping tolerance for consistent systems.
  ɛ_i = atol;                 # Stopping tolerance for inconsistent systems.
  verbose && @printf("%5s  %8s  %8s  %8s\n", "Aprod", "‖r‖", "‖x‖²", "‖A‖²")
  verbose && @printf("%5d  %8.2e  %8.2e  %8.2e\n", 1, rNorm, xNorm², Anorm²);

  status = "unknown";
  solved = rNorm <= ɛ_c;
  inconsistent = false;
  tired = iter >= itmax;

  while ! (solved || inconsistent || tired)
    # Generate the next Golub-Kahan vectors
    # 1. αv = A'u - βv
    @kscal!(n, -β, v)
    @kaxpy!(n, 1.0, A' * u, v)
    α = @knrm2(n, v)
    if α == 0.0
      inconsistent = true;
      continue;
    end
    @kscal!(n, 1.0/α, v)

    Anorm² += α * α + λ * λ;

    if λ > 0.0
      # Givens rotation to zero out the δ in position (k, 2k):
      #      k-1  k   2k     k   2k      k-1  k   2k
      # k   [ θ   α   δ ] [ c₁   s₁ ] = [ θ   ρ      ]
      # k+1 [     β     ] [ s₁  -c₁ ]   [     θ+   γ ]
      ρ  = sqrt(α * α + δ * δ);
      c₁ =  α / ρ;
      s₁ = -δ / ρ;
    else
      ρ = α;
    end

    ξ = -θ / ρ * ξ;

    if λ > 0.0
      # w1 = c₁ * v + s₁ * w2
      # w2 = s₁ * v - c₁ * w2
      # x  = x + ξ * w1
      # Save storage on w1 since it cannot be updated
      # using a BLAS call.
      # TODO: use dot notation in Julia 0.6.
      @simd for i = 1 : n
        @inbounds x[i]  = x[i] + ξ * (c₁ * v[i] + s₁ * w2[i]);
        @inbounds w2[i] = s₁ * x[i] - c₁ * w2[i];
      end
    else
      @kaxpy!(n, ξ, v, x)  # x = x + ξ * v;
    end

    # Recur y.
    @kscal!(m, -θ/ρ_prev, w)
    @kaxpy!(m, 1.0, u, w)  # w = u - θ/ρ_prev * w;
    @kaxpy!(m, ξ/ρ, w, y)  # y = y + ξ/ρ * w;

    # 2. βu = A v - αu
    @kscal!(m, -α, u)
    @kaxpy!(m, 1.0, A * v, u)
    β = @knrm2(m, u)
    β > 0.0 && @kscal!(m, 1.0/β, u)

    # Finish  updates from the first Givens rotation.
    if λ > 0.0
      θ =  β * c₁;
      γ =  β * s₁;
    else
      θ = β;
    end

    if λ > 0.0
      # Givens rotation to zero out the γ in position (k+1, 2k)
      #       2k  2k+1    2k  2k+1      2k  2k+1
      # k+1 [  γ    λ ] [ c₂   s₂ ] = [  0    δ ]
      # k+2 [  0    0 ] [ s₂  -c₂ ]   [  0    0 ]
      δ  =  sqrt(γ * γ + λ * λ);
      c₂ = -λ / δ;
      s₂ =  γ / δ;
      @kscal!(n, s₂, w2)
    end

    Anorm² += β * β;
    xNorm² += ξ * ξ;
    rNorm   = β * abs(ξ);          # r = -     β * ξ * u
    λ > 0.0 && (rNorm *= abs(c₁)); # r = -c₁ * β * ξ * u when λ > 0.
    push!(rNorms, rNorm);
    iter = iter + 1;

    ρ_prev = ρ;  # Only differs from α if λ > 0.

    verbose && @printf("%5d  %8.2e  %8.2e  %8.2e\n", 1 + 2 * iter, rNorm, xNorm², Anorm²);

    solved = rNorm <= ɛ_c;
    inconsistent = false;
    tired = iter >= itmax;
  end

  inconsistent = !solved  # is there a smarter way?

  # TODO: transfer to LSQR point and update y.

  status = solved ? "solution good enough given atol and rtol" : (inconsistent ? "system probably inconsistent" : "maximum number of iterations exceeded")
  stats = SimpleStats(solved, inconsistent, rNorms, Float64[], status);
  return (x, y, stats);
end


#
#
# JacobiFields
#
#
export βDgx, βDexpx, βDexpξ, βDlogx, βDlogy

@doc doc"""
    βDgx(κ,t,d)
weights for the [`jacobiField`](@ref) corresponding to the differential of the geodesic
with respect to its start point $D_x g(t;x,y)[\eta]$.
They are

```math
\beta(\kappa) = \begin{cases}
\frac{\sinh(d(1-t)\sqrt{-\kappa})}{\sinh(d\sqrt{-\kappa})}
&\text{ if }\kappa < 0,\\
1-t & \text{ if } \kappa = 0,\\
\frac{\sin((1-t)d\sqrt{\kappa})}{\sinh(d\sqrt{\kappa})}
&\text{ if }\kappa > 0.
\end{cases}
```

Due to a symmetry agrument, these are also used to compute $D_y g(t; x,y)[\eta]$

# See also
 [`DyGeo`](@ref), [`DxGeo`](@ref), [`jacobiField`](@ref)
"""
function βDgx(κ::Number,t::Number,d::Number)
    if (d==0) || (κ==0)
        return (1-t)
    else
        if κ < 0
            return sinh(sqrt(-κ)*(1-t)*d)/sinh(sqrt(-κ)*d)
        elseif κ > 0
            return sin(sqrt(κ)*(1-t)*d)/sin(sqrt(κ)*d)
        end
    end
end
@doc doc"""
    βDexpx(κ,t,d)

weights for the [`jacobiField`](@ref) corresponding to the differential of the geodesic
with respect to its start point $D_x \exp_x(\xi)[\eta]$. They are

```math
\beta(\kappa) = \begin{cases}
\cosh(\sqrt{-\kappa})&\text{ if }\kappa < 0,\\
1 & \text{ if } \kappa = 0,\\
\cos(\sqrt{\kappa}) &\text{ if }\kappa > 0.
\end{cases}
```

# See also
 [`DxExp`](@ref), [`jacobiField`](@ref)
"""
function βDexpx(κ::Number,t::Number,d::Number)
    if κ < 0
        return cosh(sqrt(-κ)*d)
    elseif κ > 0
        return cos(sqrt(κ)*d)
    else
        return 1.0;
    end
end
@doc doc"""
    βDexpξ(κ,t,d)

weights for the [`jacobiField`](@ref) corresponding to the differential of the geodesic
with respect to its start point $D_\xi \exp_x(\xi)[\eta]$. They are

```math
$\beta(\kappa) = \begin{cases}
\frac{\sinh(d\sqrt{-\kappa})}{d\sqrt{-\kappa}}&\text{ if }\kappa < 0,\\
1 & \text{ if } \kappa = 0,\\
\frac{\sin(d\sqrt{\kappa})}{\sqrt{d\kappa}}&\text{ if }\kappa > 0.
\end{cases}
```

# See also
 [`DξExp`](@ref), [`jacobiField`](@ref)
"""
function βDexpξ(κ::Number,t::Number,d::Number)
    if (d==0) || (κ==0)
        return 1.0
    else
        if κ < 0
            return sinh(sqrt(-κ)*d)/( d*sqrt((-κ)) )
        elseif κ > 0
            return sin( sqrt(κ)*d )/( d*sqrt(κ) )
        end
    end
end
@doc doc"""
    βDlogx(κ,t,d)

weights for the[`jacobiField`](@ref) corresponding to the differential of the geodesic
with respect to its start point $D_x \log_xy[\eta]$. They are

```math
\beta(\kappa) = \begin{cases}
-\sqrt{-\kappa}d\frac{\cosh(d\sqrt{-\kappa})}{\sinh(d\sqrt{-\kappa})}&\text{ if }\kappa < 0,\\
-1 & \text{ if } \kappa = 0,\\
-\sqrt{\kappa}d\frac{\cos(d\sqrt{\kappa})}{\sin(d\sqrt{\kappa})}&\text{ if }\kappa > 0.
\end{cases}
```

# See also
[`DxLog`](@ref), [`DyLog`](@ref), [`jacobiField`](@ref)
"""
function βDlogx(κ::Number,t::Number,d::Number)
    if (d==0) || (κ==0)
        return -1.0
    else
        if κ < 0
            return - sqrt(-κ)*d*cosh(sqrt(-κ)*d)/sinh(sqrt(-κ)*d)
        else #if κ > 0
            return - sqrt(κ)*d*cos(sqrt(κ)*d)/sin(sqrt(κ)*d)
        end
    end
end
@doc doc"""
    βDlogy(κ,t,d)

weights for the JacobiField corresponding to the differential of the logarithmic
map with respect to its argument $D_y \log_xy[\eta]$.
They are
```math
\beta(\kappa) = \begin{cases}
\frac{ d\sqrt{-\kappa} }{\sinh(d\sqrt{-\kappa})}&\text{ if }\kappa < 0,\\
1 & \text{ if } \kappa = 0,\\
\frac{ d\sqrt{\kappa} }{\sin(d\sqrt{\kappa})}&\text{ if }\kappa > 0.
\end{cases}
```

# See also
 [`DyLog`](@ref), [`jacobiField`](@ref)
"""
function βDlogy(κ::Number,t::Number,d::Number)
    if (d==0) || (κ==0)
        return 1.0
    else
        if κ < 0
            return sqrt(-κ)*d/sinh(sqrt(-κ)*d)
        else #if κ > 0
            return sqrt(κ)*d/sin(sqrt(κ)*d)
        end
    end
end


module CubedSphereDiscreteModelsTestsSeq
  using Gridap
  using GridapGeosciences
  using FillArrays
  using Test

  include("../CubedSphereDiscreteModelsTests.jl")
  include("../ConvergenceAnalysisTools.jl")

  n_values=generate_n_values(2)
  hs=[2.0/n for n in n_values]

  model_args_series=zip(n_values,Fill(1,length(n_values)))
  _,_,s=convergence_discrete_cubed_sphere_surface(hs,model_args_series,0)
  @test round(s,digits=1) ≈ 2.0

  model_args_series=zip(n_values,Fill(2,length(n_values)))
  _,_,s=convergence_discrete_cubed_sphere_surface(hs,model_args_series,4)
  @test round(s,digits=1) ≈ 4.0

  model_args_series=[(n,) for n in n_values]
  _,_,s=convergence_discrete_cubed_sphere_surface(hs,model_args_series,4)
  @test round(s,digits=1) ≈ 5.7

end


using Plots,ApproxFun,SingularIntegralEquations;  gr()

##
#  Ideal fluid flow consists of level sets of the imagainary part of a function
# that is asymptotic to c*z and whose imaginary part vanishes on Γ
#
#
# On the unit interval, -2*hilbert(ui) gives the imaginary part of cauchy(ui,z)
#  So if we want to find ui defined on Γ so that hilbert(ui) = imag(c*z)
#  then c*z + 2cauchy(u,z) vanishes on Γ
##

m=80;x = linspace(-2.,2.,m);y = linspace(-1.,1.,m+1)
    xx,yy = x.+0.*y',0.*x.+y'

k=50
    Γ=Segment(0.,1+0.5im)
    z=Fun(Γ)
    α=exp(-π*k/50im)
    S=JacobiWeight(0.5,0.5,Γ)
    c,ui=[1 Hilbert(S)] \ [imag(α*z)]

1 + 3

D = Derivative() : Chebyshev() → Ultraspherical(1)

D^2 + Fun(cos)

Derivative([1,0]) : Chebyshev()^2

xx = yy = linspace(-10,10,100)

using Plots

zz = xx' .+ im .* yy
contour(xx, yy, imag(im      .* zz.^(1/2) ); nlevels=100)

plot([0,-10], [0,10]; color=:black)
    plot!([0,-10], [0,-10]; color=:black)
    contour!(xx, yy, imag(im * zz.^(4/6) ); nlevels=100)
plot([0,-10], [0,10]; color=:black)
    plot!([0,-10], [0,-10]; color=:black)
    contour!(xx, yy, imag( zz.^(4/3) ); nlevels=100)

plot()

randn(10,10)

Hilbert() : JacobiWeight(0.5,0.5, Ultraspherical(1))

u =(x,y)->α*(x+im*y)+2cauchy(ui,x+im*y)
plot(Γ)
    contour!(x,y,imag(u(xx,yy))';nlevels=100)

##
# On an arc, the Hilbert transform no longer gives the imaginary part
#  However, pseudohilbert gives the imaginary part of pseudocauchy
#  So if we want to find ui defined on Γ so that pseudohilbert(ui) = imag(c*z)
#  then c*z + 2pseudocauchy(u,z) vanishes on Γ
##

m=80;x = linspace(-2.,2.,m);y = linspace(-2.,2.,m+1)
    xx,yy = x.+0.*y',0.*x.+y'

k=227;
    Γ=0.5+exp(im*Segment(0.1,-42))
    z=Fun(Γ)
    α=exp(-k/50im)
    S=JacobiWeight(0.5,0.5,Γ)
    c,ui=[1 PseudoHilbert(S)]\imag(α*z)

u=(x,y)->α*(x+im*y)+2pseudocauchy(ui,x+im*y)
plot(Γ)
    contour!(x,y,imag(u(xx,yy))';nlevels=50)

##
#  Circle
##

Γ=Circle()
z=Fun(Laurent(Γ))

u=(x,y)->α*(x+im*y)+2cauchy(ui,x+im*y)

m=80;x = linspace(-2.,2.,m);y = linspace(-2.,2.,m+1)
    xx,yy = x.+0.*y',0.*x.+y'

k=239;
    α=exp(-k/45im)
    c,ui=[0 DefiniteLineIntegral();
          1 real(Hilbert())]\[Fun(0.);imag(α*z)]

plot(Γ)
    contour!(x,y,imag(u(xx,yy))';nlevels=50)

##
# On a curve, the Hilbert transform may be complex, so we
# take the real part
##

m=80;x = linspace(-2.,2.,m);y = linspace(-2.,2.,m+1)
    xx,yy = x.+0.*y',0.*x.+y'

Γ=Curve(Fun(x->exp(0.8im)*(x+x^2-1+im*(x-4x^3+x^4)/6)))
    z=Fun(Γ)
    α=im
    S=JacobiWeight(0.5,0.5,Γ)
    c,ui=[1 real(Hilbert(S))]\ [imag(α*z)]

u=(x,y)->α*(x+im*y)+2cauchy(ui,x+im*y)
plot(Γ)
    contour!(x,y,imag(u.(x',y));nlevels=100)

##
#  Two intervals requires explicitely stating the space (for now)
##

Γ=Segment(-1.,-0.5) ∪ Segment(-0.3,1.)
z=Fun(Γ)

S=PiecewiseSpace(map(d->JacobiWeight(0.5,0.5,Ultraspherical(1,d)),Γ))

m=80;x = linspace(-2.,2.,m);y = linspace(-1.,1.,m+1)
    xx,yy = x.+0.*y',0.*x.+y'

k=114;
    α=exp(k/50*im)
    a,b,ui=[ones(Γ[1])+zeros(Γ[2]) zeros(Γ[1])+ones(Γ[2]) Hilbert(S)]\imag(α*z)

u= (x,y) -> α*(x+im*y)+2cauchy(ui,x+im*y)
    plot(Γ)
    contour!(x,y,imag(u(xx,yy))';nlevels=100)

Γ=Segment(-1.,0.) ∪ Segment(0.5im,1.)
z=Fun(Γ)
S=PiecewiseSpace(map(d->JacobiWeight(0.5,0.5,Ultraspherical(1,d)),Γ))

k=114;
    α=exp(k/50*im)
    a,b,ui=[ones(Γ[1])+zeros(Γ[2]) zeros(Γ[1])+ones(Γ[2])  real(Hilbert(S))]\imag(α*z)

u=(x,y)->α*(x+im*y)+2cauchy(ui,x+im*y)

m=80;x = linspace(-2.,2.,m);y = linspace(-1.,1.,m+1)
    xx,yy = x.+0.*y',0.*x.+y'
plot(Γ)
    contour!(x,y,imag(u(xx,yy))';nlevels=100)

# Segment and Curve

Γ=Segment(-im,1.0-im) ∪ Curve(Fun(x->exp(0.8im)*(x+x^2-1+im*(x-4x^3+x^4)/6)))
z=Fun(Γ)

S=PiecewiseSpace(map(d->JacobiWeight(0.5,0.5,Ultraspherical(1,d)),Γ))

m=80;x = linspace(-2.,2.,m);y = linspace(-3.,2.,m+1)
    xx,yy = x.+0.*y',0.*x.+y'

k=114;
    α=exp(k/50*im)
    a,b,ui=[ones(Γ[1])+zeros(Γ[2]) zeros(Γ[1])+ones(Γ[2]) real(Hilbert(S))]\imag(α*z)

plot(Γ)
    contour!(x,y,imag(u(xx,yy))';nlevels=100)

## Segment, Curve and Circle

Γ=Segment(-im,1.0-im) ∪ Curve(Fun(x->exp(0.8im)*(x+x^2-1+im*(x-4x^3+x^4)/6))) ∪ Circle(2.0,0.2)
    z=Fun(Γ)

S=PiecewiseSpace(map(d->isa(d,Circle) ? Fourier(d) : JacobiWeight(0.5,0.5,Ultraspherical(1,d)),Γ))

# This is a temporary work around as DefiniteLineIntegral is not implemented for curves
B=ApproxFun.SpaceOperator(ApproxFun.BasisFunctional(3),S,ApproxFun.ConstantSpace(Float64))

k=114;
    α=exp(k/50*im)
    a,b,c,ui=[0                 0                 0                 DefiniteLineIntegral(S);
              Fun(ones(Γ[1]),Γ) Fun(ones(Γ[2]),Γ) Fun(ones(Γ[3]),Γ) real(Hilbert(S))]\Any[0.;imag(α*z)]

m=160;x = linspace(-4.,4.,m);y = linspace(-2.,2.,m+1)
  xx,yy = x.+0.*y',0.*x.+y'

u =(x,y)->α*(x+im*y)+2cauchy(ui,x+im*y)

plot(Γ)
    contour!(x,y,imag(u(xx,yy))';nlevels=100)


@gen (static) function lambda_fa(arg1::Real, arg2::Real)
	fa = @trace(gamma(arg1, arg2), :fa)
	return fa
end

@gen (static) function miss_rate(arg1::Real, arg2::Real)
	miss = @trace(beta(arg1, arg2), :miss)
	return miss
end

"""
	make_visual_system(params::Video_Params)

Samples the matrix describing the initial visual system.
"""
@gen (static) function make_visual_system(params::Video_Params)
	arg1 = fill(1.0, length(params.possible_objects))
	arg2 = fill(1.0, length(params.possible_objects))
	fa = @trace(Map(lambda_fa)(arg1, arg2), :lambda_fa) #these are lambdas per receptive field
	miss = @trace(Map(miss_rate)(arg1, arg2), :miss_rate) #miss rate is misnomer. it's actually more like detection rate. it's mean of geometric distribution

	v = hcat(fa, miss)
   	return convert(Matrix{Real}, v)
end
################################################################################

"""
	main(num_videos::Int64, num_frames::Int64)

Simulates the visual system, `num_videos` scenes each with
`num_frames` frames, and percepts generated by the visual system.

arg lesioned is about whether or not to have the V kernel reflect an ideal observed
updating their beliefs as opposed to just resampling V from the prior
"""
@gen (static) function main(lesioned::Bool, num_videos::Int64, num_frames::Int64, params::Video_Params)

	receptive_fields = make_receptive_fields(params)

	init_v_matrix = @trace(make_visual_system(params), :init_v_matrix)
	alphas = fill(1, size(init_v_matrix))
	betas = fill(1, size(init_v_matrix))
	v_matrix_state = (init_v_matrix, alphas, betas)
	#println("alphas in main ", v_matrix_state[2])
    videos = @trace(video_chain(num_videos, v_matrix_state, lesioned, num_frames, params, receptive_fields), :videos)
	return videos
end

export main
export make_visual_system


function ITensors.OpSum(::Model"heisenberg", n1, n2)
  opsum = OpSum()
  opsum += 0.5, "S+", n1, "S-", n2
  opsum += 0.5, "S-", n1, "S+", n2
  opsum += "Sz", n1, "Sz", n2
  return opsum
end

# H = Σⱼ (½ S⁺ⱼS⁻ⱼ₊₁ + ½ S⁻ⱼS⁺ⱼ₊₁ + SᶻⱼSᶻⱼ₊₁)
function ITensors.MPO(::Model"heisenberg", s)
  N = length(s)
  os = OpSum()
  for j in 1:(N - 1)
    os .+= 0.5, "S+", j, "S-", j + 1
    os .+= 0.5, "S-", j, "S+", j + 1
    os .+= "Sz", j, "Sz", j + 1
  end
  return MPO(os, s)
end

"""
    reference(::Model"heisenberg", ::Observable"energy"; N)

Compute the analytic isotropic heisenberg chain ground energy per site for length `N`.
Assumes the heisenberg model is defined with spin
operators not pauli matrices (overall factor of 2 smaller). Taken from [1].

[1] Nickel, Bernie. "Scaling corrections to the ground state energy
of the spin-½ isotropic anti-ferromagnetic Heisenberg chain." Journal of
Physics Communications 1.5 (2017): 055021
"""
function reference(::Model"heisenberg", ::Observable"energy"; N=∞)
  isinf(N) && return (0.5 - 2 * log(2)) / 2
  E∞ = (0.5 - 2 * log(2)) * N
  Eᶠⁱⁿⁱᵗᵉ = π^2 / (6N)
  correction = 1 + 0.375 / log(N)^3
  return (E∞ - Eᶠⁱⁿⁱᵗᵉ * correction) / (2N)
end


module Quadratic

export quadratic, +, -, *, /, ≤

using IntervalSets
using Base: +, ≤

struct quadratic{S,T,U}
    a::S
    b::T
    c::U
end

(f::quadratic)(x) = f.a * x^2 + f.b * x + f.c

Base.:+(f::quadratic, g::quadratic) = quadratic(f.a + g.a, f.b + g.b, f.c + g.c)
Base.:-(f::quadratic, g::quadratic) = quadratic(f.a - g.a, f.b - g.b, f.c - g.c)
Base.:*(f::quadratic, c) = quadratic(f.a * c, f.b * c, f.c * c)
Base.:/(f::quadratic, c) = quadratic(f.a / c, f.b / c, f.c / c)

# return: region that f is less than g
function Base.:≤(f::quadratic, g::quadratic)
    # coefs of h ≔ f - g
    a = f.a - g.a
    b = f.b - g.b
    c = f.c - g.c

    # if h is linear or constant
    if iszero(a)
        iszero(b) && return c ≤ 0 ? [-Inf..Inf] : ClosedInterval{Float64}[]
        return b > 0 ? [-Inf..(-c / b)] : [(-c / b)..Inf]
    end

    # if h is quadratic
    D = b^2 - 4 * a * c
    if D > 0
        x₁, x₂ = (-b + √D) / (2a), (-b - √D) / (2a)
        if a > 0
            @assert x₁ > x₂
            return [x₂..x₁]
        else
            @assert x₁ < x₂
            return [-Inf..x₁, x₂..Inf]
        end
    else
        return a < 0 ? [-Inf..Inf] : ClosedInterval{Float64}[]
    end
end

end # module Quadratic

# # test
# using Plots
# f = quadratic(2, -2, 1)
# g = quadratic(-4, 5, 100)
# xs = -10:0.1:10
# plot(xs, f.(xs), label="f", c="blue", legend=:inline)
# plot!(xs, g.(xs), label="g", c="green", legend=:inline)
# region = f ≤ g
# for interval in region
#     xs = range(max(interval.left, -10), min(interval.right, 10); length=100)
#     plot!(xs, f.(xs), label=nothing, color="red", lw=3) |> display
# end


function linspace(start::T, stop::T, np::I) where {T <: AbstractFloat,I <: Integer}
    d = (stop - start) / (np - 1)
    p = Vector{T}(undef, np)
    p[1] = start
    p[end] = stop
    for i = 2:np - 1
        p[i] = start + (i - 1) * d
    end
    return p
end

function cosspace(start::T, stop::T, np::I) where {T <: AbstractFloat,I <: Integer}
    p = linspace(zero(T), convert(T, π), np)
    r = (stop - start) / 2
    for i = 1:np
        p[i] = start + r * (one(T) - cos(p[i]))
    end
    return p
end

function gen_airfoil(a::AbstractAirfoil{T}, np::Int=65; cos_space::Bool=true, orient::Symbol=:CW) where {T <: AbstractFloat}
    if cos_space
        xc = cosspace(zero(T), one(T), np)
    else
        xc = linspace(zero(T), one(T), np)
    end
    xu = Vector{T}(undef, np)
    yu = Vector{T}(undef, np)
    xl = Vector{T}(undef, np)
    yl = Vector{T}(undef, np)
    @inbounds for i = 1:np
        xu[i] = x_upper(a, xc[i])
        yu[i] = y_upper(a, xc[i])
        xl[i] = x_lower(a, xc[i])
        yl[i] = y_lower(a, xc[i])
    end
    return if orient == :CW
        vcat(xl[end:-1:2],xu), vcat(yl[end:-1:2],yu)
    elseif orient == :CCW
        vcat(xu[end:-1:2],xl), vcat(yu[end:-1:2],yl)
    else
        throw(ArgumentError("invalid orient=$orient, must be :CW or :CCW"))
    end
end

function n_upper(a::AbstractAirfoil{T}, xc::T)::Tuple{T,T} where {T <: AbstractFloat}
    θ = atan(dy_upper(a, xc))
    return -sin(θ), cos(θ)
end

function n_lower(a::AbstractAirfoil{T}, xc::T)::Tuple{T,T} where {T <: AbstractFloat}
    θ = atan(dy_lower(a, xc))
    return sin(θ), -cos(θ)
end

@inline n_upper(a::AbstractAirfoil{T}, xc::R) where {T <: AbstractFloat,R <: Real} = n_upper(a, convert(T, xc))
@inline n_lower(a::AbstractAirfoil{T}, xc::R) where {T <: AbstractFloat,R <: Real} = n_lower(a, convert(T, xc))

function t_upper(a::AbstractAirfoil{T}, xc::T)::Tuple{T,T} where {T <: AbstractFloat}
    dy = dy_upper(a, xc)
    if isinf(dy)
        return zero(T), sign(dy)
    else
        n = sqrt(dy^2 + oneunit(T))
        return 1 / n, dy / n
    end
end

function t_lower(a::AbstractAirfoil{T}, xc::T)::Tuple{T,T} where {T <: AbstractFloat}
    dy = dy_lower(a, xc)
    if isinf(dy)
        return zero(T), sign(dy)
    else
        n = sqrt(dy^2 + oneunit(T))
        return 1 / n, dy / n
    end
end

@inline t_upper(a::AbstractAirfoil{T}, xc::R) where {T <: AbstractFloat,R <: Real} = t_upper(a, convert(T, xc))
@inline t_lower(a::AbstractAirfoil{T}, xc::R) where {T <: AbstractFloat,R <: Real} = t_lower(a, convert(T, xc))

for fname in [:x_lower, :x_upper, :y_upper, :y_lower, :dy_upper, :dy_lower]
    @eval begin
        @inline function ($fname)(af::AbstractAirfoil, xc::AbstractVector)
            y = similar(xc)
            @inbounds for i in eachindex(xc)
                y[i] = ($fname)(af, xc[i])
            end
            y
        end
    end
end

for fname in [:fy_upper, :fy_lower, :n_upper, :n_lower, :t_upper, :t_lower]
    @eval begin
        @inline function ($fname)(af::AbstractAirfoil, xc::AbstractVector)
            y = similar(xc, 2, length(xc))
            @inbounds for i in eachindex(xc)
                y[:,i] .= ($fname)(af, xc[i])
            end
            y
        end
    end
end

function normalize_airfoil_data(xu, yu, xl, yl)
    iu = argmin(xu)
    il = argmin(xl)
    @assert xu[1] == xl[1]
    @assert yu[1] == yl[1]
    @assert iu == 1 || il == 1
    if iu > 1
        xl1 = vcat(xu[iu:-1:2], xl)
        yl1 = vcat(yu[iu:-1:2], yl)
        xu1 = xu[iu:end]
        yu1 = yu[iu:end]
    elseif il > 1
        xu1 = vcat(xl[il:-1:2], xu)
        yu1 = vcat(yl[il:-1:2], yu)
        xl1 = xl[il:end]
        yl1 = yl[il:end]
    else
        xu1 = copy(xu)
        yu1 = copy(yu)
        xl1 = copy(xl)
        yl1 = copy(yl)
    end
    x0 = xl1[1]
    y0 = yl1[1]
    @. xu1 -= x0
    @. yu1 -= y0
    @. xl1 -= x0
    @. yl1 -= y0
    x1 = (xl1[end] + xu1[end]) / 2
    @. xu1 /= x1
    @. yu1 /= x1
    @. xl1 /= x1
    @. yl1 /= x1
    xu1, yu1, xl1, yl1
end


module TestPresentation

using Test
using Catlab, Catlab.Doctrines

# Presentation
##############

A, B, C = Ob(FreeCategory, :A, :B, :C)
f = Hom(:f, A, B)
g = Hom(:g, B, C)

# Generators
pres = Presentation()
@test !has_generator(pres, :A)
add_generator!(pres, A)
@test generators(pres) == [ A ]
@test generator(pres, :A) == A
@test has_generator(pres, :A)
add_generator!(pres, B)
@test generators(pres) == [ A, B ]
@test_throws Exception add_generator!(pres, A)

add_generators!(pres, (f,g))
@test generators(pres) == [ A, B, f, g ]
@test generators(pres, FreeCategory.Ob) == [ A, B ]
@test generators(pres, FreeCategory.Hom) == [ f, g ]
@test generators(pres, (:A, :B)) == [ A, B ]
@test generators(pres, [:f, :g]) == [ f, g ]

# Merge presentations
pres = Presentation()
add_generators!(pres, (A, B, f))
other = Presentation()
add_generators!(other, (B, C, g))
merge_presentation!(pres, other)
@test generator(pres, :f) == f
@test generator(pres, :g) == g
@test generators(pres, FreeCategory.Ob) == [ A, B, C ]
@test generators(pres, FreeCategory.Hom) == [ f, g ]

# Presentation macro
####################

@present Company(FreeCategory) begin
  # Primitive concepts.
  Employee::Ob
  Department::Ob
  Str::Ob

  first_name::Hom(Employee, Str)
  last_name::Hom(Employee, Str)
  manager::Hom(Employee, Employee)
  works_in::Hom(Employee, Department)
  secretary::Hom(Department, Employee)

  # Defined concepts.
  second_level_manager := compose(manager, manager)
  third_level_manager := compose(manager, manager, manager)

  # Abbreviations (no syntactic term for LHS).
  boss = manager

  # Managers work in the same department as their employees.
  compose(boss, works_in) == works_in
  # The secretary of a department works in that department.
  compose(secretary, works_in) == id(Department)
end

# Check generators.
Employee, Department, Str = Ob(FreeCategory, :Employee, :Department, :Str)
@test collect(generators(Company)) == [
  Employee,
  Department,
  Str,
  Hom(:first_name, Employee, Str),
  Hom(:last_name, Employee, Str),
  Hom(:manager, Employee, Employee),
  Hom(:works_in, Employee, Department),
  Hom(:secretary, Department, Employee),
  Hom(:second_level_manager, Employee, Employee),
  Hom(:third_level_manager, Employee, Employee),
]

# Check equations.
manager = Hom(:manager, Employee, Employee)
works_in = Hom(:works_in, Employee, Department)
secretary = Hom(:secretary, Department, Employee)
@test collect(equations(Company)) == Equation[
  Hom(:second_level_manager, Employee, Employee) => compose(manager, manager),
  Hom(:third_level_manager, Employee, Employee) => compose(manager, manager, manager),
  compose(manager, works_in) => works_in,
  compose(secretary, works_in) => id(Department),
]

# Serialization
###############

to_json(expr) = to_json_sexpr(Company, expr)
from_json(sexpr) = parse_json_sexpr(Company, FreeCategory, sexpr)

# To JSON
to_json(generator(Company, :Employee)) == "Employee"
to_json(generator(Company, :manager)) == "manager"
to_json(compose(generator(Company, :manager), generator(Company, :manager))) ==
  ["compose", "manager", "manager"]

# From JSON
@test from_json("Employee") == generator(Company, :Employee)
@test from_json("manager") == generator(Company, :manager)
@test from_json(["compose", "manager", "manager"]) ==
  compose(generator(Company, :manager), generator(Company, :manager))

end


# div σ = f
# div u = g
# σ = -p I + 2 η ε(u)


module TestFMDatasetML100K

using FactorizationMachines
using Base.Test

info("Running tests on ml-100k dataset...")
(ml100kTrain, ml100kTrainRatings) = @time read_libsvm("data/ml-100k.train.libfm")
info("Train dim: $(size(ml100kTrain))")
(ml100kTest, ml100kTestRatings) = @time read_libsvm("data/ml-100k.test.libfm")
info("Test dim: $(size(ml100kTest))")

info("Test ML-100K - Training model...")
fmMl100k = @time train(ml100kTrain, ml100kTrainRatings,
        method = Methods.sgd(num_epochs = 10, alpha = 0.1),
        model_params = Models.gauss(num_factors = 4))
#info("FM Model: $fmMl100k")
info("Test ML-100K - Prediction over test dataset...")
p = @time predict(fmMl100k, ml100kTest)

@test length(p) == length(ml100kTestRatings)

rmse = sqrt(sum((p-ml100kTestRatings).^2)/length(p))
info("RMSE on test data: $rmse")

if isfile("data/ml-100k.output.libfm")
    info("File with output from libfm exists. Bencharking to its result...")
    libfmOutput = readcsv("data/ml-100k.output.libfm")
    rmseWithLibfm = sqrt(sum((p-libfmOutput).^2)/length(p))
    info("RMSE with Libfm output: $rmseWithLibfm")
end

end


# Turing.jl version of model at https://github.com/stan-dev/blob/master/misc/cluster/naive-bayes/naive-bayes.stan
# Reference: http://mlg.eng.cam.ac.uk/teaching/4f13/1617/document%20models.pdf
@model nbmodel(K, V, M, N, z, w, doc, alpha, beta) = begin
  theta ~ Dirichlet(alpha)
  phi = TArray{Any}(K)
  for k = 1:K
    phi[k] ~ Dirichlet(beta)
  end
  #for m = 1:M
  #  z[m] ~ Categorical(theta)
  #end

  log_theta = log.(theta)
  _lp += sum(log_theta[z])

  log_phi = map(x->log.(x), phi)
  _lp += mapreduce(n->log_phi[z[doc[n]]][w[n]], +, 1:N)
  # Turing.acclogp!(vi, lp)

  phi
end


"""
    cliffords_sampler(;input::Input, interf::Interferometer)

Sample photons according to the [`Bosonic`](@ref) case following
[Clifford & Clifford](https://arxiv.org/pdf/2005.04214.pdf) algorithm performed
(at most) in ``O(n2^m + Poly(n,m))`` time and ``O(m)`` space.
"""
function cliffords_sampler(;input::Input, interf::Interferometer)

    m = input.m
    n = input.n
    perm_n_ = permutations(1:n)

    U = interf.U
    A = U[:, 1:n]
    A = A[:, nthperm(1:n, rand(1:factorial(big(n))))]

    weight_array = [abs(A[i,1])^2 for i in 1:m]
    sample_array = [wsample(1:m, Weights(weight_array))]

    for k in 2:n
        k_ = collect(1:k)
        permanent_matrix = reshape(A[sample_array, k_], length(sample_array), k)
        unormalized_pmf = LaplaceExpansion(permanent_matrix, A[:,k_])
        weight_array = unormalized_pmf/sum(unormalized_pmf)
        push!(sample_array, wsample(1:m, Weights(weight_array)))
    end

    return sort(sample_array)

end


module SailingPolars

using DelimitedFiles

export SailingPolar, SurfaceParameters, getpolardata, deg2rad, rad2deg, cartesian2polar
export polar2cartesian, haversine, inverse_haversine, boatspeed, bestvectorspeed
export sailingspeed, sailsegmenttime

"""
    Structure to represent a polar CSV file's data.
Contains a matrix, speeds, of sailing speeds indexed by wind velocity and angle of boat to wind
winds is a list of wind speeds
degrees is a list of angles in degrees of direction relative to the wind
Note 0.0 degrees is directly into the wind, 180 degrees is directly downwind.
"""
struct SailingPolar
    winds::Vector{Float32}
    degrees::Vector{Float32}
    speeds::Matrix{Float32} # speeds[wind direction degrees, windspeed knots]
end

"""
    struct SurfaceParameters
Structure that represents wind and surface current direction and velocity for a given position
Angles in degrees, velocities in knots
"""
struct SurfaceParameters
    winddeg::Float32
    windkts::Float32
    currentdeg::Float32
    currentkts::Float32
end

"""
function getpolardata(filename)
Read a sailing polar CSV file and return a SailingPolar containing the file data.
A sailing polar file is a CSV file, with ';' used as the comma separator instead of a comma.
The first line of file contains labels for the wind velocities that make up columns, and
the first entry of each row makes up a column of angle of sailing direction from wind in degrees
"""
function getpolardata(filename)
    datacells, headercells = readdlm(filename, ';', header=true)
    winds = map(x -> parse(Float32, x), headercells[2:end])
    degrees = datacells[:, 1]
    speeds = datacells[:, 2:end]
    return SailingPolar(winds, degrees, speeds)
end

const R = 6372800  # Earth's approximate radius in meters

"""
    deg2rad(deg)
Convert degrees to radians
"""
deg2rad(deg) = (deg * π / 180.0 + 2π) % 2π

"""
    rad2deg(rad)
Convert radians to degrees
"""
rad2deg(rad) = (rad * (180.0 / π) + 360.0) % 360.0

"""
    cartesian2polard(x, y)
Convert x, y coordinates to polar coordinates with angle in degrees
"""
cartesian2polard(x, y) = sqrt(x * x + y * y), atand(x, y)

"""
    polard2cartesian(r, deg)
Convert polar coordinates in degrees to cartesian x, y coordinates
"""
polard2cartesian(r, deg) = r .* sincosd(deg)

"""
    function haversine(lat1, lon1, lat2, lon2)
Calculate the haversine function for two points on the Earth's surface.
Given two latitude, longitude pairs in degrees for a point on the Earth,
get distance in meters and the initial direction of travel in degrees for
movement from point 1 to point 2.
"""
function haversine(lat1, lon1, lat2, lon2)
    dlat = lat2 - lat1
    dlon = lon2 - lon1
    a = sind(dlat / 2)^2 + cosd(lat1) * cosd(lat2) * sind(dlon / 2)^2
    c = 2.0 * asind(sqrt(a))
    theta = atand(sind(dlon) * cosd(lat2),
        cosd(lat1) * sind(lat2) - sind(lat1) * cosd(lat2) * cosd(dlon))
    theta = (theta + 360) % 360
    return R * c * 0.5399565, theta
end

"""
    function inverse_haversine(lat1, lon1, distance, direction)
Calculate an inverse haversine function.
Takes the point of origin in degrees (latitude, longitude), distance in meters, and
initial direction in degrees, and returns the latitude and longitude of the endpoint
in degrees after traveling the specified distance.
"""
function inverse_haversine(lat1, lon1, distance, direction)
    lat2 = asind(sind(lat1) * cos(distance / R) + cosd(lat1) * sin(distance / R) * cosd(direction))
    lon2 = lon1 + atand(sind(direction) * sin(distance / R) * cosd(lat1),
                       cos(distance / R) - sind(lat1) * sind(lat2))
    return lat2, lon2
end

"""
    function boatspeed(sp::SailingPolar, pointofsail, windspeed)
Calculate the expected sailing speed in a specified direction in knots,
given sailing polar data, a desired point of sail in degrees, and wind speed in knots
"""
function boatspeed(sp::SailingPolar, pointofsail, windspeed)
    winds, degrees, speeds = sp.winds, sp.degrees, sp.speeds
    udeg = findlast(t -> t <= pointofsail, degrees)
    odeg = findfirst(t -> t >= pointofsail, degrees)
    uvel = findlast(t -> t <= windspeed, winds)
    ovel = findfirst(t -> t >= windspeed, winds)
    if any(t -> t == nothing, [udeg, odeg, uvel, ovel])
        return -1.0
    end
    frac = (odeg == udeg && uvel == ovel) ? 1.0 :
            (odeg == udeg) ? (windspeed - winds[uvel]) / (winds[ovel] - winds[uvel]) :
            (uvel == ovel) ? (pointofsail - degrees[udeg]) / (degrees[odeg] - degrees[udeg]) :
            ((pointofsail - degrees[udeg]) / (degrees[odeg] - degrees[udeg]) +
            (windspeed - winds[uvel]) / (winds[ovel] - winds[uvel])) / 2
    return speeds[udeg, uvel] + frac * (speeds[odeg, ovel] - speeds[udeg, uvel])
end

"""
    sailingspeed(sp::SailingPolar, azimuth, dir, ws)
Calculate the expected net boat speed in a desired direction (azimuth).
This is generally different from the actual boat speed in its actual direction.
Directions are in degrees (dir is wind direction), velocity of wind (ws) is in knots.
"""
sailingspeed(sp, azimuth, dir, ws) = boatspeed(sp, dir, ws) * cosd(abs(dir - azimuth))

"""
    function bestvectorspeed(sp::SailingPolar, dirtravel, dirwind, windspeed, dircur, velcur)
Calculate the net direction and velocity of a sailing ship.
Arguments are sailing polar data, direction of travel in degrees from north, wind direction in
degrees from north, wind velocity in knots, surface current direction in degrees, and
current velocity in knots.
"""
function bestvectorspeed(sp::SailingPolar, dirtravel, dirwind, windspeed, dircur, velcur)
    pointofsail = (dirtravel - dirwind) % 360.0
    pointofsail = pointofsail < 0 ? pointofsail + 360.0 : pointofsail
    pointofsail = pointofsail > 180.0 ? 360.0 - pointofsail : pointofsail
    VMG = boatspeed(sp, pointofsail, windspeed)
    other, idx = findmax([sailingspeed(sp, pointofsail, x, windspeed) for x in sp.degrees])
    if other > VMG
        pointofsail = sp.degrees[idx]
        VMG = other
    end
    dirchosen = deg2rad(dirwind + pointofsail)
    wx, wy = VMG * sin(dirchosen), VMG * cos(dirchosen)
    curx, cury = velcur * sin(deg2rad(dircur)), velcur * cos(deg2rad(dircur))
    return rad2deg(atan(wy + cury, wx + curx)), sqrt((wx + curx)^2 + (wy + cury)^2)
end

"""
    function sailsegmenttime(sp::SailingPolar, p::SurfaceParameters, lat1, lon1, lat2, lon2)
Calculate the trip time in minutes from (lat1, lon1) to the destination (lat2, lon2).
Uses the data in SurfaceParameters for wind and current velocity and direction.
"""
function sailsegmenttime(sp::SailingPolar, p::SurfaceParameters, lat1, lon1, lat2, lon2)
    distance, dir = haversine(lat1, lon1, lat2, lon2)
    dir2, vel = bestvectorspeed(sp, dir, p.winddeg, p.windkts, p.currentdeg, p.currentkts)
    # endlat2, endlon2 = inverse_haversine(lat1, lon1, distance, dir2)
    # minutes/s * m / (knots * (m/s / knot)) = minutes
    return (1 / 60) * distance / (vel * 1.94384)
end

end # module


using NURBS
using Base.Test

@testset "B-Spline curve generator" begin

    @testset "bspline" begin
        b = [1. 2 4 3; 1 3 3 1; 0 0 0 0]
        @test typeof(bspline(4,4,5,b)[1]) == Array{Float64,2}
        @test typeof(bspline(4,4,5,b)[2]) == Array{Array{Int64,1},1}
        @test isapprox(bspline(4,4,5,b)[1],[1.0 1.875 2.75 3.25 3.0; 1.0 2.125 2.5 2.125 1.0; 0.0 0.0 0.0 0.0 0.0],atol=1e-5)
    end

    @testset "bsplineu" begin
        b = [0. 3 6 9; 0 10 3 6; 0 0 0 0]
        @test typeof(bsplineu(4,3,5,b)[1]) == Array{Float64,2}
        @test typeof(bsplineu(4,3,5,b)[2]) == Array{Array{Int64,1},1}
        @test isapprox(bsplineu(4,3,5,b)[1],[1.5 3.0 4.5 6.0 7.5; 5.0 7.875 6.5 4.25 4.5; 0.0 0.0 0.0 0.0 0.0],atol=1e-5)
    end

    @testset "dbspline" begin
        b = [1. 2 4 3; 1 3 3 1; 0 0 0 0]
        @test typeof(dbspline(4,4,5,b)[1]) == Array{Float64,2}
        @test typeof(dbspline(4,4,5,b)[2]) == Array{Float64,2}
        @test typeof(dbspline(4,4,5,b)[3]) == Array{Float64,2}
        @test typeof(dbspline(4,4,5,b)[4]) == Array{Array{Int64,1},1}
        @test isapprox(dbspline(4,4,5,b)[1],[1.0 1.875 2.75 3.25 3.0; 1.0 2.125 2.5 2.125 1.0; 0.0 0.0 0.0 0.0 0.0],atol=1e-5)
        @test isapprox(dbspline(4,4,5,b)[2],[3.0 3.75 3.0 0.75 -3.0; 6.0 3.0 0.0 -3.0 -6.0; 0.0 0.0 0.0 0.0 0.0],atol=1e-5)
        @test isapprox(dbspline(4,4,5,b)[3],[6.0 0.0 -6.0 -12.0 -18.0; -12.0 -12.0 -12.0 -12.0 -12.0; 0.0 0.0 0.0 0.0 0.0],atol=1e-5)
    end

    @testset "dbsplineu" begin
        b = [0. 3 6 9; 0 10 3 6; 0 0 0 0]
        @test typeof(dbsplineu(4,3,5,b)[1]) == Array{Float64,2}
        @test typeof(dbsplineu(4,3,5,b)[2]) == Array{Float64,2}
        @test typeof(dbsplineu(4,3,5,b)[3]) == Array{Float64,2}
        @test typeof(dbsplineu(4,3,5,b)[4]) == Array{Array{Int64,1},1}
        @test isapprox(dbsplineu(4,3,5,b)[1],[1.5 3.0 4.5 6.0 7.5; 5.0 7.875 6.5 4.25 4.5; 0.0 0.0 0.0 0.0 0.0],atol=1e-5)
        @test isapprox(dbsplineu(4,3,5,b)[2],[3.0 3.0 3.0 3.0 3.0; 10.0 1.5 -7.0 -2.0 3.0; 0.0 0.0 0.0 0.0 0.0],atol=1e-5)
        @test isapprox(dbsplineu(4,3,5,b)[3],[0.0 0.0 0.0 0.0 0.0; -17.0 -17.0 10.0 10.0 10.0; 0.0 0.0 0.0 0.0 0.0],atol=1e-5)
    end

    @testset "nmatrix" begin
        @test typeof(nmatrix(3)[1]) == Array{Float64,2}
        @test typeof(nmatrix(3)[2]) == Float64
        @test nmatrix(3)[1] == [1.0 -2.0 1.0; -2.0 2.0 0.0; 1.0 1.0 0.0]
        @test nmatrix(3)[2] == 0.5
        @test nmatrix(4)[1] == [-1.0 3.0 -3.0 1.0; 3.0 -6.0 3.0 0.0; -3.0 0.0 3.0 0.0; 1.0 4.0 1.0 0.0]
        @test isapprox(nmatrix(4)[2],0.1666666666,atol=1e-10)
    end

    @testset "matpbspl" begin
        b = [0. 3 6 9; 0 10 3 6; 0 0 0 0]
        @test typeof(matpbspl(4,3,5,b)[1]) == Array{Float64,2}
        @test typeof(matpbspl(4,3,5,b)[2]) == Array{Array{Int64,1},1}
        @test isapprox(matpbspl(4,3,4,b)[1],[1.5 2.5 3.5 4.5 5.5 6.5 7.5 7.83333 6.83333 4.5 2.16667 1.16667 1.5 2.5 3.5; 5.0 7.38889 7.88889 6.5 4.72222 4.05556 4.5 5.0 4.5 3.0 1.88889 2.55556 5.0 7.38889 7.88889; 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0],atol=1e-5)
    end
end


function getTableauVSPARK(s, σ, o, tsym, g, h, lq, lp, ω, d=Nothing)
    # α_q_1 and α_p_2 need to be conjugate symplectic
    # α_q_1 and α_p_3 need to be conjugate symplectic
    # α_q_2 and α_p_2 need to be conjugate symplectic
    # α_q_2 and α_p_3 need to be conjugate symplectic
    # -> α_q_1 and α_q_2 need to be identical
    # -> α_p_2 and α_p_3 need to be identical

    α_q_1 = lq.a
    α_q_2 = lq.a
    α_p_2 = lp.a
    α_p_3 = lp.a

    b_q_1 = lq.b
    b_q_2 = lq.b
    b_p_2 = lp.b
    b_p_3 = lp.b

    # α_p_1 is free, but determines a_q_1 and a_q_2
    α_p_1 = h.a

    # a_p_1, a_p_2 and a_p_3 are free (they are not used in VSPARKpSecondary integrators anymore)
    a_p_1 = g.a
    b_p_1 = g.b

    a_p_2 = g.a
    a_p_3 = g.a

    β_q_1 = lq.b
    β_q_2 = lq.b

    β_p_1 = lp.b
    β_p_2 = lp.b
    β_p_3 = lp.b

    # a_q_1_ij = b_q_1_j / b_p_1_i * (b_p_1_i - α_p_1_ji)
    a_q_1 = zeros(s,σ)
    for i in 1:s
        for j in 1:σ
            a_q_1[i,j] = b_q_1[j] / b_p_1[i] * (b_p_1[i] - α_p_1[j,i])
        end
    end

    # a_q_2_ij = b_q_2_j / b_p_1_i * (b_p_1_i - α_p_1_ji)
    a_q_2 = zeros(s,σ)
    for i in 1:s
        for j in 1:σ
            a_q_2[i,j] = b_q_2[j] / b_p_1[i] * (b_p_1[i] - α_p_1[j,i])
        end
    end

    a_q = (a_q_1, a_q_2)
    b_q = (b_q_1, b_q_2)
    c_q = g.c

    a_p = (a_p_1, a_p_2, a_p_3)
    b_p = (b_p_1, b_p_2, b_p_3)
    c_p = g.c

    α_q = (α_q_1, α_q_2)
    β_q = (β_q_1, β_q_2)
    γ_q = lq.c

    α_p = (α_p_1, α_p_2, α_p_3)
    β_p = (β_p_1, β_p_2, β_p_3)
    γ_p = lp.c

    coeff_q = CoefficientsSPARK(tsym, o, s, σ, a_q, b_q, c_q)
    coeff_p = CoefficientsSPARK(tsym, o, s, σ, a_p, b_p, c_p)
    coeff_q̃ = CoefficientsSPARK(tsym, o, σ, s, α_q, β_q, γ_q)
    coeff_p̃ = CoefficientsSPARK(tsym, o, σ, s, α_p, β_p, γ_p)

    TableauVSPARKsecondary(tsym, o, s, σ, coeff_q, coeff_p, coeff_q̃, coeff_p̃, ω, d)
end

function TableauVSPARKLobattoIII(s, lq, lp; name = Symbol("VSPARKLobattoIII"))
    o = 2s-2
    getTableauVSPARK(s, s, o, name, lq, lp, lq, lp, get_lobatto_ω_matrix(s), get_lobatto_nullvector(s))
end

function TableauVSPARKLobattoIIIAB(s)
    lq = TableauLobattoIIIA(s)
    lp = TableauLobattoIIIB(s)
    TableauVSPARKLobattoIII(s, lq, lp; name = Symbol("VSPARKLobattoIIIAIIIB"))
end

function TableauVSPARKLobattoIIIBA(s)
    lq = TableauLobattoIIIB(s)
    lp = TableauLobattoIIIA(s)
    TableauVSPARKLobattoIII(s, lq, lp; name = Symbol("VSPARKLobattoIIIBIIIA"))
end

function TableauVSPARKLobattoIIICC̄(s)
    lq = TableauLobattoIIIC(s)
    lp = TableauLobattoIIIC̄(s)
    TableauVSPARKLobattoIII(s, lq, lp; name = Symbol("VSPARKLobattoIIICIIIC̄"))
end

function TableauVSPARKLobattoIIIC̄C(s)
    lq = TableauLobattoIIIC̄(s)
    lp = TableauLobattoIIIC(s)
    TableauVSPARKLobattoIII(s, lq, lp; name = Symbol("VSPARKLobattoIIIC̄IIIC"))
end

function TableauVSPARKLobattoIIID(s)
    l = TableauLobattoIIID(s)
    TableauVSPARKLobattoIII(s, l, l; name = Symbol("VSPARKLobattoIIID"))
end

function TableauVSPARKLobattoIIIE(s)
    l = TableauLobattoIIIE(s)
    TableauVSPARKLobattoIII(s, l, l; name = Symbol("VSPARKLobattoIIIE"))
end

function TableauVSPARKGLRKLobattoIII(s, σ, lq, lp; name = Symbol("VSPARKGLRKLobattoIII"))
    o = 2s
    g = TableauGauss(s)
    getTableauVSPARK(s, σ, o, name, g, get_lobatto_glrk_coefficients(s, σ), lq, lp, get_GLRK_ω_matrix(σ), get_lobatto_nullvector(σ))
end

function TableauVSPARKGLRKLobattoIIIAB(s, σ=s+1)
    TableauVSPARKGLRKLobattoIII(s, σ, TableauLobattoIIIA(σ), TableauLobattoIIIB(σ); name = Symbol("VSPARKGLRKLobattoIIIAIIIB"))
end

function TableauVSPARKGLRKLobattoIIIBA(s, σ=s+1)
    TableauVSPARKGLRKLobattoIII(s, σ, TableauLobattoIIIB(σ), TableauLobattoIIIA(σ); name = Symbol("VSPARKGLRKLobattoIIIBIIIA"))
end

function TableauVSPARKGLRKLobattoIIICC̄(s, σ=s+1)
    TableauVSPARKGLRKLobattoIII(s, σ, TableauLobattoIIIC(σ), TableauLobattoIIIC̄(σ); name = Symbol("VSPARKGLRKLobattoIIICIIIC̄"))
end

function TableauVSPARKGLRKLobattoIIIC̄C(s, σ=s+1)
    TableauVSPARKGLRKLobattoIII(s, σ, TableauLobattoIIIC̄(σ), TableauLobattoIIIC(σ); name = Symbol("VSPARKGLRKLobattoIIIC̄IIIC"))
end

function TableauVSPARKGLRKLobattoIIID(s, σ=s+1)
    TableauVSPARKGLRKLobattoIII(s, σ, TableauLobattoIIID(σ), TableauLobattoIIID(σ); name = Symbol("VSPARKGLRKLobattoIIID"))
end

function TableauVSPARKGLRKLobattoIIIE(s, σ=s+1)
    TableauVSPARKGLRKLobattoIII(s, σ, TableauLobattoIIIE(σ), TableauLobattoIIIE(σ); name = Symbol("VSPARKGLRKLobattoIIIE"))
end


using NPZ
using JuMP
using Ipopt
using LinearAlgebra
using WoodburyMatrices
using .Threads
using ProgressMeter
import Base: isless

struct TestResult
    i::Int64
    f::Float64
end
isless(a::TestResult, b::TestResult) = isless(a.f, b.f)

function main()
    T = Float64
    X1 = npzread("Kernels/G1.npy")
    X2 = npzread("Kernels/G2.npy")
    X3 = npzread("Kernels/G4.npy")
    nfactor = npzread("Testing/nfactor.npy")

    ϵ = 1e-2
    λ = 1.0
    lnorm = -log(ϵ)*(2*size(X1,2))

    X = [X3 zeros(T, size(X3));
         zeros(T, size(X3)) X3;
         nfactor*X1 nfactor*X2]
    Xn = maximum(sqrt.(sum(X.^2, dims=2)))
    X ./= Xn

    n = size(X3, 1)
    ndas = size(X1, 1)

    function Ml(μ, X, ϵ)
        M = ϵ*I + X'*Diagonal(μ)*X
        Mc = cholesky(Hermitian(M))
        l = μ.^2 .*diag(X*(Mc\X'))
        return (M, Mc, l)
    end

    Ml(μ) = Ml(μ, X, ϵ)

    function f(μ, λ, lnorm, n)
        M, Mc, l = Ml(μ)
        l2 = l[1:n] .+ l[(n+1):2*n] # coherence of point sensors only; sum across channels
        return -logdet(M) + λ*maximum(l2) - lnorm
    end

    f(μ) = f(μ, λ, lnorm, n)

    function testf(u, i)
        u_test = copy(u)
        u_test[i] = 1.0
        u_test[i+64] = 1.0
        return TestResult(i, f(u_test))
    end

    u = vcat(zeros(64+64), ones(size(X1,1)))
    to_test = Set(1:64)
    selection_order = Int[]
    f_vals = T[]
    p = Progress(32*65, dt=0.5, barglyphs=BarGlyphs("[=> ]"), barlen=50, color=:yellow)

    while length(to_test) > 0
        test_vec = TestResult[]
        for i in collect(to_test)
            push!(test_vec, testf(u, i))
            next!(p)
        end
        best = minimum(test_vec)
        delete!(to_test, best.i)
        push!(selection_order, best.i)
        push!(f_vals, best.f)
        u[best.i] = 1.0
        u[best.i+64] = 1.0
    end

    npzwrite("IncoherenceResults/seq_order.npy", selection_order)
    npzwrite("IncoherenceResults/seq_vals.npy", f_vals)

end

main()
# npzwrite("res_mixed.npy", value.(x))
# ;


"""
            Kernels, Kernelvalues and -derivatives for sph mapping.

    Author: Ludwig Böss
    Contact: lboess@usm.lmu.de
    Created: 2018-12-12

"""

#using Distributed

abstract type SPHKernel end

"""
            Cubic
"""
# struct
struct Cubic <: SPHKernel
    n_neighbours::Int64
    norm_2D::Float64
    norm_3D::Float64
    function Cubic(n_neighbours::Int64=64)
        new(n_neighbours, 8.0/π, 8.0/π)
    end
end

# kernel values
@inline function kernel_value_2D(kernel::Cubic, u::Float64, h_inv::Float64)

    n = kernel.norm_3D * h_inv^2

    if u < 0.5
        return ( 1.0 - 6.0 * (1.0 - u) * u^2) * n
    elseif u < 1.0
        return ( 2.0 * (1.0 - u) * (1.0 - u) * (1.0 - u)) * n
    else
        return 0.
    end

end

@inline function kernel_value_3D(kernel::Cubic, u::Float64, h_inv::Float64)

    n = kernel.norm_3D * h_inv^3

    if u < 0.5
        return ( 1.0 + 6.0 * (u - 1.0) * u^2) * n
    elseif u < 1.0
        return ( 2.0 * (1.0 - u) * (1.0 - u) * (1.0 - u)) * n
    else
        return 0.
    end

end

"""
            Quintic
"""
# struct
struct Quintic <: SPHKernel
    n_neighbours::Int64
    norm_2D::Float64
    norm_3D::Float64
    function Quintic(n_neighbours::Int64=200)
        new(n_neighbours, 15309.0/(478.0*π), 2187.0/(40.0*pi))
    end
end

# kernel values
@inline function kernel_value_2D(kernel::Quintic, u::Float64, h_inv::Float64)

    n = kernel.norm_2D * h_inv^2

    if u < 1.0/3.0
        u_m1  = ( 1.0 - u )
        u_m23 = ( 2.0/3.0 - u )
        u_m13 = ( 1.0/3.0 - u )
        return ( u_m1*u_m1*u_m1*u_m1*u_m1 - 6.0 * u_m23*u_m23*u_m23*u_m23*u_m23 + 15.0 * u_m13*u_m13*u_m13*u_m13*u_m13 ) * n
    elseif u < 2.0/3.0
        u_m1  = ( 1.0 - u )
        u_m23 = ( 2.0/3.0 - u )
        return ( u_m1*u_m1*u_m1*u_m1*u_m1 - 6.0 * u_m23*u_m23*u_m23*u_m23*u_m23 ) * n
    elseif u < 1.0
        u_m1  = ( 1.0 - u )
        return ( u_m1*u_m1*u_m1*u_m1*u_m1 ) * n
    else
        return 0.
    end

end

@inline function kernel_value_3D(kernel::Quintic, u::Float64, h_inv::Float64)

    n = kernel.norm_3D * h_inv^3

    if u < 1.0/3.0
        u_m1  = ( 1.0 - u )
        u_m23 = ( 2.0/3.0 - u )
        u_m13 = ( 1.0/3.0 - u )
        return ( u_m1*u_m1*u_m1*u_m1*u_m1 - 6.0 * u_m23*u_m23*u_m23*u_m23*u_m23 + 15.0 * u_m13*u_m13*u_m13*u_m13*u_m13 ) * n
    elseif u < 2.0/3.0
        u_m1  = ( 1.0 - u )
        u_m23 = ( 2.0/3.0 - u )
        return ( u_m1*u_m1*u_m1*u_m1*u_m1 - 6.0 * u_m23*u_m23*u_m23*u_m23*u_m23 ) * n
    elseif u < 1.0
        u_m1  = ( 1.0 - u )
        return ( u_m1*u_m1*u_m1*u_m1*u_m1 ) * n
    else
        return 0.
    end

end

"""
            Wendland C4
"""
# struct
struct WendlandC4 <: SPHKernel
    n_neighbours::Int64
    norm_2D::Float64
    norm_3D::Float64
    function WendlandC4(n_neighbours::Int64=200)
        new(n_neighbours,9.0/π, 495.0/(32.0 * π))
    end
end

# kernel values
@inline function kernel_value_2D(kernel::WendlandC4, u::Float64, h_inv::Float64)

    if u < 1.0
        n = kernel.norm_2D * h_inv^2
        u_m1 = 1.0 - u
        u_m1_2 = u_m1 * u_m1  # (1.0 - u)^2
        u_m1_4 = u_m1 * u_m1  # (1.0 - u)^4
        return ( u_m1_2*u_m1_4 * ( 1.0 + 6u + 35.0/3.0 * u^2 ) ) * n
    else
        return 0.
    end

end

@inline function kernel_value_3D(kernel::WendlandC4, u::Float64, h_inv::Float64)

    if u < 1.0
        n = kernel.norm_3D * h_inv^3
        u_m1 = 1.0 - u
        u_m1_2 = u_m1 * u_m1  # (1.0 - u)^2
        u_m1_4 = u_m1 * u_m1  # (1.0 - u)^4
        return ( u_m1_2*u_m1_4 * ( 1.0 + 6u + 35.0/3.0 * u^2 ) ) * n
    else
        return 0.
    end

end

"""
            Wendland C6
"""
# struct
struct WendlandC6 <: SPHKernel
    n_neighbours::Int64
    norm_2D::Float64
    norm_3D::Float64
    function WendlandC6(n_neighbours::Int64=295)
        new(n_neighbours, 78.0/(7.0*π), 1365.0/(64.0*π))
    end
end

# kernel values
@inline function kernel_value_2D(kernel::WendlandC6, u::Float64, h_inv::Float64)

    if u < 1.0
        n = kernel.norm_2D * h_inv^2
        u_m1 = 1.0 - u
        u_m1 = u_m1 * u_m1  # (1.0 - u)^2
        u_m1 = u_m1 * u_m1  # (1.0 - u)^4
        u_m1 = u_m1 * u_m1  # (1.0 - u)^8
        u2 = u*u
        return ( u_m1 * ( 1.0 + 8u + 25u2 + 32u2*u )) * n
    else
       return 0.0
   end

end

@inline function kernel_value_3D(kernel::WendlandC6, u::Float64, h_inv::Float64)

    if u < 1.0
        n = kernel.norm_3D * h_inv^3
        u_m1 = 1.0 - u
        u_m1 = u_m1 * u_m1  # (1.0 - u)^2
        u_m1 = u_m1 * u_m1  # (1.0 - u)^4
        u_m1 = u_m1 * u_m1  # (1.0 - u)^8
        u2 = u*u
        return ( u_m1 * ( 1.0 + 8u + 25u2 + 32u2*u )) * n
    else
       return 0.0
   end

end

# @inline function kernel_deriv(kernel::Cubic, u::Float64, h::Float64)
#
#     norm = 8.0/π
#     n = norm/h^4
#
#     if u < 0.5
#         return ( u * (18.0 * u - 12.0 )) * n
#     elseif u < 1.0
#         return ( -6.0 * (1.0 - u) * (1.0 - u) ) * n
#     else
#         return 0.
#     end
#
# end

# @inline function kernel_deriv(kernel::Quintic, u::Float64, h::Float64)
#
#     norm = ( 2187.0 / ( 40. * π))
#     n = norm/h^4
#
#     if u < 1.0/3.0
#         return ( -5.0 * ( 1.0 - u )^4 + 30.0 * ( 2.0/3.0 - u )^4  - 75.0 * ( 1.0/3.0 - u )^4 ) * n
#     elseif u < 2.0/3.0
#         return ( -5.0 * ( 1.0 - u )^4 + 30.0 * ( 2.0/3.0 - u )^4 - 75.0 ) * n
#     elseif u < 1.0
#         return ( -5.0 * ( 1.0 - u )^4 ) * n
#     else
#         return 0.
#     end
#
# end

# @inline function kernel_deriv(kernel::WendlandC4, u, h)
#
#     norm = 495.0/(32. * π)
#     n = norm/h^4
#
#     if u < 1.0
#         return ( -288.0/3.0 * ( 1. - u )^5 * u^2 - 56.0/3.0 * u * ( 1. - u)^5 ) * n
#     else
#         return 0.
#     end
#
# end

# @inline function kernel_deriv(kernel::WendlandC6, u::Float64, h::Float64)
#
#     norm = 1365.0/(64.0*π)
#     n = norm/h^4
#
#     if u < 1.0
#         return ( -22. * (1.0 - u)^7 * u * ( 16. * u^2 + 7. * u + 1. )) * n
#     else
#         return 0.
#     end
#
# end


using Test
using LinearAlgebra, LinearAlgebraX, Mods

@testset "Basics" begin
    A = big.(rand(Int, 10, 10) .% 100)

    include("../extras/hilbert.jl")

    H = hilbert(12)
    @test rankx(H) == 12

    @test detx(A) == det(Rational.(A))
    @test detx(A) == detx(A')
    @test A * invx(A) == eye(BigInt, 10)

    A = ones(Int, 3, 5)
    N = nullspacex(A)
    @test all(0 .== A * N)
end

@testset "Polynomials" begin
    using SimplePolynomials

    A = triu(ones(Int, 5, 5))
    p = char_poly(A)
    x = getx()
    @test p == (x - 1)^5

    A = ones(Int, 5, 5)
    p = char_poly(A)
    @test p == x^4 * (x - 5)
end

@testset "Modular" begin
    T = Mod{17}
    A = rand(T, 6, 6)
    p = char_poly(A)
    @test detx(A) == p(0)

    A = rand(Int, 20, 20) .% 100
    @test rankx(A) <= 20
end

@testset "Homogeneous" begin
    v = HVector(2, 3, 2)
    @test v[2] == 3 // 2
    @test dot(v, v) == 1
    w = HVector(20, 30, 20)
    @test v == w

    A = rand(Int, 3, 3) .% 20
    w = A * v
    @test w == HVector(A * v.data)
end

@testset "Permanent" begin
    A = ones(Int, 6, 6)
    @test permanent(A) == factorial(6)

    A = A - eye(Int, 6)
    @test permanent(A) == 265  # number of derangements of [6]

    A = triu(ones(5, 5))
    @test permanent(A) == 1
end


######################################################################
# bearing.jl
#
# bearing only sensing modality
######################################################################

struct BearingOnly <: Sensor
    noise_sigma::Float64		# noise std deviation (degrees)

    BearingOnly(ns::Real) = new(ns)
    BearingOnly() = new(10.0)
end

function observe(theta::LocTuple, s::BearingOnly, p::Pose)
    truth = true_bearing(p, theta)
    noise = s.noise_sigma * randn()
    return mod(truth + noise, 360.0)
end

function O(bo::BearingOnly, theta::TargetTuple, p::Pose, o)

    # Calculate true bearing, and find distance to bin edges
    ang_deg = true_bearing(p, theta)
    o_diff = fit_180(o - ang_deg)

    # now look at probability
    d = Normal(0, bo.noise_sigma)
    p = pdf(d, o_diff)
    return p
end


using Statistics, SpecialFunctions

"""
    tnmom2c(c, a, b)

Computes ⟨(x - c)^2⟩ for the standard normal distribution truncated to the
interval [a, b].
"""
function tnmom2c(c::Real, a::Real, b::Real)
    if !(a ≤ b) || isnan(c)
        return oftype(middle(a, b), NaN)
    elseif a == b
        return oftype(middle(a, b), (a - c) * (b - c))
    elseif isinf(c)
        return c^2
    elseif abs(a) > abs(b)
        return tnmom2c(-c, -b, -a)
    elseif isinf(a) && isinf(b)
        return oftype(middle(a, b), 1 + c^2)
    elseif c ≤ 0
        m1 = tnmean(a, b)
        @assert m1 ≥ 0
        #= at this point m1 ≥ 0, so the subtraction is fine =#
        return tnmom2(a, b) + c^2 - 2c * m1
    elseif isfinite(a) && isinf(b)
        return 1 + c^2 + √(2/π) * (a - 2c) / erfcx(a / √2)
    end

    @assert a < b
    @assert 0 ≤ a ≤ b || a ≤ 0 ≤ b

    exp_a = exp(-a^2 / 2)
    exp_b = exp(-b^2 / 2)
    erfcx_abs_a = erfcx(abs(a) / √2)
    erfcx_b = erfcx(b / √2)
    erfc_abs_a = exp_a * erfcx_abs_a
    erfc_b = exp_b * erfcx_b

    ra = √(2/π) * (a - 2c) / (1 + c^2)    # a / (1 + c^2) - 2 / (1/c + c)
    rb = √(2/π) * (b - 2c) / (1 + c^2)

    Δ = (b - a) * middle(a, b)

    if a ≤ 0 ≤ b
        erfc_a = 2 - erfc_abs_a
        erfc_ma = erfc_abs_a
        erfc_mb = 2 - erfc_b
        erf_a = erfc_abs_a - 1
        erf_b = 1 - erfc_b
        @assert erf_b ≥ erf_a

        γa = ra * exp_a - erf_a
        γb = rb * exp_b - erf_b
        @assert γa ≥ γb

        if γb ≤ 0 ≤ γa
            m = γa - γb
            @assert m ≥ 0
        elseif γa ≤ 0 && γb ≤ 0
            m = (ra * exp_a + erfc_a) - (rb * exp_b + erfc_b)
            @assert m ≥ 0
        elseif γa ≥ 0 && γb ≥ 0
            m = (ra * exp_a - erfc_ma) - (rb * exp_b - erfc_mb)
            @assert m ≥ 0
        end
        m /= (erf_b - erf_a)
    elseif 0 ≤ a ≤ b
        erfc_a = erfc_abs_a
        erfc_ma = 2 - erfc_abs_a
        erfc_mb = 2 - erfc_b
        erf_a = 1 - erfc_a
        erf_b = 1 - erfc_b
        erfcx_a = erfcx_abs_a

        γa = ra * exp_a - erf_a
        γb = rb * exp_b - erf_b
        @assert γa ≥ γb

        if γa ≥ 0 ≥ γb
            m = (γa - γb) / (erfc_a - erfc_b)
            @assert m ≥ 0
        elseif γa ≤ 0 && γb ≤ 0 # ζ ≤ a ≤ b
            exp_Δ = exp(-Δ)
            m = (ra + erfcx_a) - (rb + erfcx_b) * exp_Δ
            @assert !isnan(m)
            @assert m ≥ 0
            m /= erfcx_a - erfcx_b * exp_Δ
            @assert m ≥ 0
        elseif γa ≥ 0 && γb ≥ 0
            m = (ra * exp_a - erfc_ma) - (rb * exp_b - erfc_mb)
            m /= erfc_a - erfc_b
            @assert m ≥ 0
        else
            error("trouble")
        end
    else
        error("trouble")
    end
    @assert m ≥ 0
    return (1 + c^2) * m

    # γa = √(2/π) * (a - 2c) / (1 + c^2) * exp(-a^2 / 2) - ea
    # γb = √(2/π) * (b - 2c) / (1 + c^2) * exp(-b^2 / 2) - eb
    #
    # ea = erf(a / √2)
    # eb = erf(b / √2)
    #
    # if γb ≤ 0 ≤ γa
    #     return (1 + c^2) * (γa - γb) / (eb - ea)
    # else
    #     ea = erfcx(a / √2)
    #     eb = erfcx(b / √2)
    #     γa = √(2/pi) * (a - 2c) / (1 + c^2) + ea
    #     γb = √(2/pi) * (b - 2c) / (1 + c^2) + eb
    #     @assert 0 ≤ Δ ≤ 1
    #     #@assert γb ≤ γa
    #     @assert eb ≤ ea
    #     return (1 + c^2) * (γa - γb * Δ) / (ea - eb * Δ)
    # end
end

"""
    tnmom2c(c, a, b, μ, σ)

Computes ⟨(x - c)^2⟩ for the normal distribution with mean μ and standard
deviation σ, truncated to the interval [a, b].
"""
function tnmom2c(c, a, b, μ, σ)
    α = (a - μ) / σ
    β = (b - μ) / σ
    γ = (c - μ) / σ
    return σ^2 * tnmom2c(γ, α, β)
end


using Plexifications
using Base.Test: @test, @test_throws

@test begin
    iszerodivisor(0)
end

@test begin
    !iszerodivisor(3.14)
end

@test_throws ErrorException begin
    inv(Cmplex(0, 0.0))
end

@test begin
    iszerodivisor(Cmplex(0,0))
end

@test begin
    !iszerodivisor(Cmplex(0,1))
end

@test begin
    length(unreal(1)) == 0
end

@test begin
    a = Plexifications.randomBigFloat()
    isreal(Cmplex(a))
end

@test begin
    a = 1
    !isreal(Cmplex(a, a))
end

@test begin
    z = random(Cmplex{Int})
    z == +(z)
end

@test begin
    a = Plexifications.randomBigFloat()
    real(Cmplex(a)) == a
end

@test begin
    io = IOBuffer()
    show(io, Cmplex(1, 2))
    l = String(take!(io))
    r = "[1: 1, H: 2]"
    l == r
end

@test begin
    x = random(Cmplex{BigFloat})
    y = random(Cmplex{BigFloat})
    x != y
end

@test begin
    a = Plexifications.randomBigInt()
    z = random(Cmplex{BigInt})
    l = a + z
    r = z + a
    l == r
end

@test begin
    a = Plexifications.randomBigInt()
    z = random(Cmplex{BigInt})
    l = a - z
    r = -(z - a)
    l == r
end

@test begin
    x = random(Cmplex{BigInt})
    y = random(Cmplex{BigInt})
    l = x + y
    r = y + x
    l == r
end

@test begin
    x = random(Cmplex{BigInt})
    y = random(Cmplex{BigInt})
    l = x * y
    r = y * x
    l == r
end

@test begin
    x = random(Cmplex{BigInt})
    y = random(Cmplex{BigInt})
    l = x - y
    r = -(y - x)
    l == r
end

@test begin
    x = random(Cmplex{BigInt})
    y = random(Cmplex{BigInt})
    z = random(Cmplex{BigInt})
    l = (x + y) + z
    r = x + (y + z)
    l == r
end

@test begin
    x = random(Cmplex{BigInt})
    y = random(Cmplex{BigInt})
    z = random(Cmplex{BigInt})
    l = (x * y) * z
    r = x * (y * z)
    l == r
end

@test begin
    x = random(Cmplex{BigInt})
    barH(barH(x)) == x
end

@test begin
    x = random(Cmplex{Rational{BigInt}})
    inv(inv(x)) == x
end

@test begin
    x = random(Cmplex{BigInt})
    y = random(Cmplex{BigInt})
    l = barH(x * y)
    r = barH(y) * barH(x)
    l == r
end

@test begin
    x = random(Cmplex{BigInt})
    y = random(Cmplex{BigInt})
    l = barH(x + y)
    r = barH(x) + barH(y)
    l == r
end

@test begin
    x = random(Cmplex{BigInt})
    y = random(Cmplex{BigInt})
    l = barH(x - y)
    r = barH(x) - barH(y)
    l == r
end

@test begin
    x = random(Cmplex{Rational{BigInt}})
    y = random(Cmplex{Rational{BigInt}})
    l = inv(x * y)
    r = inv(y) * inv(x)
    l == r
end

@test begin
    x = random(Cmplex{Rational{BigInt}})
    y = random(Cmplex{Rational{BigInt}})
    l = inv(x / y)
    r = y / x
    l == r
end

@test begin
    x = random(Cmplex{Rational{BigInt}})
    y = random(Cmplex{Rational{BigInt}})
    l = inv(x \ y)
    r = y \ x
    l == r
end

@test begin
    x = random(Cmplex{BigInt})
    y = random(Cmplex{BigInt})
    z = random(Cmplex{BigInt})
    l = x * (y + z)
    r = (x * y) + (x * z)
    l == r
end

@test begin
    a = Plexifications.randomBigInt()
    x = random(Cmplex{BigInt})
    y = random(Cmplex{BigInt})
    l = a * (x + y)
    r = (a * x) + (a * y)
    l == r
end

@test begin
    a = Plexifications.randomBigInt()
    x = random(Cmplex{BigInt})
    l = a * x
    r = x * a
    l == r
end

@test begin
    x = random(Cmplex{BigInt})
    y = random(Cmplex{BigInt})
    z = random(Cmplex{BigInt})
    l = (x + y) * z
    r = (x * z) + (y * z)
    l == r
end

@test begin
    x = random(Cmplex{BigInt})
    y = random(Cmplex{BigInt})
    z = random(Cmplex{BigInt})
    l = x * (y - z)
    r = (x * y) - (x * z)
    l == r
end

@test begin
    x = random(Cmplex{BigInt})
    y = random(Cmplex{BigInt})
    z = random(Cmplex{BigInt})
    l = (x - y) * z
    r = (x * z) - (y * z)
    l == r
end

@test begin
    x = random(Cmplex{BigInt})
    abs2(x) > big(0)
end

@test begin
    x = random(Cmplex{BigInt})
    y = random(Cmplex{BigInt})
    l = abs2(x * y)
    r = abs2(x) * abs2(y)
    l == r
end

@test_throws ErrorException begin
    inv(Cmplex(0, 0.0))
end

@test_throws ErrorException begin
    random(Cmplex{Int}) / 0
end

@test_throws ErrorException begin
    0 \ random(Cmplex{Int})
end

@test begin
    a = Plexifications.randomBigRational()
    z = random(Cmplex{Rational{BigInt}})
    l = z / a
    r = a \ z
    l == r
end

@test begin
    a = Plexifications.randomBigRational()
    z = random(Cmplex{Rational{BigInt}})
    l = a / z
    r = z \ a
    l == r
end

@test begin
    z = random(Cmplex{BigInt})
    z == z + zero(Cmplex{BigInt})
end

@test begin
    z = random(Cmplex{BigInt})
    z == z * one(Cmplex{BigInt})
end

@test begin
    z = random(Cmplex{BigInt})
    z == z + zero(z)
end

@test begin
    z = random(Cmplex{BigInt})
    z == z * one(z)
end


const named_array_plot_types = [BarPlot, Heatmap, Volume]

struct NamedSparseArray{S<:Tuple, V<:AbstractVector}
    keys::S
    values::V
    function NamedSparseArray(args::AbstractVector...)
        keys = Base.front(args)
        values = last(args)
        return new{typeof(keys), typeof(values)}(keys, values)
    end
end

# this logic may be moved to AbstractPlotting
function dense(kv::NamedSparseArray)
    keys, values = kv.keys, kv.values
    labels = map(collect∘uniquesorted, keys)
    indices = map(Base.OneTo∘length, labels)
    converter = map((k, v) -> Dict(zip(k, v)), labels, indices)
    d = zeros(eltype(values), indices)
    for (k, v) in zip(StructArray(keys), values)
        I = map(getindex, converter, k)
        d[I...] = v
    end
    return labels, d
end

function convert_arguments(P::PlotFunc, m::NamedSparseArray)
    labels, values = dense(m)
    ptype = plottype(P, named_array_plot_types[length(labels)])
    to_plotspec(ptype, convert_arguments(ptype, labels..., values))
end

# TODO optimize with refarray and refvalue for pooled data
function _frequency(args...)
    s = StructArray(map(vec, args))
    gp = GroupPerm(s)
    sp = sortperm(gp)
    itr = (s[sp[first(range)]] => length(range) for range in gp)
    keys, values = fieldarrays(StructArray(itr, unwrap = t -> t <: Tuple))
    return NamedSparseArray(fieldarrays(keys)..., values)
end

const frequency = Analysis(_frequency)


#=
1. Create a regular $N^3$ grid T of random tetrahedra:
	(a) Generate four vertices for each tetrahedron randomly in a half-open fixed-size cube.
	(b) Let these cubes form a regular $N^3$ grid.
2. Create a regular $(N − 1)^3$ grid C of such cubes.
3. Align T and C such that the grid nodes of C are at the centers of the grid cells of T .
4. Measure time for T ∪ C.
=#

using ViewerGL; GL = ViewerGL
using LinearAlgebraicRepresentation
Lar = LinearAlgebraicRepresentation;

function t1t2(i,j,k)
	v1 = rand(3,4).+ [2i,2j,2k]
	v2 = rand(3,4).+ [2i,2j,2k]
	ev = [[1,2],[1,3],[1,4],[2,3],[2,4],[3,4]]
	fv = [[2,3,4],[1,3,4],[1,2,4],[1,2,3]]
	t1 = [v1,fv,ev]
	t2 = [v2,fv,ev]
	return t1,t2
end

N = 7  # N ≥ 2
tets = []
for i=0:N-1, j=0:N-1, k=0.5:0.5
	push!(tets, t1t2(i,j,k)...)
end
V,FV,EV = Lar.struct2lar(Lar.Struct(tets))
GL.VIEW([ GL.GLGrid(V,FV, GL.COLORS[1],1), GL.GLFrame2 ]);

W,(_,EW,FW,_) = Lar.cuboidGrid([1,1,1],true);
U = (W .* [2(N),2(N),2]) .+ [-.5,-.5,-.5]
GL.VIEW([ GL.GLGrid(V,FV,GL.COLORS[1],0.5), GL.GLGrid(U,FW,GL.COLORS[2],0.5), GL.GLFrame2 ]);

V,FV,EV = Lar.struct2lar(Lar.Struct([Lar.Struct(tets), Lar.Struct([(U,FW,EW)]) ]))
GL.VIEW([ GL.GLGrid(V,EV, GL.COLORS[1],0.5), GL.GLFrame2 ]);

	cop_EV = convert(Lar.ChainOp, Lar.coboundary_0(EV::Lar.Cells));
	cop_FE = Lar.coboundary_1(V, FV::Lar.Cells, EV::Lar.Cells);
	W = convert(Lar.Points, V');
	V, copEV, copFE, copCF = Lar.Arrangement.spatial_arrangement( W, cop_EV, cop_FE);
	W = convert(Lar.Points, V');
	V,CVs,FVs,EVs = Lar.pols2tria(W, copEV, copFE, copCF);

GL.VIEW( GL.GLExplode(V,CVs[3:end],1.5,1.5,1.5,99,0.5) );
GL.VIEW( GL.GLExplode(V,CVs[3:end],1,1,1,99,0.5) );
GL.VIEW( GL.GLExplode(V,CVs[2:2],5,5,5,99,0.5) );
GL.VIEW( GL.GLExplode(V,CVs[12:12],5,5,5,99,0.5) );


export odn_to_grid,grid_to_odn,fwi_wavelet,index_block

function index_block(idx,block_size::I) where {I<:Integer}
    m = div(length(idx),block_size)
    out_idx = [(i+1:i+block_size) for i in (0:m-1)*block_size]
    if length(idx) > m*block_size
        push!(out_idx, m*block_size+1:length(idx))
    end
    return [idx[out_idx[i]] for i in 1:length(out_idx)]
end

function fwi_wavelet(freqs::AbstractArray{F,1},t0::F,f0::F) where {F<:Real}
    w = exp.(-2*π*im*t0*freqs)
    if f0 > 0
        @. w = freqs^2 * exp(-(freqs./f0)^2)*w
    end
    return w
end

# Convert odn coordinates to their full grid coordinates
function odn_to_grid(o::AbstractArray{F,1},d::AbstractArray{F,1},n::AbstractArray{I,1}) where {F<:AbstractFloat, I<:Integer}
    (length(o)==length(d) && length(o)==length(n)) || throw(Exception("o,d,n must have the same length"))
    x = Array{StepRangeLen,1}(length(o))
    for i in 1:length(o)
        x[i] = o[i] + (0:(n[i]-1))*d[i]
    end
    return tuple(x...)
end

# Convert grid coordinates to odn coordinates
function grid_to_odn(x::AbstractArray{StepRangeLen{F},1}) where {F<:AbstractFloat}
    o = Array{F,1}()
    d = Array{F,1}()
    n = Array{Int64,1}()
    for i in 1:length(o)
        o[i] = x[i][1]
        d[i] = x[i][2] - x[i][1]
        n[i] = length(x[i])
    end
    return (o,d,n)
end


# License for this file: MIT (expat)
# Copyright 2017-2018, DLR Institute of System Dynamics and Control

"""
    module Simulate_Pendulum

Demonstrate how a pendulum can be defined, simulated and plotted
with ModiaMath.
"""
module Simulate_Pendulum

using ModiaMath
using ModiaMath.StaticArrays   # included via ModiaMath, to avoid requirement to add it in the standard environment

@component Pendulum(;L=1.0, m=1.0, d=0.1, g=9.81, phi0_deg=90.0) begin
    @assert(L > 0.0)
    @assert(m > 0.0)
    @assert(d >= 0.0)

    phi = RealScalar(start=phi0_deg*pi/180, unit="rad"    , fixed=true, nominal=1.0, info="Relative rotation angle",                     numericType=ModiaMath.XD_EXP)
    w   = RealScalar(start=0.0            , unit="rad/s"  , fixed=true, nominal=1.0, info="Relative angular velocity",     integral=phi, numericType=ModiaMath.XD_EXP)
    a   = RealScalar(                       unit="rad/s^2",                          info="Relative angular acceleration", integral=w  , numericType=ModiaMath.DER_XD_EXP)
    r   = RealSVector{2}(                   unit="m"      ,                          info="Tip position of pendulum"     ,               numericType=ModiaMath.WC)
end

function ModiaMath.computeVariables!(p::Pendulum, sim::ModiaMath.SimulationState)
    L = p.L
    m = p.m
    d = p.d
    g = p.g
    phi = p.phi.value
    w   = p.w.value

    p.a.value = (-m*g*L*sin(phi) - d*w) / (m*L^2)

    if ModiaMath.isStoreResult(sim)
        p.r.value = @SVector [L*sin(phi), -L*cos(phi)]
    end
end

simulationModel = ModiaMath.SimulationModel(Pendulum(L=0.8, m=0.5, d=0.2); stopTime=5.0, structureOfDAE=ModiaMath.DAE_ExplicitDerivatives)
result = ModiaMath.simulate!(simulationModel; log=true)

plot(result, [(:phi, :w) :a])
#plot(result, "r[2]", xAxis="r[1]", figure=2)

# ModiaMath.print_ModelVariables(simulationModel)
# println("result variables = ", result)

end


# Simply supported square plate with uniform distributed load
module ribbon_twist_examples

using FinEtools
using FinEtoolsDeforLinear
using FinEtoolsFlexStructures.FESetShellT3Module: FESetShellT3
using FinEtoolsFlexStructures.FESetShellQ4Module: FESetShellQ4
using FinEtoolsFlexStructures.FEMMShellT3FFModule
using FinEtoolsFlexStructures.RotUtilModule: initial_Rfield, update_rotation_field!
using VisualStructures: plot_nodes, plot_midline, render, plot_space_box, plot_midsurface, space_aspectratio, save_to_json

function _execute_dsg_model(formul, n = 2, LW_ratio = 2.0, visualize = true)
    E = 1e7;
    nu = 0.25;
    W = 1.0;
    L = LW_ratio * W
    thickness = 0.05
    analyt_sol = 26e-3 * LW_ratio / 10

    tolerance = thickness/1000
    fens, fes = T3block(L,W,n,1,:b);
    fens.xyz = xyz3(fens)

    mater = MatDeforElastIso(DeforModelRed3D, E, nu)

    # Report
    @info "Ribbon twist, L/W=$(LW_ratio), formulation=$(formul)"

    sfes = FESetShellT3()
    accepttodelegate(fes, sfes)
    femm = formul.make(IntegDomain(fes, TriRule(1), thickness), mater)
    associate = formul.associategeometry!
    stiffness = formul.stiffness

    # Construct the requisite fields, geometry and displacement
    # Initialize configuration variables
    geom0 = NodalField(fens.xyz)
    u0 = NodalField(zeros(size(fens.xyz,1), 3))
    Rfield0 = initial_Rfield(fens)
    dchi = NodalField(zeros(size(fens.xyz,1), 6))

    # Apply EBC's
    # Clamped end
    l1 = selectnode(fens; box = Float64[0 0 -Inf Inf -Inf Inf], inflate = tolerance)
    for i in [1,2,3,4,5,6]
        setebc!(dchi, l1, true, i)
    end

    applyebc!(dchi)
    numberdofs!(dchi);

    # Assemble the system matrix
    associategeometry!(femm, geom0)
    K = stiffness(femm, geom0, u0, Rfield0, dchi);

    # Load
    nl = selectnode(fens; box = Float64[L L 0 0 -Inf Inf], tolerance = tolerance)
    loadbdry = FESetP1(reshape(nl, 1, 1))
    lfemm = FEMMBase(IntegDomain(loadbdry, PointRule()))
    fi = ForceIntensity(FFlt[0, 0, -1, 0, 0, 0]);
    F = distribloads(lfemm, geom0, dchi, fi, 3);
    nl = selectnode(fens; box = Float64[L L W W -Inf Inf], tolerance = tolerance)
    loadbdry = FESetP1(reshape(nl, 1, 1))
    lfemm = FEMMBase(IntegDomain(loadbdry, PointRule()))
    fi = ForceIntensity(FFlt[0, 0, +1, 0, 0, 0]);
    F += distribloads(lfemm, geom0, dchi, fi, 3);

    # @infiltrate
    # Solve
    U = K\F
    scattersysvec!(dchi, U[:])
    targetu =  dchi.values[nl, 3][1]
    @info "Target: $(round(targetu, digits=8)),  $(round(targetu/analyt_sol, digits = 4)*100)%"

    # Visualization
    if !visualize
        return true
    end
    scattersysvec!(dchi, (L/4)/maximum(abs.(U)).*U)
    update_rotation_field!(Rfield0, dchi)
    plots = cat(plot_space_box([[0 0 -L/2]; [L/2 L/2 L/2]]),
        #plot_nodes(fens),
        plot_midsurface(fens, fes; x = geom0.values, u = dchi.values[:, 1:3], R = Rfield0.values);
    dims = 1)
    pl = render(plots)
    return true
end

function test_convergence()
    formul = FEMMShellT3FFModule
    LW_ratio = 2.0
    @info "Twisted Ribbon, L/W=$(LW_ratio), formulation=$(formul)"
    for n in [8, 12, 16, 24]
        _execute_dsg_model(formul, n, LW_ratio, false)
    end
    return true
end

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

@info "All examples may be executed with "
println("using .$(@__MODULE__); $(@__MODULE__).allrun()")

end # module
nothing


"""
		spikeCorr(arr1, binWidth, T)
		spikeCorr(arr1, arr2, binWidth, T)

Spike timing correlation function. If one array is given calculates autocorrelation if two arrays are given calculates cross-correlation.

# Arguments
arr1=1-dimensional array of spike timings\\
\\
binWidth=size of bin interval in seconds\\
\\
T=Total time overwhich to calulate correlation\\

# Output
corrArr=A 1-dimensional array of correlation coefficients

# Example
```julia-repl
julia> spikeCorr(arr1,0.002, 0.1)
Float64[0.089335, 0.01514, 0.0151317, 0.02228, ...]

```
```julia-repl
julia> spikeCorr(arr1, arr2, 0.002, 0.1)
Float64[0.0893333, 0.0207067, 0.00866, 0.00900833, ...]
```
"""
function spikeCorr(arr1, binWidth, T)
	corrArr = zeros(Int(T/binWidth))

	for i in 1:Int(T/binWidth)
		corrArr[i] = sum(fit(Histogram, arr1, (0:0.002:1200)).weights .*
						 fit(Histogram, arr1 .+ (i-1)*binWidth, 											(0:0.002:1200)).weights)/(1200/0.002)
	end
	return corrArr
end

function spikeCorr(arr1, arr2, binWidth, T)
	corrArr = zeros(Int(T/binWidth))

	for i in 1:Int(T/binWidth)
		corrArr[i] = sum(fit(Histogram, arr1, (0:0.002:1200)).weights .*
						 fit(Histogram, arr2 .+ (i-1)*binWidth, 											(0:0.002:1200)).weights)/(1200/0.002)
	end
	return corrArr
end


@testset "prox_l2s!" begin
#test prox_ls2!

m=randn(10)
x=randn(10)
rho=0.0
prox_l2s!(x,rho,m)
@test x==m

rho=1e10
y=deepcopy(x)
prox_l2s!(x,rho,m)
@test isapprox(x,y,rtol=1e-14)

x=Vector{Float64}(undef, 1); x[1]=2.0
m=Vector{Float64}(undef, 1); m[1]=1.0
rho=3.0
prox_l2s!(x,rho,m)
@test x[1]==(7/4)

end


module PUCP_Functions

using Distributions

function cdf_Wᵣ(Val::Real,Qₜ::Real,σ :: Real,t::Real)
    if Qₜ <= 0
        error("Qₜ es menor a 0, ello no es posible.")
    α=1/(log(σ + 1))
    Cₜ=(-log(1-t))^(1/α)
    β = Qₜ/Cₜ
    W = Weibull(α,β)
    Wᵣ = cdf(Val,W)
    return Wᵣ
end

function rand_Wᵣ(n::Int,Qₜ::Real,σ::Real,t::Real)
    if t >= 1
        error("t debe estar entre 0 y 1")
    end
    α = 1/(log(σ+1))
    Cₜ=(-log(1-t))^(1/α)
    β = Qₜ/Cₜ
    W = Weibull(α,β)
    random = rand(W,n)
    return random
end

function reg_Wᵣ(DF::DataFrame,ϕᵢ::Int,ϕᵢ₊₁::Int,t::Real)

    function likelihood(Parameters::AbstractVector,ϕᵢ::Int,ϕᵢ₊₁::Int,t::Real)
        β = Parameters[1]
        σ = Parameters[2]
        Cₜ = -log(1-t)
        X = DF ## Agregar 1 para col de diseño y quitar limites
        Qₜ = exp(X*β)

        ## Inicialización de veroslimilitud
        Sₗₖ = 0
        ## Identificar la forma matricial de la verosimilitud
    end

end

end


###### Periodic domains

abstract type PeriodicDomain{T} <: Domain{T} end

isperiodic(::PeriodicDomain) = true

canonicaldomain(::PeriodicDomain) = PeriodicSegment()

points(d::PeriodicDomain{T},n::Integer) where {T} =
    fromcanonical.(Ref(d), fourierpoints(real(eltype(T)),n))

fourierpoints(n::Integer) = fourierpoints(Float64,n)
fourierpoints(::Type{T},n::Integer) where {T<:Number} = convert(T,π)*collect(0:2:2n-2)/n

function indomain(x, d::PeriodicDomain{T}) where T
    y=tocanonical(d,x)
    if !isapprox(fromcanonical(d,y),x)
        return false
    end

    l=arclength(d)
    if isinf(l)
        abs(imag(y))<20eps(T)
    else
        abs(imag(y))/l<20eps(T)
    end
end

first(d::PeriodicDomain) = fromcanonical(d,0)
last(d::PeriodicDomain) = fromcanonical(d,2π)

rand(d::PeriodicDomain,k...) = fromcanonical.(Ref(d),2π*rand(k...)-π)
checkpoints(d::PeriodicDomain) = fromcanonical.(Ref(d),[1.223972,3.14,5.83273484])
boundary(d::PeriodicDomain) = EmptyDomain()

for op in (:rdirichlet,:ldirichlet,:lneumann,:rneumann,:ivp,:bvp)
    @eval $op(::PeriodicDomain) = error("Periodic domains do not have boundaries")
end

struct AnyPeriodicDomain <: PeriodicDomain{UnsetNumber} end
isambiguous(::AnyPeriodicDomain)=true

convert(::Type{D},::AnyDomain) where {D<:PeriodicDomain} = AnyPeriodicDomain()

include("PeriodicSegment.jl")
include("Circle.jl")
include("PeriodicLine.jl")
include("PeriodicCurve.jl")
include("Disk.jl")


function main()
    N = parse(Int, readline())
    As = parse.(Int, split(readline()))

    flg = true
    cnt = -1
    while flg
        for i in 1:length(As)
            if As[i] % 2 == 0
                As[i] = As[i] / 2
            else
                flg = false
                break
            end
        end
        cnt += 1
    end
    println(cnt)
end

main()


using Test

entry_dimesion = 2
number_of_hidden_units = 3
output_dimension = 2
OLNN = RandomWeightsNN(
    entry_dimesion,
    number_of_hidden_units,
    output_dimension
)

@testset "Dimension of one layer networks random initialization" begin
    # Weights have correct dimensions
    # Notemos que OLNN ha sido creada con la iniciacilización aleatoria,
    # Las única hipótesis que debe de cumplir es que:
    # 1. Inicialización con las dimensiones correctas
    @test size(OLNN.W1)==(number_of_hidden_units, 1+entry_dimesion)
    @test size(OLNN.W2)==(output_dimension, number_of_hidden_units)
    # 2. Por la aleatoriedad generada no todas las entradas debieran de ser iguales
    @test OLNN.W1[1,:] != OLNN.W1[2,:]
    @test OLNN.W2[1,:] != OLNN.W2[2,:]
end

@testset "One layer created from matrix" begin
    S = [1,2] #vector
    A = [3 4; 4 6] # matrix
    B = reshape([ 1 ; 1 ],1,2) # matrix 2 x 1
    h = FromMatrixNN(S, A, B)
    # Comprobación de tipo correcto
    @test typeof(h) <: AbstractOneLayerNeuralNetwork
    # Comprobación de tamaños correctos
    ### Para la matriz W_1
    (n_rows1, n_columns1) = size(h.W1)
    (n_rows2, n_columns2) = size(h.W2)
    (r_a, c_a) = size(A)
    @test n_rows1 == r_a
    @test n_columns1 == c_a+1
     ### Para la matriz W_1
     (n_rows2, n_columns2) = size(h.W2)
     (r_b, c_b) = size(B)
     @test n_rows2 == r_b
     @test n_columns2 == r_a
     @test n_columns2 == c_b
     println("Revisión ocular:")
     println(h)
end


using DiffEqJump, DiffEqBase
using Test, LinearAlgebra
using StableRNGs
rng = StableRNG(12345)

function regular_rate(out,u,p,t)
    out[1] = (0.1/1000.0)*u[1]*u[2]
    out[2] = 0.01u[2]
end

function regular_c(dc,u,p,t,mark)
    dc[1,1] = -1
    dc[2,1] = 1
    dc[2,2] = -1
    dc[3,2] = 1
end

dc = zeros(3,2)

rj = RegularJump(regular_rate,regular_c,dc;constant_c=true)
jumps = JumpSet(rj)

prob = DiscreteProblem([999.0,1.0,0.0],(0.0,250.0))
jump_prob = JumpProblem(prob,Direct(),rj; rng=rng)
sol = solve(jump_prob,SimpleTauLeaping();dt=1.0)

const _dc = zeros(3, 2)
dc[1,1] = -1
dc[2,1] = 1
dc[2,2] = -1
dc[3,2] = 1

function regular_c(du,u,p,t,counts,mark)
    mul!(du,dc,counts)
end

rj = RegularJump(regular_rate,regular_c,2)
jumps = JumpSet(rj)
prob = DiscreteProblem([999,1,0],(0.0,250.0))
jump_prob = JumpProblem(prob,Direct(),rj; rng=rng)
sol = solve(jump_prob,SimpleTauLeaping();dt=1.0)


@testset "Generic elliptic curve" begin

  @testset "Constructors" begin
    @test_throws ErrorException EllipticCurve([1])
    @test_throws ErrorException EllipticCurve([1, 2, 3])
    @test_throws ErrorException EllipticCurve([1, 2, 3, 4, 5, 6])
    @test_throws ErrorException EllipticCurve([0, 0])
    @test_throws ErrorException EllipticCurve([0, 0, 0, 0, 0])

    E = @inferred EllipticCurve([1, 2], false)
    @test typeof(E) == EllCrv{fmpq}

    E = @inferred EllipticCurve([1, 2, 3, 4, 5])
    @test typeof(E) == EllCrv{fmpq}

    # this is Cremona: 11a2, lmfdb: 11.a1
    E11_a1 = @inferred EllipticCurve([0, -1, 1, -7820, -263580], false)

    # this is Cremona: 41a1, lmfdb: 41.a1
    E43_a1 = @inferred EllipticCurve([0, 1, 1, 0, 0])

    Qx, x = PolynomialRing(FlintQQ, "x")
    K, a = NumberField(x^2 - x - 1, "a")
    OK = maximal_order(K)

    E31_1_a1 = @inferred EllipticCurve([K(1), a + 1, a, a, K(0)])
    @test typeof(E31_1_a1) == EllCrv{nf_elem}

    # lmfdb: 116.1-a1
    E116_1_a1 = @inferred EllipticCurve([K(1), K(-1), a, -a, K(0)] )
    @test typeof(E31_1_a1) == EllCrv{nf_elem}

    # short example
    Eshort = @inferred EllipticCurve([4, 0])
    @test typeof(Eshort) == EllCrv{fmpq}
  end

  # The following curves will be used in later tests
  # Creation of these was tested in previous testset
  E11_a1 = EllipticCurve([0, -1, 1, -7820, -263580], false)

  E43_a1 = EllipticCurve([0, 1, 1, 0, 0])

  Qx, x = PolynomialRing(FlintQQ, "x")
  K, a = NumberField(x^2 - x - 1, "a")
  OK = maximal_order(K)

  E31_1_a1 = EllipticCurve([K(1), a + 1, a, a, K(0)])

  E116_1_a1 =EllipticCurve([K(1), K(-1), a, -a, K(0)] )

  Eshort = EllipticCurve([4, 0])

  @testset "Field access" begin
    @test base_field(E11_a1) == FlintQQ
    @test base_field(E43_a1) == FlintQQ
    @test base_field(E31_1_a1) == K
    @test base_field(E116_1_a1) == K
    @test base_field(Eshort) == FlintQQ
  end

  @testset "Weierstraß model computation" begin
    E = EllipticCurve([1,2,3,4,5])
    EE, f, g = @inferred short_weierstrass_model(E)
    @test isshort(EE)
    @test EE.coeff == [fmpq(61, 16), fmpq(127, 32)]
    P = E([1, 2])
    @test P == g(f(P))

    R = ResidueRing(FlintZZ, 5)
    E = EllipticCurve(map(R, [1, 2, 3, 4, 5]))
    EE, f, g = @inferred short_weierstrass_model(E)
    @test isshort(EE)
    @test EE.coeff == [R(1), R(1)]
    P = rand(EE)
    @test P == f(g(P))
    # @inferred will break the tests
  end

  @testset "Point construction" begin
    P = @inferred E43_a1([FlintQQ(-1), FlintQQ(0)])
    @test typeof(P) == EllCrvPt{fmpq}
    @test parent(P) == E43_a1
    @test @inferred isfinite(P)
    @test @inferred !isinfinite(P)

    P = @inferred E43_a1([-1, 0], false)
    @test typeof(P) == EllCrvPt{fmpq}
    @test parent(P) == E43_a1
    @test @inferred isfinite(P)
    @test @inferred !isinfinite(P)

    P = @inferred E43_a1([fmpz(-1), fmpz(0)])
    @test typeof(P) == EllCrvPt{fmpq}
    @test parent(P) == E43_a1
    @test @inferred isfinite(P)
    @test @inferred !isinfinite(P)

# the error is/was from doing QQ(K(0)) - which is possible now
#    @test_throws MethodError E43_a1([gen(K), gen(K)])

    @test_throws ErrorException E43_a1([2, 2])

    P = @inferred infinity(E43_a1)
    @test @inferred !isfinite(P)
    @test @inferred isinfinite(P)

    P = @inferred E43_a1([FlintQQ(-1), FlintQQ(0)])

    P = @inferred E116_1_a1([K(0), -K(a)])
    @test typeof(P) == EllCrvPt{nf_elem}
    @test parent(P) == E116_1_a1
    @test @inferred isfinite(P)
    @test @inferred !isinfinite(P)

    P = @inferred infinity(E116_1_a1)
    @test !isfinite(P)
    @test isinfinite(P)

    @test_throws ErrorException E116_1_a1([1, 1])

    P = @inferred Eshort([2, 4], false)
    @test @inferred isfinite(P)
    @test typeof(P) == EllCrvPt{fmpq}
    @test parent(P) == Eshort
  end

  @testset "Discriminant" begin
    @test (2*a + 10)*OK == @inferred (disc(E116_1_a1)*OK)
    @test -43 == @inferred disc(E43_a1)
  end

  @testset "j-invariant" begin
    b = (fmpq(-215055, 58) * a - fmpq(65799, 29))
    @test  b == @inferred j_invariant(E116_1_a1)
    @test fmpq(-4096, 43) == @inferred j_invariant(E43_a1)
  end

  @testset "Point aritmetic" begin
    #addition
    P = @inferred E43_a1([FlintQQ(-1), FlintQQ(0)])
    O = infinity(E43_a1)

    @test P == @inferred P + O
    @test E43_a1([2, -4]) == P + P
    @test E43_a1([fmpq(-2, 9), fmpq(1, 27)]) == P + P + P

    P = @inferred E116_1_a1([K(0), -K(a)])
    @test E116_1_a1([1, -1]) == @inferred P + P
    @test E116_1_a1([K(1), -a]) == @inferred P + P + P
    @test infinity(E116_1_a1) == @inferred P + P + P + P + P
    @test P == @inferred P + infinity(E116_1_a1)

    P = @inferred Eshort([2, 4])
    O = @inferred infinity(Eshort)
    @test_throws ErrorException Eshort([-1, -1])

    @test Eshort([0, 0]) == @inferred P + P
    @test P == @inferred O + P

    P = Eshort([2, 4])
    @test Eshort([2, -4]) == @inferred -P
    P = infinity(Eshort)
    @test P == @inferred -P

    P = E43_a1([-1, 0])
    @test E43_a1([-1, -1]) == @inferred -P
    P = infinity(E43_a1)
    @test P == @inferred -P

    # inversion
    P = @inferred E116_1_a1([K(0), -K(a)])
    @test E116_1_a1([0, 0]) == @inferred -P
    P = infinity(E116_1_a1)
    @test P == @inferred -P

    # equality
    P1 = Eshort([2, 4])
    @test @inferred ==(P1, P1)
    P2 = infinity(Eshort)
    @test @inferred ==(P2, P2)
    @test @inferred !==(P2, P1)

    P1 = E43_a1([-1, 0])
    @test @inferred ==(P1, P1)
    P2 = infinity(E43_a1)
    @test @inferred ==(P2, P2)
    @test @inferred !==(P2, P1)

    P1 = E116_1_a1([K(0), -K(a)])
    @test @inferred ==(P1, P1)
    P2 = infinity(E116_1_a1)
    @test @inferred ==(P2, P2)
    @test @inferred !==(P2, P1)

    # scalar multiplication
    P1 = Eshort([2, 4])
    @test Eshort([0, 0]) == @inferred 2*P1
    @test infinity(Eshort) == @inferred 4*P1

    P1 = E43_a1([-1, 0])
    @test E43_a1([fmpq(11, 49), fmpq(-363, 343)]) == @inferred 4*P1

    P1 = E116_1_a1([K(0), -K(a)])
    @test E116_1_a1([K(0), K(0)]) == @inferred 4*P1
    @test infinity(E116_1_a1) == @inferred 5*P1
  end
end


@testset "Biconnect" begin
    gint = SimpleGraph(12)
    add_edge!(gint, 1, 2)
    add_edge!(gint, 2, 3)
    add_edge!(gint, 2, 4)
    add_edge!(gint, 3, 4)
    add_edge!(gint, 3, 5)
    add_edge!(gint, 4, 5)
    add_edge!(gint, 2, 6)
    add_edge!(gint, 1, 7)
    add_edge!(gint, 6, 7)
    add_edge!(gint, 6, 8)
    add_edge!(gint, 6, 9)
    add_edge!(gint, 8, 9)
    add_edge!(gint, 9, 10)
    add_edge!(gint, 11, 12)

    a = [[Edge(3, 5), Edge(4, 5), Edge(2, 4), Edge(3, 4), Edge(2, 3)],
         [Edge(9, 10)],
         [Edge(6, 9), Edge(8, 9), Edge(6, 8)],
         [Edge(1, 7), Edge(6, 7), Edge(2, 6), Edge(1, 2)],
         [Edge(11, 12)]]

    for g in testgraphs(gint)
        bcc = @inferred(biconnected_components(g))
        @test bcc == a
        @test typeof(bcc) === Vector{Vector{Edge{eltype(g)}}}
    end

    g = SimpleGraph(4)
    add_edge!(g, 1, 2)
    add_edge!(g, 2, 3)
    add_edge!(g, 3, 4)
    add_edge!(g, 1, 4)

    h = SimpleGraph(4)
    add_edge!(h, 1, 2)
    add_edge!(h, 2, 3)
    add_edge!(h, 3, 4)
    add_edge!(h, 1, 4)

    gint = blockdiag(g, h)
    add_edge!(gint, 4, 5)

    a = [[Edge(5, 8), Edge(7, 8), Edge(6, 7), Edge(5, 6)], [Edge(4, 5)], [Edge(1, 4), Edge(3, 4), Edge(2, 3), Edge(1, 2)]]

    for g in testgraphs(gint)
        bcc = @inferred(biconnected_components(g))
        @test bcc == a
        @test typeof(bcc) === Vector{Vector{Edge{eltype(g)}}}
    end
end