### A Pluto.jl notebook ###
# v0.17.4

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 iv = try Base.loaded_modules[Base.PkgId(Base.UUID("6e696c72-6542-2067-7265-42206c756150"), "AbstractPlutoDingetjes")].Bonds.initial_value catch; b -> missing; end
        local el = $(esc(element))
        global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : iv(el)
        el
    end
end

# ╔═╡ d2e14b19-98d7-4fba-8bd9-bbbb47e002f6
begin
	bigbreak = HTML("<br>"^4);
	using PlutoUI
	using Test
end

# ╔═╡ ce3c8fde-802a-11ec-1b1a-31be58ae3474
md"""
# Conditionals
with the if keyword
In Julia, the syntax

```julia
if *condition 1*
    *option 1*
elseif *condition 2*
    *option 2*
else
    *option 3*
end
```
allows us to conditionally evaluate one of our options.

For example, we might want to implement the FizzBuzz test:
given a number, N, print
-  "Fizz" if N is divisible by 3,
-  "Buzz" if N is divisible by 5, and
-  "FizzBuzz" if N is divisible by 3 and 5.

Otherwise just print the number itself! Enter your choice for N here:
"""

# ╔═╡ fd2ee11d-1734-457b-b587-4128b474b772
@bind N PlutoUI.Slider(1:20, show_value = true)

# ╔═╡ 7b619e85-abb8-4115-a9df-b5fedd04a8e4
md"N = $N"

# ╔═╡ da9796ce-4572-4d2d-bb96-5949f52d46d0
with_terminal() do

	if (N % 3 == 0) && (N % 5 == 0)
		# `&&` means "AND"; % computes the remainder after division
	    println("FizzBuzz")
	elseif N % 3 == 0
	    println("Fizz")
	elseif N % 5 == 0
	    println("Buzz")
	else
	    println(N)
	end

end

# ╔═╡ aca5d158-2902-4b7a-bf50-6f7a722f5ea7
md"""
with ternary operators
For this last block, we could instead use the ternary operator with the syntax
```julia
a ? b : c
```
which equates to
```julia
if a
    b
else
    c
end
```
"""

# ╔═╡ 8a3c8101-0c2a-438f-ba58-b80e9b14bc10
md"
Now let's say we want to return the larger of two numbers. Give x and y values here: "

# ╔═╡ 5732d2dc-c411-4c56-9720-ce6d979fc60f
x = 10

# ╔═╡ d993ffa0-c613-4b4b-baeb-a223ce408217
y = 30

# ╔═╡ 782a5034-9184-4a55-b877-bd0bb4e4aebb
md"Using the if and else keywords, we might write:
"

# ╔═╡ 14d69119-9a68-428b-a857-56298b42aee5
if x > y
    x
else
    y
end

# ╔═╡ 46533172-0fe1-406b-a52d-23305282d43b
md"and as a ternary operator, the conditional looks like this:"

# ╔═╡ b13c9cc4-3c4e-4416-896f-d5cbdc2d51c0
(x > y) ? x : y

# ╔═╡ 8b81523f-79dc-46ba-8e4e-5dbc19d5d434
true ? 5 : 2 * 6

# ╔═╡ 7925dfa8-63b1-4fc2-beae-922fa93aa081
( false  ?  1  : 2 )

# ╔═╡ fdfb1f2e-6105-43dd-92cf-4b78e1db8e73
bigbreak

# ╔═╡ 61aa9056-ebfe-4c06-a752-d081ece68167
md"""
## Exercises

5.1

Write a conditional statement that prints "negative", "zero" or "odd" according to the input number `N`.

"""

# ╔═╡ 75efd803-be55-4d0f-b8c8-6b63dd90d68c
md"""M = $(@bind M PlutoUI.Slider(-100:100, show_value = true)) """

# ╔═╡ 8f1298ab-309f-4d4d-8165-4ee522f3b1c8
with_terminal() do
	# modify this cell to return "negative", "zero" and "positive".
	println(M)
end

# ╔═╡ 602b3c77-e46e-4592-a787-4aeed77eb059
bigbreak

# ╔═╡ c7c1ec65-49ed-4ae9-b9a9-0d82a346af81
md"""
5.2

Use the ternary operator to construct a reasonable answer from Bob in the dialog below.
"""

# ╔═╡ 283c45af-47a3-4446-b873-205d8aa568e7
md"""
**Alice:** _Is the number $M even?_

**Bob:**   _I don't know._
"""

# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[compat]
PlutoUI = "~0.7.32"
"""

# ╔═╡ 00000000-0000-0000-0000-000000000002
PLUTO_MANIFEST_TOML_CONTENTS = """
# This file is machine-generated - editing it directly is not advised

julia_version = "1.7.1"
manifest_format = "2.0"

[[deps.AbstractPlutoDingetjes]]
deps = ["Pkg"]
git-tree-sha1 = "8eaf9f1b4921132a4cff3f36a1d9ba923b14a481"
uuid = "6e696c72-6542-2067-7265-42206c756150"
version = "1.1.4"

[[deps.ArgTools]]
uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f"

[[deps.Artifacts]]
uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"

[[deps.Base64]]
uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"

[[deps.ColorTypes]]
deps = ["FixedPointNumbers", "Random"]
git-tree-sha1 = "024fe24d83e4a5bf5fc80501a314ce0d1aa35597"
uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f"
version = "0.11.0"

[[deps.CompilerSupportLibraries_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae"

[[deps.Dates]]
deps = ["Printf"]
uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"

[[deps.Downloads]]
deps = ["ArgTools", "LibCURL", "NetworkOptions"]
uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6"

[[deps.FixedPointNumbers]]
deps = ["Statistics"]
git-tree-sha1 = "335bfdceacc84c5cdf16aadc768aa5ddfc5383cc"
uuid = "53c48c17-4a7d-5ca2-90c5-79b7896eea93"
version = "0.8.4"

[[deps.Hyperscript]]
deps = ["Test"]
git-tree-sha1 = "8d511d5b81240fc8e6802386302675bdf47737b9"
uuid = "47d2ed2b-36de-50cf-bf87-49c2cf4b8b91"
version = "0.0.4"

[[deps.HypertextLiteral]]
git-tree-sha1 = "2b078b5a615c6c0396c77810d92ee8c6f470d238"
uuid = "ac1192a8-f4b3-4bfe-ba22-af5b92cd3ab2"
version = "0.9.3"

[[deps.IOCapture]]
deps = ["Logging", "Random"]
git-tree-sha1 = "f7be53659ab06ddc986428d3a9dcc95f6fa6705a"
uuid = "b5f81e59-6552-4d32-b1f0-c071b021bf89"
version = "0.2.2"

[[deps.InteractiveUtils]]
deps = ["Markdown"]
uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"

[[deps.JSON]]
deps = ["Dates", "Mmap", "Parsers", "Unicode"]
git-tree-sha1 = "8076680b162ada2a031f707ac7b4953e30667a37"
uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
version = "0.21.2"

[[deps.LibCURL]]
deps = ["LibCURL_jll", "MozillaCACerts_jll"]
uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21"

[[deps.LibCURL_jll]]
deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"]
uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0"

[[deps.LibGit2]]
deps = ["Base64", "NetworkOptions", "Printf", "SHA"]
uuid = "76f85450-5226-5b5a-8eaa-529ad045b433"

[[deps.LibSSH2_jll]]
deps = ["Artifacts", "Libdl", "MbedTLS_jll"]
uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8"

[[deps.Libdl]]
uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"

[[deps.LinearAlgebra]]
deps = ["Libdl", "libblastrampoline_jll"]
uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"

[[deps.Logging]]
uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"

[[deps.Markdown]]
deps = ["Base64"]
uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"

[[deps.MbedTLS_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1"

[[deps.Mmap]]
uuid = "a63ad114-7e13-5084-954f-fe012c677804"

[[deps.MozillaCACerts_jll]]
uuid = "14a3606d-f60d-562e-9121-12d972cd8159"

[[deps.NetworkOptions]]
uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"

[[deps.OpenBLAS_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"]
uuid = "4536629a-c528-5b80-bd46-f80d51c5b363"

[[deps.Parsers]]
deps = ["Dates"]
git-tree-sha1 = "0b5cfbb704034b5b4c1869e36634438a047df065"
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
version = "2.2.1"

[[deps.Pkg]]
deps = ["Artifacts", "Dates", "Downloads", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"]
uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"

[[deps.PlutoUI]]
deps = ["AbstractPlutoDingetjes", "Base64", "ColorTypes", "Dates", "Hyperscript", "HypertextLiteral", "IOCapture", "InteractiveUtils", "JSON", "Logging", "Markdown", "Random", "Reexport", "UUIDs"]
git-tree-sha1 = "ae6145ca68947569058866e443df69587acc1806"
uuid = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
version = "0.7.32"

[[deps.Printf]]
deps = ["Unicode"]
uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"

[[deps.REPL]]
deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"]
uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"

[[deps.Random]]
deps = ["SHA", "Serialization"]
uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"

[[deps.Reexport]]
git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b"
uuid = "189a3867-3050-52da-a836-e630ba90ab69"
version = "1.2.2"

[[deps.SHA]]
uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"

[[deps.Serialization]]
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"

[[deps.Sockets]]
uuid = "6462fe0b-24de-5631-8697-dd941f90decc"

[[deps.SparseArrays]]
deps = ["LinearAlgebra", "Random"]
uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"

[[deps.Statistics]]
deps = ["LinearAlgebra", "SparseArrays"]
uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"

[[deps.TOML]]
deps = ["Dates"]
uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76"

[[deps.Tar]]
deps = ["ArgTools", "SHA"]
uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e"

[[deps.Test]]
deps = ["InteractiveUtils", "Logging", "Random", "Serialization"]
uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[[deps.UUIDs]]
deps = ["Random", "SHA"]
uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"

[[deps.Unicode]]
uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"

[[deps.Zlib_jll]]
deps = ["Libdl"]
uuid = "83775a58-1f1d-513f-b197-d71354ab007a"

[[deps.libblastrampoline_jll]]
deps = ["Artifacts", "Libdl", "OpenBLAS_jll"]
uuid = "8e850b90-86db-534c-a0d3-1478176c7d93"

[[deps.nghttp2_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d"

[[deps.p7zip_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0"
"""

# ╔═╡ Cell order:
# ╟─d2e14b19-98d7-4fba-8bd9-bbbb47e002f6
# ╟─ce3c8fde-802a-11ec-1b1a-31be58ae3474
# ╟─fd2ee11d-1734-457b-b587-4128b474b772
# ╟─7b619e85-abb8-4115-a9df-b5fedd04a8e4
# ╠═da9796ce-4572-4d2d-bb96-5949f52d46d0
# ╟─aca5d158-2902-4b7a-bf50-6f7a722f5ea7
# ╟─8a3c8101-0c2a-438f-ba58-b80e9b14bc10
# ╠═5732d2dc-c411-4c56-9720-ce6d979fc60f
# ╠═d993ffa0-c613-4b4b-baeb-a223ce408217
# ╟─782a5034-9184-4a55-b877-bd0bb4e4aebb
# ╠═14d69119-9a68-428b-a857-56298b42aee5
# ╟─46533172-0fe1-406b-a52d-23305282d43b
# ╠═b13c9cc4-3c4e-4416-896f-d5cbdc2d51c0
# ╠═8b81523f-79dc-46ba-8e4e-5dbc19d5d434
# ╠═7925dfa8-63b1-4fc2-beae-922fa93aa081
# ╟─fdfb1f2e-6105-43dd-92cf-4b78e1db8e73
# ╟─61aa9056-ebfe-4c06-a752-d081ece68167
# ╟─75efd803-be55-4d0f-b8c8-6b63dd90d68c
# ╠═8f1298ab-309f-4d4d-8165-4ee522f3b1c8
# ╟─602b3c77-e46e-4592-a787-4aeed77eb059
# ╟─c7c1ec65-49ed-4ae9-b9a9-0d82a346af81
# ╠═283c45af-47a3-4446-b873-205d8aa568e7
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002


using Printf
# ARRAYS ARE INDEXED FROM ONE NOT ZERO IN JULIA!!!!

## I wrote this for displaying arrays, but we don't actually need it. println() works just fine!
#
#function display_array(some_array)
#    for x in some_array
#        print(x, " ")
#    end
#    println()
#end

function array_examples()
    println("--------------------------------------")
    println("Array examples")

    println()

    println("We can create one-dimensional arrays of zeros")
    one_dimensional_array = zeros(Int32, 5)
    println(one_dimensional_array)

    println("We can create two-dimensional arrays of zeros")
    two_dimensional_array = zeros(Int32, 5, 2)
    println(two_dimensional_array)

    println("We can create arrays where we specify the type, the size, and the initial value (in this case undefined)")
    undefined_array = Array{Int32}(undef, 5)
    println(undefined_array)

    println("We can create a int array and set initial values")
    int_array = Int32[1,2,3,4]
    println(int_array)

    println("We can get individual values. REMEMBER JULIA INDEXED FROM ONE NOT ZERO!")
    println(int_array[2]) # The second element really is the second element (not the third as would be the case with zero-indexing)

    println("We can create a float array and set initial values")
    float_array = Float64[1,2,3,4]
    println(float_array)

    println("We can create an array where each element is off a different type")
    array_of_different_types = [99, 3.1415, "foo", true, 'x']
    println(array_of_different_types)

    println("We can create an array from a range (2 to 10, stepping at intervals of 2")
    even_number_array = collect(2:2:10)
    println(even_number_array)

    println("We can create an array using comprehension")
    square_numbers = [n^2 for n in 1:5]
    println(square_numbers)

    println("We can also create multi-dimensional arrays using comprehension")
    another_multi_dim_array = [n * m for n in 1:5, m in 1:5]
    println(another_multi_dim_array)

    println("We can get a specific (one-indexed) element from the array")
    println(array_of_different_types[1])

    println("We can get the last element from the array")
    println(array_of_different_types[end])

    println("We can get a range, in this case items 2-4")
    println(array_of_different_types[2:4])

    println("We can check if an item, e.g. 3.1415 exists in the array")
    println(3.1415 in array_of_different_types)

    println("We can find a specific item in the array, e.g. 'x'")
    println(findfirst(isequal('x'), array_of_different_types))

    int_array = [1,2,3,4,5,6,7,8,10]
    println("We can find all the even numbers in an array using a generic function and findall")
    f(a) = mod(a, 2) == 0 ? true : false
    println(findall(f, int_array))

    println("We can get the number of items that pass the test")
    println(count(f, int_array))

    println("We can get the row and column size of our 5x2 array we created earlier")
    println(size(two_dimensional_array))

    println("We can get the length of an array")
    println(length(int_array))

    println("We can get the sum of all the values in the array")
    println(sum(int_array))

    println("We can insert items at index 2 (second number after colon seems to specify range in target array to overwrite. increase this number to see old items in array go missing!)")
    splice!(int_array, 2:1, [100,200,300,400,500])
    println(int_array)

    println("We can remove items at index 2 to 6")
    splice!(int_array, 2:6)
    println(int_array)

    println("We get the maximum value in the array")
    println(maximum(int_array))

    println("We get the minimum value in the array")
    println(minimum(int_array))

    println("We can perform calculations on an array without looping")
    println(int_array * 2)

    println("We can store functions in an array")
    function_array = [sin, cos, tan]
    println(function_array)

    for n in function_array
        println(n(45))
    end

    two_dimensional_int_array = [1 2 3 4; 5 6 7 8; 9 10 11 12]
    for row = 1:3, col = 1:4
        @printf("row %d, col %d = %d\n", row, col, two_dimensional_int_array[row,col])
    end
    println()

    println("We can print just the items from column 2")
    println(two_dimensional_int_array[:, 2])

    println("We can push items to the end of an array")
    push!(int_array, 999)
    println(int_array)
end


# -*- coding: utf-8 -*-
# ---
# jupyter:
#   jupytext:
#     formats: ipynb,jl:hydrogen
#     text_representation:
#       extension: .jl
#       format_name: hydrogen
#       format_version: '1.3'
#       jupytext_version: 1.11.2
#   kernelspec:
#     display_name: Julia 1.7.0-beta3
#     language: julia
#     name: julia-1.7
# ---

# %%
using Random, Statistics, Plots

function diff!(X)
    @inbounds for i in reverse(keys(X)[begin:end-1])
        X[i+1] -= X[i]
    end
    X
end

function randsimplex!(rng::AbstractRNG, X)
    X[end] = one(eltype(X))
    Y = @view X[begin:end-1]
    rand!(rng, Y)
    sort!(Y)
    X .*= length(X)
    diff!(X)
end
randsimplex(rng::AbstractRNG, N, T=Float64) = randsimplex!(rng, Vector{T}(undef, N))
randsimplex!(X) = randsimplex!(Random.default_rng(), X)
randsimplex(N, T=Float64) = randsimplex!(Vector{T}(undef, N))

n = 10^4
A = randsimplex(n)
@show mean(A), var(A)
histogram(A; norm=true, alpha=0.3, label="randsimplex($n)")
plot!(x->exp(-x), 0, 6; xlim=(-0.1, 6), lw=2, label="Exponential(1) dist")

# %%
struct Simplex{T} N::Int end
Simplex(N) = Simplex{Float64}(N)

function Random.rand!(rng::AbstractRNG,
        X::AbstractVector{T}, d::Random.SamplerTrivial{Simplex{T}}) where T
    @assert length(X) == d[].N
    randsimplex!(rng, X)
end
Base.rand(rng::AbstractRNG, d::Random.SamplerTrivial{Simplex{T}}) where T =
    rand!(rng, Vector{T}(undef, d[].N), d)

# %%
using BenchmarkTools

X = zeros(10)
d = Simplex(10)
@btime rand!($X, $d)

# %%
rand(Simplex(10))

# %%
using Random, LinearAlgebra, Statistics, Plots

function randsphere!(rng::AbstractRNG, X)
    randn!(X)
    X .*= √(length(X)) / norm(X)
end
randsphere(rng::AbstractRNG, N, T=Float64) = randsphere!(rng, Vector{T}(undef, N))
randsphere!(X) = randsphere!(Random.default_rng(), X)
randsphere(N, T=Float64) = randsphere!(Vector{T}(undef, N))

n = 10^4
A = randsphere(n)
@show mean(A), var(A)
histogram(A; norm=true, alpha=0.3, xlim=(-5, 5), label="randsphere($n)")
plot!(x->exp(-x^2/2)/√(2π), -5, 5; lw=2, label="standard normsl dist")

# %%


using OrdinaryDiffEq, ParameterizedFunctions, ODE, ODEInterfaceDiffEq, LSODA,
      Sundials, DiffEqDevTools

f = @ode_def LotkaVolterra begin
  dx = a*x - b*x*y
  dy = -c*y + d*x*y
end a b c d

p = [1.5,1.0,3.0,1.0]
prob = ODEProblem(f,[1.0;1.0],(0.0,10.0),p)

abstols = 1.0 ./ 10.0 .^ (6:13)
reltols = 1.0 ./ 10.0 .^ (3:10);
sol = solve(prob,Vern7(),abstol=1/10^14,reltol=1/10^14)
test_sol = TestSolution(sol)
using Plots; gr()

plot(sol)

setups = [Dict(:alg=>DP5())
          #Dict(:alg=>ode45()) # fail
          Dict(:alg=>dopri5())
          Dict(:alg=>Tsit5())
          Dict(:alg=>Vern6())
]
wp = WorkPrecisionSet(prob,abstols,reltols,setups;appxsol=test_sol,save_everystep=false,maxiters=10000,numruns=100)
plot(wp)

setups = [Dict(:alg=>DP5())
          #Dict(:alg=>ode45())
          Dict(:alg=>Tsit5())
          Dict(:alg=>Vern6())
]
wp = WorkPrecisionSet(prob,abstols,reltols,setups;appxsol=test_sol,maxiters=10000,error_estimate=:L2,dense_errors=true,numruns=100)
plot(wp)

setups = [Dict(:alg=>DP8())
          #Dict(:alg=>ode78()) # fails
          Dict(:alg=>Vern7())
          Dict(:alg=>Vern8())
          Dict(:alg=>dop853())
          Dict(:alg=>Vern6())
]
wp = WorkPrecisionSet(prob,abstols,reltols,setups;appxsol=test_sol,save_everystep=false,maxiters=1000,numruns=100)
plot(wp)

setups = [Dict(:alg=>odex())
          Dict(:alg=>ddeabm())
          Dict(:alg=>Vern7())
          Dict(:alg=>Vern8())
          Dict(:alg=>CVODE_Adams())
          #Dict(:alg=>lsoda())
          Dict(:alg=>Vern6())
          Dict(:alg=>ARKODE(Sundials.Explicit(),order=6))
          ]
wp = WorkPrecisionSet(prob,abstols,reltols,setups;appxsol=test_sol,save_everystep=false,maxiters=1000,numruns=100)
plot(wp)

setups = [Dict(:alg=>DP8())
          #Dict(:alg=>ode78())
          Dict(:alg=>Vern7())
          Dict(:alg=>Vern8())
          Dict(:alg=>Vern6())
]
wp = WorkPrecisionSet(prob,abstols,reltols,setups;appxsol=test_sol,dense=true,maxiters=1000,error_estimate=:L2,numruns=100)
plot(wp)

using DiffEqBenchmarks
DiffEqBenchmarks.bench_footer(WEAVE_ARGS[:folder],WEAVE_ARGS[:file])


#HW2 Problem 2
#Minhao Yan my497, Wentong Chen wc422
#1. Generate array of random numbers named x
x = rand(Float64, (20000,20000))

#2. Column by Column
exp_column = zeros((20000,20000))
function exp_cols(x)
    for i in 1:20000
        exp_column[:,i] = exp.(x[:,i])
    end
    return exp_column
end

#3. Row by Row
exp_row = zeros((20000,20000))
function exp_rows(x)
    for i in 1:20000
        exp_row[i,:] = exp.(x[i,:])
    end
    return exp_row
end

#4.Calculate time
@time exp_cols(x)
#23.460586 seconds (523.19 k allocations: 5.984 GiB, 4.02% gc time)
@time exp_rows(x)
#320.930191 seconds (273.41 k allocations: 5.970 GiB, 0.56% gc time)

#5. The columnn by column one is much faster.

#6. Exponentiate the full array
@time exp_full = exp.(x)
#55.045882 seconds (314.79 k allocations: 2.996 GiB, 0.01% gc time)
#Overall, the column by column method is the fastest, then the overall one,
#the slowest is the row by row method.


using SparseArrays, DiffEqOperators, LinearAlgebra, Random,
      Test, BandedMatrices, FillArrays

function second_derivative_stencil(N)
  A = zeros(N,N+2)
  A[1,1:5] = [0.916667,   -1.66667,    0.5,   0.333333,  -0.0833333]
  A[2,1:5] = [-0.0833333,   1.33333,   -2.5,   1.33333,   -0.0833333]
  A[3,1:5] = [-0.0833333,   0.333333,   0.5,  -1.66667,    0.916667]
  A[4,1:5] = [0.916667,   -4.66667,    9.5,  -8.66667,    2.91667]
  for i in 5:N-4, j in 5:N-2
      (j-i==0 || j-i==2) && (A[i,j]=1)
      j-i==1 && (A[i,j]=-2)
  end
  A[end,end-4:end] = reverse(A[1,1:5])
  A[end-1,end-4:end] = reverse(A[2,1:5])
  A[end-2,end-4:end] = reverse(A[3,1:5])
  A[end-3,end-4:end] = reverse(A[4,1:5])

  return A
end

# Analytic solutions to higher order operators.
# Do not modify unless you are completely certain of the changes.
function fourth_deriv_approx_stencil(N)
    A = zeros(N,N+2)
    A[1,1:8] = [3.5 -56/3 42.5 -54.0 251/6 -20.0 5.5 -2/3]
    A[2,1:8] = [2/3 -11/6 0.0 31/6 -22/3 4.5 -4/3 1/6]
    A[N-1,N-5:end] = reverse([2/3 -11/6 0.0 31/6 -22/3 4.5 -4/3 1/6], dims=2)
    A[N,N-5:end] = reverse([3.5 -56/3 42.5 -54.0 251/6 -20.0 5.5 -2/3], dims=2)
    for i in 3:N-2
        A[i,i-2:i+4] = [-1/6 2.0 -13/2 28/3 -13/2 2.0 -1/6]
    end
    return A
end

function second_deriv_fourth_approx_stencil(N)
    A = zeros(N,N+2)
    A[1,1:6] = [5/6 -1.25 -1/3 7/6 -0.5 5/60]
    A[N,N-3:end] = reverse([5/6 -1.25 -1/3 7/6 -0.5 5/60], dims=2)
    for i in 2:N-1
        A[i,i-1:i+3] = [-1/12 4/3 -5/2 4/3 -1/12]
    end
    return A
end

function convert_by_multiplication(::Type{Array}, A::AbstractDerivativeOperator{T}, N::Int=A.dimension) where T
    @assert N >= A.stencil_length # stencil must be able to fit in the matrix
    mat = zeros(T, (N, N+2))
    v = zeros(T, N+2)
    for i=1:N+2
        v[i] = one(T)
        #=
            calculating the effect on a unit vector to get the matrix of transformation
            to get the vector in the new vector space.
        =#
        mul!(view(mat,:,i), A, v)
        v[i] = zero(T)
    end
    return mat
end

# Tests the corrrectness of stencils, along with concretization.
# Do not modify the following test-set unless you are completely certain of your changes.
@testset "Correctness of Stencils" begin
    N = 20
    L1 = UpwindDifference(1,2, 1.0, N, t->1.0)
    correct = [-3/2, 2.0, -1/2]
    @test L1.stencil_coefs ≈ correct

    L1 = UpwindDifference(1,3, 1.0, N, t->1.0)
    correct = [-2/6, -3/6, 6/6, -1/6]
    @test L1.stencil_coefs ≈ correct
end

@testset "Taking derivatives" begin
    N = 20
    x = 0:1/(N-1):1
    y = 2x.^2 .- 3x .+2
    y_ = y[2:end-1]
    dx = x[2] - x[1]
    # y = x.^2

    # Dirichlet BC with fixed end points
    Q = RobinBC((1.0, 0.0, y[1]), (1.0, 0.0, y[end]), 1.0)
    U = UpwindDifference(1,2, dx, N-2, t->1.0)
    A = CenteredDifference(1,2, dx, N-2)
    D1 = CenteredDifference(1,2, dx, N-4) # For testing whether the array is constant

    res1 = U*Q*y_
    res2 = A*Q*y_

    @test res1[3:end-2] ≈ res2[1:end-4] # shifted due to upwind operators
    # It is shifted by a constant value so its first derivative has to be 0
    @test D1*(res1[3:end-2] - res2[3:end-2]) ≈ zeros(12) atol=10.0^(-6)

    # @test res3[3:end-2] ≈ res4[1:end-4] # shifted due to upwind operators
    # It is shifted by a constant value so its first derivative has to be 0
    # @test D1*(res4[3:end-2] - res4[3:end-2]) ≈ zeros(12) atol=10.0^(-6)

    y = 3x.^3 .- 4x.^2 .+ 2x .+ 1
    y_ = y[2:end-1]
    Q = RobinBC((1.0, 0.0, y[1]), (1.0, 0.0, y[end]), 1.0)
    U = UpwindDifference(2,2, 1.0, N-2, t->1.0)
    A = CenteredDifference(2,2, 1.0, N-2)
    res1 = U*Q*y_
    res2 = A*Q*y_

    @test res1 ≈ res2 # shifted due to upwind operators
end

@testset "Full and Sparse functions:" begin
    N = 10
    d_order = 2
    approx_order = 2
    correct = second_derivative_stencil(N)
    A = UpwindDifference(d_order,approx_order,1.0,N,t->1.0)

    @test convert_by_multiplication(Array,A,N) ≈ correct atol=10.0^(-4)
    @test Array(A) ≈ second_derivative_stencil(N) atol=10.0^(-4)
    @test sparse(A) ≈ second_derivative_stencil(N) atol=10.0^(-4)
    @test BandedMatrix(A) ≈ second_derivative_stencil(N) atol=10.0^(-4)
    @test_broken opnorm(A, Inf) ≈ opnorm(correct, Inf) atol=10.0^(-4)

    # testing higher derivative and approximation concretization
    N = 20
    d_order = 4
    approx_order = 4
    A = UpwindDifference(d_order,approx_order,1.0,N,t->1.0)
    correct = convert_by_multiplication(Array,A,N)

    @test Array(A) ≈ correct
    @test sparse(A) ≈ correct
    @test BandedMatrix(A) ≈ correct

    N = 100
    d_order = 8
    approx_order = 8
    A = UpwindDifference(d_order,approx_order,1.0,N,t->1.0)
    correct = convert_by_multiplication(Array,A,N)

    @test Array(A) ≈ correct
    @test sparse(A) ≈ correct
    @test BandedMatrix(A) ≈ correct

    # testing correctness of multiplication
    N = 1000
    d_order = 4
    approx_order = 10
    y = collect(1:1.0:N+2).^4 - 2*collect(1:1.0:N+2).^3 + collect(1:1.0:N+2).^2;
    y = convert(Array{BigFloat, 1}, y)

    A = UpwindDifference(d_order,approx_order,one(BigFloat),N,t->1.0)
    correct = convert_by_multiplication(Array,A,N)
    @test Array(A) ≈ correct
    @test sparse(correct) ≈ correct
    @test BandedMatrix(A) ≈ correct
    @test A*y ≈ Array(A)*y
end

@testset "Indexing tests" begin
    N = 1000
    d_order = 4
    approx_order = 10

    A = UpwindDifference(d_order,approx_order,1.0,N,t->1.0)
    @test A[1,1] == Array(A)[1,1]
    @test A[10,20] == 0.0

    correct = Array(A)
    for i in 1:N
        @test A[i,i] ≈ correct[i,i] atol=10^-9
    end

    # Indexing Tests
    N = 1000
    d_order = 2
    approx_order = 2

    A = UpwindDifference(d_order,approx_order,1.0,N,t->1.0)
    M = Array(A,N)
    @test A[1,1] == M[1,1]
    @test A[1:4,1] == M[1:4,1]
    @test A[5,2:10] == M[5,2:10]
    @test A[60:100,500:600] == M[60:100,500:600]

    d_order = 4
    approx_order = 10

    A = UpwindDifference(d_order,approx_order,1.0,N,t->1.0)
    M = Array(A,N)
    @test A[1,1] == M[1,1]
    @test A[1:4,1] == M[1:4,1]
    @test A[5,2:10] == M[5,2:10]
    @test A[524,:] == M[524,:]
    @test A[60:100,500:600] == M[60:100,500:600]
end

@testset begin "Operations on matrices"
    N = 51
    M = 101
    d_order = 2
    approx_order = 2

    xarr = range(0,stop=1,length=N)
    yarr = range(0,stop=1,length=M)
    dx = xarr[2]-xarr[1]
    dy = yarr[2]-yarr[1]
    F = [x^2+y for x = xarr, y = yarr]

    A = UpwindDifference(d_order,approx_order,dx,length(xarr)-2,t->1.0)
    B = UpwindDifference(d_order,approx_order,dy,length(yarr),t->1.0)

    @test A*F ≈ 2*ones(N-2,M)
    F*B
    # A*F*B

    G = [x^2+y^2 for x = xarr, y = yarr]

    @test A*G ≈ 2*ones(N-2,M) atol=1e-2
    G*B
    # A*G*B
end


using RowEchelon
using Test

As = Vector{Matrix{Int}}()
Rs = Vector{Matrix{Int}}()
Ps = Vector{Vector{Int}}()
# Issue #518 of Julia Base
push!(As, [ 1  2  0   3;
            2  4  0   7])
push!(Rs, [ 1  2  0   0;
            0  0  0   1])
push!(Ps, [ 1; 4])
# Example from Wikipedia
push!(As, [ 1  3  -1;
            0  1   7])
push!(Rs, [ 1  0 -22;
            0  1   7])
push!(Ps, [ 1; 2])
# Example from Wikibooks
push!(As, [ 0  3 -6   6  4  -5;
            3 -7  8  -5  8   9;
            3 -9 12  -9  6  15])
push!(Rs, [ 1  0 -2   3  0 -24;
            0  1 -2   2  0  -7;
            0  0  0   0  1   4])
push!(Ps, [ 1; 2; 5])
# Example from Rosetta Code
push!(As, [ 1  2 -1  -4;
            2  3 -1 -11;
           -2  0 -3  22])
push!(Rs, [ 1  0  0  -8;
            0  1  0   1;
            0  0  1  -2])
push!(Ps, [ 1; 2; 3])
# Magic Square
push!(As, [16  2  3  13;
            5 11 10   8;
            9  7  6  12;
            4 14 15   1])
push!(Rs, [ 1  0  0   1;
            0  1  0   3;
            0  0  1  -3;
            0  0  0   0])
push!(Ps, [ 1; 2; 3])

@testset "Matrices of integers (treated as Float64)" begin
    for (A,R) in zip(As,Rs)
        C = rref(A)
        @test C isa Matrix{Float64}
        @test C ≈ R
    end
end

@testset "Matrices of rationals" begin
    for (A,R) in zip(As,Rs)
        B = Matrix{Rational{Int}}(A)
        C = rref(B)
        @test C isa Matrix{Rational{Int}}
        @test C == R
    end
end

@testset "Matrix of Complex numbers" begin
    A = [1+ im 1-im  4;
         3+2im 2-im  2]
    R = [1     0    -2- im;
         0     1     1+4im]
    for conv in [false, true]
        if conv
            A = Matrix{ComplexF64}(A)
        end
        C = rref(A)
        @test C isa Matrix{ComplexF64}
        @test C ≈ R
    end
end

@testset "Matrices of integers (treated as Float64) with Pivots" begin
    for (A,X) in zip(As, zip(Rs,Ps))
        R=X[1]
        P=X[2]
        C = rref_with_pivots(A)
        @test C[1] isa Matrix{Float64}
        @test C[1] ≈ R
        @test C[2] == P
    end
end

@testset "Matrices of rationals with Pivots" begin
    for (A,X) in zip(As, zip(Rs,Ps))
        R=X[1]
        P=X[2]
        B = Matrix{Rational{Int}}(A)
        C = rref_with_pivots(B)
        @test C[1] isa Matrix{Rational{Int}}
        @test C[1] == R
        @test C[2] == P
    end
end

@testset "Matrix of Complex numbers with Pivots" begin
    A = [1+ im 1-im  4;
         3+2im 2-im  2]
    R = [1     0    -2- im;
         0     1     1+4im]
    for conv in [false, true]
        if conv
            A = Matrix{ComplexF64}(A)
        end
        C = rref_with_pivots(A)
        @test C[1] isa Matrix{ComplexF64}
        @test C[1] ≈ R
        @test C[2] == [1; 2]
    end
end


using StanSample, Distributions

N = 100;
df = DataFrame(
    height = rand(Normal(10, 2), N),
    leg_prop = rand(Uniform(0.4, 0.5), N),
);
df.leg_left = df.leg_prop .* df.height + rand(Normal(0, 0.02), N);
df.leg_right = df.leg_prop .* df.height + rand(Normal(0, 0.03), N);

stan6_1 = "
data {
  int <lower=1> N;
  vector[N] H;
  vector[N] LL;
  vector[N] LR;
}
parameters {
  real a;
  vector[2] b;
  real <lower=0> sigma;
}
model {
  vector[N] mu;
  mu = a + b[1] * LL + b[2] * LR;
  a ~ normal(10, 100);
  b ~ normal(2, 10);
  sigma ~ exponential(1);
  H ~ normal(mu, sigma);
}
";

m6_1s = SampleModel("m6.1s", stan6_1);
data = (H = df.height, LL = df.leg_left, LR = df.leg_right, N = size(df, 1));

rc6_1s = stan_sample(m6_1s; data);
if success(rc6_1s)
    nt6_1s = read_samples(m6_1s)
end

nt6_1s.b |> display
println()
mean(nt6_1s.b, dims=2) |> display

init = (a = 2.0, b = [1.0, 2.0], sigma = 1.0)
rc6_2s = stan_sample(m6_1s; data, init);
if success(rc6_1s)
    nt6_2s = read_samples(m6_1s)
end

nt6_2s.b |> display
println()
mean(nt6_2s.b, dims=2) |> display
println()

read_summary(m6_1s) |> display
println()

post6_1s_df = read_samples(m6_1s; output_format=:dataframe) |> display
println()

part6_1s = read_samples(m6_1s; output_format=:particles) |> display


using RobustAndOptimalControl, ControlSystems

# example 19.1
A = [
    -1 0 4
    0 2 0
    0 0 -3
]
B1 = [
    0 0
    1 0
    0 0
]
C1 = B1'
B2 = [
    1
    1
    1
]
C2 = B2'
D11 = 0I(2)
D12 = [
    0
    1
]
D21 = D12'
D22 = 0I(1)

P = ExtendedStateSpace(A, B1, B2, C1, C2, D11, D12, D21, D22)
for γ ∈ (nothing, 1000)
    K, Cl = h2synthesize(P, γ) # the example uses h2syn
    @test norm(Cl) ≈ 8.9582 rtol = 1e-3

    K_ML = let
        A = [-1.0 -4.236148004700622 4.0; -4.23607198996497 -6.472215758593602 -4.23607198996497; 0.0 -4.236148004700622 -3.0]
        B = [0.0; 4.23607198996497; 0.0]
        C = [0.0 -4.236148004700622 0.0]
        D = [0.0]
        sys = ss(A,B,C,D)
    end
    @test K ≈ K_ML rtol=1e-3

    Cl_ML = let
        A = [-1.0 0.0 4.0 0.0 -4.236148004700622 0.0; 0.0 2.0 0.0 0.0 -4.236148004700622 0.0; 0.0 0.0 -3.0 0.0 -4.236148004700622 0.0; 0.0 0.0 0.0 -1.0 -4.236148004700622 4.0; 4.23607198996497 4.23607198996497 4.23607198996497 -4.23607198996497 -6.472215758593602 -4.23607198996497; 0.0 0.0 0.0 0.0 -4.236148004700622 -3.0]
        B = [0.0 0.0; 1.0 0.0; 0.0 0.0; 0.0 0.0; 0.0 4.23607198996497; 0.0 0.0]
        C = [0.0 1.0 0.0 0.0 0.0 0.0; 0.0 0.0 0.0 0.0 -4.236148004700622 0.0]
        D = [0.0 0.0; 0.0 0.0]
        ss(A,B,C,D)
    end

    @test Cl_ML ≈ Cl rtol=1e-3

    # s = tf("s")
    # K⁺ = ss(-148.79(s+1)*(s+3) / ((s+31.74)*(s+3.85)))
    # RobustAndOptimalControl.controller_reduction_weight(P, K⁺)
    # K̂⁺ = controller_reduction(P, K⁺, 1, true)
    # @test K̂⁺ ≈ tf(-117.085, [1, 34.526])
end

# Test conversion between H2 problem and LQG
K, Cl = h2synthesize(P)
#=
    The controller above is given by -L(sI-Ae)\K where L is feedback gain and K is Kalman gain.
    We have the following equivalences

    C1'C1    = Q
    D12'D12  = R

    B1*B1'   = R1
    D21*D21' = R2
=#
Q = C1'C1
R = D12'D12
R1 = B1*B1'
R2 = D21*D21'

L = lqr(system_mapping(P), Q, R)
@test L ≈ -K.C

Kal = kalman(system_mapping(P), R1, R2)
@test Kal ≈ K.B

l = LQGProblem(P)
@test lqr(l) == L
@test kalman(l) == Kal

# Implement normalized coprime fact from sec 13.8


# # Bound on Global Extremum

#md # [![](https://mybinder.org/badge_logo.svg)](@__BINDER_ROOT_URL__/generated/Polynomial Optimization/bound_on_blobal_extremum.ipynb)
#md # [![](https://img.shields.io/badge/show-nbviewer-579ACA.svg)](@__NBVIEWER_ROOT_URL__/generated/Polynomial Optimization/bound_on_global_extremum.ipynb)
# **Adapted from**: SOSTOOLS' SOSDEMO3 (See Section 4.3 of [SOSTOOLS User's Manual](http://sysos.eng.ox.ac.uk/sostools/sostools.pdf))

using Test #src
using DynamicPolynomials
@polyvar x1 x2

# The Goldstein-Price function $f(x)$ is defined as follows:

f1 = x1 + x2 + 1
f2 = 19 - 14x1 + 3x1^2 - 14x2 + 6x1*x2 + 3x2^2
f3 = 2x1 - 3x2
f4 = 18 - 32x1 + 12x1^2 + 48x2 - 36x1*x2 + 27x2^2
f = (1 + f1^2 * f2) * (30 + f3^2 * f4)

# We need to pick an SDP solver, see [here](https://jump.dev/JuMP.jl/v0.21.6/installation/#Supported-solvers) for a list of the available choices.
# We use `SOSModel` instead of `Model` to be able to use the `>=` syntax for Sum-of-Squares constraints.

using SumOfSquares
using CSDP
solver = optimizer_with_attributes(CSDP.Optimizer, MOI.Silent() => true)
model = SOSModel(solver);

# We create the decision variable $\gamma$ that will be the lower bound to the Goldstein-Price function.
# We maximize it to have the highest possible lower bound.

@variable(model, γ)
@objective(model, Max, γ)

# We constrain $\gamma$ to be a lower bound with the following constraint
# that ensures that $f(x_1, x_2) \ge \gamma$ for all $x_1, x_2$.

@constraint(model, f >= γ)

JuMP.optimize!(model)

# We verify that the solver has found a feasible solution:

JuMP.primal_status(model)
@test JuMP.primal_status(model) == MOI.FEASIBLE_POINT || JuMP.primal_status(model) == MOI.NEARLY_FEASIBLE_POINT #src

# We can now obtain the lower bound either with `value(γ)` or `objective_value(model)`:

objective_value(model)
@test objective_value(model) ≈ 3 rtol=1e-2 #src


using DynamicAxisWarping, DSP, LPVSpectral, Distances, SpectralDistances

# Create two signals
N = 48_000
g(x,N) = exp(-10*(x-N/2)^2/N^2)
t = 1:N
f = range(0.01, stop=1, length=N)
y = sin.(t .* f) .* g.(t, N)
y1 = [y; 0y] .+ 0.1 .* randn.()
y2 = [0y; y] .+ 0.1 .* randn.()

plot([y1 y2])
#
M1,M2 = melspectrogram.((y1,y2), 2048)
plot(plot(M1), plot(M2))
# Calculate the Dynamic Time-Warping cost between them (I do this in √ domain since that appears to produce the nicest looking plots)
@btime dtw(.√(M1.power), .√(M2.power))[1] evals=1 samples=5
# Calculating this distance is rather fast.
# Visualize the coupling matrix. The green line should cross diagonally (since the two signals are the same) starting halfway along the axes (since one signal is shifted by 50% of the length copared to the other).
dtwplot(.√(M1.power), .√(M2.power), linecolor=:green)
#

# The code above used the squared Euclidean distance between spectra for each time point. We can replace that distance with a transport-based distance instead.
# We thus combine dynamic time warping with a transport-based cost along the frequency axis
n,m = size(M1.power)
dist = DiscreteGridTransportDistance(Cityblock(), n, n)
dtw(.√(M1.power), .√(M2.power), dist)[1]
#
dtwplot(.√(M1.power), .√(M2.power), dist, linecolor=:green)
# We now see that the valley through which the path can move is slightly wider. This happens since there is now an additional degree of freedom due to transport along the frequency axis. Since there is no need for that transport in this case, the resulting path remains almost the same.
# We can create the need for some transport along the frequency axis

f1 = range(0.01, stop=1, length=N)
f2 = range(0.1, stop=1, length=N).^2
y10 = sin.(t .* f1) .* g.(t, N)
y20 = sin.(t .* f2) .* g.(t, N)
y1 = [y10; 0y10] .+ 0.1 .* randn.()
y2 = [0y20; y20] .+ 0.1 .* randn.()
M1,M2 = melspectrogram.((y1,y2), 2048)
plot(plot(M1), plot(M2))
# We now redo the same stuff as above
dtw(.√(M1.power), .√(M2.power))[1]
#
dtwplot(.√(M1.power), .√(M2.power), linecolor=:green)
#
dtw(.√(M1.power), .√(M2.power), dist)[1]
#
dtwplot(.√(M1.power), .√(M2.power), dist, linecolor=:green)
# We expect to see less time warping (straighter green line) now when it's allowed to fudge the distance a bit by shuffling mass along the frequency axis.
# How long time does it take to compute such a distance? (on a laptop, expect about 3x improvement on desktop)
@btime dtw($(M1.power), $(M2.power), $dist)
@btime dtw($(M1.power), $(M2.power), radiuslimits(50,m,m)..., $dist)
# The size of the spectrogram
size(M1.power)
# The complexity is almost linear in the number of frequency bins, but quadratic in the number of time steps.


module TestChebyshev

export test_convergence, test_convergence_fd, test_X_matrices, test_to_cheb, test_to_cheb_to_real

include("../src/CustomTypes.jl")
include("../src/Chebyshev.jl")
using .CustomTypes

import Test: @test

const tolerance = 1e-8 ## tolerance we compare to

##============================================================
import .Chebyshev as CH

"""
One norm
"""
function one_norm(v)
   norm = 0.0
   n = length(v)
   for val in v
      norm += abs(val)
   end
   return norm/n
end

"""
Evaluate derivative compared to analytic value.
"""
function evaluate_difference(
      nx::myI,
      xmin::myF,
      xmax::myF,
      f::Function,
      d1f::Function,
      d2f::Function
   )
   xvals = CH.cheb_pts(xmin, xmax, nx)

   D1 = CH.mat_D1(xmin, xmax, nx)
   D2 = D1*D1

   fv   = [f(x)   for x in xvals]
   d1fv = [d1f(x) for x in xvals]
   d2fv = [d2f(x) for x in xvals]

   compare_d1fv = D1*fv
   compare_d2fv = D2*fv

   norm_d1 = one_norm(compare_d1fv .- d1fv)
   norm_d2 = one_norm(compare_d2fv .- d2fv)

   return norm_d1, norm_d2
end

"""
Evaluate finite difference derivative compared to analytic value.
"""
function evaluate_difference_fd(
      nx::myI,
      xmin::myF,
      xmax::myF,
      f::Function,
      d1f::Function,
      d2f::Function
   )
   xvals = CH.cheb_pts(xmin, xmax, nx)

   D1 = CH.mat_fd_D1(xmin, xmax, nx)
   D2 = CH.mat_fd_D2(xmin, xmax, nx)

   fv   = [f(x)   for x in xvals]
   d1fv = [d1f(x) for x in xvals]
   d2fv = [d2f(x) for x in xvals]

   compare_d1fv = D1*fv
   compare_d2fv = D2*fv

   norm_d1 = one_norm(compare_d1fv .- d1fv)
   norm_d2 = one_norm(compare_d2fv .- d2fv)

   return norm_d1, norm_d2
end

"""
Evaluate convergence of the derivative matrices.
"""
function test_convergence(
      nx::myI,
      xmin::myF,
      xmax::myF,
      f::Function,
      d1f::Function,
      d2f::Function
   )

   norm_d1_1, norm_d2_1 = evaluate_difference(
      nx,                          xmin, xmax, f, d1f, d2f
   )
   norm_d1_2, norm_d2_2 = evaluate_difference(
      convert(myI,ceil(1.1*nx)), xmin, xmax, f, d1f, d2f
   )

   @test norm_d1_1/norm_d1_2 > 5.0
   @test norm_d2_1/norm_d2_2 > 5.0

   return nothing
end

"""
Evaluate convergence of the finite difference derivative matrices.
"""
function test_convergence_fd(
      nx::myI,
      xmin::myF,
      xmax::myF,
      f::Function,
      d1f::Function,
      d2f::Function
   )

   norm_d1_1, norm_d2_1 = evaluate_difference_fd(
      nx,                          xmin, xmax, f, d1f, d2f
   )
   norm_d1_2, norm_d2_2 = evaluate_difference_fd(
      convert(myI,ceil(2*nx)), xmin, xmax, f, d1f, d2f
   )

   @test norm_d1_1/norm_d1_2 > 3.5
   @test norm_d2_1/norm_d2_2 > 3.5

   return nothing
end

"""
Evaluate derivative on the X matrices
"""
function test_X_matrices(
      nx::myI,
      xmin::myF,
      xmax::myF
   )

   i_1 = ones(myF,nx)
   D_1 = CH.mat_D1(xmin, xmax, nx)
   X_1 = CH.mat_X( xmin, xmax, nx)

   i_2 = ones(myF,convert(myI,ceil(1.1*nx)))
   D_2 = CH.mat_D1(xmin, xmax, convert(myI,ceil(1.1*nx)))
   X_2 = CH.mat_X( xmin, xmax, convert(myI,ceil(1.1*nx)))

   norm_1 = one_norm(D_1*sin.(X_1*i_1) .- cos.(X_1*i_1))
   norm_2 = one_norm(D_2*sin.(X_2*i_2) .- cos.(X_2*i_2))

   @test norm_1/norm_2 > 2.0

   return nothing
end

"""
Test can recover Chebyshev coefficients from real space data.
"""
function test_to_cheb(nx::myI)

   i_1 = ones(myF, nx)
   X_1 = CH.mat_X(tomyF(-1.0), tomyF(1.0), nx)

   for n=0:(nx-5)
      values = cos.(n*acos.(X_1*i_1)) ## nth Chebyshev polynomial
      c_1 = CH.to_cheb(values) ## coefficients, only nth should be nonzero
      for i=1:nx
         if i==n+1
            @test abs(c_1[i] - 1.0) < 1e-14
         else
            @test abs(c_1[i]) < 1e-14
         end
      end
   end
   return nothing
end

"""
Test can go to and from Chebyshev space correctly.
"""
function test_to_cheb_to_real(nx::myI, xmin::myF, xmax::myF)

   nx_1 = nx
   nx_2 = convert(myI,ceil(1.1*nx))

   i_1 = ones(myF, nx_1)
   X_1 = CH.mat_X(xmin, xmax, nx_1)
   xv  = X_1*i_1
   values = tanh.(xv)
   c_1 = CH.to_cheb(values)
   r_1 = CH.to_real(c_1)
   n_1 = one_norm(values .- r_1)

   i_2 = ones(myF, nx_2)
   X_2 = CH.mat_X(xmin, xmax, nx_2)
   xv  = X_2*i_2
   values = tanh.(xv)
   c_2 = CH.to_cheb(values)
   r_2 = CH.to_real(c_2)

   n_2 = one_norm(values .- r_2)

   @test n_2 < n_1

   return nothing
end

end


module proj
using LinearAlgebra
# ! indicates that the variable is modified

function infnorm(x,n,γ)
# compute ∞-norm of x
# ---
# x : input vector 				(real n-vector)
# n : length of x
# γ : temp storage variable		(real scalar)    !

	γ = 0.0
	for j=1:n
		γ = max(γ,abs(x[j]))
	end
	return γ
end

function hlfspace!(x,z,u,ζ,n,β)
# projection onto halfspace { x | u⋅x ≤ ζ }
# ---
# x : projected result					(real n-vector) !
# z : vector to be projected			(real n-vector)
# u : normal to supporting hyperplane 	(real n-vector)
# ζ : scalar on RHS 					(real scalar)
# β : temporary variable                (real scalar)   !

	@assert BLAS.nrm2(n,u,1)≈1 "Normal vector u should have unit norm."

	β = BLAS.dot(n,z,1,u,1) - ζ

	if β > 0
		BLAS.blascopy!(n,z,1,x,1)
		axpy!(-β,u,x)
	else
		BLAS.blascopy!(n,z,1,x,1)
	end

end

function hlfspace2!(x,z,u1,u2,ζ1,ζ2,n,β,α1,α2)
# projection onto intersection of two halfspaces { x | u1⋅x ≤ ζ1 } ∩ { x | u2⋅x ≤ ζ2 }
# ---
# x  : projected result					(real n-vector) !
# z  : vector to be projected			(real n-vector)
# u1 : normal to supporting hyperplane 	(real n-vector)
# u2 : normal to supporting hyperplane 	(real n-vector)
# ζ1 : scalar on RHS 					(real scalar)
# ζ1 : scalar on RHS 					(real scalar)
# β  : temporary variable               (real scalar)   !
# α1 : temporary variable               (real scalar)   !
# α2 : temporary variable               (real scalar)   !

	@assert BLAS.nrm2(n,u1,1)≈1 "Normal vector u1 should have unit norm."
	@assert BLAS.nrm2(n,u2,1)≈1 "Normal vector u2 should have unit norm."

	β = BLAS.dot(n,u1,1,u2,1)
	α1 = BLAS.dot(n,z,1,u1,1)
	α2 = BLAS.dot(n,z,1,u2,1)

	if β ≈ 1
		β = min(ζ1,ζ2)
		if α1 > β
			BLAS.blascopy!(n,z,1,x,1)
			axpy!(β-α1,u1,x)
		else
			BLAS.blascopy!(n,z,1,x,1)
		end
	elseif β ≈ -1
		@assert ζ1+ζ2>0 "The two halfspaces do not intersect."
		if α1 < -ζ2
			BLAS.blascopy!(n,z,1,x,1)
			axpy!(-α1-ζ2,u1,x)
		elseif α1 > ζ1
			BLAS.blascopy!(n,z,1,x,1)
			axpy!(-α1+ζ1,u1,x)
		else
			BLAS.blascopy!(n,z,1,x,1)
		end
	else
		if α1-ζ1+β*(ζ2-α2)>0 && α2-ζ2+β*(ζ1-α1)>0
			BLAS.blascopy!(n,z,1,x,1)
			axpy!((α1-ζ1+β*(ζ2-α2))/(β*β-1),u1,x)
			axpy!((α2-ζ2+β*(ζ1-α1))/(β*β-1),u2,x)
		elseif α2>ζ2 && α1-ζ1+β*(ζ2-α2)≤0
			BLAS.blascopy!(n,z,1,x,1)
			axpy!(ζ2-α2,u2,x)
		elseif α1>ζ1 || α2>ζ2
			BLAS.blascopy!(n,z,1,x,1)
			axpy!(ζ1-α1,u1,x)
		else
			BLAS.blascopy!(n,z,1,x,1)
		end
	end

end

function box!(x,z,l,u,n)
# projection onto l∞-norm ball
# ---
# x : projected result			(real n-vector) !
# z : vector to be projected	(real n-vector)
# l : lower bound			 	(real scalar)
# u : upper bound 				(real scalar)
# n : length of z 				(integer)

	@assert u>l "Upper bound should be greater than lower bound."

	for j = 1:n
		x[j] = min(u,max(z[j],l))
	end

end

function ball!(x,z,r,n)
# projection onto l2-norm ball
# ---
# x : projected result			(real n-vector) !
# z : vector to be projected	(real n-vector)
# r : radius of l2-norm ball 	(real scalar)
# n : length of z 				(integer)

	@assert r>0 "Radius should be positive."

	BLAS.blascopy!(n,z,1,x,1)
	BLAS.scal!(n,r/max(BLAS.nrm2(n,x,1),r),x,1)

end

function soc!(x,z,α,n,β)
# projection onto second-order cone (aka soc, ice-cream, lorentz cone)
# type 1 : cone axis along last coordinate and arctan(α) is the cone angle
# ---
# x : projected result 		     (real n-vector) !
# z : vector to be projected	 (real n-vector)
# α : tan(cone angle)			 (real scalar)
# n : length of z (integer)		 (integer)
# β : temp storage variable  	 (real scalar)   !

	@assert α>0 "Cone angle arctan(α) must be positive."

	β = BLAS.nrm2(n-1,z,1)

	if β ≤ α*z[n]
		BLAS.blascopy!(n,z,1,x,1)
	elseif α*β + z[n] ≤ 0
		fill!(x,0.0)
	else
		BLAS.blascopy!(n-1,z,1,x,1)
		x[n] = (β*α+z[n])/(α*α+1)
		BLAS.scal!(n-1,α*x[n]/β,x,1)
	end

end

function soc2!(x,z,θ1,θ2,u,n,β,γ,κ)
# projection onto second-order cone (aka soc, ice-cream, lorentz cone)
# type 2 : cone axis along unit vector u and arccos(θ) is the cone angle
# ---
# x  : projected result 			(real n-vector) !
# z  : vector to be projected		(real n-vector)
# θ1 : cos(cone angle) 				(real scalar)
# θ2 : sin(cone angle) 				(real scalar)
# u  : unit vector along cone axis	(real n-vector)
# n  : length of z 					(integer)
# β  : temp storage variable 		(real scalar)   !
# γ  : temp storage variable 		(real scalar)   !
# κ  : temp storage variable 		(real n-vector) !

	@assert min(θ1,1-θ1)>0 && min(θ2,1-θ2)>0 "θ1,θ2 ∈ [0,1]"
	@assert BLAS.nrm2(n,u,1)≈1 "u must be a unit vector"

	β = BLAS.nrm2(n,z,1)
	γ = BLAS.dot(n,z,1,u,1)

	if θ1*β ≤ γ
		BLAS.blascopy!(n,z,1,x,1)
	elseif β*θ2 + γ ≤ 0
		fill!(x,0.0)
	else
		BLAS.blascopy!(n,u,1,x,1)
		BLAS.blascopy!(n,z,1,κ,1)
		axpy!(-γ,u,κ)
		axpby!(θ2/BLAS.nrm2(n,κ,1),κ,θ1,x)
		BLAS.scal!(n,BLAS.dot(n,z,1,x,1),x,1)
	end

end

function soc_ball!(x,z,α,r,n,β)
# projection onto intersection of soc cone and l2-norm ball
# ---
# x : projected result 			(real n-vector) !
# z : vector to be projected	(real n-vector)
# α : tan(cone angle) 			(real scalar)
# r : radius of l2-norm ball 	(real scalar)
# n : length of z 				(integer)
# β : temp storage variable 	(real scalar)   !

	soc!(x,z,α,n,β)
	ball!(x,x,r,n)

end

function soc2_ball!(x,z,θ1,θ2,u,r,n,β,γ,κ)
# projection onto intersection of soc cone and l2-norm ball
# ---
# x  : projected result 			(real n-vector) !
# z  : vector to be projected		(real n-vector)
# θ1 : cos(cone angle) 				(real scalar)
# θ2 : cos(cone angle) 				(real scalar)
# u  : unit vector along cone axis	(real n-vector)
# r  : radius of l2-norm ball 		(real scalar)
# n  : length of z 					(integer)
# β  : temp storage variable 		(real scalar)   !
# γ  : temp storage variable 		(real scalar)   !
# κ  : temp storage variable 		(real n-vector) !

	soc2!(x,z,θ1,θ2,u,n,β,γ,κ)
	ball!(x,x,r,n)

end

function admm!(x,y,u,z,f1,f2,α,β,ϵ,n,γ1,γ2,κ1,κ2)
# projection onto intersection of two sets with respective projections defined by f1 and f2
# ---
# x  : projected result (copy 1)	(real n-vector) !
# y  : projected result (copy 2)	(real n-vector) !
# u  : dual variable 				(real n-vector) !
# z  : vector to be projected		(real n-vector)
# f1 : projection function			(function: f!(θ,ψ) where θ is modified)
# f2 : projection function 			(function: f!(θ,ψ) where θ is modified)
# α  : ADMM step size				(real scalar)
# β  : 1/(1+α) 						(real scalar)
# ϵ  : ADMM termination criteria  	(real scalar)
# n  : length of z					(integer)
# γ  : temp storage variable        (real scalar)   !
# γ2 : temp storage variable 		(real scalar)   !
# κ1 : temp storage variable 		(real n-vector) !
# κ2 : temp storage variable 		(real n-vector) !

# x, y and u may hold values from previous call to admm! (warm-starting)

	f1(κ1,z)
	f2(κ2,z)
	κ1 .= κ1 .- z
	κ2 .= κ2 .- z
	γ1 = BLAS.nrm2(n,κ2,1)
	γ1 = max(γ1,BLAS.nrm2(n,κ1,1))
	if γ1 ≤ ϵ
	    BLAS.blascopy!(n,z,1,x,1)
		BLAS.blascopy!(n,z,1,y,1)
		# κ2[1] will be zero fortunately
		κ2[1] = 0.0 # inserted for type-stability
	else
		κ1 .= z .- u
		axpy!(α,y,κ1)
		BLAS.scal!(n,β,κ1,1)
		f1(x,κ1)
		κ2 .= z .+ u
		axpy!(α,x,κ2)
		BLAS.scal!(n,β,κ2,1)
		f2(y,κ2)
		κ1 .= x .- y
		axpy!(α,κ1,u)

		κ2[1] = infnorm(κ1,n,γ2)
		γ1 = 1.0
		while κ2[1]≥ϵ
			κ1 .= z .- u
			axpy!(α,y,κ1)
			BLAS.scal!(n,β,κ1,1)
			f1(x,κ1)
			κ2 .= z .+ u
			axpy!(α,x,κ2)
			BLAS.scal!(n,β,κ2,1)
			f2(y,κ2)
			κ1 .= x .- y
			axpy!(α,κ1,u)

			γ1 = γ1 + 1.0 # iteration counter

			κ2[1] = infnorm(κ1,n,γ2)
		end
		κ2[1] = γ1 # store the iteration count
	end

end

function multi_admm!(x,y,u,z,f,α,β,ϵ,n,N,γ1,γ2,ψ,κ)
# projection onto intersection of N sets with projections defined by f[i], i=1,...,N
# ---
# x  : projected results (copy 1)	(N-vector of real n-vectors) !
# y  : projected results (copy 2)	(N-vector of real n-vectors) !
# u  : dual variable 				(N-vector of real n-vectors) !
# z  : vector to be projected		(real n-vector)
# f  : N projection functions   	(N-vector of functions: f[i]!(θ,ψ) where θ is modified, i=1,...,N)
# α  : ADMM step-size				(positive scalar)
# β  : 1/(2α(N-1)+1)				(positive scalar)
# n  : length of z 					(integer)
# N  : no. of sets					(integer)
# γ1 : temp storage variable  		(real scalar)                !
# γ2 : temp storage variable  		(real scalar)                !
# ψ  : temp storage variable  		(real scalar)                !
# κ  : temp storage variable        (real n-vector)              !

	@assert length(f)==N "No. of projection functions is not N."
	# @assert N ≥ 3 "Use admm! for fewer than 3 sets."

	# γ1 = 100.0
	# ψ = 0.0 # iteration counter

	for i = 1:N
		κ .= x[i] # BLAS.blascopy!(n,x[i],1,κ,1)
		lmul!(N-2,κ)
		for j = 1:N
			κ .= κ .+ x[j]
		end
		lmul!(α,κ)
		κ .= κ .- u[i] .+ z
		lmul!(β,κ)
		f[i](y[i],κ)
	end

	γ1 = 0.0 # holds termination criteria value
	x[1] .= y[1]
	for i = 2:N
		x[i] .= y[i]
		κ .= x[1] .- x[i]

		γ1 = max(γ1,infnorm(κ,n,γ2))
	end

	for i = 1:N
		κ .= x[i]
		lmul!(N,κ)
		for j = 1:N
			κ .= κ .- x[j]
		end
		axpy!(α,κ,u[i])
	end

	ψ = 1.0

	while γ1≥ϵ
		for i = 1:N
			κ .= x[i] # BLAS.blascopy!(n,x[i],1,κ,1)
			lmul!(N-2,κ)
			for j = 1:N
				κ .= κ .+ x[j]
			end
			lmul!(α,κ)
			κ .= κ .- u[i] .+ z
			lmul!(β,κ)
			f[i](y[i],κ)
		end

		γ1 = 0.0 # holds termination criteria value
		x[1] .= y[1]
		for i = 2:N
			x[i] .= y[i]
			κ .= x[1] .- x[i]

			γ1 = max(γ1,infnorm(κ,n,γ2))
		end

		for i = 1:N
			κ .= x[i]
			lmul!(N,κ)
			for j = 1:N
				κ .= κ .- x[j]
			end
			axpy!(α,κ,u[i])
		end

		ψ = ψ + 1.0
	end
	κ[1] = ψ

end

end


"""
    Original Code Taken from GraphLayout.jl
    Use the spring/repulsion model of Fruchterman and Reingold (1991):
        Attractive force:  f_a(d) =  d^2 / k
        Repulsive force:  f_r(d) = -k^2 / d
    where d is distance between two vertices and the optimal distance
    between vertices k is defined as C * sqrt( area / num_vertices )
    where C is a parameter we can adjust
    Arguments:
    adj_matrix Adjacency matrix of some type. Non-zero of the eltype
               of the matrix is used to determine if a link exists,
               but currently no sense of magnitude
    C          Constant to fiddle with density of resulting layout
    MAXITER    Number of iterations we apply the forces
    INITTEMP   Initial "temperature", controls movement per iteration
"""
function layout_spring{T}(adj_matrix::Array{T,2}, dim=1; C=2.0, MAXITER=100, INITTEMP=2.0)

    size(adj_matrix, 1) != size(adj_matrix, 2) && error("Adj. matrix must be square.")
    const N = size(adj_matrix, 1)

    # Initial layout is random on the square [-1,+1]^2
    locs_x = 2*rand(N) .- 1.0
    locs_y = 2*rand(N) .- 1.0
    locs_z = 2*rand(N) .- 1.0

    # The optimal distance bewteen vertices
    const K = C * sqrt(4.0 / N)

    # Store forces and apply at end of iteration all at once
    force_x = zeros(N)
    force_y = zeros(N)
    force_z = zeros(N)

    # Iterate MAXITER times
    @inbounds for iter = 1:MAXITER
        # Calculate forces
        for i = 1:N
            force_vec_x = 0.0
            force_vec_y = 0.0
            force_vec_z = 0.0
            for j = 1:N
                i == j && continue
                d_x = locs_x[j] - locs_x[i]
                d_y = locs_y[j] - locs_y[i]
                d_z = locs_z[j] - locs_z[i]
                d   = sqrt(d_x^2 + d_y^2 + d_z^2)
                if adj_matrix[i,j] != zero(eltype(adj_matrix)) || adj_matrix[j,i] != zero(eltype(adj_matrix))
                    F_d = d / K - K^2 / d^2
                else
                    F_d = -K^2 / d^2
                end

                force_vec_x += F_d*d_x
                force_vec_y += F_d*d_y
                force_vec_z += F_d*d_z
            end
            force_x[i] = force_vec_x
            force_y[i] = force_vec_y
            force_z[i] = force_vec_z
        end

        TEMP = INITTEMP / iter
        # Now apply them, but limit to temperature
        for i = 1:N
            force_mag  = sqrt(force_x[i]^2 + force_y[i]^2 + force_z[i]^2)
            scale      = min(force_mag, TEMP)/force_mag
            locs_x[i] += force_x[i] * scale
            locs_y[i] += force_y[i] * scale
            locs_z[i] += force_y[i] * scale
        end
    end

    # Scale to unit square
    min_x, max_x = minimum(locs_x), maximum(locs_x)
    min_y, max_y = minimum(locs_y), maximum(locs_y)
    min_z, max_z = minimum(locs_z), maximum(locs_z)
    function scaler(z, a, b)
        2.0*((z - a)/(b - a)) - 1.0
    end
    map!(z -> scaler(z, min_x, max_x), locs_x)
    map!(z -> scaler(z, min_y, max_y), locs_y)
    map!(z -> scaler(z, min_z, max_z), locs_z)

    if dim == 0
        locs_z = zeros(size(locs_x))
    end

    return locs_x,locs_y,locs_z
end


using Test

using Astro

print("leap years")
@test is_leap_year(2004) == true
@test is_leap_year(333, false) == false
println(" passed")

print("7.a Convert Gregorian date to Julian day number")
jd = cal_to_jd(1957, 10, 4.81)
@test jd == 2436116.31
println(" passed")

print("7.a Convert Gregorian date to Julian day number — meeus'")
jd = cal_to_jd_m(1957, 10, 4.81)
@test jd == 2436116.31
println(" passed")

print("7.b Convert Julian date to Julian day number")
jd = cal_to_jd(333, 1, 27.5, false)
# currently fails, is off by one
#    @test isapprox(jd, 1842713.0, atol=0.1)
println(" passed")

print("7.b Convert Julian date to Julian day number — meeus")
jd = cal_to_jd_m(333, 1, 27.5, false)
# currently fails, is off by one
#    @test isapprox(jd, 1842713.0, atol=0.1)
println(" passed")

print("7.c Convert Julian day number to Gregorian date")
yr, mo, day = jd_to_cal(2436116.31)
@test yr == 1957
@test mo == 10
@test isapprox(day, 4.81, atol=0.1)
println(" passed")

print("7.c(1) Convert Julian day number to Julian date")
yr, mo, day = jd_to_cal(1842713.0, false)
@test yr == 333
@test mo == 1
@test isapprox(day, 27.5, atol=0.01)
println(" passed")

print("7.c(2) Convert Julian day number to Julian date")
yr, mo, day = jd_to_cal(1507900.13, false)
@test yr == -584
@test mo == 5
@test isapprox(day, 28.63, atol=0.01)
println(" passed")

print("7.d Time interval in days")
jd0 = cal_to_jd(1910, 4, 20.0)
jd1 = cal_to_jd(1986, 2, 9.0)
@test jd1 - jd0 == 27689
println(" passed")

print("7.d(1) Time interval in days")
jd = cal_to_jd(1991, 7, 11)
jd = jd + 10000
yr, mo, day = jd_to_date(jd)
@test yr == 2018
@test mo == 11
@test day == 26
dow = jd_to_day_of_week(jd)
@test dow == 1
println(" passed")

print("7.f Day of the year")
N = cal_to_day_of_year(1978, 11, 14)
@test N == 318
println(" passed")

print("7.g Day of the year")
N = cal_to_day_of_year(1988, 4, 22)
@test N == 113
println(" passed")

print("7(pg 66-1) Day of the year to calendar")
mo, day = day_of_year_to_cal(1978, 318)
@test mo == 11
@test day == 14
println(" passed")

print("7(pg 66-2) Day of the year to calendar")
mo, day = day_of_year_to_cal(1988, 113)
@test mo == 4
@test day == 22
println(" passed")

tbl = Array[
    [1991, 3, 31],
    [1992, 4, 19],
    [1993, 4, 11],
    [1954, 4, 18],
    [2000, 4, 23],
    [1818, 3, 22]
]

print("8(pg 68) Gregorian Easter (6 times)")
for (yr, mo, day) in tbl
    # calculate xmo and xday given yr
    xmo, xday = easter(yr)
    map(print, [" ", yr, " ", mo, " ", day, " -> ", yr, " ", xmo, " ", xday])
    println()
    @test xmo == mo
    @test xday == day
end
println(" passed")

print("8(pg 69) Julian Easter (3 times)")
for yr in [179, 711, 1243]
    mo, day = easter(yr, false)
    @test mo == 4
    @test day == 12
end
println(" passed")

print("9.a Jewish Pesach")
mo, day = pesach(1990)
@test mo == 4
@test day == 10
println(" passed")

print("9.b Julian date of Moslem year 1421 first day")
yr, mo, day = moslem_to_christian(1421, 1, 1)
@test yr == 2000
@test mo == 4
@test day == 6
println(" passed")

print("9.c Moslem date for Aug 13 1991 gregorian")
yr, mo, day = christian_to_moslem(1991, 8, 13)
@test yr == 1412
@test mo == 2
@test day == 2
println(" passed")

print("Julian day to date")
(y, month, d, h, m, s) = jd_to_date(2436116.31, true)
@test y == 1957
@test month == 10
@test d == 4
@test h == 19
@test m == 26
@test isapprox(s, 24, atol=0.1)
println(" passed")

# 2014 September 5 09:41:24  2456905.90375 Friday
print("is September 5 DST?")
@test is_dst(2456905.90375) == true
println(" passed")

print("number of Julian centuries since J2000.0")
@test jd_to_jcent(2456905.90375) == 0.14677354551677085
println(" passed")

print("Julian date to formatted string 1")
@test lt_to_str(2456905.90375, "GMT", "second") == "Fri, 05 Sep 2014 09:41:24"
println(" passed")

print("Julian date to formatted string 2")
@test lt_to_str(2456905.90375, "GMT", "day") == "Fri, 05 Sep 2014"
println(" passed")


using EmpiricalRisks
using Base.Test
import DualNumbers

### auxiliary functions

function verify_uniloss(loss::UnivariateLoss, f, u::Float64, y::Real)
    # verify inferred types
    for VT in [Float64, Float32]
        @test Base.return_types(EmpiricalRisks.value, Tuple{typeof(loss), VT, VT}) == [VT]
        @test Base.return_types(EmpiricalRisks.deriv, Tuple{typeof(loss), VT, VT}) == [VT]
        @test Base.return_types(EmpiricalRisks.value_and_deriv, Tuple{typeof(loss), VT, VT}) == [Tuple{VT, VT}]
    end

    # verify computation correctness
    r = f(DualNumbers.dual(u, 1.0), y)
    v_r = DualNumbers.realpart(r)
    dv_r = DualNumbers.epsilon(r)

    @test_approx_eq v_r value(loss, u, y)
    @test_approx_eq dv_r deriv(loss, u, y)

    (v, dv) = value_and_deriv(loss, u, y)
    @test_approx_eq v_r v
    @test_approx_eq dv_r dv
end

function verify_uniloss(loss::UnivariateLoss, f, us::AbstractVector, ys::AbstractVector)
    for u in us, y in ys
        verify_uniloss(loss, f, u, y)
    end
end

### test cases

# AbsLoss

_abs(u::DualNumbers.Dual) = DualNumbers.realpart(u) == 0.0 ? DualNumbers.dual(0.0, 0.0) : DualNumbers.abs(u)

verify_uniloss(AbsLoss(),
    (p, y) -> _abs(p - y), -3.0:3.0, -1.0:0.5:1.0)

# SqrLoss

verify_uniloss(SqrLoss(),
    (p, y) -> abs2(p - y) / 2, -3.0:3.0, -1.0:0.5:1.0)

# QuantileLoss

function _quanlossf(t::Float64, u::DualNumbers.Dual, y)
    DualNumbers.realpart(u) > y ? t * (u - y) :
    DualNumbers.realpart(u) < y ? (1.0 - t) * (y - u) :
    DualNumbers.dual(0.0, 0.0)
end

verify_uniloss(QuantileLoss(0.3), (p, y) -> _quanlossf(0.3, p, y), -2.0:0.5:2.0, -1.0:0.5:1.0)
verify_uniloss(QuantileLoss(0.5), (p, y) -> _quanlossf(0.5, p, y), -2.0:0.5:2.0, -1.0:0.5:1.0)

# EpsilonInsLoss

function _epsinsensf(eps::Float64, u::DualNumbers.Dual, y)
    a = abs(DualNumbers.realpart(u) - y)
    a > eps ? _abs(u - y) - eps : DualNumbers.dual(0.0, 0.0)
end

verify_uniloss(EpsilonInsLoss(0.3), (p, y) -> _epsinsensf(0.3, p, y), -2.0:0.25:2.0, -1.0:0.5:1.0)
verify_uniloss(EpsilonInsLoss(0.5), (p, y) -> _epsinsensf(0.5, p, y), -2.0:0.25:2.0, -1.0:0.5:1.0)

# HuberLoss

function _huberf(h::Float64, u::DualNumbers.Dual, y)
    a = abs(DualNumbers.realpart(u) - y)
    a > h ? h * _abs(u - y) - 0.5 * h^2 : 0.5 * abs2(u - y)
end

verify_uniloss(HuberLoss(0.3), (p, y) -> _huberf(0.3, p, y), -2.0:0.25:2.0, -1.0:0.5:1.0)
verify_uniloss(HuberLoss(0.5), (p, y) -> _huberf(0.5, p, y), -2.0:0.25:2.0, -1.0:0.5:1.0)

# HingeLoss

_hingef(u::DualNumbers.Dual, y) = y * DualNumbers.realpart(u) < 1.0 ? 1.0 - y * u : DualNumbers.dual(0.0, 0.0)
verify_uniloss(HingeLoss(), _hingef, -2.0:0.5:2.0, [-1.0, 1.0])

# SquaredHingeLoss

_sqrhingef(u::DualNumbers.Dual, y) = y * DualNumbers.realpart(u) < 1.0 ? (1.0 - y * u).^2 : DualNumbers.dual(0.0, 0.0)
verify_uniloss(SqrHingeLoss(), _sqrhingef, -2.0:0.5:2.0, [-1.0, 1.0])

# SmoothedHingeLoss

function _sm_hingef(h::Float64, u::DualNumbers.Dual, y)
    yu = y * DualNumbers.realpart(u)
    yu >= 1.0 + h ? DualNumbers.dual(0.0, 0.0) :
    yu <= 1.0 - h ? 1.0 - y * u :
    abs2(1.0 + h - y * u) / (4 * h)
end

verify_uniloss(SmoothedHingeLoss(0.2), (p, y) -> _sm_hingef(0.2, p, y), -2.0:0.25:2.0, -1.0:0.5:1.0)
verify_uniloss(SmoothedHingeLoss(0.5), (p, y) -> _sm_hingef(0.5, p, y), -2.0:0.25:2.0, -1.0:0.5:1.0)

# LogisticLoss

verify_uniloss(LogisticLoss(),
    (p, y) -> log(1 + exp(-y * p)), -2.0:0.5:2.0, [-1.0, -0.5, 0.5, 1.0])


function next_chebyshev(previous::Sum_Of_Functions, two_previous::Sum_Of_Functions, dim_name::Symbol)
    return PE_Function(2.0,Dict{Symbol,PE_Unit}(dim_name => PE_Unit(0.0,0.0,1))) * previous - two_previous
end

function get_first_kind_Chebyshevs(num::Int, dim_name::Symbol)
    if num < 1
        error("Select a positive number of Chebyshevs to return")
    else
        first_kind_chebyshevs = Array{Sum_Of_Functions}(undef, num)
        first_kind_chebyshevs[1] = Sum_Of_Functions([PE_Function(1.0,Dict{Symbol,PE_Unit}(dim_name => PE_Unit(0.0,0.0,0)))])
        if num > 1
            first_kind_chebyshevs[2] = Sum_Of_Functions([PE_Function(1.0,Dict{Symbol,PE_Unit}(dim_name => PE_Unit(0.0,0.0,1)))])
        end
        for n in 3:num
            first_kind_chebyshevs[n] = next_chebyshev(first_kind_chebyshevs[n-1], first_kind_chebyshevs[n-2], dim_name)
        end
        return first_kind_chebyshevs
    end
end
function get_second_kind_Chebyshevs(num::Int, dim_name::Symbol)
    if num < 1
        error("Select a positive number of Chebyshevs to return")
    else
        second_kind_chebyshevs = Array{Sum_Of_Functions}(undef, num)
        second_kind_chebyshevs[1] = Sum_Of_Functions([PE_Function(1.0,Dict{Symbol,PE_Unit}(dim_name => PE_Unit(0.0,0.0,0)))])
        if num > 1
            second_kind_chebyshevs[2] = Sum_Of_Functions([PE_Function(2.0,Dict{Symbol,PE_Unit}(dim_name => PE_Unit(0.0,0.0,1)))])
        end
        for n in 3:num
            second_kind_chebyshevs[n] = next_chebyshev(second_kind_chebyshevs[n-1], second_kind_chebyshevs[n-2], dim_name)
        end
        return second_kind_chebyshevs
    end
end

"""
    get_chevyshevs_up_to(num::Int, first_kind::Bool = true; dim_name::Symbol = default_symbol)
    Output all chebyshev polynomials up to degree num.
"""
function get_chevyshevs_up_to(num::Int, first_kind::Bool = true; dim_name::Symbol = default_symbol)
    if first_kind
        return get_first_kind_Chebyshevs(num,dim_name)
    else
        return get_second_kind_Chebyshevs(num,dim_name)
    end
end


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

using Markdown
using InteractiveUtils

# ╔═╡ 03cc6704-70eb-11eb-2f5f-d727783a1f53
begin
	using Pkg
	Pkg.activate("MLJ_env", shared=true)
end

# ╔═╡ 6d52e124-70eb-11eb-2c56-5dcea746627e
begin
	using MLJ
	using CSV
	using DataFrames
	using PlutoUI
end

# ╔═╡ b738e190-70ef-11eb-04da-8105c8390b73
using Random

# ╔═╡ a9f436f6-70fc-11eb-394e-f7a9755bb779
using Test

# ╔═╡ 28cfd44c-70fc-11eb-0155-f984980e96f6
using Printf # Pkg.add("Printf")

# ╔═╡ 62c32226-70ea-11eb-3914-730e6d2ec0a4
md"""
## C02 - w01: Simple Linear Regression

In this notebook we will use data on house sales in King County to predict house prices using simple (one input) linear regression. we will
  - compute important summary statistics
  - Write a function to compute the Simple Linear Regression weights using the closed form solution
  - Write a function to make predictions of the output given the input feature
  - Turn the regression around to predict the input given the output
  - Compare two different models for predicting house prices
  - *Finally use a predefined MLJ solution*
"""

# ╔═╡ ec3daae4-70eb-11eb-08df-416f041130f6

# ╔═╡ fa6be52e-70eb-11eb-2050-0bd51777d5d0
sales = CSV.File("../../ML_UW_Spec/C02/w01/data/kc_house_test_data.csv";
	header=true) |> DataFrame;

# ╔═╡ 39ca1334-70ed-11eb-2ee9-5bda94ba96f5
first(sales, 7)

# ╔═╡ 53b101ac-70f1-11eb-3844-3d5f21d9b7aa
size(sales)

# ╔═╡ d2a6fa36-70f7-11eb-3817-b36402af12f2
eltype.(eachcol(sales))

# ╔═╡ 299273f6-70f4-11eb-11c9-035af4af7032
md"""
### Split data into training and testing set
"""

# ╔═╡ 55c029d2-70f4-11eb-00ac-0b7698d7b6e2
function train_test_split(df; split=0.8, seed=42)
	Random.seed!(seed)
	(nr, nc) = size(df)
	nrp = round(Int, nr * split)
	row_ixes = shuffle(1:nr)
	df_train = view(df[row_ixes, :], 1:nrp, 1:nc)
	df_test = view(df[row_ixes, :], nrp+1:nr, 1:nc)
	(df_train, df_test)
end

# ╔═╡ d403506c-70f4-11eb-386f-ad0152978098
sales_train, sales_test = train_test_split(sales);

# ╔═╡ fff80614-70f3-11eb-137d-cbf3fcbaf98f
first(sales_train, 7)

# ╔═╡ 24949900-70f0-11eb-24f4-bd5c33121397
first(sales_test, 3)

# ╔═╡ 11aafb94-70f4-11eb-2438-8320f2e6cb5b
size(sales_train), size(sales_test)

# ╔═╡ 1fb75280-70f8-11eb-1cc3-29a08a026bcc
# mean and sum of a column
mean(sales_train[!, :bedrooms]), sum(sales_train[!, :bedrooms])

# ╔═╡ 799514b8-70f8-11eb-0d03-e5fe5b1c0cb4
sales_train[!, :bedrooms] .* sales_train[!, :bedrooms]

# ╔═╡ 7c9c2c1a-70f9-11eb-3ddc-d90788056518
length(sales_train[!, :bedrooms])

# ╔═╡ cc20e6e0-70f9-11eb-2b0d-7bd7f07d4733
typeof(sales_train[!, :bedrooms])

# ╔═╡ 416a6240-70f9-11eb-1962-e77cb5215607
md"""
### Build a generic simple linear regression function
"""

# ╔═╡ 5b5efd00-70f9-11eb-36f1-75d629b148e6
function slope(X, y)::Float64
	sx, sy = sum(X), sum(y)
	n = length(X)
	num = sum(X .* y) - (1/n) * sx * sy
	den = sum(X .* X) - (1/n) * sx * sx
	num / den
end

# ╔═╡ c40f3ea0-70f9-11eb-2ff0-9b1e9c4f3084
intercept(X, y, slope) = mean(y) - slope * mean(X)

# ╔═╡ 04b3c642-70fa-11eb-3e0b-b9b86108e1af
function simple_linear_regression(X, y)::Tuple{Real, Real}
	_slope = slope(X, y)
	_intercept = intercept(X, y, _slope)
	(_intercept, _slope)
end

# ╔═╡ 4e0f51e4-70fa-11eb-2511-b9813e9c82bb
begin
	test_feature = collect(1:5)
	test_output = 1 .+ test_feature

	(test_intercept, test_slope) = simple_linear_regression(test_feature, test_output)

	@test test_intercept ≈ 1.0
	@test test_slope ≈ 1.0
end

# ╔═╡ 64258a92-70fb-11eb-08d4-b11d5f75639f
begin
	sqft_intercept, sqft_slope = simple_linear_regression(
		sales_train[!, :sqft_living], sales_train[!, :price])

	with_terminal() do
		@printf("Intercept: %.4f\n", sqft_intercept)
		@printf("Slope:  %.4f\n", sqft_slope)
	end
end

# ╔═╡ beee3bd2-70fc-11eb-38cd-e7e8fda2f883
md"""
### Predicting Values

Now that we have the model parameters: intercept and slope we can make predictions.

Create a function to return the predicted output given the input_feature, slope and intercept.

"""

# ╔═╡ deda4470-70fc-11eb-20a2-15820ab34231
function get_regression_predictions(input_feature, intercept::Real,
		slope::Real)
    slope .* input_feature .+ intercept
end

# ╔═╡ 1ed980ae-70fd-11eb-07ed-09b7648af658
md"""
Now that we can calculate a prediction given the slope and intercept let's make a prediction.

**Quiz Question**: Using your Slope and Intercept from above, what is the predicted price for a house with 2650 sqft?
"""

# ╔═╡ 490bda66-70fd-11eb-3cd3-e7ccd064c7ec
with_terminal() do
	house_sqft = 2650
	estimated_price = get_regression_predictions(house_sqft,
		sqft_intercept, sqft_slope)

	@printf("The estimated price for a house with %d squarefeet is %.2f\n",
		house_sqft,
		estimated_price)
end

# ╔═╡ 96f5469a-70fd-11eb-1f17-675eeff21970
md"""
### Residual Sum of Squares

Now that we have a model and can make predictions let's evaluate our model using Residual Sum of Squares (RSS). Recall that RSS is the sum of the squares of the residuals and the residuals is just a fancy word for the difference between the predicted output and the true output.

Create a function (`get_residual_sum_of_squares`) to compute the RSS of a simple linear regression model given the input_feature, output, intercept and slope.

"""

# ╔═╡ b5e12f56-70fd-11eb-1bf7-cbdfa36fc673
function get_residual_sum_of_squares(input_feature, output,
		intercept::Real, slope::Real)
    ## 1. get the predictions
    yhat = get_regression_predictions(input_feature, intercept, slope)
    diff = (yhat .- output)   ## 2. compute the residuals
    rss = sum(diff .* diff)  ## 3. square the residuals and add them up
    return rss
end

# ╔═╡ 25479c96-70ff-11eb-2b86-29c052eb2f87
test_slope

# ╔═╡ 064ec37c-70fe-11eb-3bce-a79f7208c876
md"""
Let's test our `get_residual_sum_of_squares` function by applying it to the test model where the data lie exactly on a line. Since they lie exactly on a line the residual sum of squares should be zero!
"""

# ╔═╡ 1b0643d0-70fe-11eb-250c-53e54279e747
@test get_residual_sum_of_squares(test_feature, test_output,
	test_intercept, test_slope) ≈ 0.0

# ╔═╡ 5e2a432e-70ff-11eb-3e61-bffdc0ebd46e
md"""
Now use your function to calculate the RSS on training data from the squarefeet model calculated above.

**Quiz Question**: According to this function and the slope and intercept from the squarefeet model What is the RSS for the simple linear regression using squarefeet to predict prices on *training* data?

"""

# ╔═╡ 7e8bcf82-70ff-11eb-1c79-a56db6635447
begin
	rss_prices_on_sqft = get_residual_sum_of_squares(sales_train[!, :sqft_living],
		sales_train[!, :price],
        sqft_intercept,
		sqft_slope)
	with_terminal() do
		@printf("The RSS of predicting Prices based on Square Feet is: %1.3e\n", rss_prices_on_sqft)
	end
end

# ╔═╡ 0afc9a02-7100-11eb-0578-8b470fbb1d32
md"""
### Predict the squarefeet given price

What if we want to predict the squarefoot given the price?

Since we have an equation $y = a + b \times x$ we can solve the function for x, so that if we have the intercept (a) and the slope (b) and the price (y) we can solve for the estimated squarefeet (x) with:
$x = \frac{y - a}{b}$.

Create a function (`inverse_regression_predictions`) to compute the inverse regression estimate, i.e. predict the input_feature given the output.
"""

# ╔═╡ 5936bff2-7100-11eb-3673-03eb5174f2b0
function inverse_regression_predictions(output, intercept::Real, slope::Real)
    (output .- intercept) ./ slope
end

# ╔═╡ 7f548db8-7100-11eb-1a71-cfa1b2817f83
md"""
Now that we have a function to compute the squarefeet given the price from our simple regression model let's see how big we might expect a house that costs \$800,000 to be.

**Quiz Question**: According to this function and the regression slope and intercept from (3) what is the estimated square-feet for a house costing \$800,000?

"""

# ╔═╡ 967a4d8e-7100-11eb-369c-a7eef43335a2
begin
	house_price = 800000
	estimated_squarefeet = inverse_regression_predictions(house_price,
		sqft_intercept,
		sqft_slope)

	with_terminal() do
		@printf("The estimated squarefeet for a house worth %.2f is %.3f\n", house_price, estimated_squarefeet)
	end
end

# ╔═╡ f8cb4844-7100-11eb-2036-f1691f9277ba
md"""
### New Model: estimate prices from bedrooms

We have made one model for predicting house prices using squarefeet, but there are many other features in the sales DataFrame.

Use your simple linear regression function to estimate the regression parameters from predicting Prices based on number of bedrooms. Use the *training* data!

"""

# ╔═╡ 1a89af3e-7101-11eb-274d-43bbcf50db13
bedr_intercept, bedr_slope = simple_linear_regression(
	sales_train[!, :bedrooms],
	sales_train[!, :price]
)

# ╔═╡ 4d0ca242-7101-11eb-1001-7bb71d3b8de6
md"""
### Test your Linear Regression Algorithm

Now we have two models for predicting the price of a house. How do we know which one is better? Calculate the RSS on the *test* data (remember this data wasn't involved in learning the model). Compute the RSS from predicting prices using bedrooms and from predicting prices using squarefeet.

**Quiz Question**: Which model (square feet or bedrooms) has lowest RSS on TEST data? Think about why this might be the case.

"""

# ╔═╡ 69a38e9e-7101-11eb-2e57-83829ceb7ab9
begin
	## Compute RSS when using bedrooms on test data:
	rss_prices_on_bedr = get_residual_sum_of_squares(sales_test[!, :bedrooms],
        sales_test[!, :price],
        bedr_intercept,
		bedr_slope)
	with_terminal() do
		@printf("The RSS of predicting Prices based on number of Bedrooms is: %1.3e\n ", rss_prices_on_bedr)
	end
end

# ╔═╡ d043648a-7101-11eb-1b30-8d7ff3b37b7a
begin
	## Compute RSS when using squarefeet on test data:
	rss_prices_on_sqft_t = get_residual_sum_of_squares(sales_test[!, :sqft_living],
        sales_test[!, :price],
        sqft_intercept,
		sqft_slope)
	with_terminal() do
		@printf("The RSS of predicting Prices based on Squarefeet is: %1.3e\n ", rss_prices_on_sqft_t)
	end
end

# ╔═╡ Cell order:
# ╟─62c32226-70ea-11eb-3914-730e6d2ec0a4
# ╠═03cc6704-70eb-11eb-2f5f-d727783a1f53
# ╠═6d52e124-70eb-11eb-2c56-5dcea746627e
# ╟─ec3daae4-70eb-11eb-08df-416f041130f6
# ╠═fa6be52e-70eb-11eb-2050-0bd51777d5d0
# ╠═39ca1334-70ed-11eb-2ee9-5bda94ba96f5
# ╠═53b101ac-70f1-11eb-3844-3d5f21d9b7aa
# ╠═d2a6fa36-70f7-11eb-3817-b36402af12f2
# ╟─299273f6-70f4-11eb-11c9-035af4af7032
# ╠═b738e190-70ef-11eb-04da-8105c8390b73
# ╠═55c029d2-70f4-11eb-00ac-0b7698d7b6e2
# ╠═d403506c-70f4-11eb-386f-ad0152978098
# ╠═fff80614-70f3-11eb-137d-cbf3fcbaf98f
# ╠═24949900-70f0-11eb-24f4-bd5c33121397
# ╠═11aafb94-70f4-11eb-2438-8320f2e6cb5b
# ╠═1fb75280-70f8-11eb-1cc3-29a08a026bcc
# ╠═799514b8-70f8-11eb-0d03-e5fe5b1c0cb4
# ╠═7c9c2c1a-70f9-11eb-3ddc-d90788056518
# ╠═cc20e6e0-70f9-11eb-2b0d-7bd7f07d4733
# ╟─416a6240-70f9-11eb-1962-e77cb5215607
# ╠═5b5efd00-70f9-11eb-36f1-75d629b148e6
# ╠═c40f3ea0-70f9-11eb-2ff0-9b1e9c4f3084
# ╠═04b3c642-70fa-11eb-3e0b-b9b86108e1af
# ╠═a9f436f6-70fc-11eb-394e-f7a9755bb779
# ╠═4e0f51e4-70fa-11eb-2511-b9813e9c82bb
# ╠═28cfd44c-70fc-11eb-0155-f984980e96f6
# ╠═64258a92-70fb-11eb-08d4-b11d5f75639f
# ╟─beee3bd2-70fc-11eb-38cd-e7e8fda2f883
# ╠═deda4470-70fc-11eb-20a2-15820ab34231
# ╟─1ed980ae-70fd-11eb-07ed-09b7648af658
# ╠═490bda66-70fd-11eb-3cd3-e7ccd064c7ec
# ╟─96f5469a-70fd-11eb-1f17-675eeff21970
# ╠═b5e12f56-70fd-11eb-1bf7-cbdfa36fc673
# ╠═25479c96-70ff-11eb-2b86-29c052eb2f87
# ╟─064ec37c-70fe-11eb-3bce-a79f7208c876
# ╠═1b0643d0-70fe-11eb-250c-53e54279e747
# ╟─5e2a432e-70ff-11eb-3e61-bffdc0ebd46e
# ╠═7e8bcf82-70ff-11eb-1c79-a56db6635447
# ╟─0afc9a02-7100-11eb-0578-8b470fbb1d32
# ╠═5936bff2-7100-11eb-3673-03eb5174f2b0
# ╟─7f548db8-7100-11eb-1a71-cfa1b2817f83
# ╠═967a4d8e-7100-11eb-369c-a7eef43335a2
# ╟─f8cb4844-7100-11eb-2036-f1691f9277ba
# ╠═1a89af3e-7101-11eb-274d-43bbcf50db13
# ╟─4d0ca242-7101-11eb-1001-7bb71d3b8de6
# ╠═69a38e9e-7101-11eb-2e57-83829ceb7ab9
# ╠═d043648a-7101-11eb-1b30-8d7ff3b37b7a


"""
    find_maxlength_subarray(array::Array{Int64,1})

Find the maximum length of a subarray with a given continuous depending sequence of numbers.
For this reason, the minimum and maximum element in every subarray will be tracked and
compared with the incremental distance. So if the difference of the value is equal to the
difference of the increment in the array, the length will be updated.

# Arguments
- `array::Array{Int64,1}`: Unsorted array

# Examples
```julia-repl
julia> import ClassicAlgorithmsCollections
julia> arr = [10, 12, 11, 9, 13, 14, 17, 18, 15]
julia> ClassicAlgorithmsCollections.find_maxlength_subarray(arr)
5
```
"""
function find_maxlength_subarray(array::Array{Int64,1})

    # Initialize result
    max_len = 1
    n = length(array)
    for i in 1:n
        min_array = array[i]
        max_array = array[i]

        for j in (i+1):n
            if min_array > array[j]
                min_array = array[j]
            end
            if max_array < array[j]
                max_array = array[j]
            end
            if ((max_array - min_array) == (j - i))
                if max_len < (max_array - min_array)
                    max_len = max_array - min_array + 1
                end
            end
        end
    end
    return max_len
end


using ConvDiffMIPDECO
using Test
using jInv.Mesh

println("=== 2D test === ")

domain = [0. 3. 0 1. ]
n      = 2*[7 9 ]
M      = getRegularMesh(domain,n)
bc = (:dir,:neu,:neu,:neu)
gd = X->sin.(X[:,2]*pi)
pFor = getConvDiffFEMParam(M,bc=bc,gd=gd)

A,B,b = getConvDiffFEMConstraintsAMPL(M,gd=gd)

mc = rand(M.nc);
dobs = getData(mc,pFor)
utrue = pFor.Fields;
utest = A\(B*mc+b); # -->    A *u = B*m + b
@test norm(utrue-utest)/norm(utrue) < 1e-10

println("=== 3D test === ")

domain = [0. 3. 0 1. 0 2.]
n      = 2*[7 9 12]
M      = getRegularMesh(domain,n)
bc = (:dir,:neu,:neu,:neu,:neu,:neu)
gd = X->sin.(X[:,2]*pi)
pFor = getConvDiffFEMParam(M,bc=bc,gd=gd)

A,B,b = getConvDiffFEMConstraintsAMPL(M,gd=gd)

mc = rand(M.nc);
dobs = getData(mc,pFor)
utrue = pFor.Fields;
utest = A\(B*mc+b);
@test norm(utrue-utest)/norm(utrue) < 1e-10


using Flux, Random, LinearAlgebra, CSV
using Flux.Optimise: update!
Random.seed!(0)

data = CSV.read("../data/L1L2data.csv")
xVals, yVals = Array{Float64}(data.X), Array{Float64}(data.Y)

eta = 0.05
epsilon = 10^-7

b = rand(2)

predict(x) = b[1] .+ b[2]*x
loss(x,y) = sum((y .- predict(x)).^2)
opt = ADAM(eta)

iter, gradNorm = 0, 1.0
while gradNorm >= epsilon
    gs = gradient(()->loss(xVals,yVals),params(b))
    update!(opt,b,gs[b])
    gradNorm = norm(gs[b])
    global iter += 1
end

println("Number of iterations: ", iter)
println("Coefficients:", b)


using Plots
using Random
using NearestNeighbors

global my_seed = 1001
Random.seed!(my_seed)

function inv_dist_itp_test(idxs, dists, p, values)
    avg_inv_dist = 0
    f = 0
    for i in 1:length(idxs)
        idx = idxs[i]
        inv_dist = 1/dists[i]^p
        avg_inv_dist += inv_dist
        f += values[idx]*inv_dist
    end
    f = f/avg_inv_dist
end

function inv_dist_itp(idxs::AbstractVector,
                      dists::AbstractVector,
                      p::AbstractFloat,
                      values::AbstractVector)
    avg_inv_dist = 0
    f = 0
    for i in 1:length(idxs)
        idx = idxs[i]
        if idx > 0
            inv_dist = 1/dists[i]^p
            avg_inv_dist += inv_dist
            f += values[idx]*inv_dist
        elseif idx == -5
            # lower boundary
            f+=0
        elseif idx == -6
            # upper boundary
            f+=0
        end
    end
    f = f/avg_inv_dist
end

function sample_function(x, y)
    return exp(-x^2 - y^2)
end

n_samples = 100

samples = rand(2, n_samples).*2 .-1
x_samples = samples[1,:]
y_samples = samples[2,:]

# Create a tree
tree = KDTree(samples)

k = 15

function_values = sample_function.(x_samples, y_samples)

raster_size = 500
x_range = LinRange(-1,1,raster_size)
y_range = LinRange(-1,1,raster_size)

gr()

function plot_the_itp(p)

    raster_values = Matrix{Float64}(undef, (raster_size, raster_size))

    for i in 1:raster_size
        for j in 1:raster_size
            raster_site = [x_range[i], y_range[j]]
            nearest_neighbours = knn(tree, raster_site, k)
            raster_values[i, j] = inv_dist_itp_test(nearest_neighbours[1],
                                                    nearest_neighbours[2],
                                                    p,
                                                    function_values)
        end
    end
    heatmap(x_range,
            y_range,
            raster_values,
            title="p=$p, $k neighbours",
            dpi=350,
            aspect_ratio=:equal,
            xlim=[-1,1])
    savefig("../img/InverseDistance/inv_dist_$(Int(p*100))_$(k)")
end

p = [3]
for i in p
    plot_the_itp(i)
end

true_values = Matrix{Float64}(undef, (raster_size, raster_size))
for i in 1:raster_size
    for j in 1:raster_size
        true_values[i, j] = sample_function(x_range[i], y_range[j])
    end
end

heatmap(x_range,
        y_range,
        true_values,
        title="True Function",
        dpi=350,
        aspect_ratio=:equal,
        xlim=[-1,1])
savefig("../img/InverseDistance/inv_dist_comparison")

scatter(x_samples,
        y_samples,
        title="samples",
        dpi=350,
        aspect_ratio=:equal,
        xlim=[-1,1],
        xlabel=raw"$x$",
        ylabel=raw"$y$")
savefig("../img/InverseDistance/sample_points")


using Test
using Plots

import Pkg
Pkg.add("ToeplitzMatrices")
Pkg.add("SparseArrays")

import SparseArrays.spdiagm
import LinearAlgebra.I
import ToeplitzMatrices.Toeplitz

# 22
n = 1000
e = ones(n)

K = spdiagm(-1 => -e[1:999], 0 => e * 2, 1 => -e[1:999])

@test K[1,1] == 2.0
@test K[2,1] == -1.0
@test K[1,2] == -1.0
@test K[500,456] == 0.0
@test K[75,22] == 0.0
@test K[1000,999] == -1.0
@test K[1000,1000] == 2.0
@test K[999,1000] == -1.0

u = K \ e
plot(u)

# 23
A = rand(Float64, 4, 3)

K₂ = spdiagm(-1 => A[:,1], 0 => A[:,2], 1 => A[:,3])

# last 2 items is left out -> last(A[:,1]) and last(A[:,3])
Kₓ = convert(Array{Float64}, K₂)

@test Kₓ[5,4] == last(A[:,1]) && Kₓ[4,5] == last(A[:,3])

# 24
@test spdiagm(0 => ones(100)) == I(100)

# 26
v = [2, -1, zeros(2)..., -1]
Toeplitz(v, v)

# 27
A₀ = [
  -1 1 0 0
  0 -1 1 0
  0 0 -1 1
]

K = A₀ * A₀'

A₁ = A₀[:,2:4]

T = A₁ * A₁'

A₂ = A₀[:,2:3]

B = A₂ * A₂'


"""
x = admm(A, B; ...)

Solves non-negative least-squares problem by the Alternating Direction Method
of Multipliers (ADMM).

Optional arguments:
    ρ: penalty parameter (set heuristically by default)
    ε: tolerance for stopping (small number times sqrt[m*n] by default)

References:
    S. Boyd, N. Parikh, E. Chu, B. Peleato, and J. Eckstein (2011). Distributed
    Optimization and Statistical Learning via the Alternating Direction Method
    of Multipliers. Foundations and Trends in Machine Learning.

    http://stanford.edu/~eryu/nnlsqr.html
"""
function admm(A::Matrix{Float64},
	          B::Matrix{Float64};
	          ρ=max(0.1,vecnorm(A)^2/size(A,2)),
	          ε=sqrt(size(A,2)*size(B,2))*1e-15,
	          kwargs...)

	# Dimensions
	m,k = size(A)
	n = size(B,2)

	# Cache matrices
	AtB = A'*B
	AtAρ = A'*A + eye(k)*ρ

	# Cache cholesky factorization
	L = cholfact(AtAρ)

	# Matrix storing the solutions
	X = zeros(k,n)

	# Initialize variables
	Z,U = zeros(k,n),zeros(k,n)
	X = L \ (AtB+ρ*(Z-U))

	# Solve
	while vecnorm(X-Z) > ε
		Z = max(0,X+U)
		U = U+X-Z
		X = L \ (AtB+ρ*(Z-U))
	end

	# Z ≈ X, return Z because nonnegativity is strictly enforced
	return Z
end


const P = big(2)^256 - 2^32 - 977
const N = big"0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"

infield(x::Integer) = x >= 0 && x < P

"`FieldElement` represents an element in 𝐹ₚ where `P = 2²⁵⁶ - 2³² - 977`"
struct FieldElement <: Number
     𝑛::BigInt
     FieldElement(𝑛) = !infield(𝑛) ? throw(NotInField()) : new(𝑛)
end

FieldElement(x::FieldElement) = x
FieldElement(x::Union{Int128, Int64, Int32, Int16, Int8, Unsigned}) = FieldElement(big(x))
𝐹 = FieldElement

"Formats FieldElement showing 𝑛 in hexadecimal format"
function show(io::IO, z::FieldElement)
    print(io, string(z.𝑛, base = 16),"\n(in scep256k1 field)")
end

==(𝑋₁::FieldElement, 𝑋₂::FieldElement) = 𝑋₁.𝑛 == 𝑋₂.𝑛
==(::FieldElement, ::Integer) = false
+(𝑋₁::FieldElement, 𝑋₂::FieldElement) = 𝐹(mod(𝑋₁.𝑛 + 𝑋₂.𝑛, P))
-(𝑋₁::FieldElement, 𝑋₂::FieldElement) = 𝐹(mod(𝑋₁.𝑛 - 𝑋₂.𝑛, P))
*(𝑋₁::FieldElement, 𝑋₂::FieldElement) = 𝐹(mod(𝑋₁.𝑛 * 𝑋₂.𝑛, P))
*(𝑐::Integer, 𝑋::FieldElement) = 𝐹(mod(𝑐 * 𝑋.𝑛, P))
*(𝑋::FieldElement, 𝑐::Integer) = *(𝑐, 𝑋)
^(𝑋::FieldElement, 𝑘::Int) = 𝐹(powermod(𝑋.𝑛, mod(𝑘, (P - 1)), P))
/(𝑋₁::FieldElement, 𝑋₂::FieldElement) = 𝐹(mod(𝑋₁.𝑛 * powermod(𝑋₂.𝑛, P - 2, P), P))
div(𝑋₁::FieldElement, 𝑋₂::FieldElement) = 𝑋₁ / 𝑋₂
inv(𝑋::FieldElement) = 𝐹(powermod(𝑋.𝑛, mod(-1, (P - 1)), P))
sqrt(𝑋::FieldElement) = 𝑋^fld(P + 1, 4)


println("testing ransac")

# Just a simple line fitting test for the moment
# Adapted from fitlinedemo()
outliers = 0.2
sigma = 0.02
outsigma = 30*sigma  # outlying points have a distribution that is
                     # 30 times as spread as the inlying points
npts = 100           # Number of 3D data points
vpts = round(Int, (1-outliers)*npts)  # No of valid points
opts = npts - vpts                    # No of outlying points

# Define a line:
#    Y = m*X
#    Z = 0
m = 6
# Generate npts points in the line
X = rand(1,npts)
Y = m*X
Z = zeros(size(Y))

XYZ =  [X
    	Y
    	Z]

# Add uniform noise of +/-sigma
XYZ = XYZ + (2*rand(size(XYZ))-1)*sigma

# Generate opts random outliers
n = size(XYZ,2)
ind = randperm(n)[1:opts]  # get a random set of point indices of length opts

# Perform RANSAC fitting of the line
t = 0.02  # inlier tolerance
(V, P, inliers) = ransacfitline(XYZ, t)

# get slope of line and test values with a generous tolerance given
# the stochasic nature of things
slope = (V[2,2]-V[2,1])/(V[1,2]-V[1,1])
@test abs(slope-m) < 0.2
@test maximum(V[3,:]) < 0.1  # Check Z is close to 0


# This file illustrates calculation of energy spectral density and total enery of a rectangular pulse.

using FFTW
using Plots; plotlyjs()

# Define pulse
T = 1 / 10
p(t) = 0 ≤ t ≤ T ? 1 : 0

# Construct the signal
fs = 1000
ts = 1 / fs
l = 2^10
t = (0 : l - 1) * ts
x = p.(t)

# Compute fft
xf = fft(x)
psd = abs.(xf).^2
ff = (0 : 1 / (l - 1) : 1) * fs

# Compute energies
Δf = fs / (l - 1)
Δt = ts
Et = sum(abs.(x).^2) * Δt
Ef = sum(psd) * Δf / (fs^2)
@show Et, Ef

# Plots
plt = plot(layout=2)
plot!(t[1:500], x[1:500], subplot=1)
plot!(ff, psd, subplot=2)
display(plt)


#=
There are exactly ten ways of selecting three from five, 12345:

123, 124, 125, 134, 135, 145, 234, 235, 245, and 345

In combinatorics, we use the notation, 5C3 = 10.

In general,

nCr =
n!
r!(n−r)!
,where r ≤ n, n! = n×(n−1)×...×3×2×1, and 0! = 1.
It is not until n = 23, that a value exceeds one-million: 23C10 = 1144066.

How many, not necessarily distinct, values of  nCr, for 1 ≤ n ≤ 100, are greater than one-million?
=#
function combine(n, r)
  factorial(n)/(factorial(r)factorial(n-r))
end

function calc()
  sum = 0
  for n in 1:100, r in reverse(1:n)
    if combine(BigInt(n), BigInt(r)) > 1_000_000
      sum = sum + 1
    end
  end
  sum
end
@time println(calc())


function isprime(n::Int64)
    if n <= 3
        return n > 1
    elseif n % 2 == 0 || n % 3 == 0
        return false
    end
    i = 5
    while i^2 <= n
        if n % i == 0 || n % (i+2) == 0
            return false
        end
        i += 6
    end
    return true
end

function get_smallest_divisor(n::Int64)
    k = 2
    while n % k ≠ 0
        k += 1
    end
    return k
end

function get_prime_factors(N::Int64)
    S = Vector{Int64}()
    while true
        !isprime(N) || break
        k = get_smallest_divisor(N)
        push!(S, k)
        N::Int64 = N/k
    end
    push!(S, N)
    return S
end

function reverse_int(n::Int64)
    if n <= 9
        return n
    else
        return parse(Int, reduce(string, digits(n)))
    end
end

function totient(n::Int64)
    result::Float64 = n
    p = 2
    while p*p <= n
        if n % p == 0
            while n % p == 0
                n = n//p
            end
            result *= (1.0 - (1.0 / p))
        end
        p += 1
    end

    if n > 1
        result *= (1.0 - (1.0 / n))
    end

    return result
end

function prime_sieve(N::Int64)
    iscoprime(P, i) = !any(x -> i % x == 0, P)

    primes = Int64[]
    for num = 2:N
        if iscoprime(primes, num)
            push!(primes, num)
        end
    end
    primes
end


# The Jacobi method for solving an equation system
# Ax = b
# is an iterative method, where the k:th approximation
# to the solution is calculated as
# x⁽ᵏ⁺¹⁾ = D⁻¹(b - Rx⁽ᵏ⁾),
# where D is the diagonal elements of A, and
# R = A - D.

type JacobiSolver <: Solver
    A
    Dinv
    R
    tol
    ω
    maxiter
    function JacobiSolver(A, tol, ω=2./3, maxiter = 10)
        D = Diagonal(diag(A))
        new(A, inv(D), A-D, tol, ω, maxiter)
    end
end

# This implementation assumes that x is reasonably "close" to b; this
# is the case if A = I + Ξ, with |Ξ| << 1.
function solve!(b::StridedVecOrMat, J::JacobiSolver)
    xp = ones(b)
    x = xp
    for k = 1:J.maxiter
        x = J.ω*(J.Dinv*(b - J.R*xp)) + (1-J.ω)*xp
        if norm(J.A*x-b) < J.tol
            break
        end
        xp = x
    end
    b[:] = x
end

export JacobiSolver, solve!


"""
    ⊗(A, B)

Compute the kronecker product, ``A\\otimes B``.

# Examples
```julia-repl
julia> [1 0; 0 1]⊗[1 0; 0 -1]
4×4 Array{Int64,2}:
 1   0  0   0
 0  -1  0   0
 0   0  1   0
 0   0  0  -1
```
"""
⊗(A, B) = kron(A, B)

"""
    ⊗(A, B, C...)

Compute the kronecker product, ``A\\otimes B\\otimes C \\cdots``.
"""
function ⊗(A, B, C...)
    A = kron(A, B)
    for Ci in C
        A = kron(A, Ci)
    end
    return A
end


nprocs()==1 && addprocs()

@everywhere logis(x) = 1 ./ (1 .+ exp(-x))
@everywhere dlogis(x) = logis(x) .* (1 .- logis(x))

@everywhere function dfa!(w1,w2,w3,B1,B2,inp,out)
    a1 = w1 * inp
    z1 = logis(a1)
    a2 = w2 * z1
    z2 = logis(a2)
    ay = w3 * z2
    y = logis(ay)
    err = y - out
    d_a1 = (B1.*err) .* dlogis(a1)
    d_a2 = (B2.*err) .* dlogis(a2)
    w1 .-= d_a1 * inp'
    w2 .-= d_a2 * z1'
    w3 .-= err * z2'
    return Dict("output"=>y,"err"=>err)
end

@everywhere function bp!(bp_w1,bp_w2,bp_w3,inp,out)
    bp_a1 = bp_w1*inp
    bp_z1 = logis(bp_a1)
    bp_a2 = bp_w2 * bp_z1
    bp_z2 = logis(bp_a2)
    bp_ay = bp_w3 * bp_z2
    bp_y = logis(bp_ay)
    bp_err = bp_y - out
    bp_d_a3 = (bp_err) .* dlogis(bp_ay)
    bp_d_a2 = (bp_w3' * bp_d_a3) .* dlogis(bp_a2)
    bp_d_a1 = (bp_w2' * bp_d_a2) .* dlogis(bp_a1)
    bp_w1 .-= bp_d_a1 * inp'
    bp_w2 .-= bp_d_a2 * bp_z1'
    bp_w3 .-= bp_d_a3 * bp_z2'
    return Dict("output"=>bp_y,"err"=>bp_err)
end

inp = [1 1 ; 0 1 ; 1 0 ; 0 0]'
out = [0 1 1 0]
y=zeros(size(out))
n_hidden = 15; # Number of hidden units
num_iterations = 1000; #Number of learning steps
trials = 50;

e_store = SharedArray(Float64,(num_iterations,trials))
bp_e_store = SharedArray(Float64,(num_iterations,trials))
y_store = SharedArray(Float64,(size(out,2),num_iterations))
bp_y_store = SharedArray(Float64,(size(out,2),num_iterations))

@elapsed @sync @parallel for jj = 1:trials
    w1 = randn(n_hidden,size(inp,1))
    w2 = randn(n_hidden,n_hidden)
    w3 = randn(size(out,1),n_hidden)

    B1=rand(n_hidden,1)
    B2=rand(n_hidden,1)

    bp_w1 = randn(n_hidden,size(inp,1))
    bp_w2 = randn(n_hidden,n_hidden)
    bp_w3 = randn(size(out,1),n_hidden)

    for ii = 1:num_iterations
        dfa_res = dfa!(w1,w2,w3,B1,B2,inp,out)
        bp_res = bp!(bp_w1,bp_w2,bp_w3,inp,out)
        if jj == 1
            y_store[:,ii] = dfa_res["output"]'
            bp_y_store[:,ii] = bp_res["output"]'
        end
        e_store[ii,jj] = sum(abs(dfa_res["err"]))
        bp_e_store[ii,jj] = sum(abs(bp_res["err"]))
    end
end

using Plots
l = @layout [
    a b
    c d
]

gr()
p1=plot(y_store',label=["1,1" "0,1" "1,0" "0,0"],title="DFA")
p2=plot(bp_y_store',label=["1,1" "0,1" "1,0" "0,0"],title="BP")
p3=plot([e_store[:,1] bp_e_store[:,1]],label=["DFA" "BP"],title="First Trial")
p4=plot([mean(e_store,2) mean(bp_e_store,2)],label=["DFA" "BP"],title="Mean Error")
plot(p1,p2,p3,p4,layout=l)


using Test

function factorial(x)
    if x == 1
        return 1
    end
    return x * factorial(x-1)
end

@test factorial(5) == 120


"""
    dominates(p1::Array, p2::Array)

    Return true if each element in p1 is not less than the corresponding element in p2 and at least one element in p1 is bigger than the corresponding element in p2.

# Arguments
- `p1::Array`: Numeric array of n elements.
- `p2::Array`: Numeric array of n elements.

# Examples
```julia-repl
julia> dominates([1,2,3], [1,2,1])
true
julia> dominates([0,0,0,0], [1,0,0,0])
false
```

# References
Deb, Kalyanmoy, et al. "A fast elitist non-dominated sorting genetic algorithm for multi-objective optimization: NSGA-II."
International conference on parallel problem solving from nature. Springer, Berlin, Heidelberg, 2000.
"""
function dominates(p1::Array, p2::Array)::Bool
    n = length(p1)
    notworse = count(i -> p1[i] < p2[i], 1:n)
    better   = count(i -> p1[i] > p2[i], 1:n)
    return (notworse == 0) && (better > 0)
end

"""
    ndsranks(data)

    Sort multidimensional data usin non-dominated sorting algorithm.

# Arguments
- `data::DataFrame`: DataFrame of variables.

# References
Deb, Kalyanmoy, et al. "A fast elitist non-dominated sorting genetic algorithm for multi-objective optimization: NSGA-II."
International conference on parallel problem solving from nature. Springer, Berlin, Heidelberg, 2000.
"""
function ndsranks(data::DataFrame)::Array{Int}

    mat = convert(Matrix, data)

    return ndsranks(mat)

end

"""
    ndsranks(data)

    Sort multidimensional data usin non-dominated sorting algorithm.

# Arguments
- `data::Matrix`: n x k matrix of observations where n is number of observations and k is number of variables.

# Examples
```julia-repl

```

# References
Deb, Kalyanmoy, et al. "A fast elitist non-dominated sorting genetic algorithm for multi-objective optimization: NSGA-II."
International conference on parallel problem solving from nature. Springer, Berlin, Heidelberg, 2000.
"""
function ndsranks(data::Matrix)::Array{Int64}

    n, p = size(data)

    ranks = zeros(Int64, n)

    mat = convert(Matrix, data)

    @inbounds for i in 1:n
        @inbounds for j in 1:n
            if i != j
                if dominates(mat[i,:], mat[j,:])
                    ranks[i] += 1
                end
            end
        end
    end

    return ranks
end

function nds(data::DataFrame)

    ranks = ndsranks(data)

    bestIndex = sortperm(ranks) |> last

    result = NDSResult(
        ranks,
        bestIndex
    )

    return result
end


using LinearAlgebra

A = [2 3 0;
     1 2 4;
     0 2 1]

println("A+A=", A+A)
println("A-A=", A-A)
println("A*A=", A*A)
println("5*A=", 5*A)


# squared Euclidean distance from a set

export SqrDistL2

"""
**Squared distance from a convex set**

    SqrDistL2(ind_S, λ=1.0)

Given `ind_S` the indicator function of a convex set ``S``, and an optional positive parameter `λ`, returns the (weighted) squared Euclidean distance from ``S``, that is function
```math
g(x) = \\tfrac{λ}{2}\\mathrm{dist}_S^2(x) = \\min \\left\\{ \\tfrac{λ}{2}\\|y - x\\|^2 : y \\in S \\right\\}.
```
"""
struct SqrDistL2{R <: Real, T <: ProximableFunction} <: ProximableFunction
  ind::T
  lambda::R
  function SqrDistL2{R,T}(ind::T, lambda::R) where {R <: Real, T<:ProximableFunction}
    if !is_convex(ind) || !is_set(ind)
      error("`ind` must be the indicator of a convex set")
    end
    if lambda < 0
      error("parameter λ must be nonnegative")
    else
      new(ind, lambda)
    end
  end
end

is_prox_accurate(f::SqrDistL2) = is_prox_accurate(f.ind)
is_convex(f::SqrDistL2) = true
is_smooth(f::SqrDistL2) = true
is_quadratic(f::SqrDistL2) = is_affine(f.ind)
is_strongly_convex(f::SqrDistL2) = is_singleton(f.ind)

SqrDistL2(ind::T, lambda::R=1.0) where {R <: Real, T <: ProximableFunction} = SqrDistL2{R, T}(ind, lambda)

function (f::SqrDistL2)(x::AbstractArray{T}) where T <: RealOrComplex
  p, = prox(f.ind, x)
  return (f.lambda/2)*normdiff2(x,p)
end

function prox!(y::AbstractArray{T}, f::SqrDistL2, x::AbstractArray{T}, gamma::Real=1.0) where T <: RealOrComplex
  p, = prox(f.ind, x)
  sqrd = (f.lambda/2)*normdiff2(x,p)
  c1 = 1/(1+f.lambda*gamma)
  c2 = f.lambda*gamma*c1
  for k in eachindex(p)
    y[k] = c1*x[k] + c2*p[k]
  end
  return sqrd*c1^2
end

function gradient!(y::AbstractArray{T}, f::SqrDistL2, x::AbstractArray{T}) where T <: RealOrComplex
  p, = prox(f.ind, x)
  dist2 = normdiff2(x,p)
  y .= f.lambda.*(x .- p)
  return (f.lambda/2)*dist2
end

fun_name(f::SqrDistL2) = "squared Euclidean distance from a convex set"
fun_dom(f::SqrDistL2) = fun_dom(f.ind)
fun_expr(f::SqrDistL2) = "x ↦ (λ/2) inf { ||x-y||^2 : y ∈ S} "
fun_params(f::SqrDistL2) = string("λ = $(f.lambda), S = ", typeof(f.ind))

function prox_naive(f::SqrDistL2, x::AbstractArray{T}, gamma::Real=1.0) where T <: RealOrComplex
  p, = prox(f.ind, x)
  sqrd = (f.lambda/2)*norm(x-p)^2
  gamlam = f.lambda*gamma
  return 1/(1+gamlam)*x + gamlam/(1+gamlam)*p, sqrd/(1+gamlam)^2
end


# ---
# title: 462. Minimum Moves to Equal Array Elements II
# id: problem462
# author: Indigo
# date: 2021-02-04
# difficulty: Medium
# categories: Math
# link: <https://leetcode.com/problems/minimum-moves-to-equal-array-elements-ii/description/>
# hidden: true
# ---
#
# Given a **non-empty** integer array, find the minimum number of moves required
# to make all array elements equal, where a move is incrementing a selected
# element by 1 or decrementing a selected element by 1.
#
# You may assume the array's length is at most 10,000.
#
# **Example:**
#
#
#
#     Input:
#     [1,2,3]
#
#     Output:
#     2
#
#     Explanation:
#     Only two moves are needed (remember each move increments or decrements one element):
#
#     [1,2,3]  =>  [2,2,3]  =>  [2,2,2]
#
#
#
## @lc code=start
using LeetCode

function min_moves2(nums::Vector{Int})
    median = partialsort!(nums, (length(nums) + 1) ÷ 2)
    return sum(abs, num - median for num in nums)
end
## @lc code=end


"""
    fm = mergespec_spaceonly(ra,f,kmax)

Spectral merging of coarse field `ra` and fine field `f` at wavenumber `kmax`.
"""
function mergespec_spaceonly(ra,f,kmax)

(nx,ny)=size(f);
(nax,nay)=size(ra);

DFTr=zeros(Complex{Float64},nax,nax);
DFTf=zeros(Complex{Float64},nx,nx);

#fft di tutto il campo f
DFTf=fft(f)

#fft di tutto il campo r
DFTr=fft(ra);

DFTr=fftshift(DFTr); #centro la fft
DFTf=fftshift(DFTf); #centro la fft
DFTfm=zeros(Complex{Float64},nx,nx);
DFTr2=zeros(Complex{Float64},nax+1,nax+1);

#DFTr[nax+1,:,:]=NaN;
#DFTr[:,nax+1,:]=NaN;
DFTr2[1:nax,1:nax]=DFTr[:,:]
DFTr2[nax+1,1:nax]=conj(DFTr[1,:]);
DFTr2[1:nax,nax+1]=conj(DFTr[:,1]);

kmax2=kmax^2;

# We need to fix the phases of the large scale field!
# The first pixel of the large field is centered in dxl/2 with dxl=2pi/nax
# The first pixel of the fine field is centered in dxs/2 with dxf=2pi/nax
ddx=2*pi/nax/2-2*pi/nx/2;

 for j=1:nx
     for i=1:nx
            kx=-div(nx,2)+i-1;
            ky=-div(nx,2)+j-1;
            k2=(kx^2+ky^2);
            ir=div(nax,2)+1+kx;
            jr=div(nax,2)+1+ky;
            if(k2<=kmax2)
                DFTfm[i,j]=DFTr2[ir,jr]*exp(-1im*ddx*kx-1im*ddx*ky);;
            else
                DFTfm[i,j]=DFTf[i,j];
            end
     end
end

DFTfm=ifftshift(DFTfm);
fm=ifft(DFTfm);
fm=real(fm);

end


using NestedSamplers.Bounds: randball, volume_prefactor

@testset "Rand Sphere" begin
    for _ in 1:100, k in 1:10
        x = randball(rng, Float64, k)
        @test sum(t->t^2, x) < 1
    end
end

@testset "Volume Prefactor" begin
    @test volume_prefactor(1) ≈ 2
    @test volume_prefactor(2) ≈ π
    @test volume_prefactor(3) ≈ 4 / 3 * π
    @test volume_prefactor(4) ≈ 1 / 2 * π^2
    @test volume_prefactor(5) ≈ 8 / 15 * π^2
    @test volume_prefactor(6) ≈ 1 / 6 * π^3
    @test volume_prefactor(7) ≈ 16 / 105 * π^3
    @test volume_prefactor(9) ≈ 32 / 945 * π^4
end