using FastTransforms, Test

@testset "Chebyshev transform"  begin
    @testset "Chebyshev points" begin
        @test @inferred(chebyshevpoints(10)) == @inferred(chebyshevpoints(Float64, 10))
        @test @inferred(chebyshevpoints(10, Val(2))) == @inferred(chebyshevpoints(Float64, 10, Val(2)))
        for T in (Float32, Float64, ComplexF32, ComplexF64)
            @test chebyshevpoints(T, 0) == T[]
            @test chebyshevpoints(T, 1) == T[0]

            n = 20
            @test @inferred(chebyshevpoints(T, n)) == [sinpi(convert(T,n-2k+1)/(2n)) for k=1:n]
            @test @inferred(chebyshevpoints(T, n, Val(2))) == [sinpi(convert(T,n-2k+1)/(2n-2)) for k=1:n]

            @test_throws MethodError chebyshevpoints(n, Val(-1))
            @test_throws ArgumentError chebyshevpoints(T, 0, Val(2))
            @test_throws ArgumentError chebyshevpoints(T, 1, Val(2))
        end
    end

    @testset "Chebyshev first kind points <-> first kind coefficients" begin
        for T in (Float32, Float64, ComplexF32, ComplexF64)
            n = 20
            p_1 = chebyshevpoints(T, n)
            f = exp.(p_1)
            f̌ = @inferred(chebyshevtransform(f))
            @test f̌ == chebyshevtransform!(copy(f))

            f̃ = x -> [cos(k*acos(x)) for k=0:n-1]' * f̌
            @test f̃(0.1) ≈ exp(T(0.1))
            @test @inferred(ichebyshevtransform(f̌)) ≈ ichebyshevtransform!(copy(f̌)) ≈ exp.(p_1)

            f̃ = copy(f)
            f̄ = copy(f̌)
            P = @inferred(plan_chebyshevtransform(f))
            @test @inferred(P*f) == f̌
            @test f == f̃
            @test_throws ArgumentError P * T[1,2]
            P = @inferred(plan_chebyshevtransform!(f))
            @test @inferred(P*f) == f̌
            @test f == f̌
            @test_throws ArgumentError P * T[1,2]
            Pi = @inferred(plan_ichebyshevtransform(f̌))
            @test @inferred(Pi*f̌) ≈ f̃
            @test f̌ == f̄
            @test_throws ArgumentError Pi * T[1,2]
            Pi = @inferred(plan_ichebyshevtransform!(f̌))
            @test @inferred(Pi*f̌) ≈ f̃
            @test f̌ ≈ f̃
            @test_throws ArgumentError Pi * T[1,2]

            @test chebyshevtransform(T[1]) == T[1]
            @test ichebyshevtransform(T[1]) == T[1]
            @test chebyshevtransform(T[]) == T[]
            @test ichebyshevtransform(T[]) == T[]
        end
    end
    @testset "Chebyshev second kind points <-> first kind coefficients" begin
        for T in (Float32, Float64, ComplexF32, ComplexF64)
            n = 20
            p_2 = chebyshevpoints(T, n, Val(2))
            f = exp.(p_2)
            f̌ = @inferred(chebyshevtransform(f, Val(2)))
            @test f̌ == chebyshevtransform!(copy(f), Val(2))

            f̃ = x -> [cos(k*acos(x)) for k=0:n-1]' * f̌
            @test f̃(0.1) ≈ exp(T(0.1))
            @test @inferred(ichebyshevtransform(f̌, Val(2))) ≈ ichebyshevtransform!(copy(f̌), Val(2)) ≈ exp.(p_2)

            P = @inferred(plan_chebyshevtransform!(f, Val(2)))
            Pi = @inferred(plan_ichebyshevtransform!(f, Val(2)))
            @test all(@inferred(P \ copy(f)) .=== Pi * copy(f))
            @test all(@inferred(Pi \ copy(f̌)) .=== P * copy(f̌))
            @test f ≈ P \ (P*copy(f)) ≈ P * (P\copy(f)) ≈ Pi \ (Pi*copy(f)) ≈ Pi * (Pi \ copy(f))

            f̃ = copy(f)
            f̄ = copy(f̌)
            P = @inferred(plan_chebyshevtransform(f, Val(2)))
            @test_throws ArgumentError P * T[1,2]
            @test P*f == f̌
            @test f == f̃
            P = @inferred(plan_chebyshevtransform!(f, Val(2)))
            @test P*f == f̌
            @test f == f̌
            @test_throws ArgumentError P * T[1,2]
            Pi = @inferred(plan_ichebyshevtransform(f̌, Val(2)))
            @test Pi*f̌ ≈ f̃
            @test f̌ == f̄
            @test_throws ArgumentError Pi * T[1,2]
            Pi = @inferred(plan_ichebyshevtransform!(f̌, Val(2)))
            @test Pi*f̌ ≈ f̃
            @test f̌ ≈ f̃
            @test_throws ArgumentError Pi * T[1,2]

            @test_throws ArgumentError chebyshevtransform(T[1], Val(2))
            @test_throws ArgumentError ichebyshevtransform(T[1], Val(2))
            @test_throws ArgumentError chebyshevtransform(T[], Val(2))
            @test_throws ArgumentError ichebyshevtransform(T[], Val(2))
        end
    end

    @testset "Chebyshev first kind points <-> second kind coefficients" begin
        for T in (Float32, Float64, ComplexF32, ComplexF64)
            n = 20
            p_1 = chebyshevpoints(T, n)
            f = exp.(p_1)
            f̌ = @inferred(chebyshevutransform(f))

            f̃ = x -> [sin((k+1)*acos(x))/sin(acos(x)) for k=0:n-1]' * f̌
            @test f̃(0.1) ≈ exp(T(0.1))
            @test ichebyshevutransform(f̌) ≈ exp.(p_1)

            f̃ = copy(f)
            f̄ = copy(f̌)
            P = @inferred(plan_chebyshevutransform(f))
            @test P*f == f̌
            @test f == f̃
            @test_throws ArgumentError P * T[1,2]
            P = @inferred(plan_chebyshevutransform!(f))
            @test P*f == f̌
            @test f == f̌
            @test_throws ArgumentError P * T[1,2]
            Pi = @inferred(plan_ichebyshevutransform(f̌))
            @test Pi*f̌ ≈ f̃
            @test f̌ == f̄
            @test_throws ArgumentError Pi * T[1,2]
            Pi = @inferred(plan_ichebyshevutransform!(f̌))
            @test Pi*f̌ ≈ f̃
            @test f̌ ≈ f̃
            @test_throws ArgumentError Pi * T[1,2]

            @test chebyshevutransform(T[1]) == T[1]
            @test ichebyshevutransform(T[1]) == T[1]
            @test chebyshevutransform(T[]) == T[]
            @test ichebyshevutransform(T[]) == T[]
        end
    end

    @testset "Chebyshev second kind points <-> second kind coefficients" begin
        for T in (Float32, Float64, ComplexF32, ComplexF64)
            n = 20
            p_2 = chebyshevpoints(T, n, Val(2))[2:end-1]
            f = exp.(p_2)
            f̌ = @inferred(chebyshevutransform(f, Val(2)))

            f̃ = x -> [sin((k+1)*acos(x))/sin(acos(x)) for k=0:n-3]' * f̌
            @test f̃(0.1) ≈ exp(T(0.1))
            @test @inferred(ichebyshevutransform(f̌, Val(2))) ≈ exp.(p_2)

            f̃ = copy(f)
            f̄ = copy(f̌)
            P = @inferred(plan_chebyshevutransform(f, Val(2)))
            @test @inferred(P*f) == f̌
            @test f == f̃
            @test_throws ArgumentError P * T[1,2]
            P = @inferred(plan_chebyshevutransform!(f, Val(2)))
            @test @inferred(P*f) == f̌
            @test f == f̌
            @test_throws ArgumentError P * T[1,2]
            Pi = @inferred(plan_ichebyshevutransform(f̌, Val(2)))
            @test @inferred(Pi*f̌) ≈ f̃
            @test f̌ == f̄
            @test_throws ArgumentError Pi * T[1,2]
            Pi = @inferred(plan_ichebyshevutransform!(f̌, Val(2)))
            @test @inferred(Pi*f̌) ≈ f̃
            @test f̌ ≈ f̃
            @test_throws ArgumentError Pi * T[1,2]

            @test_throws ArgumentError chebyshevutransform(T[1], Val(2))
            @test_throws ArgumentError ichebyshevutransform(T[1], Val(2))
            @test_throws ArgumentError chebyshevutransform(T[], Val(2))
            @test_throws ArgumentError ichebyshevutransform(T[], Val(2))
        end
    end

    @testset "matrix" begin
        X = randn(1,1)
        @test chebyshevtransform!(copy(X), Val(1)) == ichebyshevtransform!(copy(X), Val(1)) == X
        @test_throws ArgumentError chebyshevtransform!(copy(X), Val(2))
        @test_throws ArgumentError ichebyshevtransform!(copy(X), Val(2))

        X = randn(10,11)

        # manual 2D Chebyshev
        X̌ = copy(X)
        for j in axes(X̌,2)
            chebyshevtransform!(view(X̌,:,j))
        end
        for k in axes(X̌,1)
            chebyshevtransform!(view(X̌,k,:))
        end
        @test chebyshevtransform!(copy(X), Val(1)) ≈ X̌
        @test ichebyshevtransform!(copy(X̌), Val(1)) ≈ X

        # manual 2D Chebyshev
        X̌ = copy(X)
        for j in axes(X̌,2)
            chebyshevtransform!(view(X̌,:,j), Val(2))
        end
        for k in axes(X̌,1)
            chebyshevtransform!(view(X̌,k,:), Val(2))
        end
        @test chebyshevtransform!(copy(X), Val(2)) ≈ X̌
        @test ichebyshevtransform!(copy(X̌), Val(2)) ≈ X
    end

    @testset "Integer" begin
        @test chebyshevtransform([1,2,3]) == chebyshevtransform([1.,2,3])
        @test chebyshevtransform([1,2,3], Val(2)) == chebyshevtransform([1.,2,3], Val(2))
        @test ichebyshevtransform([1,2,3]) == ichebyshevtransform([1.,2,3])
        @test ichebyshevtransform([1,2,3], Val(2)) == ichebyshevtransform([1.,2,3], Val(2))

        @test chebyshevutransform([1,2,3]) == chebyshevutransform([1.,2,3])
        @test chebyshevutransform([1,2,3], Val(2)) == chebyshevutransform([1.,2,3], Val(2))
        @test ichebyshevutransform([1,2,3]) == ichebyshevutransform([1.,2,3])
        @test ichebyshevutransform([1,2,3], Val(2)) == ichebyshevutransform([1.,2,3], Val(2))
    end

    @testset "BigFloat" begin
        x = BigFloat[1,2,3]
        @test ichebyshevtransform(chebyshevtransform(x)) ≈ x
        @test plan_chebyshevtransform(x)x ≈ chebyshevtransform(x)
        @test plan_ichebyshevtransform(x)x ≈ ichebyshevtransform(x)
        @test plan_chebyshevtransform!(x)copy(x) ≈ chebyshevtransform(x)
        @test plan_ichebyshevtransform!(x)copy(x) ≈ ichebyshevtransform(x)
    end
end


module GalleryNLEVP
    # A module which handles interaction with the berlin
    # manchester NLEVP collection

    using MATLAB
    using NonlinearEigenproblems.NEPCore
    using NonlinearEigenproblems.NEPTypes
    using NonlinearEigenproblems.Gallery

    # We have to explicitly specify functions that we want "overload"
    import .NEPCore.compute_Mder
    import .NEPCore.size
    import .NEPCore.compute_Mlincomb

    export NLEVP_NEP

    import .Gallery.nep_gallery
    export nep_gallery

    function fetch_nlevp_path()
        # Try to find NLEVP
        nlevp_path=joinpath(ENV["HOME"],"src","nlevp"); # default ~/src/nlevp
        try
            nlevp_path=ENV["NLEVP_PATH"]
        catch
            # Environment variables was not set
        end

        return nlevp_path;

    end

    """
         NLEVP_NEP represents a NEP in the NLEVP-toolbox
    Example usage: nep=NLEVP_NEP("gun")
    """
    struct NLEVP_NEP <: NEP
        n::Integer
        name::String
        Ai::Array
        installation_path::String
        function NLEVP_NEP(name)
            nlevp_path=fetch_nlevp_path();
            return NLEVP_NEP(name,nlevp_path)
        end
        function NLEVP_NEP(name,nlevp_path)
            if (!isfile(joinpath(nlevp_path,"nlevp.m")))
                error("Unable to find NLEVP when looking in path=",nlevp_path,"  The directory containing  `nlevp.m` should be available in this path. Downloag from https://github.com/ftisseur/nlevp")
            end

            mat"""
            addpath($nlevp_path);
            [$Ai,funs] = nlevp($name);
        """

            this=new(size(Ai[1],1),name,Ai,nlevp_path);
        end
    end

    function compute_Mder(nep::NLEVP_NEP,λ::Number,i::Integer=0)
        lambda=Complex{Float64}(λ)  # avoid type conversion problems
        #println("type",typeof(lambda))
        ## The following commented code is calling nlevp("eval",...)
        ## directly and does not work. We use functions instead
        #        nep_name::String=nep.name
        #        @mput lambda nep_name
        #        if (i==0)
        #            println(λ)
        #            @matlab begin
        #                ll=1+0.1i
        #                M=nlevp("eval",nep_name,lambda
        #            @matlab end
        #            @mget M
        #            return M
        #        else
        #            @matlab begin
        #                (M,Mp)=nlevp("eval",nep_name,lambda)
        #            @matlab end
        #            @mget Mp
        #            return Mp
        #        end
        #    return f,fp
        D=call_current_fun(lambda,i)
        f=D[i+1,:]
        M=zero(nep.Ai[1]);
        for i=1:length(nep.Ai)
            M=M+nep.Ai[i]*f[i]
        end
        return M
    end

    compute_Mlincomb(nep::NLEVP_NEP,λ::Number,V::Union{AbstractMatrix,AbstractVector}, a::Vector) = compute_Mlincomb_from_Mder(nep,λ,V,a)
    compute_Mlincomb(nep::NLEVP_NEP,λ::Number,V::Union{AbstractMatrix,AbstractVector}) = compute_Mlincomb(nep,λ,V, ones(eltype(V),size(V,2)))

    # Return function values and derivatives of the current matlab session "funs"
    # stemming from a previous call to [Ai,funs]=nlevp(nepname).
    # The returned matrix containing derivatives has (maxder+1) rows
    function call_current_fun(lambda,maxder::Integer=0)
        l::ComplexF64=ComplexF64(lambda)  # avoid type problems
        mat"""
    C=cell($maxder+1,1);
    [C{:}]=funs($l);
    $D=cell2mat(C);
    """
        return D
    end

    # size for NLEVP_NEPs
    function size(nep::NLEVP_NEP)
        return (nep.n,nep.n)
    end
    function size(nep::NLEVP_NEP,dim)
        return nep.n
    end

    """
    nep_gallery(NLEVP_NEP, name)
    nep_gallery(NLEVP_NEP, name, nlevp_path)

Loads a NEP from the Berlin-Manchester collection of nonlinear
eigenvalue problems.
"""
    function nep_gallery(::Type{T},name::String) where {T<:NLEVP_NEP}
        nep=NLEVP_NEP(name)
        return nep
    end
    function nep_gallery(::Type{T},name::String,nlevp_path::String) where {T<:NLEVP_NEP}
        nep=NLEVP_NEP(name,nlevp_path)
        return nep
    end

end


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

using Markdown
using InteractiveUtils

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

# ╔═╡ e75a9a06-22e5-11eb-2e36-8d4f62b830ed
begin
	@quickactivate "StatisticsWithJuliaPlutoNotebooks"
	using RDatasets, StatsPlots
end

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

# ╔═╡ fa68607e-22e5-11eb-0558-c9a4d9f77426
iris = dataset("datasets", "iris")

# ╔═╡ 8894d350-2511-11eb-39d6-110ab1ec30db
@df iris andrewsplot(:Species, cols(1:4),
	line=(fill=[:blue :red :green]), legend=:topleft)

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

# ╔═╡ Cell order:
# ╟─ca5fbdc8-22e5-11eb-0a60-cb0a127a3ca5
# ╠═e077ab16-22e5-11eb-15ed-5f0654bf7328
# ╠═e75a9a06-22e5-11eb-2e36-8d4f62b830ed
# ╠═fa68607e-22e5-11eb-0558-c9a4d9f77426
# ╠═8894d350-2511-11eb-39d6-110ab1ec30db
# ╟─475ff888-22e6-11eb-2354-09f8f40a8e12


### A Pluto.jl notebook ###
# v0.11.14

using Markdown
using InteractiveUtils

# ╔═╡ 88cfc46c-f46d-11ea-21df-59c027e4b86b
begin
	import Pkg
	Pkg.activate(mktempdir())
	Pkg.add(["Images", "ImageIO", "ImageMagick", "ImageTransformations", "ImageFiltering"])
	using Images, LinearAlgebra, SparseArrays
	md"Libraries added"
end

# ╔═╡ 045fc73c-f5bb-11ea-210d-c3a88c7b5c08
md"# Reversing a convolution"

# ╔═╡ ce1dd8fc-f5bb-11ea-3cf4-4b20b1250bb1
md"## Functions"

# ╔═╡ f1cf67d8-f5bc-11ea-0015-192f0ebb1fdf
md"### Kernel"

# ╔═╡ f284c890-f5bb-11ea-30e8-abbc1f458cec
function Kernel_uniform(shape)
	value = 1/prod(shape)
	return fill(value, shape)
end

# ╔═╡ fcae7582-f5bb-11ea-2b79-43f8cd3178c3
function get_pad(kernel)
	return (size(kernel).-1).÷2
end

# ╔═╡ e6b903c2-f5bc-11ea-0c2c-03e099517d90
md"### Convolution and Reverse"

# ╔═╡ fb654aee-f5bc-11ea-35f4-3397194c8722
md"## Example"

# ╔═╡ 116fe702-f5bd-11ea-0ba9-c55263d026ba
md"### Importing image"

# ╔═╡ a3f814ac-f46c-11ea-33d7-81d4c435aba2
begin
	i = 2

	if i==1
		filename = "monkey"
		large_image = load("monkey.jpg")
		image = imresize(large_image; ratio=0.15)
	elseif i==2
		filename = "lena"
		large_image = load("lena.jpg")
		image = imresize(large_image; ratio=0.35)
	end

	#image = Gray.(image)
	#image = Gray.(rand(Float64, (5, 5)))
end

# ╔═╡ e731fd9c-f475-11ea-0123-b5df0503c9ba
size(image)

# ╔═╡ 5965dbc0-f4fa-11ea-3d29-536288bec4f6
typeof(image)

# ╔═╡ 1eb50ff0-f5bd-11ea-34a9-65ceeb74698a
md"### Initializing filter"

# ╔═╡ 820976ec-f476-11ea-0113-d909496fa398
begin
	#kernel = Kernel_uniform((3, 3))
	kernel = Kernel.gaussian((3, 3))

	pad = Pad(:replicate, get_pad(kernel))
	#pad = Fill(0, size(kernel))

	size(kernel)
end

# ╔═╡ 110e881e-f5bc-11ea-0a20-3dabe51098a3
function f2mat(img, f, pad=Pad(:replicate, get_pad(kernel)))
	input_shape = size(img)
	input_size = prod(input_shape)

	indices = reshape(1:input_size, input_shape)

	pad_size = get_pad(f)
	indices = padarray(indices, pad)

	indices = parent(indices)
	f = parent(f)

	output_shape = size(indices) .- size(f) .+ (1, 1)
	output_size = prod(output_shape)

	mat = sparse([], [], Float64[], input_size, output_size)

	for j=1:output_shape[2], i=1:output_shape[1]
		for n=1:size(f, 2), m=1:size(f, 1)
			input_idx = indices[i+m-1, j+n-1]
			if input_idx > 0
				output_idx = i + (j-1)*output_shape[1]
				mat[input_idx, output_idx] += f[m, n]
			end
		end
	end

	return mat'
end

# ╔═╡ 9966750a-f5bc-11ea-2402-f16ff519f56d
function convolution(image, kernel, pad=Pad(:replicate, get_pad(kernel)))
	matrix = f2mat(image, kernel, pad)
	mono = (length(size(channelview(image)))==2)

	if mono
		return reshape(matrix*vec(image), size(image))
	else
		channels = channelview(image)
		new_image = [reshape(matrix*vec(channels[i, :, :]), size(image)) for i=1:3]
		return colorview(RGB, new_image[1], new_image[2], new_image[3])
	end
end

# ╔═╡ a053dae2-f5bc-11ea-11fb-7115b2489efa
function get_original(image, kernel, pad=Pad(:replicate, get_pad(kernel)))
	matrix = f2mat(image, kernel, pad)

	mono = (length(size(channelview(image)))==2)

	if mono
		channel = channelview(image)
		estim = matrix\vec(channel)
		estim = [(x>1) ? 1 : (x<0) ? 0 : x for x in estim]	# image values in [0, 1]
		return Gray.(reshape(estim, size(image)))
	else
		channels = channelview(image)
		new_image = zeros(size(channels))
		for i=1:3
			estim = matrix\vec(channels[i, :, :])
			estim = [(x>1) ? 1 : (x<0) ? 0 : x for x in estim] # in [0, 1]
			new_image[i, :, :] = estim
		end
		return colorview(RGB, new_image)
	end
end

# ╔═╡ 3d477c82-f5bd-11ea-2571-f5cdb9fc1525
md"### Filtered image"

# ╔═╡ 9017eb36-f47d-11ea-0ae8-f3b3bb85bf53
filtered_pad = imfilter(image, kernel, pad, Algorithm.FIR())

# ╔═╡ d89f858a-f47d-11ea-20fc-ed418cc5653b
size(filtered_pad)

# ╔═╡ 3626ce80-f482-11ea-15f0-497b0ee9b3a4
#matrix = f2mat(image, kernel, "zero")

# ╔═╡ 30f93266-f4fa-11ea-35d5-b7bd56cb73e3
#convolution(image, kernel, "replicate")

# ╔═╡ 52ae79ae-f5bd-11ea-1d9b-a5963c9bcd16
md"### Reversing the convolution"

# ╔═╡ 4bd86730-f512-11ea-1c03-bfd6d008e37a
begin
	estimate = false

	if !estimate
		estimated_kernel = kernel
		estimated_pad = pad
	else
		estimated_kernel = Kernel.gaussian((3, 3))
		#estimated_kernel = Kernel_uniform((7, 7))
		#estimated_pad = Fill(0, get_pad(estimated_kernel))
		estimated_pad = Pad(:replicate, get_pad(estimated_kernel))
	end

	recovered = get_original(filtered_pad, estimated_kernel, estimated_pad)
end

# ╔═╡ 68b18282-f5bd-11ea-1801-933fb61400ab
md"#### Original | Filtered | Recovered"

# ╔═╡ 4eb55d58-f514-11ea-064c-2b8a08b3c1eb
[image filtered_pad recovered]

# ╔═╡ 18642c9c-f5bf-11ea-2d7f-0956bb770cc7
begin
	save(filename * "_original.jpg", image)
	save(filename * "_blurred.jpg", filtered_pad)
	save(filename * "_recovered.jpg", recovered)
	md"Files saved"
end

# ╔═╡ d6714f18-f60b-11ea-15b6-517d7006d9e4
md"old functions inside"
#=
begin
	function get_b(img, f, pad, mat, idx)
		pad_size = get_pad(f)

		if pad=="replicate"
			img_padded = padarray(img, Pad(:replicate,pad_size))
			img_padded[1:end-pad_size[1], 1:end-pad_size[2]] .= 0
		else
			return 0
		end

		mono = (length(size(channelview(img)))==2)

		if mono
			b = mat*vec(img)
			return reshape(b[idx], size(img))
		else
			channels = channelview(img)
			b = zeros(size(img))
			for i=1:3
				b_i = mat*vec(channels[i, :, :])
				b[i, :, :] = reshape(b_i[idx], size(img))
			end
			return colorview(RGB, b)
		end
	end

	function f2mat2(img, f, pad="zero")
		# https://stackoverflow.com/questions/16798888/2-d-convolution-as-a-matrix-matrix-multiplication

		pad_size = get_pad(f)
		mask = padarray(fill(true, size(img)), Fill(false, pad_size))

		input_shape = size(mask)
		output_shape = input_shape .- size(f) .+ (1, 1)
		input_size = prod(input_shape)
		output_size = prod(output_shape)

		mat = sparse([], [], Float64[], input_size, output_size)

		delta = input_shape[1]-size(f, 1)
		f_flat = sparse([], [], Float64[], input_shape[1], size(f, 2))

		f_flat[1:size(f, 1), :] = parent(f)
		f_flat = sparsevec(f_flat)[1:end-delta]

		delta = input_shape[1] - size(f, 1) + 1

		for i=1:size(mat, 2)÷delta, j=1:delta
			ii = (i-1)*input_shape[1]+j
			jj = (i-1)*delta+j
			mat[ii:ii+length(f_flat)-1, jj] = f_flat
		end

		mask_flat = vec(mask)
		#b = get_b(img, f, pad, mat, mask_flat)

		idx = [i for i=1:length(mask) if mask[i]]
		mat = mat[idx, :]

		return mat'
	end
end
=#

# ╔═╡ Cell order:
# ╟─045fc73c-f5bb-11ea-210d-c3a88c7b5c08
# ╟─ce1dd8fc-f5bb-11ea-3cf4-4b20b1250bb1
# ╟─88cfc46c-f46d-11ea-21df-59c027e4b86b
# ╟─f1cf67d8-f5bc-11ea-0015-192f0ebb1fdf
# ╟─f284c890-f5bb-11ea-30e8-abbc1f458cec
# ╟─fcae7582-f5bb-11ea-2b79-43f8cd3178c3
# ╟─e6b903c2-f5bc-11ea-0c2c-03e099517d90
# ╟─110e881e-f5bc-11ea-0a20-3dabe51098a3
# ╟─9966750a-f5bc-11ea-2402-f16ff519f56d
# ╟─a053dae2-f5bc-11ea-11fb-7115b2489efa
# ╟─fb654aee-f5bc-11ea-35f4-3397194c8722
# ╟─116fe702-f5bd-11ea-0ba9-c55263d026ba
# ╠═a3f814ac-f46c-11ea-33d7-81d4c435aba2
# ╟─e731fd9c-f475-11ea-0123-b5df0503c9ba
# ╟─5965dbc0-f4fa-11ea-3d29-536288bec4f6
# ╟─1eb50ff0-f5bd-11ea-34a9-65ceeb74698a
# ╠═820976ec-f476-11ea-0113-d909496fa398
# ╟─3d477c82-f5bd-11ea-2571-f5cdb9fc1525
# ╠═9017eb36-f47d-11ea-0ae8-f3b3bb85bf53
# ╟─d89f858a-f47d-11ea-20fc-ed418cc5653b
# ╠═3626ce80-f482-11ea-15f0-497b0ee9b3a4
# ╠═30f93266-f4fa-11ea-35d5-b7bd56cb73e3
# ╟─52ae79ae-f5bd-11ea-1d9b-a5963c9bcd16
# ╠═4bd86730-f512-11ea-1c03-bfd6d008e37a
# ╟─68b18282-f5bd-11ea-1801-933fb61400ab
# ╠═4eb55d58-f514-11ea-064c-2b8a08b3c1eb
# ╟─18642c9c-f5bf-11ea-2d7f-0956bb770cc7
# ╟─d6714f18-f60b-11ea-15b6-517d7006d9e4


# regression julia
cd("G:\\My Drive\\18. Github Repo\\DataScience");

# load libraries
using Plots, Statistics, StatsBase, DataFrames, XLSX, MLBase, RDatasets, GLMNet, DecisionTree, LIBSVM;

# Load the first data to play with
R = XLSX.readxlsx(".\\data\\zillow_data_download_april2020.xlsx");
sale_counts = R["Sale_counts_city"][:];
df_sale_counts = DataFrame(sale_counts[2:end, :], Symbol.(sale_counts[1, :]));
monthly_listings = R["MonthlyListings_City"][:];
df_monthly_listings = DataFrame(monthly_listings[2:end,:], Symbol.(monthly_listings[1,:]));

# get only the 2020-02 data
monthly_listings_2020_02 = df_monthly_listings[!,[1,2,3,4,5,end]];
rename!(monthly_listings_2020_02, Symbol("2020-02") .=> Symbol("listings"));
sale_counts_2020_02 = df_sale_counts[!,[1,end]];
rename!(sale_counts_2020_02, Symbol("2020-02") .=> Symbol("sales"));

# join two dataframes
feb2020 = innerjoin(monthly_listings_2020_02, sale_counts_2020_02, on=:RegionID);
dropmissing!(feb2020);

# different data
cats = dataset("MASS", "cats");

lmap = labelmap(cats[!,:Sex]);
ci = labelencode(lmap, cats[!,:Sex]);
scatter(cats[!,:BWt],cats[!,:HWt],color=ci,legend=false)


# This code calls the first-order eccentric TTV code
# compute_ttv.jl.  Please cite Agol & Deck (2015) if
# you make use of this in your research.

include("compute_ttv.jl")
#include("laplace_coefficients_initialize.jl")

function test_ttv(jmax::Integer,n1::Integer,n2::Integer,data::Vector{Float64}, WriteOutput::Bool = true, num_evals::Integer = 1, profile::Bool = false)
  @assert(jmax>=1)  # Should there be a larger minimum?
  @assert(n1>2)
  @assert(n2>2)
  @assert(length(data)==10)
  # Performs a test of the transit_times.jl routine
  # Set up planets planar-planet types for the inner and outer planets:
  p1=TTVFaster.Planet_plane_hk(data[1],data[2],data[3],data[4],data[ 5])
  p2=TTVFaster.Planet_plane_hk(data[6],data[7],data[8],data[9],data[10])
  time1 = collect(p1.trans0 .+ range(0,stop=n1-1,length=n1) .* p1.period)
  time2 = collect(p2.trans0 .+ range(0,stop=n2-1,length=n2) .* p2.period)
  alpha0=(p1.period/p2.period)^(2//3)
  # Initialize the computation of the Laplace coefficients:
  # b0=TTVFaster.LaplaceCoefficients.initialize(jmax+1,alpha0)
  # Define arrays to hold the TTVs:
  #ttv1=Array(Float64,n1)
  #ttv2=Array(Float64,n2)
  ttv_el_type = eltype(data) == Float64 ? Float64 : Number
  ttv1=zeros(ttv_el_type,n1)
  ttv2=zeros(ttv_el_type,n2)
  # Define arrays to hold the TTV coefficients and Laplace coefficients:
  f1=zeros(Float64,jmax+2,5)
  f2=zeros(Float64,jmax+2,5)
  b=zeros(Float64,jmax+2,3)
  hashsum = 0
  for i in 1:num_evals
   # Call the compute_ttv code which implements equation (33)
   TTVFaster.compute_ttv!(jmax,p1,p2,time1,time2,ttv1,ttv2)#,f1,f2,b,alpha0,b0)
    if profile
      hashsum += hash(ttv1)+hash(ttv2)
    end
  end
  if profile
    println("# Ignore this: ",hashsum) # This just makes sure optimizer doesn't optimize away important calculations.
  end

  if WriteOutput
    # Write the mean ephemeris and TTV results to two files:
    writedlm("inner_ttv.txt",[time1 ttv1])
    writedlm("outer_ttv.txt",[time2 ttv2])
  end
  return ttv1,ttv2
end


module LinearInterp
"""
linear interpolation without the hot air
I really need this function, and honestly I don't care that much
about its speed or flexibility. I just need sth that works for my
BL formulation
"""
struct Interp1D
    x::Vector{Real}
    y::Vector{Real}
    m::Vector{Real}
    b::Vector{Real}
end

"""
interp1D(x::Array{T, 1}, y::Array{T, 1}) where T<:Real
Linear interpollation function which returns a Interp1D struct
    ```
    x = collect(sort(rand(10)))
    y = sin.(10*x)
    xfine = collect(x[1]:0.01:x[end])
    intp1 = interp1D(x, y)
    yfine = intp1(xfine)
    ```
"""
function interp1D(x::Array{T, 1}, y::Array{T, 1}) where T<:Real
    m = (y[2:end]-y[1:end-1])./(x[2:end]-x[1:end-1])
    b = -m.*x[1:end-1]+y[1:end-1]
    return Interp1D(x, y, m, b)
end

function evaluate(intrp::Interp1D, xval::Real)
    if xval<intrp.x[1]
        return intrp.y[1]
    elseif xval>=intrp.x[end]
        return intrp.y[end]
    end
    for i in 1:length(intrp.x)-1
        if intrp.x[i]<=xval<intrp.x[i+1]
            return intrp.m[i]*xval+intrp.b[i]
        end
    end
end

function evaluate(intrp::Interp1D, xvec::Array{T, 1}) where T<:Real
    [evaluate(intrp, xvec[i]) for i in 1:length(xvec)]
end

(intrp1d::Interp1D)(x::Real) = evaluate(intrp1d, x)
(intrp1d::Interp1D)(x::Array{T, 1}) where T<:Real = evaluate(intrp1d, x)

end # module


using LinearElasticity

# 2D cantilever beam
nels = (60, 20)
sizes = (1.0, 1.0)

# 3D cantilever beam
#nels = (60, 20, 4)
#sizes = (1.0, 1.0, 1.0)

E = 1.0;
ν = 0.3;
force = 1.0;
problem = PointLoadCantilever(nels, sizes, E, ν, force);

# Build element stiffness matrices and force vectors
einfo = ElementFEAInfo(problem);
# Assemble global stiffness matrix and force vector
ginfo = assemble(problem, einfo);

# Solve for deformation
u = ginfo.K \ ginfo.f
# Visualize deformation using Makie
visualize(problem, u)


module CutCellDG

using LinearAlgebra
using SparseArrays
using NearestNeighbors
using CartesianMesh
using PolynomialBasis
using ImplicitDomainQuadrature
import ImplicitDomainQuadrature: extend

include("utilities.jl")
include("cell_map.jl")
include("cg_mesh.jl")
include("dg_mesh.jl")
include("periodic_bc.jl")
include("levelset.jl")
include("cut_mesh.jl")

include("cell_quadratures.jl")
include("face_quadratures.jl")
include("interface_quadratures.jl")
include("cell_merging.jl")

include("hooke_stiffness.jl")

include("weak_form.jl")
include("interface_conditions.jl")
include("transformation_strain.jl")

include("seed_zero_levelset.jl")
include("levelset_closest_points.jl")
# include("levelset_reinitialize.jl")
include("levelset_propagate.jl")

include("assembly.jl")
include("assemble_hermite_levelset_initializers.jl")
include("assemble_displacement_bilinear_forms.jl")
include("assemble_interelement_condition.jl")
include("assemble_interface_condition.jl")
include("assemble_penalty_displacement_bc.jl")
include("assemble_penalty_displacement_component_bc.jl")
include("assemble_body_force.jl")
include("assemble_traction_force_linear_form.jl")
include("assemble_traction_force_component_linear_form.jl")
include("assemble_transformation_strain.jl")
include("postprocess.jl")

end


# ---
# title: 1465. Maximum Area of a Piece of Cake After Horizontal and Vertical Cuts
# id: problem1465
# author: Tian Jun
# date: 2020-10-31
# difficulty: Medium
# categories: Array
# link: <https://leetcode.com/problems/maximum-area-of-a-piece-of-cake-after-horizontal-and-vertical-cuts/description/>
# hidden: true
# ---
#
# Given a rectangular cake with height `h` and width `w`, and two arrays of
# integers `horizontalCuts` and `verticalCuts` where `horizontalCuts[i]` is the
# distance from the top of the rectangular cake to the `ith` horizontal cut and
# similarly, `verticalCuts[j]` is the distance from the left of the rectangular
# cake to the `jth` vertical cut.
#
# _Return the maximum area of a piece of cake after you cut at each horizontal
# and vertical position provided in the arrays`horizontalCuts` and
# `verticalCuts`. _Since the answer can be a huge number, return this modulo
# 10^9 + 7.
#
#
#
# **Example 1:**
#
# ![](https://assets.leetcode.com/uploads/2020/05/14/leetcode_max_area_2.png)
#
#
#
#     Input: h = 5, w = 4, horizontalCuts = [1,2,4], verticalCuts = [1,3]
#     Output: 4
#     Explanation: The figure above represents the given rectangular cake. Red lines are the horizontal and vertical cuts. After you cut the cake, the green piece of cake has the maximum area.
#
#
# **Example 2:**
#
# **![](https://assets.leetcode.com/uploads/2020/05/14/leetcode_max_area_3.png)**
#
#
#
#     Input: h = 5, w = 4, horizontalCuts = [3,1], verticalCuts = [1]
#     Output: 6
#     Explanation: The figure above represents the given rectangular cake. Red lines are the horizontal and vertical cuts. After you cut the cake, the green and yellow pieces of cake have the maximum area.
#
#
# **Example 3:**
#
#
#
#     Input: h = 5, w = 4, horizontalCuts = [3], verticalCuts = [3]
#     Output: 9
#
#
#
#
# **Constraints:**
#
#   * `2 <= h, w <= 10^9`
#   * `1 <= horizontalCuts.length < min(h, 10^5)`
#   * `1 <= verticalCuts.length < min(w, 10^5)`
#   * `1 <= horizontalCuts[i] < h`
#   * `1 <= verticalCuts[i] < w`
#   * It is guaranteed that all elements in `horizontalCuts` are distinct.
#   * It is guaranteed that all elements in `verticalCuts` are distinct.
#
#
## @lc code=start
using LeetCode

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


using Pkg
Pkg.activate(pwd())

# # Bank account

abstract type Currency end

struct Euro <: Currency
    value::Float64
end

struct Dollar <: Currency
    value::Float64
end

#+

Euro(1)

isa(Dollar(2), Currency) # equivalent to typeof(Dollar(2)) <: Currency

#+

struct BankAccount{C<:Currency}
    owner::String
    transaction::Vector{Currency}

    function BankAccount(owner::String, C::Type{<:Currency})
        return new{C}(owner, Currency[C(0)])
    end
end

#+

b = BankAccount("Paul", Euro)
push!(b.transaction, Dollar(2))
b

#+

w = [Euro(0)]
push!(w, Dollar(2))

#+

[Int32(123), 1, 1.5, 1.234f0]
Real[Int32(123), 1, 1.5, 1.234f0]

# ## Custom print

symbol(T::Type{<:Currency}) = string(nameof(T))
symbol(::Type{Euro}) = "€"

Base.show(io::IO, c::C) where {C <: Currency} = print(io, c.value, " ", symbol(C))
Base.show(io::IO, c::Currency) = print(io, c.value, " ", symbol(typeof(c)))

#+

Euro(1)
Euro(1.5)

# ### Exercise:
# Define a new method for the `symbol` function for `Dollar`.
#
# **Hint:** the dollar symbol `$` has a special meaning in Julia. Do not forget to use the
# `\` symbol when using the dollar symbol in a string.
#
# ---
# ### Solution:

# ---
#
# ## Conversion

dollar2euro(c::Dollar) = Euro(0.83 * c.value)
euro2dollar(c::Euro) = Euro(c.value / 0.83)

#+

eur = dollar2euro(Dollar(1.3))
euro2dollar(eur)

#+

rate(::Type{Euro}, ::Type{Dollar}) = 0.83
rate(T::Type{<:Currency}, ::Type{Euro}) = 1 / rate(Euro, T)

#+

rate(Euro, Dollar)
rate(Dollar, Euro)

#+

rate(Euro, Euro)
rate(Dollar, Dollar)

#+

rate(::Type{T}, ::Type{T}) where {T<:Currency} = 1

#+

rate(Dollar, Dollar)
rate(Euro, Euro)

#+

methods(rate)

#+

rate(::Type{Euro}, ::Type{Euro}) = 1

#+

rate(Euro, Euro)

#+

rate(T::Type{<:Currency}, C::Type{<:Currency}) = rate(Euro, C) * rate(T, Euro)

#+

struct Pound <: Currency
    value::Float64
end

symbol(::Type{Pound}) = "£"
rate(::Type{Euro}, ::Type{Pound}) = 1.13

#+

rate(Pound, Pound) # 1
rate(Euro, Pound) # 1.13
rate(Pound, Euro) # 1/1.13 = 0.8849557522123894
rate(Dollar, Pound) # 1.13 * 1/0.83 = 1.36144578313253
rate(Pound, Dollar) # 0.83 * 1/1.13 = 0.7345132743362832

#+

Base.convert(::Type{T}, c::T) where {T<:Currency} = c
Base.convert(::Type{T}, c::C) where {T<:Currency, C<:Currency} = T(c.value * rate(T, C))

#+

eur = convert(Euro, Dollar(1.3))
pnd = convert(Pound, eur)
dlr = convert(Dollar, pnd)

# ### Exercise:
# We see that the print style of currencies is not ideal. Usually, we are not interested in
# more than the first two digits after the decimal point. Redefine the method in the `show`
# function to print currencies so that the result will be rounded to 2 digits after the
# decimal point.
#
# ---
# ### Solution:

# ---
#
# Promotion

Base.promote_rule(::Type{Euro}, ::Type{<:Currency}) = Euro
Base.promote_rule(::Type{Dollar}, ::Type{Pound}) = Dollar

#+

promote_type(Euro, Dollar)
promote_type(Pound, Dollar)
promote_type(Pound, Dollar, Euro)

#+

promote(Euro(2), Dollar(2.4))
promote(Pound(1.3), Euro(2))
promote(Pound(1.3), Dollar(2.4), Euro(2))

# ### Exercise:
# Define a new currency, `CzechCrown`, that will represent Czech crowns. The exchange rate
# to euro is `0.038`, and all other currencies should take precedence over the Czech crown.
#
# ---
# ### Solution:

# ---
#
# Basic arithmetic operations

Base.:+(x::Currency, y::Currency) = +(promote(x, y)...)
Base.:+(x::T, y::T) where {T <: Currency} = T(x.value + y.value)

#+

Dollar(1.3) + CzechCrown(4.5)
CzechCrown(4.5) + Euro(3.2) + Pound(3.6) + Dollar(12)

#+

sum([CzechCrown(4.5), Euro(3.2), Pound(3.6), Dollar(12)])

#+

CzechCrown.([4.5, 2.4, 16.7, 18.3]) .+ Pound.([1.2, 2.6, 0.6, 1.8])
CzechCrown.([4.5, 2.4, 16.7, 18.3]) .+ Dollar(12)

#+

Base.broadcastable(c::Currency) = Ref(c)

#+

c_ref = Ref(Euro(1))
axes(c_ref)
ndims(c_ref)
c_ref[]

#+

CzechCrown.([4.5, 2.4, 16.7, 18.3]) .+ Dollar(12)

# ### Exercise:
# In the section above, we defined the addition for all subtypes of the `Currency`. We also
# told the broadcasting system in Julia to treat all subtypes of the `Currency` as scalars.
# Follow the same pattern and define all following operations: `-`, `*`, `/`.
#
# **Hint:** Define only the operations that make sense. For example, It makes sense
# to multiply
# `1 €` by 2 and get `2 €`. But it does not make sense to multiply `1 €` by `2 €`.
#
# ---
# ### Solution:

# ---
#
# ## Currency comparison

Dollar(1) == Euro(0.83)
Dollar(1) != Euro(0.83)

#+

Base.:(==)(x::Currency, y::Currency) = ==(promote(x, y)...)
Base.:(==)(x::T, y::T) where {T <: Currency} = ==(x.value, y.value)

#+

Dollar(1) == Euro(0.83)
Dollar(1) != Euro(0.83)

#+

Base.isless(x::Currency, y::Currency) = isless(promote(x, y)...)
Base.isless(x::T, y::T) where {T <: Currency} = isless(x.value, y.value)

#+

Dollar(1) < Euro(0.83)
Dollar(1) > Euro(0.83)
Dollar(1) <= Euro(0.83)
Dollar(1) >= Euro(0.83)

#+

vals = Currency[CzechCrown(100), Euro(0.83),  Pound(3.6), Dollar(1.2)]

extrema(vals)
argmin(vals)
sort(vals)

# ## Back to bank account

balance(b::BankAccount{C}) where {C} = convert(C, sum(b.transaction))

#+

account = BankAccount("Paul", CzechCrown)
balance(account)

#+

function Base.show(io::IO, b::BankAccount{C}) where {C<:Currency}
    println(io, "Bank Account:")
    println(io, "  - Owner: ", b.owner)
    println(io, "  - Primary currency: ", nameof(C))
    println(io, "  - Balance: ", balance(b))
    print(io,   "  - Number of transactions: ", length(b.transaction))
end

#+

account

#+

function (b::BankAccount{T})(c::Currency) where {T}
    balance(b) + c >= T(0) || throw(ArgumentError("insufficient bank account balance."))
    push!(b.transaction, c)
    return
end

#+

account(Dollar(10))
account(-2*balance(b))
account(Pound(10))
account(Euro(23.6))
account(CzechCrown(152))

#+

account
account.transaction


### A Pluto.jl notebook ###
# v0.18.4

using Markdown
using InteractiveUtils

# ╔═╡ 8fc01a3c-b6d9-45ef-b06d-b8e768d0e01b
md"# 3월15일"

# ╔═╡ f225423e-925f-4480-80f3-ddc9a68ab854
html"""

"""

# ╔═╡ a39e054e-0447-4f3b-80c7-cab889e27964
md"## Usings"

# ╔═╡ 03a212a7-c80e-42d7-8ec2-8de166ffbd13
import PlutoUI

# ╔═╡ 2c3e71b0-23dc-445a-b270-69e16f69cb97
PlutoUI.TableOfContents(aside=true)

# ╔═╡ 5a5c3101-a0ee-4b7d-bb04-1ce2fb3d9e7a
md"##### 지난시간 숙제풀이"

# ╔═╡ 18221dae-f8bc-48c1-899b-be73d6e4910a
[2^i for i in 0:10]

# ╔═╡ 12839273-8a7e-48a1-9be8-26c6e8f2e3de
md"## matrix"

# ╔═╡ c5e2fb02-21d5-44e9-985e-75078d4c5bf5
md"### 선언"

# ╔═╡ d1d3c552-9b9e-4d45-8778-ce30d4b9dfda
md"`-` 선언하는 방법"

# ╔═╡ c7e638e0-11f6-4805-aa0e-913071056172
[1 2 3; 4 5 6] # ;는 거의 엔터처럼 생각하면 된다.

# ╔═╡ dc2f43b3-fb66-4f96-86d6-48ade6e3b605
[1 2 3
4 5 6] # 그냥 실제 엔터 쳐도 선언 가능

# ╔═╡ ce9b5739-6213-434e-8444-ceb4cabbef28
[
	1 2 3
	4 5 6
] # 보기좋게?

# ╔═╡ ff454362-f653-476f-870d-7b6b3e9ee7e1
md"`-` (1,4) matrix = lenght 4 row-vector 선언"

# ╔═╡ a84c236d-1156-4997-8b83-7813a26cfd72
[1 2 3 4]

# ╔═╡ 9ba2ce1b-2173-479b-989b-42e8f22d5700
[1 ;; 2 ;; 3 ;; 4] # 이것도 가능

# ╔═╡ 8cf64d00-2d6b-4dbc-a319-7ef69079faf0
md"`-` (4,1) marix = length 4 col-vector 선언"

# ╔═╡ 84ee925b-4489-484f-b6bd-71e1f1acfd1e
[1;2;3;4]

# ╔═╡ 8220ad15-ffd8-400e-a816-e6062e8ff883
[1,2,3,4] # 이게 사실은 length 4인 col-vector 로 여길수 있음..

# ╔═╡ 94f18da5-5a21-49e4-8691-f63142373281
[
	1
	2
	3
	4
] # 편해요..

# ╔═╡ 1b226aa6-c4b5-4a9d-a649-85907ae97903
md"`-` (4,2) matrix 선언과 잘못된 선언 "

# ╔═╡ ad6c5952-3e86-415b-a127-42238734a3a9
[1 2; 2 3 ; 3 4 ; 4 5]

# ╔═╡ 3114f121-4b51-4acd-8e30-409cb42fcd3b
[1 2, 2 3 , 3 4 , 4 5] # 잘못된 선언방식

# ╔═╡ 7fa90a53-b739-4e42-a5c1-76ee80cbe3e4
[
	1 2
	2 3
	3 4
	4 5
] # 이게 젤 편해요

# ╔═╡ ecc5e457-f400-4a07-bb34-6325a5830c65
md"### 연산"

# ╔═╡ 223c1e25-5339-4de4-92f6-303e909da489
md"`-` 곱셈"

# ╔═╡ cf6bfe27-2d26-4472-8789-0456693a3a11
[1 0 ; 0 1] * [1,2]

# ╔═╡ 2bc395b2-6186-4b20-9788-f52b939464d5
[1,2] * [1 0 ; 0 1]

# ╔═╡ 25327f5a-ca46-431e-aab2-71bb070720c6
md"- 에러메시지의 교훈: 아.. [1,2]는 진짜 col-vector이구나.."

# ╔═╡ af334ef5-d37b-4b05-8cc6-cbb585c9a888
[1 2] * [1 0 ; 0 1]

# ╔═╡ 156f33fc-2d64-4e8b-b931-5920a83bf97a
md"`-` 트랜스포즈"

# ╔═╡ de53accd-33b4-4ac9-b7ec-a6c95a3af761
md"(연산자사용)"

# ╔═╡ bb2c3099-7a48-45f6-9b3d-dba1ff0ac970
[1 2; 3 4]

# ╔═╡ 35660a52-e360-4538-b7fc-d06b6fcb1723
[1 2; 3 4]'

# ╔═╡ dd37cfac-a210-44a7-9984-11267dc2dedb
[1,2,3,4]'

# ╔═╡ 69835a56-c9c6-4561-b24a-4e9ce1919637
md"(복잡한방법)"

# ╔═╡ fd62d9d1-f82e-4c00-92fd-6e0c33e2a329
adjoint([1 2; 3 4])

# ╔═╡ 52e6d112-987c-41bd-962a-ebd711648ba5
md"""
### hcat, vcat
"""

# ╔═╡ 6823bb9e-e1d0-46cb-8b80-8e0c7f9bc723
md"""
`-` hcat: R에서의 cbind, 좌우로 붙인다

`-` hcat: R에서의 rbind, 위 아래로 붙인다
"""

# ╔═╡ c6fe9b45-aa6e-4f38-9118-bbf4567ce55f
hcat([1,2,3],[11,12,13])
# 3*1 col-vec (hcat) 3*1 col-vec => 3*2 matrix

# ╔═╡ 5d7aade6-c7eb-4979-af83-d85cc7dbc5ec
hcat([1,2,3],[11,12,13],[111,112,113])
# 3*1 col-vec (hcat) 3*1 col-vec (hcat) => 3*3 matrix

# ╔═╡ 4bc16860-dd17-4831-8365-83cb8073f9c4
hcat([1 11 ; 2 12 ; 3 13 ], [111, 222, 333])
# 3*2 matrix (hcat) 3*1 col-vec (hcat) => 3*3 matrix

# ╔═╡ 3b41c9cd-63f8-4491-abed-c933834718d6
hcat([1 11 ; 2 12 ; 3 13 ], [111 222 333])
# 3*2 matrix (hcat) 1*3 row-vec (hcat) => 불가능

# ╔═╡ ec4455f7-1926-47e7-b040-ab6f6a4f6f37
vcat([1 2 3 4], [11 22 33 44])
# 1*4 row-vec (vcat) 1*4 row-vec ==> 2*4 matrix

# ╔═╡ f58c8d79-486d-462c-b1c8-c42e45174a9d
vcat(hcat(vcat([1 2 3 4], [11 22 33 44]),[-1,-2]),[-1 -2 -3 -4 -5])
# 혼합하여사용가능

# ╔═╡ ed11ba08-eca6-4fe4-b954-c46d98e581e9
md"""
##### hcat, vcat의 더 쉬운 사용방법
"""

# ╔═╡ bc5ee879-28c0-4142-b24e-57e9001f1b05
[[1,2,3] [4,5,6]] # hcat(A,B) = [A B]

# ╔═╡ 02534d28-24c4-4124-80a3-87e0b6d05ff8
[[1 2 3; 4 5 6] ; [7 8 9]] # vcat(A,B) = [A; B]

# ╔═╡ 1db942d9-b61d-45e0-9db0-625806de8663
[
	[1 2 3; 4 5 6]
	[7 8 9]
]

# ╔═╡ 51d5d6ec-4788-443e-b4a1-0da210cbaf25
[
	[
		1 2 3
		4 5 6
	]

	[
		7 8 9
	]
]

# ╔═╡ bb94a5ad-ee1b-462b-b2d9-731a45603328
md"편하고 좋아요.."

# ╔═╡ a77dff93-bb99-4c93-b00f-8de0135edcc0
a1=[1 2
    3 4]

# ╔═╡ 4e7f10d6-d4f3-4dba-bd82-4e1e027cd132
a2=[5
    6]

# ╔═╡ 2daa7139-87b5-4e07-b5c1-ee7a9b61348f
a3=[7 8]

# ╔═╡ a2b53145-323a-40f9-b138-45f3b0a5854e
[
	a1 a2
	a3 9
]

# ╔═╡ f0ca14d2-0b13-4fad-93fc-d5806957cd40
[
	a1 a3
	a2 9
] # 디멘젼이 맞지 않으므로 에러가 발생

# ╔═╡ d399a7b2-4ac5-4f2e-8039-6ffb0b09d6e6
md"### reshape"

# ╔═╡ e8dfcb15-a94d-49e6-96c7-033c649edfdb
reshape(1:6, (2,3))

# ╔═╡ b07575ab-67ed-4fcb-9574-a0e2e29bde09
reshape(1:6, (3,2))

# ╔═╡ 4e77e610-ab8a-4b08-99cc-8e5ca15522c0
reshape(1:6, (6,1))

# ╔═╡ e5a82661-28f9-42c1-8763-a633dd89c2db
reshape(1:6, (1,6))

# ╔═╡ 213fdb89-cf30-44d2-a3b2-a6da706decc3
reshape(1:8, (2,2,2))

# ╔═╡ 86a67dfe-da9c-4ed6-980e-131b8892065d
reshape(1:24, (2,2,2,3))

# ╔═╡ 24e90ab1-6bc5-44c7-a659-3a2ea3494177
md"## 벡터와 매트릭스 (자투리)"

# ╔═╡ b8373e03-4558-46f5-8f57-90f3b52d0193
md"### range"

# ╔═╡ aaa05575-1873-45f0-acf6-b18c5b3f5c29
1:3

# ╔═╡ 861b5ade-2ed9-4b28-a8ae-50d0c5e4705a
typeof(1:3)

# ╔═╡ da842d51-53b6-4ec6-875e-f6c8e7b7681b
Array(1:3)

# ╔═╡ 4ba39004-4c1e-4f99-878f-2ed95b4e9e52
typeof(Array(1:3))

# ╔═╡ 484c5ed1-65c3-42b2-9962-9916320c1856
Array(1:3:20)

# ╔═╡ 34fe6d7c-e440-453d-9d68-d11b706a8d4b
md"`-` range도 연산이 가능함"

# ╔═╡ 0afe44aa-d5e9-4f85-9ed2-27c73fd24a9a
(1:3) + (1:3)

# ╔═╡ 7cb2f34d-8362-418a-becd-3f754ce43ec7
Array((1:3) + (1:3))

# ╔═╡ 99b09469-a7f9-47ce-a21b-b82574db5cb7
(1:3) .* 2 .+ 100

# ╔═╡ ae011f7f-7ec2-43eb-a213-9dce06dcca29
Array((1:3) .* 2 .+ 100)

# ╔═╡ 25022f43-7561-4d74-948b-a149bb629fe2
md"### indexing"

# ╔═╡ 28899b48-3b43-44a2-b18d-d7727d44ef7c
md"`-` 1차원으로 인덱싱"

# ╔═╡ 47335258-4e82-4a3f-b85b-2ba48f582269
A= [1 2 3
	4 5 6
	7 8 9]

# ╔═╡ 7805cdde-7bb2-44ea-806c-9fb5ab39075f
A[1:4]

# ╔═╡ 53d142ac-3d20-4d24-b01a-77f9c0f2cc77
A[[1,3,5,7]]

# ╔═╡ 76f991d0-5e8f-431f-82ba-d592ef135ecf
md"`-` 2차원으로 인덱싱"

# ╔═╡ fe76a4cd-c685-460b-96e2-34c91fecc990
A[:,1] # 첫번째 col

# ╔═╡ 453a23b7-e99d-40b1-a94e-37eb4e870e09
A[:,[1]] # 첫번째 col /// 좀더 명시적으로!

# ╔═╡ 082932b1-899d-4f9d-a14f-87a75c7c05ba
A[1,:] # 첫번째 row

# ╔═╡ ff2f6191-e7bd-4dc5-8947-e4dc3166313b
A[[1],:] # 첫번째 row /// 좀더 명시적으로!

# ╔═╡ 752e9d30-43f6-4890-9a9d-b982f61a533d
A[[1,3],:] # 첫번째 row, 세번째 row

# ╔═╡ 7a30be54-59a8-48dc-a835-b833b1c58049
A[[1,3],2] # (첫번째 row, 세번째 row) ==> 두번째 칼럼

# ╔═╡ 5a5cdbbd-8dbc-4a32-b0f4-f6cce879412d
A[[1,3],[2]] # (첫번째 row, 세번째 row) ==> 두번째 칼럼 /// 좀더 명시적으로!

# ╔═╡ 03895c3f-2353-42a1-8f2a-e28e29574c34
md"`-` 고차원"

# ╔═╡ 45c44acf-319a-4601-8be3-ec84dea11ef8
B=reshape(1:36,(3,3,2,2))

# ╔═╡ a0f37987-7d7c-4867-8dc5-97343ae3cd63
B[1:2, 2:3, 1, :]

# ╔═╡ 014e1f0c-5c23-4104-851d-d6112f2cc9f2
B[1:2, [1,3], :, 1]

# ╔═╡ 96108638-d8a0-41ea-9332-8559c6917db5
md"`-` 로지칼인덱싱"

# ╔═╡ 5daf498c-4ab3-47ea-b15a-c88fc8d1e014
a=[1,2,3,4]

# ╔═╡ 806e5f53-e17a-4e01-a9a7-1ff79839677c
a[[true,false,true,false]]

# ╔═╡ f93db673-a907-4fe9-a54b-0c505a1c99d9
a[a.>2]

# ╔═╡ e27a7070-9a40-46b7-9054-3f4424ab841a
md"## 함수선언"

# ╔═╡ 384c4e1f-e557-4437-8122-3ea468d60961
md"### 여러가지 방식"

# ╔═╡ a67f2187-6028-4431-a2e9-71327bc5efa1
md"""
##### 스타일 1
"""

# ╔═╡ 441e0f33-fe0a-4eca-8d68-232b8a6f29d2
function f1(x,y)
	return √(x^2+y^2) # return 명시
end

# ╔═╡ 0460f841-982a-46f5-99af-955c14991075
f1(1,1)

# ╔═╡ eb67bdd5-37ff-41a1-b5c4-736fd72a61e5
md"##### 스타일2"

# ╔═╡ 2fd8d9f4-e9b4-4d88-86e7-03ecaddfd692
function f2(x,y)
	a= x^2 + y^2
	√a # return 생략
end

# ╔═╡ fce1fbc1-5caa-4a57-8a31-1b4ba007f104
f2(1,1)

# ╔═╡ 44bba248-9a02-4ea6-b9cd-41abe978e5a9
md"##### 스타일3"

# ╔═╡ 33934ed6-14d4-45d1-bc49-ff24db2ced80
f3(x,y) = √(x^2+y^2)

# ╔═╡ 23df5e92-30bd-4bad-8242-7ccb56b3c064
f3(1,1)

# ╔═╡ 4665d68f-fba8-47b3-a978-034d8e9a6c2d
md"##### 스타일4"

# ╔═╡ 57bbe8e9-280f-4d75-8f08-6559599b8188
f4 = (x,y) -> √(x^2+y^2)

# ╔═╡ e1dfd750-39b3-4e82-960f-53fe45d25fab
f4(1,1)

# ╔═╡ 5bbee205-fe9b-4477-9bc8-14d2f6540cf5
md"##### 스타일5"

# ╔═╡ 762d650b-7a7f-47d1-a571-231272e85f5e
((x,y) -> √(x^2+y^2))(1,1)

# ╔═╡ d432ab84-069e-462d-9c7f-10ad8020a827
md"### 합성함수"

# ╔═╡ 6fbe924e-feda-4a64-b752-4d752fa754e0
f= (x -> 3x) ∘ (x -> x+1) # f(x)= 3(x+1) 란 의미

# ╔═╡ 2c3ebb43-4835-4647-8907-0bfe6ba940ef
f(-5)

# ╔═╡ 2b081a93-3e67-4027-a9c2-99691ce15130
md"## 숙제"

# ╔═╡ a1732e88-bdc7-477a-89e1-d099556cdf49
md"함수 $f(x)=(x-1)^2$를 함성함수를 이용하여 선언하고 $(x,f(x))$의 그래프를 $x \in (-1,3)$의 범위에서 그린뒤에 제출 (단, x는 -1에서 시작하여 3으로 0.05간격의 벡터를 이용하여 생성한뒤에 그릴것)"

# ╔═╡ f434a8cc-e9e0-471f-b821-860b4aea670e
md"hints"

# ╔═╡ 57aa3e4d-63b9-4214-a7fe-351e5dde237c
Array(-1:0.05:3)

# ╔═╡ b63d3749-fae4-4fc6-b0a5-08fe150b174d
g(x) = x+1

# ╔═╡ f98034cc-f76a-4bc8-8655-200081eb4fca
[g(x) for x in 1:5]

# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8"

[compat]
PlutoUI = "~0.7.37"
"""

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

julia_version = "1.7.2"
manifest_format = "2.0"

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

[[deps.Parsers]]
deps = ["Dates"]
git-tree-sha1 = "85b5da0fa43588c75bb1ff986493443f821c70b7"
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
version = "2.2.3"

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

# ╔═╡ Cell order:
# ╟─8fc01a3c-b6d9-45ef-b06d-b8e768d0e01b
# ╟─f225423e-925f-4480-80f3-ddc9a68ab854
# ╟─a39e054e-0447-4f3b-80c7-cab889e27964
# ╠═03a212a7-c80e-42d7-8ec2-8de166ffbd13
# ╠═2c3e71b0-23dc-445a-b270-69e16f69cb97
# ╟─5a5c3101-a0ee-4b7d-bb04-1ce2fb3d9e7a
# ╠═18221dae-f8bc-48c1-899b-be73d6e4910a
# ╟─12839273-8a7e-48a1-9be8-26c6e8f2e3de
# ╟─c5e2fb02-21d5-44e9-985e-75078d4c5bf5
# ╟─d1d3c552-9b9e-4d45-8778-ce30d4b9dfda
# ╠═c7e638e0-11f6-4805-aa0e-913071056172
# ╠═dc2f43b3-fb66-4f96-86d6-48ade6e3b605
# ╠═ce9b5739-6213-434e-8444-ceb4cabbef28
# ╟─ff454362-f653-476f-870d-7b6b3e9ee7e1
# ╠═a84c236d-1156-4997-8b83-7813a26cfd72
# ╠═9ba2ce1b-2173-479b-989b-42e8f22d5700
# ╟─8cf64d00-2d6b-4dbc-a319-7ef69079faf0
# ╠═84ee925b-4489-484f-b6bd-71e1f1acfd1e
# ╠═8220ad15-ffd8-400e-a816-e6062e8ff883
# ╠═94f18da5-5a21-49e4-8691-f63142373281
# ╟─1b226aa6-c4b5-4a9d-a649-85907ae97903
# ╠═ad6c5952-3e86-415b-a127-42238734a3a9
# ╠═3114f121-4b51-4acd-8e30-409cb42fcd3b
# ╠═7fa90a53-b739-4e42-a5c1-76ee80cbe3e4
# ╟─ecc5e457-f400-4a07-bb34-6325a5830c65
# ╟─223c1e25-5339-4de4-92f6-303e909da489
# ╠═cf6bfe27-2d26-4472-8789-0456693a3a11
# ╠═2bc395b2-6186-4b20-9788-f52b939464d5
# ╟─25327f5a-ca46-431e-aab2-71bb070720c6
# ╟─af334ef5-d37b-4b05-8cc6-cbb585c9a888
# ╟─156f33fc-2d64-4e8b-b931-5920a83bf97a
# ╟─de53accd-33b4-4ac9-b7ec-a6c95a3af761
# ╠═bb2c3099-7a48-45f6-9b3d-dba1ff0ac970
# ╠═35660a52-e360-4538-b7fc-d06b6fcb1723
# ╠═dd37cfac-a210-44a7-9984-11267dc2dedb
# ╟─69835a56-c9c6-4561-b24a-4e9ce1919637
# ╠═fd62d9d1-f82e-4c00-92fd-6e0c33e2a329
# ╟─52e6d112-987c-41bd-962a-ebd711648ba5
# ╟─6823bb9e-e1d0-46cb-8b80-8e0c7f9bc723
# ╠═c6fe9b45-aa6e-4f38-9118-bbf4567ce55f
# ╠═5d7aade6-c7eb-4979-af83-d85cc7dbc5ec
# ╠═4bc16860-dd17-4831-8365-83cb8073f9c4
# ╠═3b41c9cd-63f8-4491-abed-c933834718d6
# ╠═ec4455f7-1926-47e7-b040-ab6f6a4f6f37
# ╠═f58c8d79-486d-462c-b1c8-c42e45174a9d
# ╟─ed11ba08-eca6-4fe4-b954-c46d98e581e9
# ╠═bc5ee879-28c0-4142-b24e-57e9001f1b05
# ╠═02534d28-24c4-4124-80a3-87e0b6d05ff8
# ╠═1db942d9-b61d-45e0-9db0-625806de8663
# ╠═51d5d6ec-4788-443e-b4a1-0da210cbaf25
# ╟─bb94a5ad-ee1b-462b-b2d9-731a45603328
# ╠═a77dff93-bb99-4c93-b00f-8de0135edcc0
# ╠═4e7f10d6-d4f3-4dba-bd82-4e1e027cd132
# ╠═2daa7139-87b5-4e07-b5c1-ee7a9b61348f
# ╠═a2b53145-323a-40f9-b138-45f3b0a5854e
# ╠═f0ca14d2-0b13-4fad-93fc-d5806957cd40
# ╟─d399a7b2-4ac5-4f2e-8039-6ffb0b09d6e6
# ╠═e8dfcb15-a94d-49e6-96c7-033c649edfdb
# ╠═b07575ab-67ed-4fcb-9574-a0e2e29bde09
# ╠═4e77e610-ab8a-4b08-99cc-8e5ca15522c0
# ╠═e5a82661-28f9-42c1-8763-a633dd89c2db
# ╠═213fdb89-cf30-44d2-a3b2-a6da706decc3
# ╠═86a67dfe-da9c-4ed6-980e-131b8892065d
# ╟─24e90ab1-6bc5-44c7-a659-3a2ea3494177
# ╟─b8373e03-4558-46f5-8f57-90f3b52d0193
# ╠═aaa05575-1873-45f0-acf6-b18c5b3f5c29
# ╠═861b5ade-2ed9-4b28-a8ae-50d0c5e4705a
# ╠═da842d51-53b6-4ec6-875e-f6c8e7b7681b
# ╠═4ba39004-4c1e-4f99-878f-2ed95b4e9e52
# ╠═484c5ed1-65c3-42b2-9962-9916320c1856
# ╟─34fe6d7c-e440-453d-9d68-d11b706a8d4b
# ╠═0afe44aa-d5e9-4f85-9ed2-27c73fd24a9a
# ╠═7cb2f34d-8362-418a-becd-3f754ce43ec7
# ╠═99b09469-a7f9-47ce-a21b-b82574db5cb7
# ╠═ae011f7f-7ec2-43eb-a213-9dce06dcca29
# ╟─25022f43-7561-4d74-948b-a149bb629fe2
# ╟─28899b48-3b43-44a2-b18d-d7727d44ef7c
# ╠═47335258-4e82-4a3f-b85b-2ba48f582269
# ╠═7805cdde-7bb2-44ea-806c-9fb5ab39075f
# ╠═53d142ac-3d20-4d24-b01a-77f9c0f2cc77
# ╟─76f991d0-5e8f-431f-82ba-d592ef135ecf
# ╠═fe76a4cd-c685-460b-96e2-34c91fecc990
# ╠═453a23b7-e99d-40b1-a94e-37eb4e870e09
# ╠═082932b1-899d-4f9d-a14f-87a75c7c05ba
# ╠═ff2f6191-e7bd-4dc5-8947-e4dc3166313b
# ╠═752e9d30-43f6-4890-9a9d-b982f61a533d
# ╠═7a30be54-59a8-48dc-a835-b833b1c58049
# ╠═5a5cdbbd-8dbc-4a32-b0f4-f6cce879412d
# ╟─03895c3f-2353-42a1-8f2a-e28e29574c34
# ╠═45c44acf-319a-4601-8be3-ec84dea11ef8
# ╠═a0f37987-7d7c-4867-8dc5-97343ae3cd63
# ╠═014e1f0c-5c23-4104-851d-d6112f2cc9f2
# ╟─96108638-d8a0-41ea-9332-8559c6917db5
# ╠═5daf498c-4ab3-47ea-b15a-c88fc8d1e014
# ╠═806e5f53-e17a-4e01-a9a7-1ff79839677c
# ╠═f93db673-a907-4fe9-a54b-0c505a1c99d9
# ╟─e27a7070-9a40-46b7-9054-3f4424ab841a
# ╟─384c4e1f-e557-4437-8122-3ea468d60961
# ╟─a67f2187-6028-4431-a2e9-71327bc5efa1
# ╠═441e0f33-fe0a-4eca-8d68-232b8a6f29d2
# ╠═0460f841-982a-46f5-99af-955c14991075
# ╟─eb67bdd5-37ff-41a1-b5c4-736fd72a61e5
# ╠═2fd8d9f4-e9b4-4d88-86e7-03ecaddfd692
# ╠═fce1fbc1-5caa-4a57-8a31-1b4ba007f104
# ╟─44bba248-9a02-4ea6-b9cd-41abe978e5a9
# ╠═33934ed6-14d4-45d1-bc49-ff24db2ced80
# ╠═23df5e92-30bd-4bad-8242-7ccb56b3c064
# ╟─4665d68f-fba8-47b3-a978-034d8e9a6c2d
# ╠═57bbe8e9-280f-4d75-8f08-6559599b8188
# ╠═e1dfd750-39b3-4e82-960f-53fe45d25fab
# ╟─5bbee205-fe9b-4477-9bc8-14d2f6540cf5
# ╠═762d650b-7a7f-47d1-a571-231272e85f5e
# ╟─d432ab84-069e-462d-9c7f-10ad8020a827
# ╠═6fbe924e-feda-4a64-b752-4d752fa754e0
# ╠═2c3ebb43-4835-4647-8907-0bfe6ba940ef
# ╟─2b081a93-3e67-4027-a9c2-99691ce15130
# ╟─a1732e88-bdc7-477a-89e1-d099556cdf49
# ╟─f434a8cc-e9e0-471f-b821-860b4aea670e
# ╠═57aa3e4d-63b9-4214-a7fe-351e5dde237c
# ╠═b63d3749-fae4-4fc6-b0a5-08fe150b174d
# ╠═f98034cc-f76a-4bc8-8655-200081eb4fca
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002


# Copyright (c) 2022 Code Komali
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
using Test
include("StudentData.jl")
using .StudentData

function swap!(arr,i,j)
    temp = arr[i]
    arr[i] = arr[j]
    arr[j] = temp
end

function partition(arr;
    startindex=1,
    endindex=lastindex(arr),
    desc = true,
    compareFn = desc ? (x,y)->x>y : (x,y) -> x<y)
    i = startindex + 1
    j = i
    pivot = arr[startindex]
    while j <= endindex
        if compareFn(arr[j], pivot)
            swap!(arr,i,j)
            i+=1
        end
        j+=1
    end
    swap!(arr,startindex,i-1)
    return i-1
end

function quicksort(arr;
    startindex=1,
    endindex=lastindex(arr),
    desc = true,
    compareFn = desc ? (x,y)->x>y : (x,y) -> x<y)
    startindex >= endindex && return arr
    j = partition(arr,startindex=startindex,endindex=endindex, desc=desc, compareFn = compareFn)
    quicksort(arr, startindex=startindex, endindex = j-1, desc=desc, compareFn = compareFn)
    quicksort(arr, startindex=j+1, endindex=endindex, desc=desc, compareFn = compareFn)
    return arr
end

# tests for quicksort
@testset "Quicksort tests" begin
    @test quicksort([11, 9, 7, 10, 8, 6]) == [11, 10, 9, 8, 7, 6]
    @test quicksort([11, 9, 7, 10, 8, 6], desc = false) == [6, 7, 8, 9, 10, 11]
    randArray = rand(Int8, 10)
    @test quicksort(randArray) == sort(randArray, rev = true) # Note: our quicksort implementation by default prefers descending order
    @test quicksort(randArray, desc = false) == sort(randArray)
    @test quicksort(students, compareFn = studentCompareFn) == sortedStudents
end


### A Pluto.jl notebook ###
# v0.14.5

using Markdown
using InteractiveUtils

# ╔═╡ f4b7d3df-213c-46a6-8156-a83fcf6ffb34
begin

	# setup paths -
	_PATH_TO_ROOT = pwd()
	_PATH_TO_SRC = joinpath(_PATH_TO_ROOT, "src")
	_PATH_TO_DATA = joinpath(_PATH_TO_ROOT, "data")
	_PATH_TO_CONFIG = joinpath(_PATH_TO_ROOT, "config")

	# load required packages -
	import Pkg

	# If you do NOT have a Project.toml file -
	# Pkg.activate(mktempdir())
	# Pkg.add([
	# 	Pkg.PackageSpec(name="DataFrames"),
	# 	Pkg.PackageSpec(name="DelimitedFiles"),
	# 	Pkg.PackageSpec(name="CSV"),
	# 	Pkg.PackageSpec(name="Plots"),
	# 	Pkg.PackageSpec(name="TOML"),
	# ])

	# If you have already activated the project -
	Pkg.activate(_PATH_TO_ROOT);
	Pkg.instantiate()
	Pkg.update()

	using DataFrames
	using DelimitedFiles
	using CSV
	using Plots
	using TOML
	using Optim
end

# ╔═╡ 4b710f82-b1cc-11eb-2d14-11b6421a813f
html"""<style>
main {
    max-width: 1200px;
  	width: 70%;
	margin: auto;
}
"""

# ╔═╡ 0df967d7-ca1a-4a19-9c40-7f3a94764504
md""" #### Setup the Julia environment
"""

# ╔═╡ e0a279c1-7677-4c5b-a6e1-ee450b27020e
md""" ### Solution: Enzyme Activity Model

Derive and test an expression for the allosteric regulation of enzyme activity.
Allosteric regulation is a fast mechanism that regulates the catalytic activity of metabolic enzymes.
In this type of regulation, the ability of an enzyme to catalyze a reaction depends upon the concentration of
effector molecules which do not directly participate in the chemical reaction.

We will be developing a model for Phosphofructokinase (PFK) with and without the effector 3$^{\prime}$-5$^{\prime}$-AMP.

"""

# ╔═╡ aa6bba31-0d33-4476-acb8-707128e63a72
md"""##### a) Describe the Model

Let the model take the form $\hat{r}_{j} = r_{j}v\left(...\right)_{j}$
where $\hat{r}_{j}$, the overall rate of the PFK reaction ($\mu$M h$^{-1}$),
is the product of a kinetic limit $r_{j}$ ($\mu$M h$^{-1}$), i.e., the maximum rate of conversion,
and a control variable $0\leq v\left(...\right)_{j}\leq 1$ (dimensionless) that describes the influence
of effector molecules. Since we have only a single enzyme let j = 1.

The model for the kinetic limit was given in the problem:

$r_{1} = k_{cat}E_{1}\left(\frac{F6P}{K_{F6P}+F6P}\right)\left(\frac{ATP}{K_{ATP}+ATP}\right)$

along with all the parameters associated with this model, and the concentration of enzyme ($E_{1}$), F6P and ATP. Thus, we can calculate the kinetic limit. However, we need to postulate a form for the v-varible, and estimate the parameters that appear in the v-variable expression. We'll do this from the data.

###### v-model
In the problem we proposed a three state model for $v\left(...\right)_{j}$: State 0: no effector+no substrate, State 1: no effector+substrate and State 2: effector+substrate. Following the procedure discussed in class, given this three state model, the v-variable takes the form:

$v_{1} = \frac{W_{1}+W_{2}f_{2}}{1+W_{1}+W_{2}f_{2}}$

where $W_{i}$ denotes the weight of state i (dimensionless), and $f_{2}$ denote the fraction of bound effector given by: $f_{2} = (x/K_{2})^{n_{2}}/(1+(x/K_{2})^{n_{2}})$. The quanity x denotes the concentration of the effector 3$^{\prime}$-5$^{\prime}$-AMP, $K_{2}$ denotes a binding constant (units of concentration) and $n_{2}$ denotes a binding cooperativity parameter (dimensionless). State 0 has no activity, while State 1 (with weight $W_{1}$) has basal enzyme activity, and State 2 describes the enhanced activity following from effector binding (with weight $W_{2}f_{2}$).

 - The probability of state 0 is $p_{0}$ = $1/Z$ where $Z$=$1+W_{1}+W_{2}f_{2}$.
 - The probability of state 1 is $p_{1}$ = $W_{1}/Z$ where $Z$=$1+W_{1}+W_{2}f_{2}$.
 - The probability of state 2 is $p_{2}$ = $W_{2}f_{2}/Z$ where $Z$=$1+W_{1}+W_{2}f_{2}$.

"""

# ╔═╡ c4077a00-bef2-4003-ba51-8596550335b2
md""" ##### b) How do we get the parameters in the activity model?

We can estimate the parameters in the activity model from the experimental data given in the problem. This data set measured the rate of PFK at the same concentration(s) of ATP and F6P but different concentrations of 3$^{\prime}$-5$^{\prime}$-AMP. Thus, the kinetic limit was constant in this experiment.
Thus, since the kinetic limit is constant, we can estimate a value of the v-variable as a function of 3$^{\prime}$-5$^{\prime}$-AMP:

$v_{1} \simeq \frac{\hat{r}_{1}^{*}}{r_{1}}$

where $\hat{r}_{1}^{*}$ denotes the _measured value_ of the overall rate.

Lets look at the ends of the experimental data. Let $\alpha$ denote the ratio of rates when there is no effector, and let $\beta$ denote the ratio of rates when there is saturating levels of effector.

###### Case: no effector
When there is no effector, the v-variable model becomes:

$\alpha \simeq \frac{W_{1}}{1+W_{1}}$

which we can solve for $W_{1}$:

$W_{1} \simeq \frac{\alpha}{1-\alpha}$

###### Case: saturating effector

When the effector levels are saturating we expect $f_{2}\simeq{1}$ which gives the expression:

$\beta\simeq\frac{W_{1}+W_{2}}{1+W_{1}+W_{2}}$

which can be solved for $W_{2}$:

$W_{2} \simeq \left(1-\beta\right)^{-1}\Bigl\{\beta\left(1+W_{1}\right)-W_{1}\Bigr\}$

"""

# ╔═╡ 50886df5-d361-4646-898f-795a064c6001
md""" ###### Estimate the configuration weights
"""

# ╔═╡ a3264531-d9c8-42cf-b671-c6f46980a5a4
md""" ###### Estimate the binding and the cooperativity parameters

To estimate the remaining model parameters, we'll use an optimization approach e.g., Solver in xls or the [Optim](https://github.com/JuliaNLSolvers/Optim.jl) package in Julia. In this case, we want to minimize the squared difference between the model predicted v-variable and the v-variable estimated from the experimental data where the decision variables are $K_{2}$ and $n_{2}$:

$\min_{K_{2},n_{2}} \sum_{m=1}^{M}\left(v_{m,1} - v^{*}_{m,1}\right)^2$

where the summation is over the experimental data (the different values of the 3$^{\prime}$-5$^{\prime}$-AMP concentrations), $v_{m,1}$ denotes the model v-variable and $v^{*}_{m,1}$ denotes the experimentally estimated v-variable.

Note: these estimates will __may not be unique__ because the problem is underspecified. Thus, your values may be different.

"""

# ╔═╡ dc395064-55a7-42fe-8fb0-880692e7d81a
md""" ###### c) Plot model versus data. Does it fit?

Now that have the model parameters, let's plot the model v-variable versus the estimated v-variable and see if the model describes the data. In real life, we would do a statistical test on the residual (difference between the model and the data) and/or a cross-validation procedure (partition the data into test and validation sets) and calculate a training and prediction error. However, in this case let's just look - does the model generally describe the trend in the data?

"""

# ╔═╡ b1c395bd-6fa7-4e07-a339-894675791d91
md""" ##### Helper Functions Below Here
"""

# ╔═╡ 169f11a1-34dd-45a9-8f9a-6b18f4957998
function compute_activity_variable(effector_array::Array{Float64,1}, 	 parameter_dictionary::Dict{String,Any})::Array{Float64,1}

    # initialize -
    v_variable_array = Array{Float64,1}()

    try

        # process each effector -
        for effector_value in effector_array

            # compute -
            value = compute_activity_variable(effector_value, parameter_dictionary)

            # cache -
            push!(v_variable_array, value)
        end

        # return -
        return v_variable_array
    catch error
        throw(error)
    end
end

# ╔═╡ de9f574f-fc03-442c-90bd-810f44673466
function compute_activity_variable(effector::Float64, parameter_dictionary::Dict{String,Any})::Float64

    try

        # grab the parameter values from the dictionary -
        W1 = parameter_dictionary["W1"]
        W2 = parameter_dictionary["W2"]
        KD = parameter_dictionary["KD"]
        n = parameter_dictionary["n"]

        # compute the f -
        f = ((effector / KD)^n) / (1 + (effector / KD)^n)

        # compute the v-variable -
        v_variable = (W1 + W2 * f) / (1 + W1 + W2 * f)

        # return -
        return v_variable
    catch error
        throw(error)
    end
end

# ╔═╡ 820c02ef-da91-482d-8bab-36de9b524fa1
function _obj_function(p,parameter_dictionary::Dict{String,Any}, v_variable_data::Array{Float64,1},
    experimental_data_array::Array{Float64,2})

    parameter_dictionary["KD"] = p[1]
    parameter_dictionary["n"] = p[2]

    # compute the model v-variable -
    model_v_variable = compute_activity_variable(experimental_data_array[:,1], parameter_dictionary)

    # compute the residual between the data and model estimated v-variable -
    error = sum((v_variable_data .- model_v_variable).^2)

    # return -
    return error
end

# ╔═╡ b2fe595d-e90e-4041-bba4-8a1558bb83e2
function estimate_parameters(parameter_dictionary::Dict{String,Any}, v_variable_data::Array{Float64,1},
    experimental_data_array::Array{Float64,2})

    try

        # get unkown parameter guess from config -
        KD = parameter_dictionary["KD"]
        n = parameter_dictionary["n"]

        # setup the calculation -
        xinitial = [KD n]
        OF(p) = _obj_function(p,parameter_dictionary, v_variable_data, experimental_data_array)

        # call the optimizer -
        opt_result = optimize(OF,xinitial,BFGS())

        # return -
        return Optim.minimizer(opt_result)
    catch error
        throw(error)
    end
end

# ╔═╡ aa2267d4-ae47-4532-9fa0-460498b40fea
function load_experimental_data_set(path_to_data_file::String)::Array{Float64,2}

    try

        # load the experimental data -
        df = readdlm(path_to_data_file)

        # return -
        return df
    catch error
        throw(error)
    end
end

# ╔═╡ b25aeabb-2be1-48ef-bd72-3680ae8e9407
function load_configuration_dictionary(path_to_configuration_file)::Dict{String,Any}

    try

        # load the config TOML file -
        configuration_dictionary = TOML.parsefile(path_to_configuration_file)

        # return -
        return configuration_dictionary
    catch error
        throw(error)
    end
end

# ╔═╡ 5f846f30-f20f-4153-b87f-71ab429019cb
function compute_kinetic_limit(parameter_dictionary::Dict{String,Any})::Float64

    try

        # grab the parameter values from the dictionary -
        E1 = parameter_dictionary["PFK_concentration"]
        ATP = parameter_dictionary["ATP_concentration"]
        F6P = parameter_dictionary["F6P_concentration"]
        kcat = parameter_dictionary["kcat"]
        K_ATP = parameter_dictionary["K_ATP"]
        K_F6P = parameter_dictionary["K_F6P"]

        # compute the kinetic limit -
        kinetic_limit = kcat * E1 * (ATP / (K_ATP + ATP)) * (F6P / (K_F6P + F6P))

        # return -
        return kinetic_limit
    catch error
        throw(error)
    end
end

# ╔═╡ 5bab3b76-be8a-44c9-8de3-ac35992bda6a
begin

	# load the experimental_data file -
	path_to_experimental_data_file = joinpath(_PATH_TO_DATA, "Data-3-5-AMP.txt")
	experimental_data_array = load_experimental_data_set(path_to_experimental_data_file)

	# load my parameters -> configuration_dictionary contains, two sub-dictionaries
	path_to_configuration_file = joinpath(_PATH_TO_CONFIG, "Configuration.toml")
	configuration_dictionary = load_configuration_dictionary(path_to_configuration_file)
	v_variable_parameter_dictionary = configuration_dictionary["vvariableparameters"]
	kinetic_limit_parameter_dictionary = configuration_dictionary["kineticparameters"]

	# lets compute the kinetic limit -
	kinetic_limit_value = compute_kinetic_limit(kinetic_limit_parameter_dictionary)

	# ok, so let's compute a data driven v-variable -
	data_driven_v_variable = Array{Float64,1}()
	(number_of_effector_values, number_of_cols) = size(experimental_data_array)
	for row_index = 1:number_of_effector_values
    	value = experimental_data_array[row_index,2]
    	scaled_value = value / kinetic_limit_value
    	push!(data_driven_v_variable, scaled_value)
	end

	# return -
	nothing
end

# ╔═╡ 2bb3a988-e054-4bfb-a306-cba7adb278f6
begin

	# compute W1 and W2 estimates -
	# W1 -
	⍺ = data_driven_v_variable[1]
	W1 = (⍺)/(1-⍺)

	# W2 -
	β = data_driven_v_variable[end]
	W2 = ((1-β)^-1)*(β*(1+W1)-W1)

	# return -
	nothing
end

# ╔═╡ 11df9c9f-e58f-450a-b630-3010db01391b
(W1,W2)

# ╔═╡ 8d8f4af4-8519-4a83-9027-bbf63b56e038
# estimate parameters -
parameter_array = estimate_parameters(v_variable_parameter_dictionary, data_driven_v_variable, experimental_data_array)

# ╔═╡ 81e97235-a5d6-4404-a701-c089f76aa791
begin

	# note: I put the values for K and n into the config file -

	# let's make an array of effector values -
	effector_concentration_array = range(0,stop=1.0, length=100) |> collect

	# compute the model v-variable -
	model_v_variable = compute_activity_variable(effector_concentration_array, v_variable_parameter_dictionary)

	# plot the data -
	plot(effector_concentration_array,model_v_variable,label="model",legend=:bottomright)
	scatter!(experimental_data_array[:,1],data_driven_v_variable,label="mean data")
	xlabel!("Effector 3-5-AMP (mM)", fontsize=16)
	ylabel!("Estimated v-variable (AU)", fontsize=16)

end

# ╔═╡ Cell order:
# ╟─4b710f82-b1cc-11eb-2d14-11b6421a813f
# ╟─0df967d7-ca1a-4a19-9c40-7f3a94764504
# ╠═f4b7d3df-213c-46a6-8156-a83fcf6ffb34
# ╟─e0a279c1-7677-4c5b-a6e1-ee450b27020e
# ╟─aa6bba31-0d33-4476-acb8-707128e63a72
# ╟─c4077a00-bef2-4003-ba51-8596550335b2
# ╟─50886df5-d361-4646-898f-795a064c6001
# ╠═5bab3b76-be8a-44c9-8de3-ac35992bda6a
# ╠═2bb3a988-e054-4bfb-a306-cba7adb278f6
# ╠═11df9c9f-e58f-450a-b630-3010db01391b
# ╟─a3264531-d9c8-42cf-b671-c6f46980a5a4
# ╠═8d8f4af4-8519-4a83-9027-bbf63b56e038
# ╟─dc395064-55a7-42fe-8fb0-880692e7d81a
# ╠═81e97235-a5d6-4404-a701-c089f76aa791
# ╟─b1c395bd-6fa7-4e07-a339-894675791d91
# ╠═820c02ef-da91-482d-8bab-36de9b524fa1
# ╠═169f11a1-34dd-45a9-8f9a-6b18f4957998
# ╠═de9f574f-fc03-442c-90bd-810f44673466
# ╠═b2fe595d-e90e-4041-bba4-8a1558bb83e2
# ╠═aa2267d4-ae47-4532-9fa0-460498b40fea
# ╠═b25aeabb-2be1-48ef-bd72-3680ae8e9407
# ╠═5f846f30-f20f-4153-b87f-71ab429019cb


#=
  Seven scientists in Turing.jl

  Port of the WebPPL model from
  https://mhtess.github.io/bdappl/chapters/03-simpleModels.html
  """
  Seven scientists with wildly-differing experimental skills all make a
  measurement of the same quantity. They get the answers

     x = {−27.020, 3.570, 8.191, 9.898, 9.603, 9.945, 10.056}.

  Intuitively, it seems clear that the first two scientists are pretty inept
  measurers, and that the true value of the quantity is probably just a bit
  below 10. The main problem is to find the posterior distribution over
  the measured quantity, telling us what we can infer from the measurement.
  A secondary problem is to infer something about the measurement skills of
  the seven scientists.

  """

  Using NUTS(1000,0.65):
  """
  Summary Statistics
  parameters      mean       std   naive_se      mcse         ess      rhat   ess_per_sec
      Symbol   Float64   Float64    Float64   Float64     Float64   Float64       Float64

          mu    9.1691    1.5779     0.0158    0.0388   1514.9796    1.0003      361.9158
   sigmas[1]   17.1826    2.1492     0.0215    0.0481   1812.3976    1.0024      432.9665
   sigmas[2]   10.1038    4.8808     0.0488    0.0947   2207.2242    1.0000      527.2872
   sigmas[3]    6.9711    5.3999     0.0540    0.1307   1840.5852    1.0000      439.7002
   sigmas[4]    5.7966    5.5111     0.0551    0.1452   1247.6705    1.0002      298.0579
   sigmas[5]    5.5648    5.4101     0.0541    0.1455   1266.5753    1.0008      302.5741
   sigmas[6]    5.6467    5.3356     0.0534    0.1272   1428.5790    1.0002      341.2754
   sigmas[7]    5.8013    5.4090     0.0541    0.1533   1491.1203    1.0003      356.2160

  Quantiles
  parameters      2.5%     25.0%     50.0%     75.0%     97.5%
      Symbol   Float64   Float64   Float64   Float64   Float64

          mu    4.4711    8.7121    9.6185    9.9814   11.3731
   sigmas[1]   12.1421   15.8454   17.5997   18.9317   19.8960
   sigmas[2]    2.3154    6.0300    9.5638   14.0701   19.2536
   sigmas[3]    0.4744    2.4507    5.4174   10.7118   18.7471
   sigmas[4]    0.1709    1.2017    3.7592    9.4112   18.4969
   sigmas[5]    0.1470    1.1639    3.5180    8.8308   18.5041
   sigmas[6]    0.1892    1.3070    3.7946    8.5708   18.6025
   sigmas[7]    0.1556    1.2915    3.7926    9.2773   18.1375
   """

=#

using Turing
include("jl_utils.jl")

@model function seven_scientists(data)
    n = length(data)
    mu ~ Normal(0, 30)
    sigmas ~ filldist(Uniform(0,20),n)
    for i in 1:n
        data[i] ~ Normal(mu, sigmas[i])
    end
end
data = [-27.020, 3.570, 8.191, 9.898, 9.603, 9.945, 10.056]

model = seven_scientists(data)

# chns = sample(model, Prior(), 10_000)
# chns = sample(model, MH(), 10_000)
# chns = sample(model, PG(15), 10_000)
# chns = sample(model, IS(), 10_000)
# chns = sample(model, SMC(), 10_000)
# chns = sample(model, SMC(), MCMCThreads(), 10_000, 4)

chns = sample(model, NUTS(1000,0.65), 10_000)
# chns = sample(model, HMC(0.1,10), 10_000)

display(chns)


import StringUtils

function main()
    if length(ARGS) > 0
        filename = ARGS[1]
        a = readlines(filename)
        m = StringUtils.grid_to_matrix(a)
        l = length(m)
        for row in 2:l
            ncols = length(m[row])
            for col in 1:ncols
                left = col == 1 ? 0 : m[row-1][col-1]
                right = col == ncols ? 0 : m[row-1][col]
                m[row][col] += max(left, right)
            end
        end
        @printf("The maximum total has value %d\n",maximum(m[l]))
    else
        error("this program needs a single numeric argument")
    end
end

main()


using PyPlot
using LinearAlgebra, Random

"""
Gradient ascent to train a 2-layers ReLU neural net for the soft-min loss
INPUT: X (training input), Y (training output), m (nb neurons), both: training both layers or just the output
OUTPUT: Ws (training trajectory)
"""
function twonet(X, Y, m, stepsize, niter; both=true)
    (n,d) = size(X)
    W_init = randn(m, d+1)
    if !both
        W_init[:,end] .= 0
    end

    W     = copy(W_init)
    Ws    = zeros(m, d+1, niter) # store optimization path
    loss  = zeros(niter)
    margins = zeros(niter)
    betas = zeros(niter)

    for iter = 1:niter
        Ws[:,:,iter] = W
        act  =  max.( W[:,1:end-1] * X', 0.0) # (size m × n)
        out  =  (1/m) * sum( W[:,end] .* act , dims=1) # (size 1 × n)
        perf = Y .* out[:]
        margin = minimum(perf)
        temp = exp.(margin .- perf) # stabilization
        gradR = temp .* Y ./ sum(temp)' # size n
        grad_w1 = (W[:,end] .* float.(act .> 0) * ( X .* gradR  ))  # (size m × d)
        grad_w2 = act * gradR  # size m

        if both
            grad = cat(grad_w1, grad_w2, dims=2) # size (m × d+1)
            betas[iter] = sum(W.^2)/m
            loss[iter] = margin - log(sum(exp.(margin .- perf))/n)
            margins[iter] = margin/betas[iter]
            W = W + stepsize * grad/(sqrt(iter+1))

         else
            grad = cat(zeros(m,d), grad_w2, dims=2) # size (m × d+1)
            betas[iter] = maximum([1,sqrt(sum(W[:,end].^2)/m)])
            loss[iter] = margin - log(sum(exp.(margin .- perf))/n)
            margins[iter] = margin/(sqrt(sum(W[:,end].^2))/m)
            W = W + betas[iter] * stepsize * grad /(sqrt(iter+1))
        end
    end
    Ws, loss, margins, betas
end

"Coordinates of the 2d cluster centers, p is k^2 the number of clusters"
function cluster_center(p,k)
    p1 = mod.(p .- 1,k) .+ 1
    p2 = div.(p .- 1,k) .+ 1
    Delta = 1/(3k-1)
    x1 =  Delta*(1 .+ 3*(p1 .- 1)) .- 1/2
    x2 =  Delta*(1 .+ 3*(p2 .- 1)) .- 1/2
    return x1,x2
end

"""
Plot the classifier for a test case, comparing training both or output layer
"""
function illustration(k, n, m; stepsize= 0.5, niter=100000, name="decision")
# data distribution
sd = 0 # number of spurious dimensions
Delta = 1/(3k-1) # interclass distance
A = ones(k^2) # cluster affectation
A[randperm(k^2)[1:div(k^2,2)]] .= -1

# sample from it
P = rand(1:k^2,n) # cluster label
T = 2π*rand(n)  # shift angle
R = Delta*rand(n) # shift magnitude
X = cat(ones(n), cluster_center(P,k)[1] .+ R .* cos.(T),cluster_center(P,k)[2] + R .* sin.(T), (rand(n,sd) .- 1/2), dims=2)
Y = A[P]

# train neural network
Ws1, loss1, margins1, betas1 = twonet(X, Y, m, stepsize, niter; both=true)
Ws2, loss2, margins2, betas2 = twonet(X, Y, m, stepsize, niter; both=false)

# plots
X1 = X[(Y .== 1),:]
X2 = X[(Y .== -1),:]

figure(figsize=[2.5,2.5])
    plot(X1[:,2],X1[:,3],"+r")
    plot(X2[:,2],X2[:,3],"_b")
    plot(cluster_center(1:k^2,k)[1],cluster_center(1:k^2,k)[2],"ok")
    axis("equal");#axis("off")
    xticks([], []); yticks([], [])
    savefig(name * "setting.pdf",bbox_inches="tight")

figure(figsize=[2.5,2.5])
    f1(x1,x2,t) = (1/m) * sum( Ws1[:,end,t] .* max.( Ws1[:,1:3,t] * [1;x1;x2], 0.0)) # (size 1 × n)
    xs = -0.8:0.01:0.8
    tab1 = [f1(xs[i],xs[j],size(Ws1,3)) for i=1:length(xs), j=1:length(xs)]
    pcolormesh(xs', xs, tanh.(1000*tab1'),cmap="coolwarm",shading="gouraud",vmin=-1.0,vmax=1.0,edgecolor="face")
    xs = -0.8:0.005:0.8
    tab1 = [f1(xs[i],xs[j],size(Ws1,3)) for i=1:length(xs), j=1:length(xs)]
    contour(xs', xs, tanh.(1000*tab1'),levels =0, colors="k",antialiased = true,linewidths=2)
    plot(X1[:,2],X1[:,3],"+k")
    plot(X2[:,2],X2[:,3],"_k")
    axis("equal");axis("off");
    savefig(name * "_both.pdf",bbox_inches="tight")

 figure(figsize=[2.5,2.5])
    f2(x1,x2,t) = (1/m) * sum( Ws2[:,end,t] .* max.( Ws2[:,1:3,t] * [1;x1;x2], 0.0)) # (size 1 × n)
    xs = -0.8:0.01:0.8
    tab2 = [f2(xs[i],xs[j],size(Ws2,3)) for i=1:length(xs), j=1:length(xs)]
    pcolormesh(xs', xs, tanh.(1000*tab2'),cmap="coolwarm",shading="gouraud",vmin=-1.0,vmax=1.0,edgecolor="face")
    xs = -0.8:0.005:0.8
    tab2 = [f2(xs[i],xs[j],size(Ws2,3)) for i=1:length(xs), j=1:length(xs)]
    contour(xs', xs, tanh.(1000*tab2'),levels =0, colors="k",antialiased = true,linewidths=2)
    plot(X1[:,2],X1[:,3],"+k")
    plot(X2[:,2],X2[:,3],"_k")
    axis("equal");axis("off");
    savefig(name *"_output.pdf",bbox_inches="tight")
end

"""
Compute margin and generalisation error.
Run the experiment with the given list of d (input dimension), n (input points), m (width)
On n_distrib random input distributions and n_repeat random initialization.
"""
function experiment_2NN(n_distrib, ds, ns, n_repeat, ms; k = 3, niter = 5*10^5, stepsize = 1, n_test=4000)

    L1s = zeros(n_distrib, length(ds), length(ns), n_repeat, length(ms))
    L2s = zeros(n_distrib, length(ds), length(ns), n_repeat, length(ms))
    m1s = zeros(n_distrib, length(ds), length(ns), n_repeat, length(ms))
    m2s = zeros(n_distrib, length(ds), length(ns), n_repeat, length(ms))

    p = Progress(length(L1s))
    for i=1:n_distrib
        # define the distribution
        Delta = 1/(3k-1) # lower bound on the interclass distance
        A = ones(k^2)
        A[randperm(k^2)[1:div(k^2,2)]] .= -1 # cluster affectation

        for ii = 1: length(ds)
            d = ds[ii]
            # sample from the distribution (we then take a subset of it)
            nmax = maximum(ns)
            P = rand(1:k^2,nmax) # cluster label
            T = 2π*rand(nmax)  # shift angle
            R = Delta*rand(nmax) # shift magnitude
            sd = d-3 # number of spurious dimensions
            X = cat(ones(nmax),
                      cluster_center(P,k)[1] .+ R .* cos.(T),
                      cluster_center(P,k)[2] + R .* sin.(T),
                      rand(nmax,sd) .- 1/2,
                      dims=2)
            Y = A[P]
            for iii = 1:length(ns)
                n = ns[iii]
                for iv=1:n_repeat # same eexperiment but with another random init
                    for v=1:length(ms)
                        m = ms[v]

                        # train neural network
                        Ws1, loss1, margins1, betas1 = twonet(X[1:n,:], Y[1:n], m, stepsize, niter; both=true)
                        Ws2, loss2, margins2, betas2 = twonet(X[1:n,:], Y[1:n], m, stepsize, niter; both=false)

                        # test set
                        T_test = 2π*rand(n_test)  # shift angle
                        R_test = Delta*rand(n_test) # shift magnitude
                        P_test = rand(1:k^2,n_test) # cluster label
                        X_test = cat(ones(n_test), cluster_center(P_test,k)[1] .+ R_test .* cos.(T_test),
                        cluster_center(P_test,k)[2] + R_test .* sin.(T_test), (rand(n_test,sd) .- 1/2), dims=2)
                        Y_test = A[P_test]

                        # test error
                        preds1 = (1/m) * sum(Ws1[:,end,end] .* max.( Ws1[:,1:end-1,end] * X_test', 0.0), dims = 1)
                        L1 = sum((Y_test .* preds1') .< 0)/n_test # probability of error
                        preds2 = (1/m) * sum(Ws2[:,end,end] .* max.( Ws2[:,1:end-1,end] * X_test', 0.0), dims = 1)
                        L2 = sum((Y_test .* preds2') .< 0)/n_test # probability of error
                        L1s[i,ii,iii,iv,v] = L1
                        L2s[i,ii,iii,iv,v] = L2
                        m1s[i,ii,iii,iv,v] = maximum(margins1)
                        m2s[i,ii,iii,iv,v] = maximum(margins2)
                        GC.gc() # garbage collection
                        next!(p)
                    end
                end
            end
        end
    end
    return L1s, L2s, m1s, m2s
end


using TerraDG
using Test
using LinearAlgebra

@testset "All tests" begin

@testset "Quadrature on the reference element" begin
    n = 5
    basis = TerraDG.Basis(n,1)
    p = basis.quadpoints
    wp = basis.quadweights
    func = (x,y) -> x*y*y
    sum = 0.0
    for i in CartesianIndices((n,n))
        sum += func(p[i[1]],p[i[2]]) * wp[i[1]]*wp[i[2]]
    end
    @test abs(sum - 1. / 6.) <= 10e-15
end

@testset "Maps reference coordinate to correct physical coord" begin
    cellcenter = [1.0, 1.0]
    cellsize = [0.5, 0.5]
    cell = TerraDG.Cell(cellcenter,
            cellsize,
            [],
            [],
            -1)

    @test TerraDG.globalposition(cell, [0.5, 0.5]) == cellcenter
    @test TerraDG.globalposition(cell, [0.0, 0.0]) == [0.75, 0.75]
    @test TerraDG.globalposition(cell, [1.0, 1.0]) == [1.25, 1.25]
    @test_throws BoundsError TerraDG.globalposition(cell, [1.1, 0.0])
    @test_throws BoundsError TerraDG.globalposition(cell, [-0.1, 0.0])
end

@testset "Global to reference to global is same" begin
    cellcenter = [1.0, 1.0]
    cellsize = [0.5, 0.5]
    cell = TerraDG.Cell(cellcenter,
            cellsize,
            [],
            [],
            -1)

    coords = [
        [0.5, 0.5],
        [0.0, 0.0],
        [1.0, 1.0]
    ]
    for coord ∈ coords
        global_coord = TerraDG.globalposition(cell, coord)
        local_coord = TerraDG.localposition(cell, global_coord)
        @test local_coord == coord
    end
end

@testset "Volume of 2D cells is correct" begin
    sizes = [
        [1.0, 1.0],
        [2.0, 2.0],
        [0.5, 0.5]
    ]

    volumes = [
        1.0,
        4.0,
        0.25
    ]
    for (size, volume) ∈ zip(sizes, volumes)
        cell = TerraDG.Cell([0.0, 0.0],
            size,
            [],
            [],
            -1)
        @test TerraDG.volume(cell) == volume
    end
end

@testset "Area of 2D cells is correct" begin
    sizes = [
        [1.0, 1.0],
        [2.0, 2.0],
        [0.5, 0.5]
    ]

    areas = [
        1.0,
        2.0,
        0.5
    ]
    for (size, area) ∈ zip(sizes, areas)
        cell = TerraDG.Cell([0.0, 0.0],
            size,
            [],
            [],
            -1)
        @test TerraDG.area(cell) == area
    end
end

@testset "Lagrange polynomials are correct" begin
    δ(a,b) = if (a == b) 1.0 else 0.0 end
    for n in range(1, length=6)
        basis = TerraDG.Basis(n, 1)
        points = basis.quadpoints
        for (i,p) in enumerate(points)
            for (j,q) in enumerate(points)
                @test TerraDG.lagrange_1d(points, i, q) == δ(i,j)
            end
        end
    end
end

@testset "Lagrange polynomials sum to one" begin
    for n in range(1, length=6)
        for x in [0.0, 0.5, 1.0]
            basis = TerraDG.Basis(n, 1)
            points = basis.quadpoints
            sum = 0.0
            for (i,p) in enumerate(points)
                sum += TerraDG.lagrange_1d(points, i, x)
            end
            @test isapprox(sum, 1, atol=10^-14)
        end
    end
end

@testset "Integral of derivatives of 1D-Lagrange polynomials is correct" begin
    Δx = 1e-4
    for n in range(1, length=6)
        basis = TerraDG.Basis(n, 1)
        points = basis.quadpoints
        for (i,p) in enumerate(points)
            for (j, x) in enumerate(points)
                f_1 = TerraDG.lagrange_1d(points, i, x)
                f_2 = TerraDG.lagrange_1d(points, i, x + Δx)
                df = TerraDG.lagrange_diff(points, i, x)
                @test isapprox(f_2, f_1 + Δx*df, atol=10^-5)
            end
        end
    end
end

@testset "Integral of derivatives of 2D-Lagrange polynomials is also correct" begin
    Δx = 1e-4
    Δy = 1e-4
    for x in [0.0, 0.5, 1.0]
        for y in [0.0, 0.5, 1.0]
            for n in range(1, length=6)
                basis = TerraDG.Basis(n, 1)
                points = basis.quadpoints
                for (i, p) in enumerate(points)
                    for (j, q) in enumerate(points)
                        f = TerraDG.lagrange_1d(points, i, x) * TerraDG.lagrange_1d(points, j, y)
                        f_1 = TerraDG.lagrange_1d(points, i, x + Δx) * TerraDG.lagrange_1d(points, j, y)
                        f_2 = TerraDG.lagrange_1d(points, i, x) * TerraDG.lagrange_1d(points, j, y + Δy)
                        df_x = TerraDG.lagrange_diff(points, i, x) * TerraDG.lagrange_1d(points, j, y)
                        df_y = TerraDG.lagrange_1d(points, i, x) * TerraDG.lagrange_diff(points, j, y)
                        @test isapprox(f_1, f + Δx*df_x, atol=10^-5) & isapprox(f_2, f + Δy*df_y, atol=10^-5)
                    end
                end
            end
        end
    end
end

@testset "Derivative of const = 0" begin
    for n=1:6
        basis = TerraDG.Basis(n, 1)
        for x in [0.0, 0.2, 0.4, 0.6, 0.8, 1.0]
            @test isapprox(
            sum([TerraDG.lagrange_diff(basis.quadpoints, i, x) for i=1:size(basis, 1)]), 0, atol=10e-14)
        end
    end
end

@testset "Mass matrix is correct for 2D" begin
    basis_order1 = TerraDG.Basis(1, 2)
    basis_order2 = TerraDG.Basis(2, 2)
    @test TerraDG.massmatrix(basis_order1, 2) == reshape([1], 1, 1)
    massmatrix_order2 = Diagonal([0.25,0.25,0.25,0.25])
    @test TerraDG.massmatrix(basis_order2, 2) == massmatrix_order2

end

# TODO: Enable for worksheet >2
#if false
@testset "Projection/evaluation works for polynomials" begin
    ns = [1,2,3,4,5,6]
    funs = [
        (x, y) -> [1],
        (x, y) -> [x + y],
        (x, y) -> [(x + y)^2],
        (x, y) -> [(x + y)^3],
        (x, y) -> [(x + y)^4],
        (x, y) -> [(x + y)^5]
    ]
    points = [
        [0.21, 0.23],
        [0.38, 0.93],
        [0.92, 0.23],
        [0.01, 0.01],
        [0.99, 0.99],
    ]
    proj_reshaped(basis, func) = reshape(
        TerraDG.project_to_reference_basis(func, basis, 1), length(basis.quadweights)^2 )

    for (n, fun) in zip(ns, funs)
        basis = TerraDG.Basis(n, 2)
        coeffs = proj_reshaped(basis, fun)
        for point in points
            evaluated = TerraDG.evaluate_basis(basis, coeffs, point)
            reference = fun(point[1], point[2])[1]
            @test isapprox(evaluated, reference, atol=10e-14)
        end
    end
end

@testset "Face projection matrix is correct" begin
    ns = [1,2,3,4,5,6]
    funs = [
        (x, y) -> [1,1,1]
        (x, y) -> [0,0,0]
        (x, y) -> [0,1,0]
    ]
    for n in ns
        basis = TerraDG.Basis(n, 2)
        for func in funs
            func_proj = TerraDG.project_to_reference_basis(func, basis, 3)
            for face in [TerraDG.top, TerraDG.bottom, TerraDG.left, TerraDG.right]
                fp = TerraDG.face_projection_matrix(basis, TerraDG.top)
                func_proj_face = fp * func_proj
                @test sum(func_proj)/length(func_proj) ≈ sum(func_proj_face)/length(func_proj_face)
            end
        end
    end
end

@testset "Derivative matrix is correct" begin
    ns = [1,2,3,2,3,4,5,6]

    funs = [
        (x, y) -> [1]
        (x, y) -> [1]
        (x, y) -> [1]
        (x, y) -> [x + y]
        (x, y) -> [(x + y)^2]
        (x, y) -> [(x + y)^3]
        (x, y) -> [(x + y)^4]
        (x, y) -> [(x + y)^5]
    ]
    funs_deriv_x = [
        (x,y) -> [0]
        (x,y) -> [0]
        (x,y) -> [0]
        (x,y) -> [1]
        (x,y) -> [2 * (x + y)]
        (x,y) -> [3 * (x + y)^2]
        (x,y) -> [4 * (x + y)^3]
        (x,y) -> [5 * (x + y)^4]
    ]
    funs_deriv_y = [
        (x,y) -> [0]
        (x,y) -> [0]
        (x,y) -> [0]
        (x,y) -> [1]
        (x,y) -> [2 * (x + y)]
        (x,y) -> [3 * (x + y)^2]
        (x,y) -> [4 * (x + y)^3]
        (x,y) -> [5 * (x + y)^4]
    ]

    for (n, fun, deriv_x, deriv_y) in zip(ns, funs, funs_deriv_x, funs_deriv_y)
        proj_reshaped(basis, func) = reshape(
            TerraDG.project_to_reference_basis(func, basis, 1), length(basis.quadweights)^2 )
        basis = TerraDG.Basis(n, 2)
        fun_evaluated = proj_reshaped(basis, fun)
        reference_derivx_evaluated = proj_reshaped(basis, deriv_x)
        reference_derivy_evaluated = proj_reshaped(basis, deriv_y)
        ∇ = TerraDG.derivativematrix(basis)
        derivx_evaluated = transpose(∇[:, 1:length(basis)]) * fun_evaluated
        derivy_evaluated = transpose(∇[:, length(basis) + 1 : end]) * fun_evaluated

        @test all(isapprox.(derivx_evaluated, reference_derivx_evaluated, atol=10e-12))
        @test all(isapprox.(derivy_evaluated, reference_derivy_evaluated, atol=10e-12))

    end
end

end