using Revise
using ApproxFun, LinearAlgebra, Parameters, Setfield

using PseudoArcLengthContinuation, Plots
const PALC = PseudoArcLengthContinuation

####################################################################################################
# specific methods for ApproxFun
import Base: eltype, similar, copyto!, length
import LinearAlgebra: mul!, rmul!, axpy!, axpby!, dot, norm

similar(x::ApproxFun.Fun, T) = (copy(x))
similar(x::ApproxFun.Fun) = copy(x)
mul!(w::ApproxFun.Fun, v::ApproxFun.Fun, α) = (w .= α * v)

eltype(x::ApproxFun.Fun) = eltype(x.coefficients)
length(x::ApproxFun.Fun) = length(x.coefficients)

dot(x::ApproxFun.Fun, y::ApproxFun.Fun) = sum(x * y)

# do not put y .= a .* x .+ y, this puts a lot of coefficients!
axpy!(a::Float64, x::ApproxFun.Fun, y::ApproxFun.Fun) = (y .= a * x + y)
axpby!(a::Float64, x::ApproxFun.Fun, b::Float64, y::ApproxFun.Fun) = (y .= a * x + b * y)
rmul!(y::ApproxFun.Fun, b::Float64) = (y.coefficients .*= b; y)
rmul!(y::ApproxFun.Fun, b::Bool) = b == true ? y : (y.coefficients .*= 0; y)

# copyto!(x::ApproxFun.Fun, y::ApproxFun.Fun) = ( copyto!(x.coefficients, y.coefficients);x)
copyto!(x::ApproxFun.Fun, y::ApproxFun.Fun) = ( (x.coefficients = copy(y.coefficients);x))

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

N(x; a = 0.5, b = 0.01) = 1 + (x + a * x^2) / (1 + b * x^2)
dN(x; a = 0.5, b = 0.01) = (1 - b * x^2 + 2 * a * x)/(1 + b * x^2)^2

function F_chan(u, p)
	@unpack alpha, beta = p
	return [Fun(u(0.), domain(u)) - beta,
			Fun(u(1.), domain(u)) - beta,
			Δ * u + alpha * N(u, b = beta)]
end

function dF_chan(u, v, p)
	@unpack alpha, beta = p
	return [Fun(v(0.), domain(u)),
			Fun(v(1.), domain(u)),
			Δ * v + alpha * dN(u, b = beta) * v]
end

function Jac_chan(u, p)
	@unpack alpha, beta = p
	return [Evaluation(u.space, 0.),
			Evaluation(u.space, 1.),
			Δ + alpha * dN(u, b = beta)]
end

function finalise_solution(z, tau, step, contResult)
	printstyled(color=:red,"--> AF length = ", (z, tau) .|> length ,"\n")
	chop!(z.u, 1e-14);chop!(tau.u, 1e-14)
	true
end

sol = Fun( x -> x * (1-x), Interval(0.0, 1.0))
const Δ = Derivative(sol.space, 2);
par_af = (alpha = 3., beta = 0.01)

optnew = NewtonPar(tol = 1e-12, verbose = true)
	out, _, flag = @time PALC.newton(
		F_chan, Jac_chan, sol, par_af, optnew, normN = x -> norm(x, Inf64))
	# Plots.plot(out, label="Solution")

optcont = ContinuationPar(dsmin = 0.001, dsmax = 0.05, ds= 0.01, pMax = 4.1, plotEveryNsteps = 10, newtonOptions = NewtonPar(tol = 1e-8, maxIter = 20, verbose = true), maxSteps = 300)

	br, _ = @time continuation(
		F_chan, Jac_chan, out, par_af, (@lens _.alpha), optcont;
		plot = true,
		plotSolution = (x, p; kwargs...) -> plot!(x; label = "l = $(length(x))", kwargs...),
		verbosity = 2,
		normC = x -> norm(x, Inf64))
####################################################################################################
# Example with deflation technique
deflationOp = DeflationOperator(2.0, (x, y) -> dot(x, y), 1.0, [out])
par_def = @set par_af.alpha = 3.3

optdef = setproperties(optnew; tol = 1e-9, maxIter = 1000)

solp = copy(out)
	solp.coefficients .*= (1 .+ 0.41*rand(length(solp.coefficients)))

plot(out);plot!(solp)

outdef1, _, flag = @time newton(
	F_chan, Jac_chan,
	solp, par_def,
	optdef, deflationOp)
	flag && push!(deflationOp, outdef1)

plot(deflationOp.roots)
####################################################################################################
# other dot product
# dot(x::ApproxFun.Fun, y::ApproxFun.Fun) = sum(x * y) * length(x) # gives 0.1

optcont = ContinuationPar(dsmin = 0.001, dsmax = 0.05, ds= 0.01, pMax = 4.1, plotEveryNsteps = 10, newtonOptions = NewtonPar(tol = 1e-8, maxIter = 20, verbose = true), maxSteps = 300, theta = 0.2)

	br, _ = @time continuation(
		F_chan, Jac_chan, out, par_af, (@lens _.alpha), optcont;
		dotPALC = (x, y) -> dot(x, y),
		plot = true,
		# finaliseSolution = finalise_solution,
		plotSolution = (x, p; kwargs...) -> plot!(x; label = "l = $(length(x))", kwargs...),
		verbosity = 2,
		# printsolution = x -> norm(x, Inf64),
		normC = x -> norm(x, Inf64))
####################################################################################################
# tangent predictor with Bordered system
br, _ = @time continuation(
	F_chan, Jac_chan, out, par_af, (@lens _.alpha), optcont,
	tangentAlgo = BorderedPred(),
	plot = true,
	finaliseSolution = finalise_solution,
	plotSolution = (x, p;kwargs...)-> plot!(x; label = "l = $(length(x))", kwargs...))
####################################################################################################
# tangent predictor with Bordered system
# optcont = @set optcont.newtonOptions.verbose = true
indfold = 2
outfold, _, flag = @time newtonFold(
		F_chan, Jac_chan,
		br, indfold, #index of the fold point
		par_af, (@lens _.alpha),
		optcont.newtonOptions)
	flag && printstyled(color=:red, "--> We found a Fold Point at α = ", outfold[end], ", β = 0.01, from ", br.bifpoint[indfold][3],"\n")
#################################################################################################### Continuation of the Fold Point using minimally augmented
indfold = 2

outfold, _, flag = @time newtonFold(
	(x, p) -> F_chan(x, p),
	(x, p) -> Jac_chan(x, p),
	(x, p) -> Jac_chan(x, p),
	br, indfold, #index of the fold point
	optcont.newtonOptions)


#=
Direct estimation of the probability of for a diffusion with X(0) = x₀ satisfying
X(T) ∈ B for the Muller potential.
=#

using Distributed
# nw = 4; # number of workers
# addprocs(nw);

using Statistics
using HypothesisTests
using Printf
@everywhere include("muller_setup.jl")
opts = MDOptions(n_iters=nΔt)

Random.seed!(100)
@everywhere n_trials = 10^4;
n_in_set = @sync @distributed (+) for j in 1:n_trials
    X = copy(x₀);
    sample_trajectory!(X, sampler, options=opts);
    f(X)
end
CI = confint(BinomialTest(n_in_set, n_trials));
@printf("95%% CI = (%g,%g)\n", CI[1], CI[2])


#! /usr/bin/julia

# Rosetta Code, Narcissistic decimal number

function isnarcissist{T<:Integer}(n::T, b::Int=10)
    -1 < n || return false
    d = digits(n, b)
    m = length(d)
    n == mapreduce((x)->x^m, +, d)
end

goal = 25
ncnt = 0
println("Finding the first ", goal, " Narcissistic numbers:")
for i in 0:typemax(1)
    isnarcissist(i) || continue
    ncnt += 1
    println(@sprintf "    %2d %7d" ncnt i)
    ncnt < goal || break
end


using NNPACK, Test, Random

Random.seed!(144)

@testset "activation" begin
    xs = rand(5,5)

    @test all(sum(softmax(xs), dims = 1) .≈ Float32(1))

    @test sum(softmax(vec(xs))) ≈ Float32(1)
end

@testset "conv2d" begin

    x = reshape(Float32[1:16;], 4, 4, 1, 1)
    w = reshape(Float32[1:9;], 3, 3, 1, 1)

    @test dropdims(conv(x, w, pad=1), dims=(3,4)) ≈ Float32.([
        29   99  207  263
        62  192  372  446
        83  237  417  485
        75  198  330  365])

    x = reshape(Float64[1:20;], 5, 4, 1, 1)
    w = reshape(Float64[1:4;], 2, 2, 1, 1)

    @test dropdims(conv(x, w), dims = (3,4)) ≈ Float32.([
        29  79 129;
        39  89 139;
        49  99 149;
        59 109 159
    ])

    @test_throws Exception dropdims(conv(x, w; stride=2), dims = (3,4)) ≈ Float32.([
        29 49;
        129 149
    ])

    @test dropdims(conv(x, w; pad=1), dims = (3,4)) ≈ Float32.([
        1    9   29   49   48;
        4   29   79  129  115;
        7   39   89  139  122;
        10  49   99  149  129;
        13  59  109  159  136;
        10  40   70  100   80
    ])

    @test size(∇conv_filter(reshape(rand(4,3), 4, 3, 1, 1), x, w)) == size(w)
    @test size(∇conv_data(reshape(rand(4,3), 4, 3, 1, 1), x, w)) == size(x)

end

@testset "maxpool2d" begin

	x = reshape(Float32[1:16;], 4, 4, 1, 1)

	@test dropdims(maxpool(x, (2,2)), dims = (3,4)) ≈ Float32.([
        6.0 14.0;
        8.0 16.0
    ])

    x = reshape(Float64[1:20;], 5, 4, 1, 1)

    @test_throws Exception dropdims(maxpool(x, (2,2)), dims = (3,4)) ≈ Float32.([7 17; 9 19])
    @test_throws Exception dropdims(maxpool(x, (2,2); stride=(2,2)), dims = (3,4)) ≈ Float32.([7 17; 9 19])
    @test_throws Exception dropdims(maxpool(x, (2,2); pad=(1,1)), dims = (3,4)) ≈ Float32.([
        1  11  16;
        3  13  18;
        5  15  20;
    ])

    x = reshape(Float64[1:16;], 4, 4, 1, 1)

    @test dropdims(maxpool(x, (2,2)), dims = (3,4)) == [6 14; 8 16]
    @test dropdims(maxpool(x, (2,2); stride=(2,2)), dims = (3,4)) == [6 14; 8 16]
    @test dropdims(maxpool(x, (3,3); stride=(1,1), pad=(1,1)), dims = (3,4)) == [
         6.0  10.0  14.0  14.0;
         7.0  11.0  15.0  15.0;
         8.0  12.0  16.0  16.0;
         8.0  12.0  16.0  16.0;
    ]

end


"""
This module provides tools to perform numerical integration on the sphere
for that, it provides different quadratures:

1. intSBazant21: Bazant quadrature 21 points
2. SlanSphere10k: Quadrature Slan 10000 points
3. intPpal: Quadrature with just 3 points in the ppal dir
"""
module SphereQuadratures

include("./Types.jl")
export SphereQuadraturervec

include("./Utils.jl")
export readquadrature

"""
This function computes the integral over the unit sphere of f(r1,r2,r3)
using the SphereQuadrature quad.

f: function of 3 director cosines f(r1,r2,r3)
quad: SphereQuadrature with fields r1v,r2v,r3v,wv
"""
function computeSphereQuad(f::Function, quad::SphereQuadraturervec)
    r1Vec = quad.r1Vec
    r2Vec = quad.r2Vec
    r3Vec = quad.r3Vec
    wVec = quad.wVec
    wsum = sum(wVec)
    Sintegral = (0.)*f(r1Vec[1], r1Vec[2], r1Vec[3])
    for i = 1 : length(r1Vec)
        r1 = r1Vec[i]
        r2 = r2Vec[i]
        r3 = r3Vec[i]
        w = wVec[i]/wsum
        Sintegral += f(r1,r2,r3)*w
    end
    return Sintegral
end

# Bazant Quadrature
BazantSphere21 = SphereQuadraturervec(readquadrature("BazantSphere21.txt", " ")...)

intSBazant21(f) = computeSphereQuad(f, BazantSphere21)

# Slan Quadrature 10k points

SlanSphere10k = SphereQuadraturervec(readquadrature("SlanSphere10k.txt", ";")...)

intSlan10k(f) = computeSphereQuad(f, SlanSphere10k)

# Ppal Quadrature

PpalQuadrature = SphereQuadraturervec(readquadrature("ppaldir.txt", " ")...)

intPpal(f) = computeSphereQuad(f, PpalQuadrature)

export computeSphereQuad, intSBazant21, intSlan10k, intPpal

export BazantSphere21, SlanSphere10k, PpalQuadrature

end #module


# Online MM algorithm via quadratic upper bound
# Majorizing function: f(u) + ∇f(u)'(Θ - u) + .5 * (Θ - u)'H(Θ - u)
# Q_t(Θ) = (Θ' * A * Θ) + (b' * Θ) + c
# Where:
#   A = (1 - γ) * A + γ * H
#   b = (1 - γ) * b + γ * (∇f(Θ) - H * Θ)

immutable MMQuadUpBound
    A::MatF
    b::VecF
end
function fit!(o::MMQuadUpBound, A, b, γ)
    smooth!(o.A, A, γ)
    smooth!(o.b, b, γ)
end
value(o::MMQuadUpBound) = o.A \ o.b


# This file contains code that was formerly a part of Julia. License is MIT: http://julialang.org/license

using SpecialFunctions
using Base.Test

const SF = SpecialFunctions

@testset "error functions" begin
    @test SF.erf(Float16(1)) ≈ 0.84270079294971486934
    @test SF.erf(1) ≈ 0.84270079294971486934
    @test SF.erfc(1) ≈ 0.15729920705028513066
    @test SF.erfc(Float16(1)) ≈ 0.15729920705028513066
    @test SF.erfcx(1) ≈ 0.42758357615580700442
    @test SF.erfcx(Float32(1)) ≈ 0.42758357615580700442
    @test SF.erfcx(Complex64(1)) ≈ 0.42758357615580700442
    @test SF.erfi(1) ≈ 1.6504257587975428760
    @test SF.erfinv(0.84270079294971486934) ≈ 1
    @test SF.erfcinv(0.15729920705028513066) ≈ 1
    @test SF.dawson(1) ≈ 0.53807950691276841914

    @test SF.erf(1+2im) ≈ -0.53664356577856503399-5.0491437034470346695im
    @test SF.erfc(1+2im) ≈ 1.5366435657785650340+5.0491437034470346695im
    @test SF.erfcx(1+2im) ≈ 0.14023958136627794370-0.22221344017989910261im
    @test SF.erfi(1+2im) ≈ -0.011259006028815025076+1.0036063427256517509im
    @test SF.dawson(1+2im) ≈ -13.388927316482919244-11.828715103889593303im

    for elty in [Float32,Float64]
        for x in logspace(-200, -0.01)
            @test isapprox(SF.erf(SF.erfinv(x)), x, atol=1e-12*x)
            @test isapprox(SF.erf(SF.erfinv(-x)), -x, atol=1e-12*x)
            @test isapprox(SF.erfc(SF.erfcinv(2*x)), 2*x, atol=1e-12*x)
            if x > 1e-20
                xf = Float32(x)
                @test isapprox(SF.erf(SF.erfinv(xf)), xf, atol=1e-5*xf)
                @test isapprox(SF.erf(SF.erfinv(-xf)), -xf, atol=1e-5*xf)
                @test isapprox(SF.erfc(SF.erfcinv(2xf)), 2xf, atol=1e-5*xf)
            end
        end
        @test SF.erfinv(one(elty)) == Inf
        @test SF.erfinv(-one(elty)) == -Inf
        @test_throws DomainError SF.erfinv(convert(elty,2.0))

        @test SF.erfcinv(zero(elty)) == Inf
        @test_throws DomainError SF.erfcinv(-one(elty))
    end

    @test SF.erfinv(one(Int)) == SF.erfinv(1.0)
    @test SF.erfcinv(one(Int)) == SF.erfcinv(1.0)
end

@testset "airy" begin
    @test_throws SF.AmosException SF.airyai(200im)
    @test_throws SF.AmosException SF.airybi(200)

    for T in [Float32, Float64, Complex64,Complex128]
        @test SF.airyai(T(1.8)) ≈ 0.0470362168668458052247
        @test SF.airyaiprime(T(1.8)) ≈ -0.0685247801186109345638
        @test SF.airybi(T(1.8)) ≈ 2.595869356743906290060
        @test SF.airybiprime(T(1.8)) ≈ 2.98554005084659907283
    end
    for T in [Complex64, Complex128]
        z = convert(T,1.8 + 1.0im)
        @test SF.airyaix(z) ≈ SF.airyai(z) * exp(2/3 * z * sqrt(z))
        @test SF.airyaiprimex(z) ≈ SF.airyaiprime(z) * exp(2/3 * z * sqrt(z))
        @test SF.airybix(z) ≈ SF.airybi(z) * exp(-abs(real(2/3 * z * sqrt(z))))
        @test SF.airybiprimex(z) ≈ SF.airybiprime(z) * exp(-abs(real(2/3 * z * sqrt(z))))
    end
    @test_throws MethodError SF.airyai(complex(big(1.0)))

    for x = -3:3
        @test SF.airyai(x) ≈ SF.airyai(complex(x))
        @test SF.airyaiprime(x) ≈ SF.airyaiprime(complex(x))
        @test SF.airybi(x) ≈ SF.airybi(complex(x))
        @test SF.airybiprime(x) ≈ SF.airybiprime(complex(x))
        if x >= 0
            @test SF.airyaix(x) ≈ SF.airyaix(complex(x))
            @test SF.airyaiprimex(x) ≈ SF.airyaiprimex(complex(x))
        else
            @test_throws DomainError SF.airyaix(x)
            @test_throws DomainError SF.airyaiprimex(x)
        end
        @test SF.airybix(x) ≈ SF.airybix(complex(x))
        @test SF.airybiprimex(x) ≈ SF.airybiprimex(complex(x))
    end
end

@testset "bessel functions" begin
    bessel_funcs = [(SF.bessely0, SF.bessely1, SF.bessely), (SF.besselj0, SF.besselj1, SF.besselj)]
    @testset "$z, $o" for (z, o, f) in bessel_funcs
        @test z(Float32(2.0)) ≈ z(Float64(2.0))
        @test o(Float32(2.0)) ≈ o(Float64(2.0))
        @test z(2) ≈ z(2.0)
        @test o(2) ≈ o(2.0)
        @test z(2.0 + im) ≈ f(0, 2.0 + im)
        @test o(2.0 + im) ≈ f(1, 2.0 + im)
    end
    @testset "besselj error throwing" begin
        @test_throws MethodError SF.besselj(1.2,big(1.0))
        @test_throws MethodError SF.besselj(1,complex(big(1.0)))
        @test_throws MethodError SF.besseljx(1,big(1.0))
        @test_throws MethodError SF.besseljx(1,complex(big(1.0)))
    end
    @testset "besselh" begin
        true_h133 = 0.30906272225525164362 - 0.53854161610503161800im
        @test SF.besselh(3,1,3) ≈ true_h133
        @test SF.besselh(-3,1,3) ≈ -true_h133
        @test SF.besselh(3,2,3) ≈ conj(true_h133)
        @test SF.besselh(-3,2,3) ≈ -conj(true_h133)
        @testset "Error throwing" begin
            @test_throws SF.AmosException SF.besselh(1,0)
            @test_throws MethodError SF.besselh(1,big(1.0))
            @test_throws MethodError SF.besselh(1,complex(big(1.0)))
            @test_throws MethodError SF.besselhx(1,big(1.0))
            @test_throws MethodError SF.besselhx(1,complex(big(1.0)))
        end
    end
    @testset "besseli" begin
        true_i33 = 0.95975362949600785698
        @test SF.besseli(3,3) ≈ true_i33
        @test SF.besseli(-3,3) ≈ true_i33
        @test SF.besseli(3,-3) ≈ -true_i33
        @test SF.besseli(-3,-3) ≈ -true_i33
        @test SF.besseli(Float32(-3),Complex64(-3,0)) ≈ -true_i33
        @testset "Error throwing" begin
            @test_throws SF.AmosException SF.besseli(1,1000)
            @test_throws DomainError SF.besseli(0.4,-1.0)
            @test_throws MethodError SF.besseli(1,big(1.0))
            @test_throws MethodError SF.besseli(1,complex(big(1.0)))
            @test_throws MethodError SF.besselix(1,big(1.0))
            @test_throws MethodError SF.besselix(1,complex(big(1.0)))
        end
    end
    @testset "besselj" begin
        @test SF.besselj(0,0) == 1
        for i = 1:5
            @test SF.besselj(i,0) == 0
            @test SF.besselj(-i,0) == 0
            @test SF.besselj(-i,Float32(0)) == 0
            @test SF.besselj(-i,Float32(0)) == 0
        end

        j33 = SF.besselj(3,3.)
        @test SF.besselj(3,3) == j33
        @test SF.besselj(-3,-3) == j33
        @test SF.besselj(-3,3) == -j33
        @test SF.besselj(3,-3) == -j33
        @test SF.besselj(3,3f0) ≈ j33
        @test SF.besselj(3,complex(3.)) ≈ j33
        @test SF.besselj(3,complex(3f0)) ≈ j33
        @test SF.besselj(3,complex(3)) ≈ j33

        j43 = SF.besselj(4,3.)
        @test SF.besselj(4,3) == j43
        @test SF.besselj(-4,-3) == j43
        @test SF.besselj(-4,3) == j43
        @test SF.besselj(4,-3) == j43
        @test SF.besselj(4,3f0) ≈ j43
        @test SF.besselj(4,complex(3.)) ≈ j43
        @test SF.besselj(4,complex(3f0)) ≈ j43
        @test SF.besselj(4,complex(3)) ≈ j43

        @test j33 ≈ 0.30906272225525164362
        @test j43 ≈ 0.13203418392461221033
        @test SF.besselj(0.1, complex(-0.4)) ≈ 0.820421842809028916 + 0.266571215948350899im
        @test SF.besselj(3.2, 1.3+0.6im) ≈ 0.01135309305831220201 + 0.03927719044393515275im
        @test SF.besselj(1, 3im) ≈ 3.953370217402609396im
        @test SF.besselj(1.0,3im) ≈ SF.besselj(1,3im)
        @testset "Error throwing" begin
            @test_throws DomainError    SF.besselj(0.1, -0.4)
            @test_throws SF.AmosException SF.besselj(20,1000im)
            @test_throws MethodError SF.besselj(big(1.0),3im)
        end
    end

    @testset "besselk" begin
        true_k33 = 0.12217037575718356792
        @test SF.besselk(3,3) ≈ true_k33
        @test SF.besselk(-3,3) ≈ true_k33
        true_k3m3 = -0.1221703757571835679 - 3.0151549516807985776im
        @test SF.besselk(3,complex(-3)) ≈ true_k3m3
        @test SF.besselk(-3,complex(-3)) ≈ true_k3m3
        # issue #6564
        @test SF.besselk(1.0,0.0) == Inf
        @testset "Error throwing" begin
            @test_throws SF.AmosException SF.besselk(200,0.01)
            @test_throws DomainError SF.besselk(3,-3)
            @test_throws MethodError SF.besselk(1,big(1.0))
            @test_throws MethodError SF.besselk(1,complex(big(1.0)))
            @test_throws MethodError SF.besselkx(1,big(1.0))
            @test_throws MethodError SF.besselkx(1,complex(big(1.0)))
        end
    end

    @testset "bessely" begin
        y33 = SF.bessely(3,3.)
        @test SF.bessely(3,3) == y33
        @test SF.bessely(3.,3.) == y33
        @test SF.bessely(3,Float32(3.)) ≈ y33
        @test SF.bessely(-3,3) ≈ -y33
        @test y33 ≈ -0.53854161610503161800
        @test SF.bessely(3,complex(-3)) ≈ 0.53854161610503161800 - 0.61812544451050328724im
        @testset "Error throwing" begin
            @test_throws SF.AmosException SF.bessely(200.5,0.1)
            @test_throws DomainError SF.bessely(3,-3)
            @test_throws DomainError SF.bessely(0.4,-1.0)
            @test_throws DomainError SF.bessely(0.4,Float32(-1.0))
            @test_throws DomainError SF.bessely(1,Float32(-1.0))
            @test_throws DomainError SF.bessely(Cint(3),Float32(-3.))
            @test_throws DomainError SF.bessely(Cint(3),Float64(-3.))

            @test_throws MethodError SF.bessely(1.2,big(1.0))
            @test_throws MethodError SF.bessely(1,complex(big(1.0)))
            @test_throws MethodError SF.besselyx(1,big(1.0))
            @test_throws MethodError SF.besselyx(1,complex(big(1.0)))
        end
    end

    @testset "besselhx" begin
        for elty in [Complex64,Complex128]
            z = convert(elty, 1.0 + 1.9im)
            @test SF.besselhx(1.0, 1, z) ≈ convert(elty,-0.5949634147786144 - 0.18451272807835967im)
            @test SF.besselhx(Float32(1.0), 1, z) ≈ convert(elty,-0.5949634147786144 - 0.18451272807835967im)
        end
        @testset "Error throwing" begin
            @test_throws MethodError SF.besselh(1,1,big(1.0))
            @test_throws MethodError SF.besselh(1,1,complex(big(1.0)))
            @test_throws MethodError SF.besselhx(1,1,big(1.0))
            @test_throws MethodError SF.besselhx(1,1,complex(big(1.0)))
        end
    end
    @testset "scaled bessel[ijky] and hankelh[12]" begin
        for x in (1.0, 0.0, -1.0), y in (1.0, 0.0, -1.0), nu in (1.0, 0.0, -1.0)
            z = Complex128(x + y * im)
            z == zero(z) || @test SF.hankelh1x(nu, z) ≈ SF.hankelh1(nu, z) * exp(-z * im)
            z == zero(z) || @test SF.hankelh2x(nu, z) ≈ SF.hankelh2(nu, z) * exp(z * im)
            (nu < 0 && z == zero(z)) || @test SF.besselix(nu, z) ≈ SF.besseli(nu, z) * exp(-abs(real(z)))
            (nu < 0 && z == zero(z)) || @test SF.besseljx(nu, z) ≈ SF.besselj(nu, z) * exp(-abs(imag(z)))
            z == zero(z) || @test SF.besselkx(nu, z) ≈ SF.besselk(nu, z) * exp(z)
            z == zero(z) || @test SF.besselyx(nu, z) ≈ SF.bessely(nu, z) * exp(-abs(imag(z)))
        end
        @test SF.besselkx(1, 0) == Inf
        @testset "Error throwing" begin
            @test_throws SF.AmosException SF.hankelh1x(1, 0)
            @test_throws SF.AmosException SF.hankelh2x(1, 0)
            @test_throws SF.AmosException SF.besselix(-1, 0)
            @test_throws SF.AmosException SF.besseljx(-1, 0)
            @test_throws SF.AmosException SF.besselyx(1, 0)
            @test_throws DomainError SF.besselix(0.4,-1.0)
            @test_throws DomainError SF.besseljx(0.4, -1.0)
            @test_throws DomainError SF.besselkx(0.4,-1.0)
            @test_throws DomainError SF.besselyx(0.4,-1.0)
        end
    end
    @testset "issue #6653" begin
        @testset "$f" for f in (SF.besselj,SF.bessely,SF.besseli,SF.besselk,SF.hankelh1,SF.hankelh2)
            @test f(0,1) ≈ f(0,Complex128(1))
            @test f(0,1) ≈ f(0,Complex64(1))
        end
    end
end

# useful test functions for relative error, which differ from isapprox
# in that relerrc separately looks at the real and imaginary parts
relerr(z, x) = z == x ? 0.0 : abs(z - x) / abs(x)
relerrc(z, x) = max(relerr(real(z),real(x)), relerr(imag(z),imag(x)))
≅(a,b) = relerrc(a,b) ≤ 1e-13

@testset "gamma and friends" begin
    @testset "digamma" begin
        @testset "$elty" for elty in (Float32, Float64)
            @test SF.digamma(convert(elty, 9)) ≈ convert(elty, 2.140641477955609996536345)
            @test SF.digamma(convert(elty, 2.5)) ≈ convert(elty, 0.7031566406452431872257)
            @test SF.digamma(convert(elty, 0.1)) ≈ convert(elty, -10.42375494041107679516822)
            @test SF.digamma(convert(elty, 7e-4)) ≈ convert(elty, -1429.147493371120205005198)
            @test SF.digamma(convert(elty, 7e-5)) ≈ convert(elty, -14286.29138623969227538398)
            @test SF.digamma(convert(elty, 7e-6)) ≈ convert(elty, -142857.7200612932791081972)
            @test SF.digamma(convert(elty, 2e-6)) ≈ convert(elty, -500000.5772123750382073831)
            @test SF.digamma(convert(elty, 1e-6)) ≈ convert(elty, -1000000.577214019968668068)
            @test SF.digamma(convert(elty, 7e-7)) ≈ convert(elty, -1428572.005785942019703646)
            @test SF.digamma(convert(elty, -0.5)) ≈ convert(elty, .03648997397857652055902367)
            @test SF.digamma(convert(elty, -1.1)) ≈ convert(elty,  10.15416395914385769902271)

            @test SF.digamma(convert(elty, 0.1)) ≈ convert(elty, -10.42375494041108)
            @test SF.digamma(convert(elty, 1/2)) ≈ convert(elty, -γ - log(4))
            @test SF.digamma(convert(elty, 1)) ≈ convert(elty, -γ)
            @test SF.digamma(convert(elty, 2)) ≈ convert(elty, 1 - γ)
            @test SF.digamma(convert(elty, 3)) ≈ convert(elty, 3/2 - γ)
            @test SF.digamma(convert(elty, 4)) ≈ convert(elty, 11/6 - γ)
            @test SF.digamma(convert(elty, 5)) ≈ convert(elty, 25/12 - γ)
            @test SF.digamma(convert(elty, 10)) ≈ convert(elty, 7129/2520 - γ)
        end
    end

    @testset "trigamma" begin
        @testset "$elty" for elty in (Float32, Float64)
            @test SF.trigamma(convert(elty, 0.1)) ≈ convert(elty, 101.433299150792758817)
            @test SF.trigamma(convert(elty, 0.1)) ≈ convert(elty, 101.433299150792758817)
            @test SF.trigamma(convert(elty, 1/2)) ≈ convert(elty, π^2/2)
            @test SF.trigamma(convert(elty, 1)) ≈ convert(elty, π^2/6)
            @test SF.trigamma(convert(elty, 2)) ≈ convert(elty, π^2/6 - 1)
            @test SF.trigamma(convert(elty, 3)) ≈ convert(elty, π^2/6 - 5/4)
            @test SF.trigamma(convert(elty, 4)) ≈ convert(elty, π^2/6 - 49/36)
            @test SF.trigamma(convert(elty, 5)) ≈ convert(elty, π^2/6 - 205/144)
            @test SF.trigamma(convert(elty, 10)) ≈ convert(elty, π^2/6 - 9778141/6350400)
        end
    end

    @testset "invdigamma" begin
        @testset "$elty" for elty in (Float32, Float64)
            for val in [0.001, 0.01, 0.1, 1.0, 10.0]
                @test abs(SF.invdigamma(SF.digamma(convert(elty, val))) - convert(elty, val)) < 1e-8
            end
        end
        @test abs(SF.invdigamma(2)) == abs(SF.invdigamma(2.))
    end

    @testset "polygamma" begin
        @test SF.polygamma(20, 7.) ≈ -4.644616027240543262561198814998587152547
        @test SF.polygamma(20, Float16(7.)) ≈ -4.644616027240543262561198814998587152547
    end

    @testset "eta" begin
        @test SF.eta(1) ≈ log(2)
        @test SF.eta(2) ≈ pi^2/12
        @test SF.eta(Float32(2)) ≈ SF.eta(2)
        @test SF.eta(Complex64(2)) ≈ SF.eta(2)
    end
end

@testset "zeta" begin
    @test SF.zeta(0) ≈ -0.5
    @test SF.zeta(2) ≈ pi^2/6
    @test SF.zeta(Complex64(2)) ≈ SF.zeta(2)
    @test SF.zeta(4) ≈ pi^4/90
    @test SF.zeta(1,Float16(2.)) ≈ SF.zeta(1,2.)
    @test SF.zeta(1.,Float16(2.)) ≈ SF.zeta(1,2.)
    @test SF.zeta(Float16(1.),Float16(2.)) ≈ SF.zeta(1,2.)
    @test isnan(SF.zeta(NaN))
    @test isnan(SF.zeta(1.0e0))
    @test isnan(SF.zeta(1.0f0))
    @test isnan(SF.zeta(complex(0,Inf)))
    @test isnan(SF.zeta(complex(-Inf,0)))
end

#(compared to Wolfram Alpha)
@testset "digamma, trigamma, polygamma & zeta" begin
    for x in -10.2:0.3456:50
        @test 1e-12 > relerr(SF.digamma(x+0im), SF.digamma(x))
    end
    @test SF.digamma(7+0im) ≅ 1.872784335098467139393487909917597568957840664060076401194232
    @test SF.digamma(7im) ≅ 1.94761433458434866917623737015561385331974500663251349960124 + 1.642224898223468048051567761191050945700191089100087841536im
    @test SF.digamma(-3.2+0.1im) ≅ 4.65022505497781398615943030397508454861261537905047116427511+2.32676364843128349629415011622322040021960602904363963042380im
    @test SF.trigamma(8+0im) ≅ 0.133137014694031425134546685920401606452509991909746283540546
    @test SF.trigamma(8im) ≅ -0.0078125000000000000029194973110119898029284994355721719150 - 0.12467345030312762782439017882063360876391046513966063947im
    @test SF.trigamma(-3.2+0.1im) ≅ 15.2073506449733631753218003030676132587307964766963426965699+15.7081038855113567966903832015076316497656334265029416039199im
    @test SF.polygamma(2, 8.1+0im) ≅ -0.01723882695611191078960494454602091934457319791968308929600
    @test SF.polygamma(30, 8.1+2im) ≅ -2722.8895150799704384107961215752996280795801958784600407589+6935.8508929338093162407666304759101854270641674671634631058im
    @test SF.polygamma(3, 2.1+1im) ≅ 0.00083328137020421819513475400319288216246978855356531898998-0.27776110819632285785222411186352713789967528250214937861im
    @test 1e-11 > relerr(SF.polygamma(3, -4.2 + 2im),-0.0037752884324358856340054736472407163991189965406070325067-0.018937868838708874282432870292420046797798431078848805822im)
    @test SF.polygamma(13, 5.2 - 2im) ≅ 0.08087519202975913804697004241042171828113370070289754772448-0.2300264043021038366901951197725318713469156789541415899307im
    @test 1e-11 > relerr(SF.polygamma(123, -47.2 + 0im), 5.7111648667225422758966364116222590509254011308116701029e291)
    @test SF.zeta(4.1+0.3im, -3.2+0.1im) ≅ -281.34474134962502296077659347175501181994490498591796647 + 286.55601240093672668066037366170168712249413003222992205im
    @test SF.zeta(4.1+0.3im, 3.2+0.1im) ≅ 0.0121197525131633219465301571139288562254218365173899270675-0.00687228692565614267981577154948499247518236888933925740902im
    @test SF.zeta(4.1, 3.2+0.1im) ≅ 0.0137637451187986846516125754047084829556100290057521276517-0.00152194599531628234517456529686769063828217532350810111482im
    @test 1e-12 > relerrc(SF.zeta(1.0001, -4.5e2+3.2im), 10003.765660925877260544923069342257387254716966134385170 - 0.31956240712464746491659767831985629577542514145649468090im)
    @test SF.zeta(3.1,-4.2) ≅ SF.zeta(3.1,-4.2+0im) ≅ 149.7591329008219102939352965761913107060718455168339040295
    @test 1e-15 > relerrc(SF.zeta(3.1+0im,-4.2), SF.zeta(3.1,-4.2+0im))
    @test SF.zeta(3.1,4.2) ≅ 0.029938344862645948405021260567725078588893266227472565010234
    @test SF.zeta(27, 3.1) ≅ 5.413318813037879056337862215066960774064332961282599376e-14
    @test SF.zeta(27, 2) ≅ 7.4507117898354294919810041706041194547190318825658299932e-9
    @test 1e-12 > relerr(SF.zeta(27, -105.3), 1.3113726525492708826840989036205762823329453315093955e14)
    @test SF.polygamma(4, -3.1+Inf*im) == SF.polygamma(4, 3.1+Inf*im) == 0
    @test SF.polygamma(4, -0.0) == Inf == -SF.polygamma(4, +0.0)
    @test SF.zeta(4, +0.0) == SF.zeta(4, -0.0) ≅ pi^4 / 90
    @test SF.zeta(5, +0.0) == SF.zeta(5, -0.0) ≅ 1.036927755143369926331365486457034168057080919501912811974
    @test SF.zeta(Inf, 1.) == 1
    @test SF.zeta(Inf, 2.) == 0
    @test isnan(SF.zeta(NaN, 1.))
    @test isa([SF.digamma(x) for x in [1.0]], Vector{Float64})
    @test isa([SF.trigamma(x) for x in [1.0]], Vector{Float64})
    @test isa([SF.polygamma(3,x) for x in [1.0]], Vector{Float64})
    @test SF.zeta(2 + 1im, -1.1) ≅ SF.zeta(2 + 1im, -1.1+0im) ≅ -64.580137707692178058665068045847533319237536295165484548 + 73.992688148809018073371913557697318846844796582012921247im
    @test SF.polygamma(3,5) ≈ SF.polygamma(3,5.)

    @test SF.zeta(-3.0, 7.0) ≅ -52919/120
    @test SF.zeta(-3.0, -7.0) ≅ 94081/120
    @test SF.zeta(-3.1, 7.2) ≅ -587.457736596403704429103489502662574345388906240906317350719
    @test SF.zeta(-3.1, -7.2) ≅ 1042.167459863862249173444363794330893294733001752715542569576
    @test SF.zeta(-3.1, 7.0) ≅ -518.431785723446831868686653718848680989961581500352503093748
    @test SF.zeta(-3.1, -7.0) ≅ 935.1284612957581823462429983411337864448020149908884596048161
    @test SF.zeta(-3.1-0.1im, 7.2) ≅ -579.29752287650299181119859268736614017824853865655709516268 - 96.551907752211554484321948972741033127192063648337407683877im
    @test SF.zeta(-3.1-0.1im, -7.2) ≅ 1025.17607931184231774568797674684390615895201417983173984531 + 185.732454778663400767583204948796029540252923367115805842138im
    @test SF.zeta(-3.1-0.1im, 7.2 + 0.1im) ≅ -571.66133526455569807299410569274606007165253039948889085762 - 131.86744836357808604785415199791875369679879576524477540653im
    @test SF.zeta(-3.1-0.1im, -7.2 + 0.1im) ≅ 1035.35760409421020754141207226034191979220047873089445768189 + 130.905870774271320475424492384335798304480814695778053731045im
    @test SF.zeta(-3.1-0.1im, -7.0 + 0.1im) ≅ 929.546530292101383210555114424269079830017210969572819344670 + 113.646687807533854478778193456684618838875194573742062527301im
    @test SF.zeta(-3.1, 7.2 + 0.1im) ≅ -586.61801005507638387063781112254388285799318636946559637115 - 36.148831292706044180986261734913443701649622026758378669700im
    @test SF.zeta(-3.1, -7.2 + 0.1im) ≅ 1041.04241628770682295952302478199641560576378326778432301623 - 55.7154858634145071137760301929537184886497572057171143541058im
    @test SF.zeta(-13.4, 4.1) ≅ -3.860040842156185186414774125656116135638705768861917e6
    @test SF.zeta(3.2, -4) ≅ 2.317164896026427640718298719837102378116771112128525719078
    @test SF.zeta(3.2, 0) ≅ 1.166773370984467020452550350896512026772734054324169010977
    @test SF.zeta(-3.2+0.1im, 0.0) ≅ SF.zeta(-3.2+0.1im, 0.0+0im) ≅ 0.0070547946138977701155565365569392198424378109226519905493 + 0.00076891821792430587745535285452496914239014050562476729610im
    @test SF.zeta(-3.2, 0.0) ≅ SF.zeta(-3.2, 0.0+0im) ≅ 0.007011972077091051091698102914884052994997144629191121056378

    @test 1e-14 > relerr(SF.eta(1+1e-9), 0.693147180719814213126976796937244130533478392539154928250926)
    @test 1e-14 > relerr(SF.eta(1+5e-3), 0.693945708117842473436705502427198307157819636785324430166786)
    @test 1e-13 > relerr(SF.eta(1+7.1e-3), 0.694280602623782381522315484518617968911346216413679911124758)
    @test 1e-13 > relerr(SF.eta(1+8.1e-3), 0.694439974969407464789106040237272613286958025383030083792151)
    @test 1e-13 > relerr(SF.eta(1 - 2.1e-3 + 2e-3 * im), 0.69281144248566007063525513903467244218447562492555491581+0.00032001240133205689782368277733081683574922990400416791019im)
    @test 1e-13 > relerr(SF.eta(1 + 5e-3 + 5e-3 * im), 0.69394652468453741050544512825906295778565788963009705146+0.00079771059614865948716292388790427833787298296229354721960im)
    @test 1e-12 > relerrc(SF.zeta(1e-3+1e-3im), -0.5009189365276307665899456585255302329444338284981610162-0.0009209468912269622649423786878087494828441941303691216750im)
    @test 1e-13 > relerrc(SF.zeta(1e-4 + 2e-4im), -0.5000918637469642920007659467492165281457662206388959645-0.0001838278317660822408234942825686513084009527096442173056im)

    # Issue #7169: (TODO: better accuracy should be possible?)
    @test 1e-9  > relerrc(SF.zeta(0 + 99.69im), 4.67192766128949471267133846066040655597942700322077493021802+3.89448062985266025394674304029984849370377607524207984092848im)
    @test 1e-12 > relerrc(SF.zeta(3 + 99.69im), 1.09996958148566565003471336713642736202442134876588828500-0.00948220959478852115901654819402390826992494044787958181148im)
    @test 1e-9  > relerrc(SF.zeta(-3 + 99.69im), 10332.6267578711852982128675093428012860119184786399673520976+13212.8641740351391796168658602382583730208014957452167440726im)
    @test 1e-13 > relerrc(SF.zeta(2 + 99.69im, 1.3), 0.41617652544777996034143623540420694985469543821307918291931-0.74199610821536326325073784018327392143031681111201859489991im)
end

@testset "vectorization of 2-arg functions" begin
    binary_math_functions = [
        SF.besselh, SF.hankelh1, SF.hankelh2, SF.hankelh1x, SF.hankelh2x,
        SF.besseli, SF.besselix, SF.besselj, SF.besseljx, SF.besselk, SF.besselkx, SF.bessely, SF.besselyx,
        SF.polygamma, SF.zeta
    ]
    @testset "$f" for f in binary_math_functions
        x = y = 2
        v = [f(x,y)]
        @test f.([x],y) == v
        @test f.(x,[y]) == v
        @test f.([x],[y]) == v
    end
end

@testset "MPFR" begin
    @testset "bessel functions" begin
        setprecision(53) do
            @test SF.besselj(4, BigFloat(2)) ≈ SF.besselj(4, 2.)
            @test SF.besselj0(BigFloat(2)) ≈ SF.besselj0(2.)
            @test SF.besselj1(BigFloat(2)) ≈ SF.besselj1(2.)
            @test SF.bessely(4, BigFloat(2)) ≈ SF.bessely(4, 2.)
            @test SF.bessely0(BigFloat(2)) ≈ SF.bessely0(2.)
            @test SF.bessely1(BigFloat(2)) ≈ SF.bessely1(2.)
        end
    end

    let err(z, x) = abs(z - x) / abs(x)
        @test 1e-60 > err(SF.eta(parse(BigFloat,"1.005")), parse(BigFloat,"0.693945708117842473436705502427198307157819636785324430166786"))
        @test 1e-60 > err(exp(SF.eta(big(1.0))), 2.0)
    end

    let a = parse(BigInt, "315135")
        @test typeof(SF.erf(a)) == BigFloat
        @test typeof(SF.erfc(a)) == BigFloat
    end
end

@testset "Base Julia issue #17474" begin
    @test SF.f64(complex(1f0,1f0)) === complex(1.0, 1.0)
    @test SF.f64(1f0) === 1.0

    @test typeof(SF.eta(big"2")) == BigFloat
    @test typeof(SF.zeta(big"2")) == BigFloat
    @test typeof(SF.digamma(big"2")) == BigFloat

    @test_throws MethodError SF.trigamma(big"2")
    @test_throws MethodError SF.trigamma(big"2.0")
    @test_throws MethodError SF.invdigamma(big"2")
    @test_throws MethodError SF.invdigamma(big"2.0")

    @test_throws MethodError SF.eta(Complex(big"2"))
    @test_throws MethodError SF.eta(Complex(big"2.0"))
    @test_throws MethodError SF.zeta(Complex(big"2"))
    @test_throws MethodError SF.zeta(Complex(big"2.0"))
    @test_throws MethodError SF.zeta(1.0,big"2")
    @test_throws MethodError SF.zeta(1.0,big"2.0")
    @test_throws MethodError SF.zeta(big"1.0",2.0)
    @test_throws MethodError SF.zeta(big"1",2.0)

    @test typeof(SF.polygamma(3, 0x2)) == Float64
    @test typeof(SF.polygamma(big"3", 2f0)) == Float32
    @test typeof(SF.zeta(1, 2.0)) == Float64
    @test typeof(SF.zeta(1, 2f0)) == Float64 # BitIntegers result in Float64 returns
    @test typeof(SF.zeta(2f0, complex(2f0,0f0))) == Complex{Float32}
    @test typeof(SF.zeta(complex(1,1), 2f0)) == Complex{Float64}
    @test typeof(SF.zeta(complex(1), 2.0)) == Complex{Float64}
end


@testset "gradient" begin

    vars = @variables x, y

    @testset "Single variable" begin
        f( (x, ) ) = x + 1
        g = ReversePropagation.gradient( f, (x,))
        @test g(3) == (4, (1,))

        f2( (x, ) ) = x^2 + x
        g2 = ReversePropagation.gradient( f2, (x,) )
        @test g2(3) == (12, (7,))
    end

    @testset "Two variables" begin
        h( (a, b) ) = (a + b) * (a + b)
        vars = @variables x, y
        ∇h = ReversePropagation.gradient( h, (x, y) )
        @test ∇h( (1, 2) ) == (9, (6, 6))

        h2( (a, b) ) = a^2 + b^2
        vars = @variables x, y
        ∇h2 = ReversePropagation.gradient( h2, (x, y) )
        @test ∇h2( (1, 2) ) == (5, (2, 4))
    end

end


using Test
using GeoParams

@testset "CreepLaw" begin

# This tests the MaterialParameters structure
CharUnits_GEO   =   GEO_units(viscosity=1e19, length=1000km);

# Define a linear viscous creep law ---------------------------------
x1      =   LinearViscous(η=1e18Pa*s)
@test x1.η.val == 1e18Pa*s

x1_ND   =   LinearViscous(η=1e18Pa*s)
@test  isDimensional(x1_ND)==true
Nondimensionalize!(x1_ND,CharUnits_GEO)                 # check that we can nondimensionalize all entries within the struct
@test  isDimensional(x1_ND)==false
@test x1_ND.η*1.0==0.1
Dimensionalize!(x1_ND,CharUnits_GEO)                    # check that we can dimensionalize it again
@test x1_ND.η.val==1e18Pa*s
Nondimensionalize!(x1_ND,CharUnits_GEO)

# perform a computation with the viscous creep laws

# Given stress
@test ComputeCreepLaw_EpsII(1e6Pa, x1, CreepLawVariables())==5e-13/s                # dimensional input
@test ComputeCreepLaw_EpsII(1e0, x1_ND, CreepLawVariables())==5.0                   # non-dimensional
@test ComputeCreepLaw_EpsII([1e0; 2.0], x1_ND, CreepLawVariables())==[5.0; 10.0]    # vector input

# Given strainrate
@test ComputeCreepLaw_TauII(1e-13/s, x1, CreepLawVariables())==1e18*2*1e-13Pa       # dimensional input
@test ComputeCreepLaw_TauII(1e0, x1_ND, CreepLawVariables())==0.2                   # non-dimensional
@test ComputeCreepLaw_TauII([1e0; 2.0], x1_ND, CreepLawVariables())==[0.2; 0.4]     # vector input
# -------------------------------------------------------------------

# -------------------------------------------------------------------
# Define powerlaw viscous rheology
x2  =   PowerlawViscous()
Nondimensionalize!(x2,CharUnits_GEO)
@test x2.ε0.val ==0.001                                 # powerlaw

# perform computations

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

#include("test_DislocationCreep.jl")

end


using StochasticDiffEq
using Bridge, StaticArrays
using BenchmarkTools, Test
#####

function lorenz!(t,u,du)
    @inbounds du[1] = 10.0(u[2]-u[1])
    @inbounds du[2] = u[1]*(28.0-u[3]) - u[2]
    @inbounds du[3] = u[1]*u[2] - (8/3)*u[3]
end

function σ_lorenz!(t,u,du)
    @inbounds du[1] = 3.0
    @inbounds du[2] = 3.0
    @inbounds du[3] = 3.0
end

n = 5000
T = (0.0, 10.0)
dt = (T[end]-T[1])/n
tt = collect(range(T[1], stop=T[2], length=n+1))
vx = [1.0,0.0,0.0]

prob_sde_lorenz = SDEProblem(lorenz!, σ_lorenz!, vx, T)
sol = StochasticDiffEq.solve(prob_sde_lorenz, StochasticDiffEq.EM(), dt=dt)

const SV = SVector{3, Float64}

mutable struct VLorenz <: ContinuousTimeProcess{Vector{Float64}}
end

Bridge.b(t, u, ::VLorenz) = [10.0(u[2]-u[1]), u[1]*(28.0-u[3]) - u[2], u[1]*u[2] - (8/3)*u[3]]
function Bridge.b!(t, u, du, ::VLorenz)
    @inbounds du[1] = 10.0(u[2]-u[1])
    @inbounds du[2] = u[1]*(28.0-u[3]) - u[2]
    @inbounds du[3] = u[1]*u[2] - (8/3)*u[3]
end
Bridge.σ(t, u, ::VLorenz) = 3.0I
function Bridge.σ!(t, u, dw, dm, ::VLorenz)
    @inbounds dm[1] = 3*dw[1]
    @inbounds dm[2] = 3*dw[2]
    @inbounds dm[3] = 3*dw[3]
end

W = sample(tt, Wiener{SV}())
VW = VSamplePath(tt, Bridge.mat(W.yy))
VX = VSamplePath(tt, copy(VW.yy))
VX2 = VSamplePath(tt, copy(VW.yy))
Bridge.solve!(Bridge.EulerMaruyama!(), VX, vx, VW, VLorenz())
Bridge.solve!(Bridge.EulerMaruyama(), VX2, vx, VW, VLorenz())

######

mutable struct Lorenz <: ContinuousTimeProcess{SV}
end

Bridge.b(s, u, ::Lorenz) = @SVector [10.0(u[2]-u[1]), u[1]*(28.0-u[3]) - u[2], u[1]*u[2] - (8/3)*u[3]           ]
Bridge.σ(t, u, ::Lorenz) = 3.0I

u = SVector{3}(vx)
X = Bridge.euler(u, W, Lorenz())
X2 = copy(X)
X3 = copy(X)

Bridge.solve!(Euler(), X, u, W, Lorenz())
Bridge.euler!(X2, u, W, Lorenz())
invoke(Bridge.solve!, Tuple{Bridge.EulerMaruyama,Any,SVector{3,Float64},Bridge.AbstractPath,Lorenz},
Bridge.EulerMaruyama(), X3, u, W, Lorenz())

@test maximum(abs.(VW.yy - Bridge.mat(W.yy))) < eps()

@test maximum(abs.(VX.yy - Bridge.mat(X.yy))) < eps()
@test maximum(abs.(VX.yy - VX2.yy)) < eps()
@test maximum(norm.(X.yy - X2.yy)) < eps()
@test maximum(norm.(X.yy - X3.yy)) < eps()

######

print("StochasticDiffEq.solve")
@btime StochasticDiffEq.solve(prob_sde_lorenz, StochasticDiffEq.EM(), dt=dt)
print("sample W")
@btime W = sample(tt, Wiener{SV}())
print("allocate X")
@btime X = copy(W)
print("euler! static")
@btime Bridge.euler!(X, u, W, Lorenz())
print("solve! static")
@btime Bridge.solve!(Bridge.EulerMaruyama(), X, u, W, Lorenz())
print("solve! static fallback")
@btime invoke(Bridge.solve!, Tuple{Bridge.EulerMaruyama,Any,SVector{3,Float64},Bridge.AbstractPath,Lorenz},
Bridge.EulerMaruyama(), X3, u, W, Lorenz())
print("solve! out of place fallback (don't do that)")
@btime Bridge.solve!(Bridge.EulerMaruyama(), VX, vx, VW, VLorenz())
print("solve! inplace")
@btime Bridge.solve!(Bridge.EulerMaruyama!(), VX, vx, VW, VLorenz())
;


# Functions determining local data ranges in the different pencil
# configurations.

function local_data_range(p, P, N)
    @assert 1 <= p <= P
    a = (N * (p - 1)) ÷ P + 1
    b = (N * p) ÷ P
    a:b
end

# "Complete" dimensions not specified in `dims` with ones.
# Example: if N = 5, dims = (2, 3) and vals = (42, 12), this returns
# (1, 42, 12, 1, 1).
function complete_dims(::Val{N}, dims::Dims{M}, vals::Dims{M}) where {N, M}
    @assert N >= M
    i = 0
    vals_all = ntuple(Val(N)) do n
        if findfirst(dims .== n) === nothing
            1  # this dimension is not included in `dims`, so we put a 1
        else
            i += 1
            vals[i]
        end
    end
    @assert i == M
    vals_all :: Dims{N}
end

# Get axes (array regions) owned by all processes in a given pencil
# configuration.
function get_axes_matrix(decomp_dims::Dims{M},
                         proc_dims::Dims{M},
                         size_global::Dims{N}) where {N, M}
    axes = Array{ArrayRegion{N}, M}(undef, proc_dims)

    # Number of processes in every direction, including those where
    # decomposition is not applied.
    procs = complete_dims(Val(N), decomp_dims, proc_dims)

    for I in CartesianIndices(proc_dims)
        coords = complete_dims(Val(N), decomp_dims, Tuple(I))
        axes[I] = local_data_range.(coords, procs, size_global)
    end

    axes
end


module day01

include("inputs.jl")
ex = inputs.ex_d1()
input = inputs.input_d1()

# Part 1
function sum_two_to_2020(input)
    for a in 1:length(input)-1, b in 1:a
        input[a] + input[b] == 2020 && return input[a] * input[b]
    end
end

@assert sum_two_to_2020(ex) == 514579
@info "Day 1, Part 1 answer: $(sum_two_to_2020(input))"

# Part 2
function sum_three_to_2020(input)
    local i = 0
    for a in 1:length(input)-1, b in 1:(a-1), c in 1:b
        input[a] + input[b] + input[c] == 2020 && return input[a]*input[b]*input[c]
    end
end

@assert sum_three_to_2020(ex) == 241861950
@info "Day 1, Part 2 answer: $(sum_three_to_2020(input))"

end


"""
    ObsFactorTest

Output type from the testobsfactor function. The fields of this type include: \n
    -aj: Test statistic defined in Bai, Ng (2006) equation 2. aj <= alpha implies
        the corresponding observed factor is close to spanning the latent factor
        space. aj --> 1 implies the corresponding observed factor is unlikely
        to span the latent factor space.
    -mj: Test statistic defined in Bai, Ng (2006) equation x. This test statistic
        is used to test the null hypothesis that the observed factor is an exact
        linear combination of the latent factor space.
    -mjpval: The p-value associated with the mj statistic.
    -r2: A heuristic metric of how well the observed factor fits the latent factor
        space. r2 --> 0 implies a very poor fit, r2 --> 1 implies a very good fit.
        Note that, heuristically, the aj and mj statistics tend to be very sensitive,
        ie observed factors that deviate from the latent factor space by a small
        amount of random noise can have aj very close to 1 and mjpval very close to 0.
        In contrast the r2 measure tends to be more stable.
"""
struct ObsFactorTest #aj, mj, mjpval, r2, all have one element for each observed factor
    aj::Vector{Float64}
    mj::Vector{Float64}
    mjpval::Vector{Float64}
    r2::Vector{Float64}
end

"""
    testobsfactor(x::Matrix{<:Number}, obsfac::Matrix{<:Number} ; kwargs) \n
    testobsfactor(x::Matrix{<:Number}, obsfac::Matrix{<:Number}, numfac::Int ; kwargs) \n

The function tests whether the observated factors in obsfac span the same space as the latent
factor space that is estimated from input dataset x. The testing methodology is described in
Bai, Ng (2006) Evaluating Latent and Observed Factors in Macroeconomics and Finance [Journal of
Econometrics]. \n

The number of factors is estimated using Bai, Ng (2002), or else the user can specify the number
of factors using a third argument. \n

Keyword arguments are as follows: \n
    - xcent::Bool=false <-- Set to true if x is already centred \n
    - covmethod::Symbol=:cshac <-- Choose the method for computing the asymptotic variance, corresponding
        to equations 4, 5, and 5 in the original paper. Use either (:eq4, :cshac) for equation 4,
        either (:eq5, :het) for equation 5, or either (:eq6, :iid) for equation 6. \n
    - alpha::Float64=0.05 <-- The significance level to use when computing statistics \n
    - standardize::Bool=true <-- Set to true to standardize x and obsfac to have unit variance \n

See ?ObsFactorTest for more information on the output type, and the meaning of the various
statistics it contains. \n
"""
function testobsfactor(x::Matrix{<:Number}, obsfac::Matrix{<:Number}, numfac::Int ;
                       xcent::Bool=false, covmethod::Symbol=:cshac, alpha::Float64=0.05,
                       standardize::Bool=true)::ObsFactorTest
    #Preliminaries
    (T, N) = (size(x, 1), size(x, 2))
    N < numfac && return ObsFactorTest()
    (T < 2 || N < 2) && return ObsFactorTest()
    !xcent && (x = x .- mean(x, 1))
    standardize && (x = x .* (1 ./ std(x, 1)))
    #Get covariance matrix
    xcov = (1 / (T*N)) * x * x'
    #Get eigenvalues and eigenvectors for largest numfac eigenvalues, and then get factor loadings and estimated factors
    ef = eigfact(Symmetric(xcov), size(xcov,1)-numfac+1:size(xcov,1))
    (eigvalvec, eigvecmat) = (flipdim(ef.values, 1), flipdim(ef.vectors, 2))
    estfac = sqrt(T) * eigvecmat #\tilde{F} from Bai, Ng (2006)  [T*numfac]
    facload = (1/T) * (x' * estfac) #\tilde{Lambda} from Bai, Ng, (2006) [N*numfac]
    ehat = x - estfac*facload' #\tilde{e} from Bai, Ng (2006) [T*N]
    #Get eigenvalue inverse matrix
    invcapvtilde = diagm(1 ./ eigvalvec) #[numfac*numfac]
    #Get gamma hat
    if covmethod == :cshac || covmethod == :eq4 #Bai, Ng (2006) equation 4
        nupper = Int(floor(min(sqrt(T), sqrt(N))))
        capgammahat = zeros(Float64, numfac, numfac)
        for i = 1:nupper
            for j = 1:nupper
                capgammahat += (1 / nupper) * facload[i, :] * facload[j, :]' * cgh_inner_sum(ehat, i, j)
            end
        end
    elseif covmethod == :het || covmethod == :eq5 #Bai, Ng (2006) equation 5
        capgammahatvec = [ zeros(Float64, numfac, numfac) for t = 1:T ]
        for t = 1:T
            for i =  1:N
                capgammahatvec[t] += ehat[t, i]^2 * facload[i, :] * facload[i, :]'
            end
            capgammahatvec[t] *= (1/N)
        end
    elseif covmethod == :iid || covmethod == :eq6 #Bai, Ng (2006) equation 6
        sigsqhat = (1/(T*N)) * sum(ehat.^2)
        capgammahat = (1/N) * sigsqhat * (facload' * facload) #[numfac*numfac]
    end
    #Loop over observed factors
    mjvec = Vector{Float64}(size(obsfac, 2))
    ajvec = Vector{Float64}(size(obsfac, 2))
    r2vec = Vector{Float64}(size(obsfac, 2))
    for k = 1:size(obsfac, 2)
        #Get gammahat (least squares of estfac on observed factor)
        capg = obsfac[:, k] - mean(view(obsfac, :, k))
        standardize && (capg = (1 / std(capg)) * capg)
        gammahat = estfac \ capg
        capghat = estfac * gammahat
        #Get estimated variance of least squares estfac estimate of observed factor (capg)
        if any(covmethod .== [:eq5, :het]) ; varhatcapghatvec = [ (1/N) * gammahat' * invcapvtilde * capgammahat * invcapvtilde *  gammahat for capgammahat in capgammahatvec ]
        else ; varhatcapghat = (1/N) * gammahat' * invcapvtilde * capgammahat * invcapvtilde *  gammahat
        end
        #Get tau statistic
        if any(covmethod .== [:eq5, :het]) ; tau = (capghat - capg) ./ sqrt.(varhatcapghatvec)
        else ; tau = (capghat - capg) / sqrt(varhatcapghat)
        end
        abstau = abs.(tau)
        #Get mj
        mjvec[k] = maximum(abstau)
        #Get aj
        ajvec[k] = mean(abstau .> quantile(Normal(0,1), 1 - (alpha/2)))
        #Get R2
        r2vec[k] = T*dot(gammahat, gammahat)*(1/dot(capg, capg))
        #r2vec[k] = var(capghat) / var(capg)
    end
    mjvec_pval = [ 1 - (2*cdf(Normal(0,1), mj) - 1)^T for mj in mjvec ]
    return ObsFactorTest(ajvec, mjvec, mjvec_pval, r2vec)
end
cgh_inner_sum(ehat::Matrix{Float64}, i::Int, j::Int)::Float64 = (1 / size(ehat, 1)) * dot(view(ehat, :, i), view(ehat, :, j))
function testobsfactor(x::Matrix{<:Number}, obsfac::Matrix{<:Number} ; kmax::Int=10,
                       xcent::Bool=false)::ObsFactorTest
    !xcent && (x = x .- mean(x, 1))
    numfac = numfactor(x, kmax=kmax, xcent=true)
    return testobsfactor(x, obsfac, avgnumfactor(numfac), xcent=true)
end


#    Copyright (c) 2021 J.A. Duffek
#    Copyright (c) 2000 D.M. Spink
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, see <http://www.gnu.org/licenses/>.

using Plots;

"""
    democirc()

Demonstration of a circle arcs in the x-y plane.

# Examples:
```julia
julia> democirc()
```
"""
function democirc()
plot(title = "NURBS construction of a 2D circle and arc.",
     framestyle=:box,
     legend = false);
for r in 1.0:9.0
  crv = nrbcirc(r,[0.0;0.0;0.0],deg2rad(45),deg2rad(315));
  nrbplot!(crv,50);
end # for r
current();
end # democirc


include("centrality.jl")

using Random
using StatsBase

function collect_data(dataset::String, k::Int64, exact::Bool)
    T = read_data_unweighted(dataset, k, exact)
    Tcc, lcc = largest_component(T)
    ntrials = 100
    z_evecs = zeros(Float64, Tcc.dimension, ntrials)
    convergences = zeros(Bool, ntrials)
    Random.seed!(1234)
    for trial = 1:ntrials
        x_init = rand(Tcc.dimension)
        x_init /= sum(x_init)
        evec, conv = Z_evec_dynsys(Tcc, 1e-5, 500, x_init)
        println("trial $trial converged = $conv")
        z_evecs[:, trial] = evec
        convergences[trial] = conv
    end

    save("results/$dataset-$k-random.jld2",
         Dict("z_evecs" => z_evecs,
              "conv"    => convergences))
end

function compute_similarity(dataset::String, k::Int64)
    data = load("results/$dataset-$k-random.jld2")
    z_evecs = data["z_evecs"]
    ntrials = size(z_evecs, 2)
    max_diff = 0.0
    min_cor  = 1.0
    for i = 1:ntrials
        zi = z_evecs[:, i]
        for j = (i + 1):ntrials
            zj = z_evecs[:, j]
            @show i, j, norm(zi - zj, 1)
            max_diff = max(max_diff, norm(zi - zj, 1))
            min_cor  = min(min_cor, corspearman(zi, zj))
        end
    end
    return max_diff, min_cor
end


# Test matrices for regularization methods from Hansen's
# Regularization toolbox

"""
Oscillating Matrix
==================
A matrix `A` is called oscillating if `A` is totally
    nonnegative and if there exists an integer `q > 0` such that
    `A^q` is totally positive.

*Input options:*

+ [type,] Σ: the singular vaule spectrum of the matrix.

+ [type,] dim, mode: `dim` is the dimension of the matrix.
        `mode = 1`: geometrically distributed singular values.
        `mode = 2`: arithmetrically distributed singular values.

+ [type,] dim: `mode = 1`.

*Groups:* ['symmetric','posdef', 'random', 'eigen']

*References:*

**Per Christian Hansen**, Test matrices for
    regularization methods. SIAM J. SCI. COMPUT Vol 16,
    No2, pp 506-512 (1995).
"""
function oscillate(Σ::Vector{T}) where T
    n = length(Σ)
    dv = rand(T, 1, n)[:] .+ eps(T)
    ev = rand(T, 1, n-1)[:] .+ eps(T)
    B = Bidiagonal(dv, ev, :U)
    U, S, V = svd(B)
    return U*Diagonal(Σ)*U'
end
function oscillate(::Type{T}, n::Integer, mode::Integer) where T
    κ = sqrt(1/eps(T))
    if mode == 1
        factor = κ^(-1/(n-1))
        Σ = factor.^[0:n-1;]
    elseif mode == 2
        Σ = ones(T, n) - T[0:n-1;]/(n-1)*(1 - 1/κ)
    else
        throw(ArgumentError("invalid mode value."))
    end
    return oscillate(Σ)
end
oscillate(::Type{T}, n::Integer) where T = oscillate(T, n, 2)
oscillate(args...) = oscillate(Float64, args...)
oscillate(::Type, args...) = throw(MethodError(oscillate, Tuple(args)))

struct RegProb{T}
    A::AbstractMatrix{T}  # matrix of interest
    b::AbstractVector{T}  # right-hand side
    x::AbstractVector{T}  # the solution to Ax = b
end

struct RegProbNoSolution{T}
    A::AbstractMatrix{T}  # matrix of interest
    b::AbstractVector{T}  # right-hand side
end

function show(io::IO, p::RegProb)
    println(io, "Test problems for Regularization Methods")
    println(io, "A:")
    display(p.A)
    println(io, "b:")
    display(p.b)
    println(io, "x:")
    display(p.x)
end

function show(io::IO, p::RegProbNoSolution)
    println(io, "Test problems for Regularization Methods with No Solution")
    println(io, "A:")
    display(p.A)
    println(io, "b:")
    display(p.b)
end

# The following test problems are derived from Per Christian Hansen's
# Regularization tools for MATLAB.
# http://www.imm.dtu.dk/~pcha/Regutools/
#
# BSD License
# Copyright (c) 2015, Per Christian Hansen
# All rights reserved.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#     * Redistributions of source code must retain the above copyright
#       notice, this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above copyright
#       notice, this list of conditions and the following disclaimer in
#       the documentation and/or other materials provided with the distribution
#     * Neither the name of the DTU Compute nor the names
#       of its contributors may be used to endorse or promote products derived
#       from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

"""
Computation of the Second Derivative
====================================
A classical test problem for regularization algorithms:
    This is a mildly ill-posed problem. It is a discretization
    of a first kind Fredholm integral equation whose kernel K
    is the Green's function for the second derivative.

*Input options:*

+ [type,] dim, example, [matrixonly]: the dimension of the
        matrix is `dim`.  One choose between between the following right-hand
        g and solution f:

        example = 1 gives g(s) = (s^3 - s)/6, f(t) = t.

        example = 2 gives g(s) = exp(s) + (1 -e)s - 1, f(t) = exp(t)

        example = 3 gives  g(s) = | (4s^3 - 3s)/24,  s < 0.5
                                  | (-4s^3 + 12s^2 - 9s + 1)/24, s>= 0.5
                           f(t) = | t, t < 0.5
                           f(t) = | 1- t, t >= 0.5.

        If `matrixonly = false`, the matrix A and vectors b and x in the linear system Ax = b will be
        generated (`matrixonly = true` by default).

+ [type,] dim, [matrixonly]: `example = 1`.

*Groups:* ["regprob"]

*References:*

**P. C. Hansen**, Regularization tools: A MATLAB pacakge for
    analysis and solution of discrete ill-posed problems.
    Numerical Algorithms, 6(1994), pp.1-35
"""
function deriv2(::Type{T}, n::Integer, example::Integer, matrixonly::Bool = true) where T
    h = convert(T, one(T)/n); sqh = sqrt(h)
    h32 = h*sqh; h2 = h^2; sqhi = one(T)/sqh
    t = 2/3; A = zeros(T, n, n)

    # compute A
    for i = 1:n
        A[i,i] = h2*((i^2 - i + 0.25)*h - (i - t))
        for j = 1:i-1
            A[i,j] = h2*(j - 0.5)*((i - 0.5)*h - 1)
        end
    end
    A = A + tril(A, -1)'

    if matrixonly
        return A
    else
        b = zeros(T, n)
        x = zeros(T, n)
        if example == 1
            # compute b
            [b[i] = h32*(i - 0.5)*((i^2 + (i-1)^2)*h2/2 - 1)/6 for i = 1:n]

            # compute x
            [x[i] = h32*(i - 0.5) for i = 1:n]
        elseif example == 2
            ee = one(T) - exp(one(T))
            [b[i] = sqhi*(exp(i*h) - exp((i-1)*h) + ee*(i-0.5)*h2 - h) for i = 1:n]
            [x[i] = sqhi*(exp(i*h) - exp((i-1)*h)) for i = 1:n]
        elseif example == 3
            mod(n, 2) == 0 || error("The order n must be even.")
            for i = 1:div(n,2)
                s12 = (i*h)^2; s22 = ((i-1)*h)^2
                b[i] = sqhi*(s12 + s22 - 1.5)*(s12 - s22)/24
            end
            for i = div(n, 2)+1:n
                s1 = i*h; s12 = s1^2; s2 = (i-1)*h; s22 = s2^2
                b[i] = sqhi*(-(s12 + s22)*(s12 - s22) + 4*(s1^3 - s2^3)
                       - 4.5*(s12 - s22) + h)/24
            end
            [x[i] = sqhi*((i*h)^2 - ((i-1)*h)^2)/2 for i = 1:div(n, 2)]
            [x[i] = sqhi*(h - ((i*h)^2 - ((i-1)*h)^2)/2) for i = div(n, 2)+1:n]
        else
            throw(ArgumentError("Illegal value of example."))
        end
        return RegProb(A, b, x)
    end
end
deriv2(::Type{T}, n::Integer, matrixonly::Bool = true) where T = deriv2(T, n, 1, matrixonly)
deriv2(args...) = deriv2(Float64, args...)
deriv2(::Type, args...) = throw(MethodError(deriv2, Tuple(args)))

"""
One-Dimensional Image Restoration Model
=======================================
This test problem uses a first-kind Fredholm integral equation
    to model a one-dimentional image restoration situation.

*Input options:*

+ [type,] dim, [matrixonly]: the dimesion of the matrix `dim` must be even.
    If `matrixonly = false`, the matrix A and vectors b and x in the linear system Ax = b will be generated
    (`matrixonly = true` by default).

*Groups:* ["regprob"]

*References:*

**C. B. Shaw, Jr.**, Improvements of the resolution of
    an instrument by numerical solution of an integral equation.
    J. Math. Ana. Appl. 37 (1972), 83-112.
"""
function shaw(::Type{T}, n::Integer, matrixonly::Bool = true) where T
    mod(n, 2) == 0 || error("The dimension of the matrix must be even.")
    h = π/n; A = zeros(T, n, n)

    # compute A
    co = cos.(-π/2 .+ T[.5:n-.5;] .* h)
    psi = π .* sin.(-π/2 .+ T[.5:n-.5;] .* h)
    for i in 1:div(n,2)
        for j in i:n-i
            ss = psi[i] +psi[j]
            A[i,j] = ((co[i] + co[j])*sin(ss)/ss)^2
            A[n-j+1, n-i+1] = A[i,j]
        end
        A[i, n-i+1] = (2*co[i])^2
    end
    A = A + triu(A, 1)'; A = A*h

    if matrixonly
        return A
    else
        # compute x and b
        a1 = 2
        c1 = 6
        t1 = .8
        a2 = 1
        c2 = 2
        t2 = -.5
        x = a1 .* exp.(-c1 .* (-π/2 .+ T[.5:n-.5;] .* h .- t1).^2) .+
            a2 .* exp.(-c2 .* (-π/2 .+ T[.5:n-.5;] .* h .- t2).^2)
        b = A*x
        return RegProb(A, b, x)
    end
end
shaw(args...) = shaw(Float64, args...)
shaw(::Type, args...) = throw(MethodError(shaw, Tuple(args)))

"""
A Problem with a Discontinuous Solution
=======================================

*Input options:*

+ [type,] dim, t1, t2, [matrixonly]: the dimension of matrix is `dim`.
    `t1` and `t2` are two real scalars such that `0 < t1 < t2 < 1`.
    If `matrixonly = false`, the matrix A and vectors b and x in the linear system Ax = b will be generated
    (`matrixonly = true` by default).

+ [type,] n, [matrixonly]: `t1 = 1/3` and `t2 = 2/3`.

*Groups:* ["regprob"]

*References:*

**G. M. Wing**, A Primer on Integral Equations of the
    First Kind, Society for Industrial and Applied Mathematics, 1991, p. 109.
"""
function wing(::Type{T}, n::Integer, t1::Real, t2::Real, matrixonly = true) where T
    t1 < t2 || error("t1 must be smaller than t2")
    A = zeros(T, n, n)
    h = T(1/n)

    # compute A
    sti = (T[1:n;] .- 0.5) * h
    for i in 1:n
        A[i,:] .= h .* sti .* exp.(-sti[i] .* sti.^2)
    end

    if matrixonly
        return A
    else
        # compute b
        b = sqrt(h) .* 0.5 .* (exp.(-sti .* t1^2) .- exp.(-sti .* t2^2))./sti

        # compute x
        indices = [findfirst(t1 .< sti):findlast(t2 .> sti);]
        x = zeros(T,n); x[indices] = sqrt(h)*ones(length(indices))
        return RegProb(A, b, x)
    end
end
wing(::Type{T}, n::Integer, matrixonly = true) where T = wing(T, n, 1/3, 2/3, matrixonly)
wing(args...) = wing(Float64, args...)
wing(::Type, args...) = throw(MethodError(wing, Tuple(args)))

"""
Severely Ill-posed Problem Suggested by Fox & Goodwin
=====================================================
This is a model problem discretized by simple quadrature, which does
not satifiy the discrete Picard condition for the small singular values.

*Input options:*

+ [type,] dim, [matrixonly]: `dim` is the dimension of the matrix.
    If `matrixonly = false`, the matrix A and vectors b and x in the linear system Ax = b will be generated
    (`matrixonly = true` by default).

*Groups:* ["regprob"]

*References:*

**C. T. H. Baker**, The Numerical Treatment of Integral
    Equations, Clarendon Press, Oxford, 1977, p. 665.
"""
function foxgood(::Type{T}, n::Integer, matrixonly = true) where T
    h = T(1/n)
    t = h*(T[1:n;] .- one(T)/2)

    A = h*sqrt.((t.^2)*ones(T,n)' + ones(T, n) * (t.^2)')

    if matrixonly
        return A
    else
        x = t
        b = zeros(T, n)
        for i in 1:n
            b[i] = ((one(T) + t[i]^2)^T(1.5) - t[i]^3)/3
        end
        return RegProb(A, b, x)
    end
end
foxgood(args...) = foxgood(Float64, args...)
foxgood(::Type, args...) = throw(MethodError(foxgood, Tuple(args)))

"""
Inverse Heat Equation
=====================

*Input options:*

+ [type,] dim, κ, [matrixonly]: `dim` is the dimension of the matrix and
    `dim` must be even. `κ` controls the ill-conditioning of the matrix.
    (`κ = 5` gives a well-conditioned problem and `κ = 1`
     gives an ill conditoned problem).
    If `matrixonly = false`, the matrix A and vectors b and x in the linear system Ax = b will be generated
    (`matrixonly = true` by default).

+ [type,] n, [matrixonly]: `κ = 1`.

*Groups:* ["regprob"]

*References:*

**A. S. Carasso**, Determining surface temperatures
    from interior observations, SIAM J. Appl. Math. 42 (1982), 558-574.
"""
function heat(::Type{T}, n::Integer, κ::Real, matrixonly::Bool = true) where T
    mod(n, 2) == 0 || error("The dimension of the matrix must be even.")
    h = one(T)/n; t = T[h/2:h:1;]
    c = h/(2*κ*sqrt(π))
    d = one(T)/(4*κ^2)

    # compute the matrix A
    m = length(t); k = zeros(T, m)
    [k[i] = c*t[i]^(-1.5)*exp(-d/t[i]) for i in 1:m]
    r = zeros(T, m); r[1] = k[1]
    A = toeplitz(T, k, r)

    if matrixonly
        return A
    else
        # compute the vectors x and b
        x = zeros(T, n)
        for i = 1:div(n,2)
            ti = i*20/n
            if ti < 2
                x[i] = 0.75*ti^2/4
            elseif ti < 3
                x[i] = 0.75 + (ti - 2)*(3 - ti)
            else
                x[i] = 0.75*exp(-(ti - 3)*2)
            end
        end
        x[div(n,2)+1:n] = zeros(T, div(n, 2))
        b = A*x
        return RegProb(A, b, x)
    end
end
heat(::Type{T}, n::Integer, matrixonly::Bool = true) where T = heat(T, n, 1, matrixonly)
heat(args...) = heat(Float64, args...)
heat(::Type, args...) = throw(MethodError(heat, Tuple(args)))

"""
Fredholm Integral Equation of the First Kind
============================================

*Input options:*

+ [type,] dim, [matrixonly]: the dimenstion of the matrix is `dim`.
    If `matrixonly = false`, the matrix A and vectors b and x in the linear system Ax = b will be generated
    (`matrixonly = true` by default).

*Groups:* ["regprob"]

*References:*

**M. L. Baart**, The use of auto-correlation for
    pesudo-rank determination in noisy ill-conditioned linear-squares
    problems, IMA, J. Numer. Anal. 2 (1982), 241-247.
"""
function baart(::Type{T}, n::Integer, matrixonly::Bool = true) where T
    mod(n, 2) == 0 || error("The dimension of the matrix must be even.")
    hs  = T(π/(2*n))
    ht  = T(π/n)
    c   = one(T)/(3*sqrt(2))
    A   = zeros(T, n, n)
    ihs = T[0:n;]*hs
    n1  = n + 1
    nh  = div(n,2)
    f3  = exp.(ihs[2:n1]) .- exp.(ihs[1:n])

    # compute A
    for j = 1:n
        f1  = f3
        co2 = cos((j - one(T)/2)*ht)
        co3 = cos(j*ht)
        f2 = (exp.(ihs[2:n1].*co2) .- exp.(ihs[1:n].*co2))./co2
        if j == nh
            f3 = hs*ones(T, n)
        else
            f3 = (exp.(ihs[2:n1].*co3) .- exp.(ihs[1:n].*co3))./co3
        end
        A[:,j] .= c.*(f1 .+ 4 .* f2 .+ f3)
    end

    if matrixonly
        return A
    else
        # compute vector b
        si      = T[.5:.5:n;] .* hs
        si      = sinh.(si) ./ si
        b       = zeros(T, n)
        b[1]    = 1 + 4 * si[1] + si[2]
        b[2:n] .= si[2:2:2*n-2] .+ 4 .* si[3:2:2*n-1] .+ si[4:2:2*n]
        b      .= b .* sqrt(hs) ./ 3

        # compute vector x
        x = -diff(cos.(T[0:n;] .* ht)) ./ sqrt(ht)
        return RegProb(A, b, x)
    end
end
baart(args...) = baart(Float64, args...)
baart(::Type, args...) = throw(MethodError(baart, Tuple(args)))

"""
Phillips's \"Famous\" Problem
=============================

*Input options:*

+ [type,] dim, [matrixonly]: the dimenstion of the matrix is `dim`.
    If `matrixonly = false`, the matrix A and vectors b and x in the linear system Ax = b will be generated
    (`matrixonly = true` by default).

*Groups:* ["regprob"]

*References:*

**D. L. Phillips**, A technique for the numerical
    solution of certain integral equations of the first kind, J. ACM
    9 (1962), 84-97.
"""
function phillips(::Type{T}, n::Integer, matrixonly::Bool = true) where T
    mod(n, 4) == 0 || error("The dimension of the matrix must be a multiple of 4.")

    # compute A
    h = 12/n
    n4 = div(n, 4)
    r1 = zeros(T,n)
    c = cos.(T[-1:n4; ] * 4 * π/n)
    for i in 1:n4
        r1[i] = h + 9 / (h * π^2) * (2 * c[i + 1] - c[i] - c[i + 2])
    end
    r1[n4 + 1] = h / 2 + 9 / (h * π^2) * (cos(4 * π / n) - 1)
    A = toeplitz(T, r1)

    if matrixonly
        return A
    else
        # compute the vector b
        b = zeros(T, n)
        c = π/3
        for i = (div(n,2) + 1):n
            t1 = -6 + i*h
            t2 = t1 - h
            b[i] = t1*(6-abs(t1)/2) +
                ((3 - abs(t1)/2)*sin(c*t1) - 2/c*(cos(c*t1) - one(T)))/c -
                t2*(6 - abs(t2)/2) -
                ((3 - abs(t2)/2)*sin(c*t2) - 2/c*(cos(c*t2) - one(T)))/c
            b[n - i + 1] = b[i]
        end
        b ./= sqrt(h)

        # compute x
        x = zeros(T, n)
        x[2n4+1:3n4] = (h .+ diff(sin.(T[0:h:(3 + 10 * eps(T));] * c)) / c) / sqrt(h)
        x[n4+1:2*n4] = x[3*n4:-1:2*n4+1]
        return RegProb(A, b, x)
    end
end
phillips(args...) = phillips(Float64, args...)
phillips(::Type, args...) = throw(MethodError(phillips, Tuple(args)))

# replicates the grid vectors xgv and ygv to produce a full grid.
function meshgrid(xgv, ygv)
    X = [i for j in ygv, i in xgv]
    Y = [j for j in ygv, i in xgv]
    return X, Y
end

# MATLAB rounding behavior
# This is equivalent to RoundNearestTiesAway
# and it can be used for both Julia v0.3, v0.4
function round_matlab(x::AbstractFloat)
    y = trunc(x)
    ifelse(x==y,y,trunc(2*x-y))
end
round_matlab(::Type{T}, x::AbstractFloat) where T<:Integer = trunc(T,round_matlab(x))

"""
One-dimensional Gravity Surverying Problem
==========================================
Discretization of a 1-D model problem in gravity surveying, in
    which a mass distribution f(t) is located at depth d, while the
    vertical component of the gravity field g(s) is measured at the
    surface.

*Input options:*

+ [type,] dim, example, a, b, d, [matrixonly]: `dim` is the dimension
        of the matrix. Three examples are implemented.

       (a) example = 1 gives f(t) = sin(π*t) + 0.5*sin(2*π*t).

       (b) example = 2 gives f(t) = piecewise linear function.

       (c) example = 3 gives f(t) = piecewise constant function.

       The t integration interval is fixed to [0, 1], while the s
       integration interval [a, b] can be specified by the user.
       The parameter d is the depth at which the magnetic deposit is
       located. The larger the d, the faster the decay of the singular values.

       If matrixonly = false, the matrix A and vectors b and x in the linear system Ax = b will be generated
       (matrixonly = true by default).

+ [type,] dim, example, [matrixonly]: `a = 0, b = 1, d = 0.25`;

+ [type,] dim, [matrixonly]: `example = 1, a = 0, b = 1, d = 0.25`.
*Groups:* ["regprob"]

*References:*

**G. M. Wing and J. D. Zahrt**, A Primer on Integral Equations of
the First Kind, Society for Industrial and Applied Mathematics, Philadelphia, 1991, p. 17.
"""
function gravity(::Type{T}, n::Integer, example::Integer,
                    a::Number, b::Number, d::Number, matrixonly::Bool = true) where T
    dt = one(T)/n
    a  = T(a)
    b  = T(b)
    d  = T(d)
    ds = (b - a)/n
    tv = dt .* (T[1:n;] .- one(T) ./ 2)
    sv = a .+ ds .* (T[1:n;] .- one(T) ./ 2)
    Tm, Sm = meshgrid(tv, sv)
    A = dt .* d .* ones(T, n, n) ./ (d^2 .+ (Sm .- Tm).^2).^T(3/2)
    if matrixonly
        return A
    else
        x = ones(T, n)
        nt = round_matlab(Int, n/3)
        nn = round_matlab(Int, n*7/8)
        if example == 1
            x .= sin.(π .* tv) .+ sin.(2 .* π .* tv) ./ 2
        elseif example == 2
            x[1:nt]        .= 2 ./ nt .* [1:nt;]
            x[(nt + 1):nn] .= ((2 .* nn .- nt) .- [(nt .+ 1):nn;]) ./ (nn .- nt)
            x[(nn + 1):n]  .= (n .- [(nn .+ 1):n;]) ./ (n .- nn)
        elseif example == 3
            x[1:nt] .= 2
        else
            error("Illegal value of example")
        end
        b = A*x
        return RegProb(A, b, x)
    end
end

gravity(::Type{T}, n::Integer, example::Integer, matrixonly::Bool = true) where T =
    gravity(T, n, example, 0, 1, 0.25, matrixonly)

gravity(::Type{T}, n::Integer, matrixonly::Bool = true) where T =
           gravity(T, n, 1, 0, 1, 0.25, matrixonly)
gravity(args...) = gravity(Float64, args...)
gravity(::Type, args...) = throw(MethodError(gravity, Tuple(args)))

"""
Image Deblurring Test Problem
=============================
The generated matrix A is an `n*n-by-n*n` sparse, symmetric,
           doubly block Toeplitz matrix that models blurring of an n-by-n
           image by a Gaussian point spread function.

*Input options:*

+ [type,] dim, band, σ, [matrixonly]: the dimension of the matrix
          is `dim^2`. `band` is the half-bandwidth, only matrix elements within
          a distance `band-1` from the diagonal are nonzero. `σ` controls the
          width of the Gaussin point spread function. The larger the `σ`, the
          wider the function and the more ill posed the problem.
          If `matrixonly = false`, the matrix A and vectors b and x in the linear system Ax = b will be generated
              (`matrixonly = true` by default).

+ [type,] dim, [matrixonly]: `band = 3, σ = 0.7`.

*Groups:* ["regprob", "sparse"]
"""
function blur(::Type{T}, n::Integer, band::Integer, σ::Number,
                 matrixonly::Bool = true) where T
    σ = T(σ)
    z = [exp.(-(T[0:band-1;].^2) / (2 * σ^2)); zeros(T, n - band)]
    A = toeplitz(T, z)
    A = sparse(A)
    A = (1 / T(2 * π * σ^2)) * kron(A, A)
    if matrixonly
        return A
    else
        # start with an image of all zeros
        n2  = round_matlab(Int, n/2)
        n3  = round_matlab(Int, n/3)
        n6  = round_matlab(Int, n/6)
        n12 = round_matlab(Int, n/12)

        m = max(n, 2 * n6 + 1 + n2 + n12)
        x = zeros(T, m, m)

        # add a large ellipse
        Te = zeros(T, n6, n3)
        for i = 1:n6
            for j = 1:n3
                if (i / n6)^2 + (j / n3)^2 < 1
                    Te[i,j] = 1
                end
            end
        end
        Te = [reverse(Te, dims=2) Te;]
        Te = [reverse(Te, dims=1); Te]

        x[2 .+ [1:2n6;], n3 - 1 .+ [1:2n3;]] = Te

        # add a smaller ellipse
        Te = zeros(T, n6, n3)
        for i = 1:n6
            for j = 1:n3
                if (i / n6)^2 + (j / n3)^2 < 0.6
                    Te[i,j] = 1
                end
            end
        end
        Te = [reverse(Te, dims=2) Te;]
        Te = [reverse(Te, dims=1); Te]
        x[n6 .+ [1:2n6;], n3 - 1 .+ [1:2n3;]] = x[n6 .+ [1:2n6;], n3 - 1 .+ [1:2n3;]] .+ 2Te
        x[findall((i) -> i==3, x)] .= 2

        # Add a triangle
        Te = triu(ones(T, n3, n3))
        mT, nT = size(Te)
        x[n3 + n12 .+ [1:nT;], 1 .+ [1:mT;]] = 3Te

        # add a cross
        Te = zeros(T, 2 * n6 + 1, 2 * n6 + 1)
        mT, nT = size(Te)
        Te[n6+1, 1:nT] = ones(T, nT)
        Te[1:mT, n6+1] = ones(T, mT)
        x[n2 + n12 .+ [1:mT;], n2 .+ [1:nT;]] = 4Te

        x = reshape(x[1:n, 1:n], n^2)
        b = A * x
        return RegProb(A, b, x)
    end
end
blur(::Type{T}, n::Integer, matrixonly::Bool = true) where T = blur(T, n, 3, 0.7, matrixonly)
blur(args...) = blur(Float64, args...)
blur(::Type, args...) = throw(MethodError(blur, Tuple(args)))

"""
Inverse Laplace Transformation
"""
function ilaplace(::Type{T}, n::Integer) where T

end

"""
Stellar Parallax Problem with 26 Fixed, Real Observations
=========================================================
The underlying problem is a Fredholm integral equation of the first
kind with kernel

   `K(s,t) = (1/(σ√(2π)))*exp(-0.5*((s-t)/σ)^2)`,

with `σ = 0.014234`, and it is dscretized by means of a Galerkin method
with `n` orthonormal basis functions. The right-hand side `b` consists of
a measured distribution function of stellar parallaxes, and its length
is fixed at `26`, i.e, the matrix `A` is `26×n`. The exact solution,
which represents the true distribution of stellar parallaxes, is unknown.

*Input options:*

+ [type,] dim, [matrixonly]: the generated matrix is `26×dim`. If
    `matrixonly = false`, the matrix A and vectors b and x in the linear system Ax = b will be generated
    (`matrixonly = true` by default).

*Groups:* ["regprob"]

*References:*

**W. M. Smart**, Stellar Dynamics, Cambridge University Press, Cambridge,
(1938), p. 30.
"""
function parallax(::Type{T}, n::Integer, matrixonly::Bool=true) where T
    # Intialization
    a = zero(T)
    b = T(0.1)
    m = 26
    σ = T(0.014234)
    hs = T(0.130/m)
    hx = (b-a)/n
    hsh = hs/2
    hxh = hx/2
    ss = (-T(0.03) .+ T[0:m-1;] * hs) * ones(T, n)'
    xx = ones(T, m) * (a .+ T[0:n-1;]' * hx)

    # compute matrix A
    A = 16exp.(-T(0.5).*((ss .+ hsh .- xx .- hxh)./σ).^2)

    A .+= 4(exp.(-T(0.5).*((ss .+ hsh .- xx)./σ).^2) .+
              exp.(-T(0.5).*((ss .+ hsh .- xx .- hx)./σ).^2) .+
              exp.(-T(0.5).*((ss .- xx .- hxh)./σ).^2) .+
              exp.(-T(0.5).*((ss .+ hs .- xx .- hxh)./σ).^2))

    A .+= (exp.(-T(0.5).*((ss .- xx)./σ).^2) .+
           exp.(-T(0.5).*((ss .+ hs .- xx)./σ).^2) .+
           exp.(-T(0.5).*((ss .- xx .- hx)./σ).^2) .+
           exp.(-T(0.5).*((ss .+ hs .- xx .- hx)./σ).^2))

    A = T(sqrt(hs*hx)/(36*σ*sqrt(2*π)))*A

    if matrixonly
        return A
    else
        # compute b
        b = T[3;7;7;17;27;39;46;51;56;50;43;45;43;32;33;29;
             21;12;17;13;15;12;6;6;5;5]/T(sqrt(hs)*640)
        return RegProbNoSolution(A,b)
    end
end
parallax(args...) = parallax(Float64, args...)
parallax(::Type, args...) = throw(MethodError(parallax, Tuple(args)))

"""
Test Problem with \"Spike\" Solution
====================================
Artifically generated discrete ill-posed problem.

*Input options:*

+ [type,] dim, t_max, [matrixonly]: `dim` is the dimension of the
              matrix. `t_max` controls the length of the pulse train.
              If `matrixonly = false`, the matrix A and vectors b and x in the linear system Ax = b will be
              generated (`matrixonly = true` by default). The solution x
              consists a unit step at t = .5 and a pulse train of spike
              of decreasing magnitude at t = .5, 1.5, 2.5, ....

+ [type,] dim, [matrixonly]: `t_max = 5`.

*Groups:* ["regprob"]
"""
function spikes(::Type{T}, n::Integer, t_max::Integer, matrixonly::Bool = true) where T
    del = convert(T, t_max/n)

    # compute A
    t, sigma = meshgrid(T[del:del:t_max;], T[del:del:t_max;])
    A = sigma ./ (2 .* sqrt.(π .* t.^3)) .* exp.(-(sigma.^2) ./ (4 .* t))

    if matrixonly
        return A
    else
        # compute b and x
        heights = 2*ones(T, t_max); heights[1] = 25
        heights[2] = 9; heights[3] = 5; heights[4] = 4; heights[5] = 3
        x = zeros(T, n); n_h = one(Integer)
        peak = convert(T, 0.5/t_max); peak_dist = one(T)/ t_max
        if peak < 1
            n_peak = round_matlab(Integer, peak*n)
            x[n_peak] = heights[n_h]
            x[n_peak+1:n] = ones(T, n-n_peak)
            peak = peak + peak_dist; n_h = n_h + one(Integer)
        end
        while peak < 1
            n_peak = round_matlab(Integer, peak*n)
            x[n_peak] = heights[n_h]
            peak = peak + peak_dist; n_h = n_h + 1
        end
        b = A*x
        return RegProb(A, b, x)
    end
end
spikes(::Type{T}, n::Integer, matrixonly::Bool = true) where T =
         spikes(T, n, 5, matrixonly)
spikes(args...) = spikes(Float64, args...)
spikes(::Type, args...) = throw(MethodError(spikes, Tuple(args)))

#
# Two-dimensional tomography problem with sparse matrix
#
function tomo(::Type{T}, n::Integer) where T

end

"""
Integral Equation with No square Integrable Solution
====================================================
Discretization of a first kind Fredholm integral equation with
kernel `K` and right-hand side `g` given by
                    `K(s,t) = 1/(s+t+1), g(s) = 1`,
where both integration intervals are `[0, 1]`. The matrix `A`
is a Hankel matrix.

*Input options:*

+ [type,] dim, [matrixonly]: `dim` is the dimension of the matrix.
              If `matrixonly = false`, the matrix A and vectors b and x in the linear system Ax = b will also
              be generated (`matrixonly = true` by default).

*Groups:* ["regprob"]

*References:*

**F. Ursell**, Introduction to the theory of linear
              integral equations., Chapter 1 in L. M. Delves & J. Walsh,
              Numerical Solution of Integral Equations, Clarendon Press,
              1974.
"""
function ursell(::Type{T}, n::Integer, matrixonly::Bool = true) where T
    r = zeros(T, n); c = copy(r)
    for k = 1:n
        d1 = one(T) + (one(T) + k)/n
        d2 = one(T) + k/n
        d3 = one(T) + (k - one(T))/n
        c[k] = n*(d1*log(d1) + d3*log(d3) - 2*d2*log(d2))
        e1 = one(T) + (n+k)/n
        e2 = one(T) + (n+k-one(T))/n
        e3 = one(T) + (n+k-2)/n
        r[k] = n*(e1*log(e1) + e3*log(e3) - 2*e2*log(e2))
    end
    A = hankel(T, c, r)
    if matrixonly
        return A
    else
        # compute the right-hand side b
        b = ones(T,n)/convert(T, sqrt(n))
        return RegProbNoSolution(A, b)
    end
end
ursell(args...) = ursell(Float64, args...)
ursell(::Type, args...) = throw(MethodError(ursell, Tuple(args)))


"""
    rse = PartitionTreeMatrix_unbalanced_haar(matrix::Matrix{Float64},jmax::Int;method::Symbol = :Totalvar, p::Float64 = 2.)

Perform the quadtree partition of image matrix depending on total-variation or changes on the boundary.

### Input Arguments
* `matrix`: The image matrix.
* `jmax`: The maximum level of partition.
* `method`: ':Totalvar' indicates using total variation, the other indicates using changes on the boundary.
* `p`: The power used in the criterion to balance the sizes of the sub-regions. The larger `p`, the more weight on balancing the sub-regions.

### Output Arguments
* `rse`: Region start and end. Each pair represents the start and end of a region in the matrix, corresponding to one node in the quadtree.

Copyright 2018 The Regents of the University of California

Implemented by Yiqun Shao (Adviser: Dr. Naoki Saito)
"""

function PartitionTreeMatrix_unbalanced_haar(matrix::Matrix{Float64},jmax::Int;method::Symbol = :Totalvar, p::Float64 = 2.)
    rows,cols = size(matrix)
    N = rows*cols
    rse = Vector{Vector{Int}}(jmax)
    rse[1] = [1,N];

    j = 1
    while j<=jmax-1
        rr = 1
        rse[j+1] = zeros(4*length(rse[j]))
        for r = 1:2:(length(rse[j]) - 1)
            rs_rows,rs_cols = ind2sub((rows,cols),rse[j][r])
            re_rows,re_cols = ind2sub((rows,cols),rse[j][r+1])

            n_rows = re_rows-rs_rows+1
            n_cols = re_cols-rs_cols+1

            n1_rows,n1_cols = PartitionTreeMatrix_unbalanced_haar_subblock(matrix[rs_rows:re_rows,rs_cols:re_cols],method = method, p = p)

            if !(n1_rows==0 || n1_rows == n_rows) && !(n1_cols==0 ||n1_cols==n_cols)
                child_rows = [rs_rows, rs_rows+n1_rows-1, rs_rows+n1_rows, re_rows, rs_rows, rs_rows+n1_rows-1, rs_rows+n1_rows, re_rows];
                child_cols = [rs_cols, rs_cols+n1_cols-1, rs_cols, rs_cols+n1_cols-1, rs_cols+n1_cols, re_cols, rs_cols+n1_cols, re_cols];
                for k = 1:8
                    rse[j+1][rr+k-1] = sub2ind((rows,cols),child_rows[k],child_cols[k])
                end
                rr = rr + 8

            elseif (n1_rows == 0 || n1_rows == n_rows) && !(n1_cols == 0 || n1_cols == n_cols)
                child_rows = [rs_rows, re_rows, rs_rows, re_rows];
                child_cols = [rs_cols, rs_cols+n1_cols-1, rs_cols+n1_cols, re_cols];
                for k = 1:4
                    rse[j+1][rr+k-1] = sub2ind((rows,cols),child_rows[k],child_cols[k])
                end
                rr = rr + 4

            elseif (n1_cols == 0 || n1_cols == n_cols) && !(n1_rows == 0 || n1_rows == n_rows)
                child_rows = [rs_rows, rs_rows+n1_rows-1, rs_rows+n1_rows, re_rows];
                child_cols = [rs_cols, re_cols, rs_cols, re_cols];
                for k = 1:4
                    rse[j+1][rr+k-1] = sub2ind((rows,cols),child_rows[k],child_cols[k])
                end
                rr = rr + 4
            else
                rse[j+1][rr:rr+1] = rse[j][r:r+1]
                rr = rr + 2
            end
        end
        j = j+1
        rse[j] = rse[j][rse[j].!=0]
    end
    return rse
end

"""
     row_cut,col_cut = PartitionTreeMatrix_unbalanced_haar_subblock(matrix::Matrix{Float64};method::Symbol = :Totalvar, p::Float64 = 2.)

Partition a matrix block into four sub-blocks.

### Input Arguments
* `matrix`: The image matrix.
* `method`: ':Totalvar' indicates using total variation, the other indicates using changes on the boundary.
* `p`: The power used in the criterion to balance the sizes of the sub-regions. The larger `p`, the more weight on balancing the sub-regions.

### Output Arguments
* `row_cut`: The cut index on the row direction.
* `col_cut`: The cut index on the col direction.

Copyright 2018 The Regents of the University of California

Implemented by Yiqun Shao (Adviser: Dr. Naoki Saito)
"""

function PartitionTreeMatrix_unbalanced_haar_subblock(matrix::Matrix{Float64};method::Symbol = :Totalvar, p::Float64 = 2.)
    m,n = size(matrix)
    Entropy = Inf

    if m>1 && n>1
        for i=1:m-1
            for j=1:n-1
                if method == :Totalvar
                    Entropy_temp = total_var(matrix[1:i,1:j])/(i*j)^p + total_var(matrix[i+1:m,1:j])/((m-i)*j)^p + total_var(matrix[1:i,j+1:n])/(i*(n-j))^p + total_var(matrix[i+1:m,j+1:n])/((m-i)*(n-j))^p;
                else
                    Entropy_temp = -( norm(matrix[i,:]-matrix[i+1,:],1) + norm(matrix[:,j] - matrix[:,j+1],1) )/((i*j)^(-p) + ((m-i)*j)^(-p) + (i*(n-j))^(-p) + ((m-i)*(n-j))^(-p));
                end
                if Entropy_temp < Entropy
                    Entropy = Entropy_temp
                    row_cut = i
                    col_cut = j
                end
            end
        end
    elseif m == 1 && n>1
        row_cut = 0
        for j = 1:n-1
            if method == :Totalvar
                Entropy_temp = total_var(matrix[1,1:j])/j^p + total_var(matrix[1,j+1:n])/(n-j)^p;
            else
                Entropy_temp = -norm(matrix[:,j] - matrix[:,j+1],1)*(j^(-p) + (n-j)^(-p));
            end
            if Entropy_temp < Entropy
                Entropy = Entropy_temp
                col_cut = j
            end
        end
    elseif n == 1 && m>1
        col_cut = 0
        for i = 1:m-1
            if method == :Totalvar
                Entropy_temp = total_var(matrix[1:i,1])/i^p + total_var(matrix[i+1:m,1])/(m-i)^p;
            else
                Entropy_temp = -norm(matrix[i,:]-matrix[i+1,:],1)*(i^(-p) + (m-i)^(-p));
            end
            if Entropy_temp < Entropy
                Entropy = Entropy_temp
                row_cut = i
            end
        end
    else
        row_cut = 0
        col_cut = 0
    end
    return row_cut,col_cut
end

"""
     v = total_var(matrix::Matrix{Float64})

Compute the total variation of matrix

Copyright 2018 The Regents of the University of California

Implemented by Yiqun Shao (Adviser: Dr. Naoki Saito)
"""
function total_var(matrix::Matrix{Float64})
    m,n = size(matrix)
    row_val = abs.(matrix[1:m-1,:] - matrix[2:m,:])
    col_val = abs.(matrix[:,1:n-1] - matrix[:,2:n])
    v = norm(vcat(row_val[:],col_val[:]),1)
    return v
end

function PartitionTreeMatrix_unbalanced_haar_binarytree(matrix::Matrix{Float64}, jmax::Int64, p::Float64)
    N = size(matrix,2)
    rs = fill(0, N+1, jmax)
    rs[1,:] .= 1
    rs[2,1] = N+1

    for j = 1:jmax - 1
        rr = 1
        regioncount = sum(!iszero, rs[:,j]) - 1

        for r = 1:regioncount
            rs1 = rs[r,j]
            rs2 = rs[r+1,j]
            n = rs2 - rs1
            if n > 1
                n1 = PartiitonTreeMatrix_unbalanced_haar_col_partition(matrix[:,rs1:rs2-1],p)
                rs[rr+1,j+1] = rs1+n1
                rs[rr+2,j+1] = rs2
                rr = rr+2

            elseif n == 1
                rs[rr+1,j+1] = rs2
                rr = rr+1
            end
        end
    end

    GP = GraphPart(Vector{Int}(1:N), rs)
    return GP
end

function PartiitonTreeMatrix_unbalanced_haar_col_partition(matrix::Matrix{Float64},p::Float64)
    n = size(matrix,2)
    entropy = Inf
    col_cut = 1
    for j = 1:n-1
        entropy_temp = total_var(matrix[:, 1:j])/(j^p) + total_var(matrix[:,j+1:n])/(n-j)^p
        if entropy_temp < entropy
            entropy = entropy_temp
            col_cut = j
        end
    end
    return col_cut
end


using LinearAlgebraicRepresentation
using Base.Test

@testset "Integration Tests" begin

	@testset "M" begin
		@test LinearAlgebraicRepresentation.M(0,0)==0.5
		@test LinearAlgebraicRepresentation.M(1,0)==0.16666666666666666
		@test LinearAlgebraicRepresentation.M(2,0)==0.08333333333333333
		@test LinearAlgebraicRepresentation.M(3,0)==0.05
		@test LinearAlgebraicRepresentation.M(1,1)==0.041666666666666685
		@test LinearAlgebraicRepresentation.M(2,0)==0.08333333333333333
		@test LinearAlgebraicRepresentation.M(2,1)==0.016666666666666663
		@test LinearAlgebraicRepresentation.M(3,0)==0.05
		@test LinearAlgebraicRepresentation.M(3,1)==0.008333333333333338
		@test LinearAlgebraicRepresentation.M(3,2)==0.0023809523809523586
		@test LinearAlgebraicRepresentation.M(3,3)==0.0008928571428571397
	end

	@testset "TT" begin
		tau=[0.0 1.0 0.0; 0.0 0.0 1.0; 0.0 0.0 0.0];
		@test LinearAlgebraicRepresentation.TT(tau, 0,0,0)==0.5
		@test LinearAlgebraicRepresentation.TT(tau, 1,0,0)==0.16666666666666666
		@test LinearAlgebraicRepresentation.TT(tau, 1,1,0)==0.041666666666666685
		@test LinearAlgebraicRepresentation.TT(tau, 1,1,1)==0.0
		@test LinearAlgebraicRepresentation.TT(tau, 2,0,0)==0.08333333333333333
		@test LinearAlgebraicRepresentation.TT(tau, 2,1,0)==0.016666666666666663
		@test LinearAlgebraicRepresentation.TT(tau, 2,2,0)==0.005555555555555545
		@test LinearAlgebraicRepresentation.TT(tau, 2,2,1)==0.0
		@test LinearAlgebraicRepresentation.TT(tau, 2,2,2)==0.0
	end

	@testset "II" begin
		V = [0.0 1.0 0.0; 0.0 0.0 1.0; 0.0 0.0 0.0];
		FV = [[1,2,3]];
		P = V,FV;
		@test LinearAlgebraicRepresentation.II(P, 0,0,0)==0.5
		@test LinearAlgebraicRepresentation.II(P, 1,0,0)==0.16666666666666666
		@test LinearAlgebraicRepresentation.II(P, 0,1,0)>=0.1666666666666666
		@test LinearAlgebraicRepresentation.II(P, 0,0,1)==0.0
		@test LinearAlgebraicRepresentation.II(P, 1,1,1)==0.0
	end

	@testset "III" begin
		V = [0.0 1.0 0.0 0.0; 0.0 0.0 1.0 0.0; 0.0 0.0 0.0 1.0];
		FV = [[1, 2, 4], [1, 3, 2], [4, 3, 1], [2, 3, 4]];
		P = V,FV;
		@test LinearAlgebraicRepresentation.III(P, 0,0,0)>0.166666666
		@test LinearAlgebraicRepresentation.III(P, 0,0,0)<0.166666888
		@test LinearAlgebraicRepresentation.III(P, 1,0,0)>0.041666666
		@test LinearAlgebraicRepresentation.III(P, 1,0,0)<0.041666888
		@test LinearAlgebraicRepresentation.III(P, 0,1,0)>0.041666666
		@test LinearAlgebraicRepresentation.III(P, 0,1,0)<0.041666888
		@test LinearAlgebraicRepresentation.III(P, 0,0,1)>0.041666666
		@test LinearAlgebraicRepresentation.III(P, 0,0,1)<0.041666888
		@test LinearAlgebraicRepresentation.III(P, 10,10,10)>1.3377e-11
		@test LinearAlgebraicRepresentation.III(P, 10,10,10)<1.3388e-11
	end

	@testset "surface" begin
		V,FV = LinearAlgebraicRepresentation.simplexGrid([1,1]);
		P = [V;[0 0 0 0]], FV
		@test LinearAlgebraicRepresentation.surface(P)==1.0
		p = LinearAlgebraicRepresentation.Struct([LinearAlgebraicRepresentation.t(0.5,0.5,0), LinearAlgebraicRepresentation.r(0,0,pi/4), P]);
		q = LinearAlgebraicRepresentation.struct2lar(p);
		@test LinearAlgebraicRepresentation.surface(q)>1.0000000
		@test LinearAlgebraicRepresentation.surface(q)<1.0000222
	end

	@testset "volume" begin
		V = [0.0 1.0 0.0 0.0; 0.0 0.0 1.0 0.0; 0.0 0.0 0.0 1.0];
		FV = [[1, 2, 4], [1, 3, 2], [4, 3, 1], [2, 3, 4]];
		P = V,FV;
		@test LinearAlgebraicRepresentation.volume(P)>0.166666666
		@test LinearAlgebraicRepresentation.volume(P)<0.166668888
	end

	@testset "firstMoment" begin
		V = [0.0 1.0 0.0 0.0; 0.0 0.0 1.0 0.0; 0.0 0.0 0.0 1.0];
		FV = [[1, 2, 4], [1, 3, 2], [4, 3, 1], [2, 3, 4]];
		P = V,FV;
		@test LinearAlgebraicRepresentation.firstMoment(P)[1]<0.0416667
		@test LinearAlgebraicRepresentation.firstMoment(P)[1]>0.0416665

		@test LinearAlgebraicRepresentation.firstMoment(P)[2]<0.0416667
		@test LinearAlgebraicRepresentation.firstMoment(P)[2]>0.0416665

		@test LinearAlgebraicRepresentation.firstMoment(P)[3]<0.0416667
		@test LinearAlgebraicRepresentation.firstMoment(P)[3]>0.0416665

		@test abs(LinearAlgebraicRepresentation.firstMoment(P)[1]-LinearAlgebraicRepresentation.firstMoment(P)[2])<0.00001
		@test abs(LinearAlgebraicRepresentation.firstMoment(P)[2]-LinearAlgebraicRepresentation.firstMoment(P)[3])<0.00001
		@test abs(LinearAlgebraicRepresentation.firstMoment(P)[3]-LinearAlgebraicRepresentation.firstMoment(P)[1])<0.00001
	end

	@testset "secondMoment" begin
		V = [0.0 1.0 0.0 0.0; 0.0 0.0 1.0 0.0; 0.0 0.0 0.0 1.0];
		FV = [[1, 2, 4], [1, 3, 2], [4, 3, 1], [2, 3, 4]];
		P = V,FV;
		@test LinearAlgebraicRepresentation.secondMoment(P)[1]<0.0166666669
		@test LinearAlgebraicRepresentation.secondMoment(P)[1]>0.0166666664

		@test LinearAlgebraicRepresentation.secondMoment(P)[2]<0.0166666669
		@test LinearAlgebraicRepresentation.secondMoment(P)[2]>0.0166666664

		@test LinearAlgebraicRepresentation.secondMoment(P)[3]<0.0166666669
		@test LinearAlgebraicRepresentation.secondMoment(P)[3]>0.0166666664

		@test abs(LinearAlgebraicRepresentation.secondMoment(P)[1]-LinearAlgebraicRepresentation.secondMoment(P)[2])<0.00001
		@test abs(LinearAlgebraicRepresentation.secondMoment(P)[2]-LinearAlgebraicRepresentation.secondMoment(P)[3])<0.00001
		@test abs(LinearAlgebraicRepresentation.secondMoment(P)[3]-LinearAlgebraicRepresentation.secondMoment(P)[1])<0.00001
	end

	@testset "inertiaProduct" begin
		V = [0.0 1.0 0.0 0.0; 0.0 0.0 1.0 0.0; 0.0 0.0 0.0 1.0];
		FV = [[1, 2, 4], [1, 3, 2], [4, 3, 1], [2, 3, 4]];
		P = V,FV;
		@test LinearAlgebraicRepresentation.inertiaProduct(P)[1]<0.00833666
		@test LinearAlgebraicRepresentation.inertiaProduct(P)[1]>0.00833000

		@test LinearAlgebraicRepresentation.inertiaProduct(P)[2]<0.00833666
		@test LinearAlgebraicRepresentation.inertiaProduct(P)[2]>0.00833000

		@test LinearAlgebraicRepresentation.inertiaProduct(P)[3]<0.00833666
		@test LinearAlgebraicRepresentation.inertiaProduct(P)[3]>0.00833000
	end

	@testset "centroid" begin
		V = [0.0 1.0 0.0 0.0; 0.0 0.0 1.0 0.0; 0.0 0.0 0.0 1.0];
		FV = [[1, 2, 4], [1, 3, 2], [4, 3, 1], [2, 3, 4]];
		P = V,FV;
		@test LinearAlgebraicRepresentation.centroid(P)[1]<0.26
		@test LinearAlgebraicRepresentation.centroid(P)[1]>0.24

		@test LinearAlgebraicRepresentation.centroid(P)[2]<0.26
		@test LinearAlgebraicRepresentation.centroid(P)[2]>0.24

		@test LinearAlgebraicRepresentation.centroid(P)[3]<0.26
		@test LinearAlgebraicRepresentation.centroid(P)[3]>0.24
	end

	@testset "inertiaMoment" begin
		V = [0.0 1.0 0.0 0.0; 0.0 0.0 1.0 0.0; 0.0 0.0 0.0 1.0];
		FV = [[1, 2, 4], [1, 3, 2], [4, 3, 1], [2, 3, 4]];
		P = V,FV;
		@test LinearAlgebraicRepresentation.inertiaMoment(P)[1]<0.0333555
		@test LinearAlgebraicRepresentation.inertiaMoment(P)[1]>0.0333111

		@test LinearAlgebraicRepresentation.inertiaMoment(P)[2]<0.0333555
		@test LinearAlgebraicRepresentation.inertiaMoment(P)[2]>0.0333111

		@test LinearAlgebraicRepresentation.inertiaMoment(P)[3]<0.0333555
		@test LinearAlgebraicRepresentation.inertiaMoment(P)[3]>0.0333111
	end

end


# To be executed from CAGD.jl

todisplay = VERSION <= VersionNumber("1.2") ? true : false

using CAGD
using SparseArrays
using LinearAlgebraicRepresentation
Lar = LinearAlgebraicRepresentation
if todisplay  using ViewerGL; GL = ViewerGL  end

include("./CAGD.jl/paper/examples/cellular_complex.jl");
include("./CAGD.jl/paper/examples/CSG_example.jl");
include("./CAGD.jl/paper/examples/cycles_boundaries.jl");
include("./CAGD.jl/paper/examples/planar_algebra_2D.jl");
include("./CAGD.jl/paper/examples/solid_algebra_3D.jl");
include("./CAGD.jl/paper/examples/space_arrangement_2d.jl");
include("./CAGD.jl/paper/examples/space_arrangement.jl");
include("./CAGD.jl/paper/examples/subdivision_of_2_cells.jl");


# Julia 0.6
function singleNumber(vector)
	reduce(xor, vector)
end

a = [1,1,2,3,3]
println(singleNumber(a))


function nominal_errors(network, X, Y; coeffs = [-0.74, -0.44])
    """ Computes absolute errors with no adversarial perturbations
        Args:
            - network: neural network model (neural verification format)
            - X: training examples (each column is an example)
            - Y: labels (each column is a label)
            ---------------
            - coeffs: coefficients to get output (linear combination of network outputs)
    """

    N_samples = size(Y, 2)

    truevals = vec(coeffs' * Y)
    predvals = [coeffs' * compute_output(network, X[:, i]) for i = 1:N_samples]

    return abs.(truevals - predvals)
end