function transition(time::Int64, system::Array{Int64,2}, parameters::Dict{String,Any})::Array{Float64,2}

    # initialize -
    T = zeros(2,2)
    A = system[time,1]
    B = system[time,2]

    # get parameters -
    K1 = parameters["K1"]
    n1 = parameters["n1"]
    K2 = parameters["K2"]
    n2 = parameters["n2"]

    # compute -
    p1 = A^n1/(K1^n1+A^n1)
    p2 = B^n2/(K2^n2+B^n2)

    # package -
    T[1,1] = 1 - p1
    T[1,2] = p1
    T[2,1] = p2
    T[2,2] = 1 - p2

    # return -
    return T
end

function machine(time::Int64, state::Int64, system::Array{Int64,2}, parameters::Dict{String,Any})::Int64

    # compute the probabilities -
    T = transition(time, system, parameters)

    # generate a random number -
    r = rand() # uniform from 0 -> 1

    # what are my choice?
    choices = 1.0 .- T[state,:]

    # what is the index of the max?
    idx_max = argmax(choices)
    p = choices[idx_max]

    if (r >= p)
        return argmax(choices)
    else
        return argmin(choices)
    end
end


"""
    LUtanpaP(A)
adalah fungsi yang digunakan untuk memfaktorisasi matriks `A` sembarang menjadi
dua matriks, yaitu matriks segitiga atas `U` dan bawah `L` sedemikian sehingga `LU=A`

# Examples
```jl
julia> A = [ 2 5 4 2 3
            4 1 0.4 6 5
            0.9 4 8 7 10
            5 7 0.5 2 7
            2 6 8 9 4];

julia> L,U = LUtanpaP(A);

julia> L
5×5 Array{Float64,2}:
 1.0    0.0        0.0       0.0      0.0
 2.0    1.0        0.0       0.0      0.0
 0.45  -0.194444   1.0       0.0      0.0
 2.5    0.611111  -1.02824   1.0      0.0
 1.0   -0.111111   0.668235  1.17806  1.0

julia> U
5×5 Array{Float64,2}:
 2.0   5.0   4.0      2.0        3.0
 0.0  -9.0  -7.6      2.0       -1.0
 0.0   0.0   4.72222  6.48889    8.45556
 0.0   0.0   0.0      2.44988    8.80541
 0.0   0.0   0.0      0.0      -15.1347
```
return matriks `L` dan `U`.
"""
function LUtanpaP(A);
    # Definisikan matriks L sebagai pencatat pengali.
    n,n = size(A);
    L = zeros(n,n);
    # Lakukan operasi baris dasar terhadap matriks
    Aug = copy(A);
    for p = 1:n-1
        # Jika pivot bernilai nol, maka gagal.
        if Aug[p,p]==0
            error("Pivot bernilai nol");
        end
        # Lakukan eliminasi lalu simpan pengali pada matriks L.
        for i = p+1:n
            k = Aug[i,p]/Aug[p,p];
            Aug[i,1:n] = Aug[i,1:n] - k*Aug[p,1:n];
            L[i,p] = k;
        end
    end
    U = Aug;
    L = L .+ I(n);
    return L, U
end


# compute primes up to max using the Sieve algorithm
# https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes
# https://docs.julialang.org/en/v1/

import Dates

function print_usage()
  println(string("julia ", PROGRAM_FILE, " max_prime"))
end

function make_array(max)
  return collect(1:2:max)
end

function remove_multiples(numbers, prime_index, max)

  prime::Int64 = numbers[prime_index]
  array_length::Int64 = length(numbers)

  # println("called remove_multiple(", prime_index, ") = ", prime, ", length: ", array_length)

  # the for loop will start at one multiple past the current prime
  # it will walk the list of numbers by multiples of prime
  # this logic removes the need for any if statements inside the loop
  @inbounds for current_index::Int64 in prime_index+prime : prime : array_length
    numbers[current_index] = 0
  end
end

function compute_primes(max::Int64)
  start_time = Dates.now()

  numbers = make_array(max)

  # println(numbers)
  # println("Length: ", length(numbers))

  println("Computing primes from 0 to ", max)

  half_length::Int64 = Int64(length(numbers)/2)

  @inbounds for i = 2:half_length
    if numbers[i] > 0
      remove_multiples(numbers, i, max)
    end
  end

  primes = filter(x -> x>0, numbers)

  mid_time = Dates.now()

  # println("Primes: ", primes)

  finish_time = Dates.now()

  println("Start: ", start_time, ", Mid: ", mid_time, ", Finish: ", finish_time)
  println("Compute time: ", (mid_time - start_time))
  println("Found: ",  length(primes), " prime numbers under: ", max)
end

function main()
  if length(ARGS) < 1
    print_usage()
  else
    compute_primes(parse(Int64, ARGS[1]))
  end

end

main()


import StaticArrays: SVector, @SVector

function distsq(A::SVector{2, T}, B::SVector{2, T}) where T <: Real
    return (A[1]-B[1])^2 + (A[2]-B[2])^2
end

function grid_idxs(point:: SVector{2, T}, cellsize::T) where T <: Real
    """ Returns cell indices, (i,j)=(row, col) for  `point` """
    return ceil(Int, point[2] / cellsize) + 1, ceil(Int, point[1] / cellsize) + 1
end

function ispoint_inrectangle(point::SVector{2, T}, rlims::Tuple{T, T}) where T <: Real
    """
    Check whether a point is within the rectangle
    [0, rlims[1]] x [0, rlims[2]];
    here rlims =(width, height)
    """
    return (0 <= point[1] <= rlims[1]) && (0 <= point[2] <= rlims[2])
end

function unif_sampling_annulus(center::SVector{2, T}, inner_rad::T, outer_rad::T;
                               k=30) where T <: Real
    """
    Samples  uniformly k points from the annulus of center, `center`,
    and radii, inner_radius, outer_radius
    """
    rad = sqrt.(inner_rad^2  .+ (outer_rad^2 - inner_rad^2)*rand(k))
    angle = 2*pi * rand(k)
    x, y = center[1] .+ rad .* cos.(angle), center[2] .+ rad .*sin.(angle)
    return [@SVector([xi, yi]) for (xi, yi) in zip(x,y)]
end

function Poisson_disk_sampling(;width =1.0, height=1.0, radius=0.05, k=30)
    """
    Bridson Algorithm to generate  Poisson disk samples
    within a 2D rectangle, [0, width] x [0, height]
    Ref: R Bridson, Fast Poisson disk sampling in arbitrary dimensions,
    in  SIGGRAPH '07: ACM SIGGRAPH 2007 sketches
    https://www.cs.ubc.ca/~rbridson/docs/bridson-siggraph07-poissondisk.pdf
    """
    function  is_accepted(point:: SVector{2, T})  where T <: Real
        """ Check whether the point, `point` can be added to the active_list"""

        i, j = grid_idxs(point, cellsize)
        m, n = maximum([i-2, 1]), maximum([j-2, 1])
        p, q = minimum([i+2, grid_rows]), minimum([j+2, grid_cols])
        for k in n:q
            for l in m:p
                gpoint = grid[(k-1) * grows + l]
                if ismissing(gpoint)
                    continue
                end
                if distsq(point, gpoint) <= r2
                    return false
                end
            end
        end
        return true
    end

    cellsize = radius/sqrt(2)
    grid_cols = ceil(Int, width / cellsize) + 1
    grid_rows = ceil(Int, height / cellsize) + 1

    grid = Vector{Any}(missing, grid_rows*grid_cols)
    r2 = radius*radius
    active_list = SVector{2, Float64}[]

    #generate a random point within the rectangle of interest
    # and  insert it into the active_list and grid
    point = @SVector([width*rand(1)[1], height*rand(1)[1]])
    push!(active_list, point)
    i, j = grid_idxs(point, cellsize)
    grid[(j-1)*grid_rows+i] = point

    while length(active_list) > 0
        i = rand(1:length(active_list), 1)[1]
        pt = active_list[i]
        active_list  =  active_list[1:end .!= i] #removes the point in pos i
        rpoints = unif_sampling_annulus(pt, radius, 2*radius; k=k)
        for rp in rpoints
            if !ispoint_inrectangle(rp, (width, height)) || !is_accepted(rp)
                continue
            end
            push!(active_list, rp)
            i, j = grid_idxs(rp, cellsize)
            grid[(j-1)*grid_rows+i] = rp
        end
    end
    return filter(x->!ismissing(x), grid);
end


#!/usr/bin/env julia
#
# Jerzy Wroczyński (nr. indeksu 250075)
# Obliczenia Naukowe, Laboratorium
# Lista 2, Zadanie 3.
#

# imports
using LinearAlgebra
include("hilb.jl")
include("matcond.jl")
using Printf

# solve Ax = b using two methods
# in this particular case x is just a column of ones, but we’re trying
# to recalculate it to see how bad is the given matrix in terms of its
# condition number `cond(A)`
function solve(A)
    # prepare
    x = ones(size(A)[1])
    b = A * x
    # calc using two methods
    gauss = A \ b
    inverted = inv(A) * b

    return gauss, inverted
end

# this function calculates the relative error between the original
# and the recalculated value
function calc_relative_error(x, x̃)
    return (norm(x - x̃)/norm(x))
end

# scientific notation formatting function
function scn(n)
    return @sprintf("%.0e", n)
    # return string(n)
end

# generates and print out the results
function printout(A, n, c, latex_table=false)

    gauss, inverted = solve(A)

    if !latex_table
        print(
            "n = ", n, "\n",
            (c != 0 ? string("c = ", scn(c), "\n") : ""),
            "  cond(A)                   = ", cond(A), "\n",
            "  rank(A)                   = ", rank(A), "\n",
            "  relative error (gauss)    = ", calc_relative_error(ones(n), gauss), "\n",
            "  relative error (inverted) = ", calc_relative_error(ones(n), inverted), "\n\n"
        )
    else
        println(
            n, " & ",
            (c != 0 ? string(scn(c), " & ") : ""),
            cond(A), " & ",
            rank(A), " & ",
            calc_relative_error(ones(n), gauss), " & ",
            calc_relative_error(ones(n), inverted), "\\\\"
        )
    end
end

# 1. Hilbert’s matrix Hₙ for n > 1
for n in 2:24

    A = hilb(n)
    printout(A, n, 0, false)

end

# 2. Random matrix Rₙ for n = 5,10,20 and c = 1, 10, 10³, 10⁷, 10^12, 10^16
for n in [5,10,20]

    for c in [1, 10, 10^3, 10^7, 10^12, 10^16]

        A = matcond(n, Float64(c))
        printout(A, n, c, false)

    end

end


"""
    basis(elem::Line,N,r)

Computes the generalized Vandermonde matrix V of degree N (along with the
derivative matrix Vr) at points r.
"""
function basis(elem::Line, N, r)
    V1D, Vr1D = ntuple(x->zeros(length(r), N+1),2)
    for j = 1:N+1
        V1D[:,j] .= jacobiP(r[:], 0, 0, j-1)
        Vr1D[:,j] .= grad_jacobiP(r[:], 0, 0, j-1)
    end
    return V1D, Vr1D
end

"""
    nodes(elem::Line,N)

Computes interpolation nodes of degree N.
"""
nodes(elem::Line, N) = first(gauss_lobatto_quad(0, 0, N))

"""
    equi_nodes(elem::Line,N)

Computes equally spaced nodes of degree N.
"""
equi_nodes(elem::Line, N) = collect(LinRange(-1, 1, N+1))

"""
    quad_nodes(elem::Line,N)

Computes (N+1)-point Gauss quadrature rule (exact for degree 2N+1 polynomials)

"""
quad_nodes(elem::Line, N) = gauss_quad(0, 0, N)


"""
    Polynomials functions for spline interpolation: basis functions for b-splines
    and bernstein polynomials for bezier curves.

    Most polynomials have two implementations: one in which the polynomial is
    computed over the entier parameter range, the other for evaluating the
    polynomial at a single parameter value.
"""
module Polynomials

    # ---------------------------------------------------------------------------- #
    #                           B-Spline BASIS FUNCTIONS                           #
    # ---------------------------------------------------------------------------- #
    """
        N_0(τ, k; i=0)

    Zero-th order basis function for b-splines for the i-th knot.
        for t∈[k[i], k[i+1]] = 1
        else: 0

    The additional "+1" index in the code is because in the bspline maths
    the knots are indexed starting from 0.
    """
    function N_0(τ; k::AbstractArray, i::Int=0)
        if i+2 < length(k)
            return k[i+1] .<= τ .< k[i+2]
        else
            return k[i+1] .<= τ .<= k[i+2]
        end
    end

    """
        ω(τ, j)

    Function used within N_D to compute the factors for the linear interpolation
    of the two lover level basis functions
    """
    function ω(τ, j, d, k)
        Δ = k[j+d]-k[j]
        @. Δ!=0 ? (τ - k[j])/Δ : zero(τ)
    end

    """
        N_D(τ, k; i=0, d=0)

    D-th order basis function for b-splines for the i-th knot.
    Built recursively on lower order basis functions.
    See: https://www.geometrictools.com/Documentation/BSplineCurveLeastSquaresFit.pdf
    and: https://pages.mtu.edu/~shene/COURSES/cs3621/NOTES/spline/B-spline/bspline-basis.html

    The "j=i+1" index in the code is because in the bspline maths
    the knots are indexed starting from 0.
    """
    function N_D(τ; k::AbstractArray, i::Int=0, d::Int=0)
        ω(τ, i+1, d, k) .* N_basis(τ; k=k, i=i, d=d-1) .+ (1 .- ω(τ, i+1+1, d, k)) .* N_basis(τ; k=k, i=i+1, d=d-1)
    end

    """
        N(τ, k; i=0, d=0)

    B-spline basis function for the i-th knot and d-th order.
    Calls either N_0 or N_D depending on the value of d.
    """
    N_basis(τ; k::AbstractArray, i::Int=0, d::Int=0) = (d == 0) ? N_0(τ; k, i=i) : N_D(τ; k, i=i, d=d)

    # ---------------------------------------------------------------------------- #
    #                             BERNSTEIN POLYNOMIALS                            #
    # ---------------------------------------------------------------------------- #
    """
        bernstein(t::Float64; i::Int, n::Int)

    Evaluate the bernstein polynomial at parameter value `t` given the index `i` and the number
    of polynomials `n`.
    """
    bernstein(τ; i::Int, n::Int) = @. binomial(n, i) * τ^i * (1 - τ)^(n-i)

end


module Gong

using Statistics, Dates

#export gc_distance, rad2deg, deg2rad, histc, meshgrid, nan, findNaNmin, findNaNmax

function gc_distance(lat1deg::Float64,lon1deg::Float64,lat2deg::Float64,lon2deg::Float64)
    # This code implements Vincenty 1975: https://www.ngs.noaa.gov/PUBS_LIB/inverse.pdf
    # In the future, need to implement: Karney 2013 https://arxiv.org/abs/1109.4448

    a = 6378.1370; # Earth semi major axis in km
    b = 6356.7523142; # Earth semi-minor axis in km
    R1 = 1/3 * (2*a + b);

    f = (a - b) / a; # flattening parameter for an elipsoid
    #f = 1/298.257223563; # defined by WGS84

    lat1 = deg2rad(lat1deg);
    lon1 = deg2rad(lon1deg);
    lat2 = deg2rad(lat2deg);
    lon2 = deg2rad(lon2deg);

    beta1 = atan((1-f)*tan(lat1)); # reduced latitude 1
    beta2 = atan((1-f)*tan(lat2)); # reduced latitude 2
    P = (beta1 + beta2)/2;
    Q = (beta2 - beta1)/2;

    lambda = abs(lon1 - lon2);
    sigma = atan(sqrt((cos(beta2)*sin(lambda))^2 + (cos(beta1)*sin(beta2) - sin(beta1)*cos(beta2)*cos(lambda))^2) / (sin(beta1)*sin(beta2) + cos(beta1)*cos(beta2)*cos(lambda)));

    alpha = asin(cos(beta1) * cos(beta2) * sin(lambda)/sin(sigma));
    sigmam = acos(cos(sigma) - 2*sin(beta1)*sin(beta2) / cos(alpha)^2)/2;

    X = (sigma - sin(sigma)) * (sin(P)^2 * cos(Q)^2) / (cos(sigma/2)^2)
    Y = (sigma + sin(sigma)) * (cos(P)^2 * sin(Q)^2) / (sin(sigma/2)^2)

    dist = a*(sigma - f/2 * (X+Y));
    return dist
end

function rad2deg(rad)
    return rad * 180.0/pi;
end

function deg2rad(deg)
    return deg * pi/180.0;
end

# define a histogram index function similar to matlab histc
function histc(x, xnodes)
    bin = Array{Number}(undef,length(x));
    for i = 1:length(x)
        a = findlast(x[i] .>= xnodes);
        if !isnothing(a)
            bin[i] = a;
        end
    end
    return bin;
end

# emulate the behavior of Matlab's meshgrid
function meshgrid(xgrid::Array{<:AbstractFloat,1},ygrid::Array{<:AbstractFloat,1})
    nx = length(xgrid);
    ny = length(ygrid);
    minx = minimum(xgrid);
    maxx = maximum(xgrid);
    miny = minimum(ygrid);
    maxy = maximum(ygrid);
    dx = (maxx .- minx) ./ (nx-1);
    dy = (maxy .- miny) ./ (ny-1);
    i = [i for j in miny:dy:maxy, i in minx:dx:maxx];
    j = [j for j in miny:dy:maxy, i in minx:dx:maxx];
    return (i,j)
end

function meshgrid(xgrid::Array{<:Integer,1},ygrid::Array{<:Integer,1})
    minx = minimum(xgrid);
    maxx = maximum(xgrid);
    miny = minimum(ygrid);
    maxy = maximum(ygrid);
    i = [i for j in miny:maxy, i in minx:maxx];
    j = [j for j in miny:maxy, i in minx:maxx];
    return (i,j)
end

function meshgrid(xgrid::UnitRange{<:Integer},ygrid::UnitRange{<:Integer})
    #minx = minimum(xgrid);
    #maxx = maximum(xgrid);
    #miny = minimum(ygrid);
    #maxy = maximum(ygrid);
    i = [i for j in ygrid[1]:ygrid[end], i in xgrid[1]:xgrid[end]];
    j = [j for j in ygrid[1]:ygrid[end], i in xgrid[1]:xgrid[end]];
    return (i,j)
end

# emulate the behavior of Matlab's nan function
function nan(m::Unsigned,n::Unsigned)
    nanarray=Array{Float64,2}(undef,m,n);
    nanarray .= NaN;
end

# adopted from https://github.com/mlubin/NaNMath.jl/issues/31
function findNaNmax(x::Array{<:AbstractFloat,1})
	result = convert(eltype(x), NaN)
    indmax = 0
    @inbounds @simd for i in eachindex(x)
    	v = x[i]
        if !isnan(v)
            if (isnan(result) || v > result)
                result = v
                indmax = i
            end
        end
    end
    return (result,indmax) # note the order of result and ind may be different that what's proposed by floswald as of 2019-12-03
end

# adopted from https://github.com/mlubin/NaNMath.jl/issues/31
function findNaNmin(x::Array{<:AbstractFloat,1})
	result = convert(eltype(x), NaN)
    indmin = 0
    @inbounds @simd for i in eachindex(x)
    	v = x[i]
        if !isnan(v)
            if (isnan(result) || v < result)
                result = v
                indmin = i
            end
        end
    end
    return (result,indmin) # note the order of result and ind may be different that what's proposed by floswald as of 2019-12-03
end

function dg_pol2cart(magnitude::AbstractFloat, compassdir::AbstractFloat)
    theta = mod.(360.0 .- compassdir .+ 90.0, 360) .* pi/180.0;
    u = magnitude .* cos.(theta);
    v = magnitude .* sin.(theta);
    return u + v*im
end

# this does not work, left in for reference. can you 'repeat' function with splat or Tuple() to reproduce Matlab's repmat behavior
#function repmat(x, shape)
#    xsize = size(x);
#    xout = reshape(repeat(x, outer=prod(shape)),[collect(size(x));collect(shape)]...);
#    return xout;
#end

# linearly scale between xmin and xmax using the range provided by xscl
function minmaxscaler(x, xscl; zeromin::Bool=false)
    if zeromin == false
        xmin = minimum(skipmissing(x));
    else
        xmin = 0.0;
    end

    xmax = maximum(skipmissing(x));
    if xmin != xmax
        x_std = (x .- xmin) ./ (xmax - xmin);
        x_scl = x_std .* (maximum(xscl) - minimum(xscl)) .+ minimum(xscl);
    else
        x_scl = x;
    end
    return x_scl, xmin, xmax;
end

# linearly scale between xmin and xmax using the range provided in xscl
function minmaxscaler(x, xscl, xminmax)
    xmin = minimum(skipmissing(xminmax));
    xmax = maximum(skipmissing(xminmax));
    if minimum(skipmissing(x)) != maximum(skipmissing(x))
        x_std = (x .- xmin) ./ (xmax - xmin);
        x_scl = x_std .* (maximum(xscl) - minimum(xscl)) .+ minimum(xscl);
    else
        x_scl = x;
    end
    return x_scl, xmin, xmax;
end

# find standard deviation of a variable, then scale to +/- n std. dev.
function varscaler(varin,nstd=2.0)
    varstd = std(skipmissing(varin));
    varmean = mean(skipmissing(varin));
    varscl = (varin .- varmean) ./ (nstd * varstd);
    return varscl, varmean, varstd, nstd;
end

# manually scale the input variable using specified mean and standard deviation
function varscaler_manual(varin, varmean, varstd, nstd=2.0)
    return (varin .- varmean) ./ (nstd * varstd);
end

# use specified mean and standard deviation to 'unscale' the input variable
function varunscaler(varscl, varmean, varstd, nstd)
    return varscl .* (nstd * varstd) .+ varmean;
end

# this function returns the indices for stock within a specified time period
function findtind(tt,trange)
    return findall(minimum(trange) .<= tt .<= maximum(trange));
end

end


using LinearAlgebra
function powermethod(A;maxiter=100000, tol=1e-6)
  x = normalize!(randn(size(A,1)))
  y = A*x
  λ = x'*y
  for i=1:maxiter
    if norm(y - λ*x)/(abs(λ)+1) <= tol
      break
    end
    x = normalize!(y)
    y = A*x
    λ = x'*y
  end
  if norm(y - λ*x)/(abs(λ)+1) >= tol
    @warn("powermethod did not converge, relative residual is ",
      norm(y - λ*x)/(abs(lam)+1))
  end
  return x, λ
end
##
using Random
Random.seed!(2)
A = randn(3,3)
v, lam = powermethod(A)
##
Lams, V = eigen(A)
##
@show lam
@show Lams[1]
##
function powermethod_explain(A,V;maxiter=9)
  x = normalize!(randn(size(A,1)))
  y = A*x
  for i=1:9
    x = normalize!(y)
    y = A*x
    println(i, ", ", V'*x)
  end
end
using Random
Random.seed!(2)
A = randn(3,3)
A = A+A' # make it symmetric
Lams, V = eigen(A)
powermethod_explain(A,V)
## example on population growth matrix
G = [1.0215 0.0127; 0.0627 1.0667 ]
v, lam = powermethod(G)
@show  g = v / sum(v), lam


#=
Problem 4
2520 is the smallest number that can be divided by each of the numbers from 1 to 10 without any remainder.
What is the smallest positive number that is evenly divisible by all of the numbers from 1 to 20?
=#

import Primes.factor

function mergeLargest(a::Dict{Int64, Int64}, b::Dict{Int64, Int64})
  for k = keys(b)
    b_k = b[k]
    a_v = get!(a, k, b_k) # Get value or store
    if (a_v < b_k)
      a[k] = b_k
    end
  end
end

p = Dict{Int64, Int64}()
for i = 1:20
  f = factor(i)
  mergeLargest(p, f)
end

@show p

smallest = 1
for k = keys(p)
  smallest *= k ^ p[k]
  println("key $(k), val $(p[k])")
end

# check
for i = 1:20
  if ( smallest % i > 0)
    println("Ooops - not divisible by $(i)")
  end
end

println("The smallest divisible number is: $(smallest)")


### A Pluto.jl notebook ###
# v0.15.1

using Markdown
using InteractiveUtils

# ╔═╡ 463264a9-0f9b-43ee-930f-74730e158372
# we write a function that caculates the product of two matrices A and B
function mult(A,B)

	# we find the sizes of A and B
	aRows, aCols = size(A)
	bRows, bCols = size(B)

	# we check if the matrix sizes are consistent with multiplication
	if aCols != bRows
		error("matrix sizes don't agree")
	end

	# we initialize C to a zero matrix of the appropriate size
	C = zeros(aRows, bCols)

	# we loop over all pairs of rows and columns
	for i = 1:aRows
		for j = 1:bCols
			# and for each pair we store their dot pruduct in C
			for k = 1:aCols
				C[i,j] += A[i,k] * B[k,j];
			end
		end
	end

	# we return C as the answer
	return C
end

# ╔═╡ c089bff8-8b2b-420e-95d4-f40026afb897
md"Simple test"

# ╔═╡ 5d274440-f3b4-4b98-953c-340ca2fb8b8e
A = [1 2; 3 4];

# ╔═╡ a8dc36db-ffc4-4068-af75-890dd3a98819
B = [2 1; 1 3];

# ╔═╡ ab386751-af17-41f9-b032-3735fd3492f4
mult(A, B)

# ╔═╡ 1073d7c1-cd7f-41eb-9531-b7a63c2d6286
md"Check with MATLAB built-in multiplication"

# ╔═╡ eb028ed8-2ff5-4c97-8b98-156f45402c7c
A*B

# ╔═╡ 507e8447-6446-44b3-a230-940254bedb6b
md"Two $100\times 100$ matrices of random numbers"

# ╔═╡ 413585fe-dbe5-4351-88b0-57cb415c31e5
time100 = @elapsed mult(rand(100,100), rand(100,100))

# ╔═╡ f014193b-04d6-430f-83c1-2bee5864ea99
md"Two $200\times 200$ matrices of random numbers"

# ╔═╡ 0faabc1c-a618-4351-9876-9fce4ceab769
time200 = @elapsed mult(rand(200,200), rand(200,200))

# ╔═╡ 31917d5e-28cf-4caf-80a4-50e630094901
md"Two $1000\times 1000$ matrices of random numbers"

# ╔═╡ 7c242e1a-c41c-4e1b-b24b-7343417fbfdd
time1000 = @elapsed mult(rand(1000,1000), rand(1000,1000))

# ╔═╡ 695be13b-3dd7-4943-a339-17872c1d128e
md"""
Now compute the ratio of the times taken for the two different sizes
and compare with the theroetical complexity $O(n^3)$, where $n$ is the size of the matrices. Note the prediction does not match exactly because it is valid only as $n$ tends to infinity, but it is a reasonable guide.
"""

# ╔═╡ 99bdb4c4-4af0-410b-a3f8-3e3e8e13d80f
ratio = time1000/time200

# ╔═╡ 25ae1cca-d886-46ca-9e57-7abd83c7ed86
predictedRatio = (1000/200)^3

# ╔═╡ 23559250-2979-40e1-87b1-f48b7eeee335
md"""
The last part shows that MATLAB is rather efficient at dealing with matrices...
"""

# ╔═╡ 426e472d-c146-41de-bf1f-bbe635a4f8cd
time = @elapsed rand(1000,1000)*rand(1000,1000)

# ╔═╡ Cell order:
# ╠═463264a9-0f9b-43ee-930f-74730e158372
# ╟─c089bff8-8b2b-420e-95d4-f40026afb897
# ╠═5d274440-f3b4-4b98-953c-340ca2fb8b8e
# ╠═a8dc36db-ffc4-4068-af75-890dd3a98819
# ╠═ab386751-af17-41f9-b032-3735fd3492f4
# ╟─1073d7c1-cd7f-41eb-9531-b7a63c2d6286
# ╠═eb028ed8-2ff5-4c97-8b98-156f45402c7c
# ╟─507e8447-6446-44b3-a230-940254bedb6b
# ╠═413585fe-dbe5-4351-88b0-57cb415c31e5
# ╟─f014193b-04d6-430f-83c1-2bee5864ea99
# ╠═0faabc1c-a618-4351-9876-9fce4ceab769
# ╟─31917d5e-28cf-4caf-80a4-50e630094901
# ╠═7c242e1a-c41c-4e1b-b24b-7343417fbfdd
# ╟─695be13b-3dd7-4943-a339-17872c1d128e
# ╠═99bdb4c4-4af0-410b-a3f8-3e3e8e13d80f
# ╠═25ae1cca-d886-46ca-9e57-7abd83c7ed86
# ╟─23559250-2979-40e1-87b1-f48b7eeee335
# ╠═426e472d-c146-41de-bf1f-bbe635a4f8cd


# # Exact Optimization with Rational Arithmetic

# This example can be found in section 4.3 [in the paper](https://arxiv.org/pdf/2104.06675.pdf).
# The package allows for exact optimization with rational arithmetic. For this, it suffices to set up the LMO
# to be rational and choose an appropriate step-size rule as detailed below. For the LMOs included in the
# package, this simply means initializing the radius with a rational-compatible element type, e.g., `1`, rather
# than a floating-point number, e.g., `1.0`. Given that numerators and denominators can become quite large in
# rational arithmetic, it is strongly advised to base the used rationals on extended-precision integer types such
# as `BigInt`, i.e., we use `Rational{BigInt}`.

# The second requirement ensuring that the computation runs in rational arithmetic is
# a rational-compatible step-size rule. The most basic step-size rule compatible with rational optimization is
# the agnostic step-size rule with ``\gamma_t = 2/(2 + t)``. With this step-size rule, the gradient does not even need to
# be rational as long as the atom computed by the LMO is of a rational type. Assuming these requirements are
# met, all iterates and the computed solution will then be rational.

using FrankWolfe
using LinearAlgebra

n = 100
k = n

x = fill(big(1)//100, n)

f(x) = dot(x, x)
function grad!(storage, x)
    @. storage = 2 * x
end

# pick feasible region
# radius needs to be integer or rational
lmo = FrankWolfe.ProbabilitySimplexOracle{Rational{BigInt}}(1)

# compute some initial vertex
x0 = FrankWolfe.compute_extreme_point(lmo, zeros(n));

x, v, primal, dual_gap, trajectory = FrankWolfe.frank_wolfe(
    f,
    grad!,
    lmo,
    x0,
    max_iteration=k,
    line_search=FrankWolfe.Agnostic(),
    print_iter=k / 10,
    verbose=true,
    emphasis=FrankWolfe.blas,
);

println("\nOutput type of solution: ", eltype(x))

# Another possible step-size rule is `rationalshortstep` which computes the step size by minimizing the
# smoothness inequality as ``\gamma_t=\frac{\langle \nabla f(x_t),x_t-v_t\rangle}{2L||x_t-v_t||^2}``. However, as this step size depends on an upper bound on the
# Lipschitz constant ``L`` as well as the inner product with the gradient ``\nabla f(x_t)``, both have to be of a rational type.

@time x, v, primal, dual_gap, trajectory = FrankWolfe.frank_wolfe(
    f,
    grad!,
    lmo,
    x0,
    max_iteration=k,
    line_search=FrankWolfe.RationalShortstep(),
    L=2,
    print_iter=k / 10,
    verbose=true,
    emphasis=FrankWolfe.blas,
);

# Note: at the last step, we exactly close the gap, finding the solution 1//n * ones(n)


import Pkg
Pkg.activate("RbfInterpolationTools")
using RbfInterpolationTools
using Plots

# Lets generate some observed points
X = hcat(rand(30) .* 6.28)
# We will try to interpolate the sin function
y = vec(map(x -> sin(x), X))

# Lets create our interpolation environment
inter = RbfInterpolationTools.Interpolator(X = X, y = (y))
# Lets regenerate our interpolation domain
X_new = hcat(0:6.28/(999):6.28)

# And we simply get our answer:
y_new = RbfInterpolationTools.interpolateMesh(inter, X_new)

# Lets plot the results
plot()
Plots.scatter!(X, y, label = "Shown points")
Plots.plot!(X_new, y_new, label = "Interpolation")

# The derivative shall be the cosine function. Lets verify it:

∂y = vec(map(x -> RbfInterpolationTools.gradientPoint(inter, [x])[1], X_new))
Plots.plot!(X_new, ∂y, label = "Interpolated Derivative")

# We can actually compare it with the real values
Plots.plot!(X_new, vec(map(x -> cos(x), X_new)), label = "Real Derivative")


using Printf
using LinearAlgebra
using DelimitedFiles
using Quaternions

#EXERCISE MADE BY ERIK MARTÍN GARZÓN & DAVID BOCES OBIS
#-------------------------------------------------------------------- EXERCISE 3.1
angle_input = 90 #degrees
axis_input = [0;1;2] #axis z
global axis_norm

function axis_angle_to_mat(axis, angle)
    #first, we normalize the axis
    modul = sqrt(axis[1]*axis[1] + axis[2]*axis[2] + axis[3]*axis[3])

    global axis_norm = axis/modul

    Z = [0 -axis_norm[3] axis_norm[2];
         axis_norm[3] 0 -axis_norm[1];
        -axis_norm[2] axis_norm[1] 0]

    #formula Rodrigues
    R = I + sind(angle)*Z + (1-cosd(angle))*(Z^2)
    #just above, we multiply pi/180 to the angle (in degrees) to obtain the angle in radians
    #so julia can do the operation we want

    #next, we will check if R is a rotation matrix: R*R'=I and det(R)=1
    #For this comprobation, we needed to round R*R' and det(R). That's because much times
    #the result was 0.999999 and not 1
    if (round.(R*R')== I && round(det(R))==1)
        #we return the rotation matrix
        return R
    else
        print("R = ", R, "\nis NOT a rotation matrix\n")
        println("R*R'= ", round.(R*R'))
        println("det(R)= ", round(det(R)))
    end
end

mat = axis_angle_to_mat(axis_input, angle_input)

println("The rotation matrix composed of AXIS = ", axis,
 " normalized as ", axis_norm, " and ANGLE = ", angle,
  " degrees is R = ", mat)

function mat_to_axis_angle(mat)
    #first, we find the angle:
    angle = acosd((tr(mat)-1)/2)

    #then, we find the axis:
    V = (mat-mat')/(2*sind(angle))
    v = [V[3,2]; V[1,3]; V[2,1]]

    #we return the angle in degrees
    return angle, v
end

println()
println("The rotation matrix R = ", mat,
" has the following ANGLE (in degrees) and unit AXIS: \n", mat_to_axis_angle(mat))

q = quat(1,0,0,0)

global q2

function axis_angle_to_quat(axis, angle)
    global q2
    #first, we normalize the axis
    modul = sqrt(axis[1]*axis[1] + axis[2]*axis[2] + axis[3]*axis[3])

    global axis_norm = axis/modul

    q2 = quat(cosd(angle/2), sind(angle/2)*axis_norm[1], sind(angle/2)*axis_norm[2], sind(angle/2)*axis_norm[3])

    return q2
end

println()
println("The Quaternion composed of AXIS = ", axis,
 " normalized as ", axis_norm, " and ANGLE = ", angle,
  " degrees is q = ", axis_angle_to_quat(axis_norm, angle_input))

function quat_to_axis_angle(q3)
    angle3 = 2*acosd(q3.s);
    axis_norm[1] = q3.v1/sind(angle3/2)
    axis_norm[2] = q3.v2/sind(angle3/2)
    axis_norm[3] = q3.v3/sind(angle3/2)

    return angle, axis_norm
end

println()
println("The Quaternion q = ", q2,
" has the following ANGLE (in degrees) and unit AXIS: \n", quat_to_axis_angle(q2))

global matQuat

function quat_to_mat(q)
    global matQuat = [((q.s)^2 + (q.v1)^2 - (q.v2)^2 - (q.v3)^2) (2*(q.v1)*(q.v2) - 2*(q.s)*(q.v3)) (2*(q.v1)*(q.v3) + 2*(q.s)*(q.v2));
            (2*(q.v1)*(q.v2) + 2*(q.s)*(q.v3)) ((q.s)^2 - (q.v1)^2 + (q.v2)^2 - (q.v3)^2) (2*(q.v2)*(q.v3) - 2*(q.s)*(q.v1));
            (2*(q.v1)*(q.v3) - 2*(q.s)*(q.v2)) (2*(q.v2)*(q.v3) + 2*(q.s)*(q.v1)) ((q.s)^2 - (q.v1)^2 - (q.v2)^2 + (q.v3)^2)]

    return matQuat
end

println()
println("The Quaternion q = ", q, " as a rotation matrix R = ", quat_to_mat(q))

quat_to_mat(q)

function mat_to_quat(mat)
    #first, we find the angle:
    angle2 = acosd((tr(mat)-1)/2)

    if (angle2 != 0)
        #then, we find the axis:
        V = (mat-mat')/(2*sind(angle2))
        v = [V[3,2]; V[1,3]; V[2,1]]

        q3 = quat(cosd(angle2/2), sind(angle2/2)*v[1], sind(angle2/2)*v[2], sind(angle2/2)*v[3])

        return q3
    else
        return "THIS ROTATION MATRIX [IDENTITY MATRIX] HAS NO AXIS. ITS ROTATION ANGLE IS 0 + 360*K [BEING K AN ENTER]. FOR THE RESULT THIS MATRIX HAS NO QUATERNION FORM."
    end

end

println()
println("The rotation matrix R = ", matQuat,
" has the following Quaternion q: \n", mat_to_quat(matQuat))


### A Pluto.jl notebook ###
# v0.12.10

using Markdown
using InteractiveUtils

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

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

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

# ╔═╡ fa68607e-22e5-11eb-0558-c9a4d9f77426
begin
	mu1, sigma1 = 10, 5
	mu2, sigma2 = 40, 12
	dist1, dist2 = Normal(mu1,sigma1), Normal(mu2,sigma2)
	p = 0.3
	mixRv() = (rand() <= p) ? rand(dist1) : rand(dist2)

	n = 2000
	data = [mixRv() for _ in 1:n]
end

# ╔═╡ cd520f9c-2509-11eb-36ce-8340182a95eb
begin
	density(data, c=:blue, label="Density via StatsPlots",
			xlims=(-20,80), ylims=(0,0.035))
	stephist!(data, bins=50, c=:black, norm=true,
		label="Histogram", xlabel="x", ylabel = "Density")
end

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

# ╔═╡ Cell order:
# ╟─ca5fbdc8-22e5-11eb-0a60-cb0a127a3ca5
# ╠═e077ab16-22e5-11eb-15ed-5f0654bf7328
# ╠═e75a9a06-22e5-11eb-2e36-8d4f62b830ed
# ╠═fa68607e-22e5-11eb-0558-c9a4d9f77426
# ╠═cd520f9c-2509-11eb-36ce-8340182a95eb
# ╟─475ff888-22e6-11eb-2354-09f8f40a8e12


using Revise

includet("src/Spring.jl")
IArray = InternalArray

# 3. Validating the solution --------------------------------------------------------------

const step = 1e-3
const nit = Int(40/step)  # round to avoid small rounding errors

# We define a problem with no friction
prob = SpringProblem(p0 = 0.8, v0 = 0, m = 1, k = 1.5)

x = eulermethod(prob, step, nit)

timeline = [prob.t0 + i*step for i in 0:length(x)-1]
plot(timeline, IArray(x, 1), label = "euler method")

# And also define the solution we would have obtained by analytic methods
# Reference: https://en.wikipedia.org/wiki/Simple_harmonic_motion
ω0 = sqrt(prob.k/prob.m)
g(t) = prob.p0*cos(ω0*t) + prob.v0/ω0*sin(ω0*t)

y = g.(timeline)
plot!(timeline, y, label = "analytic solution")

# (The comparison in my computer is very close)
# (Which means that for the second step we could have taken a smaller L very likely)
# Let's check the norm of the difference:

err = maximum(norm.(y-IArray(x, 1)))

# 4. Choosing the step to obtain a final error in the order of 1e-2  ----------------------

# Reference: https://en.wikipedia.org/wiki/Euler_method#Local_truncation_error
# The global truncation error is bounded by $\frac{hM}{2L}(ℯ^{L(t-t_0)}-1)$ where
# M is an upper bound for the second derivative of the solution (in the interval) and
# L is the lipschitz constant of f.

# This will be explained later, but L < k+b and M can be taken to be maximum of the
# second derivative for the approximated solution.

# <-- Copied from the definitions
n = 1
function f(t, x)
    p = x[1:n]
    v = x[n+1:2*n]
    return [v; (-prob.k*p -prob.b*v + [prob.A*sin(prob.ω*t)])/prob.m]
end

M = maximum((norm ∘ f).(timeline, x))
L = prob.k+prob.b
err_ct = M/L*(ℯ^(L*40)-1) # error constant
target_step = 10e-2/err_ct

# After checking that with 1e-3 the error is approximately $0.02$
# for the solution without friction.
# It seems natural to admit that an appropiate step to obtain
# an error less than 1e-2 is approximately 5e-4.
# This can be verified using this same script but
# with a smaller error for the method without friction.

# After doing it, I saw that it was not enough and
# the error was still slightly larger than 1e-2.
# With the step equal to 1e-4, the error for the problem without friction is $0.0023$.
# Hence, we can assume that it won't be much larger for the problem with friction.

savefig("media/spring_validation.png")


"""
    binary_search(array::Array{Int64,1}, target::Int64)

The binary search algorithm (BSA) finds a target value's position within a sorted array by
using a half-interval cut per each cycle. Thus, the BSA compares the target value to the
value of the array's middle element. In the case of inequality, the half array-piece in
which the target cannot be will be erased. Next, the search continues on the remaining
half array-piece and starts taking the middle element to compare it to the target value.
This procedure has to be continued until the target value is found. The search may have to
be stopped with a remaining empty half array-piece; consequently, the target is not in the
array. For more information see: [https://en.wikipedia.org/wiki/Binary_search_algorithm](https://en.wikipedia.org/wiki/Binary_search_algorithm)

# Arguments
- `array::Array{Int64,1}`: Sorted array of integers
- `target::Int64`: Target-value to find the position

# Examples
```julia-repl
julia> import ClassicAlgorithmsCollections
julia> arr = [10,11, 12, 14, 16, 18, 19, 20, 21, 22, 23, 24, 33, 35, 42, 47]
julia> target = 12
julia> ClassicAlgorithmsCollections.binary_search(arr, target)
3
```
"""
function binary_search(array::Array{Int64,1}, target::Int64)
    left = 1
    right = length(array)

    while left <= right
        # Generating the middle element position
        mid = fld((left + right), 2)
        # If element is larger than mid, then it  can only be present in right subarray
        if array[mid] < target
            left = mid + 1
            # If element is smaller than mid, then it can only be present in left subarray
        elseif array[mid] > target
            right = mid - 1
            # If element is present at the middle itself
        else
            return mid
        end
    end
    return nothing
end


module NewtonsMethod

using ForwardDiff, LinearAlgebra

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

function newtonroot(f,fprime, x_0=0.8, tolerance=1.0E-7, maxiter=1000)
    # setup the algorithm
    x_old = x_0
    normdiff = Inf
    iter = 1
    while normdiff > tolerance && iter <= maxiter
        x_new = x_old-f(x_old)/fprime(x_old) # use the passed in map
        normdiff = norm(x_new - x_old)
        x_old = x_new
        iter = iter + 1
    end
    return (x_old, normdiff, iter)
end

newtonroot(f,x_0=0.8, tolerance=1.0E-7, maxiter=1000)=newtonroot(f,D(f), x_0, tolerance, maxiter)

export newtonroot

end # module


# by Lazaro Alonso
# port from the original code used in:
# L Alonso, et. al. https://doi.org/10.1093/comnet/cnx053

# using CairoMakie # HIDE
using LinearAlgebra, Random, CairoMakie
CairoMakie.activate!()
function RRGAdjacencyM(;lengthBase = 1, radius = 0.1, nodes = 500, rseed = 123)
    Random.seed!(rseed)
    xy = rand(nodes, 2)
    x = lengthBase .* xy[:,1]
    y = xy[:,2] ./ lengthBase
    matrixAdjDiag = Diagonal(√2 * randn(nodes))
    matrixAdj = zeros(nodes, nodes)
    for point in 1:nodes-1
        distance = sqrt.((x[point + 1:end] .- x[point]).^2 .+ (y[point + 1:end] .- y[point]).^2)
        dindx = findall(distance .<= radius) .+ point
        if length(dindx) > 0
            rnd = randn(length(dindx))
            matrixAdj[point, dindx] = rnd
            matrixAdj[dindx, point] = rnd
        end
    end
    return (matrixAdj .+ matrixAdjDiag, x, y)
end
adjacencyM, x, y = RRGAdjacencyM()

function getGraphEdges(adjMatrix, x, y)
    xyos = []
    weights = []
    for i in 1:length(x), j in i+1:length(x)
        if adjMatrix[i,j] != 0.0
            push!(xyos, [x[i], y[i]])
            push!(xyos, [x[j], y[j]])
            push!(weights, adjMatrix[i,j])
            push!(weights, adjMatrix[i,j])
        end
    end
    return (Point2f0.(xyos), Float32.(weights))
end

function plotGraph(adjacencyM, x, y)
    cmap = (:Spectral_11, 0.75)
    adjmin = minimum(adjacencyM)
    adjmax = maximum(adjacencyM)
    diagValues = diag(adjacencyM)
    segm, weights = getGraphEdges(adjacencyM, x, y)

    fig, ax, pltobj = linesegments(segm, color = weights, colormap = cmap,
        linewidth = abs.(weights)/2, colorrange = (adjmin, adjmax),
        figure = (;resolution = (500,400)), axis = (;aspect = DataAspect()))
    scatter!(ax, x, y, color = diagValues, markersize = 3*abs.(diagValues),
        colorrange = (adjmin, adjmax), colormap = cmap)
    limits!(ax, -0.02,1.02,-0.02,1.02)
    Colorbar(fig[1,2], pltobj, label = "weights")
    fig
end
#plotGraph(adjacencyM, x, y)
rrgraph = with_theme(theme_black()) do
    plotGraph(adjacencyM, x, y)
end
save(joinpath(@__DIR__, "output", "RRGraph.png"), rrgraph, px_per_unit = 2.0) # HIDE
using Pkg # HIDE
Pkg.status(["CairoMakie", "LinearAlgebra", "Random"]) # HIDE


# Fgt.jl

###### Foster–Greer–Thorbecke indices #####
"""
    fgt(v, α, z)

Compute the Foster–Greer–Thorbecke Index of a vector `v` at a specified `α`
and a given poverty threshold `z`.

# Examples
```julia
julia> using Inequality
julia> fgt([8, 5, 1, 3, 5, 6, 7, 6, 3], 2, 4)
0.0763888888888889
```
"""
function fgt(v::AbstractVector{<:Real}, α::Real, z::Real)::Float64

    if α == 0
        return headcount(v, z)
    elseif α == 1
        return poverty_gap(v, z)
    else
        return sum( ((1 .- (v[v .< z]/z)).^α) )/ length(v)
    end
end

###### Weighted Foster–Greer–Thorbecke indices #####
"""
    fgt(v, w, α, z)

Compute the Foster–Greer–Thorbecke Index of a vector `v` at a specified `α` and
a given poverty threshold `z`, using weights given by a weight vector `w`.

Weights must not be negative, missing or NaN. The weights and data vectors must have the same length.

# Examples
```julia
julia> using Inequality
julia> fgt([8, 5, 1, 3, 5, 6, 7, 6, 3], collect(0.1:0.1:0.9), 2, 4)
0.05555555555555555
```
"""
function fgt(v::AbstractVector{<:Real}, w::AbstractVector{<:Real}, α::Real, z::Real)::Float64

    if α == 0
        return headcount(v, w, z)
    elseif α == 1
        return poverty_gap(v, w, z)
    else
        checks_weights(v, w)

        return sum(((1 .- (v[v .< z] /z)).^α) .* w[v .< z])/ sum(w)
    end
end

function fgt(v::AbstractVector{<:Real}, w::AbstractWeights, α::Real, z::Real)::Float64

    if α == 0
        return headcount(v, w, z)
    elseif α == 1
        return poverty_gap(v, w, z)
    else
        checks_weights(v, w)

        return sum(((1 .- (v[v .< z] /z)).^α) .* w[v .< z] )/ w.sum
    end
end

"""
wfgt(v, w)

Compute the Foster–Greer–Thorbecke Index of `v` with weights `w`. See also [`fgt`](@fgt)
"""
wfgt(v::AbstractVector{<:Real}, w::AbstractVector{<:Real}, α::Real, z::Real)::Float64 = fgt(v, w, α, z)

wfgt(v::AbstractVector{<:Real}, w::AbstractWeights, α::Real, z::Real)::Float64 = fgt(v, w, α, z)


# ------------------------------------------------------------------------------------------
# # Julia 中的线性代数
# > Based on work by Andreas Noack Jensen (MIT) (http://www.econ.ku.dk/phdstudent/noack/)
# > with edits from Jane Herriman
#
# ## 目录
#  - [基础线性代数操作](#基础线性代数操作)
#     - [乘法](#乘法)
#     - [转置](#转置)
#     - [转置的乘法](#转置的乘法)
#     - [解线性方程组](#解线性方程组)
#  - [特殊的矩阵结构](#特殊的矩阵结构)
#      - [大规模问题](#大规模问题)
# ------------------------------------------------------------------------------------------

# ------------------------------------------------------------------------------------------
# ## 基础线性代数操作
# 定义一个随机矩阵
# ------------------------------------------------------------------------------------------

A = rand(1:4,3,3)

# ------------------------------------------------------------------------------------------
# 定义一个元素全为 1 的向量
# ------------------------------------------------------------------------------------------

x = fill(1.0, (3))

# ------------------------------------------------------------------------------------------
# 注意 $A$ 的类型为 `Array{Int64,2}`，而 $x$ 的类型为 `Array{Float64,1}`。
#
# Julia 定义 `Array{Type,1}` 的别名为向量 `Vector{Type}`，`Array{Type,2}` 的别名为矩阵 `Matrix{Type}` 。
#
# 许多线性代数的基础操作和其他语言一样
#
# ### 乘法
# ------------------------------------------------------------------------------------------

b = A*x

# ------------------------------------------------------------------------------------------
# ### 转置
# 就像在其他语言中 `A'` 表示对 `A` 进行共轭转置
# ------------------------------------------------------------------------------------------

A'

# ------------------------------------------------------------------------------------------
# 我们还可以通过 `transpose` 函数获得转置矩阵
# ------------------------------------------------------------------------------------------

transpose(A)

# ------------------------------------------------------------------------------------------
# ### 转置的乘法
# Julia 中某些情况下可以省略 `*` 号
# ------------------------------------------------------------------------------------------

A'A

# ------------------------------------------------------------------------------------------
# ### 解线性方程组
# 用方阵 $A$ 表示的线性方程组 $Ax=b$ 可以用左除运算符（函数）`\` 求解
# ------------------------------------------------------------------------------------------

A\b

# ------------------------------------------------------------------------------------------
# ## 特殊的矩阵结构
#
# 矩阵结构在线性代数中非常重要。
# 接触一下大一些的线型系统就可以看到矩阵结构有*多*重要了。
#
# 用线性代数标准包 `LinearAlgebra` 可以获得结构化的矩阵（structured matrices）：
# ------------------------------------------------------------------------------------------

using LinearAlgebra

n = 1000
A = randn(n,n);

# ------------------------------------------------------------------------------------------
# Julia 可以推断特殊矩阵结构，比如判断对称矩阵
# ------------------------------------------------------------------------------------------

Asym = A + A'
issymmetric(Asym)

# ------------------------------------------------------------------------------------------
# 但有时候浮点错误会比较麻烦
# ------------------------------------------------------------------------------------------

Asym_noisy = copy(Asym)
Asym_noisy[1,2] += 5eps()

issymmetric(Asym_noisy)

# ------------------------------------------------------------------------------------------
# 幸运的是我们可以通过如 `Diagonal`，`Triangular`，`Symmetric`，`Hermitian`，`Tridiagonal`
# 和 `SymTridiagonal` 这样的函数来明确地创建矩阵
# ------------------------------------------------------------------------------------------

Asym_explicit = Symmetric(Asym_noisy);

# ------------------------------------------------------------------------------------------
# 我们来看看 Julia 计算 `Asym`，`Asym_noisy` 和 `Asym_explicit` 的特征值各要花多少时间
# ------------------------------------------------------------------------------------------

@time eigvals(Asym);

@time eigvals(Asym_noisy);

@time eigvals(Asym_explicit);

# ------------------------------------------------------------------------------------------
#
# 本例中，使用 `Symmetric()` 处理 `Asym_noisy` 后让计算效率提高了约5倍
# ------------------------------------------------------------------------------------------

# ------------------------------------------------------------------------------------------
# ### 大规模问题
# 使用 `Tridiagonal` 和 `SymTridiagonal` 类型储存三对角矩阵（tridiagonal matrices）
# 让处理大规模的三对角矩阵问题变为可能。
#
# 以下问题如果使用稠密的 `Matrix` 类型储存，在个人计算机上是无法进行求解的。
# ------------------------------------------------------------------------------------------

n = 1_000_000;
A = SymTridiagonal(randn(n), randn(n-1));
@time eigmax(A)


import Base: angle

_dot2(v::Vector, w::Vector) = v[1] * w[1] + v[2] * w[2]

_no_intersect_error = "The two clines do not intersect; no angle can be calculated."

function angle(L::Line, M::Line)::Float64

    if slope(L) == slope(M)
        return 0
    end

    v = collect(reim(L.a - L.b))
    w = collect(reim(M.a - M.b))

    top = _dot2(v, w)
    bot = sqrt(_dot2(v, v) * _dot2(w, w))
    θ = abs(acos(top / bot))

    if θ > π / 2
        θ = π - θ
    end
    return θ

end

"""
    angle(C::Cline, D::Cline)
Return the angle between two clines. If `C` and `D` are equal, or are parallel lines, return 0.
Otherwise, if `C` and `D` do not intersect, an error is thrown.
"""
function angle(C::Cline, D::Cline)
    if C == D
        return 0
    end

    X = C ∩ D

    if length(X) == 0
        error(_no_intersect_error)
    end

    z = first(X)
    F = LFT(0, 1, 1, -z)

    CC = F(C)
    DD = F(D)
    if !(isa(CC, Line) && isa(DD, Line))
        error("This can't happen, but it did.")
    end
    return angle(CC, DD)
end


#=
P27 (**) Group the elements of a set into disjoint subsets.
a) In how many ways can a group of 9 people work in 3 disjoint subgroups of 2, 3 and 4 persons? Write a function that generates all the possibilities and returns them in a list.

Example:
* (group3 '(aldo beat carla david evi flip gary hugo ida))
( ( (ALDO BEAT) (CARLA DAVID EVI) (FLIP GARY HUGO IDA) )
... )

b) Generalize the above predicate in a way that we can specify a list of group sizes and the predicate will return a list of groups.

Example:
* (group '(aldo beat carla david evi flip gary hugo ida) '(2 2 5))
( ( (ALDO BEAT) (CARLA DAVID) (EVI FLIP GARY HUGO IDA) )
... )

Note that we do not want permutations of the group members; i.e. ((ALDO BEAT) ...) is the same solution as ((BEAT ALDO) ...). However, we make a difference between ((ALDO BEAT) (CARLA DAVID) ...) and ((CARLA DAVID) (ALDO BEAT) ...).

You may find more about this combinatorial problem in a good book on discrete mathematics under the term "multinomial coefficients".
=#


# example using julia 1.0.3
# -- run using julia fit.jl or include("fit.jl") in repl
#
# like rust, functions in julia can either use an explicit return statement or
# in the absence of one, will evaluate to the last expression in the function

# include if plotting
# using Gadfly

# -- functions
"""find_coeffs: given arrays x, y of length n for a 2D function y = f(x),
                return coefficients for a fitted polynomial of order n"""
function find_coeffs(x::AbstractArray, y::AbstractArray)
    # first thought to use @assert,
    # but after reading https://github.com/JuliaLang/julia/issues/30610
    # found that check() || error(...) preferred over @assert exceptions
    length(x) == length(y) || error("Uneven arrays in find_coeffs")

    # we isolate for the coefficient vector a = [a0, a1, a2, ...] in the
    # expression y = B a .
    # The n by n matrix B has rows of [x0 ^ 0, x0 ^ 1, x0 ^ 2, ...]

    # populate a matrix of these rows using matrix comprehension
    B = [x_i ^ n for x_i in x, n in 0:length(x) - 1]

    # find the coeffecient vector and return it
    inv(B) * y
end

# type constraint here is used to only allow vectors passed in
# -- where T allows T to range over all concrete types
"""Interpolate using the polynomial coefficient values 'coeffs' and the given
argument 'arg'"""
function coeff_interpolate(coeffs::AbstractArray{T,1}, arg) where T
    sum([coeffs[i] * arg ^ (i-1) for i in eachindex(coeffs)])
end

# -- examples --

# ex 1 -> pass in three x and three y values for a parabolic fit
parabola_coeffs = find_coeffs([1,2,3], [1,4,9])
println("The coefficients for points along x^2 are:")
println(parabola_coeffs)
println()

# ex 2 -> pass uneven arrays for length check
# bad_coeffs = find_coeffs([1], [])

# ex 3 -> check the default scalar type in the array
println("Default vector type:")
println(typeof(find_coeffs([1], [1])));
println()

# ex 4 -> force the return vector to be of Float16's
f16_coeffs = find_coeffs(Vector{Float16}([1, 3, 5]), [2, 3, 9])
println("Array type coerced to:")
println(typeof(f16_coeffs), "\n")

# ex 5 -> interpolation
println("The interpolated value at x = 4 for a model of the unit parabola:")
println(coeff_interpolate(parabola_coeffs, 4), "\n")

# # ex 6 -> plotting
# println("Plotting the quadratic fit from zero to ten as output.svg")
# x = 1:10
# y = [coeff_interpolate(parabola_coeffs, xi) for xi in x]
#
# # write output file
# # -- took ~4GiB memory and roughly 30 seconds to plot (output was only 9.6 kB)
#
# # @time draw(SVG("output.svg", 6inch, 3inch), plot(x=x, y=y, Geom.point, Geom.line))


"""
The [centroid of a triangle](https://en.wikipedia.org/wiki/Centroid#Of_a_triangle) is the point of
intersection of its medians (the lines joining each vertex with the midpoint of the opposite side).
The [Centroids Exists Theorem](https://sites.math.rutgers.edu/~zeilberg/PG/CentroidExists.html)
states that the three medians indeed intersect at single point.
"""
module Centroid

using Plots
using SymPy
using PlaneGeometry
using PlaneGeometry.GeoPlots

export
    centroid_proof, centroid_draw, centroid_rand, centroid

"""
    centroid_draw(📐::Triangle)

Verify Centroid Exists Theorem for the triangle `📐`.
"""
function centroid_draw(tri::Triangle)
    trishape=shape(tri)

    cpt, medians = centroid(tri)

    if cpt == nothing
        return (nothing, false)
    end

    plt = plot(tri, fill=(0, :green), aspect_ratio=:equal, fillalpha= 0.2, leg=false)

    for e in medians
        plot!(e, leg=false, color=:orange)
    end

    plt = scatter!(shape([vertices(tri)..., cpt]),
            leg=false,
            aspect_ratio=:equal,
            color=[:red, :red, :red, :green],
            series_annotations = text.(["A", "B", "C", "cpt"], :bottom))

    plt, true
end

"""
    centroid_draw(A, B, C)

Verify Centroid Exists theorem.
"""
function centroid_draw(A, B, C)
    tri = Triangle(A, B, C)

    centroid_draw(tri)
end

"""
    centroid_draw(xA, yA, xB, yB, xC, yC)

Verify Centroid Exists theorem.
"""
function centroid_draw(xA, yA, xB, yB, xC, yC)
    A = Point(xA, yA);
    B = Point(xB, yB);
    C = Point(xC, yC);

    centroid_draw(A, B, C)
end

"""
    centroid_rand()

Verify Centroid Exists Theorem for a random triangle.
"""
function centroid_rand()
    pts = rand(0:1//10:1,6);
    plt, hold = centroid_draw(pts...)
end

"""
    centroid_proof()

Prove Centroid Exists Theorem.
"""
function centroid_proof()
    @vars by cx positive=true;
    @vars cy;

    A = Point(0, 0); B = Point(0, by); C = Point(cx, cy);
    tri = Triangle(A, B, C)

    cpt, medians = centroid(tri)
    cpt != nothing
end

"""
    centroid(📐)

Find the centroid and the three medians of `📐`.
"""
function centroid(tri)
    medians = map(i->median(circshift(vertices(tri), i)...), 0:2)
    cpt = concurrent(medians)
    cpt, medians
end

end


export LogitLogLoss, value, deriv, deriv2

# set up loss functions
# The Flux implemnetation
# logloss = logitbinarycrossentropy

# alternate definition
# softmax(w) = 1/(1 + exp(-w))
# logloss(w, y) = -(y*log(softmax(w)) + (1-y)*log(1-softmax(w)))

#x,y = rand(1000), rand([0.0, 1.10], 1000)
#Flux.logitbinarycrossentropy.(x,y) ≈ logloss.(x, y)

# function logitcrossentropy(logŷ::AbstractVecOrMat, y::AbstractVecOrMat; weight = 1)
#   return -sum(y .* logsoftmax(logŷ) .* weight) * 1 // size(y, 2)
# end

import LossFunctions:  SupervisedLoss, deriv, value, deriv2
struct LogitLogLoss <: SupervisedLoss end

binarysoftmax(w) = 1/(1 + exp(-w))

value(::LogitLogLoss, y::Number, w::Number) = -(y*log(binarysoftmax(w)) + (1-y)*log(1-binarysoftmax(w)))

# https://www.wolframalpha.com/input/?i=f%28w%29+%3D+-%28y*log%281%2F%281+%2B+exp%28-w%29%29%29+%2B+%281-y%29*log%281-1%2F%281+%2B+exp%28-w%29%29%29%29%2C+df%2Fdw
deriv(::LogitLogLoss, y::Number, w::Number) = -binarysoftmax(-w) - y + 1

# https://www.wolframalpha.com/input/?i=f%28w%29+%3D+-%28e%5Ew+%28-1+%2B+y%29+%2B+y%29%2F%281+%2B+e%5Ew%29%2C+df%2Fdw
deriv2(::LogitLogLoss, y::Number, w::Number) = exp(w)*(binarysoftmax(-w)^2)

g(loss::SupervisedLoss, y, warmstart) = begin
	deriv(loss, y, warmstart)
end

h(loss::SupervisedLoss, y, warmstart) = begin
	deriv2(loss, y, warmstart)
end

# begin: Zygote.jl
# g(loss::Function, y, warmstart) = begin
# 	gres = gradient(x->loss(x, y), warmstart)
# 	gres[1]
# end

# h(loss::Function, y, warmstart) = begin
#     hres = hessian(x->loss(x[1], y), [warmstart])
#     hres[1]
# end
# end: Zygote.jl

# begin: ForwardDiff.jl
# g(loss::Function, y, warmstart) = begin
#     gres = ForwardDiff.gradient(x->loss(x[1], y), [warmstart])
#     gres[1]
# end

# h(loss::Function, y, warmstart) = begin
#     hres = ForwardDiff.hessian(x->loss(x[1], y), [warmstart])
#     hres[1]
# end
# end: ForwardDiff.jl


abstract type Point end

struct CartesianPoint{T} <: Point
	x::T
	y::T
	z::T
end

struct CylindricalPoint{T} <: Point
	z::T
	r::T
	θ::T
end

"""
	cartesian2cylindrical(x, y, z)
Transform a point or vector from cartesian (x, y, z)  to cylindrical (z, r, θ) coordinates,
assuming the z-axis is the axis of the cylinder
"""
function cartesian2cylindrical(x, y, z)
	r = √(x^2 + y^2)
	θ = atan(y, x)
	return (z, r, θ)
end

"""
	cylindrical2cartesian(z, r, θ)
Transform a point or vector (z, r, θ) from cylindrical  to cartesian (x, y, z) coordinates,
assuming the z-axis is the axis of the cylinder
"""
function cylindrical2cartesian(z, r, θ)
	x = r * cos(θ)
	y = r * sin(θ)
	return (x, y, z)
end

function cartesian2cylindrical(cart::CartesianPoint)
	z, r, θ = cartesian2cyclindrical(cart.x, cart.y, cart.z)
	return CylindricalPoint(z, r, θ)
end

function cylindrical2cartesian(cyl::CylindricalPoint)
	x, y, z = cylindrical2cartesian(cyl.z, cyl.r, cyl.θ)
	return CartesianPoint(x, y, z)
end


using Pkg
Pkg.activate(pwd())

# # Other Useful Packages
# ## Distributions.jl

using Distributions

D = Normal(2, 0.5)

#+

mean(D)
var(D)
quantile(D, 0.9)
pdf(D, 2)
cdf(D, 2)

#+

using StatsPlots

plot(
    plot(D; title = "pdf"),
    plot(D; func = cdf,  title = "cdf");
    legend = false,
    xlabel = "x",
    ylabel = "f(x)",
    ylims = (0,1),
    linewidth = 2,
    layout = (1,2),
    size = (800, 400),
)

#+

x = rand(Normal(2, 0.5), 10000); # generate 10000 random numbers from Normal(2, 0.5)
D = fit(Normal, x)

#+

histogram(x; normalize = :pdf, legend = false, opacity = 0.5)
plot!(D; linewidth = 2, xlabel = "x", ylabel = "pdf(x)")

# ### Exercise:
# Create a figure that shows the gamma distributions with the following parameters:
# `(2, 2)`, `(9, 0.5)`, `(7.5, 1)` and `(0.5, 1)`.
#
# **Hint:** to plot cumulative probability functions, use the Plots ability to
# plot functions.
#
# ---
# ### Solution:

Ds = Gamma.([2, 9, 7.5, 0.5], [2, 0.5, 1, 1])
labels = reshape(string.("Gamma", params.(Ds)), 1, :)

plot(Ds;
    xaxis = ("x", (0, 20)),
    yaxis = ("pdf(x)", (0, 0.5)),
    labels = labels,
    linewidth = 2,
    legend = :topright,
)

#+

plot(Ds;
    func = cdf,
    xaxis = ("x", (0, 20)),
    yaxis = ("cdf(x)", (0, 1.05)),
    labels = labels,
    linewidth = 2,
    legend = :bottomright,
)

#+

cdfs = [x -> cdf(D, x) for D in Ds]
plot(cdfs, 0, 20;
    xaxis = ("x", (0, 20)),
    yaxis = ("cdf(x)", (0, 1.05)),
    labels = labels,
    linewidth = 2,
    legend = :bottomright,
)

# ---
#
# ## BSON.jl

using BSON

BSON.bson(joinpath(pwd(), "lecture_05", "test2.bson"), a = [1+2im, 3+4im], b = "Hello, World!")

#+

data = Dict(:a => [1+2im, 3+4im], :b => "Hello, World!")
BSON.bson(joinpath(pwd(), "lecture_05", "test1.bson"), data)

#+

BSON.load(joinpath(pwd(), "lecture_05", "test1.bson"))
BSON.load(joinpath(pwd(), "lecture_05", "test2.bson"))

#+

using BSON: @save, @load

a = [1+2im, 3+4im];
b = "Hello, World!";

@save joinpath(pwd(), "lecture_05", "test.bson") a b # Same as above
@load joinpath(pwd(), "lecture_05", "test.bson") a b # Loads `a` and `b` back into the workspace

# ## ProgressMeter.jl

using ProgressMeter

@showprogress 1 "Computing..." for i in 1:50
    sleep(0.1)
end

#+

x, n = 1 , 10;
p = Progress(n);

for iter in 1:10
    x *= 2
    sleep(0.5)
    ProgressMeter.next!(p; showvalues = [(:iter, iter), (:x, x)])
end

# ## BenchmarkTools.jl

using BenchmarkTools

@benchmark sin(x) setup=(x=rand())

#+

A = rand(3,3);
@btime inv($A);


#=
持橋，大羽 著　「ガウス過程と機械学習」（機械学習プロフェッショナルシリーズ）
多変量ガウス分布のサンプリングのJuliaによる実装
=#

using LinearAlgebra
using Plots
using LaTeXStrings

μ1 = μ2 = 0.0

𝛍 = [
    μ1
    μ2
]

σ11 = σ22 = 1.0
σ12 = σ21 = -0.7

Σ = [
    σ11 σ12
    σ21 σ22
]

function 𝒩(𝐱...; 𝛍 = 𝛍, Σ = Σ)
    Λ = inv(Σ)
    D = length(𝛍)
    return √(det(Λ)) * exp(-0.5 * (𝐱 .- 𝛍)' * Λ * (𝐱 .- 𝛍)) / √(2π)^D
end

x1min, x1max = -4.0, 4.0
x2min, x2max = -4.0, 4.0
x1 = range(x1min, x1max, length = 50)
x2 = range(x2min, x2max, length = 50)
z = 𝒩.(x1', x2)
contour(x1, x2, z)
plot!(
    xlabel = L"x_1",
    ylabel = L"x_2",
    aspect_ratio = 1,
    xlims = [-4.0, 4.0],
    ylims = [-4.0, 4.0],
)
strΣ="\\sigma_{11}=$σ11, \\sigma_{12}=$σ12, \\sigma_{21}=$σ21, \\sigma_{22}=$σ22"
str𝛍="\\mu = [$μ1, $μ2]"
title!(latexstring("\\mathcal{N}(x \\mid \\mu, \\Sigma),\\ $str𝛍 , \\atop $strΣ"))

# Box–Muller transform を用いた方法
L = cholesky(Σ).L
cL = size(L, 2)

boxmuller(r1, r2) = √(-2 * log(r1)) * sin(2π * r2)
univariate(n...) = boxmuller.(rand(n...), rand(n...))

function multivariate(n)
    x = univariate(cL, n) # cL 次元ベクトルを n 個生成する
    return L * x # cL 次元ベクトル を変換して 𝒩(0, Σ) に従うサンプルを生成
end

xy = multivariate(100)
scatter!(xy[1, :], xy[2, :],color=:blue, label="boxmuller")

# Distributions.jl でサンプルを作成
d = MvNormal(𝛍, Σ)
xy = rand(d,100)
scatter!(xy[1,:], xy[2,:],color=:red, label="mvnormal")


f(x) = sin(x)

function simpsons_rule(a, b, n)
    h = (b - a) / (2*n)
    res = 0.0

    for i in 0:2*n
        if (i==0 || i==2*n)
            res += f(a + i*h)
        elseif (i % 2 == 1)
            res += 4 * f(a + i*h)
        else
            res += 2 * f(a + i*h)
        end
    end

    res * h / 3
end

println("Analytical=", 2)
println("Numerical=", simpsons_rule(0, pi, 100))


module problem115

description = """
Counting block combinations II

A row measuring n units in length has red blocks with a minimum length of m units placed on it, such that any two red blocks (which are allowed to be different lengths) are separated by at least one black square.
Let the fill-count function, F(m, n), represent the number of ways that a row can be filled.
For example, F(3, 29) = 673135 and F(3, 30) = 1089155.
That is, for m = 3, it can be seen that n = 30 is the smallest value for which the fill-count function first exceeds one million.
In the same way, for m = 10, it can be verified that F(10, 56) = 880711 and F(10, 57) = 1148904, so n = 57 is the least value for which the fill-count function first exceeds one million.

For m = 50, find the least value of n for which the fill-count function first exceeds one million.
"""

function F(m::Integer, n :: Integer, memo = zeros(Int, n))
  # trivial cases
  if n < 1 return 1 end

  # Count the total with each block size, and block position,
  # recursively counting the ways to fill the remaining space at the end of the row.
  if memo[n] == 0
    total = 1
    for blocksize = m:n, blockpos = 1:(n-blocksize+1)
      total += F(m, n - blockpos - blocksize, memo)
    end
    memo[n] = total
  end
  return memo[n]
end

using Base.Test

@test F(3, 29) == 673135
@test F(3, 30) == 1089155
@test F(10, 56) == 880711
@test F(10, 57) == 1148904
@test F(50, 167) == 978181
@test F(50, 168) == 1053389 # <- answer

end


module RootFinding

export bisect_root, secant_root

function bisect_root(f, a, b; rtol=1e-8, atol=1e-8)
    fa = f(a)
    fb = f(b)

    @assert fa*fb < zero(fa)

    while abs(a-b) > atol + rtol*(abs(a) + abs(b))/2
        m = (a + b)/2
        fm = f(m)

        if fa*fm < zero(fa)
            fb = fm
            b = m
        else
            fa = fm
            a = m
        end
    end

    return (a+b)/2
end

function secant_root(f, a, b; rtol=1e-8, atol=1e-8)
    fa = f(a)
    fb = f(b)

    @assert fa*fb < zero(fa)

    # Secant guesses and function values:
    sg0 = a
    sg1 = b
    sf0 = fa
    sf1 = fb

    while abs(sg1-sg0) > atol + rtol*(abs(sg0) + abs(sg1))/2
        sg = sg0 - sf0*(sg1-sg0)/(sf1-sf0)

        if sg < a || sg > b
            m = (a+b)/2
            fm = f(m)

            if fm*fa > zero(fa)
                a = m
                fa = fm
            else
                b = m
                fb = fm
            end

            sg0 = a
            sg1 = b
            sf0 = fa
            sf1 = fb
        else
            sg0 = sg1
            sf0 = sf1
            sg1 = sg
            sf1 = f(sg)
        end
    end

    return sg1
end

end # module


"""
A 2D Rotation matrix is a mtrix that rotates a vector in a 2D real space by an angle theta.
For more info: https://en.wikipedia.org/wiki/Rotation_matrix

This function takes the angle `theta` in radians as input and returns a 2D Matrix which will rotate the the vector by angle `theta`.
"""
function rotation_matrix(θ)
    rot_mat = Matrix{Float64}(undef, 2, 2)
    rot_mat[1, 1] = cos(θ)
    rot_mat[1, 2] = -sin(θ)
    rot_mat[2, 1] = sin(θ)
    rot_mat[2, 2] = cos(θ)

    return rot_mat
end


using LinearAlgebra

# integrate sinh^(n)(ax) from 0 to r
function integrate_sinh(n; r=1.0, a=1.0)
    if n==0
        return r
    elseif n==1
        return (cosh(a*r)-1)/a
    else
        return (sinh(a*r)^(n-1))*cosh(a*r)/(a*n) - (n-1)/n * integrate_sinh(n-2,r=r, a=a)
    end
end

function hyp_radial_density(r, d; curvature=-1.0, radius=1.0)
#    k = 1/(curvature^2)
    k = 1.0
    hyp_r = (sinh(r/k))^(d-1)/integrate_sinh(d-1,r=radius, a=radius/k)
    return hyp_r
end

function euc_radial_density(r, d; radius=1.0)
    return d*(r^(d-1))/radius^d
end

# rejection sampling n=numofpts points from density dens, where the argument lies between 0 and maxval

function rejection_sampling(dens::Function, maxval,numofpts=1)
    max_val = dens(maxval)
    iter = 1
    rands = Array{Float64,1}(undef, numofpts)
    while iter <= numofpts
        x = rand()*maxval
        val = dens(x)
        u = rand()
        if u*max_val < val
            rands[iter] = x
            iter+=1
        end
    end
    return rands
end

function sample_hyp_rad(d, numofpts=1; curvature=-1.0, radius=1.0)
    rands = rejection_sampling(x->hyp_radial_density(x,d,curvature=curvature, radius=radius), radius,numofpts)

    # ...radius within the Poincare ball
    euc_rands = map(x->tanh(x/2.0),rands)
    return euc_rands
end

function sample_euc_rad(d, numofpts=1; radius=1.0)
    rands = rejection_sampling(x->euc_radial_density(x,d,radius=radius), radius, numofpts)
    return rands
end

function sample_sph(d, numofpts=1; curvature=1.0)
    rands = []
    i=0
    while i<=numofpts
        vec = randn(d+1)
        if vec[d+1]>0
            push!(rands, normalize(vec))
            i+=1
        end
    end
    return rands
end

function sample_sphere(d, numofpts=1)
    vecs = randn(d, numofpts)
    rands = []
    for i=1:numofpts
        push!(rands, normalize(vecs[:,i]))
    end
    return rands
end

function sample_hyp(d, numofpts=1; radius=1.0, curvature=-1)
    sphere_pts = sample_sphere(d,numofpts)
    radii = sample_hyp_rad(d, numofpts, radius=radius, curvature=curvature)
    ball_pts = [radii[i]*sphere_pts[i] for i=1:numofpts]
    return ball_pts
end

function sample_euc(d, numofpts=1; radius=1.0)
    sphere_pts = sample_sphere(d,numofpts)
    radii = sample_euc_rad(d, numofpts, radius=radius)
    ball_pts = [radii[i]*sphere_pts[i] for i=1:numofpts]
    return ball_pts
end

function sample_ball(d, numofpts=1; radius=1.0, curvature=0.0)
    sphere_pts = sample_sphere(d,numofpts)
    if curvature < 0
        radii = sample_hyp_rad(d, numofpts, radius=radius, curvature=curvature)
        ball_pts = [radii[i]*sphere_pts[i] for i=1:numofpts]
    elseif curvature == 0.0
        radii = sample_euc_rad(d, numofpts, radius=radius)
        ball_pts = [radii[i]*sphere_pts[i] for i=1:numofpts]
    elseif curvature > 0
        ball_pts = sample_sph(d, numofpts, curvature=curvature)
    end
end

function hyp_distance(pts; curvature=-1.0)
    distances = zeros(length(pts), length(pts))
    for i=1:length(pts)
        for j=1:i-1
            nx = 1-(norm(pts[i]))^2
            ny = 1-(norm(pts[j]))^2
            delta = 2 * norm(pts[i]-pts[j])^2/(nx*ny)
            distances[i,j] = acosh(1+delta)
            distances[j,i] = distances[i,j]
        end
    end
    return distances
end

function euc_distance(pts)
    distances = zeros(length(pts), length(pts))
    for i=1:length(pts)
        for j=1:i-1
            distances[i,j] = norm(pts[i]-pts[j])
            distances[j,i] = distances[i,j]
        end
    end
    return distances
end

function sph_distance(pts; curvature=1.0)
    distances = zeros(length(pts), length(pts))
    for i=1:length(pts)
        for j=1:i-1
            distances[i,j] = acos(dot(pts[i],pts[j]))
            distances[j,i] = distances[i,j]
        end
    end
    return distances
end

function distance_matrix(pts; curvature=0.0)
    if curvature < 0
        return hyp_distance(pts, curvature=curvature)
    elseif curvature == 0
        return euc_distance(pts)
    elseif curvature > 0
        return sph_distance(pts, curvature=curvature)
    end
end

function to_density(matr)
    dens_matr = zeros(size(matr,1), size(matr,2))
    n = size(matr)[1]
    all_entries = sort(setdiff(unique(matr), 0.0))
    total = binomial(n,2)
    for i=1:n
        for j=i+1:n
            dens_matr[i,j] = (findfirst(x->x==matr[i,j], all_entries))/total
            dens_matr[j,i] = dens_matr[i,j]
        end
    end
    return dens_matr
end

function to_density!(matr)
    matr = to_density(matr)
    return matr
end


function sieve_of_atkin(limit)
    primes = Int64[]

    sieve = falses(limit)

    square_root_of_limit = round(Int64, sqrt(limit))

    for x in 1:square_root_of_limit
        for y in 1:square_root_of_limit
            n = (4 * x * x) + (y * y)

            if n <= limit && (n % 12 == 1 || n % 12 == 5)
                sieve[n] = !sieve[n]
            end

            n = (3 * x * x) + (y * y)

            if n <= limit && n % 12 == 7
                sieve[n] = !sieve[n]
            end

            n = (3 * x * x) - (y * y)

            if x > y && n <= limit && n % 12 == 11
                sieve[n] = !sieve[n]
            end
        end
    end

    sieve[2] = true

    sieve[3] = true

    push!(primes, 2)

    push!(primes, 3)

    n = 5

    while n <= square_root_of_limit
        if sieve[n]
            i = n * n

            while i < limit
                sieve[i] = false

                i += n * n
            end

            push!(primes, n)
        end

        n += 2
    end

    while n < limit
        if sieve[n]
            push!(primes, n)
        end

        n += 2
    end

    primes
end

n = 10001

limit = round(Int64, (n * log(n)) + (n * (log(log(n)))))

println(sieve_of_atkin(limit)[n])


@doc raw"""
```
albedo,illumination_direction,slant,tilt = estimate_img_properties(img::AbstractArray)
```
Attempts to calculate the illumination properties of an image for use in SFS
algorithms.
# Output
Returns albedo (ρ), illumination vector (I), slant (σ) and tilt (τ) of the image as Float,
Array{Float} of size 3, Float and Float respectively.
# Details
The illumination properties can be estimated using the first and second
moments and the gradients of the image (``E_x, E_y``) brightness as per the following:

```math
\begin{gathered}
\mu_1=\dfrac{\pi}{4}\rho\cos\sigma\\\\\mu_2=\dfrac{1}{6}\rho^2(1+3\cos^2\sigma)\\
\\\rho=\dfrac{\sqrt{6\pi\mu_2-48\mu_1^2}}{\pi}\\\\\cos\sigma=\dfrac{4\mu_1}{\sqrt
{6\pi\mu_2-48\mu_1^2}}\\\\\tan\tau=\dfrac{\text{average}(E_y)}{\text{average}
(E_x)}\\\\I=[\cos\tau\sin\sigma,\sin\tau\sin\sigma,\cos\sigma]
\end{gathered}
```
Note: Properties are an estimate and may not be correct under all conditions.
# Arguments
The function arguments are described in more detail below.
##  `img`
An `AbstractArray` storing the grayscale value of each pixel within
the range [0,1].
# Example
Compute properties of synthetic image generated using `generate_surface`.
```julia
using Images, ShapeFromShading

#generate synthetic image
img = generate_surface(SynthSphere(), 1, [0.2,0,0.9], radius = 5)

#estimate the properties
albedo, illumination_direction, slant, tilt = estimate_img_properties(img)
```
# Reference
1. T. Ping-Sing and M. Shah, "Shape from shading using linear approximation", Image and Vision Computing, vol. 12, no. 8, pp. 487-498, 1994. [doi:10.1016/0262-8856(94)90002-7](https://doi.org/10.1016/0262-8856(94)90002-7)
"""
function estimate_img_properties(img::AbstractArray)
    E = Array{Float64}(img)
    #calculate spatial gradient of img
    Ey, Ex = imgradients(E, KernelFactors.sobel, "replicate")
    #normalize gradient
    nEx = similar(E)
    nEy = similar(E)
    Exy = sqrt.((Ex.^2) .+ (Ey.^2))
    nEx = Ex ./ (Exy .+ eps())
    nEy = Ey ./ (Exy .+ eps())

    #calculate means
    μ₁ = mean(E)
    μ₂ = mean(E.^2)

    #calculate desired values
    g = sqrt((6.0 * (π^2) * μ₂) - (48.0 * (μ₁^2)))
    ρ = g / π
    t = (4 * μ₁) / g
    if t > 1.0
        t = 1.0
    elseif t < -1.0
        t = -1.0
    end
    σ = acos(t)
    τ = atan(mean(nEy) / mean(nEx))
    if τ < 0
        τ = τ + π;
    end
    I = [cos(τ) * sin(σ), sin(τ) * sin(σ), cos(σ)]
    return ρ, I, σ, τ
end


"""
    KL(p::Beta, q::Beta)

See [KL Beta](https://en.wikipedia.org/wiki/Beta_distribution#Quantities_of_information_(entropy))
"""
function KL(p::Beta, q::Beta)
    αp, βp = params(p)
    αq, βq = params(q)
    return logbeta(αq, βq) - logbeta(αp, βp) + (αp - αq) * digamma(αp) +
        (βp - βq) * digamma(βp) + (αq - αp + βq - βp) * digamma(αp + βp)
end

"""
    KL(p::Exponential, q::Exponential)

See [KL Exponential](https://en.wikipedia.org/wiki/Exponential_distribution#Kullback%E2%80%93Leibler_divergence)
"""
function KL(p::Exponential, q::Exponential)
    λp = scale(p)
    λq = scale(q)
    return log(λp) - log(λq) + λq / λp - 1
end

"""
    KL(p::Gamma, q::Gamma)

See [KL Gamma](https://en.wikipedia.org/wiki/Gamma_distribution#Kullback%E2%80%93Leibler_divergence)
"""
function KL(p::Gamma, q::Gamma)
    # We use the parametrization with the rate β
    αp, αq = shape.((p, q))
    βp, βq = rate.((p, q))
    return (αp - αq) * digamma(αp) - loggamma(αp) + loggamma(αq) +
        αq * (log(βp) - log(βq)) + αp * (βq - βp) / βp
end

"""
    KL(p::InverseGamma, q::InverseGamma)

See [KL Inverse-Gamma](https://en.wikipedia.org/wiki/Inverse-gamma_distribution#Properties)
"""
function KL(p::InverseGamma, q::InverseGamma)
    # We can reuse the implementation of Gamma
    return KL(Gamma(shape(p), rate(p)), Gamma(shape(q), rate(q)))
end

"""
    KL(p::Normal, q::Normal)

See [KL Gaussian](https://en.wikipedia.org/wiki/Normal_distribution#Other_properties)
"""
function KL(p::Normal, q::Normal)
    μp, σp = params(p)
    μq, σq = params(q)
    return 0.5 * (abs2(σp / σq) + abs2((μp - μq) / σq) - 1 + 2 * (log(σq) - log(σp)))
end

"""
    KL(p::Poisson, q::Poisson)

See [KL Poisson](https://en.wikipedia.org/wiki/Poisson_distribution#Other_properties)
"""
function KL(p::Poisson, q::Poisson)
    λp, λq = rate.((p, q))
    return λq - λp + λp * (log(λp) - log(λq))
end


using Plots, ApproxFun
# The Blasius boundary layer problem (https://en.wikipedia.org/wiki/Blasius_boundary_layer)
# describes the boundary layer on a semi-infinite plane with a head-on steady fluid flow.
# When properly non-dimensionalized, it presents itself as a relatively simple nonlinear BVP.
# The Falkner-Skan problem generalizes the Blasius boundary layer by considering a wedge at an
# angle to the steady flow.
# In both cases, no-slip conditions are in place at the wall and the boundary layer
# grows linearly (u'(∞)→1) in the far-field.
# There are more variants which include different nonlinearities which arise in magneto-
# hydrodynamics, for example.

# We begin with Blasius:
#   solves 2u''' + uu'' = 0 ,  u(0.) = u'(0.) = 0, u'[∞] = 1
x=Fun(identity,0..4π)
d=domain(x)
B=[ldirichlet(d),lneumann(d),rneumann(d)]
D=Derivative(d)
κ = 0.33205733621519630     # This is diff(u,2)(0.), due to Boyd.
u0 = (1//2) * κ * x^2      # First-order approximation
u = 0.5x^2                  # Other initial solutions may fail to converge

f = (u)->(2.0*D^3*u + u*D^2*u)
df = (u)->(2.0*D^3 + u*D^2 + D^2*u)

plot([u,u0])      # Initial estimate and first-order approximation

for k=1:10
    global u  -= [B; df(u)]\[u(0.);u'(0.);u'(rightendpoint(d))-1.;f(u)]
end
norm(f(u))             # should be zero
abs(u''(0.)-κ)         # should also be zero
plot([u,u0])           # Solution and First-order approximation
# Now for Falkner-Skan:
#     solves 2u''' + uu'' - β(1 - (u')^2) = 0 ,  u(0.) = u'(0.) = 0, u'[∞] = 1
m = 0.11                # m ∈ [-0.0905, 2]
β = 2m/(1+m)
F = (u)->(2.0*D^3*u + u*D^2*u - β*(1.0 - (D*u)*(D*u)))
dF = (u)->(2.0*D^3 + u*D^2 + D^2*u + 2β*D*u*D)
v=u
norm(F(v))               # should be non-zero, as we've perturbed the Blasius equation

for k=1:10
  global v  -= [B; dF(v)]\[v(0.);v'(0.);v'(rightendpoint(d))-1.;F(v)]
end
norm(F(v))               # should be zero

plot([u,v])    # Blasius and Falkner-Skan solutions


function gaussian_elimination!(A::Array{Float64,2})

    rows = size(A,1)
    cols = size(A,2)

    # Row index
    row = 1

    # Main loop going through all columns
    for col = 1:(cols-1)

        # Step 1: finding the maximum element for each column
        max_index = argmax(abs.(A[row:end,col])) + row-1

        # Check to make sure matrix is good!
        if (A[max_index, col] == 0)
            println("matrix is singular!")
            continue
        end

        # Step 2: swap row with highest value for that column to the top
        temp_vector = A[max_index, :]
        A[max_index, :] = A[row, :]
        A[row, :] = temp_vector

        # Loop for all remaining rows
        for i = (row+1):rows

            # Step 3: finding fraction
            fraction = A[i,col]/A[row,col]

            # loop through all columns for that row
            for j = (col+1):cols

                 # Step 4: re-evaluate each element
                 A[i,j] -= A[row,j]*fraction

            end

            # Step 5: Set lower elements to 0
            A[i,col] = 0
        end
        row += 1
    end
end

function back_substitution(A::Array{Float64,2})

    rows = size(A,1)
    cols = size(A,2)

    # Creating the solution Vector
    soln = zeros(rows)

    for i = rows:-1:1
        sum = 0.0
        for j = rows:-1:i
            sum += soln[j]*A[i,j]
        end
        soln[i] = (A[i, cols] - sum) / A[i, i]
    end

    return soln
end

function gauss_jordan_elimination!(A::Array{Float64,2})

    rows = size(A,1)
    cols = size(A,2)

    # After this, we know what row to start on (r-1)
    # to go back through the matrix
    row = 1
    for col = 1:cols-1
        if (A[row, col] != 0)

            # divide row by pivot and leaving pivot as 1
            for i = cols:-1:col
                A[row,i] /= A[row,col]
            end

            # subtract value from above row and set values above pivot to 0
            for i = 1:row-1
                for j = cols:-1:col
                    A[i,j] -= A[i,col]*A[row,j]
                end
            end
            row += 1
        end
    end
end

function main()
    A = [2. 3 4 6;
         1 2 3 4;
         3 -4 0 10]

    gaussian_elimination!(A)
    println(A)

    gauss_jordan_elimination!(A)
    println(A)

    soln = back_substitution(A)
    println(soln)

end

main()


using PyPlot, SparseArrays, LinearAlgebra # Packages used

rows = [1,3,4,2,1,3,1,4,1,5]
cols = [1,1,1,2,3,3,4,4,5,5]
vals = [5,-2,-4,5,-3,-1,-2,-10,7,9]

A = sparse(rows, cols, vals, 5, 5)

Array(A)

spy(A, marker=".", markersize=24);   ## Note - 0-based row and columns

B = A  - 4.3A   # Will automatically convert datatype of values to Float64
B[:,4] .= -1.1  # OK since B now has Float64 values (otherwise use Float64.(A) to convert)
C = A * A'      # Matrix multiplication (note: typically increases nnz)
Matrix([B C])   # Concatenation, again automatic conversion (of C)

"""
Incremental matrix construction using the sparse-format
Not recommended: Insertion into existing matrix very slow
"""
function incremental_test_1(N)
    A = spzeros(N,N)
    for k = 1:10N
        i,j = rand(1:N, 2)
        A[i,j] = rand()
    end
    return A
end

incremental_test_1(10);   # Force compile before timing
for N in [100,1000,10000]
  @time incremental_test_1(N);
end

"""
Incremental matrix construction using COO and a single call to sparse
Fast approach, avoids incremental insertion into existing array
"""
function incremental_test_2(N)
    rows = Int64[]
    cols = Int64[]
    vals = Float64[]
    for i = 1:10N
        push!(rows, rand(1:N))
        push!(cols, rand(1:N))
        push!(vals, rand())
    end
    return sparse(rows, cols, vals, N, N)
end

incremental_test_2(10);   # Force compile before timing
for N in [100,1000,10000,100000,1000000]
  @time incremental_test_2(N);
end