# # Turtle Programming

# ## Introduction
# In this tutorial we will have a look at _Turtle Programming_. In 1967 a general purpose programming language called LOGO was created. The main highlight of this language was turtle graphics. In turtle graphics we have a robot with a pen which draws on a canvas. We can control this turtle using a few commands. The main commands are :
#
# * `forward` : move the turtle forward
# * `turn` : turn the turtle
# * `penUp/penDown` : activate/deactivate the turtle's pen
#
# To get a brief understanding of how _this_ works head over [here](https://rawgit.com/wrschneider99/js-turtle/master/turtle.html).
#
# While Turtle programming generally refers to a 2-D turtle we have a 3-D version available. In the 3-D version we have three types of `turn` available for each of the dimensions of the turtle.
#
# * `pitch`
# * `yaw`
# * `roll`
#
# Let's first start with only operating on a 2-D plane and later we will discuss 3-D.
#
# You need to clear up space for experimentation. Use `setBlocks()` with PiCraft.AIR to clear up space. If you are on the Java edition then it is suggested that you use
# `New World -> Creative -> World Type: Superflat -> Customize -> Presets -> The Void -> Create New World`
# Then we can setup a canvas for drawing as follows:

using PiCraft
p = getPos()
setBlocks(p .+ (-50, 0, -50), p .+ (50, 0, 50), Block(35, 15))
# ![canvasSetup.png](../assets/img/Turtle/canvasSetup.png)
#
# To initialize the turtle:
t = turtle(pos = p)
# Since we are only concerned with the 2 dimensions of the plane we only need a single turn function which will be `yaw`
turn = yaw

move(t, 10)
turn(t, 45) # Note that the angle is in degrees, use `deg2rad(θ)` to convert from radians.
move(t, 10)
t.penBlock = PiCraft.IRON_BLOCK # Change penBlock
move(t, 10)
turn(t, 45)
move(t, 10)
t.penDown = false
turn(t, 90)
move(t, 10)
t.penDown = true
move(t, 10)

# ![draw1.png](../assets/img/Turtle/draw1.png)
# To reset the canvas and the turtle it can be convenient to define a function

function clrscr(t, p)
    setBlocks(p .+ (-50, 0, -50), p .+ (50, 0, 50), Block(35, 15))
    t.pos = p
end
clrscr(t, p)

# Draw a square

move(t, 10)
yaw(t, 90)
move(t, 10)
yaw(t, 90)
move(t, 10)
yaw(t, 90)
move(t, 10)

# ![square.png](../assets/img/Turtle/square.png)

# We can also use a 'for loop' for the same
clrscr(t, p)
for i in 1:4
    move(t, 10)
    yaw(t, 90)
end

# Similarly, we can draw a triangle

# Draw a triangle
clrscr(t, p)
for i in 1:3
    move(t, 10)
    turn(t, 120)
end

# Challenge: Figure out how to draw a general polygon using a loop
#
# Solution:
function drawPolygon(t::turtle, n::Integer, l::Real)
    θ = 180 - 360/n
    for i in 1:n
        move(t, l)
        yaw(t, θ)
    end
end

clrscr(t, p)
# Star
for i in 1:5
    move(t, 50)
    turn(t, 144)
end

# ![star.png](../assets/img/Turtle/star.png)

# We can also make spirals.
#
# Spiral
clrscr(t, p)
for i in 1:10
    move(t, 5*i)
    turn(t, 90)
end

# ![spiral1.png](../assets/img/Turtle/spiral1.png)

clrscr(t, p)
for i in 1:10
    move(t, 5 + 5*i)
    turn(t, 120)
end

# ![spiral2.png](../assets/img/Turtle/spiral2.png)

# ---
# ## 3-D turtle
# As mentioned earlier instedad of a single `turn` command we have 3 commands, namely `yaw`, `pitch` and `roll`.
#
# ![Roll_Pitch_Yaw.JPG](../assets/img/Turtle/Roll_Pitch_Yaw.JPG)
# In essence we have 3 mutually perpendicular axis on the turtle.
#
# * Longitudinal(turtle.direction, points forward) : Roll Axis
# * Lateral : Pitch Axis
# * Vertical(turtle.normal, points downwards) : Yaw Axis
#
# To get the positive rotation direction use the [corkscrew rule](https://en.wikipedia.org/wiki/Right-hand_rule).
# ![corkscrew rule image](https://upload.wikimedia.org/wikipedia/commons/3/34/Right-hand_grip_rule.svg)
#
# Curl your right hand's fingers with the thumb pointing outwards like in a thumbs up position. When your thumb points in the axis vector then the curl direction is the positive rotation direction.
#
# We dont need a canvas to experiment with the 3-D turtle. Reset the world as required.

move(t, 10)
pitch(t, 45)
move(t, 10)
pitch(t, -45)
yaw(t, 90)
move(t, 10)
roll(t, 90)
pitch(t, 90)
move(t, 10)

# ![draw2.png](../assets/img/Turtle/draw2.png)
#
# We can use the same concepts of the 2-D turtle to draw in 3-D.
#
# Square inclined at 45 degrees

t = turtle(pos = getPos())
pitch(t, 45)
for i in 1:4
    move(t, 10)
    yaw(t, 90)
end

# ![squareInclined.png](../assets/img/Turtle/squareInclined.png)
#
# Spring
t = turtle(pos = getPos())
R = 20
C = 2*pi*R
for i in 1:720
    move(t, C/90)
    yaw(t, 7)
    pitch(t, 5)
end


using DynamicalSystemsBase
using Test

ti = time()

@testset "DynamicalSystemsBase tests" begin
include("dynsys_integrators.jl")
include("dynsys_tangent.jl")
include("dynsys_inference.jl")
include("continuous_systems.jl")
include("discrete_systems.jl")
include("integrators_with_callbacks.jl")
include("norm_tests.jl")
include("projected_integrator_tests.jl")
end

ti = time() - ti
println("\nTest took total time of:")
println(round(ti, digits=3), " seconds or ", round(ti/60, digits=3), " minutes")


immutable Exponential <: ContinuousUnivariateDistribution
    scale::Float64 # note: scale not rate
    function Exponential(sc::Real)
        if sc > 0.0
            new(float64(sc))
        else
            error("scale must be positive")
        end
    end
end

Exponential() = Exponential(1.0)

cdf(d::Exponential, q::Real) = q <= 0.0 ? 0.0 : -expm1(-q / d.scale)

function logcdf(d::Exponential, q::Real)
    if q <= 0.0
        return -Inf
    else
        qs = -q / d.scale
        if qs > log(0.5)
            return log(-expm1(qs))
        else
            return log1p(-exp(qs))
        end
    end
end

function ccdf(d::Exponential, q::Real)
    q <= 0.0 ? 1.0 : exp(-q / d.scale)
end

function logccdf(d::Exponential, q::Real)
    q <= 0.0 ? 0.0 : -q / d.scale
end

function invlogcdf(d::Exponential, lp::Real)
    if lp <= 0.0
        -d.scale * (lp > log(0.5) ? log(-expm1(lp)) : log1p(-exp(lp)))
    else
        return NaN
    end
end

function invlogccdf(d::Exponential, lp::Real)
    lp <= 0.0 ? -d.scale * lp : NaN
end

entropy(d::Exponential) = 1.0 - log(d.scale)

insupport(d::Exponential, x::Number) = isreal(x) && isfinite(x) && 0.0 <= x

kurtosis(d::Exponential) = 6.0

mean(d::Exponential) = d.scale

median(d::Exponential) = d.scale * log(2.0)

function mgf(d::Exponential, t::Real)
    s = d.scale
    return (1.0 - t * s)^(-1)
end

function cf(d::Exponential, t::Real)
    s = d.scale
    return (1.0 - t * im * s)^(-1)
end

modes(d::Exponential) = [0.0]

function pdf(d::Exponential, x::Real)
    x <= 0.0 ? 0.0 : exp(-x / d.scale) / d.scale
end

function logpdf(d::Exponential, x::Real)
    x <= 0.0 ? -Inf : -x / d.scale - log(d.scale)
end

function quantile(d::Exponential, p::Real)
    0.0 <= p <= 1.0 ? -d.scale * log1p(-p) : NaN
end

function cquantile(d::Exponential, p::Real)
    0.0 <= p <= 1.0 ? -d.scale * log(p) : NaN
end

rand(d::Exponential) = d.scale * Random.randmtzig_exprnd()

rand!(d::Exponential, A::Array{Float64}) = d.scale * Random.randmtzig_fill_exprnd!(A)

skewness(d::Exponential) = 2.0

var(d::Exponential) = d.scale * d.scale


"""
`pave` takes the given working list of boxes and splits them into inner and boundary
lists with the given separator
"""
function pave(S::Separator, working::Vector{IntervalBox{N,T}}, ϵ, bisection_point=nothing) where {N,T}

    inner_list = SubPaving{N,T}()
    boundary_list = SubPaving{N,T}()

    while !isempty(working)

        X = pop!(working)

        inner, outer = S(X)   # here inner and outer are reversed compared to Jaulin
        # S(X) returns the pair (contractor with respect to the inside of the constraing, contractor with respect to outside)

        #@show X, outer
        inside_list = setdiff(X, outer)

        if length(inside_list) > 0
            append!(inner_list, inside_list)
        end

        boundary = inner ∩ outer

        if isempty(boundary)
            continue
        end

        if diam(boundary) < ϵ
            push!(boundary_list, boundary)

        else
            if bisection_point == nothing
                push!(working, bisect(boundary)...)
            else
                push!(working, bisect(boundary, bisection_point)...)
            end

        end

    end

    return inner_list, boundary_list

end

"""
    pave(S::Separator, domain::IntervalBox, eps)`

Find the subset of `domain` defined by the constraints specified by the separator `S`.
Returns (sub)pavings `inner` and `boundary`, i.e. lists of `IntervalBox`.
"""
function pave(S::Separator, X::IntervalBox{N,T}, ϵ = 1e-2, bisection_point=nothing) where {N,T}

    inner_list, boundary_list = pave(S, [X], ϵ, bisection_point)

    return Paving(S, inner_list, boundary_list, ϵ)

end

#
# """Refine a paving to tolerance ϵ"""
# function refine!(P::Paving, ϵ = 1e-2)
#     if P.ϵ <= ϵ  # already refined
#         return
#     end
#
#     new_inner, new_boundary = pave(P.separator, P.boundary, ϵ)
#
#     append!(P.inner, new_inner)
#     P.boundary = new_boundary
#     P.ϵ = ϵ
# end


#=
   Credit-card fraud.

   From David Heckerman
   "A Tutorial on Learning With Bayesian Networks"
   page 12ff
   """
   To illustrate the process of building a Bayesian network, consider the problem of de-
   tecting credit-card fraud. We begin by determining the variables to model. One possible
   choice of variables for our problem is Fraud(F), Gas(G), Jewelry(J) Age(A) and Sex(S)
   representing whether or not the current purchase is fraudulent, whether or not there
   was a gas purchase in the last 24 hours, whether or not there was a jewelry purchase in
   the last 24 hours, and the age and sex of the card holder, respectively.
   """

   Distributions of variable fraud (num:0)
   1.00000 =>   20244  (0.506100)
   0.00000 =>   19756  (0.493900)

   Distributions of variable sex
   male       =>   21678  (0.541950)
   female     =>   18322  (0.458050)

   Distributions of variable age
   age_30_to_50 =>   17315  (0.432875)
   age_above_50 =>   15237  (0.380925)
   age_below_30 =>    7448  (0.186200)

   Distributions of variable gas (num:0)
   0.00000 =>   39821  (0.995525)
   1.00000 =>     179  (0.004475)

   Distributions of variable jewlery (num:0)
   0.00000 =>   39980  (0.999500)
   1.00000 =>      20  (0.000500)

   Cf ~/webppl/credit_card_fraud.wppl

=#

using Turing, StatsPlots, DataFrames
include("jl_utils.jl")

@model function credit_card_fraud()
    male = 1
    female = 2

    #  type Age;
    age_below_30 = 1
    age_30_to_50 = 2
    age_above_50 = 3

    fraud ~   flip(0.00001)
    age ~ Categorical(simplex([0.25, 0.40, 0.35]))  # [age_below_30, age_30_to_50, age_above_50]
    sex ~ Categorical(simplex([0.5,0.5]))           # [male, female]

    gas ~ fraud ? flip(0.2) : flip(0.01);

    jewlery ~  fraud ? flip(0.05) :
        (age == age_below_30 && sex==male)   ? flip(0.0001) :
        (age == age_30_to_50 && sex==male)   ? flip(0.0004) :
        (age == age_above_50 && sex==male)   ? flip(0.0002) :
        (age == age_below_30 && sex==female) ? flip(0.0005) :
        (age == age_30_to_50 && sex==female) ? flip(0.002)  :
        (age == age_above_50 && sex==female) ? flip(0.001)  : Dirac(0)

    # true ~ Dirac(age == age_below_30)
    # true ~ Dirac(age == age_30_to_50)
    # true ~ Dirac(age == age_above_50)
    # true ~ Dirac(gas == true)
    # true ~ Dirac(sex == female)
    # true ~ Dirac(sex == male)
    # true ~ Dirac(jewlery == true)
    true ~ Dirac(fraud == true)
    # true ~ Dirac(fraud == false)

end

model = credit_card_fraud()

num_chains = 4

# chns = sample(model, Prior(), 10_000)
# chns = sample(model, MH(), 100_000)
chns = sample(model, PG(5), 40_000)
# chns = sample(model, SMC(), 40_000)
# chns = sample(model, IS(), 100_000)

display(chns)
# display(plot(chns))

show_var_dist_pct(chns, :fraud)
show_var_dist_pct(chns, :sex,["male","female"])
show_var_dist_pct(chns, :age,["age_below_30","age_30_to_50","age_above_50"])
show_var_dist_pct(chns, :gas)
show_var_dist_pct(chns, :jewlery)


import Dates
println("Last updated: ", Dates.now(), " (PT)")

# Load environment
import Pkg; Pkg.activate("../../../")

# Import Libraries
using Turing
using Turing: Variational
import Turing.RandomMeasures.DirichletProcess
import Turing.RandomMeasures.ChineseRestaurantProcess
using Distributions
using JSON3
using PyPlot
using StatsFuns
import Random
import StatsBase.countmap
include(joinpath(@__DIR__, "../util/BnpUtil.jl"));

# Directory where all simulation data are stored.
data_dir = joinpath(@__DIR__, "../data")
path_to_data = joinpath(data_dir, "gmm-data-n50.json")

# Load data in JSON format.
data = let
    x = open(f -> read(f, String), path_to_data)
    JSON3.read(x, Dict{Symbol, Vector{Any}})
end

# Convert data to vector of floats
y = Float64.(data[:y]);

# Visualize data
plt.hist(y, bins=50, density=true)
plt.xlabel("y")
plt.ylabel("density")
plt.title("Histogram of data");

function counts_plot(x; density=false, color="C0", lw=4)
    cm_x = countmap(x)
    number = collect(keys(cm_x))
    count = let
        c = collect(values(cm_x))
        density ? c / sum(c) : c
    end
    plt.vlines(number, 0, count, color=color, lw=lw)
end

exp_num_clus(a, n) = a * log1p(n / a)

alpha_prior = Uniform(0, 0.5)
a = rand(alpha_prior, 10000)
nclus = rand.(Poisson.(exp_num_clus.(a, length(y))))

counts_plot(nclus, density=true)

plt.xlabel("counts")
plt.ylabel("prior probability")
plt.title("prior distribution for number of clusters");

# a = [0.1, 0.5, 1.0, 1.5, 2.0]
# plt.scatter(a, exp_num_clus.(a, 50))

@model function dp_gmm_crp_delayed(y, Kmax, ::Type{T}=Vector{Float64}) where T
    nobs = length(y)

    alpha ~ Gamma(1, 1/10)  # mean = a*b
    # alpha ~ Uniform(0, 1.0)
    rpm = DirichletProcess(alpha)

    # Base measure.
    H_mu = Normal(0, 3)
    H_sigma = Uniform(0, 0.3)
    # H_sigma = LogNormal(-1, 0.5)

    # Latent assignment.
    z = tzeros(Int, nobs)
    n = tzeros(Int, Kmax)
    K = 0

    mu = T(undef, Kmax)
    sigma = T(undef, Kmax)

    for i in 1:nobs
        z[i] ~ ChineseRestaurantProcess(rpm,  n)
        n[z[i]] += 1

        if z[i] > K
            mu[z[i]] ~ H_mu
            sigma[z[i]] ~ H_sigma
            K += 1
        end

        y[i] ~ Normal(mu[z[i]], sigma[z[i]])
    end
end;

# Set random seed for reproducibility
Random.seed!(0);

# Compile time approx. 32s.
# Run time approx. 70s

@time chain = begin
    burn = 3000  # NOTE: The burn in is also returned. Discard manually.
    n_samples = 1000
    iterations = burn + n_samples

    sample(dp_gmm_crp_delayed(y, 20), SMC(), iterations)

    # sample(dp_gmm_crp_delayed(y, 30), PG(10), iterations)
    # sample(dp_gmm_crp_delayed(y, 15), SMC(), iterations)
    # sample(dp_gmm_crp_delayed(y, 30), Gibbs(PG(20, :z), PG(10, :alpha, :mu, :sigma)), iterations)
    # sample(dp_gmm_crp_delayed(y, 15), Gibbs(PG(20, :z), PG(10, :mu, :sigma)), iterations)
    # sample(dp_gmm_crp_delayed(y, 30), Gibbs(MH(0.1, :alpha), PG(50, :z), MH(0.01, :mu, :sigma)), iterations)

    # sample(dp_gmm_crp_delayed(y, 30), Gibbs(MH(0.1, :alpha), PG(10, :z), HMC(0.01, 100, :mu, :sigma)), iterations)

    # NOTE: Works very poorly...
    # sample(dp_gmm_crp_delayed(y, 30), Gibbs(PG(5, :z), HMC(0.01, 10, :alpha, :mu, :sigma)), iterations)

    # sample(dp_gmm_crp(y), SMC(), iterations)
    # sample(dp_gmm_crp(y), IS(), iterations)
    # sample(dp_gmm_crp(y), Gibbs(PG(5, :z), PG(5, :mu_sigma)), iterations)
    # sample(dp_gmm_crp(y), PG(10), iterations)
end;

# TODO: Maybe try initializing at MAP?

burn=0
chain.value

z = chain.value.data[burn+1:end, (end-length(y)+1):end, 1]
nclus = [length(unique(z[i, :])) for i in 1:size(z, 1)];

plt.figure(figsize=(12,4))
plt.subplot(1, 3, 1)
plt.plot(nclus);
plt.xlabel("iteration")
plt.ylabel("number of clusters")

plt.subplot(1, 3, 2)
counts_plot(nclus, density=true);
plt.xlabel("number of clusters")
plt.ylabel("probability")

plt.subplot(1, 3, 3)
i = 1000
counts_plot(z[i, :], density=true);
plt.xlabel("cluster label for sample $(i)")
plt.ylabel("probability")

plt.tight_layout();

countmap(vec(Float64.(chain[:alpha].data)))

function extract_vec(chain, sym)
    syms = filter(x -> x != nothing, match.(Regex("$(sym)\\[\\d+\\]"), String.(chain.name_map[1])))
    syms = map(x -> Symbol(x.match), syms)
    return chain[syms]
end

plt.figure(figsize=(12, 4))

plt.subplot(1, 3, 1)
mu = coalesce.(extract_vec(chain, :mu).value.data[burn+1:end, :, 1], 0.0)
plt.boxplot(mu);
foreach(line -> plt.axhline(line, ls=":"), data[:mu])
plt.ylabel("mu")
plt.xlabel("mixture component")

plt.subplot(1, 3, 2)
sigma = coalesce.(extract_vec(chain, :sigma).value.data[burn+1:end, :, 1], 0.0)
plt.boxplot(coalesce.(sigma, 0.0));
foreach(line -> plt.axhline(line, ls=":"), data[:sig])
plt.ylabel("sigma")
plt.xlabel("mixture component")

plt.subplot(1, 3, 3)
plt.hist(Float64.(chain[:alpha].data[:, 1, 1]), density=true)
plt.xlabel("alpha")
plt.ylabel("density")

plt.tight_layout();

plt.plot(mu)
foreach(line -> plt.axhline(line, ls=":"), data[:mu])

jitter(x; eps=std(x)/30) = x + randn(length(x)) * eps

plt.scatter(jitter(vec(sigma)), jitter(vec(mu)), label="posterior samples (jittered)", s=1);
plt.scatter(data[:sig], data[:mu], marker="X", s=60, label="truth")
plt.xlabel("sigma")
plt.ylabel("mu")
# foreach(line -> plt.axhline(line, ls=":"), data[:mu])
# foreach(line -> plt.axvline(line, ls=":"), data[:sig])
plt.legend(loc="upper left");


function very_inefficient_solve(input)
	ret = 0
	conditions = ["shiny gold"]
	bags = [split(x, r" bags?", limit = 2) for x in input]
	i = 1
	while i <= length(bags)
		if any([contains(bags[i][2], condition) for condition in conditions])
			push!(conditions, bags[i][1])
			deleteat!(bags, i)
			ret += 1
			i = 1
		else
			i += 1
		end
	end
	ret
end

function solve(input)
	bags = split.(input, r" bags?")
	rules = Dict([ bag[1] => [ !contains(rule, "no other") ? match(r"\d [ \w]*", rule).match : nothing for rule in bag[2: end - 1] ] for bag in bags ])
	ret = 0
	queue = [("shiny gold", 1)]
	while (!isempty(queue))
		(bag, count) = pop!(queue)
		if !isnothing(rules[bag][1])
			children = split.(rules[bag], ' ', limit = 2)
			for child in children
				n = parse(Int64, child[1])
				push!(queue, (child[2], n * count))
				ret += n * count
			end
		end
	end
	ret
end

@assert very_inefficient_solve(readlines("input/test/test_day7.txt")) == 4 ["test case 1 failed"]
@assert solve(readlines("input/test/test_day7.txt")) == 32 ["test case 2 failed"]

println("First star: " * string(@time very_inefficient_solve(readlines("input/day7.txt"))))
println("Second star: " * string(@time solve(readlines("input/day7.txt"))))


### A Pluto.jl notebook ###
# v0.14.8

using Markdown
using InteractiveUtils

# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error).
macro bind(def, element)
    quote
        local el = $(esc(element))
        global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : missing
        el
    end
end

# ╔═╡ a565c4ee-d425-11eb-0a5c-d7d322fb95c6
using IndexFunArrays, Plots, Colors, ImageShow, PlutoUI, MappedArrays

# ╔═╡ eb26d519-61f9-49c8-bf13-fb0bd5b01be6
md"#### IndexFunArrays.jl | Fun with indices (and functions on them)
* _Rainer Heintzmann, Leibniz Institute of Photonic Technology in Jena, Germany_
* _Felix Wechsler, Leibniz Institute of Photonic Technology in Jena, Germany_

Main features:
* Arrays without memory allocations
* entries are calculated once accessed
* feels like an arrays but is some kind of generator

"

# ╔═╡ 840c51ce-1300-41b0-be75-4b298c6c3390
md"### Motivation

* Calculation with indices works efficiently with `CartesianIndices`
* However, as you can see it can get complicated
"

# ╔═╡ 1d880f4b-71fb-4b83-8d23-4a46a1481423
function rr_cartesian_indices(s)
	# entry wise function
	rr_fun(index) = √(sum(abs2.(Tuple(index) .- (s .÷ 2 .+ 1))));
	# array of tuple indices
	arr_indices = CartesianIndices(s)
	# apply rr_fun elementwise
	rr_fun.(arr_indices)
end

# ╔═╡ ab66c67d-832b-40ff-bbc2-f52761912b2d
rr_cartesian_indices((5,6))

# ╔═╡ f09a33fa-dd83-4dbd-948b-78303d3fb76d
md"### MappedArrays
* in principle offers similar functionality
* but doesn't provide the explicit functions (like `rr2`, `rr`)
"

# ╔═╡ e70c0bdb-aa02-4017-ae32-ba3b9fb50906
function rr_mapped_arrays(s)
	# entry wise function
	rr_fun(index) = √(sum(abs2.(Tuple(index) .- (s .÷ 2 .+ 1))));
	# array of tuple indices
	mappedarray(rr_fun, CartesianIndices(s))
end

# ╔═╡ bee02ceb-24a4-4d48-bfe0-c24fdfde0472
rr_mapped_arrays((5,6))

# ╔═╡ 01651a42-dce0-4015-a9d0-a92dc02cbd89
rr((5, 6))

# ╔═╡ 3faba71d-ffa3-4bad-96b8-76f488dfc600
md"### Data Structure Behind It"

# ╔═╡ da4c455d-f657-4fc8-b8d5-ac1497e29071
struct IndexFunArray_demo{T, N, F} <: AbstractArray{T, N} where {F}
    # stores the generator function to be applied to the indices.
    generator::F
    # output size of the array
    size::NTuple{N, Int}
end

# ╔═╡ 5b7b9e0b-9f93-43ed-aba5-56cbf9f1bea8
Base.getindex(A::IndexFunArray{T,N}, I::Vararg{Int, N}) where {T,N} =
    return A.generator(I)

# ╔═╡ 8604d201-c4ba-4314-bc0b-df73f8d3e24a
md"### How To Use It"

# ╔═╡ 44af166f-a01f-43c2-a774-9a09f5d42712
hello = IndexFunArray(x -> "hello", (3, 3))

# ╔═╡ 3a2ddf05-0d2a-401e-bbad-adccf7ab21bf
hello[2, 3]

# ╔═╡ 5d5835da-4331-4f06-a24c-e60177f38cba
img = IndexFunArray(x -> x[1] + x[2], (3, 3))

# ╔═╡ df51963f-ceea-4279-aa51-6d8c8afaa9a0
distance_to_corner = IndexFunArray(x -> sqrt((x[1]-1)^2 + (x[2]-1)^2), (3, 3))

# ╔═╡ dc58b7a5-8c64-4c3f-aa7b-8261647d2513
md"### What is already defined?

#### Window Functions
* Gaussian
* Hamming
* Hanning
* Linear
* ...

#### Distance Functions
* radius
* radius squared
* 1D ramps
* ...
"

# ╔═╡ 455d650b-cb51-4dc3-a02f-e6c832e3e5e0

# ╔═╡ d48829c1-c04f-47d3-aa0d-af656b6a5276

# ╔═╡ fbfbc63b-4188-4c03-9bc3-b89b4c6c6298

# ╔═╡ c0eb6cff-a47c-485e-8983-2bf4070d5223
md"## Defining apertures in 1 LOC"

# ╔═╡ d4e01cd2-3eff-404f-86c6-fcd4b772f478
@bind radius Slider(0:1:200, show_value=true)

# ╔═╡ a9ab1f95-42d2-4d64-b015-423ec4fcfc8a
@bind c_ind Select(["1" => "CtrFT", "2" => "CtrEnd", "3" => "CtrFFT"])

# ╔═╡ 04b7c231-0868-493e-99ef-3eb4d89b7ad4
ctr = [CtrFT, CtrEnd, CtrFFT][parse(Int, c_ind)];

# ╔═╡ 4512c50f-e094-4ef9-a177-27a9fdc58f09
[Gray.(rr((300, 300), offset=ctr) .<= radius) Gray.(disc((300, 300), radius, offset=ctr))]

# ╔═╡ d7878c97-068d-4040-9f73-2400e05636bf

# ╔═╡ 2d23b2d8-5dac-422a-804d-e2ccd27de12e
Gray.(0.5 .+ xx((300, 300), offset=ctr, scale=ScaNorm))

# ╔═╡ 465522c9-d209-4476-867f-f3ba1f1563ca
md"## Different kinds of Window functions"

# ╔═╡ fabeb838-fb8e-46b9-ad97-9271d4ada93b
md"
$(@bind border_out Slider(0:0.01:1, show_value=true))

$(@bind border_in Slider(0:0.01:1, show_value=true))

"

# ╔═╡ 54023a37-4843-48a2-bb94-262590f310c3
begin
	w_gauss = window_gaussian((256, 256); border_in, border_out)
	w_linear = window_linear((256, 256); border_in, border_out)
end;

# ╔═╡ fedff1f5-5595-4ac9-b53f-a11bd864554c
[Gray.(w_gauss) Gray.(w_linear) ]

# ╔═╡ f0c86a16-f255-4b85-b32a-e0adba958504
md"# Gauss"

# ╔═╡ 5c691197-0516-4ab6-9804-0bb6d0df5a66
md"
$(@bind σ1 Slider(0:0.01:200, show_value=true))
$(@bind σ2 Slider(0:0.01:200, show_value=true))
"

# ╔═╡ 7f72a169-5c33-49f4-982e-e85181425374
w_gauss2 = gaussian((256, 256), sigma=(σ1, σ2));

# ╔═╡ 0fa4b07a-5fe2-4294-8753-9adb1ed7c6f7
Gray.(w_gauss2)

# ╔═╡ Cell order:
# ╠═a565c4ee-d425-11eb-0a5c-d7d322fb95c6
# ╟─eb26d519-61f9-49c8-bf13-fb0bd5b01be6
# ╟─840c51ce-1300-41b0-be75-4b298c6c3390
# ╠═1d880f4b-71fb-4b83-8d23-4a46a1481423
# ╠═ab66c67d-832b-40ff-bbc2-f52761912b2d
# ╟─f09a33fa-dd83-4dbd-948b-78303d3fb76d
# ╠═e70c0bdb-aa02-4017-ae32-ba3b9fb50906
# ╠═bee02ceb-24a4-4d48-bfe0-c24fdfde0472
# ╠═01651a42-dce0-4015-a9d0-a92dc02cbd89
# ╟─3faba71d-ffa3-4bad-96b8-76f488dfc600
# ╠═da4c455d-f657-4fc8-b8d5-ac1497e29071
# ╠═5b7b9e0b-9f93-43ed-aba5-56cbf9f1bea8
# ╟─8604d201-c4ba-4314-bc0b-df73f8d3e24a
# ╠═44af166f-a01f-43c2-a774-9a09f5d42712
# ╠═3a2ddf05-0d2a-401e-bbad-adccf7ab21bf
# ╠═5d5835da-4331-4f06-a24c-e60177f38cba
# ╠═df51963f-ceea-4279-aa51-6d8c8afaa9a0
# ╟─dc58b7a5-8c64-4c3f-aa7b-8261647d2513
# ╟─455d650b-cb51-4dc3-a02f-e6c832e3e5e0
# ╟─d48829c1-c04f-47d3-aa0d-af656b6a5276
# ╟─fbfbc63b-4188-4c03-9bc3-b89b4c6c6298
# ╟─c0eb6cff-a47c-485e-8983-2bf4070d5223
# ╟─d4e01cd2-3eff-404f-86c6-fcd4b772f478
# ╟─a9ab1f95-42d2-4d64-b015-423ec4fcfc8a
# ╟─04b7c231-0868-493e-99ef-3eb4d89b7ad4
# ╠═4512c50f-e094-4ef9-a177-27a9fdc58f09
# ╠═d7878c97-068d-4040-9f73-2400e05636bf
# ╠═2d23b2d8-5dac-422a-804d-e2ccd27de12e
# ╟─465522c9-d209-4476-867f-f3ba1f1563ca
# ╟─fabeb838-fb8e-46b9-ad97-9271d4ada93b
# ╠═54023a37-4843-48a2-bb94-262590f310c3
# ╠═fedff1f5-5595-4ac9-b53f-a11bd864554c
# ╟─f0c86a16-f255-4b85-b32a-e0adba958504
# ╟─5c691197-0516-4ab6-9804-0bb6d0df5a66
# ╠═7f72a169-5c33-49f4-982e-e85181425374
# ╠═0fa4b07a-5fe2-4294-8753-9adb1ed7c6f7


module TensorCore

using LinearAlgebra

export ⊙, hadamard, hadamard!
export ⊗, tensor, tensor!

"""
    hadamard(a, b)
    a ⊙ b

For arrays `a` and `b`, perform elementwise multiplication.
`a` and `b` must have identical `axes`.

`⊙` can be passed as an operator to higher-order functions.

```jldoctest; setup=:(using TensorCore)
julia> a = [2, 3]; b = [5, 7];

julia> a ⊙ b
2-element Array{$Int,1}:
 10
 21

julia> a ⊙ [5]
ERROR: DimensionMismatch("Axes of `A` and `B` must match, got (Base.OneTo(2),) and (Base.OneTo(1),)")
[...]
```
"""
function hadamard(A::AbstractArray, B::AbstractArray)
    @noinline throw_dmm(axA, axB) = throw(DimensionMismatch("Axes of `A` and `B` must match, got $axA and $axB"))

    axA, axB = axes(A), axes(B)
    axA == axB || throw_dmm(axA, axB)
    return map(*, A, B)
end
const ⊙ = hadamard

"""
    hadamard!(dest, A, B)

Similar to `hadamard(A, B)` (which can also be written `A ⊙ B`), but stores its results in
the pre-allocated array `dest`.
"""
function hadamard!(dest::AbstractArray, A::AbstractArray, B::AbstractArray)
    @noinline function throw_dmm(axA, axB, axdest)
        throw(DimensionMismatch("`axes(dest) = $axdest` must be equal to `axes(A) = $axA` and `axes(B) = $axB`"))
    end

    axA, axB, axdest = axes(A), axes(B), axes(dest)
    ((axdest == axA) & (axdest == axB)) || throw_dmm(axA, axB, axdest)
    @simd for I in eachindex(dest, A, B)
        @inbounds dest[I] = A[I] * B[I]
    end
    return dest
end

"""
    tensor(A, B)
    A ⊗ B

Compute the tensor product of `A` and `B`.
If `C = A ⊗ B`, then `C[i1, ..., im, j1, ..., jn] = A[i1, ... im] * B[j1, ..., jn]`.

```jldoctest; setup=:(using TensorCore)
julia> a = [2, 3]; b = [5, 7, 11];

julia> a ⊗ b
2×3 Array{$Int,2}:
 10  14  22
 15  21  33
```

For vectors `v` and `w`, the Kronecker product is related to the tensor product by
`kron(v,w) == vec(w ⊗ v)` or `w ⊗ v == reshape(kron(v,w), (length(w), length(v)))`.
"""
tensor(A::AbstractArray, B::AbstractArray) = [a*b for a in A, b in B]
const ⊗ = tensor

const CovectorLike{T} = Union{Adjoint{T,<:AbstractVector},Transpose{T,<:AbstractVector}}
function tensor(u::AbstractArray, v::CovectorLike)
    # If `v` is thought of as a covector, you might want this to be two-dimensional,
    # but thought of as a matrix it should be three-dimensional.
    # The safest is to avoid supporting it at all. See discussion in #35150.
    error("`tensor` is not defined for co-vectors, perhaps you meant `*`?")
end
function tensor(u::CovectorLike, v::AbstractArray)
    error("`tensor` is not defined for co-vectors, perhaps you meant `*`?")
end
function tensor(u::CovectorLike, v::CovectorLike)
    error("`tensor` is not defined for co-vectors, perhaps you meant `*`?")
end

"""
    tensor!(dest, A, B)

Similar to `tensor(A, B)` (which can also be written `A ⊗ B`), but stores its results in
the pre-allocated array `dest`.
"""
function tensor!(dest::AbstractArray, A::AbstractArray, B::AbstractArray)
    @noinline function throw_dmm(axA, axB, axdest)
        throw(DimensionMismatch("`axes(dest) = $axdest` must concatenate `axes(A) = $axA` and `axes(B) = $axB`"))
    end

    axA, axB, axdest = axes(A), axes(B), axes(dest)
    axes(dest) == (axA..., axB...) || throw_dmm(axA, axB, axdest)
    if IndexStyle(dest) === IndexCartesian()
        for IB in CartesianIndices(axB)
            @inbounds b = B[IB]
            @simd for IA in CartesianIndices(axA)
                @inbounds dest[IA,IB] = A[IA]*b
            end
        end
    else
        i = firstindex(dest)
        @inbounds for b in B
            @simd for a in A
                dest[i] = a*b
                i += 1
            end
        end
    end
    return dest
end

end


using BenchmarkTools, Compat, DataFrames, IntervalRootFinding, IntervalArithmetic, StaticArrays

function benchmark_results()
    f = [] # Example functions from Dietmar Ratz - An Optimized Interval Slope Arithmetic and its Application
    push!(f, x->((x + sin(x)) * exp(-x^2)))
    push!(f, x->(x^4 - 10x^3 + 35x^2 - 50x + 24))
    push!(f, x->((log(x + 1.25) - 0.84x) ^ 2))
    push!(f, x->(0.02x^2 - 0.03exp(-(20(x - 0.875))^2)))
    push!(f, x->(exp(x^2)))
    push!(f, x->(x^4 - 12x^3 + 47x^2 - 60x - 20exp(-x)))
    push!(f, x->(x^6 - 15x^4 + 27x^2 + 250))
    push!(f, x->(atan(cos(tan(x)))))
    push!(f, x->(asin(cos(acos(sin(x))))))

    s = interval(0.75, 1.75)
    df = DataFrame()

    df[:Method] = ["Automatic Differentiation", "Slope Expansion"]
    for n in 1:length(f)

        t1 = ForwardDiff.derivative(f[n], s)
        t2 = slope(f[n], s, mid(s))
        df[Symbol("f" * "$n")] = [t1, t2]
    end
    a = []
    for i in 1:length(f)
        push!(a, Symbol("f" * "$i"))
    end
    df1 = stack(df, a)
    dfnew = unstack(df1, :variable, :Method, :value)
    dfnew = rename(dfnew, :variable => :Function)
    println(dfnew)
    dfnew
end

function benchmark_time()
    f = []
    push!(f, x->((x + sin(x)) * exp(-x^2)))
    push!(f, x->(x^4 - 10x^3 + 35x^2 - 50x + 24))
    push!(f, x->((log(x + 1.25) - 0.84x) ^ 2))
    push!(f, x->(0.02x^2 - 0.03exp(-(20(x - 0.875))^2)))
    push!(f, x->(exp(x^2)))
    push!(f, x->(x^4 - 12x^3 + 47x^2 - 60x - 20exp(-x)))
    push!(f, x->(x^6 - 15x^4 + 27x^2 + 250))
    push!(f, x->(atan(cos(tan(x)))))
    push!(f, x->(asin(cos(acos(sin(x))))))

    s = interval(0.75, 1.75)
    df = DataFrame()
    df[:Method] = ["Automatic Differentiation", "Slope Expansion"]
    for n in 1:length(f)

        t1 = @belapsed ForwardDiff.derivative($f[$n], $s)
        t2 = @belapsed slope($f[$n], $s, mid($s))
        df[Symbol("f" * "$n")] = [t1, t2]
    end
    a = []
    for i in 1:length(f)
        push!(a, Symbol("f" * "$i"))
    end
    df1 = stack(df, a)
    dfnew = unstack(df1, :variable, :Method, :value)
    dfnew = rename(dfnew, :variable => :Function)
    println(dfnew)
    dfnew
end


# Create example data
x = LinRange(0, 30, 1000)
y = cos.(x) .* x
z = sin.(x) .* x
# Draw a solid line and another with star markers
# in one of every 10 points
plot3(x, y, z, x[1:10:end], y[1:10:end], z[1:10:end], "p")


```Write a function that rotates a list by k elements. For example [1,2,3,4,5,6]
rotated by two becomes [3,4,5,6,1,2]. Try solving this without creating a copy of
 the list. How many swap or move operations do you need?```

using BenchmarkTools

list = 1:10

function rotate_list(x::Vector{Int64},y::Int64)
    x[[y:end; 1:y]]
end

@benchmark circshift(list, 3)
@benchmark rotate_list(list, 3)