"Derive Effective Concentration with Regression.jl"

include("../src/Regression.jl")
include("../src/Model.jl")
include("../src/Utils.jl")
include("../src/Opt.jl")
import PyPlot
using Printf
using Test
using DifferentialEquations
using Parameters

const plt = PyPlot

## Model and Domain
sample_model = Model.hill(k=0.5, n=4.)
domain = [0.0,1.0]
data = Utils.screen_domain(sample_model, domain)

## Get Thresholds
thr_50 = Regression.measure_EC(data.x, data.y, 0.5)[1]
thr_90 = Regression.measure_EC(data.x, data.y, 0.9)[1]
thr_10 = Regression.measure_EC(data.x, data.y, 0.1)[1]
@test Regression.measure_EC(sample_model, domain,0.5,0.01)[1] ≈ thr_50 # Simulation method and data method

### Get extremes
argmin = Regression.measure_EC(data.x, data.y, 0.)[1]
argmax = Regression.measure_EC(data.x, data.y, 1-1e-10)[1]

### Visualization

fig, ax = plt.subplots(figsize=(10,7))
color = ["black", "green", "red", "blue", "gray"]
ax.plot(data.x, data.y, label="data", color=color[1])
ax.axvline(thr_50, label= string("EC50 = $(@sprintf("%.2f", thr_50))"), color=color[2])
ax.axvline(thr_90, label="EC90 = $(@sprintf("%.2f", thr_90))", color=color[3])
ax.axvline(thr_10, label="EC10 = $(@sprintf("%.2f", thr_10))", color=color[4])

ax.axvline(argmin, label="argMin = $(@sprintf("%.2f", min))", color=color[5])
ax.axvline(argmax, label="argMax = $(@sprintf("%.2f", max))", color=color[5])

ax.legend()
ax.set_title("Estimation of Effective Concentration (EC)")
ax.set_xlabel("Input")
ax.set_ylabel("Output (Monotonous)")
plt.display_figs()

## Positive Feedback

input = LinRange(0.1,1.0,1000)
output = zeros(length(input))

for (i, s) in enumerate(input)
    local mod = Model.positive_feedback(s=s)

    local prob = SteadyStateProblem(mod, mod.x)

    local sol = solve(prob, DynamicSS(Rosenbrock23()))

    output[i] = sol.u[1]
end

thr_50 = Regression.measure_EC(input, output, 0.5)[1]
thr_90 = Regression.measure_EC(input, output, 0.9)[1]
thr_10 = Regression.measure_EC(input, output, 0.1)[1]

### Get extremes
argmin = Regression.measure_EC(input, output, 0.)[1]
argmax = Regression.measure_EC(input, output, 1-1e-10)[1]

fig, ax = plt.subplots(figsize=(10,7))
color = ["black", "green", "red", "blue", "gray"]
ax.plot(input, output, label="data", color=color[1])
ax.axvline(thr_50, label= string("EC50 = $(@sprintf("%.2f", thr_50))"), color=color[2])
ax.axvline(thr_90, label="EC90 = $(@sprintf("%.2f", thr_90))", color=color[3])
ax.axvline(thr_10, label="EC10 = $(@sprintf("%.2f", thr_10))", color=color[4])

ax.axvline(argmin, label="argMin = $(@sprintf("%.2f", min))", color=color[5])
ax.axvline(argmax, label="argMax = $(@sprintf("%.2f", max))", color=color[5])

ax.set_xscale("log")
ax.legend()
ax.set_title("Estimation of Effective Concentration (EC)")
ax.set_xlabel("Input")
ax.set_ylabel("Output (Monotonous)")
plt.display_figs()

##

#=
Utils.screen_domain(Model.positive_feedback, 0.1, :s, [0.0,1.0])

df = Model.positive_feedback()

a = :s
df.a
df.:s

eval(quote df.$a=4 end)
=#


using Test
@testset "ODE" begin let
	import DifferentiableBackwardEuler
	DBE = DifferentiableBackwardEuler
	using DifferentialEquations
	import LinearAlgebra
	import SparseArrays
	import Zygote

	f(u, p, t) = f_u(u, p, t) * u
	f_u(u, p, t) = SparseArrays.spdiagm(0=>p)
	f_p(u, p, t) = SparseArrays.spdiagm(0=>u)
	f_t(u, p, t) = zero(u)

	#test the forward solver analytically
	n = 2
	p = collect(1.0:n)
	u0 = ones(n)
	us = [u0]
	numsteps = 10 ^ 3
	ts = range(0, 1; length=numsteps)
	for i = 1:length(ts) - 1
		push!(us, DBE.step(us[end], ts[i + 1] - ts[i], f, f_u, f_p, f_t, p, ts[i + 1]))
	end
	@test isapprox(exp.(p), us[end]; atol=1e2 / numsteps, atol=1e2 / numsteps)

	#test gradients involving step(...)
	function naivegradient(f, x0s...; delta::Float64=1e-8)
		f0 = f(x0s...)
		gradient = map(x->zeros(size(x)), x0s)
		for j = 1:length(x0s)
			x0 = copy(x0s[j])
			for i = 1:length(x0)
				x = copy(x0)
				x[i] += delta
				xs = (x0s[1:j - 1]..., x, x0s[j + 1:length(x0s)]...)
				fval = f(xs...)
				gradient[j][i] = (fval - f0) / delta
			end
		end
		return gradient
	end
	g(p, u0) = sum(DBE.step(u0, ts[2] - ts[1], f, f_u, f_p, f_t, p, ts[2]))
	dgdp_zygote = Zygote.gradient((p, u0)->g(p, u0), p, u0)
	dgdp_naive = naivegradient((p, u0)->g(p, u0), p, u0)
	@test isapprox(dgdp_zygote[1], dgdp_naive[1]; atol=1e-6, rtol=1e-6)
	@test isapprox(dgdp_zygote[2], dgdp_naive[2]; atol=1e-6, rtol=1e-6)

	#test gradients involving steps(..., ts)
	numsteps = 10 ^ 1
	ts = collect(range(0, 1e0; length=numsteps))
	g(p, u0) = sum(DBE.steps(u0, f, f_u, f_p, f_t, p, ts)[:, end])
	@test isapprox(sum(exp.(p * ts[end])), g(p, u0); atol=1e2 / numsteps, rtol=1e2 / numsteps)
	dgdp_zygote = Zygote.gradient((p, u0)->g(p, u0), p, u0)
	dgdp_naive = naivegradient((p, u0)->g(p, u0), p, u0)
	@test isapprox(dgdp_zygote[1], dgdp_naive[1]; atol=1e-4, rtol=1e-4)
	@test isapprox(dgdp_zygote[2], dgdp_naive[2]; atol=1e-4, rtol=1e-4)

	#test the forward solver against DifferentialEquations.jl
	u0 = [1 / 2, 1 / 3]
	f(u, p, t) = f_u(u, p, t) * u
	f_u(u, p, t) = LinearAlgebra.Diagonal(copy(p))
	tspan = (0.0, 1.0)
	prob = ODEProblem(f, u0, tspan, p)
	sol = solve(prob, ImplicitEuler())
	dbesol = DBE.steps(u0, f, f_u, nothing, nothing, p, sol.t)
	for i = 1:length(sol.t)
		@test isapprox(dbesol[:, i], sol[:, i])
	end

	#test the forward runs involving steps(..., t0, final) are working
	dbesol = DBE.steps(u0, f, f_u, nothing, nothing, p, tspan[1], tspan[2])
	for i = 1:length(sol.t)
		@test isapprox(dbesol[:, i], sol[:, i])
	end

	#test gradients involving steps(..., t0, tfinal)
	t0 = 0.0
	tfinal = 1e0
	g(p, u0) = sum(DBE.steps(u0, f, f_u, f_p, f_t, p, t0, tfinal)[:, end])
	g_val = g(p, u0)
	@test isapprox(sum(u0 .* exp.(p * ts[end])), g_val; atol=1e2 / numsteps, rtol=1e2 / numsteps)
	dgdp_zygote = Zygote.gradient(g, p, u0)
	dgdp_naive = naivegradient(g, p, u0; delta=1e-2)
	@test isapprox(dgdp_zygote[2], dgdp_naive[2]; atol=1e-4, rtol=1e-4)
	@test isapprox(dgdp_zygote[1], dgdp_naive[1]; atol=1e-2, rtol=1e-2)
end end#end begin and let


#!/usr/bin/julia

# Copyright (C) 2021, 2022 Matheus Fernandes Bigolin <mfrdrbigolin@disroot.org>
# SPDX-License-Identifier: MIT

"Day Thirteen, Transparent Origami."

# This certainly was a enjoying day, especially part II.

include("Utils.jl")

using .Utils

"Fold the `dots` according to the `folds`’ instructions."
function fold_dots(dots :: Set{Tuple{Int64, Int64}}, folds :: Array{Tuple{Char, Int64}}) :: Set{Tuple{Int64, Int64}}
    folded_dots = Set{Tuple{Int, Int}}(copy(dots))

    for (axis, pos) in folds
        for (x, y) in folded_dots
            if axis == 'x' && x > pos
                push!(folded_dots, (2*pos - x, y))
                delete!(folded_dots, (x,y))
            elseif axis == 'y' && y > pos
                push!(folded_dots, (x, 2*pos - y))
                delete!(folded_dots, (x,y))
            end
        end
    end

    return folded_dots
end

"Calculate the number of `dots` after one `fold`."
function solve1(dots :: Set{Tuple{Int64, Int64}}, fold :: Tuple{Char, Int64}) :: Int64
    folded_dots = fold_dots(dots, [fold])

    return length(folded_dots)
end

"Apply the `folds` to the `dots` and print the secret code."
function solve2(dots :: Set{Tuple{Int64, Int64}}, folds :: Array{Tuple{Char, Int64}})
    folded_dots = fold_dots(dots, folds)

    max_x, max_y = maximum(first.(folded_dots)), maximum(last.(folded_dots))

    for i in 0:max_y
        for j in 0:max_x
            if (j,i) in folded_dots
                print('█')
            else
                print('⋅')
            end
        end

        println()
    end
end

const DOTS_REX = r"(\d+),(\d+)"
const FOLDS_REX = r"fold along (\w)=(\d+)"

function main()
    usage_and_exit(length(ARGS) != 1)

    input_data = readlines(first(ARGS))

    dots_data = filter(line -> !startswith(line, "fold along") && line != "", input_data)
    folds_data = filter(line -> startswith(line, "fold along"), input_data)

    dots_untyped = [match(DOTS_REX, dot).captures for dot in dots_data]
    dots = Set([Tuple(parse.(Int, dot)) for dot in dots_untyped])

    folds_untyped = [match(FOLDS_REX, fold).captures for fold in folds_data]
    folds = [(collect(axis)[1], parse(Int, pos)) for (axis, pos) in folds_untyped]

    println(solve1(dots, folds[1]))
    solve2(dots, folds)
end

if abspath(PROGRAM_FILE) == @__FILE__
    main()
end


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

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

# ╔═╡ 0053bace-00d4-11eb-1072-cbc1284550c5
# 環境設定
begin
	using Printf
	using Dates
	using PlutoUI
	using Plots
	# using LinearAlgebra # Most of them are in Base
	using RowEchelon # 單元 9．Column Correspondence Theorem

	_date_ = today()
	_time_ = Dates.format(now(), "HH:MM")
	md"""
	## 環境配置
	今日： $(_date_) \
	此時： $(_time_) \
	"""
end
#=
Space before $ does matter ... _O_
[Line breaks in markdown]
(https://gist.github.com/shaunlebron/746476e6e7a4d698b373)
=#

# ╔═╡ 9d9a54ea-ff9c-11ea-1db7-1ba194b9fb3c
md"""
# 線性代數 + Julia + $$\LaTeX$$ 的學習筆記
版本日期：$_date_

整個學習過程將以如下「線性代數」課程為主軸：

### 線性代數 台灣大學電機系 蘇柏青

本課程是線性代數的入門課程。線性代數係以「向量空間」(Vector Space)為核心概念之數學工具，擁有極廣泛之應用，非常值得理工商管等科系大學部同學深入修習，作為日後專業應用之基礎。

課程來源：[http://ocw.aca.ntu.edu.tw/ntu-ocw/index.php/ocw/cou/102S207](http://ocw.aca.ntu.edu.tw/ntu-ocw/index.php/ocw/cou/102S207)

## 學習目標
如下為幾個學習的子目標：

### 學科
- 線性代數 - 重新學習，了解重要概念的中文及英文詞𢑥及應用。

### 工具

- Julia - 深入學習，了解重要套件的應用及使用。
- Pluto - 隨之成長，作為撰寫學習記錄的工具。
- LaTeX - 隨緣學習，作為撰寫學習記錄的工具。
- Markdown - 隨緣學習，作為撰寫學習記錄的工具。

### 服務
- GitHub - 隨緣學習，用來記錄整個學習歷程及分享學習內容。
"""

# ╔═╡ a40a1be4-ff9c-11ea-33d3-8bcf3b73e930
md"## 單元 1．Basic Concepts on Matrices and Vectors"

# ╔═╡ 71136776-fff2-11ea-0eb6-47a04c1c77d6
md"""
### Matrix
$$A=\begin{bmatrix} a_{11} & \dots & a_{1n} \\ \vdots & \ddots & \vdots \\ a_{m1} & \dots & a_{mn}\end{bmatrix}=\begin{bmatrix} a_{ij} \end{bmatrix}=M_{mn}$$
"""

# ╔═╡ a156894a-ffd4-11ea-1e22-29fb46b20097
md"""
### Matrix Addition
$$\begin{bmatrix} 1 & 2 \\ 3 & 4 \\ 5 & 6 \end{bmatrix} + \begin{bmatrix} 1 & 1 \\ 1 & 1 \\ 1 & 2 \end{bmatrix} = \begin{bmatrix} 2 & 3 \\ 4 & 5 \\ 6 & 8 \end{bmatrix}$$
"""

# ╔═╡ 06754e3a-ffe2-11ea-0220-9b066f8651e7
[1 2; 3 4; 5 6]+[1 1; 1 1; 1 2]

# ╔═╡ c50b8e9a-ffe2-11ea-2326-c3b69913b27c
md"""
### Scalar Multiplication
$$cA$$
$$3 \cdot \begin{bmatrix} 1 & 2 \\ 3 & 4 \\ 5 & 6 \end{bmatrix}$$
"""

# ╔═╡ 04823ca4-ffe3-11ea-3fab-0d8eaedb8c9a
3 * [1 2; 3 4; 5 6]

# ╔═╡ b7bcfc2c-ffe4-11ea-2862-432081314a58
3 .* [1 2; 3 4; 5 6]

# ╔═╡ f4077336-ffe4-11ea-2482-81f261e32798
md"""
### Transpose
$$C=\begin{bmatrix} 7 & 9 \\ 18 & 31 \\ 52 & 68 \end{bmatrix} \;\:\Rightarrow\;\; C^T = \begin{bmatrix} 7 & 18 & 52 \\ 9 & 31 & 68 \end{bmatrix}$$
"""

# ╔═╡ 047d2898-ffe5-11ea-241f-6d1064a665e8
let
	C=[7 9; 18 31; 52 68]
	C'
end

# ╔═╡ ea9c0a1c-ffe8-11ea-3df9-c5a82dc5c077
md"""
### Vectors

Row Vector:

$$\begin{bmatrix} 1 & 2 & 3 & 4 \end{bmatrix}$$

Column Vector:

$$\begin{bmatrix} 1 \\ 2 \\ 3 \\ 4 \end{bmatrix}$$

$$\Downarrow$$

$$\begin{bmatrix} 1 & 2 & 3 & 4 \end{bmatrix}^T$$

The ith componet of $$\textbf{v}$$

$$v_i$$

"""

# ╔═╡ 5018d0f0-ffe9-11ea-0ba4-f3ca17a7eddb
[ 1 2 3 4]

# ╔═╡ 676ff59e-ffe9-11ea-22ec-31be78dfa211
[1; 2; 3; 4;]

# ╔═╡ 491d2fd0-ffeb-11ea-009f-4b10a02ba938
[ 1 2 3 4]'

# ╔═╡ 47e73308-fff9-11ea-2cee-7f050938fe19
md"""
### Linear Combination
A $$\textcolor{red}{linear\;combination}\;of\;vectors \;
$$\textbf{u}_1,\textbf{u}_2,\dots,\textbf{u}_k$$ is a vector of the form

$$c_1\textbf{u}_1+c_2\textbf{u}_2+\dots+c_k\textbf{u}_k$$

where $$c_1,c_2,\dots,c_k$$ are scalars. These scalars are called the $$\textcolor{red}{coefficients}$$ of the linear combination.
"""

# ╔═╡ 99e6bccc-fffa-11ea-223a-a3dfd65dee92
md"""
### Standard Vectors

The standard vectors of $$R^n$$ are defined as

$$e_1=\begin{bmatrix} 1 \\ 0 \\ \vdots \\ 0 \end{bmatrix}, e_2=\begin{bmatrix} 0 \\ 1 \\ \vdots \\ 0 \end{bmatrix}, \dots , e_n=\begin{bmatrix} 0 \\ 0 \\ \vdots \\ 1 \end{bmatrix}$$

"""

# ╔═╡ 741db230-fffb-11ea-23aa-63d365767cbd
md"""
### Matrix-Vector Product
$$Av=v_1a_1+v_2a_2+\dots+v_na_n$$
"""

# ╔═╡ c497553e-fffb-11ea-3559-e59103e843e2
let
	A=[1 2; 3 4; 5 6]
	v=[7;8]
	A*v
end

# ╔═╡ aff2680a-fffe-11ea-0494-1d349105eff0
md"""
### Identity Matrix
$$I_3=\begin{bmatrix} 1 & 0 & 0\\ 0 & 1 & 0 \\ 0 & 0 & 1\end{bmatrix}$$

"""

# ╔═╡ 42f11192-ffff-11ea-34ca-81545f4b5374
md"""
### Stochastic Matrix
$$A=\begin{bmatrix} 0.85 & 0.03 \\ 0.15 & 0.97 \end{bmatrix}$$
"""

# ╔═╡ 988b781e-026f-11eb-02b7-8d2de841d478
begin
	u01xslider = @bind u01x Slider(1:100; default=40, show_value=true)
	u01cslider = @bind u01c Slider(0:1200; default=1200, show_value=true)
	u01sslider = @bind u01s Slider(0:1200; default=0, show_value=true)
	md"""
	Slide to set number of **years**: $(u01xslider)

	Slide to set population of **city**: $(u01cslider)

	Slide to set population of **suburban**: $(u01sslider)
	"""
end

# ╔═╡ cb2c5e54-ffff-11ea-3e4e-a349d0a09341
let
	x=u01x # Number of Years (x)
	pc=u01c # Population of City
	ps=u01s # Population of Suburban
	A=[0.85 0.03; 0.15 0.97]
	#=
	# p0 Population in year 0
	p0=[500; 700]
	p1=A*p0
	p2=A*(p1)
	p3=A*(p2)
	p4=A*(p3)
	p5=A*(p4)
	x=0:5
	Y=hcat(p0, p1, p2, p3, p4, p5)
	plot(x, Y', title = "Population", label = ["City" "Suburban"])
	=#
	p=[pc; ps]
	Y=p
	for i in 1:x
		p=A*p
		Y=hcat(Y, p)
	end
	plot(0:x, Y', title = "Population Trend", label = ["City" "Suburban"])
end

# ╔═╡ 67ed416e-00d1-11eb-29cc-23eaa30c3446
html"""

"""

# ╔═╡ afcd29d0-ff9c-11ea-09bf-e5a0ed087c42
md"## 單元 2．System of Linear Equations"

# ╔═╡ 8c300b8a-ffd5-11ea-3ec9-27ccc82cea6e
# \backslash \setminus
md"""
### System of Linear Equations
$$A=\begin{bmatrix} 1 & -2 & -1 \\ 3 & -6 & -5 \\ 2 & -1 & 1 \end{bmatrix}
\;\;
b=\begin{bmatrix} 3 \\ 3\\ 0 \end{bmatrix}$$
$$Ax=b$$

Solves Ax = b by (essentially) Gaussian elimination (Julia \ Operator):

$$x=A\;\backslash\;b$$
"""

# ╔═╡ 70803966-ff9c-11ea-0a8a-d7860f4f87c0
# Solve System of Linear Equations
let
	with_terminal() do
		A=[1 -2 -1; 3 -6 -5; 2 -1 1]
		b=[3; 3; 0]
		x=A \ b
		println(x)
		println(A*x)
		println(A*x == b)
	end
end

# ╔═╡ 6a110912-0009-11eb-0a73-0b2e2ec0b634
md"""
### Row Echelon Form & Reduced Row Echelon Form
"""

# ╔═╡ 4b8f75e0-000a-11eb-175b-d7a9102f65ca
# Solve System of Linear Equations
let
	with_terminal() do
		A=[1 -3 0 2 0; 0 0 1 6 0; 0 0 0 0 1; 0 0 0 0 0]
		b=[7; 9; 2; 0]
		x=A \ b
		println(x)
		println(A*x)
		println(A*x == b)
	end
end

# ╔═╡ 1f9799ea-00d2-11eb-1b94-d7d3056a2c41
html"""

"""

# ╔═╡ ecfe3ef0-007a-11eb-3efb-7740876167a8
md"""
## 單元 3．Gaussian Elimination

實作參考：

[Gaussian-elimination.pdf](http://web.mit.edu/18.06/www/Spring17/Gaussian-elimination.pdf)

[Numerical Analysis by Julia Series 1 — Gauss Elimination | by Treee July | Medium]
(https://medium.com/@julytreee/numerical-analysis-by-julia-series-1-gauss-elimination-68e902a43c7e)
"""

# ╔═╡ b76720a6-00d2-11eb-19ae-d59e7204a005
md"""
對列及行的參照：
"""

# ╔═╡ 0c9dd736-00d8-11eb-077a-67cf34db152d
let
	A=[ 1 2 3; 4 5 6; 7 8 9]
end

# ╔═╡ 2fa8b248-00d6-11eb-0f85-871512ef16d2
let
	o=[]
	# Matrix
	A=[ 1 2 3; 4 5 6; 7 8 9]
	push!(o, @sprintf("A: %s", A))
	# Elements
	push!(o, @sprintf("A[1, 1]: %s", A[1, 1]))
	push!(o, @sprintf("A[end, end]: %s", A[end, end]))
	# Row
	r1=A[1,:]
	push!(o, @sprintf("r1: %s", r1))
	∑Ai=A[1,:]+A[2,:]+A[3,:]
	push!(o, @sprintf("∑Ai: %s", ∑Ai))
	# Column
	c1=A[:,1]
	push!(o, @sprintf("c1: %s", c1))
	∑Aj=A[:,1]+A[:,2]+A[:,3]
	push!(o, @sprintf("∑Aj: %s", ∑Aj))
	with_terminal(dump, o)
end

# ╔═╡ 479e95e2-0274-11eb-2c2a-993ba9119fba
let
	with_terminal() do
		println("👍 選定之輸出方案： 1) 容易以 do ... end 區塊包裝 3) 轉貼程式碼到他處不用修改\n")
		# Get Current Time
		command=`date`
		run(command)
		# Matrix
		A=[ 1 2 3; 4 5 6; 7 8 9]
		print("A: "); dump(A)
		# Elements
		print("A[1, 1]: "); dump(A[1, 1])
		print("A[end, end]: "); dump(A[end, end])
		# Row
		r1=A[1,:]
		print("r1: ");	dump(r1)
		∑Ai=A[1,:]+A[2,:]+A[3,:]
		print("∑Ai: "); dump(∑Ai)
		# Column
		c1=A[:,1]
		print("c1: ");	dump(c1)
		∑Aj=A[:,1]+A[:,2]+A[:,3]
		print("∑Aj: "); dump(∑Aj)
		println()
		run(`cal -h`)
	end
end

# ╔═╡ 816cc338-03a1-11eb-2872-c7643faab770
let
	Text() do io
		# Matrix
		A=[ 1 2 3; 4 5 6; 7 8 9]
		print(io, "A: "); dump(io, A)
		# Elements
		print(io, "A[1, 1]: "); dump(io, A[1, 1])
		print(io, "A[end, end]: "); dump(io, A[end, end])
		# Row
		r1=A[1,:]
		print(io, "r1: ");	dump(io, r1)
		∑Ai=A[1,:]+A[2,:]+A[3,:]
		print(io, "∑Ai: "); dump(io, ∑Ai)
		# Column
		c1=A[:,1]
		print(io, "c1: ");	dump(io, c1)
		∑Aj=A[:,1]+A[:,2]+A[:,3]
		print(io, "∑Aj: "); dump(io, ∑Aj)
	end
end

# ╔═╡ ee3ed802-03a3-11eb-025c-5bee178511af
let
		# Matrix
		A=[ 1 2 3; 4 5 6; 7 8 9]
		# Row
		r1=A[1,:]
		∑Ai=A[1,:]+A[2,:]+A[3,:]
		# Column
		c1=A[:,1]
		∑Aj=A[:,1]+A[:,2]+A[:,3]
	md"""
	A: $(Text(A))

	A[1, 1]: $(Text(A[1, 1]))

	A[end, end]: $(Text(A[end, end]))

	r1: $(Text(r1))

	∑Ai: $(Text(∑Ai))

	c1: $(Text(c1))

	∑Aj: $(Text(∑Aj))
	"""
end

# ╔═╡ f7de8296-0393-11eb-0782-57e9272ad999
md"""
## 單元 4．The language of set theory
"""

# ╔═╡ 35ec879e-03ae-11eb-15ce-016ae8a89168
md"""
#### Subset
$$Let\; S_1=\{a, b, c, d, e\},\; S_2={a, b, e}$$$$
S_2 ⊂ S_1\;means\;$$$$
∀x ∈ S_2,\:x\;is\;also ∈ S_1.$$
"""

# ╔═╡ cfaeab08-03b2-11eb-3930-ddb92e799789
let
	with_terminal() do
		s1=Set(["a", "b", "c", "d", "e"])
		println("🖐 Give Me Five, 原來集合在 Julia 裹頭是長這樣子喔！☯")
		dump(s1)
		println(s1)
		s2=Set(["a", "b", "e"])
		println(s2)
		# subset
		println(⊆(s2, s1))
		# union set
		println(∪(s1, s2))
		# intersection set
		println(∩(s1, s2))
		# difference set
		println(setdiff(s1, s2))
		println(setdiff(s2, s1))
	end
end

# ╔═╡ 0445c5ba-03cb-11eb-2093-b71bae438a19
md"""
## 單元 5．Span of a Set of Vectors
"""

# ╔═╡ 698fc53a-03d5-11eb-39b7-a776fabae2f2
md"""
實作參考：

[Linear Algebra – Quantitative Economics with Julia](https://julia.quantecon.org/tools_and_techniques/linear_algebra.html)
"""

# ╔═╡ 408f674e-03d5-11eb-3dc8-ed1addf002be
let
	x_vals = [0 0 0 ; 2 -3 -4]
	y_vals = [0 0 0 ; 4 3 -3.5]

	plot(x_vals, y_vals, arrow = true, color = :blue,
	     legend = :none, xlims = (-5, 5), ylims = (-5, 5),
	     annotations = [(2.2, 4.4, "[2, 4]"),
	                    (-3.3, 3.3, "[-3, 3]"),
	                    (-4.4, -3.85, "[-4, -3.5]")],
	     xticks = -5:1:5, yticks = -5:1:5,
	     framestyle = :origin)
end

# ╔═╡ 73360416-03da-11eb-1721-cf76aa462151
begin
	u05islider = @bind u05i Slider(1:5; default=1, show_value=true)
	u05jslider = @bind u05j Slider(1:5; default=5, show_value=true)
	md"""
	Slide to set **i**: $(u05islider)

	Slide to set **j**: $(u05jslider)
	"""
end

# ╔═╡ ac04f844-03d8-11eb-2db2-dbc19b343325
let
	i=u05i
	j=u05j
	# fixed linear function, to generate a plane
	f(x, y) = 0.2x + 0.1y

	# lines to vectors
	x_vec = [0 0 0 0 0; 3 3 -4 -4 3.5]
	y_vec = [0 0 0 0 0; 4 -4 -4 4 0]
	z_vec = [0 0 0 0 0; f(3, 4) f(3, -4) f(-4,-4) f(-4, 4) f(3.5, 0)]
	color = [:blue :green :red :brown :cyan]

	# draw the plane
	n = 20
	grid = range(-5, 5, length = n)
	z2 = [ f(grid[row], grid[col]) for row in 1:n, col in 1:n ]
	# wireframe(grid, grid, z2, fill = :blues, gridalpha =1 )
	plot(grid, grid, z2, fill = :blues, gridalpha = 1, lindwidth = 0.5, seriestype = :wireframe)
	# plot(grid, grid, z2, fill = :blues, gridalpha = 1, lindwidth = 0.5, seriestype = :surface)
	# Dots
	# plot!([0; 4; 4; -4; -4], [0; 4; -4; 4; -4], [-1.5; -1.5; -1.5; -1.5; -1.5], labels = "", seriestype = :scatter3d)
	p = [ 0 0 -1.5; 4 4 -1.5; 4 -4 -1.5; -4 4 -1.5; -4 -4 -1.5]' # Transpose
	plot!(p[1, i:j], p[2, i:j], p[3, i:j], labels = "", seriestype = :scatter3d)
	plot!(p[1, i:j], p[2, i:j], p[3, i:j], labels = "", seriestype = :path3d)
	# Vectors
	plot!(x_vec[:, i:j], y_vec[:, i:j], z_vec[:, i:j], color = color[:,i:j], linewidth = 3, xlabel = "x", ylabel = "y", zlabel = "z", labels = "", colorbar = false)
	# plot!(x_vec, y_vec, z_vec, color = color[:,i:j], linewidth = 3, xlabel = "x", ylabel = "y", zlabel = "z", labels = "", colorbar = false)
end

# ╔═╡ 8133c7e0-03d8-11eb-0697-afe2082ed481
md"""
### 單元 6．Linear Dependence and Linear Independence
"""

# ╔═╡ 82e1b674-03df-11eb-26e4-6f09a0a832d8
let
	with_terminal() do
		A=[1 2 -1; -1 1 -8; 2 -1 13; 1 -1 8]
		b=[0; 1; -2; 1]
		x=A \ b
		println(x)
		println(A*x)
		println(A*x == b)
	end
end

# ╔═╡ 55de62fc-03e8-11eb-2ade-6500eacc11c8
let
	with_terminal() do
		A=[1 -4 2 -1 2; 2 -8 3 2 1]
		b=[0; 0]
		x=A \b
		println(x)
		println(A*x)
		println(A*x == b)
	end
end

# ╔═╡ 69edd6d6-0469-11eb-1a3a-69b7f222541c
md"""
## 單元 7．Matrix Multiplication
"""

# ╔═╡ 4e3a788a-04b5-11eb-3574-e53d1db76173
md"""
### Matrix Multiplication
$$\,
\begin{align*}
Let\;v, x, y &∈ R^n. Suppose\;A\;and\;B\;are\;n × n\;matrices. \\
x &= Bv \\
y &= Ax \\
&\Downarrow\\
y &= Cv = A(Bv) = (AB)v
\end{align*}
\,$$
"""

# ╔═╡ c95c00b4-046b-11eb-09e0-69068c23a0e0
let
	with_terminal() do
		A=[1 2; 1 1]
		B = [ 1 1; 1 -1]
		dump(A * B)
		dump(B * A)
	end
end

# ╔═╡ a7edc48a-046e-11eb-2920-73dd6ab80b73
md"""
## 單元 8．Invertibility and Elmentary Matrices
"""

# ╔═╡ b6f85278-046e-11eb-25db-e71ea510fe6a
md"""
### Inverse
$$\,
\begin{align*}
&\text{An n × n matrix A is called invertible if there exists an n × n matrix B such
that }\\
\\
&AB = BA = I_n.\\
\\
&\text{In this case, B is called an inverse of A.}
\end{align*}
\,$$
"""

# ╔═╡ c42784e8-0483-11eb-2105-41108f06910e
# use of & to state the alignment points.
# Why does \\ not return a new line in an equation? - TeX - LaTeX Stack Exchange
# https://tex.stackexchange.com/questions/194236/why-does-not-return-a-new-line-in-an-equation
md"""
$$\,
\begin{matrix}
\begin{align*}
A&=\begin{bmatrix} 1 & 2 \\ 3 & 5 \end{bmatrix}
\\
B&=\begin{bmatrix} -5 & 2 \\ 3 & -1 \end{bmatrix}
\end{align*}
&
\Rightarrow
&
\begin{split}
AB=
\begin{bmatrix} 1 & 2 \\ 3 & 5 \end{bmatrix}
\begin{bmatrix} -5 & 2 \\ 3 & -1 \end{bmatrix}=\begin{bmatrix} 1 & 0 \\ 0 & 1 \end{bmatrix}=I_2
\\
BA=
\begin{bmatrix} -5 & 2 \\ 3 & -1 \end{bmatrix}
\begin{bmatrix} 1 & 2 \\ 3 & 5 \end{bmatrix}=\begin{bmatrix} 1 & 0 \\ 0 & 1\end{bmatrix}=I_2
\end{split}
\end{matrix}
\,$$
"""

# ╔═╡ 6a0af07c-0482-11eb-287b-8d97045e4f36
let
	with_terminal() do
		A=[1 2; 3 5]
		B=inv(A)
		dump(A)
		dump(B)
		dump(round.(B))
		dump.(round.(A*B))
		dump.(round.(B*A))
	end
end

# ╔═╡ 7cadabce-0483-11eb-1a22-d7fcb40f6fe9
md"""
疑：為什麼只是近似的值？因為 3?
"""

# ╔═╡ 061630b8-0496-11eb-1646-137d406b7c37
md"""
## 單元 9．Column Correspondence Theorem
"""

# ╔═╡ d32ddc0a-0497-11eb-14e8-81331c56ebd8
md"""
實作參考：

[blegat/RowEchelon.jl: Small package containing the rref fonction for computing the reduced row echelon form of the matrix A]
(https://github.com/blegat/RowEchelon.jl)
> 可做為 module 及 RREF 計算實作參考。
> 提供的 function 為
> rref, rref!
> rref_with_pivots, rref_with_pivots!,
"""

# ╔═╡ 5f220836-049a-11eb-389c-b75fa5bb21d7
md"""
### Reduced Row Echelon Form (RREF)
"""

# ╔═╡ 86827bfe-049a-11eb-241c-cbbefed2bd08
let
	with_terminal() do
		println("Using RREF:")
		A=[ 1 2 -1 2 1 2; -1 -2 1 2 3 6; 2 4 -3 2 0 3; -3 -6 2 0 3 9]
		dump(A)
		println(Text(A))
		B=rref(A)
		dump(round.(B))
		println(Text(round.(B)))

		println("Using \\:")
		A1=A[:, 1:5]
		b1=A[:, 6]
		x=A1 \ b1
		dump(round.(x))
		println(Text(round.(x)))
		println(Text(round.(A1*x)))

		println("Using \\ with RREF:")
		A2=B[:, 1:5]
		b2=B[:, 6]
		y=A2 \ b2
		dump(round.(y))
		println(Text(round.(y)))
		println(Text(round.(A2*y)))
	end
end

# ╔═╡ 5db63852-04ac-11eb-09e2-b3a09a28fe5a
md"""
## 單元 10．The Inverse of a Matrix
"""

# ╔═╡ 68240db4-04ac-11eb-3e83-819f3efb8c66
md"""
### Matrix Inversion
"""

# ╔═╡ 2ae45a8a-04b1-11eb-195e-1399a9b27328
md"""
$$\,
\begin{bmatrix} \color{blue}{A} & I_3 \end{bmatrix}=
\begin{bmatrix}
\begin{array}{ccc|ccc}1 & 2 & 3 & \color{red}{1} & 0 & 0 \\ 2 & 5 & 6 & 0 & \color{red}{1} & 0\\ 3 & 4 & 8 & 0 & 0 & \color{red}{1}\end{array}
\end{bmatrix}
\rightarrow
\begin{bmatrix}
\begin{array}{ccc|ccc}\color{red}{1} & 0 & 0 & -16 & 4 & 3 \\ 0 & \color{red}{1} & 0 & -2 & 1 & 0\\ 0 & 0 & \color{red}{1} & 7 & -2 & -1\end{array}
\end{bmatrix}
=
\begin{bmatrix} I_3 & \color{blue}{B}\end{bmatrix}
\,$$
"""

# ╔═╡ 9642ea30-04ac-11eb-0a86-b7d63a7594d7
let
	with_terminal() do
		AI=[ 1 2 3 1 0 0; 2 5 6 0 1 0; 3 4 8 0 0 1]
		println(AI)
		IB=round.(rref(AI))
		println(IB)
		C=IB[:, 4:6]
		println(C)
		D=round.(inv(AI[:, 1:3]))
		println(D)
	end
end

# ╔═╡ 97f12ddc-00cf-11eb-173d-47a7931a9a08
md"""
### <<<
"""

# ╔═╡ 4a9f304c-0543-11eb-3b96-5fb9fb043444
md"""
## 附錄：Pluto 遊樂場/Playground
"""

# ╔═╡ c13b4d10-0545-11eb-178c-9fbfe174d4a6
md"""
### 注意 \$\$ 及 \$
"""

# ╔═╡ 5367d0b2-0543-11eb-3810-b31a6e903242
let
	x=1
	y=2
	z=3
	md"""
	#### No space before $
	x:$x \
	y:$y \
	z:$z
	"""
end

# ╔═╡ 1e622640-0545-11eb-20fc-29bed94aac0c
let
	x=1
	y=2
	z=3
	md"""
	#### With space before $
	x: $x \
	y: $y \
	z: $z
	"""
end

# ╔═╡ b3f4edae-054b-11eb-02b5-199ce15d9620
let
	_test_ = "x^2"
	# 在表格中沒有變數替換，有公式替換
	md"""
	### 測試 \$\$ 及 \$ 混用
	|變數|數值|
	|:---|:---|
	|今日| $(_date_) |
	|其他| $(_date_) |
	|函數| $$x^3$$ $(_test_)  |
	"""
	# 只好用這個版本，先公式替換然後變數替換
	md"""
	## 測試 \$\$ 及 \$ 混用
	今日： $(_date_) \
	其他： $(_date_) \
	函數： $$x^3$$ $(_test_)
	"""
end

# ╔═╡ e4b8f094-04c8-11eb-3bf7-93bd85db2dc9
md"""
## 附錄：$$\LaTeX$$ 遊樂場/Playground
"""

# ╔═╡ 5897b63a-04c9-11eb-34ab-b908c731d3d5
#=
\begin{equation*}
multline, gather, align, aligned[t|b|c], alignat{9}, align*, split
\end{equation*}
=#
md"""
實作參考：

[User’s Guide for the amsmath Package]
(https://www.latex-project.org/help/documentation/amsldoc.pdf)

$$\,
\begin{multline}
a+b+c+d+e+f\\
+i+j+k+l+m+n
\end{multline}
$$$$
\begin{gather}
a_1=b_1+c_1\\
a_2=b_2+c_2-d_2+e_2
\end{gather}
$$$$
\begin{align}
a_1& =b_1+c_1\\
a_2& =b_2+c_2-d_2+e_2
\end{align}
$$$$
\left.
\begin{aligned}[b]
&{\color{red} \text{Text in red}} \\
&{\color{blue} \text{Text in blue}}\\
&\text{Text with equation $a_{11}, a_{12},\dots$}\\
&A=πr^2\\
&c^2=a^2+b^2\\
&x
\end{aligned}
\big\}\quad\text{The formula}
\Bigg( 1+2 \Bigg]
F = G \left( \frac{m_1 m_2}{r^2} \right)
\right\} \text{This is it!}
$$$$
\begin{align}
x&=y & X&=Y & a&=b+c\\
x’&=y’ & X’&=Y’ & a’&=b\\
x+x’&=y+y’ & X+X’&=Y+Y’ & a’b&=c’b
\end{align}
$$$$
\begin{alignat}{2}
x& = y_1-y_2+y_3-y_5+y_8-\dots
&\quad& \text{by \eqref{eq:C}}\\
& = y’\circ y^* && \text{by \eqref{eq:D}}\\
& = y(0) y’ && \text {by Axiom 1.}
\end{alignat}
$$$$
\left.\begin{aligned}
B’&=-\partial\times E,\\
E’&=\partial\times B - 4\pi j,
\end{aligned}
\right\}
\qquad \text{Maxwell’s equations}
$$$$
P_{r-j}=\begin{cases}
0& \text{if $r-j$ is odd},\\
r!\,(-1)^{(r-j)/2}& \text{if $r-j$ is even}.
\end{cases}
\,$$
"""

# ╔═╡ 51c9222c-0518-11eb-3827-8fcf8a59b0fd
md"""
## 附錄：Markdown 遊樂場/Playground
"""

# ╔═╡ 602f56ce-0518-11eb-27d3-7fde99127b36
md"""
實作參考：

[Markdown · The Julia Language]
(https://docs.julialang.org/en/v1/stdlib/Markdown/)

```math
f(a) = \frac{1}{2\pi}\int_{0}^{2\pi} (\alpha+R\cos(\theta))d\theta
```

A paragraph containing some ``\LaTeX`` markup.
```
Ax=b
```
```math
Ax=b
```
Some Markdown text with some *blue* text.

註：[How to apply color in Markdown? - Stack Overflow]
(https://stackoverflow.com/questions/35465557/how-to-apply-color-in-markdown)
"""

# ╔═╡ 7dbac3ec-00d1-11eb-2c17-3bab40ffaa2e
html"""

"""

# ╔═╡ 770ab5ac-fff8-11ea-1ed1-87b0ae3aca70
md"""
## 參考資料
### Linear Algebra

[ ] [線性代數 - 臺大開放式課程 (NTU OpenCourseWare)]
(http://ocw.aca.ntu.edu.tw/ntu-ocw/index.php/ocw/cou/102S207/3)

[ ] [Introduction to Applied Linear Algebra – Vectors, Matrices, and Least Squares]
(http://vmls-book.stanford.edu/)
> [Julia language companion](http://vmls-book.stanford.edu/vmls-julia-companion.pdf)

### Julia

[ ] [Introduction to Julia]
(https://juliaacademy.com/courses/enrolled/375479)

> [ ] Advanced topics

[ ] [Julia for Data Science]
(https://juliaacademy.com/courses/enrolled/937702)

[ ] [18.S191 Introduction to Computational Thinking]
(https://computationalthinking.mit.edu/Fall20/)

[Linear Algebra – Quantitative Economics with Julia]
(https://julia.quantecon.org/tools_and_techniques/linear_algebra.html)

> [QuantEcon.cheatsheet/julia-cheatsheet.pdf](https://github.com/QuantEcon/QuantEcon.cheatsheet/blob/master/julia/julia-cheatsheet.pdf)

[cheatsheets/plotsjl-cheatsheet.pdf]
(https://github.com/sswatson/cheatsheets/blob/master/plotsjl-cheatsheet.pdf)

[Unicode Input · The Julia Language]
(https://docs.julialang.org/en/v1/manual/unicode-input/)

[Visualizing Graphs in Julia using Plots and PlotRecipes – Tom Breloff]
(http://www.breloff.com/Graphs/)

### Pluto
[fonsp/Pluto.jl: 🎈 Simple reactive notebooks for Julia]
(https://github.com/fonsp/Pluto.jl)

[Docstrings · PlutoUI.jl]
(https://juliahub.com/docs/PlutoUI/abXFp/0.6.3/autodocs/)

### $$\LaTeX$$

[LaTeX Documentation]
(https://www.latex-project.org/help/documentation/)

[User’s Guide for the amsmath Package]
(https://www.latex-project.org/help/documentation/amsldoc.pdf)

[LaTeX syntax · Documenter.jl]
(https://juliadocs.github.io/Documenter.jl/v0.7/man/latex.html)

[Documentation - Overleaf, Online LaTeX Editor]
(https://www.overleaf.com/learn/latex/Main_Page)

[LaTeX - Mathematical Python]
(https://www.math.ubc.ca/~pwalls/math-python/jupyter/latex/)

[LaTeX help 1.1 - Table of Contents]
(http://www.emerson.emory.edu/services/latex/latex_toc.html)

[List of mathematical symbols - Wikiwand]
(https://www.wikiwand.com/en/List_of_mathematical_symbols)

### Markdown
[Markdown Cheatsheet · adam-p/markdown-here Wiki]
(https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet)

[Markdown · The Julia Language]
(https://docs.julialang.org/en/v1/stdlib/Markdown/)

### GitHub

[ ] [Hello World · GitHub Guides]
(https://guides.github.com/activities/hello-world/)

### 其他
[三度辭典網 > 術語中英雙語詞典]
(https://www.3du.tw/term/)
"""

# ╔═╡ b5016eda-0522-11eb-1d8b-37135a22c7bf
md"""
## 感謝建議
"""

# ╔═╡ e9001c9a-0522-11eb-0d63-f718249e03ec

# ╔═╡ Cell order:
# ╟─9d9a54ea-ff9c-11ea-1db7-1ba194b9fb3c
# ╟─0053bace-00d4-11eb-1072-cbc1284550c5
# ╟─a40a1be4-ff9c-11ea-33d3-8bcf3b73e930
# ╟─71136776-fff2-11ea-0eb6-47a04c1c77d6
# ╟─a156894a-ffd4-11ea-1e22-29fb46b20097
# ╠═06754e3a-ffe2-11ea-0220-9b066f8651e7
# ╟─c50b8e9a-ffe2-11ea-2326-c3b69913b27c
# ╠═04823ca4-ffe3-11ea-3fab-0d8eaedb8c9a
# ╠═b7bcfc2c-ffe4-11ea-2862-432081314a58
# ╟─f4077336-ffe4-11ea-2482-81f261e32798
# ╠═047d2898-ffe5-11ea-241f-6d1064a665e8
# ╟─ea9c0a1c-ffe8-11ea-3df9-c5a82dc5c077
# ╠═5018d0f0-ffe9-11ea-0ba4-f3ca17a7eddb
# ╠═676ff59e-ffe9-11ea-22ec-31be78dfa211
# ╠═491d2fd0-ffeb-11ea-009f-4b10a02ba938
# ╟─47e73308-fff9-11ea-2cee-7f050938fe19
# ╟─99e6bccc-fffa-11ea-223a-a3dfd65dee92
# ╟─741db230-fffb-11ea-23aa-63d365767cbd
# ╠═c497553e-fffb-11ea-3559-e59103e843e2
# ╟─aff2680a-fffe-11ea-0494-1d349105eff0
# ╟─42f11192-ffff-11ea-34ca-81545f4b5374
# ╟─988b781e-026f-11eb-02b7-8d2de841d478
# ╠═cb2c5e54-ffff-11ea-3e4e-a349d0a09341
# ╟─67ed416e-00d1-11eb-29cc-23eaa30c3446
# ╟─afcd29d0-ff9c-11ea-09bf-e5a0ed087c42
# ╟─8c300b8a-ffd5-11ea-3ec9-27ccc82cea6e
# ╠═70803966-ff9c-11ea-0a8a-d7860f4f87c0
# ╟─6a110912-0009-11eb-0a73-0b2e2ec0b634
# ╠═4b8f75e0-000a-11eb-175b-d7a9102f65ca
# ╟─1f9799ea-00d2-11eb-1b94-d7d3056a2c41
# ╟─ecfe3ef0-007a-11eb-3efb-7740876167a8
# ╟─b76720a6-00d2-11eb-19ae-d59e7204a005
# ╠═0c9dd736-00d8-11eb-077a-67cf34db152d
# ╠═2fa8b248-00d6-11eb-0f85-871512ef16d2
# ╠═479e95e2-0274-11eb-2c2a-993ba9119fba
# ╠═816cc338-03a1-11eb-2872-c7643faab770
# ╠═ee3ed802-03a3-11eb-025c-5bee178511af
# ╟─f7de8296-0393-11eb-0782-57e9272ad999
# ╟─35ec879e-03ae-11eb-15ce-016ae8a89168
# ╠═cfaeab08-03b2-11eb-3930-ddb92e799789
# ╠═0445c5ba-03cb-11eb-2093-b71bae438a19
# ╠═698fc53a-03d5-11eb-39b7-a776fabae2f2
# ╠═408f674e-03d5-11eb-3dc8-ed1addf002be
# ╠═73360416-03da-11eb-1721-cf76aa462151
# ╠═ac04f844-03d8-11eb-2db2-dbc19b343325
# ╟─8133c7e0-03d8-11eb-0697-afe2082ed481
# ╠═82e1b674-03df-11eb-26e4-6f09a0a832d8
# ╠═55de62fc-03e8-11eb-2ade-6500eacc11c8
# ╟─69edd6d6-0469-11eb-1a3a-69b7f222541c
# ╠═4e3a788a-04b5-11eb-3574-e53d1db76173
# ╠═c95c00b4-046b-11eb-09e0-69068c23a0e0
# ╟─a7edc48a-046e-11eb-2920-73dd6ab80b73
# ╟─b6f85278-046e-11eb-25db-e71ea510fe6a
# ╟─c42784e8-0483-11eb-2105-41108f06910e
# ╠═6a0af07c-0482-11eb-287b-8d97045e4f36
# ╟─7cadabce-0483-11eb-1a22-d7fcb40f6fe9
# ╟─061630b8-0496-11eb-1646-137d406b7c37
# ╠═d32ddc0a-0497-11eb-14e8-81331c56ebd8
# ╟─5f220836-049a-11eb-389c-b75fa5bb21d7
# ╠═86827bfe-049a-11eb-241c-cbbefed2bd08
# ╟─5db63852-04ac-11eb-09e2-b3a09a28fe5a
# ╟─68240db4-04ac-11eb-3e83-819f3efb8c66
# ╟─2ae45a8a-04b1-11eb-195e-1399a9b27328
# ╠═9642ea30-04ac-11eb-0a86-b7d63a7594d7
# ╠═97f12ddc-00cf-11eb-173d-47a7931a9a08
# ╟─4a9f304c-0543-11eb-3b96-5fb9fb043444
# ╟─c13b4d10-0545-11eb-178c-9fbfe174d4a6
# ╠═5367d0b2-0543-11eb-3810-b31a6e903242
# ╠═1e622640-0545-11eb-20fc-29bed94aac0c
# ╠═b3f4edae-054b-11eb-02b5-199ce15d9620
# ╟─e4b8f094-04c8-11eb-3bf7-93bd85db2dc9
# ╠═5897b63a-04c9-11eb-34ab-b908c731d3d5
# ╟─51c9222c-0518-11eb-3827-8fcf8a59b0fd
# ╠═602f56ce-0518-11eb-27d3-7fde99127b36
# ╟─7dbac3ec-00d1-11eb-2c17-3bab40ffaa2e
# ╠═770ab5ac-fff8-11ea-1ed1-87b0ae3aca70
# ╟─b5016eda-0522-11eb-1d8b-37135a22c7bf
# ╠═e9001c9a-0522-11eb-0d63-f718249e03ec


# ---
# title: 1627. Graph Connectivity With Threshold
# id: problem1627
# author: Tian Jun
# date: 2020-10-31
# difficulty: Hard
# categories: Math, Union Find
# link: <https://leetcode.com/problems/graph-connectivity-with-threshold/description/>
# hidden: true
# ---
#
# We have `n` cities labeled from `1` to `n`. Two different cities with labels
# `x` and `y` are directly connected by a bidirectional road if and only if `x`
# and `y` share a common divisor **strictly greater** than some `threshold`.
# More formally, cities with labels `x` and `y` have a road between them if
# there exists an integer `z` such that all of the following are true:
#
#   * `x % z == 0`,
#   * `y % z == 0`, and
#   * `z > threshold`.
#
# Given the two integers, `n` and `threshold`, and an array of `queries`, you
# must determine for each `queries[i] = [ai, bi]` if cities `ai` and `bi` are
# connected (i.e. there is some path between them).
#
# Return _an array_`answer` _, where_`answer.length == queries.length`
# _and_`answer[i]` _is_`true` _if for the_`ith` _query, there is a path
# between_`ai` _and_`bi` _, or_`answer[i]` _is_`false` _if there is no path._
#
#
#
# **Example 1:**
#
# ![](https://assets.leetcode.com/uploads/2020/10/09/ex1.jpg)
#
#
#
#     Input: n = 6, threshold = 2, queries = [[1,4],[2,5],[3,6]]
#     Output: [false,false,true]
#     Explanation: The divisors for each number:
#     1:   1
#     2:   1, 2
#     3:   1, _3_
#     4:   1, 2, _4_
#     5:   1, _5_
#     6:   1, 2, _3_ , _6_
#     Using the underlined divisors above the threshold, only cities 3 and 6 share a common divisor, so they are the
#     only ones directly connected. The result of each query:
#     [1,4]   1 is not connected to 4
#     [2,5]   2 is not connected to 5
#     [3,6]   3 is connected to 6 through path 3--6
#
#
# **Example 2:**
#
# ![](https://assets.leetcode.com/uploads/2020/10/10/tmp.jpg)
#
#
#
#     Input: n = 6, threshold = 0, queries = [[4,5],[3,4],[3,2],[2,6],[1,3]]
#     Output: [true,true,true,true,true]
#     Explanation: The divisors for each number are the same as the previous example. However, since the threshold is 0,
#     all divisors can be used. Since all numbers share 1 as a divisor, all cities are connected.
#
#
# **Example 3:**
#
# ![](https://assets.leetcode.com/uploads/2020/10/17/ex3.jpg)
#
#
#
#     Input: n = 5, threshold = 1, queries = [[4,5],[4,5],[3,2],[2,3],[3,4]]
#     Output: [false,false,false,false,false]
#     Explanation: Only cities 2 and 4 share a common divisor 2 which is strictly greater than the threshold 1, so they are the only ones directly connected.
#     Please notice that there can be multiple queries for the same pair of nodes [x, y], and that the query [x, y] is equivalent to the query [y, x].
#
#
#
#
# **Constraints:**
#
#   * `2 <= n <= 104`
#   * `0 <= threshold <= n`
#   * `1 <= queries.length <= 105`
#   * `queries[i].length == 2`
#   * `1 <= ai, bi <= cities`
#   * `ai != bi`
#
#
## @lc code=start
using LeetCode

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


# Input argument x specifies the initial iterate of the optimization
# algorithm. When x = -1, or when any of the entries of x are
# negative, the default setting for x is used.
#
# Since the MOSEK solver for the quadratic subproblem sometimes does
# not tolerate iterates in which most of the entries are nonzero
# ("dense" vectors), the default initial estimate for qpsubprob =
# "mosek" is a sparse vector in which only the first two entries are
# nonzero.
function mixsqp(L; x = -1,
                convergence_tol = 1e-8, eps_tol = 1e-8, sparse_tol = 1e-3,
                lowrank_method = "svd", lowrank_criterion = "by_rank",
                lowrank_rtol = 1e-8, lowrank_rank = minimum(size(L)),
                maxiter = 100, maxqpiter = 100,
                nullprior = 0, gradient_calc = "approx",
                linesearch = true, verbose = true)

# Get the number of rows (n) and columns (k) of L.
  n = size(L,1);
  k = size(L,2);

  # If any entries of input x are negative, set the initial estimate
  # to the default setting.
  #
  # When the MOSEK solver is used, the initial estimate is set to a
  # sparse vector in which all but two of the entries are zero.
  if any(x .< 0)
    x = ones(k)/k;
  end

  # If requested (i.e., if lowrank_rtol > 0), compute the partial QR
  # decomposition with relative precision "tol", then retrieve the
  # permutation matrix, P. For details on the partial QR, see
  # https://github.com/klho/LowRankApprox.jl.

  # Start timing for low-rank approximation of L.

  tic();

    if lowrank_method == "qr"
      if lowrank_criterion == "by_rank"
        F = pqrfact(L, rank = lowrank_rank);
        P = sparse(F[:P]);
      elseif lowrank_criterion == "by_rtol"
        F = pqrfact(L, rtol = lowrank_rtol);
        P = sparse(F[:P]);
      else
        error("lowrank_criterion must be either by_rank or by_rtol");
      end

    elseif lowrank_method == "svd"
      if lowrank_criterion == "by_rank"
        F = psvdfact(L, rank = lowrank_rank);
        S = Diagonal(F[:S]);
      elseif lowrank_criterion == "by_rtol"
        F = psvdfact(L, rtol = lowrank_rtol);
        S = Diagonal(F[:S]);
      else
        error("lowrank_criterion must be either by_rank or by_rtol");
      end
    end

  lowranktime = toq();

  # Summarize the analysis here.
  if verbose
    @printf("Running SQP algorithm with the following settings:\n")
    @printf("- %d x %d data matrix\n",n,k)
    @printf("- convergence tolerance = %0.2e\n",convergence_tol)
    @printf("- zero threshold        = %0.2e\n",sparse_tol)
    if lowrank_method == "qr"
      err = maximum(abs.(F[:Q]*F[:R]*P' - L));
      @printf("- partial QR tolerance  = %0.2e\n",lowrank_rtol)
      @printf("- partial QR max. error = %0.2e\n",err)
    elseif lowrank_method == "svd"
      err = maximum(abs.(F[:U]*S*F[:Vt] - L));
      @printf("- partial SVD tolerance  = %0.2e\n",lowrank_rtol)
      @printf("- partial SVD max. error = %0.2e\n",err)
    else
      @printf("- Exact derivative computation (partial QR not used).\n")
    end
  end

  # Initialize storage for the outputs obj, gmin, nnz and nqp.
  obj      = zeros(maxiter);
  gmin     = zeros(maxiter);
  nnz      = zeros(Int,maxiter);
  nqp      = zeros(Int,maxiter);
  timing   = zeros(maxiter);
  qptiming = zeros(maxiter);

  # Initialize loop variables used in the loops below so that they
  # are available outside the scope of the loop.
  i     = 0;
  j     = 0;
  D     = 0;
  t     = 0;
  numls = -1;

  # Print the column labels for reporting the algorithm's progress.
  if verbose
    @printf("iter      objective -min(g+1)  #nz #qp #ls\n")
  end

  # Repeat until we reach the maximum number of iterations, or until
  # convergence is reached.
  for i = 1:maxiter

    # Start timing the iteration.
    tic();

    # Compute the gradient and Hessian, optionally using the partial
    # QR decomposition to increase the speed of these computations.
    # gradient and Hessian computation -- Rank reduction method
        if lowrank_method == "qr"
          D = 1 ./(F[:Q]*(F[:R]*(P'*x)) .+ eps_tol);
          g = -P * F[:R]' * (F[:Q]'*D)/n;
          H = P * F[:R]' * (F[:Q]'*Diagonal(D.^2)*F[:Q])*F[:R]*P'/n + eps_tol * Diagonal(ones(k));
        elseif lowrank_method == "svd"
          D = 1 ./(F[:U]*(S*(F[:Vt]*x)) .+ eps_tol);
          g = -F[:Vt]'*(S * (F[:U]'*D))/n;
          H = (F[:V]*S*(F[:U]'*Diagonal(D.^2)*F[:U])* S*F[:Vt])/n + eps_tol * Diagonal(ones(k));
        else
          D = 1 ./(L*x .+ eps_tol);
          g = -L'*D/n;
          H = L'*Diagonal(D.^2)*L/n + eps_tol * Diagonal(ones(k));
        end

    if nullprior > 0
      g[1] -= nullprior/x[1]/n;
      H[1,1] += nullprior/x[1]^2/n;
    end

    # Report on the algorithm's progress.
    if lowrank_method == "qr"
      obj[i] = -sum(log.(F[:Q]*(F[:R]*(P'*x)) .+ eps_tol));
    elseif lowrank_method == "svd"
      obj[i] = -sum(log.(F[:U]*(S*(F[:Vt]*x)) .+ eps_tol));
    else
      obj[i] = mixobjective(L,x,eps_tol);
    end
    gmin[i] = minimum(g) .+ 1;
    nnz[i]  = length(find(x .> sparse_tol));
    nqp[i]  = j;
    if verbose
      if i == 1
          @printf("%4d %0.8e %+0.2e %4d\n",
              i,obj[i],-gmin[i],nnz[i]);
      else
          @printf("%4d %0.8e %+0.2e %4d %3d %3d\n",
              i,obj[i],-gmin[i],nnz[i],nqp[i-1],numls);
      end
    end

    # Check convergence of outer loop
    if gmin[i] >= -convergence_tol
      break
    end

    # Solve the QP subproblem using either the active-set or
    # interior-point (MOSEK) method.
    out, qptiming[i], bytes, gctime,
    memallocs = @timed if true
      y,nqp[i] = qpactiveset(x,g,H,convergence_tol = convergence_tol,sparse_tol = sparse_tol,
                      maxiter = maxqpiter);
    end

    # Perform backtracking line search
    if linesearch == true
        for t = 1:10
          if lowrank_method == "qr"
            D_new = 1 ./(F[:Q]*(F[:R]*(P'*y)) .+ eps_tol);
          elseif lowrank_method == "svd"
            D_new = 1 ./(F[:U]*(S*(F[:Vt]*y)) .+ eps_tol);
          else
            D_new = 1 ./(L*y .+ eps_tol);
          end
          if all(D_new .> 0)
            if sum(log.(D)) - sum(log.(D_new)) > sum((x-y) .* g) / 2
              break
            end
          end
          y = (y-x)/2 + x;
        end
        numls = t;
    else
        numls = -1;
    end

    # Update the solution to the original optimization problem.
    x = copy(y);

    # Get the elapsed time for the ith iteration.
    timing[i] = toq();
  end

  # Return: (1) the solution (after zeroing out any values below the
  # tolerance); (2) the value of the objective at each iteration; (3)
  # the minimum gradient value of the modified objective at each
  # iteration; (4) the number of nonzero entries in the vector at each
  # iteration; and (5) the number of inner iterations taken to solve
  # the QP subproblem at each outer iteration.
  x[x .< sparse_tol] .= 0;
  x             = x/sum(x);
  totaltime     = lowranktime + sum(timing[1:i]);
  if verbose
    @printf("Optimization took %d iterations and %0.4f seconds.\n",i,totaltime)
  end
  return Dict([("x",Vector(x)), ("totaltime",totaltime),
               ("lowranktime",lowranktime), ("obj",obj[1:i]),
               ("gmin",gmin[1:i]), ("nnz",nnz[1:i]),
               ("nqp",nqp[1:i]), ("timing",timing[1:i]),
               ("qptiming",qptiming[1:i])])
end

# Solve the QP subproblem for the mix-SQP algorithm using an active
# set method.
function qpactiveset(x, g, H; convergence_tol = 1e-8, sparse_tol = 1e-3, maxiter = 100)

  # Get the number of degrees of freedom in the optimization problem.
  k = length(x);

  # Initialize the solution to the QP subproblem.
  y      = sparse(zeros(k));
  ind    = find(x .> sparse_tol);
  y[ind] .= 1/length(ind);
  i = 0;

  # Repeat until we reach the maximum number of iterations, or until
  # convergence is reached.
  for i = 1:maxiter

    # Define the smaller QP subproblem.
    s   = length(ind);
    H_s = H[ind,ind];
    d   = H*y + 2*g .+ 1;
    d_s = d[ind];

    # Solve the smaller problem.
    p      = sparse(zeros(k));
    p_s    = -H_s\d_s;
    p[ind] = p_s;

    # Check convergence using KKT
    if norm(p_s) < convergence_tol

      # Compute the Lagrange multiplier.
      z = d;
      if all(z .>= -convergence_tol)
        break
      elseif length(ind) < k
        notind  = setdiff(1:k,ind);
        ind_min = notind[findmin(z[notind])[2]];
        ind     = sort([ind; ind_min]);
      end
    else

      # Find a feasible step length.
      alpha     = 1;
      alpha0    = -y[ind] ./ p_s;
      ind_block = find(p_s .< 0);
      alpha0    = alpha0[ind_block];
      if ~isempty(ind_block)
        v, t = findmin(alpha0);
        if v < 1

          # Blocking constraint.
          ind_block = ind[ind_block[t]];
          alpha     = v;

          # Update working set if there is a blocking constraint.
          deleteat!(ind,find(ind .- ind_block .== 0));
        end
      end

      # Move to the new "inner loop" iterate (y) along the search
      # direction.
      y = y + alpha * p;
    end
  end

  # Return the solution to the quadratic program.
  return y, Int(i)
end


using DigitSetSudoku
using Base.Test

const spec =
"""
400000805
030000000
000700000
020000060
000080400
000010000
000603070
500200000
104000000
"""

puzzle = SudokuPuzzle(spec)

@test (@sprintf "%s" puzzle) ==
"""
4 . . | . . . | 8 . 5
. 3 . | . . . | . . .
. . . | 7 . . | . . .
------+-------+------
. 2 . | . . . | . 6 .
. . . | . 8 . | 4 . .
. . . | . 1 . | . . .
------+-------+------
. . . | 6 . 3 | . 7 .
5 . . | 2 . . | . . .
1 . 4 | . . . | . . .
"""

solution = solve(puzzle)

@test (@sprintf "%s" solution) ==
"""
4 1 7 | 3 6 9 | 8 2 5
6 3 2 | 1 5 8 | 9 4 7
9 5 8 | 7 2 4 | 3 1 6
------+-------+------
8 2 5 | 4 3 7 | 1 6 9
7 9 1 | 5 8 6 | 4 3 2
3 4 6 | 9 1 2 | 7 5 8
------+-------+------
2 8 9 | 6 4 3 | 5 7 1
5 7 3 | 2 9 1 | 6 8 4
1 6 4 | 8 7 5 | 2 9 3
"""


# Non-bipartite: https://theory.stanford.edu/~jvondrak/CS369P/lec5.pdf

mutable struct PerfectBipartiteMatchingLPSolver <: PerfectBipartiteMatchingSolver
  solver

  # Optimisation model (reused by solve_linear).
  model # ::Model
  x # ::Matrix{Variable}

  function PerfectBipartiteMatchingLPSolver(solver)
    return new(solver, nothing, nothing)
  end
end

has_lp_formulation(::PerfectBipartiteMatchingLPSolver) = true
approximation_ratio(::PerfectBipartiteMatchingLPSolver) = 1.0
approximation_term(::PerfectBipartiteMatchingLPSolver) = 0.0
approximation_ratio_budgeted(::PerfectBipartiteMatchingLPSolver) = 1.0
approximation_term_budgeted(::PerfectBipartiteMatchingLPSolver) = 0.0
supports_solve_budgeted_linear(::PerfectBipartiteMatchingLPSolver) = true
supports_solve_all_budgeted_linear(::PerfectBipartiteMatchingLPSolver) = false

Base.copy(solver::PerfectBipartiteMatchingLPSolver) = PerfectBipartiteMatchingLPSolver(solver.solver)

function build!(solver::PerfectBipartiteMatchingLPSolver, n_arms::Int)
  # Build the optimisation model behind solve_linear.
  solver.model = Model(solver.solver)
  indices = collect((i, j) for i in 1:n_arms, j in 1:n_arms)
  solver.x = @variable(solver.model, [indices], binary=true)

  for i in 1:n_arms
    for j in 1:n_arms
      set_name(solver.x[(i, j)], "x[$i, $j]")
    end
  end

  for i in 1:n_arms # Left nodes.
    @constraint(solver.model, sum(solver.x[(i, j)] for j in 1:n_arms) == 1)
  end
  for j in 1:n_arms # Right nodes.
    @constraint(solver.model, sum(solver.x[(i, j)] for i in 1:n_arms) == 1)
  end
end

function get_lp_formulation(solver::PerfectBipartiteMatchingLPSolver, rewards::Dict{Tuple{Int, Int}, Float64})
  return solver.model,
    sum(rewards[(i, j)] * solver.x[(i, j)] for (i, j) in keys(rewards)),
    Dict{Tuple{Int, Int}, JuMP.VariableRef}((i, j) => solver.x[(i, j)] for (i, j) in keys(rewards))
end


module Cosmology

export cosmology,
       age_gyr,
       angular_diameter_dist_mpc,
       comoving_radial_dist_mpc,
       comoving_transverse_dist_mpc,
       comoving_volume_gpc3,
       H,
       hubble_dist_mpc,
       hubble_time_gyr,
       luminosity_dist_mpc,
       lookback_time_gyr,
       scale_factor

abstract AbstractCosmology
abstract AbstractClosedCosmology <: AbstractCosmology
abstract AbstractFlatCosmology <: AbstractCosmology
abstract AbstractOpenCosmology <: AbstractCosmology

immutable FlatLCDM <: AbstractFlatCosmology
    h::Float64
    Ω_Λ::Float64
    Ω_m::Float64
    Ω_r::Float64

    function FlatLCDM(h::Real, Ω_Λ::Real, Ω_m::Real, Ω_r::Real)
        new(float64(h), float64(Ω_Λ), float64(Ω_m), float64(Ω_r))
    end
end

a2E(c::FlatLCDM, a::Float64) = sqrt(c.Ω_r + c.Ω_m*a + c.Ω_Λ*a^4)

immutable ClosedLCDM <: AbstractClosedCosmology
    h::Float64
    Ω_k::Float64
    Ω_Λ::Float64
    Ω_m::Float64
    Ω_r::Float64

    function ClosedLCDM(h::Real, Ω_k::Real, Ω_Λ::Real, Ω_m::Real, Ω_r::Real)
        new(float64(h), float64(Ω_k), float64(Ω_Λ), float64(Ω_m), float64(Ω_r))
    end
end

immutable OpenLCDM <: AbstractOpenCosmology
    h::Float64
    Ω_k::Float64
    Ω_Λ::Float64
    Ω_m::Float64
    Ω_r::Float64

    function OpenLCDM(h::Real, Ω_k::Real, Ω_Λ::Real, Ω_m::Real, Ω_r::Real)
        new(float64(h), float64(Ω_k), float64(Ω_Λ), float64(Ω_m), float64(Ω_r))
    end
end

function a2E(c::Union(ClosedLCDM,OpenLCDM), a::Float64)
    a2 = a*a
    sqrt(c.Ω_r + c.Ω_m*a + (c.Ω_k + c.Ω_Λ*a2)*a2)
end

for c in ("Flat", "Open", "Closed")
    @eval begin
        immutable $(symbol("$(c)WCDM")) <: $(symbol("Abstract$(c)Cosmology"))
            h::Float64
            Ω_k::Float64
            Ω_Λ::Float64
            Ω_m::Float64
            Ω_r::Float64
            w0::Float64
            wa::Float64
        end
    end
end

function WCDM(h::Float64, Ω_k::Float64, Ω_Λ::Float64, Ω_m::Float64, Ω_r::Float64, w0::Float64, wa::Float64)
    if Ω_k < 0
        ClosedWCDM(h, Ω_k, Ω_Λ, Ω_m, Ω_r, w0, wa)
    elseif Ω_k > 0
        OpenWCDM(h, Ω_k, Ω_Λ, Ω_m, Ω_r, w0, wa)
    else
        FlatWCDM(h, Ω_k, Ω_Λ, Ω_m, Ω_r, w0, wa)
    end
end

function a2E(c::Union(FlatWCDM,ClosedWCDM,OpenWCDM), a::Float64)
    ade = exp((1.0 - 3.0*(c.w0 + c.wa))*log(a) + 3.0*c.wa*(a - 1.0))
    sqrt(c.Ω_r + (c.Ω_m + c.Ω_k*a)*a + c.Ω_Λ*ade)
end

function cosmology(;h=0.69,
                   Neff=3.04,
                   OmegaK=0,
                   OmegaM=0.29,
                   OmegaR=nothing,
                   Tcmb=2.7255,
                   w0=-1,
                   wa=0)

    if OmegaR === nothing
        OmegaG = 4.48131e-7*Tcmb^4/h^2
        OmegaN = Neff*OmegaG*(7/8)*(4/11)^(4/3)
        OmegaR = OmegaG + OmegaN
    end

    OmegaL = 1. - OmegaK - OmegaM - OmegaR

    if !(w0 == -1 && wa == 0)
        return WCDM(float64(h), float64(OmegaK), float64(OmegaL),
                    float64(OmegaM), float64(OmegaR), float64(w0), float64(wa))
    end

    if OmegaK < 0
        return ClosedLCDM(h, OmegaK, OmegaL, OmegaM, OmegaR)
    elseif OmegaK > 0
        return OpenLCDM(h, OmegaK, OmegaL, OmegaM, OmegaR)
    else
        return FlatLCDM(h, OmegaL, OmegaM, OmegaR)
    end
end

# hubble rate

scale_factor(z) = 1/(1 + z)
E(c::AbstractCosmology, z) = (a = scale_factor(z); a2E(c,a)/a^2)
H(c::AbstractCosmology, z) = 100. * c.h * E(c, z)

hubble_dist_mpc0(c::AbstractCosmology) = 2997.92458/c.h
hubble_dist_mpc(c::AbstractCosmology, z) = hubble_dist_mpc0(c)/E(c,z)

hubble_time_gyr0(c::AbstractCosmology) = 9.77814/c.h
hubble_time_gyr(c::AbstractCosmology, z) = hubble_time_gyr0(c)/E(c,z)

# distances

Z(c::AbstractCosmology, z::Real) = ((q,_) = quadgk(a::Float64->1.0/a2E(c,a), scale_factor(z), 1); q)

comoving_radial_dist_mpc(c::AbstractCosmology, z) = hubble_dist_mpc0(c)*Z(c, z)

comoving_transverse_dist_mpc(c::AbstractFlatCosmology, z) =
    comoving_radial_dist_mpc(c, z)
function comoving_transverse_dist_mpc(c::AbstractOpenCosmology, z)
    sqrtok = sqrt(c.Ω_k)
    hubble_dist_mpc0(c)*sinh(sqrtok*Z(c,z))/sqrtok
end
function comoving_transverse_dist_mpc(c::AbstractClosedCosmology, z)
    sqrtok = sqrt(abs(c.Ω_k))
    hubble_dist_mpc0(c)*sin(sqrtok*Z(c,z))/sqrtok
end

angular_diameter_dist_mpc(c::AbstractCosmology, z) =
    comoving_transverse_dist_mpc(c, z)/(1 + z)

luminosity_dist_mpc(c::AbstractCosmology, z) =
    comoving_transverse_dist_mpc(c, z)*(1 + z)

# volumes

comoving_volume_gpc3(c::AbstractFlatCosmology, z) =
    (4pi/3)*(comoving_radial_dist_mpc(c,z)*1e-3)^3
function comoving_volume_gpc3(c::AbstractOpenCosmology, z)
    DH = hubble_dist_mpc0(c)
    x = comoving_transverse_dist_mpc(c,z)/DH
    sqrtok = sqrt(c.Ω_k)
    2pi*(DH*1e-3)^3*(x*sqrt(1. + c.Ω_k*x^2) - asinh(sqrtok*x)/sqrtok)/c.Ω_k
end
function comoving_volume_gpc3(c::AbstractClosedCosmology, z)
    DH = hubble_dist_mpc0(c)
    x = comoving_transverse_dist_mpc(c,z)/DH
    sqrtok = sqrt(abs(c.Ω_k))
    2pi*(DH*1e-3)^3*(x*sqrt(1. + c.Ω_k*x^2) - asin(sqrtok*x)/sqrtok)/c.Ω_k
end

comoving_volume_element_gpc3(c::AbstractCosmology, z) =
    1e-9*hubble_dist_mpc0(c,z)*angular_diameter_dist_mpc(c,z)^2/a2E(c,scale_factor(z))

# times

T(c::AbstractCosmology, a0::Float64, a1::Float64) = ((q,_) = quadgk(x::Float64->x/a2E(c,x), a0, a1); q)
age_gyr(c::AbstractCosmology, z) = hubble_time_gyr0(c)*T(c, 0., scale_factor(z))
lookback_time_gyr(c::AbstractCosmology, z) = hubble_time_gyr0(c)*T(c, scale_factor(z), 1.)

end # module


include("euler/euler.jl")
using .Grid: max_path_sum
using BenchmarkTools
BenchmarkTools.DEFAULT_PARAMETERS.samples = 100

compute(triangle::String)::Int = max_path_sum([parse.(Int, split(line, " ")) for line ∈ split(triangle, "\n")])

small_triangle = """3
7 4
2 4 6
8 5 9 3"""

big_triangle = """75
95 64
17 47 82
18 35 87 10
20 04 82 47 65
19 01 23 75 03 34
88 02 77 73 07 63 67
99 65 04 28 06 16 70 92
41 41 26 56 83 40 80 70 33
41 48 72 33 47 32 37 16 94 29
53 71 44 65 25 43 91 52 97 51 14
70 11 33 28 77 73 17 78 39 68 17 57
91 71 52 38 17 14 91 43 58 50 27 29 48
63 66 04 68 89 53 67 30 73 16 69 87 40 31
04 62 98 27 23 09 70 98 73 93 38 53 60 04 23"""

compute(small_triangle)

compute(big_triangle)

@benchmark compute(big_triangle)


doc"""
    Arcsine(a,b)

The *Arcsine distribution* has probability density function

$f(x) = \frac{1}{\pi \sqrt{(x - a) (b - x)}}, \quad x \in [a, b]$

```julia
Arcsine()        # Arcsine distribution with support [0, 1]
Arcsine(b)       # Arcsine distribution with support [0, b]
Arcsine(a, b)    # Arcsine distribution with support [a, b]

params(d)        # Get the parameters, i.e. (a, b)
minimum(d)       # Get the lower bound, i.e. a
maximum(d)       # Get the upper bound, i.e. b
location(d)      # Get the left bound, i.e. a
scale(d)         # Get the span of the support, i.e. b - a
```

External links

* [Arcsine distribution on Wikipedia](http://en.wikipedia.org/wiki/Arcsine_distribution)

"""

immutable Arcsine{T<:Real} <: ContinuousUnivariateDistribution
    a::T
    b::T

    Arcsine(a::T, b::T) = (@check_args(Arcsine, a < b); new(a, b))
end

Arcsine{T<:Real}(a::T, b::T) = Arcsine{T}(a, b)
Arcsine(a::Real, b::Real) = Arcsine(promote(a, b)...)
Arcsine(a::Integer, b::Integer) = Arcsine(Float64(a), Float64(b))
Arcsine(b::Real) = Arcsine(0.0, b)
Arcsine() = Arcsine(0.0, 1.0)

@distr_support Arcsine d.a d.b

#### Conversions
function convert{T<:Real}(::Type{Arcsine{T}}, a::Real, b::Real)
    Arcsine(T(a), T(b))
end
function convert{T <: Real, S <: Real}(::Type{Arcsine{T}}, d::Arcsine{S})
    Arcsine(T(d.a), T(d.b))
end

### Parameters

location(d::Arcsine) = d.a
scale(d::Arcsine) = d.b - d.a
params(d::Arcsine) = (d.a, d.b)
@inline partype{T<:Real}(d::Arcsine{T}) = T

### Statistics

mean(d::Arcsine) = (d.a + d.b) / 2
median(d::Arcsine) = mean(d)
mode(d::Arcsine) = d.a
modes(d::Arcsine) = [d.a, d.b]

var(d::Arcsine) = abs2(d.b - d.a) / 8
skewness{T<:Real}(d::Arcsine{T}) = zero(T)
kurtosis{T<:Real}(d::Arcsine{T}) = -T(3/2)

entropy(d::Arcsine) = -0.24156447527049044469 + log(scale(d))

### Evaluation

function pdf(d::Arcsine, x::Real)
    insupport(d, x) ? one(d.a) / (π * sqrt((x - d.a) * (d.b - x))) : zero(d.a)
end

function logpdf{T<:Real}(d::Arcsine{T}, x::Real)
    insupport(d, x) ? -(logπ + log((x - d.a) * (d.b - x))/2) : -T(Inf)
end

cdf{T<:Real}(d::Arcsine{T}, x::Real) = x < d.a ? zero(T) :
                              x > d.b ? one(T) :
                              0.636619772367581343 * asin(sqrt((x - d.a) / (d.b - d.a)))

quantile(d::Arcsine, p::Real) = location(d) + abs2(sin(halfπ * p)) * scale(d)

### Sampling

rand(d::Arcsine) = quantile(d, rand())


"""
    nonatomicdeferredacceptance_iid(students, students_dist, capacities;
                      verbose, rev, tol)

Nonatomic (continuous) analogue of `deferredacceptance()`, simplified to return only the score cutoffs
associated with each school, after Azevedo and Leshno (2016).

Students are a continuum of profiles distributed over a fixed set of student preference
lists, and school capacities are fractions of the total student population. Returns
only the cutoffs; use `assignmentfrompreflists()` to get the match array or `nonatomicdeferredacceptance()`
for a wrapper function.
"""
function nonatomicdeferredacceptance_iid(students         ::Union{AbstractArray{Int, 2}, AbstractArray{UInt, 2}},
                           students_dist    ::AbstractArray{<:AbstractFloat, 1},
                           capacities       ::AbstractArray{<:AbstractFloat, 1};
                           verbose          ::Bool=false,
                           rev              ::Bool=false,
                           tol              ::AbstractFloat=1e-12,
                           )::AbstractArray{<:AbstractFloat, 1}
    (m, n) = size(students)

    @assert (m,) == size(capacities)     "Dim mismatch between students and capacities"
    @assert (n,) == size(students_dist)  "Dim mismatch between students and students_dist"

    nit = 0
    done = false

    if !rev
        cutoffs = zeros(m)

        while done==false
            nit += 1
            done = true

            verbose ? println("\nRound $nit") : nothing

            demands = demandfrompreflists(students, students_dist, cutoffs)

            for (c, d) in enumerate(demands)
                if d - capacities[c] > tol
                    verbose ? println("  Demand at school $c was $d > capacity $(capacities[c])") : nothing
                    verbose ? println("    Old cutoff:  ", cutoffs[c]) : nothing
                    done = false
                    cutoffs[c] += (1 - capacities[c] / demands[c]) * (1 - cutoffs[c])
                    verbose ? println("    New cutoff:  ", cutoffs[c]) : nothing
                end
            end
        end

    else        # reverse
        # Selfish cutoff: Each school assumes it's everyone's first choice.
        # Thus this is the highest possible cutoff.
        cutoffs = max.(0., 1 .- capacities ./ sum(students_dist))

        while done==false
            nit += 1
            done = true

            verbose ? println("\nRound $nit") : nothing

            demands = demandfrompreflists(students, students_dist, cutoffs)

            for (c, d) in enumerate(demands)
                if cutoffs[c] > 0 && capacities[c] - d > tol   # If school has lots of remaining capacity
                    verbose ? println("  Demand at school $c was $d < capacity $(capacities[c])") : nothing
                    verbose ? println("    Old cutoff:  ", cutoffs[c]) : nothing
                    done = false
                    cutoffs[c] = max(cutoffs[c] + (1 - capacities[c] / demands[c]) * (1 - cutoffs[c]), 0)
                    verbose ? println("    New cutoff:  ", cutoffs[c]) : nothing
                end
            end
        end
    end

    return cutoffs
end

"""
    nonatomicdeferredacceptance_iid(demand, capacities;
                      verbose, rev, tol, maxit)

Nonatomic (continuous) analogue of `deferredacceptance()`, simplified to return only the score cutoffs
associated with each school, where demand is given by an arbitrary function that
takes score cutoffs as inputs. This algorithm assumes that student preferability
is independent at each school and that the demand function satisfies weak gross
substitutability. To relax the former assumption, use `nonatomictatonnement()`.
"""
function nonatomicdeferredacceptance_iid(demand      ::Function,
                           capacities  ::AbstractArray{<:AbstractFloat, 1};
                           verbose     ::Bool=false,
                           rev         ::Bool=false,
                           tol         ::AbstractFloat=1e-12,
                           maxit       ::Int=500,
                           )::AbstractArray{<:AbstractFloat, 1}

    (m, ) = size(capacities)

    if !rev
        cutoffs = zeros(m)

        for nit in 1:maxit
            done = true

            verbose ? println("\nRound $nit") : nothing

            demands = demand(cutoffs)

            for (c, d) in enumerate(demands)
                if d - capacities[c] > tol
                    verbose ? println("  Demand at school $c was $d > capacity $(capacities[c])") : nothing
                    verbose ? println("    Old cutoff:  ", cutoffs[c]) : nothing
                    done = false
                    cutoffs[c] += (1 - capacities[c] / demands[c]) * (1 - cutoffs[c])
                    verbose ? println("    New cutoff:  ", cutoffs[c]) : nothing
                end
            end

            if done == true
                break
            elseif nit == maxit
                @warn "Exceeded maximum number of iterations; try tuning parameters"
            end
        end

    else      # reverse
        # Selfish cutoff: Each school assumes it's everyone's first choice.
        # Thus this is the highest possible cutoff.
        cutoffs = max.(0., 1 .- capacities)

        for nit in 1:maxit
            done = true
            verbose ? println("\nRound $nit") : nothing

            demands = demand(cutoffs)

            for (c, d) in enumerate(demands)
                if cutoffs[c] > 0 && capacities[c] - d > tol   # If school has lots of remaining capacity
                    verbose ? println("  Demand at school $c was $d < capacity $(capacities[c])") : nothing
                    verbose ? println("    Old cutoff:  ", cutoffs[c]) : nothing
                    done = false
                    cutoffs[c] = max(cutoffs[c] + (1 - capacities[c] / demands[c]) * (1 - cutoffs[c]), 0)
                    verbose ? println("    New cutoff:  ", cutoffs[c]) : nothing
                end
            end

            if done == true
                break
            elseif nit == maxit
                @warn "Exceeded maximum number of iterations; try tuning parameters"
            end
        end

    end

    return cutoffs
end

"""
    nonatomicdeferredacceptance(students, students_dist, schools, capacities;
                 verbose, rev, return_cutoffs, tol)

Nonatomic (continuous) analogue of `deferredacceptance()`. Students are a continuum of profiles distributed
over a fixed set of student preference lists, and school capacities are fractions of the total
student population. School preferences are optional. If you pass `schools=nothing`, it assumes
that student preferability is uncorrelated with student profile, and the schools simply accept
the best students from each profile. This is the more realistic case; the algorithm is not
incentive compatible if schools can favor students based on the students' preferences.

When school preferences are not `nothing`, `rev` is a placeholder and reverse mode
has not yet been implemented.

Returns a tuple expressing the assignment as an array, where the `[i, j]`` entry indicates the
measure of students having preference list `j` admitted to school `i` and `i + 1` indicates nonassignment,
and a vector giving the average utility in each preference list.

Set `return_cutoffs=true` to get the score cutoffs associated with the match, after Azevedo
and Leshno (2016). These cutoffs are the state space of the algorithm and therefore more
numerically accurate than the assignment array itself. To get only the cutoffs, use
`nonatomicdeferredacceptance_iid()` (which this function wraps wheen `schools===nothing`)
or `nonatomictatonnement()` (which is not a DA algorithm but can compute equilibrium cutoffs in a wider range of scenarios).
"""
function nonatomicdeferredacceptance(students          ::Union{AbstractArray{Int, 2}, AbstractArray{UInt, 2}},
                      students_dist     ::AbstractArray{<:AbstractFloat, 1},
                      schools           ::Union{AbstractArray{Int, 2}, AbstractArray{UInt, 2}, Nothing},
                      capacities_in     ::AbstractArray{<:AbstractFloat, 1};
                      verbose           ::Bool=false,
                      rev               ::Bool=false,
                      return_cutoffs    ::Bool=false,
                      tol               ::AbstractFloat=1e-12)

    m, n = size(students)
    @assert (m,) == size(capacities_in)         "Dim mismatch between students and capacities"
    @assert (n,) == size(students_dist)         "Dim mismatch between students and students_dist"
    @assert schools === nothing || rev == false  "Reverse unavailable when schools have prefs"

    done = false
    nit = 0

    if schools === nothing                    # Homogenous student preferability
        students_inv = mapslices(invperm, students, dims=1)

        cutoffs = nonatomicdeferredacceptance_iid(students, students_dist, capacities_in;
                                    verbose=verbose, rev=rev, tol=tol)
        curr_assn = assignmentfrompreflists(students_inv, students_dist, cutoffs)

        verbose ? println("Cutoffs: ", cutoffs) : nothing
        DA_rank_dist = sum([col[students_inv[:, i]] for (i, col) in enumerate(eachcol(curr_assn))])
        append!(DA_rank_dist, sum(curr_assn[m + 1, :]))

        if return_cutoffs
            return curr_assn, DA_rank_dist, cutoffs
        else
            return curr_assn, DA_rank_dist
        end

    else            # Schools have preference order over student types
        @assert (n, m) == size(schools) "Dim mismatch between schools and students"

        students_inv = mapslices(invperm, students, dims=1)
        schools_inv = mapslices(invperm, schools, dims=1)
        if rev == false
            capacities = vcat(capacities_in, sum(students_dist))  # For students who never get assigned

            # Each entry indicates the volume of students from type j assigned to school i
            curr_assn = zeros(Float64, m + 1, n)
            for (s, C) in enumerate(eachcol(students_inv))
                curr_assn[C[1], s] = students_dist[s]
            end

            while !done
                nit += 1
                verbose ? println("\n\nRound $nit") : nothing
                done = true
                demands = sum(curr_assn, dims = 2)
                for c in 1:m    # Reject bin (c = m + 1) is never overdemanded
                    if demands[c] - capacities[c] > tol
                        capacity_remaining = capacities[c]
                        verbose ? print("\n  Total demand for school $c was ", demands[c],
                                ", but capacity is ", capacities[c]) : nothing
                        for s in filter(i->curr_assn[c, i] > 0, schools_inv[:, c])
                            if capacity_remaining ≥ curr_assn[c, s]
                                capacity_remaining -= curr_assn[c, s]
                                verbose ? print("\n    Accepting demand of ", curr_assn[c, s],
                                                " from profile $s; remaining capacity ",
                                                capacity_remaining) : nothing
                            else
                                done = false
                                prop_to_reject = 1 - capacity_remaining / curr_assn[c, s]
                                verbose ? print("\n    Demand from profile $s was ", curr_assn[c, s],
                                        "; rejecting $prop_to_reject of it") : nothing
                                next_school_id = get(students_inv, CartesianIndex(students[c, s] + 1, s), m + 1)
                                curr_assn[next_school_id, s] += prop_to_reject * curr_assn[c, s]
                                curr_assn[c, s] *= 1 - prop_to_reject
                                capacity_remaining = 0
                            end
                        end
                    end
                end
            end

            verbose ? println("\nDA terminated in $nit iterations") : nothing
            DA_rank_dist = sum([col[students_inv[:, i]] for (i, col) in enumerate(eachcol(curr_assn))])
            append!(DA_rank_dist, sum(curr_assn[m + 1, :]))

            return curr_assn, DA_rank_dist

        else
            error("No reverse mode yet when schools have pref over students")
        end
    end
end

"""
    nonatomicsecant(demand, capacities; verbose, tol, maxit)

Search for a market-clearing cutoff vector using a modified secant method with
tâtonnement as a fallback when change in demand is small. Less theoretically
legit than `nonatomictatonnement()` but tends to make fewer calls to `demand()`.

`demand` is a school demand function that takes cutoffs as input.
"""
function nonatomicsecant(demand      ::Function,
                          capacities  ::AbstractArray{<:AbstractFloat, 1};
                          verbose     ::Bool=false,
                          tol         ::AbstractFloat=1e-12,
                          maxit       ::Int=500,
                          )::AbstractArray{<:AbstractFloat, 1}

    (m, ) = size(capacities)

    UB = max.(0., 1 .- capacities)
    old_cutoffs = rand(m) .* UB
    new_cutoffs = rand(m) .* UB
    # old_cutoffs = zeros(m)
    # new_cutoffs = copy(UB)
    new_excess_demand = demand(old_cutoffs) - capacities

    for nit in 1:maxit
        verbose ? println("Round $nit") : nothing
        old_excess_demand, new_excess_demand = new_excess_demand, demand(new_cutoffs) - capacities

        verbose ? println("  Old cutoff vector: ", round.(old_cutoffs, digits = 4)) : nothing
        verbose ? println("  New cutoff vector: ", round.(new_cutoffs, digits = 4)) : nothing
        verbose ? println("  Excess demand: ", round.(new_excess_demand, digits = 4)) : nothing

        for c in 1:m
            # Fall back to tatonnement when denominator of secant update would
            # be close to zero.
            if isapprox(old_excess_demand[c], new_excess_demand[c], atol=tol)
                verbose ? println("    Updating school $c by tâtonnement because its demand didn't change") : nothing
                old_cutoffs[c], new_cutoffs[c] =
                    new_cutoffs[c], max(0, min(UB[c], new_cutoffs[c] + new_excess_demand[c]))
            else
                old_cutoffs[c], new_cutoffs[c] =
                    new_cutoffs[c],
                    max(0, min(UB[c],
                                 begin
                                     new_cutoffs[c] -
                                     new_excess_demand[c] * (new_cutoffs[c] - old_cutoffs[c]) /
                                     (new_excess_demand[c] - old_excess_demand[c])
                                 end ))
            end
        end

        if nit == maxit
            @warn "Exceeded maximum number of iterations; try tuning parameters"
        end

        # Check if market clears.
        if isapprox(new_cutoffs' * new_excess_demand, 0, atol=tol) &&
           all(new_excess_demand .≤ 0 + tol)
            break
        end
    end

    return cutbox(new_cutoffs)
end

"""
    nonatomictatonnement(demand, capacities; verbose, tol, β, maxit)

Search for a market-clearing cutoff vector using a modified tâtonnement process.
Analogous to `nonatomicdeferredacceptance_iid()` but more robust; `nonatomictatonnement()`
converges for any demand function that satisfies weak gross substitutability.

`demand` is a school demand function that takes cutoffs as input.

`β` is a step size parameter, where the step size is `1 / nit ^ β`, where `nit`
is the iteration number.
"""
function nonatomictatonnement(demand      ::Function,
                               capacities  ::AbstractArray{<:AbstractFloat, 1};
                               verbose     ::Bool=false,
                               tol         ::AbstractFloat=1e-12,
                               β           ::AbstractFloat=1e-3,
                               maxit       ::Int=500,
                               )::AbstractArray{<:AbstractFloat, 1}

    (m, ) = size(capacities)

    UB = max.(0., 1 .- capacities)
    cutoffs = rand(m) .* UB

    for nit in 1:maxit
        verbose ? println("Round $nit") : nothing
        excess_demand = demand(cutoffs) - capacities

        α = 1 / nit ^ β

        verbose ? println("  Cutoff vector: ", round.(cutoffs, digits = 4)) : nothing
        verbose ? println("  Excess demand: ", round.(excess_demand, digits = 4)) : nothing
        verbose ? println("  Step size:     ", α) : nothing

        new_cutoffs = max.(0, min.(UB, cutoffs + α * excess_demand))

        if nit == maxit
            @warn "Exceeded maximum number of iterations; try tuning parameters"
        end

        if isapprox(new_cutoffs, cutoffs, atol=tol)
            cutoffs = new_cutoffs
            break
        else
            cutoffs = new_cutoffs
        end
    end

    return cutoffs
end

"""
    ismarketclearing(students, students_dist, capacities, cutoffs;
                     tol=1e-6)

Check if a set of cutoffs is market clearing with respect to the given nonatomic
market. Nonatomic analogue of `isstable()` by Lemma 1 of Azevedo and Leshno (2016).
"""
function ismarketclearing(students      ::Union{AbstractArray{Int, 2}, AbstractArray{UInt, 2}},
                          students_dist ::AbstractArray{<:AbstractFloat, 1},
                          capacities    ::AbstractArray{<:AbstractFloat, 1},
                          cutoffs       ::AbstractArray{<:AbstractFloat, 1};
                          verbose       ::Bool=false,
                          tol           ::AbstractFloat=1e-6,
                          )::Bool
    demands = demandfrompreflists(students, students_dist, cutoffs)

    crit = falses(2)

    crit[1] = isapprox(sum(demands), min(sum(students_dist), sum(capacities)), atol=tol)
    crit[2] = all(demands .≤ capacities .+ tol)

    res = all(crit)

    if verbose
        for (test, pass) in zip(["Market clearing ",
                                 "School feas.    "], crit)
            println("$test:  $pass")
        end
    end

    return res
end

"""
    ismarketclearing(qualities, capacities, cutoffs;
                     tol=1e-6)

Check if a set of cutoffs is market clearing with respect to the given nonatomic
market. Nonatomic analogue of `isstable()` by Lemma 1 of Azevedo and Leshno (2016).
"""
function ismarketclearing(qualities     ::AbstractArray{<:AbstractFloat, 1},
                          capacities    ::AbstractArray{<:AbstractFloat, 1},
                          cutoffs       ::AbstractArray{<:AbstractFloat, 1};
                          verbose       ::Bool=false,
                          tol           ::AbstractFloat=1e-6,
                          )::Bool

    demands = demandfromMNL_iid(qualities, cutoffs)

    crit = falses(2)

    crit[1] = isapprox(sum(demands), min(1., sum(capacities)), atol=tol)
    crit[2] = all(demands .≤ capacities .+ tol)

    res = all(crit)

    if verbose
        for (test, pass) in zip(["Market clearing ",
                                 "School feas.    "], crit)
            println("$test:  $pass")
        end
    end

    return res
end

"""
    ismarketclearing(qualities, capacities, cutoffs;
                     tol=1e-6)

Check if a set of cutoffs is market clearing with respect to the given nonatomic
market. Nonatomic analogue of `isstable()` by Lemma 1 of Azevedo and Leshno (2016).
"""
function ismarketclearing(demand        ::Function,
                          capacities    ::AbstractArray{<:AbstractFloat, 1},
                          cutoffs       ::AbstractArray{<:AbstractFloat, 1};
                          verbose       ::Bool=false,
                          tol           ::AbstractFloat=1e-6,
                          )::Bool

    demands = demand(cutoffs)

    crit = falses(2)

    crit[1] = isapprox(sum(demands), min(1., sum(capacities)), atol=tol)
    crit[2] = all(demands .≤ capacities .+ tol)

    res = all(crit)

    if verbose
        for (test, pass) in zip(["Market clearing ",
                                 "School feas.    "], crit)
            println("$test:  $pass")
        end
    end

    return res
end


# ---
# title: 713. Subarray Product Less Than K
# id: problem713
# author: Tian Jun
# date: 2020-10-31
# difficulty: Medium
# categories: Array, Two Pointers
# link: <https://leetcode.com/problems/subarray-product-less-than-k/description/>
# hidden: true
# ---
#
# Your are given an array of positive integers `nums`.
#
# Count and print the number of (contiguous) subarrays where the product of all
# the elements in the subarray is less than `k`.
#
# **Example 1:**
#
#
#
#     Input: nums = [10, 5, 2, 6], k = 100
#     Output: 8
#     Explanation: The 8 subarrays that have product less than 100 are: [10], [5], [2], [6], [10, 5], [5, 2], [2, 6], [5, 2, 6].
#     Note that [10, 5, 2] is not included as the product of 100 is not strictly less than k.
#
#
# **Note:**
#
# * `0 < nums.length <= 50000`.
# * `0 < nums[i] < 1000`.
# * `0 <= k < 10^6`.
#
#
## @lc code=start
using LeetCode

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


import StatsBase as SB
using BenchmarkTools
using Normalization
using Statistics
using Test

@testset "1D normalization" begin
    # * 1D array
    _X = rand(100)
    X = copy(_X)
    T = fit(ZScore, X)
    Y = normalize(X, T)
    @test !isnothing(T.p)
    @test length(T.p) == 2
    @test length(T.p[1]) == 1 == length(T.p[2])
    @test Y ≈ (X.-mean(X))./std(X)
    @test denormalize(Y, T) ≈ X
    @test_nowarn normalize!(X, T)
    @test X == Y
    @test_nowarn denormalize!(Y, T)
    @test Y ≈ _X
end

@testset "2D normalization" begin # Adapted from https://github.com/JuliaStats/StatsBase.jl/blob/e8ab26500d9a34ef358b2d3cf619ae41c71785fc/test/transformations.jl

    #* 2D array
    _X = rand(10, 5)
    X = copy(_X)

    # * ZScore a 2D array over the first dim.
    T = fit(ZScore, X, dims=1)
    Y = normalize(X, T)
    @test !isnothing(T.p)
    @test length(T.p) == 2
    @test length(T.p[1]) == length(T.p[2]) == size(X, 2)
    @test Y ≈ (X.-mean(X, dims=1))./std(X, dims=1)
    @test denormalize(Y, T) ≈ X
    @test_nowarn normalize!(X, T)
    @test X == Y
    @test_nowarn denormalize!(Y, T)
    @test Y ≈ _X

    # X = copy(_X)
    # t = fit(ZScoreTransform, X, dims=1, scale=false)
    # Y = transform(t, X)
    # @test length(t.mean) == 8
    # @test isempty(t.scale)
    # @test Y ≈ X .- mean(X, dims=1)
    # @test reconstruct(t, Y) ≈ X
    # @test transform!(t, X) === X
    # @test isequal(X, Y)
    # @test reconstruct!(t, Y) === Y
    # @test Y ≈ _X

    # X = copy(_X)
    # t = fit(ZScoreTransform, X, dims=1)
    # Y = transform(t, X)
    # @test length(t.mean) == 8
    # @test length(t.scale) == 8
    # @test Y ≈ (X .- mean(X, dims=1)) ./ std(X, dims=1)
    # @test reconstruct(t, Y) ≈ X
    # @test Y ≈ standardize(ZScoreTransform, X, dims=1)
    # @test transform!(t, X) === X
    # @test isequal(X, Y)
    # @test reconstruct!(t, Y) === Y
    # @test Y ≈ _X

    # X = copy(_X)
    # t = fit(ZScoreTransform, X, dims=2)
    # Y = transform(t, X)
    # @test length(t.mean) == 5
    # @test length(t.scale) == 5
    # @test Y ≈ (X .- mean(X, dims=2)) ./ std(X, dims=2)
    # @test reconstruct(t, Y) ≈ X
    # @test Y ≈ standardize(ZScoreTransform, X, dims=2)
    # @test transform!(t, X) === X
    # @test isequal(X, Y)
    # @test reconstruct!(t, Y) === Y
    # @test Y ≈ _X

    # X = copy(_X)
    # t = fit(UnitRangeTransform, X, dims=1, unit=false)
    # Y = transform(t, X)
    # @test length(t.min) == 8
    # @test length(t.scale) == 8
    # @test Y ≈ X ./ (maximum(X, dims=1) .- minimum(X, dims=1))
    # @test reconstruct(t, Y) ≈ X
    # @test transform!(t, X) === X
    # @test isequal(X, Y)
    # @test reconstruct!(t, Y) === Y
    # @test Y ≈ _X

    # X = copy(_X)
    # t = fit(UnitRangeTransform, X, dims=1)
    # Y = transform(t, X)
    # @test isa(t, AbstractDataTransform)
    # @test length(t.min) == 8
    # @test length(t.scale) == 8
    # @test Y ≈ (X .- minimum(X, dims=1)) ./ (maximum(X, dims=1) .- minimum(X, dims=1))
    # @test reconstruct(t, Y) ≈ X
    # @test Y ≈ standardize(UnitRangeTransform, X, dims=1)
    # @test transform!(t, X) === X
    # @test isequal(X, Y)
    # @test reconstruct!(t, Y) === Y
    # @test Y ≈ _X

    # X = copy(_X)
    # t = fit(UnitRangeTransform, X, dims=2)
    # Y = transform(t, X)
    # @test isa(t, AbstractDataTransform)
    # @test length(t.min) == 5
    # @test length(t.scale) == 5
    # @test Y ≈ (X .- minimum(X, dims=2)) ./ (maximum(X, dims=2) .- minimum(X, dims=2))
    # @test reconstruct(t, Y) ≈ X
    # @test transform!(t, X) === X
    # @test isequal(X, Y)
    # @test reconstruct!(t, Y) === Y
    # @test Y ≈ _X

    # # vector
    # X = rand(10)
    # _X = copy(X)

    # t = fit(ZScoreTransform, X, dims=1, center=false, scale=false)
    # Y = transform(t, X)
    # @test transform(t, X) ≈ Y
    # @test reconstruct(t, Y) ≈ X
    # @test transform!(t, X) === X
    # @test isequal(X, Y)
    # @test reconstruct!(t, Y) === Y
    # @test Y ≈ _X

    # X = copy(_X)
    # t = fit(ZScoreTransform, X, dims=1, center=false)
    # Y = transform(t, X)
    # @test Y ≈ X ./ std(X, dims=1)
    # @test transform(t, X) ≈ Y
    # @test reconstruct(t, Y) ≈ X
    # @test transform!(t, X) === X
    # @test isequal(X, Y)
    # @test reconstruct!(t, Y) === Y
    # @test Y ≈ _X

    # X = copy(_X)
    # t = fit(ZScoreTransform, X, dims=1, scale=false)
    # Y = transform(t, X)
    # @test Y ≈ X .- mean(X, dims=1)
    # @test transform(t, X) ≈ Y
    # @test reconstruct(t, Y) ≈ X
    # @test transform!(t, X) === X
    # @test isequal(X, Y)
    # @test reconstruct!(t, Y) === Y
    # @test Y ≈ _X

    # X = copy(_X)
    # t = fit(ZScoreTransform, X, dims=1)
    # Y = transform(t, X)
    # @test Y ≈ (X .- mean(X, dims=1)) ./ std(X, dims=1)
    # @test transform(t, X) ≈ Y
    # @test reconstruct(t, Y) ≈ X
    # @test Y ≈ standardize(ZScoreTransform, X, dims=1)
    # @test transform!(t, X) === X
    # @test isequal(X, Y)
    # @test reconstruct!(t, Y) === Y
    # @test Y ≈ _X

    # X = copy(_X)
    # t = fit(UnitRangeTransform, X, dims=1)
    # Y = transform(t, X)
    # @test Y ≈ (X .- minimum(X, dims=1)) ./ (maximum(X, dims=1) .- minimum(X, dims=1))
    # @test transform(t, X) ≈ Y
    # @test reconstruct(t, Y) ≈ X
    # @test transform!(t, X) === X
    # @test isequal(X, Y)
    # @test reconstruct!(t, Y) === Y
    # @test Y ≈ _X

    # X = copy(_X)
    # t = fit(UnitRangeTransform, X, dims=1, unit=false)
    # Y = transform(t, X)
    # @test Y ≈ X ./ (maximum(X, dims=1) .- minimum(X, dims=1))
    # @test transform(t, X) ≈ Y
    # @test reconstruct(t, Y) ≈ X
    # @test Y ≈ standardize(UnitRangeTransform, X, dims=1, unit=false)
    # @test transform!(t, X) === X
    # @test isequal(X, Y)
    # @test reconstruct!(t, Y) === Y
    # @test Y ≈ _X
end

@testset "3D normalization" begin
    _X = randn(10, 10, 100)
    X = copy(_X)
    T = fit(ZScore, X, dims=3)
    Y = normalize(X, T)
    Z = copy(_X)
    for i ∈ CartesianIndices((axes(Z, 1), axes(Z, 2)))
        x = @view Z[i, :]
        x .= (x.-mean(x))./std(x)
    end
    @test !isnothing(T.p)
    @test length(T.p) == 2
    @test size(T.p[1])[1:2] == size(T.p[2])[1:2] == size(X)[1:2]
    @test Y ≈ Z
    @test denormalize(Y, T) ≈ X
    @test_nowarn normalize!(X, T)
    @test X == Y
    @test_nowarn denormalize!(Y, T)
    @test Y ≈ _X
end

@testset "Sigmoid" begin
    _X = randn(10, 10, 100)
    X = copy(_X)
    T = fit(Sigmoid, X, dims=3)
    Y = normalize(X, T)
    Z = copy(_X)
    for i ∈ CartesianIndices((axes(Z, 1), axes(Z, 2)))
        x = @view Z[i, :]
        x .= 1.0./(1 .+ exp.(.-(x.-mean(x))./std(x)))
    end
    @test !isnothing(T.p)
    @test length(T.p) == 2
    @test size(T.p[1])[1:2] == size(T.p[2])[1:2] == size(X)[1:2]
    @test Y ≈ Z
    @test denormalize(Y, T) ≈ X
    @test_nowarn normalize!(X, T)
    @test X == Y
    @test_nowarn denormalize!(Y, T)
    @test Y ≈ _X
end

@testset "RobustSigmoid" begin
    _X = randn(10, 10, 100)
    X = copy(_X)
    T = fit(RobustSigmoid, X, dims=3)
    Y = normalize(X, T)
    Z = copy(_X)
    for i ∈ CartesianIndices((axes(Z, 1), axes(Z, 2)))
        x = @view Z[i, :]
        x .= 1.0./(1 .+ exp.(.-(x.-median(x))./(SB.iqr(x)./1.35)))
    end
    @test !isnothing(T.p)
    @test length(T.p) == 2
    @test size(T.p[1])[1:2] == size(T.p[2])[1:2] == size(X)[1:2]
    @test Y ≈ Z
    @test denormalize(Y, T) ≈ X
    @test_nowarn normalize!(X, T)
    @test X == Y
    @test_nowarn denormalize!(Y, T)
    @test Y ≈ _X
end

@testset "NaNZScore" begin
    _X = randn(10, 10, 100)
    idxs = rand(1:prod(size(_X)), 100)
    _X[idxs] .= NaN
    X = copy(_X)
    T = fit(nansafe(ZScore), X, dims=3)
    Y = normalize(X, T)
    Z = copy(_X)
    for i ∈ CartesianIndices((axes(Z, 1), axes(Z, 2)))
        x = @view Z[i, :]
        x .= (x.-mean(filter(!isnan, x)))./std(filter(!isnan, x))
    end
    @test !isnothing(T.p)
    @test length(T.p) == 2
    @test size(T.p[1])[1:2] == size(T.p[2])[1:2] == size(X)[1:2]
    @test filter!(!isnan, Y[:]) ≈ filter!(!isnan, Z[:])
    @test filter!(!isnan, denormalize(Y, T)[:]) ≈ filter!(!isnan, X[:])
    @test_nowarn normalize!(X, T)
    @test filter!(!isnan, X[:]) == filter!(!isnan, Y[:])
    @test_nowarn denormalize!(Y, T)
    @test filter!(!isnan, Y[:]) ≈ filter!(!isnan, _X[:])
end

@testset "NaNSigmoid" begin
    _X = randn(10, 10, 100)
    idxs = rand(1:prod(size(_X)), 100)
    _X[idxs] .= NaN
    X = copy(_X)
    T = fit(nansafe(Sigmoid), X, dims=3)
    Y = normalize(X, T)
    Z = copy(_X)
    for i ∈ CartesianIndices((axes(Z, 1), axes(Z, 2)))
        x = @view Z[i, :]
        x .= 1.0./(1 .+ exp.(.-(x.-mean(filter(!isnan, x)))./(std(filter(!isnan, x)))))
    end
    @test !isnothing(T.p)
    @test length(T.p) == 2
    @test size(T.p[1])[1:2] == size(T.p[2])[1:2] == size(X)[1:2]
    @test filter!(!isnan, Y[:]) ≈ filter!(!isnan, Z[:])
    @test filter!(!isnan, denormalize(Y, T)[:]) ≈ filter!(!isnan, X[:])
    @test_nowarn normalize!(X, T)
    @test filter!(!isnan, X[:]) == filter!(!isnan, Y[:])
    @test_nowarn denormalize!(Y, T)
    @test filter!(!isnan, Y[:]) ≈ filter!(!isnan, _X[:])
end

@testset "ND normalization" begin
    Nmax = 5
    for _ = 1:10
        ds = rand(2:Nmax)
        sz = rand(2:ds, ds)
        _X = rand(sz...)
        Nnorm = rand(1:Nmax÷2)
        normdims = unique(rand(1:ds, Nnorm))
        notnormdims = setdiff(1:ndims(_X), normdims)
        X = copy(_X)
        T = fit(ZScore, X, dims=normdims)
        Y = normalize(X, T)
        @test !isnothing(T.p)
        @test length(T.p) == 2
        @test size(T.p[1])[notnormdims] == size(T.p[2])[notnormdims] == size(X)[notnormdims]
        @test denormalize(Y, T) ≈ X
        @test_nowarn normalize!(X, T)
        @test X == Y
        @test_nowarn denormalize!(Y, T)
        @test Y ≈ _X
    end
end

@testset "StatsBase comparison" begin
    X = rand(1000, 50)
    Y = normalize(X, ZScore; dims=1)
    Z = SB.standardize(SB.ZScoreTransform, X; dims=1)
    @test Y ≈ Z
end

println("\nNormalization.jl")
display(@benchmark normalize(rand(1000, 10000), ZScore; dims=1))
println("\nStatsBase.jl")
display(@benchmark SB.standardize(SB.ZScoreTransform, rand(1000, 10000); dims=1))


# ---
# title: 906. Super Palindromes
# id: problem906
# author: Tian Jun
# date: 2020-10-31
# difficulty: Hard
# categories: Math
# link: <https://leetcode.com/problems/super-palindromes/description/>
# hidden: true
# ---
#
# Let's say a positive integer is a  _superpalindrome_  if it is a palindrome,
# and it is also the square of a palindrome.
#
# Now, given two positive integers `L` and `R` (represented as strings), return
# the number of superpalindromes in the inclusive range `[L, R]`.
#
#
#
# **Example 1:**
#
#
#
#     Input: L = "4", R = "1000"
#     Output: 4
#     **Explanation** : 4, 9, 121, and 484 are superpalindromes.
#     Note that 676 is not a superpalindrome: 26 * 26 = 676, but 26 is not a palindrome.
#
#
#
# **Note:**
#
#   1. `1 <= len(L) <= 18`
#   2. `1 <= len(R) <= 18`
#   3. `L` and `R` are strings representing integers in the range `[1, 10^18)`.
#   4. `int(L) <= int(R)`
#
#
#
#
## @lc code=start
using LeetCode

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


function arr_sumsqr{T <: Number}(x::Array{T})
r = zero(T)
for i = 1:length(x)
r = r + x[i] ^ 2
end
return r
end


######### Jags line program example  ###########

using StatsPlots, Jags, Statistics

ProjDir = joinpath(dirname(@__FILE__))
cd(ProjDir) do

  line = "
  model {
    for (i in 1:n) {
          mu[i] <- alpha + beta*(x[i] - x.bar);
          y[i]   ~ dnorm(mu[i],tau);
    }
    x.bar   <- mean(x[]);
    alpha    ~ dnorm(0.0,1.0E-4);
    beta     ~ dnorm(0.0,1.0E-4);
    tau      ~ dgamma(1.0E-3,1.0E-3);
    sigma   <- 1.0/sqrt(tau);
  }
  "

  monitors = Dict(
    "alpha" => true,
    "beta" => true,
    "tau" => true,
    "sigma" => true
    )

  jagsmodel = Jagsmodel(
    name="line3",
    model=line,
    monitor=monitors,
    deviance=true, dic=true, popt=true,
    jagsthin=10, thin=1,
    pdir=ProjDir
    );

  println("\nJagsmodel that will be used:")
  jagsmodel |> display

  data = Dict(
    "x" => [1, 2, 3, 4, 5],
    "y" => [1, 3, 3, 3, 5],
    "n" => 5
  )

  inits = [
    Dict("alpha" => 0,"beta" => 0,"tau" => 1),
    Dict("alpha" => 1,"beta" => 2,"tau" => 1),
    Dict("alpha" => 3,"beta" => 3,"tau" => 2),
    Dict("alpha" => 5,"beta" => 2,"tau" => 5)
  ]

  println("Input observed data dictionary:")
  data |> display
  println("\nInput initial values dictionary:")
  inits |> display
  println()

  sim = jags(jagsmodel, data, inits, ProjDir)
  println()

  ## Plotting
  p = plot(sim)

end #cd


export RandomCograph, is_cograph, CographRepresentation

"""
`RandomCograph(vtcs)` creates a random cograph with vertices
in `vtcs`.

`RandomCograph(n)` creates a random cograph with vertex set `1:n`.
"""
function RandomCograph(vtcs::Vector{T}) where {T}
    G = SimpleGraph{T}()

    # basis cases
    if length(vtcs) == 0
        return G
    end
    if length(vtcs) == 1
        add!(G, vtcs[1])
        return G
    end

    # split the vertex set
    alist = Vector{T}()
    blist = Vector{T}()
    while length(alist) == 0 || length(blist) == 0
        alist = Vector{T}()
        blist = Vector{T}()
        for v in vtcs
            if rand() < 0.5
                push!(alist, v)
            else
                push!(blist, v)
            end
        end
    end

    # recurse
    G = RandomCograph(alist)
    H = RandomCograph(blist)

    # copy H into G
    for v in blist
        add!(G, v)
    end
    for e in elist(H)
        add!(G, e[1], e[2])
    end

    # final coin flip
    if rand() < 0.5
        for a in alist
            for b in blist
                add!(G, a, b)
            end
        end
    end
    cache_save(G, :name, "Cograph")
    return G
end

RandomCograph(n::Int) = RandomCograph(collect(1:n))
RandomCograph(A::BitSet) = RandomCograph(collect(A))
RandomCograph(A::Set) = RandomCograph(collect(A))

"""
`is_cograph(G)` returns `true` is `G` is a cograph, i.e., a
complement reducible graph.
"""
function is_cograph(G::SimpleGraph{T}) where {T}
    if NV(G) <= 3    # all graphs with 3 or fewer vertices are cographs
        return true
    end
    if is_connected(G)
        GG = G'
        if is_connected(GG)
            return false
        end
        return is_cograph(GG)
    end

    clist = parts(components(G))
    for A in clist
        H = induce(G, A)
        if !is_cograph(H)
            return false
        end
    end
    return true
end

"""
`CographRepresentation(G::SimpleGraph)` returns a `String` that
presents a construction of `G` using disjoint union `+` and join `*`.
Throws an error if `G` is not a cograph.
"""
function CographRepresentation(G::SimpleGraph)
    if cache_check(G, :CographRepresentation)
        return cache_recall(G, :CographRepresentation)
    end

    if NV(G) == 0
        return "()"
    end
    if NV(G) == 1
        v = first(G.V)
        return "$v"
    end

    if is_connected(G)
        comps = collect(parts(components(G')))
        ncomps = length(comps)
        if ncomps == 1
            error("This is not a cograph")
        end
        result = ""
        for k = 1:ncomps
            H = induce(G, comps[k])
            if NV(H) > 1
                result *= "(" * CographRepresentation(H) * ")"
            else
                result *= CographRepresentation(H)
            end
            if k < ncomps
                result *= "∨"
            end
        end
    else
        comps = collect(parts(components(G)))
        ncomps = length(comps)
        result = ""
        for k = 1:ncomps
            H = induce(G, comps[k])
            if NV(H) > 1
                result *= "(" * CographRepresentation(H) * ")"
            else
                result *= CographRepresentation(H)
            end
            if k < ncomps
                result *= "+"
            end
        end
    end

    cache_save(G, :CographRepresentation, result)
    return result
end


using CSV
using Statistics
using StatsBase

function load_data(labels::Vector = [0,1], frac_val::Real = 0.1)

    # read training data
    d_train = Matrix(CSV.read(pwd()*"/ds4/pima-trn_edit.csv"; allowmissing=:auto,header=0))
    x_train = d_train[:,1:8]
    y_train = convert(Array{Int64,1},d_train[:,end])

    # read test data
    d_test = Matrix(CSV.read(pwd()*"/ds4/pima-val_edit.csv"; allowmissing=:auto,header=0))
    x_test = d_test[:,1:8]
    y_test = convert(Array{Int64,1},d_test[:,end])

    # normalize data
    x_train = (x_train .- mean(x_train,dims=1))./std(x_train,dims=1)
    x_test = (x_test .- mean(x_test,dims=1))./std(x_test,dims=1)

    # split training data to training data and val data
    nbr_obs_train = size(x_train,1)
    nbr_val = Int(floor(nbr_obs_train*frac_val))

    idx_val = sample(1:nbr_obs_train, nbr_val)
    idx_train = setdiff(1:nbr_obs_train, idx_val)

    x_val = x_train[idx_val,:]
    x_train = x_train[idx_train,:]

    y_val = y_train[idx_val]
    y_train = y_train[idx_train]

    # transpose data
    x_train = Array(x_train')
    x_test = Array(x_test')
    x_val = Array(x_val')

    # set labels for classes
    y_train[findall(y_train .== 1)] .= labels[2]
    y_train[findall(y_train .== 0)] .= labels[1]

    y_val[findall(y_val .== 1)] .= labels[2]
    y_val[findall(y_val .== 0)] .= labels[1]

    y_test[findall(y_test .== 1)] .= labels[2]
    y_test[findall(y_test .== 0)] .= labels[1]

    return x_train,y_train,x_val,y_val,x_test,y_test
end

function class_logistic(y)

    class = zeros(Int, length(y))

    for i in 1:length(y)
        if y[i] > 0.5
            class[i] = 1
        end
    end

    return class

end

function class_nll(y,labels)

    class = zeros(Int, size(y,2))

    for i = 1:size(y,2)
        if findmax(y[:,i])[2]  == 1
            class[i] = labels[1]
        else
            class[i] = labels[2]
        end
    end

    return class

end

function classification_results(y_hat, y, classes)

    idx_pos = findall(y .== classes[1])
    idx_neg = findall(y .== classes[2])

    TP = sum(y_hat[idx_pos] .== classes[1])
    TN = sum(y_hat[idx_neg] .== classes[2])

    sens = TP/length(idx_pos)
    spec = TN/length(idx_neg)
    acc = (TP+TN)/length(y)

    return [acc,sens,spec]

end