using VortexDistributions

# simple test field
Nx = 400; Ny = 400
Lx = 200; Ly = Lx
x = LinRange(-Lx/2,Lx/2, Nx+1)[1:end-1]; y = LinRange(-Ly/2,Ly/2, Ny+1)[1:end-1];
psi0 = one.(x*y') |> complex
psi = Torus(copy(psi0),x,y)
xp = 1.133
yp = 2.787
vp = PointVortex(xp,yp,1)

## Test differnt calls to ScalarVortex()
sp = ScalarVortex(vp)
sp1 = ScalarVortex(1.0, vp)
@test sp1[1].core === sp.core
@test sp1[1].vort === sp.vort

sp2 = ScalarVortex(0.8, vp)
@test !(sp2[1].core===sp.core)
@test sp2[1].vort === sp.vort

sp3 = ScalarVortex([1.0, 1.0], [vp])
@test sp3[1].core === sp3[2].core
@test sp3[1].vort === sp3[2].vort

sp4 = ScalarVortex([1.0, 0.8], [vp])
@test !(sp4[1].core === sp4[2].core)
@test (sp4[1].vort === sp4[2].vort)

sp5 = ScalarVortex([vp])
@test sp5[1].core === sp1[1].core
@test sp5[1].vort === sp1[1].vort

rs1 = rand_scalarvortex()
@test typeof(rs1) <: ScalarVortex{Exact}

rs2 = rand_scalarvortex(10)
@test length(rs2) == 10
@test typeof(rs2[1]) <: ScalarVortex{Exact}

rs3 = rand_scalarvortex(10, psi)
@test typeof(rs3) == typeof(rs2)
@test length(rs3) == 10

rs4 = rand_scalarvortex(psi)
@test typeof(rs4) <: ScalarVortex{Exact}

rv1 = rand_vortex()
@test typeof(rv1) <: ScalarVortex{Exact}

rv2 = rand_vortex(10)
@test length(rv2) == 10
@test typeof(rv2[1]) <: ScalarVortex{Exact}

rv3 = rand_scalarvortex(10, psi)
@test typeof(rv3) == typeof(rv2)
@test length(rv3) == 10


#RSDeltaSigma: simulateDSM() algorithm
#-------------------------------------------------------------------------------

#==simulateDSM()
===============================================================================#
#Main algorithm for simulateDSM
function _simulateDSM(u, nq, nlev, x0, order, A, B, C, D1, savestate::Bool, trackmax::Bool)
	if isnan(x0)
		x0 = zeros(order,1)
	end

	N = length(u)
	v = zeros(nq,N)
	y = zeros(nq,N)

	xn = nothing; xmax = nothing
	if savestate #Need to store the state information
		xn = zeros(order,N)
	end
	if trackmax #Need to keep track of the state maxima
		xmax = abs.(x0)
	end

	for i=1:N
		y[:,i] = C*x0 .+ D1 .* u[:,i]
		v[:,i] = ds_quantize_DSM(y[:,i], nlev)
		x0 = A * x0 .+ B * [u[:,i]; v[:,i]]
		if savestate #Save the next state
			xn[:,i] = x0
		end
		if trackmax #Keep track of the state maxima
			xmax = max.(abs.(x0), xmax)
		end
	end

	result = (v=v, y=y)
	if savestate
		result = merge(result, (xn=xn,))
	end
	if trackmax
		result = merge(result, (xmax=xmax,))
	end

	return result
end

function simulateDSM(u, ntf::ZPKData; nlev=2, x0=NaN, savestate::Bool=false, trackmax::Bool=false)
	u = conv2seriesmatrix2D(u)
	nq = length(nlev) #Number of quantizers
	order = length(ntf.z)

	_ss = _zp2ss(ntf.p,ntf.z,-1) #realization of 1/H
	A, B2, C, D2 = _ss.A, _ss.B, _ss.C, _ss.D
	#Transform the realization so that C = [1 0 0 ...]
	Sinv = orth([C' eye(order)])/norm(C); S = inv(Sinv)
	C = C*Sinv
	if C[1]<0
		S = -S
		Sinv = -Sinv
	end
	A = S*A*Sinv; B2 = S*B2; C = [1 zeros(1,order-1)]; #C=C*Sinv
	D2 = 0
	#!!!! Assume stf=1
	B1 = -B2
	D1 = 1
	B = [B1 B2]

	return _simulateDSM(u, nq, nlev, x0, order, A, B, C, D1, savestate, trackmax)
end

function simulateDSM(u, ABCD::Array; nlev=2, x0=NaN, savestate::Bool=false, trackmax::Bool=false)
	u = conv2seriesmatrix2D(u)
	nu, nq, order = get_nu_nq_order(u, ABCD, nlev)
	A = ABCD[1:order, 1:order]
	B = ABCD[1:order, order+1:order+nu+nq]
	C = ABCD[order+1:order+nq, 1:order]
	D1= ABCD[order+1:order+nq, order+1:order+nu]

	return _simulateDSM(u, nq, nlev, x0, order, A, B, C, D1, savestate, trackmax)
end

"""`simresult = simulateDSM(u, [NTF|ABCD]; nlev=2, x0=NaN, savestate=false, trackmax=false))`

Compute the output of a general ΔΣ modulator.

# Inputs
 - `u[]`: Input signal (Multiple inputs are implied by the number of rows in `u`)
 - `[NTF|ABCD]`: NTF or ABCD (state-space)
 - `nlev`: Quantizer levels (Multiple quantizers are implied by making `nlev` an array)
 - `x0[]`: Initial state x0 (default zero).

The NTF is zpk object. (The STF is assumed to be 1.)
The structure that is simulated is the block-diagional structure used by
zp2ss.m.

# Returns `simresult::NamedTuple`:
 - .v: Output signal of ΔΣ modulator.
 - .xn: Internal state of ΔΣ modulator.
 - .xmax: Maximum internal state of ΔΣ modulator.
 - .y:
""" simulateDSM

@warn("Implements .m version of simulateDSM(). C code might be more efficient/validated.")

#==simulateQDSM
===============================================================================#
simulateQDSM(args...; kwargs...) = throw("simulateQDSM() not implemented")

#Last line


for N in [Float64, Rational{Int}]
    # ====================================
    # Concrete convex hull for point sets
    # ====================================

    # corner cases in dimension 1
    @test convex_hull([Vector{N}(undef, 0)]) == [Vector{N}(undef, 0)]
    @test convex_hull([[N(0)]]) == [[N(0)]]
    @test ispermutation(convex_hull([N[2], N[1]]), [N[2], N[1]])
    @test convex_hull([[N(2)], [N(2)]]) == [[N(2)]]

    # corner cases in dimension 2
    @test convex_hull([[N(0), N(0)]]) == [[N(0), N(0)]]
    @test convex_hull([[N(1), N(0)], [N(0), N(1)]]) == [[N(1), N(0)], [N(0), N(1)]]
    @test convex_hull([[N(1), N(0)], [N(1), N(0)]]) == [[N(1), N(0)]]

    # test corner cases with one and two vectors (see #876)
    p1 = [1., 2.]
    p2 = [1., 3.]
    @test convex_hull([p1]) == [p1]
    @test ispermutation(convex_hull([p1, p2]), [p1, p2])

    # corner cases in higher dimension
    @test convex_hull([[N(0), N(0), N(0)]]) == [[N(0), N(0), N(0)]]
    # there is no sorting in higher dim
    @test convex_hull([[N(1), N(0), N(0)], [N(0), N(1), N(0)]]) == [[N(1), N(0), N(0)], [N(0), N(1), N(0)]]

    # dimension 1
    points_1D = [[N(2)], [N(1)], [N(1/2)], [N(-5)]]
    @test convex_hull(points_1D) == [[N(-5)], [N(2)]]
    convex_hull!(points_1D) # check in-place version
    @test points_1D == [[N(-5)], [N(2)]]

    # dimension 2
    points_2D = [[N(1), N(0)], [N(1), N(1)], [N(0), N(1)], [N(-1), N(-1)], [N(1/2), N(1/2)]]
    @test convex_hull(points_2D) == [[N(-1), N(-1)], [N(1), N(0)], [N(1), N(1)], [N(0), N(1)]]
    convex_hull!(points_2D) # check in-place version
    @test points_2D == [[N(-1), N(-1)], [N(1), N(0)], [N(1), N(1)], [N(0), N(1)]]

    # three vertex case in 2 dimensions
    ccw_points = [N[1, 1], N[-1, 1], N[-1, 0]]
    ccw_p = convex_hull!(ccw_points)
    ccw_expr = [N[1, 1], N[-1, 1], N[-1, 0]]
    @test is_cyclic_permutation(ccw_p, ccw_expr) # points sorted in a counter-clockwise fashion
    cw_points = [N[-1, 1], N[1, 1], N[-1, 0]]
    cw_p = convex_hull!(cw_points)
    cw_expr = [N[1, 1], N[-1, 1], N[-1, 0]]
    @test is_cyclic_permutation(cw_p, cw_expr) # points sorted in clockwise fashion
    @test ispermutation(convex_hull!([N[1, 1], N[2, 2], N[3, 3]]), [N[1, 1], N[3, 3]]) # points aligned
    @test ispermutation(convex_hull!([N[0, 1], N[0, 2], N[0, 3]]), [N[0, 1], N[0, 3]]) # three points on a vertical line
    @test convex_hull!([N[0, 1], N[0, 1], N[0, 1]]) == [N[0, 1]] # three equal points

    # four vertex case in 2 dimentions
    A = N[1, 0]
    B = N[1, 1]
    C = N[-1, 1]
    D = N[-1, 0]
    expr = [A, B, C, D]
    points = [A, B, C, D]
    @test is_cyclic_permutation(convex_hull!(points), expr) # ABCD
    points = [A, D, C, B]
    @test is_cyclic_permutation(convex_hull!(points), expr) # ADCB
    points = [A, B, D, C]
    @test is_cyclic_permutation(convex_hull!(points), expr) # ABDC
    points = [A, D, B, C]
    @test is_cyclic_permutation(convex_hull!(points), expr) # ADBC
    points = [D, A, C, B]
    @test is_cyclic_permutation(convex_hull!(points), expr) # DACB
    points = [A, C, D, B]
    @test is_cyclic_permutation(convex_hull!(points), expr) # ACDB
    A = N[0, 1]
    B = N[-1, -1]
    C = N[1, -1]
    D = N[0, 0]
    expr = [A, B, C]
    points = [A, B, C, D]
    @test is_cyclic_permutation(convex_hull!(points), expr) # ABC
    points = [A, C, B, D]
    @test is_cyclic_permutation(convex_hull!(points), expr) # CBA
    points = [D, B, C, A]
    @test is_cyclic_permutation(convex_hull!(points), expr) # BCD
    points = [D, C, B, A]
    @test is_cyclic_permutation(convex_hull!(points), expr) # DCB
    points = [B, D, C, A]
    @test is_cyclic_permutation(convex_hull!(points), expr) # ACD
    points = [C, D, B, A]
    @test is_cyclic_permutation(convex_hull!(points), expr) # CAD
    points = [B, C, D, A]
    @test is_cyclic_permutation(convex_hull!(points), expr) # ABD
    points = [C, B, D, A]
    @test is_cyclic_permutation(convex_hull!(points), expr) # ADB
    @test ispermutation(convex_hull!([N[1, 1], N[2, 2], N[3, 3], N[4, 4]]), [N[1, 1], N[4, 4]]) # points aligned
    @test  is_cyclic_permutation(convex_hull([[1., 1], [0., 0], [0., 1], [0., 2]]), [[1, 1.], [0., 2], [0., 0]]) # test that issue #1661 is fixed

    # five-vertices case in 2D
    points = [N[1//10, 3//10], N[1//5, 1//10], N[3//10, 7//25], N[2//5, 3//5],
              N[9//10, 1//5]]
    sorted = [N[1//10, 3//10], N[1//5, 1//10], N[9//10, 1//5], N[2//5, 3//5]]
    for algorithm in ["monotone_chain", "monotone_chain_sorted"]
        points_copy = copy(points)
        @test is_cyclic_permutation(
            convex_hull!(points_copy, algorithm=algorithm), sorted)
    end
    @test_throws ErrorException convex_hull!(points, algorithm="")

    # single-vertex case in 2D (five vertices to test the general algorithm)
    p = N[0, 0]
    @test convex_hull([p, p, p, p, p]) == [p]

    # higher dimension
    if test_suite_polyhedra && N != Float32 # no backend supporting Float32
        points_3D = [[N(1), N(0), N(4)], [N(1), N(1), N(5)], [N(0), N(1), N(6)],
                     [N(-1), N(-1), N(-7)], [N(1/2), N(1/2), N(-8)],
                     [N(0), N(0), N(0)], [N(1), N(2), N(3)]]
        sorted = [[N(1), N(0), N(4)], [N(1), N(1), N(5)], [N(0), N(1), N(6)],
            [N(-1), N(-1), N(-7)], [N(1/2), N(1/2), N(-8)], [N(1), N(2), N(3)]]
        @test ispermutation(convex_hull(points_3D), sorted)
        convex_hull!(points_3D) # check in-place version
        @test ispermutation(points_3D, sorted)
    end

    # ============================
    # Binary concrete convex hull
    # ============================

    V1 = VPolygon([[N(1), N(0)], [N(1), N(1)], [N(0), N(1)]])
    V2 = VPolygon([[N(-1), N(-1)], [N(1/2), N(1/2)]])
    ch = convex_hull(V1, V2)
    @test ispermutation(vertices_list(ch), [[N(-1), N(-1)], [N(1), N(0)], [N(1), N(1)], [N(0), N(1)]])

    if test_suite_polyhedra && N != Float32 # no backend supporting Float32
        V1 = VPolytope([[N(1), N(0), N(4)], [N(1), N(1), N(5)], [N(0), N(1), N(6)]])
        V2 = VPolytope([[N(-1), N(-1), N(-7)], [N(1/2), N(1/2), N(-8)], [N(0), N(0), N(0)],
                       [N(1), N(2), N(3)]])
        ch = convex_hull(V1, V2)
        @test ch isa VPolytope
        @test ispermutation(vertices_list(ch), [[N(1), N(0), N(4)], [N(1), N(1), N(5)],
                                    [N(0), N(1), N(6)], [N(-1), N(-1), N(-7)],
                                    [N(1/2), N(1/2), N(-8)], [N(1), N(2), N(3)]])
    end

    # general LazySets
    for (n, T) in [(1, Interval), (2, VPolygon), (3, VPolytope)]
        if n > 2 && !test_suite_polyhedra  # dimensions > 2 require Polyhedra
            continue
        end
        X = BallInf(zeros(N, n), N(1))
        Y = Ball1(ones(N, n), N(3//2))
        ch = convex_hull(X, Y)
        @test ch isa T{N}
    end
    S = Singleton(N[1])
    ch = convex_hull(S, S)
    @test ch isa Singleton{N} && element(ch) == element(S)
    U = UnionSet(Interval(N(1), N(2)), Interval(N(3), N(4)))
    @test convex_hull(U) == Interval(N(1), N(4))

    # UnionSetArray
    V1 = VPolytope([N[0, 0], N[1, 0], N[0, 1]])
    V2 = VPolytope([N[1, 1], N[1, 0], N[0, 1]])
    U = UnionSetArray([V1, V2]);
    @test ispermutation(convex_hull(U), [N[0, 0], N[1, 0], N[0, 1], N[1, 1]])
end


# An implementation of CGNE for the solution of the consistent
# (under/over-determined or square) linear system
#
#  Ax = b.
#
# The method seeks to solve the minimum-norm problem
#
#  min ‖x‖²  s.t. Ax = b,
#
# and is equivalent to applying the conjugate gradient method
# to the linear system
#
#  AA'y = b.
#
# This method is also known as Craig's method, CGME, and other
# names, and is described in
#
# J. E. Craig. The N-step iteration procedures.
# Journal of Mathematics and Physics, 34(1):64--73, 1955.
#
# which is based on Craig's thesis from MIT:
#
# J. E. Craig. Iterations Procedures for Simultaneous Equations.
# Ph.D. Thesis, Department of Electrical Engineering, MIT, 1954.
#
# Dominique Orban, <dominique.orban@gerad.ca>
# Montréal, QC, April 2015.

export cgne

"""Solve the consistent linear system

  Ax + √λs = b

using the Conjugate Gradient (CG) method, where λ ≥ 0 is a regularization
parameter. This method is equivalent to applying CG to the normal equations
of the second kind

  (AA' + λI) y = b

but is more stable. When λ = 0, this method solves the minimum-norm problem

  min ‖x‖₂  s.t. Ax = b.

When λ > 0, it solves the problem

  min ‖(x,s)‖₂  s.t. Ax + √λs = b.

CGNE produces monotonic errors ‖x-x*‖₂ but not residuals ‖r‖₂.
It is formally equivalent to CRAIG, though can be slightly less accurate,
but simpler to implement. Only the x-part of the solution is returned.

A preconditioner M may be provided in the form of a linear operator.
"""
function cgne(A :: AbstractLinearOperator, b :: AbstractVector{T};
              M :: AbstractLinearOperator=opEye(size(A,1)), λ :: Float64=0.0,
              atol :: Float64=1.0e-8, rtol :: Float64=1.0e-6, itmax :: Int=0,
              verbose :: Bool=false) where T <: Number

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

  x = zeros(T, n);
  r = copy(b)
  z = M * r
  rNorm = @knrm2(m, r)   # Marginally faster than norm(r)
  rNorm ≈ 0 && return x, SimpleStats(true, false, [rNorm], [], "x = 0 is a zero-residual solution");
  λ > 0 && (s = copy(r));

  # The following vector copy takes care of the case where A is a LinearOperator
  # with preallocation, so as to avoid overwriting vectors used later. In other
  # case, this should only add minimum overhead.
  p = copy(A' * z);

  # Use ‖p‖ to detect inconsistent system.
  # An inconsistent system will necessarily have AA' singular.
  # Because CGNE is equivalent to CG applied to AA'y = b, there will be a
  # conjugate direction u such that u'AA'u = 0, i.e., A'u = 0. In this
  # implementation, p is a substitute for A'u.
  pNorm = @knrm2(n, p)

  γ = @kdot(m, r, z)  # Faster than γ = dot(r, z);
  iter = 0;
  itmax == 0 && (itmax = m + n);

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

  status = "unknown";
  solved = rNorm <= ɛ_c;
  inconsistent = (rNorm > 1.0e+2 * ɛ_c) && (pNorm <= ɛ_i);
  tired = iter >= itmax;

  while ! (solved || inconsistent || tired)
    q = A * p;
    λ > 0 && @kaxpy!(m, λ, s, q)
    δ = @kdot(n, p, p)   # Faster than dot(p, p);
    λ > 0 && (δ += λ * @kdot(m, s, s))
    α = γ / δ;
    @kaxpy!(n,  α, p, x)     # Faster than x = x + α * p;
    @kaxpy!(m, -α, q, r)     # Faster than r = r - α * q;
    z = M * r
    γ_next = @kdot(m, r, z)  # Faster than γ_next = dot(r, z);
    β = γ_next / γ;
    @kscal!(n, β, p)
    @kaxpy!(n, 1.0, A' * z, p)   # Faster than p = A' * z + β * p;
    pNorm = @knrm2(n, p)
    if λ > 0
      @kscal!(m, β, s)
      @kaxpy!(m, 1.0, r, s)   # s = r + β * s;
    end
    γ = γ_next;
    rNorm = sqrt(γ_next);
    push!(rNorms, rNorm);
    iter = iter + 1;
    verbose && @printf("%5d  %8.2e\n", 1 + 2 * iter, rNorm);
    solved = rNorm <= ɛ_c;
    inconsistent = (rNorm > 1.0e+2 * ɛ_c) && (pNorm <= ɛ_i);
    tired = iter >= itmax;
  end

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


"""
    gradient!{T <: AbstractFloat}(
        output::AbstractArray,
        f::Function,
        x::AbstractArray{T},
        buffer::AbstractArray{T},
        ::ForwardMode,
    )

# Description

Evaluate the gradient of `f` at `x` using forward-mode finite differences.
Store the results into `output`. Work with a user-provided `buffer` to ensure
that we can work without copies, but also without mutating `x`. In Rust jargon,
we take ownership of both `output` and `buffer`, but do not require ownership
of `x`. We just need read-only access to `x`.

# Arguments

* `output::AbstractArray`: An array that will be mutated to contain the
    gradient of `f` at `x`.
* `f::Function`: The function to be differentiated.
* `x::AbstractArray{T}`: The value of `x` at which to evaluate the gradient of
    `f`.
* `buffer::AbstractArray{T}`: A buffer that is equivalent to `similar(x)`. Used
    as a temporary copy of `x` that can be mutated safely during the execution
    of the function.
* `::ForwardMode`: An instance of the `ForwardMode` type.

# Returns

* `nothing::Void`: This function is called for its side effects.

# Examples

```julia
import FiniteDiff: gradient!, ForwardMode
x = [0.0, 0.0]
output, buffer = Array(Float64, 2), Array(Float64, 2)
gradient!(
    output,
    x -> sin(x[1]) + 2 * sin(x[2]),
    x,
    buffer,
    ForwardMode(),
)
```
"""
function gradient!{T <: AbstractFloat}(
    output::AbstractArray,
    f::Function,
    x::AbstractArray{T},
    buffer::AbstractArray{T},
    ::ForwardMode,
)::Void
    # Validate that all inputs have the same number of dimensions.
    n = length(x)
    if length(output) != n || length(buffer) != n
        throw(DomainError())
    end

    # Cache the value of f(x) to avoid multiple calls to f, which is pure.
    f_x = f(x)

    # Copy x into the user-supplied buffer.
    copy!(buffer, x)

    # Iterate over the dimensions of the input.
    for i in 1:n
        # Make a copy of the true value of x[i].
        x_i = x[i]

        # Temporarily change buffer[i] to a new value.
        ϵ = step_size(ForwardMode(), x_i)
        buffer[i] = x_i + ϵ

        # Evaluate f at the new value of x stored in the buffer.
        f_xp = f(buffer)

        # Restore the true value of x[i] in the buffer.
        buffer[i] = x_i

        # Store the i-th component of the gradient into the output array.
        # TODO: Remove this
        # @printf("%d\t%s\t%s\t%s\t%s\n", i, f_xp, f_x, f_xp - f_x, ϵ)
        output[i] = (f_xp - f_x) / ϵ
    end

    # Return nothing. The result is already stored in output.
    return
end

"""
    gradient!{T <: AbstractFloat}(
        output::AbstractArray,
        f::Function,
        x::AbstractArray{T},
        buffer::AbstractArray{T},
        ::BackwardMode,
    )

# Description

Evaluate the gradient of `f` at `x` using backward-mode finite differences.
Store the results into `output`. Work with a user-provided `buffer` to ensure
that we can work without copies, but also without mutating `x`. In Rust jargon,
we take ownership of both `output` and `buffer`, but do not require ownership
of `x`. We just need read-only access to `x`.

# Arguments

* `output::AbstractArray`: An array that will be mutated to contain the
    gradient of `f` at `x`.
* `f::Function`: The function to be differentiated.
* `x::AbstractArray{T}`: The value of `x` at which to evaluate the gradient of
    `f`.
* `buffer::AbstractArray{T}`: A buffer that is equivalent to `similar(x)`. Used
    as a temporary copy of `x` that can be mutated safely during the execution
    of the function.
* `::BackwardMode`: An instance of the `BackwardMode` type.

# Returns

* `nothing::Void`: This function is called for its side effects.

# Examples

```julia
import FiniteDiff: gradient!, BackwardMode
x = [0.0, 0.0]
output, buffer = Array(Float64, 2), Array(Float64, 2)
gradient!(
    output,
    x -> sin(x[1]) + 2 * sin(x[2]),
    x,
    buffer,
    BackwardMode(),
)
```
"""
function gradient!{T <: AbstractFloat}(
    output::AbstractArray,
    f::Function,
    x::AbstractArray{T},
    buffer::AbstractArray{T},
    ::BackwardMode,
)::Void
    # Validate that all inputs have the same number of dimensions.
    n = length(x)
    if length(output) != n || length(buffer) != n
        throw(DomainError())
    end

    # Cache the value of f(x) to avoid multiple calls to f, which is pure.
    f_x = f(x)

    # Copy x into the user-supplied buffer.
    copy!(buffer, x)

    # Iterate over dimensions of the input.
    for i in 1:n
        # Make a copy of the true value of x[i].
        x_i = buffer[i]

        # Temporarily change buffer[i] to a new value.
        ϵ = step_size(BackwardMode(), x_i)
        buffer[i] = x_i - ϵ

        # Evaluate f at the new value of x stored in the buffer.
        f_xm = f(buffer)

        # Restore the true value of x[i] in the buffer.
        buffer[i] = x_i

        # Store the i-th component of the gradient into the output array.
        output[i] = (f_x - f_xm) / ϵ
    end

    # Return nothing. The result is already stored in output.
    return
end

"""
    gradient!{T <: AbstractFloat}(
        output::AbstractArray,
        f::Function,
        x::AbstractArray{T},
        buffer::AbstractArray{T},
        ::CentralMode,
    )

# Description

Evaluate the gradient of `f` at `x` using central-mode finite differences.
Store the results into `output`. Work with a user-provided `buffer` to ensure
that we can work without copies, but also without mutating `x`. In Rust jargon,
we take ownership of both `output` and `buffer`, but do not require ownership
of `x`. We just need read-only access to `x`.

# Arguments

* `output::AbstractArray`: An array that will be mutated to contain the
    gradient of `f` at `x`.
* `f::Function`: The function to be differentiated.
* `x::AbstractArray{T}`: The value of `x` at which to evaluate the gradient of
    `f`.
* `buffer::AbstractArray{T}`: A buffer that is equivalent to `similar(x)`. Used
    as a temporary copy of `x` that can be mutated safely during the execution
    of the function.
* `::CentralMode`: An instance of the `CentralMode` type.

# Returns

* `nothing::Void`: This function is called for its side effects.

# Examples

```julia
import FiniteDiff: gradient!, CentralMode
x = [0.0, 0.0]
output, buffer = Array(Float64, 2), Array(Float64, 2)
gradient!(
    output,
    x -> sin(x[1]) + 2 * sin(x[2]),
    x,
    buffer,
    CentralMode(),
)
```
"""
function gradient!{T <: AbstractFloat}(
    output::AbstractArray,
    f::Function,
    x::AbstractArray{T},
    buffer::AbstractArray{T},
    ::CentralMode,
)::Void
    # Validate that all inputs have the same number of dimensions.
    n = length(x)
    if length(output) != n || length(buffer) != n
        throw(DomainError())
    end

    # Cache the value of f(x) to avoid multiple calls to f, which is pure.
    f_x = f(x)

    # Copy x into the user-supplied buffer.
    copy!(buffer, x)

    # Iterate over dimensions of the input.
    for i in 1:n
        # Make a copy of the true value of x[i].
        x_i = buffer[i]

        # Temporarily change buffer[i] to a new value.
        ϵ = step_size(CentralMode(), x_i)
        buffer[i] = x_i + ϵ

        # Evaluate f at the new value of x stored in the buffer.
        f_xp = f(buffer)

        # Temporarily change buffer[i] to a new value.
        buffer[i] = x_i - ϵ

        # Evaluate f at the new value of x stored in the buffer.
        f_xm = f(buffer)

        # Restore the true value of x[i] in the buffer.
        buffer[i] = x_i

        # Store the i-th component of the gradient into the output array.
        # TODO: Remove this
        # @printf("%d\t%s\t%s\t%s\t%s\n", i, f_xp, f_xm, f_xp - f_xm, ϵ)
        output[i] = (f_xp - f_xm) / (ϵ + ϵ)
    end

    # Return nothing. The result is already stored in output.
    return
end

"""
    gradient!{T <: AbstractFloat}(
        output::AbstractArray,
        f::Function,
        x::AbstractArray{T},
        buffer::AbstractArray{Complex{T}},
        ::ComplexMode,
    )

# Description

Evaluate the gradient of `f` at `x` using complex-mode finite differences.
Store the results into `output`. Work with a user-provided `buffer` to ensure
that we can work without copies, but also without mutating `x`. In Rust jargon,
we take ownership of both `output` and `buffer`, but do not require ownership
of `x`. We just need read-only access to `x`.

**NOTE**: The buffer must have complex elements to allow the computation of
complex-mode finite differences.

**NOTE**: This mode of finite differences will work correctly only when:

* `f` supports complex inputs.
* `f` is an analytic function in the complex analysis sense of the word.

# Arguments

* `output::AbstractArray`: An array that will be mutated to contain the
    gradient of `f` at `x`.
* `f::Function`: The function to be differentiated.
* `x::AbstractArray{T}`: The value of `x` at which to evaluate the gradient of
    `f`.
* `buffer::AbstractArray{Complex{T}}`: A buffer that is equivalent to
    `similar(x, Complex{eltype(x)})`. Used as a temporary copy of `x` that can
    be mutated safely during the execution of the function.
* `::ComplexMode`: An instance of the `ComplexMode` type.

# Returns

* `nothing::Void`: This function is called for its side effects.

# Examples

```julia
import FiniteDiff: gradient!, ComplexMode
x = [0.0, 0.0]
output, buffer = Array(Float64, 2), Array(Complex{Float64}, 2)
gradient!(
    output,
    x -> sin(x[1]) + 2 * sin(x[2]),
    x,
    buffer,
    ComplexMode(),
)
```
"""
function gradient!{T <: AbstractFloat}(
    output::AbstractArray,
    f::Function,
    x::AbstractArray{T},
    buffer::AbstractArray{Complex{T}},
    ::ComplexMode,
)::Void
    # Validate that all inputs have the same number of dimensions.
    n = length(x)
    if length(output) != n || length(buffer) != n
        throw(DomainError())
    end

    # Copy x into the user-supplied buffer.
    copy!(buffer, x)

    # Iterate over dimensions of the input.
    for i in 1:n
        # Make a copy of the true value of x[i].
        x_i = x[i]

        # Temporarily change buffer[i] to a new value.
        ϵ = step_size(ComplexMode(), x_i)
        buffer[i] = x_i + ϵ * im

        # Evaluate f at the new value of x stored in the buffer.
        f_xim = f(buffer)

        # Restore the true value of x[i] in the buffer.
        buffer[i] = x_i

        # Store the i-th component of the gradient into the output array.
        output[i] = imag(f_xim) / ϵ
    end

    # Return nothing. The result is already stored in output.
    return
end

"""
    gradient!{T <: AbstractFloat}(
        output::AbstractArray,
        f::Function,
        x::AbstractArray{T},
        buffer::AbstractArray{T},
    )

# Description

Evaluate the gradient of `f` at `x` using finite differences. Defaults to
central mode finite differences. See the documentation for that mode for
more details.

# Arguments

* `output::AbstractArray`: An array that will be mutated to contain the
    gradient of `f` at `x`.
* `f::Function`: The function to be differentiated.
* `x::AbstractArray{T}`: The value of `x` at which to evaluate the gradient of
    `f`.
* `buffer::AbstractArray{T}`: A buffer that is equivalent to `similar(x)`. Used
    as a temporary copy of `x` that can be mutated safely during the execution
    of the function.

# Returns

* `nothing::Void`: This function is called for its side effects.

# Examples

```julia
import FiniteDiff: gradient!
x = [0.0, 0.0]
output, buffer = Array(Float64, 2), Array(Float64, 2)
gradient!(
    output,
    x -> sin(x[1]) + 2 * sin(x[2]),
    x,
    buffer,
)
```
"""
function gradient!{T <: AbstractFloat}(
    output::AbstractArray,
    f::Function,
    x::AbstractArray{T},
    buffer::AbstractArray{T},
)::Void
    gradient!(output, f, x, buffer, CentralMode())
    return
end

"""
    gradient{T <: AbstractFloat}(
        f::Function,
        x::AbstractArray{T},
        mode::Mode = CentralMode(),
    )

# Description

Evaluate the gradient of `f` at `x` using finite differences. Defaults to
central mode finite differences. See the documentation for that mode for
more details.

**NOTE**: Allocates new memory to store the output as well as memory to use as
a buffer.

# Arguments

* `f::Function`: The function to be differentiated.
* `x::AbstractArray{T}`: The value of `x` at which to evaluate the gradient of
    `f`.
* `m::Mode`: An instance of the `Mode` type. This will determine the mode
    of finite differences that will be used.

# Returns

* `output::AbstractArray`: The gradient.

# Examples

```julia
import FiniteDiff: gradient!, ForwardMode
x = [0.0, 0.0]
output = gradient!(x -> sin(x[1]) + 2 * sin(x[2]), x, ForwardMode())
```
"""
function gradient{T <: AbstractFloat}(
    f::Function,
    x::AbstractArray{T},
    mode::Mode = CentralMode(),
)::AbstractArray{T}
    if mode != ComplexMode()
        output, buffer = similar(x), similar(x)
    else
        output, buffer = similar(x), similar(x, Complex{T})
    end
    gradient!(output, f, x, buffer, mode)
    return output
end

"""
    gradient(f::Function, mode::Mode = CentralMode(); mutates::Bool = false)

# Description

Construct a new function that will evaluate the gradient of `f` at any value
of `x`. The user can specify the mode as a positional argument and can also
indicate whether to return a mutating or non-mutating function using a keyword
argument.

# Arguments

* `f::Function`: The function to be differentiated.
* `m::Mode`: An instance of the `Mode` type. This will determine the mode
    of finite differences that will be used.

# Keyword Arguments

* `mutates::Bool = false`: Determine whether the resulting function will mutate
    its inputs or will be a pure function.

# Returns

* `g′::Function`: A function to evaluate the gradient of `f` at a new point
    `x`.

# Examples

```julia
import FiniteDiff: gradient!, ForwardMode
x = [0.0, 0.0]
g′ = gradient(x -> sin(x[1]) + 2 * sin(x[2]), ForwardMode(), mutates = false)
output = g′(x)
```
"""
function gradient(
    f::Function,
    mode::Mode = CentralMode();
    mutates::Bool = false,
)::Function
    if mutates
        return (output, x, buffer) -> gradient!(output, f, x, buffer, mode)
    else
        return (x, ) -> gradient(f, x, mode)
    end
end


#=

  Euler #32 in Julia.

  Problem 32
  """
  We shall say that an n-digit number is pandigital if it makes use of
  all the digits 1 to n exactly once; for example, the 5-digit number,
  15234, is 1 through 5 pandigital.

  The product 7254 is unusual, as the identity, 39 × 186 = 7254,
  containing multiplicand, multiplier, and product is 1 through 9
  pandigital.

  Find the sum of all products whose multiplicand/multiplier/product
  identity can be written as a 1 through 9 pandigital.
  HINT: Some products can be obtained in more than one way so be sure
  to only include it once in your sum.
  """

  This Julia program was created by Hakan Kjellerstrand, hakank@gmail.com
  See also my Julia page: http://www.hakank.org/julia/

=#

include("Euler.jl")

# 0.17556319s
function euler32a()
    prod_hash = Dict{Int,Int}()
    for a in 2:98
        as = string(a)
        if occursin("0",as) continue end
        for b in a+1:9876
            p = a*b
            if occursin("0",string(p)) continue end
            l = "$as$b$p"
            if length(l) == 9 && length(Set(l)) == 9 && !occursin("0",l)
                prod_hash[p] = 1
            end
        end
    end
    return keys(prod_hash)|>sum
end

#
function euler32b()
    prod_hash = Set()
    for a in 2:98
        # as = string(a)
        if occursin("0",string(a)) continue end
        for b in a+1:9876
            p = a*b
            if occursin("0",string(p)) continue end
            l = "$a$b$p"
            if length(l) == 9 && length(Set(l)) == 9 && !occursin("0",l)
            # if length(l) == 9 && !occursin("0",l) && length(unique(l)) == 9
                push!(prod_hash,p)
            end
        end
    end

    return prod_hash|>sum
end

function euler32c()
    prod_hash = Dict{Int,Int}()
    for a in 98:-1:2
        if a % 10 == 0 continue end
        as = string(a)
        for b in a+1:9876
            p = a*b
            if p % 10 == 0 continue end
            l = "$as$b$p"
            # l = string(a,b,p)
            if length(l) != 9 continue end
            if length(Set(l)) != 9 continue end
            if occursin("0",l) continue end
            prod_hash[p] = 1
        end
    end
    return keys(prod_hash)|>sum
end

GC.gc(true)
GC.gc(false)
run_euler(euler32a)
# run_euler(euler32b)
# GC.gc(true)
# GC.gc(false)
# run_euler(euler32c)


using IndividualDisplacements, MeshArrays, OrdinaryDiffEq
p=dirname(pathof(MeshArrays))
include(joinpath(p,"../examples/Demos.jl"))

"""
    simple_flow_field(Γ::Dict,np,nz)

Set up an idealized flow field which consists of
[rigid body rotation](https://en.wikipedia.org/wiki/Rigid_body),
plus a convergent term, plus a sinking term.

```
Γ=simple_periodic_domain(12)
u,v,w=simple_flow_field(Γ,12,4)
```
"""
function simple_flow_field(Γ,np,nz)
    γ=Γ["XC"].grid;

    #Solid-body rotation around central location ...
    i=Int(np/2+1)
    u=-(Γ["YG"].-Γ["YG"][1][i,i])
    v=(Γ["XG"].-Γ["XG"][1][i,i])

    #... plus a convergent term to / from central location
    d=-0.01
    u=u+d*(Γ["XG"].-Γ["XG"][1][i,i])
    v=v+d*(Γ["YG"].-Γ["YG"][1][i,i])

    #Replicate u,v in vertical dimension
    uu=MeshArray(γ,γ.ioPrec,nz)
    [uu[k]=u[1] for k=1:nz]
    vv=MeshArray(γ,γ.ioPrec,nz)
    [vv[k]=v[1] for k=1:nz]

    #Vertical velocity component w
    w=fill(-0.01,MeshArray(γ,γ.ioPrec,nz));

    return uu,vv,w
end

"""
    setup_random_flow(;np=12,nq=18)

Set up a random flow field over a gridded domain of size np,nq

```
ϕ,u,v=setup_random_flow()
```
"""
function setup_random_flow(;np=12,nq=18)
    Γ=simple_periodic_domain(np,nq)
    (_,ϕ,_,_)=demo2(Γ)
    ϕ .*= 0.5

    #For the convergent / scalar potential case, ϕ is interpreted as being
    #on center points -- hence the standard gradient function readily gives
    #what we need
    #(u,v)=gradient(ϕ,Γ)
    #return u[1],v[1],ϕ[1]

    #For the rotational / streamfunction case, ϕ is interpreted as being
    #on S/W corner points -- this is ok since the grid is homegeneous,
    #and conveniently yields an adequate substitution u,v <- -v,u; but note
    #that doing the same with gradient() would shift indices inconsistenly
    u=-(circshift(ϕ[1], (0,-1))-ϕ[1])
    v=(circshift(ϕ[1], (-1,0))-ϕ[1])
    return u,v,ϕ[1]
end

function setup_point_cloud(U::Array{T,2},V::Array{T,2};X=[],Y=[]) where T
    np,nq=size(U)
    Γ=simple_periodic_domain(np,nq)
    u=MeshArray(Γ["XC"].grid,[U])
    v=MeshArray(Γ["XC"].grid,[V])
    #vel=dxy_dt
    (u,v)=exchange(u,v,1)
    vel=dxy_dt!

    𝑃 = (u0=u, u1=u, v0=v, v1=v, 𝑇=[0.0,1.0], ioSize=Γ["XC"].grid.ioSize)
    pp=postprocess_xy
    isempty(X) ? X=np*rand(10) : nothing
    isempty(Y) ? Y=nq*rand(10) : nothing

    xy = permutedims([[X[i];Y[i];1.0] for i in eachindex(X)])
    tr = DataFrame(ID=Int[], x=Float64[], y=Float64[], t=Float64[])
    solv(prob) = solve(prob,Tsit5(),reltol=1e-5,abstol=1e-5)

    I=(position=xy,record=tr,velocity=vel,
       integration=solv,postprocessing=pp,parameters=𝑃)

    return Individuals(I)
end

"""
    setup_global_ocean(;k=10,ny=2)

Set up Global Ocean particle simulation in 2D with seasonally varying flow field.

```
𝑃=setup_global_ocean(k=1,ny=2);
```
"""
function setup_global_ocean(;k=1,ny=2)

  #k=10 #choice of vertical level
  #ny=2 #number of simulated years (20 for k>20)
  r_reset = 0.01 #fraction of the particles reset per month (0.05 for k<=10)

  #read grid and set up connections between subdomains
  p=dirname(pathof(IndividualDisplacements))
  γ=GridSpec("LatLonCap",MeshArrays.GRID_LLC90)
  Γ=GridLoad(γ)
  Γ=merge(Γ,IndividualDisplacements.NeighborTileIndices_cs(Γ))

  #initialize u0,u1 etc
  𝑃=set_up_𝑃(k,0.0,Γ,ECCOclim_path);

  #add parameters for use in reset!
  tmp=(frac=r_reset, Γ=Γ)
  𝑃=merge(𝑃,tmp)

  return 𝑃

end

##

"""
    example3_setup(;backward_in_time::Bool=false)

Define gridded variables and return result as Dictionary (`uvetc`).
"""
function example3_setup(;backward_in_time::Bool=false)

   p=dirname(pathof(IndividualDisplacements))
   dirIn=joinpath(p,"../examples/llc90_latlon/")
   γ=gcmgrid(dirIn,"PeriodicChannel",1,
                  [(360,178)], [360 178], Float32, read, write)

   Γ=Dict("XC" => read(γ.path*"XC.latlon.data",MeshArray(γ,Float32)),
   "YC" => read(γ.path*"YC.latlon.data",MeshArray(γ,Float32)),
   "XG" => read(γ.path*"XG.data",MeshArray(γ,Float32)),
   "YG" => read(γ.path*"YG.data",MeshArray(γ,Float32)),
   "DXC" => read(γ.path*"DXC.latlon.data",MeshArray(γ,Float32)),
   "DYC" => read(γ.path*"DYC.latlon.data",MeshArray(γ,Float32)) );

   file = matopen(γ.path*"uv_lonlat.mat")
   u=read(file, "u")
   v=read(file, "v")
   close(file)

   u=dropdims(mean(u,dims=3),dims=3)
   v=dropdims(mean(v,dims=3),dims=3)

   #mask out near edge values to avoid exiting domain
   u[:,1:2].=NaN
   v[:,1:2].=NaN
   u[:,end-2:end].=NaN
   v[:,end-2:end].=NaN

   u=read(u,MeshArray(γ,Float32))
   v=read(v,MeshArray(γ,Float32));

   u[findall(isnan.(u))]=0.0
   v[findall(isnan.(v))]=0.0

   backward_in_time ? s=-1.0 : s=1.0
   u0=s*u; u1=s*u;
   v0=s*v; v1=s*v;

   t0=0.0; t1=86400*366*2.0;

   u0=u0./Γ["DXC"]
   u1=u1./Γ["DXC"]
   v0=v0./Γ["DYC"]
   v1=v1./Γ["DYC"]

   #nr=50; kk=1;

   #mskW=read(γ.path*"hFacW.latlon.data",MeshArray(γ,Float32,nr))
   #mskW=1.0 .+ 0.0 * mask(mskW[:,kk],NaN,0.0)
   #mskS=read(γ.path*"hFacS.latlon.data",MeshArray(γ,Float32,nr))
   #mskS=1.0 .+ 0.0 * mask(mskS[:,kk],NaN,0.0)
   #msk=Dict("mskW" => mskW, "mskS" => mskS)

   𝑃 = (u0=u0, u1=u1, v0=v0, v1=v1, 𝑇=[t0,t1], ioSize=(360,178),
        XC=exchange(Γ["XC"]), YC=exchange(Γ["YC"]))

   return 𝑃,Γ

end

"""
    OCCA_setup(;backward_in_time::Bool=false)

Define gridded variables and return result as Dictionary (`uvetc`).
"""
function OCCA_setup(;backward_in_time::Bool=false)

   γ=GridSpec("PeriodicChannel",MeshArrays.GRID_LL360)
   Γ=GridLoad(γ)
   n=length(Γ["RC"])
   n=10

   delete!.(Ref(Γ), ["hFacC", "hFacW", "hFacS","DXG","DYG","RAC","RAZ","RAS"]);
   backward_in_time ? s=-1.0 : s=1.0

   function rd(filename, varname,n)
   fil = NetCDF.open(filename, varname)
   siz = size(fil)
   tmp = zeros(siz[1:2]...,n)
   [tmp .+= fil[:,:,1:n,t] for t=1:12]
   tmp ./= 12.0
   tmp[findall(tmp.<-1e22)] .= 0.0
   return tmp
   end

   fileIn=OCCAclim_path*"DDuvel.0406clim.nc"
   u=s*read(rd(fileIn,"u",n),MeshArray(γ,Float32,n))

   fileIn=OCCAclim_path*"DDvvel.0406clim.nc"
   v=s*read(rd(fileIn,"v",n),MeshArray(γ,Float32,n))

   fileIn=OCCAclim_path*"DDwvel.0406clim.nc"
   w=s*rd(fileIn,"w",n)
   w=-cat(w,zeros(360, 160),dims=3)
   w[:,:,1] .=0.0
   w=read(w,MeshArray(γ,Float32,n+1))

   fileIn=OCCAclim_path*"DDtheta.0406clim.nc"
   θ=read(rd(fileIn,"theta",n),MeshArray(γ,Float32,n))

#   fileIn=OCCAclim_path*"DDsalt.0406clim.nc"
#   𝑆=read(rd(fileIn,"salt",n),MeshArray(γ,Float64,n))

   for i in eachindex(u)
      u[i]=u[i]./Γ["DXC"][1]
      v[i]=v[i]./Γ["DYC"][1]
   end

   for i in eachindex(u)
      u[i]=circshift(u[i],[-180 0])
      v[i]=circshift(v[i],[-180 0])
      θ[i]=circshift(θ[i],[-180 0])
#      𝑆[i]=circshift(𝑆[i],[-180 0])
   end

   for i in eachindex(w)
      w[i]=w[i]./Γ["DRC"][min(i[2]+1,n)]
      w[i]=circshift(w[i],[-180 0])
   end

   tmpx=circshift(Γ["XC"][1],[-180 0])
   tmpx[1:180,:]=tmpx[1:180,:] .- 360.0
   Γ["XC"][1]=tmpx

   tmpx=circshift(Γ["XG"][1],[-180 0])
   tmpx[1:180,:]=tmpx[1:180,:] .- 360.0
   Γ["XG"][1]=tmpx
   Γ["Depth"][1]=circshift(Γ["Depth"][1],[-180 0])

   t0=0.0; t1=86400*366*2.0;

   for k=1:n
    (tmpu,tmpv)=exchange(u[:,k],v[:,k],1)
    u[:,k]=tmpu
    v[:,k]=tmpv
   end
   for k=1:n+1
    tmpw=exchange(w[:,k],1)
    w[:,k]=tmpw
   end

   𝑃 = (θ0=θ, θ1=θ, u0=u, u1=u, v0=v, v1=v, w0=w, w1=w, 𝑇=[t0,t1],
   XC=exchange(Γ["XC"]), YC=exchange(Γ["YC"]),
   RF=Γ["RF"], RC=Γ["RC"],
   ioSize=(360,160,n))

   return 𝑃,Γ

end

"""
    init_global_range(lons::Tuple = (-160.0, -150.0),lats::Tuple = (35.0, 45.0))

Randomly distribute `np` points over a lon,la region, and
return position in grid index space (`i,j,subdomain`).
"""
function init_global_range(lons::Tuple = (-160.0, -150.0),lats::Tuple = (35.0, 45.0))
    lo0, lo1 = lons #(-160.0, -150.0)
    la0, la1 = lats #(35.0, 45.0)
    np = 100
    lon = lo0 .+ (lo1 - lo0) .* rand(np)
    lat = la0 .+ (la1 - la0) .* rand(np)
    (u0, _) = initialize_lonlat(Γ, lon, lat; msk = Γ["hFacC"][:, k])
    id=collect(1:np)
    return u0
end

"""
    init_global_randn(np ::Int , 𝑃::NamedTuple)

Randomly distribute `np` points over the Earth, within `𝑃.msk`
region, and return position in grid index space (`i,j,subdomain`).
"""
function init_global_randn(np ::Int , 𝑃::NamedTuple)
    (lon, lat) = randn_lonlat(2*np)
    (u0, _) = initialize_lonlat(𝑃.Γ, lon, lat; msk = 𝑃.msk)
    u0[:,1:np]
end

"""
    reset_lonlat!(𝐼::Individuals)

Randomly select a fraction (𝐼.𝑃.frac) of the particles and reset their positions.
"""
function reset_lonlat!(𝐼::Individuals)
    np=length(𝐼.🆔)
    n_reset = Int(round(𝐼.𝑃.frac*np))
    (lon, lat) = randn_lonlat(2*n_reset)
    (v0, _) = initialize_lonlat(𝐼.𝑃.Γ, lon, lat; msk = 𝐼.𝑃.msk)
    n_reset=min(n_reset,size(v0,2))
    k_reset = rand(1:np, n_reset)
    v0 = permutedims([v0[:,i] for i in 1:size(v0,2)])
    𝐼.📌[k_reset].=v0[1:n_reset]
    isempty(𝐼.🔴.ID) ? m=maximum(𝐼.🆔) : m=max(maximum(𝐼.🔴.ID),maximum(𝐼.🆔))
    𝐼.🆔[k_reset]=collect(1:n_reset) .+ m
end

##

"""
    isosurface(θ,T,z)

```
isosurface(𝐼.𝑃.θ0,15,Γ["RC"])
```
"""
function isosurface(θ,T,z)
    d=NaN*similar(θ[:,1])
    nr=size(θ,2)
    for j=1:size(d,1)
        for k=1:nr-1
            i=findall(isnan.(d[j]).&(θ[j,k].>T).&(θ[j,k+1].<=T))
            a=(θ[j,k][i] .- T)./(θ[j,k][i] .- θ[j,k+1][i])
            d[j][i]=(1 .- a).*Γ["RC"][k] + a.*Γ["RC"][k+1]
            i=findall(isnan.(d[j]).&(θ[j,k].<=T).&(θ[j,k+1].>T))
            a=(θ[j,k+1][i] .- T)./(θ[j,k+1][i] .- θ[j,k][i])
            d[j][i]=(1 .- a).*Γ["RC"][k+1] + a.*Γ["RC"][k]
        end
    end
    return d
end


using ParameterizedFunctions, DiffEqBase, SciMLBase
using Test, InteractiveUtils, Latexify

using SpecialFunctions

### ODE Macros

println("Build some examples")
f_t = @ode_def SymCheck begin # Checks for error due to symbol on 1
  dx = x
  dy = -c*y + d*x*y*t^2
end a b c d

@test SciMLBase.__has_syms(f_t)

f_t2 = @ode_def SymCheck2 begin # Checks for error due to symbol on 1
  dx = 1
  dy = -c*y + d*x*y*t^2
end a b c d

f_t3 = @ode_def ExprCheck begin # Checks for error due to symbol on 1
  dx = a*x - b*x*y
  dy = -c*y + d*x*y
end a b c d # Change to π after unicode fix

f = @ode_def_all LotkaVolterra begin
  dx = a*x - b*x*y
  dy = -c*y + d*x*y
end a b c d

f_2 = @ode_def LotkaVolterra3 begin
  dx = a*x - b^2*x*y
  dy = -c*y + d*x*y
end a b c d

println("Test Values")
t = 1.0
u = [2.0,3.0]
p = [1.5,1,3,1]
du = zeros(2)
grad = similar(du)
J = zeros(2,2)
iJ= zeros(2,2)
iW= zeros(2,2)
f(du,u,p,t)
@test du == [-3.0,-3.0]
@test du == f(u,p,t)
f_t(du,u,p,t)
@test du == [2.0,-3.0]
f_t2(du,u,p,t)
@test du == [1.0,-3.0]

println("Test t-gradient")
f.tgrad(grad,u,p,t)
@test grad == zeros(2)
f_t.tgrad(grad,u,p,t)
@test grad == [0.0;12.0]

println("Test Jacobians")
f.jac(J,u,p,t)
@test J  == [-1.5 -2.0
             3.0 -1.0]
@test f.jac(u, p, t) == [-1.5 -2.0; 3.0 -1.0]

@code_llvm SciMLBase.has_jac(f)

println("Test booleans")
@test SciMLBase.has_jac(f) == true

println("Test difficult differentiable")
NJ = @ode_def DiffDiff begin
  dx = a*x - b*x*y
  dy = -c*y + erf(x*y/d)
end a b c d
NJ(du,u,[1.5,1,3,4],t)
@test du == [-3.0;-3*3.0 + erf(2.0*3.0/4)]
@test du == NJ(u, [1.5,1,3,4], t)

test_fail(x,y,d) = erf(x*y/d)
println("Test non-differentiable")
NJ = @ode_def NoJacTest begin
  dx = a*x - μ*x*y
  dy = -c*y + test_fail(x,y,d)
end a μ c d
NJ(du,u,[1.5,1,3,4],t)
@test du == [-3.0;-3*3.0 + erf(2.0*3.0/4)]
@test du == NJ(u,[1.5,1,3,4],t)
@test_throws MethodError NJ.jac(iJ,u,p,t)
# NJ(Val{:jac},t,u,J) # Currently gives E not defined, will be fixed by the next SymEgine

println("Test anonymous definition")

f_t_noname = @ode_def begin # Checks for error due to symbol on 1
  dx = x
  dy = -c*y + d*x*y*t^2
end a b c d

@test SciMLBase.__has_syms(f_t_noname)

f = @ode_def begin
  dx = a*x - b*x*y
  dy = -c*y + d*x*y
end a b c d
@test_nowarn f([0.1,0.2], [1,2], [1,2,3,4], 1)

# Test failures of derivatives should not have #undef
sir_ode = @ode_def SIRModel begin
  dS = -b*S*I
  dI =  b*S*I - g*I
  dR =  g*I
end b g


"""script for running overnight to collect data"""

cd(dirname(@__FILE__))
include("../Percolation.jl")
using Plots, ColorSchemes, LaTeXStrings, DelimitedFiles, BenchmarkTools
figpath = "../../fig/percolation/"

for len in (10, 20, 40, 80, 160)
    samples, runs = 50, 1000

    probs = collect(range(0, 1, length=samples))
    gyration_avg, gyration_σ = zeros(samples), zeros(samples)

    for run in 1:runs
        randgrid = rand(len, len)
        for sample in 1:samples
            grid = randgrid .<= probs[sample]
            gyration = Percolation.clustgyration(grid)
            gyration_avg[sample] += gyration
            gyration_σ[sample] += gyration ^ 2
        end
    end

    gyration_avg ./= runs
    # absolute value added to ensure no negative numbers resulting from floating-point error at small σ
    gyration_σ = .√(abs.(gyration_σ ./ runs - gyration_avg .^ 2))

    # save data
    open("correlation-length-data/gyration-full-$len.txt", "w") do io
        writedlm(io, [probs gyration_avg gyration_σ])
    end

    p = scatter(probs, gyration_avg, yerr=gyration_σ, markerstrokecolor=:red,
    color=:mediumblue, lw=2, legend=false, fontfamily="Computer Modern",
    title=L"L=%$len,\ \textrm{averaged\ over\ %$runs \ runs}", xlabel=L"p", ylabel=L"\xi",
    titlefontsize=18, tickfontsize=10, labelfontsize=18)
    plot!(p, probs, gyration_avg, color=:mediumblue, lw=2)

    savefig(p, figpath * "gyration-full-$len.pdf")

    # use full range data to find narrow range for finding p_c and xi_max with better precision
    maxgyr = argmax(gyration_avg)
    gyrstep = probs[2] - probs[1]
    minprob, maxprob = probs[maxgyr - 1] - gyrstep / 2, probs[maxgyr + 1] + gyrstep / 2

    samples, runs = 50, 10000

    probs = collect(range(minprob, maxprob, length=samples))
    gyration_avg, gyration_σ = zeros(samples), zeros(samples)

    for run in 1:runs
        randgrid = rand(len, len)
        for sample in 1:samples
            grid = randgrid .<= probs[sample]
            gyration = Percolation.clustgyration(grid)
            gyration_avg[sample] += gyration
            gyration_σ[sample] += gyration ^ 2
        end
    end

    gyration_avg ./= runs
    # absolute value added to ensure no negative numbers resulting from floating-point error at small σ
    gyration_σ = .√(abs.(gyration_σ ./ runs - gyration_avg .^ 2))

    # save data
    open("correlation-length-data/gyration-zoom-$len.txt", "w") do io
        writedlm(io, [probs gyration_avg gyration_σ])
    end

    # find peak
    argpc = argmax(gyration_avg)
    pc = probs[argpc]
    ximax = gyration_avg[argpc]
    ximaxerr = gyration_σ[argpc]

    # save peak data
    open("correlation-length-data/gyration-peak.txt", "a") do io
        writedlm(io, [len pc ximax ximaxerr])
    end

    p = plot(probs, gyration_avg, ribbon=gyration_σ, fillcolor=:lightskyblue,
        color=:mediumblue, marker=:circle, lw=3, legend=false, fontfamily="Computer Modern",
        title=L"L=%$len,\ \textrm{averaged\ over\ %$runs \ runs}", xlabel=L"p", ylabel=L"\xi",
        titlefontsize=18, tickfontsize=10, labelfontsize=18)

    vline!(p, [pc], color=:red, linestyle=:dash)
    hline!(p, [ximax], color=:red, linestyle=:dash)

    annotetey = ylims(p)[1] + (ylims(p)[2] - ylims(p)[1]) / 15
    annotatex = pc + (xlims(p)[2] - xlims(p)[1]) / 100
    annotate!(p, annotatex, annotetey, text(L"p_c=%$(round(pc, digits=3))",
        :blueviolet, :left, :dash, 12))

    savefig(p, figpath * "gyration-zoom-$len.pdf")
end


using Test
using SymDiff

@test simplify(Val{:+}, :(a + b + 0)) == :(a + b)
@test simplify(Val{:+}, :(a + 0)) == :a

@test simplify(Val{:-}, :(a - b - 0)) == :(a - b)
@test simplify(Val{:-}, :(a - 0)) == :a


using MATLAB
using Test
using SparseArrays

# test engine

restart_default_msession()

a = [1. 2. 3.; 4. 5. 6.]
b = [2. 3. 4.; 8. 7. 6.]

@mput a b
mat"""
    r1 = a .* b;
    r2 = a + b;
"""
@mget r1 r2

@test isequal(r1, a .* b)
@test isequal(r2, a + b)

@mget r1::Vector
@test isequal(r1, vec(a .* b))

s = sparse([1. 0. 0.; 2. 3. 0.; 0. 4. 5.])
put_variable(:s, s)
s2 = get_variable(:s)
@test isequal(s, s2)

# mxcall

r = mxcall(:plus, 1, a, b)
@test isequal(r, a + b)

(xx, yy) = mxcall(:meshgrid, 2, [1., 2.], [3., 4.])
@test isequal(xx, [1. 2.; 1. 2.])
@test isequal(yy, [3. 3.; 4. 4.])

close_default_msession()

# test for segfault

s = MSession()
close(s)
@test_throws UndefRefError close(s)

# segfault on deleted references
x = mxarray(3.0)
delete(x)
@test_throws UndefRefError delete(x)
@test_throws UndefRefError nrows(x)
@test_throws UndefRefError is_numeric(x)
@test_throws UndefRefError jscalar(x)
@test_throws UndefRefError jvalue(x)

# make sure restart_default_msession() doesn't error on null references on
# default msession
s = get_default_msession()
close(s)
restart_default_msession()


using JuMP, EAGO

                     m = Model()

                     EAGO.register_eago_operators!(m)

                     @variable(m, -1 <= x[i=1:3] <= 1)

                     @variable(m, -8.232182974914569 <= q <= 6.070970235703473)

                     add_NL_constraint(m, :(softplus(-0.17705543456567296 + 0.5634748900178774*softplus(-0.9628319588335761 + 0.5826659544327617*$(x[1]) + -0.6099922924008303*$(x[2]) + 0.9101904633653426*$(x[3])) + 0.6106029608527352*softplus(0.06629760484384972 + -0.7340864295918403*$(x[1]) + 0.2309908240081593*$(x[2]) + 0.7867070707759196*$(x[3])) + 0.5384388086098779*softplus(-0.07370832879656497 + -0.32073040453400337*$(x[1]) + -0.07649911788086428*$(x[2]) + 0.03499506906851524*$(x[3]))) + softplus(0.08053767290922664 + 0.911477397877237*softplus(-0.9628319588335761 + 0.5826659544327617*$(x[1]) + -0.6099922924008303*$(x[2]) + 0.9101904633653426*$(x[3])) + 0.7134526118957916*softplus(0.06629760484384972 + -0.7340864295918403*$(x[1]) + 0.2309908240081593*$(x[2]) + 0.7867070707759196*$(x[3])) + -0.34097520379571256*softplus(-0.07370832879656497 + -0.32073040453400337*$(x[1]) + -0.07649911788086428*$(x[2]) + 0.03499506906851524*$(x[3]))) + softplus(0.5992621446321471 + 0.15304126986204158*softplus(-0.9628319588335761 + 0.5826659544327617*$(x[1]) + -0.6099922924008303*$(x[2]) + 0.9101904633653426*$(x[3])) + -0.37134199071135*softplus(0.06629760484384972 + -0.7340864295918403*$(x[1]) + 0.2309908240081593*$(x[2]) + 0.7867070707759196*$(x[3])) + 0.8747239793986465*softplus(-0.07370832879656497 + -0.32073040453400337*$(x[1]) + -0.07649911788086428*$(x[2]) + 0.03499506906851524*$(x[3]))) - $q <= 0.0))

                     @objective(m, Min, q)

                     return m


function f=GradGrad(OrdPoly)
DG.OrdPoly=OrdPoly;
[DG.w,DG.xw]=GaussLobattoQuad(OrdPoly);
[DG.DW,DG.DS,DG.DV,DG.DVT]=DerivativeMatrix(DG);
c=zeros(OrdPoly+1,1);
for i=1:OrdPoly+1
  c(i)=(i-OrdPoly/2-1)^2;
end
f=zeros(OrdPoly+1,1);
for i=1:OrdPoly+1
  for l=1:OrdPoly+1
    grad_phi_i=DG.DS(l,i);
    w=DG.w(l);
    t=0;
    for j=1:OrdPoly+1
      t=t+c(j)*DG.DS(l,j);
    end
    f(i)=f(i)+w*t*grad_phi_i;
  end
  f(i)=f(i)/DG.w(i);
end
f2=DG.DS*c;
f2=DG.DW*f2;
end


using MyExample
using Test

@testset "MyExample.jl" begin
    # 2x + y
    @test my_f(2,1) == 5
    @test my_f(2,2) == 6
    @test my_f(2,3) == 7
end

@testset "Derivative test" begin
    # 2x + y
    @test derivative_of_my_f(2,1) == 2
end


module Ditherings

using Images
export Quantise
export ZeroOne
export ZeroOne_PerChannel
export FloydSteinbergDither4Sample
export FloydSteinbergDither12Sample

function Quantise(pixel)
    shift = 4
    scale = 255.0
    r = Int64.(round(scale * (red(pixel)  )))>>shift
    g = Int64.(round(scale * (green(pixel))))>>shift
    b = Int64.(round(scale * (blue(pixel) )))>>shift
    r = Float64.((r<<shift)/scale)
    g = Float64.((g<<shift)/scale)
    b = Float64.((b<<shift)/scale)
    RGB(r,g,b)
end

function ZeroOne(pixel)
    r = red(pixel)
    g = green(pixel)
    b = blue(pixel)

    if(r + b + g >= 1.5)
        return RGB(1.0, 1.0, 1.0)
    else
        return RGB(0.0, 0.0, 0.0)
    end
end

function ZeroOne_PerChannel(pixel)
    r = red(pixel)
    g = green(pixel)
    b = blue(pixel)

    r= r>0.5 ? 1.0 : 0.0
    g= g>0.5 ? 1.0 : 0.0
    b= b>0.5 ? 1.0 : 0.0

    return RGB(r, g, b)

end

function FloydSteinbergDither4Sample(_img, PaletteFunction, weights=[7/16,3/16,5/16,1/16])
    width, height = size(_img);
    inputtype = typeof(_img)
    newimg = RGB{Float64}.(_img)

    for y in 2:height-1
        for x in 2:width-1
            oldpix = newimg[x,y]
            newpix = PaletteFunction(oldpix)
            newimg[x,y] = newpix
            quant_error = oldpix - newpix
            newimg[x+1,y  ] = newimg[x+1,y  ] + (quant_error * weights[1])
            newimg[x-1,y+1] = newimg[x-1,y+1] + (quant_error * weights[2])
            newimg[x  ,y+1] = newimg[x  ,y+1] + (quant_error * weights[3])
            newimg[x+1,y+1] = newimg[x+1,y+1] + (quant_error * weights[4])
        end
    end

    newimg
end

function FloydSteinbergDither12Sample(_img, PaletteFunction, weights = [0.243228, 0.0810761, 0.0417918, 0.0573652, 0.243228, 0.0573652, 0.0417918, 0.0347469, 0.0417918, 0.0810761, 0.0417918, 0.0347469])
    width, height = size(_img);
    inputtype = typeof(_img)
    newimg = RGB{Float64}.(_img)

    for y in 3:height-2
        for x in 3:width-2
            oldpix = newimg[x,y]
            newpix = PaletteFunction(oldpix)
            newimg[x,y] = newpix
            quant_error = oldpix - newpix
            newimg[x+1,y  ] = newimg[x+1,y  ] + (quant_error * weights[1] )
            newimg[x+2,y  ] = newimg[x+2,y  ] + (quant_error * weights[2] )

            newimg[x-2,y+1] = newimg[x-2,y+1] + (quant_error * weights[3] )
            newimg[x-1,y+1] = newimg[x-1,y+1] + (quant_error * weights[4] )
            newimg[x  ,y+1] = newimg[x  ,y+1] + (quant_error * weights[5] )
            newimg[x+1,y+1] = newimg[x+1,y+1] + (quant_error * weights[6] )
            newimg[x+2,y+1] = newimg[x+2,y+1] + (quant_error * weights[7] )

            newimg[x-2,y+2] = newimg[x-2,y+2] + (quant_error * weights[8] )
            newimg[x-1,y+2] = newimg[x-1,y+2] + (quant_error * weights[9] )
            newimg[x  ,y+2] = newimg[x  ,y+2] + (quant_error * weights[10] )
            newimg[x+1,y+2] = newimg[x+1,y+2] + (quant_error * weights[11] )
            newimg[x+2,y+2] = newimg[x+2,y+2] + (quant_error * weights[12] )
        end
    end

    newimg
end

function HorizontalDither3Sample(_img, PaletteFunction, weights=[0.5,0.4,0.1])
    width, height = size(_img);
    inputtype = typeof(_img)
    newimg = RGB{Float64}.(_img)

    for y in 2:height-3
        for x in 2:width-3
            oldpix = newimg[x,y]
            newpix = PaletteFunction(oldpix)
            newimg[x,y] = newpix
            quant_error = oldpix - newpix
            newimg[x+1,y  ] = newimg[x+1,y  ] + (quant_error * weights[1])
            newimg[x+2,y  ] = newimg[x+2,y  ] + (quant_error * weights[2])
            newimg[x+3,y  ] = newimg[x+3,y  ] + (quant_error * weights[3])
        end
    end

    newimg
end

function VerticalDither3Sample(_img, PaletteFunction, weights=[0.5,0.4,0.1])
    width, height = size(_img);
    inputtype = typeof(_img)
    newimg = RGB{Float64}.(_img)

    for y in 2:height-3
        for x in 2:width-3
            oldpix = newimg[x,y]
            newpix = PaletteFunction(oldpix)
            newimg[x,y] = newpix
            quant_error = oldpix - newpix
            newimg[x,y+1  ] = newimg[x,y+1  ] + (quant_error * weights[1])
            newimg[x,y+2  ] = newimg[x,y+2  ] + (quant_error * weights[2])
            newimg[x,y+3  ] = newimg[x,y+3  ] + (quant_error * weights[3])
        end
    end

    newimg
end

end


function convertInputsToFloats(U, g, lmd)
    U = convert(Array{Float64}, U)
    g = convert(Array{Float64}, g)
    lmd = convert(Float64, lmd)
    return U,g,lmd
end

function cancelZeroStates(U,g)
    ## cancel states with prior zero
    nonzeroStates = findall(a->a!=0, g)
    U = U[:,nonzeroStates]
    g = g[nonzeroStates]
    return U,g,nonzeroStates
end

function transform_utilities(U, lmd)
    # shiftterm = (((maximum(U)+minimum(U))/2))   ## reduce extremes of U for numerical stability
    # Z = exp.((U.-shiftterm)/lmd)    ## transform utilities
    shiftterm = 0
    Z = exp.(U/lmd)    ## transform utilities
    return Z, shiftterm
end

function checkForTriviality(Z,g,m)
    ## interupt if there is no RI problem
    if m==1  ## since only one action possible
        return ones(m), (log.(Z)*g)[1], 0, "Trivial: only one action is possible"
    end
    if typeof(Z)==Vector{Float64}  ## since only one state possible
        best_solution = findmax(Z[:,1])
        p = zeros(m)
        p[best_solution[2]]=1
        return p, log(best_solution[1]), 0, "Trivial: only one state is possible"
    end
    return "not trivial"
end

function checkForBoundarySolution(Z,g)
    ## check whether you have boundary solutions in case of two alternatives
    if (Z[1,:]./Z[2,:])'*g<1
        return [0, 1.0], log.(Z[2,:])'*g, 0,  "Found boundary solution (two alternatives only)"
    elseif (Z[2,:]./Z[1,:])'*g<1
        return [1.0, 0], log.(Z[1,:])'*g, 0,  "Found boundary solution (two alternatives only)"
    end
    return "no boundary solution"
end

function preprocessinput(U, g, lmd)
    m = size(U)[1]
    solution = (ones(m)/m,0,0,"Run Cover Algorithm")

    U,g = convertInputsToFloats(U, g, lmd)
    U,g,nonzeroStates = cancelZeroStates(U,g)
    Z,shiftterm = transform_utilities(U,lmd)
    intermediateSolution = checkForTriviality(Z,g,m)
    if intermediateSolution != "not trivial"
          solution = intermediateSolution
    end
    if m == 2
        intermediateSolution = checkForBoundarySolution(Z,g)
        if intermediateSolution != "no boundary solution"
              solution = intermediateSolution
        end
    end
    return Z,g,shiftterm,solution
end


# Remove this part?
export sobolevAZweight
sobolevAZweight(A::DictionaryOperator; options...) = inv(WeightedSmoothingOperator(src(A); options...))
# For Dictionary's that have a DC component
dc_index(b::ChebyshevT) = 1
dc_index(b::Fourier) = 1
function WeightedSmoothingOperator(dict::Dictionary; scaling = default_scaling_function, order = 1, options...)
    if order == 1
        WeightedSmoothingOperator(dict, scaling)
    else
        WeightedSmoothingOperator(dict, (d,i) -> scaling(d,i)^order)
    end
end
WeightedSmoothingOperator(dict::Dictionary, scaling) = DiagonalOperator(dict, dict, [scaling(dict, i) for i in eachindex(dict)][:])

function default_scaling_function(dict::Dictionary1d, idx)
    f = abs(value(native_index(dict, idx)))
    1 + f
end
default_scaling_function(dict::TensorProductDict, I) =
    default_scaling_function(element(dict, 1), I[1]) + default_scaling_function(element(dict, 2), I[2])


using ModelingToolkit, Unitful, OrdinaryDiffEq, DiffEqJump, IfElse
using Test
MT = ModelingToolkit
@parameters τ [unit = u"ms"] γ
@variables t [unit = u"ms"] E(t) [unit = u"kJ"] P(t) [unit = u"MW"]
D = Differential(t)

#This is how equivalent works:
@test MT.equivalent(u"MW", u"kJ/ms")
@test !MT.equivalent(u"m", u"cm")
@test MT.equivalent(MT.get_unit(P^γ), MT.get_unit((E / τ)^γ))

# Basic access
@test MT.get_unit(t) == u"ms"
@test MT.get_unit(E) == u"kJ"
@test MT.get_unit(τ) == u"ms"
@test MT.get_unit(γ) == MT.unitless
@test MT.get_unit(0.5) == MT.unitless
@test MT.get_unit(MT.SciMLBase.NullParameters()) == MT.unitless

# Prohibited unit types
@parameters β [unit = u"°"] α [unit = u"°C"] γ [unit = 1u"s"]
@test_throws MT.ValidationError MT.get_unit(β)
@test_throws MT.ValidationError MT.get_unit(α)
@test_throws MT.ValidationError MT.get_unit(γ)

# Non-trivial equivalence & operators
@test MT.get_unit(τ^-1) == u"ms^-1"
@test MT.equivalent(MT.get_unit(D(E)), u"MW")
@test MT.equivalent(MT.get_unit(E / τ), u"MW")
@test MT.get_unit(2 * P) == u"MW"
@test MT.get_unit(t / τ) == MT.unitless
@test MT.equivalent(MT.get_unit(P - E / τ), u"MW")
@test MT.equivalent(MT.get_unit(D(D(E))), u"MW/ms")
@test MT.get_unit(IfElse.ifelse(t > t, P, E / τ)) == u"MW"
@test MT.get_unit(1.0^(t / τ)) == MT.unitless
@test MT.get_unit(exp(t / τ)) == MT.unitless
@test MT.get_unit(sin(t / τ)) == MT.unitless
@test MT.get_unit(sin(1u"rad")) == MT.unitless
@test MT.get_unit(t^2) == u"ms^2"

eqs = [D(E) ~ P - E / τ
       0 ~ P]
@test MT.validate(eqs)
@named sys = ODESystem(eqs)

@test !MT.validate(D(D(E)) ~ P)
@test !MT.validate(0 ~ P + E * τ)

# Array variables
@variables t [unit = u"s"] x[1:3](t) [unit = u"m"]
@parameters v[1:3]=[1, 2, 3] [unit = u"m/s"]
D = Differential(t)
eqs = D.(x) .~ v
ODESystem(eqs, name = :sys)

# Difference equation
@parameters t [unit = u"s"] a [unit = u"s"^-1]
@variables x(t) [unit = u"kg"]
δ = Differential(t)
D = Difference(t; dt = 0.1u"s")
eqs = [
    δ(x) ~ a * x,
]
de = ODESystem(eqs, t, [x], [a], name = :sys)

# Nonlinear system
@parameters a [unit = u"kg"^-1]
@variables x [unit = u"kg"]
eqs = [
    0 ~ a * x,
]
@named nls = NonlinearSystem(eqs, [x], [a])

# SDE test w/ noise vector
@parameters τ [unit = u"ms"] Q [unit = u"MW"]
@variables t [unit = u"ms"] E(t) [unit = u"kJ"] P(t) [unit = u"MW"]
D = Differential(t)
eqs = [D(E) ~ P - E / τ
       P ~ Q]

noiseeqs = [0.1u"MW",
    0.1u"MW"]
@named sys = SDESystem(eqs, noiseeqs, t, [P, E], [τ, Q])

# With noise matrix
noiseeqs = [0.1u"MW" 0.1u"MW"
            0.1u"MW" 0.1u"MW"]
@named sys = SDESystem(eqs, noiseeqs, t, [P, E], [τ, Q])

# Invalid noise matrix
noiseeqs = [0.1u"MW" 0.1u"MW"
            0.1u"MW" 0.1u"s"]
@test !MT.validate(eqs, noiseeqs)

# Non-trivial simplifications
@variables t [unit = u"s"] V(t) [unit = u"m"^3] L(t) [unit = u"m"]
@parameters v [unit = u"m/s"] r [unit = u"m"^3 / u"s"]
D = Differential(t)
eqs = [D(L) ~ v,
    V ~ L^3]
@named sys = ODESystem(eqs)
sys_simple = structural_simplify(sys)

eqs = [D(V) ~ r,
    V ~ L^3]
@named sys = ODESystem(eqs)
sys_simple = structural_simplify(sys)

@variables V [unit = u"m"^3] L [unit = u"m"]
@parameters v [unit = u"m/s"] r [unit = u"m"^3 / u"s"] t [unit = u"s"]
eqs = [V ~ r * t,
    V ~ L^3]
@named sys = NonlinearSystem(eqs, [V, L], [t, r])
sys_simple = structural_simplify(sys)

eqs = [L ~ v * t,
    V ~ L^3]
@named sys = NonlinearSystem(eqs, [V, L], [t, r])
sys_simple = structural_simplify(sys)

#Jump System
@parameters β [unit = u"(mol^2*s)^-1"] γ [unit = u"(mol*s)^-1"] t [unit = u"s"] jumpmol [
    unit = u"mol",
]
@variables S(t) [unit = u"mol"] I(t) [unit = u"mol"] R(t) [unit = u"mol"]
rate₁ = β * S * I
affect₁ = [S ~ S - 1 * jumpmol, I ~ I + 1 * jumpmol]
rate₂ = γ * I
affect₂ = [I ~ I - 1 * jumpmol, R ~ R + 1 * jumpmol]
j₁ = ConstantRateJump(rate₁, affect₁)
j₂ = VariableRateJump(rate₂, affect₂)
js = JumpSystem([j₁, j₂], t, [S, I, R], [β, γ], name = :sys)

affect_wrong = [S ~ S - jumpmol, I ~ I + 1]
j_wrong = ConstantRateJump(rate₁, affect_wrong)
@test_throws MT.ValidationError JumpSystem([j_wrong, j₂], t, [S, I, R], [β, γ], name = :sys)

rate_wrong = γ^2 * I
j_wrong = ConstantRateJump(rate_wrong, affect₂)
@test_throws MT.ValidationError JumpSystem([j₁, j_wrong], t, [S, I, R], [β, γ], name = :sys)

# mass action jump tests for SIR model
maj1 = MassActionJump(2 * β / 2, [S => 1, I => 1], [S => -1, I => 1])
maj2 = MassActionJump(γ, [I => 1], [I => -1, R => 1])
@named js3 = JumpSystem([maj1, maj2], t, [S, I, R], [β, γ])

#Test unusual jump system
@parameters β γ t
@variables S(t) I(t) R(t)

maj1 = MassActionJump(2.0, [0 => 1], [S => 1])
maj2 = MassActionJump(γ, [S => 1], [S => -1])
@named js4 = JumpSystem([maj1, maj2], t, [S], [β, γ])


using Distributions
using Klara
using PGUManifoldMC

include("rv_model.jl")

using RvModelKeplerian

DATADIR = "../../data"
SUBDATADIR = "mala"

dataset = readdlm(joinpath(DATADIR, "example1.csv"), ',', header=false); # read observational data
obs_times = dataset[:,1]
obs_rv = dataset[:,2]
sigma_obs = dataset[:,3]
set_times(obs_times);     # set data to use for model evaluation
set_obs( obs_rv);
set_sigma_obs(sigma_obs);

include("utils_ex.jl")
param_true = make_param_true_ex1()
param_perturb_scale = make_param_perturb_scale(param_true)
param_init = 0
param_init = param_true.+0.01*param_perturb_scale.*randn(length(param_true))
println("param_init= ",param_init)

nmcmc = 50000
nburnin = 10000
mcrange = BasicMCRange(nsteps=nmcmc, burnin=nburnin)

outopts = Dict{Symbol, Any}(:monitor=>[:value, :logtarget], :diagnostics=>[:accept])

v0 = Dict(:p=>param_init)

p = BasicContMuvParameter(
  :p,
  logtarget=plogtarget,
  diffopts=DiffOptions(mode=:forward)
)

model = likelihood_model(p, false)
#model = likelihood_model([p], isindexed=false)

# target_accept_rates = [0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.5, 0.75, 0.9]
target_accept_rates = [0.574]
#target_accept_rates = [0.25, 0.5, 0.75, 0.9]  # 0.574 for MALA
#target_accept_rates = [0.1, 0.25, 0.5, 0.75, 0.9, 0.95, 0.99]
#target_accept_rates = [0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8]
#target_accept_rates = [0.574]
jobs = Array(Any,length(target_accept_rates))
mala_chains = Array(Any,length(target_accept_rates))
mala_times = Array(Float64, length(target_accept_rates))
mala_stepsizes = Array(Float64, length(target_accept_rates))
mala_acceptance = Array(Float64, length(target_accept_rates))
mala_esses = Array(Float64, length(target_accept_rates))
mala_iacts = Array(Float64, length(target_accept_rates))

for i in 1:length(target_accept_rates)
  target_accept_rate = target_accept_rates[i]
  println("# i= ", i, ": ",target_accept_rate)
  println("# p= ",param_init)
  sampler = MALA(0.02)
  mctuner = AcceptanceRateMCTuner(target_accept_rate, score=x -> logistic_rate_score(x, 3.), verbose=true)
  # mctuner = AcceptanceRateMCTuner(target_accept_rate, score=x -> logistic_rate_score(x, 0.3), verbose=true)
  #mctuner = VanillaMCTuner(verbose=true)
  v0 = Dict(:p=>param_init)
  job = BasicMCJob(
    model,
    sampler,
    mcrange,
    v0,
    tuner=mctuner,
    outopts=outopts
  )

  tic()
  run(job)
  runtime = toc()

  chain = output(job)
  ratio = acceptance(chain)

  println("# mean[",i,"] = ",mean(chain))
  println("# std[",i,"] =",vec(std(chain.value,2)))
  println("# ess[",i,"] = ",ess(chain))
  println("# iact[",i,"] = ",iact(chain))

  jobs[i] = job
  mala_times[i] = runtime
  mala_stepsizes[i] = job.sstate.tune.step
  mala_acceptance[i] = ratio
  mala_esses[i] = minimum(ess(chain))
  mala_iacts[i] = maximum(iact(chain))
  mala_chains[i] = chain
end


# script to compute the gray-scale color for the robsutness figure

# Global bounds -
BIG = 2.5
SMALL = 1e-6

# load the robsutness data -
data_array = readdlm("./robustness/alpha_Z1_99.dat")

# transform -
(number_of_samples,number_of_cases) = size(data_array)
gray_scale_array = zeros(number_of_samples,number_of_cases)
for sample_index = 1:number_of_samples

  for case_index = 1:number_of_cases

    value = abs(data_array[sample_index,case_index])
    if (value>BIG)
      value = BIG
    end

    gray_scale_array[sample_index,case_index] = round(255*(1 - (value - SMALL)/(BIG - SMALL)))
  end
end


module day1

readInput() = parse.(Int, readlines(joinpath(@__DIR__, "input.txt")))

function part1(input)
    for i = 1:length(input), j = i+1:length(input)
        a, b = input[i], input[j]
        a + b == 2020 && return a * b
    end
end

function part2(input)
    for i = 1:length(input), j = i+1:length(input), k = j+1:length(input)
        a, b, c = input[i], input[j], input[k]
        a + b + c == 2020 && return a * b * c
    end
end

end


@reexport module Systems

using Distributions
using DynamicalSystems
import DynamicalSystemsBase:
    DynamicalSystem,
    ContinuousDynamicalSystem,
    DiscreteDynamicalSystem,
    Dataset

import DifferentialEquations: @ode_def

include("discretemaps/ar1.jl")
include("discretemaps/anishchenko1.jl")
include("discretemaps/henon2.jl")
include("discretemaps/henon4.jl")
include("discretemaps/henontriple.jl")
include("discretemaps/linearmap.jl")
include("discretemaps/linearmap3d_nonlinearcoupling.jl")
include("discretemaps/nontrivial_pegiun.jl")
include("discretemaps/logistic2.jl")
include("discretemaps/logistic3.jl")
include("discretemaps/logistic4.jl")
include("discretemaps/var1.jl")
include("discretemaps/var1coupled.jl")
include("discretemaps/verdes.jl")

include("continuous_systems/chuacircuits_driven.jl")
include("continuous_systems/chuacircuit_nscroll_sine.jl")
include("continuous_systems/hindmarsh_rose.jl")
include("continuous_systems/lorenztriple.jl")
include("continuous_systems/mediated_link.jl")
include("continuous_systems/rosslerrossler.jl")
include("continuous_systems/rosslerlorenz.jl")

# We export both the equations of motion and function to generate
# the systems for every example. The equations of motion functions
# are always prepended with `eom_`.
# Some systems (involving multiple lags) do not return instances
# of DiscreteDynamicalSystem, but a Dataset with the finished
# iterated map.
export
###############
# Discrete maps
###############
eom_ar1, ar1,
eom_anishchenko1, anishchenko1,
eom_henon2, henon2,
eom_henon_triple, henon_triple,
eom_henon4, henon4,
eom_linearmap1, linearmap1,
eom_linear3d_nonlinearcoupling, linear3d_nonlinearcoupling,
eom_logistic2, logistic2,
eom_logistic3, logistic3,
eom_logistic4, logistic4,
eom_nontrivial_pegiun, nontrivial_pegiun,
eom_var1, var1,
eom_var1coupled, var1coupled,
eom_verdes, verdes

export
####################
# Continuous systems
###################
eom_chuacircuit_nscroll_sine, chuacircuit_nscroll_sine,
eom_chuacircuits_driven, chuacircuits_driven,
eom_hindmarsh_rose, hindmarsh_rose,
eom_lorenz_triple, lorenz_triple,
eom_mediated_link, mediated_link,
eom_rossler_rossler, rossler_rossler,
eom_rossler_lorenz, rossler_lorenz

# Initialise all the systems once, generating a trajectory.
#trajectory(anishchenko1(), 10)
#trajectory(henon2(), 10)
#trajectory(henon4(), 10)
#trajectory(logistic2(), 10)
#trajectory(logistic3(), 10)
#trajectory(logistic4(), 10)
#trajectory(var1(), 10)
#trajectory(var1coupled(), 10)
#trajectory(verdes(), 10)

#trajectory(rossler_rossler(), 10)
#trajectory(rossler_lorenz(), 10)
#trajectory(lorenz_triple(), 10)
#trajectory(mediated_link(), 10)
#trajectory(chuacircuits_driven(), 10)
#trajectory(chuacircuit_nscroll_sine(), 10)

end


"""

Grünwald–Letnikov sense fractional derivative algorithms, please refer to [Grünwald–Letnikov derivative](https://en.wikipedia.org/wiki/Gr%C3%BCnwald%E2%80%93Letnikov_derivative) for more details
"""
abstract type GL <: FracDiffAlg end

"""
# Grünwald–Letnikov sense fractional dervivative.

    fracdiff(f, α, start_point, end_point, GLDirect())

### Example:

```julia-repl
julia> fracdiff(x->x^5, 0, 0.5, GLDirect())
```

!!! info "Scope"
    Please note Grunwald-Letnikov sense fracdiff only support ``0 < \\alpha < 1``.

Please refer to [Grünwald–Letnikov derivative](https://en.wikipedia.org/wiki/Gr%C3%BCnwald%E2%80%93Letnikov_derivative) for more details.

Grunwald Letnikov direct compute method to obtain fractional derivative, precision are guaranteed but cause more memory allocation and compilation time.
"""
struct GLDirect <: GL end

"""
# Grünwald Letnikov sense Multiplicative and Addtive approximation

    fracdiff(f, α, end_point, h, GLMultiplicativeAdditive())

Grünwald–Letnikov multiplication-addition-multiplication-addition··· method to approximate fractional derivative.

### Example

```julia-repl
julia> fracdiff(x->x, 0.5, 1, 0.007, GLMultiplicativeAdditive())
1.127403405642918
```

```tex
@book{oldham_spanier_1984,
title={The fractional calculus: Theory and applications of differentiation and integration to arbitrary order},
author={Oldham, Keith B. and Spanier, Jerome},
year={1984}}
```
"""
struct GLMultiplicativeAdditive <: GL end

"""
# Grünwald Letnikov sense three point interpolation

    fracdiff(f, α, end_point, h, GLLagrangeThreePointInterp())

Using Lagrange three poitns interpolation to approximate the fractional derivative.

### Example

```julia-repl
julia> fracdiff(x->x, 0.5, 1, 0.006, GLLagrangeThreePointInterp())
1.1261297605404632
```
"""
struct GLLagrangeThreePointInterp <: GL end

"""
# Grünwald Letnikov sense finite difference approximation

    fracdiff(f::Union{Function, Number}, α::AbstractArray, end_point, h, ::GLFiniteDifference)::Vector

Use finite difference method to obtain Grünwald Letnikov sense fractional derivative.

### Example

```julia-repl
julia> fracdiff(x->x, 0.5, 1, 0.01, GLFiniteDifference())
1.1269695801851276
```
"""
struct GLFiniteDifference <: GL end

"""
# Grünwald Letnikov sense derivative approximation

    fracdiff(f, α, point, p, GLHighPrecision())

Use the high precision algorithms to compute the Grunwald letnikov fractional derivative.

The **p** here is the grade of precision.

!!! note
    The value interval passing in the function should be a array!
"""
struct GLHighPrecision <: GL end

################################################################
###                    Type definition done                  ###
################################################################

#=
Grunwald Letnikov direct method
=#
function fracdiff(f::FunctionAndNumber, α::Float64, start_point, end_point, ::GLDirect)
    #checks(f, α, start_point, end_point)
    typeof(f) <: Number ? (end_point == 0 ? (return 0) : (return f/sqrt(pi*end_point))) : nothing
    end_point == 0 ? (return 0) : nothing
    g(τ) = (f(end_point)-f(τ)) ./ (end_point - τ) .^ (1+α)
    result = f(end_point)/(gamma(1-α) .* (end_point - start_point) .^ α) .+ quadgk(g, start_point, end_point) .* α ./ gamma(1-α)
    return result
end

#TODO: Use the improved alg!! This algorithm is not accurate
#This algorithm is not so good, still more to do
function fracdiff(f::FunctionAndNumber, α, end_point, h::Float64, ::GLMultiplicativeAdditive)::Float64
    typeof(f) <: Number ? (end_point == 0 ? 0 : f/sqrt(pi*end_point)) : nothing
    summation = zero(Float64)
    n = round(Int, end_point/h)

    for i ∈ 0:n-1
        summation += gamma(i-α)/gamma(i+1)*f(end_point-i*h)
    end
    result = summation*end_point^(-α)*n^α/gamma(-α)

    return result
end
#=
function fracdiff(f::Union{Number, Function}, α::Float64, end_point::AbstractArray, h, ::GLMultiplicativeAdditive)::Vector
    ResultArray = Float64[]
    for (_, value) in enumerate(end_point)
        append!(ResultArray, fracdiff(f, α, value, h, GLMultiplicativeAdditive()))
    end
    return ResultArray
end
=#

#TODO: This algorithm is same with the above one, not accurate!!!
#This algorithm is not so good, still more to do
function fracdiff(f::Union{Function, Number}, α::Float64, end_point, h::Float64, ::GLLagrangeThreePointInterp)::Float64
    #checks(f, α, 0, end_point)
    typeof(f) <: Number ? (end_point == 0 ? 0 : f/sqrt(pi*end_point)) : nothing
    n = round(Int, end_point/h)
    summation = zero(Float64)

    for i ∈ 0:n-1
        summation += gamma(i-α)/gamma(i+1)*(f(end_point-i*h)+1/4*α*(f(end_point-(i-1)*h)-f(end_point-(i+1)*h))+1/8*α^2*(f(end_point-(i-1)*h)-2*f(end_point-i*h)+f(end_point-(i+1)*h)))
    end

    result = summation*end_point^(-α)*n^α/gamma(-α)

    return result
end
#=
function fracdiff(f::Union{Number, Function}, α::Float64, end_point::AbstractArray, h, ::GLLagrangeThreePointInterp)::Vector
    ResultArray = Float64[]
    for (_, value) in enumerate(end_point)
        append!(ResultArray, fracdiff(f, α, value, h, GLLagrangeThreePointInterp()))
    end
    return ResultArray
end
=#

function fracdiff(f::Union{Number, Function}, α::Float64, end_point, h::Float64, ::GLFiniteDifference)::Float64
    typeof(f) <: Number ? (end_point == 0 ? 0 : f/sqrt(pi*end_point)) : nothing
    n = round(Int, end_point/h)
    result = zero(Float64)

    @fastmath @simd for i = 0:n
        result += (-1)^i/(gamma(i+1)*gamma(α-i+1))*f(end_point-i*h)
    end

    result1 = result*h^(-α)*gamma(α+1)
    return result1
end
#=
function fracdiff(f::Union{Function, Number}, α::AbstractArray, end_point, h, ::GLFiniteDifference)::Vector
    ResultArray = Float64[]

    for (_, value) in enumerate(end_point)
        append!(ResultArray, fracint(f, α, value, h, GLFiniteDifference()))
    end
    return ResultArray
end
=#

function fracdiff(f, α, t, p, ::GLHighPrecision)
    if isa(f, Function)
        y=f.(t)
    elseif isa(f, Vector)
        y=f[:]
    end
    h = t[2] - t[1]

    t = t[:]
    n = length(t)
    u = 0
    du = 0
    r = collect(0:p)*h
    R = reverse(Vandermonde(r), dims=2)
    c = inv(R)*y[1:p+1]
    for i = 1:p+1
        u = u.+c[i]*t.^(i-1)
        du = du.+c[i]*t.^(i-1-α)*gamma(i)/gamma(i-α)
    end

    v = y.-u
    g = genfun(p)
    w = getvec(α, n, g)
    dv = zeros(n)
    for i=1:n
        dv[i] = w[1:i]'*v[i:-1:1]/h^α
    end

    dy = dv .+ du
    if abs(y[1])<1e-10
        dy[1]=0
    end
    return dy
end

"""
P-th precision polynomial generate function

```math
g_p(z)=\\sum_{k=1}^p \\frac{1}{k}(1-z)^k
```
"""
function genfun(p)
    a = collect(1:p+1)
    A = Vandermonde(a)'
    return (1 .-a')*inv(A')
end

function getvec(α, n, g)
    p = length(g)-1
    b = 1 + α
    g0 = g[1]
    w = Float64[]
    push!(w, g[1]^α)

    for m = 2:p
        M = m-1
        dA = b/M
        temp = (-(g[2:m] .*collect((1-dA):-dA:(1-b))))' *w[M:-1:1]/g0
        push!(w, temp)
    end

    for k = p+1:n
        M = k-1
        dA = b/M
        temp = (-(g[2:(p+1)] .*collect((1-dA):-dA:(1-p*dA))))' *w[M:-1:(k-p)]/g0
        push!(w, temp)
    end

    return w
end


"""
    setcompare(command::String, logicset::LogicalCombo; verbose=true)

Provides a mechanism for checking if a set made up of possible values a variable can take is a subset of another set.

#### Operatators
In the repl this function is called by the use of set operators.

* x ⊂ y: checks if the values x can take are all contained within the set y
**Alternatively:** x subset y

* x !⊂ y: checks if one or more of the values x can take are not within the set y
**Alternatively:** x notsubset y

* x ⊃ y: checks if the values y can take are all contained within the set x
**Alternatively:** x superset y

* x !⊃ y: checks if one or more of the values y can take are not within the set x
**Alternatively:** x notsuperset y

* x ⋂ y: checks if set x intersects set y, that is if any values of x are in y
**Alternatively:** x intersect y

* x ⋔ y:  checks if set x is disjoint from set y, that is no values of x are in y
**Alternatively:** x notintersect y, x !⋂ y, x disjoint y

#### Set Unions and Intersections
The left and right hand side of the set operator can also be constructed from
multiple sets joined together with either the `|` (or ,) operator or using the intersetion
of the set using the `&` operator.

#### Examples - API
```
julia> logicset = logicalparse("a, b, c ∈ 1:4; a > b", verbose=false)

julia> setcompare("a ⊂ c", logicset)
  a   ⊂    c    result
––––– – ––––––– ––––––
2,3,4 ⊂ 1,2,3,4  true
true

julia> setcompare("a !⊃ c", logicset)
  a   !⊃    c    result
––––– –– ––––––– ––––––
2,3,4 !⊃ 1,2,3,4  true
true
```
#### Examples - REPL
```
abstractlogic> a, b, c ∈ 1:4
a, b, c ∈ 1:4            Feasible Outcomes: 64   Perceived Outcomes: 64 ✓        :1 1 4

abstractlogic> a > b
a > b                    Feasible Outcomes: 24   Perceived Outcomes: 36 ✓        :3 2 2

abstractlogic> a ⊂ c
  a   ⊂    c    result
––––– – ––––––– ––––––
2,3,4 ⊂ 1,2,3,4  true

abstractlogic> a !⊃ c
  a   !⊃    c    result
––––– –– ––––––– ––––––
2,3,4 !⊃ 1,2,3,4  true

abstractlogic> a ⋂ b
  a   ⋂   b   result
––––– – ––––– ––––––
2,3,4 ⋂ 1,2,3  true

abstractlogic> a | b superset c
 a | b  superset    c    result
––––––– –––––––– ––––––– ––––––
2,3,4,1 superset 1,2,3,4  true

abstractlogic> a & b ⊃ c
a & b ⊃    c    result
––––– – ––––––– ––––––
 2,3  ⊃ 1,2,3,4 false
```
"""
function setcompare(command::String, logicset::LogicalCombo; verbose=true)

    m = match(r"^(.*?)([⊂⊃⊅⊄⋂⋔!]{1,2}|(notsubset|notsuperset|notintersect|disjoint|subset|superset|intersect))(.*?)$",command)
    left, operator, right  = m.captures[[1,2,4]] .|> strip

    ranges = range(logicset)

    occursin(r"(&.*\|)|(\|.*&)", left) && thow("\n | and & cannot both appear left of operator!")
    if occursin("&", left)
        leftrange = intersect([ranges[Symbol(v)] for v in strip.(split(left, r",|&"))]...)
    else
        leftrange = union([ranges[Symbol(v)] for v in strip.(split(left, r",|\|"))]...)
    end

    occursin(r"(&.*\|)|(\|.*&)", right) && thow("\n | and & cannot both appear right of operator!")
    if occursin("&", right)
        rightrange = intersect([ranges[Symbol(v)] for v in strip.(split(right, r",|&"))]...)
    else
        rightrange = union([ranges[Symbol(v)] for v in strip.(split(right, r",|\|"))]...)
    end

    ⊂(x,y) = all([v ∈ y for v in x]) # subset of
    ⋲(x,y) = any([v ∈ y for v in x]) # intersects with

   (operator ∈ ["⊂", "subset"]) && (result =  (leftrange ⊂ rightrange))
   (operator ∈ ["⊄", "!⊂", "notsubset"]) && (result = !(leftrange ⊂ rightrange))
   (operator ∈ ["⊃", "superset"]) && (result =  (rightrange ⊂ leftrange))
   (operator ∈ ["⊅", "!⊃", "notsubset"]) && (result = !(rightrange ⊂ leftrange))
   (operator ∈ ["⋂", "intersect"]) && (result =  (leftrange ⋲ rightrange))
   (operator ∈ ["!⋂", "⋔", "notintersect"]) && (result = !(leftrange ⋲ rightrange))

   leftjoin  = join(leftrange, ",")
   rightjoin = join(rightrange, ",")
   excaped(x) = replace(x, "|"=>"\\|")

   if verbose
       txtout = "| " * join([excaped.(left),operator,excaped.(right),"result"], " | ") * " | \n"
       txtout *= "| " * join(fill(":---:", 4), " | ") * " | \n"
       txtout *= "| " * join([leftjoin,operator,rightjoin,result], " | ") * " | \n"
       printmarkdown(txtout)
       println()
   end

   return result
end


using Distributions
using Iterators
reload("src/nsum.jl")

#@show closest_grid_loc([1,2,3], 30)
#@show closest_grid_loc([1,15,25], 30)
#@show closest_grid_loc([60,59,61,32], 30)

function gen_grid(mins, maxs, N=30)
    D = length(mins)
    stepsizes = ceil(float(maxs .- mins) ./N)
    ranges = [mins[i]:stepsizes[i]:maxs[i] for i=1:D]
    grid = hcat(map(collect, Iterators.product(ranges...))...)'
    return map(length,ranges), stepsizes, grid
end

function gen_unit_grid(mins, maxs)
    D = length(mins)
    ranges = [mins[i]:maxs[i] for i=1:D]
    hcat(map(collect, Iterators.product(ranges...))...)'
end

function f(x, vals)
    global iters,evals
    iters += 1
    evals += length(vals)
    pdfs = pdf(Poisson(rate), floor(vec(x)))
    vals[:] = vec(prod(reshape(pdfs,size(x)...), 1))
end

function ftree(x)
    #println("Computing fvals for $(x[1])")
    global iters,evals
    iters += 1
    evals += length(x)
    for eval in x
        pt = eval.location
        vals = eval.vals
        assert(length(vals) == 0)
        pdfs = pdf(Poisson(rate), pt)
        push!(vals, prod(pdfs))
    end
end

function ftree_skinny(x)
    #println("Computing fvals for $(x[1])")
    global iters,evals
    iters += 1
    evals += length(x)
    for eval in x
        pt = eval.location
        vals = eval.vals
        assert(length(vals) == 0)
        val = 1.0
        val *= pdf(Poisson(0.01), pt[1])
        for v in pt[2:end]
            val *= pdf(Poisson(rate), v)
        end
        push!(vals, val)
    end
end

feasy(x) = (v = zeros(size(x)...); f(x,v); sum(v))

rate = 90000
iters = 0
evals = 0

upp = 130000
D = 2

lens = [2,16]
r = NSum.RegionTree(lens)
NSum.cut!(r)

println("####################")
ndivs=1000
xs=linspace(0,upp,ndivs)
spacing=upp/ndivs
est = spacing*sum(map(x->pdf(Poisson(rate), ifloor(x)), xs))
println("Estimate using $ndivs ndivs is $est")

println("####################")
@time tot1,r = NSum.nsum(1, ftree, zeros(D).+upp, abstol=0.03, maxevals=50)
@show tot1
println("With $iters iters and $evals fun evals")

println("####################")
iters = 0
evals = 0
@time tot2,r = NSum.nsum(1, ftree_skinny, zeros(D).+upp, abstol=0.03, maxevals=50)
@show tot2
println("With $iters iters and $evals fun evals")

#println("####################")
#iters = 0
#evals = 0
#@time tot,r = IntSum.nsum(ftree, zeros(D).+upp)
#@show tot
#println("With $iters iters and $evals fun evals")

#allnodes = collect(r)

#using PyPlot
#close("all")
#figure()
#for n in allnodes
    #if length(n.vals)!=0
        #plot(n.mins[1], n.mins[2], "g.")
    #end
#end
#lens, steps, grid = gen_grid(zeros(D), zeros(D).+upp.-1, 50)

#vals = zeros(size(grid,1))
#f(grid',vals)

#imshow(reshape(vals,lens...), extent=[0,upp,0,upp], origin="lower")

## NOTE TO SELF: trying to get above code to work to verify that my new
# find uneven branch code with midpoint is working

#using Cubature
#println("####################")
#@time tot1 = hcubature_v(f, zeros(D), zeros(D).+upp, abstol=0.01)
#iters = 0
#evals = 0
#@time tot2 = hcubature_v(f, zeros(D), zeros(D).+upp, abstol=0.01)
#@show tot1, tot2
#println("With $iters iters and $evals fun evals")

#iters = 0
#evals = 0
#lens, steps, grid = gen_grid(zeros(D).+100, zeros(D).+140, 20)
#vals = Array(Float64, size(grid,1))
#f(grid',vals)
#@show steps
#@show sum(vals)


using AdditionalUnits
using Test
using Unitful: Unitful, @u_str, J, W, hr

@testset "AdditionalUnits.jl" begin
    @testset "Power Related Unit" begin
        @test 1u"Wh" == 1u"W*hr"
    end
end


using CUDAnative
CUDAnative.device!(0)
using CuArrays
using LinearAlgebra
CuArrays.allowscalar(false)  # important to prevent element-wise operation to GPU Arrays directly.
using BackwardsLinalg
using BackwardsLinalg: svd_back, qr_back, symeigen_back, trtrs!, mpow2
using Test, Random

@testset "cuda qr" begin
    Random.seed!(7)
    BackwardsLinalg.cuseed!(7)
    a = randn(ComplexF64, 5, 5) |> CuArray
    @test Matrix(copyltu!(copy(a))) ≈ copyltu!(Matrix(a))
    for T in [Float32, ComplexF32, Float64, ComplexF64]
        for sz in [(2,4), (4,2), (4,4)]
            a = CuArray(randn(T, sz...))
            Q, R = qr(a)
            @test Matrix(Q'*Q) ≈ I
            @test Matrix(Q*R) ≈ Matrix(a)
            @test Matrix(a) ≈ Matrix(a)
            dQ, dR = curand(T, size(Q)...), curand(T, size(R)...)
            @test Matrix(qr_back(a, Q, R, dQ, dR)) ≈ qr_back(Matrix(a), Matrix(Q), Matrix(R), Matrix(dQ), Matrix(dR))
        end
    end
end

@testset "cuda svd" begin
    Random.seed!(5)
    BackwardsLinalg.cuseed!(5)
    for T in [Float32, ComplexF32, Float64, ComplexF64]
        for sz in [(2,4), (4,2), (4,4)]
            a = randn(T, sz...) |> CuArray
            U, S, V = svd(a)
            @test Matrix(U * Diagonal(S) * V') ≈ Matrix(a)
            dU, dS, dV = curand(T, size(U)...), curand(T, size(S)...), curand(T, size(V)...)
            @test Matrix(svd_back(U, S, V, dU, dS, dV)) ≈ svd_back(Matrix(U), Vector(S), Matrix(V), Matrix(dU), Vector(dS), Matrix(dV))
        end
    end
end

@testset "cuda symeigen" begin
    Random.seed!(7)
    BackwardsLinalg.cuseed!(7)
    for T in [Float32, Float64, ComplexF32, ComplexF64]
        a = randn(T, 5,5) |> CuArray; a += a'
        E, U = symeigen(a)
        @test U * Diagonal(E) * U' ≈ a

        dE, dU = curand(T, size(E)...), curand(T, size(U)...)
        @test Matrix(symeigen_back(E, U, dE, dU; η=1e-20)) ≈ Matrix(symeigen_back(Vector(E), Matrix(U), Vector(dE), Matrix(dU)))
    end
end

@testset "mpow2, trtrs!" begin
    Random.seed!(3)
    for T in [Float32, Float64, ComplexF32, ComplexF64]
        bv = randn(T, 5) |> CuArray
        bm = randn(T, 5, 5) |> CuArray
        for b in [bv, bm]
            a = randn(T, 5,5) |> CuArray
            res = mpow2(a)
            @test Matrix(res) ≈ mpow2(Matrix(a))
            @test eltype(res) == T
            res = trtrs!('U', 'N', 'N', copy(a), copy(b))
            @test Array(res) ≈ trtrs!('U', 'N', 'N', Matrix(a), Array(b))
            @test eltype(res) == T
        end
    end
end


module HyperECNNs

using LinearAlgebra
using Quaternions
using Random
rng = MersenneTwister(1234);

### Bilinear form used in the paper!

function LambdaInner(U,x,Params)
    w = zeros(size(U,3),1)
    for i = 1:size(U,2)
        w = w + Params[i]*(U[:,i,:]'*x[:,i])
    end
    return w
end

### Some activation functions

function csign(x,K)
    z = x[:,1]+x[:,2]*im
    phase_quanta = (round.(K*(2*pi.+angle.(z))./(2*pi))).%K
    z = exp.(2.0*pi*phase_quanta*im/K)
    return hcat(real.(z),imag.(z))
end

function twincsign(x, Params = 16)
    c1 = csign(x[:,1:2],Params)
    c2 = csign(x[:,3:4],Params)
    return hcat(c1,c2)
end

function SplitSign(a, Params = nothing)
    x = zeros(size(a))
    for i = 1:size(a,3)
        x[:,:,i] = sign.(a[:,:,i])
    end
    return x
end

##########################################################################
### Generic Hypercomplex-Valued Networks
##########################################################################

function Sync(BilinearForm, BilinearFormParams, ActFunction, ActFunctionParams, U, xinput, alpha = 1, beta = 0, it_max = 1.e3)
    ### Exponential Hypercomplex RCNN
    Name = "Hypercomplex ECNN (Synchronous)"

    N = size(U,1)
    hyperN = size(U,2)
    tau = 1.e-6

    # Initialization
    x = copy(xinput)
    xold = copy(x)
    it = 0
    Error = 1+tau
    # Compute the weights;
    w = exp.(alpha*BilinearForm(U,x,BilinearFormParams).+beta);

    # Compute the energy
    Energy = zeros(1)
    Energy[1]=-sum(w)/alpha
    a = zeros(N,hyperN)
    while (Error>tau)&&(it<it_max)
        it = it+1

        # Compute the quaternion-valued activation potentials;
        for j = 1:hyperN
            a[:,j] = U[:,j,:]*w
        end

        # Compute the next state;
        x = ActFunction(a, ActFunctionParams)

        # Compute the weights;
        w = exp.(alpha*BilinearForm(U,x,BilinearFormParams).+beta);

        append!(Energy,-sum(w)/alpha)
        Error = norm(x-xold)
        xold = copy(x)
    end
    if it_max<=it
        println(Name," failed to converge in ",it_max," iterations.")
    end
    return x, Energy
end

function Seq(BilinearForm, BilinearFormParams, ActFunction, ActFunctionParams, U, xinput, alpha = 1, beta = 0, it_max = 1.e3)
    ### Exponential Hypercomplex RCNN
    Name = "Hypercomplex ECNN (Asynchronous)"

    N = size(U,1)
    hyperN = size(U,2)
    tau = 1.e-6

    # Initialization
    x = copy(xinput)
    xold = copy(x)
    it = 0
    Error = 1+tau
    Energy = zeros(1)
    EnergyAux = zeros(N)
    # Compute the weights;
    w = exp.(alpha*BilinearForm(U,x,BilinearFormParams).+beta);
    Energy[1]=-sum(w)/alpha
    a = zeros(1,hyperN)
    while (Error>tau)&&(it<it_max)
        it = it+1
        ind = randperm(rng, N)
        for i = 1:N
            # Compute the quaternion-valued activation potentials;
            for j = 1:hyperN
                a[1,j] = dot(U[ind[i],j,:],w)
            end

            # Compute the next state;
            x[ind[i],:] = ActFunction(a, ActFunctionParams)

            # Compute the weights;
            w = w.*exp.(alpha*BilinearForm(U[[ind[i],ind[i]],:,:],vcat(-xold[ind[i],:]',x[ind[i],:]'),BilinearFormParams));
            EnergyAux[i]=-sum(w)/alpha
        end
        append!(Energy,EnergyAux)
        Error = norm(x-xold)
        xold = copy(x)
    end
    if it_max<=it
        println(Name," failed to converge in ",it_max," iterations.")
    end
    return x, Energy
end

function Seq_slow(BilinearForm, BilinearFormParams, ActFunction, ActFunctionParams, U, xinput, alpha = 1, beta = 0, it_max = 1.e3)
    ### Exponential Hypercomplex RCNN
    Name = "Hypercomplex ECNN (Asynchronous)"

    N = size(U,1)
    hyperN = size(U,2)
    tau = 1.e-6

    # Initialization
    x = copy(xinput)
    xold = copy(x)
    it = 0
    Error = 1+tau
    Energy = zeros(1)
    EnergyAux = zeros(N)
    # Compute the weights;
    w = exp.(alpha*BilinearForm(U,x,BilinearFormParams).+beta);
    Energy[1]=-sum(w)/alpha
    a = zeros(1,hyperN)
    while (Error>tau)&&(it<it_max)
        it = it+1
        ind = randperm(rng, N)
        for i = 1:N
            # Compute the quaternion-valued activation potentials;
            for j = 1:hyperN
                a[1,j] = dot(U[ind[i],j,:],w)
            end

            # Compute the next state;
            x[ind[i],:] = ActFunction(a, ActFunctionParams)

            # Compute the weights;
            w = exp.(alpha*BilinearForm(U,x,BilinearFormParams).+beta);
            EnergyAux[i]=-sum(w)/alpha
        end
        append!(Energy,EnergyAux)
        Error = norm(x-xold)
        xold = copy(x)
    end
    if it_max<=it
        println(Name," failed to converge in ",it_max," iterations.")
    end
    return x, Energy
end

##########################################################################
### Unit Quaternion Networks
##########################################################################
function UnitQ_Sync(U, xinput, alpha = 1, beta=0, it_max = 1.e3)
    ### Exponential Unit Quaternion RCNN
    Name = "Unit Quaternion ECNN (Synchronous)"

    N = size(U,1)
    tau = 1.e-6

    # Initialization
    x = copy(xinput)
    xold = copy(x)
    it = 0
    Error = 1+tau

    # Compute the weights
    w = exp.(alpha*Array{Float64}(real((U'*x))).+beta);

    # Compute the energy
    Energy = zeros(Float64,(1,))
    Energy[1] = -sum(w)/alpha
    while (Error>tau)&&(it<it_max)
        it = it+1

        # Compute the next state
        a = U*w
        x = a ./ abs.(a)

        # Compute the weights and the energy
        w = exp.(alpha*Array{Float64}(real((U'*x))).+beta);
        append!(Energy,-sum(w)/alpha)

        Error = norm(x-xold)
        xold = copy(x)
    end
    if it_max<=it
        println(Name," failed to converge in ",it_max," iterations.")
    #else
    #    println(Name," converged in ",it," iterations using synchronous update.")
    end
    return x, Energy
end

function UnitQ_Seq(U, xinput, alpha = 1, beta=0, it_max = 1.e3)
    ### Exponential Unit Quaternion RCNN
    Name = "Unit Quaternion ECNN (Asynchronous)"

    N = size(U,1)
    tau = 1.e-6

    # Initialization
    x = copy(xinput)
    xold = copy(x)
    it = 0
    Error = 1+tau

    # Compute the weights;
    w = exp.(alpha*Array{Float64}(real((U'*x))).+beta);

    # Compute the energy
    Energy = zeros(Float64,(1,))
    EnergyAux = zeros(N)
    Energy[1]=-sum(w)/alpha
    while (Error>tau)&&(it<it_max)
        it = it+1
        ind = randperm(rng, N)
        for i = 1:N
            # Compute the quaternion-valued activation potentials;
            a = dot(conj(U[ind[i],:]),w)

            # Compute the next state;
            x[ind[i]] = a/abs(a)

            # Compute the weights;
            w = exp.(alpha*Array{Float64}(real((U'*x))).+beta);
            EnergyAux[i]=-sum(w)/alpha
        end
        append!(Energy,EnergyAux)
        Error = norm(x-xold)
        xold = copy(x)
    end
    if it_max<=it
        println(Name," failed to converge in ",it_max," iterations.")
    end
    return x, Energy
end

end