# ---
# layout: post
# title: "Pentachoron"
# date: 2019-04-03 00:00:00 +0000
# categories: blog
# mathjax: true
# ---

# > This page contains quite a few animated GIFs. I apologise in advance if your browser or network connection doesn't like them. It's much easier to add a GIF than it is to embed a video into a GitHub pages blog, unfortunately.

# ### Pentachoron

# ![image label](IMAGEFOLDER/header-image.svg){: .center-image}

# I encountered the word "pentachoron" recently for the first time, and I thought it had to be something cool. Turns out it is, so I thought I'd have a go at drawing one...

# Unfortunately, it's a 4-dimensional object, which is going to tax my limited 2D skills. But I like being out of my depth.

# ## First steps

# The first job is to define a couple of types to help organize all the various points.

# Here's an immutable structure to hold the coordinates of a point in four dimensions. In four dimensions, the axes are usually called `x`, `y`, `z`, and `w`.

struct Point4D <: AbstractArray{Float64, 1}
    x::Float64
    y::Float64
    z::Float64
    w::Float64
end

Point4D(a::Array{Float64, 1}) = Point4D(a...)

Base.size(pt::Point4D) = (4, )

Base.getindex(pt::Point4D, i) = [pt.x, pt.y, pt.z, pt.w][i]

# By adopting the AbstractArray as our stepmother type, we can pick up many useful behaviors. `size()` and `getindex()` can be taught how to handle Point4Ds. If you browse the [official documentation](https://docs.julialang.org/en/v1/manual/interfaces/#man-interface-array-1) for a while you'll find out how to effectively tap in to many desirable pre-defined abilities that our new type can inherit.

# While we're here, let's do three-dimensional points as well:

struct Point3D <: AbstractArray{Float64, 1}
    x::Float64
    y::Float64
    z::Float64
end

Base.size(pt::Point3D) = (3, )

# ## Conversion

# The primary task we have to address is how to convert a 4D point into a 2D point. Let's start with the easier task: how to convert a 3D point into a 2D point, ie how can we draw a 3D shape on a flat surface?

# Consider a simple cube. The front face and the back face could have the same X and Y coordinates, and vary only by their Z values.

# (This is just a diagram, it's not really 3D...)

# ![image label](IMAGEFOLDER/cube.svg){: .center-image}

using Luxor

@svg begin
    fontface("Menlo")
    fontsize(8)
    setblend(blend(
        boxtopcenter(BoundingBox()),
        boxmiddlecenter(BoundingBox()),
        "skyblue",
        "white"))
    box(boxtopleft(BoundingBox()),
        boxmiddleright(BoundingBox()), :fill)

    setblend(blend(
        boxmiddlecenter(BoundingBox()),
        boxbottomcenter(BoundingBox()),
        "grey95",
        "grey45"
        ))
    box(boxmiddleleft(BoundingBox()),
        boxbottomright(BoundingBox()), :fill)

    sethue("black")
    setline(2)
    bx1 = box(O, 250, 250, vertices=true)
    poly(bx1, :stroke, close=true)
    label.(["-1/1/1", "-1/-1/1", "1/-1/1", "1/1/1"],
        slope.(O, bx1), bx1)

    setline(1)
    bx2 = box(O, 150, 150, vertices=true)
    poly(bx2, :stroke, close=true)
    label.(["-1/1/0", "-1/-1/0", "1/-1/0", "1/1/0"],
        slope.(O, bx2), bx2, offset=-45)

    map((x, y) -> line(x, y, :stroke), bx1, bx2)
end 500 500 "images/pentachoron/cube.svg"

# So the idea is to project the cube from 3D to 2D by keeping the first two values, and multiplying or modifying them by the third value. We'll make a `convert()` function to do this:

function convert(Point, pt3::Point3D)
    k = 1/(K - pt3.z)
    return Point(pt3.x * k, pt3.y * k)
end

# `K` is just a constant which provides a consistent value for depth:

const K = 4.0

# Testing this quickly in Luxor.jl gives promising results:

@svg begin
    cube = Point3D[
        Point3D(-1, -1,  1),
        Point3D(-1,  1,  1),
        Point3D( 1, -1,  1),
        Point3D( 1,  1,  1),
        Point3D(-1, -1, -1),
        Point3D(-1,  1, -1),
        Point3D( 1, -1, -1),
        Point3D( 1,  1, -1),
    ]
    circle.(convert.(Point, cube) * 300, 5, :fill)
end 500 500 "images/pentachoron/basic3_to_2.png"

# ![image label](IMAGEFOLDER/basic3_to_2.png){: .center-image}

# It's a simple type of perspective projection.

# Using the same principle, let's make a method for converting a 4D point.

function convert(Point3D, pt4::Point4D)
    k = 1/(K - pt4.w)
    return Point3D(pt4.x * k, pt4.y * k, pt4.z * k)
end

# Let's hope it works.

# We can combine these into a single utility function called `flatten()` that takes a list of 4D points and double-maps them into a list of 2D points suitable for drawing.

function flatten(shape4)
    return map(pt3 -> convert(Point, pt3), map(pt4 -> convert(Point3D, pt4), shape4))
end

# ## Going up a level

# To test this, we'll define the vertices of a unit pentachoron:

const n = -1/√5

const pentachoron = [Point4D(vertex...) for vertex in [
    [ 1.0,  1.0,  1.0, n],
    [ 1.0, -1.0, -1.0, n],
    [-1.0,  1.0, -1.0, n],
    [-1.0, -1.0,  1.0, n],
    [ 0.0,  0.0,  0.0, n + √5]]]

#

#-

# (According to [Wikipedia](https://en.wikipedia.org/wiki/5-cell), other names for the pentachoron include:

# * Regular 5-cell
# * C5
# * pentatope
# * pentahedroid
# * tetrahedral pyramid
# * 4-simplex

# I think *pentachoron* is the coolest, though.)

#-

#

# Here's a list of "faces", with each face defined by three of the vertices:

const pentachoronfaces = [
    [1, 2, 3],
    [1, 2, 4],
    [1, 2, 5],
    [1, 3, 4],
    [1, 3, 5],
    [1, 4, 5],
    [2, 3, 4],
    [2, 3, 5],
    [2, 4, 5],
    [3, 4, 5]]

# A quick test:

@svg begin
    setopacity(0.2)
    pentachoron2D = flatten(pentachoron)
    for (n, face) in enumerate(pentachoronfaces)
        randomhue()
        poly(1500 * pentachoron2D[face], :fillpreserve, close=true)
        sethue("black")
        strokepath()
    end
end 600 250 "images/pentachoron/firstdraw.svg"

# ![image label](IMAGEFOLDER/firstdraw.svg){: .center-image}

# This isn't very interesting, although I *think* it's correct. To see a more appealing display, let's make it dance...

# ## Swings and roundabouts

# You usually rotate 2D points about a 1D point. You usually rotate 3D points about a 2D line (often one of the XYZ axes). So logically you'd rotate 4D points with reference to a 3D plane. We'll define some matrices that do 4D rotations relative to a plane defined by two of the X, Y, Z, and W axes.

# As I'm thinking about it, the XY plane is typically the plane of the drawing surface. If you think of the XY plane as a computer screen straight in front of you, the XZ plane is parallel to your desk or floor, and the YZ plane is like a wall beside your desk on your right or left side.

# But what about the XW plane? And the YW and ZW planes, for that matter? This is the mystery of 4D shapes: we can't see these planes, we can only imagine their existence by watching shapes moving through and around them.

# Here's a function that generates a suitable rotation matrix for rotating a 4D point in the XY plane:

function XY(θ)
    [cos(θ)   -sin(θ)  0        0;
    sin(θ)    cos(θ)   0        0;
    0         0        1        0;
    0         0        0        1]
end

# and here's another one that rotates a 4D point in the XW plane:

function XW(θ)
    [cos(θ)   0        0        -sin(θ);
    0         1        0        0;
    0         0        1        0;
    sin(θ)    0        0        cos(θ)]
end

# Oh, it looks like we're having a matrix party, so we might as well do the others while we're here:

function XZ(θ)
    [cos(θ)   0        -sin(θ)  0;
    0         1        0        0;
    sin(θ)    0        cos(θ)   0;
    0         0        0        1]
end

function YZ(θ)
    [1        0        0        0;
    0         cos(θ)   -sin(θ)  0;
    0         sin(θ)   cos(θ)   0;
    0         0        0        1]
end

function YW(θ)
    [1        0        0        0;
    0         cos(θ)   0        -sin(θ);
    0         0        1        0;
    0         sin(θ)   0        cos(θ)]
end

function ZW(θ)
    [1        0        0        0;
    0         1        0        0;
    0         0        cos(θ)   -sin(θ);
    0         0        sin(θ)   cos(θ)];
end

# We'll make a handy utility function that uses one of these matrix functions to rotate an array of 4D points `A` in the plane defined by the matrix function:

function rotate4(A, matrixfunction)
    return map(A) do pt4
        Point4D(matrixfunction * pt4)
    end
end

# So we can write `rotate4(A, XW(π/2))` to rotate the array of points in `A` in the XW plane by π/2. We can also write `rotate4(A, XW(π/2) * XY(π/2))` to rotate the points first in the XW plane and then in the XY plane.

# Notice that the `rotate4()` function returns a new array, rather than modifying the original one. This probably has some disadvantages, but for now it means that we don't have to keep track of mutating objects so carefully.

# ## It moves

# We're now able to make an animation that rotates our pentachoron in one or more planes.

# The `frame()` function generates a single frame, using the framenumber (`eased_n` provides a normalized version of the framenumber modulated by the easing function) to control the rotation between 0 and 2π. The scalefactor makes the unit shape big enough to fill the frame.

# It's helpful to color the faces very slightly, using `poly(..., :fill)`-er.

using ColorSchemes

function frame(scene, framenumber, scalefactor=1000)
    background("antiquewhite")
    setlinejoin("bevel")
    setline(1.0)
    sethue("black")
    eased_n       = scene.easingfunction(framenumber, 0, 1, scene.framerange.stop)
    pentachoron′  = rotate4(pentachoron, XZ(eased_n * 2π))
    pentachoron2D = flatten(pentachoron′)

    setopacity(0.2)
    for (n, face) in enumerate(pentachoronfaces)
        sethue(get(ColorSchemes.diverging_rainbow_bgymr_45_85_c67_n256,
            n/length(pentachoronfaces)))
        poly(scalefactor * pentachoron2D[face], :fillpreserve, close=true)
        sethue("black")
        strokepath()
    end
end

# And this function generates a series of frames and saves them to an animation:

function makemovie(w, h, fname;
        scalefactor=1000)
    movie1 = Movie(w, h, "4D movie")
    animate(movie1,
        Scene(movie1, (s, f)  -> frame(s, f, scalefactor),
            1:300,
            easingfunction=easeinoutsine),
        creategif=true,
        pathname="images/pentachoron/$(fname)")
end

makemovie(600, 600, "pentachoron-xz.gif", scalefactor=2000)

# This is a rotation in the "parallel to the desk my computer's resting on" XZ plane.

# ![image label](IMAGEFOLDER/pentachoron-xz.gif){: .center-image}

# It's not a realistic rendering with hidden-surface removal—I wouldn't know how to do that in 3D, let alone 4D. But it does make it slightly easier to follow some of the faces. Something to do one day might be to sort the "faces" to determine which are drawn first, but given the maths involved this is a challenge I'm dubious about tackling...

# We can modify `frame()` to allow various permutations of XY, XW, XZ, YZ, YW, and ZW rotations. Here's a version that rotates in XZ, followed by YW:

function frame(scene, framenumber, scalefactor=1000)
    background("antiquewhite")
    setlinejoin("bevel")
    setline(1.0)
    setopacity(0.2)

    eased_n       = scene.easingfunction(framenumber, 0, 1, scene.framerange.stop)
    pentachoron2D = flatten(
        rotate4(
            pentachoron,
                XZ(eased_n * 2π) *
                YW(eased_n * 2π)))
    for (n, face) in enumerate(pentachoronfaces)
        sethue(get(ColorSchemes.diverging_rainbow_bgymr_45_85_c67_n256,
            n/length(pentachoronfaces)))
        poly(scalefactor * pentachoron2D[face], :fillpreserve, close=true)
        sethue("black")
        strokepath()
    end
end

makemovie(800, 800, "pentachoron-xz-yw.gif", scalefactor=2000)

# ![image label](IMAGEFOLDER/pentachoron-xz-yw.gif){: .center-image}

# These are starting to make me think of kneading machines for making pizza dough.

# You can draw a few different models at once, placing each one in a separate cell:

# ![image label](IMAGEFOLDER/showall-pentachoronmovie.gif){: .center-image}

# This is showing 2D shadows of 3D shadows of 4D objects. Do these objects exist? I don't know...

# ### Other shapes

# While the word "pentachoron" was unfamiliar to me until recently, the word "tesseract" is much more familiar. After all, didn't the tesseract contain the Space Stone, one of the six Infinity Stones that apparently predate the universe and possess unlimited energy? (According to the scientists at Marvel Comics, at least...) It's also the name of a 4-dimensional hypercube, one step up from the pentachoron.

# Here are the vital numbers for the tesseract:

const tesseract = [Point4D(vertex...) for vertex in [
    [-1, -1, -1,  1],
    [ 1, -1, -1,  1],
    [ 1,  1, -1,  1],
    [-1,  1, -1,  1],
    [-1, -1,  1,  1],
    [ 1, -1,  1,  1],
    [ 1,  1,  1,  1],
    [-1,  1,  1,  1],
    [-1, -1, -1, -1],
    [ 1, -1, -1, -1],
    [ 1,  1, -1, -1],
    [-1,  1, -1, -1],
    [-1, -1,  1, -1],
    [ 1, -1,  1, -1],
    [ 1,  1,  1, -1],
    [-1,  1,  1, -1]]]

const tesseractfaces = [
    [1,   2,   3,   4],
    [1,   2,  10,   9],
    [1,   4,   8,   5],
    [1,   5,   6,   2],
    [1,   9,  12,   4],
    [2,   3,  11,  10],
    [2,   3,   7,   6],
    [3,   4,   8,   7],
    [5,   6,  14,  13],
    [5,   6,   7,   8],
    [5,   8,  16,  13],
    [6,   7,  15,  14],
    [7,   8,  16,  15],
    [9,  10,  11,  12],
    [9,  10,  14,  13],
    [9,  13,  16,  12],
    [10, 11,  15,  14],
    [13, 14,  15,  16]]

#-
# Replace `pentachoron` everywhere by `tesseract` in the above code, choosing the rotations to taste.
#-

function frame(scene, framenumber, scalefactor=1000)
    background("black")
    setlinejoin("bevel")
    setline(10.0)
    setopacity(0.7)

    eased_n       = scene.easingfunction(framenumber, 0, 1, scene.framerange.stop)
    tesseract2D = flatten(
        rotate4(
            tesseract,
                XZ(eased_n * 2π) *
                YW(eased_n * 2π)))
    for (n, face) in enumerate(tesseractfaces)
        sethue([Luxor.lighter_blue, Luxor.lighter_green,
            Luxor.lighter_purple, Luxor.lighter_red][mod1(n, 4)]...)
        poly(scalefactor * tesseract2D[face], :fillpreserve, close=true)
        sethue([Luxor.darker_blue, Luxor.darker_green,
            Luxor.darker_purple, Luxor.darker_red][mod1(n, 4)]...)
        strokepath()
    end
end

makemovie(800, 800, "tesseract-xz-yw.gif", scalefactor=2000)

# ![image label](IMAGEFOLDER/tesseract-xz-yw.gif){: .center-image}

# (Just for fun I temporarily switched the color scheme over to the Julia logo colors. You can see the 3D rendering problems more clearly, as well!)

# Here's a composite of four pairs of rotations:

# ![image label](IMAGEFOLDER/showall-tesseractmovie.gif){: .center-image}

# ## Enough is enough

# It's tempting to go further, but these web pages do get very big with all these GIFs. But just one more... I quite like this one, the *hexadecachoron*:

const hexadecachoron = [Point4D(vertex...) for vertex in [
        [ 1,  0,  0,  0],
        [ 0,  1,  0,  0],
        [ 0,  0,  1,  0],
        [ 0,  0,  0,  1],
        [-1,  0,  0,  0],
        [ 0, -1,  0,  0],
        [ 0,  0, -1,  0],
        [ 0,  0,  0, -1]]]

using Combinatorics

const hexadecachoronfaces = combinations(1:8, 3)

function frame(scene, framenumber, scalefactor=1000)
    background("antiquewhite")
    setlinejoin("bevel")
    setline(1.0)
    setopacity(0.15)
    eased_n          =
        scene.easingfunction(framenumber, 0, 1, scene.framerange.stop)
    hexadecachoron2D = flatten(
        rotate4(
            hexadecachoron,
                YZ(eased_n * 2π) *
                XZ(eased_n * 2π) *
                YW(eased_n * 2π)))
    for (n, face) in enumerate(hexadecachoronfaces)
        sethue(get(ColorSchemes.magma,
            n/length(hexadecachoronfaces)))
        poly(scalefactor * hexadecachoron2D[face], :fillpreserve, close=true)
        sethue("black")
        strokepath()
    end
end

makemovie(650, 650, "movie-hexadecachoron.gif", scalefactor=5000)

# ![image label](IMAGEFOLDER/movie-hexadecachoron.gif){: .center-image}

# There's lots more fun to be had (combining two or more different shapes is fun), but I'm worried about your network and my brain cells, so that's enough animated GIFs for today.

# [2018-04-03]

# ![cormullion signing off](http://steampiano.net/cormullionknot.gif?pentachoron){: .center-image}

                                                                                #src
using Literate                                                                           #src
# preprocess for notebooks                                                               #src
function setimagefolder(content)                                                         #src
    content = replace(content, "IMAGEFOLDER" => "$IMAGEFOLDER")                          #src
    return content                                                                       #src
end                                                                                #src
# for Jupyter notebook, put images in subfolder                                          #src
#IMAGEFOLDER = "images/pentachoron"                                                       #src
#Literate.notebook("source/pentachoron.jl", "notebooks", preprocess = setimagefolder)     #src
# for Markdown/Jekyll notebook, put images in "/images"                                  #src
IMAGEFOLDER = "/images/pentachoron"                                                      #src
Literate.markdown("source/pentachoron.jl", ".", name="_posts/2019-04-03-pentachoron",    #src
 preprocess = setimagefolder,                                                            #src
 codefence = "{% highlight julia %}" => "{% endhighlight julia %}",                      #src
 documenter=false)                                                                       #src
                                                                                #src


# ------------------------------------------------------------------------------------------
# # Callbacks and Events
#
# In working with a differential equation, our system will evolve through many states.
# Particular states of the system may be of interest to us, and we say that an ***"event"***
# is triggered when our system reaches these states. For example, events may include the
# moment when our system reaches a particular temperature or velocity. We ***handle*** these
# events with ***callbacks***, which tell us what to do once an event has been triggered.
#
# These callbacks allow for a lot more than event handling, however. For example, we can use
# callbacks to achieve high-level behavior like exactly preserve conservation laws and save
# the trace of a matrix at pre-defined time points. This extra functionality allows us to
# use the callback system as a modding system for the DiffEq ecosystem's solvers.
#
# This tutorial is an introduction to the callback and event handling system in
# DifferentialEquations.jl, documented in the [Event Handling and Callback
# Functions](http://docs.juliadiffeq.org/latest/features/callback_functions.html) page of
# the documentation. We will also introduce you to some of the most widely used callbacks in
# the [Callback Library](http://docs.juliadiffeq.org/latest/features/callback_library.html),
# which is a library of pre-built mods.
# ------------------------------------------------------------------------------------------

# ------------------------------------------------------------------------------------------
# ## Events and Continuous Callbacks
#
# Event handling is done through continuous callbacks. Callbacks take a function,
# `condition`, which triggers an `affect!` when `condition == 0`. These callbacks are called
# "continuous" because they will utilize rootfinding on the interpolation to find the
# "exact" time point at which the condition takes place and apply the `affect!` at that time
# point.
#
# ***Let's use a bouncing ball as a simple system to explain events and callbacks.*** Let's
# take Newton's model of a ball falling towards the Earth's surface via a gravitational
# constant `g`. In this case, the velocity is changing via `-g`, and position is changing
# via the velocity. Therefore we receive the system of ODEs:
# ------------------------------------------------------------------------------------------

using DifferentialEquations
ball! = @ode_def BallBounce begin
  dy =  v
  dv = -g
end g

# ------------------------------------------------------------------------------------------
# We want the callback to trigger when `y=0` since that's when the ball will hit the Earth's
# surface -- our event. We do this with the condition:
# ------------------------------------------------------------------------------------------

function condition(u,t,integrator) # Event when event_f(u,t) == 0
  u[1]
end

# ------------------------------------------------------------------------------------------
# Recall that the `condition` will trigger when it evaluates to zero, and here it will
# evaluate to zero when `u[1] == 0`, which occurs when `v == 0`. *Now we have to say what we
# want the callback to do.* Callbacks make use of the [Integrator
# Interface](http://docs.juliadiffeq.org/latest/basics/integrator.html). Instead of giving a
# full description, a quick and usable rundown is:
#
# - Values are strored in `integrator.u`
# - Times are stored in `integrator.t`
# - The parameters are stored in `integrator.p`
# - `integrator(t)` performs an interpolation in the current interval between
# `integrator.tprev` and `integrator.t` (and allows extrapolation)
# - User-defined options (tolerances, etc.) are stored in `integrator.opts`
# - `integrator.sol` is the current solution object. Note that `integrator.sol.prob` is the
# current problem
#
# While there's a lot more on the integrator interface page, that's a working knowledge of
# what's there.
#
# What we want to do with our `affect!` is to "make the ball bounce". Mathematically
# speaking, the ball bounces when the sign of the velocity flips. As an added behavior,
# let's also use a small friction constant to dampen the ball's velocity. This way only a
# percentage of the velocity will be retained when the event is triggered and the callback
# is used.  We'll define this behavior in the `affect!` function:
# ------------------------------------------------------------------------------------------

function affect!(integrator)
    integrator.u[2] = -integrator.p[2] * integrator.u[2]
end

# ------------------------------------------------------------------------------------------
# `integrator.u[2]` is the second value of our model, which is `v` or velocity, and
# `integrator.p[2]`, is our friction coefficient.
#
# Therefore `affect!` can be read as follows: `affect!` will take the current value of
# velocity, and multiply it `-1` multiplied by our friction coefficient. Therefore the ball
# will change direction and its velocity will dampen when `affect!` is called.
#
# Now let's build the `ContinuousCallback`:
# ------------------------------------------------------------------------------------------

bounce_cb = ContinuousCallback(condition,affect!)

# ------------------------------------------------------------------------------------------
# Now let's make an `ODEProblem` which has our callback:
# ------------------------------------------------------------------------------------------

u0 = [50.0,0.0]
tspan = (0.0,15.0)
p = (9.8,0.9)
prob = ODEProblem(ball!,u0,tspan,p,callback=bounce_cb)

# ------------------------------------------------------------------------------------------
# Notice that we chose a friction constant of `0.9`. Now we can solve the problem and plot
# the solution as we normally would:
# ------------------------------------------------------------------------------------------

sol = solve(prob,Tsit5())
using Plots; gr()
plot(sol)

# ------------------------------------------------------------------------------------------
# and tada, the ball bounces! Notice that the `ContinuousCallback` is using the
# interpolation to apply the effect "exactly" when `v == 0`. This is crucial for model
# correctness, and thus when this property is needed a `ContinuousCallback` should be used.
# ------------------------------------------------------------------------------------------

# ------------------------------------------------------------------------------------------
# #### Exercise 1
#
# In our example we used a constant coefficient of friction, but if we are bouncing the ball
# in the same place we may be smoothing the surface (say, squishing the grass), causing
# there to be less friction after each bounce. In this more advanced model, we want the
# friction coefficient at the next bounce to be `sqrt(friction)` from the previous bounce
# (since `friction < 1`, `sqrt(friction) > friction` and `sqrt(friction) < 1`).
#
# Hint: there are many ways to implement this. One way to do it is to make `p` a `Vector`
# and mutate the friction coefficient in the `affect!`.
# ------------------------------------------------------------------------------------------

# ------------------------------------------------------------------------------------------
# ## Discrete Callbacks
#
# A discrete callback checks a `condition` after every integration step and, if true, it
# will apply an `affect!`. For example, let's say that at time `t=2` we want to include that
# a kid kicked the ball, adding `20` to the current velocity. This kind of situation, where
# we want to add a specific behavior which does not require rootfinding, is a good candidate
# for a `DiscreteCallback`. In this case, the `condition` is a boolean for whether to apply
# the `affect!`, so:
# ------------------------------------------------------------------------------------------

function condition_kick(u,t,integrator)
    t == 2
end

# ------------------------------------------------------------------------------------------
# We want the kick to occur at `t=2`, so we check for that time point. When we are at this
# time point, we want to do:
# ------------------------------------------------------------------------------------------

function affect_kick!(integrator)
    integrator.u[2] += 50
end

# ------------------------------------------------------------------------------------------
# Now we build the problem as before:
# ------------------------------------------------------------------------------------------

kick_cb = DiscreteCallback(condition_kick,affect_kick!)
u0 = [50.0,0.0]
tspan = (0.0,10.0)
p = (9.8,0.9)
prob = ODEProblem(ball!,u0,tspan,p,callback=kick_cb)

# ------------------------------------------------------------------------------------------
# Note that, since we are requiring our effect at exactly the time `t=2`, we need to tell
# the integration scheme to step at exactly `t=2` to apply this callback. This is done via
# the option `tstops`, which is like `saveat` but means "stop at these values".
# ------------------------------------------------------------------------------------------

sol = solve(prob,Tsit5(),tstops=[2.0])
plot(sol)

# ------------------------------------------------------------------------------------------
# Note that this example could've been done with a `ContinuousCallback` by checking the
# condition `t-2`.
# ------------------------------------------------------------------------------------------

# ------------------------------------------------------------------------------------------
# ## Merging Callbacks with Callback Sets
#
# In some cases you may want to merge callbacks to build up more complex behavior. In our
# previous result, notice that the model is unphysical because the ball goes below zero!
# What we really need to do is add the bounce callback together with the kick. This can be
# achieved through the `CallbackSet`.
# ------------------------------------------------------------------------------------------

cb = CallbackSet(bounce_cb,kick_cb)

# ------------------------------------------------------------------------------------------
# A `CallbackSet` merges their behavior together. The logic is as follows. In a given
# interval, if there are multiple continuous callbacks that would trigger, only the one that
# triggers at the earliest time is used. The time is pulled back to where that continuous
# callback is triggered, and then the `DiscreteCallback`s in the callback set are called in
# order.
# ------------------------------------------------------------------------------------------

u0 = [50.0,0.0]
tspan = (0.0,15.0)
p = (9.8,0.9)
prob = ODEProblem(ball!,u0,tspan,p,callback=cb)
sol = solve(prob,Tsit5(),tstops=[2.0])
plot(sol)

# ------------------------------------------------------------------------------------------
# Notice that we have now merged the behaviors. We can then nest this as deep as we like.
#
# #### Exercise 2
#
# Add to the model a linear wind with resistance that changes the acceleration to `-g + k*v`
# after `t=10`. Do so by adding another parameter and allowing it to be zero until a
# specific time point where a third callback triggers the change.
# ------------------------------------------------------------------------------------------

# ------------------------------------------------------------------------------------------
# ## Integration Termination and Directional Handling
#
# Let's look at another model now -- the model of the [Harmonic
# Oscillator](https://en.wikipedia.org/wiki/Harmonic_oscillator). We can write this as:
# ------------------------------------------------------------------------------------------

u0 = [1.,0.]
harmonic! = @ode_def HarmonicOscillator begin
   dv = -x
   dx = v
end
tspan = (0.0,10.0)
prob = ODEProblem(harmonic!,u0,tspan)
sol = solve(prob)
plot(sol)

# ------------------------------------------------------------------------------------------
# Let's instead stop the integration when a condition is met. From the [Integrator Interface
# stepping controls](http://docs.juliadiffeq.org/latest/basics/integrator.html#Stepping-
# Controls-1) we see that `terminate!(integrator)` will cause the integration to end. So our
# new `affect!` is simply:
# ------------------------------------------------------------------------------------------

function terminate_affect!(integrator)
    terminate!(integrator)
end

# ------------------------------------------------------------------------------------------
# Let's first stop the integration when the particle moves back to `x=0`. This means we want
# to use the condition:
# ------------------------------------------------------------------------------------------

function terminate_condition(u,t,integrator)
    u[2]
end
terminate_cb = ContinuousCallback(terminate_condition,terminate_affect!)

# ------------------------------------------------------------------------------------------
# Note that instead of adding callbacks to the problem, we can also add them to the `solve`
# command. This will automatically form a `CallbackSet` with any problem-related callbacks
# and naturally allows you to distinguish between model features and integration controls.
# ------------------------------------------------------------------------------------------

sol = solve(prob,callback=terminate_cb)
plot(sol)

# ------------------------------------------------------------------------------------------
# Notice that the harmonic oscilator's true solution here is `sin` and `cosine`, and thus we
# would expect this return to zero to happen at `t=π`:
# ------------------------------------------------------------------------------------------

sol.t[end]

# ------------------------------------------------------------------------------------------
# This is one way to approximate π! Lower tolerances and arbitrary precision numbers can
# make this more exact, but let's not look at that. Instead, what if we wanted to halt the
# integration after exactly one cycle? To do so we would need to ignore the first zero-
# crossing. Luckily in these types of scenarios there's usually a structure to the problem
# that can be exploited. Here, we only want to trigger the `affect!` when crossing from
# positive to negative, and not when crossing from negative to positive. In other words, we
# want our `affect!` to only occur on upcrossings.
#
# If the `ContinuousCallback` constructor is given a single `affect!`, it will occur on both
# upcrossings and downcrossings. If there are two `affect!`s given, then the first is for
# upcrossings and the second is for downcrossings. An `affect!` can be ignored by using
# `nothing`. Together, the "upcrossing-only" version of the effect means that the first
# `affect!` is what we defined above and the second is `nothing`. Therefore we want:
# ------------------------------------------------------------------------------------------

terminate_upcrossing_cb = ContinuousCallback(terminate_condition,terminate_affect!,nothing)

# ------------------------------------------------------------------------------------------
# Which gives us:
# ------------------------------------------------------------------------------------------

sol = solve(prob,callback=terminate_upcrossing_cb)
plot(sol)

# ------------------------------------------------------------------------------------------
# ## Callback Library
#
# As you can see, callbacks can be very useful and through `CallbackSets` we can merge
# together various behaviors. Because of this utility, there is a library of pre-built
# callbacks known as the [Callback
# Library](http://docs.juliadiffeq.org/latest/features/callback_library.html). We will walk
# through a few examples where these callbacks can come in handy.
# ------------------------------------------------------------------------------------------

# ------------------------------------------------------------------------------------------
# ### Manifold Projection
#
# One callback is the manifold projection callback. Essentially, you can define any manifold
# `g(sol)=0` which the solution must live on, and cause the integration to project to that
# manifold after every step. As an example, let's see what happens if we naively run the
# harmonic oscillator for a long time:
# ------------------------------------------------------------------------------------------

tspan = (0.0,10000.0)
prob = ODEProblem(harmonic!,u0,tspan)
sol = solve(prob)
gr(fmt=:png) # Make it a PNG instead of an SVG since there's a lot of points!
plot(sol,vars=(1,2))

plot(sol,vars=(0,1),denseplot=false)

# ------------------------------------------------------------------------------------------
# Notice that what's going on is that the numerical solution is drifting from the true
# solution over this long time scale. This is because the integrator is not conserving
# energy.
# ------------------------------------------------------------------------------------------

plot(sol.t,[u[2]^2 + u[1]^2 for u in sol.u]) # Energy ~ x^2 + v^2

# ------------------------------------------------------------------------------------------
# Some integration techniques like [symplectic
# integrators](http://docs.juliadiffeq.org/latest/solvers/dynamical_solve.html#Symplectic-
# Integrators-1) are designed to mitigate this issue, but instead let's tackle the problem
# by enforcing conservation of energy. To do so, we define our manifold as the one where
# energy equals 1 (since that holds in the initial condition), that is:
# ------------------------------------------------------------------------------------------

function g(resid,u,p,t)
  resid[1] = u[2]^2 + u[1]^2 - 1
  resid[2] = 0
end

# ------------------------------------------------------------------------------------------
# Here the residual measures how far from our desired energy we are, and the number of
# conditions matches the size of our system (we ignored the second one by making the
# residual 0). Thus we define a `ManifoldProjection` callback and add that to the solver:
# ------------------------------------------------------------------------------------------

cb = ManifoldProjection(g)
sol = solve(prob,callback=cb)
plot(sol,vars=(1,2))

plot(sol,vars=(0,1),denseplot=false)

# ------------------------------------------------------------------------------------------
# Now we have "perfect" energy conservation, where if it's ever violated too much the
# solution will get projected back to `energy=1`.
# ------------------------------------------------------------------------------------------

u1,u2 = sol[500]
u2^2 + u1^2

# ------------------------------------------------------------------------------------------
# While choosing different integration schemes and using lower tolerances can achieve this
# effect as well, this can be a nice way to enforce physical constraints and is thus used in
# many disciplines like molecular dynamics. Another such domain constraining callback is the
# [`PositiveCallback()`](http://docs.juliadiffeq.org/latest/features/callback_library.html#P
# ositiveDomain-1) which can be used to enforce positivity of the variables.
# ------------------------------------------------------------------------------------------

# ------------------------------------------------------------------------------------------
# ### SavingCallback
#
# The `SavingCallback` can be used to allow for special saving behavior. Let's take a linear
# ODE define on a system of 1000x1000 matrices:
# ------------------------------------------------------------------------------------------

prob = ODEProblem((du,u,p,t)->du.=u,rand(1000,1000),(0.0,1.0))

# ------------------------------------------------------------------------------------------
# In fields like quantum mechanics you may only want to know specific properties of the
# solution such as the trace or the norm of the matrix. Saving all of the 1000x1000 matrices
# can be a costly way to get this information! Instead, we can use the `SavingCallback` to
# save the `trace` and `norm` at specified times. To do so, we first define our
# `SavedValues` cache. Our time is in terms of `Float64`, and we want to save tuples of
# `Float64`s (one for the `trace` and one for the `norm`), and thus we generate the cache
# as:
# ------------------------------------------------------------------------------------------

saved_values = SavedValues(Float64, Tuple{Float64,Float64})

# ------------------------------------------------------------------------------------------
# Now we define the `SavingCallback` by giving it a function of `(u,p,t,integrator)` that
# returns the values to save, and the cache:
# ------------------------------------------------------------------------------------------

cb = SavingCallback((u,t,integrator)->(trace(u),norm(u)), saved_values)

# ------------------------------------------------------------------------------------------
# Here we take `u` and save `(trace(u),norm(u))`. When we solve with this callback:
# ------------------------------------------------------------------------------------------

sol = solve(prob, Tsit5(), callback=cb, save_everystep=false, save_start=false, save_end = false) # Turn off normal saving

# ------------------------------------------------------------------------------------------
# Our values are stored in our `saved_values` variable:
# ------------------------------------------------------------------------------------------

saved_values.t

saved_values.saveval

# ------------------------------------------------------------------------------------------
# By default this happened only at the solver's steps. But the `SavingCallback` has similar
# controls as the integrator. For example, if we want to save at every `0.1` seconds, we do
# can so using `saveat`:
# ------------------------------------------------------------------------------------------

saved_values = SavedValues(Float64, Tuple{Float64,Float64}) # New cache
cb = SavingCallback((u,t,integrator)->(trace(u),norm(u)), saved_values, saveat = 0.0:0.1:1.0)
sol = solve(prob, Tsit5(), callback=cb, save_everystep=false, save_start=false, save_end = false) # Turn off normal saving

saved_values.t

saved_values.saveval

# ------------------------------------------------------------------------------------------
# #### Exercise 3
#
# Go back to the Harmonic oscillator. Use the `SavingCallback` to save an array for the
# energy over time, and do this both with and without the `ManifoldProjection`. Plot the
# results to see the difference the projection makes.
# ------------------------------------------------------------------------------------------


# Utility functions, mostly for unit conversions.
# NMR quantities in Bruker notation [1]:
# BF1: Basic transmitter frequency (MHz)
# SF: Spectrometer frequency (MHz)
# SR: Spectrum reference frequency (Hz)
# SR = (SF - BF1) * 10^6
# SFO1: Transmitter frequency (MHz)
# O1: Transmitter frequency offset (Hz)
# O1 = (SFO1 - BF1) * 10^6
# Ω: Absolute frequency (MHz)
# ω: Frequency relative to SF1 (Hz)
# δ: Chemical shift δ (ppm)
# δ = 10^6 * (Ω - SF) / SF = ω / SF
# 1. Numerical indices indicate different nuclei

import Base: in

limits(s::Spectrum) = limits(s["O1P"], s["SW"], s["SF"], s["BF1"])
limits(o1p, sw, sf, bf) = begin
    shift = 1e6(bf-sf)/bf
    (o1p - sw/2 + shift, o1p + sw/2 + shift)
end

function in(δ, s::Spectrum)
    l,h = limits(s)
    l < δ < h
end

function chemical_shifts(s::Spectrum)
    lo,hi = limits(s)
    range(hi; stop=lo, length=length(s[:]))
end

"""
    ppmtomhz_abs(δ, bf[, sr])
Convert chemical shift δ to absolute frequency Ω in MHz.
bf in MHz and sr in Hz.
"""
function ppmtomhz_abs(δ, bf, sr = 0.0)
    bf = bf * 1e6
    1e-6(bf+sr)δ + bf + sr
end

"""
    ppmtohz(δ, bf[, sr])
Convert chemical shift δ in ppm to relative frequency ω in Hz.
bf in MHz and sr in Hz.
"""
function ppmtohz(δ, bf, sr = 0.0)
    (bf + sr*1e-6)δ
end

"""
    hztoppm(ω, bf[, sr])
Convert relative frequency ω in Hz to chemical shift δ in ppm .
bf in MHz and sr in Hz.
"""
function hztoppm(ω, bf, sr = 0.0)
    ω / (bf + sr*1e-6)
end

"""
    sr(sf, bf)
Return spectral reference (SR in Bruker notation) in Hz.
sf and bf in MHz.
"""
sr(sf, bf) = 1e6(sf - bf)

"""
    ppmtoindex(::Spectrum, δ)
Return the index in the processed spectrum corresponding to
chemical shift δ.
"""
function ppmtoindex(s::Spectrum, δ)
    min_δ ,max_δ = limits(s)
    @. Int(cld(s["SI"]*(max_δ - δ), (max_δ - min_δ)))
end

function ppmtoindex(s::Spectrum, rng::Tuple{Float64,Float64})
    r1,r2 = rng
    if r1>r2
        ppmtoindex(s,r1):ppmtoindex(s,r2)
    else
        ppmtoindex(s,r2):ppmtoindex(s,r1)
    end
end

hztoindex(f, sw, sf, si) = Int(cld(f*si, sw*sf))
hztoindex(s::Spectrum, f) = hztoindex(f, s["SW"], s["SF"], s["SI"])

Base.length(s::Spectrum) = length(s[:])
Base.length(p::ProcessedSpectrum) = length(p[:])

function union_range(ss::AbstractArray{Spectrum})
    lims = [limits(s) for s in ss]
    l = minimum(lim[1] for lim in lims)
    h = maximum(lim[2] for lim in lims)
    h,l
end

function union_shifts(ss::AbstractArray{Spectrum})
    h,l = union_range(ss)
    res = minimum(freq_resolution(s) for s in ss)
    h:-res:l
end

union_range(s::Spectrum) = union_range([s])
union_shifts(s::Spectrum) = union_shifts([s])

freq_resolution(s::Spectrum) = s["SW"] / length(s)

title(s::Spectrum) = s[s.default_proc].title

# Returns a copy of s with all but the given
# ranges zeroed out. The default proc for s will
# have its intrng adjusted to Δs as well.
function extract(s::Spectrum, Δs::AbstractArray{Intrng})
    res = deepcopy(s)
    res[res.default_proc].re_ft = zeros(length(s))
    res[res.default_proc].im_ft = zeros(length(s))
    res[res.default_proc].intrng = Δs
    for Δ in Δs
        rng = ppmtoindex(s, Δ)
        res[res.default_proc].re_ft[rng] .= s[s.default_proc].re_ft[rng]
        res[res.default_proc].im_ft[rng] .= s[s.default_proc].im_ft[rng]
    end
    res
end

extract(s::Spectrum, Δ::Intrng) = extract(s, [Δ])

"""Tunes `param` until `expr` evaluates to zero within δ.
`expr` must be monotonically increasing in terms of `param`."""
macro binary_opt(expr, param, min, max, δ, nattempts=5)
    :(m = $(esc(min));M = $(esc(max));d=$(esc(δ));z=zero(d);n=$(esc(nattempts));
    for i=1:n
        $(esc(param)) = (m+M)/2
        e = $(esc(expr))
        # println("val: $e, param:$((m+M)/2), min: $m, max: $M")
        if norm(e) < d
            break
        elseif e < z
            m = $(esc(param))
        else
            M = $(esc(param))
        end
    end)
end

### Pulse power profile

"""     powerprofile(pulse, dt, sfo1)
Pulse power profile for arbitrary pulse shape.
pulse: Amplitude profile of pulse.
dt: 'Sampling frequency' — unit of time between points in time. (s)
sfo1: Centre frequency of pulse. (Hz)"""
function powerprofile(pulse, dt, sfo1)
    ft = abs2.(fft(pulse))
    l = length(pulse)
    df = 1/(l*dt)
    fn = interpolate(ft[1:div(l,2)], BSpline(Cubic(Natural())), OnGrid())
    max = fn[1]
    f -> fn[abs(f-sfo1)/df+1]/max
end


using ArbNumerics
using Test

setprecision(BigFloat, 512)

# @testset "Support" begin
#    include("ArblibVector.jl")
# end

@testset "ArbNumerics tests" begin
  include("specialvalues.jl")
  include("compare.jl")
  include("arithmetic.jl")
  include("elementaryfunctions.jl")
  include("misc.jl")
  include("complex.jl")
  include("dft.jl")
end


export RobustLoss,
       HuberRho, Huber, AndrewsRho, Andrews,
       BisquareRho, Bisquare, LogisticRho, Logistic,
       FairRho, Fair, TalwarRho, Talwar, QuantileRho, Quantile

abstract type RobustRho end

abstract type RobustRho1P{δ} <: RobustRho end # one parameter

struct RobustLoss{ρ} <: AtomicLoss where ρ <: RobustRho
   rho::ρ
end

(rl::RobustLoss)(x::AVR, y::AVR) = rl(x .- y)
(rl::RobustLoss)(r::AVR) = rl.rho(r)

# ψ(r) = ρ'(r)     (first derivative)
# ω(r) = ψ(r)/r    (weighing function) a threshold can be passed to clip weights
# ϕ(r) = ψ'(r)     (second derivative)

"""
$TYPEDEF

Huber weighing of the residuals corresponding to

``ρ(z) = z²/2``  if `|z|≤δ` and `ρ(z)=δ(|z|-δ/2)` otherwise.
"""
struct HuberRho{δ} <: RobustRho1P{δ}
   HuberRho(δ::Real=1.0; delta::Real=δ) = new{delta}()
end
Huber(δ::Real=1.0; delta::Real=δ) = HuberRho(delta)

(::HuberRho{δ})(r::AVR) where δ = begin
   ar = abs.(r)
   w  = ar .<= δ
   return sum( r.^2/2 .* w .+ δ .* (ar .- δ/2) .* .!w )
end

ψ(::Type{HuberRho{δ}}   ) where δ = (r, w) -> r * w + δ * sign(r) * (1.0 - w)
ω(::Type{HuberRho{δ}}, _) where δ = (r, w) -> w + (δ / abs(r)) * (1.0 - w)
ϕ(::Type{HuberRho{δ}}   ) where δ = (r, w) -> w

"""
$TYPEDEF

Andrews weighing of the residuals corresponding to

``ρ(z) = -cos(πz/δ)/(π/δ)²`` if `|z|≤δ` and `ρ(δ)` otherwise.
"""
struct AndrewsRho{δ} <: RobustRho1P{δ}
   AndrewsRho(δ::Real=1.0; delta::Real=δ) = new{delta}()
end
Andrews(δ::Real=1.0; delta::Real=δ) = AndrewsRho(delta)

(::AndrewsRho{δ})(r::AVR) where δ = begin
   ar = abs.(r)
   w  = ar .<= δ
   c  = π/δ
   κ  = (δ/π)^2
   return sum( -cos.(c .* r) .* κ .* w .+ κ .* .!w )
end

# Note, sinc(x) = sin(πx)/πx, well defined everywhere

ψ(::Type{AndrewsRho{δ}}   ) where δ = (r, w) -> (c = π/δ; w * sin(c * r) / c)
ω(::Type{AndrewsRho{δ}}, _) where δ = (r, w) -> w * sinc(r / δ)
ϕ(::Type{AndrewsRho{δ}}   ) where δ = (r, w) -> (cr = (π/δ) * r; w * cos(cr))

"""
$TYPEDEF

Bisquare weighing of the residuals corresponding to

``ρ(z) = δ²/6 (1-(1-(z/δ)²)³)`` if `|z|≤δ` and `δ²/6` otherwise.
"""
struct BisquareRho{δ} <: RobustRho1P{δ}
   BisquareRho(δ::Real=1.0; delta::Real=δ) = new{delta}()
end
Bisquare(δ::Real=1.0; delta::Real=δ) = BisquareRho(delta)

(::BisquareRho{δ})(r::AVR) where δ = begin
   ar = abs.(r)
   w  = ar .<= δ
   κ  = δ^2/6
   return sum( κ * (1.0 .- (1.0 .- (r ./ δ).^2).^3) .* w + κ .* .!w )
end

ψ(::Type{BisquareRho{δ}}   ) where δ = (r, w) -> w * r * (1.0 - (r / δ)^2)^2
ω(::Type{BisquareRho{δ}}, _) where δ = (r, w) -> w * (1.0 - (r / δ)^2)^2
ϕ(::Type{BisquareRho{δ}}   ) where δ = (r, w) -> (sr = r / δ; w * (1.0 + 5sr^4 - 6sr^2))

"""
$TYPEDEF

Logistic weighing of the residuals corresponding to

``ρ(z) = δ² log(cosh(z/δ))``
"""
struct LogisticRho{δ} <: RobustRho1P{δ}
   LogisticRho(δ::Real=1.0; delta::Real=δ) = new{delta}()
end
Logistic(δ::Real=1.0; delta::Real=δ) = LogisticRho(delta)

(::LogisticRho{δ})(r::AVR) where δ = begin
   return sum( δ^2 .* log.(cosh.(r ./ δ)) )
end

# similar to sinc, to avoid NaNs if tanh(0)/0 (lim is 1.0)
tanhc(x::Real) = ifelse(iszero(x), one(x), tanh(x)/x)

ψ(::Type{LogisticRho{δ}}   ) where δ = (r, _) -> δ * tanh(r / δ)
ω(::Type{LogisticRho{δ}}, _) where δ = (r, _) -> tanhc(r / δ)
ϕ(::Type{LogisticRho{δ}}   ) where δ = (r, w) -> sech(r / δ)^2

"""
$TYPEDEF

Fair weighing of the residuals corresponding to

``ρ(z) = δ² (|z|/δ - log(1+|z|/δ))``
"""
struct FairRho{δ} <: RobustRho1P{δ}
   FairRho(δ::Real=1.0; delta::Real=δ) = new{delta}()
end
Fair(δ::Real=1.0; delta::Real=δ) = FairRho(delta)

(::FairRho{δ})(r::AVR) where δ = begin
   sr = abs.(r) ./ δ
   return sum( δ^2 .* (sr .- log1p.(sr)) )
end

ψ(::Type{FairRho{δ}}   ) where δ = (r, _) -> δ * r / (abs(r) + δ)
ω(::Type{FairRho{δ}}, _) where δ = (r, _) -> δ / (abs.(r) + δ)
ϕ(::Type{FairRho{δ}}   ) where δ = (r, _) -> δ^2 / (abs.(r) + δ)^2

"""
$TYPEDEF

Talwar weighing of the residuals corresponding to

``ρ(z) = z²/2`` if `|z|≤δ` and `ρ(z)=ρ(δ)` otherwise.
"""
struct TalwarRho{δ} <: RobustRho1P{δ}
   TalwarRho(δ::Real=1.0; delta::Real=δ) = new{delta}()
end
Talwar(δ::Real=1.0; delta::Real=δ) = TalwarRho(delta)

(::TalwarRho{δ})(r::AVR) where δ = begin
   w = abs.(r) .<= δ
   return sum( r.^2 ./ 2 .* w .+ δ^2/2 .* .!w)
end

ψ(::Type{TalwarRho{δ}}   ) where δ = (r, w) -> w * r
ω(::Type{TalwarRho{δ}}, _) where δ = (_, w) -> w
ϕ(::Type{TalwarRho{δ}}   ) where δ = (_, w) -> w

"""
$TYPEDEF

Quantile regression weighing of the residuals corresponding to

``ρ(z) = z(δ - 1(z<0))``
"""
struct QuantileRho{δ} <: RobustRho1P{δ}
   QuantileRho(δ::Real=1.0; delta::Real=δ) = new{delta}()
end

Quantile(δ::Real=1.0; delta::Real=δ) = QuantileRho(delta)

(::QuantileRho{δ})(r::AVR) where δ = begin
   return sum( r .* (δ .- (r .<= 0.0)) )
end

ψ(::Type{QuantileRho{δ}}   ) where δ = (r, _) -> (δ - (r <= 0.0))
ω(::Type{QuantileRho{δ}}, τ) where δ = (r, _) -> (δ - (r <= 0.0)) / clip(r, τ)
ϕ(::Type{QuantileRho{δ}}   ) where δ = (_, _) -> error("Newton(CG) not available for Quantile Reg.")


# constructs f which is the transition matrix between different skill levels
# also contstructs the sgrid (agin this uses uniform weights where the weights sum to the difference between shi and slo, not 1)
function persistent_skill_process(sH_over_sL::AbstractFloat, pLH::AbstractFloat,
                                  pHL::AbstractFloat, ns::Int)
    f1 = [[1-pLH pLH];[pHL 1-pHL]] # f1[i,j] is prob of going from i to j
    ss_skill_distr = [pHL/(pLH+pHL); pLH/(pLH+pHL)]
    slo    = 1.0 / (ss_skill_distr'*[1;sH_over_sL])
    sgrid  = slo*[1;sH_over_sL]
    sscale = sgrid[2] - sgrid[1]
    swts   = (sscale/ns)*ones(ns) # Quadrature weights
    f      = f1 ./ repeat(swts',ns,1)
    return f, sgrid, swts, sscale
end

# constructs the cash on hand grid (known as both agrid and xgrid and kinda used interchangebly throughout the code
function cash_grid(sgrid::AbstractArray, ω::AbstractFloat, H::AbstractFloat,
                   r::AbstractFloat, η::AbstractFloat, γ::AbstractFloat,
                   T::AbstractFloat, zlo::AbstractFloat, na::Int)
    smin = minimum(sgrid)*zlo                                   # lowest possible skill
    xlo_ss = ω*smin*H - (1+r)*η*exp(-γ) + T + sgrid[1]*ω*H*0.05 # lowest SS possible cash on hand

    xlo = xlo_ss                        # lower bound on cash on hand - could be < xlo_ss
    xhi = max(xlo*2, xlo + 12.0)         # upper bound on cash on hand
    xscale = (xhi-xlo)                  # size of w grids

    # Make grids
    xgrid = collect(range(xlo,stop = xhi, length = na)) # Evenly spaced grid
    xwts  = (xscale/na)*ones(na)          # Quadrature weights
    return xgrid, xwts, xlo, xhi, xscale
end

"""
```
generate_us_and_zs(ni, nz)
```

There is no need to recall this function, unless one wants to undo the seeding
in all past saved output.
"""
function generate_us_and_zs(ni, nz)
    us = rand(ni, 8)
    uz = rand(ni, 8)

    zgrid  = collect(range(0., stop = 2., length = nz))
    zprob  = [2*mollifier_hetdsgegovdebt(zgrid[i], 2., 0.) / nz for i=1:nz]
    zprob /= sum(zprob)

    zcdf = cumsum(zprob)
    zave = 0.5 * zgrid[1:nz-1] + 0.5 * zgrid[2:nz]
    zs   = zsample(uz, zgrid, zcdf, ni, nz)

    return us, zs
    #=
    JLD2.jldopen("$HETDSGEGOVDEBT/reference/us_zs.jld2", true, true, true, IOStream) do file
        file["us"] = us
        file["zs"] = zs
    end
    =#
end


module Hubbard

using LinearMaps: FunctionMap

"""
    hops(n)

Computes neighbor bit masks for the `n` site square lattice with
periodic boundary conditions.

# Examples
```jldoctest
julia> [digits(h, base = 2, pad = 8) for h ∈ hops(8)]
16-element Array{Array{Int64,1},1}:
 [1, 0, 0, 0, 1, 0, 0, 0]
 [0, 1, 1, 0, 0, 0, 0, 0]
 [0, 0, 1, 1, 0, 0, 0, 0]
 [0, 0, 0, 1, 0, 0, 0, 1]
 [0, 0, 0, 0, 1, 1, 0, 0]
 [0, 0, 0, 0, 0, 1, 1, 0]
 [1, 0, 0, 0, 0, 0, 1, 0]
 [0, 1, 0, 0, 0, 0, 0, 1]
 [1, 0, 1, 0, 0, 0, 0, 0]
 [0, 1, 0, 0, 1, 0, 0, 0]
 [0, 0, 1, 0, 0, 1, 0, 0]
 [0, 0, 0, 1, 0, 0, 1, 0]
 [0, 0, 0, 1, 1, 0, 0, 0]
 [0, 0, 0, 0, 0, 1, 0, 1]
 [0, 1, 0, 0, 0, 0, 1, 0]
 [1, 0, 0, 0, 0, 0, 0, 1]

```
"""
function hops(n::Integer)::Vector
    a = first([u, v] for u=isqrt(n):-1:1 for v=0:u if sum(abs2, [u, v]) == n)
    b = [0 -1; 1 0]a  # orthogonal basis vector
    # apply periodic boundary conditions
    fold(x) = isequal(reduce((x, y) -> x - y * fld(x'y, n), [a, b], init = x))  # '
    inside(x) = fold(x)(x)
    sites = [[x, y] for y=0:sum(a) for x=b[1]:b[2] if inside([x, y])]
    masks(y) = [(1<<(i-1))|(1<<(findfirst(fold(x + y), sites)-1)) for (i, x) in enumerate(sites)]
    return [masks([1, 0]); masks([0, 1])]
end

"""
    index(m, c)

Compute the index of the combination of `count_ones(mask)` items
in lexicographic order

# Examples
```jldoctest
julia> c = [binomial(i, j) for i=0:3, j=1:2];

julia> [index(parse(Int, b, base = 2), c) for b ∈ ["0011", "0101", "0110", "1001", "1010", "1100"]]
6-element Array{Int64,1}:
 1
 2
 3
 4
 5
 6

```
"""
function index(m::Integer, c::Matrix)::Integer
    i = 1
    @inbounds for k ∈ 1:size(c, 2)
        i += c[trailing_zeros(m) + 1, k]
        m &= m - 1
    end
    return i
end

"""
    mask(i, c)

Compute the binary mask of the combination of `k` items out of `n`
at position `index` in lexicographic order

# Examples
```jldoctest
julia> c = [binomial(i, j) for i=0:3, j=1:2];

julia> [digits(mask(i, c), base = 2, pad = 4) for i=1:6]
6-element Array{Array{Int64,1},1}:
 [1, 1, 0, 0]
 [1, 0, 1, 0]
 [0, 1, 1, 0]
 [1, 0, 0, 1]
 [0, 1, 0, 1]
 [0, 0, 1, 1]

```
"""
function mask(i::Integer, c::Matrix)::Integer
    k = size(c, 2)
    m = 0
    @inbounds for n ∈ size(c, 1):-1:1
        j = i - c[n, k]
        if j ≥ 1
            m |= 1
            k == 1 && return m << (n - 1)
            i = j
            k -= 1
        end
        m <<= 1
    end
end

"""
    hamiltonian(n, k, t, U)

Implements the [Hubbard hamiltonian](https://en.wikipedia.org/wiki/Hubbard_model)
as a [mutating linear map](https://github.com/Jutho/LinearMaps.jl).

# Arguments
- `n::Integer`: sites in the square lattice with periodic boundary conditions
- `k::Integer`: spin up and spin down fermions
- `t::Real`: hopping integral
- `U::Real`: strength of on-site interaction

# Examples
```jldoctest
julia> h = hamiltonian(8, 4, 1., 4.)
LinearMaps.FunctionMap{Float64}(multiply!, 4900, 4900; ismutating=true, issymmetric=true, ishermitian=true, isposdef=false)

julia> using Arpack: eigs

julia> λ, ϕ = eigs(h, nev=1, which=:SR);

julia> λ
1-element Array{Float64,1}:
 -11.775702792136244

julia> size(ϕ)
(4900, 1)

```
"""
function hamiltonian(n::Integer, k::Integer, t::T, U::T) where T <: Real
    c = [binomial(i, j) for i=0:(n-1), j=1:k]
    nCk = binomial(n, k)
    hop = hops(n)
    function mult!(y, x)
        fill!(y, 0.0)
        xm = reshape(x, nCk, :)
        ym = reshape(y, nCk, :)
        @inbounds for i ∈ CartesianIndices(xm)
            w = xm[i]
            w == 0 && continue

            l, h = i.I
            u, d = mask(h, c), mask(l, c)
            ym[i] += U * w * count_ones(u & d)

            tw = t * w
            for p ∈ hop
                a = u ⊻ p
                if a ≠ u && count_ones(a) == k
                    ym[l, index(a, c)] -= tw
                end
                a = d ⊻ p
                if a ≠ d && count_ones(a) == k
                    ym[index(a, c), h] -= tw
                end
            end
        end
    end
    return FunctionMap{T}(mult!, abs2(nCk), ismutating=true, issymmetric=true)
end

end


### A Pluto.jl notebook ###
# v0.12.20

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

# ╔═╡ e9498332-3635-11eb-144e-9317d798a852
begin
	using Plots
	pyplot()
end

# ╔═╡ e46a9378-363b-11eb-2de3-392328463c96
using LaTeXStrings

# ╔═╡ 12dcd77c-36b2-11eb-2edb-23474d30667e
using PlutoUI

# ╔═╡ f53acdf2-363c-11eb-338d-eda9aae08443
using Distributions

# ╔═╡ 7fc34796-3648-11eb-244d-fbec5ad621b1
using Roots

# ╔═╡ ddee4202-3649-11eb-1198-236bf1d0d262
using Calculus

# ╔═╡ ae0486aa-36a6-11eb-30b9-8f03e178de69
using Random, LinearAlgebra

# ╔═╡ 790c383c-3647-11eb-2d7e-5d502338d789
using Combinatorics

# ╔═╡ 222e6098-36a9-11eb-12f9-dbb9552d0768
using PyCall

# ╔═╡ 1246fbea-3620-11eb-324c-1d4299044df9
md"# Module 1: _Introduction to Julia_

Welcome to **CS6741: Statistical Foundations of Data Science**

We will use Julia throughout the course. In this module, you will see some reasons why we choose Julia. Through these examples, we will also introduce you to Julia syntax. However this is by no means a complete presentation. You will have to try the language for yourself and read the official [docs](https://docs.julialang.org/en/v1/) and [tutorials](https://julialang.org/learning/tutorials/) to get more familiar with Juila.

"

# ╔═╡ b71b0e7e-3628-11eb-1713-092a8f0e6499
md"## Why Julia?

Through the course, we will work with Julia. Some lectures and all assignments will involve Julia code. So the natural question you may ask is: Why Julia? Why not Python which many of you would already know? Or why not R which is (still) widely used in Statistics? Or why not a standard performant language like C? Below we discuss several reasons for this.
"

# ╔═╡ 13b1268a-362b-11eb-1fb0-fbd74c2c80e5
md"#### 1. The two langauge problem

An important trade-off in programming is that between the time it takes to **develop** a program and the time it takes to **execute** a program. For the broad area of data sciences, one may argue that reducing developer time is more important than execution time. Consequently, languages such as R and Python have been designed to be **easier to learn** and have specific **libraries** for data analysis, visualisation, linear algebra, statistics, etc. However, after the program is finalised, R and Python do not provide high performance for **deployment**. Usually, at this point, the program is reimplemented in languages such as C/C++ or FORTRAN or Java. This is the **two langauge problem**.

Julia provides a very different trade-off to address this problem. Like R and Python, Julia is easy to learn, has a simple syntax, a lot of libraries for data science. And yet Julia produces high performance LLVM code which is Just-In-Time (JIT) compiled. This enables Juila to achieve performance like C."

# ╔═╡ 3826299e-3631-11eb-0602-4396f3b0375b
md"Let us illustrate this with a simple iterative function to estimate the value of the π:"

# ╔═╡ f90cf21c-3631-11eb-1db7-43e0d3eca3b6
md"```math
π = \lim_{N \to \infty} \sqrt{6 \times \sum_{x = 1}^N \frac{1}{x^2}}
```
"

# ╔═╡ 2678c03c-362d-11eb-1e35-b1a65fafe477
sqrt(6 * sum(k -> 1/k^2, 1:10^8))

# ╔═╡ 7db9d46c-362d-11eb-17de-0be78e78aafb
md"Compare this estimate against the precise value: $π"

# ╔═╡ 1c73ef50-3630-11eb-230c-2d44d12e4c64
md"The above code takes about ~ 125 ms on my machine. On the other hand, the following equivalent Python statement takes about 100x longer ~ 10 s on my machine.

`sqrt(6 * sum(1 / (x * x) for x in range(1, 10^8)))`

"

# ╔═╡ 8934782c-6956-11eb-09f4-a5fb501e3b40
md"👉 Repeat this on your device and we will discuss the results in the next class."

# ╔═╡ 1a3facd0-3631-11eb-06aa-410a6683a192
md"Results of many other micro-benchmarks are available at [this](https://julialang.org/benchmarks/) official website of Julia. The general point is that Julia seems much faster than Python - which is the default choice of many students and also courses.
"

# ╔═╡ 6f779994-36fc-11eb-1138-3d665ffea83b
md"How does Julia achieve this high performance? By JIT compiling the code into efficient, vectorized LLVM code. Indeed, you would experience a delay when you first run a piece of code (like adding a library) because Julia is compiling it.

We can inspect the implementation by looking at the source code of sqrt."

# ╔═╡ 3bdc4b1c-3644-11eb-04c8-b58a599801e0
@which sqrt(4.0)

# ╔═╡ 36b9a48a-3776-11eb-1c3c-455b531f4e81
md"Notice how the libraries in Juila are also written in Julia. This is unlike Python, where libraries like `numpy` are implemented in C for efficiency"

# ╔═╡ a639c37a-36fc-11eb-03a0-ad3ff5fbe073
md"We can also show the exact low-level code (LLVM for those who know compilers) produced by a statement. We see this for the `sum` operation for estimating π."

# ╔═╡ 83f86052-363f-11eb-23ac-6fc284636634
@code_llvm sum(k -> 1/k^2, 1:10^8)

# ╔═╡ 38cb6ee8-3640-11eb-2b0b-4b46f7a4331b
md"
```
;  @ reducedim.jl:653 within `sum'
define double @julia_sum_17200([2 x i64] addrspace(11)* nocapture nonnull readonly dereferenceable(16)) {
top:
; ┌ @ reducedim.jl:653 within `#sum#584'
; │┌ @ reducedim.jl:657 within `_sum'
; ││┌ @ reducedim.jl:307 within `mapreduce'
; │││┌ @ reducedim.jl:307 within `#mapreduce#580'
; ││││┌ @ reducedim.jl:312 within `_mapreduce_dim'
       %1 = call double @julia__mapreduce_17201([2 x i64] addrspace(11)* nocapture nonnull readonly %0)
; └└└└└
  ret double %1
}
```
"

# ╔═╡ e0d53d14-36fc-11eb-1712-312176bb50be
md"Notice how Julia is automatically doing vectorization and the code is ready to be parallelized."

# ╔═╡ 4d01b08e-3776-11eb-0f66-879f1077f72c
md"So Julia addresses the two language problem for data science - it can be used for learning and exploratory anlaysis and is fast enough for deployment."

# ╔═╡ 4e80c0d6-3633-11eb-3b70-5bba9910d0ab
md"#### 2. Reactive coolness with Pluto.jl"

# ╔═╡ f30f27a4-36fc-11eb-114f-73c46189b2a2
md"Another reason to use Julia is simply as a learning/teaching tool. We are using the `Pluto.jl` library which is a cool way to try out things as you are learning. The key concept is a `reactive document` wherein all parts of the document are re-executed when any one part changes. So when a variable is changed, all other code blocks which depend on this variable are re-executed and the output updated. Notice this is not how a Jupyter notebook for instance works. When you change a variable, it only affects the cell that is executed. You can learn about Pluto from this [video](https://www.youtube.com/watch?v=6ewFgLR1LLY). Pluto also has very helpful live docs."

# ╔═╡ 60f92516-3700-11eb-0c8e-eb6bdfa9d700
md"In the following, changing the `universe` variable automatically changes the display in the next cell"

# ╔═╡ 3375e884-36fd-11eb-05cc-61cecdc3f25a
universe = "universe"

# ╔═╡ 4838e1cc-36fd-11eb-2252-6dc30614dca7
md"hello $universe"

# ╔═╡ cede9b4e-3703-11eb-1032-3b126bef79a6
md"In the above notice the $ symbol used to interpolate or substitute a value within double quotes. This is a convenient way to print output in `Pluto`."

# ╔═╡ 27caddb4-36fd-11eb-249b-d53ac54d5bb3
md"In addition to reactive documents, `Pluto` also has very useful interactive tools using HTML widgets. See for example the following way of changing a variable with a slider."

# ╔═╡ 0cff395e-3634-11eb-038c-7382a21e61f9
@bind x html"<input type=range min=1 max=5>"

# ╔═╡ 59fdc9a0-3634-11eb-3220-e37bc5f1a21a
x

# ╔═╡ a2441cee-3700-11eb-359b-fbafc3fd80b7
md"The same variable has been used to change the number of steps in our computation of π from earlier. Reducing `x` reduces the precision of our estimate of π."

# ╔═╡ 5734e1e6-3633-11eb-3c57-d147f8440e90
sqrt(6 * sum(k -> 1/k^2, 1:10^x))

# ╔═╡ 8c85386c-3635-11eb-220d-75fc262637c9
md"This reactive approach also works for _plots_: changing a value with a slider can trigger an updated plot. This is quite powerful in building graphical intuition throughout the course."

# ╔═╡ e028d978-3700-11eb-0761-21d79fa8c3e4
md"For plotting we will use the standard `Plots` package which is included with the `using` keyword. In addition, there is the choice of different plot backends. We will use the `pyplot` backend here. Other popular backends are `gr` and `plotly`."

# ╔═╡ fd9e0792-3700-11eb-2c26-21ecd07186f4
md"Let us use this to plot sin(ωx) where ω is changed with a slider"

# ╔═╡ 5d380a9e-3635-11eb-0971-b5ab98d71d30
@bind ω html"<input type=range min=1 max=10>"

# ╔═╡ 6eaaa3ac-3635-11eb-1dd2-5bb9f61d5ec5
ω

# ╔═╡ 1c9f1b98-3703-11eb-3a07-1ff6e27dfa88
md"Notice how we use the ω symbol. Julia supports Unicode and thus we can include these symbols in variable or function names. We will take advantage of this often for mathematical notation throughout the course."

# ╔═╡ ffee87a4-3635-11eb-259a-e552b2eddb16
plot(x->x, x->sin(ω*x), 0, 2π, legend = false, line=3)

# ╔═╡ c389d9fe-363b-11eb-0f69-9bd72252bb4d
md"Julia is ``\LaTeX`` friendly. In particular, you can use latex-typesetting in plots too."

# ╔═╡ ddd34708-3776-11eb-256f-6385b9c1be4e
md"Note that Julia package names are case-sensitive!"

# ╔═╡ ee223de4-3776-11eb-3502-c9069ffb6e03
md"In the following we demonstrate a common relation from trigonometry."

# ╔═╡ df1af070-363b-11eb-11c7-21721a710d42
plot(x->x, x->sin(ω*x)*sin(ω*x) + cos(ω*x)*cos(ω*x), 0, 2π, legend = false, line=3, ylabel=L"\sin^2(\omega \theta) + \cos^2(\omega \theta)", xlabel=L"\theta")

# ╔═╡ 2e9bd7fe-3701-11eb-185d-255d69d9ca73
md"We can also use the `PlutoUI` library which creates many easy interfaces to the HTML widgets."

# ╔═╡ 3f838d3c-3701-11eb-3a47-1143a598b863
md"In the following, we have a button which can be used to trigger a function. The variable that is bound (`samplemore` below) needs to be included in every block that is expected to be re-executed when the button is pressed."

# ╔═╡ dd7b1b20-36b1-11eb-2a3c-65a767b0afa2
@bind samplemore Button("Sample more")

# ╔═╡ b919e9be-36b6-11eb-0636-6570c66ddfcc
throws = []

# ╔═╡ 1961d638-36b2-11eb-1710-854a0d4cee29
begin
	samplemore
	for _ in 1:100
		push!(throws, rand(1:6))
	end
end

# ╔═╡ 14721fb6-3702-11eb-0cf0-15d871f5fb1c
md"Everytime the button is pressed we are recording a few fair dice throws. We then plot the frequency of all the throws so far in a _histogram_. Notice how the block of code for plotting is made to re-execute every time the button is clicked."

# ╔═╡ 3ece5f9a-36b2-11eb-1eaf-63668f42ee83
histogram(throws, bins = 1:7, normalize=true, legend=false)

# ╔═╡ 3cdbc9a0-3777-11eb-1b20-b1e23cc647de
md"What do you think the `normalize` keyword does? We will see more about this in the section on plotting."

# ╔═╡ 70b285ee-363b-11eb-15d9-5721055a405f
md"#### 3. Lots of useful packages"

# ╔═╡ 453fe312-3702-11eb-00f3-e119b46c7dae
md"Julia is designed for scientific computing (often by scientists) and thus there are many different packages which often would directly map to the chapters you see in the course."

# ╔═╡ 57b76696-3702-11eb-3824-c32de45a9755
md"The following package provides many of the distributions that you would have seen in a probability course."

# ╔═╡ 768e5d4a-3702-11eb-339b-6dff178c893f
md"In the following, the two sliders control the mean and standard deviation of one of the most common distributions: the **normal distribution**."

# ╔═╡ 224999bc-363e-11eb-3ecb-ff5e7d48e97f
begin
	μslider = @bind μ html"<input type=range min=-3 max=3 step=0.2>"
	ωslider = @bind σ html"<input type=range min=0.1 max=2 step=0.1>"

	md"""Configure your **normal distribution**:

	μ: $(μslider)

	σ: $(ωslider)"""
end

# ╔═╡ 49cb6552-363d-11eb-1c02-9552cda30cc8
plot(x->x, x->pdf(Normal(μ, σ), x), -10, 10, ylim=(0, 1), fill=(0, :orange), label=L"\mathcal{N}("*string(μ)*","*string(σ)*")")

# ╔═╡ 8daef890-3702-11eb-0ef6-67637c7004e5
md"Playing with the sliders you can clearly see how the probability distribution function changes as you change mean (that affects the horizontal position) and standard deviation (that affects the spread)."

# ╔═╡ a2c78d50-3702-11eb-17cb-bd38085cd712
md"There are many such packages. Some of them along with their usage are tabulated below."

# ╔═╡ c2893df8-3644-11eb-06ba-332f8d6ce28a
md"
Package           | Usage
:--------------   | :---------------
Calculus.jl       | Numerical/symbolic differentiation, integration
Clustering.jl     | Clustering algorithms
Combinatorics.jl  | Combinatorics and permutation
CSV.jl            | Process csv files
DataFrames.jl     | Tabular data
Distributions.jl  | Various probability distributions
Flux.jl           | Machine learning
GLM.jl            | Regression with generalized linear models
HypothesisTests.jl| Statistical hypothesis testing
JSON.jl           | Parse json files
KernelDensity.jl  | Estimating kernel density
MultiVariateStats.jl | Multivariate operations - PCA, dim. reduction
Plots.jl.         | Plotting library
Random.jl         | Pseudo random number generators
Statistics.jl     | Basic statistical functions
StatsBase.jl      | Many useful functions - covariance, cdf, ...
StatsPlots.jl     | Plotting functions for statistics

"

# ╔═╡ b907691e-3702-11eb-27cc-9da4d3ba666a
md"In the following we see an example from _Algebra_ - computing roots of polynomials"

# ╔═╡ 83247b8a-3648-11eb-16a5-3f9b58acaab1
find_zeros(x->x^2 - 3x + 2, -2, 2)

# ╔═╡ c51e8688-3702-11eb-2a4f-ffed617fed4a
md"Notice how a polynomial is defined as a function with the -> notation. Also notice the notation such as `3x` without the multiplication symbol making it easy-to-read."

# ╔═╡ bc2645f8-3648-11eb-2c17-3f22b42b693b
find_zeros(x-> x^3 - 6x^2 + 11x -6, -10, 10)

# ╔═╡ ccca79e2-3648-11eb-0128-2f759f3fcfc6
begin
	plot(x->x, x-> x^3 - 6x^2 + 11x -6, 0, 4, legend=false)
	scatter!([(1, 0), (2, 0), (3, 0)],label="Some points")
end

# ╔═╡ debcc332-3704-11eb-11fd-67ed82871b22
md"Notice the ! after the function call for `scatter`. This is not a syntactic feature, but a convention. Whenever we want the arguments of a function to change, we have a trailing !. In this case, we want the same plot to be changed with the scatter point after the smooth curve is plotted."

# ╔═╡ 92f8a172-6a18-11eb-1b1d-6d7be8e043b4
md"👉 Write a function that has a slider to choose the degree of a polynomial, then pick up a random polynomial of that degree, then find the roots of the polynomial, and plot both the function and its root."

# ╔═╡ d9f5ef94-3702-11eb-309c-cd3e1717ced2
md"Below is an example from _Calculus_ of differentiating a polynomial."

# ╔═╡ e3261bf0-3649-11eb-20a7-0b03df8c2193
simplify(differentiate("x^3 - 6x^2 + 11x -6", :x))

# ╔═╡ 9d00d11e-3703-11eb-2e2e-c98ad6266c07
md"The polynomial is a symbolic expression with `:` to indicate a defined symbol."

# ╔═╡ 094fa2ea-3705-11eb-3dfb-d5ca02a0a638
md"The above is an expression which can be treated like any other object. More about this in the later section on _Metaprogramming_."

# ╔═╡ 1ab4daa0-3705-11eb-1181-fd5627d1b9cd
md"Now let us look for some examples from _Probability_. In particular, we will look at sampling which is something you will encounter frequently."

# ╔═╡ f31c52f6-6a18-11eb-042e-13bd3876752b
md"👉 Can you think of a way to estimate the value of π by sampling points in a square?"

# ╔═╡ 368c2460-3705-11eb-2b7d-d1772b239e94
md"We will estimate the value of π in a very different way - by sampling points in a square."

# ╔═╡ cd97b340-36a6-11eb-0675-7d2668297002
begin
	N = 10^4
	points = [[rand(), rand()] for _ in 1:N]
	withincircle = filter((x) -> (norm(x) <= 1), points)
	outsidecircle = filter((x) -> (norm(x) > 1), points)
	pi = 4 * length(withincircle)/N
end

# ╔═╡ 2ef6296c-36a7-11eb-35dc-0b8ea3e69423
begin
	scatter(first.(withincircle), last.(withincircle), c=:green, lenged=false)
	scatter!(first.(outsidecircle), last.(outsidecircle), c=:red, legend=false, ratio=:equal)
end

# ╔═╡ 45ca015c-3705-11eb-0fb9-1b08a3214ec4
md"In the above notice the `first` and `last` functions to extract the parts of the arrays. Also notice the . between the function name and the paranthesis. This indicates that the function will be _broadcast_ across the array."

# ╔═╡ 66eddad4-3705-11eb-1853-57b0db5ec480
md"Julia's `Random` package provides a variety of pseudo random number generators like the MersenneTwister. You can read about it [here](https://en.wikipedia.org/wiki/Mersenne_Twister)."

# ╔═╡ 4d91ccb2-36fb-11eb-17cd-e31ea660f52d
begin
	rng = MersenneTwister()
	points_ = [[rand(rng), rand(rng)] for _ in 1:N]
	withincircle_ = filter((x) -> (norm(x) <= 1), points_)
	outsidecircle_ = filter((x) -> (norm(x) > 1), points_)
	pi_mersenne = 4 * length(withincircle_)/N
end

# ╔═╡ 875efa46-3705-11eb-25dd-a17766bcfe21
md"Now let us take an example from _Combinatorics_ and _Set Theory_."

# ╔═╡ 36480462-6a19-11eb-02d9-5575357b5408
md"👉 How many unique orderings are there of 3 identical mangoes and 2 identical apples?"

# ╔═╡ 526c6f40-36aa-11eb-03e4-abfb3e08d8fd
fruits = join.(unique(permutations(["🥭", "🍎", "🥭", "🍎", "🥭"])))

# ╔═╡ 3269c696-6a19-11eb-0c2c-8b786d04a6cd
length(fruits)

# ╔═╡ 92f54d56-3705-11eb-1551-b13b297fe3cc
md"In the above we create all permutations of a set of repeating objects - 3 mangoes and 2 apples."

# ╔═╡ 5dfa7fee-6a19-11eb-2b8f-abda33ae0d5d
md"👉 How many orderings of the fruits have two consecutive fruits the same?"

# ╔═╡ b8df4a8a-3705-11eb-0364-87c24b11e381
md"Let us now try to count the number of permutations where two consecutive fruits are the same - either two mangoes or two apples. We can easily see that there is only one such combination. Let us compute this using filters and set union."

# ╔═╡ 5bb0f238-36aa-11eb-0433-139c3db1fabb
twomangoes = filter(x->occursin("🥭🥭", x), fruits)

# ╔═╡ a9389302-3705-11eb-2997-892cd90da244
md"In the above we have used the `filter` function to go through an array and created a subset based on an inline defined function."

# ╔═╡ 4351768a-36ab-11eb-0685-ada1b1de38b0
twoapples = filter(x->occursin("🍎🍎", x), fruits)

# ╔═╡ dce51590-3705-11eb-18eb-e1ac6da7de0e
md"The required number is given by the `union` function."

# ╔═╡ 57481568-36ab-11eb-03a6-d58c7fc3fbb9
length(fruits) - length(union(twomangoes, twoapples))

# ╔═╡ 492a52b8-362c-11eb-2fab-adacdfd31dfd
md"#### 4. Type system"

# ╔═╡ 82541798-4d99-11eb-1b79-6b998e8635d5
md"The supported types in Julia (from Wikipedia): ![](https://upload.wikimedia.org/wikipedia/commons/thumb/4/40/Type-hierarchy-for-julia-numbers.png/1598px-Type-hierarchy-for-julia-numbers.png)"

# ╔═╡ ae9604e2-4d9e-11eb-1b24-1d76b69c85d0
md"In the above types such as `Number` are called _abstract types_ while types such as `Int128` which can be instantiated are called _concrete types_."

# ╔═╡ 44c18aec-4d9d-11eb-1f0f-8dc399fbd105
md"You can check the type of any variable with `typeof`"

# ╔═╡ 7bd88984-6a19-11eb-1a82-2f382fb53b68
begin
	my_int = 5
	typeof(my_int)
end

# ╔═╡ 1fed9e42-4da0-11eb-21f3-614f1f643e1e
supertype(supertype(Int64))

# ╔═╡ 58b72e72-3648-11eb-157f-21eff52b0395
begin
	a = join.(unique(permutations(["🥭", "🍎", "🥭", "🍎", "🥭"])))
	typeof(a)
end

# ╔═╡ 52456f80-4d9d-11eb-0393-d3e85ab1a13d
md"You can cast a variable in different types with `::`"

# ╔═╡ a5539a80-364b-11eb-3eb9-adf898663087
p::UInt8 = 10

# ╔═╡ c8f604f8-364b-11eb-331f-b3359235a49b
q::Float32 = 10

# ╔═╡ 5dd69a90-4d9d-11eb-3249-332fc7e62703
md"Why would we ever store unsigned int over float? Because integers are more efficient to store."

# ╔═╡ dc562142-364b-11eb-08d9-390513dfba48
sizeof(p)

# ╔═╡ 59fa9b8e-364c-11eb-366d-b5321a1ef0a5
sizeof(q)

# ╔═╡ 70350de8-4d9d-11eb-305e-db2f4ba26bf6
md"Julia performs a robust type inference when variables are set based on assignments"

# ╔═╡ 78ec70ba-364c-11eb-3224-a9ca9de3e190
r = p + q

# ╔═╡ 826ed218-364c-11eb-3eac-a91824f2386d
typeof(r)

# ╔═╡ 8194604e-362c-11eb-0d28-115852d51330
md"##### Multiple Dispatch"

# ╔═╡ 9a11b3fa-4d9d-11eb-388b-d90c32a3ae60
md"One of the cores ideas in Julia is support for _multiple dispatch_. A single function can be mapped to different implementations based on the types of the arguments."

# ╔═╡ c74aad66-6a19-11eb-07eb-5b6ba01a95af
md"👉 Can you think of a function which would give different outputs for different types of its arguments?"

# ╔═╡ df1e224e-4d9d-11eb-2ae9-09dd2057e1eb
md"For instance consider the function `length`"

# ╔═╡ e74c5dd2-4d9d-11eb-1c7b-9f06f3ece8cf
length("stats")

# ╔═╡ f0feb5f0-4d9d-11eb-2879-f59586069b0b
length(["stats"])

# ╔═╡ b6fa30a2-4d9d-11eb-361d-0d77cf4f8822
md"It turns out that `length` has 214 different implementations!"

# ╔═╡ 6f6811f8-364b-11eb-3df0-e50b05fb9545
methods(length)

# ╔═╡ 0a6f8a7c-4d9f-11eb-2bf6-e37347808cd1
md"While creating arrays you can specify the type."

# ╔═╡ f767a56c-6a19-11eb-219a-632073208e39
Array{Int32}(undef, 3)

# ╔═╡ 067a1b6c-4d9f-11eb-042f-811fe0d6f626
fill!(Array{String}(undef, 3), "Julia")

# ╔═╡ 1f6014a6-4d9f-11eb-3fa1-45e25c6a046b
md"When creating a function, we can specify the type of the arguments which can then be used for multiple dispatch."

# ╔═╡ 159758c4-3706-11eb-2e64-b5ea8bb045c5
md"##### User defined types"

# ╔═╡ 7c33ca24-4da1-11eb-277b-039369fcceb1
md"Julia allows users to define types - both abstract and concrete types. Let us define an abstract class of `Shape` and then define two concrete types - `Rectangle` and `Circle` with respective fields."

# ╔═╡ 0d1beeb8-4da0-11eb-1c4c-0ffc6fd6406f
abstract type Shape end

# ╔═╡ 15725a5c-4da0-11eb-2e07-c127626a5cfd
supertype(Shape)

# ╔═╡ eb62ee20-4d9f-11eb-0962-43f235f9f35f
 mutable struct Rectangle <: Shape
    width::Float32
	height::Float32
 end

# ╔═╡ ce86782a-4da0-11eb-1343-e14d1ef20dfb
supertype(Rectangle)

# ╔═╡ 5ede89d4-4da0-11eb-3299-8ff027018541
mutable struct Circle <: Shape
	radius::Float32
end

# ╔═╡ 3abb70b2-6a1a-11eb-36a4-2f8ee483f908
md"👉 How would you write a function to compute areas for these two shapes?"

# ╔═╡ 99f852da-4da1-11eb-35a4-0509d79a84db
md"Now if we want to write a function for calculating the area, clearly depeding on the type of the object, the formula is different. This can be conveniently handled with _multiple dispatch_"

# ╔═╡ 39b3adae-4d9f-11eb-1518-bd8184027473
function Area(r::Rectangle)
	return r.width * r.height
end

# ╔═╡ 945659ea-4da0-11eb-07b2-f325478a2d44
function Area(c::Circle)
	return π * c.radius * c.radius
end

# ╔═╡ 4be539f6-6a1a-11eb-2b7f-0787e70768f5
md"👉 How would you write a function to find the bigger of two shapes?"

# ╔═╡ af17fd94-4da1-11eb-2100-491143085e3f
md"We can also define functions with the abstract type for instance when comparing the size of two shapes."

# ╔═╡ ab4c588e-4da0-11eb-0283-999e1f5d527e
function Bigger(s1::Shape, s2::Shape)
	return Area(s1) >= Area(s2)
end

# ╔═╡ c9801a40-4da0-11eb-0b3b-a9eecc3c6d54
begin
	rect = Rectangle(4, 6)
	cir  = Circle(3)
end

# ╔═╡ 1936a208-4da1-11eb-1e3f-31dd2ee22383
Bigger(rect, cir)

# ╔═╡ 19dab5fc-36a9-11eb-1b9e-4f43678437aa
md"#### 5. Calling Python from Julia is easy"

# ╔═╡ 560a20a8-4da1-11eb-1435-25671564c45e
md"Julia allows Python and R code to be directly imported. Further it allows the objects to be shared across Python and Julia."

# ╔═╡ 705c0d40-4da1-11eb-1da5-41685ce95eb4
md"We will create an array using the popular `numpy` library"

# ╔═╡ 27817c72-36a9-11eb-0d55-fbee89a6cfc3
np = pyimport("numpy")

# ╔═╡ 615af52c-36a9-11eb-2c16-6578895d238f
I4 = np.eye(4)

# ╔═╡ 86feacba-36a9-11eb-1eb6-2bbbe94602e3
typeof(I4)

# ╔═╡ a0a2edc8-36a9-11eb-04a1-3d72944d26aa
np.size(I4)

# ╔═╡ 204cdc8c-3706-11eb-1a66-e5d6c3629f42
md"#### 6. Metaprogramming"

# ╔═╡ 3c72fe04-4da2-11eb-03e2-59439ff087a3
md"Julia supports _metaprogramming_, i.e., you can write Julia code to manipulate Julia code. You can modify other parts of the source code, and even control if and when the modified code runs. Under the hood, this works because there are two stages to a Julia program's execution - first the code is parsed and converted into an _abstract syntax tree_ and then it is executed. Metaprogramming allows to intercept between these two stages and modify the source code and control its execution."

# ╔═╡ aa2e61c0-4da2-11eb-2cf4-91d7aeab3676
md"We have already used metaprogramming in this notebook when we used the `which` macro."

# ╔═╡ c464e2b2-4da2-11eb-2ff8-6b23c23bec48
@which sqrt(4.0)

# ╔═╡ d4e41496-4da2-11eb-2a78-e160b10ae7a8
md"Here the macro intercepts the execution of the statement and instead of running it simply returns the function which was called."

# ╔═╡ fee37ea0-4da2-11eb-32b1-3bbacc4b83ff
md"We can do metaprogramming at the user end also, and for this we need to define a statement which has been parsed but is yet to be executed."

# ╔═╡ e935a7d0-4da3-11eb-37f7-bb33c6ff1fa6
begin
	myexp = "15/3"
	Meta.parse(myexp)
end

# ╔═╡ 8a20899c-4da4-11eb-07ab-977dbf341978
md"The `:` operator indicates that the following is a parsed expression which can then be modified like any string and then executed with `eval`."

# ╔═╡ 5ce74e00-4da4-11eb-04f6-ddadff53075e
eval(Meta.parse(myexp))

# ╔═╡ 946e19b8-6a1a-11eb-1e69-0546f2d9afd9
md"👉 Write a macro which can check if the expression is illegal (eg. divided by 0) and execute only if the expression is legal."

# ╔═╡ a7903784-4da4-11eb-0d04-47177d9d64d1
md"But the key point is that we could programmatically change `myexp` before evaluating it."

# ╔═╡ c2f631a4-4da4-11eb-3faa-25e956990d8a
begin
	if occursin("/0", myexp)
		md"❌ We are not allowed to divide by 0."
	else
		eval(Meta.parse(myexp))
	end
end

# ╔═╡ 3a8684c6-4da5-11eb-03dd-1589d1285ac9
md"With this we are able to intercept and change the result without touching the underlying implementation of 0"

# ╔═╡ 571e4fb0-4da5-11eb-0625-37b5c96291ac
md"Expressions are also used natively for some packages, for instance for differentiation of symbolic expressions."

# ╔═╡ 7a8d3970-4da5-11eb-356d-511baaddd42a
formula = "x^3 - 6x^2 + 11x -6"

# ╔═╡ 63af6e94-4da5-11eb-2921-39cc7673ff45
typeof(differentiate(formula, :x))

# ╔═╡ 97f4d87e-4da5-11eb-1078-8f9a021a01d2
differentiate(formula, :x)

# ╔═╡ a203ce10-4da5-11eb-1490-c73b6fed97d2
md"The result of application of differentiation is also an expression which can then we forwarded to another function which takes an express - `simplify`."

# ╔═╡ c092a856-4da5-11eb-391a-8bc0301a2e29
simplify(differentiate(formula, :x))

# ╔═╡ d859f8ae-4da5-11eb-3e00-c79cecfb198c
md"If we want to compute the value of the expression based on the set value of `x`, then we use the `eval` command."

# ╔═╡ 760d74aa-4da5-11eb-0daa-b774ca6f3824
eval(differentiate(formula, :x))

# ╔═╡ 291915fc-6955-11eb-2590-857e9abc4c3a
hint(text, title) = Markdown.MD(Markdown.Admonition("hint", title, [text]))

# ╔═╡ 2dfe3660-6955-11eb-3856-05b77ebe047b
hint(md"
	- Serial interpretation of instructions
	- No built in methods for parallelism
	- Weak typing system
	- No advanced programming features such as metaprogramming
	", "Disadvantages of Python")

# ╔═╡ Cell order:
# ╟─1246fbea-3620-11eb-324c-1d4299044df9
# ╟─b71b0e7e-3628-11eb-1713-092a8f0e6499
# ╟─2dfe3660-6955-11eb-3856-05b77ebe047b
# ╟─13b1268a-362b-11eb-1fb0-fbd74c2c80e5
# ╟─3826299e-3631-11eb-0602-4396f3b0375b
# ╟─f90cf21c-3631-11eb-1db7-43e0d3eca3b6
# ╠═2678c03c-362d-11eb-1e35-b1a65fafe477
# ╟─7db9d46c-362d-11eb-17de-0be78e78aafb
# ╟─1c73ef50-3630-11eb-230c-2d44d12e4c64
# ╟─8934782c-6956-11eb-09f4-a5fb501e3b40
# ╟─1a3facd0-3631-11eb-06aa-410a6683a192
# ╟─6f779994-36fc-11eb-1138-3d665ffea83b
# ╠═3bdc4b1c-3644-11eb-04c8-b58a599801e0
# ╟─36b9a48a-3776-11eb-1c3c-455b531f4e81
# ╟─a639c37a-36fc-11eb-03a0-ad3ff5fbe073
# ╠═83f86052-363f-11eb-23ac-6fc284636634
# ╟─38cb6ee8-3640-11eb-2b0b-4b46f7a4331b
# ╟─e0d53d14-36fc-11eb-1712-312176bb50be
# ╟─4d01b08e-3776-11eb-0f66-879f1077f72c
# ╟─4e80c0d6-3633-11eb-3b70-5bba9910d0ab
# ╟─f30f27a4-36fc-11eb-114f-73c46189b2a2
# ╟─60f92516-3700-11eb-0c8e-eb6bdfa9d700
# ╠═3375e884-36fd-11eb-05cc-61cecdc3f25a
# ╠═4838e1cc-36fd-11eb-2252-6dc30614dca7
# ╟─cede9b4e-3703-11eb-1032-3b126bef79a6
# ╟─27caddb4-36fd-11eb-249b-d53ac54d5bb3
# ╠═0cff395e-3634-11eb-038c-7382a21e61f9
# ╠═59fdc9a0-3634-11eb-3220-e37bc5f1a21a
# ╟─a2441cee-3700-11eb-359b-fbafc3fd80b7
# ╠═5734e1e6-3633-11eb-3c57-d147f8440e90
# ╟─8c85386c-3635-11eb-220d-75fc262637c9
# ╟─e028d978-3700-11eb-0761-21d79fa8c3e4
# ╠═e9498332-3635-11eb-144e-9317d798a852
# ╟─fd9e0792-3700-11eb-2c26-21ecd07186f4
# ╠═5d380a9e-3635-11eb-0971-b5ab98d71d30
# ╠═6eaaa3ac-3635-11eb-1dd2-5bb9f61d5ec5
# ╟─1c9f1b98-3703-11eb-3a07-1ff6e27dfa88
# ╠═ffee87a4-3635-11eb-259a-e552b2eddb16
# ╟─c389d9fe-363b-11eb-0f69-9bd72252bb4d
# ╠═e46a9378-363b-11eb-2de3-392328463c96
# ╟─ddd34708-3776-11eb-256f-6385b9c1be4e
# ╟─ee223de4-3776-11eb-3502-c9069ffb6e03
# ╠═df1af070-363b-11eb-11c7-21721a710d42
# ╟─2e9bd7fe-3701-11eb-185d-255d69d9ca73
# ╠═12dcd77c-36b2-11eb-2edb-23474d30667e
# ╟─3f838d3c-3701-11eb-3a47-1143a598b863
# ╠═dd7b1b20-36b1-11eb-2a3c-65a767b0afa2
# ╠═b919e9be-36b6-11eb-0636-6570c66ddfcc
# ╠═1961d638-36b2-11eb-1710-854a0d4cee29
# ╟─14721fb6-3702-11eb-0cf0-15d871f5fb1c
# ╠═3ece5f9a-36b2-11eb-1eaf-63668f42ee83
# ╟─3cdbc9a0-3777-11eb-1b20-b1e23cc647de
# ╟─70b285ee-363b-11eb-15d9-5721055a405f
# ╟─453fe312-3702-11eb-00f3-e119b46c7dae
# ╟─57b76696-3702-11eb-3824-c32de45a9755
# ╠═f53acdf2-363c-11eb-338d-eda9aae08443
# ╟─768e5d4a-3702-11eb-339b-6dff178c893f
# ╟─224999bc-363e-11eb-3ecb-ff5e7d48e97f
# ╠═49cb6552-363d-11eb-1c02-9552cda30cc8
# ╟─8daef890-3702-11eb-0ef6-67637c7004e5
# ╟─a2c78d50-3702-11eb-17cb-bd38085cd712
# ╟─c2893df8-3644-11eb-06ba-332f8d6ce28a
# ╟─b907691e-3702-11eb-27cc-9da4d3ba666a
# ╠═7fc34796-3648-11eb-244d-fbec5ad621b1
# ╠═83247b8a-3648-11eb-16a5-3f9b58acaab1
# ╟─c51e8688-3702-11eb-2a4f-ffed617fed4a
# ╠═bc2645f8-3648-11eb-2c17-3f22b42b693b
# ╠═ccca79e2-3648-11eb-0128-2f759f3fcfc6
# ╟─debcc332-3704-11eb-11fd-67ed82871b22
# ╟─92f8a172-6a18-11eb-1b1d-6d7be8e043b4
# ╟─d9f5ef94-3702-11eb-309c-cd3e1717ced2
# ╠═ddee4202-3649-11eb-1198-236bf1d0d262
# ╠═e3261bf0-3649-11eb-20a7-0b03df8c2193
# ╟─9d00d11e-3703-11eb-2e2e-c98ad6266c07
# ╟─094fa2ea-3705-11eb-3dfb-d5ca02a0a638
# ╟─1ab4daa0-3705-11eb-1181-fd5627d1b9cd
# ╠═ae0486aa-36a6-11eb-30b9-8f03e178de69
# ╟─f31c52f6-6a18-11eb-042e-13bd3876752b
# ╟─368c2460-3705-11eb-2b7d-d1772b239e94
# ╠═cd97b340-36a6-11eb-0675-7d2668297002
# ╠═2ef6296c-36a7-11eb-35dc-0b8ea3e69423
# ╟─45ca015c-3705-11eb-0fb9-1b08a3214ec4
# ╟─66eddad4-3705-11eb-1853-57b0db5ec480
# ╠═4d91ccb2-36fb-11eb-17cd-e31ea660f52d
# ╟─875efa46-3705-11eb-25dd-a17766bcfe21
# ╠═790c383c-3647-11eb-2d7e-5d502338d789
# ╟─36480462-6a19-11eb-02d9-5575357b5408
# ╠═526c6f40-36aa-11eb-03e4-abfb3e08d8fd
# ╠═3269c696-6a19-11eb-0c2c-8b786d04a6cd
# ╟─92f54d56-3705-11eb-1551-b13b297fe3cc
# ╟─5dfa7fee-6a19-11eb-2b8f-abda33ae0d5d
# ╟─b8df4a8a-3705-11eb-0364-87c24b11e381
# ╠═5bb0f238-36aa-11eb-0433-139c3db1fabb
# ╟─a9389302-3705-11eb-2997-892cd90da244
# ╠═4351768a-36ab-11eb-0685-ada1b1de38b0
# ╟─dce51590-3705-11eb-18eb-e1ac6da7de0e
# ╠═57481568-36ab-11eb-03a6-d58c7fc3fbb9
# ╟─492a52b8-362c-11eb-2fab-adacdfd31dfd
# ╟─82541798-4d99-11eb-1b79-6b998e8635d5
# ╟─ae9604e2-4d9e-11eb-1b24-1d76b69c85d0
# ╟─44c18aec-4d9d-11eb-1f0f-8dc399fbd105
# ╠═7bd88984-6a19-11eb-1a82-2f382fb53b68
# ╠═1fed9e42-4da0-11eb-21f3-614f1f643e1e
# ╠═58b72e72-3648-11eb-157f-21eff52b0395
# ╟─52456f80-4d9d-11eb-0393-d3e85ab1a13d
# ╠═a5539a80-364b-11eb-3eb9-adf898663087
# ╠═c8f604f8-364b-11eb-331f-b3359235a49b
# ╟─5dd69a90-4d9d-11eb-3249-332fc7e62703
# ╠═dc562142-364b-11eb-08d9-390513dfba48
# ╠═59fa9b8e-364c-11eb-366d-b5321a1ef0a5
# ╟─70350de8-4d9d-11eb-305e-db2f4ba26bf6
# ╠═78ec70ba-364c-11eb-3224-a9ca9de3e190
# ╠═826ed218-364c-11eb-3eac-a91824f2386d
# ╟─8194604e-362c-11eb-0d28-115852d51330
# ╟─9a11b3fa-4d9d-11eb-388b-d90c32a3ae60
# ╟─c74aad66-6a19-11eb-07eb-5b6ba01a95af
# ╟─df1e224e-4d9d-11eb-2ae9-09dd2057e1eb
# ╠═e74c5dd2-4d9d-11eb-1c7b-9f06f3ece8cf
# ╠═f0feb5f0-4d9d-11eb-2879-f59586069b0b
# ╟─b6fa30a2-4d9d-11eb-361d-0d77cf4f8822
# ╠═6f6811f8-364b-11eb-3df0-e50b05fb9545
# ╟─0a6f8a7c-4d9f-11eb-2bf6-e37347808cd1
# ╠═f767a56c-6a19-11eb-219a-632073208e39
# ╠═067a1b6c-4d9f-11eb-042f-811fe0d6f626
# ╟─1f6014a6-4d9f-11eb-3fa1-45e25c6a046b
# ╟─159758c4-3706-11eb-2e64-b5ea8bb045c5
# ╟─7c33ca24-4da1-11eb-277b-039369fcceb1
# ╠═0d1beeb8-4da0-11eb-1c4c-0ffc6fd6406f
# ╠═15725a5c-4da0-11eb-2e07-c127626a5cfd
# ╠═eb62ee20-4d9f-11eb-0962-43f235f9f35f
# ╠═ce86782a-4da0-11eb-1343-e14d1ef20dfb
# ╠═5ede89d4-4da0-11eb-3299-8ff027018541
# ╟─3abb70b2-6a1a-11eb-36a4-2f8ee483f908
# ╟─99f852da-4da1-11eb-35a4-0509d79a84db
# ╠═39b3adae-4d9f-11eb-1518-bd8184027473
# ╠═945659ea-4da0-11eb-07b2-f325478a2d44
# ╟─4be539f6-6a1a-11eb-2b7f-0787e70768f5
# ╟─af17fd94-4da1-11eb-2100-491143085e3f
# ╠═ab4c588e-4da0-11eb-0283-999e1f5d527e
# ╠═c9801a40-4da0-11eb-0b3b-a9eecc3c6d54
# ╠═1936a208-4da1-11eb-1e3f-31dd2ee22383
# ╟─19dab5fc-36a9-11eb-1b9e-4f43678437aa
# ╟─560a20a8-4da1-11eb-1435-25671564c45e
# ╠═222e6098-36a9-11eb-12f9-dbb9552d0768
# ╟─705c0d40-4da1-11eb-1da5-41685ce95eb4
# ╠═27817c72-36a9-11eb-0d55-fbee89a6cfc3
# ╠═615af52c-36a9-11eb-2c16-6578895d238f
# ╠═86feacba-36a9-11eb-1eb6-2bbbe94602e3
# ╠═a0a2edc8-36a9-11eb-04a1-3d72944d26aa
# ╟─204cdc8c-3706-11eb-1a66-e5d6c3629f42
# ╟─3c72fe04-4da2-11eb-03e2-59439ff087a3
# ╟─aa2e61c0-4da2-11eb-2cf4-91d7aeab3676
# ╠═c464e2b2-4da2-11eb-2ff8-6b23c23bec48
# ╟─d4e41496-4da2-11eb-2a78-e160b10ae7a8
# ╟─fee37ea0-4da2-11eb-32b1-3bbacc4b83ff
# ╠═e935a7d0-4da3-11eb-37f7-bb33c6ff1fa6
# ╟─8a20899c-4da4-11eb-07ab-977dbf341978
# ╠═5ce74e00-4da4-11eb-04f6-ddadff53075e
# ╟─946e19b8-6a1a-11eb-1e69-0546f2d9afd9
# ╟─a7903784-4da4-11eb-0d04-47177d9d64d1
# ╠═c2f631a4-4da4-11eb-3faa-25e956990d8a
# ╟─3a8684c6-4da5-11eb-03dd-1589d1285ac9
# ╟─571e4fb0-4da5-11eb-0625-37b5c96291ac
# ╠═7a8d3970-4da5-11eb-356d-511baaddd42a
# ╠═63af6e94-4da5-11eb-2921-39cc7673ff45
# ╠═97f4d87e-4da5-11eb-1078-8f9a021a01d2
# ╟─a203ce10-4da5-11eb-1490-c73b6fed97d2
# ╠═c092a856-4da5-11eb-391a-8bc0301a2e29
# ╟─d859f8ae-4da5-11eb-3e00-c79cecfb198c
# ╠═760d74aa-4da5-11eb-0daa-b774ca6f3824
# ╠═291915fc-6955-11eb-2590-857e9abc4c3a


using Luxor

isinside(p, a, b) = (b.x - a.x) * (p.y - a.y) > (b.y - a.y) * (p.x - a.x)

function intersection(a, b, s, f)
    dc = [a.x - b.x, a.y - b.y]
    dp = [s.x - f.x, s.y - f.y]
    n1 = a.x * b.y - a.y * b.x
    n2 = s.x * f.y - s.y * f.x
    n3 = 1.0 / (dc[1] * dp[2] - dc[2] * dp[1])
    Point((n1 * dp[1] - n2 * dc[1]) * n3, (n1 * dp[2] - n2 * dc[2]) * n3)
end

function clipSH(spoly, cpoly)
    outarr = spoly
    q = cpoly[end]
    for p in cpoly
        inarr = outarr
        outarr = Point[]
        s = inarr[end]
        for vtx in inarr
            if isinside(vtx, q, p)
                if !isinside(s, q, p)
                    push!(outarr, intersection(q, p, s, vtx))
                end
                push!(outarr, vtx)
            elseif isinside(s, q, p)
                push!(outarr, intersection(q, p, s, vtx))
            end
            s = vtx
        end
        q = p
    end
    outarr
end

subjectp = [Point(50, 150), Point(200, 50), Point(350, 150), Point(350, 300),
    Point(250, 300), Point(200, 250), Point(150, 350), Point(100, 250), Point(100, 200)]

clipp = [Point(100, 100), Point(300, 100), Point(300, 300), Point(100, 300)]

Drawing(400, 400, "intersecting-polygons.png")
background("white")
sethue("red")
poly(subjectp, :stroke, close=true)
sethue("blue")
poly(clipp, :stroke, close=true)
clipped = clipSH(subjectp, clipp)
sethue("gold")
poly(clipped, :fill, close=true)
finish()
preview()
println(clipped)


#
#  LennardJones.jl
#  CollagenPolymer
#
#  Created by Christopher Revell on 30/03/2020.
#
#

module LennardJones

# Import Julia packages
using LinearAlgebra

# Import local program modules

function lennardJones(dxMag,ϵ,σ)

  magF  = 24.0*ϵ*((σ^6.0)/(dxMag^7.0) - 2.0*(σ^12.0)/(dxMag^13.0))

  return magF

end

export lennardJones

end


function POMDPs.observation(pomdp::DoubleCovidPOMDP, s::DoubleCovidState, a::CovidAction, sp::DoubleCovidState)
    iszero(a.testing_prop) && return CovidPOMDPs.UninformedDist() # not getting any info; any transition equally likely
    tot_mean = 0.0
    tot_variance = 0.0
    p1 = pomdp.params
    p2 = s.params
    for (i,inf) in enumerate(s.I1)
        num_already_tested = sum(@view s.T1[:,i])
        num_tested = floor(Int,(inf-num_already_tested)*a.testing_prop)
        dist = Binomial(num_tested,p1.pos_test_probs[i])
        tot_mean += Statistics.mean(dist)
        tot_variance += Statistics.var(dist)
    end

    for (i,inf) in enumerate(s.I2)
        num_already_tested = sum(@view s.T2[:,i])
        num_tested = floor(Int,(inf-num_already_tested)*a.testing_prop)
        dist = Binomial(num_tested,p2.pos_test_probs[i])
        tot_mean += Statistics.mean(dist)
        tot_variance += Statistics.var(dist)
    end

    return Normal(tot_mean, sqrt(tot_variance))
end


"""
    AmoebaFiber3D(f::AbstractPolynomial, w=(0., 0., 0.))

Construct the fiber `AmoebaFiber3D` of the bivariate polynomial `f` at `w`.
"""
function AmoebaFiber3D(p::MP.AbstractPolynomialLike{S}, w=(0.0, 0.0, 0.0)) where S
    vars = MP.variables(p)
    @assert (length(vars) == 3) "Expected a trivariate polynomial, but got $(p)."

    T = realified_coefficient_type(S)
    f_re, f_im = SP.Polynomial.(1.0 .* realify(p))

    v = exp.(w)
    U = zeros(T, 2, 6)
    x = SVector{6, Float64}(0.0, 0.0, 0.0, 0.0, 0.0, 0.0)

    exponents = SP.exponents.((f_re, f_im))
    original_coefficients = copy.(SP.coefficients.((f_re, f_im)))

    AmoebaFiber3D(f_re, f_im, v, U, x, exponents, original_coefficients)
end

function update_fiber!(F::AmoebaFiber3D, w)
    w1, w2, w3 = w
    F.v = (exp(w1), exp(w2), exp(w3))
    condition!(F)
    F
end

function condition!(fiber::AmoebaFiber3D)
    fiber.f_re.coefficients .= fiber.original_coefficients[1]
    fiber.f_im.coefficients .= fiber.original_coefficients[2]

    n1 = normalizer_amoeba_3d(fiber.exponents[1], fiber.v)
    n2 = normalizer_amoeba_3d(fiber.exponents[2], fiber.v)
    normalizer = inv(max(n1, n2))
    SP.scale_coefficients!(fiber.f_re, normalizer)
    SP.scale_coefficients!(fiber.f_im, normalizer)
    nothing
end

function normalizer_amoeba_3d(E, v)
    v1, v2, v3 = v
    normalizer = sum(1:size(E, 2)) do j
        m1 = E[1,j]+E[4,j]
        m2 = E[2,j]+E[5,j]
        m3 = E[3,j]+E[6,j]
        Base.FastMath.pow_fast(v1, m1) *
        Base.FastMath.pow_fast(v2, m2) *
        Base.FastMath.pow_fast(v3, m3)
    end
end

function condition_amoeba_fiber_3d!(f, E, v)
    v1, v2, v3 = v
    normalizer = sum(1:size(E, 2)) do j
        m1 = E[1,j]+E[4,j]
        m2 = E[2,j]+E[5,j]
        m3 = E[3,j]+E[6,j]
        Base.FastMath.pow_fast(v1, m1) *
        Base.FastMath.pow_fast(v2, m2) *
        Base.FastMath.pow_fast(v3, m3)
    end
    SP.scale_coefficients!(f, 1 / normalizer)
end

function phi!(fiber::AmoebaFiber3D, θ)
    v1, v2, v3 = fiber.v
    x = SVector(
        v1 * cos(θ[1]), v2 * cos(θ[2]), v3 * cos(θ[3]),
        v1 * sin(θ[1]), v2 * sin(θ[2]), v3 * sin(θ[3])
        )
    fiber.x = x
    x
end

function evaluate(fiber::AmoebaFiber3D, θ)
    x = phi!(fiber, θ)
    SVector(SP.evaluate(fiber.f_re, x), SP.evaluate(fiber.f_im, x))
end

function jacobian(fiber::AmoebaFiber3D, θ, precomputed=false)
    x = precomputed ? fiber.x : phi!(fiber, θ)
    U = fiber.U
    @inbounds begin
        ∇f_re = SP.gradient(fiber.f_re, x)
        for i=1:6
            U[1, i] = ∇f_re[i]
        end
        U[2, 4] = U[1, 1]
        U[2, 5] = U[1, 2]
        U[2, 6] = U[1, 3]
        U[2, 1] = -U[1, 4]
        U[2, 2] = -U[1, 5]
        U[2, 3] = -U[1, 6]
    end

    @inbounds out = begin
        x4, x5, x6 = -x[4], -x[5], -x[6]
        ∂re∂θ1 = U[1, 1] * x4
        ∂re∂θ2 = U[1, 2] * x5
        ∂re∂θ3 = U[1, 3] * x6
        ∂im∂θ1 = U[2, 1] * x4
        ∂im∂θ2 = U[2, 2] * x5
        ∂im∂θ3 = U[2, 3] * x6

        ∂re∂θ1 = muladd(U[1, 4], x[1], ∂re∂θ1)
        ∂re∂θ2 = muladd(U[1, 5], x[2], ∂re∂θ2)
        ∂re∂θ3 = muladd(U[1, 6], x[3], ∂re∂θ3)
        ∂im∂θ1 = muladd(U[2, 4], x[1], ∂im∂θ1)
        ∂im∂θ2 = muladd(U[2, 5], x[2], ∂im∂θ2)
        ∂im∂θ3 = muladd(U[2, 6], x[3], ∂im∂θ3)

        # SMatrix has column-major storage
        SMatrix{2,3}(∂re∂θ1, ∂im∂θ1, ∂re∂θ2, ∂im∂θ2, ∂re∂θ3, ∂im∂θ3)
    end

    out
end