# types
1		# Int64
1.10	# Float64
'j'		# Char
"julia"	# String

# function that calculates cube
julia> function cube(number::Int64)
           return number ^ 3
       end
cube (generic function with 1 method)

# call the function with 10 as an argument
julia> cube(10)
1000

# The following usages of the function cube will thrown error
# julia> cube("10")
# ERROR: MethodError: no method matching cube(::String)
# Closest candidates are:
#   cube(::Int64) at REPL[1]:2

# The :: operator
# returns the value itself if true, else with throw an error
# julia> 2:: Float64
# ERROR: TypeError: typeassert: expected Float64, got Int64
julia> 2:: Int64
2

# Or
julia> typeassert(2, Int64)
2

# checking out what machine we are on, whether a 64 bit or 32 bit
julia> bits(1)
"0000000000000000000000000000000000000000000000000000000000000001"
julia> sizeof(bits(1))
64

# typemax and typemin for Julia types
# Int64
julia> typemin(1)
-9223372036854775808
julia> typemax(1)
9223372036854775807

# Float64
julia> typemin(1.11)
-Inf
julia> typemax(1.11)
Inf

# Type conversions using convert function
# Syntax - convert( desiredType, valueToConvert)
julia> convert(Float64, 10)
10.0

# Sample usage in a function
julia> function distance_covered(speed::Int64, time ::Int64)
           convert(Float64, speed * time)
       end
distance_covered (generic function with 1 method)
julia> distance_covered(62,2)
124.0

# Manipulating Float values
julia> ceil(Int, 3.4)
4
julia> floor(Int, 3.4)
3
julia> round(Int, 3.4)
3

# Subtypes and Supertypes
julia> supertype(Number)
Any

julia> subtypes(Any)
275-element Array{Union{DataType, UnionAll},1}:
 AbstractArray
 AbstractChannel
 AbstractRNG
 AbstractSerializer
 AbstractSet
 AbstractString
 Any
 Associative
 Base.AbstractCartesianIndex
 Base.AbstractCmd
 Base.AsyncCollector
 Base.AsyncCollectorState
 Base.AsyncCondition
 Base.AsyncGenerator
 Base.AsyncGeneratorState
 ⋮
 Timer
 Tuple
 Type
 TypeMapEntry
 TypeMapLevel
 TypeName
 TypeVar
 UniformScaling
 Val
 Vararg
 VecElement
 VersionNumber
 Void
 WeakRef
 WorkerConfig

# Function to check and print all the subtypes of a given a abstract type
julia> function check_all_subtypes(T, space=0)
           println("\t" ^ space, T)
           for t in subtypes(T)
               if t!= Any
               check_all_subtypes(t, space+1)
           end
       end
       end
check_all_subtypes (generic function with 2 methods)

# checking all subtypes of Number type
julia> check_all_subtypes(Number)
Number
	Complex
	Real
		AbstractFloat
			BigFloat
			Float16
			Float32
			Float64
		Integer
			BigInt
			Bool
			Signed
				Int128
				Int16
				Int32
				Int64
				Int8
			Unsigned
				UInt128
				UInt16
				UInt32
				UInt64
				UInt8
		Irrational
		Rational

# Checking all subtypes of AbstractString type
julia> check_all_subtypes(AbstractString)
AbstractString
	Base.SubstitutionString
	Base.Test.GenericString
	DirectIndexString
	RevString
	String
	SubString

# User defined types
julia> type Person
           name:: String
           age::Int64
       end

# create the object for this Person type
julia> rahul = Person("rahul", 27)
Person("rahul", 27)

julia> typeof(rahul)
Person

# access the fields inside this object
julia> rahul.name
"rahul"
julia> rahul.age
27

# To know all the fieldnames inside a object, use the below function
julia> fieldnames(rahul)
2-element Array{Symbol,1}:
 :name
 :age

# Composite Types
# Immutable types using struct keyword
julia> struct Point
              x::Int64
              y::Int64
              z::Int64
       end

julia> p = Point(1,2,3)
Point(1, 2, 3)

julia> p.x
1

# This is NOT allowed for a struct, as its immutable
# julia> p.x = 10
# ERROR: type Point is immutable

# Mutable types
julia> mutable struct MutPoint
              x::Int64
              y::Int64
              z::Int64
       end

julia> p = MutPoint(1,2,3)
MutPoint(1, 2, 3)
julia> p.x
1
julia> p.x = 10
10
julia> p
MutPoint(10, 2, 3)

### Inner Constructors

# showcasing default constructor with extra validation function
julia> type Family
           num_members :: Int64
           members :: Array{String, 1}
       end

julia> f1 = Family(2, ["husband", "wife"])
Family(2, String["husband", "wife"])

julia> f2 = Family(1, ["husband", "wife"])
Family(1, String["husband", "wife"])

# creating a simple function that validates the count provided with the length of the array
julia> function validate(obj::Family)
           if obj.num_members != length(obj.members)
               println("ERROR! Not all members listed!!")
           else
               println("Valid!!")
       end
       end

# lets validate
julia> validate(f1)
Valid!!

julia> validate(f2)
ERROR! Not all members listed!!

# Using Inner constructor to help solve the above problem better
julia> workspace()

julia> type Family
           num_members:: Int64
           members :: Array{String, 1}

           # inner constructor in play
           Family(num_members, members) = num_members != length(members) ? error("Mismatch!!") : new(num_members, members)
           end

# This throws error!
# julia> f1 = Family(1, ["husband", "wife"])
# ERROR: Mismatch!!
# Stacktrace:
#  [1] Family(::Int64, ::Array{String,1}) at ./REPL[73]:6

# While this works!
julia> f2 = Family(2, ["husband", "wife"])
Family(2, String["husband", "wife"])

### Modules & Interfaces

# create a module
julia> module MyModule
           foo=10
           bar=20
           function baz()
               "baz"
           end
           function qux()
               "qux"
           end
           export foo, baz
           end
MyModule

julia> import MyModule

julia> MyModule.bar
20

julia> MyModule.baz()
"baz"

julia> MyModule.foo
10

julia> MyModule.qux()
"qux"

# Including files in modules
# this can be done by using the function include
# open the shell using ; and then creat file by the name transformations.jl
# put say_hello function inside the file

shell> cat transformations.jl
function say_hello(name:: String)
	"hello, $name"
end

# now include this file
julia> include("transformations.jl")
say_hello (generic function with 1 method)

julia> say_hello("rahul")
"hello, rahul"

### Module precompilation

# without precompilation
module Samplecode

export sum_of_numbers

sum_of_numbers = 0
for num in 1:1000000000
	sum_of_numbers += num
end

end

# With precompilation
__precompile__()
module Samplecode

export sum_of_numbers

sum_of_numbers = 0
for num in 1:1000000000
	sum_of_numbers += num
end

end

### Multiple dispatch revisited

julia> type Coordinate{T}
           x::T
           y::T
           z::T
       end

julia> function calc_sum(value::Coordinate{Int64})
           value.x + value.y + value.z
       end
calc_sum (generic function with 1 method)

julia> function calc_sum(value::Coordinate{Float64})
                  value.x + value.y + value.z
       end
calc_sum (generic function with 2 methods)

# we use the function methods to know the available method signatures
julia> methods(calc_sum)
# 2 methods for generic function "calc_sum":
calc_sum(value::Coordinate{Float64}) in Main at REPL[41]:2
calc_sum(value::Coordinate{Int64}) in Main at REPL[40]:2

julia> calc_sum(Coordinate(1,2,3))
6
julia> calc_sum(Coordinate(1.0,2.0,3.0))
6.0


principal_point = PixelCoordinate(55.4, 49.6)
focal_length = Vector2(61.2, 66.4)

model = Pinhole(100, 100, principal_point, focal_length)

run_test_bench(model)

@testset "Check specifics for Pinhole model." begin
    some_point = Point3(0, 1, 0)
    should_be_principal_point = point2pixel(model, some_point)
    @test principal_point ≈ should_be_principal_point

    ray = pixel2ray(model, principal_point)
    @test direction(ray) ≈ Vector3(0,1,0)
    @test origin(ray) ≈ Point3(0,0,0)

end


using Test
using LogicCircuits

@testset "Trimmed apply test" begin

    num_vars = 7
    mgr = SddMgr(num_vars, :balanced)

    x = Var(1)
    y = Var(2)

    x_c = compile(mgr, var2lit(x))
    y_c = compile(mgr, var2lit(y))

    notx = -var2lit(x)
    notx_c = compile(mgr,notx)
    true_c = compile(mgr,true)
    @test true_c isa Sdd
    @test true_c === mgr(true)
    @test true_c === compile(Sdd,mgr,true)
    @test true_c === (Sdd,mgr)(true)
    @test true_c === (mgr,Sdd)(true)
    false_c = compile(mgr,false)
    @test false_c === mgr(false)

    @test false_c & true_c == false_c
    @test false_c & notx_c == false_c
    @test false_c & x_c == false_c
    @test true_c & notx_c == notx_c
    @test x_c & notx_c == false_c
    @test true_c & true_c == true_c
    @test false_c & false_c == false_c
    @test x_c & x_c == x_c
    @test !x_c & !x_c == !x_c

    @test false_c | true_c == true_c
    @test false_c | notx_c == notx_c
    @test false_c | x_c == x_c
    @test true_c | notx_c == true_c
    @test x_c | notx_c == true_c
    @test true_c | true_c == true_c
    @test false_c | false_c == false_c
    @test x_c | x_c == x_c
    @test !x_c | !x_c == !x_c

    v1 = compile(mgr, Lit(1))
    v1_2 = compile(Sdd, mgr, Lit(1))
    @test v1 === v1_2

    v2 = compile(mgr, Lit(2))
    v3 = compile(mgr, Lit(3))
    v4 = compile(mgr, Lit(4))
    v5 = compile(mgr, Lit(5))
    v6 = compile(mgr, Lit(6))
    v7 = compile(mgr, Lit(7))

    t1 = v1 & v3
    t2 = v3 & v1

    @test t1 === t2
    @test model_count(t1,num_vars) == BigInt(2)^(num_vars-2)

    @test model_count(v1 & v2 & v6,num_vars) == BigInt(2)^(num_vars-3)

    c1 = v1 | v3
    c2 = v3 | v1

    @test c1 === c2
    @test model_count(c1,num_vars) == BigInt(2)^(num_vars-2) * 3

    f1 = (c1 & c2)

    @test f1 === (c2 & c1)
    @test f1 === (c1 & c2 & c2)
    @test f1 === (c1 & c2 & c2 & true_c)

    @test (v3 | v7) !== false_c
    @test (v3 | v7) & (!v3 | v7) !== false_c
    @test (v3 | v7) & (!v3 | v7) & (v3 | !v7) !== false_c
    @test (v3 | v7) & (!v3 | v7) & (v3 | !v7) & (!v3 | !v7) === false_c

    f2 = (c1 | c2)

    @test f2 === (c2 | c1)
    @test f2 === (c1 | c2 | c2)
    @test f2 === (c1 | c2 | c2 | false_c)

    @test (v3 & v7) !== true_c
    @test (v3 & v7) | (!v3 & v7) !== true_c
    @test (v3 & v7) | (!v3 & v7) | (v3 & !v7) !== true_c
    @test (v3 & v7) | (!v3 & v7) | (v3 & !v7) | (!v3 & !v7) === true_c

    @test (v1 & v7 & v3) === !(!v1 | !v7 | !v3)

    @test f2 & !f2 === false_c
    @test f2 | !f2 === true_c

    f3 = (v1 | !v3 | v5) & (!v2 | !v3 | v7) & (v2 | !v6 | !v5) & (v1 | v2 | v3) & (!v5 | v6 | v7) & (v2 | !v3 | !v6) & (!v1 | !v6 | !v7)

    @test f3 & !f3 === false_c
    @test f3 | !f3 === true_c
    @test f3 & !(v2 | !v6 | !v5) === false_c

    f4 = ((v1 | !v3 | v5) & (!v5 | v6 | v7| v1) & (!v5 | v6 | v7)  & (!v1 | !v6 | !v7)  & (!v2 | !v3 | v7)) & ((v2 | !v3 | !v6)  & (v2 | !v6 | !v5) & (v1 | v2 | v3) & (v1 | v2 | v3 | !v6) )

    @test f3 === f4

    @test isdeterministic(f4)
    @test respects_vtree(f4)

    io = IOBuffer()
    show(io,f3)
    show(io,t1)
    show(io,c1)
    show(io,true_c)
    show(io,false_c)
    show(io,v1)
    @test length(String(take!(io))) > 0 # see if it runs, regardless of result

    f4c = LogicCircuit(f4)
    @test f4c isa PlainLogicCircuit
    @test num_edges(f4c) == num_edges(f4)
    @test num_nodes(f4c) == num_nodes(f4)
    @test model_count(f4c) == model_count(f4)
    @test isdeterministic(f4c)

    f4c = StructLogicCircuit(mgr, f4)
    @test f4c isa PlainStructLogicCircuit
    @test num_edges(f4c) == num_edges(f4)
    @test num_nodes(f4c) == num_nodes(f4)
    @test model_count(f4c) == model_count(f4)
    @test f4c.vtree === f4.vtree
    @test isdeterministic(f4c)

    f4c = StructLogicCircuit(mgr, LogicCircuit(f4))
    @test f4c isa PlainStructLogicCircuit
    @test num_edges(f4c) == num_edges(f4)
    @test num_nodes(f4c) == num_nodes(f4)
    @test model_count(f4c) == model_count(f4)
    @test f4c.vtree === f4.vtree

    f4c = mgr(StructLogicCircuit(mgr,f4))
    @test Sdd(mgr,f4c) === f4c
    @test f4 === f4c
    @test f4c isa Sdd
    @test num_edges(f4c) == num_edges(f4)
    @test num_nodes(f4c) == num_nodes(f4)
    @test model_count(f4c) == model_count(f4)
    @test f4c.vtree === f4.vtree

    # @test_throws Exception propagate_constants(f4) # breaks bit in canonical false node, creating problems in other tests

end

@testset "Trimmed apply regression test 1" begin

    v = zoo_vtree("iscas89/s386.scan.min.vtree")
    mgr = SddMgr(v)
    v70 = compile(mgr,Lit(70))
    v71 = compile(mgr,Lit(71))
    n = (v70 & v71) | (!v70 & !v71)
    n & v70
    !n & !v70
    r = (!n & v70)
    @test "$r" == "[(70,-71),(-70,⊥)]" || "$r" == "[(-70,⊥),(70,-71)]"

end


#
# diff-tests.jl -
#
# Tests for finite difference operators.
#
module TestingLazyAlgebraDiff

using Test
using MayOptimize
using LazyAlgebra
using LazyAlgebra.Foundations
using LazyAlgebra.FiniteDifferences: limits, ArrayAxis, ArrayAxes,
    differentiation_order, dimensions_of_interest, optimization_level

include("common.jl")

@inline colons(n::Integer) = ntuple(x -> Colon(), max(Int(n), 0))

# Slice along the last dimension.
slice(A::AbstractArray{T,N}, d::Int) where {T,N} =
    view(A, colons(N - 1)..., d)

# Reference implementation of finite differences.

gram_diff_ref(order::Integer, A::AbstractArray; dim::Int=1) =
    diff_ref(order, diff_ref(order, A; dim=dim, adj=false); dim=dim, adj=true)

diff_ref(order::Integer, A::AbstractArray; kwds...) =
    (order == 1 ? diff1_ref(A; kwds...) :
     order == 2 ? diff2_ref(A; kwds...) :
     error("invalid derivative order ", order))

diff1_ref(A::AbstractArray; kwds...) = diff1_ref!(similar(A), A; kwds...)

diff2_ref(A::AbstractArray; kwds...) = diff2_ref!(similar(A), A; kwds...)

function diff1_ref!(dst::AbstractArray{<:Any,N},
                    src::AbstractArray{<:Any,N};
                    dim::Int=1,
                    adj::Bool=false) where {N}
    inds = axes(src)
    @assert axes(dst) == inds
    @assert 1 ≤ dim ≤ N
    I = colons(dim - 1)
    J = inds[dim]
    K = colons(N - dim)
    diff1_ref!(dst, src, I, J, K, adj)
end

function diff2_ref!(dst::AbstractArray{<:Any,N},
                    src::AbstractArray{<:Any,N};
                    dim::Int=1,
                    adj::Bool=false) where {N}
    inds = axes(src)
    @assert axes(dst) == inds
    @assert 1 ≤ dim ≤ N
    I = colons(dim - 1)
    J = inds[dim]
    K = colons(N - dim)
    diff2_ref!(dst, src, I, J, K)
end

#
# Code to apply 1st order finite difference operator D or its adjoint D' (given
# below) along a given dimensions of an array.
#
#     D = [ -1   1   0   0
#            0  -1   1   0
#            0   0  -1   1
#            0   0   0   0];
#
#     D' = [ -1   0   0   0
#             1  -1   0   0
#             0   1  -1   0
#             0   0   1   0];
#
function diff1_ref!(dst::AbstractArray{<:Any,N},
                    src::AbstractArray{<:Any,N},
                    I::Tuple{Vararg{Colon}},
                    J::ArrayAxis,
                    K::Tuple{Vararg{Colon}},
                    adj::Bool) where {N}
    # Forward 1st order finite differences assuming flat boundary conditions
    # along a given dimension.
    len = length(J)
    if len > 1
        j_first = first(J)
        j_last = last(J)
        if adj
            map!(-,
                 view(dst, I..., j_first, K...),
                 view(src, I..., j_first, K...))
            if len > 2
                map!(-,
                     view(dst, I..., (j_first+1):(j_last-1), K...),
                     view(src, I..., (j_first  ):(j_last-2), K...),
                     view(src, I..., (j_first+1):(j_last-1), K...))
            end
            copyto!(view(dst, I..., j_last,   K...),
                    view(src, I..., j_last-1, K...))
        else
            map!(-,
                 view(dst, I..., (j_first  ):(j_last-1), K...),
                 view(src, I..., (j_first+1):(j_last  ), K...),
                 view(src, I..., (j_first  ):(j_last-1), K...))
            fill!(view(dst, I..., j_last, K...), zero(eltype(dst)))
        end
    else
        fill!(dst, zero(eltype(dst)))
    end
    return dst
end

#
# 2nd order finite differences D2 with flat boundary conditions is given by:
#
#     D2 = [-1   1   0   0
#            1  -2   1   0
#            0   1  -2   1
#            0   0   1  -1]
#
# Note that this operator is self-adjoint, so there is no `adj` argument.
#
function diff2_ref!(dst::AbstractArray{<:Any,N},
                    src::AbstractArray{<:Any,N},
                    I::Tuple{Vararg{Colon}},
                    J::ArrayAxis,
                    K::Tuple{Vararg{Colon}}) where {N}
    # Forward 1st order finite differences assuming flat boundary conditions
    # along a given dimension.
    len = length(J)
    if len ≥ 2
        j_first = first(J)
        j_last = last(J)
        map!((b, c) -> c - b,
             view(dst, I..., j_first,   K...),
             view(src, I..., j_first,   K...),
             view(src, I..., j_first+1, K...))
        if len > 2
            map!((a, b, c) -> (c - b) - (b - a),
                 view(dst, I..., (j_first+1):(j_last-1), K...),
                 view(src, I..., (j_first  ):(j_last-2), K...),
                 view(src, I..., (j_first+1):(j_last-1), K...),
                 view(src, I..., (j_first+2):(j_last  ), K...))
        end
        map!((a, b) -> a - b,
             view(dst, I..., j_last,   K...),
             view(src, I..., j_last-1, K...),
             view(src, I..., j_last,   K...))
    else
        fill!(dst, zero(eltype(dst)))
    end
    return dst
end

@testset "Finite differences" begin
    # First test the correctness of the result compared to the above reference
    # implementation and use the saclar product to check the adjoint.  Use
    # integer values for exact computations, over a reduced range to avoid
    # overflows and have exact floating-point representation.
    vmin = -(vmax = Float64(7_000))
    vals = vmin:vmax
    @testset "Differentiation order = $order" for order in 1:2
        # Dimensions to test: 1, 2 and any ≥ 3 for 1st order derivatives, 1:4
        # and any ≥ 5 for 2nd order derivatives.
        dimlist = (order == 1 ? (1, 2, 5) : ((1:4)..., 6))
        sizes = Tuple{Vararg{Int}}[]
        for dim1 in dimlist
            push!(sizes, (dim1,))
        end
        for dim1 in dimlist, dim2 in dimlist
            push!(sizes, (dim1,dim2))
        end
        for dim1 in dimlist, dim2 in dimlist, dim3 in dimlist
            push!(sizes, (dim1,dim2,dim3))
        end
        for dims in sizes
            # Random values (an copies to check that input variables do not
            # change).
            x = rand(vals, dims)
            y = rand(vals, (size(x)..., ndims(x)))
            xsav = copy(x)
            ysav = copy(y)
            atol = zero(eltype(x))
            rtol = 16*eps(eltype(x))
            # Apply along all dimensions specified as a colon.
            D_all = Diff(order,:,Debug)
            @test differentiation_order(D_all) === order
            @test dimensions_of_interest(D_all) === Colon
            @test optimization_level(D_all) === Debug
            D_all_x = D_all*x;   @test x == xsav
            Dt_all_y = D_all'*y; @test y == ysav
            @test vdot(x, Dt_all_y) == vdot(y, D_all_x)
            test_api(Direct, D_all, x, y; atol=atol, rtol=rtol)
            test_api(Adjoint, D_all, x, y; atol=atol, rtol=rtol)
            DtD_all = D_all'*D_all
            @test DtD_all === gram(D_all)
            @test isa(DtD_all, Gram{typeof(D_all)})
            @test differentiation_order(DtD_all) === order
            @test dimensions_of_interest(DtD_all) === dimensions_of_interest(D_all)
            @test optimization_level(DtD_all) === optimization_level(D_all)
            @test DtD_all*x == D_all'*D_all_x; @test x == xsav
            test_api(Direct, DtD_all, x, x; atol=atol, rtol=rtol)
            # Apply along all dimensions specified as a list.
            D_lst = Diff(order,((1:ndims(x))...,),Debug)
            @test differentiation_order(D_lst) === order
            @test dimensions_of_interest(D_lst) === ((1:ndims(x))...,)
            @test optimization_level(D_lst) === Debug
            D_lst_x = D_lst*x;   @test x == xsav
            Dt_lst_y = D_lst'*y; @test y == ysav
            @test vdot(x, Dt_lst_y) == vdot(y, D_lst_x)
            test_api(Direct, D_lst, x, y; atol=atol, rtol=rtol)
            test_api(Adjoint, D_lst, x, y; atol=atol, rtol=rtol)
            DtD_lst = D_lst'*D_lst
            @test DtD_lst === gram(D_lst)
            @test isa(DtD_lst, Gram{typeof(D_lst)})
            @test differentiation_order(DtD_lst) === order
            @test dimensions_of_interest(DtD_lst) === dimensions_of_interest(D_lst)
            @test optimization_level(DtD_lst) === optimization_level(D_lst)
            @test DtD_lst*x == D_lst'*D_lst_x
            test_api(Direct, DtD_lst, x, x; atol=atol, rtol=rtol)
            if ndims(x) > 1
                # Apply along all dimensions in reverse order specified as a
                # range and to avoid all dimensions at a time version.
                D_rev = Diff(order,ndims(x):-1:1,Debug)
                @test differentiation_order(D_rev) === order
                @test dimensions_of_interest(D_rev) === ((ndims(x):-1:1)...,)
                @test optimization_level(D_rev) === Debug
                D_rev_x = D_rev*x;   @test x == xsav
                Dt_rev_y = D_rev'*y; @test y == ysav
                @test vdot(x, Dt_rev_y) == vdot(y, D_rev_x)
                test_api(Direct, D_rev, x, y; atol=atol, rtol=rtol)
                test_api(Adjoint, D_rev, x, y; atol=atol, rtol=rtol)
                DtD_rev = D_rev'*D_rev
                @test DtD_rev === gram(D_rev)
                @test isa(DtD_rev, Gram{typeof(D_rev)})
                @test differentiation_order(DtD_rev) === order
                @test dimensions_of_interest(DtD_rev) === dimensions_of_interest(D_rev)
                @test optimization_level(DtD_rev) === optimization_level(D_rev)
                @test DtD_rev*x == D_rev'*D_rev_x
                test_api(Direct, DtD_rev, x, x; atol=atol, rtol=rtol)
            end
            for d in 1:ndims(x)
                # Apply along a single dimension.
                D_one = Diff(order,d,Debug)
                @test differentiation_order(D_one) === order
                @test dimensions_of_interest(D_one) === d
                @test optimization_level(D_one) === Debug
                z = slice(y, d)
                D_ref_x = diff_ref(order, x, dim=d, adj=false)
                Dt_ref_z = diff_ref(order, z, dim=d, adj=true)
                @test D_one*x == D_ref_x;   @test x == xsav
                @test D_one'*z == Dt_ref_z; @test y == ysav # z is a view on y
                @test slice(D_all_x, d) == D_ref_x
                @test slice(D_lst_x, d) == D_ref_x
                if ndims(x) > 1
                    r = ndims(x) - d + 1
                    @test slice(D_rev_x, r) == D_ref_x
                end
                test_api(Direct, D_one, x, z; atol=atol, rtol=rtol)
                test_api(Adjoint, D_one, x, z; atol=atol, rtol=rtol)
                DtD_one = D_one'*D_one
                @test DtD_one === gram(D_one)
                @test isa(DtD_one, Gram{typeof(D_one)})
                @test differentiation_order(DtD_one) === order
                @test dimensions_of_interest(DtD_one) === dimensions_of_interest(D_one)
                @test optimization_level(DtD_one) === optimization_level(D_one)
                DtD_one_x = DtD_one*x; @test x == xsav
                @test DtD_one_x == gram_diff_ref(order, x, dim=d)
                @test DtD_one_x == D_one'*(D_one*x)
                test_api(Direct, DtD_one, x, x; atol=atol, rtol=rtol)
            end
        end
    end
end

nothing

end # module


@testset "complete substitution" begin
    @polyvar x y z

    p = x^2 * y + y^2 + x
    @test (@inferred p(x=>1, y=>2)) == 7
    @test (@inferred p(x=>5)(y=>3)) == p(x=>5, y=>3)
    @test @wrappedallocs(p(x=>1, y=>2)) == 0 # Works on Julia v0.6 but fails on Julia v0.7
    @test (@inferred x(x=>5)) == 5
    @test (@inferred x(y=>2)) == x
    @test (@inferred x(x=>y)) == y

end

@testset "partial substitution" begin
    @polyvar x y z

    p = x^2 * y + y^2 + x
    p2 = @inferred p(x=>4)
    @test length(terms(p2)) == 3
    @test terms(p2)[1].coefficient == 1
    @test degree(terms(p2)[1].monomial) == 2
    @test exponents(monomial(terms(p2)[1])) == (2,)
    @test terms(p2)[2].coefficient == 16
    @test degree(terms(p2)[2].monomial) == 1
    @test exponents(monomial(terms(p2)[2])) == (1,)
    @test terms(p2)[3].coefficient == 4
    @test degree(terms(p2)[3].monomial) == 0
end

@testset "more inference" begin
    @polyvar x y z

    @test @inferred(subs(x, x=>1)) == 1
    @test @inferred(subs(x, x=>1, y=>2)) == 1
    @test @inferred(subs(x^2, x=>3.0)) == 9.0
    @test @inferred(subs(x^2 * y, x=>1)) == y
    @test @inferred(subs(x^2 * y, y=>5)) == 5x^2
    @test @inferred(subs(x^2 * y, y=>1.0, x=>2)) == 4
    @test @inferred(subs(1x, x=>2)) == 2
    @test @inferred(subs(x + 1, x=>1)) == 2
    @test @inferred(subs(x + 2y + z, y=>2.0, z=>π)) ≈ π + 4.0 + x
end

@testset "more call overloads" begin
    @polyvar x y z
    t = 5x^2 * z * 3 * y * z
    @test @inferred(t(x=>2, y=>2)) == @inferred(15 * 2^2 * z^2 * 2)
    m = z * z * z
    @test @inferred(m(x=>5)) == z^3
    @test @inferred(m(z=>2)) == 8
end

@testset "monomial substitution" begin
    @polyvar x y z

    @test @inferred(subs(x^2 * y, y=>z^2)) == x^2 * z^2
end

@testset "tuple substitution" begin
    @polyvar x y z

    @test @inferred(subs(x^2 * y * z^3, (x, y)=>(2, 3))) == (2^2 * 3 * z^3)
    @test @inferred(subs(5x, (x,)=>(6,))) == 30
    @test @inferred(subs(x * y * z^2, (y, x) => (z, z))) == z^4
    @test @inferred(subs(x^2 * y, (x, y) => (z^3, 5))) == 5 * z^6

    @testset "call overloads" begin
        @test @inferred(x((x,)=>(3,))) == 3

        m = z^1 * y^2
        @test @inferred(m((y, z) => (2, 3))) == 2^2 * 3

        t = 3 * m
        @test @inferred(t((y, z) => (2, 3))) == 3 * 2^2 * 3

        p = 3 * m + 1
        @test @inferred(p((y, z) => (2, 3))) == 3 * 2^2 * 3 + 1
    end
end

@testset "swapping variables" begin
    @polyvar x y z

    @test subs(x^2 * y, x=>y^2, y=>x) == y^4 * x
    # From example 1 in MultivariatePolynomials README
    p = 2x + 3.0x*y^2 + y
    @test p((x, y)=>(y, x)) == 2y + 3y*x^2 + x
    @test p((x, y)=>[1, 2]) == 16
end

@testset "non-variable substitution" begin
    @polyvar x y z

    @test @inferred(subs(5, x=>2)) == 5
    @test @inferred(subs(11, (x, y)=>(y, x))) == 11
end


# Queueing problems
m = 2;  n = 40 ; lmd = 1.0
g = ones(n); g /=sum(g)
U = zeros(m, n)
for i in (1:n)
   U[2,i] = 28 - i
end

@test  all(risolve(U,g,1,
                  initialIterations = 10,
                  aTolSetChoiceProbToZero = 10e-6,
                  aTolerance4Stopping = 0.0,
                  maxIterations = 9999)[1:2] .≈ ([0.2901315939203102, 0.7098684060796899],8.880930086724845))

# Matejka and McKay 2015, RBT
UtilTable = [0 1 0 1;
    0 0 1 1;
    1/2 1/2 1/2 1/2]
λ = 0.4
ρs = -1:0.1:1
results = [risolve(UtilTable, [1+ρ; 1-ρ; 1-ρ; 1+ρ]./4, λ)[1][1] for ρ in ρs]

correct_results = [ 0.5
 0.5000000000000001
 0.5
 0.5
 0.5
 0.5
 0.5
 0.49803143236800007
 0.47259634434957937
 0.4481775023391477
 0.4248079737420439
 0.4025132814392701
 0.38131040477540723
 0.3612070466744384
 0.34220131237025564
 0.32428179482751757
 0.3074280331092877
 0.2916113993696781
 0.27679620629020485
 0.2629410102570515
 0.2500000009320786 ]
@test all((results .+10) .≈ (correct_results .+ 10))


#== # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# Description
#
#   Tests related to the transformations between Geodetic and Geocentric frames.
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# References
#
#   [1] Vallado, D. A (2013). Fundamentals of Astrodynamics and Applications.
#       Microcosm Press, Hawthorn, CA, USA.
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # ==#

# File: ./src/transformations/geodetic_geocentric.jl
# ==================================================

# Function: ECEFtoGeodetic
# ------------------------

################################################################################
#                                 Test Results
################################################################################
#
# Scenario 01
# ===========
#
# Example 3-3: Converting ECEF to Lat Lon [1, p. 173].
#
# According to this example, using:
#
#   r_ecef = 6524.834 i + 6862.875 j + 6448.296 k [km]
#
# one gets:
#
#   Geodetic Latitude  = 34.352496°
#            Longitude = 46.4464°
#            Altitude  = 5085.22 km
#
# Scenario 02
# ===========
#
# At the poles, we have a singularity. We know that, if:
#
#   r_ecef = 0 i + 0 j + Z k
#
# then
#
#   Geodetic Latitude  = 90° for Z > 0 and -90° for Z < 0
#            Longitude =  0°
#            Altitude  = Z - b_wgs84
#
################################################################################

@testset "Function ECEFtoGeodetic" begin
    # Scenario 01
    # ===========

    r = [6524.834e3, 6862.875e3, 6448.296e3]

    ϕ_gd, λ_gd, h = ECEFtoGeodetic(r)

    @test rad2deg(ϕ_gd) ≈ 34.352496 atol = 1e-6
    @test rad2deg(λ_gd) ≈ 46.4464   atol = 1e-4
    @test h/1000        ≈ 5085.22   atol = 1e-2

    # Scenario 02
    # ===========

    aux = rand(0:1000)

    Z = R0 + aux
    ϕ_gd, λ_gd, h = ECEFtoGeodetic([0;0;Z])

    @test rad2deg(ϕ_gd) ≈ 90
    @test rad2deg(λ_gd) ≈ 0
    @test h             ≈ Z - SatelliteToolbox.b_wgs84

    Z = -R0 + aux
    ϕ_gd, λ_gd, h = ECEFtoGeodetic([0;0;Z])

    @test rad2deg(ϕ_gd) ≈ -90
    @test rad2deg(λ_gd) ≈ 0
    @test h             ≈ -Z - SatelliteToolbox.b_wgs84
end


#--------------------------------------------------------------------
# Spacetime Discretization methods in Julia
# Soham 01-2018
#--------------------------------------------------------------------

function delta{T<:Int}(i::T, j::T)::Float64
	return i==j ? 1 : 0
end

function coordtransL2G{T<:Int}(M::T, loc::T, xp::Float64)::Float64
    if loc > M || loc < 1
        error("Location incompatible with the number of patches")
    end
    s = Float64[d for d in M-1:-2:-M+1]
	xg = (xp + s[loc])/M
	return xg
end

function coordtransG2L{T<:Int}(M::T, loc::T, xg::Float64)::Float64
    if loc > M || M < 1
        error("Location incompatible with the number of patches")
    end
    s = Float64[d for d in M-1:-2:-M+1]
	xp = xg*M - s[loc]
	return xp
end

function jacobian(M::Int)::Float64
    return 1/M
end

function shapeH2L(op4N::Array{Float64,4})::Array{Float64,2}
    Nx   = size(op4N)[1] - 1
    Ny   = size(op4N)[2] - 1
    op2N = reshape(op4N, ((Nx+1)*(Ny+1),(Nx+1)*(Ny+1)))
    return op2N
end

function shapeH2L(b2N::Array{Float64,2})::Array{Float64,1}
    Nx = size(b2N)[1] - 1
    Ny = size(b2N)[2] - 1
    b1N = reshape(b2N, (Nx+1)*(Ny+1))
    return b1N
end

function shapeL2H(op2N::Array{Float64,2}, Nx::Int, Ny::Int)::Array{Float64,4}
    op4N = reshape(op2N, (Nx+1, Ny+1, Nx+1, Ny+1))
    return op4N
end

function shapeL2H(b1N::Array{Float64,1}, Nx::Int, Ny::Int)::Array{Float64,2}
    b2N = reshape(b1N, (Nx+1, Ny+1))
    return b2N
end

function LInfnorm{T<:Array{Float64,2}}(numericalGridData::T, exactGridData::T)::Float64
    errorGridData = numericalGridData - exactGridData
    return maximum(abs.(errorGridData))
end

function L1norm{T<:Array{Float64,2}}(numericalGridData::T, exactGridData::T, wx::Array{Float64,1}, wy::Array{Float64,1})::Float64
    errorGridData = numericalGridData - exactGridData
    return (wx'*(errorGridData)*wy)
end

function L2norm{T<:Array{Float64,2}}(numericalGridData::T, exactGridData::T, wx::Array{Float64,1}, wy::Array{Float64,1})::Float64
    errorGridData = numericalGridData - exactGridData
    return sqrt(abs(wx'*(errorGridData.^2)*wy))
end

function dict2array(dbase::Dict{Array{Int,1}, Patch})::Array{Float64,2}
    Nx = size(dbase[[1,1]].value)[1] - 1
    Ny = size(dbase[[1,1]].value)[2] - 1
    M  = convert(Int, sqrt(length(dbase)))
    sPatch = zeros((Nx+1)*M, (Ny+1)*M)
    for m in 1:M, n in 1:M
        sPatch[1+(m-1)*(Nx+1):m*(Nx+1), 1+(n-1)*(Ny+1):n*(Ny+1)] = dbase[[m,n]].value
    end
    return sPatch
end

function array2dict(mPatch::Array{Float64,2}, Nx::Int, Ny::Int, M::Int)::Dict{Array{Int,1}, Patch}
    dbase = Dict{Array{Int,1}, Patch}()
    for m in 1:M, n in 1:M
        dbase[[m,n]] = Patch([m,n], mPatch[1+(m-1)*(Nx+1):m*(Nx+1), 1+(n-1)*(Ny+1):n*(Ny+1)])
    end
    return dbase
end


export ss, ss2tf

# State Space Constructor
# =======================

"""
### function StateSpace(A::Matrix, B::Matrix, C::Matrix, D::Matrix)

Initialize a state space with the following form:

x = Ax + Bu
y = Cx + Du

##### Args

* A: Matrix A.
* B: Matrix B.
* C: Matrix C.
* D: Matrix D.

##### Returns

The new state space.

"""

function ss(A::Array, B::Array, C::Array, D::Array)
    StateSpace(A,B,C,D)
end

# Print Functions
# ===============

"""
### function Base.show(io::IO, Sys::StateSpace)

Print space state.

##### Args

* io: IO in which the state space will be printed.
* Sys: Space state.

"""

function Base.show(io::IO, Sys::StateSpace)
    print(io, "\n")
    print(io, "A = ")
    print(io, Sys.A, "\n\n")
    print(io, "B = ")
    print(io, Sys.B, "\n\n")
    print(io, "C = ")
    print(io, Sys.C, "\n\n")
    print(io, "D = ")
    print(io, Sys.D, "\n\n")
end

Base.print(io::IO, Sys::StateSpace) = Base.show(io, Sys)

# Conversion from State Space to Transfer Function
# ================================================

"""
### function ss2tf(sys::StateSpace)

Convert a state space representation of a system to a transfer function.

##### Args

* sys: State space representation.

##### Returns

The transfer function.

##### Remarks

The output will always be of type `Float64` due to the operations to convert
between the representations.

"""

function ss2tf(sys::StateSpace)
    ( !issiso(sys) ) && error("ss2tf() is only supported for SISO systems.")

    # Get the invariant zeros of the system.
    z = izero(sys)

    # Get the poles of the system.
    p = pole(sys)

    # Get the gain of the system.
    K = gain(sys)

    # Compute the transfer function.
    zpk(z, p, K)
end


# Copyright (c) 2016, Jernej Kovacic
# Licensed under the MIT "Expat" License.
#
# See LICENSE.md for more details.

#
# Note: code in this file is based on the C++ library "Math", available at:
# https://github.com/jkovacic/math and
# https://github.com/jkovacic/math/blob/master/lib/specfun/SpecFunGeneric.cpp
#

include("ctdfrac.jl")
include("settings.jl")
#include("exception.jl")

#
# The following two publications are often referred in this file:
#
# - [Numerical Recipes] or shorter [NR]:
#      William H. Press, Saul A. Teukolsky, William T. Vetterling, Brian P. Flannery
#      Numerical Recipes, The Art of Scientific Computing, 3rd Edition,
#      Cambridge University Press, 2007
#      http://www.nr.com
#
# - [Abramowitz & Stegun] or shorter [AS]:
#      Milton Abramowitz, Irene A. Stegun
#      Handbook of Mathematical Functions with Formulas, Graphs and Mathematical Tables
#      National Bureau of Standards,
#      Applied Mathematics Series 55
#      Issued in June 1964
#
#      The book is copyright free and can be downloaded from:
#      http://www.cs.bham.ac.uk/~aps/research/projects/as/book.php
#

#     betac(x, y)
#
# Unlike Julia 0.5, Julia 0.4 does not support the beta function
# for complex arguments.
# To support regularized beta function on complex numbers as well,
# this convenience function has been introduced.
# TODO: the function may be removed when Julia 0.4 is no longer supported.
function betac{T<:AbstractFloat}(x::Complex{T}, y::Complex{T})

    #
    # It can be shown that B(x,y) can be expressed with gamma
    # functions:
    #
    #             G(x) * G(y)
    #   B(x,y) = -------------
    #               G(x+y)
    #

    const EPS2 = eps(T) * eps(T)
    const gxy = gamma(x + y);

    # handle a very unlikely occassion that G(x,y) gets very close to 0:
    if ( abs2(gxy) < EPS2 )
        throw(SpecfunUndefinedError)
    end

    return gamma(x) * gamma(y) / gxy
end

#     inc_gamma_ctdfrac_closure(a, x)
#
# Return a tupple of parameterized functions `fa(i)` and `fb(i)` that
# evaluate coefficients of the following continued fraction, necessary
# to evaluate the incomplete gamma function:
#
# When x > (1+a), the following continued fraction will be evaluated:
#
#                            1*(1-a)
#   cf = (x-a+1) - ---------------------------
#                                  2*(2-a)
#                    (x-a+3) - ---------------
#                               (x-a+5) - ...
#
# `a(i)` and `b(i)` depend on parameters `a` and `x` and
# are defined as follows:
#
# * a(x,i) = - i * (i-a)
# * b(x,i) = x - a + 1 + 2*i
#
# `a` and `x` are the returned functions' parameters.
#

function inc_gamma_ctdfrac_closure(a, x)
    return i::Integer -> (-i) * (i-a),
           i::Integer -> x - a + 1 + 2*i
end

#     inc_beta_ctdfrac_closure(a, b, x)
#
# Return a tupple of parameterized functions `fa(i)` and `fb(i)` that
# evaluate coefficients of the following continued fraction, necessary
# to evaluate the incomplete beta function:
#
#                     a1
#   cf = 1 + ---------------------
#                       a2
#             1 + ---------------
#                         a3
#                  1 + ---------
#                       1 + ...
# `a(i)` and `b(i)` depend on parameters `a`, `b` and `x` and
# are defined as follows:
#
# * b(x,i) = 1
# *
# *           /    (a+m) * (a+b+m) * x
# *           | - -------------------------   <== i = 2*m+1
# *           /    (a+2*m) * (a + 2*m + 1)
# * a(x,i) = {
# *           \       m * (b-m) * x
# *           |   ---------------------       <== i = 2*m
# *           \    (a+2*m-1) * (a+2*m)
#
# `a`, `b` and `x` are the returned functions' parameters.
#

function inc_beta_ctdfrac_closure(a, b, x)
    T = typeof(x)

    fa =
        function (i::Integer)
            m = div(i, 2)

            return ( 1 == rem(i, 2) ?
                     -((a+m) * (a+b+m) * x) / ((a+i-1) * (a+i)) :
                     (m * (b-m) * x) / ((a+i-1) * (a+i))  )
        end

    fb = i::Integer -> T(1)

    return fa, fb
end

#     incGamma(a, x, upper, reg)
#
# Evaluate an incomplete gamma function with real arguments.
# The exact kind of the returned value depends on parameters
# `upper` and `reg`.
#
# # Arguments:
# * `a`: parameter of the incomplete gamma function
# * `x`: second input argument, the integration limit
# * `upper::Bool`: should the upper (if 'true') or the lower (if 'false') inc. gamma function be returned
# * `reg::Bool`: if 'true', the regularized gamma function is returned, i.e. divided by `gamma(a)`
#
# Note that both `a` and `x` must be strictly greater than 0.
#
# The function throws `SpecfunUndefinedError` when the inc. gamma is
# not defined for any input argument, or `NoConvergenceError`
# when the internal algorithm does not converge.
#
function incGamma{T<:AbstractFloat}(a::T, x::T, upper::Bool, reg::Bool)

    const EPS = eps(T)
    # sanity check:
    if ( a < EPS || x < EPS )
        throw(SpecfunUndefinedError)
    end

    #
    # The algorithm for numerical approximation of the incomplete gamma function
    # as proposed by [Numerical Recipes], section 6.2:
    #
    # When x > (a+1), the upper gamma function can be evaluated as
    #
    #                 -x    a
    #                e   * x
    #   G(a,x) ~= --------------
    #                cf(a,x)
    #
    # where 'cf(a,x) is the continued fraction defined above, its coefficients
    # 'a(i)' and 'b(i)' are implemented in 'inc_gamma_ctdfrac_closure'.
    #
    # When x < (a+1), it is more convenient to apply the following Taylor series
    # that evaluates the lower incomplete gamma function:
    #
    #                          inf
    #                         -----
    #              -x    a    \        G(a)       i
    #   g(a,x) ~= e   * x  *   >    ---------- * x
    #                         /      G(a+1+i)
    #                         -----
    #                          i=0
    #
    # Applying the following property of the gamma function:
    #
    #   G(a+1) = a * G(a)
    #
    # The Taylor series above can be further simplified to:
    #
    #                          inf
    #                         -----              i
    #              -x    a    \                 x
    #   g(a,x) ~= e   * x  *   >    -------------------------
    #                         /      a * (a+1) * ... * (a+i)
    #                         -----
    #                          i=0
    #
    # Once either a lower or an upper incomplete gamma function is evaluated,
    # the other value may be quickly obtained by applying the following
    # property of the incomplete gamma function:
    #
    #   G(a,x) + g(a,x) = G(a)
    #
    # A value of a regularized incomplete gamma function is obtained
    # by dividing g(a,x) or G(a,x) by G(a).
    #

    # This factor is common to both algorithms described above:
    ginc = exp(-x) * (x^a)

    if ( x > (a + 1) )
        #
        # x > (a + 1)
        #
        # In this case evaluate the upper gamma function as described above.
        #

        fa, fb = inc_gamma_ctdfrac_closure(a, x)
        const G = ( true==upper && false==reg ? T(0) : gamma(a) )

        ginc /= ctdfrac_eval(fa, fb)

        #
        # Apply properties of the incomplete gamma function
        # if anything else except a generalized upper incomplete
        # gamma function is desired.
        #
        if ( false == upper )
            ginc = G - ginc
        end

        if ( true == reg )
            # Note: if a>0, gamma(a) is always greater than 0
            ginc /= G
        end
    else
        #
        # x < (a + 1)
        #
        # In this case evaluate the lower gamma function as described above.
        #
        const G = ( false==upper && false==reg ? T(0) : gamma(a) )

        # Initial term of the Taylor series at i=0:
        ginc /= a
        term = ginc

        # Proceed the Taylor series for i = 1, 2, 3... until it converges:
        const TOL = SPECFUN_ITER_TOL(T)
        at = a
        i = 1
        while ( abs(term) > TOL && i<SPECFUN_MAXITER )
            at += T(1)
            term *= (x / at)
            ginc += term
            i += 1
        end

        # has the series converged?
        if ( i >= SPECFUN_MAXITER )
            throw(NoConvergenceError)
        end

        #
        # Apply properties of the incomplete gamma function
        # if anything else except a generalized lower incomplete
        # gamma function is requested.
        #
        if ( true == upper )
            ginc = G - ginc
        end

        if ( true == reg )
            # Note: if a>0, gamma(a) is always greater than 0
            ginc /= G
        end
    end

    return ginc
end

#     incGamma(a, x, upper, reg)
#
# Evaluate an incomplete gamma function with complex arguments.
# The exact kind of the returned value depends on parameters
# `upper` and `reg`.
#
# # Arguments:
# * `a::Complex`: parameter of the incomplete gamma function
# * `x::Complex`: second input argument, the integration limit
# * `upper::Bool`: should the upper (if 'true') or the lower (if 'false') inc. gamma function be returned
# * `reg::Bool`: if 'true', the regularized gamma function is returned, i.e. divided by `gamma(a)`
#
# Note that unlike at real numbers, incomplete gamma function is defined
# virtually everywhere on the complex plane except at `a` = negative integer.
#
# The function throws `SpecfunUndefinedError` when the inc. gamma is
# not defined for any input argument, or `NoConvergenceError`
# when the internal algorithm does not converge.
#
function incGamma{T<:AbstractFloat}(a::Complex{T}, x::Complex{T}, upper::Bool, reg::Bool)

    #
    # The lower incomplete gamma function can be expanded into:
    #
    #                          inf
    #                         -----              i
    #              -x    a    \                 x
    #   g(a,x) ~= e   * x  *   >    -------------------------
    #                         /      a * (a+1) * ... * (a+i)
    #                         -----
    #                          i=0
    #
    # Once the lower incomplete gamma function is evaluated,
    # the upper one may be quickly obtained by applying the following
    # property of the incomplete gamma function:
    #
    #   G(a,x) + g(a,x) = G(a)
    #
    # A value of a regularized incomplete gamma function is obtained
    # by dividing g(a,x) or G(a,x) by G(a).
    #

    const EPS2 = eps(T) * eps(T)
    # If 'a' equals zero, division by zero would occur
    if ( abs2(a) < EPS2 )
        throw(SpecfunUndefinedError)
    end

    # G = gamma(a) or (0,0) when the value is not used
    const G = ( true==reg || true==upper ? gamma(a) : Complex{T}(0) )

    at = a

    # The first term of the series
    ginc = (x^a) * exp(-x) / a
    term = ginc

    # proceed the series until it converges
    const TOL = SPECFUN_ITER_TOL(T)
    const TOL2 = TOL * TOL
    i = 0
    while ( abs2(term) > TOL2 && i < SPECFUN_MAXITER )
        at += T(1)

        # if 'a' is a negative integer, sooner or later this exception will be thrown
        if ( abs2(at) < EPS2 )
            throw(SpecfunUndefinedError)
        end

        term *= x / at
        ginc += term

        i += 1
    end

    # has the series converged?
    if ( i >= SPECFUN_MAXITER )
        throw(NoConvergenceError)
    end

    #
    # Apply properties of the incomplete gamma function
    # if anything else except a non-regularized lower incomplete
    # gamma function is requested.
    #
    if ( true == upper )
        ginc = G - ginc
    end

    if ( true == reg )
        # very unlikely but check it anyway
        if ( abs2(G) < EPS2 )
            throw(SpecfunUndefinedError)
        end

        ginc /= G
    end

    return ginc
end

#     incBeta(a, b, x, lower, reg)
#
# Evaluate the incomplete beta function for real arguments. The exact
# kind of the returned value depends on parameters `upper` and `reg`.
#
# # Arguments
# * `a`: parameter `a` of the beta function
# * `b`: parameter `b` of the beta function
# * `x`: the integration limit
# * `lower::Bool`: should the lower (if 'true') or the upper (if 'false) inc. beta function be returned
# * `reg::Bool`: if 'true', the regularized beta function is returned, i.e. divided by `beta(a, b)`
#
# Note that `a`, `b` and `x` must be strictly greater than 0,
# `x` must be greater than 0 and less than 1
#
# The function throws `SpecfunUndefinedError` when the inc. gamma is
# not defined for any input argument, or `NoConvergenceError`
# when the internal algorithm does not converge.
#

function incBeta{T<:AbstractFloat}(a::T, b::T, x::T, lower::Bool, reg::Bool)

    const EPS = eps(T)

    # sanity check
    if ( a < EPS || b < EPS || x < T(0) || x > T(1) )
        throw(SpecfunUndefinedError)
    end

    #
    # The algorithm is described in detail in [Numerical Recipes], section 6.4
    #
    # If x < (a+1)/(a+b+2), the incomplete beta function will be evaluated as:
    #
    #                  a        b
    #                 x  * (1-x)
    #   Bx(a,b) ~= ------------------
    #                a * cf(x,a,b)
    #
    # where cf(x,a,b) is the continued fraction defined above, its
    # coefficients are implemented in 'inc_beta_ctdfrac_closure'.
    #
    # If x > (a+1)/(a+b+2), the algorithm will converge faster if the following
    # property of the incomplete beta function is applied:
    #
    #   B (a, b) + B   (b, a) = B(a, b)
    #    x          1-x
    #
    # and B_1-x(b,a) can be evaluated as described above.
    #
    # Once either a lower or an upper incomplete beta function is evaluated,
    # the other value may be quickly obtained by applying the following
    # property of the incomplete beta function:
    #
    #   Bx(a,b) + bx(a,b) = B(a,b)
    #
    # A value of a regularized incomplete beta function is obtained
    # by dividing Bx(a,b) or bx(a,b) by B(a,b).
    #

    # checks if x is "large", i.e. greater than the threshold defined above:
    const xlarge = (x > ( (a+T(1)) / (a+b+T(2)) ) )

    # If x is large, the parameters must be swapped
    const xn = ( false==xlarge ? x :  T(1)-x )
    const an = ( false==xlarge ? a : b )
    const bn = ( false==xlarge ? b : a )

    # B = B(a,b) or 0 when the value is not used
    const B = (
        true==reg || (false==xlarge && false==lower) || (true==xlarge  && true==lower) ?
        beta(a, b) : T(0) )

    binc = T(0)

    if ( abs(xn) < EPS )
        # Both boundary conditions are handled separately:
        #   B0(a,b) = 0  and  B1(a,b) = B(a,b)

        binc = ( true==xlarge ? B : T(0) )
    else
        # 'x' is somewhere between 0 and 1, apply the algorithm described above

        fa, fb = inc_beta_ctdfrac_closure(an, bn, xn)
        binc = (xn^an) * ((T(1)-xn)^bn) / an
        binc /= ctdfrac_eval(fa, fb)
    end

    #
    # When x is "large", the algorithm actually returns the
    # upper incomplete beta function!
    #
    # Depending on the requested result ('lower') adjust
    # 'binc' if necessary
    #
    if ( (false==xlarge && false==lower) ||
         (true==xlarge  && true==lower) )
        binc = B - binc
    end

    # Finally regularize the result if requested (via 'reg')
    if ( true == reg )
        # Just in case handle the very unlikely case
        if ( abs(B) < EPS )
            throw(SpecfunUndefinedError)
        end

        binc /= B
    end

    return binc
end

#     incBeta(a, b, x, lower, reg)
#
# Evaluate the incomplete beta function for real arguments. The exact
# kind of the returned value depends on parameters `upper` and `reg`.
#
# # Arguments
# * `a`: parameter `a` of the beta function
# * `b`: parameter `b` of the beta function
# * `x`: the integration limit
# * `lower::Bool`: should the lower (if 'true') or the upper (if 'false) inc. beta function be returned
# * `reg::Bool`: if 'true', the regularized beta function is returned, i.e. divided by `beta(a, b)`
#
# Note that `a`, `b` and `x` must be strictly greater than 0,
# `x` must be greater than 0 and less than 1
#
# The function throws `SpecfunUndefinedError` when the inc. gamma is
# not defined for any input argument, or `NoConvergenceError`
# when the internal algorithm does not converge.
#

#     incBeta(a, b, x, lower, reg)
#
# Evaluate the incomplete beta function for real arguments. The exact
# kind of the returned value depends on parameters `upper` and `reg`.
#
# Arguments
# * `a::Complex`: parameter `a` of the beta function
# * `b::Complex`: parameter `b` of the beta function
# * `x::Complex`: the integration limit
# * `lower::Bool`: should the lower (if 'true') or the upper (if 'false) inc. beta function be returned
# * `reg::Bool`: if 'true', the regularized beta function is returned, i.e. divided by `beta(a, b)`
#
# Note that unlike at real numbers, incomplete beta function is defined
# virtually everywhere except where 'a' is a negative integer, currently this
# function additionally requires that /x/ < 1 otherwise it may not converge.
#
# The function throws `SpecfunUndefinedError` when the inc. gamma is
# not defined for any input argument, or `NoConvergenceError`
# when the internal algorithm does not converge.
#
function incBeta{T<:AbstractFloat}(a::Complex{T}, b::Complex{T}, x::Complex{T}, lower::Bool, reg::Bool)

    #
    # Series expansion of the incomplete beta function as explained at:
    # http://functions.wolfram.com/GammaBetaErf/Beta3/06/01/03/01/01/
    #
    #                a    /                                   2        \
    #               x     |      a*(1-b)*x     a*(1-b)*(2-b)*x         |
    #   Bx(a,b) ~= ---- * | 1 + ----------- + ------------------ + ... | =
    #               a     |        (a+1)           2*(a+2)             |
    #                     \                                            /
    #
    #
    #                     /       inf                                         \
    #                a    |      -----                                     i  |
    #               x     |      \      a * (1-b) * (2-b) * ... * (i-b) * x   |
    #            = ---- * | 1 +   >    -------------------------------------- |
    #               a     |      /                  i! * (a+i)                |
    #                     |      -----                                        |
    #                     \       i=1                                         /
    #
    #

    const EPS2 = eps(T) * eps(T)

    # Division by zero will occur if a==0
    if ( abs2(a) < EPS2 )
        throw(SpecfunUndefinedError)
    end

    # Currently the function requires that /x/ < 1
    # when the series is guaranteed to converge
    if ( abs2(x) >= T(1) )
        throw(SpecfunUndefinedError)
    end

    # B = B(a,b) or (0,0) when the value is not used
    const B = ( true==reg || false==lower ? betac(a, b) : Complex{T}(0) )

    # The first term of the series
    binc = (x^a) / a
    term = binc * a

    at = a;
    bt = -b;

    # proceed the series until it converges
    const TOL = SPECFUN_ITER_TOL(T)
    const TOL2 = TOL * TOL
    i = 1
    while ( abs2(term) > TOL2 && i <= SPECFUN_MAXITER )
        at += T(1)
        bt += T(1)

        # if 'a' is a negative integer, sooner or later this exception will be thrown
        if ( abs2(at) < EPS2 )
            throw(SpecfunUndefinedError)
        end

        term *= x * bt / T(i)

        binc += term / at

        i += 1
    end

    # has the series converged?
    if ( i >= SPECFUN_MAXITER )
        throw(NoConvergenceError)
    end

    #
    # Apply properties of the incomplete beta function
    # if anything else except a non-regularized lower incomplete
    # beta function is desired.
    #
    if ( false == lower )
        binc = B - binc
    end

    if ( true == reg )
        # Very unlikely but check it anyway
        if ( abs2(B) < EPS2 )
            throw(SpecfunUndefinedError)
        end

        binc /= B
    end

    return binc
end

"""
    inc_gamma_upper(a, x)

Compute the upper incomplete gamma function, defined as:

           inf
            /
            |  a-1    -t
   G(a,x) = | t    * e   dt
            |
            /
            x

# Arguments:
* `a`: parameter of the incomplete gamma function
* `x`: second input argument, the lower integration limit

For real arguments, `a` and `x` must be greater than 0.
For complex arguments, the function is defined for virtually
any combination of `a` and `x` except when `a` is 0 or a
negative integer.

The function throws `SpecfunUndefinedError` when the function is
not defined for any input argument, or `NoConvergenceError`
when the internal algorithm does not converge.

# Examples:

```jldoctest
julia> inc_gamma_upper(2.0, 0.5)
0.9097959895689625

julia> inc_gamma_upper(2.0, 5.0)
0.040427681994512805

julia> inc_gamma_upper(2.0+im, 1.0-im)
0.294740622917453 + 1.1235452373416361im
```
"""
function inc_gamma_upper(a, x)
    return incGamma(a, x, true, false)
end

"""
    inc_gamma_lower(a, x)

Compute the lower incomplete gamma function, defined as:

            x
            /
            |  a-1    -t
   g(a,x) = | t    * e   dt
            |
            /
            0

# Arguments:
* `a`: parameter of the incomplete gamma function
* `x`: second input argument, the lower integration limit

For real arguments, `a` and `x` must be greater than 0.
For complex arguments, the function is defined for virtually
any combination of `a` and `x` except when `a` is 0 or a
negative integer.

The function throws `SpecfunUndefinedError` when the function is
not defined for any input argument, or `NoConvergenceError`
when the internal algorithm does not converge.

# Examples:

```jldoctest
julia> inc_gamma_lower(3.0, 2.5)
0.9123737682332481

julia> inc_gamma_lower(1.5, 4.0)
0.8454501129849447

julia> inc_gamma_lower(1.4-0.7im, 0.3+0.1im)
-0.016291214690464333 + 0.13052172604872464im
```
"""
function inc_gamma_lower(a, x)
    return incGamma(a, x, false, false)
end

"""
    inc_gamma_upper_reg(a, x)

Compute the regularized upper incomplete gamma function, defined as:

                  inf
                   /
              1    |  a-1    -t
   Q(a,x) = ------ | t    * e   dt
             G(a)  |
                   /
                   x

# Arguments:
* `a`: parameter of the incomplete gamma function
* `x`: second input argument, the lower integration limit

For real arguments, `a` and `x` must be greater than 0.
For complex arguments, the function is defined for virtually
any combination of `a` and `x` except when `a` is 0 or a
negative integer.

The function throws `SpecfunUndefinedError` when the function is
not defined for any input argument, or `NoConvergenceError`
when the internal algorithm does not converge.

# Examples:

```jldoctest
julia> inc_gamma_upper_reg(3.0, 0.5)
0.9856123220330416

julia> inc_gamma_upper_reg(-1.1+0.8im, 2.4-1.3im)
0.001971575402883159 + 0.016252537792016058im
```
"""
function inc_gamma_upper_reg(a, x)
    return incGamma(a, x, true, true)
end

"""
    inc_gamma_lower_reg(a, x)

Compute the regularized lower incomplete gamma function, defined as:

                   x
                   /
              1    |  a-1    -t
   P(a,x) = ------ | t    * e   dt
             G(a)  |
                   /
                   0

# Arguments:
* `a`: parameter of the incomplete gamma function
* `x`: second input argument, the lower integration limit

For real arguments, `a` and `x` must be greater than 0.
For complex arguments, the function is defined for virtually
any combination of `a` and `x` except when `a` is 0 or a
negative integer.

The function throws `SpecfunUndefinedError` when the function is
not defined for any input argument, or `NoConvergenceError`
when the internal algorithm does not converge.

# Examples:

```jldoctest
julia> inc_gamma_lower_reg(1.5, 3.2)
0.9063092095923061

julia> inc_gamma_lower_reg(-3.2-1.4im, -2.0-0.5im)
0.23971636700888468 + 0.4357835552823487im
```
"""
function inc_gamma_lower_reg(a, x)
    return incGamma(a, x, false, true)
end

"""
    inc_beta_lower(a, b, x)

Compute the lower incomplete beta function, defined as:

             x
             /
             |  a-1        b-1
   Bx(a,b) = | t    * (1-t)    dt
             |
             /
             0

# Arguments:
* `a`: parameter `a` of the beta function
* `b`: parameter `b` of the beta function
* `x`: integration limit

For real arguments, `a` and `b` must be greater than 0,
`x` must be greater than 0 and less than 1.
For complex arguments, `a` and `b` can be any complex values except that
`a` must not be 0 or a negative integer. Currently the `x` is restricted
to: /x/ < 1.

The function throws `SpecfunUndefinedError` when the function is
not defined for any input argument, or `NoConvergenceError`
when the internal algorithm does not converge.

# Examples:

```jldoctest
julia> inc_beta_lower(2.0, 5.0, 0.2)
0.01148800000000001

julia> inc_beta_lower(2.0, 5.0, 0.7)
0.032968833333333336

julia> inc_beta_lower(2.0-im, 3.0+im, 0.1+0.1im)
-0.014279979073458599 - 0.010562832806049189im
```
"""
function inc_beta_lower(a, b, x)
    return incBeta(a, b, x, true, false)
end

"""
    inc_beta_upper(a, b, x)

Compute the upper incomplete beta function, defined as:

             1
             /
             |  a-1        b-1
   bx(a,b) = | t    * (1-t)    dt = B(a,b) - Bx(a,b)
             |
             /
             x

# Arguments:
* `a`: parameter `a` of the beta function
* `b`: parameter `b` of the beta function
* `x`: integration limit

For real arguments, `a` and `b` must be greater than 0,
`x` must be greater than 0 and less than 1.
For complex arguments, `a` and `b` can be any complex values except that
`a` must not be 0 or a negative integer. Currently the `x` is restricted
to: /x/ < 1.

The function throws `SpecfunUndefinedError` when the function is
not defined for any input argument, or `NoConvergenceError`
when the internal algorithm does not converge.

# Examples:

```jldoctest
julia> inc_beta_upper(1.0, 2.0, 0.15)
0.36125

julia> inc_beta_upper(1.0, 2.0, 0.82)
0.01620000000000001

julia> inc_beta_upper(-1.2+im, -3.0-im, 0.1-0.2im)
35.013019655187826 + 12.573969337465748im
```
"""
function inc_beta_upper(a, b, x)
    return incBeta(a, b, x, false, false)
end

"""
    inc_beta_lower_reg(a, b, x)

Compute the regularized lower incomplete beta function, defined as:

                      x
                      /
                1     |  a-1        b-1
   Ix(a,b) = -------- | t    * (1-t)    dt
              B(a,b)  |
                      /
                      0

# Arguments:
* `a`: parameter `a` of the beta function
* `b`: parameter `b` of the beta function
* `x`: integration limit

For real arguments, `a` and `b` must be greater than 0,
`x` must be greater than 0 and less than 1.
For complex arguments, `a` and `b` can be any complex values except that
`a` must not be 0 or a negative integer. Currently the `x` is restricted
to: /x/ < 1.

The function throws `SpecfunUndefinedError` when the function is
not defined for any input argument, or `NoConvergenceError`
when the internal algorithm does not converge.

# Examples:

```jldoctest
julia> inc_beta_lower_reg(3.0, 2.0, 0.12)
0.00628992

julia> inc_beta_lower_reg(2.2-0.2im, 1.4+0.7im, -0.3+0.4im)
0.8221304290364263 - 0.40178180061491814im
```
"""
function inc_beta_lower_reg(a, b, x)
    return incBeta(a, b, x, true, true)
end

"""
    inc_beta_upper_reg(a, b, x)

Compute the regularized upper beta function, defined as:

                      1
                      /
                1     |  a-1        b-1
   ix(a,b) = -------- | t    * (1-t)    dt = 1 - Ix(a,b)
              B(a,b)  |
                      /
                      x

# Arguments:
* `a`: parameter `a` of the beta function
* `b`: parameter `b` of the beta function
* `x`: integration limit

For real arguments, `a` and `b` must be greater than 0,
`x` must be greater than 0 and less than 1.
For complex arguments, `a` and `b` can be any complex values except that
`a` must not be 0 or a negative integer. Currently the `x` is restricted
to: /x/ < 1.

The function throws `SpecfunUndefinedError` when the function is
not defined for any input argument, or `NoConvergenceError`
when the internal algorithm does not converge.

# Examples:

```jldoctest
julia> inc_beta_upper_reg(4.0, 1.0, 0.32)
0.98951424

julia> inc_beta_upper_reg(-3.8-0.7im, -3.4+2.7im, -0.6-0.1im)
0.0010989969710151117 + 0.0021282694661565148im
```
"""
function inc_beta_upper_reg(a, b, x)
    return incBeta(a, b, x, false, true);
end

#     as26_2_22(p)
#
# Implement the formula 26.2.22 in [Abramowitz & Stegun], i.e
# return such `x` that approximately satisfies Q(x) ~= p.
#
# # Arguments:
# * `p` - desired probability
#
# Unlike the original formula, this function accepts `p` being
# greater than 0.5.
#
# This "private" function expects that `p` is greater than 0
# and less than 1.
#
function as26_2_22(p)

    #
    # If 0 < p < 0.5:
    #
    #             +--------+
    #      -+    /      1               +------------+
    #   t =  \  /  ln -----   =    -+  / (-2) * ln(p)
    #         \/       p^2           \/
    #
    # Then initial approximation of 'x' can be calculated as:
    #
    #                 2.30753 + 0.27061 * t
    #   x ~= t - ---------------------------------
    #             1 + t * (0.99229 + 0.04481 * t)
    #
    # If p > 0.5, the expressions above are performed on its complementary
    # value (1-p) and the sign of the final 'x' is reversed.
    #

    T = typeof(p)

    const pp = ( p>= T(0.5) ? T(1) - p : p )

    const t = sqrt(T(-2) * log(pp) )

    # [Abramowitz & Stegun], section 26.2.22:
    const a0 = T(2.30753)
    const a1 = T(0.27061)
    const b0 = T(0.99229)
    const b1 = T(0.04481)

    x = t - (a0 + a1 * t) / (T(1) + t * (b0 + b1 * t))
    if ( p > T(0.5) )
        x = -x
    end

    return x
end

#     invIncGamma(a, g, upper, reg)
#
# Compute the inverse of the incomplete gamma function. The exact kind
# of the returned value depends on parameters `upper` and `reg`.
#
# # Arguments:
# * `a`: parameter of the incomplete gamma function
# * `g`: desired value of the incomplete gamma function
# * `upper::Bool`: if 'true', inverse of the upper incomplete gamma function will be evaluated
# * `reg::Bool`: if 'true', inverse of the regularized incomplete gamma function (divided by `gamma(a)`) will be evaluated
#
# Note that both `a` and `g` must be strictly greater than 0.
#
# The function is defined for real arguments only.
#
# The function throws `SpecfunUndefinedError` when the function is
# not defined for any input argument, or `NoConvergenceError`
# when the internal algorithm does not converge.
#
function invIncGamma{T<:AbstractFloat}(a::T, g::T, upper::Bool, reg::Bool)

    const EPS = eps(T)

    # sanity check
    if ( a < EPS || g < T(0) )
        throw(SpecfunUndefinedError)
    end

    #
    # The algorithm finds an inverse of the regularized lower
    # incomplete gamma function. If 'g' represents a result of
    # any other kind of the incomplete gamma function (specified)
    # by 'upper' and 'reg', it should be converted to a result of
    # the regularized lower incomplete gamma function by applying
    # well known properties of this function.
    #
    # The algorithm is proposed in [Numerical Recipes], section 6.2.1.
    #

    const G = gamma(a)

    p = g;
    if ( false == reg )
        p /= G
    end

    if ( true == upper )
        p = T(1) - p
    end

    # if 'p' equals 0 (or is very close to it),
    # the inverse inc. gamma function will also equal 0.
    if ( abs(p) < EPS )
        return T(0)
    end

    #
    # If 'p' is greater than 1, the incomplete gamma function
    # is not defined at all.
    # If 'p' equals 1 (or is very close to it), the result would
    # be infinity which is not supported by this algorithm.
    #
    if ( p >= (T(1) - EPS) )
        throw(SpecfunUndefinedError)
    end

    #
    # There is no direct series or continued fraction to evaluate
    # an inverse of the incomplete gamma function. On the other hand
    # there are known algorithms that estimate this value very well.
    # This algorithm implements approximation method proposed by the
    # [Numerical Recipes], section 6.2.1 and [Abramowitz & Stegun],
    # sections 26.2.22 and 26.4.17.
    #
    # Note: another method to estimate the initial value of the incomplete
    # gamma function is described in:
    #   Armido R. DiDonato, Alfred H. Morris
    #   Computation of the incomplete gamma function ratios and their inverse
    #   ACM Transactions on Mathematical Software
    #   Volume 12 Issue 4, Dec. 1986, pp. 377-393
    #   http://dl.acm.org/citation.cfm?id=23109&coll=portal&dl=ACM
    #
    # When an approximation is known, the Newton - Raphson method can be
    # applied to refine it further to the desired tolerance.
    #

    x = T(1)

    if ( a <= T(1) )
        #
        # a <= 1:
        #
        # First P(a,1) is approximated by:
        #
        #   Pa = P(a,1) ~= a* (0.253 + 0.12 * a)
        #
        # If 'p' is less than Pa's complement value, 'x' is
        # approximated as:
        #
        #         a      +------+
        #        --+    /   p
        #   x ~=    \  / ------
        #            \/   1-Pa
        #
        # If 'p' is greater than 1-Pa, 'x' is approximated as:
        #
        #                1 - p
        #   x ~= 1 - ln -------
        #                 Pa
        #
        # Note: as long as 0<a<1, Pa will never get even close to 1,
        #       hence the first expression is always defined.
        #

        const c1 = T(0.253)
        const c2 = T(0.12)
        const Pa = a * (c1 + c2 * a)
        const cpa = T(1) - Pa

        x = ( p < cpa ?
                 (p / cpa)^ (T(1) / a) :
                 T(1) - log((T(1) - p)/ Pa)  )
    else
        #
        # a > 1:
        #
        # The initial approximation of 'x' is obtained by the function
        # as26_2_22() that implements [Abramowitz & Stegun], section 26.2.22.
        #
        # Finally the approximated x0 is evaluated as:
        #
        #             /       1          x      \ 3
        #   x0 ~= a * | 1 - ----- + ----------- |
        #             \      9*a     3*sqrt(a)  /
        #

        x = -as26_2_22(p)

        # [Abramowitz & Stegun], section 26.4.17:
        x = T(1) - ( T(1) / (T(9) * a) ) + ( x / ( T(3) * sqrt(a) ) )

        # x = a * x^3:
        x  *= a * x * x
    end

    #
    # When the initial value of 'x' is obtained, a root finding
    # method is applied to determine the exact inverse. This algorithm
    # applies the slightly modified Newton - Raphson method:
    # if 'x' tries to go negative, its half value is assigned
    # to the new value.
    #

    #
    # The Newton - Raphson algorithm also requires the differentiation of
    # the regularized lower incomplete gamma function:
    #
    #                   a-1    -x
    #    d P(a,x)      x    * e
    #   ---------- = --------------
    #       dx         gamma(a)
    #
    # Verified by Maxima:
    # (%i1)  diff(gamma_incomplete_regularized(a, x), x);
    # (%o1)  (x^a-1*%e^-x)/gamma(a)
    #

    const TOL = SPECFUN_ITER_TOL(T)
    xn = T(0)
    f = inc_gamma_lower_reg(a, x) - p
    i = 0
    while ( abs(f) > TOL && i < SPECFUN_MAXITER )
        xn = x - f * G * exp(x) / (x^(a-T(1)))

        # x must not go negative!
        x = ( xn > EPS ? xn : x * T(0.5) )

        f = inc_gamma_lower_reg(a, x) - p

        i += 1
    end

    # Has the algorithm converged?
    if ( i >= SPECFUN_MAXITER )
        throw(NoConvergenceError)
    end

    return x
end

#     invIncBeta(a, b, y, lower, reg)
#
# Compute the inverse of the incomplete beta function. The exact kind
# of the returned value depends on parameters `lower` and `reg`.
#
# # Arguments:
# * `a`: parameter `a` of the incomplete beta function
# * `b`: parameter `b` of the incomplete beta function
# * `y`: desired value of the incomplete beta function
# * `lower::Bool`: if 'true', inverse of the lower incomplete beta function will be evaluated
# * `reg::Bool`: if 'true', inverse of the regularized incomplete beta function (divided by `beta(a,b)`) will be evaluated
#
# Note that `a`, `b` and `y` must be strictly greater than 0.
#
# The function is defined for real arguments only.
#
# The function throws `SpecfunUndefinedError` when the function is
# not defined for any input argument, or `NoConvergenceError`
# when the internal algorithm does not converge.
#
function invIncBeta{T<:AbstractFloat}(a::T, b::T, y::T, lower::Bool, reg::Bool)

    const EPS = eps(T)

    # sanity check
    if ( a < EPS || b < EPS || y < T(0) )
        throw(SpecfunUndefinedError)
    end

    #
    # The algorithm finds an inverse of the regularized lower
    # incomplete beta function. If 'y' represents a result of
    # any other kind of the incomplete beta function (specified
    # by 'lower' and 'reg'), it should be converted to a result of
    # the regularized lower incomplete beta function by applying
    # well known properties of this function.
    #
    # The algorithm is proposed in [Numerical Recipes], section 6.4.
    #

    const B = beta(a, b)

    p = y

    if ( false == reg )
        p /= B
    end

    if (false == lower)
        p = T(1) - p
    end

    # if 'p' equals 0 (or is very close to it),
    # the inverse inc. beta function will also equal 0.
    if ( abs(p) < EPS )
        return T(0)
    end

    #
    # If 'p' is greater than 1, the incomplete beta function
    # is not defined at all.
    # If 'p' equals 1 (or is very close to it), the result would
    # be infinity which is not supported by this algorithm.
    #
    if ( p >= (T(1) - EPS) )
        throw(SpecfunUndefinedError)
    end

    #
    # There is no known direct series or continued fraction to evaluate
    # an inverse of the incomplete beta function. On the other hand
    # there are known algorithms that estimate this value quite well.
    # This algorithm implements approximation method proposed by the
    # [Numerical Recipes], section 6.4 and [Abramowitz & Stegun],
    # section 26.5.22.
    #
    # When an approximation is known, the Newton - Raphson method can be
    # applied to refine it further to the desired tolerance.
    #

    x = T(0)

    if ( a>=T(1) && b>=T(1) )
        #
        # a >= 1  and  b >= 1:
        #
        # Initial 'x' is approximated as described in [Abramowitz & Stegun],
        # section 26.2.22 (implemented in a separate function).
        #
        # Then it is further refined as described in [Abramowitz & Stegun],
        # section 26.5.22:
        #
        #           1                   1
        #   ta = -------   and  tb = -------
        #         2*a-1               2*b-1
        #
        #              2
        #             x  - 3
        #   lambda = --------
        #               6
        #
        #           2
        #   h = ---------
        #        ta + tb
        #
        #        x * sqrt(h + lambda)                /           5      2   \
        #   w = ---------------------- - (tb - ta) * | lambda + --- - ----- |
        #                h                           \           6     3*h  /
        #
        #                   a
        #   x  ~=   ------------------
        #                       2*w
        #              a + b * e
        #

        # [Abramowitz & Stegun], section 26.2.22:
        x = as26_2_22(p)

        const lambda = (x*x - T(3)) / T(6)
        const ta = T(1) / ( T(2) * a - T(1) )
        const tb = T(1) / ( T(2) * b - T(1) )
        const h = T(2) / (ta + tb)

        const w = x * sqrt(h + lambda) / h - (tb - ta) *
                    (lambda + T(5) / T(6) - T(2) / (T(3) * h) )

        x = a / (a + b * exp(T(2) * w))
    else
        #
        # Either 'a' or 'b' or both are less than 1:
        #
        # In this case the initial 'x' is approximated as
        # described in [Numerical Recipes], section 6.4:
        #
        #
        #         1    /    a    \a              1    /    b    \b
        #   ta = --- * | ------- |    and  tb = --- * | ------- |
        #         a    \  a + b  /               b    \  a + b  /
        #
        #
        #   S = ta + tb
        #
        # If p < ta/S:
        #
        #         a    +----------+
        #   x ~=  -+  / p * S * a
        #           \/
        #
        # If p >= ta/S:
        #
        #        b    +----------+
        #   x ~= -+  / 1 - p*S*b
        #          \/
        #

        const ta = ( (a/(a+b))^a ) / a
        const tb = ( (b/(a+b))^b ) / b
        const S = ta + tb

        x = ( p < ta/S ?   (p*S*a)^(T(1)/a) :  (T(1)-p*S*b)^(T(1)/b) )
    end

    #
    # When the initial value of 'x' is obtained, a root finding
    # method is applied to determine the exact inverse. This algorithm
    # applies the slightly modified Newton - Raphson method:
    # if 'x' tries to go negative or beyond 1, it is bisected between
    # its current value and the upper/lower bondary.
    #

    #
    # The Newton - Raphson algorithm also requires the differentiation of
    # the regularized lower incomplete beta function:
    #
    #                   a-1        b-1
    #    d Ix(a,b)     x    * (1-x)
    #   ----------- = ------------------
    #      dx             beta(a,b)
    #
    # Verified by Maxima:
    # (%i1) diff(beta_incomplete_regularized(a, b, x), x);
    # (%o1) ((1-x)^b-1*x^a-1)/beta(a,b)
    #

    xn = T(0)
    f = inc_beta_lower_reg(a, b, x) - p

    const TOL = SPECFUN_ITER_TOL(T)
    i = 0
    while ( abs(f) > TOL && i < SPECFUN_MAXITER )
        xn = x - f * B / (x^(a-T(1)) * (T(1)-x)^(b-T(1)) )

        # x must not go negative or beyond 1!
        if ( xn < EPS )
            x *= T(0.5)
        elseif ( xn > (T(1)-EPS) )
            x = (T(1) + x) * T(0.5)
        else
            x = xn
        end

        f = inc_beta_lower_reg(a, b, x) - p

        i += 1
    end

    # Has the algorithm converged?
    if ( i >= SPECFUN_MAXITER )
        throw(NoConvergenceError)
    end

    return x
end

"""
    inc_gamma_lower_inv(a, g)

Compute the inverse of the lower incomplete gamma function,
i.e. return such `x` that satisfies:

    x
    /
    |  a-1    -t
    | t    * e   dt  =  g
    |
    /
    0

# Arguments:
* `a`: parameter of the incomplete gamma function
* `g`: desired value of the lower incomplete gamma function

`a` must be strictly greater than 0, `g` must be greater or equal
to 0 and less than `gamma(a)`.

The function is defined for real arguments only.

The function throws `SpecfunUndefinedError` when the function is
not defined for any input argument, or `NoConvergenceError`
when the internal algorithm does not converge.

# Examples:

```jldoctest
julia> inc_gamma_lower_inv(0.24, 0.94)
0.002024362637442518

julia> inc_gamma_lower_inv(2.8, 0.17)
0.9873918720287759
```
"""
function inc_gamma_lower_inv(a, g)
    return invIncGamma(a, g, false, false)
end

"""
    inc_gamma_upper_inv(a, g)

Compute the inverse of the upper incomplete gamma function,
i.e. return such `x` that satisfies:

   inf
    /
    |  a-1    -t
    | t    * e   dt  =  g
    |
    /
    x

# Arguments:
* `a`: parameter of the incomplete gamma function
* `g`: desired value of the upper incomplete gamma function

`a` must be strictly greater than 0, `g` must be greater or equal
to 0 and less than `gamma(a)`.

The function is defined for real arguments only.

The function throws `SpecfunUndefinedError` when the function is
not defined for any input argument, or `NoConvergenceError`
when the internal algorithm does not converge.

# Examples:

```jldoctest
julia> inc_gamma_upper_inv(0.65, 0.86)
0.21736651483074873

julia> inc_gamma_upper_inv(3.5, 0.43)
5.609013631489573
```
"""
function inc_gamma_upper_inv(a, g)
    return invIncGamma(a, g, true, false)
end

"""
    inc_gamma_lower_reg_inv(a, g)

Compute the inverse of the regularized lower incomplete gamma function,
i.e. return such `x` that satisfies:

          x
          /
     1    |  a-1    -t
   ------ | t    * e   dt  =  g
    G(a)  |
          /
          0

# Arguments:
* `a`: parameter of the incomplete gamma function
* `g`: desired value of the regularized lower incomplete gamma function

`a` must be strictly greater than 0, `g` must be greater or equal
to 0 and less than 1.

The function is defined for real arguments only.

The function throws `SpecfunUndefinedError` when the function is
not defined for any input argument, or `NoConvergenceError`
when the internal algorithm does not converge.

# Examples:

```jldoctest
julia> inc_gamma_lower_reg_inv(0.2, 0.3)
0.0015877907243440615

julia> inc_gamma_lower_reg_inv(3.0, 0.7)
3.6155676658661333
```
"""
function inc_gamma_lower_reg_inv(a, g)
    return invIncGamma(a, g, false, true)
end

"""
    inc_gamma_upper_reg_inv(a, g)

Compute the inverse of the upper incomplete gamma function,
i.e. return such `x` that satisfies:

         inf
          /
     1    |  a-1    -t
   ------ | t    * e   dt  =  g
    G(a)  |
          /
          x

# Arguments:
* `a`: parameter of the incomplete gamma function
* `g`: desired value of the regularized upper incomplete gamma function

`a` must be strictly greater than 0, `g` must be greater or equal
to 0 and less than 1.

The function is defined for real arguments only.

The function throws `SpecfunUndefinedError` when the function is
not defined for any input argument, or `NoConvergenceError`
when the internal algorithm does not converge.

# Examples:

```jldoctest
julia> inc_gamma_upper_reg_inv(0.3, 0.4)
0.1412525036310711

julia> inc_gamma_upper_reg_inv(5.2, 0.82)
3.1296773937114972
```
"""
function inc_gamma_upper_reg_inv(a, g)
    return invIncGamma(a, g, true, true)
end

"""
    inc_beta_lower_inv(a, b, y)

Compute the inverse of the lower incomplete beta function,
i.e. returns such `x` that satisfies:

             x
             /
             |  a-1        b-1
   Bx(a,b) = | t    * (1-t)    dt = y
             |
             /
             0

# Arguments:
* `a`: parameter `a` of the incomplete beta function
* `b`: parameter `b` of the incomplete beta function
* `y`: desired value of the lower incomplete beta function (`Bx(a,b)`)

`a` and `b` must be strictly greater than 0, `y` must be greater or equal
to 0 and less than `beta(a,b)`.

The function is defined for real arguments only.

The function throws `SpecfunUndefinedError` when the function is
not defined for any input argument, or `NoConvergenceError`
when the internal algorithm does not converge.

# Examples:

```jldoctest
julia> inc_beta_lower_inv(2.8, 0.3, 2.0)
0.9997335668051313

julia> inc_beta_lower_inv(1.1, 1.3, 0.4)
0.5190110112035177
```
"""
function inc_beta_lower_inv(a, b, y)
    return invIncBeta(a, b, y, true, false)
end

"""
    inc_beta_upper_inv(a, b, y)

Return the inverse of the upper incomplete beta function,
i.e. it returns such `x` that satisfies:

             1
             /
             |  a-1        b-1
   bx(a,b) = | t    * (1-t)    dt = y
             |
             /
             x

# Arguments:
* `a`: parameter `a` of the incomplete beta function
* `b`: parameter `b` of the incomplete beta function
* `y`: desired value of the upper incomplete beta function (`bx(a,b)`)

`a` and `b` must be strictly greater than 0, `y` must be greater or equal
to 0 and less than `beta(a,b)`.

The function is defined for real arguments only.

The function throws `SpecfunUndefinedError` when the function is
not defined for any input argument, or `NoConvergenceError`
when the internal algorithm does not converge.

# Examples:

```jldoctest
julia> inc_beta_upper_inv(0.4, 0.5, 1.8)
0.41086943388583214

julia> inc_beta_upper_inv(1.7, 1.1, 0.2)
0.7205525719752999
```
"""
function inc_beta_upper_inv(a, b, y)
    return invIncBeta(a, b, y, false, false)
end

"""
    inc_beta_lower_reg_inv(a, b, y)

Compute the inverse of the regularized lower incomplete beta function,
i.e. return such `x` that satisfies:

                       x
                       /
                1      |  a-1        b-1
   Ix(a,b) = --------  | t    * (1-t)    dt = y
              B(a,b)   |
                       /
                       0

# Arguments:
* `a`: parameter `a` of the incomplete beta function
* `b`: parameter `b` of the incomplete beta function
* `y`: desired value of the regularized lower incomplete beta function (`Ix(a,b)`)

`a` and `b` must be strictly greater than 0, `y` must be greater or equal
to 0 and less than 1.

The function is defined for real arguments only.

The function throws `SpecfunUndefinedError` when the function is
not defined for any input argument, or `NoConvergenceError`
when the internal algorithm does not converge.

# Examples:

```jldoctest
julia> inc_beta_lower_reg_inv(0.3, 0.2, 0.7)
0.9785534149620168

julia> inc_beta_lower_reg_inv(2.4, 3.5, 0.6)
0.44942828803639806
```
"""
function inc_beta_lower_reg_inv(a, b, y)
    return invIncBeta(a, b, y, true, true)
end

"""
    inc_beta_upper_reg_inv(a, b, y)

Compute the inverse of the regularized upper incomplete beta function,
i.e. return such `x` that satisfies:

                      1
                      /
                1     |  a-1        b-1
   ix(a,b) = -------  | t    * (1-t)    dt = y
              B(a,b)  |
                      /
                      x

# Arguments:
* `a`: parameter `a` of the incomplete beta function
* `b`: parameter `b` of the incomplete beta function
* `y`: desired value of the regularozed upper incomplete beta function (`ix(a,b)`)

`a` and `b` must be strictly greater than 0, `y` must be greater or equal
to 0 and less than 1.

The function is defined for real arguments only.

The function throws `SpecfunUndefinedError` when the function is
not defined for any input argument, or `NoConvergenceError`
when the internal algorithm does not converge.

# Examples:

```jldoctest
julia> inc_beta_upper_reg_inv(0.9, 1.5, 0.7)
0.18163313417789242

julia> inc_beta_upper_reg_inv(1.9, 2.7, 0.25)
0.5648299809468559
```
"""
function inc_beta_upper_reg_inv(a, b, y)
    return invIncBeta(a, b, y, false, true)
end


@testset "Exceptions" begin
  # initial population size is zero or negative
  @test_throws DomainError trans_prob( 0, 20, 72.0, [0.1, 0.05])
  @test_throws DomainError trans_prob(-1, 20, 72.0, [0.1, 0.05])

  # final population size is negative
  @test_throws DomainError trans_prob(10, -1, 72.0, [0.1,  0.05])

  # time is negative
  @test_throws DomainError trans_prob(10, 20, -1.0, [0.1,  0.05])

  # birth rate is negative
  @test_throws DomainError trans_prob(10, 20, 72.0, [-0.1, 0.05])

  # death rate is negative
  @test_throws DomainError trans_prob(10, 20,  1.0, [0.1, -0.05])
end

@testset "Type stability" begin
  @testset for I = [Int8, Int16, Int32, Int64, Int128], F = [Float32, Float64, BigFloat]
    # parameters are zero
    @test @inferred(trans_prob(I(1), I(1), F(0), [F(1), F(1)])) == F(0)
    @test typeof(trans_prob(I(1), I(1), F(0), [F(1), F(1)])) === F

    @test @inferred(trans_prob(I(1), I(1), F(1), [F(0), F(0)])) == F(0)
    @test typeof(trans_prob(I(1), I(1), F(1), [F(0), F(0)])) === F

    @test @inferred(trans_prob(I(1), I(2), F(0), [F(1), F(1)])) == F(-Inf)
    @test typeof(trans_prob(I(1), I(2), F(0), [F(1), F(1)])) === F

    @test @inferred(trans_prob(I(1), I(2), F(1), [F(0), F(0)])) == F(-Inf)
    @test typeof(trans_prob(I(1), I(2), F(1), [F(0), F(0)])) === F

    # equal rates
    # t = 1 / λ, j > 0
    g = trans_prob(I(8), I(4), F(0.5), [F(2), F(2)])
    @test @inferred(trans_prob(I(8), I(4), F(0.5), [F(2), F(2)])) == g
    @test typeof(g) === F

    # t = 1 / λ, j = 0
    g = trans_prob(I(8), I(0), F(0.5), [F(2), F(2)])
    @test @inferred(trans_prob(I(8), I(0), F(0.5), [F(2), F(2)])) == g
    @test typeof(g) === F

    # t != 1 / λ, j = 0
    g = trans_prob(I(8), I(0), F(1), [F(2), F(2)])
    @test @inferred(trans_prob(I(8), I(0), F(1), [F(2), F(2)])) == g
    @test typeof(g) === F

    # t > 1 / λ, j > 0
    g = trans_prob(I(8), I(4), F(1), [F(2), F(2)])
    @test @inferred(trans_prob(I(8), I(4), F(1), [F(2), F(2)])) == g
    @test typeof(g) === F

    # t < 1 / λ, j > 0
    g = trans_prob(I(8), I(4), F(0.1), [F(2), F(2)])
    @test @inferred(trans_prob(I(8), I(4), F(0.1), [F(2), F(2)])) == g
    @test typeof(g) === F

    # pure birth process
    g = trans_prob(I(4), I(8), F(1), [F(2), F(0)])
    @test @inferred(trans_prob(I(4), I(8), F(1), [F(2), F(0)])) == g
    @test typeof(g) === F

    @test @inferred(trans_prob(I(8), I(4), F(1), [F(2), F(0)])) == F(-Inf)
    @test typeof(trans_prob(I(8), I(4), F(1), [F(2), F(0)])) === F

    # pure death process
    g = trans_prob(I(8), I(4), F(1), [F(0), F(2)])
    @test @inferred(trans_prob(I(8), I(4), F(1), [F(0), F(2)])) == g
    @test typeof(g) === F

    @test @inferred(trans_prob(I(4), I(8), F(1), [F(0), F(2)])) == F(-Inf)
    @test typeof(trans_prob(I(4), I(8), F(1), [F(0), F(2)])) === F

    # unequal rates
    # t = log(λ / μ) / (λ - μ), j > 0
    g = trans_prob(I(4), I(8), F(log(2)), [F(2), F(1)])
    @test @inferred(trans_prob(I(4), I(8), F(log(2)), [F(2), F(1)])) == g
    @test typeof(g) === F

    # t = log(λ / μ) / (λ - μ), j = 0
    g = trans_prob(I(4), I(0), F(log(2)), [F(2), F(1)])
    @test @inferred(trans_prob(I(4), I(0), F(log(2)), [F(2), F(1)])) == g
    @test typeof(g) === F

    # t != log(λ / μ) / (λ - μ), j = 0
    g = trans_prob(I(4), I(0), F(1), [F(2), F(1)])
    @test @inferred(trans_prob(I(4), I(0), F(1), [F(2), F(1)])) == g
    @test typeof(g) === F

    # t > log(λ / μ) / (λ - μ), j > 0
    g = trans_prob(I(4), I(8), F(1), [F(2), F(1)])
    @test @inferred(trans_prob(I(4), I(8), F(1), [F(2), F(1)])) == g
    @test typeof(g) === F

    # t < log(λ / μ) / (λ - μ), j > 0
    g = trans_prob(I(4), I(8), F(0.5), [F(2), F(1)])
    @test @inferred(trans_prob(I(4), I(8), F(0.5), [F(2), F(1)])) == g
    @test typeof(g) === F
  end
end

@testset "Time is zero" begin
  @test trans_prob(10, 10, 0.0, [0.1, 0.05]) === 0.0
  @test trans_prob(10,  5, 0.0, [0.1, 0.05]) === -Inf
  @test trans_prob(10, 15, 0.0, [0.1, 0.05]) === -Inf
end

@testset "Both parameters are zero" begin
  @test trans_prob(10, 10, 72.0, [0.0, 0.0]) === 0.0
  @test trans_prob(10,  5, 72.0, [0.0, 0.0]) === -Inf
  @test trans_prob(10, 15, 72.0, [0.0, 0.0]) === -Inf
end

@testset "Pure birth process" begin
  t = 1.0
  η = [1.5, 0.0]

  @test trans_prob(1, 0, t, η) === -Inf
  @test trans_prob(100, 50, t, η) === -Inf

  p = [trans_prob(100, j, t, η, log_value=false) for j = 0:10000]
  @test sum(p) ≈ 1.0
end

@testset "Pure death process" begin
  t = 1.0
  η = [0.0, 1.5]

  @test trans_prob(1, 2, t, η) === -Inf
  @test trans_prob(100, 150, t, η) === -Inf

  p = [trans_prob(10000, j, t, η, log_value=false) for j = 0:10000]
  @test sum(p) ≈ 1.0
end

@testset "Equal rates" begin
  i = 1000
  η = [0.1, 0.1]

  # addends are all positive
  p = [trans_prob(i, j, 5.0, η, log_value=false) for j = 0:10000]
  @test sum(p) ≈ 1.0

  # addends have alternating signs
  p = [trans_prob(i, j, 100.0, η, log_value=false) for j = 0:10000]
  @test sum(p) ≈ 1.0

  # time is exactly equal to 1 / λ
  p = [trans_prob(i, j, 10.0, η, log_value=false) for j = 0:10000]
  @test sum(p) ≈ 1.0
end

@testset "Death rate greater than birth rate" begin
  i = 1000
  η = [0.01, 0.02]
  ω = log(η[1] / η[2]) / (η[1] - η[2])

  # addends are all positive
  p = [trans_prob(i, j, ω * 0.5, η, log_value=false) for j = 0:10000]
  @test sum(p) ≈ 1.0

  # addends have alternating signs
  p = [trans_prob(i, j, ω * 2.0, η, log_value=false) for j = 0:10000]
  @test sum(p) ≈ 1.0

  # time is exactly equal to log(λ / μ) / (λ - μ)
  p = [trans_prob(i, j, ω, η, log_value=false) for j = 0:10000]
  @test sum(p) ≈ 1.0
end

@testset "Birth rate greater than death rate" begin
  i = 1000
  η = [0.02, 0.01]
  ω = log(η[1] / η[2]) / (η[1] - η[2])

  # addends are all positive
  p = [trans_prob(i, j, ω * 0.5, η, log_value=false) for j = 0:10000]
  @test sum(p) ≈ 1.0

  # addends have alternating signs
  p = [trans_prob(i, j, ω * 2.0, η, log_value=false) for j = 0:10000]
  @test sum(p) ≈ 1.0

  # time is exactly equal to log(λ / μ) / (λ - μ)
  p = [trans_prob(i, j, ω, η, log_value=false) for j = 0:10000]
  @test sum(p) ≈ 1.0
end


# special_sequences.jl
# A collection of special sequences.

"The ZeroSequence is a sequence of all zeros."
immutable ZeroSequence{T} <: Sequence{T}
end

ZeroSequence() = ZeroSequence{Float64}()

getindex(s::ZeroSequence, k) = zero(eltype(s))

ztransform(s::ZeroSequence, z) = zero(eltype(s))

"The DiracSequence is a sequence of all zeros, except for `h[0] = 1`."
immutable DiracSequence{T} <: Sequence{T}
end

DiracSequence() = DiracSequence{Float64}()

getindex(s::DiracSequence, k) = k == 0 ? one(eltype(s)) : zero(eltype(s))

ztransform(s::DiracSequence, z) = one(eltype(s))


using Test
using EllZeroTrendFiltering: add_quadratic!

global const TEST_PRINT_LEVEL = 0

Random.seed!(27182)
#---

# Given a matrix with coefficients for quadratic polynomials this function
# constructs the quadratic polynomials and inserts them into a piecewise
# quadratic object
function verify_piecewise_quadratic(c_mat, show_plot=false)

    poly_list = [QuadraticPolynomial(c_mat[k, :]) for k in 1:size(c_mat,1)]

    if show_plot
        xgrid = linspace(-2,2)
        plot(show=true)
        for p in poly_list
            plot!(xgrid, p.(xgrid))
        end
    end

    # Insert quadratics into piecewise quadfatic
    pwq = create_new_pwq(Float64)
    for p in poly_list
        TEST_PRINT_LEVEL > 0 && println("Inserting: ", p)
        add_quadratic!(pwq, p)

        TEST_PRINT_LEVEL > 0 && println("After Insertion: ")
        TEST_PRINT_LEVEL > 0 && println(pwq)
    end

    # Check that the intersections between the quadratics are in increasing order
    TEST_PRINT_LEVEL > 0 && println("Checking that intersections points are increasing: ")
    for λ in pwq
        @test λ.left_endpoint < get_right_endpoint(λ)
    end

    # Check for continuity of the piecewise quadratic
    TEST_PRINT_LEVEL > 0 && println("Checking continuity: ")
    for λ in pwq
        x = get_right_endpoint(λ)
        if x == Inf
            break
        end

        TEST_PRINT_LEVEL > 0 && println("Continutiy diff: ", λ.p(x) - λ.next.p(x))
        @test λ.p(x) ≈ λ.next.p(x)
    end

    # Check that the piecewise quadratic is smaller than all the quadratics
    # that it was formed by
    x_grid = -5:0.1:5
    y_pwq = pwq(x_grid)
    # Test both versions of evaluating piecewise-quadratic
    @test y_pwq ≈ pwq.(x_grid)  rtol=sqrt(eps())

    # Test get_vals
    x, y, x_all, y_all = get_vals(pwq)
    @test pwq(x) ≈ y            rtol = sqrt(eps())

    for p in poly_list
        TEST_PRINT_LEVEL > 0 && @printf("suboptimality diff: %2.3f\n", maximum(y_pwq - p.(x_grid)))
        #println("   ",  x_grid[ (y_pwq - p.(x_grid)) .> 0])
        @test (y_pwq .<= p.(x_grid)) == trues(length(x_grid))
    end

end

pwq = generate_PiecewiseQuadratic(([2.0, 2, 1], -Inf), ([2.0, -2, 1], 0.0))

@test length(pwq) == 2
add_quadratic!(pwq, QuadraticPolynomial([1, 0, 1.0]))

#Test print string
str = "PiecewiseQuadratic{Float64} with 3 elements:\n[  -∞ , -0.50]\t  :   1.00*x^2 + 2.00*x + 2.00   \n[-0.50, 0.50]\t  :   1.00*x^2 + 0.00*x + 1.00   \n[0.50, ∞  ]\t  :   1.00*x^2 + -2.00*x + 2.00   \n"
str2 = sprint(print, pwq)
@test str == str2

@test length(pwq) == 3

#---
# Tests with (2x^2 + 1) and (x^2 + 2) which have intersections in -1, +1
# and 5/2*x(x-1) + 1 = 2.5x^2 - 2.5x + 1

p1 = QuadraticPolynomial([2.0, 0, 1])
p2 = QuadraticPolynomial([1.0, 0, 2])
p3 = QuadraticPolynomial(2.5, -2.5, 1.0)

pwq1 = create_new_pwq(p1)
add_quadratic!(pwq1, p2)
@test length(pwq1) == 3
add_quadratic!(pwq1, p3)
@test length(pwq1) == 4

pwq2 = create_new_pwq(p2)
add_quadratic!(pwq2, p3)
@test length(pwq2) == 3
add_quadratic!(pwq2, p1)
@test length(pwq2) == 4

@test pwq1[1].p === pwq2[1].p == p1
@test pwq1[2].left_endpoint == pwq2[2].left_endpoint == -1
@test pwq1[2].p === pwq2[2].p == p2
@test pwq1[3].left_endpoint == pwq2[3].left_endpoint == 0
@test pwq1[3].p === pwq2[3].p

#---
c_mat1 = [
    2.53106  1.59097   1.60448
    2.51349  0.681205  1.60553
    2.09364  0.714858  1.08607
    ]
verify_piecewise_quadratic(c_mat1)

#---
c_mat2 = [
    2.01532  1.59269   1.65765
    2.50071  1.56421   1.53899
    2.02767  0.706143  1.129
    ]
verify_piecewise_quadratic(c_mat2)

#---

c_mat3 = [
    2.42298  0.796953  1.02011
    2.45055  1.32235   1.4894
    2.1762   0.855014  1.0647
    ]
verify_piecewise_quadratic(c_mat3)
#---
# Random test case
N = 10
c_mat_rand = [2 .+ rand(N) 2*rand(N) 1 .+ rand(N)]

verify_piecewise_quadratic(c_mat_rand)


using DataStructure101
const DS = DataStructure101

export key, value

key(tn::DS.TreeNode) = tn.value.first

value(tn::DS.TreeNode) = tn.value.second

function find(tn::DS.TreeNode, k::Int64)
    if k == key(tn)
        return tn
    elseif k < key(tn)
        return find(tn.left, k)
    else
        return find(tn.right, k)
    end
end

find(tn::DS.NullNode, k::Int64) = nothing

mutable struct BinarySearchTree
    root::DS.AbstractNode
    length::Int

    BinarySearchTree(value::Pair{Int64,<:Any}) = new(DS.TreeNode(value), 1)
    BinarySearchTree() = new(DS.NullNode(), 0)
end

root(bst::BinarySearchTree) = bst.root

Base.length(bst::BinarySearchTree) = bst.length

Base.eltype(bst::BinarySearchTree) = Base.eltype(bst.root)

function Base.show(io::IO, bst::BinarySearchTree)
    print(io, "BinarySearchTree($(DS.tree_repr(root(bst))))")
end

function close_to(tn::DS.TreeNode{Pair{Int64, T}}, k::Int64) where T
    if k <= key(tn)
        left = tn.left
        return (left isa DS.NullNode) ? tn : close_to(left, k)
    else
        right = tn.right
        return (right isa DS.NullNode) ? tn : close_to(right, k)
    end
end

function Base.maximum(bst::BinarySearchTree)
    tn = close_to(root(bst), typemax(Int64))
    return value(tn)
end

function Base.minimum(bst::BinarySearchTree)
    tn = close_to(root(bst), typemin(Int64))
    return value(tn)
end

function Base.push!(bst::BinarySearchTree, data::Pair{Int64,T}) where T
    tn = DS.TreeNode(data)

    if root(bst) isa DS.NullNode
        bst.root = tn
        bst.length += 1
        return
    end

    parent = close_to(root(bst), data.first)
    tn.parent = parent

    if data.first < key(parent)
        parent.left = tn
    elseif data.first > key(parent)
        parent.right = tn
    end

    bst.length += 1
end

Base.getindex(bst::BinarySearchTree, k::Int) = value(find(root(bst), k))