module ConvolutionalOperatorLearning

export CAOL, generatefilters

include("util.jl")          # utility functions
include("core.jl")          # core implementation
include("signatures.jl")    # CAOL function signatures
include("filters.jl")       # filters for initialization

"""
    CAOL(x, λ, h0; p=0, maxiters=2000, tol=1e-13, trace=false)

Learn convolutional analysis operators, i.e., sparsifying filters,
for signals `x` with sparsity regularization `λ` and initial filters `h0`.
Preserve the first `p` filters in `h0`, i.e., consider them handcrafted.

`x` can be either:
+ a vector of training signals each a D-dimensional array
+ a (D+1)-dimnensional array with training sample `i` being slice `x[:,...,:,i]`

`h0` can be either:
+ a vector of filters each a D-dimensional array
+ a tuple `(H0,R)` where each column of the matrix `H0` is a vectorized filter
  and where `R` gives the size/shape of the filter.
The filters must be orthogonal to one another
and each filter must be normalized to have norm `1/sqrt(filter length)`,
i.e., `H0'H0 ≈ (1/size(H0,1))*I`.

When `trace=false`, CAOL returns just the learned filters (in the same form as `h0`).
When `trace=true`, CAOL also returns
+ the iterates (only the learned filters since handcrafted filters do not change)
+ the objective function values (evaluated on only the learned filters)
+ the iterate differences `norm(H[t]-H[t-1])/norm(H[t])` used for the stopping criterion

# Examples
Passing in a vector of ten 100x50 training signals and a vector of 3x3 DCT filters
```julia-repl
julia> x = [randn(100,50) for _ in 1:10];
julia> h0 = generatefilters(:DCT,(3,3));
julia> h = CAOL(x,1e-3,h0,maxiters=10);
```
Passing the ten 100x50 training signals as an array
```julia-repl
julia> X = randn(100,50,10);
julia> h0 = generatefilters(:DCT,(3,3));
julia> h = CAOL(X,1e-3,h0,maxiters=10);
```
Passing the DCT filters in matrix form
```julia-repl
julia> x = [randn(100,50) for _ in 1:10];
julia> H0 = generatefilters(:DCT,(3,3),form=:matrix);
julia> H = CAOL(x,1e-3,(H0,(3,3)),maxiters=10);
```
Getting the trace
```julia-repl
julia> x = [randn(100,50) for _ in 1:10];
julia> H0 = generatefilters(:DCT,(3,3),form=:matrix);
julia> H, Htrace, objtrace, Hdifftrace = CAOL(x,1e-3,(H0,(3,3)),maxiters=10,trace=true);
```
"""
function CAOL end

end # module


import Base.push!

#----------------------------------------------------------------------------------------------------

"""
    monomial_compress(io_equation, ode)

Compresses an input-output equation for the rank computation
Input:
- `io_equation` - input-output equation
- `ode` - the corresponding ODE model

Output:
- pair (coeffs, terms) such that:
    - sum of `coeffs[i] * terms[i] = io_equation`
    - `coeffs` involve only parameters, `terms` involve only inputs and outputs
    - length of the representation is the smallest possible
"""
function monomial_compress(io_equation, ode::ODE)
    return monomial_compress(io_equation, ode.parameters)
end

function monomial_compress(io_equation, params::Array{<: MPolyElem, 1})
    other_vars = [v for v in gens(parent(io_equation)) if !(var_to_str(v) in map(var_to_str, params))]
	coeffdict = extract_coefficients(io_equation, other_vars)
	expvect = collect(keys(coeffdict))
	coeffs = collect(values(coeffdict))
	termlist = map(x->prod(other_vars.^x), expvect)

    echelon_form = Array{Any, 1}()
    for (c, p) in zip(coeffs, termlist)
        for i in 1:length(echelon_form)
            basis_c = echelon_form[i][1]
            coef = coeff(c, leading_monomial(basis_c)) // leading_coefficient(basis_c)
            if coef != 0
                c = c - coef * basis_c
                echelon_form[i][2] += coef * p
            end
        end
        if c != 0
            push!(echelon_form, [c, p])
        end
    end

    result = ([a[1] for a in echelon_form], [a[2] for a in echelon_form])
    #s = 0
    #for (a, b) in zip(result[1], result[2])
    #    s += parent_ring_change(a, parent(io_equation)) * parent_ring_change(b, parent(io_equation))
    #end
    #println("====================")
    #println(s - io_equation)

    return result
end

#----------------------------------------------------------------------------------------------------

# A data structure for storing exponent vectors as a trie in order
# to reduce the number of multiplications when computing many monomials
# in `massive_eval`
mutable struct ExpVectTrie
    depth::Int
    subtries::Dict{Int, ExpVectTrie}

    function ExpVectTrie(depth::Int)
        return new(depth, Dict{Int, ExpVectTrie}())
    end
end

function Base.push!(t::ExpVectTrie, vect::Array{Int, 1})
    if t.depth == 0
        if length(vect) != 0
            throw(DomainError("Inserting too long vector"))
        end
        return
    end

    f = last(vect)
    if !(f in keys(t.subtries))
        t.subtries[f] = ExpVectTrie(t.depth - 1)
    end
    push!(t.subtries[f], vect[1:end - 1])
end

"""
    get_max_below(t, vect)

Input:
- `t` - a trie with exponent vectors
- `vect` - yet another exponent vector

Output:
- a pair `(d, v)` where `v` is a vector in the trie which is componenwise ≤ `vect` and the difference `d` is as small as possible
"""
function get_max_below(t::ExpVectTrie, vect::Array{Int, 1})
    if t.depth == 0
        return (0, [])
    end
    min_diff, best_below = nothing, nothing
    for k in keys(t.subtries)
        last_diff = last(vect) - k
        if last_diff >= 0
            cur_diff, cur_below = get_max_below(t.subtries[k], vect[1:end - 1])
            if !isnothing(cur_diff)
                if isnothing(min_diff) || min_diff > cur_diff + last_diff
                    min_diff = cur_diff + last_diff
                    push!(cur_below, k)
                    best_below = cur_below
                end
            end
        end
    end
    return (min_diff, best_below)
end

#----------------------------------------------------------------------------------------------------

"""
    massive_eval(polys, eval_dict)

Input:
- `polys` - a list of polynomials
- `eval_dict` - dictionary from variables to the values. Missing values are treated as zeroes

Output:
- a list of values of the polynomials

Evaluates a list of polynomails at a point. Assumes that multiplications are relatively expensive
(like in truncated power series) so all the monomials are precomputed first and the values of monomials
of lower degree are cached and used to compute the values of the monomials of higher degree
"""
function massive_eval(polys, eval_dict)
    R = parent(first(values(eval_dict)))
    point = [get(eval_dict, v, zero(R)) for v in gens(parent(first(polys)))]
    n = length(point)

    monomials = Set()
    for p in polys
        for exp in exponent_vectors(p)
            push!(monomials, exp)
        end
    end

    cache = Dict()
    cache[ [0 for i in 1:n] ] = one(R)
    cached_monoms = ExpVectTrie(n)
    push!(cached_monoms, [0 for _ in 1:n])

    for i in 1:n
        var_exp = [(i != j) ? 0 : 1 for j in 1:n]
        cache[var_exp] = point[i]
        push!(cached_monoms, var_exp)
    end

    for exp in sort!(collect(monomials), by=sum)
        if !(exp in keys(cache))
            monom_val = one(R)
            computed = [0 for i in 1:n]
            while sum(exp) > 0
                _, below = get_max_below(cached_monoms, exp)
                monom_val = monom_val * cache[below]
                exp = exp .- below
                computed = computed .+ below
                cache[computed] = monom_val
                push!(cached_monoms, computed)
            end
        end
    end

    results = []
    for p in polys
        res = zero(R)
        for (exp, coef) in zip(exponent_vectors(p), coefficients(p))
            res += coef * cache[exp]
        end
        push!(results, res)
    end
    return results
end

#----------------------------------------------------------------------------------------------------

"""
    wronskian(io_equations, ode)

Input:
- `io_equations` - a set of io-equations in the form of the `Dict` as returned by `find_ioequations`
- `ode` - the `ODE` object

Output:
- a list of wronskians evaluated at a point modulo prime

Computes the wronskians of io_equations
"""
function wronskian(io_equations::Dict{P, P}, ode::ODE{P}) where P <: MPolyElem
    @debug "Compressing monomials"
    termlists = [monomial_compress(ioeq, ode)[2] for ioeq in values(io_equations)]
    @debug "Matrix sizes $(map(length, termlists))"

    # estimating the required order of truncation
    ord = max(map(length, termlists)...) + length(ode.x_vars)

    # reducing everything modulo prime
    PRIME = 2^31 - 1
    F = Nemo.GF(PRIME)
    polyring_red, gens_red = Nemo.PolynomialRing(F, map(var_to_str, gens(parent(termlists[1][1]))))
    termlists = [map(p -> parent_ring_change(p, polyring_red), tlist) for tlist in termlists]
    ode_red = reduce_ode_mod_p(ode, PRIME)

    @debug "Computing power series solution up to order $ord"
	ps = power_series_solution(
        ode_red,
        Dict(p => rand(1:100) for p in ode_red.parameters),
        Dict(x => rand(1:100) for x in ode_red.x_vars),
        Dict(u => [rand(1:100) for i in 0:ord] for u in ode_red.u_vars),
        ord
    )
    @debug "Computing the derivatives of the solution"
    ps_ext = Dict{MPolyElem, Nemo.gfp_abs_series}()# Generic.AbsSeries}()
    for v in vcat(ode_red.y_vars, ode_red.u_vars)
        cur_ps = ps[v]
        for i in 0:length(ode_red.x_vars)
            ps_ext[str_to_var(var_to_str(v) * "_$i", polyring_red)] = cur_ps
            cur_ps = ps_diff(cur_ps)
        end
    end

    @debug "Constructing wronskians"
    result = []
    for (i, tlist) in enumerate(termlists)
        n = length(tlist)
        evaled = massive_eval(tlist, ps_ext)
        S = Nemo.MatrixSpace(F, n, n)
        W = zero(S)
        for (i, ps) in enumerate(evaled)
            for j in 1:n
                W[i, j] = coeff(ps, j - 1)
            end
        end
        push!(result, W)
    end

    return result
end


# coding: utf-8

# In[1]:

#bloom filter is a space-efficient probabilistic data structure
#it is commonly used for query in large dataset
#the query can possibly return false positive matches but never false negative matches
#the workflow is very straight forward
#for each string in the database, it has to go through k number of hash functions
#k hash value would be allocated to the hashtable accordingly
#to make any query,the query string has to go through k number of hash functions as well
#if every underlying cell has been occupied in the hashtable
#the query returns positive matches

#fowler–noll–vo hash function
#pseudo code can be found in the link below
# https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function#FNV-1_hash
function fnv1(text,
              offset_basis=0xcbf29ce484222325,
              prime=0x100000001b3)

        hash_value=offset_basis
        for letter in text
            hash_value*=prime

            #for some unknown reasons,large int xor in julia is different from python
            hash_value=hash_value ⊻ codepoint(letter)

        end

        return hash_value

end

# In[2]:

#jenkins one at a time hash function
#pseudo code can be found in the link below
# https://en.wikipedia.org/wiki/Jenkins_hash_function#one_at_a_time
#actual code can be found in the link below
# https://stackoverflow.com/a/51730837
function one_at_a_time(text)

    hash_value=0

    for letter in text
        hash_value+=codepoint(letter)
        hash_value=hash_value & 0xFFFFFFFF
        hash_value+=hash_value<<10
        hash_value=hash_value & 0xFFFFFFFF
        hash_value=hash_value ⊻ hash_value>>6
        hash_value=hash_value & 0xFFFFFFFF
    end

    hash_value+=hash_value<<3
    hash_value=hash_value & 0xFFFFFFFF
    hash_value=hash_value ⊻ hash_value>>11
    hash_value=hash_value & 0xFFFFFFFF
    hash_value+=hash_value<<15
    hash_value=hash_value & 0xFFFFFFFF

    return hash_value

end

# In[3]:

#compute hash value from two different functions
#update the underlying cells in hashtable
function update_hashtable(text,hashtable,verbose=true)

    #use extra computation to shrink hashtable size
    fnv_hash=Int64(round(fnv1(text)^0.0625))
    jenkins_hash=Int64(round(one_at_a_time(text)^0.25))

    #prevent overflow
    if max(fnv_hash,jenkins_hash)<=hashtable_size

        #update hashtable
        hashtable[fnv_hash]=1
        hashtable[jenkins_hash]=1
        if verbose
            println("Hashtable updated.")
        end

    else
        if verbose
            println("Hash value exceeds hashtable size!")
        end
    end

    return hashtable

end

# In[4]:

#compute hash value from two different functions
#check the underlying cells in hashtable
function search_hashtable(text,hashtable)

    #use extra computation to shrink hashtable size
    fnv_hash=Int64(round(fnv1(text)^0.0625))
    jenkins_hash=Int64(round(one_at_a_time(text)^0.25))

    #check hashtable
    if hashtable[fnv_hash]==1 && hashtable[jenkins_hash]==1
        return true
    else
        return false
    end

end

# In[5]:

#initialize
hashtable_size=500
hashtable=[0 for _ in 1:hashtable_size];
update_list=["raison d'être","porte-voix","prêt-à-gouverner",
             "proxénétisme","hydroxychloroquine"];

# In[6]:

#update hashtable
for text in update_list
    hashtable=update_hashtable(text,hashtable)
end

# In[7]:

#true negative
println(search_hashtable("raison d’être",hashtable))
println(search_hashtable("raison d être",hashtable))
println(search_hashtable("raison d\"être",hashtable))

#true positive
println(search_hashtable("raison d'être",hashtable))

#false positive
println(search_hashtable("rajson d'être",hashtable))


################################################################################################################################
# video. Video Analysis
################################################################################################################################

# Motion Analysis and Object Tracking

#------------------------------------------------------------------------------------------------------------------------------#
# calcOpticalFlowPyrLK
# Calculates an optical flow for a sparse feature set using the iterative Lucas-Kanade method with pyramids
# Lucas-Kanade optical flow in pyramids (Bouguet 2000)

# void calcOpticalFlowPyrLK(InputArray prevImg, InputArray nextImg, InputArray prevPts, InputOutputArray nextPts,
# OutputArray status, OutputArray err, Size winSize=Size(21,21), int maxLevel=3,
# TermCriteria criteria=TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 30, 0.01), int flags=0, double minEigThreshold=1e-4 )

# parameters:
# prevImg   – first 8-bit input image (InputArray) or pyramid constructed by buildOpticalFlowPyramid()
# nextImg   – second input image (InputArray) or pyramid of the same size and the same type as prevImg
# prevPts   – vector of 2D points (InputArray single-precision fp X,Y) to find flow
# nextPts   – output vector of 2D points (InputOutputArray single-precision fp X,Y) with new positions of input features in the second image
#             if OPTFLOW_USE_INITIAL_FLOW => Set size of vector = size of input vector
# status    – output status vector (uchar)
#             status = 1 if flow for the corresponding features has been found, else status = 0
# err       – output vector of errors; each element of the vector is set to an error for the corresponding feature
#             type of the error measure can be set in flags parameter
#             if no flow, error is not defined (status = 0)
# winSize   – cv::Size (size) of the search window at each pyramid level, default = cv::Size(21,21)
# maxLevel  – int 0-based maximal pyramid level number, default = 3
#             if maxLevel = 0, single level, i.e., pyramids are not used
#             if maxLevel = 1, two levels; etc
#             pyramids < maxLevel
# criteria  – TermCriteria parameter of the iterative search algorithm
#             stop when max iterations criteria.maxCount || search window moves < criteria.epsilon
#             default = TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 30, 0.01)

# flags, int default =0
# OPTFLOW_USE_INITIAL_FLOW       =>  uses initial estimations, stored in nextPts
#                                    default: prevPts => nextPts
# OPTFLOW_LK_GET_MIN_EIGENVALS   =>  use minimum eigen values as an error measure (minEigThreshold);
#                                    default: L1 distance between patches around the original and a moved point,
#                                    divided by number of pixels in a window, is used as a error measure
# minEigThreshold (double)       =>  min eigen value of a 2x2 normal matrix of optical flow equations (spatial gradient matrix in [Bouguet00]/window pixels)
#                                    if eigen value < minEigThreshold, feature is filtered out and flow ignored
#                                    default = 1e-4
#-------------------------------------------------------------------------------------------------------------------#

# e.g., parameters
# prevImg::Mat(rows, columns, CV_8UC3)  || buildOpticalFlowPyramid()
# nextImg::Mat(rows, columns, CV_8UC3)
# prevPts = tostdvec([pts1::cvPoint2f, pts2::cvPoint2f, pts3::cvPoint2f, pts4::cvPoint2f, pts5::cvPoint2f . . .])
# nextPts = tostdvec([pts1::cvPoint2f, pts2::cvPoint2f, pts3::cvPoint2f, pts4::cvPoint2f, pts5::cvPoint2f . . .])

calcOpticalFlowPyrLK(prevImg, nextImg, prevPts, nextPts, status, err, winSize=Size(21,21), maxLevel=3,
    criteria=TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 30, 0.01), flags=0, minEigThreshold=1e-4) =
       @cxx cv::calcOpticalFlowPyrLK(prevImg, nextImg, prevPts, nextPts, status, err, winSize, maxLevel, criteria, flags, minEigThreshold)

# buildOpticalFlowPyramid
# Constructs the image pyramid which can be passed to calcOpticalFlowPyrLK().
# int buildOpticalFlowPyramid(InputArray img, OutputArrayOfArrays pyramid, Size winSize, int maxLevel, bool withDerivatives=true,
# int pyrBorder=BORDER_REFLECT_101, int derivBorder=BORDER_CONSTANT, bool tryReuseInputImage=true)
# Parameters:
# img                – 8-bit input image
# pyramid            – output pyramid
# winSize            – window size of optical flow algorithm, >= winSize in calcOpticalFlowPyrLK()
# maxLevel           – 0-based maximal pyramid level number
# withDerivatives    – set to precompute gradients for the every pyramid level
# pyrBorder          – the border mode for pyramid layers
# derivBorder        – the border mode for gradients
# tryReuseInputImage – put ROI of input image into the pyramid if possible
#                      if tryReuseInputImage = false, then forces data copying
# Output:
# number of levels in constructed pyramid, maybe < maxLevel

buildOpticalFlowPyramid(img, pyramid, winSize, maxLevel, withDerivatives=true, pyrBorder=BORDER_REFLECT_101,
    derivBorder=BORDER_CONSTANT, tryReuseInputImage=true) = @cxx cv::buildOpticalFlowPyramid(img, pyramid, winSize,
        maxLevel, withDerivatives, pyrBorder, derivBorder, tryReuseInputImage)

# calcOpticalFlowFarneback
# Computes a dense optical flow using the Gunnar Farneback’s algorithm
# void calcOpticalFlowFarneback(InputArray prev, InputArray next, InputOutputArray flow, double pyr_scale,
# int levels, int winsize, int iterations, int poly_n, double poly_sigma, int flags)

# Parameters:
# prev       – first 8-bit single-channel input image
# next       – second input image: size and type = prev
# flow       – CV_32FC2 computed flow image, size = prev
# pyr_scale  – image scale (<1) to build pyramids for each image
#              pyr_scale=0.5 => classical pyramid, next layer is 2X smaller than the previous one
# levels     – number of pyramid layers including the initial image
#              levels=1 => only the original images are used
# winsize    – averaging window size
#              larger => increase robustness, less noise and better fast motion detection), but yield more blurred motion field
# iterations – iterations/pyramid level
# poly_n     – typically poly_n =5 or 7
#              size of the pixel neighborhood used to find polynomial expansion in each pixel
#              larger => smoother surfaces, robust but more blurred motion field,
# poly_sigma – standard deviation of the Gaussian to smooth derivatives for the polynomial expansion
#              poly_n=5  => poly_sigma=1.1
#              poly_n=7  => poly_sigma=1.5
# flags      – same as calcOpticalFlowPyrLK

calcOpticalFlowFarneback(prev, next, flow, pyr_scale=0.5, levels=1, winsize=10, iterations=5, poly_n=5,
    poly_sigma=1.1, flags=0) = @cxx cv::calcOpticalFlowFarneback(prev, next, flow, pyr_scale, levels, winsize,
      iterations, poly_n, poly_sigma, flags)

# estimateRigidTransform
# Computes an optimal affine transformation between two 2D point sets
# Mat estimateRigidTransform(InputArray src, InputArray dst, bool fullAffine)

# Parameters:
# src – 2D point set stored in std::vector or Mat, or an image stored in Mat
# dst – 2D point set of the same size and the same type as A, or another image
# fullAffine =true  => affine transformation with no additional restrictions (6 dof) - degrees of freedom
# fullAffine =false => translation, rotation, and uniform scaling (5 dof)

estimateRigidTransform(src, dst, fullAffine=false) = @cxx cv::estimateRigidTransform(src, dst, fullAffine)

# findTransformECC
# Finds the geometric transform (warp) between two images in terms of the ECC criterion [EP08]
# double findTransformECC(InputArray templateImage, InputArray inputImage, InputOutputArray warpMatrix,
# int motionType=MOTION_AFFINE, TermCriteria criteria=TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 50, 0.001))

# Parameters:
# templateImage – single-channel template image, CV_8U or CV_32F array
# inputImage    – single-channel input image which should be warped with the final warpMatrix, type = temlateImage
# warpMatrix    – floating-point 2x3 or 3x3 mapping matrix (warp)
# motionType    – type of motion parameter
# MOTION_TRANSLATION  => translational motion model: warpMatrix is 2x3 with the first 2x2 part being the unity matrix and the rest two parameters being estimated
# MOTION_EUCLIDEAN    => Euclidean (rigid) transformation as motion model: 2 parameters are estimated, warpMatrix is 2x3
# MOTION_AFFINE       => affine motion model (DEFAULT):                    6 parameters are estimated, warpMatrix is 2x3
# MOTION_HOMOGRAPHY   => homography as a motion model:                     8 parameters are estimated, warpMatrix is 3x3
# criteria      – termination criteria of the ECC algorithm
#                 criteria.epsilon = threshold of the increment in the correlation coefficient between two iterations
#                 criteria.epsilon < 0 => criteria.maxcount is termination criterion

findTransformECC(templateImage, inputImage, warpMatrix, motionType=MOTION_AFFINE, criteria=TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 50, 0.001)) =
      @cxx cv::findTransformECC(templateImage, inputImage, warpMatrix, motionType, criteria)

#-------------------------------------------------------------------------------------------------------------------#
# CamShift
# Finds an object center, size, and orientation
# CAMSHIFT object tracking algorithm [Bradski98]
# RotatedRect CamShift(InputArray probImage, Rect& window, TermCriteria criteria)

# Parameters:
# probImage – Back projection of the object histogram. See calcBackProject() .
# window    – Initial search window.
# criteria  – Stop criteria for the underlying meanShift() .
# Returns:
# RotatedRect

# e.g., input
# probImage = calcBackProject(...) see OpenCV_imgproc.jl
# window = cvRect
# criteria = TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 30, 0.01)
CamShift(probImage, window, criteria) = @cxx cv::CamShift(probImage, window, criteria)

# meanShift
# Finds an object on a back projection image
# iterative object search algorithm
# int meanShift(InputArray probImage, Rect& window, TermCriteria criteria)

# Parameters:
# probImage  – Back projection of the object histogram. See calcBackProject().
# window     – Initial search window.
# criteria   – Stop criteria for the iterative search algorithm
# Returns:
# Number of iterations CAMSHIFT took to converge

meanShift(probImage, window, criteria) = @cxx cv::meanShift(probImage, window, criteria)

#-------------------------------------------------------------------------------------------------------------------#
# KalmanFilter
# class
# implements a standard Kalman filter http://en.wikipedia.org/wiki/Kalman_filter, [Welch95]
# however, can be extended by modifying transitionMatrix, controlMatrix, and measurementMatrix

# Parameters:
# dynamParams   – Dimensionality of the state
# measureParams – Dimensionality of the measurement
# controlParams – Dimensionality of the control vector
# type          – Type of the created matrices that should be CV_32F or CV_64F

# constructors, return KF
KalmanFilter() = @cxxnew cv::KalmanFilter::KalmanFilter()
KalmanFilter(dynamParams::Int,measureParams::Int, controlParams=0, cvtype=CV_32F) =
     @cxxnew cv::KalmanFilter::KalmanFilter(dynamParams,measureParams, controlParams, cvtype)

# Re-initializes Kalman filter
KalmanInit(KF, dynamParams::Int,measureParams::Int, controlParams=0, cvtype=CV_32F) =
      @cxx KF->init(dynamParams,measureParams, controlParams, cvtype)

# Compute a predicted state (optional= cv::Mat)
KalmanPredict(KF, control=Mat()) = @cxx KF->predict(control)

# Update the predicted state from the measurement (measurement = cv::Mat)
KalmanCorrect(KF,measurement) = @cxx KF->correct(measurement)
#-------------------------------------------------------------------------------------------------------------------#

# BackgroundSubtractor class
# BackgroundSubtractor::apply
# Computes a foreground mask
# void BackgroundSubtractor::apply(InputArray image, OutputArray fgmask, double learningRate=-1)
# Parameters:
# image        – Next video frame
# fgmask       – The output foreground mask as an 8-bit binary image
# learningRate – 0-1 that indicates how fast the background model is learnt
#                  < 0 automatically chosen learning rate
#                    0 not updated at all
#                    1 reinitialized from the last frame

# Create a pointer to the abstract class BackgroundSubtractorMOG
cxx"""
   cv::bgsegm::BackgroundSubtractorMOG* BackgroundSubtractorMOG()
   {
       cv::bgsegm::BackgroundSubtractorMOG* bkgsub;
       return bkgsub;
   }
"""

# Constructor
bkgsubMOG() = @cxx BackgroundSubtractorMOG()

# Apply
bkgsubApply(bkgsubMOG, Iimage, fgmask, learningRate=-1.0) = @cxx bkgsubMOG->apply(image,fgmask, learningRate)

# Computes a background image
# input: backgroundImage = Mat(r,c,cvtype)
getBackgroundImage(bkgsubMOG, backgroundImage) = @cxx bkgsubMOG->getBackgroundImage(backgroundImage)

# BackgroundSubtractorMOG class: algorithm described in [KB2001]
# Create a mixture-of-gaussian background subtractor
# Ptr<BackgroundSubtractorMOG> createBackgroundSubtractorMOG(int history=200, int nmixtures=5, double backgroundRatio=0.7, double noiseSigma=0)
# Parameters:
# history           – length of the history
# nmixtures         – Number of Gaussian mixtures
# backgroundRatio   – Background ratio
# noiseSigma        – Noise strength (SD for brightness, each color channel). 0 = automatic value

createBackgroundSubtractorMOG(history=200, nmixtures=5, backgroundRatio=0.7, noiseSigma=0) =
     @cxx cv::bgsegm::createBackgroundSubtractorMOG(history, nmixtures, backgroundRatio, noiseSigma)

# BackgroundSubtractorMOG2
# Creates MOG2 Background Subtractor
# Ptr<BackgroundSubtractorMOG2> createBackgroundSubtractorMOG2(int history=500, double varThreshold=16, bool detectShadows=true )
# Parameters:
# history       – length of the history
# varThreshold  – Threshold on the squared Mahalanobis distance between the pixel
# detectShadows – if =true, detect shadows and mark them. slows down!, so set to false.

# http://www.developark.com/5098_19238765/

# Create a pointer to the abstract class BackgroundSubtractorMOG2
# cxx"""
# cv::BackgroundSubtractorMOG2* BackgroundSubtractorMOG2()
# {
#      cv::BackgroundSubtractorMOG2* bkgsub;
#      return bkgsub;
# }
# """

createBackgroundSubtractorMOG2(history=500, varThreshold=16, detectShadows=true) =
    @cxx cv::createBackgroundSubtractorMOG(history, varThreshold, detectShadows)

# Returns the number of last frames that affect the background model
getHistory(bkgsubMOG2) = @cxx bkgsubMOG2->getHistory()

# Sets the number of last frames that affect the background model
setHistory(bkgsubMOG2, history::Int) = @cxx bkgsubMOG2->setHistory(history)

# Returns number of gaussian components in the background model
getNMixtures(bkgsubMOG2) = @cxx bkgsubMOG2->getNMixtures()

# Sets number of gaussian components in the background model, needs to be reinitialized
setNMixtures(bkgsubMOG2, nmixtures::Int) = @cxx bkgsubMOG2->setNMixtures(nmixtures)

# Returns the “background ratio” parameter of the algorithm
getBackgroundRatio(bkgsubMOG2) = @cxx bkgsubMOG2->getBackgroundRatio()

# Sets the “background ratio” parameter of the algorithm
setBackgroundRatio(bkgsubMOG2, ratio::Float64) = @cxx bkgsubMOG2->setBackgroundRatio(ratio)

# Returns the variance threshold for the pixel-model match
getVarThreshold(bkgsubMOG2) = @cxx bkgsubMOG2->getVarThreshold()

# Sets the variance threshold for the pixel-model match
setVarThreshold(bkgsubMOG2, varThreshold::Float64) = @cxx bkgsubMOG2-> setVarThreshold(varThreshold)

# Returns the variance threshold for the pixel-model match used for new mixture component generation
getVarThresholdGen(bkgsubMOG2) = @cxx bkgsubMOG2-> getVarThresholdGen()

# Set the variance threshold for the pixel-model match used for new mixture component generation
setVarThresholdGen(bkgsubMOG2, VarThreshold::Float64) = @cxx bkgsubMOG2-> setVarThresholdGen(VarThreshold)

# Returns the initial variance of each gaussian component
getVarInit(bkgsubMOG2) = @cxx bkgsubMOG2->getVarInit()

# Sets the initial variance of each gaussian component
setVarInit(bkgsubMOG2, varInit::Float64) = @cxx bkgsubMOG2->setVarInit(varInit)

# Returns the complexity reduction threshold
getComplexityReductionThreshold(bkgsubMOG2) = @cxx bkgsubMOG2->getComplexityReductionThreshold()

# Sets the complexity reduction threshold
setComplexityReductionThreshold(bkgsubMOG2, ct::Float64) = @cxx bkgsubMOG2->setComplexityReductionThreshold(ct)

# Returns the shadow detection flag
getDetectShadows(bkgsubMOG2) = @cxx bkgsubMOG2->getDetectShadows()

# Enables or disables shadow detection
setDetectShadows(bkgsubMOG2, detectShadows::Bool) = @cxx bkgsubMOG2->setDetectShadows(detectShadows)

# Returns the shadow value, Default value is 127
getShadowValue(bkgsubMOG2) = @cxx bkgsubMOG2->getShadowValue()

# Sets the shadow value
setShadowValue(bkgsubMOG2, value::Int) = @cxx bkgsubMOG2->setShadowValue(bkgsubMOG2, value)

# Returns the shadow threshold
getShadowThreshold(bkgsubMOG2) = @cxx bkgsubMOG2->getShadowThreshold()

# Sets the shadow threshold
setShadowThreshold(bkgsubMOG2, threshold::Float64) = @cxx bkgsubMOG2->setShadowThreshold(threshold)

# BackgroundSubtractorKNN
# K-nearest neigbours - based Background/Foreground Segmentation Algorithm
# The class implements the K-nearest neigbours background subtraction described in [Zivkovic2006]
# Very efficient if number of foreground pixels is low.

# C++: Ptr<BackgroundSubtractorKNN> createBackgroundSubtractorKNN(int history=500,
#     double dist2Threshold=400.0, bool detectShadows=true )

# Parameters:
# history        – Length of the history
# dist2Threshold – Threshold on the squared distance between the pixel
#                  This parameter does not affect the background update
# detectShadows  – If true, the algorithm will detect shadows and mark them
#                  set to false to speed up

createBackgroundSubtractorKNN(history=500, dist2Threshold=400.0,detectShadows=true) =
    @cxx cv::createBackgroundSubtractorKNN(history, dist2Threshold, detectShadows)

# Returns the number of last frames that affect the background model
getHistory() = @cxx bkgsubKNN->getHistory()

# Sets the number of last frames that affect the background model
setHistory(history::Int) = @cxx bkgsubKNN->setHistory(history)

# Returns the number of data samples in the background model
getNSamples() = @cxx bkgsubKNN->getNSamples()

# Sets the number of data samples in the background model. The model needs to be reinitalized to reserve memory
setNSamples(_nN::Int) = @cxx bkgsubKNN->setNSamples(_nN)

# Returns the threshold on the squared distance between the pixel and the sample
getDist2Threshold() = @cxx bkgsubKNN->getDist2Threshold()

# Sets the threshold on the squared distance
setDist2Threshold(_dist2Threshold::Float64) = @cxx bkgsubKNN->setDist2Threshold(_dist2Threshold)

# Returns the number of neighbours, the k in the kNN. K is the number of samples that need to be within
# dist2Threshold in order to decide that that pixel is matching the kNN background mode
getkNNSamples() = @cxx bkgsubKNN->getkNNSamples()

# Sets the k in the kNN. How many nearest neigbours need to match
setkNNSamples(_nkNN::Int) = @cxx bkgsubKNN->setkNNSamples(_nkNN)

# Returns the shadow detection flag
getDetectShadows() = @cxx bkgsubKNN->getDetectShadows()

# Enables or disables shadow detection
setDetectShadows(detectShadows::Bool) = @cxx bkgsubKNN->setDetectShadows(detectShadows)

# Returns the shadow value
getShadowValue() = @cxx bkgsubKNN->getShadowValue()

# Sets the shadow value
setShadowValue(value::Int) = @cxx bkgsubKNN->setShadowValue(value)

# Returns the shadow threshold
getShadowThreshold() = @cxx bkgsubKNN->getShadowThreshold()

# Sets the shadow threshold
setShadowThreshold(threshold::Float64) = @cxx bkgsubKNN->setShadowThreshold(threshold)

# BackgroundSubtractorGMG
# Background Subtractor module based on the algorithm given in [Gold2012]
# Creates a GMG Background Subtractor
# Ptr<BackgroundSubtractorGMG> createBackgroundSubtractorGMG(int initializationFrames=120,
# double decisionThreshold=0.8)

# Parameters:
# initializationFrames – number of frames used to initialize the background models
# decisionThreshold    – Threshold value, above which it is marked foreground, else background

createBackgroundSubtractorGMG(initializationFrames=120, decisionThreshold=0.8) =
   @cxx cv::bgsegm::createBackgroundSubtractorGMG(initializationFrames, decisionThreshold)

# Returns the number of frames used to initialize background model
getNumFrames(bkgsubGMG) = @cxx bkgsubGMG->getNumFrames()

# Sets the number of frames used to initialize background model
setNumFrames(nframes::Int) = @cxx bkgsubGMG->setNumFrames(nframes)

# Returns the learning rate of the algorithm
# It lies between 0.0 and 1.0. It determines how quickly features are “forgotten” from histograms
getDefaultLearningRate() = @cxx bkgsubGMG->getDefaultLearningRate()

# Sets the learning rate of the algorithm
setDefaultLearningRate(lr::Float64) = @cxx bkgsubGMG->setDefaultLearningRate(lr)

# Returns the value of decision threshold
# Decision value is the value above which pixel is determined to be FG
getDecisionThreshold() = @cxx bkgsubGMG->getDecisionThreshold()

# Sets the value of decision threshold
setDecisionThreshold(thresh::Float64) = @cxx bkgsubGMG->setDecisionThreshold(thresh)

# Returns total number of distinct colors to maintain in histogram
getMaxFeatures() = @cxx bkgsubGMG->getMaxFeatures()

# Sets total number of distinct colors to maintain in histogram
setMaxFeatures(maxFeatures::Int) = @cxx bkgsubGMG->setMaxFeatures(maxFeatures)

# Returns the parameter used for quantization of color-space.
# It is the number of discrete levels in each channel to be used in histograms.
getQuantizationLevels() = @cxx bkgsubGMG->getQuantizationLevels()

# Sets the parameter used for quantization of color-space
setQuantizationLevels(nlevels::Int) = @cxx bkgsubGMG->setQuantizationLevels(nlevels)

# Returns the kernel radius used for morphological operations
getSmoothingRadius() = @cxx bkgsubGMG->getSmoothingRadius()

# Sets the kernel radius used for morphological operations
setSmoothingRadius(radius::Int) = @cxx bkgsubGMG->setSmoothingRadius(radius)

# Returns the status of background model update
getUpdateBackgroundModel() = @cxx bkgsubGMG->getUpdateBackgroundModel()

# Sets the status of background model update
setUpdateBackgroundModel(update::Bool) = @cxx bkgsubGMG->setUpdateBackgroundModel(update)

# Returns the minimum value taken on by pixels in image sequence. Usually 0
getMinVal() = @cxx bkgsubGMG->getMinVal()

# Sets the minimum value taken on by pixels in image sequence
setMinVal(val::Float64) = @cxx bkgsubGMG->setMinVal(val)

# Returns the maximum value taken on by pixels in image sequence. e.g. 1.0 or 255
getMaxVal() = @cxx bkgsubGMG->getMaxVal()

# Sets the maximum value taken on by pixels in image sequence
setMaxVal(val::Float64) = @cxx bkgsubGMG->setMaxVal(val)

# Returns the prior probability that each individual pixel is a background pixel
getBackgroundPrior() = @cxx bkgsubGMG->getBackgroundPrior()

# Sets the prior probability that each individual pixel is a background pixel
setBackgroundPrior(bgprior::Float64) = @cxx bkgsubGMG->setBackgroundPrior(bgprior)

#-------------------------------------------------------------------------------------------------------------------#
# createOptFlow_DualTVL1
# “Dual TV L1” optical flow algorithm described in [Zach2007] and [Javier2012]
#  See http://stackoverflow.com/questions/19309567/speeding-up-optical-flow-createoptflow-dualtvl1
# Ptr<DenseOpticalFlow> createOptFlow_DualTVL1()
createOptFlow_DualTVL1() = @cxxnew cv::createOptFlow_DualTVL1()

# DenseOpticalFlow::calc
# void DenseOpticalFlow::calc(InputArray I0, InputArray I1, InputOutputArray flow)
# Parameters:
# I0     – first 8-bit single-channel input image
# I1     – second input image of the same size and the same type as I0
# flow   – computed flow image that has the same size as I0 and type CV_32FC2

calcDenseOpticalFLow(tvl1, I0, I1, flow) = @cxx tvl1->calc(I0, I1, flow)

# DenseOpticalFlow::collectGarbage
# void DenseOpticalFlow::collectGarbage()
collectGarbage(tvl1) = tvl1->collectGarbage()

# how to use:
# tvl1 = createOptFlow_DualTVL1()
# tvl1->calc(Previous_Gray_Frame, Current_Gray_Frame, Optical_Flow)

# createOptFlow_DualTVL1 parameters (class members)
# double tau
    # Time step of the numerical scheme
    # tvl1->getDouble("tau");
    # tvl1->set("tau",0.125);

# double lambda
    # Weight parameter for the data term, attachment parameter.
    # This is the most relevant parameter, which determines the smoothness of the output.
    # The smaller this parameter is, the smoother the solutions we obtain.
    # It depends on the range of motions of the images, so its value should be adapted to each image sequence.
    # tvl1->getDouble("lambda");
    # tvl1->set("lambda",0.125);

# double theta
    # Weight parameter for (u - v)^2, tightness parameter.
    # It serves as a link between the attachment and the regularization terms.
    # In theory, it should have a small value in order to maintain both parts in correspondence.
    # The method is stable for a large range of values of this parameter.
    # tvl1->getDouble("theta");
    # tvl1->set("theta",0.125);

# int nscales
    # Number of scales used to create the pyramid of images.
    # tvl1->getInt("nscales");
    # tvl1->set("nscales",2);

# int warps
    # Number of warpings per scale. Represents the number of times that I1(x+u0) and grad( I1(x+u0) ) are computed per scale.
    # This is a parameter that assures the stability of the method.
    # It also affects the running time, so it is a compromise between speed and accuracy.
    # tvl1->getInt("warps");
    # tvl1->set("warps",2);

# double epsilon
    # Stopping criterion threshold used in the numerical scheme, which is a trade-off between precision and running time.
    # A small value will yield more accurate solutions at the expense of a slower convergence.
    # tvl1->getDouble("epsilon");
    # tvl1->set("epsilon",0.5);

# int iterations
    # Stopping criterion iterations number used in the numerical scheme.
    # tvl1->getInt("iterations");
    # tvl1->set("iterations",20);


module ProbabilisticExprRules

using ExprRules
using StatsBase
using AbstractTrees
using LinearAlgebra

export
    @grammar,
    RuleNode,

    ProbabilisticGrammar,
    probabilities,

    fit_mle!,
    uniform!

const ProbType = Dict{Symbol,Vector{Float64}}

struct ProbabilisticGrammar
    grammar::Grammar
    probs::ProbType
end
function ProbabilisticGrammar(grammar::Grammar)
    probs = ProbType()
    for nt in nonterminals(grammar)
        n = length(grammar[nt])
        probs[nt] = ones(Float64, n) / n
    end
    ProbabilisticGrammar(grammar, probs)
end

probabilities(pcfg::ProbabilisticGrammar, typ::Symbol) = pcfg.probs[typ]

"""
    rand(::Type{RuleNode}, grammar::ProbabilisticGrammar, typ::Symbol, dmap::AbstractVector{Int},
        max_depth::Int=10)

Generates a random RuleNode of return type typ and maximum depth max_depth.
"""
function Base.rand(::Type{RuleNode}, pcfg::ProbabilisticGrammar, typ::Symbol, dmap::AbstractVector{Int},
                    max_depth::Int=10)
    grammar = pcfg.grammar
    rules = grammar[typ]
    probs = probabilities(pcfg, typ)

    inds = findall(r->dmap[r] ≤ max_depth, rules)
    rules, probs = rules[inds], probs[inds]
    rule_index = StatsBase.sample(rules, weights(probs))

    rulenode = grammar.iseval[rule_index] ?
        RuleNode(rule_index, Core.eval(Main, grammar.rules[rule_index].args[2])) :
        RuleNode(rule_index)

    if !grammar.isterminal[rule_index]
        for ch in child_types(grammar, rule_index)
            push!(rulenode.children, rand(RuleNode, pcfg, ch, dmap, max_depth-1))
        end
    end
    return rulenode
end

"""
    uniform!(pcfg::ProbabilisticGrammar)

Set all probability vectors to uniform distribution
"""
function uniform!(pcfg::ProbabilisticGrammar)
    fill!(pcfg, 0.0)
    normalize!(pcfg)
end

"""
    fit_mle!(pcfg::ProbabilisticGrammar, pop::AbstractVector{RuleNode}; initial_value::Float64=0.0)

Update the probability vectors based on population using MLE.
"""
function fit_mle!(pcfg::ProbabilisticGrammar, pop::AbstractVector{RuleNode}, initial_value::Float64=0.0)
    fill!(pcfg, initial_value)
    for x in pop
        _fit_mle!(pcfg, x)
    end
    normalize!(pcfg)
    pcfg
end
function _fit_mle!(pcfg::ProbabilisticGrammar, x::RuleNode)
    grammar = pcfg.grammar
    typ = return_type(grammar, x)
    i = something(findfirst(isequal(x.ind),grammar[typ]), 0)
    pcfg.probs[typ][i] += 1.0
    for c in x.children
        _fit_mle!(pcfg, c)
    end
    pcfg
end

"""
    Base.fill!(pcfg::ProbabilisticGrammar, x::Float64)

Fill all probability values to x
"""
function Base.fill!(pcfg::ProbabilisticGrammar, x::Float64)
    for v in values(pcfg.probs)
        fill!(v, x)
    end
end

"""
    LinearAlgebra.normalize!(pcfg::ProbabilisticGrammar)

Normalize all probability vectors so that each vector sums to 1.0
"""
function LinearAlgebra.normalize!(pcfg::ProbabilisticGrammar)
    for v in values(pcfg.probs)
        if sum(v) > 0.0
            normalize!(v, 1)
        else
            fill!(v, 1.0/length(v))
        end
    end
end

end # module


import Clustering
import Distances
import LinearAlgebra: dot

"""
    noselection(algo, data)

Perform no clustering selection.
"""
noselection(algo::Function, data::AbstractMatrix{<:Real}) = Clustering.assignments(algo(data))

"""
Knee point detection
"""
function knee(x)
    n = length(x)
    pts = hcat(1:n, x)'
    # calculate line vector
    lvec = pts[:,end] - pts[:,1]
    lvec ./= sqrt(sum(lvec.^2))
    # find a vector parallel to the line vector
    vecfst = pts .- pts[:,1]
    vecfstpar = lvec * mapslices(v->dot(v, lvec), vecfst, dims=1)
    # calculate a length of a projection to the line vector
    ldist = vec(sqrt.(sum((vecfst - vecfstpar).^2, dims=1)))
    # get maximal projection
    return findmax(ldist)
end

function getmaxk(N::Int; kwargs...)
    N <= 5 && return 2

    # setup parameters
    Kmax = 10 # default
    for (p,v) in kwargs
        if p == :maxk
            Kmax = v
        end
    end
    return min(floor(Int, N/2), Kmax)
end

"""
    elbow(algo, data)

Perform automatic selection of the best clustering of `data` by `algo` using elbow method.
Clustering algorithm `algo` must be a function with one argument which determines number of clusters.
"""
function elbow(algo::Function, data::AbstractMatrix{<:Real}; kwargs...)
    N = size(data,2)
    Kmax = getmaxk(N; kwargs...)

    S = [let C = algo(data, k)
            # println("$k => ", C.centers)
            # println("$k => ", C.costs)
            C.totalcost => Clustering.assignments(C)
         end
    for k in 2:Kmax]
    pushfirst!(S, sum(mapslices(p->sum(p.^2), data .- mean(data, dims=2), dims=1))=>fill(1,N))

    # return a clustering corespondig to an elbow point
    return S[knee(map(first, S)) |> last] |> last
end

"""
    silhouette(algo, data)

Perform automatic selection of the best clustering of `data` by `algo` using silhouette method.
Clustering algorithm `algo` must be a function with one argument which determines number of clusters.
"""
function silhouette(algo::Function, data::AbstractMatrix{<:Real}; kwargs...)
    N = size(data,2)
    Kmax = getmaxk(N; kwargs...)
    S = [let C = algo(data, k),
             D = Distances.pairwise(Distances.Euclidean(), data)
             mean(Clustering.silhouettes(C, D)) => Clustering.assignments(C)
    end
    for k in 2:Kmax]

    # return a clustering corespondig to a maximum average silhouette
    return S[findmax(map(first, S)) |> last] |> last
end


abstract type Kernel end

mutable struct Linear <: Kernel
    σ::Real
    function Linear(σ::Real)
        @assert σ > 0 "The output variance, σ, must be positive."
        new(σ)
    end
end

mutable struct RBF <: Kernel
    l::Real
    σ::Real
    function RBF(l::Real, σ::Real)
        @assert σ > 0 "The output variance, σ, must be positive."
        @assert l > 0 "The lengthscale, l, must be positive."
        new(l, σ)
    end
end

mutable struct RationalQuadratic <: Kernel
    a::Real
    l::Real
    σ::Real
    function RationalQuadratic(a::Real, l::Real, σ::Real)
        @assert a > 0 "The relative weighting of different lengthscales, a, must be positive."
        @assert σ > 0 "The output variance, σ, must be positive."
        @assert l > 0 "The lengthscale, l, must be positive."
        new(a, l, σ)
    end
end

mutable struct Periodic <: Kernel
    p::Real
    l::Real
    σ::Real
    function Periodic(p::Real, l::Real, σ::Real)
        @assert p > 0 "The period, p, must be positive."
        @assert σ > 0 "The output variance, σ, must be positive."
        @assert l > 0 "The lengthscale, l, must be positive."
        new(p, l, σ)
    end
end

mutable struct LocallyPeriodic <: Kernel
    p::Real
    l::Real
    σ::Real
    function LocallyPeriodic(p::Real, l::Real, σ::Real)
        @assert p > 0 "The period, p, must be positive."
        @assert σ > 0 "The output variance, σ, must be positive."
        @assert l > 0 "The lengthscale, l, must be positive."
        new(p, l, σ)
    end
end

abstract type Model end

mutable struct RLS{T <: AbstractFloat} <: Model
    y::Vector{T}
    w::Vector{T}
    λ::Real
    ŷ::Vector{T}
    σ::Vector{T}
end

mutable struct KRLS{T <: AbstractFloat} <: Model
    y::REALVEC
    x::REALMAT
    kernel::Kernel
    λ::Real
    budget::Integer
    mode::String
    μ::Vector{T}
    Σ::Matrix{T}
    Q::Matrix{T}
    ŷ::Vector{T}
    σ::Vector{T}
    #function KRLS(λ::Real, budget::Integer, forgetting::String)
    #    @assert λ <= 1 && λ > 0 "Forgetting factor, λ, must be ]0;1]."
    #    @assert budget > 0 "Budget size must be a positive integer."
    #    @assert (forgetting == "B2P" || forgetting == "UI") "Forgetting mode must be either B2P (back 2 prior) or UI (uncertainty injection)."
    #    new(λ, budget, forgetting)
    #end
end


"""
    Count{S <: MOI.AbstractScalarSet}(dimension::Int, set::MOI.AbstractScalarSet)

``\\{(y, x) \\in \\mathbb{N} \\times \\mathbb{T}^\\mathtt{dimension} : y = |\\{i | x_i \\in S \\}|\\}``

`dimension` is the number of variables that are checked against the `set`.

Also called `among`.

## Example

    [w, x, y, z] in Count(3, MOI.EqualTo(2.0))
    # w == sum([x, y, z] .== 2.0)
"""
struct Count{S <: MOI.AbstractScalarSet} <: MOI.AbstractVectorSet
    dimension::Int
    set::S
end

function Count(dimension::Int, value::Real)
    return Count(dimension, MOI.EqualTo(value))
end

MOI.dimension(set::Count{S}) where {S} = set.dimension + 1
copy(set::Count{S}) where {S} = Count(set.dimension, copy(set.set))
function Base.:(==)(x::Count{S}, y::Count{S}) where {S}
    return x.dimension == y.dimension && x.set == y.set
end

"""
    CountedValuesType

Kind of values to be counted for a `GlobalCardinality` constraint:

* either the values to count are fixed when creating the set:
  `FIXED_COUNTED_VALUES`
* or the values are themselves variables (typically constrained elsewhere):
  `VARIABLE_COUNTED_VALUES`
"""
@enum CountedValuesType begin
    FIXED_COUNTED_VALUES
    VARIABLE_COUNTED_VALUES
end

"""
    CountedValuesClosureType

Whether values that are not counted in `GlobalCardinality` constraint are
allowed in the array whose values are counted:

* either uncounted values are allowed: `OPEN_COUNTED_VALUES`
* or they are not allowed: `CLOSED_COUNTED_VALUES`
"""
@enum CountedValuesClosureType begin
    OPEN_COUNTED_VALUES
    CLOSED_COUNTED_VALUES
end

"""
    GlobalCardinality{CVT, CVCT, T}(dimension::Int, values::Vector{T})

This set represents the large majority of the variants of the
global-cardinality constraint, with the parameters set in `CountedValuesType`
(`CVT` parameter) and `CountedValuesClosureType` (`CVCT` parameter).

## Fixed and open

``\\{(x, y) \\in \\mathbb{T}^\\mathtt{dimension} \\times \\mathbb{N}^d : y_i = |\\{ j | x_j = \\mathtt{values}_i, \\forall j \\}| \\}``

The first `dimension` variables are an array, the last variables are the
number of times that each item of `values` is present in the first array.
Values that are not in `values` are ignored.

Also called [`gcc`](https://sofdem.github.io/gccat/gccat/Cglobal_cardinality.html)
or `count`.

### Example

    [x, y, z, v, w] in GlobalCardinality{FIXED_COUNTED_VALUES, OPEN_COUNTED_VALUES}(3, [2.0, 4.0])
    [x, y, z, v, w] in GlobalCardinality{OPEN_COUNTED_VALUES}(3, [2.0, 4.0])
    [x, y, z, v, w] in GlobalCardinality(3, [2.0, 4.0])
    # v == sum([x, y, z] .== 2.0)
    # w == sum([x, y, z] .== 4.0)

## Variable and open

``\\{(x, y, z) \\in \\mathbb{T}^\\mathtt{dimension} \\times \\mathbb{N}^\\mathtt{n\\_values} \\times \\mathbb{T}^\\mathtt{n\\_values} : y_i = |\\{ j | x_j = z_i, \\forall j \\}| \\}``

The first `dimension` variables are an array, the next `n_values` variables
are the number of times that each item of the last `n_values` variables is
present in the first array. Values of the first array that are not in the
`n_values` are ignored.

Also called `distribute`.

### Example

    [x, y, z, t, u, v, w] in GlobalCardinality{VARIABLE_COUNTED_VALUES, OPEN_COUNTED_VALUES, T}(3, 2)
    [x, y, z, t, u, v, w] in GlobalCardinality{OPEN_COUNTED_VALUES, T}(3, 2)
    [x, y, z, t, u, v, w] in GlobalCardinality{T}(3, 2)
    # t == sum([x, y, z] .== v)
    # u == sum([x, y, z] .== w)

## Fixed and closed

``\\{(x, y) \\in \\mathbb{T}^\\mathtt{dimension} \\times \\mathbb{N}^d : y_i = |\\{ j | x_j = \\mathtt{values}_i, \\forall j \\}| \\}``

The first `dimension` variables are an array, the last variables are the
number of times that each item of `values` is present in the first array.
Each value of the first array must be within `values`.

### Example

    [x, y, z, v, w] in GlobalCardinality{FIXED_COUNTED_VALUES, CLOSED_COUNTED_VALUES, T}(3, [2.0, 4.0])
    # v == sum([x, y, z] .== 2.0)
    # w == sum([x, y, z] .== 4.0)
    # x ∈ [2.0, 4.0], y ∈ [2.0, 4.0], z ∈ [2.0, 4.0]

## Variable and closed

``\\{(x, y, z) \\in \\mathbb{T}^\\mathtt{dimension} \\times \\mathbb{N}^\\mathtt{n\\_values} \\times \\mathbb{T}^\\mathtt{n\\_values} : y_i = |\\{ j | x_j = z_i, \\forall j \\}| \\}``

The first `dimension` variables are an array, the next `n_values` variables
are the number of times that each item of the last `n_values` variables is
present in the first array. Each value of the first array must be within the
next given `n_values`.

Also called `distribute`.

### Example

    [x, y, z, t, u, v, w] in GlobalCardinality{VARIABLE_COUNTED_VALUES, CLOSED_COUNTED_VALUES, T}(3, 2)
    # t == sum([x, y, z] .== v)
    # u == sum([x, y, z] .== w)
    # x ∈ [v, w], y ∈ [v, w], z ∈ [v, w]
"""
struct GlobalCardinality{CVT, CVCT, T <: Real} <: MOI.AbstractVectorSet
    dimension::Int
    values::Vector{T}
    n_values::Int

    function GlobalCardinality{CVT, CVCT, T}(dimension::Int, values::Vector{T}, n_values::Int) where {CVT, CVCT, T <: Real}
        if CVT == FIXED_COUNTED_VALUES && length(values) == 0
            error("Inconsistent GlobalCardinality set: the counted values should be fixed, but array of values to count is $(values).")
        end
        if CVT == VARIABLE_COUNTED_VALUES && n_values <= 0
            error("Inconsistent GlobalCardinality set: the counted values should be variables, but their number is $(n_values).")
        end

        return new{CVT, CVCT, T}(dimension, values, n_values)
    end
end

# Helper functions: always FIXED_COUNTED_VALUES.
function GlobalCardinality(dimension::Int, values::Vector{T}) where {T <: Real}
    return GlobalCardinality{FIXED_COUNTED_VALUES, OPEN_COUNTED_VALUES, T}(dimension, values, -1)
end

function GlobalCardinality{CVCT}(dimension::Int, values::Vector{T}) where {CVCT, T <: Real}
    return GlobalCardinality{FIXED_COUNTED_VALUES, CVCT, T}(dimension, values, -1)
end

function GlobalCardinality{CVCT, T}(dimension::Int, values::Vector{T}) where {CVCT, T <: Real}
    return GlobalCardinality{FIXED_COUNTED_VALUES, CVCT, T}(dimension, values, -1)
end

function GlobalCardinality{CVT, CVCT}(dimension::Int, values::Vector{T}) where {CVT, CVCT, T <: Real}
    return GlobalCardinality{CVT, CVCT, T}(dimension, values, -1)
end

function GlobalCardinality{CVT, CVCT, T}(dimension::Int, values::Vector{T}) where {CVT, CVCT, T <: Real}
    return GlobalCardinality{CVT, CVCT, T}(dimension, values, -1)
end

# Helper functions: always VARIABLE_COUNTED_VALUES.
function GlobalCardinality{T}(dimension::Int, n_values::Int) where {T <: Real}
    return GlobalCardinality{VARIABLE_COUNTED_VALUES, OPEN_COUNTED_VALUES, T}(dimension, T[], n_values)
end

function GlobalCardinality{CVCT, T}(dimension::Int, n_values::Int) where {CVCT, T <: Real}
    return GlobalCardinality{VARIABLE_COUNTED_VALUES, CVCT, T}(dimension, T[], n_values)
end

function GlobalCardinality{CVT, CVCT, T}(dimension::Int, n_values::Int) where {CVT, CVCT, T <: Real}
    return GlobalCardinality{CVT, CVCT, T}(dimension, T[], n_values)
end

function n_values(set::GlobalCardinality)
    if length(set.values) > 0
        return length(set.values)
    else
        return set.n_values
    end
end

function MOI.dimension(set::GlobalCardinality{CVT, CVCT, T}) where {CVT, CVCT, T <: Real}
    dim = set.dimension + n_values(set) # Array to count in, counts.
    if CVT == VARIABLE_COUNTED_VALUES
        dim += n_values(set) # Values to count.
    end
    return dim
end

function copy(set::GlobalCardinality{CVT, CVCT, T})  where {CVT, CVCT, T <: Real}
    return GlobalCardinality{CVT, CVCT, T}(set.dimension, copy(set.values), set.n_values)
end

function Base.:(==)(x::GlobalCardinality{CVT, CVCT, T}, y::GlobalCardinality{CVT, CVCT, T}) where {CVT, CVCT, T <: Real}
    return x.dimension == y.dimension && x.values == y.values && x.n_values == y.n_values
end

# Shortcuts for types.
# const GlobalCardinality{T} = GlobalCardinality{FIXED_COUNTED_VALUES, OPEN_COUNTED_VALUES, T}
const GlobalCardinalityOpen{T} = GlobalCardinality{FIXED_COUNTED_VALUES, OPEN_COUNTED_VALUES, T}
const GlobalCardinalityClosed{T} = GlobalCardinality{FIXED_COUNTED_VALUES, CLOSED_COUNTED_VALUES, T}
const GlobalCardinalityFixed{T} = GlobalCardinality{FIXED_COUNTED_VALUES, OPEN_COUNTED_VALUES, T}
const GlobalCardinalityFixedOpen{T} = GlobalCardinality{FIXED_COUNTED_VALUES, OPEN_COUNTED_VALUES, T}
const GlobalCardinalityFixedClosed{T} = GlobalCardinality{FIXED_COUNTED_VALUES, CLOSED_COUNTED_VALUES, T}
const GlobalCardinalityVariable{T} = GlobalCardinality{VARIABLE_COUNTED_VALUES, OPEN_COUNTED_VALUES, T}
const GlobalCardinalityVariableOpen{T} = GlobalCardinality{VARIABLE_COUNTED_VALUES, OPEN_COUNTED_VALUES, T}
const GlobalCardinalityVariableClosed{T} = GlobalCardinality{VARIABLE_COUNTED_VALUES, CLOSED_COUNTED_VALUES, T}

# Ease dispatch for implementing solvers.
function MOI.supports_constraint(o::MOI.AbstractOptimizer, f::MOI.AbstractVectorFunction, s::Type{GlobalCardinality{CVT, CVCT, T}}) where {CVT, CVCT, T <: Real}
    return MOI.supports_constraint(o, f, s, Val(CVT), Val(CVCT))
end

function MOI.supports_constraint(o::MOI.AbstractOptimizer, f::MOI.AbstractVectorFunction, s::Type{GlobalCardinality{CVT, OPEN_COUNTED_VALUES, T}}, ::Val{CVT}, ::Val{OPEN_COUNTED_VALUES}) where {CVT, T <: Real}
    return MOI.supports_constraint(o, f, s, Val(CVT))
end

function MOI.supports_constraint(o::MOI.AbstractOptimizer, f::MOI.AbstractVectorFunction, s::Type{GlobalCardinality{FIXED_COUNTED_VALUES, CVCT, T}}, ::Val{FIXED_COUNTED_VALUES}, ::Val{CVCT}) where {CVCT, T <: Real}
    return MOI.supports_constraint(o, f, s, Val(CVCT))
end

"""
    CountCompare(dimension::Int)

``\\{(z, x, y) \\in \\mathbb{N} \\times \\mathbb{R}^\\mathtt{dimension} \\times \\mathbb{R}^\\mathtt{dimension} : Z = |\\{i | x_i = y_i\\}|\\}``

The first `dimension` variables are the first array that is compared to the
second one, indicated by the next `dimension` variables. The last variable
is the number of values that are identical in both arrays.

## Example

    [v, w, x, y, z] in Count(2)
    # w == sum([w, x] .== [y, z])
"""
struct CountCompare <: MOI.AbstractVectorSet
    dimension::Int
end

MOI.dimension(set::CountCompare) = 2 * set.dimension + 1

"""
    CountDistinct(dimension::Int)

The first variable in the set is forced to be the number of distinct values in
the rest of the expressions.

This is a relaxed version of `AllDifferent`; it encodes an `AllDifferent`
constraint when the first variable is the number of variables in the set.

Also called `nvalues`.

## Example

    [x, y, z] in CountDistinct(3)
    # x = 1 if y == z, x = 2 if y != z
"""
struct CountDistinct <: MOI.AbstractVectorSet
    dimension::Int
end

MOI.dimension(set::CountDistinct) = set.dimension + 1

# isbits types, nothing to copy
function copy(
    set::Union{CountCompare, CountDistinct},
)
    return set
end


# =====================================
# Flowpipe composition with a lazy map
# =====================================

"""
    MappedFlowpipe{FT<:AbstractFlowpipe, ST} <: AbstractFlowpipe

### Fields

- `F`    -- flowpipe
- `func` -- function representing the map
"""
struct MappedFlowpipe{FT<:AbstractFlowpipe, ST} <: AbstractFlowpipe
    F::FT
    func::ST
end

"""
    Projection(fp::AbstractFlowpipe, vars::NTuple{D, T}) where {D, T<:Integer}

Return the lazy projection of a flowpipe.

### Input

### Output

### Notes

The projection is lazy, and consists of mapping each set
`X` in the flowpipe to `MX`, where `M` is the projection matrix associated with
the given variables `vars`.
"""
function LazySets.Projection(fp::AbstractFlowpipe, vars::NTuple{D, T}) where {D, T<:Integer}
    # TODO: assert that vars belong to the variables of the flowpipe
    M = projection_matrix(collect(vars), dim(F), Float64)
    func = @map(x -> M*x)
    return MappedFlowpipe(fp, func)
end

function overapproximate(fp::Flowpipe, args...)
    return Flowpipe(map(R -> overapproximate(R, args...), fp), fp.ext)
end

function overapproximate(fp::VRT, args...) where {RT, VRT<:AbstractVector{RT}}
    return Flowpipe(map(R -> overapproximate(R, args...), fp))
end


include("MLPClassifier.jl")
include("../datasets/Drug-Classification/dataset1.jl")
include("../datasets/Income-Classification/dataset2.jl")
include("../datasets/Wine-Classification/dataset3.jl")
include("../datasets/Drug-Consumism-Classification/dataset4.jl")
include("../datasets/Heart-Disease-Classification/dataset5.jl")

net = MLPClassifier(dataset1_nfeatures, dataset1_labels, [15,5,10])
#net = MLPClassifier(dataset1_nfeatures, dataset1_labels, [5])
x_train, y_train, x_test, y_test = get_dataset1("../datasets/Drug-Classification")
println("Accuracy before training: ", accuracy(zip(x_test, y_test), net))
println("Error rate before training: ", error_rate(zip(x_test, y_test), net))
#fit!(net, x_train, y_train; max_epochs = 1000)
fit!(net, x_train, y_train; max_epochs = 10000)
println("Accuracy after training: ", accuracy(zip(x_test, y_test), net))
println("Error rate after training: ", error_rate(zip(x_test, y_test), net))
#=
-> 5 classes
Accuracy before training: 0.05
Error rate before training: 0.95

Accuracy after training: 0.5
Error rate after training: 0.5
=#

#=
net = MLPClassifier(dataset2_nfeatures, dataset2_labels, [26,13,6])
x_train, y_train, x_test, y_test = get_dataset2("../datasets/Income-Classification")
println("Accuracy before training: ", accuracy(zip(x_test, y_test), net))
println("Error rate before training: ", error_rate(zip(x_test, y_test), net))
#fit!(net, x_train, y_train; max_epochs = 5000)
fit!(net, x_train, y_train; max_epochs = 10000)
println("Accuracy after training: ", accuracy(zip(x_test, y_test), net))
println("Error rate after training: ", error_rate(zip(x_test, y_test), net))
=#
#=
-> 2 classes
Accuracy before training: 0.24888684170121295
Error rate before training: 0.751113158298787

Accuracy after training: 0.7543374788883771
Error rate after training: 0.2456625211116229
=#

#=
net = MLPClassifier(dataset3_nfeatures, dataset3_labels, [22,10,15,11])
x_train, y_train, x_test, y_test = get_dataset3("../datasets/Wine-Classification")
println("Accuracy before training: ", accuracy(zip(x_test, y_test), net))
println("Error rate before training: ", error_rate(zip(x_test, y_test), net))
#fit!(net, x_train, y_train; max_epochs = 15000)
fit!(net, x_train, y_train; max_epochs = 10000)
println("Accuracy after training: ", accuracy(zip(x_test, y_test), net))
println("Error rate after training: ", error_rate(zip(x_test, y_test), net))
=#
#=
-> 11 classes
Accuracy before training: 0.007692307692307693
Error rate before training: 0.9923076923076923

Accuracy after training: 0.5030769230769231
Error rate after training: 0.4969230769230769
=#

#=
#net = MLPClassifier(dataset4_nfeatures, dataset4_labels, [24,6,12,7])
net = MLPClassifier(dataset4_nfeatures, dataset4_labels, [24,6,12,4,7])
x_train, y_train, x_test, y_test = get_dataset4("../datasets/Drug-Consumism-Classification")
println("Accuracy before training: ", accuracy(zip(x_test, y_test), net))
println("Error rate before training: ", error_rate(zip(x_test, y_test), net))
#fit!(net, x_train, y_train; max_epochs = 20000)
fit!(net, x_train, y_train; max_epochs = 10000)
println("Accuracy after training: ", accuracy(zip(x_test, y_test), net))
println("Error rate after training: ", error_rate(zip(x_test, y_test), net))
=#
#=
-> 7 classes
Accuracy before training: 0.17506631299734748
Error rate before training: 0.8249336870026526

Accuracy after training: 0.3952254641909814
Error rate after training: 0.6047745358090185
=#

#=
net = MLPClassifier(dataset5_nfeatures, dataset5_labels, [26, 13, 7, 5])
x_train, y_train, x_test, y_test = get_dataset5("../datasets/Heart-Disease-Classification")
println("Accuracy before training: ", accuracy(zip(x_test, y_test), net))
println("Error rate before training: ", error_rate(zip(x_test, y_test), net))
fit!(net, x_train, y_train; max_epochs = 10000)
println("Accuracy after training: ", accuracy(zip(x_test, y_test), net))
println("Error rate after training: ", error_rate(zip(x_test, y_test), net))
=#
#=
-> 5 classes
Accuracy before training: 0.03333333333333333
Error rate before training: 0.9666666666666667

Accuracy after training: 0.48333333333333334
Error rate after training: 0.5166666666666667
=#


abstract type Shaft <: GeneratorComponent end

@def shaft_ports begin
    state_input = Vector{Int64}()
    inner_input = [τe_var, τm_var]
end

"""
Parameters of single mass shaft model. Typically represents the rotor mass.

# Conmutable structor
```julia
SingleMass(H, D)
```

# Arguments
* `H`   ::Float64 : Rotor inertia constant in MWs/MVA
* `D`   ::Float64 : Rotor natural damping in pu

"""
mutable struct SingleMass <: Shaft
    H::Float64
    D::Float64
    n_states::Int64
    states::Vector{Symbol}
    ports::Ports

    function SingleMass(H::Float64,
                        D::Float64)

        n_states = 2
        states = [:δ, :ω]
        @shaft_ports

        new(H,
            D,
            n_states,
            states,
            Ports(state_input, inner_input))
    end

end

"""
Parameters of 5 mass-spring shaft model.
It contains a High-Pressure (HP) steam turbine, Intermediate-Pressure (IP)
steam turbine, Low-Pressure (LP) steam turbine, the Rotor and
an Exciter (EX) mover.

# Conmutable structor
```julia
FiveMassShaft(H, H_hp, H_ip, H_lp, H_ex,
              D, D_hp, D_ip, D_lp, D_ex,
              D_12, D_23, D_34, D_45,
              K_hp, K_ip, K_lp, K_ex)
```

# Arguments
* `H`   ::Float64 : Rotor inertia constant in MWs/MVA
* `H_hp`::Float64 : High pressure turbine inertia constant in MWs/MVA
* `H_ip`::Float64 : Intermediate pressure turbine inertia constant in MWs/MVA
* `H_lp`::Float64 : Low pressure turbine inertia constant in MWs/MVA
* `H_ex`::Float64 : Exciter inertia constant in MWs/MVA
* `D`   ::Float64 : Rotor natural damping in pu
* `D_hp`::Float64 : High pressure turbine natural damping in pu
* `D_ip`::Float64 : Intermediate pressure turbine natural damping in pu
* `D_lp`::Float64 : Low pressure turbine natural damping in pu
* `D_ex`::Float64 : Exciter natural damping in pu
* `D_12`::Float64 : High-Intermediate pressure turbine damping
* `D_23`::Float64 : Intermediate-Low pressure turbine damping
* `D_34`::Float64 : Low pressure turbine-Rotor damping
* `D_45`::Float64 : Rotor-Exciter damping
* `K_hp`::Float64 : High pressure turbine angle coefficient
* `K_ip`::Float64 : Intermediate pressure turbine angle coefficient
* `K_lp`::Float64 : Low pressure turbine angle coefficient
* `K_ex`::Float64 : Exciter angle coefficient

"""
mutable struct FiveMassShaft <: Shaft
    H::Float64
    H_hp::Float64
    H_ip::Float64
    H_lp::Float64
    H_ex::Float64
    D::Float64
    D_hp::Float64
    D_ip::Float64
    D_lp::Float64
    D_ex::Float64
    D_12::Float64
    D_23::Float64
    D_34::Float64
    D_45::Float64
    K_hp::Float64
    K_ip::Float64
    K_lp::Float64
    K_ex::Float64
    n_states::Int64
    states::Vector{Symbol}
    ports::Ports

    function FiveMassShaft(H::Float64,
                           H_hp::Float64,
                           H_ip::Float64,
                           H_lp::Float64,
                           H_ex::Float64,
                           D::Float64,
                           D_hp::Float64,
                           D_ip::Float64,
                           D_lp::Float64,
                           D_ex::Float64,
                           D_12::Float64,
                           D_23::Float64,
                           D_34::Float64,
                           D_45::Float64,
                           K_hp::Float64,
                           K_ip::Float64,
                           K_lp::Float64,
                           K_ex::Float64)

        n_states = 10
        states = [:δ, :ω,
                  :δ_hp, :ω_hp,
                  :δ_ip, :ω_ip,
                  :δ_lp, :ω_lp,
                  :δ_ex, :ω_ex]
        @shaft_ports

        new(H,
            H_hp,
            H_ip,
            H_lp,
            H_ex,
            D,
            D_hp,
            D_ip,
            D_lp,
            D_ex,
            D_12,
            D_23,
            D_34,
            D_45,
            K_hp,
            K_ip,
            K_lp,
            K_ex,
            n_states,
            states,
            Ports(state_input, inner_input))
    end

end


####################

using Soss
using SampleChainsDynamicHMC

p = @model k,x begin
    σ ~ Exponential()
    α ~ Normal()
    β ~ Normal() |> iid(k)
    yhat = α .+ x * β
    y ~ For(eachindex(yhat)) do j
        Normal(μ = yhat[j], σ=σ)
    end
    return y
end;

m = @model k,x begin
    σ ~ Exponential()
    α ~ Normal()
    β ~ Normal() |> iid(k)
    yhat = α .+ x * β
    y ~ For(eachindex(yhat)) do j
        Normal(μ = yhat[j], σ=σ)
    end
    return y
end;

k=1

x = randn(1000,k)

y = rand(p(k=k,x=x))

post = m(k=k, x=x) | (;y)

ℓ(nt) = logdensity(post, nt) ;

t = xform(post);

chains = initialize!(DynamicHMCChain, ℓ, t);
drawsamples!(chains, 199)

drawsamples!(chains, 800)

pred = [predict(Soss.withmeasures(m)(k=k, x=x), c) for c in chains];
yhat = hcat(getproperty.(pred, :yhat)...)
d = getproperty.(pred, :_y_dist)
ppc = hcat((cdf.(dj.data, y) for dj in d)...)

using StatsBase
using AverageShiftedHistograms

function grassfire(v,m)
    ordv = ordinalrank(v)
    n = length(v)
    (lo,hi) = extrema(m)
    Δ = (hi-lo)/20
    lo = lo - Δ
    hi = hi + Δ
    a = ash(ordv, m[:,1]; rngx=range(1-n/20,n+n/20,length=800), rngy=range(lo,hi,length=600), kernelx=AverageShiftedHistograms.Kernels.uniform, kernely=AverageShiftedHistograms.Kernels.uniform)
    for j in 2:size(m,2)
        ash!(a,ordv, m[:,j])
    end
    ash!(a; mx=3,my=10)

    # [ash(ordv, m[:,j]; rngx=range(1-n/20,n+n/20,length=800), rngy=range(lo,hi,length=600)) for j in 1:size(m,2)]
end

# a = grassfire(y,ppc)

a = grassfire(vec(mean(yhat, dims=2)),ppc)
using Plots
plot(a)

b = grassfire(vec(x),y .-yhat)
plot(b)

using Plots

scatter(ordinalrank(y), ppc)

using NestedTuples: rmap
rmap(length, chains)

# chain1 = initialize!(DynamicHMCChain, ℓ, t);
# drawsamples!(chain1,1000)

# chain2 = initialize!(DynamicHMCChain, ℓ, t);
# drawsamples!(chain2,1000)

# chain3 = initialize!(DynamicHMCChain, ℓ, t);
# drawsamples!(chain3,1000)

# chains = MultiChain(chain1, chain2, chain3)


phasefile = joinpath("data", "small", "Phase.nii")
magfile = joinpath("data", "small", "Mag.nii")
phase = Float32.(readphase(phasefile))
magni = readmag(magfile)

iswrap(x, y) = abs(rem2pi(x - y, RoundNearest)) < 1e-6

function test(f)
    unwrapped = f(phase; mag=magni, TEs=[4,8,12])
    @test !all(unwrapped .== phase)
    @test all(iswrap.(unwrapped, phase))
    return unwrapped
end

test(romeo)
test(unwrap)
test(unwrap_individual)
@test !all(laplacianunwrap(phase) .== phase)


using MosekTools, DifferentialEquations, PyPlot, LaTeXStrings, MarkovBounds
DE = DifferentialEquations
u = []
@polyvar(x[1:2])
@polyvar(t)

μ = 1/2
f = [x[2]; - x[1] + μ*(1 - x[1]^2) * x[2]]
g = [0; 0.5]
σ = g*g'

Tf = 2.0
x0 = [0.5; 0.5]
U = FullSpace()
X = @set(x[1] >= 0)

## data
order = 8
nt = 20
trange = range(Tf/nt, stop = Tf, length=nt)

## solve
MP = DiffusionProcess(x, f, σ, FullSpace())
CP = ControlProcess(MP, Tf, u, t, U, nothing, nothing, ExitProbability(X), 0)

bnds, V = optimal_control(CP, x0, order, trange, Mosek.Optimizer; value_function = true)

N = 100
drift(x,p,t) = [f[1](x[2]); f[2](x[1],x[2])]
diffusion(x,p,t) = [0; g[2]]
sde = DE.SDEProblem(drift, diffusion, x0, (0.0,Tf))
sde_ensemble = DE.EnsembleProblem(sde)

trajectories = DE.solve(sde_ensemble, trajectories=N, EM(), dt = 0.01)

fig, ax = subplots(figsize=(5,5))
stayed = 0
for sol in trajectories
    max_idx = findfirst(m -> m[1] < 0, sol.u)
    if max_idx == nothing
        global stayed += 1
    end
    max_idx = (max_idx == nothing ? length(sol.u) : max_idx)
    global ln = ax.plot([sol.u[i][1] for i in 1:max_idx], [sol.u[i][2] for i in 1:max_idx], color="blue", linewidth=0.5)
end
ax.scatter([x0[1]], [x0[2]], marker="o", color="black", s=5)
ax.set_xlabel(L"x_1")
ax.set_ylabel(L"x_2")
display(fig)

println("Upper Bound:  P(exit) ≦ ", bnds.bounds)
println("Sampled: P(exit) = ", stayed/N)


@testset "Dimer with Linesearch" begin

heading1("TEST: Dimer type methods with linesearch")

heading2("Muller potential")
V = MullerPotential()
x0, v0 = ic_dimer(V, :near)
E, dE = objective(V)

dimer = StaticDimer(a_trans=0.002, a_rot=0.002, len=1e-3, maxnumdE=100, verbose=verbose)
x, v, log = run!(dimer, E, dE, copy(x0), copy(v0))
@test res_trans(log)[end] <= dimer.tol_trans
@test res_rot(log)[end] <= dimer.tol_rot

bbdimer = BBDimer(a0_trans=0.002, a0_rot=0.002, maxnumdE=100, verbose=verbose)
xbb, vbb, bblog = run!(bbdimer, E, dE, copy(x0), copy(v0))
@test res_trans(bblog)[end] <= dimer.tol_trans
@test res_rot(bblog)[end] <= dimer.tol_rot
@test vecnorm(xbb - x, Inf) < 1e-4

lsbbdimer = BBDimer(a0_trans=0.002, a0_rot=0.002, maxnumdE=100, verbose=verbose,
                     ls = Backtracking() )
xls, vls, lslog = run!(lsbbdimer, E, dE, copy(x0), copy(v0))
@test res_trans(lslog)[end] <= lsbbdimer.tol_trans
@test res_rot(lslog)[end] <= lsbbdimer.tol_rot
@test vecnorm(xls - x, Inf) < 1e-4

supdimer = SuperlinearDimer(maximum_translation=0.02, max_num_rot=1, len=1e-3,
                            maxnumdE=1000, verbose=verbose)
xsup, vsup, logsup = run!(supdimer, E, dE, copy(x0), copy(v0))
@test res_trans(logsup)[end] <= supdimer.tol_trans
@test vecnorm(xsup - x, Inf) < 1e-4

odedimer = ODEDimer(verbose=verbose)
xode, vode, logode = run!(odedimer, E, dE, copy(x0), copy(v0))
# @test res_trans(logode)[end] <= odedimer.tol_trans
@test maxres(logode)[end] <= odedimer.tol_trans
@test vecnorm(xode - x, Inf) < 1e-4

heading2("Standard double-well potential")

V = DoubleWell()
x0, v0 = ic_dimer(V, :near)
E, dE = objective(V)

dimer = StaticDimer(a_trans=0.66, a_rot=0.4, len=1e-3, maxnumdE=100, verbose=verbose)
x, v, log = run!(dimer, E, dE, x0, v0)
@test res_trans(log)[end] <= dimer.tol_trans
@test res_rot(log)[end] <= dimer.tol_rot

bbdimer = BBDimer(a0_trans=0.66, a0_rot=0.4, maxnumdE=100, verbose=verbose)
xbb, vbb, bblog = run!(bbdimer, E, dE, x0, v0)
@test res_trans(bblog)[end] <= dimer.tol_trans
@test res_rot(bblog)[end] <= dimer.tol_rot
@test vecnorm(xbb - x, Inf) < 1e-4

lsbbdimer = BBDimer(a0_trans=0.66, a0_rot=0.4, maxnumdE=100, verbose=verbose,
                     ls = Backtracking() )
xls, vls, lslog = run!(lsbbdimer, E, dE, x0, v0)
@test res_trans(lslog)[end] <= lsbbdimer.tol_trans
@test res_rot(lslog)[end] <= lsbbdimer.tol_rot
@test vecnorm(xls - x, Inf) < 1e-4

supdimer = SuperlinearDimer(maximum_translation=1.0, max_num_rot=1, len=1e-3,
                            maxnumdE=1000, verbose=verbose)
xsup, vsup, logsup = run!(supdimer, E, dE, x0, v0)
@test res_trans(logsup)[end] <= supdimer.tol_trans
@test vecnorm(xsup - x, Inf) < 1e-4

odedimer = ODEDimer(verbose=verbose)
xode, vode, logode = run!(odedimer, E, dE, copy(x0), copy(v0))
# @test res_trans(logode)[end] <= odedimer.tol_trans
@test maxres(logode)[end] <= odedimer.tol_trans
@test vecnorm(xode - x, Inf) < 1e-4

end


@testset "Statistics" begin
  @testset "Solution" begin
    grid2D = RegularGrid(3,3)
    realizations = Dict(:value => [i*ones(nelms(grid2D)) for i in 1:3])
    solution2D = SimulationSolution(grid2D, realizations)

    # mean
    mean2D = mean(solution2D)
    @test mean2D[:value] == 2.0*ones(nelms(mean2D))
    @test domain(mean2D) == solution2D.domain

    # variance
    variance2D = var(solution2D)
    @test variance2D[:value] == 1.0*ones(nelms(variance2D))
    @test domain(variance2D) == solution2D.domain

    # quantile (scalar)
    p = 0.5
    quantile2D = quantile(solution2D, p)
    @test quantile2D[:value] == 2.0*ones(nelms(quantile2D))
    @test domain(quantile2D) == solution2D.domain

    # quantile (vector)
    ps = [0.0, 0.5, 1.0]
    quantiles2D = quantile(solution2D, ps)
    @test quantiles2D[2][:value] == quantile2D[:value]
  end

  @testset "Data" begin
    # load data with bias towards large values (gold mine)
    sdata = readgeotable(joinpath(datadir,"clustered.csv"), coordnames=(:x,:y))

    # spatial mean
    μn = mean(sdata[:Au])
    μs = mean(sdata, :Au)
    @test abs(μn - 0.5) > abs(μs - 0.5)
    @test mean(sdata)[:Au] ≈ μs

    # spatial variance
    σn = var(sdata[:Au])
    σs = var(sdata, :Au)
    @test σn ≤ σs
    @test var(sdata)[:Au] ≈ σs

    # spatial quantile
    qn = quantile(sdata[:Au], 0.5)
    qs = quantile(sdata, :Au, 0.5)
    @test qn ≥ qs
    @test quantile(sdata, 0.5)[:Au] ≈ qs
  end
end


#!/usr/bin/env julia

# testing isinside(), is point inside polygon
# all the random points are drawn only if they're inside one of the polygons

using Luxor

using Test

using Random
Random.seed!(42)

function ngon_poly(x, y, radius, sides::Int, orientation=0, action=:nothing; close=true)
    [Point(x+cos(orientation + n * 2pi/sides) * radius,
    y+sin(orientation + n * 2pi/sides) * radius) for n in 1:sides]
end

function bounding_b(x, y, o)
    return [Point(x-o, y-o), Point(x-o, y+o), Point(x+o, y+o), Point(x+o, y-o)]
end

function point_inside_polygon(fname)
    Drawing(1200, 1200, fname)
    origin()
    background("grey20")
    polys = [ngon_poly(x, y, 60, 5, pi/2) for x in -500:150:500, y in -500:150:500]
    for pol in polys
        randomhue()
        poly(pol, :fill)
    end
    # test whether random points are inside any of the polygons
    for i in 1:5000
        pt = randompoint(-600, -600, 600, 600)
        for pol in polys
            randomsize = rand(4:12)
            bp = bounding_b(pt.x, pt.y, randomsize)
            # check that all four corners of a 5x5 bounding box are inside this polygon
            # slow but this is Julia so it's still quick...
            if isinside(bp[1], pol) && isinside(bp[2], pol) && isinside(bp[3], pol) && isinside(bp[4], pol)
                randomhue()
                circle(pt.x, pt.y, randomsize, :fill)
            end
        end
    end

    @test finish() == true
    println("...finished test: output in $(fname)")
end

point_inside_polygon("point-inside-polygon.pdf")


# 1-Steiner spanner functionality

# Returns a graph G = (V, E) which is a Manhattan network of the original point
# set, i.e., a graph where each edge is parallel to one of the main coordinate
# axes and each pair of points has a path in the graph equal to the L1 distance.
# If the original set S has n points, this graph will have O(n log n) points
# and edges. Based on the algorithm from Gudmundsson et al.
#
# Args:
#   S - n x p matrix of points in R^d
# Returns
#   X - the original points plus the new projected points
#   E - an array of (int, int) representing the edges of the graph where the
#       int index the rows of Xnew.

# constant used for numerical issues
EPS = 10 * eps(0.0)

function makel1spanner(X::AbstractArray{T,2}) where {T<:Number}
    n, d = size(X)
    # copy so we don't corrupt the original data
    Xnew = copy(X)
    E = Tuple{Int, Int}[]
    # begin the recursion!
    Xnew = _makel1spanner!(Xnew, E, collect(1:n), d)
    return (Xnew, E)
end

function _makel1spanner!(X::AbstractArray{T,2},
                         E::Array{Tuple{Int,Int},1},
                         Xidx::Array{Int,1},
                         k::Int) where {T<:Number}
    # initialize the iterative counters
    Xindexs = Array{Int,1}[]
    k_indexs = Int[]
    push!(Xindexs, Xidx)
    push!(k_indexs, k)
    # now iterate through the recursion
    while length(Xindexs) > 0
        # pop off the local counters
        Xidx = pop!(Xindexs)
        k = pop!(k_indexs)
        # this is crazy, but if points already lie on hyperplane,
        # we should decrement k
        while (k > 1) && (maximum(X[Xidx,k]) - minimum(X[Xidx,k])) < EPS
            k -= 1
        end
        # handle base case first
        if k == 0 || length(Xidx) <= 1
            continue
        elseif k == 1
            # in this case, we only must add the adjacent points
            pointidx = sortperm(X[Xidx,1])
            for ii in 1:(length(pointidx)-1)
                push!(E, (Xidx[pointidx[ii]], Xidx[pointidx[ii+1]]))
            end
            continue
        else
            n = size(X,1)
            # make projected points (be sure to copy!)
            P = copy(X[Xidx,:])
            pstar = median(P[:,k])
            P[:,k] = pstar
            # add projected points
            X = vcat(X, P)
            nP = size(P,1)
            # add projected edges
            for ii in 1:nP
                push!(E, (Xidx[ii], n + ii))
            end
            Lhighidx = Xidx[X[Xidx,k] .> pstar]
            Llowidx = Xidx[X[Xidx,k] .< pstar]
            # Make sure we put median points on smaller indices
            Lequalidx = Xidx[X[Xidx,k] .== pstar]
            if length(Lequalidx) > 0 && (length(Llowidx) < length(Lhighidx))
                Llowidx = vcat(Llowidx, Lequalidx)
            elseif length(Lequalidx) > 0
                Lhighidx = vcat(Lhighidx, Lequalidx)
            end
            # call the operation on the upper half of original points
            push!(Xindexs, Lhighidx)
            push!(k_indexs, k)
            # call the operation on the lower half of original points
            push!(Xindexs, Llowidx)
            push!(k_indexs, k)
            # call the operation on the projected points
            Pidx = collect((n + 1):(n + nP))
            push!(Xindexs, Pidx)
            push!(k_indexs, k - 1)
        end
    end
    # return X!
    return X
end


# Example of polynomial matrix that is not a Sum-of-Squares matrix given in
# [C15] as the following biquadratic form:
#
# F(x; y) = (x_1^2 + 2 x_3^2) * y_1^2
#         + (x_2^2 + 2 x_1^2) * y_2^2
#         + (x_3^2 + 2 x_2^2) * y_3^2
#         - 2 (x_1x_2 y_1y_2 + x_2x_3 y_2y_3 + x_3x_1 y_3y_1)
#
# [C15] Choi, M. D.
# *Positive semidefinite biquadratic forms*.
# Linear Algebra and its Applications, **1975**, 12(2), 95-100.
@testset "Choi with $(factory.constructor)" for factory in sdp_factories
    @polyvar x y z

    m = SOSModel(factory)

    C = [ x^2+2y^2 -x*y      -x*z;
         -x*y       y^2+2z^2 -y*z;
         -x*z      -y*z       z^2+2x^2]

    @SDconstraint m C >= 0

    JuMP.optimize!(m)
    @test JuMP.termination_status(m) == MOI.INFEASIBLE
    @test JuMP.dual_status(m) == MOI.INFEASIBILITY_CERTIFICATE
end


# used as a notebook
using Plots, GLM, DataFrames
using Polynomials

xs = collect(5:5:50)
vs = [3.98, 6.3, 7.73, 8.66, 9.23, 9.46, 9.65, 9.86, 9.88, 9.89]

V₀ = 10
yp = log.(V₀ .- vs)

plot(xs, yp)

p = Polynomials.fit(xs, yp, 1)

tau = -1 / p[1]

b(n) = V₀ / (n * π) * (n % 2 == 0 ? 0 : 2)

T = 100
v_ft(t, N) = V₀ / 2 + sum([b(n) * sin(2π * n * t / T) for n ∈ 1:N])

v15 = [v_ft(t, 15) for t ∈ 5:5:40]
plot(collect(1:100), v15)

v100 = [v_ft(t, 100) for t ∈ 5:5:40]
plot!(collect(1:100), v100)

vs = vs[1:8]
yp = log.(v100 .- vs)
xs = collect(5:5:40)


module Rasterization
  using CG;
  using PyPlot;

  export FrameBuffer, clear!, colorPixel!, line!, triangle!, plot;
  type FrameBuffer
    nx::Int;
    ny::Int;
    data::Array{UInt8};
    FrameBuffer(nx::Int, ny::Int) = new(nx, ny, zeros(nx, ny));
  end

  function clear!(buffer::FrameBuffer)
    buffer.data = zeros(buffer.nx, buffer.ny);
  end

  function colorPixel!(buffer::FrameBuffer, x::Int, y::Int, color::UInt8)
    buffer.data[x, y] = color;
  end

  function plot(buffer::FrameBuffer)
    figure()
    gray()
  	clf()
    imshow(buffer.data', interpolation="nearest");
    show();
  end

  function swap!(a, b)
    tmp = a;
    a = b;
    b = tmp;
  end

  function line!(buffer::FrameBuffer, x0::Int, y0::Int, x1::Int, y1::Int, color::UInt8)
    # reassign input variables:
    if (x1 < x0)
      tmp = x0;
      x0 = x1;
      x1 = tmp;
    end

    if (y0 > y1)
      tmp = y0;
      y0 = y1;
      y1 = tmp;
    end

    m = (y1 - y0) / (x1 - x0);
    if m < 0

    elseif m >= 1
      tmp = y0;
      y0 = x0;
      x0 = tmp;

      tmp = y1;
      y1 = x1;
      x1 = tmp;
    end
    # Bresenham
    A = y1 - y0;
    B = -(x1 - x0);
    C = (x1 - x0) * y0 - (y1 - y0) * x0;

    F = function(x, y)
      return A*x + B*y + C
    end

    x = x0;
    y = y0;
    while x < x1
      colorPixel!(buffer, x, y, color);

      if (F(x + 1, y + 0.5) > 0)
        y = y + 1;
      end

      x = x + 1;
    end
  end

  function triangle!(s::FrameBuffer, x0::Int, y0::Int, x1::Int, y1::Int, x2::Int, y2::Int, ca::UInt8, cb::UInt8, cc::UInt8)
    # calculate bounding box
    xmax = min(max(x0, x1, x2), s.nx);
    ymax = min(max(y0, y1, y2), s.ny);
    xmin = max(min(x0, x1, x2), 1);
    ymin = max(min(y0, y1, y2), 1);

    for x = xmin:xmax
      for y = ymin:ymax
        gamma = ((y0 - y1) * x + (x1 - x0) * y + x0*y1 - x1*y0) / ((y0 - y1) * x2 + (x1 - x0) * y2 + x0*y1 - x1*y0);
        beta = ((y0 - y2) * x + (x2 - x0) * y + x0*y2 - x2*y0) / ((y0 - y2) * x1 + (x2 - x0) * y1 + x0*y2 - x2*y0);
        alpha = 1 - beta - gamma;

        if (0 < alpha && alpha < 1 && 0 < beta && beta < 1 && 0 < gamma && gamma < 1)
          color = UInt8(round(alpha * ca + beta * cb + gamma * cc));
          colorPixel!(s, x, y, color);
        end
      end
    end
  end
end


struct TaskStats{T}
    rwd::T
    dpl::T
end

function no_reward(prob::TaskStats)
    p, g = prob.rwd, prob.dpl
    return (1-p)*g / (1-(1-p)*(1-g))
end

function no_reward(prob::TaskStats, n)
    @assert n >= 0
    p, g = prob.rwd, prob.dpl
    return n == 0 ? 1.0 : (1-p)*(g+(1-g)*no_reward(prob, n-1))
end

function no_more_reward(prob::TaskStats, args...)
    g = prob.dpl
    g+(1-g)*no_reward(prob, args...)
end

function high_sequence_probability(prob::TaskStats, res::AbstractArray{Bool})
    p, g = prob.rwd, prob.dpl
    n = length(res)
    r = count(res)
    o = n-r
    return p^r*(1-p)^o*(1-g)^(n-1)
end

function sequence_probability(prob::TaskStats, res::AbstractArray{Bool})
    p, g = prob.rwd, prob.dpl
    ls = findlast(res)
    ls === nothing && return no_reward(prob)
    ph = high_sequence_probability(prob, res[1:ls])
    return ph*no_more_reward(prob, length(res)-ls)
end

one_reward(prob::TaskStats) = prob.rwd*no_more_reward(prob)


@testset "Basic" begin
  Qx, x = PolynomialRing(FlintQQ, "x")
  K, a = NumberField(x^2 - 5, "a")
  OK = maximal_order(K)
  G = matrix(K, 5, 5, [ 1, 0, 0, 0, 0,
                        0, 1, 0, 0, 0,
                        0, 0, 7, 0, 0,
                        0, 0, 0, 1//2*(a + 1), 0,
                        0, 0, 0, 0, 1//2*(7*a + 35) ])

  V = quadratic_space(K, G)
  @test hasse_invariant(V, 2 * OK) == -1
  @test hasse_invariant(V, 7 * OK) == -1
  @test hasse_invariant(V, real_places(K)[1]) == 1
  @test hasse_invariant(V, real_places(K)[2]) == 1

  @test witt_invariant(V, 2 * OK) == -1
  @test witt_invariant(V, 7 * OK) == -1
  @test witt_invariant(V, real_places(K)[1]) == -1
  @test witt_invariant(V, real_places(K)[2]) == -1

  plc = real_places(K)
  _, _, d, H, I = @inferred invariants(V)
  @test d == 147//2*a+245//2
  @test H == Dict(2 * OK => -1, 7 * OK => -1)
  @test length(I) == 2 && (I[1][2], I[2][2]) in [(1, 0), (0, 1)]
  @test is_isometric(V, V)
  @test !is_isometric(V, quadratic_space(K, 5))

  W = quadratic_space(K, G[1:2,1:2])
  f = hom(W, V, K[1 0 0 0 0; 0 1 0 0 0], check=true)
  v = [K(1), K(0)]
  @test f(v) == image(f, v)
  g = hom(W, W, K[0 1; 1 0], check=true)
  h = compose(g, f)
  @test h(v) == f(g(v))
  @test Hecke.is_isotropic(V, infinite_place(K,1))

end


using VLOptionsModeling
using Plots

# create an emprt contract set for now -
contract_set = Set{VLAbstractAsset}()
assetSymbol = "XYZ"
strikePrice = 50.0
underlyingPrice = 50.0
expirationDate = nothing
premimumValue = 0.0
numberOfContracts = 1
sense = :buy
contractMultiplier = 1.0
put_contract = VLPutOptionContract(assetSymbol, strikePrice, premimumValue; sense=sense, contractMultiplier=contractMultiplier)
push!(contract_set, put_contract)

# setup the lattice (Example 19.1/Fig. 19.3 Hull)
𝝙t = 30.42 / (365.0) # years
μ = log(1.0084) * (1 / 𝝙t)
σ = (1 / √𝝙t) * log(1.1224)
numberOfLevels = 6

# Create the lattice model -
latticeModel = VLBinomialLattice(μ, σ, 𝝙t, numberOfLevels)

# compute delta -
delta_value = 𝝙(contract_set, latticeModel, underlyingPrice) |> check


using MinFEM

mesh = import_mesh("../meshes/rounded.msh")

L = asmLaplacian(mesh)

f(x) = x[1]^2 + x[2]^2
s = evaluateMeshFunction(mesh, f, region=union(mesh.Boundaries[1003].Nodes,
                                               mesh.Boundaries[1004].Nodes))

s = asmBoundarySource(mesh, s, union(mesh.Boundaries[1003].Edges,
                                     mesh.Boundaries[1004].Edges))

pde = PDESystem(A=L, b=s, bc=zeros(mesh.nnodes), DI=union(mesh.Boundaries[1001].Nodes,
                                                          mesh.Boundaries[1002].Nodes))
solve(pde)

vtkfile = open_vtk_file(mesh, "boundary_source.vtu")
write_point_data(vtkfile, pde.state, "y")
write_point_data(vtkfile, s, "s")
save_vtk_file(vtkfile)


using JuMP
using MosekTools
import MathOptInterface
const MOI = MathOptInterface
const MOIU = MathOptInterface.Utilities

"""
Formulate the problem

      min.  sum_{j=0,...,(n-2)/2} s[j] + t[j] + p[j] + q[j]

      s.t.  (1/2, s[j], x[i]+10x[i+1]) \in Qr,
            (1/2, t[j], 5^{1/2}*(x[i+2]-x[i+3])) \in Qr
            (1/2, r[j], (x[i+1]-2x[i+2])) \in Qr
	    (1/2, u[j], 10^{1/4}*(x[i]-10x[i+3])) \in Qr
	    (1/2, p[j], r[j]) \in Qr
	    (1/2, q[j], u[j]) \in Qr,                       j=0,...,(n-2)/2, i = 2j

	    0.1 <= x[i] <= 1.1,                             i=0,2,...,n-2

"""
function chainsing1(M :: Model,n :: Int)
    m = (n-2) >> 1

    @variable(M,x[1:n])
    @variable(M,p[1:m])
    @variable(M,q[1:m])
    @variable(M,r[1:m])
    @variable(M,s[1:m])
    @variable(M,t[1:m])
    @variable(M,u[1:m])

    for j in 1:m
        i = ((j - 1) << 1) + 1

        # s[j] >= (x[i] + 10*x[i+1])^2
        @constraint(M, [ 0.5, s[j], x[i]+10*x[i+1] ]            in MOI.RotatedSecondOrderCone(3))
        # t[j] >= 5*(x[i+2] - x[i+3])^2
        @constraint(M, [ 0.5, t[j], sqrt(5.0)*(x[i+2] - x[i+3]) ] in MOI.RotatedSecondOrderCone(3))
        # r[j] >= (x[i+1] - 2*x[i+2])^2
        @constraint(M, [ 0.5, r[j], x[i+1] - 2.0*x[i+2] ]         in MOI.RotatedSecondOrderCone(3))
        # 1/10 * u[j] >= (x[i] - 10*x[i+3])^2
        @constraint(M, [ 0.5 * 10^(-1.0), u[j], x[i] - 10.0*x[i+3] ] in MOI.RotatedSecondOrderCone(3))
        # p[j] >= r[j]^2
        @constraint(M, [ 0.5, p[j], r[j] ]                      in MOI.RotatedSecondOrderCone(3))
        # q[j] >= u[j]^2
        @constraint(M, [ 0.5, q[j], u[j] ]                      in MOI.RotatedSecondOrderCone(3))
    end

    #0.1 <= x[i] <= 1.1, i=0,2,...,n-2
    for i in 1:2:n-1
        @constraint(M,x[i] <= 1.1)
        @constraint(M,x[i] >= 0.1)
    end

    @objective(M, Min, sum(s) + sum(t) + sum(p) + sum(q))
end

function chainsing2(M :: Model,n :: Int)
    m = (n-2) >> 1

    @variable(M,x[1:n])
    @variable(M,p[1:m])
    @variable(M,q[1:m])
    @variable(M,r[1:m])
    @variable(M,s)
    @variable(M,u[1:m])

    se = Array{Any}(2 * m + 2)

    #@constraint(M, [ 0.5, s,
    #                 @expression(M,    x[i]   + 10*x[i+1]  for i in 1:2:n-3 )...,
    #                 @expression(M, 5*(x[i+2] -    x[i+3]) for i in 1:2:n-3 )... ] in MOI.RotatedSecondOrderCone(2*m+2))
    @constraint(M, @expression(M,  [ 0.5; s; [ x[i]   + 10*x[i+1]  for i in 1:2:n-3 ] ; [ 5*(x[i+2] -    x[i+3]) for i in 1:2:n-3 ] ] ) in MOI.RotatedSecondOrderCone(2*m+2))
    for j in 1:m
        i = ((j - 1) << 1) + 1

        # r[j] >= (x[i+1] - 2*x[i+2])^2
        @constraint(M, [ 0.5, r[j], x[i+1] - 2*x[i+2] ]         in MOI.RotatedSecondOrderCone(3))
        # u[j] >= sqrt(10)*(x[i] - 10*x[i+3])^2
        @constraint(M, [ 0.5 * 10.0^(-1.0), u[j], x[i] - 10*x[i+3] ] in MOI.RotatedSecondOrderCone(3))
        # p[j] >= r[j]^2
        @constraint(M, [ 0.5, p[j], r[j] ]                      in MOI.RotatedSecondOrderCone(3))
        # q[j] >= u[j]^2
        @constraint(M, [ 0.5, q[j], u[j] ]                      in MOI.RotatedSecondOrderCone(3))
    end

    #0.1 <= x[i] <= 1.1, i=0,2,...,n-2
    for i in 1:2:n-1
        @constraint(M,x[i] <= 1.1)
        @constraint(M,x[i] >= 0.1)
    end

    @objective(M, Min, sum(s) + sum(p) + sum(q))
end

function chainsing3(M :: Model,n :: Int)
    m = (n-2) >> 1

    @variable(M,x[1:n])
    @variable(M,p[1:m])
    @variable(M,q[1:m])
    @variable(M,r[1:m])
    @variable(M,s)
    @variable(M,u[1:m])

    se = Array{Any}(2 * m + 2)

    @constraint(M, @expression(M, [ 0.5; s;
                                    [ x[i]   + 10*x[i+1]  for i in 1:2:n-3 ] ;
                                    [ 5*(x[i+2] -    x[i+3]) for i in 1:2:n-3 ] ;
                                    p ; q ]) in MOI.RotatedSecondOrderCone(4*m+2))
    for j in 1:m
        i = ((j - 1) << 1) + 1

        # r[j] >= (x[i+1] - 2*x[i+2])^2
        @constraint(M, [ 0.5, r[j], x[i+1] - 2*x[i+2] ]         in MOI.RotatedSecondOrderCone(3))
        # u[j] >= sqrt(10)*(x[i] - 10*x[i+3])^2
        @constraint(M, [ 0.5 * 10.0^(-1.0), u[j], x[i] - 10*x[i+3] ] in MOI.RotatedSecondOrderCone(3))
    end

    #0.1 <= x[i] <= 1.1, i=0,2,...,n-2
    for i in 1:2:n-1
        @constraint(M,x[i] <= 1.1)
        @constraint(M,x[i] >= 0.1)
    end

    @objective(M, Min, s)
end

function main(argv :: Vector{String})
    n = 8192
    backend = :generic
    method = 1
    timefile = nothing
    taskfile = nothing
    opt = false

    for a in argv
        m = match(r"--([^=]+)(?:=(.*))?",a)
        if m == nothing
            n = parse(Int,argv[1])
        else
            key = m.captures[1]
            val = m.captures[2]

            if key == "optimize"
                opt = true
            elseif key == "method"
                method = parse(Int,val)
            elseif key == "timefile"
                timefile = val
            elseif key == "out"
                taskfile = val
            elseif key == "backend"
                if val     == "generic" val == nothing
                    backend = :generic
                elseif val == "mock"
                    backend = :mock
                elseif val == "mosek"
                    backend = :mosek
                end
            elseif key == "help"
                println("Usage: chainsing n [options]")
                println("  n The problem scale, the default being 8192. Must be even.")
                println("Options:")
                println("\t--method=[1|2|3] Which formulation to use")
                println("\t--timefile=filename Which formulation to use")
                println("\t--backend=[generic|mosek|mock] Which backend to use.")
                println("\t\tgeneric Use generic backend, then copy to MOSEK optimizer")
                println("\t\tmosek   Use mosek as backend")
                println("\t\tmock    Use generic backend, then copy to JuMP mock optimizer")
                println("\t--out=filename Write task to this file")
                println("\t--optimize Call optimizer")
                return
            end
        end
    end

    #println("Chainsing")
    #println("\tn        : $n")
    #println("\tmethod   : $method")
    #println("\ttimefile : $timefile")
    #println("\ttaskfile : $taskfile")
    #println("\toptimize : $opt")
    #println("\tbackend  : $backend")

    T = 0.0

    m =
        if  backend == :generic
            solver = Mosek.Optimizer()
            Model( optimizer = solver)
        elseif backend == :mosek
            solver = Mosek.Optimizer()
            Model( mode = JuMP.Direct, backend = solver)
        elseif backend == :mock
            solver = MOIU.MockOptimizer(JuMP.JuMPMOIModel{Float64}())
            Model( optimizer = solver)
        end

    if     backend == :generic
        println("Use: Generic backend, then copy to Mosek")
    elseif backend == :mosek
        println("Use: Mosek directly as backend")
    elseif backend == :mock
        println("Use: Generic backend, then copy to mock solver")
    end

    T0 = time()
    if method == 1
        chainsing1(m,n)
    elseif method == 2
        chainsing2(m,n)
    elseif method == 3
        chainsing3(m,n)
    end

    if backend != :mosek
        MOIU.attachoptimizer!(m)
    end
    T1 = time()
    T = T1-T0

    @printf("Model build time: %.2f secs\n",T)
    if timefile != nothing
        open(timefile, "w") do f
            @printf(f,"%.2f", T)
        end
    end

end

main(ARGS)


export graph_rational
"""
     (graph, cref) = graph_rational(den_coeffs, num_coeffs, poly_gen=graph_ps)
     (graph, cref) = graph_rational(
        den_graph,
        num_graph;
        den_cref = Vector{Tuple{Symbol,Int}}(),
        num_cref = Vector{Tuple{Symbol,Int}}(),
    )

Generates the graph for the rational approximation

    r(A)=q(A)^{-1}p(A)

where p(A) and q(A) are polynomials defined by the coefficients `den_coeffs` and
`num_coeffs`, and generated by the function `poly_gen`, which is called as
`(graph,cref)=poly_gen(coeffs)`, see, e.g., [`graph_monomial`](@ref) and
[`graph_ps`](@ref).

The alternative call-signature involves the graphs for p and q directly, as
`den_graph` and `num_graph`. The corresponding `den_cref` and `num_cref` can
also be passed to be modified accordingly, otherwise the return value `cref` is
empty for this call.
"""
function graph_rational(den_coeffs, num_coeffs, poly_gen)
    (den_graph, den_cref) = poly_gen(den_coeffs)
    (num_graph, num_cref) = poly_gen(num_coeffs)

    return graph_rational(
        den_graph,
        num_graph;
        den_cref = den_cref,
        num_cref = num_cref,
    )
end

function graph_rational(
    den_graph,
    num_graph;
    den_cref = Vector{Tuple{Symbol,Int}}(),
    num_cref = Vector{Tuple{Symbol,Int}}(),
)
    graph = merge_graphs(
        den_graph,
        num_graph,
        prefix1 = "de",
        prefix2 = "nu",
        skip_basic1 = true,
        skip_basic2 = true,
        cref1 = den_cref,
        cref2 = num_cref,
    )
    add_ldiv!(graph, :pade, graph.outputs[2], graph.outputs[1])
    cref = vcat(den_cref, num_cref)
    empty!(graph.outputs)
    compress_graph_redundant!(graph)
    add_output!(graph, :pade)

    return (graph, cref)
end