### A Pluto.jl notebook ###
# v0.12.6

using Markdown
using InteractiveUtils

# ╔═╡ 7ccb2b9c-1ecd-11eb-205e-11296dd5bb76
using Pkg, DrWatson

# ╔═╡ 855951b0-1ecd-11eb-0108-71f29ba2082f
begin
	@quickactivate "StatisticsWithJuliaPlutoNotebooks"
	using StatsBase, Plots
end

# ╔═╡ 54e90298-1ecd-11eb-01d6-714cdcc0e877
md"## Listing 2.4"

# ╔═╡ 97360536-1ecd-11eb-36d6-038ba8a75dc5
function proportionFished(gF, sF, n, N, withReplacement = false)

    function fishing()
        fishInPond = [ones(Int64,gF); zeros(Int64,sF)]
        fishCaught = Int64[]

        for fish in 1:n
            fished = rand(fishInPond)
            push!(fishCaught,fished)
            if withReplacement == false
                deleteat!(fishInPond, findfirst(x->x==fished, fishInPond))
            end
        end
        sum(fishCaught)
    end

    simulations = [fishing() for _ in 1:N]
    proportions = counts(simulations, 0:n)/N

    if withReplacement
        plot!(0:n, proportions,
             line=:stem, marker=:circle, c=:blue, ms=6, msw=0,
             label="With replacement",
             xlabel="n",
             ylims=(0, 0.6), ylabel="Probability")
    else
        plot!(0:n, proportions,
            line=:stem, marker=:xcross, c=:red, ms=6, msw=0,
            label="Without replacement")
    end
end

# ╔═╡ f505b35c-1ecd-11eb-1eef-6fe09900d4be
begin
	N = 10^6
	goldFish, silverFish, n = 3, 4, 3

	p = plot()
	proportionFished(goldFish, silverFish, n, N)
	proportionFished(goldFish, silverFish, n, N, true)
	p
end

# ╔═╡ f506085a-1ecd-11eb-1e55-db1a82f7bf85
md"## End of listing 2.4"

# ╔═╡ Cell order:
# ╟─54e90298-1ecd-11eb-01d6-714cdcc0e877
# ╠═7ccb2b9c-1ecd-11eb-205e-11296dd5bb76
# ╠═855951b0-1ecd-11eb-0108-71f29ba2082f
# ╠═97360536-1ecd-11eb-36d6-038ba8a75dc5
# ╠═f505b35c-1ecd-11eb-1eef-6fe09900d4be
# ╟─f506085a-1ecd-11eb-1e55-db1a82f7bf85


using LinearAlgebra, Random

"""
    generate_procrustes_data(m, n, d, σ=0.1, T=Float64)

Generate random (generalized) Procrustes data.

# Input
- `m :: Integer`: number of images.
- `n :: Integer`: number of image landmarks.
- `d :: Integer`: dimension of image (landmark points).
- `σ :: Number = 0.1`: noise level adding to each rotated image.
- `T :: DataType = Float64`: data type.

# Output
- `A :: Vector{Matrix{T}}`: `m` rotated images.
- `S :: Matrix{Matrix{T}}`: matrix of `S[i, j]` for OTSM problem.
- `Ā :: Matrix{T}`: the true/center image.
- `A_manopt :: Array{T, 3}`: 3D array of data suitable for Manopt software.
"""
function generate_procrustes_data(
    m :: Integer,
    n :: Integer,
    d :: Integer,
    σ :: Number   = 0.1,
    T :: DataType = Float64
    )
    # true center image
    Ā = randn(T, n, d)
    # randomly rotated images
    A = Vector{Matrix{T}}(undef, m)
    for k in 1:m
        # Haar measure (uniform) on O(n)
        # M with iid std normal entries
        # M = QR
        Q, R = qr!(randn(T, d, d))
        O = Matrix(Q)
        # In Julia, diagonal entries of R not necessarily positive
        for j in 1:d
            R[j, j] < 0 && (O[:, j] *= -1)
        end
        # now O is uniform on O(n), not on SO(n)
        # if det(O)=-1 (reflection), swap the first two columns
        det(O) < 0 && (O[:, [1, 2]] = O[:, [2, 1]])
        # rotate each point (row) in A
        A[k] = Ā * transpose(O) + σ * randn(T, n, d)
    end
    # set S[i, j]
    S = [A[i]'A[j] for i in 1:m, j in 1:m]
    for i in 1:m
        fill!(S[i, i], 0)
    end
    A_manopt = Array{T, 3}(undef, d, n, m)
    for k in 1:m
        A_manopt[:, :, k] = transpose(A[k])
    end
    A, S, Ā, A_manopt
end


"""
    simpsons_rule(x::Vector, y::Vector)

Numerical integration via Simpson's rule.

Lengths of vectors must be equal to each other, and be odd (to have an even number of intervals).
Values in `x` represent abcissa values, while `y` are the integrand counterparts.
"""
function simpsons_rule(x::Vector, y::Vector)
    n = length(y)-1
    n % 2 == 0 || error("`y` length (number of intervals) must be odd")
    length(x)-1 == n || error("`x` and `y` length must be equal")
    h = (x[end]-x[1])/n
    @inbounds @views s = sum(y[1:2:n] .+ 4y[2:2:n] .+ y[3:2:n+1])
    return h/3 * s
end

"""
    trapezoidal_rule(x::Vector, y::Vector)

Numerical integration via the Trapezoidal rule.

Lengths of vectors must be equal to each other.
Values in `x` represent equally-spaced abcissa values, while `y` are the integrand counterparts.
"""
function trapezoidal_rule(x::Vector, y::Vector)
    return (x[2] - x[1]) * ( sum(y) - (y[1] + y[end])/2 )
end

"""
    trapezoidal(fun::Function, n, flim...)

Applies the `trapezoidal_rule` using `n` points over the frequency intervals specified by `flim...`.
"""
function trapezoidal(fun::Function, n, flim...)
    ii = 0.0
    for i in 2:length(flim)
        xi = collect(range(flim[i-1], stop=flim[i], length=n))
        yi = fun.(xi)
        ii += trapezoidal_rule(xi, yi)
    end
    return ii
end

"""
    gauss_interval(integrand::Function, n, fmin, fmax)

Computes Gauss-Legendre integration using `n` nodes and weights over the intervals `[fmin,fmax]`.
"""
function gauss_interval(integrand::Function, n, fmin, fmax)
    xi, wi = gausslegendre(n)
    ifi = @. integrand( (fmax-fmin)/2 * xi + (fmin+fmax)/2 )
    return (fmax-fmin)/2 * dot( wi, ifi )
end

"""
    gauss_intervals(fun::Function, n, flim...)

Computes Gauss-Legendre integration using `n` nodes and weights of the intervals specified in `flim...`.
"""
function gauss_intervals(fun::Function, n, flim...)
    xi, wi = gausslegendre(n)
    ii = 0.0
    for i in 2:length(flim)
        ii += (flim[i]-flim[i-1])/2 * dot( wi, fun.( (flim[i]-flim[i-1])/2 * xi .+ (flim[i]+flim[i-1])/2 ) )
    end
    return ii
end


#= This is one of the famous Interview Problems. You are given
an array of numbers, treat this array as stone heights. When the rainfalls
the water get's filled inside the stone gaps. You need to calculate the amount
of water which will get trapped inside the stones.
The code calculates the answer in the best time complexity.
=#

## Function

function rainWater(n, array)
    left = zeros(n)
    left[1] = array[1]
    for i = 2:n
        left[i] = max(left[i-1], array[i])
    end
    right = zeros(n)
    right[n] = array[n]
    for i = n-1:-1:1
        right[i] = max(right[i+1], array[i])
    end
    total = 0
    for i = 1:n
        total += min(left[i], right[i]) - array[i]
    end
    return total
end

## Input

println("Enter the length of array!")
n = readline()
n = parse(Int64, n)
array = Int64[]
println("Enter the array !")
for i = 1:n
    temp = readline()
    temp = parse(Int64, temp)
    push!(array, temp)
end

## Calling the function

rainWater(n, array)

#=
Sample Test Case:
Input:
    n = 3
    array = 2 0 2
Output:
    2

Time complexity: O ( N )
=#


"""
    NegativeBinomialLikelihood(l=logistic; successes::Real=1)

Negative binomial likelihood with number of successes `successes`.

```math
    p(k|successes, f) = \\frac{\\Gamma(k+successes)}{k! \\Gamma(successes)} l(f)^successes (1 - l(f))^k
```
On calling, this returns a negative binomial distribution with `successes` successes and
probability of success equal to `l(f)`.

!!! warning "Parameterization"
    The parameter `successes` is different from the parameter `r` in the
    [Wikipedia definition](http://en.wikipedia.org/wiki/Negative_binomial_distribution),
    which denotes the number of failures.
    This parametrization is used in order to stay consistent with the parametrization in
    [Distributions.jl](https://juliastats.org/Distributions.jl/stable/univariate/#Distributions.NegativeBinomial).
    To use the Wikipedia definition, set `successes` as the number of "failures" and
    change the probability of success from `l(f)` to `1 - l(f)`.
    Note that with symmetric functions like the [`LogisticLink`](@ref), this corresponds to
    using `l(-f)`.
"""
struct NegativeBinomialLikelihood{Tl<:AbstractLink,T<:Real} <: AbstractLikelihood
    successes::T    # number of successes parameter
    invlink::Tl
end

function NegativeBinomialLikelihood(l=logistic; successes::Real=1)
    return NegativeBinomialLikelihood(successes, link(l))
end

@functor NegativeBinomialLikelihood

(l::NegativeBinomialLikelihood)(f::Real) = NegativeBinomial(l.successes, l.invlink(f))

(l::NegativeBinomialLikelihood)(fs::AbstractVector{<:Real}) = Product(map(l, fs))


module _CoSiEn
export CoSiEn
using Statistics: std, mean, median
using LinearAlgebra: Diagonal, UpperTriangular
    """
        CoSi, Bm = CoSiEn(Sig)

    Returns the cosine similarity entropy (`CoSi`) and the corresponding
    global probabilities estimated from the data sequence (`Sig`) using the
    default parameters:   embedding dimension = 2, time delay = 1,
    angular threshold = .1,  logarithm = base 2,

        CoSi, Bm = CoSiEn(Sig::AbstractArray{T,1} where T<:Real; m::Int=2, tau::Int=1, r::Real=.1, Logx::Real=2, Norm::Int=0)

    Returns the cosine similarity entropy (`CoSi`) estimated from the data
    sequence (`Sig`) using the specified 'keyword' arguments:

    # Arguments:
    `m`     - Embedding Dimension, an integer > 1   \n
    `tau`   - Time Delay, a positive integer    \n
    `r`     - Angular threshold, a value in range [0 < r < 1]   \n
    `Logx`  - Logarithm base, a positive scalar (enter 0 for natural log) \n
    `Norm`  - Normalisation of `Sig`, one of the following integers:    \n
            [0]  no normalisation - default
            [1]  normalises `Sig` by removing median(`Sig`)
            [2]  normalises `Sig` by removing mean(`Sig`)
            [3]  normalises `Sig` w.r.t. SD(`Sig`)
            [4]  normalises `Sig` values to range [-1 1]

    # See also `PhasEn`, `SlopEn`, `GridEn`, `MSEn`, `cMSEn`

    # References:
        [1] Theerasak Chanwimalueang and Danilo Mandic,
            "Cosine similarity entropy: Self-correlation-based complexity
            analysis of dynamical systems."
            Entropy
            19.12 (2017): 652.

    """
    function CoSiEn(Sig::AbstractArray{T,1} where T<:Real; m::Int=2, tau::Int=1,
        r::Real=.1, Logx::Real=2, Norm::Int=0)

    Logx == 0  ? Logx = exp(1) : nothing

    N = size(Sig,1)
    (N > 10) ? nothing : error("Sig:   must be a numeric vector")
    (m > 1) ? nothing :  error("m:     must be an integer > 1")
    (tau>0) ? nothing :  error("tau:   must be an integer > 0")
    (0<r<1) ? nothing :  error("r:     must be a scalar in range [0 1]")
    (Logx>0) ? nothing : error("Logx:  must be a positive number > 0")
    (Norm in collect(0:4)) ? nothing : error("Norm:   must be an integer in range [0 4]")

    if Norm == 1
        Xi = Sig .- median(Sig);
    elseif Norm == 2
        Xi = Sig .- mean(Sig);
    elseif Norm == 3
        Xi = (Sig .- mean(Sig))/std(Sig,corrected=false)
    elseif Norm == 4
        Xi = (2*(Sig .- minimum(Sig))/(maximum(Sig)-minimum(Sig))) .- 1;
    else
        Xi = Sig;
    end

    Nx = N-((m-1)*tau);
    Zm = zeros(Nx,m);
    for n = 1:m
        Zm[:,n] = Xi[(n-1)*tau+1:Nx+(n-1)*tau]
    end

    Num = Zm*transpose(Zm);
    Mag = sqrt.(sum(Diagonal(Num),dims=1))[:]
    Den = Mag*transpose(Mag)
    AngDis = round.(acos.(round.(Num./Den,digits=6))/pi,digits=6)
    if maximum(imag.(AngDis)) < (10^-5)
        Bm = (sum(UpperTriangular(AngDis .< r))-Nx)/(Nx*(Nx-1)/2)
    else
        Bm = (sum(UpperTriangular(real.(AngDis) .< r))-Nx)/(Nx*(Nx-1)/2)
        @warn("Complex values ignored.")
    end
    if Bm == 1 || Bm == 0
        CoSi = 0
    else
        CoSi = -(Bm*log(Logx, Bm)) - ((1-Bm)*log(Logx, 1-Bm))
    end

    return CoSi, Bm
    end

end

"""
Copyright 2021 Matthew W. Flood, EntropyHub

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

For Terms of Use see https://github.com/MattWillFlood/EntropyHub
"""


#= Sigmoid.jl

a quick script to perform a sigmoid transformation using the equation:

σ(X) = 1 /(1 + ℯ ^ vX)

function:
    sigmoid(signal;v)

where signal is the input timeseries and the parameter v determines the how shallow /
steep the sigmoid curve will be. Smaller values of v flatten the curve

From França et al (2018; https://doi.org/10.3389/fphys.2018.01767):
    The parameter v was chosen based on its effect on the estimated multifractal width for
    three types of time series: icEEG (NHNN1-channel 1), surrogate EEG (temporally
    shuffled values of the original time series from NHNN1-channel 1) and a simulated
    random series (with the same mean and variance), across the range v = [0.1, 2.0]
    in steps of 0.1. To find the optimal value for the parameter v, we needed to balance
    the trade-off between the three series in terms of presenting the most distinct Δα
    values (Appendix Figure E3A in Supplementary Material), while showing minimum
    distortion on the recording, or maximum correlation with the original time series

=#

function sigmoid(signal; v)
    sigmoid_transform = 1 ./(1 .+ ℯ .^(v .* signal))
    return sigmoid_transform
end


# coding: utf-8

# In[1]:

#fractal is one of the interesting topics in geometry
#it is usually described by a recursive function
#voila,here we are!
using Plots

# In[2]:

#create rectangle shape
rectangle(top_left,top_right,bottom_left,
        bottom_right)=Shape([(top_left[1],top_left[2]),
        (top_right[1],top_right[2]),
        (bottom_right[1],bottom_right[2]),
        (bottom_left[1],bottom_left[2])])

# In[3]:

#compute euclidean distance
function euclidean_distance(point1,point2)
    return √((point1[1]-point2[1])^2+(point1[2]-point2[2])^2)
end

# In[4]:

#simple solution to get coefficients of the equation
function get_line_params(x1,y1,x2,y2)

    slope=(y1-y2)/(x1-x2)
    intercept=y1-slope*x1

    return slope,intercept

end

# In[5]:

#standard solution to quadratic equation
function solve_quadratic_equation(A,B,C)
    x1=(-B+√(B^2-4*A*C))/(2*A)
    x2=(-B-√(B^2-4*A*C))/(2*A)
    return [x1,x2]
end

# In[6]:

#analytic geometry to compute target datapoints
function get_datapoint(pivot,measure,length,direction="inner")

    #for undefined slope
    if pivot[1]==measure[1]
        y1=pivot[2]+length
        y2=pivot[2]-length
        x1=pivot[1]
        x2=pivot[1]

    #for general cases
    else

        #get line equation
        slope,intercept=get_line_params(pivot[1],pivot[2],
                                   measure[1],measure[2],)

        #solve quadratic equation
        A=1
        B=-2*pivot[1]
        C=pivot[1]^2-length^2/(slope^2+1)
        x1,x2=solve_quadratic_equation(A,B,C)

        #get y from line equation
        y1=slope*x1+intercept
        y2=slope*x2+intercept

    end

    if direction=="inner"

        #take the one between pivot and measure points
        if euclidean_distance((x1,y1),measure)<euclidean_distance((x2,y2),measure)
            datapoint=(x1,y1)
        else
            datapoint=(x2,y2)
        end

    else

        #take the one farther away from measure points
        if euclidean_distance((x1,y1),measure)>euclidean_distance((x2,y2),measure)
            datapoint=(x1,y1)
        else
            datapoint=(x2,y2)
        end

    end

    return datapoint

end

# In[7]:

#recursively plot symmetric pythagorean tree at 45 degree
# https//larryriddle.agnesscott.org/ifs/pythagorean/pythTree.htm
function pythagorean_tree(top_left,top_right,bottom_left,
        bottom_right,current_angle,line_len,n)

    #plot square
    plot!(rectangle(top_left,top_right,bottom_left,bottom_right))

    if n==0
        return
    else

        #find mid point
        #midpoint has to satisfy two conditions
        #it has to be on the same line as bottom_left and bottom_right
        #assume this line follows y=kx+b
        #the midpoint is (x,kx+b)
        #bottom_left is (α,kα+b),bottom_right is (δ,kδ+b)
        #the euclidean distance between midpoint and bottom_left should be
        #half of the euclidean distance between bottom_left and bottom_right
        #(x-α)**2+(kx+b-kα-b)**2=((α-δ)**2+(kα+b-kδ-b)**2)/4
        #apart from x,everything else in the equation is constant
        #this forms a simple quadratic solution to get two roots
        #one root would be between bottom_left and bottom_right which yields midpoint
        #and the other would be farther away from bottom_right
        #this function solves the equation via (-B+(B**2-4*A*C)**0.5)/(2*A)
        #alternatively,you can use scipy.optimize.root
        #the caveat is it does not offer both roots
        #a wrong initial guess could take you to the wrong root
        bottom_mid=get_datapoint(bottom_left,bottom_right,line_len/2)
        top_mid=get_datapoint(top_left,top_right,line_len/2)

        #compute the top point of a triangle
        #the computation is similar to midpoint
        #the euclidean distance between triangle_top and top_mid should be
        #half of the distance between top_mid and bottom_mid
        triangle_top=get_datapoint(top_mid,bottom_mid,
                                   line_len/2,"outer")

        #get top left for right square
        #the computation is similar to midpoint
        #the euclidean distance between triangle_top and rightsq_topleft
        #should be the same as the distance between triangle_top and top_left
        rightsq_topleft=get_datapoint(triangle_top,top_left,
                                      line_len/(√(2)),"outer")

        #get midpoint of the diagonal between rightsq_topleft and top_right
        #the computation is similar to midpoint
        #the euclidean distance between rightsq_diag_mid and rightsq_topleft
        #should be half of the distance between rightsq_topleft and top_right
        rightsq_diag_mid=get_datapoint(top_right,rightsq_topleft,line_len/2)
        rightsq_topright=get_datapoint(rightsq_diag_mid,triangle_top,
                                       line_len/2,"outer")

        #get top left and right for left square similar to right square
        leftsq_topleft=get_datapoint(triangle_top,rightsq_topright,
                                     line_len,"outer")
        leftsq_topright=get_datapoint(triangle_top,top_right,
                                      line_len/(√(2)),"outer")

        #recursive do the same for left square
        pythagorean_tree(leftsq_topleft,leftsq_topright,
                         top_left,triangle_top,current_angle+45,
                         line_len/(√(2)),n-1)

        #recursive do the same for right square
        pythagorean_tree(rightsq_topleft,rightsq_topright,
                         triangle_top,top_right,current_angle-45,
                         line_len/(√(2)),n-1)

    end

end

# In[8]:

#initialize
top_left=(0,0)
top_right=(1,0)
bottom_left=(0,-1)
bottom_right=(1,-1)
n=4
current_angle=0
line_len=euclidean_distance(top_left,top_right);

# In[9]:

#viz
gr(size=(250,200))
fig=plot(legend=false,grid=false,axis=false,ticks=false,
    )

pythagorean_tree(top_left,top_right,bottom_left,
                     bottom_right,current_angle,line_len,n)
fig


# Energy spectrum of oscillations at a fixed point.

using FFTW, JLD2, CurveFit, PyPlot
using Vlasiator: RE

file = "satellites_uniform_sampled.jld2"

data = JLD2.load(file)

nSatellite = length(data["t"])
nI, nJ = size(data["rho"])[2:3]

t = data["t"]
# Select spatial point
i, j = 5, 5
var = data["rho"][:,i,j]

dt = t[2] - t[1] # uniform sample interval [s]
Fs = 1 / dt      # sample frequency, [Hz]
Fn = Fs / 2      # Nyquist frequency, [Hz]

## Frequency calculation

nPoints = length(var)
nFFT = nPoints
df = Fs / nFFT
freq_fullrange = -Fn:df:Fn

freq = freq_fullrange[(nPoints ÷ 2 + 1):end-1]

var_freq = fft(var)
var_power = abs.(fftshift(var_freq))[(nPoints ÷ 2 + 1):end]

# k is the exponential coefficient
a, k = @views power_fit(freq[10:end], var_power[10:end])

figure(figsize=(6,8))
loglog(freq, var_power)
axis("scaled")

min_power, max_power = extrema(@view var_power[10:end])
xlim(freq[8], Fs)
ylim(min_power * 0.75, max_power * 2.0)

xlabel("Frequency [Hz]"; fontsize=14)
ylabel("Power Density "; fontsize=14)
title(string(round.(data["locations"][i,j]./RE, digits=1))*"Re"; fontsize=14)

loglog(freq[10:end], a.*freq[10:end].^k, label="k = $(round(k, digits=1))")

legend()


function euclidian(x::Vector{T},y::Vector{T}) where T<:Real
  nsz = length(x)
  dist = zeros(T,nsz,nsz)

  for i in 1:nsz, j in 1:nsz
    if i<=j
      xd = (x[i]-x[j])^2
      yd = (y[i]-y[j])^2
      dist[i,j] = dist[j,i] = round(sqrt(xd+yd),RoundNearestTiesUp)
    end
  end
  return dist
end

function manhattan(x::Vector{T},y::Vector{T}) where T<:Real
  nsz = length(x)
  dist = zeros(T, nsz, nsz)

  for i in 1:nsz, j in 1:nsz
    if i<=j
      xd = abs(x[i] - x[j])
      yd = abs(y[i] - y[j])
      dist[i, j] = dist[j, i] = round(xd + yd, RoundNearestTiesUp)
    end
  end
  return dist
end

function max_norm(x::Vector{T},y::Vector{T}) where T<:Real
  nsz = length(x)
  dist = zeros(T, nsz, nsz)

  for i in 1:nsz, j in 1:nsz
    if i<=j
      xd = abs(x[i] - x[j])
      yd = abs(y[i] - y[j])
      dist[i, j] = dist[j, i] = max(round(xd, RoundNearestTiesUp), round(yd, RoundNearestTiesUp))
    end
  end
  return dist
end

function att_euclidian(x::Vector{T},y::Vector{T}) where T<:Real
  nsz = length(x)
  dist = zeros(T,nsz,nsz)

  for i in 1:nsz, j in 1:nsz
    if i<=j
      xd = (x[i]-x[j])^2
      yd = (y[i]-y[j])^2
      dist[i,j] = dist[j,i] = ceil(sqrt((xd+yd)/10.0))
    end
  end
  return dist
end

function ceil_euclidian(x::Vector{T},y::Vector{T}) where T<:Real
  nsz = length(x)
  dist = zeros(T,nsz,nsz)

  for i in 1:nsz, j in 1:nsz
    if i<=j
      xd = (x[i]-x[j])^2
      yd = (y[i]-y[j])^2
      dist[i,j] = dist[j,i] = ceil(sqrt(xd+yd))
    end
  end
  return dist
end

function geo(x::Vector{T},y::Vector{T}) where T<:Real
  PI = 3.141592
  RRR = 6378.388
  nsz = length(x)
  dist = zeros(T,nsz,nsz)
  degs = trunc.(hcat(x,y))
  mins = hcat(x,y).-degs
  coords = PI.*(degs.+(5.0.*(mins./3.0)))./180.0
  lat = coords[:,1]
  lon = coords[:,2]

  for i in 1:nsz, j in 1:nsz
    if i<=j
      q1 = cos(lon[i]-lon[j])
      q2 = cos(lat[i]-lat[j])
      q3 = cos(lat[i]+lat[j])
      dij = RRR.*acos(0.5.*((1.0.+q1).*q2.-(1.0.-q1).*q3)).+1.0
      dist[i,j] = dist[j,i] = floor(dij)
    end
  end
  return dist
end


struct CSTShapeFunction{N,T <: AbstractFloat}
    bino::NTuple{N,Int}
    CSTShapeFunction(N::Int, ::Type{T}) where T = new{N,T}(tuple((binomial(N, i) for i in 1:N)...))
end

struct CSTClassFunction{N1,N2,T}
    CSTClassFunction(N1::T, N2::T) where T = new{N1,N2,T}()
end

shape_f(s::CSTShapeFunction{N,T}, i::Int, x::T) where {N,T} =
if i == 0
    (1 - x)^N
elseif i == N
    x^N
else
    s.bino[i] * x^i * (1 - x)^(N - i)
end

shape_d(s::CSTShapeFunction{N,T}, i::Int, x::T) where {N,T} =
if i == 0
    -N * (1 - x)^(N - 1)
elseif i == N
    N * x^(N - 1)
else
    s.bino[i] * (i * x^(i - 1) * (1 - x)^(N - i) - (N - i) * x^i * (1 - x)^(N - i - 1))
end

class_f(::CSTClassFunction{N1,N2,T},x::T) where {N1,N2,T} = x^N1 * (1 - x)^N2

class_d(::CSTClassFunction{N1,N2,T},x::T) where {N1,N2,T} = N1 * x^(N1 - 1) * (1 - x)^N2 - N2 * x^N1 * (1 - x)^(N2 - 1)

function cst_preprocess(xx::AbstractVector{T}, yy::AbstractVector{T}) where T
    xte = (xx[1] + xx[end]) / 2
    if yy[end-1] > yy[2]
        reverse!(xx)
        reverse!(yy)
    end
    imin = argmin(xx)
    x0 = xx[imin]
    y0 = yy[imin]
    cc = xte - x0
    xu_ = @view(xx[imin:-1:1])
    yu_ = @view(yy[imin:-1:1])
    xl_ = @view(xx[imin:end] )
    yl_ = @view(yy[imin:end] )
    xu = (xu_ .- x0) ./ cc
    yu = (yu_ .- y0) ./ cc
    xl = (xl_ .- x0) ./ cc
    yl = (yl_ .- y0) ./ cc
    return xu, yu, xl, yl
end

function cst_matrix(
    class::CSTClassFunction{N1,N2,T},
    shape::CSTShapeFunction{N,T},
    xu::V, yu::V, xl::V, yl::V)　where {T,V <: AbstractVector{T},N1,N2,N}
    NP = length(xu) + length(xl) - 2
    A = zeros(T, NP, 2N + 3)
    b = vcat(yu[2:end], yl[2:end])
    ip = 1
    @inbounds for x in @view xu[2:end]
        c = class_f(class, x)
        for i in 1:N
            A[ip, i] = c * shape_f(shape, i, x) # cstShapeF(N, i, x)
        end
        A[ip, 2N + 1] = c * shape_f(shape, 0, x) # a_le
        A[ip, 2N + 2] = x # y_tu
        ip += 1
    end
    @inbounds for x in @view xl[2:end]
        c = class_f(class, x)
        for i in 1:N
            A[ip, i + N] = c * shape_f(shape, i, x)
        end
        A[ip, 2N + 1] = -c * shape_f(shape, 0, x) # -a_le
        A[ip, 2N + 3] = x # y_tl
        ip += 1
    end
    return A, b
end

function cst_fit(
    class::CSTClassFunction{N1,N2},
    shape::CSTShapeFunction{N,T},
    xu::AbstractVector{T}, yu::AbstractVector{T},
    xl::AbstractVector{T}, yl::AbstractVector{T} ) where {T,N,N1,N2}
    A, b = cst_matrix(class, shape, xu, yu, xl, yl)
    U, S, V = svd(A)
    params = V * ((transpose(U) * b) ./ S)
    au = params[1:N]
    al = params[N + 1:2N]
    a0 = params[2N + 1]
    yu = params[2N + 2]
    yl = params[2N + 3]
    return au, al, a0, yu, yl
end


#=
Macklemore Problem:

We are going to the thrift shop and need 99 cents to buy something. What is the
lightest way to carry 99 cents using US currency?

This is a Knapsack problem.

We specify that you need at least 99 cents - does the answer change if you need exact change?
=#

using JuMP
using Cbc # Must support integer programming.

m = Model(solver=CbcSolver())

# Variables represent how many of each coin we want to carry
@defVar(m, pennies >= 0, Int)
@defVar(m, nickels >= 0, Int)
@defVar(m, dimes >= 0, Int)
@defVar(m, quarters >= 0, Int)

# We need at least 99 cents
@addConstraint(m, 1 * pennies + 5 * nickels + 10 * dimes + 25 * quarters >= 99)

# Minimize mass (Grams)
# (source: US Mint)
@setObjective(m, Min, 2.5 * pennies + 5 * nickels + 2.268 * dimes + 5.670 * quarters)

# Solve
status = solve(m)

println("Minimum mass: ", getObjectiveValue(m), " grams")
println("using:")
println(round(getValue(pennies)), " pennies") # "round" to cast as integer
println(round(getValue(nickels)), " nickels")
println(round(getValue(dimes)), " dimes")
println(round(getValue(quarters)), " quarters")


if "./src" in LOAD_PATH
    # print("in src")
else
    dir = "./src"
    push!(LOAD_PATH,dir)
    # print("not in src")
end

module aerosolGen

using Distributions

# "The Mechanism of Breath Formation"
# DOI: 10.1089/jamp.2008.0720

# Particle size distribution for young subjects (b-1-0-f3-m-m)
# Mean = 1.015056269 μm
# Variance = 0.457766837 μm

# Determining parameters for log(X)
mean = 1.015056269e-6
var = 0.209550477e-6
std = sqrt(var)

# mean = 243900.6582
# var = 93909632285

# std = 0.457766837e-6

mu = log((mean^2)/(sqrt(var + mean^2)))
sigma = sqrt(log(1+((var)/(mean^2))))

# log_mean = exp(mean + var/2)
# log_var = (exp(var)-1)*exp(2*mean+var)

# log_mean = log(mean)-0.5*log((std/mean)^2 + 1)
# log_std = sqrt(log((std/mean)^2 + 1))

# println(log_mean)
# println("\n")
# println(log_std)
# println(mu)
# println("\n")
# println(sigma)

# println(log_mean + "\n" + log_var)

distrib0 = Distributions.LogNormal(mu, sigma)
# distrib0 = Distributions.LogNormal(mean, var)
range0 = 0.5e-10:0.5e-9:5e-8
# range0 = 0.5:0.1:4
# range0=-0.5e-6:0.2e-6:0.5e-6
discrete0 = pdf(distrib0, range0)
# discrete1 = cdf(distrib0, range0)
# discrete0=pdf(distrib0, )

# mean(distrib0)

# println(discrete0[1])
println(discrete0)
# println(discrete1)
# println(params(distrib0))
# println(meanlogx(distrib0))

end


"""
    flow(xy) -> Float64

Analyze hydrogen peroxide position to compute the magnitude of flow

By laminar flow, all nonzero flow biases the particle downstream
in the positive x direction.
"""
function flow_arlett(xy::Array{Float64,1})
    y = xy[2]
    if !FLOW_BIAS || y <= WALL_Y || inenz(xy)
        return 0.0
    else
        xdisplacementbias = getspeed(y) * SECONDS_PER_STEP
        return xdisplacementbias
    end
end

# lengths are in microns
const V_MAX = 6326.0
const R = 1270.0

"""
    getspeed(y) -> Float64

Compute the approximate laminar flow bias on the particle; magnitude of flow increases with the y-coordinate

Excerpt from \"Simulating Chemical Cross-Talk in High Density Enzymatic Biosensors\" by Sonali Madisetti:
\" In the experimental conditions, the probe had a constant flow of water, in which water entered the
system at 40 mL/s for 700 seconds through a 0.21 in x 0.1 in pipe and exited at a similar rate. The radius
of the rectangular probe apparatus was around 1270 microns. From Q = v_eff * A, where Q represented the
flow-rate, v_eff was approximately 4217 um/s. For a square pipe, it can be derived that v_eff = (2/3)v_max,
and thus that v_max = 6326 um/s. From there, using the velocity equation
                                    v(r) = v_max * (1 - r^2 / R^2)
where r is the y-distance from the center and R is the radius of the pipe. \"
"""
function getspeed(y::Float64)
    r = R - y
    return V_MAX * (1 - (r^2) / (R^2))
end


#Basic function to use

"""
    absolute_error(x, x̂)

return the Absolute error of x and x̂
"""
absolute_error(x, x̂) = abs(x .- x̂)

"""
    relative_error(x, x̂)

The relative error between X1 and X2
"""
relative_error(x, x̂) = abs((x .- x̂) ./ x)

"""
    ξ(x₀, x)

return the number ξ(x) bwteen x₀ and x.
"""
@inline ξ(x₀, x) = rand(1)[1] * (x - x₀)/x + x₀

"""
    NthDrivative(f::Function, x::Real, n::Int)

return the nth derivative value at x
"""
@inline function NthDerivative(f::Function, x::Real, n::Int)
    if n == 0
        return f(x)
    else
        return ForwardDiff.derivative(x->NthDerivative(f, x, n-1), x)
    end
end

"""
    TaylorPolynomials(f::Function, x::Real, x₀::Real, n::Int)

return the value of nth Taylor Ploynomial at x.
"""
@inline function TaylorPolynomials(f::Function, x::Real, x₀::Real, n::Int)
    result = []
    for i in 0:n
        push!(result, NthDerivative(f, x₀, i)*(x - x₀)^i/factorial(i))
    end
    return sum(result)

end


"""
	corrp(x,y)
Compute Pearson's correlation coefficient (`r`) and corresponding `p` value with
null hypothesis of no correlation (against alternative of a non-zero correlation).
`p` values (two-tailed) below 0.05 typically indicate that `r` is statistically
significant, see e.g., https://www.medcalc.org/manual/correlation.php

**Example**
```
x,y = collect(1.:1:10),collect(0.:10:90);
y[end] = 80;
r,p = corrp(x,y);
```
"""
function corrp(x::Vector{Float64},y::Vector{Float64})
	xi,yi = HypoTest.prepdata(x,y);
	n = length(xi);
	r = Statistics.cor(xi,yi);
	t = HypoTest.tpearval(r,n);
	return r, HypoTest.pvalcorr(t,n)
end

# t-value for Pearson's correlation coefficient: https://www.danielsoper.com/statcalc/formulas.aspx?id=44
tpearval(r,n) = r/sqrt((1-r^2)/(n-2));

# two-tailed probability for correlation (=t-distribution)
pvalcorr(t,n) = 2*(1-Distributions.cdf(Distributions.TDist(n-2),t));


"""
FEniCS tutorial demo program: Poisson equation with Dirichlet conditions.
Test problem is chosen to give an exact solution at all nodes of the mesh.
  -Laplace(u) = f    in the unit square
            u = u_D  on the boundary
  u = 1 + x^2 + 2y^2 = u_D
  f = -6
This is an extended version of the demo program poisson.py which
encapsulates the solver as a Python function.
"""
module ft12

using FenicsPy

function solver(f, u_D, Nx, Ny, degree=1)
    """
    Solve -Laplace(u) = f on [0,1] x [0,1] with 2*Nx*Ny Lagrange
    elements of specified degree and u = u_D (Expresssion) on
    the boundary.
    """

    # Create mesh and define function space
    mesh = UnitSquareMesh(Nx, Ny)
    V = FunctionSpace(mesh, "P", degree)

    bc = DirichletBC(V, u_D, "on_boundary")

    # Define variational problem
    u = TrialFunction(V)
    v = TestFunction(V)
    a = dot(grad(u), grad(v))*dx
    L = f*v*dx

    # Compute solution
    u = FeFunction(V)
    solve(a == L, u, bc)

    return u
end

function run_solver()
    "Run solver to compute and post-process solution"

    # Set up problem parameters and call solver
    u_D = Expression("1 + x[0]*x[0] + 2*x[1]*x[1]", degree=2)
    f = Constant(-6.0)
    u = solver(f, u_D, 8, 8, 1)

    # Plot solution and mesh
    #plot(u)
    #plot(u.function_space().mesh())

    # Save solution to file in VTK format
    vtkfile = File("poisson_solver/solution.pvd")
    vtkfile << u
end

function test_solver()
    "Test solver by reproducing u = 1 + x^2 + 2y^2"

    # Set up parameters for testing
    tol = 1E-10
    u_D = Expression("1 + x[0]*x[0] + 2*x[1]*x[1]", degree=2)
    f = Constant(-6.0)

    # Iterate over mesh sizes and degrees
    for Nxy in [(3, 3), (3, 5), (5, 3), (20, 20)]
        Nx, Ny = Nxy
        for degree in [1, 2, 3]
            println("Solving on a 2 x (", Nx," x ", Ny, ") mesh with P", degree, " elements.")

            # Compute solution
            u = solver(f, u_D, Nx, Ny, degree)

            # Extract the mesh
            mesh = u.function_space().mesh()

            # Compute maximum error at vertices
            vertex_values_u_D = u_D.compute_vertex_values(mesh)
            vertex_values_u  = u.compute_vertex_values(mesh)
            error_max = max(abs.(vertex_values_u_D - vertex_values_u)...)

            # Check maximum error
            msg = string("error_max = " , error_max)
            @assert error_max<tol msg
        end
    end
end

run_solver()
println("------------------")
test_solver()

end # module ft12


using Polynomials: Poly, polyint

struct LagrangeBasis{T,N} <: PolynomialBasis{T,N}
    x::Vector{T}

    denom::Vector{T}
    diffs::Matrix{T}
    vdminv::Matrix{T}

    function LagrangeBasis{T,N}(x) where {T,N}
        @assert length(x) == N

        local p::T

        denom = zeros(N)
        diffs = zeros(N,N)

        for i in eachindex(x)
            p = 1
            for j in eachindex(x)
                diffs[i,j] = x[i] - x[j]
                if i ≠ j
                    p *= diffs[i,j]
                end
            end
            denom[i] = 1/p
        end

        new(x, denom, diffs, vandermonde_matrix_inverse(x))
    end
end

function LagrangeBasis(x::Vector{T}) where {T}
    LagrangeBasis{T, length(x)}(x)
end

nodes(b::LagrangeBasis{T,N})  where {T,N} = b.x

Base.hash(b::LagrangeBasis, h::UInt) = hash(b.c, h)

Base.:(==)(b1::LagrangeBasis, b2::LagrangeBasis) = (b1.x == b2.x)

Base.isequal(b1::LagrangeBasis{T1,N1}, b2::LagrangeBasis{T2,N2}) where {T1,N1,T2,N2} = (T1 == T2 && N1 == N2)

function eval_basis(b::LagrangeBasis{T,N}, j::Int, x::T) where {T,N}
    local y::T = 1

    for i in 1:nnodes(b)
        i ≠ j ? y *= (x - b.x[i]) : nothing
    end
    y * b.denom[j]
end

function deriv_basis(b::LagrangeBasis{T,N}, j::Int, x::T) where {T,N}
    local y::T = 0
    local z::T

    for l in 1:nnodes(b)
        if l ≠ j
            z = 1 / b.diffs[j,l]
            for i in 1:nnodes(b)
                i ≠ j && i ≠ l ? z *= (x - b.x[i]) / b.diffs[j,i] : nothing
            end
            y += z
        end
    end
    y
end

deriv_basis(b::LagrangeBasis, j::Int, i::Int) = deriv_basis(b, j, b.x[i])

function int_basis(b::LagrangeBasis{T,N}, j::Int, x::T) where {T,N}
    y = zero(b.x)
    y[j] = 1
    lint = polyint(Poly(*(b.vdminv, y)))
    return lint(x)
end

int_basis(b::LagrangeBasis, j::Int, i::Int) = int_basis(b, j, b.x[i])


using FractionalDiffEq
using Plots, LaTeXStrings
using MittagLeffler

#Analytical solution
target = []

#MittagLeffler.jl doesn't support array argument
for i in 0:0.01:20
    push!(target, i^1.8*mittleff(1.8,2.8,-i^1.8))
end

s="\$D^{1.8}y(x)=1-y(x),\\ y(0)=0\$"

#Numerical solution
fun(x, y) = 1-y
prob = FDEProblem(fun, 1.8, 0, 20, 0.01)
result=solve(prob, PECE())
tspan=collect(0:0.01:20)

gr()

plot(tspan, result, title=s, linewidth=5, label="Numerical", legend=:bottomright)
plot!(tspan, target, lw=3, ls=:dash, label="Analytical")


# this is the log-likelihood for the linear model
# y = x*b+e, with e~N(0,s^2)
# the parameter theta = [b' s]
# this file is used for demonstration of MLE, to compare to OLS
function normal(theta, y, x)
    b = theta[1:end-1][1]
    s = theta[end][1]
    e = (y - x*b)./s
    logdensity = -log.(sqrt.(2.0*pi)) .- 0.5*log(s.^2) .- 0.5*e.*e
end


using FFTW
using Plots
using Scat

# plot filters in the frequency domain
# Plot filters for the first layer
N = 12 # log2 of the length of the signal
Q = [12, 1] # number of wavelets per octave
J = 8 # log2 of the maximum scale
σ0 = 0.1

FilterBank1, FilterBank2 = get_FilterBanks(N, Q, J, σ0)

ω = FFTW.fftshift(FilterBank1.ω)
plot(ω, FFTW.fftshift(FilterBank1.Λ[1].ψ), xlims=(0.0, 0.5), color=:steelblue, linewidth=2, legend=false)
for ψ in FilterBank1.Λ[2:end]
    plot!(ω, FFTW.fftshift(ψ.ψ), xlims=(0.0, 0.5), color=:steelblue, linewidth=2, legend=false)
end
plot!(ω, FFTW.fftshift(FilterBank1.ϕ.ϕ), xlims=(0.0, 0.5), linewidth=2, color=:crimson, legend=false)
xlabel!("Frequency")
savefig("FilterBank1.png")

# Plot filters for the second layer
ω = FFTW.fftshift(FilterBank2.ω)
plot(ω, FFTW.fftshift(FilterBank2.Λ[1].ψ), xlims=(0.0, 0.5), color=:steelblue, linewidth=2, legend=false)
for ψ in FilterBank2.Λ[2:end]
    plot!(ω, FFTW.fftshift(ψ.ψ), xlims=(0.0, 0.5), color=:steelblue, linewidth=2, legend=false)
end
plot!(ω, FFTW.fftshift(FilterBank2.ϕ.ϕ), xlims=(0.0, 0.5), linewidth=2, color=:crimson, legend=false)
xlabel!("Frequency")
savefig("FilterBank2.png")

# plot filter in the time domain
plot(real.(FFTW.fftshift(FFTW.ifft(FilterBank1.Λ[74].ψ))), linewidth=2, color=:steelblue, label="Real", axis=nothing)
plot!(imag.(FFTW.fftshift(FFTW.ifft(FilterBank1.Λ[74].ψ))), linewidth=2, linestyle=:dash, color=:black, label="Imaginary", axis=nothing)
xlabel!("Time")
savefig("MorletWavelet.png")


# SPDX-License-Identifier: MIT

# Stable implemenation of the log(exp(x) + exp(y)).
function logaddexp(x::T, y::T) where T
    diff = zero(T)
    if x < y
        diff = x - y
        x = y
    else y < x
        diff = y - x
    end

    if diff >= log(eps(T))
        return x + log1p(exp(diff))
    end
    return x
end

abstract type Semifield <: Number end

Base.convert(T::Type{<:Number}, x::Semifield) = T(x.val)
Base.promote(x::SF, y::Real) where SF <: Semifield = x, SF(y)
Base.promote(x::Real, y::SF) where SF <: Semifield = SF(x), y
Base.show(io::IO, x::Semifield) = print(io, x.val)

#======================================================================
Log-semifield:
    x ⊕ y := log( exp(x) + exp(y))
    x ⊗ y := x + y
    x ⊘ y := x - y
======================================================================#

struct LogSemifield{T<:AbstractFloat} <: Semifield
    val::T
end

Base.:+(x::LogSemifield{T}, y::LogSemifield{T}) where T =
    LogSemifield{T}(logaddexp(x.val, y.val))
Base.:*(x::LogSemifield, y::LogSemifield) = LogSemifield(x.val + y.val)
Base.:/(x::LogSemifield, y::LogSemifield) = LogSemifield(x.val - y.val)
Base.zero(::Type{LogSemifield{T}}) where T = LogSemifield{T}(T(-Inf))
Base.zero(::LogSemifield{T}) where T = LogSemifield{T}(T(-Inf))
Base.one(::Type{LogSemifield{T}}) where T = LogSemifield{T}(T(0))
Base.one(::LogSemifield{T}) where T = LogSemifield{T}(T(0))
Base.isless(x::LogSemifield, y::LogSemifield) = isless(x.val, y.val)
Base.typemin(x::Type{LogSemifield{T}}) where T = LogSemifield{T}(typemin(T))
Base.typemax(x::Type{LogSemifield{T}}) where T = LogSemifield{T}(typemax(T))
Base.conj(x::LogSemifield) = conj(x.val)

#======================================================================
Tropical-semifield:
    x ⊕ y := max(x, y)
    x ⊗ y := x + y
    x ⊘ y := x - y
======================================================================#

struct TropicalSemifield{T<:AbstractFloat} <: Semifield
    val::T
end

Base.:+(x::TropicalSemifield, y::TropicalSemifield) =
    TropicalSemifield(max(x.val, y.val))
Base.:*(x::TropicalSemifield, y::TropicalSemifield) =
    TropicalSemifield(x.val + y.val)
Base.:/(x::TropicalSemifield, y::TropicalSemifield) =
    TropicalSemifield(x.val - y.val)
Base.zero(::Type{TropicalSemifield{T}}) where T = TropicalSemifield{T}(T(-Inf))
Base.zero(::TropicalSemifield{T}) where T = TropicalSemifield{T}(T(-Inf))
Base.one(::Type{TropicalSemifield{T}}) where T = TropicalSemifield{T}(T(0))
Base.one(::TropicalSemifield{T}) where T = TropicalSemifield{T}(T(0))
Base.isless(x::TropicalSemifield, y::TropicalSemifield) = isless(x.val, y.val)
Base.typemin(x::Type{TropicalSemifield{T}}) where T =
    TropicalSemifield{T}(typemin(T))
Base.typemax(x::Type{TropicalSemifield{T}}) where T =
    TropicalSemifield{T}(typemax(T))
Base.conj(x::TropicalSemifield) = conj(x.val)


module HWconstrained

greet() = print("Hello World!")

	using JuMP, NLopt, DataFrames, Ipopt
	using LinearAlgebra

	export data, table_NLopt, table_JuMP

	function data(a=0.5)
        na = 3
		nc = 1
		ns = 4
		nss = 16
		e = [2,0,0]
		p = [1,1,1]
		z_2 = [0.72,0.92,1.12,1.32]
		z_3 = [0.86,0.96,1.06,1.16]
		z = [[1.0,z_2[j],z_3[k]] for j in 1:4 for k in 1:4]
		pi = 1/16*ones(1,16)
		return Dict("a"=>a,"na"=>na,"nc"=>nc,"ns"=>ns,"nss"=>nss,"e"=>e,"p"=>p,"z"=>z,"pi"=>pi)
	end

	function maxJuMP(a=0.5)
	        d = data(a)
			n = Model(with_optimizer(Ipopt.Optimizer))
			@variable(n, omega[1:3])
			@variable(n, c)
			@NLobjective(n, Min, exp(-a*c)+sum(d["pi"][i]*exp((-a)*sum(d["z"][i][j]'*omega[j] for j in 1:d["na"])) for i in 1:d["nss"]))
			@constraint(n, c + d["p"]'*(omega - d["e"]) <= 0)
			JuMP.optimize!(n)
	  	return Dict("obj"=>objective_value(n),"c"=>value(c),"omegas"=>[value(omega[i]) for i in 1:length(omega)])
	end

	function table_JuMP()
		d = DataFrame(a=[0.5;1.0;5.0],c = zeros(3),omega1=zeros(3),omega2=zeros(3),omega3=zeros(3),fval=zeros(3))
		for i in 1:nrow(d)
			xx = maxJuMP(d[i,:a])
			d[i,:c] = xx["c"]
			d[i,:omega1] = xx["omegas"][1]
			d[i,:omega2] = xx["omegas"][2]
			d[i,:omega3] = xx["omegas"][3]
			d[i,:fval] = xx["obj"]
		end
		return d
	end

	function obj(x::Vector,d::Dict)
		a = d["a"]
		z = d["z"]
		pi = d["pi"]
		if length(grad)>0
			  grad[1] = sum(d["pi"][i]*(-a)*exp((-a)*z[i][1]*sum(z[i]'*x[1:3])) for i in 1:d["nss"])
			  grad[2] = sum(d["pi"][i]*(-a)*exp((-a)*z[i][2]*sum(z[i]'*x[1:3])) for i in 1:d["nss"])
			  grad[3] = sum(d["pi"][i]*(-a)*exp((-a)*z[i][3]*sum(z[i]'*x[1:3])) for i in 1:d["nss"])
			  grad[4] = (-a)*exp.(-a*x[4])
		  end
		return (exp(-a*x[4])+sum(d["pi"][i]*exp((-a)*sum(z[i]'*x[1:3])) for i in 1:d["nss"]))
	  end

	function constr(x::Vector,grad::Vector,d::Dict)
		if length(grad)>0
			   grad[1] = d["p"][1]
			   grad[2] = d["p"][2]
			   grad[3] = d["p"][3]
			   grad[4] = 1
	   end
	   return x[4] + d["p"]'*(x[1:3] - d["e"])
	end

	function max_NLopt(a=0.5)
		count = 0 # keep track of # function evaluations
		opt = Opt(:LD_MMA, 4)
		lower_bounds!(opt, [-Inf, -Inf, -Inf, 0.])
		xtol_rel!(opt, 1e-7)
		grad = zeros(4)
		println(length(grad))
		d = data(a)
		min_objective!(opt,(x,grad) -> obj(x,grad,d))
		inequality_constraint!(opt, (x,grad) -> constr(x,grad,d))
		ftol_rel!(opt,1e-9)
		NLopt.optimize(opt, [0,0,0,0])
end

	function table_NLopt()
		d = DataFrame(a=[0.5;1.0;5.0],c = zeros(3),omega1=zeros(3),omega2=zeros(3),omega3=zeros(3),fval=zeros(3))
		for i in 1:nrow(d)
			xx = max_NLopt(d[i,:a])
			for j in 2:ncol(d)-1
				d[i,j] = xx[2][j-1]
			end
			d[i,end] = xx[1]
		end
		return d
	end

end # module


#Unary Operators
"""
Identity: `z=x`
"""
UnaryOps.IDENTITY
juliaop(::typeof(UnaryOps.IDENTITY)) = identity
UnaryOps.UnaryOp(::typeof(identity)) = UnaryOps.IDENTITY
"""
Additive Inverse: `z=-x`
"""
UnaryOps.AINV
juliaop(::typeof(UnaryOps.AINV)) = -
UnaryOps.UnaryOp(::typeof(-)) = UnaryOps.AINV
"""
Logical Negation

`z=¬x::Bool`

`Real`:  `z=¬(x::ℝ ≠ 0)`
"""
UnaryOps.LNOT
juliaop(::typeof(UnaryOps.LNOT)) = !
UnaryOps.UnaryOp(::typeof(!)) = UnaryOps.LNOT
"""
Multiplicative Inverse: `z=1/x`
"""
UnaryOps.MINV
juliaop(::typeof(UnaryOps.MINV)) = inv
UnaryOps.UnaryOp(::typeof(inv)) = UnaryOps.MINV
"""
One: `z=one(x)`
"""
UnaryOps.ONE
juliaop(::typeof(UnaryOps.ONE)) = one
UnaryOps.UnaryOp(::typeof(one)) = UnaryOps.ONE
"""
Absolute Value: `z=|x|`
"""
UnaryOps.ABS
juliaop(::typeof(UnaryOps.ABS)) = abs
UnaryOps.UnaryOp(::typeof(abs)) = UnaryOps.ABS
"""
Bitwise Negation: `z=¬x`
"""
UnaryOps.BNOT
juliaop(::typeof(UnaryOps.BNOT)) = ~
UnaryOps.UnaryOp(::typeof(~)) = UnaryOps.BNOT
"""
Square Root: `z=√(x)`
"""
UnaryOps.SQRT
juliaop(::typeof(UnaryOps.SQRT)) = sqrt
UnaryOps.UnaryOp(::typeof(sqrt)) = UnaryOps.SQRT
"""
Natural Logarithm: `z=logₑ(x)`
"""
UnaryOps.LOG
juliaop(::typeof(UnaryOps.LOG)) = log
UnaryOps.UnaryOp(::typeof(log)) = UnaryOps.LOG
"""
Natural Base Exponential: `z=eˣ`
"""
UnaryOps.EXP
juliaop(::typeof(UnaryOps.EXP)) = exp
UnaryOps.UnaryOp(::typeof(exp)) = UnaryOps.EXP
"""
Log Base 2: `z=log₂(x)`
"""
UnaryOps.LOG2
juliaop(::typeof(UnaryOps.LOG2)) = log2
UnaryOps.UnaryOp(::typeof(log2)) = UnaryOps.LOG2
"""
Sine: `z=sin(x)`
"""
UnaryOps.SIN
juliaop(::typeof(UnaryOps.SIN)) = sin
UnaryOps.UnaryOp(::typeof(sin)) = UnaryOps.SIN
"""
Cosine: `z=cos(x)`
"""
UnaryOps.COS
juliaop(::typeof(UnaryOps.COS)) = cos
UnaryOps.UnaryOp(::typeof(cos)) = UnaryOps.COS
"""
Tangent: `z=tan(x)`
"""
UnaryOps.TAN
juliaop(::typeof(UnaryOps.TAN)) = tan
UnaryOps.UnaryOp(::typeof(tan)) = UnaryOps.TAN
"""
Inverse Cosine: `z=cos⁻¹(x)`
"""
UnaryOps.ACOS
juliaop(::typeof(UnaryOps.ACOS)) = acos
UnaryOps.UnaryOp(::typeof(acos)) = UnaryOps.ACOS
"""
Inverse Sine: `z=sin⁻¹(x)`
"""
UnaryOps.ASIN
juliaop(::typeof(UnaryOps.ASIN)) = asin
UnaryOps.UnaryOp(::typeof(asin)) = UnaryOps.ASIN
"""
Inverse Tangent: `z=tan⁻¹(x)`
"""
UnaryOps.ATAN
juliaop(::typeof(UnaryOps.ATAN)) = atan
UnaryOps.UnaryOp(::typeof(atan)) = UnaryOps.ATAN
"""
Hyperbolic Sine: `z=sinh(x)`
"""
UnaryOps.SINH
juliaop(::typeof(UnaryOps.SINH)) = sinh
UnaryOps.UnaryOp(::typeof(sinh)) = UnaryOps.SINH
"""
Hyperbolic Cosine: `z=cosh(x)`
"""
UnaryOps.COSH
juliaop(::typeof(UnaryOps.COSH)) = cosh
UnaryOps.UnaryOp(::typeof(cosh)) = UnaryOps.COSH
"""
Hyperbolic Tangent: `z=tanh(x)`
"""
UnaryOps.TANH
juliaop(::typeof(UnaryOps.TANH)) = tanh
UnaryOps.UnaryOp(::typeof(tanh)) = UnaryOps.TANH
"""
Inverse Hyperbolic Sine: `z=sinh⁻¹(x)`
"""
UnaryOps.ASINH
juliaop(::typeof(UnaryOps.ASINH)) = asinh
UnaryOps.UnaryOp(::typeof(asinh)) = UnaryOps.ASINH
"""
Inverse Hyperbolic Cosine: `z=cosh⁻¹(x)`
"""
UnaryOps.ACOSH
juliaop(::typeof(UnaryOps.ACOSH)) = acosh
UnaryOps.UnaryOp(::typeof(acosh)) = UnaryOps.ACOSH
"""
Inverse Hyperbolic Tangent: `z=tanh⁻¹(x)`
"""
UnaryOps.ATANH
juliaop(::typeof(UnaryOps.ATANH)) = atanh
UnaryOps.UnaryOp(::typeof(atanh)) = UnaryOps.ATANH
"""
Sign Function: `z=signum(x)`
"""
UnaryOps.SIGNUM
juliaop(::typeof(UnaryOps.SIGNUM)) = sign
UnaryOps.UnaryOp(::typeof(sign)) = UnaryOps.SIGNUM
"""
Ceiling Function: `z=⌈x⌉`
"""
UnaryOps.CEIL
juliaop(::typeof(UnaryOps.CEIL)) = ceil
UnaryOps.UnaryOp(::typeof(ceil)) = UnaryOps.CEIL
"""
Floor Function: `z=⌊x⌋`
"""
UnaryOps.FLOOR
juliaop(::typeof(UnaryOps.FLOOR)) = floor
UnaryOps.UnaryOp(::typeof(floor)) = UnaryOps.FLOOR
"""
Round to nearest: `z=round(x)`
"""
UnaryOps.ROUND
juliaop(::typeof(UnaryOps.ROUND)) = round
UnaryOps.UnaryOp(::typeof(round)) = UnaryOps.ROUND
"""
Truncate: `z=trunc(x)`
"""
UnaryOps.TRUNC
juliaop(::typeof(UnaryOps.TRUNC)) = trunc
UnaryOps.UnaryOp(::typeof(trunc)) = UnaryOps.TRUNC
"""
Base-2 Exponential: `z=2ˣ`
"""
UnaryOps.EXP2
juliaop(::typeof(UnaryOps.EXP2)) = exp2
UnaryOps.UnaryOp(::typeof(exp2)) = UnaryOps.EXP2
"""
Natural Exponential - 1: `z=eˣ - 1`
"""
UnaryOps.EXPM1
juliaop(::typeof(UnaryOps.EXPM1)) = expm1
UnaryOps.UnaryOp(::typeof(expm1)) = UnaryOps.EXPM1
"""
Log Base 10: `z=log₁₀(x)`
"""
UnaryOps.LOG10
juliaop(::typeof(UnaryOps.LOG10)) = log10
UnaryOps.UnaryOp(::typeof(log10)) = UnaryOps.LOG10
"""
Natural Log of x + 1: `z=logₑ(x + 1)`
"""
UnaryOps.LOG1P
juliaop(::typeof(UnaryOps.LOG1P)) = log1p
UnaryOps.UnaryOp(::typeof(log1p)) = UnaryOps.LOG1P
"""
Log of Gamma Function: `z=log(|Γ(x)|)`
"""
UnaryOps.LGAMMA
juliaop(::typeof(UnaryOps.LGAMMA)) = lgamma
UnaryOps.UnaryOp(::typeof(lgamma)) = UnaryOps.LGAMMA
"""
Gamma Function: `z=Γ(x)`
"""
UnaryOps.TGAMMA
juliaop(::typeof(UnaryOps.TGAMMA)) = gamma
UnaryOps.UnaryOp(::typeof(gamma)) = UnaryOps.TGAMMA
"""
Error Function: `z=erf(x)`
"""
UnaryOps.ERF
juliaop(::typeof(UnaryOps.ERF)) = erf
UnaryOps.UnaryOp(::typeof(erf)) = UnaryOps.ERF
"""
Complimentary Error Function: `z=erfc(x)`
"""
UnaryOps.ERFC
juliaop(::typeof(UnaryOps.ERFC)) = erfc
UnaryOps.UnaryOp(::typeof(erfc)) = UnaryOps.ERFC

#There is no exact equivalent here, since Julia's frexp returns (frexpx, frexpe).
"""
Normalized Exponent: `z=frexpe(x)`
"""
UnaryOps.FREXPE
function frexpe end
juliaop(::typeof(UnaryOps.FREXPE)) = frexpe
UnaryOps.UnaryOp(::typeof(frexpe)) = UnaryOps.FREXPE
"""
Normalized Fraction: `z=frexpx(x)`
"""
UnaryOps.FREXPX
function frexpx end
juliaop(::typeof(UnaryOps.FREXPX)) = frexpx
UnaryOps.UnaryOp(::typeof(frexpx)) = UnaryOps.frexpx

"""
Complex Conjugate: `z=x̄`
"""
UnaryOps.CONJ
juliaop(::typeof(UnaryOps.CONJ)) = conj
UnaryOps.UnaryOp(::typeof(conj)) = UnaryOps.CONJ
"""
Real Part: `z=real(x)`
"""
UnaryOps.CREAL
juliaop(::typeof(UnaryOps.CREAL)) = real
UnaryOps.UnaryOp(::typeof(real)) = UnaryOps.CREAL
"""
Imaginary Part: `z=imag(x)`
"""
UnaryOps.CIMAG
juliaop(::typeof(UnaryOps.CIMAG)) = imag
UnaryOps.UnaryOp(::typeof(imag)) = UnaryOps.CIMAG
"""
Angle: `z=carg(x)`
"""
UnaryOps.CARG
juliaop(::typeof(UnaryOps.CARG)) = angle
UnaryOps.UnaryOp(::typeof(angle)) = UnaryOps.CARG
"""
isinf: `z=(x == ±∞)`
"""
UnaryOps.ISINF
juliaop(::typeof(UnaryOps.ISINF)) = isinf
UnaryOps.UnaryOp(::typeof(isinf)) = UnaryOps.ISINF
"""
isnan: `z=(x == NaN)`
"""
UnaryOps.ISNAN
juliaop(::typeof(UnaryOps.ISNAN)) = isnan
UnaryOps.UnaryOp(::typeof(isnan)) = UnaryOps.ISNAN
"""
isfinite: `z=isfinite(x)`
"""
UnaryOps.ISFINITE
juliaop(::typeof(UnaryOps.ISFINITE)) = isfinite
UnaryOps.UnaryOp(::typeof(isfinite)) = UnaryOps.ISFINITE
"""
0-based Row Index: `z=i`
"""
UnaryOps.POSITIONI #No Julia version since it's 0-based.
"""
1-Based Row Index: `z=i + 1`
"""
UnaryOps.POSITIONI1
function positioni end
juliaop(::typeof(UnaryOps.POSITIONI1)) = positioni
UnaryOps.UnaryOp(::typeof(positioni)) = UnaryOps.POSITIONI1
"""
0-Based Column Index: `z=j`
"""
UnaryOps.POSITIONJ #No Julia version since it's 0-based.
"""
1-Based Column Index: `z=j + 1`
"""
UnaryOps.POSITIONJ1
function positionj end
juliaop(::typeof(UnaryOps.POSITIONJ1)) = positionj
UnaryOps.UnaryOp(::typeof(positionj)) = UnaryOps.POSITIONJ1

#Binary Operators
"""
First argument: `f(x::T,y::T)::T = x`
"""
BinaryOps.FIRST
juliaop(::typeof(BinaryOps.FIRST)) = first
BinaryOps.BinaryOp(::typeof(first)) = BinaryOps.FIRST
"""
Second argument: `f(x::T,y::T)::T = y`
"""
BinaryOps.SECOND
function second end
juliaop(::typeof(BinaryOps.SECOND)) = second
BinaryOps.BinaryOp(::typeof(second)) = BinaryOps.SECOND
"""
Power: `f(x::T,y::T)::T = xʸ`
"""
BinaryOps.POW
juliaop(::typeof(BinaryOps.POW)) = ^
BinaryOps.BinaryOp(::typeof(^)) = BinaryOps.POW
"""
Addition: `f(x::T,y::T)::T = x + y`
"""
BinaryOps.PLUS
juliaop(::typeof(BinaryOps.PLUS)) = +
BinaryOps.BinaryOp(::typeof(+)) = BinaryOps.PLUS
"""
Subtraction: `f(x::T,y::T)::T = x - y`
"""
BinaryOps.MINUS
juliaop(::typeof(BinaryOps.MINUS)) = -
BinaryOps.BinaryOp(::typeof(-)) = BinaryOps.MINUS
"""
Multiplication: `f(x::T,y::T)::T = xy`
"""
BinaryOps.TIMES
juliaop(::typeof(BinaryOps.TIMES)) = *
BinaryOps.BinaryOp(::typeof(*)) = BinaryOps.TIMES
"""
Division: `f(x::T,y::T)::T = x / y`
"""
BinaryOps.DIV
juliaop(::typeof(BinaryOps.DIV)) = /
BinaryOps.BinaryOp(::typeof(/)) = BinaryOps.DIV
"""
Reverse Subtraction: `f(x::T,y::T)::T = y - x`
"""
BinaryOps.RMINUS
function rminus end
juliaop(::typeof(BinaryOps.RMINUS)) = rminus
BinaryOps.BinaryOp(::typeof(rminus)) = BinaryOps.RMINUS
"""
Reverse Division: `f(x::T,y::T)::T = y / x`
"""
BinaryOps.RDIV
juliaop(::typeof(BinaryOps.RDIV)) = \
BinaryOps.BinaryOp(::typeof(\)) = BinaryOps.RDIV
"""
One when both x and y exist: `f(x::T,y::T)::T = 1`
"""
BinaryOps.PAIR
function pair end
juliaop(::typeof(BinaryOps.PAIR)) = pair
BinaryOps.BinaryOp(::typeof(pair)) = BinaryOps.PAIR
"""
Pick x or y arbitrarily: `f(x::T,y::T)::T = x or y`
"""
BinaryOps.ANY
#This is sort of incorrect
juliaop(::typeof(BinaryOps.ANY)) = any
BinaryOps.BinaryOp(::typeof(any)) = BinaryOps.ANY
"""
Equal: `f(x::T,y::T)::T = x == y``
"""
BinaryOps.ISEQ
function iseq end
juliaop(::typeof(BinaryOps.ISEQ)) = iseq
BinaryOps.BinaryOp(::typeof(iseq)) = BinaryOps.ISEQ
"""
Not Equal: `f(x::T,y::T)::T = x ≠ y`
"""
BinaryOps.ISNE
function isne end
juliaop(::typeof(BinaryOps.ISNE)) = isne
BinaryOps.BinaryOp(::typeof(isne)) = BinaryOps.ISNE
"""
Greater Than: `f(x::ℝ,y::ℝ)::ℝ = x > y`
"""
BinaryOps.ISGT
function isgt end
juliaop(::typeof(BinaryOps.ISGT)) = isgt
BinaryOps.BinaryOp(::typeof(isgt)) = BinaryOps.ISGT
"""
Less Than: `f(x::ℝ,y::ℝ)::ℝ = x < y`
"""
BinaryOps.ISLT
function islt end
juliaop(::typeof(BinaryOps.ISLT)) = islt
BinaryOps.BinaryOp(::typeof(islt)) = BinaryOps.ISLT
"""
Greater Than or Equal: `f(x::ℝ,y::ℝ)::ℝ = x ≥ y`
"""
BinaryOps.ISGE
function isge end
juliaop(::typeof(BinaryOps.ISGE)) = isge
BinaryOps.BinaryOp(::typeof(isge)) = BinaryOps.ISGE
"""
Less Than or Equal: `f(x::ℝ,y::ℝ)::ℝ = x ≤ y`
"""
BinaryOps.ISLE
function isle end
juliaop(::typeof(BinaryOps.ISLE)) = isle
BinaryOps.BinaryOp(::typeof(isle)) = BinaryOps.ISLE
"""
Minimum: `f(x::ℝ,y::ℝ)::ℝ = min(x, y)`
"""
BinaryOps.MIN
juliaop(::typeof(BinaryOps.MIN)) = min
BinaryOps.BinaryOp(::typeof(min)) = BinaryOps.MIN
"""
Maximum: `f(x::ℝ,y::ℝ)::ℝ = max(x, y)`
"""
BinaryOps.MAX
juliaop(::typeof(BinaryOps.MAX)) = max
BinaryOps.BinaryOp(::typeof(max)) = BinaryOps.MAX
"""
Logical OR: `f(x::ℝ,y::ℝ)::ℝ = (x ≠ 0) ∨ (y ≠ 0)`
"""
BinaryOps.LOR
function ∨ end
juliaop(::typeof(BinaryOps.LOR)) = ∨
BinaryOps.BinaryOp(::typeof(∨)) = BinaryOps.LOR
"""
Logical AND: `f(x::ℝ,y::ℝ)::ℝ = (x ≠ 0) ∧ (y ≠ 0)`
"""
BinaryOps.LAND
function ∧ end
juliaop(::typeof(BinaryOps.LAND)) = ∧
BinaryOps.BinaryOp(::typeof(∧)) = BinaryOps.LAND
"""
Logical AND: `f(x::ℝ,y::ℝ)::ℝ = (x ≠ 0) ⊻ (y ≠ 0)`
"""
BinaryOps.LXOR
function lxor end
juliaop(::typeof(BinaryOps.LXOR)) = lxor
BinaryOps.BinaryOp(::typeof(lxor)) = BinaryOps.LXOR
"""
4-Quadrant Arc Tangent: `f(x::F, y::F)::F = tan⁻¹(y/x)`
"""
BinaryOps.ATAN2
juliaop(::typeof(BinaryOps.ATAN2)) = atan
BinaryOps.BinaryOp(::typeof(atan)) = BinaryOps.ATAN2
"""
Hypotenuse: `f(x::F, y::F)::F = √(x² + y²)`
"""
BinaryOps.HYPOT
juliaop(::typeof(BinaryOps.HYPOT)) = hypot
BinaryOps.BinaryOp(::typeof(hypot)) = BinaryOps.HYPOT
"""
Float remainder of x / y rounded towards zero.
"""
BinaryOps.FMOD
#Is this available?
function fmod end
juliaop(::typeof(BinaryOps.FMOD)) = fmod
BinaryOps.BinaryOp(::typeof(fmod)) = BinaryOps.FMOD
"""
Float remainder of x / y rounded towards nearest integral value.
"""
BinaryOps.REMAINDER
juliaop(::typeof(BinaryOps.REMAINDER)) = rem
BinaryOps.BinaryOp(::typeof(rem)) = BinaryOps.REMAINDER
"""
LDEXP: `f(x::F, y::F)::F = x × 2ⁿ`
"""
BinaryOps.LDEXP
juliaop(::typeof(BinaryOps.LDEXP)) = ldexp
BinaryOps.BinaryOp(::typeof(ldexp)) = BinaryOps.LDEXP
"""
Copysign: Value with magnitude of x and sign of y.
"""
BinaryOps.COPYSIGN
juliaop(::typeof(BinaryOps.COPYSIGN)) = copysign
BinaryOps.BinaryOp(::typeof(copysign)) = BinaryOps.COPYSIGN
"""
Bitwise OR: `f(x::ℤ, y::ℤ)::ℤ = x | y`
"""
BinaryOps.BOR
juliaop(::typeof(BinaryOps.BOR)) = |
BinaryOps.BinaryOp(::typeof(|)) = BinaryOps.BOR
"""
Bitwise AND: `f(x::ℤ, y::ℤ)::ℤ = x & y`
"""
BinaryOps.BAND
juliaop(::typeof(BinaryOps.BAND)) = &
BinaryOps.BinaryOp(::typeof(&)) = BinaryOps.BAND
"""
Bitwise XOR: `f(x::ℤ, y::ℤ)::ℤ = x ^ y`
"""
BinaryOps.BXOR
juliaop(::typeof(BinaryOps.BXOR)) = ⊻
BinaryOps.BinaryOp(::typeof(⊻)) = BinaryOps.BXOR
"""
Bitwise XNOR: : `f(x::ℤ, y::ℤ)::ℤ = ~(x ^ y)`
"""
BinaryOps.BXNOR
juliaop(::typeof(BinaryOps.BXNOR)) = !⊻
BinaryOps.BinaryOp(::typeof(!⊻)) = BinaryOps.BXNOR
"""
BGET: `f(x::ℤ, y::ℤ)::ℤ = get bit y of x.`
"""
BinaryOps.BGET
"""
BSET: `f(x::ℤ, y::ℤ)::ℤ = set bit y of x.`
"""
BinaryOps.BSET
"""
BCLR: `f(x::ℤ, y::ℤ)::ℤ = clear bit y of x.`
"""
BinaryOps.BCLR
"""
BSHIFT: `f(x::ℤ, y::Int8)::ℤ = bitshift(x, y)`
"""
BinaryOps.BSHIFT

"""
Equals: `f(x::T, y::T)::Bool = x == y`
"""
BinaryOps.EQ
juliaop(::typeof(BinaryOps.EQ)) = ==
BinaryOps.BinaryOp(::typeof(==)) = BinaryOps.EQ
"""
Not Equals: `f(x::T, y::T)::Bool = x ≠ y`
"""
BinaryOps.NE
juliaop(::typeof(BinaryOps.NE)) = !=
BinaryOps.BinaryOp(::typeof(!=)) = BinaryOps.NE
"""
Greater Than: `f(x::T, y::T)::Bool = x > y`
"""
BinaryOps.GT
juliaop(::typeof(BinaryOps.GT)) = >
BinaryOps.BinaryOp(::typeof(>)) = BinaryOps.GT
"""
Less Than: `f(x::T, y::T)::Bool = x < y`
"""
BinaryOps.LT
juliaop(::typeof(BinaryOps.LT)) = <
BinaryOps.BinaryOp(::typeof(<)) = BinaryOps.LT
"""
Greater Than or Equal: `f(x::T, y::T)::Bool = x ≥ y`
"""
BinaryOps.GE
juliaop(::typeof(BinaryOps.GE)) = >=
BinaryOps.BinaryOp(::typeof(>=)) = BinaryOps.GE
"""
Less Than or Equal: `f(x::T, y::T)::Bool = x ≤ y`
"""
BinaryOps.LE
juliaop(::typeof(BinaryOps.LE)) = <=
BinaryOps.BinaryOp(::typeof(<=)) = BinaryOps.LE
"""
Complex: `f(x::F, y::F)::Complex = x + y × i`
"""
BinaryOps.CMPLX
juliaop(::typeof(BinaryOps.CMPLX)) = complex
BinaryOps.BinaryOp(::typeof(complex)) = BinaryOps.CMPLX
"""
0-Based row index of a: `f(aᵢⱼ::T, bₖₗ::T)::Int64 = i`
"""
BinaryOps.FIRSTI
"""
1-Based row index of a: `f(aᵢⱼ::T, bₖₗ::T)::Int64 = i + 1`
"""
BinaryOps.FIRSTI1
function firsti end
juliaop(::typeof(BinaryOps.FIRSTI1)) = firsti
BinaryOps.BinaryOp(::typeof(firsti)) = BinaryOps.FIRSTI1
"""
0-Based column index of a: `f(aᵢⱼ::T, bₖₗ::T)::Int64 = j`
"""
BinaryOps.FIRSTJ
"""
1-Based column index of a: `f(aᵢⱼ::T, bₖₗ::T)::Int64 = j + 1`
"""
BinaryOps.FIRSTJ1
function firstj end
juliaop(::typeof(BinaryOps.FIRSTJ1)) = firstj
BinaryOps.BinaryOp(::typeof(firstj)) = BinaryOps.FIRSTJ1
"""
0-Based row index of b: `f(aᵢⱼ::T, bₖₗ::T)::Int64 = k`
"""
BinaryOps.SECONDI
"""
0-Based row index of b: `f(aᵢⱼ::T, bₖₗ::T)::Int64 = k + 1`
"""
BinaryOps.SECONDI1
function secondi end
juliaop(::typeof(BinaryOps.SECONDI1)) = secondi
BinaryOps.BinaryOp(::typeof(secondi)) = BinaryOps.SECONDI1
"""
0-Based column index of b: `f(aᵢⱼ::T, bₖₗ::T)::Int64 = l`
"""
BinaryOps.SECONDJ
"""
1-Based column index of b: `f(aᵢⱼ::T, bₖₗ::T)::Int64 = l + 1`
"""
BinaryOps.SECONDJ1
function secondj end
juliaop(::typeof(BinaryOps.SECONDJ1)) = secondj
BinaryOps.BinaryOp(::typeof(secondj)) = BinaryOps.SECONDJ1

#All binary ops will default to emul
defaultadd(f) = emul
# Default to eadd. We're limiting this to + and OR for now to enable easy graph unions.
for op ∈ [
    :+,
    :∨,
]
    funcquote = quote
        defaultadd(::typeof($op)) = eadd
    end
    @eval($funcquote)
end

#Monoid operators
"""
Minimum monoid: `f(x::ℝ, y::ℝ)::ℝ = min(x, y)`
* Identity: +∞
* Terminal: -∞
"""
Monoids.MIN_MONOID
op(::typeof(Monoids.MIN_MONOID)) = BinaryOps.MIN
Monoids.Monoid(::typeof(min)) = Monoids.MIN_MONOID
"""
Max monoid: `f(x::ℝ, y::ℝ)::ℝ = max(x, y)`
* Identity: -∞
* Terminal: +∞
"""
Monoids.MAX_MONOID
op(::typeof(Monoids.MAX_MONOID)) = BinaryOps.MAX
Monoids.Monoid(::typeof(max)) = Monoids.MAX_MONOID
"""
Plus monoid: `f(x::T, y::T)::T = x + y`
* Identity: 0
* Terminal: nothing
"""
Monoids.PLUS_MONOID
op(::typeof(Monoids.PLUS_MONOID)) = BinaryOps.PLUS
Monoids.Monoid(::typeof(+)) = Monoids.PLUS_MONOID
"""
Times monoid: `f(x::T, y::T)::T = xy`
* Identity: 1
* Terminal: 0 for non Floating-point numbers.
"""
Monoids.TIMES_MONOID
op(::typeof(Monoids.TIMES_MONOID)) = BinaryOps.TIMES
Monoids.Monoid(::typeof(*)) = Monoids.TIMES_MONOID
"""
Any monoid: `f(x::T, y::T)::T = x or y`
* Identity: any
* Terminal: any
"""
Monoids.ANY_MONOID
op(::typeof(Monoids.ANY_MONOID)) = BinaryOps.ANY
Monoids.Monoid(::typeof(any)) = Monoids.ANY_MONOID
"""
Logical OR monoid: `f(x::Bool, y::Bool)::Bool = x ∨ y`
* Identity: false
* Terminal: true
"""
Monoids.LOR_MONOID
op(::typeof(Monoids.LOR_MONOID)) = BinaryOps.LOR
Monoids.Monoid(::typeof(∨)) = Monoids.LOR_MONOID
"""
Logical AND monoid: `f(x::Bool, y::Bool)::Bool = x ∧ y`
* Identity: true
* Terminal: false
"""
Monoids.LAND_MONOID
op(::typeof(Monoids.LAND_MONOID)) = BinaryOps.LAND
Monoids.Monoid(::typeof(∧)) = Monoids.LAND_MONOID
"""
Logical XOR monoid: `f(x::Bool, y::Bool)::Bool = x ⊻ y`
* Identity: false
* Terminal: nothing
"""
Monoids.LXOR_MONOID
op(::typeof(Monoids.LXOR_MONOID)) = BinaryOps.LXOR
Monoids.Monoid(::typeof(lxor)) = Monoids.LXOR_MONOID
"""
Logical XNOR monoid: `f(x::Bool, y::Bool)::Bool = x == y`
* Identity: true
* Terminal: nothing
"""
Monoids.LXNOR_MONOID
#Don't care, this is ==.
"""
Boolean Equality `f(x::Bool, y::Bool)::Bool = x == y`.
"""
Monoids.EQ_MONOID
op(::typeof(Monoids.EQ_MONOID)) = BinaryOps.EQ
Monoids.Monoid(::typeof(==)) = Monoids.EQ_MONOID
"""
Bitwise OR monoid: `f(x::ℤ, y::ℤ)::ℤ = x|y`
* Identity: All bits `0`.* Terminal: All bits `1`.
"""
Monoids.BOR_MONOID
op(::typeof(Monoids.BOR_MONOID)) = BinaryOps.BOR
Monoids.Monoid(::typeof(|)) = Monoids.BOR_MONOID
"""
Bitwise AND monoid: `f(x::ℤ, y::ℤ)::ℤ = x&y`
* Identity: All bits `1`.
* Terminal: All bits `0`.
"""
Monoids.BAND_MONOID
op(::typeof(Monoids.BAND_MONOID)) = BinaryOps.BAND
Monoids.Monoid(::typeof(&)) = Monoids.BAND_MONOID
"""
Bitwise XOR monoid: `f(x::ℤ, y::ℤ)::ℤ = x^y`
* Identity: All bits `0`.
* Terminal: nothing
"""
Monoids.BXOR_MONOID
op(::typeof(Monoids.BXOR_MONOID)) = BinaryOps.BXOR
Monoids.Monoid(::typeof(⊻)) = Monoids.BXOR_MONOID
"""
Bitwise XNOR monoid: `f(x::ℤ, y::ℤ)::ℤ = ~(x^y)`
* Identity: All bits `1`.
* Terminal: nothing
"""
Monoids.BXNOR_MONOID
op(::typeof(Monoids.BXNOR_MONOID)) = BinaryOps.BXNOR
Monoids.Monoid(::typeof(!⊻)) = Monoids.BXNOR_MONOID

mulop(rig::AbstractSemiring) = nothing
addop(rig::AbstractSemiring) = nothing

# TODO:
# The definitions below are less than ideal, these should likely be fields of the semiring
# structs, not evaluated functions.
for oplus ∈ [(:max, "MAX"), (:min, "MIN"), (:+, "PLUS"), (:*, "TIMES"), (:any, "ANY")]
    for otimes ∈ [
        (:/, "DIV"),
        (:\, "RDIV"),
        (:first, "FIRST"),
        (:firsti, "FIRSTI1"),
        (:firstj, "FIRSTJ1"),
        (:iseq, "ISEQ"),
        (:isge, "ISGE"),
        (:isgt, "ISGT"),
        (:isle, "ISLE"),
        (:islt, "ISLT"),
        (:isne, "ISNE"),
        (:∧, "LAND"),
        (:∨, "LOR"),
        (:lxor, "LXOR"),
        (:max, "MAX"),
        (:min, "MIN"),
        (:-, "MINUS"),
        (:rminus, "RMINUS"),
        (:second, "SECOND"),
        (:secondi, "SECONDI1"),
        (:secondj, "SECONDJ1"),
        (:*, "TIMES"),
        (:+, "PLUS"),
        (:pair, "PAIR")
    ]
    rig = Symbol(oplus[2], "_", otimes[2])
    funcquote = quote
        Semirings.Semiring(::typeof($(oplus[1])), ::typeof($(otimes[1]))) = $rig
        addop(::typeof($rig)) = $(oplus[1])
        mulop(::typeof($rig)) = $(otimes[1])
    end
    @eval($funcquote)
    end
end

for otimes ∈ [(:(==), "EQ"), (:(>=), "GE"), (:(>), "GT"), (:(<=), "LE"), (:(<), "LT"), (:(!=), "NE")]
    oplus = (:any, "ANY")
    rig = Symbol(oplus[2], "_", otimes[2])
    funcquote = quote
        Semirings.Semiring(::typeof($(oplus[1])), ::typeof($(otimes[1]))) = $rig
        addop(::typeof($rig)) = $(oplus[1])
        mulop(::typeof($rig)) = $(otimes[1])
    end
    @eval($funcquote)
end
for oplus ∈ [(:(==), "EQ"), (:∧, "LAND"), (:∨, "LOR"), (:lxor, "LXOR")]
    for otimes ∈ [
        (:(==), "EQ"),
        (:(>=), "GE"),
        (:(>), "GT"),
        (:(<=), "LE"),
        (:(<), "LT"),
        (:(!=), "NE"),
        (:first, "FIRST"),
        (:second, "SECOND"),
        (:∧, "LAND"),
        (:∨, "LOR"),
        (:lxor, "LXOR"),
        (:pair, "PAIR")
    ]
    rig = Symbol(oplus[2], "_", otimes[2])
    funcquote = quote
        Semirings.Semiring(::typeof($(oplus[1])), ::typeof($(otimes[1]))) = $rig
        addop(::typeof($rig)) = $(oplus[1])
        mulop(::typeof($rig)) = $(otimes[1])
    end
    @eval($funcquote)
    end
end

#Select Ops

"""
    select(SuiteSparseGraphBLAS.TRIL, A, k=0)
    select(tril, A, k=0)

Select the entries on or below the `k`th diagonal of A.

See also: `LinearAlgebra.tril`
"""
TRIL
SelectOp(::typeof(LinearAlgebra.tril)) = TRIL
"""
    select(SuiteSparseGraphBLAS.TRIU, A, k=0)
    select(triu, A, k=0)

Select the entries on or above the `k`th diagonal of A.

See also: `LinearAlgebra.triu`
"""
TRIU
SelectOp(::typeof(LinearAlgebra.triu)) = TRIU
"""
    select(DIAG, A, k=0)

Select the entries on the `k`th diagonal of A.

See also: `LinearAlgebra.diag`
"""
DIAG
SelectOp(::typeof(LinearAlgebra.diag)) = DIAG
"""
    select(OFFDIAG, A, k=0)

Select the entries **not** on the `k`th diagonal of A.
"""
OFFDIAG
SelectOp(::typeof(offdiag)) = OFFDIAG
"""
    select(NONZERO, A)
    select(nonzeros, A)
Select all entries in A with nonzero value.
"""
NONZERO
SelectOp(::typeof(nonzeros)) = NONZERO

# I don't believe these should have Julia equivalents.
# Instead select(==, A, 0) will find EQ_ZERO internally.
"""
    select(EQ_ZERO, A)

Select all entries in A equal to zero.
"""
EQ_ZERO
"""
    select(GT_ZERO, A)

Select all entries in A greater than zero.
"""
GT_ZERO
"""
    select(GE_ZERO, A)

Select all entries in A greater than or equal to zero.
"""
GE_ZERO
"""
    select(LT_ZERO, A)

Select all entries in A less than zero.
"""
LT_ZERO
"""
    select(LE_ZERO, A)

Select all entries in A less than or equal to zero.
"""
LE_ZERO
"""
    select(NE, A, k)
    select(!=, A, k)
Select all entries not equal to `k`.
"""
NE
SelectOp(::typeof(!=)) = NE
"""
    select(EQ, A, k)
    select(==, A, k)
Select all entries equal to `k`.
"""
EQ
SelectOp(::typeof(==)) = EQ
"""
    select(GT, A, k)
    select(>, A, k)
Select all entries greater than `k`.
"""
GT
SelectOp(::typeof(>)) = GT
"""
    select(GE, A, k)
    select(>=, A, k)
Select all entries greater than or equal to `k`.
"""
GE
SelectOp(::typeof(>=)) = GE
"""
    select(LT, A, k)
    select(<, A, k)
Select all entries less than `k`.
"""
LT
SelectOp(::typeof(<)) = LT
"""
    select(LE, A, k)
    select(<=, A, k)
Select all entries less than or equal to `k`.
"""
LE
SelectOp(::typeof(<=)) = LE


###############################################################################
#
# nanmean, nanmedian, nanstd, nanmax, nanmin
#
###############################################################################
"""

    nanmax(x::Array)

Return the maximum of array ommiting the NaN, given
- `x` Array of numbers, can be NaN

---
Example
```julia
xs = [1, 2, 4, NaN];
nmax = nanmax(xs);
```
"""
function nanmax(x::Array)
    _x = filter(!isnan, x);

    if length(_x) == 0 return NaN end;

    return maximum( _x )
end

"""

    nanmean(x::Array)

Return the mean of array by ommiting the NaN, given
- `x` Array of numbers, can be NaN

---
Example
```julia
xs = [1, 2, 4, NaN];
nmean = nanmean(xs);
```
"""
function nanmean(x::Array)
    _x = filter(!isnan, x);

    if length(_x) == 0 return NaN end;

    return mean( _x )
end

"""

    nanmedian(x::Array)

Return the median of array by ommiting the NaN, given
- `x` Array of numbers, can be NaN

---
Example
```julia
xs = [1, 2, 4, NaN];
nmed = nanmedian(xs);
```
"""
function nanmedian(x::Array)
    _x = filter(!isnan, x);

    if length(_x) == 0 return NaN end;

    return median( _x )
end

"""

    nanmin(x::Array)

Return the maximum of array ommiting the NaN, given
- `x` Array of numbers, can be NaN

---
Example
```julia
xs = [1, 2, 4, NaN];
nmin = nanmin(xs);
```
"""
function nanmin(x::Array)
    _x = filter(!isnan, x);

    if length(_x) == 0 return NaN end;

    return minimum( _x )
end

"""

    nanpercentile(x::Array, p::Number)

Return the percentile by excluding the NaN of given
- `x` Array of data
- `p` Percentile

---
Example
```julia
xs = rand(100);
pth = nanpercentile(rand(100), 50);
xs[1:10] .= NaN;
pth = nanpercentile(rand(100), 50);
```
"""
function nanpercentile(x::Array, p::Number)
    @assert 0 <= p <= 100

    _x = filter(!isnan, x);

    if length(_x) == 0 return NaN end;

    return percentile( _x, p )
end

"""

    nanstd(x::Array)

Return the std of array by ommiting the NaN, given
- `x` Array of numbers, can be NaN

---
Example
```julia
xs = [1, 2, 4, NaN];
nstd = nanstd(xs);
```
"""
function nanstd(x::Array)
    _x = filter(!isnan, x);

    if length(_x) == 0 return NaN end;

    return std( _x )
end

###############################################################################
#
# Error measures
#
###############################################################################
"""

    mae(y::Array, pred::Array)

Return the mean absolute error by ommiting the NaN, given
- `y` Array of numbers, can be NaN
- `pred` Array of predictions, can be NaN

---
Example
```julia
ys = [1, 2, 4, NaN];
preds = [2, 4, 1, NaN];
nmae = mae(ys, preds);
```
"""
function mae(y::Array, pred::Array)
    return nanmean( abs.(y .- pred) )
end

"""

    mape(y::Array, pred::Array)

Return the mean absolute percentage error by ommiting the NaN, given
- `y` Array of numbers, can be NaN
- `pred` Array of predictions, can be NaN

---
Example
```julia
ys = [1, 2, 4, NaN];
preds = [2, 4, 1, NaN];
nmape = mape(ys, preds);
```
"""
function mape(y::Array, pred::Array)
    _mean = abs( nanmean(y) );
    _diff = abs.(y .- pred) ./ _mean .* 100;

    return nanmean( _diff )
end

"""

    mase(y::Array, pred::Array)

Return the mean absolute standardized error by ommiting the NaN, given
- `y` Array of numbers, can be NaN
- `pred` Array of predictions, can be NaN

---
Example
```julia
ys = [1, 2, 4, NaN];
preds = [2, 4, 1, NaN];
nmase = mase(ys, preds);
```
"""
function mase(y::Array, pred::Array)
    _nstd = nanstd(y);
    _diff = abs.(y .- pred) ./ _nstd .* 100;

    return nanmean( _diff )
end

"""

    rmse(y::Array, pred::Array)

Return the root mean square error by ommiting the NaN, given
- `y` Array of numbers, can be NaN
- `pred` Array of predictions, can be NaN

---
Example
```julia
ys = [1, 2, 4, NaN];
preds = [2, 4, 1, NaN];
rmse = rmse(ys, preds);
```
"""
function rmse(y::Array, pred::Array)
    return sqrt( nanmean( (y .- pred) .^ 2 ) )
end


"""
    schottky_genus_4(τ)

Compute the value of the genus 4 Schottky-Igusa polynomial, given as input a 4x4 matrix in the Siegel upper half space.
"""
function schottky_genus_4(τ::Array{<:Number})
    R = RiemannMatrix(τ, siegel=false, ϵ=1.0e-10, nderivs=0);
    m1 = [[1,0,1,0], [1,0,1,0]];
    m2 = [[0,0,0,1], [1,0,0,0]];
    m3 = [[0,0,1,1], [1,0,1,1]];
    n1 = [[0,0,0,1], [1,1,1,0]];
    n2 = [[0,0,1,1], [0,0,0,1]];
    n3 = [[0,0,1,0], [1,0,1,1]];
    p1 = 1;
    p2 = 1;
    p3 = 1;
    z = [0; 0; 0; 0];
    for i1=0:1, i2=0:1, i3=0:1
        n = i1*n1 + i2*n2 + i3*n3;
        c1 = theta(z, R, char=remainder_char(m1+n));
        c2 = theta(z, R, char=remainder_char(m2+n));
        c3 = theta(z, R, char=remainder_char(m3+n));
        p1 *= c1;
        p2 *= c2;
        p3 *= c3;
    end
    schottky_poly = p1^2 + p2^2 + p3^2 - 2*p1*p2 - 2*p1*p3 - 2*p2*p3;
    return schottky_poly
end

"""
    random_nonschottky_genus_4(tol=0.1, trials=100)

Find a random 4x4 matrix in the Siegel upper half space which is not in the Schottky locus, up to the input tolerance and number of trials.
"""
function random_nonschottky_genus_4(tol::Real=0.1, trials::Integer=100)
    t = 0; # largest value of schottky polynomial
    i = 0; # counter for number of trials
    max_matrix = rand(4,4); # stores best non-schottky candidate
    while t < tol && i < trials
        τ = random_siegel(4);
        s = schottky_genus_4(τ);
        if abs(s) > t
            t = abs(s)
            max_matrix = τ;
        end
        i += 1;
    end
    return [max_matrix, t];
end


# -- PRIVATE METHODS --------------------------------------------------------------------------------------------------- #
# ---------------------------------------------------------------------------------------------------------------------- #

# -- PUBLIC METHODS ---------------------------------------------------------------------------------------------------- #
function compute_exponential_moving_average(data::DataFrame, startDate::Date, delay::Int64;
    timestampColKeySymbol::Symbol=Symbol("timestamp"), dataColKeySymbol::Symbol=Symbol("adjusted_close"))::PSResult

    # initialize -
    price_array = Array{Float64,1}()
    exponential_moving_average = Array{Float64,1}()

    # check: delay is non-negative?
    if (delay < 0)
        return PSResult(ArgumentError("delay parameter must be non-negative"))
    end

    # check: find the index of this date -
    idx_start_date = findfirst(x -> x == startDate, data[!, timestampColKeySymbol])
    if (idx_start_date === nothing)
        return PSResult(ArgumentError("the start date is not contained in the data set"))
    end

    try

        # populate the price array -
        (number_of_days, number_of_cols) = size(data)
        for index = idx_start_date:number_of_days
            value = data[index,dataColKeySymbol]
            push!(price_array, value)
        end

        # compute the SMA (start point) -
        tmp_array = Array{Float64,1}()
        for index = 1:delay
            value = price_array[index]
            push!(tmp_array, value)
        end
        SMA = mean(tmp_array)

        # what is the weight parameter -
        ω = 2.0 / (delay + 1.0)

        # compute -
        push!(exponential_moving_average, SMA)
        for index = 1:number_of_days

            price_value = price_array[index]
            EMA_old = exponential_moving_average[index]
            EMA_new = ω * price_value + (1.0 - ω) * EMA_old
            push!(exponential_moving_average, EMA_new)
        end

        # return -
        return PSResult(exponential_moving_average)
    catch error
        return PSResult(error)
    end
end

function compute_fibonacci_retracement_levels(data::DataFrame;
    dataColKeySymbol::Symbol=Symbol("adjusted_close"))::PSResult

    # initialize -
    fnrl_array = zeros(7, 2)
    fn_array = [0.0 0.236 0.382 0.5 0.618 0.786 1.0];

    try

        # find the min and the max in the data being passed in -
        min_value = minimum(data[!,dataColKeySymbol])
        max_value = maximum(data[!,dataColKeySymbol])
        𝝙 = max_value - min_value

        # compute the levels -
        for (index, level) in enumerate(fn_array)
            tmp = max_value - 𝝙 * level
            fnrl_array[index,1] = level
            fnrl_array[index,2] = tmp
        end

        # return -
        return PSResult{Array{Float64,2}}(fnrl_array)
    catch error
        return PSResult(error)
    end
end
# ---------------------------------------------------------------------------------------------------------------------- #


# project x1 on to x2
project(x1, x2) = (x1' * x2) / (x2' * x2) * x2

# compute intercept and slope from two points
function get_coefs(x1, x2)
     β1 = (x2[2] - x1[2]) / (x2[1] - x1[1])
     return x2[2] - β1 * x2[1], β1
end

# make a line
make_line(β0, β1, x) = β0 .+ β1 .* x


immutable State
    intercept::Float64
    slope::Float64
end

immutable Datum
    x::Float64
    y::Float64
end

immutable LinregMHSampler <: Sampler
    num_steps::Int
    prior_std::Float64
    noise_std::Float64
    data::Array{Datum,1}
    function LinregMHSampler(x::Array{Float64,1}, y::Array{Float64,1},
                            prior_std::Float64, noise_std::Float64, num_steps::Int)
        @assert length(x) == length(y)
        data = Array{Datum,1}()
        for (xi, yi) in zip(x, y)
            push!(data, Datum(xi, yi))
        end
        new(num_steps, prior_std, noise_std, data)
    end
end

function prior_sample(sampler::LinregMHSampler)
    intercept = rand(Normal(0., sampler.prior_std))
    slope = rand(Normal(0., sampler.prior_std))
    State(intercept, slope)
end

function log_prior(sampler::LinregMHSampler, state::State)
    lp = logpdf(Normal(0., sampler.prior_std), state.intercept)
    lp += logpdf(Normal(0., sampler.prior_std), state.slope)
    lp
end

function log_likelihood(sampler::LinregMHSampler, state::State)
    ll = 0.0
    for datum in sampler.data
        y_expected = state.intercept + state.slope * datum.x
        ll += logpdf(Normal(y_expected, sampler.noise_std), datum.y)
    end
    ll
end

function log_unnormalized_posterior(sampler::LinregMHSampler, state::State)
    log_prior(sampler, state) + log_likelihood(sampler, state)
end

function imh_step(sampler::LinregMHSampler, state::State)
    # perform one step of independent MH
    # propose from prior
    state_proposed = prior_sample(sampler)
    if log(rand()) <= log_likelihood(sampler, state_proposed) - log_likelihood(sampler, state)
        # accept
        new_state = state_proposed
    else
        # reject
        new_state = state
    end
    new_state
end

function run_markov_chain(sampler::LinregMHSampler, state::State, steps::Int)
    for step in 1:steps
        state = imh_step(sampler, state)
    end
    state
end

function run_inference(sampler::LinregMHSampler)
    init_state = prior_sample(sampler)
    state = run_markov_chain(sampler, init_state, sampler.num_steps)
    # returns the initial and final state of the Markov chain
    # the initial state is all we need from the trace
    return (init_state, state)
end

function log_weight(sampler::LinregMHSampler, init_state::State, output::State)
    lw = 0.
    lw += log_prior(sampler, init_state)
    lw += log_unnormalized_posterior(sampler, output)
    lw -= log_unnormalized_posterior(sampler, init_state)
    lw
end

function simulate(sampler::LinregMHSampler)
    (init_state, state) = run_inference(sampler)
    ([state.intercept, state.slope], log_weight(sampler, init_state, state))
end

function regenerate(sampler::LinregMHSampler, intercept_and_slope::Array{Float64,1})
    @assert length(intercept_and_slope) == 2
    intercept, slope = intercept_and_slope
    state = State(intercept, slope)
    init_state = run_markov_chain(sampler, state, sampler.num_steps)
    log_weight(sampler, init_state, state)
end

immutable PriorSampler <: Sampler
    x::Array{Float64,1}
    y::Array{Float64,1}
    prior_std::Float64
    noise_std::Float64
end

function simulate(sampler::PriorSampler)
    dist = Normal(0., sampler.prior_std)
    intercept = rand(dist)
    slope = rand(dist)
    ([intercept, slope], logpdf(dist, intercept) + logpdf(dist, slope))
end

function regenerate(sampler::PriorSampler, intercept_and_slope::Array{Float64,1})
    (intercept, slope) = intercept_and_slope
    dist = Normal(0., sampler.prior_std)
    logpdf(dist, intercept) + logpdf(dist, slope)
end


export sbesselj, shankelh1, diffsbessel, diffbessel
export diffbesselj, diffhankelh1, diffsbesselj, diffshankelh1
export gaunt_coefficient
export associated_legendre_indices, spherical_harmonics_indices, lm_to_spherical_harmonic_index
export spherical_harmonics
export cartesian_to_radial_coordinates, radial_to_cartesian_coordinates
export atan

#NOTE spherical bessel and hankel functions soon coming to SpecialFunctions.jl https://github.com/JuliaMath/SpecialFunctions.jl/pull/196

#NOTE atan(x,y) is defined here but will likely be added to base soon.

import Base.atan
atan(y::Complex,x::Complex) = - im * log( (x+y*im) / sqrt(x^2+y^2) )

"""
    sbesselj(m,x)

Returns the spherical besselj function. The order is 'm' and the argument is 'x'. Note 'x' can be a complex number.
"""
# function sbesselj(m::Number,x::T) where T<: Union{F,Complex{F}} where F <: AbstractFloat
    # if (abs(x) > eps(F))
    #     return sqrt(pi/(T(2)*x)) * besselj(m+1/2,x)
    # else
    #     return (m > 0 ? zero(T) : one(T))
    # end
# end
function sbesselj(nu::Number, x::T) where {T}
    besselj_nuhalf_x = besselj(nu + one(nu)/2, x)
    if abs(x) ≤ sqrt(eps(real(zero(besselj_nuhalf_x))))
        nu == 0 ? one(besselj_nuhalf_x) : zero(besselj_nuhalf_x)
    else
        √((float(T))(π)/2x) * besselj_nuhalf_x
    end
end

"""
    shankelh1(m,x)

Returns the spherical hankel function of the first kind. The order is 'm' and the argument is 'x'. Note 'x' can be a complex number.
"""
shankelh1(nu::Number,x::T) where T = √((float(T))(π)/2x) * hankelh1(nu+one(nu)/2,x)

"""
    diffsbessel(f::Function,m,x)

Differentiates the spherical bessel function 'f' (for any spherical bessel). The order is 'm' and the argument is 'x'. Note 'x' can be a complex number.
"""
function diffsbessel(f::Function,n::Number,z::Number)
    return f(n-1,z) - (n+1) * f(n,z) / z
end

function diffsbesselj(n::Number,z::Number)
    return if n == 0
        - sbesselj(1,z) # case due to numerical stability
    else
        sbesselj(n-1,z) - (n+1) * sbesselj(n,z) / z
    end
end

function diffshankelh1(n::Number,z::Number)
    return shankelh1(n-1,z) - (n+1) * shankelh1(n,z) / z
end

"""
    diffbessel(f::Function,m,x,n::Int)

Differentiates 'n' times any bessel function 'f' of order 'm' and at the argument 'x'.
"""
function diffbessel(f::Function,m::Number,z,n::Int)
    if n == 0
        return f(m, z)
    elseif n > 0
        n = n - 1
        return 0.5*(diffbessel(f,m-1,z,n) - diffbessel(f,m+1,z,n))
    else
        error("Can not differentiate a negative number of times")
    end
end

"""
    diffhankelh1(m,x,n::Int)

Differentiates 'n' times the hankelh1 function of order 'm' and at the argument 'x'.
"""
function diffhankelh1(m::Number,z::T,n::Int) where T<:Number
    if n == 0
        return hankelh1(m, z)
    elseif n > 0
        n = n - 1
        return 0.5*(diffhankelh1(m-1,z,n) - diffhankelh1(m+1,z,n))
    else
        error("Can not differentiate a negative number of times")
    end
end

"Derivative of Hankel function of the first kind"
diffhankelh1(n,z) = 0.5*(hankelh1(-1 + n, z) - hankelh1(1 + n, z))

"Derivative of Bessel function of first kind"
diffbesselj(n::Number,z::T) where T<:Number = 0.5*(besselj(-1 + n, z) - besselj(1 + n, z))

"m-th Derivative of Hankel function of the first kind"
function diffbesselj(n::Number,z::T,m::Int) where T<:Number
    if m == 0
        return besselj(n, z)
    elseif m > 0
        m = m - 1
        return T(0.5)*(diffbesselj(n-1,z,m) - diffbesselj(n+1,z,m))
    else
        error("Can not differentiate a negative number of times")
    end
end

"""
    gaunt_coefficient(l1,m1,l2,m2,l3,m3)

A version of the Gaunt coefficients which are used to write the product of two spherical harmonics. If Y_{l,m} is a complex spherical harmonic, with the typical phase conventions from quantum mechanics, then:

    gaunt_coefficient(l1,m1,l2,m2,l3,m3) = 4*π*im^{l2+l3-l1} Integral[Y_{l1,m1}*conj(Y_{l2,m2})*conj(Y_{l3,m3})]

where the integral is over the solid angle.

The most standard gaunt coefficients `G(l1,m1;l2,m2;l3)` are related through the identity:

    4pi * G(l1,m1;l2,m2;l3) = im^(l1-l2-l3) * (-1)^m2 * gaunt_coefficient(l1,m1,l2,-m2,l3,m1+m2)

"""
function gaunt_coefficient(T::Type{<:AbstractFloat},l1::Int,m1::Int,l2::Int,m2::Int,l3::Int,m3::Int)
    # note the wigner3j has only one convention, and is highly symmetric.
    return (one(T)*im)^(l2+l3-l1) * (-T(1))^m1 * sqrt(4pi*(2*l1+1)*(2*l2+1)*(2*l3+1)) *
        wigner3j(T,l1,l2,l3,0,0,0) * wigner3j(T,l1,l2,l3,m1,-m2,-m3)
end
gaunt_coefficient(l1::Int,m1::Int,l2::Int,m2::Int,l3::Int,m3::Int) = gaunt_coefficient(Float64,l1,m1,l2,m2,l3,m3)

lm_to_spherical_harmonic_index(l::Int,m::Int)::Int = l^2 + m + l + 1

function spherical_harmonics_indices(l_max::Int)
    ls = [l for l in 0:l_max for m in -l:l]
    ms = [m for l in 0:l_max for m in -l:l]

    return ls, ms
end

function associated_legendre_indices(l_max::Int)
    ls = [l for l in 0:l_max for m in 0:l]
    ms = [m for l in 0:l_max for m in 0:l]

    return ls, ms
end

"""
`spherical_harmonics(l_max::Int, θ::T, φ::T)`

returns a vector of all spherical harmonics with degree `l <= l_max`. The degree and order (indices) of the elements of the vector are given by `spherical_harmonics_indices(l_max::Int)`.

The associated legendre polynomials are taken from the package GSL.jl.
"""
function spherical_harmonics(l_max::Int, θ::Complex{T}, φ::Union{T,Complex{T}}) where T <: AbstractFloat

    throw(DomainError(θ, "Currently GLS.jl is used to calculate associated legendre polynomials, which only take real angles as arguments. Hopefully soon SpecialFunctions.jl will implement associated Legendre for complex arguments: https://github.com/JuliaMath/SpecialFunctions.jl/pull/175"))

end
function spherical_harmonics(l_max::Int, θ::T, φ::T) where T <: AbstractFloat

    ls, ms = associated_legendre_indices(l_max)
    Plm_arr = sf_legendre_array(GSL_SF_LEGENDRE_SPHARM, l_max, cos(θ))[eachindex(ls)]

    Ylm_vec = Vector{Complex{T}}(undef, (l_max+1)^2)
    Ylm_vec[1] = Plm_arr[1]

    ind1 = 1
    ind2 = 1
    for i = 1:l_max
        inds1 = (ind1+i):(ind1+2i)
        Ylm_vec[(ind2+i):(ind2+3i)] = [reverse((-1).^ms[inds1[2:end]] .* conj(Plm_arr[inds1[2:end]])); Plm_arr[inds1]]
        ind1 += i
        ind2 += 2i
    end

    ls, ms = spherical_harmonics_indices(l_max)
    Ylm_vec = (-one(T)) .^ ms .* exp.(ms .* (im*φ)) .* Ylm_vec

    return Ylm_vec
end

cartesian_to_radial_coordinates(x::Vector) = cartesian_to_radial_coordinates(SVector(x...))
radial_to_cartesian_coordinates(θ::Vector) = radial_to_cartesian_coordinates(SVector(θ...))

function cartesian_to_radial_coordinates(x::SVector{3,CT}) where CT
    r = sqrt(sum(x .^2)) # note this is, and should be, a complex number when x is a complex vector
    θ = atan(sqrt(x[1]^2+x[2]^2),x[3])
    φ = (!iszero(x[1]) || !iszero(x[2])) ? atan(x[2], x[1]) : zero(CT)
    return [r,θ,φ]
end

function cartesian_to_radial_coordinates(x::SVector{2,CT}) where CT
    r = sqrt(sum(x .^2)) # note this should be complex if x is complex
    θ = atan(x[2], x[1])
    return [r,θ]
end

function radial_to_cartesian_coordinates(rθφ::SVector{3,CT}) where CT
    r, θ, φ = rθφ
    x = r * sin(θ) * cos(φ)
    y = r * sin(θ) * sin(φ)
    z = r * cos(θ)

    return [x,y,z]
end

function radial_to_cartesian_coordinates(rθ::SVector{2,CT}) where CT
    r, θ = rθ
    x = r * cos(θ)
    y = r * sin(θ)

    return [x,y]
end


module SUN_generator
    using LinearAlgebra

    struct Generator
        NC::Int64
        generator::Array{Array{ComplexF64,2},1}
        function Generator(NC)
            return new(NC,make_generators(NC))
        end
    end

    function Base.getindex(g::Generator,i)
        return g.generator[i]
    end

    function Base.length(a::Generator)
        return length(a.generator)
    end

    function lie2matrix!(matrix,g::Generator,a)
        matrix .= 0
        NC = g.NC
        for (i,genmatrix) in enumerate(g.generator)
            matrix .+= a[i]*genmatrix
        end
        return
    end

    function matrix2lie!(a,g::Generator,A)
        for i=1:length(a)
            #println("i = $i")
            #display(g.generator[i]*g.generator[i])
            #println("\t")
            #println(tr(g.generator[i]*A)/2)
            a[i] = tr(g.generator[i]*A)/2
        end
        return
    end

    sigma = []
    s = ComplexF64[
        0 1
        1 0
    ]
    push!(sigma,s)
    s = ComplexF64[
        0 -im
        im 0
    ]
    push!(sigma,s)
    s = ComplexF64[
        1 0
        0 -1
    ]
    push!(sigma,s)

    function make_largematrix(σ,i,j,Nc)
        λ = zeros(ComplexF64,Nc,Nc)
        λ[i,i] = σ[1,1]
        λ[i,j] = σ[1,2]
        λ[j,i] = σ[2,1]
        λ[j,j] = σ[2,2]
        return λ
    end

    function normalization(A)
        Norm = sqrt(real(tr(A*A))/2)
        #print("    Normalization = √",real(tr(A*A))/2,"= ")
        #println("$Norm")
        return Norm
    end

    function make_generators(Nc)
        d=["x","y","z"]
        #
        lams = Array{Complex,2}[]
        #
        if Nc < 2
            error("Invalid Nc=$Nc")
        end
        #
        # off-diagonal part
        #println("Off diagonals")
        for i=1:Nc
            for j=i+1:Nc
                for a = 1:2
                    #println("$(d[a]): i j = $i $j")
                    A = make_largematrix(sigma[a],i,j,Nc)
                    #showmat(A)
                    N=normalization(A)
                    A = A/N
                    push!(lams,A)
                    #println(" ")
                end
            end
        end
        #
        # diagonal part
        #println("diagonals")
        for a=1:Nc-1
            A = zeros(ComplexF64,Nc,Nc)
            for i=1:a
                A[i,i] = 1
            end
            A[a+1,a+1] = -tr(A)
            #showmat(A)
            N=normalization(A)
            A = A/N
            push!(lams,A)
            #println(" ")
        end
        return lams
    end
end


"""
    Chebyshev( xmin, xmax, nx )

Chebyshev nodes

"""
struct Chebyshev <: NodesType

    nx    :: Int64
    xmin  :: Float64
    xmax  :: Float64
    xk    :: Vector{Float64}

    function Chebyshev( xmin, xmax, nx )

        xk  = zeros(Float64, nx)
        θ = (π:-π/(nx-1):-1e-14)
        xk  .= ((cos.(θ).+1)*(xmax-xmin))/2 .+ xmin
        new( nx, xmin, xmax, xk )

    end

end


using Statistics
function fit(x; axis = 1)
    if axis == 2
        x = x'
    end
    if ndims(x) == 1
        p = [quantile(x, 1/4), quantile(x, 1/2), quantile(x, 3/4)]
    else
        p = []
        for i in 1:size(x)[2]
            push!(p, [quantile(x[:, i], 1/4), quantile(x[:, i], 1/2), quantile(x[:, i], 3/4)])
        end
    end
    return p
end
function transform(x, p; axis = 1)
    if axis == 2
        x = x'
    end
    if ndims(x) == 1
        t = (x .- p[2]) / (p[3] - p[1])
    else
        t = []
        for i in 1:size(x)[2]
            push!(t, (x[:, i] .- p[i][2]) / (p[i][3] - p[i][1]))
        end
    end
    return t
end
function inverse_transform(x, p; axis = 1)
    if axis == 2
        x = x'
    end
    if ndims(x) == 1
        t = (x * (p[3] - p[1])) .+ p[2]
    else
        t = []
        for i in 1:size(x)[2]
            push!(t, (x[:, i][1] * (p[i][3] - p[i][1])) .+ p[i][2])
        end
    end
    return t
end
function fit_transform(x; axis = 1)
    if axis == 2
        x = x'
    end
    if ndims(x) == 1
        p = [quantile(x, 1/4), quantile(x, 1/2), quantile(x, 3/4)]
        t = (x .- p[2]) / (p[3] - p[1])
    else
        t, p = [], []
        for i in 1:size(x)[2]
            push!(p, [quantile(x[:, i], 1/4), quantile(x[:, i], 1/2), quantile(x[:, i], 3/4)])
            push!(t, (x[:, i] .- p[i][2]) / (p[i][3] - p[i][1]))
        end
    end
    return t, p
end


# Used "Conjugate Bayesian analysis of the Gaussian distribution" by Murphy as
# a reference.  Note that there were some typos in that document so the code
# here may not correspond exactly.

struct NormalGamma{T<:Real} <: ContinuousUnivariateDistribution
    mu::T
    nu::T     # scales precision of Normal
    shape::T
    rate::T

    function NormalGamma{T}(mu::T, nu::T, sh::T, r::T) where T<:Real
    	nu > zero(nu) && sh > zero(sh) && r > zero(r) || error("Both shape and scale must be positive")
    	new(T(mu), T(nu), T(sh), T(r))
    end
end

function NormalGamma(mu::Real, nu::Real, sh::Real, r::Real)
    T = promote_type(typeof(mu), typeof(nu), typeof(sh), typeof(r))
    return NormalGamma{T}(T(mu),T(nu),T(sh),T(r))
end

mu(d::NormalGamma) = d.mu
nu(d::NormalGamma) = d.nu
shape(d::NormalGamma) = d.shape
scale(d::NormalGamma) = 1. / d.rate
rate(d::NormalGamma) = d.rate

insupport(::Type{NormalGamma}, x::T, tau2::T) where T<:Real =
    isfinite(x) && zero(tau2) <= tau2 < Inf

# Probably should guard agains dividing by and taking the log of 0.
function pdf(d::NormalGamma, x::T, tau2::T) where T<:Real
    Zinv = d.rate.^d.shape / gamma(d.shape) * sqrt(d.nu / (2.0*pi))
    return Zinv * tau2.^(d.shape-0.5) * exp(-0.5*tau2*(d.nu*(x-d.mu).^2 + 2.0*d.rate))
end
function logpdf(d::NormalGamma, x::T, tau2::T) where T<:Real
    lZinv = d.shape*log(d.rate) - lgamma(d.shape) + 0.5*(log(d.nu) - log(2.0*pi))
    return lZinv + (d.shape-0.5)*log(tau2) - 0.5*tau2*(d.nu*(x-d.mu).^2 + 2*d.rate)
end

function rand(d::NormalGamma)
    # Guard against invalid precisions
    tau2 = rand(Gamma(d.shape, scale(d)))
    if tau2 <= zero(Float64)
        tau2 = eps(Float64)
    end
    mu = rand(Normal(d.mu, sqrt(1.0/(tau2*d.nu))))
    return mu, tau2
end


using LinearAlgebra

function backtrackLineSearch(xInit, dirΔ, f, dfdx, paramA, paramB, verbose = false)
    # Output an updated x

    if verbose
        println("\nBeginning Line Search")
        println("xInit = $xInit")
        println("dirΔ = $dirΔ")
        println("objective function (current x) = $(f(xInit))")
        println("Parameters: $paramA and $paramB")
        println("Verbose = $verbose \n")
    end

    # First, initializae α = 1. This is the "learning rate"

    α = 1
    if verbose
        print("Left side: ")
        println(f(xInit + α * dirΔ))
        print("Right side: ")
        print(f(xInit))
        print(" + ")
        print(paramA * α * dirΔ'dfdx(xInit))
        print(" = ")
        println(f(xInit) + paramA * α * dirΔ'dfdx(xInit))
    end

    # Check the condition:
    # f(x + alpha * dir) > f(x) + a * grad f of (alpha * delta)
    while f(xInit + α * dirΔ) > (f(xInit) + paramA * α * (dirΔ'dfdx(xInit))[1])
        # If the condition passes, set α to b * α
        if verbose
            print("Left side: ")
            println(f(xInit + α * dirΔ))
            print("Right side: ")
            print(f(xInit))
            print(" + ")
            print(paramA * α * dirΔ'dfdx(xInit))
            print(" = ")
            println(f(xInit) + paramA * α * dirΔ'dfdx(xInit))
        end

        α = paramB * α

        if verbose
            println(α)
        end
    end

    # the updated x is xInit - alpha * gradf(xInit)
    xUpdated = xInit + α * dirΔ
    # Return updated x
    return xUpdated, α
end

runTest = false

if runTest
    # Test Script

    # One Dimensional Function
    xInit1 = 600
    fFun(x) = x^2
    dfdx(x) = 2 * x
    dir = -dfdx(xInit1)
    aTest = 0.3
    bTest = 0.707

    xNew = backtrackLineSearch(xInit1, dir, fFun, dfdx, aTest, bTest, true)
    println("New x = $xNew")
    println("Result should by 0.181")

    # Two Dimensional Function
    xInit2 = [600, 400]
    fFun(x) = x'x
    dfdx(x) = 2 * x
    dir2 = -dfdx(xInit2)
    aTest = 0.3
    bTest = 0.707

    xNew = backtrackLineSearch(xInit2, dir2, fFun, dfdx, aTest, bTest, true)
    println("New x = $xNew")
    println("Result should by 0.181")
end


using FFTW, DSP

const h1 = [-8, 2, -9, -2, 9, -8, -2]
const f1 = [ 6, -9, -7, -5]
const g1 = [-48, 84, -16, 95, 125, -70, 7, 29, 54, 10]

const h2nested = [
      [-8, 1, -7, -2, -9, 4],
      [4, 5, -5, 2, 7, -1],
      [-6, -3, -3, -6, 9, 5]]
const f2nested = [
      [-5, 2, -2, -6, -7],
      [9, 7, -6, 5, -7],
      [1, -1, 9, 2, -7],
      [5, 9, -9, 2, -5],
      [-8, 5, -2, 8, 5]]
const g2nested = [
      [40, -21, 53, 42, 105, 1, 87, 60, 39, -28],
      [-92, -64, 19, -167, -71, -47, 128, -109, 40, -21],
      [58, 85, -93, 37, 101, -14, 5, 37, -76, -56],
      [-90, -135, 60, -125, 68, 53, 223, 4, -36, -48],
      [78, 16, 7, -199, 156, -162, 29, 28, -103, -10],
      [-62, -89, 69, -61, 66, 193, -61, 71, -8, -30],
      [48, -6, 21, -9, -150, -22, -56, 32, 85, 25]]

const h3nested = [
      [[-6, -8, -5, 9], [-7, 9, -6, -8], [2, -7, 9, 8]],
      [[7, 4, 4, -6], [9, 9, 4, -4], [-3, 7, -2, -3]]]
const f3nested = [
      [[-9, 5, -8], [3, 5, 1]],
      [[-1, -7, 2], [-5, -6, 6]],
      [[8, 5, 8],[-2, -6, -4]]]
const g3nested = [
      [  [54, 42, 53, -42, 85, -72],
         [45, -170, 94, -36, 48, 73],
         [-39, 65, -112, -16, -78, -72],
         [6, -11, -6, 62, 49, 8]],
      [  [-57, 49, -23, 52, -135, 66],
         [-23, 127, -58, -5, -118, 64],
         [87, -16, 121, 23, -41, -12],
         [-19, 29, 35, -148, -11, 45]],
      [  [-55, -147, -146, -31, 55, 60],
         [-88, -45, -28, 46, -26, -144],
         [-12, -107, -34, 150, 249, 66],
         [11, -15, -34, 27, -78, -50]],
      [  [56, 67, 108, 4, 2, -48],
         [58, 67, 89, 32, 32, -8],
         [-42, -31, -103, -30, -23, -8],
         [6, 4, -26, -10, 26, 12]]]

function flatnested2d(a, siz)
    ret = zeros(Int, prod(siz))
    for i in 1:length(a), j in 1:length(a[1])
        ret[siz[2] * (i - 1) + j] = a[i][j]
    end
    Float64.(ret)
end

function flatnested3d(a, siz)
    ret = zeros(Int, prod(siz))
    for i in 1:length(a), j in 1:length(a[1]), k in 1:length(a[1][1])
        ret[siz[2] * siz[3] * (i - 1) + siz[3] * (j - 1) + k] = a[i][j][k]
    end
    Float64.(ret)
end

topow2(siz) = map(x -> nextpow(2, x), siz)
deconv1d(f1, g1) = Int.(round.(deconv(Float64.(g1), Float64.(f1))))

function deconv2d(f2, g2, xd2)
    siz = topow2([length(g2), length(g2[1])])
    h2 = Int.(round.(real.(ifft(fft(flatnested2d(g2, siz)) ./ fft(flatnested2d(f2, siz))))))
    [[h2[siz[2] * (i - 1) + j] for j in 1:xd2[2]] for i in 1:xd2[1]]
end

function deconv3d(f3, g3, xd3)
    siz = topow2([length(g3), length(g3[1]), length(g3[1][1])])
    h3 = Int.(round.(real.(ifft(fft(flatnested3d(g3, siz)) ./ fft(flatnested3d(f3, siz))))))
    [[[h3[siz[2] * siz[3] *(i - 1) + siz[3] * (j - 1) + k] for k in 1:xd3[3]]
        for j in 1:xd3[2]] for i in 1:xd3[1]]
end

deconvn(f, g, tup=()) = length(tup) < 2 ? deconv1d(f, g) :
                       length(tup) == 2 ? deconv2d(f, g, tup) :
                       length(tup) == 3 ? deconv3d(f, g, tup) :
                       println("Array nesting > 3D not supported")

deconvn(f1, g1)  # 1D
deconvn(f2nested, g2nested, (length(h2nested), length(h2nested[1]))) # 2D
println(deconvn(f3nested, g3nested,
    (length(h3nested), length(h3nested[1]), length(h3nested[1][1])))) # 3D