import ..UncertainValues:
    AbstractUncertainValue
import ..Resampling:
    resample

# Cosine functions
"""
    Base.cos(x::AbstractUncertainValue; n::Int = 10000) -> Vector{Float64}

Compute the cosine of the uncertain value `x`, where `x` is in radians. Computes the
element-wise cosine for `n` realizations.
"""
Base.cos(x::AbstractUncertainValue; n::Int = 10000) = cos.(resample(x, n))

"""
    Base.cos(x::AbstractUncertainValue, n::Int = 10000) -> Vector{Float64}

Compute the cosine of the uncertain value `x`, where `x` is in radians. Computes the
element-wise cosine for `n` realizations.
"""
Base.cos(x::AbstractUncertainValue, n::Int) = cos.(resample(x, n))

"""
    Base.cos(x::AbstractUncertainValue; n::Int = 10000) -> Vector{Float64}

Compute the cosine of the uncertain value `x`, where `x` is in degrees. Computes the
element-wise cosine for `n` realizations.
"""
Base.cosd(x::AbstractUncertainValue; n::Int = 10000) = cosd.(resample(x, n))

"""
    Base.cos(x::AbstractUncertainValue, n::Int = 10000) -> Vector{Float64}

Compute the cosine of the uncertain value `x`, where `x` is in degrees. Computes the
element-wise cosine for `n` realizations.
"""
Base.cosd(x::AbstractUncertainValue, n::Int) = cosd.(resample(x, n))

"""
    Base.cos(x::AbstractUncertainValue; n::Int = 10000) -> Vector{Float64}

Compute the hyperbolic cosine of the uncertain value `x`.
Computes the element-wise hyperbolic cosine for `n` realizations.
"""
Base.cosh(x::AbstractUncertainValue; n::Int = 10000) = cosh.(resample(x, n))

"""
    Base.cos(x::AbstractUncertainValue, n::Int = 10000) -> Vector{Float64}

Compute the hyperbolic cosine of the uncertain value `x`.
Computes the element-wise hyperbolic cosine for `n` realizations.
"""
Base.cosh(x::AbstractUncertainValue, n::Int) = cosh.(resample(x, n))

# Sine functions
"""
    Base.sin(x::AbstractUncertainValue; n::Int = 10000) -> Vector{Float64}

Compute the sine of the uncertain value `x`, where `x` is in radians. Computes the
element-wise sine for `n` realizations.
"""
Base.sin(x::AbstractUncertainValue; n::Int = 10000) = sin.(resample(x, n))

"""
    Base.sin(x::AbstractUncertainValue, n::Int = 10000) -> Vector{Float64}

Compute the sine of the uncertain value `x`, where `x` is in radians. Computes the
element-wise sine for `n` realizations.
"""
Base.sin(x::AbstractUncertainValue, n::Int) = sin.(resample(x, n))

"""
    Base.sind(x::AbstractUncertainValue; n::Int = 10000) -> Vector{Float64}

Compute the sine of the uncertain value `x`, where `x` is in degrees. Computes the
element-wise sine for `n` realizations.
"""
Base.sind(x::AbstractUncertainValue; n::Int = 10000) = sind.(resample(x, n))

"""
    Base.sind(x::AbstractUncertainValue, n::Int = 10000) -> Vector{Float64}

Compute the sine of the uncertain value `x`, where `x` is in degrees. Computes the
element-wise sine for `n` realizations.
"""
Base.sind(x::AbstractUncertainValue, n::Int) = sind.(resample(x, n))

"""
    Base.sinh(x::AbstractUncertainValue; n::Int = 10000) -> Vector{Float64}

Compute the hyperbolic sine of the uncertain value `x`.
Computes the element-wise hyperbolic sine for `n` realizations.
"""
Base.sinh(x::AbstractUncertainValue; n::Int = 10000) = sinh.(resample(x, n))

"""
    Base.sinh(x::AbstractUncertainValue, n::Int = 10000) -> Vector{Float64}

Compute the hyperbolic sine of the uncertain value `x`.
Computes the element-wise hyperbolic sine for `n` realizations.
"""
Base.sinh(x::AbstractUncertainValue, n::Int) = sinh.(resample(x, n))

# Tan functions
"""
    Base.tan(x::AbstractUncertainValue; n::Int = 10000) -> Vector{Float64}

Compute the tangent of the uncertain value `x`, where `x` is in radians. Computes the
element-wise tangent for `n` realizations.
"""
Base.tan(x::AbstractUncertainValue; n::Int = 10000) = tan.(resample(x, n))

"""
    Base.tan(x::AbstractUncertainValue, n::Int = 10000) -> Vector{Float64}

Compute the tangent of the uncertain value `x`, where `x` is in radians. Computes the
element-wise tangent for `n` realizations.
"""
Base.tan(x::AbstractUncertainValue, n::Int) = tan.(resample(x, n))

"""
    Base.tand(x::AbstractUncertainValue; n::Int = 10000) -> Vector{Float64}

Compute the tangent of the uncertain value `x`, where `x` is in degrees. Computes the
element-wise tangent for `n` realizations.
"""
Base.tand(x::AbstractUncertainValue; n::Int = 10000) = tand.(resample(x, n))

"""
    Base.tand(x::AbstractUncertainValue, n::Int = 10000) -> Vector{Float64}

Compute the tangent of the uncertain value `x`, where `x` is in degrees. Computes the
element-wise tangent for `n` realizations.
"""
Base.tand(x::AbstractUncertainValue, n::Int) = tand.(resample(x, n))

"""
    Base.tanh(x::AbstractUncertainValue; n::Int = 10000) -> Vector{Float64}

Compute the hyperbolic tangent of the uncertain value `x`.
Computes the element-wise hyperbolic tangent for `n` realizations.
"""
Base.tanh(x::AbstractUncertainValue; n::Int = 10000) = tanh.(resample(x, n))

"""
    Base.tanh(x::AbstractUncertainValue, n::Int = 10000) -> Vector{Float64}

Compute the hyperbolic tangent of the uncertain value `x`.
Computes the element-wise hyperbolic tangent for `n` realizations.
"""
Base.tanh(x::AbstractUncertainValue, n::Int) = tanh.(resample(x, n))

# Other

"""
    Base.sincos(x::AbstractUncertainValue; n::Int = 10000)

Simultaneously compute the sine and cosine of the uncertain value `x`, where `x` is in
radians. Computes the element-wise `sincos` for `n` realizations.
"""
Base.sincos(x::AbstractUncertainValue; n::Int = 10000) = sincos.(resample(x, n))

"""
    Base.sincos(x::AbstractUncertainValue, n::Int = 10000)

Simultaneously compute the sine and cosine of the uncertain value `x`, where `x` is in
radians. Computes the element-wise `sincos` for `n` realizations.
"""
Base.sincos(x::AbstractUncertainValue, n::Int) = sincos.(resample(x, n))

"""
    Base.sinc(x::AbstractUncertainValue; n::Int = 10000)

In an element-wise manner for `n` realizations of the uncertain value `x`, compute
``\\sin(\\pi x) / (\\pi x)`` if ``x \\neq 0``, and ``1`` if ``x = 0``.
"""
Base.sinc(x::AbstractUncertainValue; n::Int = 10000) = sinc.(resample(x, n))

"""
    Base.sinc(x::AbstractUncertainValue, n::Int = 10000)

Compute ``\\sin(\\pi x) / (\\pi x)`` if ``x \\neq 0``, and ``1`` if ``x = 0`` element-wise
over `n` realizations of the uncertain value `x`.
"""
Base.sinc(x::AbstractUncertainValue, n::Int) = sinc.(resample(x, n))

"""
    Base.sinpi(x::AbstractUncertainValue; n::Int = 10000)

Compute ``\\sin(\\pi x)`` more accurately than `sin(pi*x)`, especially for large `x`,
in an element-wise over `n` realizations of the uncertain value `x`.
"""
Base.sinpi(x::AbstractUncertainValue; n::Int = 10000) = sinpi.(resample(x, n))

"""
    Base.sinpi(x::AbstractUncertainValue; n::Int = 10000)

Compute ``\\sin(\\pi x)`` more accurately than `sin(pi*x)`, especially for large `x`,
in an element-wise over `n` realizations of the uncertain value `x`.
"""
Base.sinpi(x::AbstractUncertainValue, n::Int) = sinpi.(resample(x, n))

"""
    Base.cosc(x::AbstractUncertainValue; n::Int = 10000)

Compute ``\\cos(\\pi x) / x - \\sin(\\pi x) / (\\pi x^2)`` if ``x \\neq 0``, and ``0`` if
``x = 0``, in an element-wise manner over `n` realizations of the uncertain value `x`.

This is the derivative of `sinc(x)`.
"""
Base.cosc(x::AbstractUncertainValue; n::Int = 10000) = cosc.(resample(x, n))

"""
    Base.cosc(x::AbstractUncertainValue, n::Int = 10000)

Compute ``\\cos(\\pi x) / x - \\sin(\\pi x) / (\\pi x^2)`` if ``x \\neq 0``, and ``0`` if
``x = 0``, in an element-wise manner over `n` realizations of the uncertain value `x`.

This is the derivative of `sinc(x)`.
"""
Base.cosc(x::AbstractUncertainValue, n::Int) = cosc.(resample(x, n))

"""
    Base.cospi(x::AbstractUncertainValue; n::Int = 10000)

Compute ``\\cos(\\pi x)`` more accurately than `cos(pi*x)`, especially for large `x`,
in an element-wise over `n` realizations of the uncertain value `x`.
"""
Base.cospi(x::AbstractUncertainValue; n::Int = 10000) = cospi.(resample(x, n))

"""
    Base.cospi(x::AbstractUncertainValue, n::Int = 10000)

Compute ``\\cos(\\pi x)`` more accurately than `cos(pi*x)`, especially for large `x`,
in an element-wise over `n` realizations of the uncertain value `x`.
"""
Base.cospi(x::AbstractUncertainValue, n::Int) = cospi.(resample(x, n))

############################
# Inverse trig functions
############################
"""
    Base.acos(x::AbstractUncertainValue; n::Int = 10000) -> Vector{Float64}

Compute the inverse cosine of the uncertain value `x`, where `x` is in radians. Computes the
element-wise inverse cosine for `n` realizations.
"""
Base.acos(x::AbstractUncertainValue; n::Int = 10000) = acos.(resample(x, n))

"""
    Base.acos(x::AbstractUncertainValue, n::Int = 10000) -> Vector{Float64}

Compute the inverse cosine of the uncertain value `x`, where `x` is in radians. Computes the
element-wise inverse cosine for `n` realizations.
"""
Base.acos(x::AbstractUncertainValue, n::Int) = acos.(resample(x, n))

"""
    Base.acosd(x::AbstractUncertainValue; n::Int = 10000) -> Vector{Float64}

Compute the inverse cosine of the uncertain value `x`, where `x` is in degrees. Computes the
element-wise inverse cosine for `n` realizations.
"""
Base.acosd(x::AbstractUncertainValue; n::Int = 10000) = acosd.(resample(x, n))

"""
    Base.acosd(x::AbstractUncertainValue, n::Int = 10000) -> Vector{Float64}

Compute the inverse cosine of the uncertain value `x`, where `x` is in degrees. Computes the
element-wise inverse cosine for `n` realizations.
"""
Base.acosd(x::AbstractUncertainValue, n::Int) = acosd.(resample(x, n))

"""
    Base.acosh(x::AbstractUncertainValue; n::Int = 10000) -> Vector{Float64}

Compute the inverse hyperbolic cosine of the uncertain value `x`.
Computes the element-wise inverse hyperbolic cosine for `n` realizations.
"""
Base.acosh(x::AbstractUncertainValue; n::Int = 10000) = acosh.(resample(x, n))

"""
    Base.acosh(x::AbstractUncertainValue, n::Int = 10000) -> Vector{Float64}

Compute the inverse hyperbolic cosine of the uncertain value `x`.
Computes the element-wise inverse hyperbolic cosine for `n` realizations.
"""
Base.acosh(x::AbstractUncertainValue, n::Int) = acosh.(resample(x, n))

"""
    Base.asin(x::AbstractUncertainValue; n::Int = 10000) -> Vector{Float64}

Compute the inverse sine of the uncertain value `x`, where `x` is in radians. Computes the
element-wise inverse sine for `n` realizations.
"""
Base.asin(x::AbstractUncertainValue; n::Int = 10000) = asin.(resample(x, n))

"""
    Base.asin(x::AbstractUncertainValue, n::Int = 10000) -> Vector{Float64}

Compute the inverse sine of the uncertain value `x`, where `x` is in radians. Computes the
element-wise inverse sine for `n` realizations.
"""
Base.asin(x::AbstractUncertainValue, n::Int) = asin.(resample(x, n))

"""
    Base.asind(x::AbstractUncertainValue; n::Int = 10000) -> Vector{Float64}

Compute the inverse sine of the uncertain value `x`, where `x` is in degrees. Computes the
element-wise inverse sine for `n` realizations.
"""
Base.asind(x::AbstractUncertainValue; n::Int = 10000) = asind.(resample(x, n))

"""
    Base.asind(x::AbstractUncertainValue, n::Int = 10000) -> Vector{Float64}

Compute the inverse sine of the uncertain value `x`, where `x` is in degrees. Computes the
element-wise inverse sine for `n` realizations.
"""
Base.asind(x::AbstractUncertainValue, n::Int) = asind.(resample(x, n))

"""
    Base.asinh(x::AbstractUncertainValue; n::Int = 10000) -> Vector{Float64}

Compute the inverse hyperbolic sine of the uncertain value `x`.
Computes the element-wise inverse hyperbolic csine for `n` realizations.
"""
Base.asinh(x::AbstractUncertainValue; n::Int = 10000) = asinh.(resample(x, n))

"""
    Base.asinh(x::AbstractUncertainValue, n::Int = 10000) -> Vector{Float64}

Compute the inverse hyperbolic sine of the uncertain value `x`.
Computes the element-wise inverse hyperbolic csine for `n` realizations.
"""
Base.asinh(x::AbstractUncertainValue, n::Int) = asinh.(resample(x, n))

"""
    Base.atan(x::AbstractUncertainValue; n::Int = 10000) -> Vector{Float64}

Compute the inverse tangent of the uncertain value `x`, where `x` is in radians.
Computes the element-wise inverse tangent for `n` realizations.
"""
Base.atan(x::AbstractUncertainValue; n::Int = 10000) = atan.(resample(x, n))

"""
    Base.atan(x::AbstractUncertainValue, n::Int = 10000) -> Vector{Float64}

Compute the inverse tangent of the uncertain value `x`, where `x` is in radians.
Computes the element-wise inverse tangent for `n` realizations.
"""
Base.atan(x::AbstractUncertainValue, n::Int) = atan.(resample(x, n))

"""
    Base.atand(x::AbstractUncertainValue; n::Int = 10000) -> Vector{Float64}

Compute the inverse tangent of the uncertain value `x`, where `x` is in degrees.
Computes the element-wise inverse tangent for `n` realizations.
"""
Base.atand(x::AbstractUncertainValue; n::Int = 10000) = atand.(resample(x, n))

"""
    Base.atand(x::AbstractUncertainValue, n::Int = 10000) -> Vector{Float64}

Compute the inverse tangent of the uncertain value `x`, where `x` is in degrees.
Computes the element-wise inverse tangent for `n` realizations.
"""
Base.atand(x::AbstractUncertainValue, n::Int) = atand.(resample(x, n))

"""
    Base.atanh(x::AbstractUncertainValue; n::Int = 10000) -> Vector{Float64}

Compute the inverse hypoerbolic tangent of the uncertain value `x`.
Computes the element-wise inverse hypoerbolic tangent for `n` realizations.
"""
Base.atanh(x::AbstractUncertainValue; n::Int = 10000) = atanh.(resample(x, n))

"""
    Base.atanh(x::AbstractUncertainValue, n::Int = 10000) -> Vector{Float64}

Compute the inverse hypoerbolic tangent of the uncertain value `x`.
Computes the element-wise inverse hypoerbolic tangent for `n` realizations.
"""
Base.atanh(x::AbstractUncertainValue, n::Int) = atanh.(resample(x, n))

"""
    Base.asec(x::AbstractUncertainValue; n::Int = 10000) -> Vector{Float64}

Compute the inverse secant of the uncertain value `x`, where `x` is in radians.
Computes the element-wise inverse secant for `n` realizations.
"""
Base.asec(x::AbstractUncertainValue; n::Int = 10000) = asec.(resample(x, n))

"""
    Base.asec(x::AbstractUncertainValue, n::Int = 10000) -> Vector{Float64}

Compute the inverse secant of the uncertain value `x`, where `x` is in radians.
Computes the element-wise inverse secant for `n` realizations.
"""
Base.asec(x::AbstractUncertainValue, n::Int) = asec.(resample(x, n))

"""
    Base.asecd(x::AbstractUncertainValue; n::Int = 10000) -> Vector{Float64}

Compute the inverse secant of the uncertain value `x`, where `x` is in degrees.
Computes the element-wise inverse secant for `n` realizations.
"""
Base.asecd(x::AbstractUncertainValue; n::Int = 10000) = asec.(resample(x, n))

"""
    Base.asecd(x::AbstractUncertainValue, n::Int = 10000) -> Vector{Float64}

Compute the inverse secant of the uncertain value `x`, where `x` is in degrees.
Computes the element-wise inverse secant for `n` realizations.
"""
Base.asecd(x::AbstractUncertainValue, n::Int) = asec.(resample(x, n))

"""
    Base.asech(x::AbstractUncertainValue; n::Int = 10000) -> Vector{Float64}

Compute the inverse hyperbolic secant of the uncertain value `x`.
Computes the element-wise inverse hyperbolic secant for `n` realizations.
"""
Base.asech(x::AbstractUncertainValue; n::Int = 10000) = asec.(resample(x, n))

"""
    Base.asech(x::AbstractUncertainValue, n::Int = 10000) -> Vector{Float64}

Compute the inverse hyperbolic secant of the uncertain value `x`.
Computes the element-wise inverse hyperbolic secant for `n` realizations.
"""
Base.asech(x::AbstractUncertainValue, n::Int) = asec.(resample(x, n))

"""
    Base.acsc(x::AbstractUncertainValue; n::Int = 10000) -> Vector{Float64}

Compute the inverse cosecant of the uncertain value `x`, where `x` is in radians.
Computes the element-wise inverse cosecant for `n` realizations.
"""
Base.acsc(x::AbstractUncertainValue; n::Int = 10000) = acsc.(resample(x, n))

"""
    Base.acsc(x::AbstractUncertainValue, n::Int = 10000) -> Vector{Float64}

Compute the inverse cosecant of the uncertain value `x`, where `x` is in radians.
Computes the element-wise inverse cosecant for `n` realizations.
"""
Base.acsc(x::AbstractUncertainValue, n::Int) = acsc.(resample(x, n))

"""
    Base.acscd(x::AbstractUncertainValue; n::Int = 10000) -> Vector{Float64}

Compute the inverse cosecant of the uncertain value `x`, where `x` is in degrees.
Computes the element-wise inverse cosecant for `n` realizations.
"""
Base.acscd(x::AbstractUncertainValue; n::Int = 10000) = acsc.(resample(x, n))

"""
    Base.acscd(x::AbstractUncertainValue, n::Int = 10000) -> Vector{Float64}

Compute the inverse cosecant of the uncertain value `x`, where `x` is in degrees.
Computes the element-wise inverse cosecant for `n` realizations.
"""
Base.acscd(x::AbstractUncertainValue, n::Int) = acsc.(resample(x, n))

"""
    Base.acscd(x::AbstractUncertainValue; n::Int = 10000) -> Vector{Float64}

Compute the inverse hyperbolic cosecant of the uncertain value `x`.
Computes the element-wise inverse hypoerbolic cosecant for `n` realizations.
"""
Base.acsch(x::AbstractUncertainValue; n::Int = 10000) = acsc.(resample(x, n))

"""
    Base.acscd(x::AbstractUncertainValue, n::Int = 10000) -> Vector{Float64}

Compute the inverse hyperbolic cosecant of the uncertain value `x`.
Computes the element-wise inverse hypoerbolic cosecant for `n` realizations.
"""
Base.acsch(x::AbstractUncertainValue, n::Int) = acsc.(resample(x, n))

"""
    Base.acot(x::AbstractUncertainValue; n::Int = 10000) -> Vector{Float64}

Compute the inverse cotangent of the uncertain value `x`, where `x` is in radians.
Computes the element-wise inverse secant for `n` realizations.
"""
Base.acot(x::AbstractUncertainValue; n::Int = 10000) = acot.(resample(x, n))

"""
    Base.acot(x::AbstractUncertainValue, n::Int = 10000) -> Vector{Float64}

Compute the inverse cotangent of the uncertain value `x`, where `x` is in radians.
Computes the element-wise inverse secant for `n` realizations.
"""
Base.acot(x::AbstractUncertainValue, n::Int) = acot.(resample(x, n))

"""
    Base.acotd(x::AbstractUncertainValue; n::Int = 10000) -> Vector{Float64}

Compute the inverse cotangent of the uncertain value `x`, where `x` is in degrees.
Computes the element-wise inverse secant for `n` realizations.
"""
Base.acotd(x::AbstractUncertainValue; n::Int = 10000) = acot.(resample(x, n))

"""
    Base.acotd(x::AbstractUncertainValue, n::Int = 10000) -> Vector{Float64}

Compute the inverse cotangent of the uncertain value `x`, where `x` is in degrees.
Computes the element-wise inverse secant for `n` realizations.
"""
Base.acotd(x::AbstractUncertainValue, n::Int) = acot.(resample(x, n))

"""
    Base.acoth(x::AbstractUncertainValue; n::Int = 10000) -> Vector{Float64}

Compute the inverse hyperbolic cotangent of the uncertain value `x`.
Computes the element-wise inverse hyperbolic secant for `n` realizations.
"""
Base.acoth(x::AbstractUncertainValue; n::Int = 10000) = acot.(resample(x, n))

"""
    Base.acoth(x::AbstractUncertainValue, n::Int = 10000) -> Vector{Float64}

Compute the inverse hyperbolic cotangent of the uncertain value `x`.
Computes the element-wise inverse hyperbolic secant for `n` realizations.
"""
Base.acoth(x::AbstractUncertainValue, n::Int) = acot.(resample(x, n))

############################
# Reciprocal trig functions
############################

"""
    Base.csc(x::AbstractUncertainValue; n::Int = 10000) -> Vector{Float64}

Compute the cosecant of the uncertain value `x`, where `x` is in radians. Computes the
element-wise cosecant for `n` realizations.
"""
Base.csc(x::AbstractUncertainValue; n::Int = 10000) = csc.(resample(x, n))

"""
    Base.csc(x::AbstractUncertainValue, n::Int = 10000) -> Vector{Float64}

Compute the cosecant of the uncertain value `x`, where `x` is in radians. Computes the
element-wise cosecant for `n` realizations.
"""
Base.csc(x::AbstractUncertainValue, n::Int) = csc.(resample(x, n))

"""
    Base.cscd(x::AbstractUncertainValue; n::Int = 10000) -> Vector{Float64}

Compute the cosecant of the uncertain value `x`, where `x` is in degrees. Computes the
element-wise cosecant for `n` realizations.
"""
Base.cscd(x::AbstractUncertainValue; n::Int = 10000) = cscd.(resample(x, n))

"""
    Base.cscd(x::AbstractUncertainValue, n::Int = 10000) -> Vector{Float64}

Compute the cosecant of the uncertain value `x`, where `x` is in degrees. Computes the
element-wise cosecant for `n` realizations.
"""
Base.cscd(x::AbstractUncertainValue, n::Int) = cscd.(resample(x, n))

"""
    Base.cscd(x::AbstractUncertainValue; n::Int = 10000) -> Vector{Float64}

Compute the hyperbolic cosecant of the uncertain value `x`, where `x` is in degrees.
Computes the element-wise hyperbolic cosecant for `n` realizations.
"""
Base.csch(x::AbstractUncertainValue; n::Int = 10000) = csch.(resample(x, n))

"""
    Base.cscd(x::AbstractUncertainValue, n::Int = 10000) -> Vector{Float64}

Compute the hyperbolic cosecant of the uncertain value `x`, where `x` is in degrees.
Computes the element-wise hyperbolic cosecant for `n` realizations.
"""
Base.csch(x::AbstractUncertainValue, n::Int) = csch.(resample(x, n))

"""
    Base.sec(x::AbstractUncertainValue; n::Int = 10000) -> Vector{Float64}

Compute the secant of the uncertain value `x`, where `x` is in radians. Computes the
element-wise secant for `n` realizations.
"""
Base.sec(x::AbstractUncertainValue; n::Int = 10000) = sec.(resample(x, n))

"""
    Base.sec(x::AbstractUncertainValue, n::Int = 10000) -> Vector{Float64}

Compute the secant of the uncertain value `x`, where `x` is in radians. Computes the
element-wise secant for `n` realizations.
"""
Base.sec(x::AbstractUncertainValue, n::Int) = sec.(resample(x, n))

"""
    Base.secd(x::AbstractUncertainValue; n::Int = 10000) -> Vector{Float64}

Compute the secant of the uncertain value `x`, where `x` is in degrees. Computes the
element-wise cosecant for `n` realizations.
"""
Base.secd(x::AbstractUncertainValue; n::Int = 10000) = secd.(resample(x, n))

"""
    Base.secd(x::AbstractUncertainValue, n::Int = 10000) -> Vector{Float64}

Compute the secant of the uncertain value `x`, where `x` is in degrees. Computes the
element-wise cosecant for `n` realizations.
"""
Base.secd(x::AbstractUncertainValue, n::Int) = secd.(resample(x, n))

"""
    Base.sech(x::AbstractUncertainValue; n::Int = 10000) -> Vector{Float64}

Compute the hyperbolic secant of the uncertain value `x`, where `x` is in degrees.
Computes the element-wise hyperbolic secant for `n` realizations.
"""
Base.sech(x::AbstractUncertainValue; n::Int = 10000) = sech.(resample(x, n))

"""
    Base.sech(x::AbstractUncertainValue, n::Int = 10000) -> Vector{Float64}

Compute the hyperbolic secant of the uncertain value `x`, where `x` is in degrees.
Computes the element-wise hyperbolic secant for `n` realizations.
"""
Base.sech(x::AbstractUncertainValue, n::Int) = sech.(resample(x, n))

"""
    Base.cot(x::AbstractUncertainValue; n::Int = 10000) -> Vector{Float64}

Compute the cotangent of the uncertain value `x`, where `x` is in radians. Computes the
element-wise cotangent for `n` realizations.
"""
Base.cot(x::AbstractUncertainValue; n::Int = 10000) = cot.(resample(x, n))

"""
    Base.cot(x::AbstractUncertainValue, n::Int = 10000) -> Vector{Float64}

Compute the cotangent of the uncertain value `x`, where `x` is in radians. Computes the
element-wise cotangent for `n` realizations.
"""
Base.cot(x::AbstractUncertainValue, n::Int) = cot.(resample(x, n))

"""
    Base.cotd(x::AbstractUncertainValue; n::Int = 10000) -> Vector{Float64}

Compute the cotangent of the uncertain value `x`, where `x` is in degrees. Computes the
element-wise cotangent for `n` realizations.
"""
Base.cotd(x::AbstractUncertainValue; n::Int = 10000) = cotd.(resample(x, n))

"""
    Base.cotd(x::AbstractUncertainValue, n::Int = 10000) -> Vector{Float64}

Compute the cotangent of the uncertain value `x`, where `x` is in degrees. Computes the
element-wise cotangent for `n` realizations.
"""
Base.cotd(x::AbstractUncertainValue, n::Int) = cotd.(resample(x, n))

"""
    Base.coth(x::AbstractUncertainValue; n::Int = 10000) -> Vector{Float64}

Compute the hyperbolic cotangent of the uncertain value `x`, where `x` is in radians.
Computes the element-wise hyperbolic cotangent for `n` realizations.
"""
Base.coth(x::AbstractUncertainValue; n::Int = 10000) = coth.(resample(x, n))

"""
    Base.coth(x::AbstractUncertainValue, n::Int = 10000) -> Vector{Float64}

Compute the hyperbolic cotangent of the uncertain value `x`, where `x` is in radians.
Computes the element-wise hyperbolic cotangent for `n` realizations.
"""
Base.coth(x::AbstractUncertainValue, n::Int) = coth.(resample(x, n))

#####################################################################################
# Special cases
#####################################################################################

trigfuncs = [:(cos), :(cosd), :(cosh), :(sin), :(sind), :(sinh), :(tan), :(tand), :(tanh),
    :(sincos), :(sinc), :(sinpi), :(cosc), :(cospi), :(acos), :(acosd), :(acosh), :(asin),
    :(asind), :(asinh), :(atan), :(atand), :(atanh), :(asec), :(asecd), :(asech), :(acsc),
    :(acscd), :(acsch), :(acot), :(acotd), :(acoth), :(csc), :(cscd), :(csch), :(sec),
    :(secd), :(sech), :(cot), :(cotd), :(coth)]

##################
# `CertainValue`s
#################
import ..UncertainValues: CertainValue

"""
    Base.:-(a::Union{CertainValue, Real}, b::Union{CertainValue, Real})

Subtraction of certain values with themselves or scalars acts as regular subtraction,
but returns the result wrapped in a `CertainValue` instance.
"""

for trigfunc in trigfuncs
    f = Meta.parse("Base.$(trigfunc)")

    regular_func = quote
        """
            $($f)(x::CertainValue)

        Compute `$($trigfunc)(x)`.
        """
        $(f)(x::CertainValue) = x.value
    end

    n_reps_func = quote
        """
            $($f)(x::CertainValue, n::Int)

        Compute `$($trigfunc)(x)` `n` times and return the result(s) as a vector (just
        repeating the value `n` times).
        """
        $(f)(x::CertainValue, n::Int) = [x.value for i = 1:n]
    end

    eval(regular_func)
    eval(n_reps_func)
end


using StructJuMP

m = StructuredModel()

@defStochasticVar(m, xh0[ASSETS] >= 0)
@defStochasticVar(m, xb0[ASSETS] >= 0)
@addConstraint(m, (1+Ct)*sum{val[j]*xb0[j], j=ASSETS} <= BUDGET)

for n1 in NODES
    if PARENT[n1] == 0
        sub = StructuredModel(parent=m)
        @defStochasticVar(sub, xh1[ASSETS] >= 0)
        @defStochasticVar(sub, xb1[ASSETS] >= 0)
        @defStochasticVar(sub, xs1[ASSETS] >= 0)
        for j in ASSETS
            @addConstraint(sub, xh1[j] == (1+Ret[n1,j])*xh0[j]+xb1[j]-xs1[j])
        end
        @addConstraint(sub, (1-Ct)*sum{val[j]*xs1[j], j=ASSETS} == Liability1 + (1+Ct)*sum{val[j]*xb1[j]})
    end

    for n2 in NODES
        if PARENT[n2] == n1
            sub = StructuredModel(parent=m)
            @defStochasticVar(sub, xh2[ASSETS] >= 0)
            @defStochasticVar(sub, xb2[ASSETS] >= 0)
            @defStochasticVar(sub, xs2[ASSETS] >= 0)
            for j in ASSETS
                @addConstraint(sub, xh2[j] == (1+Ret2[n2,j])*xh1[j] + xb2[j] - xs2[j])
            end
            @addConstraint(sub, (1-Ct)*sum{val[j]*xs2[j], j=ASSETS} == Liability2 + (1+Ct)*sum{val[j]*xb2[j]})
            @defStochasticVar(sub, wealth)
            @addConstraint(sub, wealth == Prob[n1]*Prob[n2]*sum{val[j]*xh2[j], j=ASSETS})
            @setObjective(sub, mu - Rho*((wealth^2-mu^2))*Prob[n1]*Prob[n2])
        end
    end
end

@addConstraint(m, sum{Prob[n1]*Prob[n2]*wealth[(n1,n2)], n1=NODES, n2=NODES; PARENT[n1]==0 && PARENT[n2]==n1})

# things of note:
# Block(parent::Union(Model,Block), index) constructor
# should do name mangling so that variables get associated with their block
# add function block(parent, child1, child2, ...) that returns reference to subproblem. This should then be passable as index to subproblem variables


using Test, Astro

print("25.a Sun position, low precision")
# Meeus page 165
# Calculate the Sun's position on 1992 October 13 at 0h TD
# should be geometric long mean equinox of date 199degrees  54 minutes 26.18 seconds
#          apparent longitude 199d54m23.56s
#          apparent latitude +0.72 seconds
#          radius vector 0.99760853
#          apparent RA 13h13m30.749s
#          apparent decl -1d47m01.74s
L, R = longitude_radius_low(2448908.5)
@test rad2deg(L) ≈  199.90988 atol= 1e-2# should be 1e-5 ?
@test isapprox(R, 0.99766, atol=1e-5)
L = apparent_longitude_low(2448908.5, L)
println("\nL is $(rad2deg(L)) degrees, R is $R au")
@test rad2deg(L) ≈  199.90895 atol= 1e-2# should be 1e-5 ?
println(" passed\n\n")

print("28.a Equation of time")
# Find equation of time on 1992 October 13 at 0h
# should be 13.70949 minutes, 13m42.6s
E = equation_time(cal_to_jd(1992,10,13))
@test E ≈  13.7 atol= .5# half a second?
E = equation_time(cal_to_jd(1980,7,27.5))
@test isapprox(E, -6.4, atol=.1)
println(" passed")

#=

map(rad2deg,sun_dimension3(cal_to_jd(1992,10,13)))
 2014-10-08T16:59:16 loaded data for Earth 2.4489085e6 (1992.0,10.0,13.0)
(199.90729724204937,0.00020664594475074667,57.158757815790885)

=#

print(" Duffett-Smith: RA and DEC of the sun of July 27 1980")
# Duffett-Smith page 83 Practical Astronomy with your calculator
# What were the RA and DEC of the sun of July 27 1980
# sun's longitude should be 124.108828
# > ra should be 08h25m44s
# dec should be 19h13m53s
jd = cal_to_jd(1980,7,27)
Lsun, radius = longitude_radius_low(cal_to_jd(1980,7,27))
# ecliptic latitude is 0
Bsun = 0
set_obliquity(obliquity_high(jd))
ra, decl = ecl_to_equ(Lsun, Bsun, currentobliquity)

#println(" For July 27, 1980, the Sun's RA and Dec were")
#println(" RA: $(d_to_dms(rad2deg(ra))), Decl: $(d_to_dms(rad2deg(decl)))")
# RA is (126.0,26.0,8.426670651592705), Decl is (19.0,13.0,52.04805574268332)

#=

Lsun, radius = longitude_radius_low(cal_to_jd(1980,7,27))
> (2.166121184301187,1.0155158031473217)

rad2deg(2.166121184301187)
> 124.10960177433758        - sun's longitude looks good

Bsun = 0
obliquity = obliquity_high(jd)

ra, decl = ecl_to_equ(Lsun, Bsun, obliquity)

ecl_to_equ returns:
        Right ascension in radians (2 pi is 24 hours)
        Declination in radians

> (2.206718804590345,0.335646440686569)

map(rad2deg, (ra,decl))
> (126.435674075181,19.231124459928523)
θθ

=#

print("\n\n25.b Sun position, high precision, page 169")

jd_to_cal(2448908.5)

L, B, R = sun_dimension3(cal_to_jd(1992,10,13))

# meeus gives L as 19°.907372, B as -0.000179, R as 0.99760775
# L, B, R = 199.91598259307455, 0.00020664594475074667, 0.9976085202355934

@test isapprox(rad2deg(L), 199.907372, atol=0.1)
@test isapprox(rad2deg(B) , -0.000179, atol=.1)
@test isapprox(R, 0.9976, atol=.1)

L, B = vsop_to_fk5(2448908.5, L, B)
@test isapprox(rad2deg(L), 199.907347, atol=.1)

aberration = aberration_low(R)
@test isapprox(rad2deg(aberration) * 3600, -20.539, atol=0.001)
println(" passed")

print("\n\n26.a 1 rectangular coordinates of the sun, relative to the mean equinox of the date")
X, Y, Z = rectangular_md(2448908.5)
@test isapprox(X, -0.9379952, atol=.1) #
@test isapprox(Y, -0.3116544, atol=0.1) # ..
@test isapprox(Z, -0.1351215, atol=0.1) # ..
println(" passed")


## ODEs of three species in a simple food chain

#### Define 3 species network
# i(rows) consumes resource j(columns), with i,j =( 1,2,3) corresponding to (basal, intermediate, top) species

A = [0 0 0; 1 0 0; 0 1 0]

# ## Trophic levels
# tl = [1, 2, 3]

# # Z is consumer-resource body-mass ratio
# Z = 100

# # basal species mass
# m = 0.01

#### Parameters
# # write a function to generate mass and foodweb
# function Network(m = 0.01, tl = [1, 2, 3]; Z)
#     # Body mass calculated from Z, trophic levels and basal mass
#     M = m * (Z .^ (tl .- 1))
#     FW = (Z = Z, M = M)
#     return FW
# end

# Z, M = Network(Z = 10)

# write a function to generate just body masses
function BodyMasses(m = 0.01, tl = [1, 2, 3]; Z)
    # Body mass calculated from Z, trophic levels and basal mass
    M = m .* (Z .^ (tl .- 1))
    return M
end


using OptEvapCool
using StatsPlots
using Printf: @sprintf
using LaTeXStrings
using Plots.PlotMeasures

function anu_crossbeam_trap(duration = 1.97, input_dir = "")
    # Physical parameters
    Np = 3e7 # Initial atom count
    T₀ = 15e-6 # Initial temperature
    species = Rb87

    # Numerical parameters
    Nt = ceil(Int64, 1e5) # Test particles
    F = Np / Nt
    Nc = 3 # Target number of test particles per cell

    # Beam parameters
    P₁ = exponential_ramp(15, 1.5, 0.8) # Watts
    P₂ = exponential_ramp(7.5, 1.5, 0.8)
    # Set final power to 1.5W to achieve BEC

    w₀ = 130e-6 # Waist (m)
    θ = ( 22.5 * π / 180 ) / 2 # Half-angle between beams

    dir1 = [sin(θ), 0, cos(θ)] # Directions
    dir2 = [-sin(θ), 0, cos(θ)]
    λ₁ = 1064e-9 # Wavelengths
    λ₂ = 1090e-9

    beam1 = GaussianBeam([0,0,0], dir1, P₁, w₀, λ₁)
    beam2 = GaussianBeam([0,0,0], dir2, P₂, w₀, λ₂)

    # Trapping frequencies
    m = species.m
    κ = kappa(species)

    Uₜ_coeff = 2 * κ / (π * w₀^2)
    Uₜ(t) = Uₜ_coeff * (P₁(t) + P₂(t)) #Trap depth

    ωx_coeff = sqrt(4 * cos(θ)^2 / (m * w₀^2))
    ωz_coeff = sqrt(4 * sin(θ)^2 / (m * w₀^2))
    ωy_coeff = sqrt(4 / (m * w₀^2))

    ωx(t) = ωx_coeff * sqrt(Uₜ(t))
    ωz(t) = ωz_coeff * sqrt(Uₜ(t))
    ωy(t) = ωy_coeff * sqrt(Uₜ(t))

    # Cloud initialisation
    positions = harmonic_boltzmann_positions(Nt, m, T₀, ωx(0), ωy(0), ωz(0))
    velocities = boltzmann_velocities(Nt, m, T₀)

    # Adjust vertical equilibrium position
    function y0(t)
        q = -4*g^2 / (ωy(t)^4 * w₀^2)
        return - w₀/2 * sqrt(- q * exp(q))
    end

    centre(t) = [0, y0(t), 0]
    positions .+= centre(0)

    # Three-body loss & background loss
    K = 4.3e-29 * 1e-12
    τ_bg = 180

    # Maximum timestep
    max_dt(t) = 0.05 * 2π / max(ωx(t), ωy(t), ωz(t))

    # Gravitaty
    acc_grav! = acceleration(gravity)
    pot_grav! = potential(gravity)

    # GAUSSIAN BEAM SIMULATION
    acc_b1! = acceleration(beam1)
    acc_b2! = acceleration(beam2)
    pot_b1! = potential(beam1)
    pot_b2! = potential(beam2)

    function accel_gb(p, s, t, o)
        fill!(o, 0.0)
        acc_b1!(p, s, t, o)
        acc_b2!(p, s, t, o)
        acc_grav!(p, s, t, o)
        return nothing
    end

    function poten_gb(p, s, t, o)
        fill!(o, 0.0)
        pot_b1!(p, s, t, o)
        pot_b2!(p, s, t, o)
        pot_grav!(p, s, t, o)
        return nothing
    end

    sensor_gb = GlobalSensor()
    measure_gb = measurer(sensor_gb, 0.001, ωx, ωy, ωz, centre)

    T_gb(t) = (length(sensor_gb.ke) > 0) ? 2 * last(sensor_gb.ke) / (3 * kB) : T₀
    evap_gb = ellipsoid_evap(ωx, ωy, ωz, T_gb, 1e-6, centre)

    gb_conditions = SimulationConditions(species, F, positions, velocities,
        accel_gb, poten_gb, evap = evap_gb, τ_bg = τ_bg, K = K)

    if input_dir == "" # Simulate new results
        @info "Gaussian beam simulation"
        final_gb_cloud = evolve(gb_conditions, duration;
            Nc = Nc, max_dt = max_dt, measure = measure_gb)
    end

    # HARMONIC SIMULATION
    harm = HarmonicField(ωx, ωy, ωz, centre)
    acc_harm! = acceleration(harm)
    pot_harm! = potential(harm)

    function accel_harm(p, s, t, o)
        fill!(o, 0.0)
        acc_harm!(p, s, t, o)
        acc_grav!(p, s, t, o)
        return nothing
    end

    function poten_harm(p, s, t, o)
        fill!(o, 0.0)
        pot_harm!(p, s, t, o)
        pot_grav!(p, s, t, o)
        return nothing
    end

    function pot_evap_harm(p, s, t, o)
        fill!(o, 0.0)
        pot_harm!(p, s, t, o)
        return nothing
    end

    evap_harm = energy_evap(Uₜ, pot_evap_harm)

    sensor_harm = GlobalSensor()
    measure_harm = measurer(sensor_harm, 0.001, ωx, ωy, ωz, centre)

    harm_conditions = SimulationConditions(species, F, positions, velocities,
        accel_harm, poten_harm, evap = evap_harm, τ_bg = τ_bg, K = K)

    if input_dir == ""
        @info "Harmonic approximation simulation"
        final_harm_cloud = evolve(harm_conditions, duration;
            Nc = Nc, max_dt = max_dt, measure = measure_harm)
    end

    if input_dir != "" # Load results
        sensor_gb = loadsensor("$input_dir/sensor-gb-data.csv")
        sensor_harm = loadsensor("$input_dir/sensor-harm-data.csv")
    end

    # Save sensor data (before plotting, in case plotting fails)
    ft = filetime()
    dir = "./results/$ft-anu-crossbeam-trap"
    mkpath(dir)

    savecsv(sensor_gb, "$dir/sensor-gb-data.csv")
    savecsv(sensor_harm, "$dir/sensor-harm-data.csv")

    # THEORY
    # Pethick model
    peth_t, peth_N, peth_T, peth_Γ = harmonic_theory(
        species, ωx, ωy, ωz, T₀, Np, Uₜ, duration, τ_bg
    )
    # Purdue model
    pur_t, pur_N, pur_T, pur_Γ = purdue_theory(
        species, ωx, ωy, ωz, T₀, Np, Uₜ, duration, τ_bg, K
    )

    # PLOTTING
    default(fontfamily="Computer Modern",
        linewidth=2, framestyle=:box, label=nothing, grid=false)
    scalefontsizes()
    scalefontsizes(1.5)

    window_time = min(1e-2, duration/2)
    gb_time = sensor_gb.time
    harm_time = sensor_harm.time
    gb_window_size = OptEvapCool.window_time_size(gb_time, window_time)
    harm_window_size = OptEvapCool.window_time_size(harm_time, window_time)
    rolling_gb_time = rollmean(gb_time, gb_window_size)
    rolling_harm_time = rollmean(harm_time, harm_window_size)

    # Colour palette
    col1 = RGB(0.0588, 0, 0.5882)
    col2 = RGB(0.9000, 0, 0.3490)
    col3 = RGB(1, 0.6510, 0)
    col4 = RGB(0.0824, 0.8392, 0)

    # Temperature
    rolling_gb_temp = 2 / (3 * kB) * rollmean(sensor_gb.ke, gb_window_size)
    rolling_harm_temp = 2 / (3 * kB) * rollmean(sensor_harm.ke, harm_window_size)

    temp_order = -6
    temp_yformatter(y) = @sprintf("%.1f",y/(10.0^temp_order))

    temperature_plt = plot(rolling_gb_time, rolling_gb_temp,
        xlabel = "Time (s)",
        ylabel = L"\textrm{Temperature\ \ }({}\times10^{%$temp_order}\mathrm{K})",
        label = false,
        linecolor = col1,
        ls = :solid,
        ylims = (0, Inf),
        minorticks = 5,
        yformatter = temp_yformatter,
        dpi = 300)
    plot!(rolling_harm_time, rolling_harm_temp,
          label = false, linecolor = col2, ls= :dash)
    plot!(peth_t, peth_T, label = false, linecolor = col3, ls = :dot)
    plot!(pur_t, pur_T, label = false, linecolor = col4, ls = :dashdot)

    # Number
    gb_Np = sensor_gb.Nt .* sensor_gb.F
    harm_Np = sensor_harm.Nt .* sensor_harm.F

    num_order = 6
    num_yformatter(y) = @sprintf("%.1f",y/(10.0^num_order))

    number_plt = plot(gb_time, gb_Np,
        xlabel = "Time (s)",
        ylabel = L"\textrm{Number\ \ }({}\times10^{%$num_order})",
        label = false,
        linecolor = col1,
        ls = :solid,
        ylims = (0, Inf),
        minorticks = 5,
        yformatter = num_yformatter,
        dpi = 300)
    plot!(harm_time, harm_Np,
          label = false, linecolor = col2, ls= :dash)
    plot!(peth_t, peth_N, label = false, linecolor = col3, ls = :dot)
    plot!(pur_t, pur_N, label = false, linecolor = col4, ls = :dashdot)

    # Collrate
    rolling_gb_timesteps = (
        gb_time[gb_window_size:end] - gb_time[1:end - gb_window_size + 1]
    )
    rolling_harm_timesteps = (
        harm_time[harm_window_size:end] - harm_time[1:end - harm_window_size + 1]
    )
    rolling_gb_Nt = rollmean(sensor_gb.Nt, gb_window_size)
    rolling_harm_Nt = rollmean(sensor_harm.Nt, harm_window_size)
    rolling_gb_Γ = (2 * rolling(sum, sensor_gb.coll, gb_window_size) ./
        (rolling_gb_Nt .* rolling_gb_timesteps)
    )
    rolling_harm_Γ = (2 * rolling(sum, sensor_harm.coll, harm_window_size) ./
        (rolling_harm_Nt .* rolling_harm_timesteps)
    )

    collrate_plt = plot(rolling_gb_time, rolling_gb_Γ,
        xlabel = "Time (s)",
        ylabel = "Collision rate (Hz)",
        label = false,
        linecolor = col1,
        ls = :solid,
        dpi = 300)
    plot!(rolling_harm_time, rolling_harm_Γ,
          label = false, linecolor = col2, ls= :dash)
    plot!(peth_t, peth_Γ, label = false, linecolor = col3, ls = :dot)
    plot!(pur_t, pur_Γ, label = false, linecolor = col4, ls = :dashdot)

    # Phase space density
    rolling_gb_n0 = rollmean(sensor_gb.n0 .* sensor_gb.F, gb_window_size)
    rolling_harm_n0 = rollmean(sensor_harm.n0 .* sensor_harm.F, harm_window_size)

    rolling_gb_psd = rolling_gb_n0 .* (
        (2*π*h̄^2 ./ (m * kB .* rolling_gb_temp)) .^1.5
    )
    rolling_harm_psd = rolling_harm_n0 .* (
        (2*π*h̄^2 ./ (m * kB .* rolling_harm_temp)) .^1.5
    )

    psd_plt = plot(rolling_gb_time, rolling_gb_psd,
        xlabel = "Time (s)",
        ylabel = "Phase space density",
        label = false,
        linecolor = col1,
        ls = :solid,
        ylims = (0, Inf),
        minorticks = 5,
        dpi = 300)
    plot!(rolling_harm_time, rolling_harm_psd,
          label = false, linecolor = col2, ls= :dash)

    # Number vs temp.
    rolling_gb_Np = rollmean(gb_Np, gb_window_size)
    rolling_harm_Np = rollmean(harm_Np, harm_window_size)

    numtemp_plt = plot(rolling_gb_temp, rolling_gb_Np,
        xlabel = L"\textrm{Temperature\ }(\mu K)",
        ylabel = L"\textrm{Number\ \ }({}\times10^{%$num_order})",
        yformatter = num_yformatter,
        xformatter = temp_yformatter,
        label = false,
        linecolor = col1,
        ls = :solid,
        ylims = (0, Inf),
        xlims = (0, Inf),
        minorticks = 5,
        right_margin = 5mm,
        dpi = 300)
    #=
    plot!(rolling_harm_temp, rolling_harm_Np,
          label = false, linecolor = col2, ls= :dash)
    plot!(peth_T, peth_N, label = false, linecolor = col3, ls = :dot)
    plot!(pur_T, pur_N, label = false, linecolor = col4, ls = :dashdot)
    =#
    # Experimental Results
    scatter!([700e-9], [6e6], color = "black", label = false,
        markersize = 8, markershape = :utriangle)
    #vline!([2.6], line = (:black, 5))

    # Number vs PSD.
    numpsd_plt = plot(rolling_gb_psd, rolling_gb_Np,
        xlabel = "Phase space density",
        ylabel = L"\textrm{Number\ \ }({}\times10^{%$num_order})",
        yformatter = num_yformatter,
        label = false,
        linecolor = col1,
        ls = :solid,
        ylims = (0, Inf),
        xlims = (0, Inf),
        minorticks = 5,
        right_margin = 5mm,
        dpi = 300)
    # Experimental Results
    expt_T = 700e-9
    expt_N = 6e6
    expt_n0 = expt_N * ωx(1.97) * ωy(1.97) * ωz(1.97) * (m / (2π*kB*expt_T))^1.5
    expt_λdB =sqrt(2*π*h̄^2 / (m * kB * expt_T))
    expt_psd = expt_n0 * expt_λdB^3
    scatter!([expt_psd], [expt_N], color = "black", label = false,
        markersize = 8, markershape = :utriangle)
    #vline!([2.6], line = (:black, 5))

    # Save plots
    savefig(temperature_plt, "$dir/temp.png")
    savefig(collrate_plt, "$dir/collrate.png")
    savefig(number_plt, "$dir/number.png")
    savefig(psd_plt, "$dir/psd.png")
    savefig(numtemp_plt, "$dir/numtemp.png")
    savefig(numpsd_plt, "$dir/numpsd.png")

    return nothing
end


using FixedPointNumbers: Fixed

function rand_qp_data(n, q)
    Q = randn(n, n)
    Q = Q * Q'
    c = randn(n)
    G = randn(q, n)
    g = randn(q)
    QP(Q, c, G, g)
end

function rand_infeasible_qp_data(n, q)
    qp = rand_qp_data(n, q - 1)
    Q, c, G, g = qp.Q, qp.c, qp.G, qp.g
    index = rand(1 : q - 1)
    Gi = G[index, :]'
    G = [G; -Gi]
    g = [g; -g[index] - 100]
    QP(Q, c, G, g)
end

# More straightforward, less efficient implementation of the paper
function quadprog_bemporad_simple(qp::QP)
    Q, c, G, g = qp.Q, qp.c, qp.G, qp.g
    n = size(Q, 1)
    T = Float64
    L = cholesky(Hermitian(Q, :U))
    M = G / L.U
    e = (L \ c)
    d = g + G * e
    γ = 1
    A = [-M'; -d']
    b = [zeros(n); γ]
    y = nnls(A, b)
    r = A * y - b
    status = sum(abs, r) < 1e-7 ? :Infeasible : :Optimal
    z = - inv(Q) * (c + 1 / (γ + d ⋅ y) * G' * y)

    @assert isapprox(L.U' * L.U, Q; rtol = 1e-4)
    @assert isapprox(G * inv(L.U), M; rtol = 1e-4)
    @assert isapprox(g + G * (Q \ c), d; rtol = 1e-4)

    status, z
end

# Solve quadratic program with ECOS (use trick from http://www.seas.ucla.edu/~vandenbe/publications/socp.pdf)
function quadprog_ecos(qp::QP)
    Q, c, G, g = qp.Q, qp.c, qp.G, qp.g
    n = length(c)
    q = length(g)
    m = Model(solver = ECOSSolver(verbose = false))
    @variable m z[1 : n]
    constr = @constraint m G * z .<= g
    @variable m slack >= 0
    P = sqrt(Q)
    @constraint m norm(P * z + P \ c) <= slack
    @objective m Min slack
    status = solve(m, suppress_warnings = true)
    z = status == :Optimal ? getvalue(z) : fill(NaN, n)
    λ = status == :Optimal ? getdual(constr) : fill(NaN, q)
    status, z, λ
end

# Solve a quadratic program using the NNLS solver from JuMP
function qp_jump(qp::QP)
    Q, c, G, g = qp.Q, qp.c, qp.G, qp.g
    n = length(c)
    q = length(g)
    m = Model(solver=NNLS.NNLSSolver())
    @variable m z[1:n]
    constr = @constraint m G * z .<= g
    @static if VERSION < v"0.6-"
        @objective m Min 0.5 * (z' * Q * z)[1] + (c' * z)[1]
    else
        @objective m Min 0.5 * (z' * Q * z) + c' * z
    end
    status = solve(m, suppress_warnings = true)
    z = status == :Optimal ? getvalue(z) : fill(NaN, n)
    λ = status == :Optimal ? getdual(constr) : fill(NaN, q)
    status, z, λ
end

function qp_test(qp::QP)
    status_scs, z_scs, λ_scs = quadprog_ecos(qp)
    status_basic, z_basic = quadprog_bemporad_simple(qp)
    status_nnlsqp, z_nnlsqp, λ_nnlsqp = qp_jump(qp)

    work = QPWorkspace(qp)
    z, λ = solve!(work)

    work_big = QPWorkspace(QP{BigFloat}(qp))
    z_big, λ_big = solve!(work_big)

    norminf = x -> norm(x, Inf)
    @test status_scs == status_basic
    @test status_scs == work.status
    @test status_scs == status_nnlsqp
    @test status_scs == work_big.status

    if work.status == :Optimal
        @test check_optimality_conditions(qp, z, λ) <= 2e-5
        @test isapprox(z_basic, z; norm = norminf, atol = 1e-2)
        @test isapprox(z_scs, z; norm = norminf, atol = 5e-2)
        @test isapprox(λ_scs, λ; norm = norminf, atol = 5e-2)
        @test isapprox(z_scs, z_nnlsqp; norm = norminf, atol = 5e-2)
        @test isapprox(λ_scs, λ_nnlsqp; norm = norminf, atol = 5e-2)
        @test isapprox(z_scs, z_big; norm = norminf, atol = 5e-2)
        @test isapprox(λ_scs, λ_big; norm = norminf, atol = 5e-2)
    end
end

@testset "qp" begin
    Random.seed!(1)
    n, q = 100, 50
    for i = 1 : 100
        qp = rand_qp_data(n, q)
        qp_test(qp)
    end
    for j = 1 : 100
        qp = rand_infeasible_qp_data(n, q)
        qp_test(qp)
    end
    qp = rand_qp_data(n, q)
    QPWorkspace(qp)
end

@testset "qp precision" begin
    Random.seed!(2)
    n, q = 10, 5

    # Generate a random QP with the default scalar type of Float64 (equivalent to C double)
    qp_float64 = rand_qp_data(n, q)
    @test typeof(qp_float64) == QP{Float64}

    # Solve the high-precision QP and check its solution:
    z_float64, λ_float64 = solve!(QPWorkspace(qp_float64))
    @test check_optimality_conditions(qp_float64, z_float64, λ_float64) <= 1e-12

    # Generate a copy of the QP using a lower precision scalar type:
    qp_float32 = convert(QP{Float32}, qp_float64)

    # Solve the low-precision QP and check its solution:
    z_float32, λ_float32 = solve!(QPWorkspace(qp_float32))
    @test check_optimality_conditions(qp_float32, z_float32, λ_float32) <= 1e-4

    # Test whether the low-precision QP solution also satisfies the optimality
    # conditions of the high-precision QP:
    @test check_optimality_conditions(qp_float64, z_float32, λ_float32) <= 1e-4

    # Also solve the QP at very high precision, just for fun. This uses
    # Julia's built-in BigFloat:
    # https://docs.julialang.org/en/stable/stdlib/numbers/#BigFloats-1
    setprecision(BigFloat, 1000) do
        qp_big = convert(QP{BigFloat}, qp_float64)
        z_big, λ_big = solve!(QPWorkspace(qp_big))
        @test check_optimality_conditions(qp_big, z_big, λ_big) <= 1e-299
    end

    # Solve a QP using fixed point numbers from the FixedPointNumbers.jl package:
    # https://github.com/JuliaMath/FixedPointNumbers.jl
    F = Fixed{Int64, 32} # fixed-point number using 64 total bits, with 32 bits used for the fraction
    qp_fixed_64 = convert(QP{F}, qp_float64)
    z_fixed_64, λ_fixed_64 = solve!(QPWorkspace(qp_fixed_64))
    @test check_optimality_conditions(qp_fixed_64, z_fixed_64, λ_fixed_64) <= 1e-8
end


#md# The positions of words can be initialized with pre-trained word vectors so that similar words will appear near each other.
#md# ### Words
using WordCloud
stwords = ["us"];
words_weights = processtext(open(pkgdir(WordCloud)*"/res/Barack Obama's First Inaugural Address.txt"), stopwords=WordCloud.stopwords_en ∪ stwords)
words_weights = Dict(zip(words_weights...))
#md# ### Embeddings
using Embeddings
using TSne
const embtable = load_embeddings(GloVe{:en})
const get_word_index = Dict(word=>ii for (ii,word) in enumerate(embtable.vocab))
function get_embedding(word)
    ind = get_word_index[word]
    emb = embtable.embeddings[:,ind]
    return emb
end
wordvec = Dict()
for k in keys(words_weights)
    if k in keys(get_word_index)
        wordvec[k] = get_embedding(k)
    elseif lowercase(k) in keys(get_word_index)
        wordvec[k] = get_embedding(lowercase(k))
    else
        pop!(words_weights, k)
        println("remove ", k)
    end
end
embedded = tsne(hcat(values(wordvec)...)', 2)
#md# ### WordCloud
wc = wordcloud(
    words_weights,
    maskshape = box,
    masksize = (1000, 1000, 0),
    density=0.3,
    run = initwords!
)

pos = embedded
mean = sum(pos, dims=1) / size(pos, 1)
r = maximum(sqrt.(pos[:,1].^2 + pos[:,2].^2 ))
pos = (pos .- mean) ./ 2r
sz = collect(size(wc.mask))'
pos = round.(Int, pos .* sz .+ sz ./ 2)

setpositions!(wc, keys(wordvec)|>collect, eachrow(pos), type=setcenter!)
setstate!(wc, :placewords!)
generate!(wc, teleporting=false)
println("results are saved to embedding.png")
paint(wc, "embedding.png")
wc
#eval# runexample(:embedding)
#md# ![](embedding.png)


# try out some ideas for sequences
#   starting with the direct sequence
using Polylogarithms
using DataFrames, CSV
using PyPlot
using Printf
include("utilities.jl")
using SpecialFunctions

# chose a "hard value"
s = -7.46526-7.56069im
z1 = -7.60722-7.41506im
z = 1/z1
Li = 0.5783+114012.0im
result1 = polylog(s, z1; min_iterations=1000 )
result2 = Polylogarithms.polylog_series_2(s, z1; min_iterations=1000 )
error1 = Li - result1[1]
error2 = Li - result2[1]
rel_error1 = abs(error1) / abs(Li)
rel_error2 = abs(error2) / abs(Li)

k = 1:30
a = z.^k ./ k.^s
k_star = Int(ceil( real(s)/real(log(z)) ))

figure()
semilogy(k,abs.(a), "-")
plot(k, abs.(real.(a)), "-.")
plot(k, abs.(imag.(a)),"--")
plot(k_star, abs.(a[k_star]), "o")

let k=0
    tmp = z
    converged = false
    b = zeros(Complex{Float64}, size(a))
    max_iterations = length(a)
    min_iterations = k_star
    accuracy= 1.0e-12
    while k<max_iterations && ~converged
        k = k+1
        b[k+1] = b[k] + tmp
        tmp *= z * ( k/(k+1.0) )^s
        # println("   total = $total")
        if k > min_iterations && abs(tmp)/abs(b[k+1]) < 0.5*accuracy
            converged = true
        end
    end
    figure()
    plot(abs.(b[1:k]))
    println(b)
end

twoπ = 2*pi
G = (twoπ*im)^s * SpecialFunctions.zeta( 1-s, 0.5 + log(complex(-z1))/(twoπ*im) ) /  SpecialFunctions.gamma(s)


using MomentClosure, Symbolics
using Test

@variables x, y, z
@variables u, v, w
@parameters p, q, r

ps = [p]
eq = p*x
subs = Dict(x => u)
sub_eq = MomentClosure.poly_subs(eq, subs, ps)

@test isequal(sub_eq, p*u)

ps = [p, q, r]
eq = ((p*x + q*y)^2 + r*z)^2
subs = Dict(y^4 => u, x*y*z => v, z*x^2 => w)
sub_eq = MomentClosure.poly_subs(eq, subs, ps)

@test isequal(sub_eq, (p^4)*(x^4) + (q^4)*u +
                      (r^2)*(z^2) + 4p*x*(q^3)*(y^3) +
                      4q*y*(p^3)*(x^3) + 2r*(p^2)*w + 2r*z*(q^2)*(y^2) +
                      6(p^2)*(q^2)*(x^2)*(y^2) + 4p*q*r*v)

@variables x, y, z
@variables u, v, w

f = x * y
n = 2
@test isequal(0, MomentClosure.nth_differential(f, x, n))
@test isequal(u*v + v * (x - u) + u * (y - v) + (x - u)*(y - v), MomentClosure.taylor_expand(f, [x, y], [u, v], n))
f = exp(x)
n = 10
@test isequal(exp(x), MomentClosure.nth_differential(f, x, 10))
@test isequal(sum(x^i/factorial(i) for i in 0:n), MomentClosure.taylor_expand(f, [x], [0], n))

f = 1/x
for n in 1:9 # for higher values of n, numerical inaccuracies preclude comparison ...
    @test isequal(sum((-1)^i*(x-1)^i for i in 0:n), MomentClosure.taylor_expand(f, [x], [1.0], n))
end

f = log(x)
for n in 1:10
    @test isequal((-1)^(n-1)*factorial(n-1)*x^(-n), MomentClosure.nth_differential(f, x, n))
end


import AbstractAlgebra

function read_MQ_GF(filename)
    @info "Reading MQ $filename"
    polys = []
    apath = abspath("") * "benchmark\\MQ\\$filename"
    open(apath, "r") do fio
        line1, line2, line3 = readline(fio), readline(fio), readline(fio)
        ch = parse(Int, line1[findfirst('(', line1)+1:findfirst(')', line1)-1])
        nv = parse(Int, line2[findfirst(':', line2)+1:end])
        ne = parse(Int, line3[findfirst(':', line3)+1:end])

        R, xs = AbstractAlgebra.PolynomialRing(AbstractAlgebra.GF(ch), nv, ordering=:degrevlex)
        @info "Created ring" R xs

        labels = sort!(union([x*y for x in xs for y in xs], xs, [R(1)]), rev=true)

        rest = readlines(fio)
        rest = rest[5:end]

        for (i, s) in enumerate(rest)
            @info "Reading poly $i/$(length(rest))"
            s = replace(s, " ;" => "")
            poly = sum(map(x -> parse(Int, x), split(s, " ")) .* labels)
            push!(polys, poly)
        end
    end
    @info "Read" length(polys)
    R = parent(first(polys))
    map(R, polys)
end


"""
    Duality(optimizer)

Duality uses Lagrangian relaxation to compute over-approximated bounds for a network

# Problem requirement
1. Network: any depth, any activation function that is monotone
2. Input: hyperrectangle
3. Output: halfspace

# Return
`BasicResult`

# Method
Lagrangian relaxation.
Default `optimizer` is `GLPKSolverMIP()`.

# Property
Sound but not complete.

# Reference
[K. Dvijotham, R. Stanforth, S. Gowal, T. Mann, and P. Kohli,
"A Dual Approach to Scalable Verification of Deep Networks,"
*ArXiv Preprint ArXiv:1803.06567*, 2018.](https://arxiv.org/abs/1803.06567)
"""
@with_kw struct Duality{O<:AbstractMathProgSolver}
    optimizer::O = GLPKSolverMIP()
end

# can pass keyword args to optimizer
# Duality(optimizer::DataType = GLPKSolverMIP; kwargs...) =  Duality(optimizer(kwargs...))

function solve(solver::Duality, problem::Problem)
    model = Model(solver = solver.optimizer)
    c, d = tosimplehrep(problem.output)
    λ = init_multipliers(model, problem.network)
    μ = init_multipliers(model, problem.network)
    o = dual_value(solver, problem, model, λ, μ)
    @constraint(model, last(λ) .== -c)
    status = solve(model, suppress_warnings = true)
    return interpret_result(solver, status, o - d[1])
end

# False if o > 0, True if o <= 0
function interpret_result(solver::Duality, status, o)
    status != :Optimal && return BasicResult(:Unknown)
    getvalue(o) <= 0.0 && return BasicResult(:SAT)
    return BasicResult(:UNSAT)
end

function dual_value(solver::Duality,
                    problem::Problem,
                    model::Model,
                    λ::Vector{Vector{Variable}},
                    μ::Vector{Vector{Variable}})
    bounds = get_bounds(problem)
    layers = problem.network.layers
    # input layer
    o = input_layer_value(layers[1], μ[1], bounds[1])
    o += activation_value(layers[1], μ[1], λ[1], bounds[1])
    # other layers
    for i in 2:length(layers)
        o += layer_value(layers[i], μ[i], λ[i-1], bounds[i])
        o += activation_value(layers[i], μ[i], λ[i], bounds[i])
    end
    @objective(model, Min, o)
    return o
end

function activation_value(layer::Layer,
                          μᵢ::Vector{Variable},
                          λᵢ::Vector{Variable},
                          bound::Hyperrectangle)
    o = zero(eltype(μᵢ))
    b_hat = linear_transformation(layer, bound)
    l_hat, u_hat = low(b_hat), high(b_hat)
    l, u = layer.activation(l_hat), layer.activation(u_hat)

    o += sum(symbolic_max.(μᵢ.*l_hat, μᵢ.*u_hat))
    o += sum(symbolic_max.(λᵢ.*l, λᵢ.*u))

    # also possible
    # o += sum(@. symbolic_max(μᵢ*l_hat, μᵢ*u_hat))
    # o += sum(@. symbolic_max(λᵢ*l, λᵢ*u))

    # original
    # for j in 1:length(l)
    #     o += symbolic_max(μᵢ[j]*l_hat[j], μᵢ[j]*u_hat[j])
    #     o += symbolic_max(λᵢ[j]*l[j], λᵢ[j]*u[j])
    # end
    return o
end

function layer_value(layer::Layer,
                     μᵢ::Vector{Variable},
                     λᵢ::Vector{Variable},
                     bound::Hyperrectangle)
    (W, b) = (layer.weights, layer.bias)
    o = λᵢ' * bound.center - μᵢ' * (W * bound.center + b)
    # instead of for-loop:
    o += sum(symbolic_abs.(λᵢ .- W'*μᵢ) .* bound.radius) # TODO check that this is equivalent to before
    return o
end

function input_layer_value(layer::Layer,
                           μᵢ::Vector{Variable},
                           input::Hyperrectangle)
    W, b = layer.weights, layer.bias
    o = -μᵢ' * (W*input.center .+ b)
    o += sum(symbolic_abs.(μᵢ'*W) .* input.radius)   # TODO check that this is equivalent to before
    return o
end


module Hubbard
include("common.jl")
# include("hilbert.jl")
using ..Hilbert
# include("green.jl")
using ..Green
using LinearAlgebra
# import QuantumStatistics.Basis:tau2matfreq, tau2dlr

function fermiHubbard(t, U, μ, sites, bonds)
    Nsite = length(sites)
    Fock = Hilbert.BinaryFock(Nsite)
    cu⁺ = [Hilbert.creation(Fock, s, UP) for s in sites]
    cd⁺ = [Hilbert.creation(Fock, s, DOWN) for s in sites]
    cu⁻ = [c' for c in cu⁺]
    cd⁻ = [c' for c in cd⁺]
    nu = [cu⁺[s] * cu⁻[s] for s in sites]
    nd = [cd⁺[s] * cd⁻[s] for s in sites]

    K = sum([cu⁺[i] * cu⁻[j] + cd⁺[i] * cd⁻[j] for (i, j) in bonds])
    V = sum(nu[s] * nd[s] for s in sites)
    C = sum(nu[s] + nd[s] for s in sites)

    # show(stdout, "text/plain", Matrix(K))
    # println()

    H = -t * K + U * V - μ * C

    # show(stdout, "text/plain", Matrix(H))
    # println()

    return H, cu⁺, cd⁺
end

function hubbardAtom(type, U, μ, β)
    E = [0.0, -μ, -μ, U - 2μ]
    H = zeros(Float, (4, 4))
    H[diagind(H)] = E

    # |0>=|00>=1, |↑>=|10>=2, |↓>=|01>=3, |↑↓>=|11>=4
    # the first is the forck state for ↑ spin, the second is forck state for ↓

    cpup = zeros(Float, (4, 4))
    cpdown = zeros(Float, (4, 4))

    cpup[2, 1], cpup[4, 3] = 1, 1
    cpdown[3, 1], cpdown[4, 2] = 1, 1
    cmup, cmdown = cpup', cpdown'

    @assert abs(tr(cpup * cmdown)) < 1e-16
    @assert abs(tr(cpdown * cmup)) < 1e-16

    m = Model(β, H, [cpup, cpdown])
    println("U=$U, μ=$μ, β=$β")
    println("Model Hilbert space: $(m.dim)")
    println("The lowest eigen energy per site:\n$(E)")
    return m
end

function hubbardAtom2(type, t, U, μ, β)
    Nsite = 2
    bonds = [(1, 2), (2, 1)]
    sites = [s for s in 1:Nsite]

    if type == :fermi
        H, cu⁺, cd⁺ = fermiHubbard(t, U, μ, sites, bonds)
    else
        @error("Not implemented!")
    end

    m = Model(β, H, hcat(cu⁺, cd⁺))

    println("t=$t, U=$U, μ=$μ, β=$β")
    println("Model Hilbert space: $(m.dim)")
    println("The lowest eigen energy per site:\n$(m.E[1:8] / Nsite)")
    # E, U = eigen(Matrix(H))
    # println(E)
    return m
end

function hubbardAtom4(type, t, U, μ, β)
    Nsite = 4
    bonds = [(1, 2), (2, 1), (2, 3), (3, 2), (3, 4), (4, 3), (1, 4), (4, 1)]
    sites = [s for s in 1:Nsite]

    if type == :fermi
        H, cu⁺, cd⁺ = fermiHubbard(t, U, μ, sites, bonds)
    else
        @error("Not implemented!")
    end

    m = Model(β, H, hcat(cu⁺, cd⁺))

    println("t=$t, U=$U, μ=$μ, β=$β")
    println("Model Hilbert space: $(m.dim)")
    println("The lowest eigen energy per site:\n$(m.E[1:16] / Nsite)")
    # E, U = eigen(Matrix(H))
    # println(m.E[1])
    return m
end

# const m = Model(10.0, 5.0, β=10.0)
# propagator(m)
end


input = [(parse.(Int, split(pair, ','))..., ) for pair in readlines("06.input")]

manhattan_dist(a,b) = sum(abs.(a.-b))

function findmin_noties(xs, tie_sentinel=0)
    minx = first(xs)
    mini = 0
    for (i,x) in enumerate(xs)
        mini = x > minx ? mini : x == minx ? tie_sentinel : i
        minx = min(x, minx)
    end
    mini
end

function count_map(xs, n)
    counts = zeros(Int, n)
    for x in xs
        x > 0 && (counts[x] += 1)
    end
    counts
end

function star1(input)
    xs = 1:maximum(first.(input))+50
    ys = 1:maximum(last.(input))+50
    closest = [findmin_noties(manhattan_dist((x,y), i) for i in input) for x in xs, y in ys]
    dq = union(Set(closest[1:end, 1]),
               Set(closest[1:end, end]),
               Set(closest[1, 1:end]),
               Set(closest[end, 1:end]))
    delete!(dq, 0)
    counts[collect(dq)] .= 0
    findmax(counts)
end

function star2(input)
    xs = 1:maximum(first.(input))+50
    ys = 1:maximum(last.(input))+50
    sum(sum(manhattan_dist((x,y), i) for i in input)<10000 for x in xs, y in ys)
end


module FractionalDiffEq

using LinearAlgebra, SpecialFunctions, InvertedIndices, MittagLeffler

include("main.jl")
include("matrix.jl")

export FractionalDiffEqAlgorithm
export PECE, MatrixDiscrete

# Export some special equtions
export bagleytorvik

export solve, FDEProblem

end


function raw_to_df(raw_data)

    # Argument ":auto" is required to generate column names in latest version of DataFrames.jl (v1.2.1)
    df = DataFrame(raw_data[1], :auto)
    df_names = Symbol.(vcat(raw_data[2]...))
    df = DataFrames.rename(df, df_names)

    timestamps = df[!,:timestamp]

    select!(df, Not([:timestamp]))

    for col in eachcol(df)
        col = Float64.(col)
    end

    df[!,:Date] = Date.(timestamps)
    return df
end

function average_price_df(currency::String, df_in::DataFrame)

    df_out_price, df_out_candle  = [DataFrame() for i = 1:2]

    df_out_price[!,:Date] = df_in[!,:Date]

    df_out_price[!,Symbol("$currency")] = (df_in[!,Symbol("open (EUR)")] + df_in[!,Symbol("high (EUR)")] + df_in[!,Symbol("low (EUR)")] + df_in[!,Symbol("close (EUR)")])/4

    candle_col = Any[]
    for i in 1:size(df_in)[1]
        push!(candle_col, (df_in[!,Symbol("open (EUR)")][i], df_in[!,Symbol("high (EUR)")][i], df_in[!,Symbol("low (EUR)")][i], df_in[!,Symbol("close (EUR)")][i]))
    end

    df_out_candle[!,:Date] = df_in[!,:Date]

    df_out_candle[!,Symbol("$currency")] = candle_col

    return df_out_price, df_out_candle
end

function vol_df(currency::String, df_in::DataFrame)

    df_out_vol = DataFrame()

    df_out_vol[!,:Date] = df_in[!,:Date]
    df_out_vol[!,Symbol("$currency")] = df_in[!,:volume]

    return df_out_vol
end

function moving_averages(Price_df::DataFrame, duration::Int64, window::Int64)

    # Price_df should have date order - oldest to latest
    Price_col = Price_df[end-duration+1-window+1:end,2]
    rows1 = length(Price_col)
    Price_SMA, Price_WMA, Price_EMA = [Float64[] for i = 1:3]

    weights = collect(1:window)/(window*(window+1)/2)
    k = 2/(window+1)

    # Calculate different Moving Averages
    for i = 1:rows1-(window-1)
        # Simple Moving Average (SMA)
        push!(Price_SMA, mean(Price_col[i:i+(window-1)]))

        # Weighted Moving Average
        push!(Price_WMA, sum(Price_col[i:i+(window-1)].*weights))

        # Exponential Moving Average
        SMA = mean(Price_col[i:i+(window-1)])
        EMA = (Price_col[i+(window-1)]*k)+(SMA*(1-k))
        push!(Price_EMA, EMA)
    end

    return Price_SMA, Price_WMA, Price_EMA
end

function calculate_ema(Price_col::Vector{Float64}, window::Int64)

    k(window) = 2/(window+1)
	Price_EMA = Float64[]
	rows = length(Price_col)

	for i = 1:rows-(window-1)

		SMA = mean(Price_col[i:i+(window-1)])
        EMA = (Price_col[i+(window-1)]*k(window)) +
		      (SMA*(1-k(window)))
        push!(Price_EMA, EMA)
	end

	return Price_EMA
end

function calculate_macd(Price_df::DataFrame, window_long::Int64 = 26,
                        window_short::Int64 = 12, window_signal::Int64 = 9)

	# Price_df should have date order - oldest to latest
	Price_col = Price_df[!, 2]

	# Calculate 26 (long) and 12 (short) period EMA
	Price_EMA_short = calculate_ema(Price_col, window_short)
	Price_EMA_long = calculate_ema(Price_col, window_long)

	# Make EMA_long and EMA_short equal
	EMA_short_col = Price_EMA_short[window_long - window_short + 1:end]

	# Calculate MACD = EMA_short - EMA_long
	MACD_col = EMA_short_col - Price_EMA_long

	# Calculate signal line (9 period EMA of MACD)
	Signal_col = calculate_ema(MACD_col, window_signal)

	df_ema = DataFrame(date = Price_df[window_long+window_signal-1:end, :Date],
                      Raw = Price_df[window_long+window_signal-1:end, 2],
                      EMA_long = Price_EMA_long[window_signal:end],
                      EMA_short = EMA_short_col[window_signal:end],
                      MACD = MACD_col[window_signal:end], Signal = Signal_col)

	return df_ema
end


#=

"""
Return the normalized and stacked (along dim=1) power spectra of time series x in dB
as an AxisArray.
"""
function stacked_power_spectra(x, fs=1.0)
	# allocate rfft domain
	cx=rfft(x,[1]); # rfft along first dimension

	abs2.(cx) #
	for i in CartesianIndices(size(cx))
		rmul!()
		if(1≤i[1]≤nttb)
			x[i] *= sin((i[1]-1)*kb)
		end
		if(nt-ntte+1≤i[1]≤nt)
			x[i] *= sin((-i[1]+nt)*ke)
		end
	end

end
=#

function findfreq(
		  x::Array{Float64, ND},
		  tgrid;
		  attrib::Symbol=:peak,
		  threshold::Float64=-50.
		  ) where {ND}

	cx=rfft(x,[1]);
	fgrid=DSP.rfftfreq(length(tgrid),inv(step(tgrid))) # corresponding fgrid

	ax = (abs.(cx).^2); # power spectrum in dB

	if(maximum(ax) == 0.0)
		warn("x is zero"); return 0.0
	else
		ax /= maximum(ax);
		ax = 10. .* log10.(ax)
	end

	if(attrib == :max)
		iii=findlast(ax .>= threshold)
	elseif(attrib == :min)
		iii=findfirst(ax .>= threshold)
	elseif(attrib == :peak)
		iii=argmax(ax)
	end
	ii=CartesianIndices(size(ax))[iii][1]
	return fgrid[ii]

end


include("utils.jl")

@testset "Hyperbolic Space" begin
    M = Hyperbolic(2)
    @testset "Hyperbolic Basics" begin
        @test repr(M) == "Hyperbolic(2)"
        @test base_manifold(M) == M
        @test manifold_dimension(M) == 2
        @test typeof(get_embedding(M)) ==
              MetricManifold{ℝ,Euclidean{Tuple{3},ℝ},MinkowskiMetric}
        @test representation_size(M) == (3,)
        @test isinf(injectivity_radius(M))
        @test isinf(injectivity_radius(M, ExponentialRetraction()))
        @test isinf(injectivity_radius(M, [0.0, 0.0, 1.0]))
        @test isinf(injectivity_radius(M, [0.0, 0.0, 1.0], ExponentialRetraction()))
        @test !is_manifold_point(M, [1.0, 0.0, 0.0, 0.0])
        @test !is_tangent_vector(M, [0.0, 0.0, 1.0], [0.0, 0.0, 1.0, 0.0])
        @test_throws DomainError is_manifold_point(M, [2.0, 0.0, 0.0], true)
        @test !is_manifold_point(M, [2.0, 0.0, 0.0])
        @test !is_tangent_vector(M, [1.0, 0.0, 0.0], [1.0, 0.0, 0.0])
        @test Manifolds.default_metric_dispatch(M, MinkowskiMetric()) === Val{true}()
        @test_throws DomainError is_tangent_vector(
            M,
            [1.0, 0.0, 0.0],
            [1.0, 0.0, 0.0],
            true,
        )
        @test !is_tangent_vector(M, [0.0, 0.0, 1.0], [1.0, 0.0, 1.0])
        @test_throws DomainError is_tangent_vector(
            M,
            [0.0, 0.0, 1.0],
            [1.0, 0.0, 1.0],
            true,
        )
        @test is_default_metric(M, MinkowskiMetric())
        @test Manifolds.default_metric_dispatch(M, MinkowskiMetric()) === Val{true}()
        @test manifold_dimension(M) == 2

        for (P, T) in zip(
            [HyperboloidPoint, PoincareBallPoint, PoincareHalfSpacePoint],
            [HyperboloidTVector, PoincareBallTVector, PoincareHalfSpaceTVector],
        )
            p = convert(P, [1.0, 0.0, sqrt(2.0)])
            X = convert(T, [1.0, 0.0, sqrt(2.0)], [0.0, 1.0, 0.0])
            @test number_eltype(p) == eltype(p.value)
            @test X * 2.0 == T(X.value * 2.0)
            @test 2 \ X == T(2 \ X.value)
            @test +X == T(+X.value)
            @test Manifolds.allocate_result_type(M, log, (p, p)) == T
            @test Manifolds.allocate_result_type(M, inverse_retract, (p, p)) == T
            convert(T, p, X) == X
        end
    end
    @testset "Hyperbolic Representation Conversion I" begin
        p = [0.0, 0.0, 1.0]
        pH = HyperboloidPoint(p)
        @test minkowski_metric(pH, pH) == minkowski_metric(p, p)
        @test convert(HyperboloidPoint, p).value == pH.value
        @test convert(AbstractVector, pH) == p
        X = [1.0, 0.0, 0.0]
        XH = HyperboloidTVector(X)

        @test convert(HyperboloidTVector, X).value == XH.value
        @test convert(AbstractVector, XH) == X
        @test convert(HyperboloidPoint, p).value == pH.value
        is_manifold_point(M, pH)
        pB = convert(PoincareBallPoint, p)
        @test pB.value == convert(PoincareBallPoint, pH).value
        @test is_manifold_point(M, pB)
        @test convert(AbstractVector, pB) == p # convert back yields again p
        @test convert(HyperboloidPoint, pB).value == pH.value
        @test_throws DomainError is_manifold_point(
            M,
            PoincareBallPoint([0.9, 0.0, 0.0]),
            true,
        )
        @test_throws DomainError is_manifold_point(M, PoincareBallPoint([1.0, 0.0]), true)

        @test is_tangent_vector(M, pB, PoincareBallTVector([2.0, 2.0]))

        pS = convert(PoincareHalfSpacePoint, p)
        pS2 = convert(PoincareHalfSpacePoint, pB)
        pS3 = convert(PoincareHalfSpacePoint, pH)

        @test_throws DomainError is_manifold_point(
            M,
            PoincareHalfSpacePoint([0.0, 0.0, 1.0]),
            true,
        )
        @test_throws DomainError is_manifold_point(
            M,
            PoincareHalfSpacePoint([0.0, -1.0]),
            true,
        )

        @test pS.value == pS2.value
        @test pS.value == pS3.value
        @test convert(AbstractVector, pS) == convert(HyperboloidPoint, pS).value
        @test convert(PoincareBallPoint, pS2).value == pB.value
    end
    @testset "Hyperbolic Representation Conversion II" begin
        M = Hyperbolic(2)
        pts = [[0.0, 0.0, 1.0], [1.0, 0.0, sqrt(2.0)]]
        X = log(M, pts[2], pts[1])
        # For HyperboloidTVector we can do a plain wrap/unwrap
        X1 = convert(HyperboloidTVector, X)
        @test convert(AbstractVector, X1) == X
        # Convert to types and back to Array
        for (P, T) in zip(
            [HyperboloidPoint, PoincareBallPoint, PoincareHalfSpacePoint],
            [HyperboloidTVector, PoincareBallTVector, PoincareHalfSpaceTVector],
        )
            # convert to P,T
            p1 = convert(P, pts[2])
            X1 = convert(T, pts[2], X)
            (p2, X2) = convert(Tuple{P,T}, (pts[2], X))
            @test isapprox(M, p1, p2)
            @test isapprox(M, p1, X1, X2)
            for (P2, T2) in zip(
                [HyperboloidPoint, PoincareBallPoint, PoincareHalfSpacePoint],
                [HyperboloidTVector, PoincareBallTVector, PoincareHalfSpaceTVector],
            )
                @test isapprox(M, convert(P2, p1), convert(P2, pts[2]))
                @test convert(T, p1, X1) == convert(T, pts[2], X)
                (p3, X3) = convert(Tuple{P2,T2}, (pts[2], X))
                (p3a, X3a) = convert(Tuple{P2,T2}, (p1, X1))
                @test isapprox(M, p3, p3a)
                @test isapprox(M, p3, X3, X3a)
                @test isapprox(M, convert(P2, p2), p3)
                @test isapprox(M, pts[2], convert(AbstractVector, p3))
                @test isapprox(M, p3, convert(T2, p2, X2), X3)
                # coupled conversion
                (pT, XT) = convert(Tuple{AbstractVector,AbstractVector}, (p2, X2))
                @test isapprox(M, pts[2], pT)
                @test isapprox(M, pts[2], X, XT)
            end
        end
    end

    types = [
        Vector{Float64},
        SizedVector{3,Float64},
        HyperboloidPoint,
        PoincareBallPoint,
        PoincareHalfSpacePoint,
    ]
    TEST_FLOAT32 && push!(types, Vector{Float32})
    for T in types
        @testset "Type $T" begin
            is_plain_array =
                T ∉ [HyperboloidPoint, PoincareBallPoint, PoincareHalfSpacePoint]
            pts = [
                convert(T, [0.0, 0.0, 1.0]),
                convert(T, [1.0, 0.0, sqrt(2.0)]),
                convert(T, [0.0, 1.0, sqrt(2.0)]),
            ]
            test_manifold(
                M,
                pts,
                test_project_tangent=is_plain_array || T == HyperboloidPoint,
                test_musical_isomorphisms=is_plain_array,
                test_default_vector_transport=true,
                vector_transport_methods=[
                    ParallelTransport(),
                    SchildsLadderTransport(),
                    PoleLadderTransport(),
                ],
                is_tangent_atol_multiplier=10.0,
                exp_log_atol_multiplier=10.0,
                retraction_methods=(ExponentialRetraction(),),
                test_vee_hat=false,
                test_forward_diff=is_plain_array,
                test_reverse_diff=is_plain_array,
                test_tangent_vector_broadcasting=is_plain_array,
                test_vector_spaces=is_plain_array,
            )
        end
    end
    @testset "Embedding test" begin
        p = [0.0, 0.0, 1.0]
        X = [1.0, 1.0, 0.0]
        @test embed(M, p) == p
        q = similar(p)
        embed!(M, q, p)
        @test q == p
        @test embed(M, p, X) == X
        Y = similar(X)
        embed!(M, Y, p, X)
        @test Y == X
        p2 = HyperboloidPoint(p)
        X2 = HyperboloidTVector(X)
        q2 = HyperboloidPoint(similar(p))
        @test embed(M, p2).value == p2.value
        embed!(M, q2, p2)
        @test q2.value == p2.value
        @test embed(M, p2, X2).value == X2.value
        Y2 = HyperboloidTVector(similar(X))
        embed!(M, Y2, p2, X2)
        @test Y2.value == X2.value
    end
    @testset "Hyperbolic mean test" begin
        pts = [
            [0.0, 0.0, 1.0],
            [1.0, 0.0, sqrt(2.0)],
            [-1.0, 0.0, sqrt(2.0)],
            [0.0, 1.0, sqrt(2.0)],
            [0.0, -1.0, sqrt(2.0)],
        ]
        ws = UnitWeights{Float64}(length(pts))
        @test isapprox(M, mean(M, pts), pts[1]; atol=10^-4)
        @test isapprox(M, mean(M, pts, ws), pts[1]; atol=10^-4)
        @test_throws DimensionMismatch mean(M, pts, UnitWeights{Float64}(length(pts) + 1))
    end
    @testset "Hyperbolic ONB test" begin
        M = Hyperbolic(2)
        p = Manifolds._hyperbolize(M, [1.0, 0.0])
        B = get_basis(M, p, DefaultOrthonormalBasis())
        V = get_vectors(M, p, B)
        for v in V
            @test is_tangent_vector(M, p, v, true)
            for b in [DefaultOrthonormalBasis(), DiagonalizingOrthonormalBasis(V[1])]
                @test isapprox(M, p, v, get_vector(M, p, get_coordinates(M, p, v, b), b))
            end
        end
        for v in V, w in V
            @test inner(M, p, v, w) ≈ (v == w ? 1 : 0)
        end
        X = 0.5 * V[1] + 1.0 .* V[2]
        @test is_tangent_vector(M, p, X)
        c = get_coordinates(M, p, X, B)
        @test c ≈ [0.5, 1.0]
        B2 = DiagonalizingOrthonormalBasis(X)
        V2 = get_vectors(M, p, get_basis(M, p, B2))
        @test V2[1] ≈ X ./ norm(M, p, X)
        @test inner(M, p, V2[1], V2[2]) ≈ 0.0 atol = 5e-16
        B3 = DiagonalizingOrthonormalBasis(-V[2])
        V3 = get_vectors(M, p, get_basis(M, p, B3))
        @test isapprox(M, p, V3[1], -V[2])
    end
    @testset "Hyperboloid Minkowski Metric tests" begin
        p = Manifolds._hyperbolize(M, [0.0, 1.0])
        X = [1.0, 0.0, 0.0]
        pH = HyperboloidPoint(p)
        XH = HyperboloidTVector(X)
        @test minkowski_metric(XH, pH) == minkowski_metric(X, p)
        @test minkowski_metric(pH, XH) == minkowski_metric(p, X)
        @test minkowski_metric(XH, XH) == minkowski_metric(X, X)
    end
    @testset "PoincareBall project tangent test" begin
        p = PoincareBallPoint([0.5, 0.0])
        X = PoincareBallTVector([0.2, 0.2])
        Y = PoincareBallTVector(allocate(X.value))
        project!(M, Y, p, X)
        @test Y == X
        Z = project(M, p, X)
        @test isa(Z, PoincareBallTVector)
        @test Z == X
        p2 = PoincareHalfSpacePoint([1.1, 1.1])
        X2 = PoincareHalfSpaceTVector([0.2, 0.2])
        Y2 = PoincareHalfSpaceTVector(allocate(X2.value))
        project!(M, Y2, p2, X2)
        @test Y2 == X2
        Z2 = project(M, p2, X2)
        @test isa(Z2, PoincareHalfSpaceTVector)
        @test Z2 == X2
    end
end


using Polynomials
using Test
using LinearAlgebra

@testset "Basic constructor, arithmetic" begin

    p,q = fromroots(Polynomial, [1,2,3]), fromroots(Polynomial, [2,3,4])
    r,s = SparsePolynomial([1,2,3], :x), SparsePolynomial([1,2,3], :y)
    t,u = Polynomial([1,2,pi]), SparsePolynomial([1.1, 2.2, 3.4], :y)

    # constructor
    @test p // q isa RationalFunction
    @test p // r isa RationalFunction
    @test_throws ArgumentError r // s
    @test RationalFunction(p) == p // one(p)

    # We expect p::P // q::P (same type polynomial).
    # As Immutable Polynomials have N as type parameter, we disallow
    @test_throws ArgumentError variable(ImmutablePolynomial) // variable(ImmutablePolynomial)

    pq = p // t # promotes to type of t
    @test pq isa RationalFunction{Float64, :x}

    # iterations, broadcast
    pp,qq = p // q
    @test (pp,qq) == (p,q)
    @test eltype(p//q) == typeof(pp)
    u = gcd(p//q...)
    @test u/u[end] ≈ fromroots(Polynomial, [2,3])
    @test degree.(p//q) == degree(p//q) # no broadcast over rational functions

    # evaluation
    pq = p//q
    @test pq(2.5) ≈ p(2.5) / q(2.5)
    @test pq(2) ≈ fromroots([1,3])(2) / fromroots([3,4])(2)
    @test zero(pq)(10) == 0
    @test one(pq)(10) == 1

    # arithmetic
    rs = r // (r-1)
    x = 1.2
    @test (pq + rs)(x) ≈ (pq(x) + rs(x))
    @test (pq * rs)(x) ≈ (pq(x) * rs(x))
    @test (-pq)(x) ≈ -p(x)/q(x)
    @test pq .* (pq, pq) == (pq*pq, pq*pq)
    @test pq .* [pq pq] == [pq*pq pq*pq]

    # derivative
    pq = p // one(p)
    x = 1.23
    @test derivative(pq)(x) ≈ derivative(p)(x)
    pq = p // q
    dp,dq = derivative.((p,q))
    @test derivative(pq)(x) ≈ (dp(x)*q(x) - p(x)*dq(x))/q(x)^2

    # integral
    pq = p // (2*one(p))
    @test iszero(derivative(integrate(pq)) - pq)
    pq = p // q
    @test_throws ArgumentError integrate(pq) # need logs terms in general

    # lowest terms
    pq = p // q
    pp, qq = Polynomials.pqs(lowest_terms(pq))
    @test all(abs.(pp.(roots(qq))) .> 1/2)

    # ≈
    @test (2p)//(2one(p)) ≈ p//one(p)
    @test p//one(p) ≈  p//one(p) + eps()
    x = variable(p)
    @test (x*p)//x ≈ p // one(p)

end

@testset "zeros, poles, residues" begin
    x = variable(Polynomial{Float64})
    p,q = x^7,(x^5 + 4*x^4 + 7*x^3 + 8*x^2 + 5*x + 2)
    pq = p // q

    ## zeros
    zs = roots(pq)
    @test length(zs.zs) == 1
    @test zs.multiplicities == [7]

    ## poles
    ps = poles(pq)
    @test length(ps.zs) == 3

    ## residues
    d,r = residues(pq)
    @test d ≈ 9 - 4x + x^2

    z = variable(pq)
    for (λ, rs) ∈ r # reconstruct p/q from output of `residues`
        for (i,rᵢ) ∈ enumerate(rs)
            d += rᵢ/(z-λ)^i
        end
    end
    @test norm(numerator(lowest_terms(d - pq)), Inf) <= sqrt(eps())

end

@testset "As matrix elements" begin

    p, q = Polynomial([1,2], :x), Polynomial([1,2],:y)
    pp = p // (p-1)
    PP = typeof(pp)

    r, s = SparsePolynomial([1,2], :x), SparsePolynomial([1,2],:y)
    rr = r // (r-1)

    ## T, Polynomial{T} promotes
    @test eltype([1, p, pp]) == PP
    @test eltype(eltype(eltype([im, p, pp]))) == Complex{Int}

    ## test mixed types promote polynomial type
    @test eltype([pp rr p r]) == PP

    ## test non-constant polys of different symbols throw error
    @test_throws ArgumentError [pp, q]
    @test_throws ArgumentError [pp, q//(q-1)]

    ## constant polys will work -- if specified
    @test eltype(PP[pp one(q)]) == PP
    @test eltype(PP[pp one(q//(q-1))]) == PP

    ## if not specified, the rational function will not promote
    @test eltype([pp one(q)]) == PP
    @test_throws ArgumentError [pp one(q//(q-1))]

end

@testset "Rational function fit" begin

    p,q = Polynomial([1,1,1]), Polynomial([1,2])
    xs = range(-0.25,stop=1, length=15)
    ys = (p//q).(xs)
    pq = fit(RationalFunction, xs, ys, 2, 2)
    @test numerator(pq) ≈ p
    @test denominator(pq) ≈ q

    x = variable(Polynomial{Float64})
    pq = (1+x)//(1-x)
    xs = 2.0:.1:3;
    ys = pq.(xs);
    v = fit(RationalFunction, xs, ys, 2, 2)
    @test maximum(abs, v(x)-pq(x) for x ∈ 2.1:0.1:3.0) <= sqrt(eps())

end

@testset "Pade fit" begin

    #Tests for Pade approximants from Pade.jl

    # exponential
    a = Polynomial( 1 .// factorial.(big(0):17) )
    pq = fit(RationalFunction,a,8,8)
    @test pq(1.0) ≈ exp(1.0)
    @test pq(-1.0) ≈ exp(-1.0)

    # sine
    b = Polynomial(Int.(sinpi.((0:16)/2)) .// factorial.(big(0):16))
    pq = fit(RationalFunction, b, 7, 8)
    @test pq(1.0) ≈ sin(1.0)
    @test pq(-1.0) ≈ sin(-1.0)

    # cosine
    c = Polynomial(Int.(sinpi.((1:17)/2)) .// factorial.(big(0):16))
    pq = fit(RationalFunction, c, 8, 8)
    @test pq(1.0) ≈ cos(1.0)
    @test pq(-1.0) ≈ cos(-1.0)

    # summation of a factorially divergent series
    d = Polynomial( (-big(1)).^(0:60) .* factorial.(big(0):60) )
    pq = fit(RationalFunction, d, 30, 30)
    @test pq(1.0) ≈ exp(1)*(-Base.MathConstants.γ-sum([(-1).^k/k./factorial(k) for k=1:20]))

end