function WeightedMinMaxModel(A,b,C,d,XLB,XUB,VariableDef,YR)

    const RO = 1e-6
    m,n = size(A);
    nObj = size(C)[1];
    MyModel = Model();
    @variable(MyModel,XLB[i] <= X[i=1:n] <= XUB[i],category = VariableType(VariableDef,i))#
    @variable(MyModel,Z)

    @objective(MyModel,Min,Z + RO* sum(sum(C[j,i]*X[i] for i = 1:n) + d[j] for j =1:nObj) )
    # @constraintref Ucons[1:m];
    # @constraintref LAMBDAcons[1:nObj];
    for i = 1:m
         @constraint(MyModel,sum(A[i,j]*X[j] for j = 1:n) >= b[i] )
    end
    for j = 1:nObj
         @constraint(MyModel,sum(C[j,i]*X[i] for i = 1:n) + d[j] - Z <= YR[j] )
    end
    for j = 1:nObj
         @constraint(MyModel,sum(C[j,i]*X[i] for i = 1:n) + d[j] >= YR[j])
    end
    return MyModel;

end


# modified example from GradDescent.jl documentation
using GradDescent
using Yota
using Statistics
using Random

obj(Y, X, b) = mean((Y .- X * b) .^ 2.0) # objective to minimize

function main()
    Random.seed!(1) # set seed
    n = 1000 # number of observations
    d = 10   # number of covariates
    X = randn(n, d) # simulated covariates
    b = randn(d)    # generated coefficients
    ϵ = randn(n) * 0.1 # noise
    Y = X * b + ϵ # observed outcome

    epochs = 100 # number of epochs

    θ = randn(d) # initialize model parameters
    opt = Adam(α=1.0)  # initalize optimizer with learning rate 1.0

    for i in 1:epochs
        val, g = grad(obj, Y, X, θ)
        println("Epoch: $i; loss = $val")

        δ = update(opt, g[3])
        θ -= δ
    end
end


using Submodular
using FactCheck

Tol = 1e-3

facts("Algorithms") do

  context("card-inc-fix") do
    n = 20
    g = rand(n)                      # the vector used to generate the cardinality-based function
    g = sort(g, rev=true)
    g = (g + 0.1)/1.01
    for i = 2: n
      g[i] = g[i] + g[i-1]
    end
    S = SetVariable(n)
    gg = card(g, S)                  # F(S) = g(|S|)

    y = rand(n)                      # the point to be projected on the base polytope of F(S) = g(|S|)
    euclidean_proj = card_inc_fix(gg, y, "euclidean")

    # sanity checks:
    # Is the sorted order of indices in y and the projection the same?
    # this is a known property of projections under uniform divergences over cardinality-based polytopes
    @fact sortperm(y) == sortperm(euclidean_proj[1:n]) --> true

    # Is the constraint x(E) = F(E) = g(n) satisfied upto an error of Tol^3?
    @fact sum(euclidean_proj) - g[n] --> roughly(0, Tol^3)
  end

  context("frank-wolfe with away steps") do
    n = 4
    x = Variable(n)
    g = norm(x - [3, 2, 5, 1])
    S = SetVariable(n)
    F = card(S)
    p(z) = -0.5*z^2 + n*z + 0.5 * z
    perm_func = compose(p, F)
    P = BasePoly(perm_func)
    prob = SCOPEminimize(g, x in P)
    @fact frank_wolfe_away(prob, verbose = false) --> roughly([3, 2, 4, 1], Tol)
  end

end


besselh0(z) = besselj0(z) + im*bessely0(z)
besselh1(z) = besselj1(z) + im*bessely1(z)

"Evaluate the Helmholtz 2D single layer potential kernel"
function hh_slp(x, y, wavenumber)
    T = eltype(x)
    if norm(x-y) < eps(T)
        return zero(Complex{T})
    else
        return im * besselh0(wavenumber*norm(x-y)) / 4
    end
end

"Evaluate the Helmholtz 2D double layer potential kernel"
hh_dlp(x, y, wavenumber, normal_y, z = norm(x-y)) =
    -(normal_y' * (x-y)) * im * wavenumber/4 * besselh1(wavenumber*z) / z

"Diagonal limiting value of the Helmholtz 2D double layer potential kernel"
function hh_dlp_limit(x, y, wavenumber, normal_y, grad_y, grad_d_y)
    -(grad_d_y[1] * grad_y[2] - grad_y[1] * grad_d_y[2]) / (4*norm(grad_y)^3*pi) +0im
end

"Evaluate the Helmholtz 2D double layer potential kernel"
function hh_dlp_kernel(x, y, tau, wavenumber, param)
    z = norm(x-y)
    if abs(z) > 100*eps(eltype(x))
        hh_dlp(x, y, wavenumber, normal(param, tau), z)
    else
        # Evaluate the limiting value
        hh_dlp_limit(x, y, wavenumber, normal(param, tau),
            gradient(param, tau), gradient_derivative(param, tau))
    end
end

hh_slpt(x, y, wavenumber, normal_x) = error("to do")
hh_hypersingular(x, y, wavenumber, normal_x, normal_y) = error("to do")

"Supertype of all Helmholtz kernels."
abstract type HelmholtzKernel <: BoundaryKernel end

"Wavenumber of the Helmholtz equation"
wavenumber(op::HelmholtzKernel) = op.wavenumber

export Helmholtz_SLP_2D
"The single layer potential kernel of the Helmholtz equation."
struct Helmholtz_SLP_2D{T} <: HelmholtzKernel
    wavenumber  ::  T
end

Helmholtz_SLP_2D(wavenumber::Integer) = Helmholtz_SLP_2D(float(wavenumber))

(kernel::Helmholtz_SLP_2D)(t, tau, param, x, y) = hh_slp(x, y, wavenumber(kernel))
(kernel::Helmholtz_SLP_2D)(x, tau, param, y) = hh_slp(x, y, wavenumber(kernel))

is_symmetric(::Helmholtz_SLP_2D) = true

singularity(::Helmholtz_SLP_2D) = LogSingularDiagonal()

export Helmholtz_DLP_2D
"The double layer potential kernel of the Helmholtz equation in 2D."
struct Helmholtz_DLP_2D{T} <: HelmholtzKernel
    wavenumber  ::  T
end

Helmholtz_DLP_2D(wavenumber::Integer) = Helmholtz_DLP_2D(float(wavenumber))

BasisFunctions.name(kernel::Helmholtz_DLP_2D) = "2D Helmholtz double layer potential kernel"

(kernel::Helmholtz_DLP_2D)(t, tau, param, x, y) =
    hh_dlp_kernel(x, y, tau, wavenumber(kernel), param)
(kernel::Helmholtz_DLP_2D)(x, tau, param, y) =
    hh_dlp_kernel(x, y, tau, wavenumber(kernel), param)

is_symmetric(::Helmholtz_DLP_2D) = false

# The kernel is continuous but its derivative is singular
singularity(::Helmholtz_DLP_2D) = LogSingularDiagonal()

## A few standard boundary conditions

export make_boundary_condition_planewave,
    make_parboundary_condition_planewave,
    make_boundary_condition_pointsource,
    make_parboundary_condition_pointsource

boundary_condition_wave(x, wavenumber, direction, amplitude) =
    amplitude * exp(im*wavenumber*dot(direction, x))

"Return a field function that evaluates to a plane wave."
make_boundary_condition_planewave(wavenumber, direction, amplitude = 1) =
    (x,y) -> boundary_condition_wave(SVector(x,y), wavenumber, direction, amplitude)

"Return a parametric boundary function that evaluates to a plane wave."
make_parboundary_condition_planewave(param, wavenumber, direction, amplitude = 1) =
    t -> boundary_condition_wave(applymap(param, t), wavenumber, direction, amplitude)

boundary_condition_pointsource(x, center, wavenumber, amplitude) =
    amplitude * besselh(0, 1, wavenumber*norm(x-center))

"Return a field function that evaluates to a point source."
make_boundary_condition_pointsource(center, wavenumber, amplitude = 1) =
    (x,y) -> boundary_condition_pointsource(SVector(x,y), center, wavenumber, amplitude)

"Return a parametric boundary function that evaluates to a point source."
make_parboundary_condition_pointsource(param, center, wavenumber, amplitude = 1) =
    t -> boundary_condition_pointsource(applymap(param, t), center, wavenumber, amplitude)


"""Lateral flow in a HF135 nitrocellulose membrane."""
module ExampleHF135

using Fronts
using Fronts.PorousModels
using Plots

ϵ = 1e-7

# Wetting of an HF135 membrane, Van Genuchten model
# Data from Buser (PhD thesis, 2016)
# http://hdl.handle.net/1773/38064
k = 5.50e-13  # m^2
α = 0.2555  # 1/m
n = 2.3521
θi = 0.102755  # Computed from P0

θr = 0.0473
θs = 0.945

model = VanGenuchten(n=n, α=α, k=k, θr=θr, θs=θs)

prob = DirichletProblem(model, i=θi, b=θs-ϵ)

θ = solve(prob)

r = range(0, 0.05, length=500)

plt = plot(r, r -> θ(r,60), label="t=60 s", xguide="r [m]", yguide="θ")
display(plt)

plt = plot(r, r -> flux(θ, r, 60), label="t=60 s", xguide="r [m]", yguide="U [m/s]")
display(plt)

end


abstract hypobolicSolver_oneD <: oneDoperator

type upwind{n} <: hypobolicSolver_oneD
  f :: Function
end

type LaxWendroff <: hypobolicSolver_oneD
  f :: Function
end

type DiscreteData_oneD
  value :: Array{Float64,1}
  dx :: Float64
end

function eval!(p :: hypobolicSolver_oneD, data :: DiscreteData_oneD, t :: Float64, dt :: Float64)
  error("No Function for abstract types")
end

function eval(p :: upwind{1}, data :: DiscreteData_oneD , t :: Float64, dt :: Float64)
  y1 = data.value
  dx = data.dx
  fy1 = p.f(y1)
  fy2 = cat(1,fy1[end], fy1[1:end-1] )
  y2 = y1 - (fy1 - fy2) * dt/dx
  return DiscreteData_oneD(y2,dx)
end

function eval(p :: upwind{2}, data :: DiscreteData_oneD , t :: Float64, dt :: Float64)
  y1 = data.value
  dx = data.dx
  fy1 = p.f(y1)
  fy2 = cat(1, fy1[end], fy1[1:end-1])
  fy3 = cat(1, fy1[end-1:end], fy1[1:end-2])
  y2 = y1 - (+fy3 -4fy2 + 3fy1) * dt / (2dx)
  return DiscreteData_oneD(y2,dx)
end

function eval(p :: LaxWendroff, data :: DiscreteData_oneD , t :: Float64, dt :: Float64)
  y1 = data.value
  dx = data.dx
  fy1 = p.f(y1)

  yright = (cat(1, fy1[2:end], fy1[1])+ fy1)/2
  yleft = (cat(1, fy1[end], fy1[1:end-1])+ fy1)/2
  yright = yright - (cat(1, fy1[2:end], fy1[1]) - fy1) * dt / (2dx)
  yleft = yleft  - (fy1 - cat(1, fy1[end], fy1[1:end-1])) * dt / (2dx)

  y2 = y1 - (p.f(yright) - p.f(yleft)) * dt/dx
  return DiscreteData_oneD(y2,dx)
end


using DiffEqOperators
using StaticArrays

N = 10
deriv_order = 2
approx_order = 2
bound_order = 1

Q = Neumann0BC(1.0, bound_order)
D = CenteredDifference(deriv_order, approx_order, 1.0, N)
L = D*Q

conc = sparse(L)[1]

Ax = sparse(Tridiagonal([1.0 for i in 1:N-1],[-2.0 for i in 1:N],[1.0 for i in 1:N-1]))
Ax[2,1] = 2.0
Ax[end-1,end] = 2.0


@testset "Tracker" begin
    @testset "tracking - AD : $AD" for AD = 0:3
        @var x a y b
        F = System([x^2 - a, x * y - a + b], [x, y], [a, b])

        tracker = Tracker(
            ParameterHomotopy(F, [1, 0], [2, 4]),
            options = TrackerOptions(automatic_differentiation = AD),
        )
        s = [1, 1]
        res = track(tracker, s, 1, 0)
        @test is_success(res)
        @test steps(res) ≤ 5
        @test isa(solution(res), Vector{ComplexF64})
        @test solution(res) ≈ [sqrt(2), -sqrt(2)]
        @test length(solution(res)) == 2
        @test is_success(track(tracker, res, 0, 1))

        code = track!(tracker, s, 1, 0)
        @test is_success(code)
        s0 = copy(tracker.state.x)
        @unpack μ, ω = tracker.state
        @test is_success(track(tracker, s0, 0, 1))
        @test is_success(track(tracker, s0, 0, 1, μ = μ, ω = ω))

        s = @SVector [1, 1]
        @test is_success(track(tracker, s, 1, 0))
    end

    @testset "projective tracking- AD: $AD" for AD = 0:3
        @var x a y b z
        F = System([x^2 - a * z^2, x * y + (b - a) * z^2], [x, y, z], [a, b])
        H = ParameterHomotopy(F, [1, 0], [2, 4])
        tracker = Tracker(
            on_affine_chart(H, (2,)),
            options = TrackerOptions(automatic_differentiation = AD),
        )

        s = [1, 1, 1]
        res = track(tracker, s, 1, 0)
        @test is_success(res)
        @test isa(solution(res), Vector{ComplexF64})
        x₀ = abs(solution(res)[end])
        @test affine_chart(PVector(solution(res))) ≈ [sqrt(2), -sqrt(2)] rtol = 1e-12 / x₀
    end

    @testset "iterator" begin
        @var x a y b
        F = System([x^2 - a, x * y - a + b], [x, y], [a, b])
        tracker = Tracker(ParameterHomotopy(F, [1, 0], [2, 4]))
        s = [1, 1]

        # path iterator
        typeof(first(iterator(tracker, s, 1.0, 0.0))) == Tuple{Vector{ComplexF64},Float64}

        tracker.options.max_step_size = 0.01
        @test length(collect(iterator(tracker, s, 1.0, 0.0))) ≥ 101

        F = System([x - a], [x], [a])
        ct = Tracker(
            ParameterHomotopy(F, [1], [2]),
            options = TrackerOptions(max_step_size = 0.015625),
        )
        Xs = Vector{ComplexF64}[]
        for (x, t) in iterator(ct, [1.0], 1.0, 0.0)
            push!(Xs, x)
        end

        @test length(Xs) ≥ length(1:0.015625:2)
    end

    @testset "path info" begin
        @var x a y b
        F = System([x^2 - a, x * y - a + b], [x, y], [a, b])
        tracker = Tracker(ParameterHomotopy(F, [1, 0], [2, 4]))
        info = path_info(tracker, [1, 1], 1, 0)
        @test !isempty(sprint(show, info))
    end

    @testset "Change parameters" begin
        @var x a y b
        F = System([x^2 - a, x * y - a + b]; parameters = [a, b])
        s = [1.0, 1.0 + 0im]
        tracker = Tracker(ParameterHomotopy(F, [2.2, 3.2], [2.2, 3.2]))
        start_parameters!(tracker, [1, 0])
        target_parameters!(tracker, [2, 4])
        res = track(tracker, s, 1.0, 0.0)
        @test is_success(res)
    end

    @testset "Straight Line Homotopy" begin
        @var x y
        F = System([x^2 + y^2 - 2.3, 2x + 3y - 4], [x, y])
        G = System((0.2 + 0.4im) .* [x^2 - 1, y - 1], [x, y])
        H = StraightLineHomotopy(G, F)
        S = [[1, 1], [-1, 1]]
        tracker = Tracker(H, options = (automatic_differentiation = 3,))

        @test is_success(track(tracker, S[1], 1, 0))
        @test is_success(track(tracker, S[2], 1, 0))

        @test is_invalid_startvalue(track(tracker, [100, -100], 1, 0))
    end

    @testset "Compiled/InterpretedHomotopy" begin
        @var x y t γ
        H = Homotopy(
            t * γ * [x^2 - 1, y^2 - 1] + (1 - t) .* [2 * x^2 - 4 + 2x * y, y^2 - 5],
            [x, y],
            t,
            [γ],
        )
        S = total_degree_start_solutions([2, 2])
        for make in [CompiledHomotopy, InterpretedHomotopy]
            tracker = Tracker(
                fix_parameters(make(H), [randn(ComplexF64)]);
                options = (automatic_differentiation = 3,),
            )
            @test all(is_success, track.(tracker, S))
        end

        # also test without parameters
        γ = randn(ComplexF64)
        H = Homotopy(
            t * γ * [x^2 - 1, y^2 - 1] + (1 - t) .* [2 * x^2 - 4 + 2x * y, y^2 - 5],
            [x, y],
            t,
        )
        for make in [CompiledHomotopy, InterpretedHomotopy]
            tracker = Tracker(make(H); options = (automatic_differentiation = 1,))
            @test all(is_success, track.(tracker, S))
        end
    end

    @testset "invalid_startvalue_singular_jacobian" begin
        # https://github.com/JuliaHomotopyContinuation/HomotopyContinuation.jl/issues/454
        p0 = [
            0 0
            1 0
            1 1
            2 1
            3/2 1/2
            3.0 1.0
            4 1
            5 1
            9/2 1/2
            5 0
            6 0
        ]
        E = [
            1 2
            2 3
            2 5
            3 4
            3 5
            4 5
            4 6
            5 9
            6 7
            7 8
            7 9
            8 9
            8 10
            9 10
            10 11
        ]
        pinnedvertices = [1; 6; 11]
        freevertices = [2; 3; 4; 5; 7; 8; 9; 10]

        @var x[1:size(p0)[1], 1:size(p0)[2]] # x[1:11, 1:2] # create all variables
        xvarz_moving_frame = [Variable(:x, i, k) for i in freevertices for k = 1:2]

        # create random, real-valued, linear equation in the moving frame variables
        # created so that it passes through the initial configuration p0
        a = randn(1, length(xvarz_moving_frame)) # random coefficients
        a = [0 1.0 0 1.0 0 1.0 0 1.0 0 1.0 0 1.0 0 1.0 0 1.0]
        b0 = evaluate(
            a * xvarz_moving_frame,
            [Variable(:x, i, k) => p0[i, k] for i in freevertices for k = 1:2]...,
        )[1]
        # the parameters to move linear "slice" later in a parameter homotopy, just move
        # the constant term "b"
        bvarz = [Variable(:b)]

        # the linear equation with parameters
        L = (a*xvarz_moving_frame)[1] .- Variable(:b)

        ε = 1e-3 #1e-10
        p1 = p0 + ε * randn(size(p0))

        b1 = subs(
            a * xvarz_moving_frame,
            [Variable(:x, i, k) => p1[i, k] for i in freevertices for k = 1:2]...,
        )[1]
        b1 = to_number(b1) # Float64(b1) throws an error

        function edge_equation(i, j)
            eqn = sum([(x[i, k] - x[j, k])^2 for k = 1:2])
            eqn += -sum([(p0[i, k] - p0[j, k])^2 for k = 1:2])
        end
        fs = [edge_equation(E[m, 1], E[m, 2]) for m = 1:size(E)[1]]

        # pin the vertices by substitution
        gs = [
            subs(
                fij,
                [Variable(:x, i, k) => p0[i, k] for i in pinnedvertices for k = 1:2]...,
            ) for fij in fs
        ]

        G = System(vcat(gs, L); variables = xvarz_moving_frame, parameters = bvarz)
        startsolutions0 = [p0[i, k] for i in freevertices for k = 1:2]
        tracker = Tracker(ParameterHomotopy(G, [b0], [b1]; compile = false))
        result = track(tracker, startsolutions0, 1, 0)
        @test is_invalid_startvalue(result)
        @test result.return_code == :terminated_invalid_startvalue_singular_jacobian
    end

    include("test_cases/steiner_higher_prec.jl")
    include("test_cases/four_bar.jl")
end


# ------------------------------------------------------------------
# Licensed under the MIT License. See LICENSE in the project root.
# ------------------------------------------------------------------

using .Optim

function _lsif_coeffs(H, h, dre::LSIF, optlib::Type{OptimLib})
  # retrieve parameters
  λ, b = dre.λ, length(h)

  # constants for inequality constraints
  T  = eltype(H)
  lx = fill(zero(T), b)
  ux = fill(Inf, b)

  # objective
  f(α)       = (1/2) * dot(α, H*α - 2h) + λ * sum(α)
  ∇f!(grad, α)  = (grad .= H*α .- h .+ λ)
  ∇²f!(hess, α) = (hess .= H)

  # initial guess
  αₒ = fill(one(T), b)

  # optimization problem
  objective   = TwiceDifferentiable(f, ∇f!, ∇²f!, αₒ)
  constraints = TwiceDifferentiableConstraints(lx, ux)
  initguess   = αₒ

  # solve problem with interior-point primal-dual Newton
  solution = optimize(objective, constraints, initguess, IPNewton())

  # optimal coefficients
  solution.minimizer
end


include("../src/geom_module.jl")

module meshtest
using PyPlot
PyPlot.pygui(true)
using ..geom

rectangle = geom.Rectangle(-1.0, -1.0, 1.0, 1.0)
circle = geom.Circle(0.0, 0.0, 0.4)

distance_fcn = p -> geom.difference_signed_distance_function(
    geom.signed_distance_function(rectangle, p),
    geom.signed_distance_function(circle, p) )

src_point     = [0.0 0]
h_0           = 0.15
h_inf         = 0.5
spread_factor = 1.0
node_spacing_fcn(p) = geom.point_source_node_spacing(p, src_point, h_0, h_inf, spread_factor)
# node_spacing_fcn(p) = geom.uniform_node_spacing(p)

initial_spacing 	= 0.1
bounding_box 		= geom.Rectangle(-1.0, -1.0, 1.0, 1.0)
fixed_points 		= [
    -1.0 -1.0;
    +1.0 -1.0;
    +1.0 +1.0;
    -1.0 +1.0] # zeros(0,2)
max_iteration 		= 20

options = geom.MeshGenerationOptions(
    distance_fcn,
	node_spacing_fcn,
	initial_spacing,
	bounding_box,
	fixed_points;
	max_iteration = max_iteration)

(point_array, triangle_index, delmesh) = geom.create_mesh(options);

n_points = size(point_array, 1)

px = point_array[:,1]
py = point_array[:,2]
Lt = size(triangle_index,1)

PyPlot.figure()
PyPlot.grid()

PyPlot.plot( px, py,
    marker = :., linestyle = :none, markersize = 10)

for tt = 1:size(triangle_index,1)
    t = triangle_index[tt,:]
    tx = px[ [t[1], t[2], t[3], t[1]]]
    ty = py[ [t[1], t[2], t[3], t[1]]]
    PyPlot.plot(tx, ty)
end

end


using Images
using ImageIO
using ImageMagick
using LinearAlgebra
using Pipe: @pipe
using ProgressMeter
using CSV
using DataFrames
X = load("jpgtest.jpg")

a = @pipe X |> Float64.(Gray.(_)) |> svd(_).S[1:10]'

data = Float64.(Gray.(X))
abssvd = svd(data)abs
a = Array{Float64}(undef, 0, 10)
@time a = vcat(a,re)

"""
Usage:
```julia
    recodeimage(path_to_folder_with_images, no_of_required_singular_vals)
```
Currently only supported for jpeg/png files
"""
function recodeimage(pathtoimage, n_singularvlas)
    imagelist = glob("*.jpg", pathtoimage)
    append!(imagelist,  glob("*.png", pathtoimage))
    recodedArray = Array{Float64}(undef, 0, n_singularvlas)
    @showprogress for images in imagelist
        X = load(images)
        img_singluar = @pipe X |> Float64.(Gray.(_)) |> svd(_).S[1:n_singularvlas]'
        recodedArray = vcat(recodedArray, img_singluar)
    end
    filename = joinpath(pathtoimage, "image_recoded.csv")
    CSV.write(filename,  DataFrame(recodedArray), writeheader=true)
end
using StatsBase
recodeimage(pwd(),2)
using WAV
using Pipe: @pipe
using MFCC
using Glob
using WAV

temp = Array{Float64}(undef,0,10)
x,fs = wavread("ENG_M.wav")
a = collect(mfcc(x,fs, numcep = 10)[1])
temp = vcat(temp,mean(a,dims=1))
using CSV
using DataFrames
using ProgressMeter
function recodeaudio(filepath,ncep)
    audiolist = glob("*.wav",filepath)
    recodedArray = Array{Float64}(undef, 0, ncep)
    @showprogress for files in audiolist
        x,fs = wavread(files)
        cep = collect(mfcc(x,fs, numcep = ncep)[1])# this is a matrix with ncep columns
        recodedArray = vcat(recodedArray, mean(cep,dims=1))
    end
    filename = joinpath(filepath, "audio_recoded.csv")
    CSV.write(filename,  DataFrame(recodedArray), writeheader=true)
end

using TextAnalysis
using Taro
using Languages
Taro.init()
function recodetext(pathtotxt, n_singularvals)
    files = glob("*.pdf", pathtotxt)
    docs = Array{StringDocument{String},1}[]
    getTitle(t) = TextAnalysis.sentence_tokenize(Languages.English(), t)[1]
   @showprogress for i in 1:length(files)
        meta,txt = Taro.extract(files[i]);
        txt = replace(txt, '\n' => ' ')
        title = getTitle(txt)
        dm = TextAnalysis.DocumentMetadata(Languages.English(), title, "", meta["Creation-Date"] )
        doc = StringDocument(txt, dm)
        docs = vcat(docs, doc)
    end
    crps = Corpus(docs)

    prepare!(crps, strip_non_letters | strip_punctuation | strip_case | strip_stopwords)
    stem!(crps)
    update_lexicon!(crps)
    update_inverse_index!(crps)
    U,S,Vt = @pipe crps |> DocumentTermMatrix(_) |> dtm(_, :dense) |> svd(_)
    if n_singularvals > length(S)
        println("You have set more number of singluar values than present \n so seting number of singluar values = length(singular values)")
        n_singularvals = length(S)
    end
    DTM = U[:,1:n_singularvals]*diagm(S[1:n_singularvals])*Vt[:,1:n_singularvals]'
    filename = joinpath(pathtotxt, "reconstructed_document_term_matrix.csv")
    CSV.write(filename,  DataFrame(DTM), writeheader=true)
end

a = glob("*.pdf", pwd())
docs= Any[]
getTitle(t) = TextAnalysis.sentence_tokenize(Languages.English(), t)[1]
for i in 1:length(a)
        meta,txt = Taro.extract(a)
        txt = replace(txt, '\n' => ' ')
        title = getTitle(txt)
        dm = TextAnalysis.DocumentMetadata(Languages.English(), title, "", meta["Creation-Date"] )
        doc = StringDocument(txt, dm)
        push!(docs, doc)

    end
end
a = recodetext(pwd(),50)


using Stochy
using DataStructures

@pp function transition(state)
    state ? flip(0.7) : flip(0.3)
end

@pp function observe(state)
    state ? flip(0.9) : flip(0.1)
end

@pp function hmm(n)
    if n == 0
        local states       = list(true),
              observations = list()
        list(states, observations)
    else
        local prev       = hmm(n-1),
              prevstates = first(prev),
              prevobs    = second(prev),
              newstate   = transition(first(prevstates)),
              newobs     = observe(newstate)
        list(newstate..prevstates, newobs..prevobs)
    end
end

dist = @pp enum() do
    local trueobs = list(false,false,false),
          r = hmm(3)
    factor(trueobs == second(r) ? 0 : -Inf)
    tail(reverse(first(r)))
end

println(dist)


type NeuralNetwork
    hidden::Union{Vector,Int64}
    act::String
    weights::Dict{Integer, Matrix}
    max_iter::Integer
end

function NeuralNetwork(;
                       hidden::Union{Vector,Int64} = [2,2,1],
                       act::String = "sigmoid",
                       weights::Dict{Integer,Matrix} = Dict{Integer,Matrix}(),
                       max_iter::Integer = 500000)
    return NeuralNetwork(hidden, act, weights, max_iter)
end

function train!(model::NeuralNetwork, X::Matrix, y::Vector)
    if typeof(model.hidden) <: Integer
        model.hidden = [size(X, 2), model.hidden, size(y, 2)]
    end
    init_weights(model)
    depth = size(model.hidden,1)
    a::Dict{Integer, Vector} = Dict()
    z::Dict{Integer, Vector} = Dict()
    X = hcat(X,ones(size(X,1)))
    for i = 1:model.max_iter
        #batch
        r = rand(1:size(X,1))
        a[1] = X[r,:]
        for j = 2:(depth)
            z[j] = vec(a[j-1]' * model.weights[j-1])
            a[j] = vec(sigmoid(z[j]))
        end
        delta = Dict{Integer, Vector}()
        error_ = a[depth] - y[r]
        #if i % 1000 == 0
        #    println("$(i) epochs: error $(error_)")
        #end
        delta[depth] = error_ .* sigmoid_prime(z[depth])
        for j = depth-1:-1:2
            delta[j] = vec(delta[j+1]' * model.weights[j]') .* sigmoid_prime(z[j])
        end
        for j = 1:depth-1
            del = delta[j+1]
            a_temp = a[j]
            model.weights[j] = model.weights[j] - 0.1 * a_temp * del'
        end
    end

end

function init_weights(model::NeuralNetwork)
    depth_ = size(model.hidden,1)
    for i = 1:(depth_-2)
        model.weights[i] = 2*rand(model.hidden[i]+1,model.hidden[i+1]+1)-1
    end
    model.weights[depth_-1] = 2*rand(model.hidden[depth_-1]+1,model.hidden[depth_])-1
end

function predict(model::NeuralNetwork,
                 x::Matrix)
    n = size(x,1)
    m = model.hidden[end]
    res = zeros(n,m)
    for i = 1:n
        res[i,:] = predict(model, x[i,:])
    end
    return res
end

function predict(model::NeuralNetwork,
                 x::Vector)
    x = x'
    x = hcat(x,[1])
    for i = 1:length(model.hidden)-1
        x = sigmoid(x * model.weights[i])
    end
    return x
end

## fixed

function test_NeuralNetwork()
    #xor_network
    X_train = [0.1 0.1; 0.1 0.9; 0.9 0.1; 0.9 0.9]
    y_train = [0.1,0.9,0.9,0.1]
    model = NeuralNetwork(hidden=10)
    train!(model,X_train, y_train)
    predictions = predict(model,X_train)
    print("regression msea", mean_squared_error(y_train, predictions))
end


using LinearAlgebra, SparseArrays
"""
Index 2 RCL Ladder Network

Description:
  This demo provides a semi-explicit index-2 port-Hamiltonian DAE system
  derived from a simple RCL ladder network with buffer capacitor

Input Arguments:
  - ns:           Number of loops in network
  - r, c, l:      Resistances, capacitances and inductances vectors with length ns+1 / ns / ns
  - m:            Number of inputs (1: SISO or 2: MIMO)

Output Arguments:
  - E, J, R, G:      Index-2 PH-DAE model of the RCL ladder network

References:   R. W. Freund. Structure-Preserving Model Order Reduction of
              RCL Circuit Equations, 2008.

  Author: Tim Moser
  E-Mail: tim.moser@tum.de
  Date:   2021/12/14

"""
function  setup_DAE2_RCL_LadderNetwork_sparse(; ns, r::AbstractVector, c::AbstractVector, l::AbstractVector, m)
  # Transform scalar inputs to vectors
  # Dimensions
  nk = 2*ns+m; # Number of nodes for network analysis
  nR = ns+1
  nL = ns
  nC = ns
  # Sub-matrices
  R = spdiagm(0 => r)
  L = spdiagm(0 => l)
  C = spdiagm(0 => c)
  # Initialization
  Ar = spzeros(nk,nR)
  Ac = spzeros(nk,nC)
  Al = spzeros(nk,nL)
  # Ar
  ptr = 1
  for i=1:nR-1
    Ar[ptr,i]=1
    Ar[ptr+1,i]=-1
    ptr = ptr+2
  end
  Ar[nk-m+1,nR] = 1
  if m == 2
    Ar[end,end] = -1
  end
  # Ac
  ptr = 1
  for i=1:nC
    Ac[ptr,i] = -1
    ptr = ptr+2
  end
  # Al
  ptr = 2
  for i=1:nL
    Al[ptr,i]=1
    Al[ptr+1,i]=-1
    ptr = ptr+2
  end
  # Av
  Av = spzeros(nk,1)
  Av[1] = -1.0
  if m == 2
    Av = hcat(Av, zeros(nk,1))
    Av[end,end] = 1
  end
  # Bring to semi-explicit index-2 pH-Form
  A11 = -Ar*(R\Ar')
  E11 = Ac*C*Ac';
  E = blockdiag(sparse(E11),sparse(L),spzeros(m, m))
  J = [spzeros(size(A11, 1), size(A11, 2)) -Al -Av;
       Al' spzeros(size(Al',1),size(Al,2)+size(Av,2));
       Av' spzeros(size(Av',1),size(Al,2)+size(Av,2))]
  R = [-A11 spzeros(size(A11,1),size(Al,2)+size(Av,2));
       spzeros(size(Al',1)+size(Av',1),size(A11,2)+size(Al,2)+size(Av,2))]
  G = spzeros(size(A11,1)+size(Al',1)+size(Av',1),m)
  @views G[end-m+1:end, end-m+1:end] = -sparse(I(m))
  return E, J, R, G
end

"""
Description:
  This demo provides a semi-explicit index-1 port-Hamiltonian DAE system
  derived from a simple RCL ladder network with shunt resistor

Input Arguments:
  - ns:           Number of loops in network
  - r, c, l:      Resistances, capacitances and inductances vectors with length
                  ns+2 / ns-1 / ns
  - m:            Number of inputs (1: SISO or 2: MIMO)

Output Arguments:
  - E, J, R, G:      Index-1 PH-DAE model of the RCL ladder network

References:   R. W. Freund. Structure-Preserving Model Order Reduction of
              RCL Circuit Equations, 2008.

  Author: Tim Moser
  E-Mail: tim.moser@tum.de
  Date:   2021/11/03
"""
function setup_DAE1_RCL_LadderNetwork_sparse(; ns, r, c, l, m)
  # Dimensions
  nk = 2*ns+m; # Number of nodes for network analysis
  nR = ns+2
  nL = ns
  nC = ns-1
  # Sub-matrices
  R = spdiagm(0 => r)
  L = spdiagm(0 => l)
  C = spdiagm(0 => c)
  # Initialization
  Ar = spzeros(nk, ns+2)
  Ac = spzeros(nk, ns-1)
  Al = spzeros(nk, ns)
  U = spzeros(nk+nL+m, nk+nL+m)
  # Ar
  Ar[1,1] = -1
  ptr = 1
  for i=2:nR-1
    Ar[ptr,i]=1
    Ar[ptr+1,i]=-1
    ptr = ptr+2
  end
  Ar[nk-m+1,nR] = 1
  # Ac
  ptr = 3
  for i=1:nC
    Ac[ptr,i] = -1
    U[ptr,i] = 1
    U[ptr+1, i+ns+length(l)+1] = 1
    ptr = ptr+2
  end
  # U
  for i = nk+1:nk+nL
    U[i, i-nk+ns-1] = 1
  end
  U[1, nL+ns] = 1
  U[2, nL+ns+1] = 1
  for i = 1:m
    U[ptr, nC+ns+length(l)+i+1] = 1
    ptr = ptr + 1
  end
  @views U[end-m+1:end,end-m+1:end] .= I(m)
  # Al
  ptr = 2
  for i=1:nL
    Al[ptr,i]=1
    Al[ptr+1,i]=-1
    ptr = ptr+2
  end
  # Av
  Av = spzeros(nk,1)
  Av[1] = -1.0
  if m == 2
    Av = hcat(Av,zeros(nk,1))
    Av[end,end] = 1
    Ar[end,end] = -1
  end
  # Create unstructured DAE
  A11 = -Ar*(R\Ar')
  E11 = Ac*C*Ac';
  E = blockdiag(sparse(E11),sparse(L),spzeros(m, m))
  A = [ A11 -Al -Av; Al' spzeros(size(Al',1),size(Al,2)+size(Av,2)) ; Av' spzeros(size(Av',1),size(Al,2)+size(Av,2)) ]
  G = spzeros(size(A11,1)+size(Al',1)+size(Av',1),m)
  @views G[end-m+1:end,end-m+1:end] .= -I(m)
  E = U'*E*U
  J = U'*[ spzeros(size(A11, 1), size(A11, 2)) -Al -Av;
          Al' spzeros(size(Al',1),size(Al,2)+size(Av,2));
          Av' spzeros(size(Av',1),size(Al,2)+size(Av,2))]*U
  R = U'*[ -A11 spzeros(size(A11,1),size(Al,2)+size(Av,2));
          spzeros(size(Al',1)+size(Av',1),size(A11,2)+size(Al,2)+size(Av,2))]*U
  Q = U'*sparse(I(size(A11,1)+size(Al',1)+size(Av',1)))*U
  G = U'*G
  return E, J, R, Q, G
end

export setup_DAE1_RCL_LadderNetwork_sparse, setup_DAE2_RCL_LadderNetwork_sparse


using SIAN

println("Setting up the problem")

ode = @ODEmodel(
  S'(t) = -b * S(t) * In(t) / N(t),
  E'(t) = b * S(t) * In(t) / N(t) - nu * E(t),
  In'(t) = nu * E(t) - a * In(t),
  N'(t) = 0,
  Cu'(t) = nu * E(t),
  y1(t) = Cu(t),
  y2(t) = N(t)
)

res = identifiability_ode(ode, get_parameters(ode); p=0.99, p_mod=0, nthrds=1)

println(res)


function default_convex_hull_algorithm(points)
    if length(points) != 0 && length(first(points)) == 2
        return "monotone_chain"
    else
        return nothing
    end
end

"""
    convex_hull(points::Vector{VN};
                [algorithm]=default_convex_hull_algorithm(points),
                [backend]=nothing
                )::Vector{VN} where {N<:Real, VN<:AbstractVector{N}}

Compute the convex hull of the given points.

### Input

- `points`    -- list of vectors
- `algorithm` -- (optional, default: depends on the dimension) the convex hull
                 algorithm, see valid options below
- `backend`   -- (optional, default: `"nothing"`) polyhedral computation backend
                 for higher-dimensional point sets

### Output

The convex hull as a list of vectors with the coordinates of the points.

### Algorithm

A pre-processing step treats the cases with `0`, `1` and `2` points in any dimension.
For more than `3` points, the algorithm used depends on the dimension.

For the one-dimensional case we return the minimum and maximum points, in that order.

The two-dimensional case is handled with a planar convex hull algorithm. The
following algorithms are available:

* `"monotone_chain"`        -- compute the convex hull of points in the plane
                               using Andrew's monotone chain method
* `"monotone_chain_sorted"` -- the same as `"monotone_chain"` but assuming that
                               the points are already sorted in counter-clockwise
                               fashion

See the reference docstring of each of those algorithms for details.

The higher dimensional case is treated using the concrete polyhedra library
`Polyhedra`, that gives access to libraries such as `CDDLib` and `ConvexHull.jl`.
These libraries can be chosen from the `backend` argument.

### Notes

For the in-place version use `convex_hull!` instead of `convex_hull`.

### Examples

Compute the convex hull of a random set of points:

```jldoctest ch_label
julia> points = [randn(2) for i in 1:30]; # 30 random points in 2D

julia> hull = convex_hull(points);

julia> typeof(hull)
Array{Array{Float64,1},1}
```

Plot both the random points and the computed convex hull polygon:

```jldoctest ch_label
julia> using Plots;

julia> plot([Tuple(pi) for pi in points], seriestype=:scatter);

julia> plot!(VPolygon(hull), alpha=0.2);
```
"""
function convex_hull(points::Vector{VN};
                     algorithm=default_convex_hull_algorithm(points),
                     backend=nothing
                     )::Vector{VN} where {N<:Real, VN<:AbstractVector{N}}
    return convex_hull!(copy(points), algorithm=algorithm, backend=backend)
end

function convex_hull!(points::Vector{VN};
                      algorithm=default_convex_hull_algorithm(points),
                      backend=nothing)::Vector{VN} where {N<:Real, VN<:AbstractVector{N}}

    m = length(points)

    # =====================
    # Treat special cases
    # =====================

    # zero or one point
    if m == 0 || m == 1
        return points
    end

    n = length(first(points))

    # two points
    if m == 2
        if n == 1
            p1, p2 = points[1], points[2]
            if p1 == p2  # check for redundancy
                pop!(points)
            elseif p1[1] > p2[1]
                points[1], points[2] = p2, p1
            end
        elseif n == 2
            # special case, see #876
            p1, p2 = points[1], points[2]
            if p1 == p2  # check for redundancy
                pop!(points)
            elseif p1 <= p2
                nothing
            else
                points[1], points[2] = p2, p1
            end
        end
        return points
    end

    # ===========================
    # Dispatch for general cases
    # ===========================

    # _convex_hull_x can assume that there are at least three points
    if n == 1
        return _convex_hull_1d!(points)
    elseif n == 2
        return _convex_hull_2d!(points, algorithm=algorithm)
    else
        return _convex_hull_nd!(points, backend=backend)
    end
end

function _convex_hull_1d!(points::Vector{VN})::Vector{VN} where {N<:Real, VN<:AbstractVector{N}}
    points[1:2] = [minimum(points), maximum(points)]
    return resize!(points, 2)
end

function _convex_hull_nd!(points::Vector{VN};
                          backend=nothing
                          )::Vector{VN} where {N<:Real, VN<:AbstractVector{N}}
    V = VPolytope(points)
    if backend == nothing
        backend = default_polyhedra_backend(V, N)
    end
    Vch = remove_redundant_vertices(V, backend=backend)
    m = length(Vch.vertices)
    points[1:m] = Vch.vertices
    return resize!(points, m)
end

function _convex_hull_2d!(points::Vector{VN};
                          algorithm::String="monotone_chain"
                          )::Vector{VN} where {N<:Real, VN<:AbstractVector{N}}
    if algorithm == "monotone_chain"
        return monotone_chain!(points)
    elseif algorithm == "monotone_chain_sorted"
        return monotone_chain!(points, sort=false)
    else
        error("the convex hull algorithm $algorithm is unknown")
    end
end

"""
    right_turn(O::AbstractVector{N}, A::AbstractVector{N}, B::AbstractVector{N}
              )::N where {N<:Real}

Determine if the acute angle defined by the three points `O`, `A`, `B` in the
plane is a right turn (counter-clockwise) with respect to the center `O`.

### Input

- `O` -- 2D center point
- `A` -- 2D one point
- `B` -- 2D another point

### Output

Scalar representing the rotation.

### Algorithm

The [cross product](https://en.wikipedia.org/wiki/Cross_product) is used to
determine the sense of rotation. If the result is 0, the points are collinear;
if it is positive, the three points constitute a positive angle of rotation
around `O` from `A` to `B`; otherwise they constitute a negative angle.
"""
@inline function right_turn(O::AbstractVector{N},
                            A::AbstractVector{N},
                            B::AbstractVector{N})::N where {N<:Real}
    return (A[1] - O[1]) * (B[2] - O[2]) - (A[2] - O[2]) * (B[1] - O[1])
end

"""
    monotone_chain!(points::Vector{VN}; sort::Bool=true
                   )::Vector{VN} where {N<:Real, VN<:AbstractVector{N}}

Compute the convex hull of points in the plane using Andrew's monotone chain
method.

### Input

- `points` -- list of 2D vectors; is sorted in-place inside this function
- `sort`   -- (optional, default: `true`) flag for sorting the vertices
              lexicographically; sortedness is required for correctness

### Output

List of vectors containing the 2D coordinates of the corner points of the
convex hull.

### Notes

For large sets of points, it is convenient to use static vectors to get
maximum performance. For information on how to convert usual vectors
into static vectors, see the type `SVector` provided by the
[StaticArrays](http://juliaarrays.github.io/StaticArrays.jl/stable/)
package.

### Algorithm

This function implements Andrew's monotone chain convex hull algorithm to
construct the convex hull of a set of ``n`` points in the plane in
``O(n \\log n)`` time.
For further details see
[Monotone chain](https://en.wikibooks.org/wiki/Algorithm_Implementation/Geometry/Convex_hull/Monotone_chain)
"""
function monotone_chain!(points::Vector{VN}; sort::Bool=true
                        )::Vector{VN} where {N<:Real, VN<:AbstractVector{N}}

    @inline function build_hull!(semihull, iterator, points, zero_N)
        @inbounds for i in iterator
            while length(semihull) >= 2 &&
                    (right_turn(semihull[end-1], semihull[end], points[i])
                         <= zero_N)
                pop!(semihull)
            end
            push!(semihull, points[i])
        end
    end

    if sort
        # sort the rows lexicographically (requires a two-dimensional array)
        # points = sortrows(hcat(points...)', alg=QuickSort) # out-of-place version
        sort!(points, by=x->(x[1], x[2]))                    # in-place version
    end

    zero_N = zero(N)

    # build lower hull
    lower = Vector{VN}()
    build_hull!(lower, axes(points)[1], points, zero_N)

    # build upper hull
    upper = Vector{VN}()
    build_hull!(upper, reverse(axes(points)[1]), points, zero_N)

    # remove the last point of each segment because they are repeated
    copyto!(points, @view(lower[1:end-1]))
    copyto!(points, length(lower), @view(upper[1:end-1]))
    return resize!(points, length(lower) + length(upper) - 2)
end


function scomponents_test()
    A = load_matrix_network("cores_example")
    cc = scomponents(MatrixNetwork(A))

    sizes = [15;1;5]
    components = 3
    if cc.sizes != sizes
        error("scomponents failed")
    end
    if length(cc.map) != 21
        error("scomponents failed")
    end
    if cc.number != components
        error("scomponents failed")
    end
    return true
end


function Source{T1<:AbstractFloat,T2<:Complex}(isz::Array{Int,1},isx::Array{Int,1},ot::Array{T1,1},WAV::Array{T2,1},waxis::Array{T1,1},w::T1,nz::Int,nx::Int,ext::Int)

# This function computes the source field at the given frequency. The source
# field is vectorized using column major order. This code is intended for
# simultaneous sources.
#
# INPUTS:     isz       - z-index for the source locations
#             isx       - x-index for the source locations
#             ot        - Activation time for the sources
#             WAV       - Source wavelet (frequency domain)
#             waxis     - Angular frequency axis; contains the angular frequency for each entry in 'WAV'
#             w         - Angular frequency to perform modeling for
#             nz        - Number of grid points in the z-direction
#             nx        - Number of grid points in the x-direction
#             ext       - Thickness (number of grid points) of absorbing boundary region
#
# OUTPUTS:    src       - Source field (vector)

    _,iw = findmin(abs(waxis-w))
    src = complex(zeros(nz*nx))
    for isrc = 1:length(isz)
        k = (isx[isrc]+ext-1)*nz + isz[isrc] + ext
        coeff = -WAV[iw]*exp(-im*w*ot[isrc])
        src[k] = coeff
    end

    return src

end

function Source{T1<:AbstractFloat,T2<:Complex}(isz::Int,isx::Int,ot::T1,WAV::Array{T2,1},waxis::Array{T1,1},w::T1,nz::Int,nx::Int,ext::Int)

# This function computes the source field at the given frequency. The source
# field is vectorized using column major order. This code is intended for only
# a single source.
#
# INPUTS:     isz       - z-index for the source location
#             isx       - x-index for the source location
#             ot        - Activation time for the source
#             WAV       - Source wavelet (frequency domain)
#             waxis     - Angular frequency axis; contains the angular frequency for each entry in 'WAV'
#             w         - Angular frequency to perform modeling for
#             nz        - Number of grid points in the z-direction
#             nx        - Number of grid points in the x-direction
#             ext       - Thickness (number of grid points) of absorbing boundary region
#
# OUTPUTS:    src       - Source field (vector)

    _,iw = findmin(abs(waxis-w))
    k = (isx+ext-1)*nz + isz + ext
    coeff = -WAV[iw]*exp(-im*w*ot)

    src = complex(zeros(nz*nx))
    src[k] = coeff

    return src

end


### visualize a solution.

function visualizefiltersolution(𝓧, xq, wfunc, dfunc,f)
    𝑤_new = get𝑤scaled(𝓧)
    h_new = geth(𝑤_new, wfunc, dfunc, 𝓧)

    f_itp = xx->getp(xx, 𝑤_new, h_new, wfunc, dfunc, 𝓧)
    f_itp_xq = collect( f_itp(xq[i]) for i = 1:length(xq) )
    f_itp_𝓧 = collect( f_itp(𝓧[i]) for i = 1:length(𝓧) )

    𝑒 = xx->(f(xx)-f_itp(xx))
    𝑒_xq = collect( 𝑒(xq[i]) for i = 1:length(xq) )
    𝑒_𝓧 = collect( 𝑒(𝓧[i]) for i = 1:length(𝓧) )

    return f_itp_xq, f_itp_𝓧, 𝑒_xq, 𝑒_𝓧
end

function computeDTFTviaformula(h::AbstractArray{T}, ω::T)::Complex{T} where T <: Real
    N = length(h)
    return sum( h[n+1]*exp(-im*ω*n) for n = 0:N-1 )
end

function computeDTFTviaformula(h::AbstractArray{T}, ω::Vector{T}) where T

    return collect( computeDTFTviaformula(h, ω[i]) for i = 1:length(ω))
end

function getfreqrsp(h::Vector{Float64}, resolution_multiple::Int = 20)
    N_samples = length(h)

    ω_set_fft = collect( LinRange(0,2*π-2*π/N_samples,N_samples))
    DFT_evals = fft(h)

    ω_set = collect( LinRange(0,2*π,N_samples*resolution_multiple) )
    DTFT_evals = collect( computeDTFTviaformula(h, ω_set[i]) for i = 1:length(ω_set) )

    return ω_set_fft, DFT_evals, ω_set, DTFT_evals
end

function plotmagnitudersp(h::Vector{Float64}, fig_num::Int, title_string::String = "Magnitude response")

    ω_set_fft, DFT_evals, ω_set, DTFT_evals = getfreqrsp(h)

    # visualize.
    mag_rsp_G = abs.(DTFT_evals)
    mag_rsp_fft = abs.(DFT_evals)

    PyPlot.figure(fig_num)
    fig_num += 1

    PyPlot.plot(ω_set_fft, mag_rsp_fft, ".", label = "DFT")
    PyPlot.plot(ω_set, mag_rsp_G, label = "DTFT")

    PyPlot.title(title_string)
    PyPlot.legend()

    return fig_num
end


# -*- coding: utf-8 -*-
# ---
# jupyter:
#   jupytext:
#     formats: jl:light,ipynb
#     text_representation:
#       extension: .jl
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.5.1
#   kernelspec:
#     display_name: Julia 1.4.2
#     language: julia
#     name: julia-1.4
# ---

# # VV10 Non-local correlation kernel
# One of the largest deficiencies of semilocal functionals is the lack of long-range correlation effects. The most notable effect is the lack of dispersion in the interactions between molecules. VV10 was expressly created to bridge the gap between the expensive true non-local correlation and a computational tractable form. We will begin by writing the overall expression:
#
# $$E_c^{\rm{nl}} = \frac{1}{2}\int \int d{\bf r}d{\bf r'}\rho({\bf r})\Phi({\bf r},{\bf r'})\rho({\bf r'}),$$
#
# where the two densities are tied together through the $\Phi$ operator.
#
# For VV10 we have:
# $$
# \begin{align}
# \Phi &= -\frac{3}{2gg'(g + g')},\\
#  g &= \omega_0({\rm r}) R^2 + \kappa({\rm r)},\\
#  g' &= \omega_0({\rm r}) R^2 + \kappa({\rm r')},
# \end{align}
# $$
#
# where $\omega_{0}$:
#
# $$
# \begin{align}
# \omega_{0}(r) &= \sqrt{\omega_{g}^2(r) + \frac{\omega_p^2(r)}{3}} \\
# \omega_g^2(r) &= C \left | \frac{\nabla \rho({\bf r})}{\rho({\bf r})} \right |^4 \\
# \omega_p^2(r) &= 4 \pi \rho({\bf r}),
# \end{align}
# $$
#
# and finally:
#
# $$\kappa({\bf r}) = b * \frac{3 \pi}{2} \left [ \frac{\rho({\bf r})}{9\pi} \right ]^\frac{1}{6}.$$
#
# While there are several expressions, this is actually relatively easy to compute. First let us examine how the VV10 energy is reintegrated:
#
# $$E_c^{\rm{VV10}} = \int d{\bf r} \rho{\bf r} \left [ \beta + \frac{1}{2}\int d{\bf r'} \rho{\bf r'} \Phi({\bf r},{\bf r'}) \right].$$
#
#

# +
using PyCall: pyimport
psi4 = pyimport("psi4")
np   = pyimport("numpy") # used only to cast to Psi4 arrays
using LinearAlgebra: dot
import Formatting: printfmt
include("ks_helper.jl")

mol = psi4.geometry("""
He 0 0 -5
He 0 0  5
symmetry c1
""")
options = (Dict("BASIS" => "aug-cc-pvdz",
                "DFT_SPHERICAL_POINTS" => 110,
                "DFT_RADIAL_POINTS" => 20))
# -

# ## VV10 coefficients
# First let us build set and build a few static coefficients:

coef_C = 0.0093
coef_B = 5.9
β = 1.0 / 32.0 * (3.0 / (coef_B^2.0))^(3.0 / 4.0)

# ## VV10 kernel
# First let us construct a function that computes $\omega_0$, and $\kappa$ quantities. We simplify the following piece contained in $\omega_g$:
# $$\left |\frac{\nabla \rho({\bf r})}{\rho({\bf r})} \right|^4$$
#
# by recalling that
#
# $$\gamma({\bf r}) = \nabla\rho({\bf r})\cdot\nabla\rho({\bf r}),$$
#
# therefore, we can simplify the above to:
#
# $$\left |\frac{\nabla \rho({\bf r})}{\rho({\bf r})} \right |^4 = \left | \frac{\gamma({\bf r})}{\rho({\bf r})\cdot \rho({\bf r})} \right | ^2 $$

function compute_vv10_kernel(ρ, γ)
    κ_pref = coef_B * (1.5 * π) / ((9.0 * π)^(1.0 / 6.0))

    # Compute R quantities
    Wp = (4.0 / 3.0) * π * ρ
    Wg = @. coef_C * ((γ / (ρ * ρ))^2.0)
    W0 = @. sqrt(Wg + Wp)

    κ = @. ρ^(1.0 / 6.0) * κ_pref

    W0, κ
end

# ## VV10 energy and gradient evaluation
#
# The next block of code computes the VV10 energy and its gradient. In the very end we plug this function in
# the Khon-Sham solver `ks_solver` that is in a separate file `ks_helper.jl`. We conveniently separated the solver to focus in the non-local kernel.

# +
function compute_vv10(D, Vpot)

    nbf = D.shape[1]
    Varr = zeros(nbf, nbf)

    total_e = 0.0
    tD = 2np.array(D)

    points_func = Vpot.properties()[1]
    superfunc = Vpot.functional()

    xc_e = 0.0
    vv10_e = 0.0

    # First loop over the outer set of blocks
    for l_block in 1:Vpot.nblocks()

        # Obtain general grid information
        l_grid = Vpot.get_block(l_block-1)
        l_w = np.array(l_grid.w())
        l_x = np.array(l_grid.x())
        l_y = np.array(l_grid.y())
        l_z = np.array(l_grid.z())
        l_npoints = size(l_w,1)

        points_func.compute_points(l_grid)

        # Compute the functional itself
        ret = superfunc.compute_functional(points_func.point_values(), -1)

        xc_e += dot(l_w, np.array(ret["V"])[1:l_npoints])
        v_ρ = np.array(ret["V_RHO_A"])[1:l_npoints]
        v_γ = np.array(ret["V_GAMMA_AA"])[1:l_npoints]

        # Begin VV10 information
        l_ρ = np.array(points_func.point_values()["RHO_A"])[1:l_npoints]
        l_γ = np.array(points_func.point_values()["GAMMA_AA"])[1:l_npoints]

        l_W0, l_κ = compute_vv10_kernel(l_ρ, l_γ)

        phi_kernel = zero(l_ρ)
        phi_U = zero(l_ρ)
        phi_W = zero(l_ρ)

        # Loop over the inner set of blocks
        for r_block in 1:Vpot.nblocks()

            # Repeat as for the left blocks
            r_grid = Vpot.get_block(r_block-1)
            r_w = np.array(r_grid.w())
            r_x = np.array(r_grid.x())
            r_y = np.array(r_grid.y())
            r_z = np.array(r_grid.z())
            r_npoints = size(r_w,1)

            points_func.compute_points(r_grid)

            r_ρ = np.array(points_func.point_values()["RHO_A"])[1:r_npoints]
            r_γ = np.array(points_func.point_values()["GAMMA_AA"])[1:r_npoints]

            r_W0, r_κ = compute_vv10_kernel(r_ρ, r_γ)

            newaxis = [CartesianIndex()]

            # Build the distance matrix
            R2  = (l_x[:, newaxis] .- r_x').^2
            R2 += (l_y[:, newaxis] .- r_y').^2
            R2 += (l_z[:, newaxis] .- r_z').^2

            # Build g
            g  = @. l_W0[:, newaxis] * R2 + l_κ[:, newaxis]
            gp = @. r_W0' * R2 + r_κ'

            F_kernel = @. -1.5(r_w * r_ρ)' / (g * gp * (g + gp))
            F_U = @. F_kernel * ((1.0 / g) + (1.0 / (g + gp)))
            F_W = F_U .* R2

            phi_kernel += sum(F_kernel, dims=2)
            phi_U += -sum(F_U, dims=2)
            phi_W += -sum(F_W, dims=2)
        end

        # Compute those derivatives
        κ_dn = l_κ ./ (6l_ρ)
        w0_dγ = @. coef_C * l_γ / (l_W0 * l_ρ^4.0)
        w0_dρ = @. 2.0 / l_W0 * (π/3.0 - coef_C * l_γ^2.0 / (l_ρ^5.0))

        # Sum up the energy
        vv10_e += sum(@. l_w * l_ρ * (β + 0.5phi_kernel))

        # Perturb the derivative quantities
        v_ρ += @. β + phi_kernel + l_ρ * (κ_dn * phi_U + w0_dρ * phi_W)
        v_ρ *= 0.5

        v_γ += l_ρ .* w0_dγ .* phi_W

        # Recompute to l_grid
        lpos = np.array(l_grid.functions_local_to_global()) .+ 1
        points_func.compute_points(l_grid)
        nfunctions = size(lpos,1)

        # Integrate the LDA and GGA quantities
        phi = np.array(points_func.basis_values()["PHI"])[1:l_npoints, 1:nfunctions]
        phi_x = np.array(points_func.basis_values()["PHI_X"])[1:l_npoints, 1:nfunctions]
        phi_y = np.array(points_func.basis_values()["PHI_Y"])[1:l_npoints, 1:nfunctions]
        phi_z = np.array(points_func.basis_values()["PHI_Z"])[1:l_npoints, 1:nfunctions]

        # LDA
        # Vtmp[ab] = ϕ[pb] Vρ[p] lw[p] ϕ[pa]
        Vtmp = phi' * (v_ρ .* l_w .* phi)

        # GGA
        l_ρ_x = np.array(points_func.point_values()["RHO_AX"])[1:l_npoints]
        l_ρ_y = np.array(points_func.point_values()["RHO_AY"])[1:l_npoints]
        l_ρ_z = np.array(points_func.point_values()["RHO_AZ"])[1:l_npoints]

        tmp_grid = 2l_w .* v_γ
        # Vtmp[ab] += ϕ[pb] tmp_grid[p] ∇ₓρ[p] ∇ₓϕ[pa] ∀ x,y,z
        Vtmp += phi' * (tmp_grid .* l_ρ_x .* phi_x)
        Vtmp += phi' * (tmp_grid .* l_ρ_y .* phi_y)
        Vtmp += phi' * (tmp_grid .* l_ρ_z .* phi_z)

        # Sum back to the correct place
        Varr[lpos, lpos] += Vtmp + Vtmp'
    end

    printfmt("   VV10 NL energy: {:16.8f}\n", vv10_e)

    xc_e += vv10_e
    return xc_e, Varr
end

ks_solver("VV10", mol, options, compute_vv10)
# -

# Refs:
#  - Vydrov O. A.; Van Voorhis T., *J. Chem. Phys.*, **2010**, *133*, 244103


# This file is a part of JuliaFEM.
# License is MIT: see https://github.com/JuliaFEM/NodeNumbering.jl/blob/master/LICENSE

"""
    node_degrees(adjacency::Dict{Int, Vector{Int}})

Calculate the degrees for the adjacency graph nodes. Node degree = number of nodes a certain node is adjacent to.

# Examples

```jldoctest
julia> adjacency = Dict{Int, Vector{Int}}(
                   1 => [3, 8, 9],
                   2 => [3, 8, 7],
                   3 => [1, 2],
                   4 => [8, 9],
                   5 => [7, 8],
                   6 => [2, 7],
                   7 => [5, 2, 6],
                   8 => [1, 2, 4, 5],
                   9 => [1, 4]);

julia> node_degrees(adjacency::Dict{Int, Vector{Int}})
Dict{Int64,Int64} with 9 entries:
  7 => 3
  4 => 2
  9 => 2
  2 => 3
  3 => 2
  5 => 2
  8 => 4
  6 => 2
  1 => 3

```

# References

* Wikipedia contributors. "Degree (graph theory)". Wikipedia, The Free Encyclopedia. 24 Nov. 2016. Web. 18 Jul. 2017. https://en.wikipedia.org/wiki/Degree_(graph_theory)
"""
function node_degrees(adjacency::Dict{Int, Vector{Int}})
    degrees = Dict{Int, Int}()
    for (k, v) in adjacency
        degree = length(v)
        degrees[k] = degree
    end
    return degrees
end


# fill and reverse Cint array with padding etc. for cudnn calls
function cdsize(w, nd)
    if isa(w,Number)
        fill(Cint(w),nd)
    elseif length(w)==nd
        [ Cint(w[nd-i+1]) for i=1:nd ]
    else
        throw(DimensionMismatch("$w $nd"))
    end
end

# convert padding etc. size to an Int array of the right dimension
function psize(p, x)
    nd = ndims(x)-2
    if isa(p,Number)
        fill(Int(p),nd)
    elseif length(p)==nd
        collect(Int,p)
    else
        throw(DimensionMismatch("psize: $p $nd"))
    end
end

DT(::CuArray{Float32})=UInt32(0)
DT(::CuArray{Float64})=UInt32(1)
DT(::CuArray{Float16})=UInt32(2)

function cdims(w,x; padding=0, stride=1, o...)
    N = ndims(x)
    ntuple(N) do i
        if i < N-1
            pi = (if isa(padding,Number); padding; else padding[i]; end)
            si = (if isa(stride,Number); stride; else stride[i]; end)
            1 + div(size(x,i) - size(w,i) + 2*pi, si)
        elseif i == N-1
            size(w,N)
        else # i == N
            size(x,N)
        end
    end
end

function pdims(x; window=2, padding=0, stride=window, o...)
    N = ndims(x)
    ntuple(N) do i
        if i < N-1
            wi = (if isa(window,Number); window; else window[i]; end)
            pi = (if isa(padding,Number); padding; else padding[i]; end)
            si = (if isa(stride,Number); stride; else stride[i]; end)
            1 + div(size(x,i) + 2*pi - wi, si)
        else
            size(x,i)
        end
    end
end

function dcdims(w,x; padding=0, stride=1, o...)
    N = ndims(x)
    ntuple(N) do i
        if i < N-1
            pi = (if isa(padding,Number); padding; else padding[i]; end)
            si = (if isa(stride,Number); stride; else stride[i]; end)
            si*(size(x,i)-1) + size(w,i) - 2*pi
        elseif i == N-1
            size(w,N)
        else
            size(x,N)
        end
    end
end

function updims(x; window=2, padding=0, stride=window, o...)
    window = psize(window,x)
    stride = psize(stride,x)
    padding = psize(padding,x)
    N = ndims(x)
    ntuple(N) do i
        if i < N-1
            (size(x,i)-1)*stride[i]+window[i]-2*padding[i]
        else
            size(x,i)
        end
    end
end

# convolution padding size that preserves the input size when filter size is odd and stride=1
padsize(w)=ntuple(i->div(size(w,i)-1,2), ndims(w)-2)


### A Pluto.jl notebook ###
# v0.14.0

using Markdown
using InteractiveUtils

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

# ╔═╡ 42085492-ac8c-11eb-0620-adcb307077f1
html"""

Section 3.4

 Our first climate model

Lecture Video

"""

# ╔═╡ c7d387fa-cd19-458c-a45d-7893e8c21bbf
begin
    import Pkg
    Pkg.activate(mktempdir())

    Pkg.add([
        Pkg.PackageSpec(name="DifferentialEquations", version="6"),
        Pkg.PackageSpec(name="Plots", version="1"),
		Pkg.PackageSpec(name="PlutoUI", version="0.7"),
	    Pkg.PackageSpec(name="CSV", version="0.8"),
	    Pkg.PackageSpec(name="DataFrames", version="1")

         ])

    using DifferentialEquations, Plots, PlutoUI, LinearAlgebra, CSV, DataFrames
end

# ╔═╡ 14195fc4-40e1-4576-973a-69d649fddc02
TableOfContents(title="📚 Table of Contents", indent=true, depth=4, aside=true)

# ╔═╡ 92883ed9-5572-41fd-96c7-190279f90804
md"""
This lecture is based on the fall 2020 "Introduction to Climate Modeling" lecture by Henri Drake. [Henri's video](https://computationalthinking.mit.edu/Fall20/lecture20/),
[Henri's original notebook](https://github.com/mitmath/18S191/blob/Fall20/lecture_notebooks/week11/01_energy_balance_model.jl) where he describes the Energy Balance Model.
"""

# ╔═╡ 340fc7c4-128f-4476-b445-65005ffa9f5a
md"""
### Pre - Computational Thinking
(traditional ode "analytic" solutions)
"""

# ╔═╡ 7673c7b7-0921-4a7e-ae4d-e785c6391a0c
md"""
Notation:

``y=y(t)`` is a function of time $t$

Inititally $t=0$, $y(0)=y_0$

We write ``y'`` for ``dy/dt``.
"""

# ╔═╡ ebfeb0b0-d29e-4244-a0e3-a7185f3c8124
md"""
---
#### ``y'=`` constant:

``y' = a``
$(html"
") Solution: ``y(t) = at + y_0``

---
"""

# ╔═╡ 4bf60f27-8db5-42c1-83d8-750bec2060e5
md"""

#### ``y'=`` linear in y:

``y' = a - by``
$(html"
") Solution: ``y(t) =(- \frac{a}{b})(e^{-bt}-1) + y_0 e^{-bt}``

Comment:  Equilibrium obtained  (if $b\ne0$)  by solving y'=0 for y giving equilibrium $y=a/b$. Also
can be obtained by letting $t\rightarrow \infty$ in the solution killing the exponential terms.

---
"""

# ╔═╡ e2799d05-36a7-4547-8663-90708ce01ce8
md"""
#### ``y'=`` linear in y + "forcing term" in t:

``y' = a - by + f(t)``
$(html"
")
Solution: ``y(t) = e^{-bt} \left( y_0 + \int_0^t  e^{bu}(a+f(u)) du \right)``

---
"""

# ╔═╡ 4d58018b-752c-43f8-aafb-f11a95ca1874
md"""
### Computational Thinking
"""

# ╔═╡ 279b4748-829f-44a3-94da-da3abf32ff13
md"""
#### Review: Solving an ODE (y'=f(y)) in Julia:

0. Define f(y,p,t)
1. ODEProblem(  f, f(0), time_span, parameters)
2. solve
3. plot etc
"""

# ╔═╡ 578a80ad-fcf1-4d5c-81e5-205113bbf78d
sol = solve(  ODEProblem( f, y₀, (0,10.0), (a,b,forcing(c)) ) );

# ╔═╡ c1d87c42-e1e0-4e52-8023-6cc176266e86
f(y,(a,b,forcing(c)),t) = a - b*y + forcing(c)(t)

# ╔═╡ adb59adc-b30a-4dc4-bc3f-2804b7e02876
forcing(c)  =  t->c*t

# ╔═╡ 220420c1-e8cc-4ff6-8b79-ba2ec49c7695
y₀

# ╔═╡ 7bee45a5-fa62-455b-813a-3e5dcf430289
md"""
a = $(@bind a Slider(0:.01:10, show_value=true, default=0) )

b = $(@bind b Slider(0:.1:5, show_value=true, default=0) )

y₀ = $(@bind y₀ Slider(-5:.1:15, show_value=true, default=2.0) )

c = $(@bind c Slider(0:.1:5, show_value=true, default=0.0) )
"""

# ╔═╡ bda41881-75c7-4732-9a66-d7947607b1b6
begin
	p = plot( sol , legend=false, background_color_inside=:black , ylims=(0,10), lw=3, c=:red)
	# plot direction field:
		xs = Float64[]
		ys = Float64[]

	    lrx = LinRange( xlims(p)..., 30)
		for x in  lrx
			for y in LinRange( ylims(p)..., 30)
				v = [1, a - b*y + forcing(c)(x) ]
				v ./=  (20* (lrx[2]-lrx[1]))
				# plot!([x, x + v[1]], [y, y + v[2]], alpha=0.5, c=:gray)

				push!(xs, x - v[1], x + v[1], NaN)
				push!(ys, y - v[2], y + v[2], NaN)

			end
		end
		hline!( [b==0 ? 0 : a/b],c=:white,ls=:dash)
		plot!(xs, ys, alpha=0.7, c=:yellow)
	    ylabel!("y")
	    annotate!(-.5,y₀,text("y₀",color=:red))
	    title!("Solution to y'(t) = a - by + forcing(c)(t)")
end

# ╔═╡ aba7fc93-c0ac-4c9e-b975-18145f87707f
md"""#### 1) Background: climate physics

The simplest climate model can be conceptualized as:
\begin{align}
\text{\color{brown}{change in heat content}} = & + \text{\color{orange}{absorbed solar radiation (energy from the Sun's rays)}} \newline
& - \text{\color{blue}{outgoing thermal radiation (i.e. blackbody cooling to space)}}
\newline
& + \text{\color{grey}{human-caused greenhouse effect (trapped outgoing radiation)}}
\end{align}

where each of these is interpreted as an average over the entire globe (hence "zero-dimensional").
"""

# ╔═╡ b9eca29e-9028-4fd7-8c62-718a2dcf87d1
html""""""

# ╔═╡ dabca25c-e34b-4aed-8729-132d03c5bb45
md"""
### 1.1 Incoming 🌞: Absorbed solar radiation
(an example of $\mathrm{temp}$'=constant)
"""

# ╔═╡ d74936e9-b760-4add-b3e7-46d544064c16
md"""
In math we just write down a differential equation, but in the physical world there are physical variables to identify.

In our baking the earth example, we will identify the following quantities:

- Industrial Revolution Start: 1850
- Avg Temperature in 1850: 14.0 °C

- Solar Insolation $S=$1368 W/m^2:  energy from the sun

- Albedo or plentary reflectivity: α = 0.3

- atmosphere and upper-ocean heat capacity: C= 51  J/m^2/°C

Earth Baking Formula:
$(html"
")
 `` C\  \mathrm{temp}'(t) = S(1-α)/4 = `` $(S*(1-α)/4)
"""

# ╔═╡ 5123525a-3437-4b76-813c-8ad6b158f7f2
md"""
##### (Heating the earth nonstop)
"""

# ╔═╡ 2f19cbac-4f13-4c2b-9b11-fb92e8055527
begin
	plot(solve(p1),       legend = false,
		 background_color_inside = :black,
		                  xlabel = "years from $(1850)",
	                      ylabel = "Temperature °C")
	hline!( [temp₀,temp₀] ,c=:white,ls=:dash)
	annotate!( 80, 25+temp₀, text("Preindustrial Temperature = $(temp₀)°C",color=:white))
	title!("Absorbing Solar Radiation (only)")
end

# ╔═╡ 087f47b2-8283-4205-88f2-4d5883a340c2
md"""

At Earth's orbital distance from the Sun, the power of the Sun's rays that intercept the Earth is equal to
"""

# ╔═╡ 9c89c4e9-65ee-4424-bd74-17168b211797
S = 1368; # solar insolation [W/m^2]  (energy per unit time per unit area)

# ╔═╡ ca5b41d2-b511-486a-91fe-cceb8f7282c3
md"A small fraction"

# ╔═╡ 13984c61-4c34-40db-9043-fcff2721522e
α = 0.3; # albedo, or planetary reflectivity [unitless]

# ╔═╡ 868e19f4-d71e-4222-9bdd-470387991c67
md"""
of this incoming solar radiation is reflected back out to space (by reflective surfaces like white clouds, snow, and ice), with the remaining fraction $(1-\alpha)$ being absorbed.

Since the incoming solar rays are all approximately parallel this far from the Sun, the cross-sectional area of the Earth that intercepts them is just a disc of area $\pi R^{2}$. Since all of the other terms we will consider act on the entire surface area $4\pi R^{2}$ of the spherical Earth, the absorbed solar radiation *per unit surface area* (averaged over the entire globe) is reduced by a factor of 4.

![](https://www.open.edu/openlearn/ocw/pluginfile.php/101161/mod_oucontent/oucontent/890/639dcd57/ce3f1c3a/s250_3_002i.jpg)

The absorbed solar radiation per unit area is thus

$\textcolor{orange}{\text{absorbed solar radiation} \equiv \frac{S(1-\alpha)}{4}}$
"""

# ╔═╡ 0e4dedc5-0b1e-4e46-b943-284bfb2de57f
absorbed_solar_radiation = S*(1 - α)/4; # [W/m^2]

# ╔═╡ 91329865-f593-4388-a5d0-01af4be6e01d
begin
	C = 51.; # atmosphere and upper-ocean heat capacity [J/m^2/°C]
	temp₀ = 14.0 # preindustrial temperature [°C]
end

# ╔═╡ 7187ae25-239d-4752-898e-6674009b5de6
 p1 = ODEProblem( (temp,p,t)-> (1/C) * absorbed_solar_radiation, temp₀,  (0.0,170) )

# ╔═╡ ad1e294e-ad8a-48f9-b924-2474cce16aaf
md"""The heat content $C temp$ is determined by the temperature $temp$ (in Kelvin) and the heat capacity of the climate system. While we are interested in the temperature of the atmosphere, which has a very small heat capacity, its heat is closely coupled with that of the upper ocean, which has a much larger heat capacity of
"""

# ╔═╡ 2086e386-194b-4863-a9ee-178002925d42
md"""
The *change in heat content over time* is thus simply given by $\frac{d(CT)}{dt}$. Since the heat capacity of sea water hardly changes with temperature, we can rewrite this in terms of the change in temperature with time as:

$\color{brown}{\text{change in heat content } =\; C \frac{dtemp}{dt}}$
"""

# ╔═╡ 2b66ffaf-a9a3-4ca2-a6e9-732912c9d48e
md"""
### 1.2 Outgoing ♨ : thermal radiation
"""

# ╔═╡ b186d026-d02c-4f06-8097-aa54b363f4fb
md"""

The outgoing thermal radiation term $\mathcal{G}(T)$ (or "blackbody cooling to space") represents the combined effects of *negative feedbacks that dampen warming*, such as **blackbody radiation**, and *positive feedbacks that amplify warming*, such as the **water vapor feedback**.

Since these physics are too complicated to deal with here, we *linearize* the model
combining the incoming and the outgoing.

We assume that the preindustrial world was in energy balance, and thus
the equilibrium temperature is the preindustrial temperature.

Thus we assume

 temp'($t$) = $B$(temp($0$)-temp($t$))  for some value of $B$.
The minus sign in front of temp(t) indicating it restores equilibrium.
"""

# ╔═╡ edafca88-dd4a-4fe4-8ffc-bb50f7bd561d
md"""
The value that has been chosen is
"""

# ╔═╡ 9b97e41b-76ff-4cc2-90c1-446d59c4ece7
B = 1.3; # climate feedback parameter [W/m^2/°C],

# ╔═╡ af893e1e-3bd7-4d05-83b6-f5f7e751b5e3
md"""
start\_temp = $(@bind start_temp Slider(0:28; show_value=true, default=14))
"""

# ╔═╡ 333290b0-0198-4306-b19b-6d30df79a280
p2 = ODEProblem( (temp,p,t)-> (1/C) * B*(temp₀-temp), start_temp,  (0.0,170) )

# ╔═╡ eeaf3735-5139-4924-9fce-14df51a61042
begin
	plot(solve(p2),       legend = false,
		 background_color_inside = :black,
		                  xlabel = "years from start",
	                      ylabel = "Temperature °C",
	                      ylim = (0,30))
	hline!( [temp₀,temp₀] ,c=:white,ls=:dash)
	annotate!( 80, temp₀, text("Preindustrial Temperature = $(temp₀)°C",:bottom,color=:white))
	title!("Energy Balance Model (Healthy Earth)")
end

# ╔═╡ 20d6c513-2ca6-4dea-9092-156e2805d467
md"""
### 1.3 Greenhouse 🏭: Human-caused greenhouse effect

Empirically, the greenhouse effect is known to be a logarithmic function of gaseous carbon dioxide (CO₂) concentrations

$\color{grey}{\text{human-caused greenhouse effect}\; = {\mbox {(forcing\_coef)}} \ln \left( \frac{[\text{CO}₂]}{[\text{CO}₂]_{\text{PreIndust}}} \right),}$

How this depends on time into the future depends on human behavior!
Time is not modelled in the above equation.
"""

# ╔═╡ 692a6928-c7a2-4b61-a794-16bef5d4e919
md"where"

# ╔═╡ 6d1058bf-8a05-4b8e-835a-de9e95f567c4
forcing_coef = 5.0; # CO2 forcing coefficient [W/m^2]

# ╔═╡ 35184e6f-ac3e-480b-92e9-ed8baaaa833b
CO₂_PreIndust = 280.; # preindustrial CO2 concentration [parts per million; ppm];

# ╔═╡ 437faadd-0301-403a-bcd7-18ce279589d0
greenhouse_effect(CO₂) = forcing_coef * log(CO₂/CO₂_PreIndust)

# ╔═╡ 1a4d21bd-85ad-4935-913a-8992a8996db4
greenhouse_effect(CO₂(15))

# ╔═╡ ee6414b7-e92d-4055-af17-6b02f05c28cd
begin
	CO2_present = 420.
	CO2_range = 280*(2 .^(range(-1, stop=3,length=100)))
	plot(CO2_range, greenhouse_effect.(CO2_range), lw=2.5, label=nothing, color=:black)
	plot!([CO₂_PreIndust], [greenhouse_effect(CO₂_PreIndust)], marker=:., ms=6, linecolor=:white,
		color=:blue, lw=0, label="pre-industrial (PI)")
	plot!([CO2_present], [greenhouse_effect(CO2_present)], marker=:., ms=6, color=:red, linecolor=:white, lw=0, label="present day (2020)")
	plot!(xticks=[280, 280*2, 280*4, 280*8], legend=:bottomright, size=(400, 250))
	plot!(ylabel="Radiative forcing [W/m²]", xlabel="CO₂ concentration [ppm]")
end

# ╔═╡ fac5012a-960c-473c-bbf0-62c0be87f608
begin
	 #CO₂(t) = CO₂_PreIndust # no emissions
	 # CO₂(t) = CO₂_PreIndust * 1.01^t # test model
	 CO₂(t) = CO₂_PreIndust * (1+ (t/220)^3 )
end

# ╔═╡ 99629ec2-dc70-4253-b191-305bccc9f36b
p3 = ODEProblem( (temp,p,t)-> (1/C) * ( B*(temp₀-temp)  + greenhouse_effect(CO₂(t))    ) , start_temp,  (0.0,170) )

# ╔═╡ 6b2beeec-6383-42b3-b694-8d77b961c8a1
begin
	plot(solve(p3),       legend = false,
		 background_color_inside = :black,
		                  xlabel = "years from 1850",
	                      ylabel = "Temperature °C",
	                      ylim = (10,20))
	hline!( [temp₀,temp₀] ,c=:white,ls=:dash)
	annotate!( 80, temp₀, text("Preindustrial Temperature = $(temp₀)°C",:bottom,color=:white))
	title!("Model with CO₂")
end

# ╔═╡ 0b24f105-0166-4a41-97aa-156417d7203a
begin
	years = 1850:2020
	plot( years, CO₂.(years.-1850), lw=3, legend=false)
end

# ╔═╡ c6f8dcf6-950d-48ff-b040-55bdd347d74b
md"""
### Observations from Mauna Loa Volcano ![Mauna Loa Volcano](https://i.pinimg.com/originals/df/1a/e7/df1ae72cfd5e6d0d535c0ec99e708f6f.jpg)
"""

# ╔═╡ cd1dcbc4-2273-4eda-85d9-9af4fd71b3c1
md"""
information is available at
[https://www.ncei.noaa.gov/pub/data/paleo/icecore/antarctica/law/law2006.txt]
(https://www.ncei.noaa.gov/pub/data/paleo/icecore/antarctica/law/law2006.txt).
"""

# ╔═╡ c3b0b7fc-19be-4f04-8787-6349ab9bff7f
begin
	CO2_historical_data_url = "https://scrippsco2.ucsd.edu/assets/data/atmospheric/stations/in_situ_co2/monthly/monthly_in_situ_co2_mlo.csv"

	CO2_historical_data = CSV.read(download(CO2_historical_data_url), DataFrame, header=4, datarow=7, );

	first(CO2_historical_data, 11)
end

# ╔═╡ f5c2d0de-f5d2-43f2-bfca-41bd727b3ca9
md"""
Data is in the fifth column CO₂
"""

# ╔═╡ ef9d9512-cb40-4641-9d6a-5a36f5a4b33d
md"""
Oh no, missing data (-99.99)
"""

# ╔═╡ 2bc4c596-b313-49f6-84b5-5d53f2baf0e9
validrowsmask = CO2_historical_data[:, "     CO2"] .> 0

# ╔═╡ 648dfce3-5363-4539-8f41-f5d27bae4c6f
begin
	begin
		plot( CO2_historical_data[validrowsmask, "      Date"] , CO2_historical_data[validrowsmask, "     CO2"], label="Mauna Loa CO₂ data (Keeling curve)")
		plot!( years, CO₂.(years.-1850), lw=3 , label="Cubic Fit", legend=:topleft)
		xlabel!("year")
		ylabel!("CO₂ (ppm)")
		title!("CO₂ observations and fit")

	end
end

# ╔═╡ 288bfc6b-6848-40d5-916b-95a8a4248a4f
md"""
We will use this fit to compare against historical temperatures.
"""

# ╔═╡ a62ca8d1-321a-435e-a9df-f63d000376c7
begin
	plot(years,solp4.(years.-1850),lw=2,label="Predicted Temperature from model", legend=:topleft)
	xlabel!("year")
	ylabel!("Temp °C")

	plot!( parse.(Float64, T_df[:,1]), parse.(Float64, T_df[:,2]) .+ 14.15, color=:black, label="NASA Observations", legend=:topleft)
end

# ╔═╡ 3304174c-289d-47c5-b5ef-161b11e515eb
md"""
Climate feedback BB = $(@bind BB Slider(0:.1:4, show_value=true, default=B))

Ocean Heat Capacity CC =$(@bind CC Slider(10:.1:200, show_value=true, default=C))
"""

# ╔═╡ 13269c86-06a4-4354-b620-bdf3f6432294
p4 = ODEProblem( (temp,p,t)-> (1/CC) * ( BB*(temp₀-temp)  + greenhouse_effect(CO₂(t))    ) , start_temp,  (0.0,170) )

# ╔═╡ 28acb5a4-2a5f-49c5-9c78-deb40fdeed36
md""" #####  Best- and worst-case projections of future global warming

"""

# ╔═╡ 13b003d2-1fd4-4a4a-960c-4a1d9b673dc6
md"""Consider two divergent hypothetical futures:
1. a **low-emissions** world in which emissions decrease such that CO2 concentrations stay below 500 ppm by 2100 (known in climate circles as "RCP8.5") and
2. a **high-emissions** world in which emissions continue increasing and CO2 concentrations soar upwards of 1200 ppm ("RCP2.6").
"""

# ╔═╡ a2288816-3621-4871-9faf-3e9c78674969
md"""
![](https://raw.githubusercontent.com/mitmath/18S191/Spring21/notebooks/week12/predictthefuture.svg)
"""

# ╔═╡ 2b9c3427-c3ec-41ba-b3c5-268cf3dddccf
md"""
In the low-emissions scenario, the temperature increase stays below $ΔT = 2$ °C by 2100, while in the high-emissions scenario temperatures soar upwards of 3.5ºC above pre-industrial levels.
"""

# ╔═╡ fef0a76b-98dc-42a0-975b-0fba8a32e5cc
md"Although the greenhouse effect due to human-caused CO₂ emissions is the dominant forcing behind historical and future-projected warming, modern climate modelling considers a fairly exhaustive list of other forcing factors (aerosols, other greenhouse gases, ozone, land-use changes, etc.). The video below shows a breakdown of these forcing factors in a state-of-the-art climate model simulation of the historical period."

# ╔═╡ 2368f16c-9805-4dd6-a130-d831211f6155
html"""

"""

# ╔═╡ 6800c94d-e7f3-43a8-a823-1c550cb8fc95
solp4 = solve(p4)

# ╔═╡ b38727a9-72ff-499f-9751-ddfeee21959c
begin
	T_url = "https://data.giss.nasa.gov/gistemp/graphs/graph_data/Global_Mean_Estimates_based_on_Land_and_Ocean_Data/graph.txt";
	T_df = CSV.read(download(T_url),DataFrame, header=false, datarow=6,delim="     ");
    #T_df = T_df[:,[1,6]]

end

# ╔═╡ Cell order:
# ╟─42085492-ac8c-11eb-0620-adcb307077f1
# ╟─c7d387fa-cd19-458c-a45d-7893e8c21bbf
# ╟─14195fc4-40e1-4576-973a-69d649fddc02
# ╟─92883ed9-5572-41fd-96c7-190279f90804
# ╟─340fc7c4-128f-4476-b445-65005ffa9f5a
# ╟─7673c7b7-0921-4a7e-ae4d-e785c6391a0c
# ╟─ebfeb0b0-d29e-4244-a0e3-a7185f3c8124
# ╟─4bf60f27-8db5-42c1-83d8-750bec2060e5
# ╟─e2799d05-36a7-4547-8663-90708ce01ce8
# ╟─4d58018b-752c-43f8-aafb-f11a95ca1874
# ╟─279b4748-829f-44a3-94da-da3abf32ff13
# ╠═578a80ad-fcf1-4d5c-81e5-205113bbf78d
# ╠═c1d87c42-e1e0-4e52-8023-6cc176266e86
# ╠═adb59adc-b30a-4dc4-bc3f-2804b7e02876
# ╠═220420c1-e8cc-4ff6-8b79-ba2ec49c7695
# ╟─7bee45a5-fa62-455b-813a-3e5dcf430289
# ╟─bda41881-75c7-4732-9a66-d7947607b1b6
# ╟─aba7fc93-c0ac-4c9e-b975-18145f87707f
# ╟─b9eca29e-9028-4fd7-8c62-718a2dcf87d1
# ╟─dabca25c-e34b-4aed-8729-132d03c5bb45
# ╟─d74936e9-b760-4add-b3e7-46d544064c16
# ╟─5123525a-3437-4b76-813c-8ad6b158f7f2
# ╠═2f19cbac-4f13-4c2b-9b11-fb92e8055527
# ╟─087f47b2-8283-4205-88f2-4d5883a340c2
# ╠═9c89c4e9-65ee-4424-bd74-17168b211797
# ╟─ca5b41d2-b511-486a-91fe-cceb8f7282c3
# ╠═13984c61-4c34-40db-9043-fcff2721522e
# ╟─868e19f4-d71e-4222-9bdd-470387991c67
# ╠═0e4dedc5-0b1e-4e46-b943-284bfb2de57f
# ╠═91329865-f593-4388-a5d0-01af4be6e01d
# ╠═7187ae25-239d-4752-898e-6674009b5de6
# ╟─ad1e294e-ad8a-48f9-b924-2474cce16aaf
# ╟─2086e386-194b-4863-a9ee-178002925d42
# ╟─2b66ffaf-a9a3-4ca2-a6e9-732912c9d48e
# ╟─b186d026-d02c-4f06-8097-aa54b363f4fb
# ╟─edafca88-dd4a-4fe4-8ffc-bb50f7bd561d
# ╠═9b97e41b-76ff-4cc2-90c1-446d59c4ece7
# ╟─af893e1e-3bd7-4d05-83b6-f5f7e751b5e3
# ╠═333290b0-0198-4306-b19b-6d30df79a280
# ╠═eeaf3735-5139-4924-9fce-14df51a61042
# ╟─20d6c513-2ca6-4dea-9092-156e2805d467
# ╟─692a6928-c7a2-4b61-a794-16bef5d4e919
# ╠═6d1058bf-8a05-4b8e-835a-de9e95f567c4
# ╠═35184e6f-ac3e-480b-92e9-ed8baaaa833b
# ╠═437faadd-0301-403a-bcd7-18ce279589d0
# ╠═1a4d21bd-85ad-4935-913a-8992a8996db4
# ╟─ee6414b7-e92d-4055-af17-6b02f05c28cd
# ╠═fac5012a-960c-473c-bbf0-62c0be87f608
# ╠═99629ec2-dc70-4253-b191-305bccc9f36b
# ╠═6b2beeec-6383-42b3-b694-8d77b961c8a1
# ╠═0b24f105-0166-4a41-97aa-156417d7203a
# ╟─c6f8dcf6-950d-48ff-b040-55bdd347d74b
# ╟─cd1dcbc4-2273-4eda-85d9-9af4fd71b3c1
# ╠═c3b0b7fc-19be-4f04-8787-6349ab9bff7f
# ╟─f5c2d0de-f5d2-43f2-bfca-41bd727b3ca9
# ╟─ef9d9512-cb40-4641-9d6a-5a36f5a4b33d
# ╠═2bc4c596-b313-49f6-84b5-5d53f2baf0e9
# ╟─648dfce3-5363-4539-8f41-f5d27bae4c6f
# ╟─288bfc6b-6848-40d5-916b-95a8a4248a4f
# ╠═a62ca8d1-321a-435e-a9df-f63d000376c7
# ╟─3304174c-289d-47c5-b5ef-161b11e515eb
# ╠═13269c86-06a4-4354-b620-bdf3f6432294
# ╟─28acb5a4-2a5f-49c5-9c78-deb40fdeed36
# ╟─13b003d2-1fd4-4a4a-960c-4a1d9b673dc6
# ╟─a2288816-3621-4871-9faf-3e9c78674969
# ╟─2b9c3427-c3ec-41ba-b3c5-268cf3dddccf
# ╟─fef0a76b-98dc-42a0-975b-0fba8a32e5cc
# ╟─2368f16c-9805-4dd6-a130-d831211f6155
# ╠═6800c94d-e7f3-43a8-a823-1c550cb8fc95
# ╠═b38727a9-72ff-499f-9751-ddfeee21959c


# ---
# title: 775. Global and Local Inversions
# id: problem775
# author: Indigo
# date: 2021-01-28
# difficulty: Medium
# categories: Array, Math
# link: <https://leetcode.com/problems/global-and-local-inversions/description/>
# hidden: true
# ---
#
# We have some permutation `A` of `[0, 1, ..., N - 1]`, where `N` is the length
# of `A`.
#
# The number of (global) inversions is the number of `i < j` with `0 <= i < j <
# N` and `A[i] > A[j]`.
#
# The number of local inversions is the number of `i` with `0 <= i < N` and
# `A[i] > A[i+1]`.
#
# Return `true` if and only if the number of global inversions is equal to the
# number of local inversions.
#
# **Example 1:**
#
#
#
#     Input: A = [1,0,2]
#     Output: true
#     Explanation: There is 1 global inversion, and 1 local inversion.
#
#
# **Example 2:**
#
#
#
#     Input: A = [1,2,0]
#     Output: false
#     Explanation: There are 2 global inversions, and 1 local inversion.
#
#
# **Note:**
#
#   * `A` will be a permutation of `[0, 1, ..., A.length - 1]`.
#   * `A` will have length in range `[1, 5000]`.
#   * The time limit for this problem has been reduced.
#
#
## @lc code=start
using LeetCode

function is_ideal_permutation(A::Vector{Int})
    m = A[end]
    for i in length(A):-1:3
        m = min(m, A[i])
        if A[i - 2] > m
            return false
        end
    end
    return true
end
## @lc code=end


using ReinforcementLearning, Base.Test

mdp = MDP(4, 6)
@test mdp.ns == 4
@test mdp.na == 6

states = getAllStates(mdp)

for i=1:4
	@test (State(i) in states) == true
end

for s in states
	for a in getActions(s, mdp)
		total = 0.0
		for (sprime, reward, prob) in getSuccessors(s, a, mdp)
			@test (prob >= 0.0 && prob <= 1.0)
			total += prob
		end
		@test abs(total - 1.0) <= 1e4
	end
end

ss = [State(1), State(2)]
as = [Action(1), Action(2), Action(3)]

graph = Dict{State, Dict{Action, Array{Tuple{State, Float64, Float64}}}}()
graph[ss[1]] = Dict{Action, Array{Tuple{State, Float64, Float64}}}()
graph[ss[2]] = Dict{Action, Array{Tuple{State, Float64, Float64}}}()
graph[ss[1]][as[1]] = [(ss[1], 5.0, 0.5), (ss[2], 5.0, 0.5)]
graph[ss[1]][as[2]] = [(ss[2], 10.0, 1.0)]
graph[ss[1]][as[3]] = [(ss[1], 0.0, 1.0)]
graph[ss[2]][as[1]] = [(ss[2], -1.0, 1.0)]

mdp = MDP(2, 3, graph)

@test mdp.ns == 2
@test mdp.na == 3

mapping = Dict{State, Array{Tuple{Action, Float64}}}()
mapping[ss[1]] = [(as[1], 1.0)]
mapping[ss[2]] = [(as[1], 1.0)]
policy = Policy(mapping)

V = iterative_policy_evaluation(mdp, policy, Ɣ=0.8; verbose=false)
@test abs(V[ss[1]] - 5) < 1e-4
@test abs(V[ss[2]] - -5) < 1e-4

policy.mapping[ss[1]] = [(as[2], 1.0)]

p, V = policy_iteration(mdp; Ɣ=0.8, verbose=false)
@test policy.mapping[ss[1]][1][1] == p.mapping[ss[1]][1][1]
@test policy.mapping[ss[2]][1][1] == p.mapping[ss[2]][1][1]

p, V = synchronous_value_iteration(mdp; Ɣ=0.8, verbose=false)
@test policy.mapping[ss[1]][1][1] == p.mapping[ss[1]][1][1]
@test policy.mapping[ss[2]][1][1] == p.mapping[ss[2]][1][1]

p, V = gauss_seidel_value_iteration(mdp; Ɣ=0.8, verbose=false)
@test policy.mapping[ss[1]][1][1] == p.mapping[ss[1]][1][1]
@test policy.mapping[ss[2]][1][1] == p.mapping[ss[2]][1][1]


using ModelingToolkit, StaticArrays, LinearAlgebra
using DiffEqBase
using Test

# Define some variables
@parameters t σ ρ β
@variables x(t) y(t) z(t)
@derivatives D'~t

eqs = [D(x) ~ σ*(y-x),
       D(y) ~ x*(ρ-z)-y,
       0 ~ x + y + β*z]

lorenz1 = ODESystem(eqs,t,[x,y,z],[σ,ρ,β],name=:lorenz1)
lorenz2 = ODESystem(eqs,t,[x,y,z],[σ,ρ,β],name=:lorenz2)

@parameters α
@variables a(t)
connnectedeqs = [D(a) ~ a*lorenz1.x]

connected1 = ODESystem(connnectedeqs,t,[a],[α],systems=[lorenz1,lorenz2],name=:connected1)

u0map = [lorenz1.x => 1.0,
         lorenz2.x => 2.0,
         lorenz1.y => 3.0,
         lorenz2.y => 4.0,
         lorenz1.z => 5.0,
         lorenz2.z => 6.0,
         a => 7.0]

parammap = SA[lorenz1.σ => 1.0,
              lorenz1.ρ => 2.0,
              lorenz1.β => 3.0,
              lorenz2.σ => 4.0,
              lorenz2.ρ => 5.0,
              lorenz2.β => 6.0,
              α => 7.0]

prob = ODEProblem(connected1,u0map,(0.0,100.0),parammap,jac=true)

@test prob.u0 == [7.0,1.0,3.0,5.0,2.0,4.0,6.0]
@test prob.p == [7.0,1.0,2.0,3.0,4.0,5.0,6.0]
@test prob.u0 isa Vector
@test prob.p isa SVector
@test prob.f.syms == [:a,:lorenz1₊x,:lorenz1₊y,:lorenz1₊z,:lorenz2₊x,:lorenz2₊y,:lorenz2₊z]

eqs_flat = [D(a) ~ a*lorenz1.x,
            D(lorenz1.x) ~ lorenz1.σ*(lorenz1.y-lorenz1.x),
            D(lorenz1.y) ~ lorenz1.x*(lorenz1.ρ-lorenz1.z)-lorenz1.y,
            0 ~ lorenz1.x + lorenz1.y + lorenz1.β*lorenz1.z,
            D(lorenz2.x) ~ lorenz2.σ*(lorenz2.y-lorenz2.x),
            D(lorenz2.y) ~ lorenz2.x*(lorenz2.ρ-lorenz2.z)-lorenz2.y,
            0 ~ lorenz2.x + lorenz2.y + lorenz2.β*lorenz2.z]

@test states(connected1) == convert.(Variable,[a,lorenz1.x,lorenz1.y,lorenz1.z,lorenz2.x,lorenz2.y,lorenz2.z])
@test parameters(connected1) == convert.(Variable,[α,lorenz1.σ,lorenz1.ρ,lorenz1.β,lorenz2.σ,lorenz2.ρ,lorenz2.β])
@test eqs_flat == equations(connected1)

@variables lorenz1₊x(t) lorenz1₊y(t) lorenz1₊z(t) lorenz2₊x(t) lorenz2₊y(t) lorenz2₊z(t)
@parameters lorenz1₊σ lorenz1₊ρ lorenz1₊β lorenz2₊σ lorenz2₊ρ lorenz2₊β

eqs_flat2 = [D(a) ~ a*lorenz1₊x,
            D(lorenz1₊x) ~ lorenz1₊σ*(lorenz1₊y-lorenz1₊x),
            D(lorenz1₊y) ~ lorenz1₊x*(lorenz1₊ρ-lorenz1₊z)-lorenz1₊y,
            0 ~ lorenz1₊x + lorenz1₊y + lorenz1₊β*lorenz1₊z,
            D(lorenz2₊x) ~ lorenz2₊σ*(lorenz2₊y-lorenz2₊x),
            D(lorenz2₊y) ~ lorenz2₊x*(lorenz2₊ρ-lorenz2₊z)-lorenz2₊y,
            0 ~ lorenz2₊x + lorenz2₊y + lorenz2₊β*lorenz2₊z]

@test [x.name for x in states(connected1)] == [:a,:lorenz1₊x,:lorenz1₊y,:lorenz1₊z,:lorenz2₊x,:lorenz2₊y,:lorenz2₊z]
@test [x.name for x in parameters(connected1)] == [:α,:lorenz1₊σ,:lorenz1₊ρ,:lorenz1₊β,:lorenz2₊σ,:lorenz2₊ρ,:lorenz2₊β]
@test eqs_flat == equations(connected1)

connected2 = ODESystem(connnectedeqs,t,[a],[α],systems=[lorenz1,lorenz2],name=:connected2)

@parameters γ
@variables g(t)
connnectedeqs2 = [D(g) ~ g*connected1.lorenz1.x]
doublelevel = ODESystem(connnectedeqs2,t,[g],[γ],systems=[connected1,connected2],name=:doublelevel)

@test states(doublelevel) == convert.(Variable,[g,connected1.a,connected1.lorenz1.x,connected1.lorenz1.y,connected1.lorenz1.z,connected1.lorenz2.x,connected1.lorenz2.y,connected1.lorenz2.z,
                                          connected2.a,connected2.lorenz1.x,connected2.lorenz1.y,connected2.lorenz1.z,connected2.lorenz2.x,connected2.lorenz2.y,connected2.lorenz2.z])

@test parameters(doublelevel) == convert.(Variable,[γ,
                                             connected1.α,connected1.lorenz1.σ,connected1.lorenz1.ρ,connected1.lorenz1.β,connected1.lorenz2.σ,connected1.lorenz2.ρ,connected1.lorenz2.β,
                                             connected2.α,connected2.lorenz1.σ,connected2.lorenz1.ρ,connected2.lorenz1.β,connected2.lorenz2.σ,connected2.lorenz2.ρ,connected2.lorenz2.β])

eqs_flat = [D(g) ~ g*connected1.lorenz1.x,
             D(connected1.a) ~ connected1.a*connected1.lorenz1.x,
             D(connected1.lorenz1.x) ~ connected1.lorenz1.σ*(connected1.lorenz1.y-connected1.lorenz1.x),
             D(connected1.lorenz1.y) ~ connected1.lorenz1.x*(connected1.lorenz1.ρ-connected1.lorenz1.z)-connected1.lorenz1.y,
             0 ~ connected1.lorenz1.x + connected1.lorenz1.y + connected1.lorenz1.β*connected1.lorenz1.z,
             D(connected1.lorenz2.x) ~ connected1.lorenz2.σ*(connected1.lorenz2.y-connected1.lorenz2.x),
             D(connected1.lorenz2.y) ~ connected1.lorenz2.x*(connected1.lorenz2.ρ-connected1.lorenz2.z)-connected1.lorenz2.y,
             0 ~ connected1.lorenz2.x + connected1.lorenz2.y + connected1.lorenz2.β*connected1.lorenz2.z,
             D(connected2.a) ~ connected2.a*connected2.lorenz1.x,
             D(connected2.lorenz1.x) ~ connected2.lorenz1.σ*(connected2.lorenz1.y-connected2.lorenz1.x),
             D(connected2.lorenz1.y) ~ connected2.lorenz1.x*(connected2.lorenz1.ρ-connected2.lorenz1.z)-connected2.lorenz1.y,
             0 ~ connected2.lorenz1.x + connected2.lorenz1.y + connected2.lorenz1.β*connected2.lorenz1.z,
             D(connected2.lorenz2.x) ~ connected2.lorenz2.σ*(connected2.lorenz2.y-connected2.lorenz2.x),
             D(connected2.lorenz2.y) ~ connected2.lorenz2.x*(connected2.lorenz2.ρ-connected2.lorenz2.z)-connected2.lorenz2.y,
             0 ~ connected2.lorenz2.x + connected2.lorenz2.y + connected2.lorenz2.β*connected2.lorenz2.z]

@test eqs_flat == equations(doublelevel)

@test [x.name for x in states(doublelevel)] == [:g,
                                                :connected1₊a,:connected1₊lorenz1₊x,:connected1₊lorenz1₊y,:connected1₊lorenz1₊z,:connected1₊lorenz2₊x,:connected1₊lorenz2₊y,:connected1₊lorenz2₊z,
                                                :connected2₊a,:connected2₊lorenz1₊x,:connected2₊lorenz1₊y,:connected2₊lorenz1₊z,:connected2₊lorenz2₊x,:connected2₊lorenz2₊y,:connected2₊lorenz2₊z]
@test [x.name for x in parameters(doublelevel)] == [:γ,
                                                   :connected1₊α,:connected1₊lorenz1₊σ,:connected1₊lorenz1₊ρ,:connected1₊lorenz1₊β,:connected1₊lorenz2₊σ,:connected1₊lorenz2₊ρ,:connected1₊lorenz2₊β,
                                                   :connected2₊α,:connected2₊lorenz1₊σ,:connected2₊lorenz1₊ρ,:connected2₊lorenz1₊β,:connected2₊lorenz2₊σ,:connected2₊lorenz2₊ρ,:connected2₊lorenz2₊β]

@variables connected1₊a(t) connected1₊lorenz1₊x(t) connected1₊lorenz1₊y(t) connected1₊lorenz1₊z(t) connected1₊lorenz2₊x(t) connected1₊lorenz2₊y(t) connected1₊lorenz2₊z(t)
@variables connected2₊a(t) connected2₊lorenz1₊x(t) connected2₊lorenz1₊y(t) connected2₊lorenz1₊z(t) connected2₊lorenz2₊x(t) connected2₊lorenz2₊y(t) connected2₊lorenz2₊z(t)
@parameters connected1₊α connected1₊lorenz1₊σ connected1₊lorenz1₊ρ connected1₊lorenz1₊β connected1₊lorenz2₊σ connected1₊lorenz2₊ρ connected1₊lorenz2₊β
@parameters connected2₊α connected2₊lorenz1₊σ connected2₊lorenz1₊ρ connected2₊lorenz1₊β connected2₊lorenz2₊σ connected2₊lorenz2₊ρ connected2₊lorenz2₊β

eqs_flat2 = [D(g) ~ g*connected1₊lorenz1₊x,
            D(connected1₊a) ~ connected1₊a*connected1₊lorenz1₊x,
            D(connected1₊lorenz1₊x) ~ connected1₊lorenz1₊σ*(connected1₊lorenz1₊y-connected1₊lorenz1₊x),
            D(connected1₊lorenz1₊y) ~ connected1₊lorenz1₊x*(connected1₊lorenz1₊ρ-connected1₊lorenz1₊z)-connected1₊lorenz1₊y,
            0 ~ connected1₊lorenz1₊x + connected1₊lorenz1₊y + connected1₊lorenz1₊β*connected1₊lorenz1₊z,
            D(connected1₊lorenz2₊x) ~ connected1₊lorenz2₊σ*(connected1₊lorenz2₊y-connected1₊lorenz2₊x),
            D(connected1₊lorenz2₊y) ~ connected1₊lorenz2₊x*(connected1₊lorenz2₊ρ-connected1₊lorenz2₊z)-connected1₊lorenz2₊y,
            0 ~ connected1₊lorenz2₊x + connected1₊lorenz2₊y + connected1₊lorenz2₊β*connected1₊lorenz2₊z,
            D(connected2₊a) ~ connected2₊a*connected2₊lorenz1₊x,
            D(connected2₊lorenz1₊x) ~ connected2₊lorenz1₊σ*(connected2₊lorenz1₊y-connected2₊lorenz1₊x),
            D(connected2₊lorenz1₊y) ~ connected2₊lorenz1₊x*(connected2₊lorenz1₊ρ-connected2₊lorenz1₊z)-connected2₊lorenz1₊y,
            0 ~ connected2₊lorenz1₊x + connected2₊lorenz1₊y + connected2₊lorenz1₊β*connected2₊lorenz1₊z,
            D(connected2₊lorenz2₊x) ~ connected2₊lorenz2₊σ*(connected2₊lorenz2₊y-connected2₊lorenz2₊x),
            D(connected2₊lorenz2₊y) ~ connected2₊lorenz2₊x*(connected2₊lorenz2₊ρ-connected2₊lorenz2₊z)-connected2₊lorenz2₊y,
            0 ~ connected2₊lorenz2₊x + connected2₊lorenz2₊y + connected2₊lorenz2₊β*connected2₊lorenz2₊z]

@test eqs_flat2 == equations(doublelevel)

M = Array(I,15,15)
M[5,5] = false
M[8,8] = false
M[12,12] = false
M[15,15] = false
@test calculate_massmatrix(doublelevel) == M

jac = Num[connected1₊lorenz1₊x 0 g zeros(1,12)
          zeros(7,1) calculate_jacobian(connected1) zeros(7,7)
          zeros(7,8) calculate_jacobian(connected2)]

jac2 = Num[connected1₊lorenz1₊x 0 g zeros(1,12)
           zeros(7,1) ModelingToolkit.namespace_operation.(calculate_jacobian(connected1),connected1.name,:t) zeros(7,7)
           zeros(7,8) ModelingToolkit.namespace_operation.(calculate_jacobian(connected2),connected2.name,:t)]

@test all(isequal.(calculate_jacobian(doublelevel),jac2))

# Try higher hierarchy

@parameters t σ ρ β
@variables x(t) y(t) z(t)
@derivatives D'~t

eqs = [D(x) ~ σ*(y-x),
       D(y) ~ x*(ρ-z)-y,
       D(z) ~ x*y - β*z]

lorenz1 = ODESystem(eqs,name=:lorenz1)
lorenz2 = ODESystem(eqs,name=:lorenz2)

@variables a(t)
@parameters γ
connections = [0 ~ lorenz1.x + lorenz2.y + a*γ]

# Now deviate from tutorial by introducing a 2nd level of connected blocks
# Other issue: How does scoping work? Is it ok to re-use components named lorenz1 and lorenz2, or will the hierarchy be flattened eventually
# connected1 and 2 are pairs of Lorenz oscillators. I'd like to solve multiple pairs together.
connected1 = ODESystem(connections,t,[a],[γ],systems=[lorenz1,lorenz2], name=:connected1)
connected2 = ODESystem(connections,t,[a],[γ],systems=[lorenz1,lorenz2], name=:connected2)

connections2ndLevel = Equation[] # Pairs are not connected
variables2ndLevel = [] # No extra variables
parameters2ndLevel = [] # No extra parameters

totalSystem = ODESystem(connections2ndLevel,t,variables2ndLevel, parameters2ndLevel, systems = [connected1, connected2],
                        name=:totalSystem)

connections2ndLevel = [0 ~ connected1.lorenz1.x + connected2.lorenz1.x]


function bounded_sum(x::Int64,y::Int64,bound::Int64)
	return x + y < bound ? x + y : bound
end

function bounded_diff(x::Int64,y::Int64,bound::Int64)
	return x - y > bound ? x - y : bound
end

function random_array_fixed_sum(sum_value::Int64,array_size::Int64)
	if sum_value == 0
		return [ 0 for i in 1:array_size ]
	end

	total = sum_value
	array = []
	for i in 1:array_size-1
		push!(array,rand(1:total - (array_size - i) ))
		total -= array[i]
	end
	push!(array,total)

	return array
end

function node_distance(p1::Tuple,p2::Tuple)
	return round(evaluate(Euclidean(),collect(p1),collect(p2)),digits=3)
end

function random_elements_from_array(S::Array,num::Int)
	if length(S) <= num
		return S
	end

	V = copy(S)
	chosed = []
	while length(chosed) < num
		e = rand(S)
		setdiff!(S,[e])
		push!(chosed,e)
	end
	return chosed
end


# ---
# title: 1014. Best Sightseeing Pair
# id: problem1014
# author: Tian Jun
# date: 2020-10-31
# difficulty: Medium
# categories: Array
# link: <https://leetcode.com/problems/best-sightseeing-pair/description/>
# hidden: true
# ---
#
# Given an array `A` of positive integers, `A[i]` represents the value of the
# `i`-th sightseeing spot, and two sightseeing spots `i` and `j` have distance
# `j - i` between them.
#
# The _score_  of a pair (`i < j`) of sightseeing spots is (`A[i] + A[j] + i -
# j)` : the sum of the values of the sightseeing spots, **minus** the distance
# between them.
#
# Return the maximum score of a pair of sightseeing spots.
#
#
#
# **Example 1:**
#
#
#
#     Input: [8,1,5,2,6]
#     Output: 11
#     Explanation: i = 0, j = 2, A[i] + A[j] + i - j = 8 + 5 + 0 - 2 = 11
#
#
#
#
# **Note:**
#
#   1. `2 <= A.length <= 50000`
#   2. `1 <= A[i] <= 1000`
#
#
## @lc code=start
using LeetCode

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


@printf("\nTesting BlockArrays\n")

using AbstractOperators.BlockArrays
using LinearAlgebra
using Printf

T = Float64
x = [2.0; 3.0]
xb = ([2.0; 3.0], Complex{T}[4.0; 5.0; 6.0], [1.0 2.0 3.0; 4.0 5.0 6.0])

x2 = randn(2)
x2b = (randn(2), randn(3)+im.*randn(3), randn(2,3))

@test blocksize(x) == (2,)
@test blocksize(xb) == ((2,),(3,),(2,3))

@test blockeltype(x) == T
@test blockeltype(xb) == (T, Complex{T},T)

@test blocklength(x) == 2
@test blocklength(xb) == 2+3+6

@test blockvecnorm(x) == norm(x)
@test blockvecnorm(xb) == sqrt(norm(xb[1])^2+norm(xb[2])^2+norm(xb[3])^2)

@test blockmaxabs(x) == 3.0
@test blockmaxabs(xb) == 6.0

@test typeof(blocksimilar(x)) == typeof(x)
@test typeof(blocksimilar(xb)) == typeof(xb)

@test blockcopy(x) == x
@test blockcopy(xb) == xb

y = blocksimilar(x)
yb = blocksimilar(xb)
blockcopy!(y,x)
blockcopy!(yb,xb)

@test y == x
@test yb == xb

y = blocksimilar(x)
yb = blocksimilar(xb)
blockset!(y,x)
blockset!(yb,xb)

@test y == x
@test yb == xb

z = blockvecdot(x,x2)
zb = blockvecdot(xb,x2b)
@test z == dot(x,x2)
@test zb == dot(xb[1],x2b[1])+dot(xb[2],x2b[2])+dot(xb[3],x2b[3])

y = blockzeros(x)
yb = blockzeros(xb)
@test y == zeros(2)
@test yb == (zeros(2),zeros(3)+im*zeros(3),zeros(2,3))

y = blockzeros(blocksize(x))
yb = blockzeros(blocksize(xb))
@test y == zeros(2)
@test yb == (zeros(2),zeros(3)+im*zeros(3),zeros(2,3))

y = blockzeros(blockeltype(x),blocksize(x))
yb = blockzeros(blockeltype(xb),blocksize(xb))
@test y == zeros(2)
@test yb == (zeros(2),zeros(3)+im*zeros(3),zeros(2,3))

y = blockones(x)
yb = blockones(xb)
@test y == ones(2)
@test yb == (ones(2),ones(3)+im*zeros(3),ones(2,3))

y = blockones(blocksize(x))
yb = blockones(blocksize(xb))
@test y == ones(2)
@test yb == (ones(2),ones(3)+im*zeros(3),ones(2,3))

y = blockones(blockeltype(x),blocksize(x))
yb = blockones(blockeltype(xb),blocksize(xb))
@test y == ones(2)
@test yb == (ones(2),ones(3)+im*zeros(3),ones(2,3))

blockscale!(y,2,x2)
blockscale!(yb,2,x2b)

@test y ==  2 .*x2
@test yb == (2 .*x2b[1], 2 .*x2b[2], 2 .*x2b[3])

blockcopy!(y,x)
blockcopy!(yb,xb)

blockcumscale!(y,2,x2)
blockcumscale!(yb,2,x2b)

@test y == x .+ 2 .*x2
@test yb == (xb[1] .+ 2 .*x2b[1], xb[2] .+  2 .*x2b[2], xb[3] .+  2 .*x2b[3])

blockaxpy!(y,x,2,x2)
blockaxpy!(yb,xb,2,x2b)

@test y == x .+ 2 .*x2
@test yb == (xb[1] .+ 2 .*x2b[1], xb[2] .+ 2 .*x2b[2], xb[3] .+ 2 .*x2b[3])

x = (ones(Float64, 5), zeros(Float64, 2, 3))
@test blockiszero(x) == false

y = (zeros(Complex{Float64}, 3, 2), ones(Complex{Float64}, 5))
@test blockiszero(x) == false

x = (zeros(Float64, 5), zeros(Complex{Float64}, 2, 3))
@test blockiszero(x) == true

y = (zeros(Float64, 2, 3), zeros(Complex{Float64}, 5))
@test blockiszero(x) == true


struct Rectangle
    x::Number
    y::Number
    w::Number
    h::Number
end

Base.:(==)(lhs::Rectangle, rhs::Rectangle) = lhs.x == rhs.x && lhs.y == rhs.y && lhs.w == rhs.w && lhs.h == rhs.h

# AABB check
function checkCollision(box1::Rectangle, box2::Rectangle)
    return (
        box1.x < box2.x + box2.w &&
        box1.x + box1.w > box2.x &&
        box1.y < box2.y + box2.h &&
        box1.y + box1.h > box2.y
    )
end

function intersection(box1::Rectangle, box2::Rectangle)
    x = max(box1.x, box2.x)
    y = max(box1.y, box2.y)
    w = min(box1.x + box1.w, box2.x + box2.w) - x
    h = min(box1.y + box1.h, box2.y + box2.h) - y
    return w > 0 && h > 0 ? Rectangle(x, y, w, h) : nothing
end

# Find top left corner and bottom right corner
function bounds(rects::Array{Rectangle, 1})
    tlx = tly = typemax(Int)
    brx = bry = typemin(Int)

    for rect in rects
        tlx = min(tlx, rect.x)
        tly = min(tly, rect.y)

        brx = max(brx, rect.x + rect.w)
        bry = max(bry, rect.y + rect.h)
    end

    return tlx, tly, brx, bry
end

# Rectangle that covers all input rectangles
function coverRectangles(rects::Array{Rectangle, 1})
    tlx, tly, brx, bry = bounds(rects)

    return Rectangle(tlx, tly, brx - tlx, bry - tly)
end