using MultivariateStats
using Base.Test

## testing data

function pwdists(X)
	S = Base.sumabs2(X, 1)
	D2 = S .+ S' - 2 * (X'X)
	D2[diagind(D2)] = 0.0
	return sqrt(D2)
end

X0 = randn(3, 5)
G0 = X0'X0
D0 = pwdists(X0)

## conversion between dmat and gram

@assert issym(D0)
@assert issym(G0)

D = gram2dmat(G0)
@test issym(D)
@test_approx_eq D D0

G = dmat2gram(D0)
@test issym(G)
@test_approx_eq gram2dmat(G) D0

## classical MDS

X = classical_mds(D0, 3)
@test_approx_eq pwdists(X) D0


#!/usr/bin/julia

# Trizen
# Date: 12 February 2017
# https://github.com/trizen

# Implementation of the sigma function (sum of the divisors).

# See also:
#    https://en.wikipedia.org/wiki/Divisor_function

using Primes

function divisor_sum(n, x=1)
    sigma = 1
    for (p,e) in factor(n)
        s = 1
        for j in 1:e
            s += p^(j*x)
        end
        sigma *= s
    end
    sigma
end

println(divisor_sum(1234))        # 1854
println(divisor_sum(99000))       # 365040
println(divisor_sum(99000, 2))    # 15359172920


"Convert Cartesian to Spherical coordinates"
function cart2pol(x::Real, y::Real, z::Real)
    ρ = sqrt(x^2 + y^2 + z^2)  # distance
    θ = atand(y, x)  # horizontal angle
    ϕ = acosd(z / ρ)  # incidence angle
    (ρ, θ, ϕ)
end

"Convert Spherical to Cartesian coordinates"
function pol2cart(ρ::Real, θ::Real, ϕ::Real)
    sθ, cθ = sincosd(θ)
    sϕ, cϕ = sincosd(ϕ)
    x = ρ * sϕ * cθ
    y = ρ * sϕ * sθ
    z = ρ * cϕ
    (x, y, z)
end


using Roots

# Asymptotic formula for zeros of Bessel J function of order nu
"""
    besselj_asymptotic_zero(nu,n)

Asymptotic formula for the `n`th zero fo the the Bessel J function of order `nu`.
`besselj_asymptotic_zero` is vectorized.
"""
besselj_asymptotic_zero(nu,n) = return pi * (n-1 + nu/2 + 3//4)

# Find nth zero of Bessel J. Use the asymptotic values as
# starting values. These find the correct zeros even
# for n = 1,2,...

"""
    besselj_zero(nu,n)

`n`th zero of the Bessel J function of order `nu`,
for `n` = `1,2,...`. `besselj_zero` is vectorized.
"""
function besselj_zero(nu,n)
    z = besselj_asymptotic_zero(nu,n)
    bf = (x) -> besselj(nu,x)
#    return fzero(bf,z,ftol=1e-15)
    return fzero(bf,z)
end

@mkapprule BesselJZero nargs => 2

@doap BesselJZero(n::Number,z::AbstractFloat) = besselj_zero(n,z)
@doap BesselJZero(n::AbstractFloat,z::Number) = besselj_zero(n,z)
@doap BesselJZero(n::AbstractFloat,z::AbstractFloat) = besselj_zero(n,z)


using Distributions
using StatsBase
using Random
using DataFrames
using StatsPlots
using BenchmarkTools;

function rate_to_proportion(r::Float64,t::Float64)
    1-exp(-r*t)
end;

@enum InfectionStatus Susceptible Infected Recovered

function sir_abm(u,p,t)
    du = deepcopy(u)
    (β,c,γ,δt) = p
    N = length(u)
    for i in 1:N # loop through agents
        # If recovered
        if u[i]==Recovered continue
        # If susceptible
        elseif u[i]==Susceptible
            ncontacts = rand(Poisson(c*δt))
            du[i]=Susceptible
            ncontacts = rand(Poisson(c*δt))
            while ncontacts > 0
                j = sample(1:N)
                if j==i
                    continue
                end
                a = u[j]
                if a==Infected && rand() < β
                    du[i] = Infected
                    break
                end
                ncontacts -= 1
            end
        # If infected
    else u[i]==Infected
            if rand() < γ
                du[i] = Recovered
            end
        end
    end
    du
end;

function sir_abm!(du,u,p,t)
    (β,c,γ,δt) = p
    N = length(u)
    # Initialize du to u
    for i in 1:N
        du[i] = u[i]
    end
    for i in 1:N # loop through agents
        # If recovered
        if u[i]==Recovered
            continue
        # If susceptible
        elseif u[i]==Susceptible
            ncontacts = rand(Poisson(c*δt))
            while ncontacts > 0
                j = sample(1:N)
                if j==i
                    continue
                end
                a = u[j]
                if a==Infected && rand() < β
                    du[i] = Infected
                    break
                end
                ncontacts -= 1
            end
        # If infected
        else u[i]==Infected
            if rand() < γ
                du[i] = Recovered
            end
        end
    end
    nothing
end;

δt = 0.1
nsteps = 400
tf = nsteps*δt
tspan = (0.0,nsteps)
t = 0:δt:tf;

β = 0.05
c = 10.0
γ = rate_to_proportion(0.25,δt)
p = [β,c,γ,δt];

N = 1000
I0 = 10
u0 = Array{InfectionStatus}(undef,N)
for i in 1:N
    if i <= I0
        s = Infected
    else
        s = Susceptible
    end
    u0[i] = s
end;

Random.seed!(1234);

susceptible(x) = count(i == Susceptible for i in x)
infected(x) = count(i == Infected for i in x)
recovered(x) = count(i == Recovered for i in x);

function sim(u0,nsteps,dt)
    u = copy(u0)
    t = 0.0
    ta = []
    Sa = []
    Ia = []
    Ra =[]
    push!(ta,t)
    push!(Sa,susceptible(u))
    push!(Ia,infected(u))
    push!(Ra,recovered(u))
    for i in 1:nsteps
        u=sir_abm(u,p,t)
        t = t + dt
        push!(ta,t)
        push!(Sa,susceptible(u))
        push!(Ia,infected(u))
        push!(Ra,recovered(u))
    end
    DataFrame(t=ta,S=Sa,I=Ia,R=Ra)
end;

function sim!(u0,nsteps,dt)
    u = copy(u0)
    du = copy(u0)
    t = 0.0
    ta = []
    Sa = []
    Ia = []
    Ra =[]
    push!(ta,t)
    push!(Sa,susceptible(u))
    push!(Ia,infected(u))
    push!(Ra,recovered(u))
    for i in 1:nsteps
        sir_abm!(du,u,p,t)
        u,du = du,u
        t = t + dt
        push!(ta,t)
        push!(Sa,susceptible(u))
        push!(Ia,infected(u))
        push!(Ra,recovered(u))
    end
    DataFrame(t=ta,S=Sa,I=Ia,R=Ra)
end;

df_abm = sim(u0,nsteps,δt);

df_abm! = sim!(u0,nsteps,δt);

@df df_abm plot(:t,
    [:S :I :R],
    label=["S" "I" "R"],
    xlabel="Time",
    ylabel="Number",
    title="New state")

@df df_abm! plot(:t,
    [:S :I :R],
    label=["S" "I" "R"],
    xlabel="Time",
    ylabel="Number",
    title="In-place")

@benchmark sim(u0,nsteps,δt)

@benchmark sim!(u0,nsteps,δt)


using DataFrames, Plots, NumericalMethodsforEngineers
pyplot()

old = pwd()
ProjDir = dirname(@__FILE__)
cd(ProjDir) do

  f(x::Float64, y::Vector{Float64}) = (x + y[1])^2
  steps = 25
  h = 0.02

  x = 0.0
  y = [1.0]

  # y(3.0) = 4.2165

  r = Array{Float64,2}[]
  push!(r, euler(f, x, y, steps, h))
  push!(r, modified_euler(f, x, y, steps, h))
  push!(r, mid_point_euler(f, x, y, steps, h))
  push!(r, runga_kutta_4(f, x, y, steps, h))
  rr = hcat(r[1], r[2], r[3], r[4])
  println()
  rr[21:26,:] |> display
  println()

  xs = Float64[(i-1)*h for i in 1:steps+1]
  titles = ["Euler", "Modified_Euler", "Mid_Point_Euler", "Runga_Kutta_4"]

  p1 = plot(xs, r[1][:, 2])
  plot!(p1, xs, r[2][:, 2])
  plot!(p1, xs, r[3][:, 2])
  plot!(p1, xs, r[4][:, 2])
  show(p1)
  savefig("Ex7.2.png")
  gui()

end


struct DiscreteDistribution
    p::Vector{Float64}
    P::Vector{Float64} # p(k<=i)
end

function DiscreteDistribution(w)
    @assert all(w .>= 0) "All weights should be non-negative"
    p = w / sum(w)
    P = cumsum(p)
    return DiscreteDistribution(p, P)
end

rand(d::DiscreteDistribution) = searchsortedfirst(d.P, rand())

"""
    randint([rng,] probs::Vector{Float64})

Generate a random integer `i` between `1` and `length(probs)`.
`i` is sampled with probability probs[i].
The vector `probs` is assumed to sum to one.
"""
function randint(rng::AbstractRNG, probs::Vector{Float64})
    r = rand(rng)
    c = 0.
    i = 0
    for p in probs
        i += 1
        c += p
        r <= c && return i
    end
    return 0 #not supposed to arrive here
end

randint(probs::Vector{Float64}) = randint(GLOBAL_RNG, probs)


export stirling2, stirling2_partitions, stirling2_matrices
export permutation_matrices, n_choose_k_matrices
export base_n_val

"""
    stirling2( n :: Int64, k :: Int64  ) :: Int64

Counts the number of ways to partition `n` items into `k` unlabelled groups.
This quantity is known as Stirling's number of the 2nd kind:

```math
\\left\\{n \\atop k \\right\\} = \\frac{1}{k!}\\sum_{i=0}^k (-1)^i\\binom{k}{i}(k-i)^n
```

Throws a `DomainError` if inputs do not satisfy `n ≥ k ≥ 1`.
"""
function stirling2(n :: Int64, k :: Int64) :: Int64
    if !(n ≥ k ≥ 1)
        throw(DomainError((n,k), "Inputs (n,k) do not satisfy `n ≥ k ≥ 1`."))
    end

    sum(map( i -> (-1)^i*binomial(k, i)*(k-i)^n, 0:k) )/factorial(k)
end

"""
    stirling2_partitions( n :: Int64, k :: Int64 ) :: Vector{Vector{Vector{Int64}}}

Enumerates the unique partitions of `n` items into `k` unlabelled sets.
Each partition is a vector containing a  set of `k` vectors designating each group.

E.g.

```jldoctest
julia> stirling2_partitions( 4, 2 )
7-element Array{Array{Array{Int64,1},1},1}:
 [[1, 2, 3], [4]]
 [[3], [1, 2, 4]]
 [[1, 2], [3, 4]]
 [[1, 3], [2, 4]]
 [[2], [1, 3, 4]]
 [[2, 3], [1, 4]]
 [[1], [2, 3, 4]]
```

This recursive algorithm was inspired by [this blog](https://devblogs.microsoft.com/oldnewthing/20140324-00/?p=1413).
"""
function stirling2_partitions(n :: Int64, k :: Int64) :: Vector{Vector{Vector{Int64}}}
    if (n ==  0) || (k == 0) || (n < k)
        return Vector{Vector{Vector{Int64}}}(undef,0)
    elseif (k == 1)
        return [[[1:n...]]]
    else
        # partitions include all (n-1, k-1) paritions with [n] as k-th group
        partitions1 = _stirling2_add_partition(n, stirling2_partitions(n-1, k-1))

        # partitions include all (n-1,  k) partitions with [n] appended to each existing group
        partitions2 = _stirling2_extend_partitions(n, k, stirling2_partitions(n-1, k))

        return cat(partitions1, partitions2, dims=1)
    end
end

# helper function for adding a new group to a partition
function _stirling2_add_partition(n :: Int64, partitions :: Vector{Vector{Vector{Int64}}}) :: Vector{Vector{Vector{Int64}}}
    if length(partitions) == 0
        return [[[n]]] :: Vector{Vector{Vector{Int64}}}
    else
        return map(s -> cat(s, [[n]], dims=1), partitions) :: Vector{Vector{Vector{Int64}}}
    end
end

# helper function for extending existing groups with a new element
function _stirling2_extend_partitions(n :: Int64, k :: Int64, partitions :: Vector{Vector{Vector{Int64}}}) :: Vector{Vector{Vector{Int64}}}
    new_partitions = Vector{Vector{Vector{Int64}}}(undef, length(partitions)*k)
    id = 1

    _extend_partition_set(partition :: Vector{Vector{Int64}}) = begin
        for i in 1:k
            new_partitions[id] = cat(partition[1:k .!= i], [cat(partition[i], [n], dims=1)], dims=1)
            id += 1
        end
    end

    map(_extend_partition_set, partitions)

    new_partitions
end

"""
    stirling2_matrices( n :: Int64, k :: Int64 ) :: Vector{Matrix{Bool}}

Generates the set of matrices with `k` rows and `n` columns where rows correspond
to the groups and columns are the grouped elements. A non-zero element designates
that the column id is grouped into the corresponding row.

E.g.

```jldoctest
julia> stirling2_matrices( 4, 2 )
7-element Array{Array{Bool,2},1}:
 [1 1 1 0; 0 0 0 1]
 [0 0 1 0; 1 1 0 1]
 [1 1 0 0; 0 0 1 1]
 [1 0 1 0; 0 1 0 1]
 [0 1 0 0; 1 0 1 1]
 [0 1 1 0; 1 0 0 1]
 [1 0 0 0; 0 1 1 1]
```

A `DomainError` is thrown if `n ≥ k ≥ 1` is not satisfied.
"""
function stirling2_matrices(n :: Int64, k :: Int64) :: Vector{Matrix{Bool}}
    if !(n ≥ k ≥ 1)
        throw(DomainError((n,k), "Inputs (n,k) do not satisfy `n ≥ k ≥ 1`."))
    end

    _partition_to_matrix(partition :: Vector{Vector{Int64}}) :: Matrix{Bool} = begin
        m = fill(false, k, n)
        for row_id in 1:k
            col_ids = partition[row_id]
            m[row_id,col_ids] .= true
        end

        return m
    end

    map(_partition_to_matrix, stirling2_partitions(n, k))
end

"""
    permutation_matrices( dim :: Int64 ) :: Vector{Matrix{Bool}}

Generates the set of square permutation matrices of dimension `dim`.

E.g.

```jldoctest
julia> permutation_matrices( 3 )
6-element Array{Array{Bool,2},1}:
 [1 0 0; 0 1 0; 0 0 1]
 [1 0 0; 0 0 1; 0 1 0]
 [0 1 0; 1 0 0; 0 0 1]
 [0 0 1; 1 0 0; 0 1 0]
 [0 1 0; 0 0 1; 1 0 0]
 [0 0 1; 0 1 0; 1 0 0]
```
"""
function permutation_matrices(dim :: Int64) :: Vector{Matrix{Bool}}
    map(perm_ids -> Matrix{Bool}(I,dim,dim)[:,perm_ids], permutations(1:dim))
end

"""
    n_choose_k_matrices( n :: Int64, k :: Int64 ) :: Vector{Matrix{Bool}}

Generates a set of `n` by `k` matrices which represent all combinations of selecting
`k` columns from `n` rows.  Each column, contains a single non-zero element and
`k` rows contain a non-zero element.

E.g.

```jldoctest
julia> n_choose_k_matrices( 4, 2 )
6-element Array{Array{Bool,2},1}:
 [1 0; 0 1; 0 0; 0 0]
 [1 0; 0 0; 0 1; 0 0]
 [1 0; 0 0; 0 0; 0 1]
 [0 0; 1 0; 0 1; 0 0]
 [0 0; 1 0; 0 0; 0 1]
 [0 0; 0 0; 1 0; 0 1]
```

A `DomainError` is thrown if `n ≥ k ≥ 1` is not satisfied.
"""
function n_choose_k_matrices(n :: Int64, k :: Int64) :: Vector{Matrix{Bool}}
    if !(n ≥ k ≥ 1)
        throw(DomainError((n,k), "Inputs (n,k) must satisfy `n ≥ k ≥ 1`."))
    end
    _combo_to_matrix(combo :: Vector{Int64}) :: Matrix{Bool} = begin
        m = fill(false,n,k)
        for col_id in 1:k
            m[combo[col_id],col_id] = true
        end

        return m
    end

    map(_combo_to_matrix, combinations(1:n, k))
end

"""
    base_n_val(
        num_array :: Vector{Int64},
        base :: Int64;
        big_endian=true :: Bool
    ) :: Int64

Given an array representing a number in base-n returns the value of that
number in base-10.

Inputs:
* `num_array` - Vector containing semi-positive integers less than base.
* `base` - The base-n number represented by num_array.
* `big_endian` - `true` if most significant place is at index 1, else `false`.
"""
function base_n_val(num_array :: Vector{Int64}, base :: Int64; big_endian = true :: Bool) :: Int64
    if maximum(num_array) >= base
        throw(ArgumentError("max value of $num_array is not valid in $base"))
    elseif minimum(num_array) < 0
        throw(ArgumentError("min value of $num_array must be zero or greater"))
    end

    pad = length(num_array)

    place_vals = big_endian ? map(
        x -> base^(pad - x[1]), enumerate(num_array)
    ) : map(
        x -> base^(x[1] - 1), enumerate(num_array)
    )

    val = place_vals'*num_array

    val
end


# If p is the perimeter of a right angle triangle with integral length sides,
# {a,b,c}, there are exactly three solutions for p = 120.
#
# {20,48,52}, {24,45,51}, {30,40,50}
#
# For which value of p ≤ 1000, is the number of solutions maximised?

using ProjectEulerSolutions

# Simple approach that cycles through legal ranges for a and b and storing
# counts of integer values of c.  Solution is the max of that array.  An even
# faster solution would involve directly generating Pythagorean triplets.
function p039solution(n::Integer=12)::Integer
    solutions = zeros(Integer, n)

    for a in 3:fld(n,3) # Range between 3 and n/3 (3 could be larger)
        for b in (a+1):fld(n,2)
            c = sqrt(a * a + b * b)
            if c - floor(c) == 0 && a + b + c <= n
                solutions[Integer(a + b + c)] += 1
            end
        end
    end

    return findmax(solutions)[2]
end

p039 = Problems.Problem(p039solution)

Problems.benchmark(p039, 1000)


# ---
# title: 507. Perfect Number
# id: problem507
# author: Tian Jun
# date: 2020-10-31
# difficulty: Easy
# categories: Math
# link: <https://leetcode.com/problems/perfect-number/description/>
# hidden: true
# ---
#
# A [**perfect number**](https://en.wikipedia.org/wiki/Perfect_number) is a
# **positive integer** that is equal to the sum of its **positive divisors** ,
# excluding the number itself. A **divisor** of an integer `x` is an integer
# that can divide `x` evenly.
#
# Given an integer `n`, return `true` _if_`n` _is a perfect number, otherwise
# return_`false`.
#
#
#
# **Example 1:**
#
#
#
#     Input: num = 28
#     Output: true
#     Explanation: 28 = 1 + 2 + 4 + 7 + 14
#     1, 2, 4, 7, and 14 are all divisors of 28.
#
#
# **Example 2:**
#
#
#
#     Input: num = 6
#     Output: true
#
#
# **Example 3:**
#
#
#
#     Input: num = 496
#     Output: true
#
#
# **Example 4:**
#
#
#
#     Input: num = 8128
#     Output: true
#
#
# **Example 5:**
#
#
#
#     Input: num = 2
#     Output: false
#
#
#
#
# **Constraints:**
#
#   * `1 <= num <= 108`
#
#
## @lc code=start
using LeetCode

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


## We need to use a library for Symbolic calculations, a very common one is SymPy from the python
## language. Luckily Julia has excellent support for it.
using SymPy

#jl Remove the follow `#jl` to use this file standalone
#jl include("lv_model.jl")

# We want to deal with all of these as symbolic variables, which are not the default type in Julia,
# unlike Mathematica.
@vars u1 u2
@vars r1 r2 a12 a21 K1 K2
# We need to make a symbolic parameter list, as `LVPar` is numeric
spar = Dict(
    :α12 => a12,
    :α21 => a21,
    :r1 => r1,
    :r2 => r2,
    :K1 => K1,
    :K2 => K2);

# we need to make symbolic versions of the model equations. We do this by calling the function with
# the symbolic parameters. the last parameter could be anythign as the time (`t`) argument is not
# used. I have set it to `NaN` which is a name for not a number.
f1, f2 = lv_comp([u1, u2], spar, NaN)
#-
SymPy.solve(f1, u1)
#-
SymPy.solve(f1, u2)
#-
SymPy.solve(f2, u1)
#-
SymPy.solve(f2, u2)
#-

#### Another version of above (does the same thing but not as elegant)

# x, y, r1, r2, k1, k2, a12, a21 = symbols("x, y, r1, r2, k1, k2, a12, a21", real = true)
#
# f(x, y) = x * r1 * (1 - (x + a12 * y) / k1)
# g(x ,y) = y * r2 * (1 - (y + a21 * x) / k2)
#
# SymPy.solve(f(x,y),x)
# SymPy.solve(f(x,y),y)
# SymPy.solve(g(x,y),x)
# SymPy.solve(g(x,y),y)


#=
Takes one step in the Markov chain according to the given state's transition
vector.
Inputs:
- transition_vector: An array of length p where the ith element denotes the
                     probability of transitioning into state i from the current
                     state (which is implicitly given).
Outputs:
- An integer in {1,2,..., p} denoting the index of the next state which occurs
with the probability specified in transition_vector.
=#
function one_step(transition_vector)
    cumulative_vector = cumsum(transition_vector)
    state_index = 1
    U = rand()
    while(U > cumulative_vector[state_index])
        state_index += 1
    end

    return state_index
end

#=
Runs a Markov chain a fixed number of times and returns the final state.
Inputs:
- states: An array of length p where each element represents a state of the
          chain. Assumes the ordering is such that the probability of
          transitioning from state i to state j is given by P[i,j].
          Examples:
          [0,1,2,3,4,5]
          ["red", "green", "blue"]
          [0,"blue",3,2]
- P: A (p x p) matrix denoting the transition matrix of the Markov chain.
     Assumes the rows of P sum to 1 and P[i,j] >= 0 for all i,j.
- S0: The initial state of the Markov chain. Assumed to be an element of the
      state variable.
- steps: A nonnegative integer specifying the number of steps for the Markov
         chain to go through.
Outputs:
- An element of the states variable denoting the state the chain is in after
  the specified number of steps.
=#
function run_fixed_simulation(states, P, S0, steps)

    # Define a dictionary mapping state to their index in P
    state_dict = Dict(states[i] => i for i = 1:length(states))

    # Define the current state of the chain
    Sn = S0

    # Run through the specified number of steps
    for i = 1:steps
        state_index = get(state_dict, Sn, -1)
        next_state_index = one_step(P[state_index,:])
        Sn = states[next_state_index]
    end

    return Sn
end

# State space (χ)
states = [1,2,3,4]

# Transition matrix for gambler's ruin
P = [.1 .1 .4 .4; .1 .2 .3 .4; .4 .3 .15 .15; .4 .4 .15 .05]

# Create histogram of S_100
num_simulations = 100000
state100 = [run_fixed_simulation(states, P, 4, 100) for i = 1:num_simulations]

using Plots
histogram(state100, legend = false, grid = false,
    bg = RGB(247/255, 236/255, 226/255))

cd("/Users/elvis/Documents/DSI/courses/content/stochastic-approximations/julia-code/")
savefig("../images/invariant_measure_hist_2.svg")


include("header.jl")

#=
# Velocity profiles
In this notebook, we will discuss a useful way to visualize a flow in fluid
mechanics: the **velocity profile**.
=#
#-

# ### Set up the module
using MAE103
#-
using Plots

#=
To start, let's consider one of the most basic types of flow: the linear shear flow,
often called **Couette flow**. This type of flow is generated, for example, between
two parallel walls when one wall is moving and the other is stationary. In the figure
below, the top wall is moving with velocity $U$, the lower wall is stationary:

Because of the **no-slip condition**, the fluid next to each wall moves with it. The
fluid next to the upper wall moves at velocity $U$, the fluid next to the lower wall
is at rest.

The velocity profile depicts $u(y)$, the horizontal component of velocity as a
function of vertical position, $y$. This increases linearly from the lower to the
upper wall. In fact, the function is just

$$u(y) = Uy/H$$

The arrows indicate the direction that the fluid is moving, and the lengths of
the arrows indicate the relative speed at that $y$ position.
=#
#-
#=
### Shear stress
We can calculate the viscous shear stress at any location with the equation

$$\tau = \mu \frac{\mathrm{d}u}{\mathrm{d}y}$$

In fact, in Couette flow, shear stress is the same everywhere:

$$\tau = \mu U/H$$

This represents, e.g., the resistive force (per unit area) that the fluid applies
(to the left) on the upper wall while the wall moves to the right.

It also represents the force per unit area that the lower wall must withstand in
order to remain stationary. The fluid is trying to drag the lower wall to the right,
so this must be balanced by some reaction force in the wall to the left.

The **rate of work** (per unit area) required to move the upper wall is $\dot{W} = \tau U$.
This would be measured in units of, e.g., $W/m^2$.
=#
#-
#=
### Plotting velocity profiles
The arrows are helpful, but you can also plot a velocity profile without them.
For example, consider the following velocity:

$$u(y) = \frac{4U_c}{H^2} y (H - y)$$

The coefficient $U_c$ is a speed, and $H$ is the gap height.
=#
#=
Let's define a function that evaluates this velocity. Here, `y`, `Uc`, and `H`
are to be given as arguments to the function.
=#
u(y,Uc,H) = 4*Uc/H^2*y*(H-y)

#=
Suppose the gap height $H$ is 1 cm and the speed $U_c$ is 1 m/s. We will evaluate
this velocity at a range of locations between 0 and $H$. Here, we evaluate
the velocity at 101 points. There's nothing magical about this number; it just
is enough to make the curve look smooth. Also, note that we have to give
units for the `y` values, as well:
=#
H = 1u"cm"
Uc = 1u"m/s"
y = range(0u"cm",H,length=101)

#=
Now we evaluate the velocity function at the range of $y$ locations. (Remember that
the `.` vectorizes the evaluation of a function. Here, we are telling it to
evaluate the function `u` at each element of the `y` array.) The resulting
velocities are assigned to `v`.
=#
v = u.(y,Uc,H)

#=
Notice that $u$ is 0 at the beginning and end of the range. Let's plot it. But
let's plot it as a velocity profile, which means we make velocity the 'x' axis and $y$
the 'y' axis:
=#
plot(v,y,xlim=(0,Inf),ylim=(0,Inf),xlabel=L"u(y)",ylabel=L"y")

#=
NOTE: The use of `xlim=(0,Inf),ylim=(0,Inf)` simply ensures that the axes start at 0
and end at the upper range of the data in `v` and `y`, respectively.

The top and bottom of this plot suggest that these are stationary walls where the
flow is at rest. In fact, this is the velocity profile associated with pressure-driven
flow through the gap.
=#
#-
#=
Suppose we wish to plot the velocity in different units. We can easily do this with
the `xunit` flag. For example, in `ft/s`:
=#
plot(v,y,xunit=u"ft/s",xlim=(0,Inf),ylim=(0,Inf),xlabel=L"u(y)",ylabel=L"y")
#-
#=
In the previous notebook, we showed that we can set velocity using
the `Velocity` function. We can do that here if we like. We'll call it `vv`.
We have to use the `.` notation to make sure each element in `v` gets the
same treatment:
=#
vv = Velocity.(v)
plot(vv,y,xlim=(0,Inf),ylim=(0,Inf),xlabel="u(y)",ylabel="y")

#=
The `Velocity` function simply *wraps* the velocity data in `v`. It is
probably not immediately clear why we would need to do this, since the
data are already in the correct units (m/s). However, it
will be useful later when we use functions that perform calculations,
and we wish to make sure we don't try to provide the wrong type of data
to those functions.
=#
#-

#=
#### Viscometry
We can use the relationship between the velocity and shear stress for many practical
tasks. In particular, it can be used to measure the viscosity of a given fluid,
in a device called a **viscometer**. This device consists of a rotating inner
cylinder of radius $R$ with prescribed angular velocity $\Omega$ and a stationary
outer wall, and a fluid with unknown viscosity, $\mu$, in the gap of size $H$ between
them.

The basic idea of a viscometer is that one knows the angular velocity $\Omega$
and measures the *torque*, $T$, required to rotate it. This torque is directly
attributable to the viscous shear stress acting on the inner cylinder.

Even though this is an annular region and not a straight gap like in the basic
Couette flow, the curvature does not matter here because the gap $H$ is very small
compared to the radius $R$.

Comparing this gap flow with the Couette flow above, the wall velocity here is
$U = \Omega R$, so the wall shear stress applied on the inner cylinder is

$$\tau = \mu \Omega R/H$$

How do we relate this to the measured torque? Torque is the force applied on the
wall times the distance to the center, $R$. The shear stress is uniformly applied
on the inner cylinder, so the force applied by the fluid is the shear stress times
the surface area,

$$T = \tau (2\pi R L) R$$

The term in parentheses is the surface area of the inner cylinder, where $L$ is its
length (into the page).

Thus, putting all of this together:

$$T = 2\pi L\mu \Omega R^3/H$$
=#
#-
#=
#### A viscometer example
Let's suppose that we take measurements of a viscometer with the following
characteristics: inner radius $R = 6$ cm, gap height $H = 0.125$ cm, and length
$L = 13$ cm. We will convert everything to SI (i.e., meters):
=#
R = 6u"cm"
H = 0.125u"cm"
L = 13u"cm";

#=
We run the viscometer at 6 different speeds and measure the required torque in
each case. Here is the data we measured. Angular velocity is given in rad/s,
and torque in N$\cdot$m
=#
Ω = [1.0,2.0,3.0,4.0,5.0,6.0]u"rad/s"

T = [0.181,0.36,0.54,0.72,0.899,1.086]u"N*m";

#-
#=
Let's plot the data to see the trend qualitatively:
=#
scatter(Ω,T,xlim=(0,Inf),ylim=(0,Inf),xlabel="Angular velocity",ylabel="Torque")

#=
It looks quite linear! This is expected, since torque is linearly dependent on
the angular velocity in the relationship above.
Based on the equation above, we expect that the ratio of these two quantities
will be equal to $2\pi L\mu R^3/H$. So if we multiply the ratio by $H/(2\pi L R^3)$,
we expect

$$\dfrac{T H}{2\pi L R^3 \Omega} = \mu$$

Let's do this for our data. Since we are doing this for each data point,
we will get an array of values. (Notice the `.` in front of `/` when we divide by `Ω`.
Dividing by an array needs to be done in a vectorized manner.) We assign this
array the name `a`:
=#
a = T./Ω*H/(2π*L*R^3)

#=
We expect `a` to give us viscosity, but there's always the chance for
experimental error. So we will take the mean of `a`. And we will use the
`Viscosity` function to put this in the default units of viscosity:
=#
Viscosity(mean(a))

#=
It's quite viscous! Only slightly smaller viscosity than glycerin.
=#
Viscosity(Glycerin)


# LSAC JUNE 2007 Form 8LSN75
# https://www.lsac.org/sites/default/files/legacy/docs/default-source/jd-docs/sampleptjune.pdf

# Questions 1-5
using AbstractLogic

#A company employee generates a series of five-digit product
#codes in accordance with the following rules:
#The codes use the digits 0, 1, 2, 3, and 4, and no others.
logicset = logicalparse(["a, b, c, d, e  ∈  0:4"]); showfeasible(logicset)

#Each digit occurs exactly once in any code.
logicset = logicalparse(["{{i}} != {{!i}}"], logicset); showfeasible(logicset)

#The second digit has a value exactly twice that of the first digit.
logicset = logicalparse(["b == a*2"], logicset); showfeasible(logicset)

#The value of the third digit is less than the value of the fifth digit
logicset = logicalparse(["c < e"], logicset); showfeasible(logicset)

#1. If the last digit of an acceptable product code is 1...
# (which of the following has must be true N out of N cases).

#(A) first digit is 2
checkfeasible("e == 1 ==> a==2", logicset)
# Returns true: 6 of 6 possible.

#(B) second digit is 0
checkfeasible("e == 1 ==> b==0", logicset)
# False: 5 out of 6

#(C) third digit is 3
checkfeasible("e == 1 ==> c==3", logicset)
# False: 5 out of 6

#(D) fourth digit is 4
checkfeasible("d == 1 ==> d==4", logicset)
# False: 5 out of 6

#(E) fourth digit is 0
checkfeasible("d == 1 ==> d==0", logicset)
# False: 5 out of 6

#2. Which one of the following must be true about any
#acceptable product code?
range(logicset)

#(A) The digit 1 appears in some position before the digit 2.
checkfeasible("{{i}} == 1 &&& {{>i}} == 2 {{1}}", logicset, force=true)
# False: only 3 of 6

#(B) The digit 1 appears in some position before the digit 3.
checkfeasible("{{i}} == 1 &&& {{>i}} == 3 {{1}}", logicset, force=true)
# False: only 5 of 6

#(C) The digit 2 appears in some position before the digit 3.
checkfeasible("{{i}} == 2 &&& {{>i}} == 3 {{1}}", logicset, force=true)
# True! 6 of 6

#(D) The digit 3 appears in some position before the digit 0.
checkfeasible("{{i}} == 0 &&& {{>i}} == 3 {{1}}", logicset, force=true)
# False: only 5 of 6

#(E) The digit 4 appears in some position before the digit 3.
checkfeasible("{{i}} == 4 &&& {{>i}} == 3 {{1}}", logicset, force=true)
# False: only 4 of 6

#3. If the third digit of an acceptable product code is not 0, which one of the
# following must be true?

#(A) The second digit of the product code is 2.
checkfeasible("c != 0 ==> b == 2", logicset, force=true)
# False: only 5 of 6

#(B) The third digit of the product code is 3.
checkfeasible("c != 0 ==> b == 2", logicset, force=true)
# False: only 5 of 6

#(C) The fourth digit of the product code is 0.
checkfeasible("c != 0 ==> d == 0", logicset, force=true)
# True! 6 of 6

#(D) The fifth digit of the product code is 3. <true>
checkfeasible("c != 0 ==> e == 3", logicset, force=true)
# False: 5 of 6

#(E) The fifth digit of the product code is 1.
checkfeasible("c != 0 ==> e == 1", logicset, force=true)
# False: 4 of 6

#4. Any of the following pairs could be the third and fourth digits,
# respectively, of an acceptable product
#code, EXCEPT:

# (A) 0, 1
checkfeasible("c == 0 &&& d == 1", logicset)
# Possible 1 of 6

# (B) 0, 3
checkfeasible("c == 0 &&& d == 3", logicset)
# Possible 2 of 6

#(C) 1, 0
checkfeasible("c == 1 &&& d == 0", logicset)
# Possible 1 of 6

#(D) 3, 0
checkfeasible("c == 3 &&& d == 0", logicset)
# Possible 1 of 6

#(E) 3, 4
checkfeasible("c == 3 &&& d == 4", logicset)
# False! 0 of 6

#5. Which one of the following must be true about any acceptable product code?
logicset |> showfeasible

#(A) There is exactly one digit between the digit 0 and the digit 1.
checkfeasible("{{i}} == 1|0 ==> {{i+2}} == 1|0", logicset, force=true)
# False 1 of 6 outcomes

#(B) There is exactly one digit between the digit 1 and the digit 2.
checkfeasible("{{i}} == 1|2 ==> {{i+2}} == 1|2", logicset, force=true)
# False 0 of 6 outcomes

#(C) There are at most two digits between the digit 1 and the digit 3.
checkfeasible("{{i}} == 1|3 ==> {{i+4}} != 1,3", logicset, force=true)
# False 5 out of 6 outcomes true

#(D) There are at most two digits between the digit 2 and the digit 3.
checkfeasible("{{i}} == 2|3 ==> {{i+4}} != 2,3", logicset, force=true)
# False 4 out of 6 outcomes feasible

#(E) There are at most two digits between the digit 2 and the digit 4.
checkfeasible("{{i}} == 2|4 ==> {{i+4}} != 2,4", logicset, force=true)
# True 6 out of 6 outcomes feasible


using CSV, GLM, LinearAlgebra, StatsPlots, Plots, Measures; pyplot()

df = CSV.read("../data/weightHeight.csv")
n = size(df)[1]

model = lm(@formula(Height ~ Weight), df)
MSE = deviance(model)/dof_residual(model)
pred(x) = coef(model)'*[1, x]

A = [ones(n) df.Weight]
H = A*pinv(A)
residuals = (I-H)*df.Height
studentizedResiduals = residuals ./ (sqrt.(MSE*(1 .- diag(H))))

tau = rank(H)
println("tau = ", tau, " or ", sum(diag(H)), " or ", tr(H))

cookDistances = (studentizedResiduals.^2/tau) .* diag(H) ./ (1 .- diag(H))
maxCook, indexMaxCook = findmax(cookDistances)
println("Maximal Cook's distance = ", maxCook)

p1 = plot([minimum(df.Weight),maximum(df.Weight)],[0,0],
		lw = 2, c=:black, label=:none)
     scatter!([df.Weight[indexMaxCook]],[studentizedResiduals[indexMaxCook]],
                c=:red, ms = 10, msw = 0, shape =:cross,
                label = "Point with maximal Cook's distance",legend=:topleft)

    scatter!(df.Weight, studentizedResiduals, xlabel = "Weight (kg)",
             ylabel = "Studentized Residual (cm)",c=:blue,msw=0,label=:none)

p2 = qqnorm(studentizedResiduals, msw=0, lw=2, c =[:red :blue],legend = false,
        xlabel="Normal Theoretical Quantiles",
        ylabel="Studentized Residual Quantiles")

plot(p1,p2,size=(1000,500),margin = 5mm)


using ParallelAccelerator

@acc function covariance(m::Int, n::Int, float_n::Float64, data::Matrix{Float64})
    cov = Matrix{Float64}(m, m)
    mean = Vector{Float64}(m)
    data_m_mean = Matrix{Float64}(n, m)
    for j in 1:m
        mean[j] = 0.0
        for i = 1:n
            mean[j] += data[i, j]
        end
        mean[j] /= float_n
    end
    for i = 1:n
        for j = 1:m
            data_m_mean[i, j] = data[i, j] - mean[j]
        end
    end
    for i = 1:m
        for j = i:m
            cov[i, j] = 0.0
            for k = 1:n
                cov[i, j] += data_m_mean[k, i] * data_m_mean[k, j]
            end
            cov[i, j] /= (float_n - 1.0)
            cov[j, i] = cov[i, j]
        end
    end
    return cov
end

@inbounds function covariance_o(m::Int, n::Int, float_n::Float64, data::Matrix{Float64})
    cov = Matrix{Float64}(m, m)
    mean = Vector{Float64}(m)
    data_m_mean = Matrix{Float64}(n, m)
    tic()
    for j in 1:m
        mean[j] = 0.0
        for i = 1:n
            mean[j] += data[i, j]
        end
        mean[j] /= float_n
    end
    for i = 1:n
        for j = 1:m
            data_m_mean[i, j] = data[i, j] - mean[j]
        end
    end
    for i = 1:m
        for j = i:m
            cov[i, j] = 0.0
            for k = 1:n
                cov[i, j] += data_m_mean[k, i] * data_m_mean[k, j]
            end
            cov[i, j] /= (float_n - 1.0)
            cov[j, i] = cov[i, j]
        end
    end
    println(" Julia $(toq())")
    return cov
end

m = 2300
n = 2300
float_n = Float64(n)
data = Matrix{Float64}(n, m)
data = rand(n, m)

res_tiramisu = covariance(m, n, float_n, data)
res_julia  = covariance_o(m, n, float_n, data)

function compare(A, B)
    for i in 1:m
        for j in 1:m
            if A[i, j] != B[i, j]
                println("different $((i, j)) : $(A[i, j]) != $(B[i, j])")
                return false
            end
        end
    end
    return true
end

println(compare(res_tiramisu, res_julia))


# Question
# By considering the terms in the Fibonacci sequence whose values do not exceed four million, find the sum of the even-valued terms
# 0, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ...

# Time
# O(n) any better solution??
# every third term is even (neglect 0 ) we can use it and remove if
# any better solution?

function main()
    limit = 4_000_000 # 4 million
    # series odd odd even types
    s = 2
    a = [1,2]
    b = 2
    i = 3
    while (b<limit)
        b = a[i-1]+a[i-2]
        push!(a, b)
        if(b %2 == 0)
            s+=b
        end
        i+=1
    end
    println(s)
end

@time main()


# # Lovász Theta Function
#
# The Lovász theta function is an important concept in graph theory. Consider an undirected graph $G = (V,E)$ with vertex set $V = \{1,\dots,n\}$ and edge set $E$ with  $E_{ij} = 1$ if there exists an edge between the vertices $i$ and $j$. A stable set (or independent set) is a subset of $V$ such that the induced subgraph does not contain edges. The *stability number* $\alpha(G)$ of the graph is equal to the cardinality of the largest stable set. It is closely related to the *Shannon capacity* $\Theta(G)$ that models the amount of information that a noisy communication channel can carry if certain signal values can be confused with each other. Unfortunately, the determination of $\alpha(G)$ is an NP-hard problem and the computational complexity to determine $\Theta(G)$ remains unknown. The Lovász theta function $\vartheta(G)$ was introduced in \[1\], can be computed in polynomial time, and represents an upper bound on both the stability number and the Shannon capacity:
# $$
# \alpha(G) \leq \Theta(G) \leq \vartheta(G).
# $$
# The value of $\vartheta(G)$ can be determined by computing the optimal value $p^*$ of the following SDP:
# $$
# \begin{array}{ll} \text{maximize} &   \text{Tr}(JX)\\
# \text{subject to} &  \text{Tr}(X) = 1, \\
#                   &  X_{ij} = 0, \quad (i, j) \in E, \\
#                   &  X \succeq 0,
# \end{array}
# $$
# with matrix variable $X$, matrix $J \in \mathbf{R}^{n \times n}$ of all ones and $\text{Tr}()$ denoting the matrix trace.
# In this simple example we will compute the value of the Lovász theta function for the *Petersen Graph* (which is known to be $4$).

#-
using LinearAlgebra, COSMO, JuMP, Plots, GraphRecipes

## let's define the Petersen graph
n = 10
E = zeros(n, n)
E[1, 2] = E[1, 5] = E[1, 6] = 1.
E[2, 3] = E[2, 7]  = 1.
E[3, 4] = E[3, 8]  = 1.
E[4, 5] = E[4, 9] = 1.
E[5, 10] = 1.
E[6, 8] = E[6, 9] = 1.
E[7, 9] = E[7, 10] = 1.
E[8, 10] = 1.

## plot the graph
ri = 1.
ro = 2.
coordinates = []
for θ = 90:-72:-198
  push!(coordinates, [ro * cosd(θ), ro * sind(θ)])
end
for θ = 90:-72:-198
  push!(coordinates, [ri * cosd(θ), ri * sind(θ)])
end
graphplot(E, names = 1:n,  x = getindex.(coordinates, 1), y = getindex.(coordinates, 2), fontsize = 10, nodesize = 1, nodeshape =:circle, curvature = 0.)

# Let's solve the SDP with COSMO and JuMP:
#-
model = JuMP.Model(COSMO.Optimizer);

@variable(model, X[1:n, 1:n], PSD)
x = vec(X)
@objective(model, Max, sum(x))
@constraint(model, tr(X)== 1.)
for j = 1:n
  for i = 1:j-1
    if E[i, j] == 1.
      @constraint(model, X[i, j] == 0)
    end
  end
end
status = JuMP.optimize!(model)

# The optimal objective is given by:
#-
JuMP.objective_value(model)
# Which is the correct known value for the Petersen Graph.
#
# ## References
# [1] Lovász - On the Shannon Capacity of a Graph, IEEE Transactions on Information Theory (1979)


# This file illustrates the simulation of Q-PSK signalling. The simulation is performed using
# waveform AWGN channel.

using Plots
using FFTW
using LinearAlgebra
using Statistics
using DigitalCommunications

# Settings
k = 2                                   # Bits per symbol
M = 2^k                                 # Constellation size
T = 1.                                  # Symbol duration
f = 1 / T                               # Symbols per second
fs = 10                                 # Sampling frequency
ts = 1 / fs                             # Sampling period
t = 0 : ts : T - ts                     # Time vector for symbol duration
l = length(t)                           # Number of samples per symbol
# g(t) = 0 ≤ t ≤ T ? sqrt(Eg / T) : 0     # Modulation pulse: rectangular pulse
# Eg = 1                                  # Modulation pulse energy: rectangular pulse
A = 1                                     # Amplitude for raised cosine pulse
g(t) = 0 ≤ t ≤ T ?  A/2 * (1 - cos(2π/T * t)) : 0       # Raised cosine modulation pulse
Eg = A^2 * 3 / 8                                        # Energy for raised cosine modulation pulse
nsymbols = Int(1e6)                     # Number of symbols

# Define basis
basis = [
    t -> g(t) / sqrt(Eg)
]
alphabet = [
    [1 + 0im],
    [0 + 1im],
    [-1 + 0im],
    [0 - 1im],
]
basisvals = map(base -> base.(t), basis)
references = map(1 : M) do mi
    Ai = alphabet[mi] * sqrt(Eg)
    sum(Ai .* basisvals)
end

# Simulate the system
ebno = collect(0 : 1 : 10)
esno = ebno .+ 10 * log10(k)
m = rand(1 : M, nsymbols)
tx = map(m) do mi
    Ai = alphabet[mi] * sqrt(Eg)
    sum(Ai .* basisvals)
end
Es = mean(txi -> sum(abs.(txi).^2) * ts, tx)
symerr = zeros(length(esno))
for i = 1 : length(symerr)
    # Generate noise
    σ = sqrt(Es * fs / 2 / (10^(esno[i] / 10)))
    n = [σ * (randn(l) + 1im * randn(l)) for i = 1 : length(tx)]
    # Corrupt transmitted signal
    rx = tx + n
    # Detect symbols
    mr = map(rx) do rxi
        argmax(map(reference -> real(rxi ⋅ reference), references))
    end
    # Calculate symbol
    ber = sum(m .!= mr) / length(m)
    symerr[i] = ber
end

# Plots
plot(ebno, symerr, marker=:circle, yscale=:log10, label="montecarlo")
plot!(ebno, berpsk.(esno, M), marker=:circle, yscale=:log10, label="theoretical")
title!("$M-PSK")
xlabel!("EbNo")
ylabel!("Pe")


# Text provided under a Creative Commons Attribution license, CC-BY.  All code is made available under the FSF-approved BSD-3 license.  (c) Lorena A. Barba, Gilbert F. Forsyth 2017. Thanks to NSF for support via CAREER award #1149784.
# [@LorenaABarba](https://twitter.com/LorenaABarba)
# Translation to Julia by [@miguelraz_](https://twitter.com/miguelraz_)

## 12 steps to Navier–Stokes
# ======
# ***

# This Jupyter notebook continues the presentation of the **12 steps to Navier–Stokes**,
# the practical module taught in the interactive CFD class of [Prof. Lorena Barba](http://lorenabarba.com).
# You should have completed [Step 1](./01_Step_1.jl) before continuing, having written your own Julia script
# or notebook and having experimented with varying the parameters of the discretization and observing what happens.

## Step 2: Nonlinear Convection
# -----
# ***

# Now we're going to implement nonlinear convection using the same methods as in step 1.  The 1D convection equation is:

# $$\frac{\partial u}{\partial t} + u \frac{\partial u}{\partial x} = 0$$

# Instead of a constant factor $c$ multiplying the second term, now we have the solution $u$ multiplying it. Thus, the second term of the equation is now *nonlinear*. We're going to use the same discretization as in Step 1 — forward difference in time and backward difference in space. Here is the discretized equation.

# $$\frac{u_i^{n+1}-u_i^n}{\Delta t} + u_i^n \frac{u_i^n-u_{i-1}^n}{\Delta x} = 0$$

# Solving for the only unknown term, $u_i^{n+1}$, yields:

# $$u_i^{n+1} = u_i^n - u_i^n \frac{\Delta t}{\Delta x} (u_i^n - u_{i-1}^n)$$

# As before, the Julia code starts by loading the necessary libraries. Then, we declare some variables
# that determine the discretization in space and time (you should experiment by changing these parameters to see what happens).
# Then, we create the initial condition $u_0$ by initializing the array for the
# solution using $u = 2\ @\ 0.5 \leq x \leq 1$  and $u = 1$ everywhere else in $(0,2)$ (i.e., a hat function).
# **Note**
# We're now going to be exploting Julia's amazing capabilities for Unicode. This lets us write code that is closer to the mathematics we want to model.
# To type `Δx`, write `\Delta<TAB>` and Julia should autocomplete it. You can also autocomplete incomplete characters so there's
# no need to memeorize them all, although they mirror how they're written in LaTeX so that helps a lot.

import Plots    # and our 2D plotting library
Plots.plotly()

nₓ = 41 # n\_<TAB>x
Δx = 2 / (nₓ - 1)
nₜ = 20    #nₜ is the number of timesteps we want to calculate
Δt = .025  #Δt is the amount of time each timestep covers (delta t)

u = ones(nₓ)      #as before, we initialize u with every value equal to 1.
#then set u = 2 between 0.5 and 1 as per our I.C.s
u[Int.((.5/Δx):(1/Δx)] .= 2 # This is a very numpy-ish way of writing this

# An equivalent way would be just
for i in length((.5/Δx):(1/Δx))
      u[i] = 2
end

uₙ = ones(nₓ) #initialize our placeholder array un, to hold the time-stepped solution

# The code snippet below is *unfinished*. We have copied over the line from [Step 1](./01_Step_1.jl)
# that executes the time-stepping update. Can you edit this code to execute the nonlinear convection instead?

for n in nₜ  #iterate through time
    uₙ = copy(u) ##copy the existing values of u into uₙ
    for i in 1:nₓ  ##now we'll iterate through the u array

     ###This is the line from Step 1, copied exactly.  Edit it for our new equation.
     ###then uncomment it and run the cell to evaluate Step 2
           ###u[i] = uₙ[i] - c * dt / dx * (uₙ[i] - uₙ[i-1])
    end
end

plot(range(start = 0, stop = 2, length = nₓ), u) ##Plot the results

# What do you observe about the evolution of the hat function under the nonlinear convection equation? What happens when you change the numerical parameters and run again?

## Learn More

# For a careful walk-through of the discretization of the convection equation with finite differences (and all steps from 1 to 4), watch **Video Lesson 4** by Prof. Barba on YouTube.

# TODO
# from IPython.display import YouTubeVideo
# YouTubeVideo('y2WaK7_iMRI')


export cg
using LinearAlgebra

"""
`cg(A, b [, x0];  maxiter=10, tol=1e-3, log=false) -> x [, history_x, history_r]`

Solve the system `Ax = b` of a symmetric, positive definite linear map given by `A` using the conjugate gradients method.

Parameters
----------
* `A`

Linear map, which must have two methods defined: multiplication by `AbstractVector` and transpose multiplication by `AbstractVector`.

* `b` : `AbstractVector`

RHS of equation `Ax = b`.

* `x0` : `AbstractVector`

Starting solution.

* `maxiter` : Int, optional

Maximum number of iterations. Defaults to 10.

* `tol` : Real, optional

Stopping criterion tolerance. Stops if the square root of the ration between the previous and current residuals is smaller than `tol`. Defaults to 1e-3.

* `log` : Bool, optional

If `true`, returns also model and residuals at each iterations.
"""
function cg(A, b::AbstractVector, x0::AbstractVector; maxiter::Int=10, tol::Real=1e-3, log::Bool=false)
    x = x0[:]
    r = b - A * x
    p = r
    rsold = dot(r, r)
    r1 = rsold
    if log
        history_x = [x]
        history_r = [rsold]
    end

    for i = 1:minimum([length(b), maxiter])
        Ap = A * p
        alpha = rsold / dot(p, Ap)
        x = x + alpha * p
        r = r - alpha * Ap
        rsnew = dot(r, r)
        p = r + (rsnew / rsold) * p
        if log
            push!(history_x, x)
            push!(history_r, rsnew)
        end
        if sqrt(rsnew/rsold) < tol || p ≈ zero(p)
            break
        end
        rsold = rsnew
    end
    if log
        return x, history_x, history_r
    else
        return x
    end
end
cg(A, b; kwargs...) = cg(A, b, zeros(length(b)); kwargs...)


using ModelingToolkit
using Base.Test

# Derivatives
@IVar t
@Var x(t) y(t) z(t)
@Param σ ρ β
@Deriv D'~t
dsin = D*sin(t)
expand_derivatives(dsin)

@test expand_derivatives(dsin) == cos(t)
dcsch = D*csch(t)
@test expand_derivatives(dcsch) == Operation(-1*coth(t)*csch(t))
# Chain rule
dsinsin = D*sin(sin(t))
@test expand_derivatives(dsinsin) == cos(sin(t))*cos(t)
# Binary
dpow1 = Derivative(^,[x, y],Val{1})
dpow2 = Derivative(^,[x, y],Val{2})
@test dpow1 == y*x^(y-1)
@test dpow2 == x^y*log(x)

d1 = D*(sin(t)*t)
d2 = D*(sin(t)*cos(t))
@test expand_derivatives(d1) == t*cos(t)+sin(t)
@test expand_derivatives(d2) == cos(t)*cos(t)+sin(t)*(-1*sin(t))

eqs = [0 ~ σ*(y-x),
       0 ~ x*(ρ-z)-y,
       0 ~ x*y - β*z]
sys = NonlinearSystem(eqs,[x,y,z],[σ,ρ,β])
jac = ModelingToolkit.calculate_jacobian(sys)
@test jac[1,1] == σ*-1
@test jac[1,2] == σ
@test jac[1,3] == 0
@test jac[2,1] == ρ-z
@test jac[2,2] == -1
@test jac[2,3] == x*-1
@test jac[3,1] == y
@test jac[3,2] == x
@test jac[3,3] == -1*β


function computus(year; servois=false)

    # Year's position on the 19 year metonic cycle
    a = mod(year, 19)

    # Century index
    k = floor(Int, year/100)

    # Shift of metonic cycle, add a day offset every 300 years
    p = floor(Int, (13 + 8 * k) / 25)

    # Correction for non-observed leap days
    q = floor(Int, k / 4)

    # Correction to starting point of calculation each century
    M = mod(15 - p + k - q, 30)

    # Number of days from March 21st until the full moon
    d = mod(19 * a + M, 30)

    # Returning if user wants value for Servois' table
    if servois
        return mod(21+d,31)
    end

    # Finding the next Sunday
    # Century-based offset in weekly calculation
    N = mod(4 + k - q, 7)

    # Correction for leap days
    b = mod(year, 4)
    c = mod(year, 7)

    # Days from d to next Sunday
    e = mod(2 * b + 4 * c + 6 * d + N, 7)

    # Historical corrections for April 26 and 25
    if (d == 29 && e == 6) || (d == 28 && e == 6 && a > 10)
        e = -1
    end

    # Determination of the correct month for Easter
    if(22+ d + e > 31)
        return "April "*string(d + e - 9)
    else
        return "March "*string(22+d+e)
    end
end

# Here, we will output the date of the Paschal full moon
# (using Servois notation), and Easter for 2020-2030

a = [i for i = 2020:2030]
servois_numbers = computus.(a;servois=true)
easter_dates = computus.(a)

println("The following are the dates of the Paschal full moon (using Servois "*
        "notation) and the date of Easter for 2020-2030 AD:")
println("Year	Servois number	Easter")
for i = 1:length(a)
    println(string(a[i])*"\t"*string(servois_numbers[i])*"\t\t"*
            string(easter_dates[i]))
end


# See https://en.wikipedia.org/wiki/List_of_fractals_by_Hausdorff_dimension

const Fractal{D} = Vector{Segment{D}}

function unit_circle(npoints::Int)::Fractal{2}
    @assert npoints > 2
    θ = linspace(0, 2*pi, npoints)
    x = cos.(θ)
    y = sin.(θ)
    [Segment(Point(x[i], y[i]), Point(x[i+1], y[i+1])) for i in 1:npoints-1]
end

#"""Step a segment into
#   /\
#--/  \--
#"""
function koch_line(line::Segment{2})::Fractal{2}
    p0, p1, p2, p3 = line.O, line(1./3), line(2./3), line.B

    v = 0.5*(p1 + p2)
    v += Point(-line.t[2], line.t[1])*sqrt(3)/2*line.length/3

    [Segment(p0, p1), Segment(p1, v), Segment(v, p2), Segment(p2, p3)]
end

"""Grow by stepiing from straigt line. FD = log(4)/log(3)"""
function koch_line(depth::Int)::Fractal{2}
    @assert depth > 0
    lines = [Segment(Point(0, 0), Point(1, 0))]
    while depth > 0
        lines = vcat(map(koch_line, lines)...)
        depth -= 1
    end
    lines
end

"""Grow (outward) by stepping from quilateral triangle. FD = log(4)/log(3)"""
function koch_flake(depth::Int)::Fractal{2}
    @assert depth > 0
    lines = [Segment(Point(0, 0), Point(0.5, sqrt(3)/2)),
             Segment(Point(0.5, sqrt(3)/2), Point(1, 0)),
             Segment(Point(1, 0), Point(0, 0))]
    while depth > 0
        lines = vcat(map(koch_line, lines)...)
        depth -= 1
    end
    lines
end

"""Like Kock flake but grows inward. Triflake in wiki?. FD = log(4)/log(3)"""
function triflake(depth::Int)::Fractal{2}
    @assert depth > 0
    lines = [Segment(Point(0, 0), Point(1, 0)),
             Segment(Point(1, 0), Point(0.5, sqrt(3)/2)),
             Segment(Point(0.5, sqrt(3)/2), Point(0, 0))]
    while depth > 0
        lines = vcat(map(koch_line, lines)...)
        depth -= 1
    end
    lines
end

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

#"""                  |-|
#--------  becomes   -| |-
#"""
function koch_quadratic_1(line::Segment{2})
    v1, v2, v3, v4 = line.O, line(1./3), line(2./3), line.B
    shift = Point(-line.t[2], line.t[1])*line.length/3

    [Segment(v1, v2),
     Segment(v2, v2 + shift),
     Segment(v2 + shift, v3 + shift),
     Segment(v3 + shift, v3),
     Segment(v3, v4)]
end

"""Grow by stepping from (0, 0) - (1, 0). FD = log(5)/log(3)"""
function koch_quadratic_1(depth::Int)::Fractal{2}
    @assert depth > 0
    lines = [Segment(Point(0, 0), Point(1, 0))]
    while depth > 0
        lines = vcat(map(koch_quadratic_1, lines)...)
        depth -= 1
    end
    lines
end

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

#"""                  |-|
#--------  becomes   -| | |-
#                       |-|
#"""
function koch_quadratic_2(line::Segment{2})
    v1, v2, v3, v4, v5 = line.O, line(0.25), line(0.5), line(0.75), line.B
    shift = Point(-line.t[2], line.t[1])*line.length/4

    [Segment(v1, v2),
     Segment(v2, v2 + shift),
     Segment(v2 + shift, v3 + shift),
     Segment(v3 + shift, v3),
     Segment(v3, v3 - shift),
     Segment(v3 - shift, v4 - shift),
     Segment(v4 - shift, v4),
     Segment(v4, v5)]
end

"""Grow by stepping from (0, 0) - (1, 0). FD = 1.5"""
function koch_quadratic_2(depth::Int)::Fractal{2}
    @assert depth > 0
    lines = [Segment(Point(0, 0), Point(1, 0))]
    while depth > 0
        lines = vcat(map(koch_quadratic_2, lines)...)
        depth -= 1
    end
    lines
end

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

#"""               _
#\  / becomes   | |
# \/            |_|
#"""
function dragon_curve(lines::NTuple{2, Segment{2}})
    line = first(lines)
    v1, v2 = line.O, line.B
    shift = Point(line.t[2], -line.t[1])*line.length/2
    m = 0.5*(v1 + v2) + shift

    l1 = (Segment(v1, m), Segment(m, v2))

    line = last(lines)
    v1, v2 = line.O, line.B
    shift = Point(-line.t[2], line.t[1])*line.length/2
    m = 0.5*(v1 + v2) + shift

    [l1, (Segment(v1, m), Segment(m, v2))]
end

"""Grow by stepping from (0, 0) - (1, -1) - (2, 0). FD = 1.526"""
function dragon_curve(depth::Int)::Fractal{2}
    @assert depth > 0
    pairs = [(Segment(Point(0, 0), Point(1, -1)),
              Segment(Point(1, -1), Point(2, 0)))]
    while depth > 0
        pairs = vcat(map(dragon_curve, pairs)...)
        depth -= 1
    end
    # Saw them together
    [l for pair in pairs for l in pair]
end


"""
`LinearKernel([ρ=1.0,[c=0.0]])`
The linear kernel is a Mercer kernel given by
```
    κ(x,y) = ρ²xᵀy + c
```
Where `c` is a real number
"""
struct LinearKernel{Tc<:Real} <: BaseKernel
    c::Vector{Tc}
    function LinearKernel(;c::T=0.0) where {T}
        new{T}([c])
    end
end

kappa(κ::LinearKernel, xᵀy::Real) = xᵀy + first(κ.c)

metric(::LinearKernel) = DotProduct()

"""
`PolynomialKernel([ρ=1.0[,d=2.0[,c=0.0]]])`
The polynomial kernel is a Mercer kernel given by
```
    κ(x,y) = (ρ²xᵀy + c)^d
```
Where `c` is a real number, and `d` is a shape parameter bigger than 1
"""
struct PolynomialKernel{Td<:Real, Tc<:Real} <: BaseKernel
    d::Vector{Td}
    c::Vector{Tc}
    function PolynomialKernel(; d::Td=2.0, c::Tc=0.0) where {Td<:Real, Tc<:Real}
        @check_args(PolynomialKernel, d, d >= one(Td), "d >= 1")
        return new{Td, Tc}([d], [c])
    end
end

kappa(κ::PolynomialKernel, xᵀy::T) where {T<:Real} = (xᵀy + first(κ.c))^(first(κ.d))

metric(::PolynomialKernel) = DotProduct()


"""
    CG(j1,m1,j2,m2,j,m)

a shortcut for `clebschgordan`
"""
CG(j1,m1,j2,m2,j,m) = clebschgordan(j1,m1,j2,m2,j,m)

"""
    CG_doublearg(j1,m1,j2,m2,j,m)

a shortcut for `clebschgordan_doublearg`
"""
CG_doublearg(two_j1,two_m1,two_j2,two_m2,two_j,two_m) = clebschgordan_doublearg(two_j1,two_m1,two_j2,two_m2,two_j,two_m)

"""
    clebschgordan(j1,m1,j2,m2,j,m)

gives a numerical value of the Clebsch-Gordan coefficient
```
    ⟨ j₁ m₁ ; j₂ m₂ | j m ⟩
```
The input values are expected to be __integers__.
For a general case including half-integers see `clebschgordan_doublearg`.
"""
clebschgordan(j1,m1,j2,m2,j,m) = clebschgordan_doublearg(2j1,2m1,2j2,2m2,2j,2m)

"""
    clebschgordan_doublearg(two_j1,two_m1,two_j2,two_m2,two_j,two_m)

gives a numerical value of the Clebsch-Gordan coefficient
```
    ⟨ j₁ m₁ ; j₂ m₂ | j m ⟩
```
The function requires __doubled value of the momenta__ for the input.
"""
function clebschgordan_doublearg(two_j1,two_m1,two_j2,two_m2,two_j,two_m)
    ((abs(two_m1) > two_j1) || (abs(two_m2) > two_j2) || (abs(two_m ) > two_j )) && return 0.0
    ((two_m1+two_m2 != two_m) || !(abs(two_j1-two_j2) ≤ two_j ≤ two_j1+two_j2))  && return 0.0
     #
    prefactor = sqrt(two_j+1)*
        exp( ( f_logfact2(two_j1+two_j2-two_j) +
               f_logfact2(two_j1+two_j -two_j2) +
               f_logfact2(two_j2+two_j -two_j1) -
               f_logfact2(two_j1+two_j2+two_j+2) +
               #
               f_logfact2(two_j1+two_m1) +
               f_logfact2(two_j1-two_m1) +
               f_logfact2(two_j2+two_m2) +
               f_logfact2(two_j2-two_m2) +
               f_logfact2(two_j +two_m ) +
               f_logfact2(two_j -two_m ) ) / 2)
    res = 0.0
    two_t_min = max(0,
                    two_j2-two_m1-two_j,
                    two_j1+two_m2-two_j)
    two_t_max = min(two_j1+two_j2-two_j,
                    two_j1-two_m1,
                    two_j2+two_m2)
    #
    for two_t = two_t_min:2:two_t_max
        logs = f_logfact2(two_t) +
               f_logfact2(two_j-two_j2+two_m1+two_t) +
               f_logfact2(two_j-two_j1-two_m2+two_t) +
               f_logfact2(two_j1+two_j2-two_j-two_t) +
               f_logfact2(two_j1-two_m1-two_t) +
               f_logfact2(two_j2+two_m2-two_t);
        res += (abs(two_t) % 4 == 2 ? -1.0 : 1.0) * exp(-logs);
    end
    res *= prefactor
    return res;
end


# ---
# title: 397. Integer Replacement
# id: problem397
# author: Indigo
# date: 2021-06-20
# difficulty: Medium
# categories: Math, Bit Manipulation
# link: <https://leetcode.com/problems/integer-replacement/description/>
# hidden: true
# ---
#
# Given a positive integer `n`, you can apply one of the following operations:
#
#   1. If `n` is even, replace `n` with `n / 2`.
#   2. If `n` is odd, replace `n` with either `n + 1` or `n - 1`.
#
# Return _the minimum number of operations needed for`n` to become `1`_.
#
#
#
# **Example 1:**
#
#
#
#     Input: n = 8
#     Output: 3
#     Explanation: 8 -> 4 -> 2 -> 1
#
#
# **Example 2:**
#
#
#
#     Input: n = 7
#     Output: 4
#     Explanation: 7 -> 8 -> 4 -> 2 -> 1
#     or 7 -> 6 -> 3 -> 2 -> 1
#
#
# **Example 3:**
#
#
#
#     Input: n = 4
#     Output: 2
#
#
#
#
# **Constraints:**
#
#   * `1 <= n <= 231 - 1`
#
#
## @lc code=start
using LeetCode

function integer_replacement(n::Int)
    res = 0
    while n != 1
        if iseven(n)
            n >>= 1
        else
            n += (n & 2 == 0 || n == 3) ? -1 : 1
        end
        res += 1
    end
    return res
end
## @lc code=end


##
##  f z e r o . j l  Root Finding
##

function ridders(f::Function, a::Real, b::Real;
                 maxiter::Integer = 1000, tol::Real = eps())

    x1 = a;     x2 = b
    f1 = f(x1); f2 = f(x2)
    if f1 * f2 > 0
        error("f(a) and f(b) must have different signs.")
    elseif f1 == 0
        return x1
    elseif f2 == 0
        return f2
    end

    niter = 2
    while niter < maxiter && abs(x1 - x2) > tol
        xm = (x1 + x2)/2.0
        fm = f(xm)
        if fm == 0; return xm; end

        x3 = xm + (xm - x1) * sign(f1 - f2) * fm / sqrt(fm^2 - f1 * f2)
        f3 = f(x3)
        niter += 2
        if f3 == 0; return x3; end

        if fm * f3 < 0
            x1 = xm;  f1 = fm
            x2 = x3;  f2 = f3
        elseif f1 * f3 < 0
            x2 = x3;  f2 = f3
        elseif f2 * f3 < 0
            x1 = x3;  f1 = f3
        else
            error("Inform the maintainer: you should never get here.")
        end
    end

    return (x1 + x2) / 2.0
end

function brent_dekker(f::Function, a::Real, b::Real;
                       maxiter::Integer = 1000, tol::Real = eps())

	x1 = a; f1 = f(x1)
	if f1 == 0; return a; end
	x2 = b; f2 = f(x2)
	if f2 == 0; return b; end
	if (f1*f2 > 0.0)
	    error("Brent-Dekker: Root is not bracketed in [a, b]")
	end

	x3 = 0.5*(a+b)
	# Beginning of iterative loop
	niter = 1
	while (niter <= maxiter)
		f3 = f(x3)
		if abs(f3) < tol
		    x0 = x3
		    break
		end

		# Tighten brackets [a, b] on the root
		if f1*f3 < 0.0
		    b = x3
		else
		    a = x3
		end
		if (b-a) < tol*max(abs(b), 1.0)
		    x0 = 0.5*(a + b)
		    break
	    end

		# Try quadratic interpolation
		denom = (f2 - f1)*(f3 - f1)*(f2 - f3)
		numer = x3*(f1 - f2)*(f2 - f3 + f1) + f2*x1*(f2 - f3) + f1*x2*(f3 - f1)
		# if denom==0, push x out of bracket to force bisection
		if denom == 0
			dx = b - a
		else
			dx = f3*numer/denom
		end

		x = x3 + dx
		# If interpolation goes out of bracket, use bisection.
		if (b - x)*(x - a) < 0.0
			dx = 0.5*(b - a)
			x  = a + dx;
		end

		# Let x3 <-- x & choose new x1, x2 so that x1 < x3 < x2.
		if x1 < x3
			x2 = x3; f2 = f3
		else
			x1 = x3; f1 = f3
		end

		niter += 1
		if abs(x - x3) < tol
		    x0 = x
		    break
	    end
		x3 = x
	end

    if (niter > maxiter)
        println("Maximum number of iterations has been reached.")
    end

    prec = min(abs(x1-x3), abs(x2-x3))
    return x3
end


using LinearAlgebra
using CVChannel
using Test

"""
This verifies the multiplicativity of the phase rotated amplitude channel family.

The channel family is parameterized by one parameters. We scan over this parameter
and check that it is multiplicative at each point.
...
"""

println("First we define the channel and verify it works.")
function phaseRotAmpDamp(ρ :: Matrix{<:Number}, ε :: Union{Int,Float64}) :: Matrix{<:Number}
    K0 = [1 0 ; 0 sqrt(1-ε)]
    K1 = 1/sqrt(2)*[1 exp(1im*pi/4) ; exp(-1im*pi/4) 1]*[0 sqrt(ε) ; 0 0]

    return K0*ρ*K0' + K1*ρ*K1'
end

@testset "Verify the phase rotated Amplitude Damping Channel Works" begin
    ρ1 = [3/4 0 ; 0 1/4]
    ρ2 = 1/2*[1 -1im ; 1im 1]
    ε1 = 0.3; ε2 = 0.7;

    target1(ε) = [
        3/4+1/8*ε  1/8*exp(1im*pi/4)*ε
        1/8*exp(-1im*pi/4)*ε 1/4-1/8*ε
    ]

    target2(ε) = [
        1/2+1/4*ε -1im/2*sqrt(1-ε)+1/4*exp(1im*pi/4)*ε
        1im/2*sqrt(1-ε)+1/4*exp(-1im*pi/4)*ε 1/2-1/4*ε
    ]

    @test isapprox(phaseRotAmpDamp(ρ1,ε1),target1(ε1), atol=1e-6)
    @test isapprox(phaseRotAmpDamp(ρ1,ε2),target1(ε2), atol=1e-6)
    @test isapprox(phaseRotAmpDamp(ρ2,ε1),target2(ε1), atol=1e-6)
    @test isapprox(phaseRotAmpDamp(ρ2,ε2),target2(ε2), atol=1e-6)
end

println("Now we are going to verify the multiplicativity of the channel")
@testset "Verify Multiplicativity of the Channel" begin
    ε_vals = [0:0.01:1;];
    ctr = 1;
    results = zeros(size(ε_vals)[1],4)
    println("\nBeginning to get the data...")
    println("\nPlease note it starts slow calling the solver.")
    for ε_val in ε_vals
        if (ε_val != 0)&&(Int(floor(ε_val*100)) % 5 == 0)
            println("\nNow evaluating for ε = ", ε_val)
        end
        phaseRotAmpDampVarep(ρ) = phaseRotAmpDamp(ρ,ε_val)
        orig_choi = choi(phaseRotAmpDampVarep,2,2)
        test1 = pptCVDual(orig_choi,2,2)
        kron_par_choi = kron(orig_choi,orig_choi)
        par_choi = permuteSubsystems(kron_par_choi,[1,3,2,4],[2,2,2,2])
        test2 = pptCVPrimal(par_choi,4,4)
        results[ctr,:] = [ε_val, test1[1], test2[1], test2[1]-test1[1]^2]
        ctr = ctr + 1
    end
    println("\ncolumn labels at bottom")
    show(stdout, "text/plain", results)
    println("\n p     cv(N)   cv(N^2)  diff")
    println("\nThus by looking at the diff column, we see what we were verifying")
    println("up to a numerical accuracy of 1e-5 (some computers can do 5e-6).")
    @test all(result -> result > 0 || isapprox(result, 0, atol=1e-5), results[:,4])
end


include("header.jl")

#=
# Streamlines
In this notebook, we will learn some basic ways to calculate and plot streamlines
=#

# ### Set up the module
using MAE103
#-
using Plots

#=
Streamlines are curves in space that are everywhere tangent to the local velocity field.
They indicate the lines of flow in a steady fluid flow. If the flow is unsteady,
the lines represent a snapshot of the flow, but those lines will tend to change
with time.

The basic equations for streamlines come from the fact that the streamline must
be locally parallel to the velocity. This means that the local slope
of a streamline, $\mathrm{d}y/\mathrm{d}x$, must be equal to the ratio of the
velocity components, $v/u$, if we use Cartesian components for the velocity,
$\boldsymbol{V} = u \boldsymbol{i} + v \boldsymbol{j}$. Sometimes streamlines may
not be describable as $y$ as a function of $x$. For example, if a streamline is vertical,
then it can't be written this way. In that case, it would be better to write it as $x(y)$.
Or better yet, we could write it *parametrically*: e.g., $x(s)$ and $y(s)$, where $s$
is a parameter that increases along the streamline curve. So the more general way
to write the streamline equation that covers all cases is

$$\dfrac{\mathrm{d}x}{u} =  \dfrac{\mathrm{d}y}{u}$$

From this, we can choose either $x$ or $y$ as our independent variable, or another
one if necessary. We will see a few ways in this notebook.
=#
#-
#=
# Example 1:
Suppose we are given the velocity field for a *stagnation flow*:
$u(x,y) = 10*x$, $v(x,y) = -10*y$
We seek the streamlines.
=#
#-
#=
We first write these as a
$$\dfrac{\mathrm{d}y}{\mathrm{d}x} = \dfrac{v}{u} = \dfrac{-10y}{10x} = -\dfrac{y}{x}$$

Then, we separate variables and integrate:
$$\dfrac{\mathrm{d}y}{y} = -\dfrac{\mathrm{d}x}{x}$$

This leads to $\ln y = c - \ln x$, where $c$ is an arbitrary constant. Raising these
to a power of $\mathrm{e}$, we get

$$y = k/x$$

where $k$ is an arbitrary constant. Different choices for $k$ correspond to different streamlines.
=#
#-
#=
Set up a function, which we'll call `ys`. We'll set a default value of `k`.
=#
ys(x;k=1) = k/x

#=
Now plot several for different choices of `k`:
=#
x = range(-3,3,length=101)
plot(xlim=(-3,3),ylim=(-3,3),ratio=1)
plot!(x,ys.(x;k=2),color=:black)
plot!(x,ys.(x;k=1),color=:black)
plot!(x,ys.(x;k=-1),color=:black)
plot!(x,ys.(x;k=-2),color=:black)
plot!(x,ys.(x;k=0),color=:black)

#=
The $x$ axis is a streamline ($k = 0$). The $y$ axis is also a streamline, but
we can't get it because we assumed that $y$ was a function of $x$. If we repeat
the process by assuming that $x$ is a function of $y$, we get

$$x = k/y$$
=#
xs(y;k=1) = k/y
y = range(-3,3,length=101)
plot!(xs.(y;k=0),y,color=:black)
#=
(Note that we swapped the order of arguments.)
=#

#=
# Example 2: A polar streamline plot
In this example, we wish to generate streamlines when the velocity is given
in polar coordinates: $\boldsymbol{V} = v_r \boldsymbol{e}_r + v_\theta \boldsymbol{e}_\theta$.
The velocity field is $v_r = 0$, $v_\theta = 10/r$. This velocity field has
only a circumferential component, which means that that flow only goes around in
circles; there is no radial flow.
=#
#-
#=
We still apply the fact that the streamline is tangent to the velocity. However,
now, we don't use $\mathrm{d}x$ and $\mathrm{d}y$, but rather $\mathrm{d}r$
and $r\mathrm{d}\theta$:

$$\dfrac{\mathrm{d}r}{r\mathrm{d}\theta} = \dfrac{v_r}{v_\theta} = 0$$
=#
#-
#=
In other words, $\mathrm{d}r/\mathrm{d}\theta = 0$ in this case, which simply
says that the streamlines are lines of constant $r$ --- they are circles!
That is, $r(\theta) = k$. Each choice of $k$ is a different streamline, and $k$
simply gives the radius of the streamline:
=#
rs(Θ;k=1) = k

#=
Let's plot these streamlines. For plotting purposes, it is easiest to
convert the polar coordinates into Cartesian:

$$x = r\cos\theta,\qquad y = r\sin\theta$$
=#
xs(Θ;k=1) = rs(Θ,k=k)*cos(Θ)
ys(Θ;k=1) = rs(Θ,k=k)*sin(Θ)

#-
Θ = range(0,2π,length=201)
plot(xlim=(-2,2),ylim=(-2,2),ratio=1)
plot!(xs.(Θ,k=1),ys.(Θ,k=1),color=:black)
plot!(xs.(Θ,k=1.5),ys.(Θ,k=1.5),color=:black)
plot!(xs.(Θ,k=0.5),ys.(Θ,k=0.5),color=:black)


module problem128

description = """
Hexagonal tile differences

A hexagonal tile with number 1 is surrounded by a ring of six hexagonal tiles, starting at "12 o'clock" and numbering the tiles 2 to 7 in an anti-clockwise direction.
New rings are added in the same fashion, with the next rings being numbered 8 to 19, 20 to 37, 38 to 61, and so on. The diagram below shows the first three rings.
By finding the difference between tile n and each its six neighbours we shall define PD(n) to be the number of those differences which are prime.
For example, working clockwise around tile 8 the differences are 12, 29, 11, 6, 1, and 13. So PD(8) = 3.
In the same way, the differences around tile 17 are 1, 17, 16, 1, 11, and 10, hence PD(17) = 2.
It can be shown that the maximum value of PD(n) is 3.

If all of the tiles for which PD(n) = 3 are listed in ascending order to form a sequence, the 10th tile would be 271.
Find the 2000th tile in this sequence.
"""

# Value at the top of each ring is calculated using arithmetic series
# a = First element, d = common difference, n = num terms to sum
arithmetic_series(a, d, n) = div((2*a + (n-1)*d)*n,2)

# The value at the start of each ring
ringstart(ring :: Int) = ring == 1 ? 1 : 2 + arithmetic_series(0, 6, ring-1)

# The value at the start of one side in a ring
sidestart(ring :: Int, side :: Int) = ringstart(ring) + (side-1) * (ring-1)

# The value at a given ring, side and position along the side
valueat(ring :: Int, side :: Int, pos :: Int) = sidestart(ring, side) + (pos-1)

# Calculates the neighbours of a given tile, given by its ring, side and position
# Special care required at side=1,pos=1 and side=6,pos=ring-1
# There are always 2 tiles adjacent in the same ring
# When pos = 1, there are 3 adjacent on the next ring, and 1 on the previous
# otherwise, there are 2 adjacent on the next ring, and 2 on the previous
function neighbours(ring :: Int, side :: Int, pos :: Int)
  a = valueat(ring, side, pos)
  b = (side == 1 && pos == 1) ? valueat(ring, 6, ring-1) : (a-1)
  c = (side == 6 && pos == (ring-1)) ? valueat(ring, 1, 1) : (a+1)
  d = (side == 6 && pos == (ring-1)) ? valueat(ring-1, 1, 1) : valueat(ring-1, side, pos)
  if pos == 1
    e = valueat(ring+1, side, pos)
    f = valueat(ring+1, side, pos+1)
    g = valueat(ring+1, side == 1 ? 6 : side-1, ring)
  else
    e = valueat(ring-1, side, pos-1)
    f = valueat(ring+1, side, pos)
    g = valueat(ring+1, side, pos+1)
  end
  return [b,c,d,e,f,g]
end

# search for the N'th value having 3 adjacent tiles with prime difference
# Testing found that these only occur at side=1, pos=1 and side=6, pos=ring-1
function search(N=2000)
  found = 1 # 1 is a 3 prime diff with 3,4,6
  for ring = 2:Inf, (side, pos) in [(1, 1), (6, ring-1)]
    v = valueat(ring, side, pos)
    n = neighbours(ring, side, pos)
    diffs = abs(n - v)
    if count(isprime, diffs) == 3
      found += 1
    end
    if found == N
      return v
    end
  end
end

using Base.Test

@test ringstart(1) == 1
@test ringstart(2) == 2
@test ringstart(3) == 8
@test ringstart(4) == 20
@test sidestart(2, 1) == 2
@test sidestart(2, 3) == 4
@test sidestart(4, 6) == 35
@test valueat(4, 1, 1) == 20
@test valueat(4, 6, 3) == 37
@test sort(neighbours(2, 1, 1)) == [1, 3, 7, 8, 9, 19]
@test sort(neighbours(3, 2, 2)) == [3, 4, 10, 12, 24, 25]
@test sort(neighbours(3, 6, 2)) == [2, 7, 8, 18, 36, 37]
@test search(10) == 271
end


# b-splines

using Plots, LaTeXStrings

function b_spline_basis(i,r,t,u)
    u_i = u[i+1]
    u_i1 = u[i+2]
    u_ir = u[i+r+1]
    u_i1r = u[i+r+2]

    if r == 0
        if u_i <= t <= u_i1
            return 1
        else
            return 0
        end
    else
        N = b_spline_basis
        left = 0
        if (u_ir-u_i) != 0
            left = (t-u_i)/(u_ir-u_i) * N(i,r-1,t,u)
        end

        right = 0
        if (u_i1r-u_i1) != 0
            right = (u_i1r-t)/(u_i1r-u_i1) * N(i+1,r-1,t,u)
        end
        return left + right
    end
end

function b_spline(dBx,dBy, n, knot_vector; steps=200)
    m = length(dBx)-1
    if length(knot_vector) != m+1+n+1
        println("The knot vector has the wrong length it should have m+1+n+1 = ", m+1+n+1, " components.")
        println("But it has ", length(knot_vector), " components")
        return
    end
    plot(;size=(700,500), axisratio=:equal, legend=false)
    u_min = knot_vector[1]
    u_max = knot_vector[end]
    step_size = (u_max-u_min)/(steps-1)

    curve = zeros(steps,2)
    N = (i,r,t)->b_spline_basis(i,r,t,knot_vector)
    c = 1
    for t=u_min:step_size:u_max
        pos = zeros(2)
        for i=0:m
            if knot_vector[i+1] != knot_vector[i+1+n]
                pos = pos .+ [dBx[i+1],dBy[i+1]].*N(i,n,t)
            end
        end
        curve[c,:] .= pos
        c += 1
    end
    plot!(curve[:,1],curve[:,2])
    plot!(dBx,dBy, linetype=:scatter, color=:springgreen)
    plot!(dBx,dBy, color=:springgreen)
    for i in 0:length(dBx)-1
        annotate!(dBx[i+1]+0.2,dBy[i+1]+0.35, latexstring("d_"*string(i)))
    end

    png("b_spline")
end

dBx = [0.0,-1,5,7, 9,13]
dBy = [0.0, 3,5,4,-2, 1]
@time b_spline(dBx, dBy, 2, [0,0,0,1,2,3,4,4,4])


import Arpack,LinearAlgebra#, NLsolve
#TODO: Degeneracy
# function householder_update(f,a=1)
#     order=length(f)-1
#     a=1.0/a
#
#     if order==1   #aka Newton's method
#         dz=-f[1]/(a*f[2])
#     elseif order==2 #aka Halley's method
#         dz=-2*f[1]*f[2]/((a+1)*f[2]^2-f[1]*f[3])
#     elseif order==3
#         dz=-3*f[1]*((a+1)*f[2]^2-f[1]*f[3]) / ((a^2 + 3*a + 2) *f[2]^3 - 3* (a + 1)* f[1]* f[2] *f[3] + f[1]^2* f[4])
#     elseif order==4
#         dz=-4*f[1]*((a^2 + 3*a + 2) *f[2]^3 - 3* (a + 1)* f[1]* f[2] *f[3] + f[1]^2* f[4]) / (-6* (a^2 + 3 *a + 2) *f[1]* f[2]^2* f[3] + (a^3 + 6 *a^2 + 11* a + 6)* f[2]^4 + f[1]^2 *(3* (a + 1)*f[3]^2 - f[1]* f[5]) + 4* (a + 1)*f[1]^2* f[4] *f[2])
#     else
#         dz=-5*f[1]*(-6* (a^2 + 3 *a + 2) *f[1]* f[2]^2* f[3] + (a^3 + 6 *a^2 + 11* a + 6)* f[2]^4 + f[1]^2 *(3* (a + 1)*f[3]^2 - f[1]* f[5]) + 4* (a + 1)*f[1]^2* f[4] *f[2]) / ((a + 1)*(a + 2)*(a + 3)*(a + 4)* f[2]^5 + 10* (a + 1)*(a + 2)*f[1]^2*f[4]*f[2]^2-10*(a + 1)*(a + 2)*(a + 3)*f[1]*f[2]^3*f[3]+f[1]^3*(f[1]*f[6] - 10 *(a + 1) *f[4]* f[3]) + 5 *(a + 1) *f[1]^2*f[2]* (3* (a + 2)* f[3]^2 - f[1]*f[5]))
#     end
#     return dz
# end

function householder_update(f)
    order=length(f)-1
    if order==1
        dz=-f[1]/f[2]
    elseif order==2
        dz=-f[1]*f[2]/ (f[2]^2-0.5*f[1]*f[3])
    elseif order==3
        dz=-(6*f[1]*f[2]^2-3*f[1]^2*f[3]) / (6*f[2]^3-6*f[1]*f[2]*f[3]+f[1]^2*f[4])
    elseif order == 4
        dz=-(4 *f[1] *(6* f[2]^3 - 6 *f[1] *f[2]* f[3] + f[1]^2* f[4]))/(24 *f[2]^4 - 36* f[1] *f[2]^2 *f[3] + 6*f[1]^2* f[3]^2 + 8*f[1]^2* f[2]* f[4] - f[1]^3* f[5])
    else
        dz=(5 *f[1] *(24 *f[2]^4 - 36* f[1] *f[2]^2* f[3] + 6 *f[1]^2* f[3]^2 + 8* f[1]^2* f[2]* f[4] - f[1]^3* f[5]))/(-120* f[2]^5 + 240* f[1]* f[2]^3* f[3] - 60* f[1]^2* f[2]^2* f[4] + 10* f[1]^2* f[2]* (-9* f[3]^2 + f[1]* f[5]) + f[1]^3* (20* f[3]* f[4] - f[1]* f[6]))
    end
    return dz
end
"""
    sol::Solution, n, flag = householder(L::LinearOperatorFamily, z; <keyword arguments>)

Use a Householder method to iteratively find an eigenpar of `L`, starting the the iteration from `z`.

# Arguments
- `L::LinearOperatorFamily`: Definition of the nonlinear eigenvalue problem.
- `z`: Initial guess for the eigenvalue.
- `maxiter::Integer=10`: Maximum number of iterations.
- `tol=0`: Absolute tolerance to trigger the stopping of the iteration. If the difference of two consecutive iterates is `abs(z0-z1)<tol` the iteration is aborted.
- `relax=1`: relaxation parameter
- `lam_tol=Inf`: tolerance for the auxiliary eigenvalue to test convergence. The default is infinity, so there is effectively no test.
- `order::Integer=1`: Order of the Householder method, Maximum is 5
- `nev::Integer=1`: Number of Eigenvalues to be searched for in intermediate ARPACK calls.
- `v0::Vector`: Initial vector for Krylov subspace generation in ARPACK calls. If not provided the vector is initialized with ones.
- `v0_adj::Vector`: Initial vector for Krylov subspace generation in ARPACK calls. If not provided the vector is initialized with `v0`.
- `output::Bool`: Toggle printing online information.

# Returns
- `sol::Solution`
- `n::Integer`: Number of perforemed iterations
- `flag::Integer`: flag reporting the success of the method. Most important values are `1`: method converged, `0`: convergence might be slow, `-1`:maximum number of iteration has been reached. For other error codes see the source code.

# Notes
Housholder methods are a generalization of Newton's method. If order=1 the Housholder method is identical to Newton's method. The solver then reduces to the "generalized Rayleigh Quotient iteration" presented in [1]. If no relaxation is used (`relax == 1`), the convergence rate is of `order+1`. With relaxation (`relax != 1`) the convergence rate is 1.
Thus, with a higher order less iterations will be necessary. However, the computational time must not necessarily improve nor do the convergence properties. Anyway, if the method converges, the error in the eigenvalue is bounded above by `tol`. For more details on the solver, see the thesis [2].

# References
[1] P. Lancaster, A Generalised Rayleigh Quotient Iteration for Lambda-Matrices,Arch. Rational Mech Anal., 1961, 8, p. 309-322, https://doi.org/10.1007/BF00277446

[2] G.A. Mensah, Efficient Computation of Thermoacoustic Modes, Ph.D. Thesis, TU Berlin, 2019

See also: [`beyn`](@ref)
"""
function householder(L,z;maxiter=10,tol=0.,relax=1.,lam_tol=Inf,order=1,nev=1,v0=[],v0_adj=[],output=true)
    if output
        println("Launching Householder...")
        println("Iter    Res:     dz:     z:")
        println("----------------------------------")
    end
    z0=complex(Inf)
    lam=float(Inf)

    n=0
    active=L.active #IDEA: better integrate activity into householder
    mode=L.mode #IDEA: same for mode
    if v0==[]
        v0=ones(ComplexF64,size(L(0))[1])
    end
    if v0_adj==[]
        v0_adj=conj.(v0)#ones(ComplexF64,size(L(0))[1])
    end

    flag=1
    M=-L.terms[end].coeff
    try
        while  abs(z-z0)>tol   && n<maxiter #&& abs(lam)>lam_tol
            if output; println(n,"\t\t",abs(lam),"\t",abs(z-z0),"\t", z );flush(stdout); end
            z0=z
            L.params[L.eigval]=z
            L.params[L.auxval]=0
            A=L(z)
            lam,v = Arpack.eigs(A,M,nev=nev, sigma = 0,v0=v0)
            lam_adj,v_adj = Arpack.eigs(A',M', nev=nev, sigma = 0,v0=v0_adj)
            #TODO: consider constdouton
            #TODO: multiple eigenvalues
            indexing=sortperm(lam, by=abs)
            lam=lam[indexing]
            v=v[:,indexing]
            indexing=sortperm(lam_adj, by=abs)
            lam_adj=lam_adj[indexing]
            v_adj=v_adj[:,indexing]
            delta_z =[]
            L.active=[L.auxval,L.eigval]
            for i in 1:nev
                L.params[L.auxval]=lam[i]
                sol=Solution(L.params,v[:,i],v_adj[:,i],L.auxval)
                perturb!(sol,L,L.eigval,order,mode=:householder)
                f=[factorial(idx-1)*coeff for (idx,coeff) in enumerate(sol.eigval_pert[Symbol("$(string(L.eigval))/Taylor")])]
                dz=householder_update(f) #TODO: implement multiplicity
                #print(z+dz)
                push!(delta_z,dz)
            end
            L.active=[L.eigval]
            indexing=sortperm(delta_z, by=abs)
            lam=lam[indexing[1]]
            L.params[L.auxval]=lam #TODO remove this from the loop body
            z=z+relax*delta_z[indexing[1]]
            v0=(1-relax)*v0+relax*v[:,indexing[1]]
            v0_adj=(1-relax)*v0_adj+relax*v_adj[:,indexing[1]]
            n+=1
        end

    catch excp
        if output
            println("Error occured:")
            println(excp)
            println(typeof(excp))
            println("...aborted Householder!")
        end

        flag=-2
        if typeof(excp) <: Arpack.ARPACKException
           flag=-4
           if excp==Arpack.ARPACKException(-9999)
               flag=-9999
           end
       elseif excp== LinearAlgebra.SingularException(0) #This means that the solution is so good that L(z) cannot be LU factorized...TODO: implement other strategy into perturb
           flag=-6
           L.params[L.eigval]=z
        end
    end
    if flag==1
        L.params[L.eigval]=z
        if output;  println(n,"\t\t",abs(lam),"\t",abs(z-z0),"\t", z ); end

        if n>=maxiter
            flag=-1
            if output; println("Warning: Maximum number of iterations has been reached!");end

        elseif abs(lam)<=lam_tol
            flag=1
            if output; println("Solution has converged!"); end
        elseif abs(z-z0)<=tol
            flag=0
            if output; println("Warning: Slow convergence!"); end
        elseif isnan(z)
            flag=-5
            if output; println("Warning: computer arithmetics problem. Eigenvalue is NaN"); end
        else
            if output; println("Warning: This should not be possible....\n If you can read this contact GAM!");end
            flag=-3
            println(z)
        end

        if output
            println("...finished Householder!")
            println("#####################")
            println(" Householder results ")
            println("#####################")
            println("Number of steps: ",n)
            println("Last step parameter variation:",abs(z0-z))
            println("Auxiliary eigenvalue λ residual (rhs):", abs(lam))
            println("Eigenvalue:",z)
            println("Eigenvalue/(2*pi):",z/2/pi)
        end
    end
    L.active=active
    L.mode=mode
    #normalization
    v0/=sqrt(v0'*M*v0)
    v0_adj/=conj(v0_adj'*L(L.params[L.eigval],1)*v0)
    return Solution(L.params,v0,v0_adj,L.eigval), n, flag
end

##PAde solver
function poly_roots(p)
  N=length(p)-1
  C=zeros(ComplexF64,N,N)
  for i=2:N
    C[i,i-1]=1
  end
  C[:,N].=-p[1:N]./p[N+1]
  return LinearAlgebra.eigvals(C)
end

function padesolve(L,z;maxiter=10,tol=0.,relax=1.,lam_tol=Inf,order=1,nev=1,v0=[],v0_adj=[],output=true,num_order=1)
    if output
        println("Launching Pade solver...")
        println("Iter    Res:     dz:     z:")
        println("----------------------------------")
    end
    z0=complex(Inf)
    lam=float(Inf)
    lam0=float(Inf)

    n=0
    active=L.active #IDEA: better integrate activity into householder
    mode=L.mode #IDEA: same for mode
    if v0==[]
        v0=ones(ComplexF64,size(L(0))[1])
    end
    if v0_adj==[]
        v0_adj=conj.(v0)#ones(ComplexF64,size(L(0))[1])
    end

    flag=1
    M=-L.terms[end].coeff
    try
        while  abs(z-z0)>tol   && n<maxiter #&& abs(lam)>lam_tol
            if output; println(n,"\t\t",abs(lam),"\t",abs(z-z0),"\t", z );flush(stdout); end

            L.params[L.eigval]=z
            L.params[L.auxval]=0
            A=L(z)
            lam,v = Arpack.eigs(A,M,nev=nev, sigma = 0,v0=v0)
            lam_adj,v_adj = Arpack.eigs(A',M', nev=nev, sigma = 0,v0=v0_adj)
            #TODO: consider constdouton
            #TODO: multiple eigenvalues
            indexing=sortperm(lam, by=abs)
            lam=lam[indexing]
            v=v[:,indexing]
            indexing=sortperm(lam_adj, by=abs)
            lam_adj=lam_adj[indexing]
            v_adj=v_adj[:,indexing]
            delta_z =[]
            back_delta_z=[]
            L.active=[L.auxval,L.eigval]
            #println("#############")
            #println("lam0:$lam0 ")
            for i in 1:nev
                L.params[L.auxval]=lam[i]
                sol=Solution(L.params,v[:,i],v_adj[:,i],L.auxval)
                perturb!(sol,L,L.eigval,order,mode=:householder)
                coeffs=sol.eigval_pert[Symbol("$(L.eigval)/Taylor")]
                num,den=pade(coeffs,num_order,order-num_order)

                #forward calculation (has issues with multi-valuedness)
                roots=poly_roots(num)
                indexing=sortperm(roots, by=abs)
                dz=roots[indexing[1]]
                push!(delta_z,dz)
                #poles=sort(poly_roots(den),by=abs)
                #poles=poles[1]
                #println(">>>$i<<<")
                #println("$coeffs")
                #println("residue:$(LinearAlgebra.norm((A-lam[i]*M)*v[:,i])) and $(LinearAlgebra.norm((A'-lam_adj[i]*M')*v_adj[:,i]))")
                #println("poles: $(poles+z) r:$(abs(poles))")
                #backward check(for solving multi-valuedness problems by continuity)
                if z0!=Inf
                    back_lam=polyval(num,z0-z)/polyval(den,z0-z)
                    #println("back:$back_lam")
                    #println("root: $(z+dz)")
                    #estm_lam=polyval(num,dz)/polyval(den,dz)
                    #println("estm. lam: $estm_lam")
                    back_lam=lam0-back_lam
                    push!(back_delta_z,back_lam)
                end
            end
            L.active=[L.eigval]
            if z0!=Inf
                indexing=sortperm(back_delta_z, by=abs)
            else
                indexing=sortperm(delta_z, by=abs)
            end
            lam=lam[indexing[1]]
            L.params[L.auxval]=lam #TODO remove this from the loop body
            z0=z
            lam0=lam
            z=z+relax*delta_z[indexing[1]]
            v0=(1-relax)*v0+relax*v[:,indexing[1]]
            v0_adj=(1-relax)*v0_adj+relax*v_adj[:,indexing[1]]
            n+=1
        end

    catch excp
        if output
            println("Error occured:")
            println(excp)
            println(typeof(excp))
            println("...aborted Householder!")
        end

        flag=-2
        if typeof(excp) <: Arpack.ARPACKException
           flag=-4
           if excp==Arpack.ARPACKException(-9999)
               flag=-9999
           end
       elseif excp== LinearAlgebra.SingularException(0) #This means that the solution is so good that L(z) cannot be LU factorized...TODO: implement other strategy into perturb
           flag=-6
           L.params[L.eigval]=z
        end
    end
    if flag==1
        L.params[L.eigval]=z
        if output;  println(n,"\t\t",abs(lam),"\t",abs(z-z0),"\t", z ); end

        if n>=maxiter
            flag=-1
            if output; println("Warning: Maximum number of iterations has been reached!");end

        elseif abs(lam)<=lam_tol
            flag=1
            if output; println("Solution has converged!"); end
        elseif abs(z-z0)<=tol
            flag=0
            if output; println("Warning: Slow convergence!"); end
        elseif isnan(z)
            flag=-5
            if output; println("Warning: computer arithmetics problem. Eigenvalue is NaN"); end
        else
            if output; println("Warning: This should not be possible....\n If you can read this contact GAM!");end
            flag=-3
            println(z)
        end

        if output
            println("...finished Householder!")
            println("#####################")
            println(" Householder results ")
            println("#####################")
            println("Number of steps: ",n)
            println("Last step parameter variation:",abs(z0-z))
            println("Auxiliary eigenvalue λ residual (rhs):", abs(lam))
            println("Eigenvalue:",z)
            println("Eigenvalue/(2*pi):",z/2/pi)
        end
    end
    L.active=active
    L.mode=mode
    #normalization
    v0/=sqrt(v0'*M*v0)
    v0_adj/=conj(v0_adj'*L(L.params[L.eigval],1)*v0)
    return Solution(L.params,v0,v0_adj,L.eigval), n, flag
end


module NewtonsMethod

greet() = print("Hello! This function computes the Newton's Method; starting value is 0.3.")

using LinearAlgebra, ForwardDiff

# Function using f'
function newtonroot(f, g; iv = 0.3, tol = 1E-13, maxiter = 1000)

    x_old = iv
    normdiff = Inf
    iter = 1

    while normdiff > tol && iter <= maxiter

        # handle negative first derivative
        if !(g(x_old) == 0)
            x_new = x_old - f(x_old) / g(x_old)
            normdiff = norm(x_new - x_old)
            x_old = x_new
            iter = iter + 1
        else
            return (value = nothing, normdiff = nothing, iter = nothing)
        end

    end

    # handle non convergence
    if normdiff <= tol && iter <= maxiter
        return (value = x_old, normdiff = normdiff, iter = iter)
    else
        return (value = nothing, normdiff = nothing, iter = nothing)
    end

end

# Function using ForwardDiff
function newtonroot(f; iv = 0.3, tol = 1E-13, maxiter = 1000)

    x_old = iv
    normdiff = Inf
    iter = 1

    D(f) = x -> ForwardDiff.derivative(f,x)
    g = D(f)

    sol = newtonroot(f, g; iv = iv, tol = tol, maxiter = maxiter)

    return sol
end

export newtonroot

end


#=
  Medical test.

  https://www.math.hmc.edu/funfacts/ffiles/30002.6.shtml
  """
  Suppose that you are worried that you might have a rare disease. You decide to get tested,
  and suppose that the testing methods for this disease are correct 99 percent of the time
  (in other words, if you have the disease, it shows that you do with 99 percent probability,
  and if you don't have the disease, it shows that you do not with 99 percent probability).
  Suppose this disease is actually quite rare, occurring randomly in the general population
  in only one of every 10,000 people.

  If your test results come back positive, what are your chances that you actually have the disease?

  Do you think it is approximately: (a) .99, (b) .90, (c) .10, or (d) .01?

  Surprisingly, the answer is (d), less than 1 percent chance that you have the disease!

  """

  Cf ~/cplint/medical_test.pl
     ~/blog/medical_test.blog
     ~/psi/medical_test.psi
     ~/webppl/medical_test.wppl

=#

# using Memoization
using Turing, StatsPlots, DataFrames
using ReverseDiff, Zygote, Tracker
using Printf
# Turing.setadbackend(:reversediff)
# Turing.setadbackend(:zygote)
# Turing.setadbackend(:tracker)

include("jl_utils.jl")

@model function medical_test(test, disease,prob_of_disease=1/10000.0,reliability=0.99)
    # Suppose this disease is actually quite rare, occurring randomly in the general population
    # in only one of every 10,000 people.
    # prob_of_disease = 1/10000.0
    # prob_of_disease = 1/1000.0

    # Probability that a person has a disease (and there's a reason to do a test)
    disease ~ flip(prob_of_disease)

    # The test is quite accurate: It shows correct result (test is positive if disease) in 99%.
    # However, in 1% of the case it shows incorrect result (positive even if there is no disease).
    # reliability = 0.99    # => disease:0.009803921568627446
    # reliability = 0.999  # A more reliable test => disease:0.09083469721767588
    # reliability = 0.9999 # An even more reliable test => disease:0.500000000000028

    test ~ disease ? flip(reliability) : flip(1-reliability)

end

function run_medical_test()
    for t in [missing,true,false],
        d in [missing,true,false],
        pd in [1/10000.0, 1/1000.0],
        r in [0.99, 0.999,0.9999]
        println("\n\ntest:$t disease:$d prob disease:$pd reliability:$r ")
        # model = medical_test(t, d,pd,r)
        model = medical_test(t, d)

        # chns = sample(model, Prior(), 40_000)
        chns = sample(model, MH(), 100_000)
        # display(chns)
        show_var_dist(chns,:test)
        show_var_dist(chns,:disease)

    end
end

@time run_medical_test()


############### in file hailstone.jl ###############
module Hailstone

function hailstone(n)
    ret = [n]
    while n > 1
        if n & 1 > 0
            n = 3n + 1
        else
            n = Int(n//2)
        end
        append!(ret, n)
    end
    return ret
end

export hailstone

end

if PROGRAM_FILE == "hailstone.jl"
    using Hailstone
    h = hailstone(27)
    n = length(h)
    println("The sequence of hailstone(27) is:\n $h.\nThis sequence is of length $n. It starts with $(h[1:4]) and ends with $(h[n-3:end]).")
end
############ in file moduletest.jl ####################
include("hailstone.jl")
using Hailstone
function countstones(mi, mx)
    lengths2occurences = Dict()
    mostfreq = mi
    maxcount = 1
    for i in mi:mx
        h = hailstone(i)
        n = length(h)
        if haskey(lengths2occurences, n)
            newoccurences = lengths2occurences[n] + 1
            if newoccurences > maxcount
                maxcount = newoccurences
                mostfreq = n
            end
            lengths2occurences[n] = newoccurences
        else
            lengths2occurences[n] = 1
        end
    end
    mostfreq, maxcount
end

nlen, cnt = countstones(1,99999)

print("The most common hailstone sequence length for hailstone(n) for 1 <= n < 100000 is $nlen, which occurs $cnt times.")


#!/usr/bin/env bash
    #=
    exec julia --project="$(realpath $(dirname $(dirname $0)))" --color=yes --startup-file=no -e "include(popfirst!(ARGS))" \
    "${BASH_SOURCE[0]}" "$@"
    =#

#=
Gauss-Jordan elimination over finite fields, modulo n
=#

function swaprows!(A::Matrix{T}, i::Int, j::Int) where T
    for k in axes(A, 2)
        A[i, k], A[j, k] = A[j, k], A[i, k]
    end

    return A
end

function swapcols!(A::Matrix{T}, i::Int, j::Int) where T
    for k in axes(A, 1)
        A[k, i], A[k, j] = A[k, j], A[k, i]
    end

    return A
end

"""
```julia
rref!(A::Matrix{Int}, n::Int; colswap::Bool=false, verbose::Bool=false, vverbose::Bool=false) -> Matrix{Int}
```

Performs Gauss-Jordan Elimination on a matrix `A`.  *This directly changes the matrix A.  Use `rref` for a non-mutating version of this function.*

Parameters:
  - `A::Matrix{Int}`: A matrix of Ints you wish to perform Gauss-Jordan elimiation on.
  - `n::Int`: The modulus of the finite field you are working under.
  - `colswap::Bool` (kwarg): Whether or not you allow for column swapping.
  - `verbose::Bool` (kwarg): Print the row operations.
  - `vverbose::Bool` (kwarg): Print the intermediate matrices of the algorithm.

Returns:
  - `Matrix{Int}`: a matrix in row echelon form.

---

### Examples

```julia
julia> rref!([1 1 0 2 3 1; 2 0 1 3 4 1; 1 2 2 1 4 3], 5, colswap=true) # gauss-jordan elimitation modulo 5 with column swapping
3×6 Array{Int64,2}:
 1  0  0  3  2  2
 0  1  0  2  1  1
 0  0  1  0  0  4
```
"""
function rref!(A::Matrix{Int},
    n::Int;
    colswap::Bool = false,
    verbose::Bool = false,
    vverbose::Bool = false)

    i = j = 1

    while i ≤ size(A, 1) && j ≤ size(A, 2)
        # Rule 1: Swap zero rows if out of order and ensure leading ones cascade down diagonally.
        s = findfirst(!iszero, A[i:end, :])
        isnothing(s) && break
        swaprows!(A, i, i + s[1] - 1)
        if verbose
            println("r$(i) ⟷  r$(i + s[1] - 1)")
            if vverbose
                displaymatrix(A); println()
            end
        end

        # Rule 2: Normalize each row
        s = findfirst(!iszero, A[i,:])
        isnothing(s) && break
        α = invmod(A[i, s], n)
        A[i,:] .= mod.(A[i,:] .* α, n)
        if verbose
            if ! iszero(α)
                isone(α) || println("r$(i) ⟵  r$(i) × $(α)")
                if vverbose && ! isone(α)
                    displaymatrix(A); println()
                end
            end
        end

        # Rule 3: Subtract it from the others
        s = findfirst(!iszero, A[i,:])
        isnothing(s) && break
        for k in axes(A, 1)
            if i ≠ k
                β = A[k, s]
                A[k, :] .= mod.(A[k, :] .- β .* A[i, :], n)
                if verbose
                    if ! iszero(β)
                        if isone(β)
                            println("r$(k) ⟵  r$(k) - r$(i)")
                        else
                            println("r$(k) ⟵  r$(k) - $(β)r$(i)")
                        end
                        if vverbose
                            displaymatrix(A); println()
                        end
                    end
                end
            end
        end

        # Rule 4: Swap columns if needed (and allowed)
        if colswap
            if iszero(A[i, j])
                s = findfirst(!iszero, A[i, :])
                swapcols!(A, j, s)
                if verbose
                    println("c$(j) ⟷  c$(s)")
                    if vverbose
                        displaymatrix(A); println()
                    end
                end
            end
        end

        # increment row and column counter respectively
        i += 1
        j += 1
    end

    if verbose
        println()
    end

    return A
end

"""
```julia
rref(A::Matrix{Int}, n::Int; colswap::Bool=false, verbose::Bool=false, vverbose::Bool=false) -> Matrix{Int}
```

Performs Gauss-Jordan Elimination on a matrix `A`.

Parameters:
  - `A::Matrix{Int}`: A matrix of Ints you wish to perform Gauss-Jordan elimiation on.
  - `n::Int`: The modulus of the finite field you are working under.
  - `colswap::Bool` (kwarg): Whether or not you allow for column swapping.
  - `verbose::Bool` (kwarg): Print the row operations.
  - `vverbose::Bool` (kwarg): Print the intermediate matrices of the algorithm.

Returns:
  - `Matrix{Int}`: a matrix in row echelon form.

---

### Examples

```julia
julia> rref([1 1 0 2 3 1; 2 0 1 3 4 1; 1 2 2 1 4 3], 5, colswap=true) # gauss-jordan elimitation modulo 5 with column swapping
3×6 Array{Int64,2}:
 1  0  0  3  2  2
 0  1  0  2  1  1
 0  0  1  0  0  4
```
"""
rref(A::Matrix{Int},
    n::Int;
    colswap::Bool = false,
    verbose::Bool = false,
    vverbose::Bool = false
    ) = rref!(copy(A), n::Int; colswap = colswap, verbose = verbose, vverbose = vverbose)