export Measure,
    Weight,
    DiscreteWeight,
    support,
    domaintype,
    codomaintype,
    isdiscrete,
    iscontinuous,
    isnormalized,
    isuniform,
    weightfun,
    weightfunction,
    points,
    weights,
    weight

"Supertype of measures."
abstract type Measure{T} end

domaintype(μ::Measure) = domaintype(typeof(μ))
domaintype(::Type{<:Measure{T}}) where {T} = T

"What is the codomain type of the measure?"
codomaintype(μ::Measure) = codomaintype(typeof(μ))
codomaintype(::Type{<:Measure{T}}) where {T} = prectype(T)

prectype(::Type{<:Measure{T}}) where {T} = prectype(T)
numtype(::Type{<:Measure{T}}) where {T} = numtype(T)

"Is the measure normalized?"
isnormalized(μ::Measure) = false

# conversion to `Measure{T}` is a means to ensure a specific domain type `T`
convert(::Type{Measure{T}}, μ::Measure{T}) where {T} = μ
convert(::Type{Measure{T}}, μ::Measure{S}) where {S,T} = similar(μ, T)

"Return the support of the measure."
support(μ::Measure{T}) where {T} = FullSpace{T}()

"""
A `Weight` is a continuous measure that is defined in terms of a weight
function: `dμ = w(x) dx`.
"""
abstract type Weight{T} <: Measure{T} end

"""
A `DiscreteWeight` is a measure defined in terms of a discrete set of points
and an associated set of weights.

The measure implements the `points` and `weights` functions.
"""
abstract type DiscreteWeight{T} <: Measure{T} end

"Is the measure discrete?"
isdiscrete(μ::Weight) = false
isdiscrete(μ::DiscreteWeight) = true
# We don't know the result for an abstract Measure,
# so we can't provide a default here

"Is the measure continuous?"
iscontinuous(μ::Weight) = true
iscontinuous(μ::DiscreteWeight) = false
# Like above, no default

support(μ::DiscreteWeight) = points(μ)

covering(μ::DiscreteWeight{T}) where {T} = FullSpace{T}()

###############################
## Continuous weight functions
###############################

# We define the functionality at the level of `Measure`, since not
# all continuous measures are of type `Weight`.
# This implementation is typically safe, as invoking these functions on a
# discrete measure is likely to result in an error (because it does not implement
# a weight function).

"Evaluate the weight function associated with the measure."
function weightfun(μ::Measure{T}, x::S) where {S,T}
    U = promote_type(S,T)
    weightfun(convert(Measure{U}, μ), convert(U, x))
end

# If the argument x is a vector: ensure the element types match
function weightfun(μ::Measure{Vector{S}}, x::AbstractVector{T}) where {S,T}
    U = promote_type(S,T)
    weightfun(convert(Weight{Vector{U}}, μ), convert(AbstractVector{U}, x))
end
# If the measure expects SVector, convert x to SVector (we can assume matching eltype)
weightfun(μ::Measure{SVector{N,S}}, x::AbstractVector{T}) where {N,S,T} =
    weightfun(μ, convert(SVector{N,S}, x))

# Accept matching types, and matching vectors
weightfun(μ::Measure{T}, x::T) where {T} = weightfun1(μ, x)
weightfun(μ::Measure{Vector{T}}, x::AbstractVector{T}) where {T} =
    weightfun1(μ, x)
weightfun(μ::Measure{SVector{N,T}}, x::SVector{N,T}) where {N,T} =
    weightfun1(μ, x)

# Check for support, then invoke unsafe_weight
weightfun1(μ::Measure, x) =
    x ∈ support(μ) ? unsafe_weightfun(μ, x) : zero(codomaintype(μ))

# These are safe defaults for any measure
weightfunction(μ::Measure) = x->weightfun(μ, x)
unsafe_weightfunction(μ::Measure) = x->unsafe_weightfun(μ, x)

####################
## Discrete weights
####################

# The main interface: return the points and weights of the discrete measure
points(μ::DiscreteWeight) = μ.points
weights(μ::DiscreteWeight) = μ.weights

length(μ::DiscreteWeight) = length(points(μ))
size(μ::DiscreteWeight) = size(points(μ))

isnormalized(μ::DiscreteWeight) = _isnormalized(μ, points(μ), weights(μ))
_isnormalized(μ, points, weights) = sum(weights) ≈ 1

"Does the discrete measure have equal weights?"
isuniform(μ::DiscreteWeight) = _isuniform(μ, points(μ), weights(μ))
_isuniform(μ, points, weights) = allequal(weights)

allequal(A) = all(y -> y == A[1], A)

# Discrete weights are equal if their points and weights are equal elementwise
Base.:(==)(μ1::DiscreteWeight, μ2::DiscreteWeight) =
    points(μ1) == points(μ2) && weights(μ1)==weights(μ2)
Base.:(≈)(μ1::DiscreteWeight, μ2::DiscreteWeight) =
    points(μ1) ≈ points(μ2) && weights(μ1) ≈ weights(μ2)

function weight(μ::DiscreteWeight, i)
    # Perform a bounds check and invoke unsafe_discrete_weight,
    # so that concrete measures may implement e.g. an on-the-fly formula for
    # the weights without bounds checking
    @boundscheck checkbounds(μ, i)
    unsafe_weight(μ, i)
end
checkbounds(μ::DiscreteWeight, i) = checkbounds(points(μ), i)

function unsafe_weight(μ::DiscreteWeight, i)
    @inbounds weights(μ)[i]
end

"A generic discrete weight that stores points and weights."
struct GenericDiscreteWeight{T,P,W} <: DiscreteWeight{T}
    points  ::  P
    weights ::  W
end

GenericDiscreteWeight(points, weights) =
    GenericDiscreteWeight{eltype(points)}(points, weights)
GenericDiscreteWeight{T}(points::P, weights::W) where {T,P,W} =
    GenericDiscreteWeight{T,P,W}(points, weights)

####################
## Mapped weight
####################

"""
Representation of a weight mapped to a new weight.

Given a weight `w(x)dx` and a map `y=m(x)`, the mapped weight is defined as
`w(m(x))/J(m(x))dx`, where `J` is the jacobian determinant of `m`.

The definition is such that we have the following equality after a change of
variables:

integral(t->f(t), domain, weight) =
    integral(t -> f(inverse(m,t))), m.(domain), MappedWeight(m, weight)
"""
struct MappedWeight{T,MAP,M} <: Weight{T}
    fmap    ::  MAP
    weight  ::  M
end

MappedWeight(map, weight::Weight{T}) where {T} = MappedWeight{T}(map, weight)
MappedWeight{T}(map, weight) where {T} =
    MappedWeight{T,typeof(map),typeof(weight)}(map, weight)

export mappedmeasure
"Map the weight using the given map."
mappedmeasure(map, weight::Weight) = MappedWeight(map, weight)
mappedmeasure(map, weight::MappedWeight) = MappedWeight(map ∘ forward_map(weight), supermeasure(weight))

forward_map(m::MappedWeight) = m.fmap
supermeasure(m::MappedWeight) = m.weight

support(m::MappedWeight) = forward_map(m).(support(supermeasure(m)))

unsafe_weightfun(m::MappedWeight, x) = unsafe_weightfun(supermeasure(m), inverse(forward_map(m), x)) / jacdet(forward_map(m), x)

####################
## Product weights
####################

"A product weight."
struct ProductWeight{T,M} <: Weight{T}
    weights ::  M
end

components(m::ProductWeight) = m.weights

function ProductWeight(weights...)
    T = Tuple{map(domaintype, weights)...}
    ProductWeight{T}(weights...)
end
function ProductWeight(weights::Vararg{Weight{<:Number},N}) where {N}
    T = promote_type(map(domaintype, weights)...)
    ProductWeight{SVector{N,T}}(weights...)
end
ProductWeight{T}(weights::Weight...) where {T} =
    ProductWeight{T,typeof(weights)}(weights)

islebesguemeasure(μ::ProductWeight) = all(map(islebesguemeasure, components(μ)))
isnormalized(m::ProductWeight) = all(map(isnormalized, components(μ)))

export productmeasure
"Construct the product of the given measures."
productmeasure(weights::Weight...) = ProductWeight(weights...)

support(m::ProductWeight{T}) where {T} = ProductDomain{T}(map(support, components(m))...)

unsafe_weightfun(m::ProductWeight, x) = mapreduce(unsafe_weightfun, *, components(m), x)

# I/O routines
Display.combinationsymbol(d::ProductWeight) = Display.Times()
Display.displaystencil(d::ProductWeight) = composite_displaystencil(d)
Base.show(io::IO, mime::MIME"text/plain", d::ProductWeight) = composite_show(io, mime, d)
Base.show(io::IO, d::ProductWeight) = composite_show_compact(io, d)


"""
    tpors(xi, eta, ra, dec)

In the tangent plane projection, given the rectangular coordinates
of a star and its spherical coordinates, determine the spherical
coordinates of the tangent point.

### Given ###

- `xi`, `eta`: rectangular coordinates of star image (Note 2)
- `a`, `b`: star's spherical coordinates (Note 3)

### Returned ###

- `status`: number of solutions:
    - 0 = no solutions returned (Note 5)
    - 1 = only the first solution is useful (Note 6)
    - 2 = both solutions are useful (Note 6)
- `a01`, `b01`: tangent point's spherical coordinates, Soln. 1
- `a02`, `b02`: tangent point's spherical coordinates, Soln. 2

### Notes ###

1. The tangent plane projection is also called the "gnomonic
   projection" and the "central projection".

2. The eta axis points due north in the adopted coordinate system.
   If the spherical coordinates are observed (RA,Dec), the tangent
   plane coordinates (xi,eta) are conventionally called the
   "standard coordinates".  If the spherical coordinates are with
   respect to a right-handed triad, (xi,eta) are also right-handed.
   The units of (xi,eta) are, effectively, radians at the tangent
   point.

3. All angular arguments are in radians.

4. The angles a01 and a02 are returned in the range 0-2pi.  The
   angles b01 and b02 are returned in the range +/-pi, but in the
   usual, non-pole-crossing, case, the range is +/-pi/2.

5. Cases where there is no solution can arise only near the poles.
   For example, it is clearly impossible for a star at the pole
   itself to have a non-zero xi value, and hence it is meaningless
   to ask where the tangent point would have to be to bring about
   this combination of xi and dec.

6. Also near the poles, cases can arise where there are two useful
   solutions.  The return value indicates whether the second of the
   two solutions returned is useful;  1 indicates only one useful
   solution, the usual case.

7. The basis of the algorithm is to solve the spherical triangle PSC,
   where P is the north celestial pole, S is the star and C is the
   tangent point.  The spherical coordinates of the tangent point are
   [a0,b0];  writing rho^2 = (xi^2+eta^2) and r^2 = (1+rho^2), side c
   is then (pi/2-b), side p is sqrt(xi^2+eta^2) and side s (to be
   found) is (pi/2-b0).  Angle C is given by sin(C) = xi/rho and
   cos(C) = eta/rho.  Angle P (to be found) is the longitude
   difference between star and tangent point (a-a0).

8. This function is a member of the following set:

   | spherical         |   vector        | solve for |
   |:------------------|:----------------|:----------|
   |  [`tpxes`](@ref)  | [`tpxev`](@ref) | xi,eta    |
   |  [`tpsts`](@ref)  | [`tpstv`](@ref) | star      |
   | *[`tpors`](@ref)* | [`tporv`](@ref) | origin    |

### Called ###

- [`anp`](@ref): normalize angle into range 0 to 2pi

### References ###

- Calabretta M.R. & Greisen, E.W., 2002, "Representations of
  celestial coordinates in FITS", Astron.Astrophys. 395, 1077

- Green, R.M., "Spherical Astronomy", Cambridge University Press,
  1987, Chapter 13.
"""
function tpors(xi, eta, ra, dec)
    az1 = Ref{Cdouble}()
    az2 = Ref{Cdouble}()
    bz1 = Ref{Cdouble}()
    bz2 = Ref{Cdouble}()
    status = ccall((:eraTpors, liberfa), Cint,
                   (Cdouble, Cdouble, Cdouble, Cdouble,
                    Ref{Cdouble}, Ref{Cdouble}, Ref{Cdouble}, Ref{Cdouble}),
                   xi, eta, ra, dec, az1, az2, bz1, bz2)
    return status, az1[], az2[], bz1[], bz2[]
end

"""
    tporv(xi, eta, v)

In the tangent plane projection, given the rectangular coordinates
of a star and its direction cosines, determine the direction
cosines of the tangent point.

### Given ###

- `xi`, `eta`: rectangular coordinates of star image (Note 2)
- `v`: star's direction cosines (Note 3)

### Returned ###

- `status`: number of solutions:
    - 0 = no solutions returned (Note 4)
    - 1 = only the first solution is useful (Note 5)
    - 2 = both solutions are useful (Note 5)
- `v01`: tangent point's direction cosines, Solution 1
- `v02`: tangent point's direction cosines, Solution 2

### Notes ###

1. The tangent plane projection is also called the "gnomonic
   projection" and the "central projection".

2. The eta axis points due north in the adopted coordinate system.
   If the direction cosines represent observed (RA,Dec., the tangent
   plane coordinates (xi,eta. are conventionally called the
   "standard coordinates".  If the direction cosines are with
   respect to a right-handed triad, (xi,eta. are also right-handed.
   The units of (xi,eta. are, effectively, radians at the tangent
   point.

3. The vector v must be of unit length or the result will be wrong.

4. Cases where there is no solution can arise only near the poles.
   For example, it is clearly impossible for a star at the pole
   itself to have a non-zero xi value, and hence it is meaningless
   to ask where the tangent point would have to be.

5. Also near the poles, cases can arise where there are two useful
   solutions.  The return value indicates whether the second of the
   two solutions returned is useful;  1 indicates only one useful
   solution, the usual case.

6. The basis of the algorithm is to solve the spherical triangle
   PSC, where P is the north celestial pole, S is the star and C is
   the tangent point.  Calling the celestial spherical coordinates
   of the star and tangent point (a,b. and (a0,b0) respectively, and
   writing rho^2 = (xi^2+eta^2. and r^2 = (1+rho^2), and
   transforming the vector v into (a,b. in the normal way, side c is
   then (pi/2-b., side p is sqrt(xi^2+eta^2) and side s (to be
   found. is (pi/2-b0), while angle C is given by sin(C) = xi/rho
   and cos(C. = eta/rho;  angle P (to be found) is (a-a0).  After
   solving the spherical triangle, the result (a0,b0. can be
   expressed in vector form as v0.

7. This function is a member of the following set:

   | spherical       | vector            | solve for |
   |:----------------|:------------------|:----------|
   | [`tpxes`](@ref) |  [`tpxev`](@ref)  | xi,eta    |
   | [`tpsts`](@ref) |  [`tpstv`](@ref)  | star      |
   | [`tpors`](@ref) | *[`tporv`](@ref)* | origin    |

### References ###

- Calabretta M.R. & Greisen, E.W., 2002, "Representations of
  celestial coordinates in FITS", Astron.Astrophys. 395, 1077

- Green, R.M., "Spherical Astronomy", Cambridge University Press,
  1987, Chapter 13.
"""
function tporv(xi, eta, v)
    vz1 = Array{Cdouble}(undef, 3)
    vz2 = Array{Cdouble}(undef, 3)
    status = ccall((:eraTporv, liberfa), Cint,
                   (Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}),
                   xi, eta, v, vz1, vz2)
    return status, vz1, vz2
end

"""
    tpsts(xi, eta, raz, decz)

In the tangent plane projection, given the star's rectangular
coordinates and the spherical coordinates of the tangent point,
solve for the spherical coordinates of the star.

### Given ###

- `xi`, `eta`: rectangular coordinates of star image (Note 2)
- `a0`, `b0`: tangent point's spherical coordinates

### Returned ###

- `a`, `b`: star's spherical coordinates

### Notes ###

1. The tangent plane projection is also called the "gnomonic
   projection" and the "central projection".

2. The eta axis points due north in the adopted coordinate system.
   If the spherical coordinates are observed (RA,Dec), the tangent
   plane coordinates (xi,eta) are conventionally called the
   "standard coordinates".  If the spherical coordinates are with
   respect to a right-handed triad, (xi,eta) are also right-handed.
   The units of (xi,eta) are, effectively, radians at the tangent
   point.

3. All angular arguments are in radians.

4. This function is a member of the following set:

   |   spherical       | vector           | solve for |
   |:------------------|:-----------------|:----------|
   |  [`tpxes`](@ref)  | [`tpxev`](@ref)  | xi,eta    |
   | *[`tpsts`](@ref)* | [`tpstv`](@ref)  | star      |
   |  [`tpors`](@ref)  | [`tporv`](@ref)  | origin    |

### Called ###

- [`anp`](@ref) normalize angle into range 0 to 2pi

### References ###

- Calabretta M.R. & Greisen, E.W., 2002, "Representations of
  celestial coordinates in FITS", Astron.Astrophys. 395, 1077

- Green, R.M., "Spherical Astronomy", Cambridge University Press,
  1987, Chapter 13.
"""
function tpsts(xi, eta, raz, decz)
    ra = Ref{Cdouble}()
    dec = Ref{Cdouble}()
    ccall((:eraTpsts, liberfa), Cvoid,
          (Cdouble, Cdouble, Cdouble, Cdouble, Ref{Cdouble}, Ref{Cdouble}),
          xi, eta, raz, decz, ra, dec)
    return ra[], dec[]
end

"""
    tpstv(xi, eta, vz)

In the tangent plane projection, given the star's rectangular
coordinates and the direction cosines of the tangent point, solve
for the direction cosines of the star.

### Given ###

- `xi`, `eta`: rectangular coordinates of star image (Note 2)
- `v0`: tangent point's direction cosines

### Returned ###

- `v`: star's direction cosines

### Notes ###

1. The tangent plane projection is also called the "gnomonic
   projection" and the "central projection".

2. The eta axis points due north in the adopted coordinate system.
   If the direction cosines represent observed (RA,Dec), the tangent
   plane coordinates (xi,eta) are conventionally called the
   "standard coordinates".  If the direction cosines are with
   respect to a right-handed triad, (xi,eta) are also right-handed.
   The units of (xi,eta) are, effectively, radians at the tangent
   point.

3. The method used is to complete the star vector in the (xi,eta)
   based triad and normalize it, then rotate the triad to put the
   tangent point at the pole with the x-axis aligned to zero
   longitude.  Writing (a0,b0) for the celestial spherical
   coordinates of the tangent point, the sequence of rotations is
   (b-pi/2) around the x-axis followed by (-a-pi/2) around the
   z-axis.

4. If vector v0 is not of unit length, the returned vector v will
   be wrong.

5. If vector v0 points at a pole, the returned vector v will be
   based on the arbitrary assumption that the longitude coordinate
   of the tangent point is zero.

6. This function is a member of the following set:

   | spherical       | vector            | solve for |
   |:----------------|:------------------|:----------|
   | [`tpxes`](@ref) |  [`tpxev`](@ref)  | xi,eta    |
   | [`tpsts`](@ref) | *[`tpstv`](@ref)* | star      |
   | [`tpors`](@ref) |  [`tporv`](@ref)  | origin    |

### References ###

- Calabretta M.R. & Greisen, E.W., 2002, "Representations of
  celestial coordinates in FITS", Astron.Astrophys. 395, 1077

- Green, R.M., "Spherical Astronomy", Cambridge University Press,
  1987, Chapter 13.
"""
function tpstv(xi, eta, vz)
    v = Array{Cdouble}(undef, 3)
    ccall((:eraTpstv, liberfa), Cvoid,
          (Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}),
          xi, eta, vz, v)
    return v
end

"""
    tpxes(ra, dec, raz, decz)

In the tangent plane projection, given celestial spherical
coordinates for a star and the tangent point, solve for the star's
rectangular coordinates in the tangent plane.

### Given ###

- `a`, `b`: star's spherical coordinates
- `a0`, `b0`: tangent point's spherical coordinates

### Returned ###

- `status`:
    - 0 = OK
    - 1 = star too far from axis
    - 2 = antistar on tangent plane
    - 3 = antistar too far from axis
- `xi`, `eta`: rectangular coordinates of star image (Note 2)

### Notes ###

1. The tangent plane projection is also called the "gnomonic
   projection" and the "central projection".

2. The eta axis points due north in the adopted coordinate system.
   If the spherical coordinates are observed (RA,Dec), the tangent
   plane coordinates (xi,eta) are conventionally called the
   "standard coordinates".  For right-handed spherical coordinates,
   (xi,eta) are also right-handed.  The units of (xi,eta) are,
   effectively, radians at the tangent point.

3. All angular arguments are in radians.

4. This function is a member of the following set:

   | spherical         | vector          | solve for |
   |:------------------|:----------------|:----------|
   | *[`tpxes`](@ref)* | [`tpxev`](@ref) | xi,eta    |
   |  [`tpsts`](@ref)  | [`tpstv`](@ref) | star      |
   |  [`tpors`](@ref)  | [`tporv`](@ref) | origin    |

### References ###

- Calabretta M.R. & Greisen, E.W., 2002, "Representations of
  celestial coordinates in FITS", Astron.Astrophys. 395, 1077

- Green, R.M., "Spherical Astronomy", Cambridge University Press,
  1987, Chapter 13.
"""
function tpxes(ra, dec, raz, decz)
    xi = Ref{Cdouble}()
    eta = Ref{Cdouble}()
    status = ccall((:eraTpxes, liberfa), Cint,
                   (Cdouble, Cdouble, Cdouble, Cdouble, Ref{Cdouble}, Ref{Cdouble}),
                   ra, dec, raz, decz, xi, eta)
    return status, xi[], eta[]
end

"""
    tpxev(v, vz)

In the tangent plane projection, given celestial direction cosines
for a star and the tangent point, solve for the star's rectangular
coordinates in the tangent plane.

### Given ###

- `v`: direction cosines of star (Note 4)
- `v0`: direction cosines of tangent point (Note 4)

### Returned ###

- `status`:
    - 0 = OK
    - 1 = star too far from axis
    - 2 = antistar on tangent plane
    - 3 = antistar too far from axis
- `xi`, `eta`: tangent plane coordinates of star

### Notes ###

1. The tangent plane projection is also called the "gnomonic
   projection" and the "central projection".

2. The eta axis points due north in the adopted coordinate system.
   If the direction cosines represent observed (RA,Dec), the tangent
   plane coordinates (xi,eta) are conventionally called the
   "standard coordinates".  If the direction cosines are with
   respect to a right-handed triad, (xi,eta) are also right-handed.
   The units of (xi,eta) are, effectively, radians at the tangent
   point.

3. The method used is to extend the star vector to the tangent
   plane and then rotate the triad so that (x,y) becomes (xi,eta).
   Writing (a,b) for the celestial spherical coordinates of the
   star, the sequence of rotations is (a+pi/2) around the z-axis
   followed by (pi/2-b) around the x-axis.

4. If vector v0 is not of unit length, or if vector v is of zero
   length, the results will be wrong.

5. If v0 points at a pole, the returned (xi,eta) will be based on
   the arbitrary assumption that the longitude coordinate of the
   tangent point is zero.

6. This function is a member of the following set:

   | spherical       | vector            | solve for |
   |:----------------|:------------------|:----------|
   | [`tpxes`](@ref) | *[`tpxev`](@ref)* | xi,eta    |
   | [`tpsts`](@ref) |  [`tpstv`](@ref)  | star      |
   | [`tpors`](@ref) |  [`tporv`](@ref)  | origin    |

### References ###

- Calabretta M.R. & Greisen, E.W., 2002, "Representations of
  celestial coordinates in FITS", Astron.Astrophys. 395, 1077

- Green, R.M., "Spherical Astronomy", Cambridge University Press,
  1987, Chapter 13.
"""
function tpxev(v, vz)
    xi = Ref{Cdouble}()
    eta = Ref{Cdouble}()
    status = ccall((:eraTpxev, liberfa), Cint,
                   (Ptr{Cdouble}, Ptr{Cdouble}, Ref{Cdouble}, Ref{Cdouble}),
                   v, vz, xi, eta)
    return status, xi[], eta[]
end

"""
    tr(r)

Transpose an r-matrix.

!!! warning "Deprecated"
    Use `r'` instead.

### Given ###

- `r`: R-matrix

### Returned ###

- `rt`: Transpose

### Note ###

   It is permissible for r and rt to be the same array.

### Called ###

- [`cr`](@ref): copy r-matrix

"""
tr

function _tr(r)
    @checkdims 3 3 r
    rt = zeros(Cdouble, 3, 3)
    ccall((:eraTr, liberfa), Cvoid,
          (Ptr{Cdouble}, Ptr{Cdouble}),
          permutedims(r), rt)
    return permutedims(rt)
end

@deprecate tr(r) r'

"""
    trxpv(r, pv)

Multiply a pv-vector by the transpose of an r-matrix.

!!! warning "Deprecated"
    Use `[r' * pv[1], r' * pv[2]]` instead.

### Given ###

- `r`: R-matrix
- `pv`: Pv-vector

### Returned ###

- `trpv`: R * pv

### Note ###

   It is permissible for pv and trpv to be the same array.

### Called ###

- [`tr`](@ref): transpose r-matrix
- [`rxpv`](@ref): product of r-matrix and pv-vector

"""
trxpv

function _trxpv(r, pv)
    @checkdims 3 3 r
    _pv = array_to_cmatrix(pv; n=3)
    rp = zeros(Cdouble, 3, 2)
    ccall((:eraTrxpv, liberfa), Cvoid,
            (Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}),
            permutedims(r), _pv, rp)
    return cmatrix_to_array(rp)
end

@deprecate trxpv(r, pv) [r' * pv[1], r' * pv[2]]

"""
    trxp(r, p)

Multiply a p-vector by the transpose of an r-matrix.

!!! warning "Deprecated"
    Use `r' * p` instead.

### Given ###

- `r`: R-matrix
- `p`: P-vector

### Returned ###

- `trp`: R * p

### Note ###

   It is permissible for p and trp to be the same array.

### Called ###

- [`tr`](@ref): transpose r-matrix
- [`rxp`](@ref): product of r-matrix and p-vector

"""
trxp

function _trxp(r, p)
    @checkdims 3 3 r
    @checkdims 3 p
    rp = zeros(Cdouble, 3)
    ccall((:eraTrxp, liberfa), Cvoid,
            (Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}),
            permutedims(r), p, rp)
    return rp
end

@deprecate trxp(r, p) r' * p

"""
    taiut1(tai1, tai2, dta)

Time scale transformation:  International Atomic Time, TAI, to
Universal Time, UT1.

### Given ###

- `tai1`, `tai2`: TAI as a 2-part Julian Date
- `dta`: UT1-TAI in seconds

### Returned ###

- `ut11`, `ut12`: UT1 as a 2-part Julian Date

### Notes ###

1. tai1+tai2 is Julian Date, apportioned in any convenient way
   between the two arguments, for example where tai1 is the Julian
   Day Number and tai2 is the fraction of a day.  The returned
   UT11,UT12 follow suit.

2. The argument dta, i.e. UT1-TAI, is an observed quantity, and is
   available from IERS tabulations.

### Reference ###

- Explanatory Supplement to the Astronomical Almanac,
    P. Kenneth Seidelmann (ed), University Science Books (1992)

"""
taiut1

"""
    tdbtt(tdb1, tdb2, dtr)

Time scale transformation:  Barycentric Dynamical Time, TDB, to
Terrestrial Time, TT.

### Given ###

- `tdb1`, `tdb2`: TDB as a 2-part Julian Date
- `dtr`: TDB-TT in seconds

### Returned ###

- `tt1`, `tt2`: TT as a 2-part Julian Date

### Notes ###

1. tdb1+tdb2 is Julian Date, apportioned in any convenient way
   between the two arguments, for example where tdb1 is the Julian
   Day Number and tdb2 is the fraction of a day.  The returned
   tt1,tt2 follow suit.

2. The argument dtr represents the quasi-periodic component of the
   GR transformation between TT and TCB.  It is dependent upon the
   adopted solar-system ephemeris, and can be obtained by numerical
   integration, by interrogating a precomputed time ephemeris or by
   evaluating a model such as that implemented in the ERFA function
   [`dtdb`](@ref).   The quantity is dominated by an annual term of 1.7 ms
   amplitude.

3. TDB is essentially the same as Teph, the time argument for the
   JPL solar system ephemerides.

### References ###

- McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
    IERS Technical Note No. 32, BKG (2004)

- IAU 2006 Resolution 3

"""
tdbtt

"""
    tttdb(tt1, tt2, dtr)

Time scale transformation:  Terrestrial Time, TT, to Barycentric
Dynamical Time, TDB.

### Given ###

- `tt1`, `tt2`: TT as a 2-part Julian Date
- `dtr`: TDB-TT in seconds

### Returned ###

- `tdb1`, `tdb2`: TDB as a 2-part Julian Date

### Notes ###

1. tt1+tt2 is Julian Date, apportioned in any convenient way between
   the two arguments, for example where tt1 is the Julian Day Number
   and tt2 is the fraction of a day.  The returned tdb1,tdb2 follow
   suit.

2. The argument dtr represents the quasi-periodic component of the
   GR transformation between TT and TCB.  It is dependent upon the
   adopted solar-system ephemeris, and can be obtained by numerical
   integration, by interrogating a precomputed time ephemeris or by
   evaluating a model such as that implemented in the ERFA function
   [`dtdb`](@ref).   The quantity is dominated by an annual term of 1.7 ms
   amplitude.

3. TDB is essentially the same as Teph, the time argument for the JPL
   solar system ephemerides.

### References ###

- McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
    IERS Technical Note No. 32, BKG (2004)

- IAU 2006 Resolution 3

"""
tttdb

"""
    ttut1(tt1, tt2, dt)

Time scale transformation:  Terrestrial Time, TT, to Universal Time,
UT1.

### Given ###

- `tt1`, `tt2`: TT as a 2-part Julian Date
- `dt`: TT-UT1 in seconds

### Returned ###

- `ut11`, `ut12`: UT1 as a 2-part Julian Date

### Notes ###

1. tt1+tt2 is Julian Date, apportioned in any convenient way between
   the two arguments, for example where tt1 is the Julian Day Number
   and tt2 is the fraction of a day.  The returned ut11,ut12 follow
   suit.

2. The argument dt is classical Delta T.

### Reference ###

- Explanatory Supplement to the Astronomical Almanac,
    P. Kenneth Seidelmann (ed), University Science Books (1992)

"""
ttut1

for name in ("taiut1",
             "tdbtt",
             "tttdb",
             "ttut1")
    f = Symbol(name)
    fc = "era" * uppercasefirst(name)
    @eval begin
        function ($f)(a, b, c)
            r1 = Ref{Cdouble}()
            r2 = Ref{Cdouble}()
            i = ccall(($fc, liberfa), Cint,
                      (Cdouble, Cdouble, Cdouble, Ref{Cdouble}, Ref{Cdouble}),
                      a, b, c, r1, r2)
            @assert i == 0
            r1[], r2[]
        end
    end
end

"""
    taitt(tai1, tai2)

Time scale transformation:  International Atomic Time, TAI, to
Terrestrial Time, TT.

### Given ###

- `tai1`, `tai2`: TAI as a 2-part Julian Date

### Returned ###

- `tt1`, `tt2`: TT as a 2-part Julian Date

### Note ###

   tai1+tai2 is Julian Date, apportioned in any convenient way
   between the two arguments, for example where tai1 is the Julian
   Day Number and tai2 is the fraction of a day.  The returned
   tt1,tt2 follow suit.

### References ###

- McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
    IERS Technical Note No. 32, BKG (2004)

- Explanatory Supplement to the Astronomical Almanac,
    P. Kenneth Seidelmann (ed), University Science Books (1992)

"""
taitt

"""
    taiutc(tai1, tai2)

Time scale transformation:  International Atomic Time, TAI, to
Coordinated Universal Time, UTC.

### Given ###

- `tai1`, `tai2`: TAI as a 2-part Julian Date (Note 1)

### Returned ###

- `utc1`, `utc2`: UTC as a 2-part quasi Julian Date (Notes 1-3)

### Notes ###

1. tai1+tai2 is Julian Date, apportioned in any convenient way
   between the two arguments, for example where tai1 is the Julian
   Day Number and tai2 is the fraction of a day.  The returned utc1
   and utc2 form an analogous pair, except that a special convention
   is used, to deal with the problem of leap seconds - see the next
   note.

2. JD cannot unambiguously represent UTC during a leap second unless
   special measures are taken.  The convention in the present
   function is that the JD day represents UTC days whether the
   length is 86399, 86400 or 86401 SI seconds.  In the 1960-1972 era
   there were smaller jumps (in either direction) each time the
   linear UTC(TAI) expression was changed, and these "mini-leaps"
   are also included in the ERFA convention.

3. The function [`d2dtf`](@ref) can be used to transform the UTC quasi-JD
   into calendar date and clock time, including UTC leap second
   handling.

4. The warning status "dubious year" flags UTCs that predate the
   introduction of the time scale or that are too far in the future
   to be trusted.  See [`dat`](@ref) for further details.

### Called ###

- [`utctai`](@ref): UTC to TAI

### References ###

- McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
    IERS Technical Note No. 32, BKG (2004)

- Explanatory Supplement to the Astronomical Almanac,
    P. Kenneth Seidelmann (ed), University Science Books (1992)

"""
taiutc

"""
    tcbtdb(tcb1, tcb2)

Time scale transformation:  Barycentric Coordinate Time, TCB, to
Barycentric Dynamical Time, TDB.

### Given ###

- `tcb1`, `tcb2`: TCB as a 2-part Julian Date

### Returned ###

- `tdb1`, `tdb2`: TDB as a 2-part Julian Date

### Notes ###

1. tcb1+tcb2 is Julian Date, apportioned in any convenient way
   between the two arguments, for example where tcb1 is the Julian
   Day Number and tcb2 is the fraction of a day.  The returned
   tdb1,tdb2 follow suit.

2. The 2006 IAU General Assembly introduced a conventional linear
   transformation between TDB and TCB.  This transformation
   compensates for the drift between TCB and terrestrial time TT,
   and keeps TDB approximately centered on TT.  Because the
   relationship between TT and TCB depends on the adopted solar
   system ephemeris, the degree of alignment between TDB and TT over
   long intervals will vary according to which ephemeris is used.
   Former definitions of TDB attempted to avoid this problem by
   stipulating that TDB and TT should differ only by periodic
   effects.  This is a good description of the nature of the
   relationship but eluded precise mathematical formulation.  The
   conventional linear relationship adopted in 2006 sidestepped
   these difficulties whilst delivering a TDB that in practice was
   consistent with values before that date.

3. TDB is essentially the same as Teph, the time argument for the
   JPL solar system ephemerides.

### Reference ###

- IAU 2006 Resolution B3

"""
tcbtdb

"""
    tcgtt(tcg1, tcg2)

Time scale transformation:  Geocentric Coordinate Time, TCG, to
Terrestrial Time, TT.

### Given ###

- `tcg1`, `tcg2`: TCG as a 2-part Julian Date

### Returned ###

- `tt1`, `tt2`: TT as a 2-part Julian Date

### Note ###

   tcg1+tcg2 is Julian Date, apportioned in any convenient way
   between the two arguments, for example where tcg1 is the Julian
   Day Number and tcg22 is the fraction of a day.  The returned
   tt1,tt2 follow suit.

### References ###

- McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),.
    IERS Technical Note No. 32, BKG (2004)

- IAU 2000 Resolution B1.9

"""
tcgtt

"""
    tdbtcb(tdb1, tdb2)

Time scale transformation:  Barycentric Dynamical Time, TDB, to
Barycentric Coordinate Time, TCB.

### Given ###

- `tdb1`, `tdb2`: TDB as a 2-part Julian Date

### Returned ###

- `tcb1`, `tcb2`: TCB as a 2-part Julian Date

### Notes ###

1. tdb1+tdb2 is Julian Date, apportioned in any convenient way
   between the two arguments, for example where tdb1 is the Julian
   Day Number and tdb2 is the fraction of a day.  The returned
   tcb1,tcb2 follow suit.

2. The 2006 IAU General Assembly introduced a conventional linear
   transformation between TDB and TCB.  This transformation
   compensates for the drift between TCB and terrestrial time TT,
   and keeps TDB approximately centered on TT.  Because the
   relationship between TT and TCB depends on the adopted solar
   system ephemeris, the degree of alignment between TDB and TT over
   long intervals will vary according to which ephemeris is used.
   Former definitions of TDB attempted to avoid this problem by
   stipulating that TDB and TT should differ only by periodic
   effects.  This is a good description of the nature of the
   relationship but eluded precise mathematical formulation.  The
   conventional linear relationship adopted in 2006 sidestepped
   these difficulties whilst delivering a TDB that in practice was
   consistent with values before that date.

3. TDB is essentially the same as Teph, the time argument for the
   JPL solar system ephemerides.

### Reference ###

- IAU 2006 Resolution B3

"""
tdbtcb

"""
    tttai(tt1, tt2)

Time scale transformation:  Terrestrial Time, TT, to International
Atomic Time, TAI.

### Given ###

- `tt1`, `tt2`: TT as a 2-part Julian Date

### Returned ###

- `tai1`, `tai2`: TAI as a 2-part Julian Date

### Note ###

   tt1+tt2 is Julian Date, apportioned in any convenient way between
   the two arguments, for example where tt1 is the Julian Day Number
   and tt2 is the fraction of a day.  The returned tai1,tai2 follow
   suit.

### References ###

- McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
    IERS Technical Note No. 32, BKG (2004)

- Explanatory Supplement to the Astronomical Almanac,
    P. Kenneth Seidelmann (ed), University Science Books (1992)

"""
tttai

"""
    tttcg(tt1, tt2)

Time scale transformation:  Terrestrial Time, TT, to Geocentric
Coordinate Time, TCG.

### Given ###

- `tt1`, `tt2`: TT as a 2-part Julian Date

### Returned ###

- `tcg1`, `tcg2`: TCG as a 2-part Julian Date

### Note ###

   tt1+tt2 is Julian Date, apportioned in any convenient way between
   the two arguments, for example where tt1 is the Julian Day Number
   and tt2 is the fraction of a day.  The returned tcg1,tcg2 follow
   suit.

### References ###

- McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
    IERS Technical Note No. 32, BKG (2004)

- IAU 2000 Resolution B1.9

"""
tttcg

for name in ("taitt",
             "taiutc",
             "tcbtdb",
             "tcgtt",
             "tdbtcb",
             "tttai",
             "tttcg")
    f = Symbol(name)
    fc = "era" * uppercasefirst(name)
    @eval begin
        function ($f)(a, b)
            r1 = Ref{Cdouble}()
            r2 = Ref{Cdouble}()
            i = ccall(($fc, liberfa), Cint,
                      (Cdouble, Cdouble, Ref{Cdouble}, Ref{Cdouble}),
                      a, b, r1, r2)
            @assert i == 0
            return r1[], r2[]
        end
    end
end

"""
    tf2a(s, ihour, imin, sec)

Convert hours, minutes, seconds to radians.

### Given ###

- `s`: Sign:  '-' = negative, otherwise positive
- `ihour`: Hours
- `imin`: Minutes
- `sec`: Seconds

### Returned ###

- `rad`: Angle in radians

### Notes ###

1.  The result is computed even if any of the range checks fail.

2.  Negative ihour, imin and/or sec produce a warning status, but
    the absolute value is used in the conversion.

3.  If there are multiple errors, the status value reflects only the
    first, the smallest taking precedence.

"""
tf2a

"""
    tf2d(s, ihour, imin, sec)

Convert hours, minutes, seconds to days.

### Given ###

- `s`: Sign:  '-' = negative, otherwise positive
- `ihour`: Hours
- `imin`: Minutes
- `sec`: Seconds

### Returned ###

- `days`: Interval in days

### Notes ###

1.  The result is computed even if any of the range checks fail.

2.  Negative ihour, imin and/or sec produce a warning status, but
    the absolute value is used in the conversion.

3.  If there are multiple errors, the status value reflects only the
    first, the smallest taking precedence.

"""
tf2d

for name in ("tf2a",
             "tf2d")
    f = Symbol(name)
    fc = "era" * uppercasefirst(name)
    @eval begin
        function ($f)(s, ideg, iamin, asec)
            rad = Ref{Cdouble}()
            i = ccall(($fc, liberfa), Cint,
                      (Cchar, Cint, Cint, Cdouble, Ref{Cdouble}),
                       s, ideg, iamin, asec, rad)
            if i == 1
                throw(ERFAException("ihour outside range 0-23"))
            elseif i == 2
                throw(ERFAException("imin outside range 0-59"))
            elseif i == 3
                throw(ERFAException("sec outside range 0-59.999..."))
            end
            return rad[]
        end
    end
end


using FastAnonymous
using Base.Test

immutable RGB
    r::Float64
    g::Float64
    b::Float64
end

offset = 1.2
f = @anon x->sqrt(x+offset)
@test_approx_eq f(7.8) 3.0

to_gray(x::Real) = x
to_gray(x::RGB) = 0.299*x.r + 0.587*x.g + 0.114*x.b
gray = @anon x->to_gray(x)
@test gray(0.8) == 0.8
@test gray(RGB(1,0,0)) == 0.299

h = @anon x->sin(x^2)
@test h(2.1) == sin((2.1)^2)

# Check that we are well optimized
# (allocation == 0 means no boxing)
x = rand(10)
y = similar(x)
map!(f, y, x)
@test (@allocated map!(f, y, x)) == 0

c = reinterpret(RGB, rand(30))
map!(gray, y, c)
@test (@allocated map!(gray, y, c)) == 0
map!(gray, y, x)
@test (@allocated map!(gray, y, x)) == 0

# Does it work in a function?
function testwrap(dest, A, offset)
    ff = @anon x->(x+offset)^2
    map!(ff, dest, A)  # compile map! with ff
    @allocated map!(ff, dest, A)
end

@test testwrap(y, x, 1.2) == 0
@test y[1] == (x[1]+1.2)^2
@test testwrap(y, x, 3.4) == 0
@test y[1] == (x[1]+3.4)^2

# Does it work across modules?
module TestMod
mysquare(x) = x^2
end

f = @anon x->TestMod.mysquare(x)
@test f(3) == 9

module TestMod2
export mycube
mycube(x) = x^3
end

using TestMod2
f = @anon x->mycube(x)
@test f(-2) == -8


using LinearAlgebra

# Define function and gradient
f(x) = x[1] + exp(x[2]-x[1]);
grad_f(z) = [1-exp(z[2]-z[1]), exp(z[2]-z[1])];
z = [1, 2];
f_hat(x) = f(z) + grad_f(z)'*(x-z);

# Compare f and f_hat for some specific x’s
f([1,2]),  f_hat([1,2])

f([0.96,1.98]),  f_hat([0.96,1.98])

f([0.96,1.98]),  f_hat([0.96,1.98])


using SparseArrays
using LinearAlgebra
using Plots

function vekit(A, y, e)
    l=[0];
    while true
        # Berechnung gemäß Algorithmus (Arbeitsblatt)
        y_h = A*y
        l_h = dot(y_h,y)
        # neue Nährung Eigenwert zu Liste Nährungen Eigenwert hinzufügen
        l=[l;l_h]
        y=y_h/norm(y_h)
        # Abbruchkriterium
        if (norm(A*y-l_h*y)<e)
            # Liste Nährungen Eigenwert und Eigenvektor zurückgeben
            return l,y;
        end
    end
end


@testset "PARAFAC2" begin

Random.seed!(1)
r = 2
A = randn(r, 10)
T = [(randn(4 * (i + 1), r) * A) for i in 1:5]
@time factors = parafac2(T, 2, compute_error=true)
@test size.(factors.factors) == ntuple(i -> (size(T[i], 1), r), length(T))
@test size(factors.B) == (r, length(T))
@test size(factors.A) == (size(T[1], 2), r)
@test rel_residue(factors) < 0.05

end


abstract type AbstractKernel end

radial(k, θ::Tuple, τ) = radial(k, θ..., τ)

struct Gaussian <: AbstractKernel end

radial(::Gaussian, α::Number, τ) = α * exp(-τ / 2)

struct Spherical <: AbstractKernel end

function radial(::Spherical, α::Number, r::Number, τ)
    τ′ = τ / r
    return τ′ > 1 ? zero(τ) : α * (1 - (3 / 2) * τ′ + (1 / 2) * τ′ ^ 3)
end


abstract type PR78AlphaModel <: AlphaModel end

struct PR78AlphaParam <: EoSParam
    acentricfactor::SingleParam{Float64}
end

@newmodelsimple PR78Alpha PR78AlphaModel PR78AlphaParam

export PR78Alpha
function PR78Alpha(components::Vector{String}; userlocations::Vector{String}=String[], verbose::Bool=false)
    params = getparams(components, ["properties/critical.csv"]; userlocations=userlocations, verbose=verbose)
    acentricfactor = SingleParam(params["w"],"acentric factor")
    packagedparams = PR78AlphaParam(acentricfactor)
    model = PR78Alpha(packagedparams, verbose=verbose)
    return model
end

function α_function(model::CubicModel,V,T,z,alpha_model::PR78AlphaModel)
    Tc = model.params.Tc.values
    ω  = alpha_model.params.acentricfactor.values
    α = zeros(typeof(T),length(Tc))
    for i in @comps
        ωi = ω[i]
        m = ifelse(ωi<=0.491,
            evalpoly(ωi,(0.37464,1.54226,-0.26992)),
            evalpoly(ωi,(0.379642,1.487503,-0.164423,-0.016666)))
        Tr = T/Tc[i]
        α[i] = (1+m*(1-√(Tr)))^2
    end

    return α

end


function ikegami(m_prefs::Vector{Vector{Int}}, f_prefs::Vector{Vector{Int}})
    m = length(m_prefs)
    n = length(f_prefs)

    m_prefs_2d = Matrix{Int64}(n+1, m)
    n_prefs_2d = Matrix{Int64}(m+1, n)

    for (t,i) in enumerate(m_prefs)
        if length(i) != n
            new_element = Array(Int, n+1-length(i))
            new_element[1] = 0
            new_element[2:end] = [j for j in range(1,n) if !(j in i)]
            m_prefs[t] = append!(i, new_element)
        else
            m_prefs[t] = push!(i, 0)
        end
        m_prefs_2d[:, t] = m_prefs[t]
    end

    for (t,i) in enumerate(n_prefs)
        if length(i) != m
            new_element = Array(Int, m+1-length(i))
            new_element[1] = 0
            new_element[2:end] = [j for j in range(1,m) if !(j in i)]
            n_prefs[t] = append!(i, new_element)
        else
            n_prefs[t] = push!(i, 0)
        end
        n_prefs_2d[:, t] = n_prefs[t]
    end

    return ikegamida(m_prefs_2d, n_prefs_2d)
end

function ikegamida(m_prefs, f_prefs)

    m_num, f_num = size(m_prefs)[2], size(f_prefs)[2]
    m_pool, f_pool = collect(1:m_num), collect(1:f_num)
    match_or_unmatch = Array(Bool, m_num)
    for i in 1:m_num
        match_or_unmatch[i] = true
    end
    m_matched, f_matched = ones(Int64, m_num) + f_num, ones(Int64, f_num) + m_num

    m_rank = Array(Int64, (f_num + 1, m_num))
    f_rank = Array(Int64, (m_num + 1, f_num))

    sorting = Array(Int64, (f_num + 1, 2))
    sorting[:, 1] = collect(1:(f_num + 1))

    for i in 1:m_num
        sorting[:, 2] = m_prefs[:, i]
        m_rank[:,i] = sortrows(sorting, by = x->(x[2]))[:,1]
        sorting = sortrows(sorting, by = x->(x[1]))
    end

    sorting2 = Array(Int64, (m_num + 1, 2))
    sorting2[:, 1] = collect(1:(m_num + 1))

    for i in 1:f_num
        sorting2[:, 2] = f_prefs[:, i]
        f_rank[:,i] = sortrows(sorting2, by = x->(x[2]))[:,1]
        sorting2 = sortrows(sorting2, by = x->(x[1]))
    end

    while sum(match_or_unmatch) > 0
        for i in 1:m_num
            if match_or_unmatch[i] == true

                for j in m_prefs[:, i]

                    if j == 0
                        m_matched[i] = 0
                        match_or_unmatch[i] = false
                        break

                    else
                        no_marriage = f_rank[1, j]

                        if f_rank[i+1, j] < no_marriage

                            if f_matched[j] == m_num + 1
                                f_matched[j] = i
                                m_matched[i] = j
                                match_or_unmatch[i] = false
                                break

                            else

                                if f_rank[f_matched[j]+1, j] > f_rank[i+1, j]
                                    match_or_unmatch[f_matched[j]] = true
                                    f_matched[j] = i
                                    m_matched[i] = j
                                    match_or_unmatch[i] = false
                                    break
                                end
                            end
                        end
                    end
                end
            end
        end
    end

    for (i,t) in enumerate(f_matched)
        if t == m_num + 1
            f_matched[i] = 0
        end
    end

     for (i,t) in enumerate(m_matched)
        if t == f_num + 1
            m_matched[i] = 0
        end
    end

    return m_matched, f_matched
end


module Findpeaks

export findpeaks

"""
`findpeaks(y::Array{T},
x::Array{S}=collect(1:length(y))
;minHeight::T=minimum(y), minProm::T=minimum(y),
minDist::S=0, threshold::T=0 ) where {T<:Real,S}`\n
Returns indices of local maxima (sorted from highest peaks to lowest)
in 1D array of real numbers. Similar to MATLAB's findpeaks().\n
*Arguments*:\n
`y` -- data\n
*Optional*:\n
`x` -- x-data\n
*Keyword*:\n
`minHeight` -- minimal peak height\n
`minProm` -- minimal peak prominence\n
`minDist` -- minimal peak distance (keeping highest peaks)\n
`threshold` -- minimal difference (absolute value) between
 peak and neighboring points\n
"""
function findpeaks(y::Array{T},
    x::Array{S}=collect(1:length(y))
    ;minHeight::T=minimum(y), minProm::T=minimum(y),
    minDist::S=zero(x[1]), threshold::T=zero(y[1]) ) where {T<:Real,S}

    peaks = Array{Int64,1}()
    dy = diff(y)
    #differences inside threshold
    for i = 2:length(dy)
        if dy[i] <= -threshold && dy[i-1] >= threshold
            push!(peaks,i)
        end
    end

    #calculate prominences
    yP = y[peaks]
    prominence = zeros(Float64,length(yP))
    for (i,p) in enumerate(peaks)
        lP, rP = 1, length(y)
        for j = (i-1):-1:1
            if yP[j] > yP[i]
                lP = peaks[j]
                break
            end
        end
        ml = minimum(y[lP:p])
        for j = (i+1):length(yP)
            if yP[j] > yP[i]
                rP = peaks[j]
                break
            end
        end
        mr = minimum(y[p:rP])
        ref = max(mr,ml)
        prominence[i] = yP[i] - ref
    end

    #minimal prominence refinement
    peaks = peaks[prominence .> minProm]

    #minimal height refinement
    peaks = peaks[y[peaks] .> minHeight]
    yP = y[peaks]

    #minimal distance refinement
    peaks2del = zeros(Bool,length(peaks))
    inds = sortperm(y[peaks],rev=true)
    permute!(peaks,inds)
    for i = 1:length(peaks)
        for j = 1:(i-1)
            if abs(x[peaks[i]] - x[peaks[j]]) <= minDist
                if !peaks2del[j]
                    peaks2del[i] = true
                end
            end
        end
    end

    peaks[.!peaks2del]
end

end # module


if !@isdefined(Femshop)
    include("../Femshop.jl");
    using .Femshop
end
init_femshop("FVadvection1d");

useLog("FVadvection1dlog")

# Configuration setup
domain(1)
solverType(FV)
timeStepper(RK4)

# Mesh
n = 40 # number of elements
mesh(LINEMESH, elsperdim=n, bids=2)

# Variables and BCs
u = variable("u", SCALAR, CELL)
v = variable("v", SCALAR, CELL)
boundary(u, 1, FLUX, "t<0.2 ? 1 : 0")
boundary(u, 2, NO_BC)
boundary(v, 1, FLUX, "t<0.2 ? 1 : 0")
boundary(v, 2, NO_BC)

# Time interval and initial condition
T = 0.5;
timeInterval(T)
initial(u, "0")
initial(v, "0")

# The flux and source terms of the conservation equation
# F and S in the following equation:
# Dt(int(u dx)) = int(S dx) - int(F.n ds)
coefficient("a", 1) # advection velocity
# The "upwind" function applies upwinding to the term (a.n)*u with flow velocity a.
# The optional third parameter is for tuning. Default upwind = 0, central = 1. Choose something between these.
flux([u, v], ["upwind(a,u)", "upwind(a,v,0.75)"])
# Note that there is no source() for this problem.

#@exportCode("fvad1dcode") # uncomment to export generated code to a file
#@importCode("fvad1dcode") # uncomment to import code from a file

solve([u,v])

finalize_femshop()

##### Uncomment below to compare to exact solution

# # The exact solution with constant velocity v
# a = 1;
# exact = zeros(n);
# x = Femshop.fv_info.cellCenters[:]
# for i=1:n
#     xt = x[i] - a*T;
#     if xt < 0
#         exact[i] = xt > -0.2 ? 1 : 0
#     end
# end

# using Plots
# pyplot();
# display(plot([x x x], [exact u.values[:] v.values[:]], markershape=:circle, label=["exact" "upwind" "alpha=0.75"]))


function quicsearch(A, b)
    first_index = firstindex(A)
    last_index = lastindex(A)
    current_index = -1
    flag = false
    while (first_index + 1 < last_index) &&(current_index == -1)
        mid = div((first_index + last_index), 2)
        if A[mid] == b
            current_index = mid
            flag = true
        else
            if b < A[mid]
                last_index = mid -1
            else
                first_index = mid +1
            end
        end
    end
    if A[first_index] > b
        current_index = first_index
        flag = false
    elseif A[last_index] > b
        current_index = last_index
        flag = false
    elseif A[last_index] < b
        current_index = last_index + 1
        flag = false
    end
    return flag, current_index
end

function insertsort!(A)
    for i in 2:length(A)
        key = A[i]
        j = i
        c,d = quicsearch(A[1:i-1],key)
        while d != j
            A[j],A[j-1] = A[j-1],A[j]
            j -= 1
        end
    end
    return A
end


using LinearAlgebra
using ProgressMeter

struct HamiltonSystem
    d
    m
    F
    q0
    p0
    Minv
    sol
    U
    HamiltonSystem(d,m,F,q0,p0,Minv,sol,U) =
        length(q0)==d*length(m) && length(p0)==d*length(m) ?
        new(d,m,F,q0,p0,Minv,sol,U) : error("dimension mismatch")
end

function getMinv(m,d)
    m_long=zeros(d*length(m))
    for (i,n) in enumerate(m)
        m_long[(i-1)*d+1:(i)*d] = length(n)==d ? collect(n) : [n for i in 1:d]
    end
    return Diagonal(1 ./ m_long)
end

HamiltonSystem(d,m,q0,p0,F=missing,U=missing,sol=missing) = HamiltonSystem(d,m,F,q0,p0,getMinv(m,d),sol,U)

function getEnergy(HS,q,p)
    return 1/2*p'*HS.Minv*p + HS.U(q)
end

function getEnergyNorm(HS,Q,P)
    QA = [Q[:,i] for i in 1:size(Q,2)]
    PA = [P[:,i] for i in 1:size(P,2)]
    E = map(getEnergy,[HS for q in QA],QA,PA)
    E = E.-E[1]
    return map(abs,E)
end

getEnergyErrors(HS,Qs,Ps) = [getEnergyNorm(HS,Q,P) for (Q,P) in zip(Qs,Ps)]
#getMaximumErrors(HS,ts,Qs)= [[norm((Q-hcat(map(HS.sol,t)...))[:,i]) for i in 1:length(t)] for (t,Q) in zip(ts,Qs)]
function getMaximumErrors(HS,ts,Qs)
    ME = []
    for (t,Q) in zip(ts,Qs)
        QA = QA = [Q[:,i] for i in 1:size(Q,2)]
        E=map(norm,QA-map(HS.sol,t))
        push!(ME,E)
    end
    return ME
end

println("Finished loading HamiltonSystem")


# ---
# title: 611. Valid Triangle Number
# id: problem611
# author: Indigo
# date: 2021-06-26
# difficulty: Medium
# categories: Array
# link: <https://leetcode.com/problems/valid-triangle-number/description/>
# hidden: true
# ---
#
# Given an array consists of non-negative integers, your task is to count the
# number of triplets chosen from the array that can make triangles if we take
# them as side lengths of a triangle.
#
# **Example 1:**
#
#
#
#     Input: [2,2,3,4]
#     Output: 3
#     Explanation:
#     Valid combinations are:
#     2,3,4 (using the first 2)
#     2,3,4 (using the second 2)
#     2,2,3
#
#
# **Note:**
#
#   1. The length of the given array won't exceed 1000.
#   2. The integers in the given array are in the range of [0, 1000].
#
#
## @lc code=start
using LeetCode

function triangle_number(nums::Vector{Int})
    res, len = 0, length(nums)
    sort!(nums)
    @inbounds for i in 1:len-2
        nums[i] == 0 && continue
        k = i + 2
        for j in i+1:len-1
            while k <= len && nums[i] + nums[j] > nums[k]; k += 1; end
            res += k - j - 1
        end
    end
    return res
end
## @lc code=end


push!( LOAD_PATH, "../" )
import InformationInequalities  # Import the module to  have access to functions

"""
List of Elemental Information measures (EIM) for a given `n` number of random variables. EIM comprise of conditional entropies `H(X1...,Xn|Y1...Ym)` and conditional mutual information `I(X₁...Xₙ;Y₁....Yₘ|Z₁...Zₖ)` measures.

```julia-repl
julia> ElementalMeasures(2)
["H(X1)","H(X1|X2)","H(X2)","H(X2|X1)","I(X1;X2)","I(X2;X1)"]

julia> ElementalMeasures(3,"🐘")
["H(🐘1)"
"H(🐘1|🐘2)"
"H(🐘1|🐘2,🐘3)"
"H(🐘1|🐘3)"
"H(🐘2)"
"H(🐘2|🐘1)"
"H(🐘2|🐘1,🐘3)"
"H(🐘2|🐘3)"
"H(🐘3)"
"H(🐘3|🐘1)"
"H(🐘3|🐘1,🐘2)"
"H(🐘3|🐘2)"
"I(🐘1;🐘2)"
"I(🐘1;🐘2|🐘3)"
"I(🐘1;🐘3)"
"I(🐘1;🐘3|🐘2)"
"I(🐘2;🐘1)"
"I(🐘2;🐘1|🐘3)"
"I(🐘2;🐘3)"
"I(🐘2;🐘3|🐘1)"
"I(🐘3;🐘1)"
"I(🐘3;🐘1|🐘2)"
"I(🐘3;🐘2)"
"I(🐘3;🐘2|🐘1)"]
```
"""
function ElementalMeasures(n::Int64=2,RV::AbstractString="X")
	yI=ConditionalMutualInformationList(n,RV)
	yH=ConditionalEntropyList(n,RV)
	y=[yH;yI][:]
	return y
end

"""
For a given number `n` of random variables, list down the maximum number of elemental information measures in minimal canonical form.
```julia-repl
julia> u,v,m,n=numEIM(2)
```
"""
function numEIM(n::Int64=2)
	~,~,k,l=minimal_EIM_list_canonical(n)
	return k,l
end

"""
    MutualInformation_ElemToCanon(s::String="I(Xi;X🐬|π)")
Convert a mutual information expression s to elemental form.
If `s` is unspecified, it performs a default expression.
##### Examples
```julia-repl
julia> Elemental2Canonical("I(Xi;X🐬|π)")
"H(Xi,π)+H(X🐬,π)-H(Xi,X🐬,π)-H(π)"
```
"""
function Elemental2Canonical_MI(s::String="I(Xi;X🐬|π)")
	s0=replace(s," " =>"")
	if (occursin(r"\|", s0))
		m1=match(r"(I.+;)",s0)
		m2=match(r"(;.+\|)",s0)
		m3=match(r"(\|.+\))",s0)
		a1=replace(m1.captures[1],"I("=>"",";"=>"")
		a2=replace(m2.captures[1],";"=>"","|"=>"")
		a3=replace(m3.captures[1],")"=>"","|"=>"")

		P1="H(" * a1* "," * a3 * ")"
		P2="H(" * a2* "," * a3 * ")"
		P3="H(" * a1 * "," * a2 * "," * a3 * ")"
		P4="H(" * a3 * ")"
		C= P1 * "+" * P2 * "-" * P3 * "-" * P4
	else
		m1=match(r"(I.+;)",s0)
		m2=match(r"(;.+\))",s0)
		a1=replace(m1.captures[1],"I("=>"",";"=>"")
		a2=replace(m2.captures[1],";"=>"",")"=>"")

		P1="H(" * a1* ")"
		P2="H(" * a2*  ")"
		P3="H(" * a1 * "," * a2 *")"
		C= P1 * "+" * P2  * "-" * P3
	end
	return C
end

"""
Elemental2Canonical_H(s::String="I(Xi;X🐬|π)")
Convert a Entropy or Conditional entropy expression s to elemental form.
If `s` is unspecified, it performs a default expression.
##### Examples
```julia-repl
julia> Elemental2Canonical_H("H(Xi,X🐬|π,γ)")
"H(Xi,X🐬,π,γ)-H(π,γ)"
```
"""
function Elemental2Canonical_H(s::String="H(ρ12|Xπ)")
s0=replace(s," " =>"")
if (occursin(r"\|", s0))
    m = match(r"([H-I]\(.*\|([^\)]*)\))", s0)
    y=replace(m.captures[1], '|' => ',') * "-H($(m.captures[2]))"
else
    y=s
end
return y
end

"""
    Elemental2Canonical(s::String="I(Xi;X🐬|π)")
Convert elemental Information measure to canonical form.
If `s` is unspecified, it performs a default expression.
##### Examples
```julia-repl
julia> Elemental2Canonical("I(Xi;X🐬|π)")
"H(Xi,π)+H(X🐬,π)-H(Xi,X🐬,π)-H(π)"

julia> Elemental2Canonical("H(Xi,X2|π,β)")
"H(Xi,X2,π,β) - H(π,β)"

julia>Elemental2Canonical("I(Xi;X2,ρ,🍎|Zπ,β,🍩)")
"H(Xi,Zπ,β,🍩)+H(X2,ρ,🍎,Zπ,β,🍩)-H(Xi,X2,ρ,🍎,Zπ,β,🍩)-H(Zπ,β,🍩)"
```
"""
function Elemental2Canonical(s::String="I(Xi;X🐬|π)")
	s0=replace(s," " =>"")
	if(startswith(s,"I"))
		C=Elemental2Canonical_MI(s)
	elseif(startswith(s,"H"))
		C=Elemental2Canonical_H(s)
	else
		error("$(s) is an invalid Information measure. Information expression must be of the form H(...) or I(...); e.g. H(Xi,X🐬|π)")
	end
	return C
end

#=
"""
	InformationExpressionToCanonical(F::AbstractString="-2.32I(X;Y|Z1,Z2)")
Convert a single scaled Information measure to its canonnical decomposition form. elemental Information measure to canonical form.
If `F` is unspecified, it performs on a default expression.

##### Examples
```julia-repl
julia> InformationExpressionToCanonical("2.32I(X;Y🍎|Z1,Z2)")
"2.32I(X;Y🍎|Z1,Z2)"

julia> InformationExpressionToCanonical("3H(β😄,Y🍎|Z1,Z2)")
"3H(β😄,Y🍎|Z1,Z2)"
```
"""
=#
function InformationExpressionToCanonical(F::AbstractString="-2.32I(X;Y|Z1,Z2)")
	# Do check and pick only one Iexpr H() or I() with scaling

	 kk=match(r"(^[+|-]?[H|I][(])",F)  #Check if starts with bare I() or H(
	if kk != nothing # starts with bare I() or H(
		sExpr=replace(string(F),"-I("=>"I(","-H("=>"H(")
		# sCoef="1"
		# kk.match[1]
		ρ = (kk.match[1]=='-') ? -1 : 1

		sCoef = string(ρ)
		 Elemental2Canonical(sExpr)
		# @show sCoef=(kk.match[1]=='-') ? "-1" : "1"
	else

	strIH=r"([H|I]+\([\w](.)*[\)])"
	strN=r"([-|+]?[\d]*[\.]?[\d ?]*)"
	Mexp=match(strIH,F)
	Mcoef=match(strN,F)

	sExpr=string(Mexp.captures[1])
	sCoef=string(Mcoef.captures[1])
	# scalar=parse(Float64,sCoef)
	end
	CanExpr=Elemental2Canonical(sExpr)
	Z0=replace(CanExpr,"H(" =>sCoef *"H(")
	Z1=replace(Z0,"--"=>"+")
	Z=replace(Z1,"+-"=>"-")

	return Z
end

function InformationMeasureToCanonical(S::AbstractString="-2.32I(X;Y|Z1,Z2)")
	# Do check and pick only one Iexpr H() or I() with scaling
    F=replace(S," "=>"")
	# Return an error if there are more than one IMeasures
	F1=replace(F,"-"=>"+-")
	F2= split(F1,"+")
	F3 = filter(!isempty, F2)
	if(length(F3)>1)
		error("A single Information Meausure expected; I(X...;Y...|Z...) or H(X,Y...|U,V...)")
	end

	 kk=match(r"(^[+|-]?[H|I][(])",F)  #Check if starts with bare ±I() or ±H(
	if kk != nothing # starts with bare I() or H(
		sExpr=replace(string(F),"-I("=>"I(","-H("=>"H(","+I("=>"I(","+H("=>"H(")
		# sCoef="1"
		# kk.match[1]
		ρ = (kk.match[1]=='-') ? -1 : 1
		sCoef = string(ρ)
		 # Elemental2Canonical(sExpr)
		# @show sCoef=(kk.match[1]=='-') ? "-1" : "1"
	else

		strIH=r"([H|I]+\([\w](.)*[\)])"
		strN=r"([-|+]?[\d]*[\. | \/]?[\d ?]*)"
		Mexp=match(strIH,F)
		Mcoef=match(strN,F)

		sExpr=string(Mexp.captures[1])
		sCoef=string(Mcoef.captures[1])
		# @show parse(Float64,sCoef)
		# @show scalar=parse(Float64,sCoef)
	end
	CanExpr=Elemental2Canonical(sExpr)
	Z0=replace(CanExpr,"H(" =>sCoef *"H(")
	Z1=replace(Z0,"--"=>"+")
	Z2=replace(Z1,"+-"=>"-")
	Z3=replace(Z2,"-+"=>"-")
	Z4= replace(Z3," "=>"")
	Z=replace(Z4,"++"=>"+")
	return Z
end

"""
LinearInformationExpressionToCanonical(A)
```julia-repl
julia>LinearInformationExpressionToCanonical("I(X;Y|Z)-2.3H(U,V)-2H(u)")
"1H(X,Z)+1H(Y,Z)-1H(X,Y,Z)-1H(Z)-2.3H(U,V)-2H(u)"
```
"""
function LinearInformationExpressionToCanonical(A)
    A1=replace(A,"-"=>"+-","+I("=>"+1I(","-I("=>"+-1I(","+H("=>"+1H(","-H("=>"+-1H(")
    A1a =replace(A1," "=>"")
    A2=split.(A1a,"+")
	A2a = filter(!isempty, A2)
    # A3=InformationExpressionToCanonical.(A2)
	A3=InformationMeasureToCanonical.(A2a)
	# A4=prod(A3)
	A4=""
	for ii=1:length(A3)
		A4= A4 * "+" * A3[ii]
	end
	A5=replace(A4,"+-"=>"-")
	return A5
end

"""
For a given number `n` of random variables, list down all the elemental information measures and their corresponding entropic decompositions. The entropic vectors are identified with the prefix `h` and follows lexicographic mapping. e.g., `H(X1,X3,X7)=h137`. Note that, for now this lexicographic mapping works only for `n < 10`.
"""
function entropy_vector(n::Int64,RV::AbstractString="X")
	X=Elemental2Canonical.(ElementalMeasures(n,RV))
	Y=sort(replace.(X,","=>"","X"=>"",")"=>"","("=>"","H"=>"h"))
	return Y
end

"""
For a given number `n<10` of random variables, list down all the unique elemental information measures and their corresponding entropic decompositions. The entropic vectors are identified with the prefix `h` and follows lexicographic mapping. e.g., `H(X1,X3,X7)=h137`. Note that, for now this lexicographic mapping works only for `n < 10`.
"""
function unique_entropy_vector(n::Int64=3)
    EMeasures=Elemental2Canonical.(ElementalMeasures(n))
    EMnumber=(replace.(EMeasures,","=>"","X"=>"",")"=>"","("=>"","H"=>"","+"=>",","-"=>","))
    y=[];
    for kk in 1:length(EMnumber)
        y=[y;"h" .* unique(sort(join.(sort.(digits.(parse.(Int64,split(EMnumber[kk],",")))))))]
    end
    Hfull=y;
    Hminimal = unique(Hfull)
    return EMeasures,Hfull,Hminimal
end

function find_entropic_vectorInt(n::Int64=3,entropicVector="1h12-1h2")
	# vv=toy_entropic_matrix(n)[5]
	x= entropicVector * "+"
		listE=unique_entropy_vector(n)[3]
	   sortedListE="h" .* string.(sort(parse.(Int64,replace.(listE,"h"=>"")),rev=true))
	G=zeros(1,length(listE))

	for idx=1:length(listE)
		mx=match(Regex("([-|+]?\\d$(sortedListE[idx])[-|+]+)"),x) # special quantifier Z to identify the end word

	if(mx == nothing)
		G[idx]=0
	else
		mz=match(r"([-|+]?\dh)",mx.captures[1])
		G[idx]= parse(Int64,replace(mz.captures[1],"h"=>""))
	end
		end
	return G[:]
end

"""
Find the Entropic matrix `G`` for a given `n`
```julia-repl
julia> entropic_matrix(3)
```
"""
function entropic_matrix(m::Int64=2)
	Q=minimal_EIM_list_canonical(m)[2]
	Qtmp=replace.(Q,r"^\+h" => s"1h") # Substitute the first +h
	Qtmp1=replace.(Qtmp,r"\+h" => s"+1h") # Substitute all +h
	Qout=replace.(Qtmp1,r"\-h" => s"-1h")
	return Qout
end

"""
For a given number `n` of random variables, list down all the elemental information measures in minimal canonical form.
```julia-repl
julia> u,v,m,n=minimal_EIM_list_canonical(2)
```
"""
function minimal_EIM_list_canonical(n::Int64=2)
	x=entropy_vector(n);
	x2=order_entropic_expression.(order_entropic.(x))
	y=unique(x2)
	return x,y,length(x),length(y)
end

"""
Each entropy word in an entropic vector is sorted.

#### Examples
```julia-repl
julia> order_entropic("h24-h32-h132-h2")
"h24 - h23 - h123 - h2"

```
"""
function order_entropic(s::AbstractString="Hello world")

	s0=replace(s,"h"=>"h ","+"=>" + ","-"=>" - ")
	x=split(s0," ") # Split into words delim=" "

	Y=""
	for wIdx = 1:length(x)
		# w=x[wIdx] # word
		y=string(sort([a for a in x[wIdx]])...)  # sort each word
		Y= Y * " " * y
	end
	Yout=replace(Y,"h "=>"h"," " =>"")

	return Yout
end

"""
Express an entropic expression in the lexicographic order of entropic vectors
```julia-repl
julia> order_entropic_expression("h12-h32-123")
```
"""
function order_entropic_expression(X::AbstractString="h12-h32-123")
	Y=join.(sort.(split.(replace.(X,"-h"=>" -h","+h"=>" +h","h"=>" +h")," ")))
	return Y
end

"""
Find the Entropic matrix `G` for a given `n`
```julia-repl
julia> find_matrixG(3)
```
"""
function find_matrixG(n::Int64=3)
	L=length(entropic_matrix(n))
	K=length(unique_entropy_vector(n)[3]) # Number of unique entropic dimensions
	Gv=[]
	# @show L
	for idx=1:L
		U = entropic_matrix(n)[idx]
	    gg = find_entropic_vector(n,U)
		# @show n,L,idx, gg
		Gv=[Gv;gg]
		# Gm[idx,:]=gg
	end
	G=reshape(Gv,K,L)
	H0=Int.(parse.(Float64,string.(G)))'
    H=Int.(H0)
	return H,K,L
end

function find_entropic_vector_int(n::Int64=3,entropicVector="1h12-1h2")
	x= entropicVector * "+"
		listE=unique_entropy_vector(n)[3]
	   sortedListE="h" .* string.(sort(parse.(Int64,replace.(listE,"h"=>"")),rev=true))
	G=zeros(1,length(listE))

	for idx=1:length(listE)
		mx=match(Regex("([-|+]?\\d$(sortedListE[idx])[-|+]+)"),x) # special quantifier Z to identify the end word

	if(mx == nothing)
		G[idx]=0
	else
		mz=match(r"([-|+]?\dh)",mx.captures[1])
		G[idx]= parse(Int64,replace(mz.captures[1],"h"=>""))
	end
		end
	return G[:]
end

"""
Given a linear expression in canonical form, it finds the co-ordinates in the entropic space `Γ`
"""
function find_entropic_vector(n::Int64=3,entropicVector="1h12-1h2")
	x= entropicVector * "+"
	listE=unique_entropy_vector(n)[3]
	sortedListE="h" .* string.(sort(parse.(Int64,replace.(listE,"h"=>"")),rev=true))
    G=zeros(1,length(listE))
	for idxx=1:length(listE)
	    mx=match(Regex("([-|+]?\\d*[.]?\\d*$(sortedListE[idxx])[-|+]+)"),x)

	    if(mx == nothing)
		    G[idxx]=0.0
	    else
		# mz=match(r"([-|+]?\dh)",mx.captures[1])
		    mz=match(r"([-|+]?[\d]*[.]?[\d]*h)",mx.captures[1])
		    G[idxx]= parse(Float64,replace(mz.captures[1],"h"=>""))
	    end
	end
    return G[:]
end

"""
Γ₂ geometry. This is the simplest case with two random variables (say `X,Y`) forming a geometry in three dimension. The geometric space is spanned by entropy vectors `H(X)`, `H(Y)` and `H(X,Y)`. Γ₂ is a 3D cone in the positive orthant.
This function is used for visualizing the entropic space in 3D.
"""
function GeometryConeGamma2(x,y,z)
	H=[ 0.0   0   1
 		-1   1   1
  		1  -1   0
  		0   1   0
  		1   0  -1]
	X=(0.0,0.0,0)
	Y=[]
	Z=[]
	p = 0.0
	idx=1
	if(-z+y+x ≥0 && z -y ≥ 0 && z -x ≥ 0 && z ≥0 && y≥0 && x ≥ 0)
		p=1.0

		idx,X=[X;x]
		 Y=[Y;y]
		# Z=[Z;z]
		# (x,y,z)
		# Z=push!(Z,z)
	end
	if(-z+y+x ≥0)
		if ( z -y ≥ 0)
			if(z -x ≥ 0)
				if(z ≥0)
					if (y≥0)
						if (x ≥ 0)
							Y=push!(Z,tuple(x,y,z))
						end
					end
				end
			end
		end
	end

	return Y
end

"""
Set of discrete points in `Γ₂`	 confined within a hypercube
"""
function elementsGamma2(;rmin::Float64=0.0,rmax::Float64=1.0,rstep::Float64 = 0.1)
	x = rmin:rstep:rmax
    y = rmin:rstep:rmax
    z = rmin:rstep:rmax
	vol = [GeometryConeGamma2(ix,iy,iz) for ix in x, iy in y, iz in z]
	U=vol[findall( x -> x != [], vol )]
	V=hcat(U...)
	W=hcat(collect.(V)...)
	return W
end

"""
    E,UE,V,λ =entropic_terms(S::AbstractString)

Find the additive entropic terms in the canonical expression. This function is internally used in `simplifyH(S::AbstractString)`.
#### Arguments
- S  = Any linear Information Expression. These are linear combination of ``I(X_1,...X_k;Y_1,..Y_l|Z_1,...Z_n)`` and ``H(X_1,X_2,...X_m|Z_1,..Z_n)``
#### Output
- E  = Constituent Information measures in S
- U = The distinct (unique) elements from E
- V = The individual elements of S
- λ = The scaling coefficients of U (i.e., ``V=λ^T E``)

#### Examples
```julia-repl
julia> S="I(X;Y)-H(X,Y|Z)-3H(X,Y)+2I(X;Y)"
julia> E,UE,V,λ=entropic_terms(S)
["I(X;Y)" "H(X,Y|Z)" "H(X,Y)" "I(X;Y)"],
["I(X;Y)" "H(X,Y|Z)" "H(X,Y)"],
["1I(X;Y)" "-1H(X,Y|Z)" "-3H(X,Y)" "2I(X;Y)"],
[1.0 -1.0 -3.0 2.0]
```
"""
function entropic_terms(S::AbstractString)
	A0= "+" * S
	A1= replace(A0,"+H"=>"+1H","-H"=>"-1H")
	A2= replace(A1,"+I"=>"+1I")
	A2a= replace(A2,"-I"=>"-1I")
	A2b= replace(A2a,"-"=>"+-")
	A3=replace(A2b,"++"=>"+")
	A4=split(A3,"+")
	V=A4[2:end]

	E=[match(r"([H|I]?\(.*)",i).captures[1] for i in V]
	λ=parse.(Float64,[match(r"([-|+]*\d*[.]?(\d*)?)",i).captures[1] for i in V])
	UE=unique(E)

	return E,UE,V,λ
end

"""
    C=minimalE(V,U,λ)

This function is internally used in `simplifyH(S::AbstractString)`.
### Arguments
- ``E``  = Constituent Information measures in ``S``
- ``U`` = The distinct (unique) elements from ``E``
- ``V`` = The individual elements of ``S``
- ``λ`` = The scaling coefficients of ``U`` (i.e., ``V=λ^T E``)

### Output
- C = Simplified Information Expression as a linear combination of ``U``

#### Examples
```julia-repl
julia> S="7H(X,Y|Z1,Z2)+2H(X,Y)-4H(X,Y)+H(Z)-3H(X,Y|Z1,Z2)"
julia> E,U,V,λ=entropic_terms(S)
julia> Z= minimalE(V,U,λ)
"+4.0H(X,Y|Z1,Z2)-2.0H(X,Y)+H(Z)"
```
"""
function minimalE(V,UE,λ)
	Z=""
	for u in UE
		Λ0=sum(λ[occursin.(u, V)])
		Λ = round(Λ0,digits=3)
		if Λ ≠ 0
			Y="+" * string(Λ) * u
		else
			Y=""
		end
		Z=Z*Y
	end
	P=replace(Z," "=>"","+-"=>"-","++"=>"+")
	C=replace(P,"-1.0H"=>"-H","+1.0H"=>"+H","-1.0I"=>"-I","+1.0I"=>"+I")
	return C
end

"""
Express an Information Expression in the simplest form (algebraically).
#### Examples
```julia-repl
julia> simplify("I(X;Y)-H(X,Y|Z)-3H(X,Y)+2I(X;Y)")
"+3.0I(X;Y)-H(X,Y|Z)-3.0H(X,Y)"

julia> simplify("3I(X;Y|Z)-H(X,Y|Z)-3I(X;Y|Z)-3H(X,Y|Z)+H(X1,X2)")
"-4.0H(X,Y|Z)+H(X1,X2)"
```
"""
function simplify(S)
	E,UE,V,λ =	entropic_terms(S)
	M=minimalE(V,UE,λ)
	return M
end

"""
Express an Information Expression in the simplest form (algebraically). This is used in conjunction with the canonical expression.
#### Examples
```julia-repl
julia>
```
"""
function simplifyH(S)
	E,UE,V,λ =	entropic_terms(S)
	M=minimalE(V,UE,λ)
	return M
end


# ---
# title: 910. Smallest Range II
# id: problem910
# author: Indigo
# date: 2021-07-12
# difficulty: Medium
# categories: Math, Greedy
# link: <https://leetcode.com/problems/smallest-range-ii/description/>
# hidden: true
# ---
#
# Given an array `A` of integers, for each integer `A[i]` we need to choose
# **either  `x = -K` or `x = K`**, and add `x` to `A[i] **(only once)**`.
#
# After this process, we have some array `B`.
#
# Return the smallest possible difference between the maximum value of `B` and
# the minimum value of `B`.
#
#
#
# **Example 1:**
#
#
#
#     Input: A = [1], K = 0
#     Output: 0
#     **Explanation** : B = [1]
#
#
# **Example 2:**
#
#
#
#     Input: A = [0,10], K = 2
#     Output: 6
#     **Explanation** : B = [2,8]
#
#
# **Example 3:**
#
#
#
#     Input: A = [1,3,6], K = 3
#     Output: 3
#     **Explanation** : B = [4,6,3]
#
#
#
#
# **Note:**
#
#   1. `1 <= A.length <= 10000`
#   2. `0 <= A[i] <= 10000`
#   3. `0 <= K <= 10000`
#
#
## @lc code=start
using LeetCode

function smallest_rangeII(nums::Vector{Int}, k::Integer)
    sort!(nums)
    res = nums[end] - nums[1]
    for i in 2:length(nums)
        res = min(res, max(nums[end] - k, nums[i - 1] + k) - min(nums[1] + k, nums[i] - k))
    end
    return res
end
## @lc code=end


module Parameters

export Param, Mesh, setParameters

const BCDirichlet,BCRiemann = 1,2 # Constants for different boundary types

using TOML

# Parameters
struct Param
   nD::Int             # Dimension of the system
   LatticeType::Int    # DmQn lattice model
   Δx::Float64         # Discretized space interval
   Δt::Float64         # Discretized time interval
   α ::Float64         # Thermal diffusivity
   ω ::Float64         # Relaxation coefficient
   TotalTime::Float64  # Total simulation time
   nT::Int             # Total timesteps

   Tleft ::Float64     # Left boundary temperature
   Tright::Float64     # Right boundary temperature
   Ttop  ::Float64     # Top boundary temperature
   Tbot  ::Float64     # Bottom boundary temperature

   BCtype::Int         # Type of boundary conditions

   DoPlot::Bool

   function Param(nD::Int=2,LatticeType::Int=3,dx::Float64=1.0,dt::Float64=1.0,
      α::Float64=0.25,TotalTime::Float64=400.0,
      Tleft::Float64=1.0,Tright::Float64=0.0,
      Ttop::Float64=0.0,Tbot::Float64=0.0,
      BCtype::Int=1,DoPlot::Bool=false)
      @assert(0 ≤ TotalTime, "Simulation time must be positive!")

      new(nD,LatticeType,dx,dt,α,inv(nD*dt*α/dx^2 + 0.5),TotalTime,
         Int(TotalTime/dt),Tleft,Tright,Ttop,Tbot,BCtype,DoPlot)
   end
end

struct Mesh
   connectivity::Array{Int,2}
   BcNodeI1::Vector{Int}
   BcNodeI2::Vector{Int}
   BcNodeJ1::Vector{Int}
   BcNodeJ2::Vector{Int}
   coord::Array{Float64,2}
   nN::Int     # Total number of nodes
end

function setParameters()
   paramIn = TOML.parsefile("PARAM.toml")
   nD = paramIn["Parameters"]["nD"]
   LatticeType = paramIn["Parameters"]["LatticeType"]
   dx = paramIn["Parameters"]["dx"]
   dt = paramIn["Parameters"]["dt"]
   α = paramIn["Parameters"]["α"]
   TotalTime = paramIn["Parameters"]["TotalTime"]
   Tleft = paramIn["Parameters"]["Tleft"]
   Tright = paramIn["Parameters"]["Tright"]
   Ttop = paramIn["Parameters"]["Ttop"]
   Tbot = paramIn["Parameters"]["Tbot"]
   BCtype = paramIn["Parameters"]["BCtype"]
   DoPlot = paramIn["Plots"]["DoPlot"]

   if LatticeType == "D2Q4"
      LT = 1
   elseif LatticeType == "D2Q5"
      LT = 2
   elseif LatticeType == "D2Q9"
      LT = 3
   end

   if BCtype == "Dirichlet"
      BC = 1
   elseif BCtype == "Riemann"
      BC = 2
   end

   connectivity = hcat(paramIn["Mesh"]["connectivity"]...)
   nN = size(connectivity,2)
   coord = hcat(paramIn["Mesh"]["coord"]...)

   mesh = Mesh(connectivity,
      paramIn["Mesh"]["BcNodeI1"],
      paramIn["Mesh"]["BcNodeI2"],
      paramIn["Mesh"]["BcNodeJ1"],
      paramIn["Mesh"]["BcNodeJ2"],
      coord,
      nN)

   param = Param(nD,LT,dx,dt,α,TotalTime,Tleft,Tright,Ttop,Tbot,BC,DoPlot)

   return param,mesh
end

end


# This file includes the simulation of five node network simulation

import Cairo, Fontconfig
using GraphPlot
using Graphs
using SimpleWeightedGraphs
using LinearAlgebra
using DynamicalNetworks
using PyPlot
pygui(true)
close("all")

# Helper functions
getwave(x, i, d) = getindex.(x, (i - 1) * d + 1)

# Node settings
nodetype = Lorenz           # Node type
P = diagm([1.0, 0.0, 0])    # State coupling matrix
ηt = 10.0                   # Threshold for maximum Lyapuno exponents

# Topology
n = 8
graph = SimpleWeightedGraph(cycle_graph(n))
add_edge!(graph, 2, 7, 1)
add_edge!(graph, 3, 6, 1)
gplot(graph, nodelabel = 1:nv(graph), layout = circular_layout) |> display

# Coupling strength
W = laplacian_matrix(graph)
Q = [
    0 0 0 0 0 0 0 1
    0 0 0 0 0 0 1 0
    0 0 0 0 0 1 0 0
    0 0 0 0 1 0 0 0
    0 0 0 1 0 0 0 0
    0 0 1 0 0 0 0 0
    0 1 0 0 0 0 0 0
    1 0 0 0 0 0 0 0
]
M = eigvecs(Q)
G = inv(M) * (W ./ diag(W)) * M
B = G[1:4, 1:4]
D = G[5:8, 5:8]
λt = eigvals(B)
λs = eigvals(D)
λt[1] > λs[2] || error("Synchronization is not possible")
ϵr = ηt ./ [λt[1], λs[2]]
ϵ = ϵr[1] + abs(ϵr[2] - ϵr[1]) * 0.5

# Network
σ = 0.0  # Noise strength
H = getnoisemat(graph) * σ
net = SDENetwork(nodetype, ϵ, graph, H, P)
sol = solvenet!(net, (0.0, 100.0), solverkwargs = (saveat = 0.001,))
t = sol.t
x = sol.u

# Plots
d = dimension(nodetype)
subplot(221)
plot(t, abs.(getwave(x, 1, d) - getwave(x, 2, d)))
xlabel("Time")
ylabel("e12")
subplot(222)
plot(t, abs.(getwave(x, 1, d) - getwave(x, 8, d)))
xlabel("Time")
ylabel("e18")
subplot(223)
plot(t, getwave(x, 1, d))
xlabel("Time")
ylabel("x1")
subplot(224)
plot(getindex.(x, 1), getindex.(x, 2))
xlabel("x1")
ylabel("x2")
tight_layout()

figure()
plot(λt, zeros(length(λt)), marker = "*", label = L"\lambda_t")
plot(λs, ones(length(λs)), marker = "o", label = L"\lambda_s")
axvline.([λt[1], λs[2]], linestyle = "dashed", color = "red")
axvline(ηt / ϵ, linestyle = "dashed", color = "green")
xlabel(L"\lambda")
title("Eigenvalue distribution")
legend()


using Synchrony, Base.Test
datadir = joinpath(Pkg.dir("Synchrony"), "test", "data")

# Test Morlet wavelet bases
#
# Reference implementation is from Torrence, C., and G. P. Compo. “A
# Practical Guide to Wavelet Analysis.” Bulletin of the American
# Meteorological Society 79, no. 1 (1998): 61–78.
#
# Our bases differ from Torrence and Compo's by a factor corresponding
# to the length of the FFT, which eliminates the need to normalize
# the output of the inverse FFT.
d1 = [wavebases(MorletWavelet([0.1], 5), 1024) * 1024; zeros(511, 1)]
d2 = map(Float64, readdlm(joinpath(datadir, "morlet_bases_f_0.1_k0_5.txt")))
@test_approx_eq d1 d2

# Test Morlet wavelet by comparing output for a 0.1 Hz oscillation
# embedded in white noise
test_in = map(Float64, readdlm(joinpath(datadir, "wavelet_test_in.txt"))[:])
foi = map(Float64, readdlm(joinpath(datadir, "wavelet_test_foi.txt"))[:])
d1 = cwt(test_in, MorletWavelet(foi, 5))
d2 = complex(map(Float64, readdlm(joinpath(datadir, "wavelet_test_out_re.txt"), '\t')),
             map(Float64, readdlm(joinpath(datadir, "wavelet_test_out_im.txt"), '\t')))
coi_periods = map(Float64, readdlm(joinpath(datadir, "wavelet_test_coi.txt"))[:])
for j = 1:length(foi)
    period = 1/foi[j]
    for i = 1:length(coi_periods)
        @test isnan(real(d1[i, j])) == (period > coi_periods[i]) || abs(period - coi_periods[i]) <= eps(coi_periods[i])
    end
end
d2[isnan(real(d1))] = NaN
@test_approx_eq d1 d2

# Test tstd
for w in (MorletWavelet([0.0003]), MorseWavelet([0.0003], 8, 3))
    wb = [wavebases(w, 100000, 3); zeros(49999)]
    p = abs2(ifftshift(bfft(wb)))
    x = (0:length(p)-1)/3
    μ = sum(p.*x)
    σ = sqrt(sum(p.*abs2(x.-μ)))
    @test_approx_eq_eps σ tstd(w) σ*1e-5
end

# Test fstd
for w in (MorletWavelet([0.75]), MorseWavelet([0.75], 8, 3))
    x = frequencies(100000, 3)[1]
    p = abs2(wavebases(w, 100000, 3))*100000
    μ = sum(p.*x)
    σ = sqrt(sum(p.*abs2(x.-μ)))
    @test_approx_eq_eps σ fstd(w) σ*1e-5
end

# Test missing data handling in wavelet transform
y = ones(1000)
y[499] = 0
y[500] = 1/3
y[501] = 2/3
foi = 2.^(0:0.25:7)
w = MorletWavelet(foi, 5)
cois = ceil(Int, tstd(w)*2*1000)
z1 = cwt(y, w, 1000)
y[500] = NaN
z2 = cwt(y, w, 1000)
for k = 1:length(foi), i = 1:length(y)
    @test isnan(real(z2[i, k])) == (i <= cois[k] || i >= length(y) - cois[k] + 1 ||
                                    500 - cois[k] <= i <= 500 + cois[k])
end
@test_approx_eq z1[!isnan(real(z2))] z2[!isnan(real(z2))]
y[501] = NaN
z2 = cwt(y, w, 1000)
for k = 1:length(foi), i = 1:length(y)
    @test isnan(real(z2[i, k])) == (i <= cois[k] || i >= length(y) - cois[k] + 1 ||
                                    500 - cois[k] <= i <= 501 + cois[k])
end
@test_approx_eq z1[!isnan(real(z2))] z2[!isnan(real(z2))]


using TiledViews, TestImages, Images, Statistics
# lets normalize each tile to a mean and standard deviation of 0.5 and stitch them back together
data = Float32.(testimage("cameraman"));
Gray.(data)

tile_size = (50, 50)
tile_overlap = (20,20)
myview = TiledView(data, tile_size, tile_overlap);
size(myview)

# make a new view to copy into
dest = TiledViews.zeros_like(myview);
typeof(dest)

# Define a function that does the work
do_normalize(tile) = 0.5 .+ 0.5.*(tile .- mean(tile))./ std(tile) ;

for (src, dst) in zip(eachtile(myview), eachtile(dest))
    dst .= do_normalize(src)
end
Gray.(dest.parent)

# now lets do this with smoother transitions using a window
my_window = get_window(myview);
Gray.(my_window)
typeof(my_window)   # This is actually an IndexFunArray (~ no memory needed)

dest2 = TiledViews.zeros_like(myview); # just to make a new view to copy into
for (src, dst) in zip(eachtile(myview), eachtile(dest2))
    dst .+= my_window .* do_normalize(src);
end
Gray.(dest.parent)
Gray.(dest2.parent)

# A more convenient way:
myview, matching_window = TiledWindowView(data, (50, 50);verbose=true);

# Evan more convenient:
res = tiled_processing(myview, do_normalize; verbose=true);
Gray.(res.parent)


"""
    second-order cone
"""
function second_order_cone_projection(z)
    n = length(z)

    z0 = z[1]
    z1 = view(z, 2:n)

    if norm(z1) <= z0
        return z, true
    elseif norm(z1) <= -z0
        return zero(z), false
    else
        a = 0.5 * (1.0 + z0 / norm(z1))
        z_proj = zero(z)
        z_proj[1:end-1] = a * z1
        z_proj[end] = a * norm(z1)
        return z_proj, false
    end
end

function second_order_cone_product(z, s)
    n = length(z)
    SVector{n}([z' * s; z[1] * view(s, 2:n) + s[1] * view(z, 2:n)])
end

# check that second-order cone constraints are satisfied
function second_order_cone_check(x, idx_soc)
    for idx in idx_soc
        !second_order_cone_projection(view(x, idx))[2] && return false
    end
    return true
end

# check that inequality (non-negative orthant) constraints are satisfied
function inequality_check(x, idx_ineq)
    for i in idx_ineq
        if x[i] <= 0.0
            return false
        end
    end
    return true
end

function cone_check(x, idx_ineq, idx_soc)
    !inequality_check(x, idx_ineq) && return true
    !second_order_cone_check(x, idx_soc) && return true
end

function cone_plot(λ::AbstractVector{T}, Δ::AbstractVector{T};
		ϵ = 1e-20, N::Int = 100, verbose::Bool = false, show_α::Bool = false) where {T}
	plt = plot(aspect_ratio = 1.0, fmt = :svg)

	# unit circle
	θ = Vector(0:2π/N:(1.0+1/N)*2π)
	circ = [cos.(θ) , sin.(θ)]
	plot!(plt, circ[1], circ[2], label = false)

	# Keypoints
	λp = project(λ)
	λΔp = project(λ + Δ)
	verbose && @show soc_value(λ )
	verbose && @show soc_value(λ + Δ)
	scatter!(plt, λΔp[1:1],  λΔp[2:2],  markersize = 4.0, color = :orange, label = "λ+Δ")
	scatter!(plt, λp[1:1],   λp[2:2],   markersize = 8.0, color = :red,    label = "λ")

	if show_α
		α = soc_step_length(λ, Δ, τ = 1.0, ϵ = ϵ)
		λαΔp = project(λ + α * Δ)
		verbose && @show α
		verbose && @show soc_value(λ + α * Δ)
		scatter!(plt, λαΔp[1:1], λαΔp[2:2], markersize = 2.0, color = :black,   label = "λ+αΔ")
	end

	# Line
	A = Vector(0:1/N:1.0)
	L = [[project(λ + a * Δ)[1] for a in A], [project(λ + a * Δ)[2] for a in A]]
	plot!(plt, L[1], L[2], label = false)

	display(plt)
	return nothing
end

function project(x::AbstractVector{T}; ϵ::T = 1e-15) where {T}
	if length(x) == 3
		project_3D(x, ϵ = ϵ)
	elseif length(x) == 2
		project_2D(x, ϵ = ϵ)
	end
end

function project_3D(x::AbstractVector{T}; ϵ::T = 1e-15) where {T}
	@assert length(x) == 3
	xs = x[1]
	xv = x[2:end]
	r = norm(xv) / (xs + ϵ)
	v = xv ./ norm(xv)
	p = r * v
	return p
end

function project_2D(x::AbstractVector{T}; ϵ::T = 1e-15) where {T}
	@assert length(x) == 2
	xs = x[1]
	xv = x[2:end]
	r = norm(xv) / (xs + ϵ)
	v = xv ./ norm(xv)
	p = r * v
	return [p[1], 0.0]
end


# SPDX-License-Identifier: X11
# 2020-11-10
# Frog 2 (100pt)

const INF = 10^9 + 5

function main()
  N,K = parse.(Int, (split ∘ readline)())
  H = parse.(Int, (split ∘ readline)())

  DP = fill(INF, N)
  DP[1] = 0

  for i ∈ 2:N
    for j ∈ 1:K
      i <= j && break
      DP[i] = min(DP[i], abs(H[i - j] - H[i]) + DP[i - j])
    end
  end

  println(DP[N])
end

main()


using LinearAlgebra
using PyPlot

function gauss_quad(p)
    n = ceil((p+1)/2)
    b = 1:n-1
    b = @. b / sqrt(4*b^2 - 1)
    eval, evec = eigen(diagm(1 => b, -1 => b))
    return eval, 2*evec[1,:].^2
end

function legendre_poly(x, p)
    z = zeros(size(x))
    o = ones(size(x))
    y = hcat(o, x, repeat(z, 1, p-1))
    dy = hcat(z, o, repeat(z, 1, p-1))
    for i = 1:p-1
        @. y[:,i+2] = ((2i+1)*x*y[:,i+1] - i*y[:,i]) / (i+1)
        @. dy[:,i+2] = ((2i+1)*(x*dy[:,i+1] + y[:,i+1]) - i*dy[:,i]) / (i+1)
    end
    y, dy
end

function dgconvect(; n=10, p=1, T=1.0, dt=1e-3)
    # Discretization
    h = 1 / n
    s = @.cos((pi*collect(0:p))/p)
    s1 = 0.5*(-s.+1)
    x = @. h*s1 + (0:h:1-h)'

    # Gaussian initial condition (and exact solution if shifted)
    uinit(x) = exp(-(x - 0.5)^2 / 0.1^2)

    # Coefficient Matrix
    lhsc = legendre_poly(-s,p)[1]
    cmx = lhsc \ Matrix{Float64}(I,p+1,p+1)

    # Mass and Stiffness Matrices

    Mel = zeros(p+1,p+1)
    Kel = zeros(p+1,p+1)
    gx, gw = gauss_quad(2p)
    #gx = (h/2).*(gx.+1)
    for i = 0:p, j = 0:p
	    mdvec = zeros(p+1)
	    kdvec = zeros(p+1)
	    for k = 0:p
	         lpval, dlpval = legendre_poly([gx[k+1]], p)
		 mdvec[k+1] = dot(lpval,cmx[:,i+1]) * dot(lpval,cmx[:,j+1])
		 kdvec[k+1] = dot(dlpval,cmx[:,i+1]) * dot(lpval,cmx[:,j+1])
	    end
	    Mel[i+1,j+1] = dot(mdvec, gw)
	    Kel[i+1,j+1] = dot(kdvec, gw)
    end

    Mel = Mel.*(h/2)
    Kel = Kel

    # RHS function in semi-discrete system
    function rhs(u)
        r = Kel * u
        r[end,:] = r[end,:] - u[end,:]
        r[1,:] = r[1,:] + u[end, [end; 1:end-1]]
        r = Mel \ r
    end

    # Setup
    u = uinit.(x)
    nsteps = round(Int, T/dt)

    # Setup plotting
    xx = collect(0:0.01:1) # For exact solution
    clf(); axis([0, 1, -0.1, 1.1]); grid(true);
    ph1 = plot(x, u, "b")   # Many line object (since discontinuous)
    ph2, = plot(xx, uinit.(xx), "k") # Single line object

    # Main loop
    for it = 1:nsteps
        # Runge-Kutta 4
        k1 = dt * rhs(u)
        k2 = dt * rhs(u + k1/2)
        k3 = dt * rhs(u + k2/2)
        k4 = dt * rhs(u + k3)
        u += (k1 + 2*k2 + 2*k3 + k4) / 6

        # Plotting
        if mod(it, round(nsteps/100)) == 0
            uexact = @. uinit(mod(xx - dt*it, 1.0))
            for line in ph1 line.remove(); end
            ph1 = plot(x, u, "b")
            ph2.set_data(xx, uexact)
            pause(1e-3)
        end
    end
    ua = uinit.(x)
    xw = zeros(size(x)[1],size(x)[2])
    for j = 1:p
	xw[p-j+2,:] = x[p-j+2,:] - x[p-j+1,:]
    end
    eu = (sum((u.^2).*xw))^0.5
    eua	= (sum((ua.^2).*xw))^0.5
    return u, abs(eua-eu)
end

function dgconvect_convergence()
    errors = zeros(5,5)
    for i = 0:4, j = 4:8
        p1 = ceil(Int,2^i)
        n1 = ceil(Int,(2^j)/p1)
        println(p1, " ", n1)
	errors[j-3,i+1] = dgconvect( n=n1, p=p1, dt=2e-4)[2]
    end
    clf()
    loglog([16 32 64 128 256]', errors)
    rates = @. log2(errors[end-1,:]) - log2(errors[end,:])
    return errors, rates
end

function dgconvdiff(; n=10, p=1, T=1.0, dt=1e-3, k=1e-3)
    # Discretization
    h = 1 / n
    s = @.cos((pi*collect(0:p))/p)
    s1 = 0.5*(-s.+1)
    x = @. h*s1 + (0:h:1-h)'

    # Gaussian initial condition (and exact solution if shifted)
    uinit(x) = exp(-(x - 0.5)^2 / 0.1^2)

    # Coefficient Matrix
    lhsc = legendre_poly(-s,p)[1]
    cmx = lhsc \ Matrix{Float64}(I,p+1,p+1)

    # Mass and Stiffness Matrices

    Mel = zeros(p+1,p+1)
    Kel = zeros(p+1,p+1)
    gx, gw = gauss_quad(2p)
    #gx = (h/2).*(gx.+1)
    for i = 0:p, j = 0:p
	    mdvec = zeros(p+1)
	    kdvec = zeros(p+1)
	    for k = 0:p
	         lpval, dlpval = legendre_poly([gx[k+1]], p)
		 mdvec[k+1] = dot(lpval,cmx[:,i+1]) * dot(lpval,cmx[:,j+1])
		 kdvec[k+1] = dot(dlpval,cmx[:,i+1]) * dot(lpval,cmx[:,j+1])
	    end
	    Mel[i+1,j+1] = dot(mdvec, gw)
	    Kel[i+1,j+1] = dot(kdvec, gw)
    end

    Mel = Mel.*(h/2)
    Kel = Kel

    u = uinit.(x)
    us = uinit.(x)

    # RHS function in semi-discrete system
    function rhs(u)
        up = vcat(u[end,:]' , u[1:end-1,:])
        un = vcat(u[2:end,:] , u[1,:]')
        xrev = abs.(x.-vcat(x[2,:]' , x[1:end-1,:]))
        u2 = (2u - un - up)./(2xrev)

        r = -Kel * (u)
        r[end,:] = r[end,:] + u[end,:]
        r[1,:] = r[1,:] - u[end, [end; 1:end-1]]
        r = Mel \ r
        sigma = r
        r = Kel * (u - k*(r))
        r[end,:] = r[end,:] - u[end,:] + k*sigma[end,:]
        r[1,:] = r[1,:] + u[end, [end; 1:end-1]] - k*sigma[end, [end; 1:end-1]]
        r = Mel \ r
    end

    # Setup

    nsteps = round(Int, T/dt)

    # Setup plotting
    xx = collect(0:0.01:1) # For exact solution
    uex(x,t,i,k) = exp(-100((x-t-0.5+i)^2/(1+400*k*t)))/sqrt(1+400*k*t)
    clf(); axis([0, 1, -0.1, 1.1]); grid(true);
    ph1 = plot(x, u, "b")   # Many line object (since discontinuous)
    ph2, = plot(xx, uinit.(xx), "k") # Single line object

    # Main loop
    for it = 1:nsteps
        # Runge-Kutta 4
        k1 = dt * rhs(u)
        k2 = dt * rhs(u + k1/2)
        k3 = dt * rhs(u + k2/2)
        k4 = dt * rhs(u + k3)
        u += (k1 + 2*k2 + 2*k3 + k4) / 6

        # Plotting
        if mod(it, round(nsteps/100)) == 0
            uexact = @. uex(xx,dt*it,0,k)+uex(xx,dt*it,1,k)+uex(xx,dt*it,-1,k)
            for line in ph1 line.remove(); end
            ph1 = plot(x, u, "b")
            ph2.set_data(xx, uexact)
            pause(1e-3)
        end
    end
    ua = @. uex(x,1,0,k)+uex(x,1,1,k) + uex(x,1,-1,k)
    xw = zeros(size(x)[1],size(x)[2])
    for j = 1:p
	xw[p-j+2,:] = x[p-j+2,:] - x[p-j+1,:]
    end
    eu = (sum((u.^2).*xw))^0.5
    eua	= (sum((ua.^2).*xw))^0.5
    return u, abs(eua-eu)
end

function dgconvdiff_convergence()
    errors = zeros(4,5)
    for i = 0:4, j = 4:7
        p1 = ceil(Int,2^i)
        n1 = ceil(Int,(2^j)/p1)
        println(p1, " ", n1)
	errors[j-3,i+1] = dgconvdiff( n=n1, p=p1, dt=1e-3, k = 1e-3)[2]
    end
    clf()
    loglog([16 32 64 128]', errors)
    rates = @. log2(errors[end-1,:]) - log2(errors[end,:])
    return errors, rates
end


module Lyaponuv

export lyaponuv_k, lyaponuv, lyaponuv_exp, lyaponuv_next, match_pairs, attractor,
    follow_points, compute_norms, column_norms, get_next

function lyaponuv_k(time_series, J, m, ref)
    X = attractor(time_series, m, J)
    norms = compute_norms(X)
    pairs = match_pairs(norms)
    y = follow_points(pairs, norms, ref)
    return(norms,y)
end

function match_pairs(norms)
    M = size(norms)[1]
    pairs = Array(Int, M)
    for row in 1:M
        mn, idx = findmin(norms[row, :])
        pairs[row] = idx
    end
    return(pairs)
end

function attractor(time_series, m, J)
    N = length(time_series)
    M = N - (m - 1) * J
    X = Array(Float64, m, M)
    i = 1
    for i=1:M
        X[:,i] = time_series[i:J:(i+(m-1)*J)]
    end
    return(X)
end

function follow_points(pairs, norms, ref)
    y = Array(Float64, ref)
    M = size(norms)[1]
    for i=0:ref-1
        agg = 0
        count = 0
        for j=1:M
            jhat = pairs[j]+i
            jtrue = j+i

            if jhat <= M && jtrue <= M
                agg = agg + log(norms[jtrue, jhat])
               # agg = agg + log(vecnorm(X[:, jtrue] - X[:, jhat]))
                count = count + 1
            end
        end
        y[i+1] = agg/count # divide by delta-t also?
    end
    return(y)
end

function compute_norms(X)
    M = size(X)[2]
    norms = Array(Float64, M, M)
    for i=1:M
        norms[i,:] = column_norms(X, i)
    end
    return(norms)
end

function column_norms(X, i)
    M = size(X)[2]
    X_diff = X .- X[:, i]
    norm_vector = [vecnorm(X_diff[:, k]) for k=1:M]
    norm_vector[i] = 10^10
    return(norm_vector)
end

function lyaponuv_exp(series)
    nn = !isnan(series)
    A = ones(length(series), 2)
    A[:,1] = linspace(1, length(series), length(series))
    gradient = \(A, series)
    return(gradient[1])
end

function lyaponuv(time_series, J, m, ref)
    ts = lyaponuv_k(time_series, J, m, ref)[2]
    exponent = lyaponuv_exp(ts[isfinite(ts)])  ## only input those which are finite
    return(exponent)
end

function get_next(ts, m, M, norms, ref, J)

    attractor_array = attractor(ts, m, J)
    temp_norms = Array(Float64, M+1, M+1)
    temp_norms[1:M, 1:M] = norms
    col = column_norms(attractor_array, M+1)
    temp_norms[M+1, :] = col
    temp_norms[:, M+1] = col

    pairs=match_pairs(temp_norms)
    lyap_k_temp = follow_points(pairs, temp_norms, ref)
    return(lyaponuv_exp(lyap_k_temp))
end

function lyaponuv_next(time_series, J, m, ref, sample_size)
    ts_diff = time_series[2:end] - time_series[1:end-1]
    sigma = std(ts_diff)
    samples = randn(sample_size) * sigma + time_series[end]
    @time norms, lyap_k = lyaponuv_k(time_series, J, m, ref)
    true_exponent = lyaponuv_exp(lyap_k)
    exponents = Array(Float64, sample_size)
    M = size(norms)[1]
    tasks = Array(Future, sample_size)

    for i=1:sample_size
        s = samples[i]
        tasks[i] = @spawn get_next(vcat(time_series, s), m, M, norms, ref, J)
        #@time exponents[i] = get_next(vcat(time_series, s), m, M, norms, ref, J)
        #@printf("process: %d\n", i)
    end

    for i=1:sample_size
        exponents[i]=fetch(tasks[i])
    end

    diff = abs(exponents .-  true_exponent)
    val, idx = findmin(diff)
    println("Next Value:", samples[idx])
    return(samples[idx])
end

end