export # Convex Hull Functions
       ch_akl_toussaint,
       ch_bykat,
       ch_eddy,
       ch_graham_andrew,
       ch_jarvis,
       ch_melkman,
       convex_hull_2,
       # Convexity Checking
       is_ccw_strongly_convex_2,
       is_cw_strongly_convex_2,
       # Hull Subsequence Functions
       ch_graham_andrew_scan,
       ch_jarvis_march,
       lower_hull_points_2,
       upper_hull_points_2,
       # Extreme Point Functions
       ch_e_point,
       ch_n_point,
       ch_ns_point,
       ch_nswe_point,
       ch_s_point,
       ch_w_point,
       ch_we_point

@doc raw"""
    ch_akl_toussaint(ps::AbstractVector{Point2})
    ch_akl_toussaint(ps::Point2...)

Returns the counterclockwise sequence of extreme points of points in the vector
`ps`.

It is not specified at which point the cyclic sequence of extreme points is cut
into a linear sequence.

!!! info "Precondition"

    The vector `ps` does not contain the result.

### Implementation

This function uses the algorithm of Akl and Toussaint [^sga78] that requires
``O(n \log n)`` time for ``n`` input points.

[^sga78]: S. G. Akl and G. T. Toussaint. A fast convex hull algorithm. *Inform.
          Process. Lett.*, 7(5):219--222, 1978.

See also: [`ch_bykat()`](@ref), [`ch_eddy()`](@ref),
[`ch_graham_andrew()`](@ref), [`ch_jarvis()`](@ref), [`ch_melkman()`](@ref),
[`convex_hull_2()`](@ref)
"""
ch_akl_toussaint(ps::AbstractVector{Point2})

@doc raw"""
    ch_bykat(ps::AbstractVector{Point2})
    ch_bykat(ps::Point2...)

Returns the counterclockwise sequence of extreme points of points in the vector
`ps`.

It is not specified at which point the cyclic sequence of extreme points is cut
into a linear sequence.

!!! info "Precondition"

    The vector `ps` does not contain the result.

### Implementation

This function implements the non-recursive variation of Eddy's algorithm (see
[`ch_eddy()`](@ref)) described in [^kra78]. This algorithm requires ``O(nh)``
time in the worst case for ``n`` input points with ``h`` extreme points.

[^kra78]: K. R. Anderson. A reevaluation of an efficient algorithm for determining
          the convex hull of a finite planar set. *Inform. Process. Lett.*,
          7(1):53--55, 1978.

See also: [`ch_akl_toussaint()`](@ref), [`ch_eddy()`](@ref),
[`ch_graham_andrew()`](@ref), [`ch_jarvis()`](@ref), [`ch_melkman()`](@ref),
[`convex_hull_2()`](@ref)
"""
ch_bykat(ps::AbstractVector{Point2})

@doc raw"""
    ch_eddy(ps::AbstractVector{Point2})
    ch_eddy(ps::Point2...)

Returns the counterclockwise sequence of extreme points of points in the vector
`ps`.

It is not specified at which point the cyclic sequence of extreme points is cut
into a linear sequence.

!!! info "Precondition"

    The vector `ps` does not contain the result.

### Implementation

The function implements Eddy's algorithm [^wfe77], which is the two-dimensional
version of the quickhull algorithm [^cbb77].

This algorithm requires ``O(nh)`` time in the worst case for ``n`` input points
with ``h`` extreme points.

[^wfe77]: W. F. Eddy. A new convex hull algorithm for planar sets. *ACM Trans.
          Math. Softw.*, 3:398--403 and 411--412, 1977.

[^cbb96]: C. Bradford Barber, David P. Dobkin, and Hannu Huhdanpaa. The
          Quickhull algorithm for convex hulls. *ACM Trans. Math. Softw.*,
          22(4):469--483, December 1996.

See also: [`ch_akl_toussaint()`](@ref), [`ch_bykat()`](@ref),
[`ch_graham_andrew()`](@ref), [`ch_jarvis()`](@ref), [`ch_melkman()`](@ref),
[`convex_hull_2()`](@ref)
"""
ch_eddy(ps::AbstractVector{Point2})

@doc raw"""
    ch_graham_andrew(ps::AbstractVector{Point2})
    ch_graham_andrew(ps::Point2...)

Returns the counterclockwise sequence of extreme points of points in the vector
`ps`.

It is not specified at which point the cyclic sequence of extreme points is cut
into a linear sequence.

!!! info "Precondition"

    The vector `ps` does not contain the result.

### Implementation

This function implements Andrew's variant of the Graham scan algorithm
[^ama79] and follows the presentation of Mehlhorn [^km84]. This algorithm
requires ``O(n \log n)`` time in the worst case for ``n`` input points.

[^ama79]: A. M. Andrew. Another efficient algorithm for convex hulls in two
    dimensions. *Inform. Process. Lett.*, 9(5):216--219, 1979.

[^km84]: Kurt Mehlhorn. *Data Structures and Algorithms 3: Multi-dimensional
         Searching and Computational Geometry*, volume 3 of *EATCS Monographs
         on Theoretical Computer Science*. Springer-Verlag, Heidelberg,
         Germany, 1984.

See also: [`ch_akl_toussaint()`](@ref), [`ch_bykat()`](@ref),
[`ch_eddy()`](@ref), [`ch_graham_andrew_scan()`](@ref), [`ch_jarvis()`](@ref),
[`ch_melkman()`](@ref), [`convex_hull_2()`](@ref),
[`lower_hull_points_2`](@ref), [`upper_hull_points_2`](@ref)
"""
ch_graham_andrew(ps::AbstractVector{Point2})

@doc raw"""
    ch_jarvis(ps::AbstractVector{Point2})
    ch_jarvis(ps::Point2...)

Returns the counterclockwise sequence of extreme points of points in the vector
`ps`.

It is not specified at which point the cyclic sequence of extreme points is cut
into a linear sequence.

!!! info "Precondition"

    The vector `ps` does not contain the result.

### Implementation

This functions uses the Jarvis march (gift-wrapping) algorithm [^raj73]. This
algorithm requires ``O(nh)`` time in the worst case for ``n`` input points with
``h`` extreme points.

[^raj73]: R. A. Jarvis. On the identification of the convex hull of a finite
          set of points in the plane. *Inform. Process. Lett.*, 2:18--21, 1973.

See also: [`ch_akl_toussaint()`](@ref), [`ch_bykat()`](@ref),
[`ch_eddy()`](@ref), [`ch_graham_andrew()`](@ref), [`ch_jarvis_march()`](@ref),
[`ch_melkman()`](@ref), [`convex_hull_2()`](@ref)
"""
ch_jarvis(ps::AbstractVector{Point2})

@doc raw"""
    ch_melkman(ps::AbstractVector{Point2})
    ch_melkman(ps::Point2...)

Returns the counterclockwise sequence of extreme points of points in the vector
`ps`.

It is not specified at which point the cyclic sequence of extreme points is cut
into a linear sequence.

!!! info "Precondition"

    The vector `ps` does not contain the result.

### Implementation

It uses an implementation of Melkman's algorithm [^am87]. Running time of
this is linear.

[^am87] A. Melkman. On-line construction of the convex hull of a simple
        polyline. *Inform. Process. Lett.*, 25:11--12, 1987.

See also: [`ch_akl_toussaint()`](@ref), [`ch_bykat()`](@ref),
[`ch_eddy()`](@ref), [`ch_graham_andrew()`](@ref), [`ch_jarvis()`](@ref),
[`convex_hull_2()`](@ref)
"""
ch_melkman(ps::AbstractVector{Point2})

@doc raw"""
    convex_hull_2(ps::AbstractVector{Point2})
    convex_hull_2(ps::Point2...)

Returns the counterclockwise sequence of extreme points of points in the vector
`ps`.

It is not specified at which point the cyclic sequence of extreme points is cut
into a linear sequence.

!!! info "Precondition"

    The vector `ps` does not contain the result.

### Implementation

The ``O(n \log n)`` Akl and Toussaint [^sga78] algorithm is used.

See also: [`ch_akl_toussaint()`](@ref), [`ch_bykat()`](@ref),
[`ch_eddy()`](@ref), [`ch_graham_andrew()`](@ref), [`ch_jarvis()`](@ref),
[`ch_melkman()`](@ref)
"""
convex_hull_2(ps::AbstractVector{Point2})

"""
    is_ccw_strongly_convex_2(ps::AbstractVector{Point2})
    is_ccw_strongly_convex_2(ps::Point2...)

The function [`is_ccw_strongly_convex_2()`](@ref) determines if a given
sequence of points defines a counterclockwise-oriented, strongly convex polygon.

It returns `true`, iff the point elements in `ps` form a
counterclockwise-oriented strongly convex polygon.

A set of points is said to be strongly convex if it consists of only extreme
points (*i.e.*, vertices of the convex hull).

### Implementation

The algorithm requires ``O(n)`` time for a set of ``n`` input points.

See also: [`is_cw_strongly_convex_2()`](@ref)
"""
is_ccw_strongly_convex_2(ps::AbstractVector{Point2})

"""
    is_cw_strongly_convex_2(ps::AbstractVector{Point2})
    is_cw_strongly_convex_2(ps::Point2...)

The function [`is_cw_strongly_convex_2()`](@ref) determines if a given sequence
of points defines a clockwise-oriented, strongly convex polygon.

It returns `true`, iff the point elements in `ps` form a clockwise-oriented
strongly convex polygon.

A set of points is said to be strongly convex if it consists of only extreme
points (*i.e.*, vertices of the convex hull).

### Implementation

The algorithm requires ``O(n)`` time for a set of ``n`` input points.

See also: [`is_ccw_strongly_convex_2()`](@ref)
"""
is_cw_strongly_convex_2(ps::AbstractVector{Point2})

for F ∈ (:is_ccw_strongly_convex_2, :is_cw_strongly_convex_2)
    @eval begin
        $F(ps::AbstractVector) = $F(collect(CxxRef{Point2}, CxxRef.(ps)))
        $F(ps::reference_type_union(Point2)...) = $F(collect(CxxRef.(ps)))
    end
end

"""
    ch_graham_andrew_scan(ps::AbstractVector{Point2})
    ch_graham_andrew_scan(p::Point2, q::Point2, ps::Point2...)

Generates the counterclockwise sequence of extreme points from a given
sequence of input points that are not left of the line defined by the first and
last points in this sequence.

More precisely, it generates the counterclockwise sequence of extreme points
from a given sequence of input points that are not left of the line ``pq``
defined by the first (``p``) and last (``q``) points in the sequence (``p`` is
the value of `first(ps)` and ``q`` is the value of `last(ps)`).  The resulting
sequence is returned starting with ``p``; point ``q`` is omitted.

!!! info "Precondition"

    `ps` contains at least two different points.  The points in `ps` are sorted
    with respect to ``pq``, *i.e.*, the sequence of points in `ps` defines a
    counterclockwise polygon, for which the Graham-Sklansky-procedure [^js72]
    works.

### Implementation

This algorithm requires ``O(n)`` time in the worst case for ``n`` input points.

[^js72]: J. Sklansky. Measuring concavity on rectangular mosaic. *IEEE Trans.
         Comput.*, C-21:1355--1364, 1972.

See also: [`ch_graham_andrew()`](@ref), [`lower_hull_points_2()`](@ref),
[`upper_hull_points_2()`](@ref)
"""
ch_graham_andrew_scan(ps::AbstractVector{Point2})

@cxxdereference ch_graham_andrew_scan(ps::AbstractVector) =
    ch_graham_andrew_scan(collect(CxxRef{Point2}, CxxRef.(ps)))
@cxxdereference ch_graham_andrew_scan(p::Point2, q::Point2,
                                      ps::reference_type_union(Point2)...) =
    ch_graham_andrew_scan(CxxRef.([p, q, ps...]))

"""
    ch_jarvis_march(ps::AbstractVector{Point2}, p::Point2, q::Point2)
    ch_jarvis_march(p::Point2, q::Point2, ps::Point2...)

Generates the counterclockwise sequence of extreme points from a given set of
input points that line between two input points.

More precisely, it generates the counterclockwise subsequence of extreme points
between `p` and `q` of the points in `ps`.  Said sequence is returned starting
with point `p`.  The last point generated is the point preceding `q` in the
counterclockwise order of extreme points.

!!! info "Precondition"

    `p` and `q` are extreme points with respect to the points in `ps` and `q`
    belongs to `ps`.

### Implementation

The functions uses the Jarvis march (gift-wrapping) algorithm [^raj73]. This
algorithm requires ``O(nh)`` time in the worst case for ``n`` input points with
``h`` extreme points.

See also: [`ch_jarvis()`](@ref), [`lower_hull_points_2()`](@ref),
[`upper_hull_points_2()`](@ref)
"""
ch_jarvis_march(ps::AbstractVector{Point2}, p::Point2, q::Point2)

@cxxdereference ch_jarvis_march(ps::AbstractVector, p::Point2, q::Point2) =
    ch_jarvis_march(collect(CxxRef{Point2}, CxxRef.(ps)), p, q)
@cxxdereference ch_jarvis_march(p::Point2, q::Point2, ps::Point2...) =
    ch_jarvis_march(collect(ps), p, q)

@doc raw"""
    lower_hull_points_2(ps::AbstractVector{Point2})
    lower_hull_points_2(ps::Point2...)

Generates the counterclockwise sequence of extreme points on the lower hull of a
given set of input points.

More precisely, it generates the counterclockwise sequence of extreme points in
the lower hull of the points in `ps`.  The resulting sequence is returned,
starting with the leftmost point; the rightmost point is not included.  If there
is only one extreme point (*i.e.*, the leftmost and rightmost points are equal),
the extreme point is reported.

!!! info "Precondition"

    `ps` does not contain the result.

The different treatment by [`upper_hull_points_2()`](@ref) of the case that all
points are equal ensures that concatenation of lower and upper hull points gives
the sequence of extreme points.

### Implementation

This function uses Andrew's variant of Graham's scan algorithm [^ama79],
[^km84]. The algorithm has worst-case running time of ``O(n \log n)`` for
``n`` input points.

See also: [`ch_graham_andrew()`](@ref), [`ch_graham_andrew_scan()`](@ref),
[`upper_hull_points_2()`](@ref)
"""
lower_hull_points_2(ps::AbstractVector{Point2})

@doc raw"""
    upper_hull_points_2(ps::AbstractVector{Point2})
    upper_hull_points_2(ps::Point2...)

Generates the counterclockwise sequence of extreme points on the upper hull of a
given set of input points.

More precisely, it generates the counterclockwise sequence of extreme points in
the lower hull of the points in `ps`.  The resulting sequence is returned,
starting with the rightmost point; the leftmost point is not included.  If there
is only one extreme point (*i.e.*, the leftmost and rightmost points are equal),
the extreme point is reported.

!!! info "Precondition"

    `ps` does not contain the result.

The different treatment by [`lower_hull_points_2()`](@ref) of the case that all
points are equal ensures that concatenation of lower and upper hull points gives
the sequence of extreme points.

### Implementation

This function uses Andrew's variant of Graham's scan algorithm [^ama79],
[^km84]. The algorithm has worst-case running time of ``O(n \log n)`` for
``n`` input points.

See also: [`ch_graham_andrew()`](@ref), [`ch_graham_andrew_scan()`](@ref),
[`lower_hull_points_2()`](@ref)
"""
upper_hull_points_2(ps::AbstractVector{Point2})

for F ∈ (:ch_akl_toussaint
       , :ch_bykat
       , :ch_eddy
       , :ch_graham_andrew
       , :ch_jarvis
       , :ch_melkman
       , :convex_hull_2
       , :lower_hull_points_2
       , :upper_hull_points_2)
    @eval begin
        $F(ps::AbstractVector) = $F(collect(CxxRef{Point2}, CxxRef.(ps)))
        $F(ps::reference_type_union(Point2)...) = $F(collect(CxxRef.(ps)))
    end
end

@doc raw"""
    ch_e_point(ps::AbstractVector{Point2})
    ch_e_point(ps::Point2...)

The function [`ch_e_point()`](@ref) finds a point of a given set of input points
with maximal ``x`` coordinate.

It traverses `ps`. After execution, the returned value is the first point
``e`` such that ``∀p\! ∈\! ps\; e ≥_{xy} p``.

See also: [`ch_n_point()`](@ref), [`ch_nswe_point()`](@ref),
[`ch_ns_point()`](@ref), [`ch_s_point()`](@ref), [`ch_w_point()`](@ref),
[`ch_we_point()`](@ref)
"""
ch_e_point(ps::AbstractVector{Point2})

@doc raw"""
    ch_n_point(ps::AbstractVector{Point2})
    ch_n_point(ps::Point2...)

The function [`ch_n_point()`](@ref) finds a point of a given set of input points
with maximal ``y`` coordinate.

It traverses `ps`. After execution, the returned value is the first point
``n`` such that ``∀p\! ∈\! ps\; n ≥_{yx} p``.

See also: [`ch_e_point()`](@ref), [`ch_nswe_point()`](@ref),
[`ch_ns_point()`](@ref), [`ch_s_point()`](@ref), [`ch_w_point()`](@ref),
[`ch_we_point()`](@ref)
"""
ch_n_point(ps::AbstractVector{Point2})

@doc raw"""
    ch_ns_point(ps::AbstractVector{Point2})
    ch_ns_point(ps::Point2...)

The function [`ch_ns_point()`](@ref) finds two points of a given set of input
points with minimal and maximal ``y`` coordinate.

It traverses `ps`. After execution, the returned value is a tuple where the
first point ``n`` is such that ``∀p\! ∈\! ps\; n ≥_{yx} p``, and the second
point ``s`` is such that ``∀p\! ∈\! ps\; s ≤_{yx} p``.

See also: [`ch_e_point()`](@ref), [`ch_nswe_point()`](@ref),
[`ch_n_point()`](@ref), [`ch_s_point()`](@ref), [`ch_w_point()`](@ref),
[`ch_we_point()`](@ref)
"""
ch_ns_point(ps::AbstractVector{Point2})

@doc raw"""
    ch_nswe_point(ps::AbstractVector{Point2})
    ch_nswe_point(ps::Point2...)

The function [`ch_nswe_point()`](@ref) finds the four extreme points of a given
set of input points using a linear scan of the input points.

That is, it determines the points with maximal ``y``, minimal ``y``, minimal
``x``, and maximal ``x`` coordinates.

It traverses `ps`. After execution, the returned value is a tuple where the
first point ``n`` is such that ``∀p\! ∈\! ps\; n ≥_{yx} p``, the second
point ``s`` is such that ``∀p\! ∈\! ps\; s ≤_{yx} p``, the third point ``w`` is
such that ``∀p\! ∈\! ps\; w ≤_{xy} p``, and the fourth point ``e`` is such that
``∀p\! ∈\! ps\; e ≥_{xy} p``.

See also: [`ch_e_point()`](@ref), [`ch_n_point()`](@ref),
[`ch_ns_point()`](@ref), [`ch_s_point()`](@ref), [`ch_w_point()`](@ref),
[`ch_we_point()`](@ref)
"""
ch_nswe_point(ps::AbstractVector{Point2})

@doc raw"""
    ch_s_point(ps::AbstractVector{Point2})
    ch_s_point(ps::Point2...)

The function [`ch_s_point()`](@ref) finds a point of a given set of input points
with minimal ``y`` coordinate.

It traverses `ps`. After execution, the returned value is the first point
``s`` such that ``∀p\! ∈\! ps\; s ≤_{yx} p``.

See also: [`ch_e_point()`](@ref), [`ch_nswe_point()`](@ref),
[`ch_n_point()`](@ref), [`ch_ns_point()`](@ref), [`ch_w_point()`](@ref),
[`ch_we_point()`](@ref)
"""
ch_s_point(ps::AbstractVector{Point2})

@doc raw"""
    ch_w_point(ps::AbstractVector{Point2})
    ch_w_point(ps::Point2...)

The function [`ch_w_point()`](@ref) finds a point of a given set of input points
with minimal ``x`` coordinate.

It traverses `ps`. After execution, the returned value is the first point
``w`` such that ``∀p\! ∈\! ps\; w ≤_{xy} p``.

See also: [`ch_e_point()`](@ref), [`ch_nswe_point()`](@ref),
[`ch_n_point()`](@ref), [`ch_ns_point()`](@ref), [`ch_s_point()`](@ref),
[`ch_we_point()`](@ref)
"""
ch_w_point(ps::AbstractVector{Point2})

@doc raw"""
    ch_we_point(ps::AbstractVector{Point2})
    ch_we_point(ps::Point2...)

The function [`ch_we_point()`](@ref) finds two points of a given set of input
points with minimal and maximal ``x`` coordinate.

It traverses `ps`. After execution, the returned value is a tuple where the
first point ``w`` is such that ``∀p\! ∈\! ps\; w ≤_{xy} p``, and the second
point ``e`` is such that ``∀p\! ∈\! ps\; e ≥_{xy} p``.

See also: [`ch_e_point()`](@ref), [`ch_nswe_point()`](@ref),
[`ch_n_point()`](@ref), [`ch_ns_point()`](@ref), [`ch_s_point()`](@ref),
[`ch_w_point()`](@ref)
"""
ch_we_point(ps::AbstractVector{Point2})

for S ∈ (:e, :n, :ns, :nswe, :s, :w, :we)
    F = Symbol(:ch_, S, :_point)
    @eval begin
        $F(ps::AbstractVector) =
            isempty(ps) ?
                $F([CxxRef(Point2())]) :
                $F(CxxRef.(ps))
        $F(ps::reference_type_union(Point2)...) = $F(collect(CxxRef.(ps)))
    end
end


#=
N-queens problem in Julia.

Cf with the ConstraintSolver.jl solution: http://hakank.org/julia/constraint/nqueens.jl

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

=#

include("jl_utils.jl") # for next_permutation

# Brute force version using next_permutation
# Find all solutions
function queens(n=8,num=0,printit=true)
    println("n:$n")
    q = collect(1:n)
    rev = reverse(q)
    c = 0
    while q != rev
      check = true
      for i in 1:n
        for j in 1:i-1
          if q[i] === q[j] ||
            q[i] + i === q[j] + j ||
            q[i] - i === q[j] - j
            check = false
            break
          end
        end
      end

      if check === true
        c += 1
        if printit
          println(q)
        end
        if num > 0 && c >= num
          return c
        end
      end
      if q != rev
        q = next_permutation(q)
      end
    end
    if printit
      println(c)
    end

    return c
end

@time queens(8,0, true)

@time queens(8,0, false)

println("\nCount all solutions:")
for n in 2:10
  @time c = queens(n,0,false)
  println("n:$n count:$c")
end

println("\nFirst solution:")
for n in 2:12
  @time c = queens(n,1,true)
  println("n:$n count:$c")
end


"""
    unitcircle(ndirection; half = false, normal = [0, 0, 1])

Create `ndirection` directions unformly distributed on the unit circle defined by `normal`.

If `half` is `true`, the directions lie on a half-circle instead of the whole circle.
"""
function unitcircle(n; half = false, normal = [0, 0, 1])
    # Create directions (unformly distributed on unit circle)
    θ = 2π * collect(0:n-1)' / (1 + half)n
    directions = [
        cos.(θ)
        sin.(θ)
        zeros(1, ndirection)
    ]

    # Create rotation matrix to transform normal to e_z
    normal = normal / norm(normal)
    c = normal + [0, 0, 1]
    R = 2(c * c') / c'c - I(3)
    normal ≈ [0, 0, 1] || (directions = R'directions)

    directions
end


using EngEconomics

# Given
xInit = 80000
xMaintenance = 30000
xSalvage = 40000
yInit = 97000
yMaintenance = 27000
ySalvage = 50000
i = 0.15
N = 3 # years

# Find: The Future Worth Analysis
# Determine Alternatives
option1Init = xInit * 2
option2Init = yInit * 2
option3Init = xInit + yInit
option1Maintenance = xMaintenance * 2
option2Maintenance = yMaintenance * 2
option3Maintenance = xMaintenance + yMaintenance
option1Salvage = 2 * xSalvage
option2Salvage = 2 * ySalvage
option3Salvage = xSalvage + ySalvage

# Determine the FW of Option1
option1InitFW = -option1Init * compoundAmountFactor(i, N)
option1MaintenanceFW = -option1Maintenance * uniformSeriesCompoundAmoundFactor(i, N)
option1SalvageFW = option1Salvage
option1FW = option1InitFW + option1MaintenanceFW + option1SalvageFW

# Determine the FW of Option2
option2InitFW = -option2Init * compoundAmountFactor(i, N)
option2MaintenanceFW = -option2Maintenance * uniformSeriesCompoundAmoundFactor(i, N)
option2SalvageFW = option2Salvage
option2FW = option2InitFW + option2MaintenanceFW + option2SalvageFW

# Determine the FW of Option3
option3InitFW = -option3Init * compoundAmountFactor(i, N)
option3MaintenanceFW = -option3Maintenance * uniformSeriesCompoundAmoundFactor(i, N)
option3SalvageFW = option3Salvage
option3FW = option3InitFW + option3MaintenanceFW + option3SalvageFW

# Pick the Better one
maxOption = max(option1FW, option2FW, option3FW)

if maxOption == option1FW
	println("Pick XX")
elseif maxOption == option2FW
	println("Pick YY")
else
	println("Pick XY")
end


abstract type AbstractHeap{T} <: AbstractTree{T} end

mutable struct BinaryHeap{T} <: AbstractHeap{T}
    tree::CompleteBinaryTree{T}
    size::Int
    maxsize::Int
    comparator::Function
    function BinaryHeap{T}(data, comparator) where T
        tree = CompleteBinaryTree{T}(data)
        size = tree.size
        maxsize = DEFAULT_MAX_SIZE
        n = new(tree, size, maxsize, comparator)
        buildheap!(n)
    end
end

BinaryHeap(data::Vector, comparator) = BinaryHeap{eltype(data)}(data, comparator)
MinHeap(data::Vector) = BinaryHeap(data, isless)
MaxHeap(data::Vector) = BinaryHeap(data, (x,y)->isless(y,x))

function _siftdown!(h::BinaryHeap, pos)
    let i = pos
        while !isleaf(h.tree, i)
            j = leftchild(h.tree, i)
            rc = rightchild(h.tree, i)
            # check if rightchild is within bounds
            if (rc != 0) && h.comparator(h.tree.data[rc], h.tree.data[j])
                j = rc
            end
            h.comparator(h.tree.data[i], h.tree.data[j]) && break
            swap!(h.tree, i, j)
            i = j
        end
    end
end

function _siftup!(h::BinaryHeap, pos)
    let i = pos
        while (i != 1) && h.comparator(h.tree.data[i], h.tree.data[parent(h.tree, i)])
            swap!(h.tree, i, parent(h.tree, i))
            i = parent(h.tree, i)
        end
    end
end

function insert!(h::BinaryHeap, item)
    (h.size < h.maxsize) || throw(ErrorException("heap capacity exceeded"))
    append!(h.tree, item)
    h.size += 1
    _siftup!(h, h.size)
end

function remove!(h::BinaryHeap)
    (h.size > 0) || throw(ArgumentError("no current element"))
    item = h.tree.data[1]
    swap!(h.tree, 1, h.size)
    h.size -= 1; h.tree.size -= 1
    if h.size != 0
        _siftdown!(h, 1)
    end
    return item
end

function remove!(h::BinaryHeap, pos)
    (1 <= pos <= h.size) || throw(BoundsError(h, pos))
    item = h.tree.data[pos]
    if pos == h.size
        h.size -= 1; h.tree.size -= 1
    else
        swap!(h.tree, pos, h.size)
        h.size -= 1; h.tree.size -= 1
        _siftup!(h, pos)
        if h.size != 0
            _siftdown!(h, pos)
        end
    end
    return item
end

function buildheap!(h::BinaryHeap)
    let i = h.size ÷ 2
        while i>0
            _siftdown!(h, i)
            i -= 1
        end
    end
    return h
end

peek(h::BinaryHeap) =  (h.size > 0) ? h.tree.data[1] : throw(ArgumentError("no current element"))
length(h::BinaryHeap) = h.size
isempty(h::BinaryHeap) = h.size == 0


### A Pluto.jl notebook ###
# v0.12.7

using Markdown
using InteractiveUtils

# ╔═╡ 6fd616f6-1ea1-11eb-3814-8bfb4a096c49
using Pkg, DrWatson

# ╔═╡ 6ffe2628-1ea1-11eb-24ea-57f985146a72
begin
	@quickactivate "StatisticsWithJuliaPlutoNotebooks"
	using Distributions, Random, StatsBase, DataFrames, Plots
	Random.seed!(1)
end;

# ╔═╡ ed174bc4-1ea0-11eb-1e2f-a32874cec549
md"## Listing 3.14"

# ╔═╡ 70179b62-1ea1-11eb-13ce-7fb4d54bad4b
begin
	faces, N = 1:6, 10^6
	mcEstimate = counts(rand(faces,N), faces)/N
end

# ╔═╡ b93d18b8-2161-11eb-299b-0d801d262dd3
begin
	plot(faces, mcEstimate,
		line=:stem, marker=:circle,
		c=:blue, ms=10, msw=0, lw=4, label="MC estimate")
	plot!([i for i in faces], [1/6 for _ in faces],
		line=:stem, marker=:xcross, c=:red,
		ms=6, msw=0, lw=2, label="PMF",
		xlabel="Face number", ylabel="Probability", ylims=(0,0.22))
end

# ╔═╡ 70182cd8-1ea1-11eb-094d-8d0d49cf15f3
md"## End of listing 3.14"

# ╔═╡ Cell order:
# ╟─ed174bc4-1ea0-11eb-1e2f-a32874cec549
# ╠═6fd616f6-1ea1-11eb-3814-8bfb4a096c49
# ╠═6ffe2628-1ea1-11eb-24ea-57f985146a72
# ╠═70179b62-1ea1-11eb-13ce-7fb4d54bad4b
# ╠═b93d18b8-2161-11eb-299b-0d801d262dd3
# ╟─70182cd8-1ea1-11eb-094d-8d0d49cf15f3


# Whitening

"""
    cov_whitening(C)

Derive the whitening transform coefficient matrix `W` given the covariance matrix `C`. Here, `C` can be either a square matrix, or an instance of `Cholesky`.

Internally, this function solves the whitening transform using Cholesky factorization. The rationale is as follows: let ``\\mathbf{C} = \\mathbf{U}^T \\mathbf{U}`` and ``\\mathbf{W} = \\mathbf{U}^{-1}``, then ``\\mathbf{W}^T \\mathbf{C} \\mathbf{W} = \\mathbf{I}``.

**Note:** The return matrix `W` is an upper triangular matrix.
"""
function cov_whitening(C::Cholesky{T}) where {T<:Real}
    cf = C.UL
    Matrix{T}(inv(istriu(cf) ? cf : cf'))
end

"""
    cov_whitening!(C)

In-place version of `cov_whitening(C)`, in which the input matrix `C` will be overwritten during computation. This can be more efficient when `C` is no longer used.
"""
cov_whitening!(C::AbstractMatrix{<:Real}) = cov_whitening(cholesky!(Hermitian(C, :U)))
cov_whitening(C::AbstractMatrix{<:Real}) = cov_whitening!(copy(C))

"""
    cov_whitening!(C, regcoef)

In-place version of `cov_whitening(C, regcoef)`, in which the input matrix `C` will be overwritten during computation. This can be more efficient when `C` is no longer used.
"""
cov_whitening!(C::AbstractMatrix{<:Real}, regcoef::Real) = cov_whitening!(regularize_symmat!(C, regcoef))

"""
    cov_whitening(C, regcoef)

Derive a whitening transform based on a regularized covariance, as `C + (eigmax(C) * regcoef) * eye(d)`.
"""
cov_whitening(C::AbstractMatrix{<:Real}, regcoef::Real) = cov_whitening!(copy(C), regcoef)

## Whitening type

"""
A whitening transform representation.
"""
struct Whitening{T<:Real} <: AbstractDataTransform
    mean::AbstractVector{T}
    W::AbstractMatrix{T}

    function Whitening{T}(mean::AbstractVector{T}, W::AbstractMatrix{T}) where {T<:Real}
        d, d2 = size(W)
        d == d2 || error("W must be a square matrix.")
        isempty(mean) || length(mean) == d ||
        throw(DimensionMismatch("Sizes of mean and W are inconsistent."))
        return new(mean, W)
    end
end
Whitening(mean::AbstractVector{T}, W::AbstractMatrix{T}) where {T<:Real} = Whitening{T}(mean, W)

"""
    length(f)

Get the dimension of the  whitening transform `f`.
"""
length(f::Whitening) = size(f.W, 1)

"""
    size(f)

Dimensions of the coefficient matrix of the whitening transform `f`.
"""
size(f::Whitening) = size(f.W)

"""
    mean(f)

Get the mean vector of the whitening transformation `f`.

**Note:** if mean is empty, this function returns a zero vector of `length(f)`.
"""
mean(f::Whitening) = fullmean(length(f), f.mean)

"""
    transform(f, x)

Apply the whitening transform `f` to a vector or a matrix `x` with samples in columns, as ``\\mathbf{W}^T (\\mathbf{x} - \\boldsymbol{\\mu})``.
"""
function transform(f::Whitening, x::AbstractVecOrMat{<:Real})
    s = size(x)
    Z, dims = if length(s) == 1
        length(f.mean) == s[1] || throw(DimensionMismatch("Inconsistent dimensions."))
        x - f.mean, 2
    else
        dims = (s[1] == length(f.mean)) + 1
        length(f.mean) == s[3-dims] || throw(DimensionMismatch("Inconsistent dimensions."))
        x .- (dims == 2 ? f.mean : transpose(f.mean)), dims
    end
    if dims == 2
        transpose(f.W) * Z
    else
        Z * f.W
    end
end

"""
    fit(Whitening, X::AbstractMatrix{T}; kwargs...)

Estimate a whitening transform from the data given in `X`.

This function returns an instance of [`Whitening`](@ref)

**Keyword Arguments:**
- `regcoef`: The regularization coefficient. The covariance will be regularized as follows when `regcoef` is positive `C + (eigmax(C) * regcoef) * eye(d)`. Default values is `zero(T)`.

- `dims`: if `1` the transformation calculated from the row samples. fit standardization parameters in column-wise fashion;
  if `2` the transformation calculated from the column samples. The default is `nothing`, which is equivalent to `dims=2` with a deprecation warning.

- `mean`: The mean vector, which can be either of:
    - `0`: the input data has already been centralized
    - `nothing`: this function will compute the mean (**default**)
    - a pre-computed mean vector

**Note:** This function internally relies on [`cov_whitening`](@ref) to derive the transformation `W`.
"""
function fit(::Type{Whitening}, X::AbstractMatrix{T};
             dims::Union{Integer,Nothing}=nothing,
             mean=nothing, regcoef::Real=zero(T)) where {T<:Real}
    if dims === nothing
        Base.depwarn("fit(Whitening, x) is deprecated: use fit(Whitening, x, dims=2) instead", :fit)
        dims = 2
    end
    if dims == 1
        n = size(X,1)
        n >= 2 || error("X must contain at least two rows.")
    elseif dims == 2
        n = size(X, 2)
        n >= 2 || error("X must contain at least two columns.")
    else
        throw(DomainError(dims, "fit only accept dims to be 1 or 2."))
    end
    mv = preprocess_mean(X, mean; dims=dims)
    Z = centralize((dims==1 ? transpose(X) : X), mv)
    C = rmul!(Z * transpose(Z), one(T) / (n - 1))
    return Whitening(mv, cov_whitening!(C, regcoef))
end

# invsqrtm

function _invsqrtm!(C::AbstractMatrix{<:Real})
    n = size(C, 1)
    size(C, 2) == n || error("C must be a square matrix.")
    E = eigen!(Symmetric(C))
    U = E.vectors
    evs = E.values
    for i = 1:n
        @inbounds evs[i] = 1.0 / sqrt(sqrt(evs[i]))
    end
    rmul!(U, Diagonal(evs))
    return U * transpose(U)
end

"""
    invsqrtm(C)

Compute `inv(sqrtm(C))` through symmetric eigenvalue decomposition.
"""
invsqrtm(C::AbstractMatrix{<:Real}) = _invsqrtm!(copy(C))


"""
Paper: Bayesian inference for finite mixtures of univariate and multivariate
       skew-normal and skew-t distributions, Biostatistics 2010.
skew (delta): a real number in (-1, 1)
"""
function rand_skewnormal(loc, scale, skew)
    z = rand(TruncatedNormal(0, 1, 0, Inf))
    return loc + scale * skew * z + scale * sqrt(1 - skew ^ 2) * randn()
end

function rand_skewnormal(loc, scale, skew, dims...)
    z = rand(TruncatedNormal(0, 1, 0, Inf), dims...)
    return loc .+ scale * skew * z + scale * sqrt(1 - skew ^ 2) * randn(dims...)
end


"""
`MaternKernel([ρ=1.0,[ν=1.0]])`
The matern kernel is an isotropic Mercer kernel given by the formula:
```
    κ(x,y) = 2^{1-ν}/Γ(ν)*(√(2ν)‖x-y‖)^ν K_ν(√(2ν)‖x-y‖)
```
For `ν=n+1/2, n=0,1,2,...` it can be simplified and you should instead use [`ExponentialKernel`](@ref) for `n=0`, [`Matern32Kernel`](@ref), for `n=1`, [`Matern52Kernel`](@ref) for `n=2` and [`SqExponentialKernel`](@ref) for `n=∞`.
"""
struct MaternKernel{Tν<:Real} <: BaseKernel
    ν::Vector{Tν}
    function MaternKernel(;nu::T=1.5, ν::T=nu) where {T<:Real}
        @check_args(MaternKernel, ν, ν > zero(T), "ν > 0")
        return new{T}([ν])
    end
end

@inline function kappa(κ::MaternKernel, d::Real)
    ν = first(κ.ν)
    iszero(d) ? one(d) :
    exp(
        (one(d) - ν) * logtwo - logabsgamma(ν)[1] +
        ν * log(sqrt(2ν) * d) +
        log(besselk(ν, sqrt(2ν) * d))
    )
end

metric(::MaternKernel) = Euclidean()

"""
`Matern32Kernel([ρ=1.0])`
The matern 3/2 kernel is an isotropic Mercer kernel given by the formula:
```
    κ(x,y) = (1+√(3)ρ‖x-y‖)exp(-√(3)ρ‖x-y‖)
```
"""
struct Matern32Kernel <: BaseKernel end

kappa(κ::Matern32Kernel, d::Real) = (1 + sqrt(3) * d) * exp(-sqrt(3) * d)

metric(::Matern32Kernel) = Euclidean()

"""
`Matern52Kernel([ρ=1.0])`
The matern 5/2 kernel is an isotropic Mercer kernel given by the formula:
```
    κ(x,y) = (1+√(5)ρ‖x-y‖ + 5ρ²‖x-y‖^2/3)exp(-√(5)ρ‖x-y‖)
```
"""
struct Matern52Kernel <: BaseKernel end

kappa(κ::Matern52Kernel, d::Real) = (1 + sqrt(5) * d + 5 * d^2 / 3) * exp(-sqrt(5) * d)

metric(::Matern52Kernel) = Euclidean()


### A Pluto.jl notebook ###
# v0.12.16

using Markdown
using InteractiveUtils

# ╔═╡ e077ab16-22e5-11eb-15ed-5f0654bf7328
using Pkg, DrWatson

# ╔═╡ e75a9a06-22e5-11eb-2e36-8d4f62b830ed
begin
	@quickactivate "StatisticsWithJuliaPlutoNotebooks"
	using Random, Distributions, NLsolve
	Random.seed!(0)
end;

# ╔═╡ ca5fbdc8-22e5-11eb-0a60-cb0a127a3ca5
md"## Listing5.08"

# ╔═╡ fa68607e-22e5-11eb-0558-c9a4d9f77426
begin
	a, b, c = 3, 5, 4
	dist = TriangularDist(a,b,c)
	n = 2000
	samples = rand(dist,n)
end

# ╔═╡ d91f4766-3a9b-11eb-2892-7b7925b084b2
begin
	m_k(k,data) = 1/n*sum(data.^k)
	mHats = [m_k(i,samples) for i in 1:3]
end

# ╔═╡ d91f7dd0-3a9b-11eb-313d-81f3bcee7e68
function equations(F, x)
    F[1] = 1/3*( x[1] + x[2] + x[3] ) - mHats[1]
    F[2] = 1/6*( x[1]^2 + x[2]^2 + x[3]^2 + x[1]*x[2] + x[1]*x[3] +
		 x[2]*x[3] ) - mHats[2]
    F[3] = 1/10*( x[1]^3 + x[2]^3 + x[3]^3 + x[1]^2*x[2] + x[1]^2*x[3] +
		 x[2]^2*x[1] + x[2]^2*x[3] + x[3]^2*x[1] + x[3]^2*x[2] +
		 x[1]*x[2]*x[3] ) - mHats[3]
end

# ╔═╡ d9200de0-3a9b-11eb-2204-db202244e154
begin
	nlOutput = nlsolve(equations, [ 0.1; 0.1; 0.1])
	sol = sort(nlOutput.zero)
	aHat, bHat, cHat = sol[1], sol[3], sol[2]
	Text("Found estimates for (a,b,c) = $((aHat, bHat, cHat))")
	nlOutput
end

# ╔═╡ 475ff888-22e6-11eb-2354-09f8f40a8e12
md"## End of listing5.08"

# ╔═╡ Cell order:
# ╟─ca5fbdc8-22e5-11eb-0a60-cb0a127a3ca5
# ╠═e077ab16-22e5-11eb-15ed-5f0654bf7328
# ╠═e75a9a06-22e5-11eb-2e36-8d4f62b830ed
# ╠═fa68607e-22e5-11eb-0558-c9a4d9f77426
# ╠═d91f4766-3a9b-11eb-2892-7b7925b084b2
# ╠═d91f7dd0-3a9b-11eb-313d-81f3bcee7e68
# ╠═d9200de0-3a9b-11eb-2204-db202244e154
# ╟─475ff888-22e6-11eb-2354-09f8f40a8e12


using NPFinancial
using Test

@testset "Tests" begin

    @test NPFinancial._convert_when(:begin) == 1
    @test NPFinancial._convert_when(:end) == 0

    @info "Custom tests"
    @test fv(0.01, 1, 0, -100, :end) == 101.0
    @test map(x -> fv(x, 10*12, -100, -100), [0.05, 0.06, 0.07]/12) ≈ [15692.92889434,  16569.87435405,  17509.44688102]
    @test pmt(0.075/12, 12*15, 200000) ≈ -1854.0247200054619
    @test nper(0.07/12, -150, 8000) ≈ 64.07334877066185
    @test ipmt(0.01, 4, 10, -100.0) ≈ 0.7103767498422946
    @test rate(1, 0, -100, 101) ≈ 0.01
    @test isnan(rate(10, -10, 100, 102))

    # unit tests borrowed from numpy

    @info "Testing irr"
    v = [-150000, 15000, 25000, 35000, 45000, 60000]
    @test round(irr(v), digits = 4) == 0.0524

    v = [-100, 0, 0, 74]
    @test round(irr(v), digits = 4) == -0.0955

    v = [-100, 39, 59, 55, 20]
    @test round(irr(v), digits = 5) == 0.28095

    v = [-100, 100, 0, -7]
    @test round(irr(v), digits = 4) == -0.0833

    v = [-100, 100, 0, 7]
    @test round(irr(v), digits = 5) == 0.06206

    v = [-5, 10.5, 1, -8, 1]
    @test round(irr(v), digits = 4) == 0.0886

    v = [-1, -2, -3]
    @test isnan(irr(v))    # in Julia, we cannot compare NaN with NaN

    @info "Testing pv"
    @test round(pv(0.07, 20, 12000, 0), digits = 2) == -127128.17

    @info "Testing pmt"
    res = pmt(0.08 / 12, 5 * 12, 15000)
    tgt = -304.145914
    @test round(res, digits = 6) == round(tgt, digits = 6)

    # Test the edge case where rate == 0.0
    res = pmt(0.0, 5 * 12, 15000)
    tgt = -250.0
    @test round(res, digits = 6) == round(tgt, digits = 6)

    # julia broadcast
    res = pmt.([0.3, 0.8], [12, 3], [2000, 20000])
    tgt = [-626.90814, -19311.258]
    # @info "res=$res"
    # @info "tgt=$tgt"
    # @info "result=$(round.(res,3) .== round.(tgt,3))"
    @test all(round.(res, digits = 3) .== round.(tgt, digits = 3)) == true

    @info "Testing ppmt"
    @test round(ppmt(0.1 / 12, 1, 60, 55000), digits = 2) == -710.25
    @test round(ppmt(0.23 / 12, 1, 60, 10000000000), digits = 8) == -90238044.232277036

    @info "Testing ipmt"
    @test round(ipmt(0.1 / 12, 1, 24, 2000), digits = 2) == -16.67

    @info "Testing nper"
    @test round(nper(0.075, -2000, 0, 100000.), digits = 2) == 21.54
    @test round(nper(0.0, -2000, 0, 100000.), digits = 1) == 50.0

    @info "Testing npv"
    @test round(npv(0.05, [-15000, 1500, 2500, 3500, 4500, 6000]), digits = 2) == 122.89

    @info "Testing mirr"
    val = [-4500, -800, 800, 800, 600, 600, 800, 800, 700, 3000]
    @test round(mirr(val, 0.08, 0.055), digits = 4) == 0.0666

    val = [-120000, 39000, 30000, 21000, 37000, 46000]
    @test round(mirr(val, 0.10, 0.12), digits = 6) == 0.126094

    val = [100, 200, -50, 300, -200]
    @test round(mirr(val, 0.05, 0.06), digits = 4) == 0.3428

    val = [39000, 30000, 21000, 37000, 46000]
    @test isnan(mirr(val, 0.10, 0.12))

    @info "Testing eir"
    @test round(eir(0.08, 2), digits = 3) == 0.082

    @info "All tests completed"
end


#=
The Fibonacci sequence is defined by the recurrence relation:

Fn = Fn−1 + Fn−2, where F1 = 1 and F2 = 1.
Hence the first 12 terms will be:

F1 = 1
F2 = 1
F3 = 2
F4 = 3
F5 = 5
F6 = 8
F7 = 13
F8 = 21
F9 = 34
F10 = 55
F11 = 89
F12 = 144
The 12th term, F12, is the first term to contain three digits.

What is the first term in the Fibonacci sequence to contain 1000 digits?
=#
function calc()
  a = BigInt[1, 1]
  while length("$(last(a))") < 1_000
    a1 = a[length(a)] + a[length(a)-1]
    push!(a, a1)
  end
  length(a)
end
@time println(calc())


"""Calculate the bitpattern entropy in base `base` for all elements in array A."""
function bitpattern_entropy(A::AbstractArray,base::Real=2)
    nbits = sizeof(eltype(A))*8
    T = whichUInt(nbits)
    return bitpattern_entropy(T,A,base)
end

"""Calculate the bitpattern entropy in base `base` for all elements in array A.
In place version that will sort the array."""
function bitpattern_entropy!(A::AbstractArray,base::Real=2)
    nbits = sizeof(eltype(A))*8
    T = whichUInt(nbits)
    return bitpattern_entropy!(T,A,base)
end

"""Calculate the bitpattern entropy for an array A by reinterpreting the elements
as UInts and sorting them to avoid creating a histogram."""
function bitpattern_entropy(::Type{T},A::AbstractArray,base::Real) where {T<:Unsigned}
    return bitpattern_entropy!(T,copy(A),base)
end

"""Calculate the bitpattern entropy for an array A by reinterpreting the elements
as UInts and sorting them to avoid creating a histogram.
In-place version of bitpattern_entropy."""
function bitpattern_entropy!(::Type{T},A::AbstractArray,base::Real) where {T<:Unsigned}

    # reinterpret to UInt then sort to avoid allocating a histogram
    # in-place ver
    sort!(reinterpret(T,vec(A)))    # TODO maybe think of an inplace version?
    n = length(A)

    E = 0.0     # entropy
    m = 1       # counter

    for i in 1:n-1
        @inbounds if A[i] == A[i+1]     # check whether next entry belongs to the same bin in histogram
            m += 1
        else
            p = m/n
            E -= p*log(p)
            m = 1
        end
    end

    p = m/n         # complete for last bin
    E -= p*log(p)

    # convert to given base, 2 i.e. [bit] by default
    E /= log(base)

    return E
end


# ---
# title: 335. Self Crossing
# id: problem335
# author: Tian Jun
# date: 2020-10-31
# difficulty: Hard
# categories: Math
# link: <https://leetcode.com/problems/self-crossing/description/>
# hidden: true
# ---
#
# You are given an array _x_ of `n` positive numbers. You start at point `(0,0)`
# and moves `x[0]` metres to the north, then `x[1]` metres to the west, `x[2]`
# metres to the south, `x[3]` metres to the east and so on. In other words,
# after each move your direction changes counter-clockwise.
#
# Write a one-pass algorithm with `O(1)` extra space to determine, if your path
# crosses itself, or not.
#
#
#
# **Example 1:**
#
#
#
#     **┌───┐
#     │   │
#     └───┼──>
#         │
#
#     Input: **[2,1,1,2]
#     Output: true
#
#
# **Example 2:**
#
#
#
#     **┌──────┐
#     │      │
#     │
#     │
#     └────────────>
#
#     Input:** [1,2,3,4]
#     Output: false
#
#
# **Example 3:**
#
#
#
#     **┌───┐
#     │   │
#     └───┼>
#
#     Input:** [1,1,1,1]
#     Output: true
#
#
#
## @lc code=start
using LeetCode

## add your code here:
## @lc code=end


"""
    rand_unitary(N, dims=(N,))

Generate a Haar distributed random unitary operator for a Hilbert space of size `N`. It is possible to specify the subspace dimensions with the `dims` argument. Returns a dense matrix.

# Example
```jldoctest
julia> U = rand_unitary(4,(2,2));

julia> U'*U ≈ qeye(4,(2,2))
true
```
"""
function rand_unitary(N::Integer, dims::Dims=(N,))
    return Operator(rand_unitary(ComplexF64,N),dims)
end

function rand_unitary(::Type{T},n::Integer) where {T<:Compat.LinearAlgebra.BlasComplex}
    # Generate a Haar distributed random unitary matrix
    # ref https://arxiv.org/pdf/math-ph/0609050.pdf
    A = randn(T,n,n)
    f = qr!(A)
    U = Matrix(Compat.LinearAlgebra.QRCompactWYQ(f.factors,f.T))
    @inbounds for i = 1:n
        U[:,i] .*= sign(f.factors[i,i]) # U = Q*Diagonal(diag(R)./abs.(diag(R)))
    end
    return U
end


"""
    evolve!(curr::Field, prev::Field, a, dt)

Calculate a new temperature field curr based on the previous
field prev. a is the diffusion constant and dt is the largest
stable time step.
"""
function evolve!(curr::Field, prev::Field, a, dt)
    for j = 2:curr.ny+1
        for i = 2:curr.nx+1
            xderiv = (prev.data[i-1, j] - 2.0 * prev.data[i, j] + prev.data[i+1, j]) / curr.dx^2
            yderiv = (prev.data[i, j-1] - 2.0 * prev.data[i, j] + prev.data[i, j+1]) / curr.dy^2
            curr.data[i, j] = prev.data[i, j] + a * dt * (xderiv + yderiv)
        end
    end
end

"""
    swap_fields!(curr::Field, prev::Field)

Swap the data of two fields curr and prev.
"""
function swap_fields!(curr::Field, prev::Field)
    tmp = curr.data
    curr.data = prev.data
    prev.data = tmp
end

"""
    average_temperature(f::Field)

Calculate average temperature of a temperature field.
"""
average_temperature(f::Field) = sum(f.data[2:f.nx+1, 2:f.ny+1]) / (f.nx * f.ny)

"""
    simulate!(current, previous, nsteps)

Run the heat equation solver on fields curr and prev for nsteps.
"""
function simulate!(curr::Field, prev::Field, nsteps)

    println("Initial average temperature: $(average_temperature(curr))")

    # Diffusion constant
    a = 0.5
    # Largest stable time step
    dt = curr.dx^2 * curr.dy^2 / (2.0 * a * (curr.dx^2 + curr.dy^2))

    # display a nice progress bar
    p = Progress(nsteps)

    for i = 1:nsteps
        # calculate new state based on previous state
        evolve!(curr, prev, a, dt)

        # swap current and previous fields
        swap_fields!(curr, prev)

        # increment the progress bar
        next!(p)
    end

    # print final average temperature
    println("Final average temperature: $(average_temperature(curr))")
end


export Sin

"""
`Sin([domainType=Float64::Type,] dim_in::Tuple)`

Creates a sinusoid non-linear operator with input dimensions `dim_in`:
```math
\\sin( \\mathbf{x} ).
```

"""
struct Sin{T,N} <: NonLinearOperator
	dim::NTuple{N,Int}
end

function Sin(DomainType::Type, DomainDim::NTuple{N,Int}) where {N}
	Sin{DomainType,N}(DomainDim)
end

Sin(DomainDim::NTuple{N,Int}) where {N} = Sin{Float64,N}(DomainDim)
Sin(DomainDim::Vararg{Int}) = Sin{Float64,length(DomainDim)}(DomainDim)

function mul!(y::AbstractArray{T,N}, L::Sin{T,N}, x::AbstractArray{T,N}) where {T,N}
	y .= sin.(x)
end

function mul!(y::AbstractArray,
              J::AdjointOperator{Jacobian{A,TT}},
              b::AbstractArray) where {T,N, A<: Sin{T,N}, TT <: AbstractArray{T,N}}
    L = J.A
    y .= conj.(cos.(L.x)).*b
end

fun_name(L::Sin) = "sin"

size(L::Sin) = (L.dim, L.dim)

domainType(L::Sin{T,N}) where {T,N} = T
codomainType(L::Sin{T,N}) where {T,N} = T


function dJv_seriesdk(k2::T,v::Int64) where {T <: Real}
# Use series expansion to compute J_v:
nmax = 100
n = 1; error = Inf; if k2 < 1; tol = eps(k2); else; tol = eps(inv(k2)); end
# Computing leading coefficient (n=0):
#coeff = 3pi/(2^(2+v)*factorial(v+2))
if k2 < 1
  coeff = .75*pi/exp(lfact(v+2))
#  println("coefficient: ",coeff)
# multiply by (2v-1)!!
  for i=1:v
    coeff *= (2i-1)/2
  end
# Add leading term to J_v:
  Jv = one(k2)*coeff
  dJvdk = one(k2)*coeff*(2v+1)
# Now, compute higher order terms until desired precision is reached:
  while n < nmax && abs(error) > tol
    coeff *= (2n-1)*(2(n+v)-1)/(2n*(2n+2v+4))*k2
    Jv += coeff
    dJvdk += coeff*(2n+2v+1)
#    error = coeff/Jv
    error = coeff
    n += 1
  end
  dJvdk *= k2^v
  Jv *= k2^v*sqrt(k2)
#  println("Jv: ",Jv," dJv/dk: ",dJvdk)
  return Jv,dJvdk
else # k^2 >= 1
  coeff = convert(typeof(k2),pi)
  # Compute (2v-1)!!/(2^v v!):
  for i=1:v
#    coeff *= 1.-.5/i
    coeff *= (2i-1)/(2i)
  end
  Jv = one(k2)*coeff
  dJvdk = zero(k2)
  k2inv = inv(k2); n=2
  while n < nmax && abs(error) > tol
#    coeff *= (1.-2.5/n)*(1.-.5/(n+v))*k2inv
    coeff *= (n-5)*(n+2v-1)/(n*(n+2v))
    coeff *= k2inv
    Jv += coeff
    dJvdk -= n*coeff
#    error = coeff/Jv
    error = coeff
    n += 2
  end
  dJvdk /= sqrt(k2)
  return Jv,dJvdk
end
end


list_test_str = split(
"forward 5
down 5
forward 8
up 3
down 8
forward 2",
'\n'
)

list_str = readlines("day2_input.txt")

# Part 1

function part1(list)
    depth = 0
    pos = 0
    for cmd in list
        m, k = split(cmd)
        k = parse(Int, k)
        pos += m == "forward" ? k : 0
        depth += m == "up" ? -k : m == "down" ? k : 0
    end
    return pos * depth
end

@info "Part 1"
@info "test:"
@show part1(list_test_str) == 150
@info "challenge:"
@show part1(list_str) == 1714680

# Part 2

function part2(list)
    depth = 0
    pos = 0
    aim = 0
    for cmd in list
        m, k = split(cmd)
        k = parse(Int, k)
        pos += m == "forward" ? k : 0
        depth += m == "forward" ? aim * k : 0
        aim += m == "up" ? -k : m == "down" ? k : 0
    end
    return pos * depth
end

@info "Part 2"
@info "test:"
@show part2(list_test_str) == 900
@info "challenge:"
@show part2(list_str) == 1963088820

# Alternative

# Part 1

forward(k) = [0, k] # depth and pos
up(k) = [-k, 0]
down(k) = [k, 0]

function part1_alt(list)
    commands = [split(cmd) |> u -> (Symbol(first(u)), parse(Int, last(u))) for cmd in list]
    return prod(mapreduce(u -> eval(first(u))(last(u)), +, commands))
end

@info "Part 1 alt"
@info "test:"
@show part1_alt(list_test_str) == 150
@info "challenge:"
@show part1_alt(list_str) == 1714680

# Part 2

function forward!(u, k)
    u[1] += u[3] * k
    u[2] += k
end
function up!(u, k)
    u[3] -= k
end
function down!(u, k)
    u[3] += k
end

function part2_alt2(list)
    u = [0, 0, 0]
    for l in list
        cmd = l[1]
        k = parse(Int, l[end])
        if cmd == 'f'
            forward!(u, k)
        elseif cmd == 'u'
            up!(u, k)
        else
            down!(u, k)
        end
    end
    return u[1] * u[2]
end

@info "Part 2 alt"
@info "test:"
@show part2_alt2(list_test_str) == 900
@info "challenge:"
@show part2_alt2(list_str) == 1963088820

nothing


# ---
# title: 354. Russian Doll Envelopes
# id: problem354
# author: Indigo
# date: 2021-03-04
# difficulty: Hard
# categories: Binary Search, Dynamic Programming
# link: <https://leetcode.com/problems/russian-doll-envelopes/description/>
# hidden: true
# ---
#
# You have a number of envelopes with widths and heights given as a pair of
# integers `(w, h)`. One envelope can fit into another if and only if both the
# width and height of one envelope is greater than the width and height of the
# other envelope.
#
# What is the maximum number of envelopes can you Russian doll? (put one inside
# other)
#
# **Note:**
# Rotation is not allowed.
#
# **Example:**
#
#
#
#     Input: [[5,4],[6,4],[6,7],[2,3]]
#     Output: 3
#     **Explanation: T** he maximum number of envelopes you can Russian doll is 3 ([2,3] => [5,4] => [6,7]).
#
#
#
## @lc code=start
using LeetCode

function max_envelopes(envelopes::Vector{Vector{Int}})
    isempty(envelopes) && return 0
    sort!(envelopes, by = x -> (x[1], -x[2]))
    dp = [envelopes[1][2]]
    for envo in @view(envelopes[2:end])
        if envo[2] > dp[end]
            push!(dp, envo[2])
        else
            dp[searchsortedfirst(dp, envo[2])] = envo[2]
        end
    end
    length(dp)
end
## @lc code=end


#=
# 2. Potential flow with an impenetrable surface

This part introduces the treatment of rigid impenetrable surfaces in `GridPotentialFlow`. We can impose the no-penetration constraint by setting the fluid streamfunction equal to that of the surface $\mathfrak{s}_b$, up to a uniform value $s_0$. The discrete no-penetration constraint is thus

$\mathsf{Es}=\mathfrak{s}_{b}-\mathsf{E} \mathsf{s}_{\infty}-\mathfrak{s}_{0},$

where $\mathsf{E}$ is the interpolation operator that interpolates grid data to the surface points, $\mathsf{s}_{\infty}$ is the streamfunction of the uniform flow. For a body translating at velocity $(U,V)$ and rotating at angular velocity $\Omega$, this streamfunction would be

$\mathfrak{s}_{b,k} = U \mathfrak{r}_y - V \mathfrak{r}_x - \frac{1}{2} \Omega (\mathfrak{r}_x^2+\mathfrak{r}_y^2).$

For later shorthand, we will denote the difference between the body motion streamfunction and interpolated uniform flow streamfunction by $\mathfrak{s}'_b \equiv \mathfrak{s}_b - \mathsf{E} \mathsf{s}_{\infty}$. The no-penetration constraint is enforced in the basic potential flow problem with the help of a vector of Lagrange multipliers, $\mathfrak{f}$, on the surface points. The modified potential flow problem is thus

$\mathsf{Ls} + \mathsf{R}\mathfrak{f} = -\mathsf{w},$

where $\mathsf{R}$ is the regularization operator that transfers data from surface data to nodes. $\mathsf{E}$ can be constructed (and we will assume it has) so that it is the transpose of the interpolation operator, $\mathsf{E} = \mathsf{R}^{T}$.

From the previous equation, it is clear (by simple comparison with the vorticity on the right-hand side) that the vector $\mathfrak{f}$ represents the strength of the discrete bound vortex sheet on the surface. Suppose we consider the bound vortex sheet $\gamma(s)$ that emerges from the analogous continuous problem on the undiscretized surface, where $s$ is the arc-length parameter along the surface. At each point $p$, the discrete solution $\mathfrak{f}$ is approximately equal to this continuous solution, multiplied by the length $\delta S_p$ of the small segment surrounding the point:

$\mathfrak{e}_{p}^{T} \mathfrak{f} \approx \gamma(s) \delta S_p.$

Thus, the potential flow problem in the presence of the impenetrable surface is

$\begin{bmatrix}
\mathsf{L} & \mathsf{R} \\
\mathsf{E} &  0
\end{bmatrix} \begin{pmatrix} \mathsf{s} \\ \mathfrak{f} \end{pmatrix} =
\begin{pmatrix} -\mathsf{w} \\ \mathfrak{s}'_b - \mathfrak{s}_0 \end{pmatrix}.$

This problem has the structure of a generic saddle-point problem. `GridPotentialFlow.jl` automatically formulates the no-penetration constraint on the streamfunction and solves the saddle system when it needs to. See the next page for the role of $\mathfrak{s}_0$ and how it will be treated internally.
=#

# We will now consider two examples. In these examples, we use the following grid.
#md # ```@setup 2.-Potential-flow-with-an-impenetrable-surface
#md # using GridPotentialFlow
#md # using Plots
#md # ```
#!md using GridPotentialFlow
#!md using Plots
Δx = 0.02
Lx = 4.0
xlim = (-Lx/2,Lx/2)
ylim = (-Lx/2,Lx/2)
g = PhysicalGrid(xlim,ylim,Δx);

#=
## Vortex near a cylinder

As a basic example, consider now a point vortex near a circular cylinder.
=#

# A body in `GridPotentialFlow.jl` is represented by `PotentialFlowBody`. This is a wrapper around a `Body` from `RigidBodyTools.jl` and can be endowed with a linear velocity, rotational velocity, circulation, and other properties using the appropriate keywords during construction or by mutating its fields using its setter methods. For our circular cylinder, we use the `Circle` shape.
Rc = Lx/4
Δs = 2Δx
body = PotentialFlowBody(Circle(Rc,Δs))

# We choose an initial position $R_v$ of the vortex at $3/2 R_c$ from the origin.
Rv = 3/2*Rc
Γv = 1.0
v = Vortex(Rv,0.0,Γv);

# And we set an equal and opposite circulation about the body
setΓ(body,-Γv)

# We construct the `VortexModel` by using the optional `bodies` keyword. In this case, we are interested in the bound vortex sheet strength $\mathfrak{f}$, instead of just the streamfunction field. For this reason, we use `solve`, which in this case returns a `ConstrainedIBPoissonSolution`.
model = VortexModel(g,vortices=[v],bodies=[body]);
sol = solve(model);

#md # ```@setup 2.-Potential-flow-with-an-impenetrable-surface
#md # # Analytical solution (vortex at Rv and image vortex at Rc^2/Rv)
#md # r1 = sqrt.((body.points.x.-Rv).^2 .+ (body.points.y).^2) # distance from vortex to points on circle
#md # r2 = sqrt.((body.points.x.-Rc^2/Rv).^2 .+ (body.points.y).^2) # distance from image vortex to points on circle
#md # θ1 = π.+atan.((body.points.y)./(body.points.x.-Rv)) # angle from vortex to points on circle
#md # θ2 = atan.((body.points.y),(body.points.x.-Rc^2/Rv)) # angle from image vortex to points on circle
#md # v1x = -v.Γ./(2π*r1).*cos.(θ1.-π/2) # x velocity on circle induced by vortex
#md # v1y = -v.Γ./(2π*r1).*sin.(θ1.-π/2) # y velocity on circle induced by vortex
#md # v2x = v.Γ./(2π*r2).*cos.(θ2.-π/2) # x velocity on circle induced by image vortex
#md # v2y = v.Γ./(2π*r2).*sin.(θ2.-π/2) # y velocity on circle induced by image vortex
#md # V = sqrt.((v1x.+v2x).^2+(v1y.+v2y).^2) # velocity magnitude on circle
#md # γ = -V; # bound vortex sheet strength on circle (velocity on circle is clockwise if positive vortex is to the right of it)
#md # ```

#!md # Analytical solution (vortex at Rv and image vortex at Rc^2/Rv)
#!md r1 = sqrt.((body.points.x.-Rv).^2 .+ (body.points.y).^2) # distance from vortex to points on circle
#!md r2 = sqrt.((body.points.x.-Rc^2/Rv).^2 .+ (body.points.y).^2) # distance from image vortex to points on circle
#!md θ1 = π.+atan.((body.points.y)./(body.points.x.-Rv)) # angle from vortex to points on circle
#!md θ2 = atan.((body.points.y),(body.points.x.-Rc^2/Rv)) # angle from image vortex to points on circle
#!md v1x = -v.Γ./(2π*r1).*cos.(θ1.-π/2) # x velocity on circle induced by vortex
#!md v1y = -v.Γ./(2π*r1).*sin.(θ1.-π/2) # y velocity on circle induced by vortex
#!md v2x = v.Γ./(2π*r2).*cos.(θ2.-π/2) # x velocity on circle induced by image vortex
#!md v2y = v.Γ./(2π*r2).*sin.(θ2.-π/2) # y velocity on circle induced by image vortex
#!md V = sqrt.((v1x.+v2x).^2+(v1y.+v2y).^2) # velocity magnitude on circle
#!md γ = -V; # bound vortex sheet strength on circle (velocity on circle is clockwise if positive vortex is to the right of it)

# We can then easily retrieve the streamfunction and the bound vortex sheet strength from the fields of the solution variable.
plot(sol.ψ,g)
plot!(body,fillcolor=:black,fillrange=0,fillalpha=0.25,linecolor=:black,linewidth=2)
scatter!([v.x],[v.y],color=:black,markersize=2,xlabel="x",ylabel="y")

# For this example, we can easily calculate the analytical bound vortex sheet strength $\gamma$ and compare it with our numerical solution $f$.
#md # See the notebook in the examples folder for the analytical solution.
plot(sol.f./Δs,label="f/ds",xlabel="body point index")
plot!(γ,label="gamma")

# Finally, we can also check if the vortex will move around the cylinder on a circular path if we advance the system in time, as predicted by analytical potential flow theory. The period of the circular motion can be easily determined by calculating the velocity induced by the image vortex at $R_c^2/R_v$:

Vv = Γv/(2π*(Rv-Rc^2/Rv))
T = 2π*Rv/Vv

# For the time stepping, we again use the fourth-order Runge Kutta scheme from `OrdinaryDiffEq.jl`.
import OrdinaryDiffEq
function rhs(X,model,t)
    setvortexpositions!(model,X)
    Ẋ = vortexvelocities!(model)
    return Ẋ
end
X = getvortexpositions(model)
prob = OrdinaryDiffEq.ODEProblem(rhs,X,(0.0,T),model);
sol = OrdinaryDiffEq.solve(prob,dt=0.1,OrdinaryDiffEq.RK4(),dense=false,adaptive=false);
plot(body,fillcolor=:black,fillrange=0,fillalpha=0.25,linecolor=:black,linewidth=2)
plot!(map(s->s.u[1],sol.u),map(s->s.v[1],sol.u))
scatter!([v.x],[v.y],color=:black,markersize=2,xlabel="x",ylabel="y")

#jl @testset "Vortex near cylinder" begin
#jl     import OrdinaryDiffEq
#jl
#jl     # analytical solution
#jl     Rv = sqrt(v.x^2+v.y^2)
#jl     Vθ = -v.Γ/(2π*(Rv-Rc^2/Rv))
#jl     Tv = 2π*Rv/abs(Vθ)
#jl
#jl     # numerical solution
#jl     tspan = (0,Tv)
#jl     model = VortexModel(g,bodies=[body],vortices=[v])
#jl
#jl     function rhs(X,model,t)
#jl         setvortexpositions!(model,X)
#jl         Ẋ = vortexvelocities!(model)
#jl         return Ẋ
#jl     end
#jl
#jl     X = getvortexpositions(model)
#jl     prob = OrdinaryDiffEq.ODEProblem(rhs,X,tspan,model);
#jl     sol = OrdinaryDiffEq.solve(prob,dt=0.1,OrdinaryDiffEq.RK4(),dense=false,adaptive=false);
#jl
#jl     @test isapprox(sol.u[end].u[1], 1.5; atol = 1e-2)
#jl     @test isapprox(sol.u[end].v[1], 0.0; atol = 1e-1)
#jl end

#=
## Moving cylinder

In this example we will create a model with a moving body.
=#

# Here we use the keyword `U` to add create a body with a linear velocity.
body = PotentialFlowBody(Circle(Rc,Δs), U=(1.0,0.0))
model = VortexModel(g,bodies=[body]);
sol = solve(model);

#md # ```@setup 2.-Potential-flow-with-an-impenetrable-surface
#md # # Analytical solution
#md # θ = atan.(body.points.y,body.points.x)
#md # γ = 2*sin.(θ);
#md # ```

#!md # # Analytical solution
#!md θ = atan.(body.points.y,body.points.x)
#!md γ = 2*sin.(θ);

plot(sol.ψ,g)
plot!(body,fillcolor=:black,fillrange=0,fillalpha=0.25,linecolor=:black,linewidth=2,xlabel="x",ylabel="y")

# Again, we can compare the discrete bound vortex sheet strength with the analytical vortex sheet strength.
plot(sol.f./Δs,label="f/ds",xlabel="body point index")
plot!(γ,label="gamma")


import RowEchelon

#=
Homework 2
Section 1.3
Problem 13
=#

println()

# Create the Matrix to be evaluated
A = [1 -4 2 3 ;
     0 3 5 -7 ;
    -2 8 -4 -3]

# Store the number of columns
columns = 4

# Print the original Matrix
println("Original Matrix:")
println(A)
println()

# Row reduce the Matrix
A_rref = RowEchelon.rref(A)

# Print the RREF'd matrix
println("Row Reduced Echelon Form:")
println(A_rref)
println()

# Determine whether or not matrix was consistent
index = 0
leadingEntryLocated = false
consistent = true
for value in A_rref
    global index
    global columns
    global leadingEntryLocated
    global consistent

    # Determine first if we are in the 0 column of a Row
    if index % columns == 0
        # If here then:
        # We are in the 0 columns
        # Reset Leading Entry Locator
        leadingEntryLocated = false
    end
    # Now we can save time by skipping the rest of the Row
    # If a leading entry has already been found
    if leadingEntryLocated == false
        # If here then:
        # Leading entry hasnt been found yet
        # Evaluate the current position
        # Determine if its a leading entry
        if value != 0
            # If here then:
            # This row has a leading entry somewhere
            if index % columns == (columns - 1)
                # If here then:
                # This entry is in the last column
                # Matrix is not consistent
                consistent = false
            else
                # Else:
                # The matrix hasnt been proven inconsistent
                leadingEntryLocated = true
            end
        end
    end
    index = index+1
end

if consistent == true
    # If here then:
    # Matrix is consistent
    println("This matrix is consistent.")
    print("Hence the vector b is a linear combination of the ")
    print("vectors made by the columns of the original matrix.")
else
    # Else:
    # Matrix is inconsistent
    println("This matrix is inconsistent.")
    print("Hence the vector b is NOT a linear combination of the ")
    print("vectors made by the columns of the original matrix.")
end

# Print two blanks lines for a better display
println()
println()
exit(0)


cd(@__DIR__)
using Pkg; Pkg.activate("."); Pkg.instantiate()

#This script simulates the Fisher-KPP equation and fits
#a neural PDE to the data with the growth (aka reaction) term replaced
#by a feed-forward neural network and the diffusion term with a CNN

using PyPlot, Printf
using LinearAlgebra
using Flux, DiffEqFlux, Optim, DiffEqSensitivity
using BSON: @save, @load
using Flux: @epochs
using OrdinaryDiffEq

#parameter
D = 0.01; #diffusion
r = 1.0; #reaction rate

#domain
X = 1.0; T = 5;
dx = 0.04; dt = T/10;
x = collect(0:dx:X);
t = collect(0:dt:T);
Nx = Int64(X/dx+1);
Nt = Int64(T/dt+1);

#initial conditions
Amp = 1.0;
Delta = 0.2
#IC-1
rho0 = Amp*(tanh.((x .- (0.5 - Delta/2))/(Delta/10)) - tanh.((x .- (0.5 + Delta/2))/(Delta/10)))/2
#IC-2
#rho0 = Amp*(1 .- tanh.((x .- 0.2)/(Delta/6)))/2.

save_folder = "data"

if isdir(save_folder)
    rm(save_folder, recursive=true)
end
mkdir(save_folder)

close("all")
figure()
plot(x, rho0)
title("Initial Condition")
gcf()

########################
# Generate training data
########################
reaction(u) = r * u .* (1 .- u)
lap = diagm(0 => -2.0 * ones(Nx), 1=> ones(Nx-1), -1 => ones(Nx-1)) ./ dx^2
#Periodic BC
lap[1,end] = 1.0/dx^2
lap[end,1] = 1.0/dx^2
#Neumann BC
#lap[1,2] = 2.0/dx^2
#lap[end,end-1] = 2.0/dx^2

function rc_ode(rho, p, t)
    #finite difference
    D * lap * rho + reaction.(rho)
end

prob = ODEProblem(rc_ode, rho0, (0.0, T), saveat=dt)
sol = solve(prob, Tsit5());
ode_data = Array(sol);

figure(figsize=(8,3))

subplot(121)
pcolor(x,t,ode_data')
xlabel("x"); ylabel("t");
colorbar()

subplot(122)
for i in 1:2:Nt
    plot(x, ode_data[:,i], label="t=$(sol.t[i])")
end
xlabel("x"); ylabel(L"$\rho$")
legend(frameon=false, fontsize=7, bbox_to_anchor=(1, 1), loc="upper left", ncol=1)
tight_layout()
savefig(@sprintf("%s/training_data.pdf", save_folder))
gcf()

########################
# Define the neural PDE
########################
n_weights = 10

#for the reaction term
rx_nn = Chain(Dense(1, n_weights, tanh),
                Dense(n_weights, 2*n_weights, tanh),
                Dense(2*n_weights, n_weights, tanh),
                Dense(n_weights, 1),
                x -> x[1])

#conv with bias with initial values as 1/dx^2
w_err = 0.0
init_w = reshape([1.1 -2.5 1.0], (3, 1, 1, 1))
diff_cnn_ = Conv(init_w, [0.], pad=(0,0,0,0))

#initialize D0 close to D/dx^2
D0 = [6.5]

p1,re1 = Flux.destructure(rx_nn)
p2,re2 = Flux.destructure(diff_cnn_)
p = [p1;p2;D0]
full_restructure(p) = re1(p[1:length(p1)]), re2(p[(length(p1)+1):end-1]), p[end]

function nn_ode(u,p,t)
    rx_nn = re1(p[1:length(p1)])

    u_cnn_1   = [p[end-4] * u[end] + p[end-3] * u[1] + p[end-2] * u[2]]
    u_cnn     = [p[end-4] * u[i-1] + p[end-3] * u[i] + p[end-2] * u[i+1] for i in 2:Nx-1]
    u_cnn_end = [p[end-4] * u[end-1] + p[end-3] * u[end] + p[end-2] * u[1]]

    # Equivalent using Flux, but slower!
    #CNN term with periodic BC
    #diff_cnn_ = Conv(reshape(p[(end-4):(end-2)],(3,1,1,1)), [0.0], pad=(0,0,0,0))
    #u_cnn = reshape(diff_cnn_(reshape(u, (Nx, 1, 1, 1))), (Nx-2,))
    #u_cnn_1 = reshape(diff_cnn_(reshape(vcat(u[end:end], u[1:1], u[2:2]), (3, 1, 1, 1))), (1,))
    #u_cnn_end = reshape(diff_cnn_(reshape(vcat(u[end-1:end-1], u[end:end], u[1:1]), (3, 1, 1, 1))), (1,))

    [rx_nn([u[i]])[1] for i in 1:Nx] + p[end] * vcat(u_cnn_1, u_cnn, u_cnn_end)
end

########################
# Soving the neural PDE and setting up loss function
########################
prob_nn = ODEProblem(nn_ode, rho0, (0.0, T), p)
sol_nn = concrete_solve(prob_nn,Tsit5(), rho0, p)

function predict_rd(θ)
  # No ReverseDiff if using Flux
  Array(concrete_solve(prob_nn,Tsit5(),rho0,θ,saveat=dt,sensealg=InterpolatingAdjoint(autojacvec=ReverseDiffVJP())))
end

#match data and force the weights of the CNN to add up to zero
function loss_rd(p)
    pred = predict_rd(p)
    sum(abs2, ode_data .- pred) + 10^2 * abs(sum(p[end-4 : end-2])), pred
end

########################
# Training
########################

#Optimizer
opt = ADAM(0.001)

global count = 0
global save_count = 0
save_freq = 50

train_arr = Float64[]
diff_arr = Float64[]
w1_arr = Float64[]
w2_arr = Float64[]
w3_arr = Float64[]

#callback function to observe training
cb = function (p,l,pred)
    rx_nn, diff_cnn_, D0 = full_restructure(p)
    push!(train_arr, l)
    push!(diff_arr, p[end])

    weight = diff_cnn_.weight[:]
    push!(w1_arr, weight[1])
    push!(w2_arr, weight[2])
    push!(w3_arr, weight[3])

    println(@sprintf("Loss: %0.4f\tD0: %0.4f Weights:(%0.4f,\t %0.4f, \t%0.4f) \t Sum: %0.4f"
            ,l, D0[1], weight[1], weight[2], weight[3], sum(weight)))

    global count

    if count==0
        fig = figure(figsize=(8,2.5));
        ttl = fig.suptitle(@sprintf("Epoch = %d", count), y=1.05)
        global ttl
        subplot(131)
        pcolormesh(x,t,ode_data')
        xlabel(L"$x$"); ylabel(L"$t$"); title("Data")
        colorbar()

        subplot(132)
        img = pcolormesh(x,t,pred')
        global img
        xlabel(L"$x$"); ylabel(L"$t$"); title("Prediction")
        colorbar(); clim([0, 1]);

        ax = subplot(133); global ax
        u = collect(0:0.01:1)
        rx_line = plot(u, rx_nn.([[elem] for elem in u]), label="NN")[1];
        global rx_line
        plot(u, reaction.(u), label="True")
        title("Reaction Term")
        legend(loc="upper right", frameon=false, fontsize=8);
        ylim([0, r*0.25+0.2])

        subplots_adjust(top=0.8)
        tight_layout()
    end

    if count>0
        println("updating figure")
        img.set_array(pred[1:end-1, 1:end-1][:])
        ttl.set_text(@sprintf("Epoch = %d", count))

        u = collect(0:0.01:1)
        rx_pred = rx_nn.([[elem] for elem in u])
        rx_line.set_ydata(rx_pred)
        u = collect(0:0.01:1)

        min_lim = min(minimum(rx_pred), minimum(reaction.(u)))-0.1
        max_lim = max(maximum(rx_pred), maximum(reaction.(u)))+0.1

        ax.set_ylim([min_lim, max_lim])
    end

    global save_count
    if count%save_freq == 0
        println("saved figure")
        savefig(@sprintf("%s/pred_%05d.png", save_folder, save_count), dpi=200, bbox_inches="tight")
        save_count += 1
    end

    display(gcf())
    count += 1

    false
end

#train
res1 = DiffEqFlux.sciml_train(loss_rd, p, ADAM(0.001), cb=cb, maxiters = 100)
res2 = DiffEqFlux.sciml_train(loss_rd, res1.minimizer, ADAM(0.001), cb=cb, maxiters = 300)
res3 = DiffEqFlux.sciml_train(loss_rd, res2.minimizer, BFGS(), cb=cb, maxiters = 1000)

pstar = res3.minimizer

## Save trained model
@save @sprintf("%s/model.bson", save_folder) pstar

########################
# Plot for paper
########################
@load @sprintf("%s/model.bson", save_folder) pstar
#re-defintions for newly loaded data

diff_cnn_ = Conv(reshape(pstar[(end-4):(end-2)],(3,1,1,1)), [0.0], pad=(0,0,0,0))
diff_cnn(x) = diff_cnn_(x) .- diff_cnn_.bias
D0 = res3.minimizer[end]

fig = figure(figsize=(4,4))

rcParams = PyPlot.PyDict(PyPlot.matplotlib."rcParams")
rcParams["font.size"] = 10
rcParams["text.usetex"] = true
rcParams["font.family"] = "serif"
rcParams["font.sans-serif"] = "Helvetica"
rcParams["axes.titlesize"] = 10

subplot(221)
pcolormesh(x,t,ode_data', rasterized=true)
xlabel(L"$x$"); ylabel(L"$t$"); title("Data")
yticks([0, 1, 2, 3, 4, 5])

ax = subplot(222)
cur_pred = predict_rd(pstar)[1]
img = pcolormesh(x,t,cur_pred', rasterized=true)
global img
xlabel(L"$x$"); ylabel(L"$t$"); title("Prediction")
yticks([0, 1, 2, 3, 4, 5])
cax = fig.add_axes([.48,.62,.02,.29])
colb = fig.colorbar(img, cax=cax)
colb.ax.set_title(L"$\rho$")
clim([0, 1]);
colb.set_ticks([0, 1])

subplot(223)
plot(Flux.data(w1_arr ./ w3_arr) .- 1, label=L"$w_1/w_3 - 1$")
plot(Flux.data(w1_arr .+ w2_arr .+ w3_arr), label=L"$w_1 + w_2 + w_3$")
axhline(0.0, linestyle="--", color="k")
xlabel("Epochs"); title("CNN Weights")
xticks([0, 1500, 3000]); yticks([-0.4, -0.3,-0.2, -0.1, 0.0, 0.1])
legend(loc="lower right", frameon=false, fontsize=6)

subplot(224)
u = collect(0:0.01:1)
plot(u, rx_nn.([[elem] for elem in u]), label="UPDE")[1];
plot(u, reaction.(u), linestyle="--", label="True")
xlabel(L"$\rho$")
title("Reaction Term")
legend(loc="lower center", frameon=false, fontsize=6);
ylim([0, 0.3])

tight_layout(h_pad=1)
gcf()
savefig(@sprintf("%s/fisher_kpp.pdf", save_folder))

#plot loss vs epochs and save
figure(figsize=(6,3))
plot(log.(train_arr), "k.", markersize=1)
xlabel("Epochs"); ylabel("Log(loss)")
tight_layout()
savefig(@sprintf("%s/loss_vs_epoch.pdf", save_folder))
gcf()


"""
    komptrap(f,a,b,M)
adalah fungsi yang digunakan untuk menghitung nilai integral `f` secara numerik
pada interval `[a,b]` menggunakan komposit trapesium dengan `M` sub-interval.

# Example
```jl
julia> a = 1; b = 6;

julia> f(x) = 2+sin(2*sqrt(x));

julia> y = komptrap(f,a,b,10)
8.193854565172531
```
return solusi `y`
"""
function komptrap(f,a,b,M)
  h = (b-a)/M;
  s = 0;
  for k = 1:M-1
    x = a+k*h;
    s = s+f(x);
  end
  y = h/2*(f(a)+f(b)+2*s)
end


mutable struct PWGrid
    Ns::Array{Int64,1}
    LatVecs::Array{Float64,2}
    RecVecs::Array{Float64,2}
    Npoints::Int
    Ω::Float64
    R::Array{Float64,2}
    G::Array{Float64,2}
    G2::Array{Float64}
end

function PWGrid( Ns::Array{Int,1},LatVecs::Array{Float64,2} )
    Npoints = prod(Ns)
    RecVecs = 2*pi*inv(LatVecs')
    Ω = det(LatVecs)
    R,G,G2 = init_grids( Ns, LatVecs, RecVecs )
    return PWGrid( Ns, LatVecs, RecVecs, Npoints, Ω, R, G, G2 )
end

function mm_to_nn(mm::Int,S::Int)
    if mm > S/2
        return mm - S
    else
        return mm
    end
end

function init_grids( Ns, LatVecs, RecVecs )
    #
    Npoints = prod(Ns)
    #
    R = Array(Float64,3,Npoints)
    ip = 0
    for k in 0:Ns[3]-1
    for j in 0:Ns[2]-1
    for i in 0:Ns[1]-1
        ip = ip + 1
        R[1,ip] = LatVecs[1,1]*i/Ns[1] + LatVecs[1,2]*j/Ns[2] + LatVecs[1,3]*k/Ns[3]
        R[2,ip] = LatVecs[2,1]*i/Ns[1] + LatVecs[2,2]*j/Ns[2] + LatVecs[2,3]*k/Ns[3]
        R[3,ip] = LatVecs[3,1]*i/Ns[1] + LatVecs[3,2]*j/Ns[2] + LatVecs[3,3]*k/Ns[3]
    end
    end
    end
    #
    G  = Array(Float64,3,Npoints)
    G2 = Array(Float64,Npoints)
    ip    = 0
    for k in 0:Ns[3]-1
    for j in 0:Ns[2]-1
    for i in 0:Ns[1]-1
        gi = mm_to_nn( i, Ns[1] )
        gj = mm_to_nn( j, Ns[2] )
        gk = mm_to_nn( k, Ns[3] )
        ip = ip + 1
        G[1,ip] = RecVecs[1,1]*gi + RecVecs[1,2]*gj + RecVecs[1,3]*gk
        G[2,ip] = RecVecs[2,1]*gi + RecVecs[2,2]*gj + RecVecs[2,3]*gk
        G[3,ip] = RecVecs[3,1]*gi + RecVecs[3,2]*gj + RecVecs[3,3]*gk
        G2[ip] = G[1,ip]^2 + G[2,ip]^2 + G[3,ip]^2
    end
    end
    end
    return R,G,G2
end


using LinearAlgebra

A = [
   1 2
   2 1
]

e = eigen(A)
V = e.vectors
@show Diagonal(e.values)
V⁻¹ = inv(V)
@show V⁻¹ * A * V

F = rand(3,5)
s = svd(F)

F |> display
s.U * Diagonal(s.S) * s.Vt |> display


include("Shor.jl")
using Test

"""Euler theorem states that the order is a devisor of Eulerφ (or the size of Z* group of `N`)"""
function check_Euler_theorem(N::Int)
    Z = NumberTheory.Z_star(N)
    Nz = length(Z)   # Eulerφ
    for x in Z
        @test powermod(x,Nz,N) == 1  # the order is a devisor of Eulerφ
    end
end

@testset "Euler" begin
    check_Euler_theorem(150)
end

@testset "shor_classical" begin
    Random.seed!(129)
    L = 35
    f = shor(L, Val(:classical))
    @test f == 5 || f == 7

    L = 25
    f = shor(L, Val(:classical))
    @test f == 5

    L = 7*11
    f = shor(L, Val(:classical))
    @test f == 7 || f == 11

    L = 14
    f = shor(L, Val(:classical))
    @test f == 2 || f == 7

    @test NumberTheory.factor_a_power_b(25) == (5, 2)
    @test NumberTheory.factor_a_power_b(15) == nothing
end

@testset "shor quantum" begin
    Random.seed!(129)
    L = 15
    f = shor(L, Val(:quantum))
    @test f == 5 || f == 3
end


using LinearAlgebra

"""
Calculates the jacobian matrix
"""
function genjacobian(fun, X, w)
    n = size(w)[1]
    J = zeros(size(X)[1], n)
    h = zeros(n)
    for i in 1:n
        h[i] = 10^-5
        J[:, i] = (fun(w+h, X) - fun(w-h, X)) / h[i]
        h[i] = 0
    end
    return J
end

"""
implements the gauss newton method.
fit's a non-linear model (fun) to data points X, Y.

...
# Arguments
- `fun::function`: the model, form: fun(w, X), where w are the weights
- `X::Array/Vector`: X data.
- `Y::Array/Vector`: Y data.
- `b0::Array/Vector`: initial guess.
...
"""
function gaussnewton(fun, X, Y, w0)
    r(w, X) = fun(w, X) - Y             # Anonymous function for the residual (error)
    f_grad(r_jac, r) = 2 * r_jac' * r   # Anonymous function for the gradient of the residual

    alpha = 1                           # Static Step width, alternatively use Arijo-Goldstein or Wolfe-Powell line search
    r_jac = genjacobian(fun, w0, X)
    rw = r(w0, X)                       # The actual residual
    w = w0
    iter = 0

    # if gradient is app. 0 -> local minima reached
    while norm(f_grad(r_jac, rw)) > 1^-8

        # calculate step direction d
        C = cholesky(r_jac' * r_jac)
        z = C.L \ (r_jac' * rw)
        d = C.U \ z

        # update weights w
        w = w - alpha * d
        r_jac = genjacobian(fun, w, X)
        rw = r(w, X)

        iter = iter + 1
        if iter == 10000
            break
        end
    end
    return w
end


"""
    struct PolynomialV <: AbstractVApproximator{Int}
        weights::Vector{Float64}
    end

See more details at Section (9.5.1) on Page 210 of the book *Sutton, Richard S., and Andrew G. Barto. Reinforcement learning: An introduction. MIT press, 2018.*
"""
struct PolynomialV <: AbstractVApproximator{Int}
    weights::Vector{Float64}
end

PolynomialV(order::Int) = PolynomialV(zeros(Float64, order+1))

function (p::PolynomialV)(s)
    sum(w * s^(i-1) for (i, w) in enumerate(p.weights))
end

function update!(p::PolynomialV, s, e)
    for i in 1:length(p.weights)
        p.weights[i] += e * s^(i-1)
    end
end


# Meshgrid function (ndgrid)
function meshgrid{T}(vx::Array{T}, vy::Array{T})
    m, n = length(vy), length(vx)
    vx = reshape(vx, 1, n)
    vy = reshape(vy, m, 1)
    (repmat(vx, m, 1), repmat(vy, 1, n))
end

function smart_innerY{T<:Signed, F<:AbstractFloat}(Y::Array{F}, N::T, U::Array{F}, L::Array{F}, σ1::F, μ1::F)
    ## Here, we assume that the vector Y is already normalized.

    ## The computation of lambda max involves taking all inner products of Y.
    ## The smart method allows for the computation of the i+1_th parcel based on
    ## the result of the i_th parcel.

    arraytype = typeof(Y[1])

    ## Preallocating the vector IP_vector for speed
    IP_vector = Array{arraytype}(zeros(N,1))

    index_array = collect(1:N)'
    IP_vector[1]= sum( Y.* (index_array'-μ1) / σ1 )
    sumY = 0

    for i = 2:N
        sumY = sumY + Y[i-1]
        IP_vector[i] = sumY * ( U[i-1] - L[i-1] )
    end

    return IP_vector
end

function calculate_matrices{T<:Int}(N::T)
    ## index
    i = collect(1:N-1)'

    ## calculate arrays mean and variance
    array_μ = (N-i')/N
    array_σ = sqrt( (1/(N-1)) *( (i').*array_μ .^2 + (N-i').*(1-array_μ).^2 ) )

    ## With these parameters, compose the Lower and Upper diagonal matrices
    U = - array_μ ./ array_σ
    L = (1 - array_μ) ./ array_σ

    ## Calculate μ1 and σ1
    μ1 = (N+1)/2
    σ1 = sqrt( (1/6)*(N+1)*(2*N+1)-μ1^2 )

    ## Concatenate
    array_σ2 = [σ1; array_σ]

    ## Build X
    repU, repLtranspose = meshgrid(U, L)
    #x1 = ( ((1:N)' - μ1) / σ1 )'
    #x2 = triu(repU) + tril(repLtranspose') - diagm(L[:])
    #x3 = [x1 [x2; L']]
    X = [( ((1:N)' - μ1) / σ1 )' [triu(repU) + tril(repLtranspose') - diagm(L[:]); L']]

    ## Calculate Gram Matrix (inner products matrix)
    GMat = triu( GramMatrixTM(N, U, L, σ1, μ1) );
    GMat = GMat + GMat' - N*eye(N);

    return U, L, μ1, σ1, X, GMat, array_σ2
end

function GramMatrixTM{T<:Int, F<:AbstractFloat}(N::T, U::Array{F}, L::Array{F}, σ1::F, μ1::F)
    ## builds the inner products matrix in the smart way
    ## (much faster than X'*X)
    I, J = meshgrid(collect(1:(N-1)), collect(1:(N-1)))
    Uaux, Laux = meshgrid(U, L)

    minimo = min(I, J);
    maximo = max(I, J);

    GMat1 = minimo .* (U*U') + (maximo-minimo) .* (Laux.*Uaux) + (N-maximo) .* (L*L')

    ## reduce the memory usage, since we don't need theses variables anymore...
    I, J, minimo, maximo = 0, 0, 0, 0
    gc()

    j = collect(1:N-1)';
    IP = (1/σ1) * ( j .* U' .* ((j+1)/2 - μ1) + (N-j) .* L' .* ((j+1+N)/2 - μ1) )

    GMat = [[N; IP'] [IP; GMat1]]
end

function calculate_μ{T<:Int}(N::T)
    i = 1:N-1
    (N-i')/(N)
end

function calculate_μ1{T<:Int}(N::T)
    (N+1)/2
end

function calculate_σ{T<:Int, F<:AbstractFloat}(N::T,array_μ::Array{F})
    i = 1:N-1
    sqrt( (1/(N- 1)) *( (i').*array_μ .^2 + (N-i').*(1-array_μ).^2 ) )
end

function calculate_σ1{T<:Int, F<:AbstractFloat}(N::T,μ1::F)
    sqrt( (1/6)*(N+1)*(2*N+1)-μ1^2 )
end

function CoordinateDescentCore{T<:Signed, F<:AbstractFloat}(A_actual::Array{T}, GMat::Array{F, 2}, j::T, β_tilde::Array{F}, β_ols::Array{F}, N::T, arrayInnerY::Array{F}, λ::F, array_σ2::Array{F})
    # aux = 0
    #
    # if size(A_actual)[1] >= 1
    #     aux = ( GMat[j,A_actual]' * β_tilde[A_actual] )[1]
    # end
    aux = sum(GMat[j,A_actual]' * β_tilde[A_actual])

    ##  Computation of the OLS estimator as simple regression
    β_ols[j] = 1 / N * (arrayInnerY[j] - aux) + β_tilde[j]

    ##  Soft-thresholding operator
    if j == 1
        λEff = λ
    else
        λEff = λ * array_σ2[j]
    end

    return aux, λEff, β_ols
end

function updateCandidates{T<:Signed, F<:AbstractFloat}(λEff::F, β_ols::Array{F}, j::T, A_actual::Array{T}, lastchange::T, β_tilde::Array{F}; w = [])
    breakThis = 0

    if size(w,1) == 0
        w = ones( size(β_ols) )
    end

    if λEff*w[j] >= abs(β_ols[j])
        if in(j, A_actual) # any(j == A_actual)
            splice!(A_actual,findin(A_actual,j)[1])
            lastchange = j
        elseif j == lastchange
            breakThis = 1
        end
        β_tilde[j] = 0
    else
        β_tilde[j] = sign(β_ols[j]) * (abs(β_ols[j]) - λEff*w[j])
        ## Includes an index
        if !in(j, A_actual) #not(any(j==A_actual))
            push!(A_actual, Int(j))
            sort!(A_actual)
            lastchange = j
        elseif j == lastchange
            breakThis = 1
        end
    end

    return A_actual, β_tilde, lastchange, breakThis
end

function convCheck{T<:Signed}(A_reference::Array{T}, A_actual::Array{T})
    if length(A_reference) != length(A_actual)
        ##  Did not converge
        convergenceFlag = 0
    elseif A_reference == A_actual && length(A_reference) == length(A_actual)
        ##  Converged!
        convergenceFlag = 2
    end

    return convergenceFlag
end

function calcBestB{T<:Signed, F<:AbstractFloat}(N::T, Y::Array{F}, X::Array{F}, β_lasso::Array{F}, array_σ2::Array{F})
    bestB = -1
    BICold = 1000000

    for i = 1:size(β_lasso, 2)
        BICnew = N * ( log(var( Y - X*β_lasso[:,i] )) ) + sum( abs(β_lasso[:,i]./array_σ2) .> 1e-3 ) * log(N)
        if BICnew < BICold
            BICold = BICnew
            bestB = i
        end
    end

    return bestB
end


using LinearAlgebra
using Memoize

"""
    pauli_vector(x, y, z)

Assemble the "Pauli vector" matrix.
"""
pauli_vector(x, y, z) = ComplexF64[z x - im * y; x + im * y -z]

"""
    binary_digits(x::Integer, M::Integer)

Return `M` bits of the binary representation of the integer `x` as array (least significant bit at first index).
"""
function binary_digits(M::Integer, x::Integer)
    binary_digits!(BitArray(undef, M), x)
end

"""
    binary_digits!(m::BitArray{1}, x::Integer)

Fill `m` with the binary representation of the integer `x` (least significant bit at first index).
"""
function binary_digits!(m::BitArray{1}, x::Integer=0)
    for i in 1:length(m)
        m[i] = x & 1
        x >>= 1
    end
    return m
end

"""
    binary_to_int(m::BitArray{1}, s=0)

converts BitArray `m` into Integer
"""
function binary_to_int(m::BitArray{1}, x=0)
    v = 1
    for i in Base.view(m, :)
        x += v*i
        v <<= 1
    end
    x
end

"""
    quaternary_digits(x::Integer, M::Integer)

Return `M` base-4 digits of the quaternary representation of the integer `x` as array (least significant digit at first index).
"""
function quaternary_digits(M::Integer, x::Integer)
    quaternary_digits!(Vector{Int}(undef, M), x)
end

"""
    quaternary_digits!(m::Vector{Int}, x::Integer)

Fill `m` with the quaternary representation of the integer `x` (least significant digit at first index).
"""
function quaternary_digits!(m::Vector{Int}, x::Integer)
    for i in 1:length(m)
        m[i] = x & 3
        x >>= 2
    end
    return m
end

"""
    intlog2(x::Integer)

Compute integer base-2 logarithm. Rounds to floor if x is not a power of 2.
"""
function intlog2(x::Integer)
    x == 0 && error("Logarithm of 0 is undefined")
    x < 0 && error("Logarithm of negative number is undefined")
    ret::Int = 0
    while x > 1
        x >>= 1
        ret += 1
    end
    ret
end

"""
    sliced_index(idx, targetwires::Tuple, N::Int)

Construct sliced index via target map (utility function).
"""
function sliced_index(idx, targetwires::Tuple, N::Int)
    islice = Vector{Any}(fill(Colon(), N))
    for k in 1:length(targetwires)
        islice[targetwires[k]] = idx[k] + 1
    end
    return islice
end

"""
    gramm_schmidt(a::Array{Float64})

Compute orthogonalized real vectors.
"""
function gramm_schmidt!(a::Array{Float64})
    num_vectors = size(a,2)

    a[:, end] = a[:, end] ./ norm(a[:, end])

    for i in num_vectors-1:-1:1
        for j in i+1:num_vectors
            a[:, i] = a[:, i] - (dot(a[:, i], a[:, j]) / dot(a[:, j], a[:, j])) * a[:, j]
        end
        a[:, i] = a[:, i] ./ norm(a[:, i])
    end

    return a
end

"""
    gramm_schmidt(a::Array{ComplexF64})

Compute orthogonalized complex vectors.
"""
function gramm_schmidt!(a::Array{ComplexF64})
    num_vectors = size(a,2)

    a[:, end] = a[:, end] ./ norm(a[:, end])

    for i in num_vectors-1:-1:1
        for j in num_vectors:-1:i+1
            a[:, i] = a[:, i] - conj(dot(a[:, i], a[:, j]) / dot(a[:, j], a[:, j])) * a[:, j]
        end
        a[:, i] = a[:, i] ./ norm(a[:, i])
    end
    return a
end

"""
    rdm(N, iwire, ψ, χ, d=2)

Compute the reduced density matrix ``tr_B[|ψ⟩⟨χ|]``, where the trace runs over
the subsystem complementary to the qubits specified by `iwire`.
"""
@views function rdm(N::Integer, iwire::NTuple{M,<:Integer}, ψ::Vector{T}, χ::Vector{T}, d::Int=2) where {M,T}
    M ≥ 1 || error("Need at least one wire to act on.")
    M ≤ N || error("Number of gate wires cannot be larger than total number of wires.")
    length(unique(iwire)) == M || error("Wire indices must be unique.")
    minimum(iwire) ≥ 1 || error("Wire index cannot be smaller than 1.")
    maximum(iwire) ≤ N || error("Wire index cannot be larger than total number of wires.")
    length(ψ) == d^N || error("Input vector 'ψ' has wrong length.")
    length(χ) == d^N || error("Input vector 'χ' has wrong length.")

    # convert to array
    iwire = collect(iwire)
    # complementary wires
    iwcompl = setdiff(1:N, iwire)
    @assert length(iwire) + length(iwcompl) == N

    ρ = zeros(eltype(ψ), d^M, d^M)
    # # Note: following the ordering convention of `kron` here, i.e.,
    # # last qubit corresponds to fastest varying index
    strides = [d^(j-1) for j in 1:N]
    wstrides = strides[iwire]
    cstrides = strides[iwcompl]

    if d == 2
        iw = BitArray{1}(undef, M)
        jw = BitArray{1}(undef, M)
        kw = BitArray{1}(undef, N - M)
        # TODO: optimize memory access pattern
        for k in 0:2^(N-M)-1
            binary_digits!(kw, k)
            koffset = dot(kw, cstrides)
            for i in 1:2^M
                binary_digits!(iw, i - 1)
                for j in 1:2^M
                    binary_digits!(jw, j - 1)
                    rowind = koffset + dot(iw, wstrides) + 1
                    colind = koffset + dot(jw, wstrides) + 1
                    @inbounds ρ[i, j] += ψ[rowind] * conj(χ[colind])
                end
            end
        end
    elseif d == 4
        iw = Vector{Int}(undef, M)
        jw = Vector{Int}(undef, M)
        kw = Vector{Int}(undef, N - M)
        # TODO: optimize memory access pattern
        for k in 0:4^(N-M)-1
            quaternary_digits!(kw, k)
            koffset = dot(kw, cstrides)
            for i in 1:4^M
                quaternary_digits!(iw, i - 1)
                for j in 1:4^M
                    quaternary_digits!(jw, j - 1)
                    rowind = koffset + dot(iw, wstrides) + 1
                    colind = koffset + dot(jw, wstrides) + 1
                    @inbounds ρ[i, j] += ψ[rowind] * conj(χ[colind])
                end
            end
        end
    else
        error("d = $d not supported yet.")
    end

    return ρ
end


"""
Riesz sense fractional derivative
"""
abstract type Riesz <: FracDiffAlg end

"""
# Riesz sense symmetric fractional derivative algorithm.

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

Compute fractional derivative of Riesz sense using Triangular Strip Matrix algorithm.

### Example

```julia-repl
julia> fracdiff(x->x, 0.5, 1, 0.01, RieszSymmetric())
```
"""
struct RieszSymmetric <: Riesz end

"""
# Riesz sense Ortigueira definition fractional derivative

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

Ortigueira's definition of the symmetric Riesz derivative via centred differences

### Usage

```julia-repl
julia> fracdiff(x->x, 0.5, 1, 0.01, RieszOrtigueira())
```
"""
struct RieszOrtigueira <: Riesz end

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

function fracdiff(f::FunctionAndNumber, α, end_point, h, ::RieszSymmetric)
    N=floor(Int, end_point/h)

    mat = RieszMatrix(α, N+1, h)
    return mat*f.(collect(0:h:end_point))
end
function RieszMatrix(α, N, h)
    caputo = B(N+1, α)
    caputo = caputo[2:(N+1), 1:N]
    result = 1/2*(caputo+caputo')
    result = h^(-α)*result
    return result
end

#FIXME: Values are changing when h become smaller?
function fracdiff(f, α, point, h, ::RieszOrtigueira)
    N = round(Int, point/h)
    t = collect(0:h:point)
    return ranort(α, N+1, h)*f.(t)
end

function ranort(alpha, N)
    k=collect(0:N-1)
    rc = ((-1)*ones(size(k))).^k.*gamma.(alpha+1).*(gamma.(alpha*0.5 .-k.+1).*gamma.(alpha*0.5 .+ k.+1)).^(-1)
    rc = rc*(cos(alpha*π*0.5))
    R = zeros(N, N)

    for m=1:N
        R[m, m:N] = rc[1:N-m+1]
    end

    for i=1:N-1
        for j=i:N
            R[j, i] = R[i, j]
        end
    end
    return R
end
# Dispatch for ranort
function ranort(alpha, N, h)
    R = ranort(alpha, N)
    R = R*h^(-alpha)
    return R
end


gaussian_2d(x = -3:0.01:3, y = -7:0.01:3; x₀ = 0, y₀ = -2, σx = 1, σy = 2) = begin
    X = repeat(x', length(y), 1)
    Y = repeat(y, 1, length(x))
    x, y, map((x, y) -> exp(-((x - x₀) / 2σx)^2 - ((y - y₀) / 2σy)^2), X, Y)
end

@testset "arbitrary colormap" begin
    colormap = :cividis
    p = @inferred contourplot(gaussian_2d()...; colormap = colormap)
    test_ref("references/contourplot/gauss_$colormap.txt", @show_col(p))
end

@testset "number of levels" begin
    levels = 5
    p = @inferred contourplot(gaussian_2d()...; levels = levels)
    test_ref("references/contourplot/gauss_$(levels)levels.txt", @show_col(p))
end

@testset "update contourplot" begin
    p = @inferred contourplot(gaussian_2d()...; levels = 2)
    contourplot!(p, gaussian_2d(; σx = 0.5, σy = 0.25)...; levels = 1, colormap = :magma)
    test_ref("references/contourplot/gauss_nested.txt", @show_col(p))
end

@testset "function" begin
    p = @inferred contourplot(
        -3:0.01:3,
        -3:0.01:4,
        (x, y) -> (exp(-x^2 - y^2) + exp(-(x - 1)^2 - 2(y - 2)^2))^2,
    )
    test_ref("references/contourplot/function_contour.txt", @show_col(p))
end


#
# Maneuver calculations for orbits within the Twobody Problem
#

"""
Provides the radius of escape.
"""
escape_radius(r₀, v₀, aₜ) = r₀ * v₀ / (20 * aₜ^2 * r₀^2)^(1/4)
escape_radius(orbit::T, m::ConstantManeuver) where T <: RestrictedTwoBodySystem = escape_radius(radius(orbit), velocity(orbit), m.aₜ)

"""
Provides the velocity at escape.
"""
escape_velocity(r₀, v₀, aₜ, μ) = √(2 * μ / escape_radius(r₀, v₀, aₜ))
escape_velocity(orbit::T, m::ConstantManeuver) where T <: RestrictedTwoBodySystem = escape_velocity(radius(orbit), velocity(orbit), m.aₜ, orbit.body.μ)

"""
Provides time delta from the provided initial orbit to escape.
"""
escape_time(r₀, v₀, aₜ) = upreferred(v₀ / aₜ) * (1 - (upreferred(20aₜ^2 * r₀^2) / upreferred(v₀^4))^(1/8))
escape_time(orbit::T, m::ConstantManeuver) where T <: RestrictedTwoBodySystem = escape_time(radius(orbit), velocity(orbit), m.aₜ)

"""
Provides the path length from the initial condition to escape.
"""
escape_path_length(r₀, v₀, aₜ) = upreferred(v₀^2 / 2aₜ) * (1 - upreferred((1/v₀) * (20aₜ^2 * r₀^2)^(1/4)))
escape_path_length(orbit::T, m::ConstantManeuver) where T <: RestrictedTwoBodySystem = escape_path_length(radius(orbit), velocity(orbit), m.aₜ)


using Plots, OrdinaryDiffEq, DiffEqPhysics, RecursiveArrayTools
gr()

G = 2.95912208286e-4
M = [1.00000597682, 0.000954786104043, 0.000285583733151, 0.0000437273164546, 0.0000517759138449, 1/1.3e8]
planets = ["Sun", "Jupiter", "Saturn", "Uranus", "Neptune", "Pluto"]

pos_x = [0.0,-3.5023653,9.0755314,8.3101420,11.4707666,-15.5387357]
pos_y = [0.0,-3.8169847,-3.0458353,-16.2901086,-25.7294829,-25.2225594]
pos_z = [0.0,-1.5507963,-1.6483708,-7.2521278,-10.8169456,-3.1902382]
pos = ArrayPartition(pos_x,pos_y,pos_z)

vel_x = [0.0,0.00565429,0.00168318,0.00354178,0.00288930,0.00276725]
vel_y = [0.0,-0.00412490,0.00483525,0.00137102,0.00114527,-0.00170702]
vel_z = [0.0,-0.00190589,0.00192462,0.00055029,0.00039677,-0.00136504]
vel = ArrayPartition(vel_x,vel_y,vel_z)

tspan = (0.,200_000)

const ∑ = sum
const N = 6
potential(p, t, x, y, z, M) = -G*∑(i->∑(j->(M[i]*M[j])/sqrt((x[i]-x[j])^2 + (y[i]-y[j])^2 + (z[i]-z[j])^2), 1:i-1), 2:N)

nprob = NBodyProblem(potential, M, pos, vel, tspan)
sol = solve(nprob,Yoshida6(), dt=100);

orbitplot(sol,body_names=planets)

using DiffEqTutorials
DiffEqTutorials.tutorial_footer(WEAVE_ARGS[:folder],WEAVE_ARGS[:file])


function calc_seat_id(pass)
    digits = map(c -> c == 'F' || c == 'L' ? '0' : '1', pass)
    row = parse(Int64, digits[1:7]; base = 2)
    column = parse(Int64, digits[8:end]; base = 2)
    row * 8  + column
end

function calc_highest_id(boarding_passes)
    function update(acc, pass)
        id = calc_seat_id(pass)
        id > acc ? id : acc
    end

    foldl(update, boarding_passes; init = 0)
end

function main()
    lines = readlines("../resources/input_05.txt")
    result = calc_highest_id(lines)
    println(result)
end

main()


#! /usr/bin/julia

# Rosetta Code, Arithmetic-geometric mean

function agm{T<:FloatingPoint,U<:Integer}(x::T, y::T, e::U=5)
    0 < y && 0 < y && 0 < e || throw(DomainError())
    err = e*eps(x)
    (g, a) = extrema([x, y])
    while err < (a - g)
        ap = a
        a = 0.5*(a + g)
        g = sqrt(ap*g)
    end
    return a
end

x = 1.0
y = 1.0/sqrt(2.0)

println("Using literal-precision float numbers:")
println("    agm(", x, ",", y, ") = ", agm(x, y))

println()
println("Using half-precision float numbers:")
x = float16(x)
y = float16(y)
println("    agm(", x, ",", y, ") = ", agm(x, y))

println()
println("Using ", get_bigfloat_precision(), "-bit float numbers:")
x = BigFloat(1.0)
y = x/sqrt(BigFloat(2.0))
println("    agm(", x, ",", y, ") = \n    ", agm(x, y))


## Exercise 6-7
## A number, a, is a power of b if it is divisible by b and a/b is a power of b. Write a function called ispower that takes parameters a and b and returns true if a is a power of b.

## TIP
## You will have to think about the base case.
println("Ans: ")

function ispower(a, b)
    if a == b
        return true
    end

    if a % b == 0 && ispower(a / b, b)
        return true
    end

    return false
end

println(ispower(1000, 10))
println(ispower(343, 7))
println(ispower(31, 2))

println("End.")


using NumericalMethodsforEngineers
using Test

b = [16. -24. 18.; 3. -2. 0.; -9. 18. -17.]
x0 = [1., 1., 1.]

tol = 1.0e-5
limit = 100

(iters, eigval, eigvec) = nmex(b, x0; show=5)

println("\nNo of iters:                    $iters")
println("Largest eigen value:            $eigval")
println("Corresponding eigen vector:     $eigvec")
println()

jeigval, jeigvec = eigen(b);

println("\nEigen values:                   $jeigval")
println("Corresponding eigen vectors:")
jeigvec |> display
println()

@test round(minimum(jeigval), digits=3) == round(eigval, digits=3)


#using TensorToolbox, Test, LinearAlgebra

println("\n\n**** Testing tensor.jl")

X=rand(20,10,50,5)
N=ndims(X)
println("\n**Test tensor X of size: ", size(X))

println("\n...Testing functions matten and tenmat (by mode).")
for n=1:N
  Xn=tenmat(X,n);
  M=setdiff(1:N,n);
  println("Size of $n-mode matricization: ", [size(Xn)...])
  @test size(Xn) == (size(X,n),prod([size(X,M[k]) for k=1:length(M)]))
  println("Check if it folds back correctly: ",matten(Xn,n,[size(X)...]) == X)
  @test matten(Xn,n,[size(X)...]) == X
end

println("\n...Testing functions matten and tenmat (by rows and columns).")
R=[2,1];C=[4,3];
Xmat=tenmat(X,row=R,col=C);
println("Size of R=$R and C=$C matricization: ", [size(Xmat)...])
println("Check if it folds back correctly: ",matten(Xmat,R,C,[size(X)...]) == X)
@test matten(Xmat,R,C,[size(X)...]) == X

println("\n...Testing function ttm.")
M=MatrixCell(undef,N)
for n=1:N
  M[n]=rand(5,size(X,n))
end
println("Created $N matrices with 5 rows and appropriate number of columns.")
Xprod=ttm(X,M)
println("Size of tensor Y=ttm(X,M): ",size(Xprod))
err=norm(tenmat(Xprod,1) - M[1]*tenmat(X,1)*kron(M[end:-1:2])')
println("Multiplication error: ",err)
@test err ≈ 0 atol=1e-10

println("\n...Testing function ttv.")
Xk=reshape(collect(1:24),(3,4,2))
mode=2
v=collect(1:4)
println("Multiplying a tensor X by a vector v in mode $mode.")
Xprod=ttv(Xk,v,mode)
println("Size of tensor Y=ttv(X,v): ",size(Xprod))
res=[70 190;80 200;90 210]
@test Xprod==res

println("\n...Testing function krontm.")
X=rand(5,4,3)
Y=rand(2,5,4)
println("Created two tensors X and Y of order ",ndims(X)," and sizes ",size(X)," and ",size(Y),".")
mode=3
M1=rand(20,10)
M2=rand(20,20)
M3=rand(20,12)
println("Multiplying tkron(X,Y) by random matrix in mode $mode.")
Z=krontm(X,Y,M3,mode)
err= norm(Z-ttm(tkron(X,Y),M3,mode))
println("Multiplication error: ",err)
@test err ≈ 0 atol=1e-10
mode=[3,2]
M=[M3,M2]
println("Multiplying tkron(X,Y) by random matrices in modes $mode.")
Z=krontm(X,Y,M,mode)
err = norm(Z-ttm(tkron(X,Y),M,mode))
println("Multiplication error: ",err)
@test err ≈ 0 atol=1e-10
M=[M1,M2,M3]
println("Multiplying tkron(X,Y) by random matrices in all modes.")
Z=krontm(X,Y,M)
err = norm(Z-ttm(tkron(X,Y),M))
println("Multiplication error: ",err)
@test err ≈ 0 atol=1e-10

println("\n...Testing function mkrontv.")
v=rand(240)
mode=1
println("Multiplying mode-$mode matricized tkron(X,Y) by a random vector.")
Z=mkrontv(X,Y,v,mode)
err = norm(Z-tenmat(tkron(X,Y),mode)*v)
println("Multiplication error: ",err)
@test err ≈ 0 atol=1e-10
v=rand(10)
Z=mkrontv(X,Y,v,mode,'t')
err = norm(Z-tenmat(tkron(X,Y),mode)'*v)
println("Multiplication error: ",err)
@test err ≈ 0 atol=1e-10

println("\n...Testing function mttkrp.")
X=rand(5,4,3)
mode=1
A1=rand(2,5);
A2=rand(4,5);
A3=rand(3,5);
A=[A1,A2,A3]
println("Multiplying mode-$mode matricized tensor X by Khatri-Rao product of matrices.")
Z=mttkrp(X,A,mode)
err = norm(Z-tenmat(X,mode)*khatrirao(A3,A2))
println("Multiplication error: ",err)
@test err ≈ 0 atol=1e-10

println("\n...Testing function dropdims.")
X=rand(5,4,1,3,6,1)
Xsq=dropdims(X)
println("Tensor X of size :",size(X)," squeezed to size :",size(Xsq),".")

println("\n...Testing contraction (contract).")
X=rand(5,4,3);
Y=rand(3,4,2);
Z=contract(X,Y)
for i1=1:5, i2=1:4, j2=1:4,j3=1:2
    s=0;
    for i=1:3
        s+=X[i1,i2,i]*Y[i,j2,j3];
    end
    @test Z[i1,i2,j2,j3] ≈ s atol=1e-10
end
X=rand(5,4,3,2);
Y=rand(5,7,3,6);
Z=contract(X,[1,3],Y,[1,3])
err=norm(Z-reshape(tenmat(X,row=[1,3])'*tenmat(Y,row=[1,3]),(4,2,7,6)))
println("Error: ",err)
@test err ≈ 0 atol=1e-10