### A Pluto.jl notebook ###
# v0.14.4

using Markdown
using InteractiveUtils

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

# ╔═╡ f8e2f7e1-d6a7-43ef-879c-db33c5f66924
begin
	using PlutoUI
	PlutoUI.TableOfContents(aside=true)
end

# ╔═╡ c44c6c6a-3b8c-46e4-93ad-365d03082226
using SymPy, LinearAlgebra

# ╔═╡ c226e28d-3a8a-42f6-8a51-2ac418023a27
md"""
# Singular Value Decomposition - Definitions and Facts

__Prerequisites__

The reader should be familiar with basic linear algebra concepts and notebooks related to eigenvalue decomposition.

__Competences__

The reader should be able to undestand and check the facts about singular value decomposition.

__Selected references__

There are many excellent books on the subject. Here we list a few:

* J.W. Demmel, _Applied Numerical Linear Algebra_, SIAM, Philadelphia, 1997.
* G. H. Golub and C. F. Van Loan, _Matrix Computations_, 4th ed., The John Hopkins University Press, Baltimore, MD, 2013.
* N. Higham, _Accuracy and Stability of Numerical Algorithms_, SIAM, Philadelphia, 2nd ed., 2002.
* L. Hogben, ed., _Handbook of Linear Algebra_, CRC Press, Boca Raton, 2014.
* B. N. Parlett, _The Symmetric Eigenvalue Problem_, Prentice-Hall, Englewood Cliffs, NJ, 1980, also SIAM, Philadelphia, 1998.
* G. W. Stewart, _Matrix Algorithms, Vol. II: Eigensystems_, SIAM, Philadelphia, 2001.
* L. N. Trefethen and D. Bau, III, _Numerical Linear Algebra_, SIAM, Philadelphia, 1997.

"""

# ╔═╡ ddbd255b-7ddd-4fba-ac4d-722e88801e59
md"""
# Singular value problems

For more details and the proofs of the Facts below, see [R. C. Li, Matrix Perturbation Theory, pp. 21.6-21.8](https://www.routledge.com/Handbook-of-Linear-Algebra/Hogben/p/book/9781138199897) and [R. Mathias, Singular Values and Singular Value Inequalities,pp. 24.1-24.17](https://www.routledge.com/Handbook-of-Linear-Algebra/Hogben/p/book/9781138199897) and the references therein.

## Definitions

Let $A\in\mathbb{C}^{m\times n}$ and let $q=\min\{m,n\}$.

The __singular value decomposition__ (SVD) of $A$ is

$$A=U\Sigma V^*,$$

where $U\in\mathbb{C}^{m\times m}$ and $V\in\mathbb{C}^{n\times n}$ are unitary, and $\Sigma=\mathop{\mathrm{diag}}(\sigma_1,\sigma_2,\ldots)\in\mathbb{R}^{m\times n}$ with all $\sigma_j\geq 0$.

Here $\sigma_j$ is the __singular value__, $u_j\equiv U_{:,j}$ is the corresponding __left singular vector__, and $v_j\equiv V_{:,j}$ is the corresponding __right singular vector__.

The __set of singular values__ is $sv(A)=\{\sigma_1,\sigma_2,\ldots,\sigma_{q}\}$.

We assume that singular values are ordered, $\sigma_1\geq\sigma_2\geq\cdots\sigma_q\geq 0$.

The __Jordan-Wielandt__ matrix is the Hermitian matrix

$$
J=\begin{bmatrix}0 & A \\ A^* & 0 \end{bmatrix}
\in \mathbb{C}^{(m+n) \times (m+n)}.$$
"""

# ╔═╡ caa44e3e-3af5-4836-8ae3-dd3b9d68d222
md"""
## Facts

There are many facts related to the singular value problem for general matrices. We state some basic ones:

1. If $A\in\mathbb{R}^{m\times n}$, then $U$ and $V$ are real.

2. Singular values are unique (uniquely determined by the matrix).

3.  $\sigma_j(A^T)=\sigma_j(A^*)=\sigma_j(\bar A)=\sigma_j(A)$ for $j=1,2,\ldots,q$.

4.  $A v_j=\sigma_j u_{j}$ and $A^* u_{j}=\sigma_j v_{j}$ for $j=1,2,\ldots,q$.

5.  $A=\sigma_1 u_{1} v_{1}^* + \sigma_2 u_{2} v_{2}^* +\cdots + \sigma_q u_{q} v_{q}^*$.

6. __Unitary invariance.__ For any unitary $U\in\mathbb{C}^{m\times m}$ and $V\in\mathbb{C}^{n\times n}$, $sv(A)=sv(UAV)$.

7. There exist unitary matrices $U\in\mathbb{C}^{m\times m}$ and $V\in\mathbb{C}^{n\times n}$ such that $A=UBV$ if and only if  $sv(A)=sv(B)$.

8. SVD of $A$ is related to eigenvalue decompositions of Hermitian matrices $A^*A=V\Sigma^T\Sigma V^*$ and $AA^*=U\Sigma\Sigma^TU^*$. Thus, $\sigma_j^2(A)=\lambda_j(A^*A)=\lambda_j(AA^*)$ for $j=1,2,\ldots,q$.

9. The eigenvalues of Jordan-Wielandt matrix are $\pm \sigma_1(A), \pm\sigma_2(A), \cdots,\pm\sigma_q(A)$ together with $|m-n|$ zeros. The eigenvectors are obtained from an SVD of $A$. This relationship is used to deduce singular value results from the results for eigenvalues of Hermitian matrices.

10.  $\mathop{\mathrm{trace}}(|A|_{spr})=\sum_{i=1}^q \sigma_i$, where $|A|_{spr}=(A^*A)^{1/2}$.

11. If $A$ is square, then $|\det(A)|=\prod_{i=1}^n \sigma_i$.

12. If $A$ is square, then $A$ is singular $\Leftrightarrow$ $\sigma_j(A)=0$ for some $j$.

13. __Min-max Theorem.__ It holds:

$$
\begin{aligned}
\sigma_k& =\max_{\dim(W)=k}\min_{x\in W, \ \|x\|_2=1} \|Ax\|_2\\
& =\min_{\dim(W)=n-k+1}\max_{x\in W, \ \|x\|_2=1} \|Ax\|_2.
\end{aligned}$$

14.  $\|A\|_2=\sigma_1(A)$.

15. For $B\in\mathbb{C}^{m\times n}$,

$$
|\mathop{\mathrm{trace}}(AB^*)|\leq \sum_{j=1}^q \sigma_j(A)\sigma_j(B).$$

16. __Interlace Theorems.__ Let $B$ denote $A$ with the one of its rows _or_ columns deleted. Then

$$
\sigma_{j+1}(A)\leq \sigma_j(B)\leq \sigma_j(A),\quad j=1,\ldots,q-1.$$

Let $B$ denote $A$ with a row _and_ a column deleted. Then

$$
\sigma_{j+2}(A)\leq \sigma_j(B)\leq \sigma_j(A),\quad j=1,\ldots,q-2.$$

17. __Weyl Inequalities.__ For $B\in\mathbb{C}^{m\times n}$, it holds:

$$
\begin{aligned}
\sigma_{j+k-1}(A+B)& \leq \sigma_j(A)+\sigma_k(B), \quad  j+k\leq q+1,\\
\sum_{j=1}^k \sigma_j(A+B)& \leq \sum_{j=1}^k \sigma_j(A) + \sum_{j=1}^k \sigma_j(B),
\quad k=1,\ldots,q.
\end{aligned}$$

"""

# ╔═╡ 4b764569-a32d-4170-a11d-2072d971846a
md"""
## Examples

### Symbolic computation
"""

# ╔═╡ 772342da-a960-4aa7-b30b-7122b049eff9
A=[  3   2   1
 -5  -1  -4
  5   0   2]

# ╔═╡ 314c41e0-03e7-4f8e-851c-a4374e7f5092
x=symbols("x")

# ╔═╡ 4ab83338-0428-4942-979e-c1021b81c309
B=A'*A

# ╔═╡ bf02e40e-94f4-4c69-ab74-3496cbcd6cdf
# Characteristic polynomial p_B(λ)
p(x)=simplify(det(B-x*Matrix{Int}(I,3,3)))

# ╔═╡ 54264dd9-98cf-4c8f-b6f0-c6ee2ba11cdb
λ=solve(p(x),x)

# ╔═╡ 6cc74bec-e335-471d-87a0-9b9bad391a1d
map(Float64,λ)

# ╔═╡ 2d1ea310-24a8-448c-9a3d-0a4420bbc90c
begin
	V=Array{Any}(undef,3,3)
	for j=1:3
	    V[:,j]=nullspace(B-map(Float64,λ[j])*I)
	end
	V
end

# ╔═╡ 92b68122-5e9a-443a-aaa3-ad301cb1878a
begin
	U=Array{Any}(undef,3,3)
	for j=1:3
	    U[:,j]=nullspace(A*A'-map(Float64,λ[j])*I)
	end
	U
end

# ╔═╡ edaaa469-6a73-43e6-a8b3-b365bf265e04
σ=sqrt.(λ)

# ╔═╡ c1ac5d19-4971-461f-b73d-10aabb4eb56f
# Diagonalizing
V'*A'*A*V

# ╔═╡ 72352834-0bf5-41b7-86e2-0b2cb733d7f4
# Residual -what is wrong?
A-U*Diagonal(float(σ))*V'

# ╔═╡ ace071e5-764a-4c04-8916-7287ed53a01e
A-U*Diagonal(float(σ))*(V*Diagonal(sign.(U'*A*V)))'

# ╔═╡ 8d911867-9b90-4bff-9d0c-7358dfcc04ec
#?svd

# ╔═╡ 1b1b7ddd-f50d-4d65-8cc8-398955dcce34
# Using the structure SVD
S=svd(A)

# ╔═╡ b6663c47-1293-43ba-8c13-20858054c975
S.Vt

# ╔═╡ 74e584f4-a233-4f18-b04c-dfd1710515e1
# Residual
A-S.U*Diagonal(S.S)*S.Vt

# ╔═╡ 6ba22886-faa3-4f30-af96-366d9c001c94
md"""
Observe the signs of the columns!
"""

# ╔═╡ 80d341c5-43ae-4568-bcf3-ced1947662ce
S.U

# ╔═╡ 1245fe85-25a8-44cb-bbd7-3bc477859d04
U

# ╔═╡ abe3e648-65e6-423c-83de-673e96013670
md"
### Random complex matrix
"

# ╔═╡ 64535706-eaba-4155-b593-12cba7366127
begin
	import Random
	Random.seed!(421)
	m=5
	n=3
	q=min(m,n)
	A₁=rand(ComplexF64,m,n)
end

# ╔═╡ bd88592a-781f-46e7-a175-342b78906c45
S₁=svd(A₁,full=true)

# ╔═╡ f54a7c12-d175-45cf-b996-d13024910135
norm(A₁-S₁.U[:,1:q]*Diagonal(S₁.S)*S₁.Vt), norm(S₁.U'*S₁.U-I), norm(S₁.Vt*S₁.V-I)

# ╔═╡ b39c5987-d320-499a-816c-31608eb1d856
@bind k Slider(1:q,show_value=true)

# ╔═╡ e7aa4857-76c0-42da-964a-cecd428ec70e
# Fact 4
norm(A₁*S₁.V[:,k]-S₁.S[k]*S₁.U[:,k]), norm(A₁'*S₁.U[:,k]-S₁.S[k]*S₁.V[:,k])

# ╔═╡ da2e1129-474e-4a6a-9d66-7224f1f25b34
λ₁,V₁=eigen(A₁'*A₁)

# ╔═╡ b740803d-7bb2-4ce2-8338-4a53e333f9f9
sqrt.(λ₁)

# ╔═╡ 43316c8b-149b-4a52-90e9-ccb2c2ca4d67
S₁.V

# ╔═╡ 8a06f9fc-62d0-4394-8f5c-b861a63da947
abs.(S₁.Vt*V₁)

# ╔═╡ 879285b0-8fc9-43a2-b06d-30fd95556d61
md"""
__Explain non-uniqueness of $U$ and $V$!__
"""

# ╔═╡ ff0ea61d-7944-4316-a224-72461a808cdc
# Jordan-Wielandt matrix
J=[zero(A*A') A; A' zero(A'*A)]

# ╔═╡ 5d941c94-b186-49ba-9f03-1613a638ecb4
λJ,UJ=eigen(J)

# ╔═╡ 9909c421-607d-4883-9b35-705290ce8bf3
λJ

# ╔═╡ dda804cf-71c1-4735-bfc7-a97453698c8f
 float(σ)

# ╔═╡ f4b2ce9d-4783-420d-bc87-2ce27671c7e4
md"
### Random real matrix
"

# ╔═╡ 87de7452-77af-466b-920c-1a562ee05ea2
begin
	m₂=8
	n₂=5
	q₂=min(m₂,n₂)
	A₂=rand(-9:9,m₂,n₂)
end

# ╔═╡ 27f76447-12cf-4096-a1ab-ad997367b336
S₂=svd(A₂)

# ╔═╡ 63af09b0-1b32-478e-868f-95ddf2e951e8
# Fact 10
tr(sqrt(A₂'*A₂)), sum(S₂.S)

# ╔═╡ c30340fd-cc4e-4cbe-b68a-fb0b01f383fa
begin
	# Fact 11
	B₁=rand(n₂,n₂)
	det(B₁), prod(svdvals(B₁))
end

# ╔═╡ ecae308e-7444-4883-84bf-8106bb7106e9
# Fact 14
norm(A₂), opnorm(A₂), S₂.S[1]

# ╔═╡ eb15023f-f126-4177-bc2c-057543b8fcc1
begin
	# Fact 15
	B₂=rand(m₂,n₂)
	abs(tr(A₂*B₂')), svdvals(A₂)⋅svdvals(B₂)
end

# ╔═╡ 07f30928-36aa-4d59-bb53-fa8df06fdad0
@bind j Slider(1:q₂,show_value=true)

# ╔═╡ bf28d15d-c243-4229-8d41-0945c63e3baa
# Interlace Theorems (repeat several times)
S₂.S, svdvals(A₂[[1:j-1;j+1:m₂],:]), svdvals(A₂[:,[1:j-1;j+1:n₂]])

# ╔═╡ 69470862-8321-4953-b324-3a5b4e93d9c8
# Weyl Inequalities
# B=rand(m,n)
[svdvals(A₂+B₂) S₂.S svdvals(B₂)]

# ╔═╡ 35fead5b-4425-40b3-a64d-9cf7ebab18d2
@bind l Slider(1:q₂,show_value=true)

# ╔═╡ 61ecca0f-1e5e-4dec-90d3-924961b5f9c8
sum(svdvals(A₂+B₂)[1:l]),sum(svdvals(A₂)[1:l].+sum(svdvals(B₂)[1:l]))

# ╔═╡ 2d804bbd-c6ce-49ce-9197-d7850ccb7db5
md"""
# Matrix approximation

Let $A=U\Sigma V^*$, let $\tilde \Sigma$ be equal to $\Sigma$ except that $\tilde \Sigma_{jj}=0$ for $j>k$, and let $\tilde A=U\tilde \Sigma V^*$. Then $\mathop{\mathrm{rank}}(\tilde A)\leq k$ and

$$
\begin{aligned}
\min\{\|A-B\|_2: \mathop{\mathrm{rank}}(B)\leq k\} & =\|A-\tilde A\|_2=\sigma_{k+1}(A)\\
\min\{\|A-B\|_F: \mathop{\mathrm{rank}}(B)\leq k\} & =\|A-\tilde A\|_F=
\bigg(\sum_{j=k+1}^{q}\sigma_{j}^2(A)\bigg)^{1/2}.
\end{aligned}$$

This is the __Eckart-Young-Mirsky Theorem__.
"""

# ╔═╡ d7221da7-78fc-4d86-a693-95bab6d12d5a
A₂

# ╔═╡ 4d9640da-d85c-41ec-afcf-fa9548c25804
S₂.S

# ╔═╡ 997d0c9c-22a4-4a60-9730-0668eda26af4
@bind k₀ Slider(1:q₂,show_value=true)

# ╔═╡ 76bc9a58-c163-4df2-a315-5f9474e746aa
B₀=S₂.U*Diagonal([S₂.S[1:k₀];zeros(q₂-k₀)])*S₂.Vt

# ╔═╡ 36fb7249-ecc5-432a-9842-1c9cbf17c30a
opnorm(A₂-B₀), S₂.S[k₀+1]

# ╔═╡ 42ec2898-6676-4254-b553-215da8ec7490
norm(A₂-B₀),norm(S₂.S[k₀+1:q₂])

# ╔═╡ Cell order:
# ╟─f8e2f7e1-d6a7-43ef-879c-db33c5f66924
# ╟─c226e28d-3a8a-42f6-8a51-2ac418023a27
# ╟─ddbd255b-7ddd-4fba-ac4d-722e88801e59
# ╟─caa44e3e-3af5-4836-8ae3-dd3b9d68d222
# ╟─4b764569-a32d-4170-a11d-2072d971846a
# ╠═c44c6c6a-3b8c-46e4-93ad-365d03082226
# ╠═772342da-a960-4aa7-b30b-7122b049eff9
# ╠═314c41e0-03e7-4f8e-851c-a4374e7f5092
# ╠═4ab83338-0428-4942-979e-c1021b81c309
# ╠═bf02e40e-94f4-4c69-ab74-3496cbcd6cdf
# ╠═54264dd9-98cf-4c8f-b6f0-c6ee2ba11cdb
# ╠═6cc74bec-e335-471d-87a0-9b9bad391a1d
# ╠═2d1ea310-24a8-448c-9a3d-0a4420bbc90c
# ╠═92b68122-5e9a-443a-aaa3-ad301cb1878a
# ╠═edaaa469-6a73-43e6-a8b3-b365bf265e04
# ╠═c1ac5d19-4971-461f-b73d-10aabb4eb56f
# ╠═72352834-0bf5-41b7-86e2-0b2cb733d7f4
# ╠═ace071e5-764a-4c04-8916-7287ed53a01e
# ╠═8d911867-9b90-4bff-9d0c-7358dfcc04ec
# ╠═1b1b7ddd-f50d-4d65-8cc8-398955dcce34
# ╠═b6663c47-1293-43ba-8c13-20858054c975
# ╠═74e584f4-a233-4f18-b04c-dfd1710515e1
# ╟─6ba22886-faa3-4f30-af96-366d9c001c94
# ╠═80d341c5-43ae-4568-bcf3-ced1947662ce
# ╠═1245fe85-25a8-44cb-bbd7-3bc477859d04
# ╟─abe3e648-65e6-423c-83de-673e96013670
# ╠═64535706-eaba-4155-b593-12cba7366127
# ╠═bd88592a-781f-46e7-a175-342b78906c45
# ╠═f54a7c12-d175-45cf-b996-d13024910135
# ╠═b39c5987-d320-499a-816c-31608eb1d856
# ╠═e7aa4857-76c0-42da-964a-cecd428ec70e
# ╠═da2e1129-474e-4a6a-9d66-7224f1f25b34
# ╠═b740803d-7bb2-4ce2-8338-4a53e333f9f9
# ╠═43316c8b-149b-4a52-90e9-ccb2c2ca4d67
# ╠═8a06f9fc-62d0-4394-8f5c-b861a63da947
# ╟─879285b0-8fc9-43a2-b06d-30fd95556d61
# ╠═ff0ea61d-7944-4316-a224-72461a808cdc
# ╠═5d941c94-b186-49ba-9f03-1613a638ecb4
# ╠═9909c421-607d-4883-9b35-705290ce8bf3
# ╠═dda804cf-71c1-4735-bfc7-a97453698c8f
# ╟─f4b2ce9d-4783-420d-bc87-2ce27671c7e4
# ╠═87de7452-77af-466b-920c-1a562ee05ea2
# ╠═27f76447-12cf-4096-a1ab-ad997367b336
# ╠═63af09b0-1b32-478e-868f-95ddf2e951e8
# ╠═c30340fd-cc4e-4cbe-b68a-fb0b01f383fa
# ╠═ecae308e-7444-4883-84bf-8106bb7106e9
# ╠═eb15023f-f126-4177-bc2c-057543b8fcc1
# ╠═07f30928-36aa-4d59-bb53-fa8df06fdad0
# ╠═bf28d15d-c243-4229-8d41-0945c63e3baa
# ╠═69470862-8321-4953-b324-3a5b4e93d9c8
# ╠═35fead5b-4425-40b3-a64d-9cf7ebab18d2
# ╠═61ecca0f-1e5e-4dec-90d3-924961b5f9c8
# ╟─2d804bbd-c6ce-49ce-9197-d7850ccb7db5
# ╠═d7221da7-78fc-4d86-a693-95bab6d12d5a
# ╠═4d9640da-d85c-41ec-afcf-fa9548c25804
# ╠═997d0c9c-22a4-4a60-9730-0668eda26af4
# ╠═76bc9a58-c163-4df2-a315-5f9474e746aa
# ╠═36fb7249-ecc5-432a-9842-1c9cbf17c30a
# ╠═42ec2898-6676-4254-b553-215da8ec7490

src/Module B - Eigenvalue and Singular Value Decompositions/L5a Singular Value Decomposition - Definitions and Facts .jl


using ChaosTools
using ChaosTools.DelayEmbeddings
using Test, ChaosTools.StaticArrays
using DynamicalSystemsBase: CDS, DDS
using DynamicalSystemsBase.Systems: hoop, hoop_jac, hiip, hiip_jac
using DynamicalSystemsBase.Systems: loop, loop_jac, liip, liip_jac
using OrdinaryDiffEq
using Statistics

println("\nTesting lyapunov exponents...")
@testset "Lyapunov exponents" begin
u0 = [0, 10.0, 0]
p = [10, 28, 8/3]
u0h = ones(2)
ph = [1.4, 0.3]

FUNCTIONS = [liip, liip_jac, loop, loop_jac, hiip, hiip_jac, hoop, hoop_jac]
INITCOD = [u0, u0h]
PARAMS = [p, ph]
MLE = [[0.75, 0.95], [0.41, 0.43]]
SLE = [[-0.1, 0.1], [-1.63, -1.61]]

@testset "Lyapunov spectrum" begin
for i in 1:8
    @testset "combination $i" begin
        sysindx = i < 5 ? 1 : 2
        if i < 5
            if isodd(i)
                ds = CDS(FUNCTIONS[i], INITCOD[sysindx], PARAMS[sysindx])
            else
                ds = CDS(FUNCTIONS[i-1], INITCOD[sysindx], PARAMS[sysindx], FUNCTIONS[i])
            end
        else
            if isodd(i)
                ds = DDS(FUNCTIONS[i], INITCOD[sysindx], PARAMS[sysindx])
            else
                ds = DDS(FUNCTIONS[i-1], INITCOD[sysindx], PARAMS[sysindx], FUNCTIONS[i])
            end
        end

        λ = lyapunovspectrum(ds, 2000)
        if i < 5
            λ2 = lyapunovspectrum(ds, 2000; Δt = 2.0, Ttr = 10.0,
            diffeq = (abstol = 1e-9, alg = Tsit5()))
        else
            λ2 = lyapunovspectrum(ds, 1000; Δt = 5, Ttr = 20)
        end

        @test MLE[sysindx][1] < λ[1] < MLE[sysindx][2]
        @test SLE[sysindx][1] < λ[2] < SLE[sysindx][2]
        @test MLE[sysindx][1] < λ2[1] < MLE[sysindx][2]
        @test SLE[sysindx][1] < λ2[2] < SLE[sysindx][2]

        if isodd(i) # Jacobians dont matter for MLE
            T = i < 5 ? 10000 : 1000
            λ = lyapunov(ds, T)
            @test MLE[sysindx][1] < λ < MLE[sysindx][2]
            if i < 5
                λ2 = lyapunov(ds, 2000; Δt = 1.0, Ttr = 10.0,
                diffeq = (abstol = 1e-9, alg = Tsit5()))
                @test MLE[sysindx][1] < λ2 < MLE[sysindx][2]
            end
        end
    end
end
end

@testset "1D Lyapunovs" begin
    ds = Systems.logistic(;r = 4.0)
    λ = lyapunov(ds, 10000; Ttr = 100)
    @test 0.692 < λ < 0.694
end

@testset "Negative λ, continuous" begin
    f(u, p, t) = -0.9u
    g(du, u, p, t) = (du .= -0.9u)

    ds = ContinuousDynamicalSystem(f, rand(SVector{3}), nothing)
    λ1 = lyapunov(ds, 10000)
    @test λ1 < 0
    ds = ContinuousDynamicalSystem(g, rand(3), nothing)
    λ2 = lyapunov(ds, 10000)
    @test λ2 < 0

    @testset "Lorenz stable" begin
        ds = Systems.lorenz(ρ = 20.0)
        @test lyapunov(ds, 2000, Ttr = 100) ≈ 0 atol = 1e-4
    end
end

@testset "Negative λ, discrete" begin
    f(u, p, t) = 0.9u
    g(du, u, p, t) = (du .= 0.9u)

    ds = DiscreteDynamicalSystem(f, rand(SVector{3}), nothing)
    λ1 = lyapunov(ds, 100000)
    @test isapprox(λ1, log(0.9); rtol = 1e-4)
    ds = DiscreteDynamicalSystem(g, rand(3), nothing)
    λ2 = lyapunov(ds, 100000)
    @test isapprox(λ1, log(0.9); rtol = 1e-4)
end

@testset "Lyapunov convergence" begin

ds = Systems.towel()
tinteg = tangent_integrator(ds)
ls, t = ChaosTools.lyapunovspectrum_convergence(tinteg, 20000, 1, 0)
l1 = [x[1] for x in ls]
@test 0.434 < l1[end] < 0.436
end

@testset "Lyapunov convergence, discrete" begin
ds = Systems.logistic(; r=4.0)
λs, t = ChaosTools.lyapunovspectrum_convergence(ds, 5000)
@test 0.692 < λs[end] < 0.694
end

@testset "Local growth rates" begin
    # input arguments
    ds = Systems.henon()
    points = trajectory(ds, 2000; Ttr = 100)
    λ = lyapunov(ds, 100000)

    λlocal = local_growth_rates(ds, points; Δt = 20, S = 20, e = 1e-12)

    @test size(λlocal) == (2001, 20)
    @test all(λlocal .< 1.0)
    mean_local = mean(λlocal[findall(!isinf, λlocal)])
    @test λ-0.1 ≤ mean_local ≤ λ+0.1
end

@testset "Lyapunov from data" begin
    ds = Systems.henon()
    data = trajectory(ds, 100000)
    x = data[:, 1] # some "recorded" timeseries
    ks = 1:20
    @testset "meth = $meth" for meth in
        [NeighborNumber(1), NeighborNumber(4), WithinRange(0.01)]
        @testset "distance = $di" for di in [Euclidean(), Cityblock()]
            for D in [2, 4]
                R = embed(x, D, 1)
                E = lyapunov_from_data(R, ks,
                refstates = 1:1000, distance=di, ntype=meth)
                λ = linear_region(ks, E)[2]
                test_value(λ, 0.3, 0.5)
            end
        end
    end
end

end


# Plotting routines -- uses PyPlot

using PyPlot

# Bodemag with frsp and om given
function BodeMagPlot(freqResponse::Vector{ComplexF64},om::Vector{Float64})
    plot(om,abs.(freqResponse),linewidth=.75);
    xscale("log");
    yscale("log");
    xlabel("Frequency (rad/s)");
    ylabel("Magnitude");
    tight_layout();
end

# Bodemag directly from system
function BodeMagPlot(sys::StateSpace,no::Integer, ni::Integer, om::Vector{Float64}=Array{Float64}(undef,0))
    if (ni>sys.nu) error("Input index exceeds number of system inputs") end
    if (no>sys.ny) error("Output index exceeds number of system output") end

    if isempty(om)
        freqResponse,om = FrequencyResponse(sys,no,ni);
    else
        freqResponse = FrequencyResponse(sys,no,ni,om);
    end
    BodeMagPlot(freqResponse,om);
end

# Bode phase with frsp and om given -- this method should be sufficient. Other methods are not necessary.
function BodePhasePlot(freqResponse::Vector{ComplexF64},om::Vector{Float64})
    phase = 180*atan.(imag.(freqResponse),real.(freqResponse))/pi; # Convert it to [-180,180]
    plot(om,phase,linewidth=.75);
    xscale("log");
    #yscale("log");
    xlabel("Frequency (rad/s)");
    ylabel("Phase");
    tight_layout();
end

function BodePlot(freqResponse::Vector{ComplexF64},om::Vector{Float64})
    subplot(211); BodeMagPlot(freqResponse,om);
    subplot(212); BodePhasePlot(freqResponse,om);
end

# Bodemag directly from system
function BodePlot(sys::StateSpace,no::Integer, ni::Integer, om::Vector{Float64}=Array{Float64}(undef,0))
    if (ni>sys.nu) error("Input index exceeds number of system inputs") end
    if (no>sys.ny) error("Output index exceeds number of system output") end

    if isempty(om)
        freqResponse,om = FrequencyResponse(sys,no,ni);
    else
        freqResponse = FrequencyResponse(sys,no,ni,om);
    end
    BodePlot(freqResponse,om);
end

function NyquistPlot(sys::StateSpace,no::Integer, ni::Integer, om::Vector{Float64}=Array{Float64}(undef,0))
    if (ni>sys.nu) error("Input index exceeds number of system inputs") end
    if (no>sys.ny) error("Output index exceeds number of system output") end

    if isempty(om)
        f1,om = FrequencyResponse(sys,no,ni);
        f2 = FrequencyResponse(sys,no,ni,-om);
    else
        f1 = FrequencyResponse(sys,no,ni,om);
        f2 = FrequencyResponse(sys,no,ni,-om);
    end
    plot(real.(f1),imag.(f1),linewidth=0.5); # Positive ω
    plot(real.(f2),imag.(f2),linewidth=0.5); # Negative ω
    grid(which="both",color=[1,1,1]*0.9,linewidth=0.25)
    xlabel("Real");
    ylabel("Imaginary");
    tight_layout();
end

function PoleZeroMap()
    # Todo ...
end


using Test
include("test_ms.jl")
include("poincareMap.jl")
include("testNF.jl")
include("stuartLandauSH.jl")
include("test_linear.jl")
include("simple_continuation.jl")
include("test_newton.jl")
include("test-cont-non-vector.jl")
include("testJacobianFoldDeflation.jl")
include("testHopfMA.jl")
include("test_potrap.jl")
include("test-bordered-problem.jl")
include("precond.jl")


using DynamicHMCModels, MCMCChains, Random

#Random.seed!(1233)

ProjDir = @__DIR__
cd(ProjDir)

include(joinpath(@__DIR__, "LBA_functions.jl"))

Base.@kwdef struct LBAModel{T}
   data::T
   N::Int
   Nc::Int
end

function make_transformation(model::LBAModel)
    as((v=as(Array,asℝ₊,Nc), A=asℝ₊, k=asℝ₊, tau=asℝ₊))
end

N = 10
v = [1.0, 1.5]
Nc = length(v)
data=simulateLBA(;Nd=N,v=v,A=.8,k=.2,tau=.4)

#dist = LBA(ν=[1.0,1.5,2.0],A=.8,k=.2,τ=.4)
#data = rand(dist,N)

model = LBAModel(; data=data, N=N, Nc=Nc)

function (model::LBAModel)(θ)
    @unpack data=model
    @unpack v,A,k,tau=θ
    d=LBA(ν=v,A=A,k=k,τ=tau)
    minRT = minimum(x->x[2],data)
    logpdf(d,data)+sum(logpdf.(TruncatedNormal(0,3,0,Inf),v)) +
    logpdf(TruncatedNormal(.8,.4,0,Inf),A)+logpdf(TruncatedNormal(.2,.3,0,Inf),k)+
    logpdf(TruncatedNormal(.4,.1,0,minRT),tau)
end

d = [(c,r) for (c,r) in zip(data.choice,data.rt)]

p = LBAModel(d,N,Nc)
p((v=fill(.5,Nc),A=.8,k=.2,tau=.4))

# Use Flux for the gradient.
P = TransformedLogDensity(make_transformation(p), p)
∇P = ADgradient(:ForwardDiff, P)

# Sample from the posterior.
results = mcmc_with_warmup(Random.GLOBAL_RNG, ∇P, 1000;
  warmup_stages = default_warmup_stages(local_optimization=nothing),
  reporter = NoProgressReport()
  )
posterior = P.transformation.(results.chain)

@show DynamicHMC.Diagnostics.EBFMI(results.tree_statistics)
println()
@show DynamicHMC.Diagnostics.summarize_tree_statistics(results.tree_statistics)
println()

parameter_names = ["v[1]", "v[2]", "A", "k", "tau"]

# Create a3d
a3d = Array{Float64, 3}(undef, 1000, 5, 1);
for j in 1:1
  for i in 1:1000
    a3d[i, 1:2, j] = values(posterior[i].v)
    a3d[i, 3, j] = values(posterior[i].A)
    a3d[i, 4, j] = values(posterior[i].k)
    a3d[i, 5, j] = values(posterior[i].tau)
  end
end

chns = MCMCChains.Chains(a3d,
  vcat(parameter_names),
  Dict(
    :parameters => parameter_names
  )
)

describe(chns)


### A Pluto.jl notebook ###
# v0.16.0

using Markdown
using InteractiveUtils

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

# ╔═╡ bf1385da-4ac2-11eb-3992-41abac921370
using Plots

# ╔═╡ e97e5984-4ab9-11eb-3efb-9f54c6c307dd
# edit the code below to set your name and UGent username

student = (name = "Jenke Janssen", email = "Jenke.Janssen@UGent.be");

# press the ▶ button in the bottom right of this cell to run your edits
# or use Shift+Enter

# you might need to wait until all other cells in this notebook have completed running.
# scroll down the page to see what's up

# ╔═╡ f089cbaa-4ab9-11eb-09d1-05f49911487f
begin
	import Pkg; Pkg.activate("..")
	using DSJulia;
	using PlutoUI;
	tracker = ProgressTracker(student.name, student.email);
	md"""

	Submission by: **_$(student.name)_**
	"""
end

# ╔═╡ fd21a9fa-4ab9-11eb-05e9-0d0963826b9f
md"""
# Notebook 1: Getting up and running

First of all, welcome to the course, **$(student[:name])**! We hope you enjoy the ride.
"""

# ╔═╡ 0f47f5b2-4aba-11eb-2e5a-b10407e3f928

# ╔═╡ 23d3c9cc-4abd-11eb-0cb0-21673effee6c
md"""## 1. The basics
*From zero to newbie.*
"""

# ╔═╡ 62c3b076-4ab7-11eb-0cf2-25cdf7d2540d
md"""
Let's get started with the basics. Some mathematical operations, """

# ╔═╡ 7bf5bdbe-4ab7-11eb-0d4b-c116e02cb9d9
1 + 2       # adding integers

# ╔═╡ 83306610-4ab7-11eb-3eb5-55a465e0abb9
1.0 + 2.0   # adding floats

# ╔═╡ 3fa0a15c-5008-11eb-13b5-a91b02c1eb2d
1 + 2.0     # adding a float to an integer...

# ╔═╡ 83311b8a-4ab7-11eb-0067-e57ceabdfe9d
2 / 4       # standard division

# ╔═╡ 833dbc66-4ab7-11eb-216d-f9900f95deb8
div(2, 4)   # Computes 2/4 truncated to an integer

# ╔═╡ 8342c042-4ab7-11eb-2136-497fc9e1b9c4
2 ÷ 4       # looks nicer but does exactly the same!

# ╔═╡ 834d4cbc-4ab7-11eb-1f1a-df05b0c00d66
7 % 3       # get the remainder of the integer division

# ╔═╡ 8360ffac-4ab7-11eb-1162-f7a536eb0765
35 \ 7      # inverse division

# ╔═╡ 8365cb3e-4ab7-11eb-05c0-85f51cc9b018
1 // 3      # fractions, gives the result as a rational

# ╔═╡ 8370eaf0-4ab7-11eb-1cd3-dfeec9341c4b
1//2 + 1//4

# ╔═╡ 50bb93e6-5a6c-11eb-0a6c-d5d749857771
2.0 + 3.0im  # complex numbers

# ╔═╡ 8383f104-4ab7-11eb-38a5-33e59b1591f6
'c'        # characters (unicode)

# ╔═╡ 8387934a-4ab7-11eb-11b2-471b08d87b31
:symbol    # symbols, mostly used for macros

# ╔═╡ 8bab2e50-5a6c-11eb-3c5f-a9f811483814
:ζ         # any LaTeX symbol

# ╔═╡ 9d2708ca-5a6c-11eb-1c0f-473f0e2b5363
:🎉        # or Unicode emoji

# ╔═╡ 8c14cb9a-4ab7-11eb-0666-b1d4aca00f97
md"variable assignment"

# ╔═╡ 93b5a126-4ab7-11eb-2f67-290ed869d44a
x = 2

# ╔═╡ 353efeea-6492-11eb-3d09-353d4dae491a
md"In the Pluto notebook environment you are currently working in, it is not possible to define the same variable in two cells. However, this is not standard Julia behaviour. You can see that redefining a variable is possible,"

# ╔═╡ 92f57780-6492-11eb-1264-bbef04a8ae99
begin
	variable1 = 2.0
	variable1 = 4.0
end;

# ╔═╡ a17ebeba-6492-11eb-07ba-f516a990affc
variable1

# ╔═╡ 3055711a-6493-11eb-252b-7f5d99115551
md"""
```julia
begin
 statement1
 statement2
end
```

Enable to wrap multiple statements, since only single-line statements are allowed in this notebook environment.

"""

# ╔═╡ 962ae6d2-4ab7-11eb-14a2-c76a2221f544
τ = 1 / 37  # unicode variable names are allowed

# ╔═╡ 98d48302-4ab7-11eb-2397-710d0ae425f7
md"""

unicode! In most Julia editing environments, unicode math symbols can be typed when starting with a '\' and hitting '[TAB]'.

"""

# ╔═╡ acb3b57a-661c-11eb-2c6a-99793a47ff29
fyi(md"""Unsure what the LaTeX name for a symbol is or how to type an emoiji? Just copy-paste it in the REPL with a `?` at the beginning, e.g., `?ζ` and it will tell you how to type it.""")

# ╔═╡ cee8a766-4ab7-11eb-2bc7-898df2c9b1ff
# type \alpha  and <TAB>

# ╔═╡ e2c5b558-4ab7-11eb-09be-b354fc56cc6e
md"Operators are overrated."

# ╔═╡ ec754104-4ab7-11eb-2a44-557e4304dd43
5x         # This works

# ╔═╡ f23a2d2a-4ab7-11eb-1e26-bb2d1d19829f
md"But strings are quite essential,"

# ╔═╡ fa836e88-4ab7-11eb-0ba6-5fc7372f32ab
mystery = "life, the universe and everything"

# ╔═╡ 0138ef46-4ab8-11eb-1813-55594927d661
md"and string interpolation is performed with `$`."

# ╔═╡ 0b73d66a-4ab8-11eb-06e9-bbe95285a69f
"The answer to $mystery is $(3*2*7)"

# ╔═╡ 6b6eb954-4ab8-11eb-17f9-ef3445d359a3
md"""
Printing can be done with `println()` but in this notebook environment this does not seem to do much.
"""

# ╔═╡ 94e3eb74-4ab8-11eb-1b27-573dd2f02b1d
println("The answer to $mystery is $(3*2*7)")

# ╔═╡ 7592f8a2-4ac0-11eb-375c-61c915380eeb
md"... but take a look at the terminal window."

# ╔═╡ 97b7f7d8-61aa-11eb-2bf6-739bd5f2e941
md"So the [Stdout](https://www.computerhope.com/jargon/s/stdout.htm) is printed to the terminal window and not the notebooks. We can use the @terminal macro of our own making to circumvent this. More on macros tomorrow! What you need to know now is that the macro @terminal does some additional processing to the statement is proceeds."

# ╔═╡ dcf78e38-61a7-11eb-1ac8-9927f5dca4f2
@terminal println("The answer to $mystery is $(3*2*7)")

# ╔═╡ abf00a78-4ab8-11eb-1063-1bf4905ca250
md"""
repetitions of strings can be done using the operators `*` and `^`.
This use of `*` and `^` makes sense by analogy with multiplication and exponentiation. Just as `4^3` is equivalent to `4*4*4`, we expect `"Spam"^3` to be the same as `"Spam"*"Spam"*"Spam"`, and it is.
"""

# ╔═╡ be220a48-4ab8-11eb-1cd4-db99cd9db066
breakfast = "eggs"

# ╔═╡ cadaf948-4ab8-11eb-3110-259768055e85
abetterbreakfast = "SPAM"

# ╔═╡ cadb506e-4ab8-11eb-23ed-2d5f88fd30b0
breakfast * abetterbreakfast

# ╔═╡ caf56346-4ab8-11eb-38f5-41336c5b45a7
breakfast * abetterbreakfast^3 * breakfast

# ╔═╡ 046133a8-4ab9-11eb-0591-9de27d85bbca
md"""
Lots of handy `String`-operations are available in the standard library of Julia:
"""

# ╔═╡ 0c8bc7f0-4ab9-11eb-1c73-b7ec002c4155
uppercase("Sometimes I feel like shouting.")

# ╔═╡ 0f8a311e-4ab9-11eb-1b64-cd62b65c49bf
findfirst("a", "banana")

# ╔═╡ 0f8a5e94-4ab9-11eb-170b-cfec74d6ebbc
findfirst("na", "banana")

# ╔═╡ 0f96fdd6-4ab9-11eb-0e33-2719394a66ba
findnext("na", "banana", 4)  # start looking from position 4 onwards

# ╔═╡ 1f255304-4ab9-11eb-34f1-270fd5a95256
md"Unlike `Strings`, a `Char` value represents a single character and is surrounded by single quotes."

# ╔═╡ 34a18900-4ab9-11eb-17a0-1168dd9d06f9
'x'

# ╔═╡ 39a0a328-4ab9-11eb-0f37-6717095b56aa
md"
The operator `∈` (\in [TAB]) is a boolean operator that takes a character and a string and returns `true` if the first appears in the second:"

# ╔═╡ 4749f268-4ab9-11eb-15a7-579437e0bd20
'a' ∈ "banana"

# ╔═╡ 5a9bbbe6-4aba-11eb-3652-43eb7891f437

# ╔═╡ 6bdc8a5e-4aba-11eb-263c-df3af7afa517
QuestionBlock(;
	title=md"**Question 1: logical statements**",
	description = md"""

	Check the behaviour of the relational operators on strings.

	```julia

	"apples" == "pears"
	"apples" < "pears"
	"apples" < "Pears"
	```
	""",
	questions = [Question(;description=md"", validators = Bool[], status=md"")]
)

# ╔═╡ 77c5280c-58d0-11eb-19ad-83ec82b6d7c7

# ╔═╡ a69ead46-4abc-11eb-3d1d-eb1c73f65150
md"All binary arithmetic and bitwise operators have an updating version that assigns the result of the operation back into the left operand. The updating version of the binary operator is formed by placing a, `=`, immediately after the operator."

# ╔═╡ b482b998-4abc-11eb-36da-379010485bfa
let         # the `let`-environment creates a local workspace, x will only exist here.
	x = 1   # inplace update of x
	x += 2  # inplace update of x
	x += 2
end

# ╔═╡ 07b103ae-4abd-11eb-311b-278d1e033642
md"Similarly to Matlab, when using the REPL, Julia will print the result of every statement by default. To suppress this behavious, just end the statement with a semicolon."

# ╔═╡ 15f8b7fe-4abd-11eb-2777-8fc8bf9d342e
var1 = 10;  # not printed...

# ╔═╡ efae58fa-5008-11eb-32fe-c3ae588d14f2
var1  # ...but still defined

# ╔═╡ 18f99e46-4abd-11eb-20a8-859cb1b12fe3
var2 = 20

# ╔═╡ 3a7954da-4abd-11eb-3c5b-858054b4d06b
md"""## 2. Logical statements

*From zero to one.*
"""

# ╔═╡ 8b17d538-4abd-11eb-0543-ab95c9548d6f
md"""**Boolean operators**

Julia uses `true` and `false` for Boolean variables.
"""

# ╔═╡ 29d34e64-5009-11eb-3301-f729150e17b2
I💖Julia = true

# ╔═╡ 91a9d1a0-4abd-11eb-3337-71983f32b6ae
!true

# ╔═╡ 942d4202-4abd-11eb-1f01-dfe3df40a5b7
!false

# ╔═╡ 942dae0e-4abd-11eb-20a2-37d9c9882ba8
1 == 1

# ╔═╡ 943d9850-4abd-11eb-1cbc-a1bef988c910
2 == 1

# ╔═╡ 943de2ce-4abd-11eb-2410-31382ae9c74f
1 != 1

# ╔═╡ 9460c03c-4abd-11eb-0d60-4d8aeb5b0c1d
2 != 1

# ╔═╡ 946161f4-4abd-11eb-0ec5-df225dc140d0
1 < 10

# ╔═╡ 947d143a-4abd-11eb-067d-dff955c90407
1 > 10

# ╔═╡ 947fea8e-4abd-11eb-1d6a-2bc540f7a50e
2 <= 2  # or 2 ≤ 2  (\le<TAB>)

# ╔═╡ 948eff10-4abd-11eb-36d0-5183e882a9e2
2 >= 2  # or 2 ≥ 2  (\ge<TAB>)

# ╔═╡ 948f5032-4abd-11eb-3d1c-7da4cb64521c
# Comparisons can be chained
1 < 2 < 3

# ╔═╡ 94b520e6-4abd-11eb-3161-addf3b0e4f24
2 < 3 < 2

# ╔═╡ 94b78322-4abd-11eb-3006-454548efd164
# Logical operators
true && true

# ╔═╡ 94d28c80-4abd-11eb-08c0-717207e4c682
true || false

# ╔═╡ 9fe6e1a2-4abd-11eb-0c39-458ce94265c0
md"Likewise, we have the Boolean logic operators `&&` (AND), `||` (OR) and `⊻` (XOR, exclusive or)."

# ╔═╡ ae26ab9e-4abd-11eb-3270-33558dbdf663
true && true

# ╔═╡ b08dc886-4abd-11eb-1807-096a7e6fd6f9
true && false

# ╔═╡ b08e3a28-4abd-11eb-258a-a5a93b4b882c
true || false

# ╔═╡ b0a8dfe0-4abd-11eb-167d-2fc3974c7c92
false || false

# ╔═╡ b0a97e00-4abd-11eb-371c-e138aea17bb6
true ⊻ false

# ╔═╡ b0ccc252-4abd-11eb-048b-4bec3750bbf1
true ⊻ true

# ╔═╡ 60b066d8-5009-11eb-3b4c-8b8fa2f4831d
md"""
Chaining logic operators is frequently done in Julia as a short alternative for an `if` statement. The idea is if you use an `&&` statement, the second part is only evaluated if the first part is true! The inverse is true for `||`, where the second part is only evaluated if the first part is false."""

# ╔═╡ b8b7c0f6-5009-11eb-1eb7-0be723a88863
let
	x = 12
	(x > 10) && (x -= 5)  # I am evaluated!
	(x > 4) || (x *= 2)   # I am not...
	x
end

# ╔═╡ 1c5975ec-4abe-11eb-0ff0-bfb2f03a520b
statements = [
	missing,  # first statement
	missing,  # second statement
	missing   # third statement
]

# ╔═╡ bd446c42-4abd-11eb-0465-d9a61c48ff48
begin

qb1 = QuestionBlock(;
	title=md"**Question 2: advanced logical statements**",
	description = md"""

	Predict the outcomes of the following statements.

	```julia
	z, y = true, false

	(z || y) && !(y || y) # first

	(z ⊻ y) && (!z ⊻ !y)  # second

	(z || y) ⊻ (z && y)   # third
	```
	""",
	questions = [Question(validators = statements, description = md"Replace `missing` with the correct boolean (true, false) below.")]
)
	validate(qb1, tracker)
end

# ╔═╡ 1c22b880-4abf-11eb-3f18-756c1198ccad
md"## 3. Control flow"

# ╔═╡ 37086212-4abf-11eb-3ec9-7f8dae57121e
md"The `if`, `else`, `elseif`-statement is instrumental to any programming language./ Note that control flow is ended with an `end` statement. In constrast to Python, tabs are only for clarity but do not impact functionality."

# ╔═╡ 489421d8-4abf-11eb-0d5e-fd779cc918a1
if 4 > 3
  'A'
elseif 3 > 4
  'B'
else
  'C'
end

# ╔═╡ 6736dafe-4abf-11eb-1fce-0716d2b7f4a8
md"""
Julia allows for some very condense control flow structures.
y = `condition` ? `valueiftrue` : `valueiffalse`. To make this work Julia requires spaces around the ? and the :
"""

# ╔═╡ 0c693c24-4ac0-11eb-2329-c743dcc5039d
clip(x) = missing

# ╔═╡ 8933033a-4abf-11eb-1156-a53a5ee9152c
begin
   q2 = Question(
			description=md"""
			Complete the clip function: $\max(0, \min(1, x))$ for a given $x$, **without** making use of the functions `min` and `max`.

			Open assignments always return `missing`.
			""",
			validators= @safe[
			clip(-1)==Solutions.clip(-1),
			clip(0.3)==Solutions.clip(0.3),
			clip(1.1)==Solutions.clip(1.1)],
		)

   qb2 = QuestionBlock(
	title=md"**Question 3: silly clipping exercise**",
	questions = [q2],
	);

	validate(qb2, tracker)
end

# ╔═╡ 035a53ba-4ac1-11eb-3c34-b50a803b7b7d
if !ismissing(clip(3))
	md"Oh yeah! 🎉 You defined your first function in Julia. More on this later."
end

# ╔═╡ 2a5fca7c-4ac0-11eb-33a3-23d972ca27b8
md"## 4. Looping"

# ╔═╡ 3896642a-4ac0-11eb-2c7c-4f376ab82217
characters = ["Harry", "Ron", "Hermione"]

# ╔═╡ 3ef3faf8-4ac0-11eb-1965-fd23413e29f3
@terminal begin
	for char in characters
	  println("Character $char")
	end
end

# ╔═╡ 3916f50e-661d-11eb-0829-cb3821836fdf
md"We can use `enumerate` to generate an iterator of tuples containing the index and the values of an iterator."

# ╔═╡ 4118016e-4ac0-11eb-18bf-5de326782c87
@terminal begin
	for (i, char) in enumerate(characters)
	  println("$i. $char")
	end
end

# ╔═╡ 4119fbca-4ac0-11eb-1ea9-0bdd324214c5
pets = ["Hedwig", "Pig", "Crookhanks"]

# ╔═╡ 5ebe25c0-661d-11eb-389b-3d81570f7cf0
md"`zip` binds two or more iterators and yield tuples of the pairs."

# ╔═╡ 4139bf3c-4ac0-11eb-2b63-77a513149351
@terminal begin
	for (char, pet) in zip(characters, pets)
	  println("$char has $pet as a pet")
	end
end

# ╔═╡ a1d4127c-4ac0-11eb-116f-79c6ee58f524
md"Strings can also be looped"

# ╔═╡ a93b28e6-4ac0-11eb-074f-a7b64f43a194
getme = "a shrubbery"

# ╔═╡ ac35b796-4ac0-11eb-3bc5-5ff4350d5452
@terminal begin
	for letter in getme  # or, equivalently, use `∈` instead of `in` to look like a mathematician!
	  println("$letter")
	end
end

# ╔═╡ b18e55ae-4ac0-11eb-1455-21b83b7c61d5
@terminal let
	n = 16757676187786;
	steps = 0
	while n > 1
	  println(n)
	  steps = steps +1
	  if iseven(n)
		n = div(n, 2)
	  else
		n = 3n + 1
	  end
	end
	n, steps
end

# ╔═╡ ec4190a8-4ac0-11eb-0421-398f063775bb
md"(Mathematical note: [they got closer to cracking this one](https://www.quantamagazine.org/mathematician-terence-tao-and-the-collatz-conjecture-20191211/?mc_cid=a3adbffb9f&mc_eid=41ed2fca13).)"

# ╔═╡ 7dce10be-500c-11eb-12cf-43f6989aae91
md"A useful trick when working with loops is using `break` and `continue` statements. If a `break` is encountered, the loop will immediately be exited. A `continue` statement will only skip the rest of the current iteration but will not halt the loop."

# ╔═╡ bafbe0ea-500c-11eb-10be-75d1738d42a2
let
	i = 1
	while true  # infinite loop
		if i^2 ≥ 231
			break
		end
		i += 1
	end
	i  # first integer with a square larger than 231
end

# ╔═╡ 2033383e-500d-11eb-3aea-cdc1e90dad2c
let
	s = 0
	for i in 1:100
		!isodd(i) && continue
		s += i
	end
	s # sum of all odd numbers between 1 and 100
end

# ╔═╡ fdb67aba-4ac0-11eb-1d4a-c354de54baa9
md"""## 5. Functions
Julia puts the fun in functions. User-defined functions can be declared as follows,
"""

# ╔═╡ 28f47a24-4ac1-11eb-271f-6b4de7311db3
function square(x)
  result = x * x
  return result
end

# ╔═╡ 47338c78-4ac1-11eb-04d6-35c2361eaea6
md"A more condensed version of of `square(x)`."

# ╔═╡ 463689b0-4ac1-11eb-1b0f-b7a239011c5c
s(x) = x * x

# ╔═╡ 52bfff04-4ac1-11eb-1216-25eedd9184c3
md"Passing an array to a function that takes a single element as argument takes a special syntax. By putting a `.` before the brackets, the function is executed on all the elements of the Array. More on this in **part2: collections**."

# ╔═╡ 61846dae-4ac1-11eb-389a-4fbe3f6145b1
s([1, 2, 3, 4, 5])   # Multiplication is not defined for Arrays

# ╔═╡ 6321ae1a-4ac1-11eb-04cb-33e939694874
s.([1, 2, 3, 4, 5])  # This is an elements-wise execution of s()

# ╔═╡ 3eda5d12-5a6e-11eb-3264-9925907c85b5
md"Maybe you are of the opinion that squaring a vector should automatically involve squaring the elements, or you might want to concatenate strings using `+`. Tomorrow we will learn to 'overload' functions to induce specific behaviour for specific datatypes!"

# ╔═╡ 7b874424-4ac1-11eb-2d4e-0b4607559b8f
md"""Keyword arguments are defined using a semicolon in the back signature and a default value can be assigned. "Keywords" assigned before a semicolon (`;`) are default values but their keywords are not ignored."""

# ╔═╡ 86defe2a-4ac1-11eb-3c01-c5e671877212
safelog(x, offset=0.1; base=10) = log(x + offset) / log(base)

# ╔═╡ 886512de-4ac1-11eb-00e1-73292ec23277
safelog(0)

# ╔═╡ 88678820-4ac1-11eb-272e-0df61e418900
safelog(0, 0.01)

# ╔═╡ 888dee1e-4ac1-11eb-264d-cd4a4f30f498
safelog(0, 0.01, base=2)

# ╔═╡ 8acb086c-4ac1-11eb-1715-756fde34b38f
md"""When functions have a variable number of arguments, one can use the *slurping* `...` operator to denote a variable number of arguments. The argument will be treated as a collection. For example"""

# ╔═╡ 944e1aaa-4ac1-11eb-0e23-41b1c5d0e889
function mymean(X...)
  m = 0.0
  for x in X
	m += x
  end
  return m / length(X)
end

# ╔═╡ 9d4e11be-4ac1-11eb-1fa0-13f1fe60c3bc
mymean(1, 3, 5)

# ╔═╡ 9d514ef6-4ac1-11eb-25fc-ffaa2dcc9b02
mymean(1, 3, 5, 7)

# ╔═╡ a0781222-4ac1-11eb-3425-d9b9603487f3
md"Similarly, the *splatting* operator can be used to split a collection into its individual elements."

# ╔═╡ a6b95d62-4ac1-11eb-0c93-7fa0f6a120d5
var3 = [1.0, 3.0, 5.0];

# ╔═╡ ab006064-4ac1-11eb-32be-6557b8d45f32
mymean(var3...)

# ╔═╡ b0603566-4ac1-11eb-17bc-3b63cd2aa1e9
md"""When unsure of what a function does, in the REPL the documentation can be viewed by adding a "?" in front of the function. Here, in the Pluto environment, put the cursor in the function of interest and open the documentation tab (look on your right)."""

# ╔═╡ b6db7e2a-5a6e-11eb-1a55-e74b15cbb9e2
begin
   q40 = Question(;
			description=md"""
			What is the difference between `max` and `maximum`?
			""")

   qb40 = QuestionBlock(;
	title=md"**Question 4: max(imum)**",
	questions = [q40],
	)

end

# ╔═╡ beafce06-4ac1-11eb-2431-1ffeba45716b
sort

# ╔═╡ ec487488-4ac1-11eb-1be3-a93e41f78bf3
md"""
For a lot of standard Julia functions an in-place version is defined. In-place means that the function changes one of the input arguments of the function. As an example, `sort()` sorts and returns the array passed as argument, it does not change the original array. In contrast, `sort!()` is the inplace version of sort and directly sorts the array passed as argument.
"""

# ╔═╡ f88fee6c-4ac1-11eb-1671-43493122f061
my_unsorted_list = [4, 5, 9, 7, 1, 9]

# ╔═╡ fa759f92-4ac1-11eb-0d72-1f9d6d38a831
sort(my_unsorted_list)

# ╔═╡ fa7ba458-4ac1-11eb-2ca1-59ff3c032b26
my_unsorted_list

# ╔═╡ fa9b3266-4ac1-11eb-153a-87c6a1124890
sort!(my_unsorted_list)

# ╔═╡ fa9d43b2-4ac1-11eb-33fc-a37503cedabf
my_unsorted_list

# ╔═╡ 0dbe0c34-500e-11eb-2633-67d8dc6b24c8
md"""
Functions are first-class and work just like any other variable! For example, you can give a function as an input in another function.

In some cases, you might want to define an **anonymous function**, without given them a name:"""

# ╔═╡ 900da226-500e-11eb-3f1c-5582ed0cf749
afun = x -> 2x^2 - 2x + 4

# ╔═╡ b24bee9a-500e-11eb-2130-45b78bbebdec
afun(4)  # can be used as a regular function

# ╔═╡ c8cf6520-500e-11eb-2dd3-4507a3994a83
md"""
A particular powerful tool is creating a **pipeline**, i.e., using the output of a previous function as input of the next one. This can be done using the `|>` syntax."""

# ╔═╡ 19402512-500f-11eb-3ea7-c3d1c52025d6
π/4 |> sin |> inv

# ╔═╡ 6dc0eaae-500f-11eb-3051-37d870366201
md"The `.` syntax also works here and it is often useful to combine with anonymous functions."

# ╔═╡ 8b57c506-500f-11eb-3114-55785eb593a7
1:1000 .|> abs2 .|> inv |> sum |> (x->6x) |> sqrt  # poor man's pi

# ╔═╡ fd171e0e-4ac1-11eb-09ea-337d17500149
md"Specific functions can be generated if you have more information on the input type.
This is called multiple dispatch.

The `::` operator attaches type annotations to expressions and variables.

How does the documentation for the function square look like after you executed the cell below?"

# ╔═╡ 10e71260-4ac2-11eb-1069-55613ee7df0a
function square(x::Float64)
  println("using function with floats")
  result = x * x
  return result
end

# ╔═╡ 3e433ab4-4ac1-11eb-2178-53b7220fa9ab
square(2)

# ╔═╡ 46112c44-4ac1-11eb-2ad8-030406c7cf67
square(2.0)

# ╔═╡ 461489fa-4ac1-11eb-0596-1d3bedb61778
square("ni")   # the multiplication of strings is defined as a concatenation

# ╔═╡ 1c0230f8-4ac2-11eb-32aa-e7a4b2ae9cff
@terminal square(4) #... nothing is printed, obviously

# ╔═╡ 226417c2-4ac2-11eb-2914-196461e2b40e
@terminal square(4.)

# ╔═╡ 3daf4fa6-4ac2-11eb-0541-b98c2e97dfe4
md"More about types in the next section!"

# ╔═╡ a777f624-4f2f-11eb-0595-432ea5115a2d
md"## Intermezzo: Types"

# ╔═╡ abb7bf4e-4f2f-11eb-1dde-abf1cb0fb8b4
 md"""All Julia objects, both those already defined as well as those you might make yourself, have a type. The type system is the secret sauce, allowing Julia to be fast because code can be specialized for a particular combination of types. It is also supremely useful in conjunction with *multiple dispatch*, in which functions work differently depending on which types you feed into them. In one of the exercises at the end of this notebook you will get a taster of how multiple dispatch can be used. A function behaviour changes depending on the type of the arguments. Day 2 will mainly focus on the type system, so in what follows, a concise introduction is given to types."""

# ╔═╡ b63533e8-4f2f-11eb-3811-5f43fcaa2a9e
begin
	number = 42
	str = "mice"
	var4 = 0.9
	A = [1 2; 3 4]
end;

# ╔═╡ b730b7b8-4f2f-11eb-3216-613c6100fb9c
typeof(number)

# ╔═╡ b99eb16c-4f2f-11eb-0f1e-5d2b1244689f
typeof(str)

# ╔═╡ bc67fc50-4f2f-11eb-2209-bf35df0e8c27
typeof(var4)

# ╔═╡ bf40274a-4f2f-11eb-0086-d3502f67161b
typeof(A)

# ╔═╡ c48e035c-4f2f-11eb-12c7-576aadca7511
md"**Multiple dispatch**"

# ╔═╡ c8435740-4f2f-11eb-3046-3d45d5e0e805
md"Multiple dispatch is a concept where the behaviour of a function can be specified differently according to the type of the input arguments. Let's us look at an example,"

# ╔═╡ cacb7254-4f2f-11eb-1daa-1bc04678835c
begin
	translate(xy::Array) = xy .+ [1 3]
	translate(s::String) = Markdown.parse("[Ctrl + click here](https://translate.google.com/?sl=en&tl=nl&text=$(s)&op=translate)")
	translate(anything::Any) = "I'm lost in translation..."
end

# ╔═╡ cc48bc9a-4f2f-11eb-134c-71bd8a944943
translate(1.0)

# ╔═╡ d9f28c04-4f2f-11eb-0255-1965fb8f07b5
translate([0.0 0.0])

# ╔═╡ dc1dbe90-4f2f-11eb-05ce-c1fe46ae14dd
translate("Hi how are you?")

# ╔═╡ de48a3f6-4f2f-11eb-314b-493546c37a21
 md"A great deal of time will be spend on the julia's time system in day 2. So do not worry if this still feels vague. Just **remember** that all objects have a type and the behaviour of a function can vary depending on the type of the input arguments."

# ╔═╡ 6da71180-4ac2-11eb-1cac-410bd1cce70c
md"""## 6. Macros
Macros provide a method to include generated code in the final body of a program. It is a way of generating a new output expression, given an unevaluated input expression. When your Julia program runs, it first parses and evaluates the macro, and the processed code produced by the macro is eventually evaluated like an ordinary expression.

Some nifty basic macros are `@time` and `@show`. `@time` prints the cpu time and memory allocations of an expression.
"""

# ╔═╡ 85b96ff0-4ac2-11eb-077f-cf4aad8a3c24
@terminal @time square(10)

# ╔═╡ a11c2898-4ac2-11eb-24d3-6f8060b5fd65
md"""The `@show` macro is often useful for debugging purposes. It displays both the expression to be evaluated and its result, finally returning the value of the result."""

# ╔═╡ a686e67e-4ac2-11eb-228e-23524a3ddc59
@terminal @show 1 + 1

# ╔═╡ d50cced2-500d-11eb-2dcc-21fc50825f43
md"We will provide more examples tomorrow!"

# ╔═╡ ad156892-4ac2-11eb-3634-a3783231e5a1
md"""## 7. Plotting

Quite essential for scientific programming is the visualisation of the results. `Plots` is the Julia package that handles a lot of the visualisation. `rand(10)`, returns an array of 10 random floats between 0 and 1.
"""

# ╔═╡ d779956a-4ac2-11eb-39de-4b3cecace452
md"""When loading in a package for the first time Julia will have to precompile this package, hence this step can take some time."""

# ╔═╡ c7d2a048-4ac2-11eb-3902-b7c8505096ae
begin
	plot(1:10, rand(10), label="first")
	plot!(1:10, rand(10), label="second") # adding to current figure using plot!

	scatter!([1:10], randn(10), label="scatter")

	xlabel!("x")
	ylabel!("f(x)")
	title!("My pretty Julia plot")
end

# ╔═╡ cf35b2b2-4ac2-11eb-1ae6-5d3c108210df
plot(0:0.1:10, x -> sin(x) / x, xlabel="x", ylabel="sin(x)/x", color=:red, marker=:square, legend=:none)
# notice the use of a symbol as an argument !

# ╔═╡ d1010f88-4ac2-11eb-0fa9-0902fef0cf9f
contour(-5:0.1:5, -10:0.1:10, (x, y) -> 3x^2-4y^2 + x*y/6)

# ╔═╡ 0e63d722-4ac3-11eb-3740-d31b47a77912
md"""### 8. Exercises"""

# ╔═╡ a3969292-57ff-11eb-059b-e9e931a30dc1
stirling(n) = missing

# ╔═╡ a48be23a-57ff-11eb-1bc3-3d3e046ea67c
begin
	q4 = Question(
			validators = @safe[
				stirling(5) ≈ Solutions.stirling(5),
				stirling(10) ≈ Solutions.stirling(10)
				],
			description = md""
		)

	qo4 = QuestionOptional{Easy}(
			description=md"""
				How accurate is this approximation?
				Plot the true factorial function (scatter) on top of the approximation 					for n ∈ [0, 10].
				""",
				validators = Bool[]
			)

	qb4 = QuestionBlock(;
		title=md"**Question 5: Stirling's approximation for factorials**",
		description = md"""
	The factorial function,

	$${\displaystyle n!=1\cdot 2\cdot 3\cdots (n-2)\cdot (n-1)\cdot n,}$$

	is often used in combinatorics but also other mathematical areas. Especially for large numbers it can get quite inefficient to compute. Stirling's approximation is an approximation for factorials,

	$${\displaystyle n!\sim {\sqrt {2\pi n}}\left({\frac {n}{e}}\right)^{n},}$$

	Complete the function `stirling()` by implementing Stirling's approximation.

		""",
		questions = [q4, qo4]
	)

	validate(qb4, tracker)
end

# ╔═╡ adb47b64-5af8-11eb-1b32-57cbe5d47200

# ╔═╡ 0cd2d0e4-59e1-11eb-112e-83ebe626f597
present = time()

# ╔═╡ 0c306fd8-4ad5-11eb-1a9f-2d3d1e838a77
function since_epoch(t)
	days, hours, minutes, seconds = missing, missing, missing, missing
	return days, hours, minutes, seconds
end

# ╔═╡ c34ede1c-4ad4-11eb-050f-bb07c5d19c1c
begin
 now = time()
qb5 = QuestionBlock(;
	title=md"**Question 6: time is relative**",
	description = md"""
The function `time` returns the current Greenwich Mean Time in seconds since "the epoch", which is an arbitrary time used as a reference point. On UNIX systems, the epoch is 1 January 1970.
Write a script that reads the current time in seconds (use `time()`) and converts it to a time of day in hours, minutes, and seconds, plus the number of days since the beginning of the year.
	""",
	questions = [Question(validators = @safe[since_epoch(now)==Solutions.since_epoch(now)], description = md"")]
)
	validate(qb5, tracker)
end

# ╔═╡ 40557d76-661e-11eb-3df0-659c6095285d
since_epoch(present)

# ╔═╡ b1af96ea-5af8-11eb-0d08-f59a4c2b686c

# ╔═╡ e99d6b96-4ad5-11eb-2144-f97a97e71ae4
begin

q6 = Question(;
	description=md"""
1. Write a function named `checkfermat` that takes four parameters ($a$, $b$, $c$ and $n$) and checks to see if Fermat’s theorem holds. If $n$ is greater than 2 and $a^n + b^n == c^n$ the program should print, *"Holy smokes, Fermat was wrong!"* Otherwise the program should print and return, *"No, that doesn’t work."* """,
	validators = [], status=md""
)

q7 = Question(;
	description=md"""
2. Write a function that takes 4 random positive integers for $a$, $b$, $c$ and $n$, and use `checkfermat` to check whether they violate Fermat’s theorem.""",
	validators = Bool[], status=md""
)

oq8 = QuestionOptional{Easy}(;
	description=md"""
3. Can you write the code so that the functions in 4.1 and 4.2 have the same name?""",
	validators = [], status=md""
)

qb6 = QuestionBlock(;
	title=md"**Question 7: Fermat's Last Theorem**",
	description = md"""
Fermat’s Last Theorem says that there are no positive integers $a$, $b$, and $c$ such that

$a^n + b^n = c^n$

for any value of $n$ greater than 2.""",
	questions = [q6, q7, oq8],
	hints=[
			hint(md"check the functions `rand` and `abs`."),
			hint(md"You can write multiple functions with the same name but with a different number or type of input arguments. So you can write a second version of `checkfermat` with no input arguments for a random version. Depending on the number and the type of the arguments, Julia will choose the appropriate version of `checkfermat` Remember that this construct is called multiple dispatch."),
			hint(md"In Pluto, you have to put methods of the same name in a `begin ... end` block.")
			],

	)
end

# ╔═╡ bf53d86c-59e1-11eb-1456-5518e1f63390
function checkfermat(a::Int, b::Int, c::Int, n::Int)
	answer = missing
	println(answer)
	return answer
end

# ╔═╡ da5537b5-88e7-4377-90c6-abbc7eaf6495
md"""
Insert some numbers below to test your function `checkfermat()`\
a = $(@bind a NumberField(1:100000000))\
b = $(@bind b NumberField(1:100000000))\
c = $(@bind c NumberField(1:100000000))\
n = $(@bind n NumberField(1:100000000))
"""

# ╔═╡ b4118bfa-5af8-11eb-0aca-cddef8e191ee
@terminal checkfermat(a,b,c,n)

# ╔═╡ f077c390-57fe-11eb-1ad9-31110b3dac39
md"""### 9. Extra exercises"""

# ╔═╡ 87871f34-4ad1-11eb-3903-93e3f63ea14a
function rightjustify(text)
	missing
end

# ╔═╡ 42f24f58-4ac3-11eb-06b5-ebc015c17520
begin
qb7 = QuestionBlock(;
	title=md"**Question 8: can you justify this?**",
	description = md"""

	Write a function named `rightjustify` that takes a string named `s` as a parameter and prints the string with enough leading spaces so that the last letter of the string is in column 70 of the display.

	Use string concatenation and repetition. Also, Julia provides a built-in function called `length`. Check what it does!

	""",
	questions = [Question(
					validators = @safe[
						rightjustify("To the left!") ==
						Solutions.rightjustify("To the left!")
					],
					description = md"")]
)
end

# ╔═╡ e2583a06-6479-11eb-091b-8be70024930d

# ╔═╡ 1f588828-6477-11eb-0c70-557f130c6785
function degrade(molecule)
	return missing # replace this with the correct code
end

# ╔═╡ 4d0e1d60-6476-11eb-154e-d9cc3bf284c2
caproic_acid = "CH3-CH2-CH2-CH2-CH2-COOH"

# ╔═╡ 5167ded6-6463-11eb-3279-0dd226fae82e
verylongium = "COOH-CO-CO-CH2-CH2-CO-CO-CH2-CO-CO-CH2-CH2-CO-CH2-CH2-CH2-CO-CH2-CO-CH2-CH2-CO-CH2-CH2-CO-CH2-CO-CH2-CH2-CO-CO-CH2-CH2-CH2-CH2-CH2-CO-CO-CH2-CH2-CO-CH2-CO-CH2-CO-CH2-CH2-CO-CO-CO-CH2-CO-CH2-CH2-CO-CO-CH2-CH2-CH2-CH2-CO-CH2-CH2-CO-CO-CO-CH2-CO-CH2-CH2-CH2-CO-CO-CO-CO-CO-CO-CH2-CH2-CH2-CO-CO-CO-CO-CH2-CH2-CH2-CO-CH2-CO-CO-CO-CO-CO-CH2-CO-CO-CH2-CH2-CH3"

# ╔═╡ c4d03faa-647d-11eb-3a84-d7b2d53e4720

# ╔═╡ c42f69f6-647d-11eb-143b-2f0ab33616e7

# ╔═╡ c55ed390-647d-11eb-22d5-33fbae27a2bb
function generator(N; tips=["CH3", "COOH"], backbone=["CH2", "CO"])
	return rand(tips) * "-" * reduce(*,rand(backbone,N-2).* "-") * rand(tips)
end

# ╔═╡ 23db2764-647e-11eb-37dd-a34db2ff0fcd
generator(6) #try rerunning this cell multiple times

# ╔═╡ 989d0dd2-647a-11eb-3123-39786452714a
C12 = [generator(12) for i in 1:10]

# ╔═╡ 061e4894-647b-11eb-06db-310d02463b80
function molecular_weight(molecule)
	return missing
end

# ╔═╡ e6217d50-63de-11eb-28d8-452aeffc956c
begin

molecule1_mm = "CH3-CH2-CH2-CH2-CH2-COOH"
molecule2_mm = "COOH-CO-CO-CH2-CH2-CO-CO-CH2-CO-CO-CH2-CH2-CO-CH2-CH2-CH2-CO-CH2-CO-CH2-CH2-CO-CH2-CH2-CO-CH2-CO-CH2-CH2-CO-CO-CH2-CH2-CH2-CH2-CH2-CO-CO-CH2-CH2-CO-CH2-CO-CH2-CO-CH2-CH2-CO-CO-CO-CH2-CO-CH2-CH2-CO-CO-CH2-CH2-CH2-CH2-CO-CH2-CH2-CO-CO-CO-CH2-CO-CH2-CH2-CH2-CO-CO-CO-CO-CO-CO-CH2-CH2-CH2-CO-CO-CO-CO-CH2-CH2-CH2-CO-CH2-CO-CO-CO-CO-CO-CH2-CO-CO-CH2-CH2-CH3"

test1_mm = 	@safe[molecule1_mm |> Solutions.degrade == molecule1_mm |> degrade]

molecule1_mm = "CH3-CH2-CH2-CH2-CH2-COOH"
molecule2_mm = "COOH-CO-CO-CH2-CH2-CO-CO-CH2-CO-CO-CH2-CH2-CO-CH2-CH2-CH2-CO-CH2-CO-CH2-CH2-CO-CH2-CH2-CO-CH2-CO-CH2-CH2-CO-CO-CH2-CH2-CH2-CH2-CH2-CO-CO-CH2-CH2-CO-CH2-CO-CH2-CO-CH2-CH2-CO-CO-CO-CH2-CO-CH2-CH2-CO-CO-CH2-CH2-CH2-CH2-CO-CH2-CH2-CO-CO-CO-CH2-CO-CH2-CH2-CH2-CO-CO-CO-CO-CO-CO-CH2-CH2-CH2-CO-CO-CO-CO-CH2-CH2-CH2-CO-CH2-CO-CO-CO-CO-CO-CH2-CO-CO-CH2-CH2-CH3"

test2_mm = 	@safe[
		molecule1_mm |> Solutions.molecular_weight ==
			molecule1_mm |> molecular_weight,
		molecule2_mm |> Solutions.molecular_weight ==
			molecule2_mm |> molecular_weight,
		]

q_mm1 = Question(validators = test1_mm,
					description = md"""Happy with their discovery (less happy with the odours coming from their machine), the scientist take out a small vial... We wonder what would be the stable molecule obtained from this very expensive bottle filled with the molecule `verylongium`.""")

q_mm2 = Question(validators = test2_mm,
		description = md""" ...
		All of the sudden you hear, *\"Eureka!\"* from the other end of the corridor. It appears the chemical engineers finally figured out how to get their molecular generator working. This machine creates random molecules with a set number of carbon atoms `N`. They even show you how it works (see `generator` below), it is quite simple actually (try to understand which string manupilation are being used here).

The engineers would like you to analyse the molecules they generate, using the mass spectroscopy machine. They do not only want to know which molecules are obtained after degradation but also want to see the spread of the molecular weight of the degradation product.

Degrade *1000* molecules containing *12* carbon atoms, compute the molecular weight of each component and plot a histogram that depicts the relative frequency to the molecular weight of these components.

| Group | Molecular weight (g/mol) |
|-------|--------------------------|
| CH4   | 16.0                     |
| CH3   | 15.0                     |
| CH2   | 14.0                     |
| CO    | 28.0                     |
| OH    | 17.0                     |

"""

	)

qb_mm = QuestionBlock(;
	title=md"**Question 9: Molecular mass spectroscopy**",
	description = md"""
	Analytical chemists at Ghent University have discovered a new analytical technique where organic molecules are partially burned into smaller molecules. These oxidised molecules can then be detected via acetatometry.

	An organic molecule entering the analyser will always follow the same degradation pattern and subsequently,

	1. *CH₃*-endpoints will turn into *COOH*;
	2. *CO-COOH*-endpoints are unstable and will evaporate;
	3. Any *CH₂*-endpoints are unstable and will turn into *CH₃*;
	4. The first *-CH₂-CH₂-* wil react to *CO*;
	5. *CO* at the endpoints will be converted to *COOH*;
	6. A *CO-CO-CO* is also very unstable and will turn into *CO-CH₂-CO*.
	7. If the *CH2* or *CH3* is left at the very end, *CH4* is formed.

	This degradation pattern is repeated until a stable molecule is obtained.

	Let's take caproic acid for example,
		![](https://i.imgur.com/xsb9tGR.png)

	with a structural formula of CH3-CH2-CH2-CH2-CH2-COOH, caproic acid will degrade following this pattern,

	- Cycle 1: Step 1: CH₃ will turn into COOH: COOH-CH2-CH2-CH2-CH2-COOH.
	- Cycle 1: Step2: No CO-COOH to remove: COOH-CH2-CH2-CH2-CH2-COOH.
	- Cycle 1: Step3: No unstable CH₂ ends:  COOH-CH2-CH2-CH2-CH2-COOH.
	- Cycle 1: Step4: Reaction of the first -CH₂-CH₂- to CO: COOH-CO-CH2-CH2-COOH.
	- Cycle 1: Steps 5 & 6 do not change the molecule and a new cycle is repeated
	- Cycle 2: Step 1 does not change the molecule.
	- Cycle 2: Step 2 removes a single CO-COOH: CH2-CH2-COOH.
	- Cycle 2: Step 3: changes the molecule to: CH3-CH2-COOH.
	- ...
	- Cycle 3: step 1: changes the molecule CH3-CH2-COOH to **COOH-CH2-COOH**.

	  Additional cycles will not further degrade the structure of the molecule and can be considered stable. Caproic acid degraded into [malonic acid](https://en.wikipedia.org/wiki/Malonic_acid) (COOH-CH2-COOH)

	""",
	questions = [q_mm1, q_mm2],
	hints= [hint(md"Make sure you to check both endpoints CH₃-, -CH₃ and -CO-COOH or COOH-CO-, etc."),
			hint(md"""`replace(string1, \"-CH2-CH2-\" => \"-CO-\"; count=1)` replaces the first occurence of  "-CH2-CH2-" in `string1` to \"-CO-\". """),
			hint(md"""For part 2: use the function `count` and the function `histogram` from the Plots library"""),
			hint(md"""Type \"histogram\", put your cursor on histogram and open the "Live docs", to find informaiton on how to use the histogram function.""")
		]
)
	validate(qb_mm)
end

# ╔═╡ 89a9ad00-6489-11eb-2a2d-bfa18af103ab
#Plotting

# ╔═╡ 8ab56dcc-6489-11eb-16a4-b7d12003ed99

# ╔═╡ 0507186a-647a-11eb-07cf-ebf32bdff5b0

# ╔═╡ 01eb4816-4ad2-11eb-3991-af76de0110c5
begin
q9 = Question(validators = [],
		description = md"")

oq9 = QuestionOptional{Easy}(validators = [],
		description = md"Write a function that draws a similar grid with four rows and four columns.")

qb8 = QuestionBlock(;
	title=md"**Question 10: grid print**",
	description = md"""
	Complete the function `printgrid` that draws a grid like the following:
	```
	+ - - - - + - - - - +
	|         |         |
	|         |         |
	|         |         |
	|         |         |
	+ - - - - + - - - - +
	|         |         |
	|         |         |
	|         |         |
	|         |         |
	+ - - - - + - - - - +
	```

	""", questions = [q9, oq9],
		hints=[
				hint(md"""The function `print` does not advance to the next line."""),
				hint(md"""To print more than one value on a line, you can print a comma-separated sequence of values:

`println("+", "-")`
""")
			]
	)
	validate(qb8)
end

# ╔═╡ 448ef88e-4ad2-11eb-20d6-17a51d665ef9
function print_grid()
	missing
end

# ╔═╡ 14d50ee8-4ad3-11eb-3b81-9138aec66207
function print_big_grid()
	missing
end

# ╔═╡ 2bff11a1-7e39-43e5-8058-5d1496f43387
md"""## Answers:
If you would like to take a look at the answers, you can do so by checking the boxo the question you would like to see. The function will be shown just below the question you want to look at.

| Question | Show solution |
|-----|:---------:|
| Question 1 | $(@bind answ_q1 CheckBox()) |
| Question 2 | $(@bind answ_q2 CheckBox()) |
| Question 3 | $(@bind answ_q3 CheckBox()) |
| Question 4 | $(@bind answ_q4 CheckBox()) |
| Question 5 | $(@bind answ_q5 CheckBox()) |
| Question 6 | $(@bind answ_q6 CheckBox()) |
| Question 7 | $(@bind answ_q7 CheckBox()) |
| Question 8 | $(@bind answ_q8 CheckBox()) |
| Question 9 | $(@bind answ_q9 CheckBox()) |
| Question 10 | $(@bind answ_q10 CheckBox()) |
"""

# ╔═╡ c297d307-a640-4d86-aa0b-a149fe741bac
if answ_q1
	md"""
	```julia
	false
	true
	false
	```
	"""
end

# ╔═╡ 10a31233-5de3-401b-abdd-a27908e2de9d
if answ_q2
	md"""
	```julia
	statements = [
		true,
		true,
		true
	]
	```
	"""
end

# ╔═╡ 32c8ff60-9a35-4d40-8b54-b4805ca81885
if answ_q3
	md"""
	```julia
	function clip(x)
	  if x ≤ 0 && return 0.
	  elseif x ≥ 1 && return 1.
	  else
	  return x
	  end
	end
	```
	"""
end

# ╔═╡ a647ec42-1b3c-4280-958a-e2a96cd3d103
if answ_q4
	md"""
	`max` gives you the maximum of the given arguments while `maximum` gives you the max of the given collection.
	"""
end

# ╔═╡ 9e211fbf-4c43-439a-95ba-86b48767c053
if answ_q5
	md"""
	```julia
	stirling(n) = sqrt(2*π*n)*(n/exp(1))^n
	```
	"""
end

# ╔═╡ fd368fc2-2c64-463e-976e-9dd52ce80f13
if answ_q6
	md"""
	```julia
	function since_epoch(t)
	  now = t
	  seconds = now % 60 # 60 seconds in a minute
	  now -= seconds
	  # to minutes
	  now = div(now, 60)
	  minutes = now % 60 # 60 minutes in an hour
	  now -= minutes
	  # to hours
	  now = div(now, 60)
	  hours = now % 24 # 24 hours in a day
	  now -= hours
	  # to days
	  now = div(now, 24)
	  days = now % 365 # ± 365 days in a year
	  return days, hours, minutes, seconds
	end
	```
	"""
end

# ╔═╡ db78af32-b822-49dd-b085-6f51ba6b623f
if answ_q7
	md"""
	```julia
	function checkfermat(a::Int, b::Int, c::Int, n::Int)
	  if a^n + b^n == c^n
		  println("Holy smokes, Fermat was wrong!")
	  else
		  println("No, that doesn’t work.")
	  end
	end
	```
	"""
end

# ╔═╡ c8151b58-735c-4f16-b410-0c5c88818e6d
if answ_q8
	md"""
	```julia
	rightjustify(s) = println(" "^(70-length(s)) * s)
	```
	"""
end

# ╔═╡ c01bb803-2255-4164-b216-23ec7310f89f
if answ_q9
	md"""
	```julia
	f1(s) = replace(s, "-CH3"=> "-COOH") |> s -> replace(s, "CH3-"=> "COOH-")
	f2(s) = replace(s, "-CO-COOH"=> "") |> s -> replace(s, "COOH-CO-"=> "")

	function f3(molecule)
	  if length(molecule) < 4
		molecule = "CH4"
		return molecule
	  end

	  if molecule[1:4] == "CH2-"
		molecule = "CH3-" * molecule[5:end]
	  end
	  if molecule[end-3:end] == "-CH2"
		molecule = molecule[1:end-4] * "-CH3"
	  end
	  return molecule
	end

	f4(s) = replace(s, "-CH2-CH2-"=> "-CO-")

	function f5(molecule)
	  if molecule[1:3] == "CO-"
		molecule = "COOH-" * molecule[4:end]
	  end

	  if molecule[end-2:end] == "-CO"
		molecule = molecule[1:end-3] * "-COOH"
	  end
	  return molecule
	end

	f6(s) = replace(s, "CO-CO-CO" => "CO-CH2-CO")

	function cycle(molecule)
	  molecule |> f1 |> f2 |> f3 |> f4 |> f5 |> f6
	end

	function degrade(molecule)
	  mol_prev = ""
	  while mol_prev !== molecule
		mol_prev = molecule
		molecule = cycle(molecule)
	  end
	  return molecule
	end
	```
	"""
end

# ╔═╡ 743924b0-0b3b-48e2-9b93-961311d08022
if answ_q9
	md"""
	```julia
	function molecular_weight(molecule)
		return count("CH3", molecule)* 15.0 +
				count("CH4", molecule) * 16.0 +
				count("CH2", molecule) * 14.0 +
				count("CO", molecule) * 28.0 +
				count("OH", molecule) * 17
	end
	```
	"""
end

# ╔═╡ 15257785-52b5-4129-9e65-7fa15e83a6e3
if answ_q10
	md"""
	```julia
	function print_grid()
	  dashed = " -"^4*" "
	  plusdash = "+"*dashed*"+"*dashed*"+"
	  verticals = ("|"*" "^9)^2*"|"
	  s = (plusdash * "\n" * (verticals * "\n")^4)^2*plusdash * "\n"
	  println(s)
	end
	```
	"""
end

# ╔═╡ 0f095a13-8586-4da3-84ae-24c43a303776
if answ_q10
	md"""
	```julia
	function print_big_grid()
	  dashed = " -"^4*" "
	  plusdash = "+"*dashed*"+"*dashed*"+"
	  verticals = ("|"*" "^9)^2*"|"
	  s = (plusdash * "\n" * (verticals * "\n")^4)^2*plusdash * "\n"
	  println(s)
	end
	```
	"""
end

# ╔═╡ Cell order:
# ╟─f089cbaa-4ab9-11eb-09d1-05f49911487f
# ╠═e97e5984-4ab9-11eb-3efb-9f54c6c307dd
# ╟─fd21a9fa-4ab9-11eb-05e9-0d0963826b9f
# ╟─0f47f5b2-4aba-11eb-2e5a-b10407e3f928
# ╟─23d3c9cc-4abd-11eb-0cb0-21673effee6c
# ╟─62c3b076-4ab7-11eb-0cf2-25cdf7d2540d
# ╠═7bf5bdbe-4ab7-11eb-0d4b-c116e02cb9d9
# ╠═83306610-4ab7-11eb-3eb5-55a465e0abb9
# ╠═3fa0a15c-5008-11eb-13b5-a91b02c1eb2d
# ╠═83311b8a-4ab7-11eb-0067-e57ceabdfe9d
# ╠═833dbc66-4ab7-11eb-216d-f9900f95deb8
# ╠═8342c042-4ab7-11eb-2136-497fc9e1b9c4
# ╠═834d4cbc-4ab7-11eb-1f1a-df05b0c00d66
# ╠═8360ffac-4ab7-11eb-1162-f7a536eb0765
# ╠═8365cb3e-4ab7-11eb-05c0-85f51cc9b018
# ╠═8370eaf0-4ab7-11eb-1cd3-dfeec9341c4b
# ╠═50bb93e6-5a6c-11eb-0a6c-d5d749857771
# ╠═8383f104-4ab7-11eb-38a5-33e59b1591f6
# ╠═8387934a-4ab7-11eb-11b2-471b08d87b31
# ╠═8bab2e50-5a6c-11eb-3c5f-a9f811483814
# ╠═9d2708ca-5a6c-11eb-1c0f-473f0e2b5363
# ╟─8c14cb9a-4ab7-11eb-0666-b1d4aca00f97
# ╠═93b5a126-4ab7-11eb-2f67-290ed869d44a
# ╟─353efeea-6492-11eb-3d09-353d4dae491a
# ╠═92f57780-6492-11eb-1264-bbef04a8ae99
# ╠═a17ebeba-6492-11eb-07ba-f516a990affc
# ╟─3055711a-6493-11eb-252b-7f5d99115551
# ╠═962ae6d2-4ab7-11eb-14a2-c76a2221f544
# ╟─98d48302-4ab7-11eb-2397-710d0ae425f7
# ╟─acb3b57a-661c-11eb-2c6a-99793a47ff29
# ╠═cee8a766-4ab7-11eb-2bc7-898df2c9b1ff
# ╟─e2c5b558-4ab7-11eb-09be-b354fc56cc6e
# ╠═ec754104-4ab7-11eb-2a44-557e4304dd43
# ╟─f23a2d2a-4ab7-11eb-1e26-bb2d1d19829f
# ╠═fa836e88-4ab7-11eb-0ba6-5fc7372f32ab
# ╟─0138ef46-4ab8-11eb-1813-55594927d661
# ╠═0b73d66a-4ab8-11eb-06e9-bbe95285a69f
# ╟─6b6eb954-4ab8-11eb-17f9-ef3445d359a3
# ╠═94e3eb74-4ab8-11eb-1b27-573dd2f02b1d
# ╟─7592f8a2-4ac0-11eb-375c-61c915380eeb
# ╟─97b7f7d8-61aa-11eb-2bf6-739bd5f2e941
# ╠═dcf78e38-61a7-11eb-1ac8-9927f5dca4f2
# ╟─abf00a78-4ab8-11eb-1063-1bf4905ca250
# ╠═be220a48-4ab8-11eb-1cd4-db99cd9db066
# ╠═cadaf948-4ab8-11eb-3110-259768055e85
# ╠═cadb506e-4ab8-11eb-23ed-2d5f88fd30b0
# ╠═caf56346-4ab8-11eb-38f5-41336c5b45a7
# ╟─046133a8-4ab9-11eb-0591-9de27d85bbca
# ╠═0c8bc7f0-4ab9-11eb-1c73-b7ec002c4155
# ╠═0f8a311e-4ab9-11eb-1b64-cd62b65c49bf
# ╠═0f8a5e94-4ab9-11eb-170b-cfec74d6ebbc
# ╠═0f96fdd6-4ab9-11eb-0e33-2719394a66ba
# ╟─1f255304-4ab9-11eb-34f1-270fd5a95256
# ╠═34a18900-4ab9-11eb-17a0-1168dd9d06f9
# ╟─39a0a328-4ab9-11eb-0f37-6717095b56aa
# ╠═4749f268-4ab9-11eb-15a7-579437e0bd20
# ╟─5a9bbbe6-4aba-11eb-3652-43eb7891f437
# ╟─6bdc8a5e-4aba-11eb-263c-df3af7afa517
# ╠═77c5280c-58d0-11eb-19ad-83ec82b6d7c7
# ╟─c297d307-a640-4d86-aa0b-a149fe741bac
# ╟─a69ead46-4abc-11eb-3d1d-eb1c73f65150
# ╠═b482b998-4abc-11eb-36da-379010485bfa
# ╟─07b103ae-4abd-11eb-311b-278d1e033642
# ╠═15f8b7fe-4abd-11eb-2777-8fc8bf9d342e
# ╠═efae58fa-5008-11eb-32fe-c3ae588d14f2
# ╠═18f99e46-4abd-11eb-20a8-859cb1b12fe3
# ╟─3a7954da-4abd-11eb-3c5b-858054b4d06b
# ╟─8b17d538-4abd-11eb-0543-ab95c9548d6f
# ╠═29d34e64-5009-11eb-3301-f729150e17b2
# ╠═91a9d1a0-4abd-11eb-3337-71983f32b6ae
# ╠═942d4202-4abd-11eb-1f01-dfe3df40a5b7
# ╠═942dae0e-4abd-11eb-20a2-37d9c9882ba8
# ╠═943d9850-4abd-11eb-1cbc-a1bef988c910
# ╠═943de2ce-4abd-11eb-2410-31382ae9c74f
# ╠═9460c03c-4abd-11eb-0d60-4d8aeb5b0c1d
# ╠═946161f4-4abd-11eb-0ec5-df225dc140d0
# ╠═947d143a-4abd-11eb-067d-dff955c90407
# ╠═947fea8e-4abd-11eb-1d6a-2bc540f7a50e
# ╠═948eff10-4abd-11eb-36d0-5183e882a9e2
# ╠═948f5032-4abd-11eb-3d1c-7da4cb64521c
# ╠═94b520e6-4abd-11eb-3161-addf3b0e4f24
# ╠═94b78322-4abd-11eb-3006-454548efd164
# ╠═94d28c80-4abd-11eb-08c0-717207e4c682
# ╟─9fe6e1a2-4abd-11eb-0c39-458ce94265c0
# ╠═ae26ab9e-4abd-11eb-3270-33558dbdf663
# ╠═b08dc886-4abd-11eb-1807-096a7e6fd6f9
# ╠═b08e3a28-4abd-11eb-258a-a5a93b4b882c
# ╠═b0a8dfe0-4abd-11eb-167d-2fc3974c7c92
# ╠═b0a97e00-4abd-11eb-371c-e138aea17bb6
# ╠═b0ccc252-4abd-11eb-048b-4bec3750bbf1
# ╟─60b066d8-5009-11eb-3b4c-8b8fa2f4831d
# ╠═b8b7c0f6-5009-11eb-1eb7-0be723a88863
# ╟─bd446c42-4abd-11eb-0465-d9a61c48ff48
# ╠═1c5975ec-4abe-11eb-0ff0-bfb2f03a520b
# ╟─10a31233-5de3-401b-abdd-a27908e2de9d
# ╟─1c22b880-4abf-11eb-3f18-756c1198ccad
# ╟─37086212-4abf-11eb-3ec9-7f8dae57121e
# ╠═489421d8-4abf-11eb-0d5e-fd779cc918a1
# ╟─6736dafe-4abf-11eb-1fce-0716d2b7f4a8
# ╟─8933033a-4abf-11eb-1156-a53a5ee9152c
# ╠═0c693c24-4ac0-11eb-2329-c743dcc5039d
# ╟─32c8ff60-9a35-4d40-8b54-b4805ca81885
# ╟─035a53ba-4ac1-11eb-3c34-b50a803b7b7d
# ╟─2a5fca7c-4ac0-11eb-33a3-23d972ca27b8
# ╠═3896642a-4ac0-11eb-2c7c-4f376ab82217
# ╠═3ef3faf8-4ac0-11eb-1965-fd23413e29f3
# ╟─3916f50e-661d-11eb-0829-cb3821836fdf
# ╠═4118016e-4ac0-11eb-18bf-5de326782c87
# ╠═4119fbca-4ac0-11eb-1ea9-0bdd324214c5
# ╟─5ebe25c0-661d-11eb-389b-3d81570f7cf0
# ╠═4139bf3c-4ac0-11eb-2b63-77a513149351
# ╟─a1d4127c-4ac0-11eb-116f-79c6ee58f524
# ╠═a93b28e6-4ac0-11eb-074f-a7b64f43a194
# ╠═ac35b796-4ac0-11eb-3bc5-5ff4350d5452
# ╠═b18e55ae-4ac0-11eb-1455-21b83b7c61d5
# ╟─ec4190a8-4ac0-11eb-0421-398f063775bb
# ╟─7dce10be-500c-11eb-12cf-43f6989aae91
# ╠═bafbe0ea-500c-11eb-10be-75d1738d42a2
# ╠═2033383e-500d-11eb-3aea-cdc1e90dad2c
# ╟─fdb67aba-4ac0-11eb-1d4a-c354de54baa9
# ╠═28f47a24-4ac1-11eb-271f-6b4de7311db3
# ╠═3e433ab4-4ac1-11eb-2178-53b7220fa9ab
# ╠═46112c44-4ac1-11eb-2ad8-030406c7cf67
# ╠═461489fa-4ac1-11eb-0596-1d3bedb61778
# ╟─47338c78-4ac1-11eb-04d6-35c2361eaea6
# ╠═463689b0-4ac1-11eb-1b0f-b7a239011c5c
# ╟─52bfff04-4ac1-11eb-1216-25eedd9184c3
# ╠═61846dae-4ac1-11eb-389a-4fbe3f6145b1
# ╠═6321ae1a-4ac1-11eb-04cb-33e939694874
# ╟─3eda5d12-5a6e-11eb-3264-9925907c85b5
# ╟─7b874424-4ac1-11eb-2d4e-0b4607559b8f
# ╠═86defe2a-4ac1-11eb-3c01-c5e671877212
# ╠═886512de-4ac1-11eb-00e1-73292ec23277
# ╠═88678820-4ac1-11eb-272e-0df61e418900
# ╠═888dee1e-4ac1-11eb-264d-cd4a4f30f498
# ╟─8acb086c-4ac1-11eb-1715-756fde34b38f
# ╠═944e1aaa-4ac1-11eb-0e23-41b1c5d0e889
# ╠═9d4e11be-4ac1-11eb-1fa0-13f1fe60c3bc
# ╠═9d514ef6-4ac1-11eb-25fc-ffaa2dcc9b02
# ╟─a0781222-4ac1-11eb-3425-d9b9603487f3
# ╠═a6b95d62-4ac1-11eb-0c93-7fa0f6a120d5
# ╠═ab006064-4ac1-11eb-32be-6557b8d45f32
# ╟─b0603566-4ac1-11eb-17bc-3b63cd2aa1e9
# ╟─b6db7e2a-5a6e-11eb-1a55-e74b15cbb9e2
# ╟─a647ec42-1b3c-4280-958a-e2a96cd3d103
# ╠═beafce06-4ac1-11eb-2431-1ffeba45716b
# ╟─ec487488-4ac1-11eb-1be3-a93e41f78bf3
# ╠═f88fee6c-4ac1-11eb-1671-43493122f061
# ╠═fa759f92-4ac1-11eb-0d72-1f9d6d38a831
# ╠═fa7ba458-4ac1-11eb-2ca1-59ff3c032b26
# ╠═fa9b3266-4ac1-11eb-153a-87c6a1124890
# ╠═fa9d43b2-4ac1-11eb-33fc-a37503cedabf
# ╟─0dbe0c34-500e-11eb-2633-67d8dc6b24c8
# ╠═900da226-500e-11eb-3f1c-5582ed0cf749
# ╠═b24bee9a-500e-11eb-2130-45b78bbebdec
# ╟─c8cf6520-500e-11eb-2dd3-4507a3994a83
# ╠═19402512-500f-11eb-3ea7-c3d1c52025d6
# ╟─6dc0eaae-500f-11eb-3051-37d870366201
# ╠═8b57c506-500f-11eb-3114-55785eb593a7
# ╟─fd171e0e-4ac1-11eb-09ea-337d17500149
# ╠═10e71260-4ac2-11eb-1069-55613ee7df0a
# ╠═1c0230f8-4ac2-11eb-32aa-e7a4b2ae9cff
# ╠═226417c2-4ac2-11eb-2914-196461e2b40e
# ╟─3daf4fa6-4ac2-11eb-0541-b98c2e97dfe4
# ╟─a777f624-4f2f-11eb-0595-432ea5115a2d
# ╟─abb7bf4e-4f2f-11eb-1dde-abf1cb0fb8b4
# ╠═b63533e8-4f2f-11eb-3811-5f43fcaa2a9e
# ╠═b730b7b8-4f2f-11eb-3216-613c6100fb9c
# ╠═b99eb16c-4f2f-11eb-0f1e-5d2b1244689f
# ╠═bc67fc50-4f2f-11eb-2209-bf35df0e8c27
# ╠═bf40274a-4f2f-11eb-0086-d3502f67161b
# ╟─c48e035c-4f2f-11eb-12c7-576aadca7511
# ╟─c8435740-4f2f-11eb-3046-3d45d5e0e805
# ╠═cacb7254-4f2f-11eb-1daa-1bc04678835c
# ╠═cc48bc9a-4f2f-11eb-134c-71bd8a944943
# ╠═d9f28c04-4f2f-11eb-0255-1965fb8f07b5
# ╠═dc1dbe90-4f2f-11eb-05ce-c1fe46ae14dd
# ╟─de48a3f6-4f2f-11eb-314b-493546c37a21
# ╟─6da71180-4ac2-11eb-1cac-410bd1cce70c
# ╠═85b96ff0-4ac2-11eb-077f-cf4aad8a3c24
# ╟─a11c2898-4ac2-11eb-24d3-6f8060b5fd65
# ╠═a686e67e-4ac2-11eb-228e-23524a3ddc59
# ╟─d50cced2-500d-11eb-2dcc-21fc50825f43
# ╟─ad156892-4ac2-11eb-3634-a3783231e5a1
# ╠═bf1385da-4ac2-11eb-3992-41abac921370
# ╟─d779956a-4ac2-11eb-39de-4b3cecace452
# ╠═c7d2a048-4ac2-11eb-3902-b7c8505096ae
# ╠═cf35b2b2-4ac2-11eb-1ae6-5d3c108210df
# ╠═d1010f88-4ac2-11eb-0fa9-0902fef0cf9f
# ╟─0e63d722-4ac3-11eb-3740-d31b47a77912
# ╟─a48be23a-57ff-11eb-1bc3-3d3e046ea67c
# ╠═a3969292-57ff-11eb-059b-e9e931a30dc1
# ╟─9e211fbf-4c43-439a-95ba-86b48767c053
# ╟─adb47b64-5af8-11eb-1b32-57cbe5d47200
# ╟─c34ede1c-4ad4-11eb-050f-bb07c5d19c1c
# ╠═0cd2d0e4-59e1-11eb-112e-83ebe626f597
# ╠═0c306fd8-4ad5-11eb-1a9f-2d3d1e838a77
# ╠═40557d76-661e-11eb-3df0-659c6095285d
# ╟─fd368fc2-2c64-463e-976e-9dd52ce80f13
# ╟─b1af96ea-5af8-11eb-0d08-f59a4c2b686c
# ╟─e99d6b96-4ad5-11eb-2144-f97a97e71ae4
# ╠═bf53d86c-59e1-11eb-1456-5518e1f63390
# ╟─da5537b5-88e7-4377-90c6-abbc7eaf6495
# ╟─b4118bfa-5af8-11eb-0aca-cddef8e191ee
# ╟─db78af32-b822-49dd-b085-6f51ba6b623f
# ╟─f077c390-57fe-11eb-1ad9-31110b3dac39
# ╟─42f24f58-4ac3-11eb-06b5-ebc015c17520
# ╠═87871f34-4ad1-11eb-3903-93e3f63ea14a
# ╟─c8151b58-735c-4f16-b410-0c5c88818e6d
# ╟─e2583a06-6479-11eb-091b-8be70024930d
# ╟─e6217d50-63de-11eb-28d8-452aeffc956c
# ╠═1f588828-6477-11eb-0c70-557f130c6785
# ╠═4d0e1d60-6476-11eb-154e-d9cc3bf284c2
# ╠═5167ded6-6463-11eb-3279-0dd226fae82e
# ╠═c4d03faa-647d-11eb-3a84-d7b2d53e4720
# ╟─c01bb803-2255-4164-b216-23ec7310f89f
# ╟─c42f69f6-647d-11eb-143b-2f0ab33616e7
# ╠═c55ed390-647d-11eb-22d5-33fbae27a2bb
# ╠═23db2764-647e-11eb-37dd-a34db2ff0fcd
# ╠═989d0dd2-647a-11eb-3123-39786452714a
# ╠═061e4894-647b-11eb-06db-310d02463b80
# ╠═89a9ad00-6489-11eb-2a2d-bfa18af103ab
# ╠═8ab56dcc-6489-11eb-16a4-b7d12003ed99
# ╟─743924b0-0b3b-48e2-9b93-961311d08022
# ╟─0507186a-647a-11eb-07cf-ebf32bdff5b0
# ╟─01eb4816-4ad2-11eb-3991-af76de0110c5
# ╠═448ef88e-4ad2-11eb-20d6-17a51d665ef9
# ╟─15257785-52b5-4129-9e65-7fa15e83a6e3
# ╠═14d50ee8-4ad3-11eb-3b81-9138aec66207
# ╟─0f095a13-8586-4da3-84ae-24c43a303776
# ╟─2bff11a1-7e39-43e5-8058-5d1496f43387


# Copyright (c) 2021, Eric Sabo
# All rights reserved.
#
# This source code is licensed under the BSD-style license found in the
# LICENSE file in the root directory of this source tree.

include("quantumcode.jl")
# include("tricolorcodes488trellis.jl")
# include("tricolorcodes666trellis.jl")

#############################
        # Misc codes
#############################

# miscellaneous small codes useful for testing functions

function fivequbitcode()
    # is a perfect code
    return QuantumCode(["XZZXI", "IXZZX", "XIXZZ", "ZXIXZ"])
end

function Q513()
    return fivequbitcode()
end

# should also do a test for other CSS construction via Hamming code and actually make that one default
function Steanecode()
    # standard
    return CSSCode(["XXXXIII", "XXIIXXI", "XIXIXIX", "ZZZZIII", "ZZIIZZI", "ZIZIZIZ"])

    # trellis numbering
    # return CSSCode(["XXIXXII", "IXXIXXI", "IIIXXXX", "ZZIZZII", "IZZIZZI", "IIIZZZZ"])
    # also ZZIZZII, ZIZZIZI, IZZZIIZ, XXIXXII, XIXXIXI, IXXXIIX
end

function Shorcode()
    return CSSCode(["ZZIIIIIII", "IZZIIIIII", "IIIZZIIII", "IIIIZZIII", "IIIIIIZZI", "IIIIIIIZZ", "XXXXXXIII", "IIIXXXXXX"])
end

function Q412()
    return CSSCode(["XXXX", "ZZII", "IIZZ"])
end

function Q422()
    return CSSCode(["XXXX", "ZZZZ"])
end

function Q511()
    return QuantumCode(["ZXIII", "XZXII", "IXZXI", "IIXZX"])
end

function Q823()
    F, _ = FiniteField(2, 1, "α")
    S = matrix(F, [1 0 0 0 1 0 0 0 1 1 1 1 0 0 0 0;
    0 0 0 1 0 1 0 0 1 0 0 0 0 1 0 0;
    0 1 0 0 1 1 1 0 0 0 1 1 1 0 1 0;
    0 0 1 0 1 1 1 0 0 1 1 0 1 1 0 0;
    0 0 1 1 1 0 1 0 0 0 0 1 0 1 1 1;
    0 0 0 0 0 0 1 1 0 0 1 0 0 0 1 0]);
    return QuantumCode(S, true)
end

function Q15RM()
    return QuantumCode(["ZIZIZIZIZIZIZIZ", "IZZIIZZIIZZIIZZ", "IIIZZZZIIIIZZZZ",
        "IIIIIIIZZZZZZZZ", "IIZIIIZIIIZIIIZ", "IIIIZIZIIIIIZIZ", "IIIIIZZIIIIIIZZ",
        "IIIIIIIIIZZIIZZ", "IIIIIIIIIIIZZZZ", "IIIIIIIIZIZIZIZ", "XIXIXIXIXIXIXIX",
        "IXXIIXXIIXXIIXX", "IIIXXXXIIIIXXXX", "IIIIIIIXXXXXXXX"])
end

#############################
#  Triangular Surface Codes #
#############################

function _triangularlattice(L::Int)
    # 0 - vertical
    # 1 - horizontal
    # 2 - diagonal
    numbering = zeros(Int, L, L, 3)
    num = 1
    for i in 1:L
        for j in 1:L
            for k in 1:3
                numbering[i, j, k] = num
                num += 1
            end
        end
    end
    return numbering
end

function _triangularlatticeXstabilizers(L::Int, numbering::Array{Int64, 3}, symp::Bool=true)
    F, _ = FiniteField(2, 1, "α")
    stabilizers = zero_matrix(F, L^2, 3 * L^2)
    r = 1
    for i in 1:L
        for j in 1:L
            stabilizers[r, numbering[i, j, 1]] = 1
            stabilizers[r, numbering[i, j, 2]] = 1
            stabilizers[r, numbering[i, j, 3]] = 1
            if i == 1
                stabilizers[r, numbering[end, j, 1]] = 1
            else
                stabilizers[r, numbering[i - 1, j, 1]] = 1
            end
            if j == 1
                stabilizers[r, numbering[i, end, 2]] = 1
            else
                stabilizers[r, numbering[i, j - 1, 2]] = 1
            end
            if i == 1 && j == 1
                stabilizers[r, numbering[end, end, 3]] = 1
            elseif i != 1 && j == 1
                stabilizers[r, numbering[i - 1, end, 3]] = 1
            elseif i == 1 && j != 1
                stabilizers[r, numbering[end, j - 1, 3]] = 1
            else
                stabilizers[r, numbering[i - 1, j - 1, 3]] = 1
            end
            r += 1
        end
    end
    if symp
        return hcat(stabilizers, zero_matrix(F, L^2, 3 * L^2))
    end
    return stabilizers
end

function _triangularlatticeZstabilizers(L::Int, numbering::Array{Int64, 3}, symp::Bool=true)
    F, _ = FiniteField(2, 1, "α")
    stabilizers = zero_matrix(F, 2 * L^2, 3 * L^2)
    r = 1
    for i in 1:L
        for j in 1:L
            stabilizers[r, numbering[i, j, 2]] = 1
            stabilizers[r, numbering[i, j, 3]] = 1
            if j == L
                stabilizers[r, numbering[i, 1, 1]] = 1
            else
                stabilizers[r, numbering[i, j + 1, 1]] = 1
            end

            r += 1
            stabilizers[r, numbering[i, j, 1]] = 1
            stabilizers[r, numbering[i, j, 3]] = 1
            if i == L
                stabilizers[r, numbering[1, j, 2]] = 1
            else
                stabilizers[r, numbering[i + 1, j, 2]] = 1
            end
            r += 1
        end
    end
    if symp
        return hcat(zero_matrix(F, 2 * L^2, 3 * L^2), stabilizers)
    end
    return stabilizers
end

function _triangularlatticeXlogicals(L::Int, numbering::Array{Int64, 3}, symp::Bool=true)
    # should be 0110110110
    z = zeros(UInt8, 3 * L^2)
    logical1 = zeros(UInt8, 3 * L^2)
    for j in 0:L - 1
        for k in 0:2
            if k == 0
                logical1[3 * j + k + 1] = 0x01
            elseif k == 1
                logical1[3 * j + k + 1] = 0x00
            else
                logical1[3 * j + k + 1] = 0x01
            end
        end
    end
    if symp
        logical1 = [logical1; z]
    end

    logical2 = zeros(UInt8, 3 * L^2)
    for j in 1:L
        for k in 0:2
            if k == 0
                logical2[3 * j + k + 1] = 0x01
            elseif k == 1
                logical2[3 * j + k + 1] = 0x00
            else
                logical2[3 * j + k + 1] = 0x01
            end
        end
    end
    if symp
        logical2 = [logical2; z]
    end

  return [logical1, logical2]
end

function _triangularlatticeZlogicals(L::Int, numbering::Array{Int64, 3}, symp::Bool=true)
    # should be 1001001001
    x = zeros(UInt8, 3 * L^2)
    logical1 = zeros(UInt8, 3 * L^2)
    for j in 0:L - 1
        for k in 0:2
            if k == 0
                logical1[3 * j + k + 1] = 0x01
            elseif k == 1
                logical1[3 * j + k + 1] = 0x00
            else
                logical1[3 * j + k + 1] = 0x01
            end
        end
    end
    if symp
        logical1 = [z; logical1]
    end

    logical2 = zeros(UInt8, 3 * L^2)
    for j in 1:L
        for k in 0:2
            if k == 0
                logical2[3 * j + k + 1] = 0x01
            elseif k == 1
                logical2[3 * j + k + 1] = 0x00
            else
                logical2[3 * j + k + 1] = 0x01
            end
        end
    end
    if symp
        logical2 = [z; logical2]
    end

  return [logical1, logical2]
end

function triangularsurfacecode(L::Int)
    numbering = _triangularlattice(L)
    Xstabs = _triangularlatticeXstabilizers(L, numbering, false)
    # println(rank(Xstabs))
    Zstabs = _triangularlatticeZstabilizers(L, numbering, false)
    # println(Zstabs)
    # logicals = [triangularlatticeXlogicals(L, numbering), triangularlatticeZlogicals(L, numbering)]
    return CSSCode(Xstabs[1:end - 1, :], Zstabs[1:end - 1, :])
end

#############################
#  Rotated Surface Codes #
#############################

function _RSurfstabslogs(d::Int)
    n = d^2
    E, ω = FiniteField(2, 2, "ω")
    X = E(1)
    S = zero_matrix(E, n - 1, n)
    row = 1

    # X's
    i = 1
    while i <= n - d
        S[row, i] = X
        S[row, i + 1] = X
        S[row, i + d] = X
        S[row, i + d + 1] = X
        row += 1
        if (i + 2) % d == 0
            i += 4
        else
            i += 2
        end
    end

    # top row X's
    i = 2
    while i <= d - 1
        S[row, i] = X
        S[row, i + 1] = X
        row += 1
        i += 2
    end

    # bottom row X's
    i = d * (d - 1) + 1
    while i <= d * d - 2
        S[row, i] = X
        S[row, i + 1] = X
        row += 1
        i += 2
    end

    # Z's
    i = 2
    while i < n - d
        S[row, i] = ω
        S[row, i + 1] = ω
        S[row, i + d] = ω
        S[row, i + d + 1] = ω
        row += 1
        if (i + 2) % d == 0
            i += 4
        else
            i += 2
        end
    end

    # left Z's
    i = 1
    while i < d * (d - 1)
        S[row, i] = ω
        S[row, i + d] = ω
        row += 1
        i += 2 * d
    end

    # right Z's
    i = 2 * d
    while i < d * d
        S[row, i] = ω
        S[row, i + d] = ω
        row += 1
        i += 2 * d
    end

    logs = zero_matrix(E, 2, n)
    i = d
    while i <= d * d
        logs[1, i] = 0x01
        i += d
    end
    i = 1
    while i <= d
        logs[2, i] = ω
        i += 1
    end

    return S, logs
end

# flint is being ridiculous here
# julia> @time Q = rotatedsurfacecode(21);
# 226.128107 seconds (1.73 G allocations: 80.659 GiB, 18.33% gc time, 0.09% compilation time)
# julia> Base.summarysize(Q)
# 16872
function rotatedsurfacecode(d::Int)
    d >= 3 || error("Current implementation requires d ≥ 3.")

    S, logs = _RSurfstabslogs(d)
    Q = QuantumCode(S)
    setlogicals!(Q, logs)
    return Q
end

#############################
#     XZZX Surface Codes    #
#############################

function _XZZXstabslogs(d::Int)
    n = d^2
    E, ω = FiniteField(2, 2, "ω")
    S = zero_matrix(E, n - 1, n)
    row = 1
    X = E(1)

    i = 1
    for i in 1:n - d
        if i % d != 0
            S[row, i] = X
            S[row, i + 1] = ω
            S[row, i + d] = ω
            S[row, i + d + 1] = X
            row += 1;
        end
    end

    # top row ZX's
    i = 2
    while i <= d - 1
        S[row, i] = ω
        S[row, i + 1] = X
        row += 1
        i += 2
    end

    # bottom row XZ's
    i = d * (d - 1) + 1
    while i <= d * d - 2
        S[row, i] = X
        S[row, i + 1] = ω
        row += 1
        i += 2
    end

    # left ZX's
    i = 1
    while i < d * (d - 1)
        S[row, i] = ω
        S[row, i + d] = X
        row += 1
        i += 2 * d
    end

    # right XZ's
    i = 2 * d
    while i < d * d
        S[row, i] = X
        S[row, i + d] = ω
        row += 1
        i += 2 * d
    end

    logs = zero_matrix(E, 2, n)
    i = d
    count = 1
    while i <= d * d
        if count % 2 == 1
            logs[1, i] = X
        else
            log[1, i] = ω
        end
        i += d
        count += 1
    end
    i = 1
    count = 1
    while i <= d
        if count % 2 == 1
            log[2, i] = ω
        else
            log[2, i] = X
        end
        i += 1
        count += 1
    end

    return S, logs
end

function XZZXsurfacecode(d::Int)
    d >= 3 || error("Current implementation requires d ≥ 3.")

    S, logs = _XZZXstabslogs(d)
    Q = QuantumCode(S)
    setlogicals!(Q, logs)
    return Q
end

################################
# Triangular Color Codes 4.8.8 #
################################

function tricolorcode488(d::Int)
    3 <= d <= 21 || error("Current implementation requires 3 ≤ d ≤ 21.")

    if d == 3
        S, logs = _488d3trellis()
        Q = QuantumCode(S)
        setlogicals!(Q, logs)
        return Q
    elseif d == 5
        S, logs = _488d5trellis()
        Q = QuantumCode(S)
        setlogicals!(Q, logs)
        return Q
    elseif d == 7
        S, logs = _488d7trellis()
        Q = QuantumCode(S)
        setlogicals!(Q, logs)
        return Q
    elseif d == 9
        S, logs = _488d9trellis()
        Q = QuantumCode(S)
        setlogicals!(Q, logs)
        return Q
    elseif d == 11
        S, logs = _488d11trellis()
        Q = QuantumCode(S)
        setlogicals!(Q, logs)
        return Q
    elseif d == 13
        S, logs = _488d13trellis()
        Q = QuantumCode(S)
        setlogicals!(Q, logs)
        return Q
    elseif d == 15
        S, logs = _488d15trellis()
        Q = QuantumCode(S)
        setlogicals!(Q, logs)
        return Q
    elseif d == 17
        S, logs = _488d17trellis()
        Q = QuantumCode(S)
        setlogicals!(Q, logs)
        return Q
    elseif d == 19
        S, logs = _488d19trellis()
        Q = QuantumCode(S)
        setlogicals!(Q, logs)
        return Q
    elseif d == 21
        S, logs = _488d21trellis()
        Q = QuantumCode(S)
        setlogicals!(Q, logs)
        return Q
    end
end

################################
# Triangular Color Codes 6.6.6 #
################################

function tricolorcode666(d::Int)
    3 <= d <= 21 || error("Current implementation requires 3 ≤ d ≤ 21.")

    if d == 3
        # same as 4.8.8
        S, logs = _488d3trellis()
        Q = QuantumCode(S)
        setlogicals!(Q, logs)
        return Q
    elseif d == 5
        S, logs = _666d5trellis()
        Q = QuantumCode(S)
        setlogicals!(Q, logs)
        return Q
    elseif d == 7
        S, logs = _666d7trellis()
        Q = QuantumCode(S)
        setlogicals!(Q, logs)
        return Q
    elseif d == 9
        S, logs = _666d9trellis()
        Q = QuantumCode(S)
        setlogicals!(Q, logs)
        return Q
    elseif d == 11
        S, logs = _666d11trellis()
        Q = QuantumCode(S)
        setlogicals!(Q, logs)
        return Q
    elseif d == 13
        S, logs = _666d13trellis()
        Q = QuantumCode(S)
        setlogicals!(Q, logs)
        return Q
    elseif d == 15
        S, logs = _666d15trellis()
        Q = QuantumCode(S)
        setlogicals!(Q, logs)
        return Q
    elseif d == 17
        S, logs = _666d17trellis()
        Q = QuantumCode(S)
        setlogicals!(Q, logs)
        return Q
    elseif d == 19
        S, logs = _666d19trellis()
        Q = QuantumCode(S)
        setlogicals!(Q, logs)
        return Q
    elseif d == 21
        S, logs = _666d21trellis()
        Q = QuantumCode(S)
        setlogicals!(Q, logs)
        return Q
    end
end


# Let S₊ be the cone of symmetric semidefinite matrices in
# the n*(n+1)/2 dimensional space of symmetric R^{nxn} matrices.
# It is well known that S₊ is a self-dual proper cone.
# Let P₊ be the cone of symmetric semidefinite matrices in
# the n^2 dimensional space of R^{nxn} matrices and
# let D₊ be the cone of matrices A such that A+Aᵀ ∈ P₊.
# P₊ is not proper since it is not solid (as it is not n^2 dimensional) so it is not ensured that (P₊)** = P₊
# However this is the case since, as we will see, (P₊)* = D₊ and (D₊)* = P₊.
# * Let us first see why (P₊)* = D₊.
#   If B is symmetric, then ⟨A,B⟩ = ⟨Aᵀ,Bᵀ⟩ = ⟨Aᵀ,B⟩ so 2⟨A,B⟩ = ⟨A,B⟩ + ⟨Aᵀ,B⟩ = ⟨A+Aᵀ,B⟩
#   Therefore, ⟨A,B⟩ ⩾ 0 for all B ∈ P₊ if and only if ⟨A+Aᵀ,B⟩ ⩾ 0 for all B ∈ P₊
#   Since A+Aᵀ is symmetric and we know that S₊ is self-dual, we have shown that (P₊)*
#   is the set of matrices A such that A+Aᵀ is PSD
# * Let us now see why (D₊)* = P₊.
#   Since A ∈ D₊ implies that Aᵀ ∈ D₊, B ∈ (D₊)* means that ⟨A+Aᵀ,B⟩ ⩾ 0 for any A ∈ D₊ hence B is positive semi-definite.
#   To see why it should be symmetric, simply notice that if B[i,j] < B[j,i] then ⟨A,B⟩ can be made arbitrarily small by setting
#   A[i,j] += s
#   A[j,i] -= s
#   with s arbitrarilly large, and A stays in D₊ as A+Aᵀ does not change.
#
# Typically, SDP primal/dual are presented as
# min ⟨C, X⟩                                                                max ∑ b_ky_k
# ⟨A_k, X⟩ = b_k ∀k                                                         C - ∑ A_ky_k ∈ S₊
#        X ∈ S₊                                                                      y_k free ∀k
# Here, as we allow A_i to be non-symmetric, we should rather use
# min ⟨C, X⟩                                                                max ∑ b_ky_k
# ⟨A_k, X⟩ = b_k ∀k                                                         C - ∑ A_ky_k ∈ P₊
#        X ∈ D₊                                                                      y_k free ∀k
# which is implemented as
# min ⟨C, Z⟩ + (C[i,j]-C[j-i])s[i,j]                                        max ∑ b_ky_k
# ⟨A_k, Z⟩ + (A_k[i,j]-A_k[j,i])s[i,j] = b_k ∀k                   C+Cᵀ - ∑ (A_k+A_kᵀ)y_k ∈ S₊
#       s[i,j] free  1 ⩽ i,j ⩽ n with i > j     C[i,j]-C[j-i] - ∑ (A_k[i,j]-A_k[j,i])y_k = 0  1 ⩽ i,j ⩽ n with i > j
#        Z ∈ S₊                                                                      y_k free ∀k
# where "∈ S₊" only look at the diagonal and upper diagonal part.
# In the last primal program, we have the variables Z = X + Xᵀ and a upper triangular matrix S such that X = Z + S - Sᵀ

"""
    SquareBridge{T, F<:MOI.AbstractVectorFunction,
                 G<:MOI.AbstractScalarFunction,
                 TT<:MOI.AbstractSymmetricMatrixSetTriangle,
                 ST<:MOI.AbstractSymmetricMatrixSetSquare} <: AbstractBridge

The `SquareBridge` reformulates the constraint of a square matrix to be in `ST`
to a list of equality constraints for pair or off-diagonal entries with
different expressions and a `TT` constraint the upper triangular part of the
matrix.

For instance, the constraint for the matrix
```math
\\begin{pmatrix}
  1      & 1 + x & 2 - 3x\\\\
  1 +  x & 2 + x & 3 -  x\\\\
  2 - 3x & 2 + x &     2x
\\end{pmatrix}
```
to be PSD can be broken down to the constraint of the symmetric matrix
```math
\\begin{pmatrix}
  1      & 1 + x & 2 - 3x\\\\
  \\cdot & 2 + x & 3 -  x\\\\
  \\cdot & \\cdot &    2x
\\end{pmatrix}
```
and the equality constraint between the off-diagonal entries (2, 3) and (3, 2)
``2x == 1``. Note that now symmetrization constraint need to be added between
the off-diagonal entries (1, 2) and (2, 1) or between (1, 3) and (3, 1) since
the expressions are the same.
"""
struct SquareBridge{
    T,
    F<:MOI.AbstractVectorFunction,
    G<:MOI.AbstractScalarFunction,
    TT<:MOI.AbstractSymmetricMatrixSetTriangle,
    ST<:MOI.AbstractSymmetricMatrixSetSquare,
} <: AbstractBridge
    square_set::ST
    triangle::CI{F,TT}
    sym::Vector{Pair{Tuple{Int,Int},CI{G,MOI.EqualTo{T}}}}
end
function bridge_constraint(
    ::Type{SquareBridge{T,F,G,TT,ST}},
    model::MOI.ModelLike,
    f::F,
    s::ST,
) where {T,F,G,TT,ST}
    f_scalars = MOIU.eachscalar(f)
    sym = Pair{Tuple{Int,Int},CI{G,MOI.EqualTo{T}}}[]
    dim = MOI.side_dimension(s)
    upper_triangle_indices = Int[]
    trilen = div(dim * (dim + 1), 2)
    sizehint!(upper_triangle_indices, trilen)
    k = 0
    for j in 1:dim
        for i in 1:j
            k += 1
            push!(upper_triangle_indices, k)
            # We constrain the entries (i, j) and (j, i) to be equal
            upper = f_scalars[i+(j-1)*dim]
            lower = f_scalars[j+(i-1)*dim]
            diff = MOIU.operate!(-, T, upper, lower)
            MOIU.canonicalize!(diff)
            # The value 1e-10 was decided in https://github.com/jump-dev/JuMP.jl/pull/976
            # This avoid generating symmetrization constraints when the
            # functions at entries (i, j) and (j, i) are almost identical
            if !MOIU.isapprox_zero(diff, 1e-10)
                if MOIU.isapprox_zero(diff, 1e-8)
                    @warn "The entries ($i, $j) and ($j, $i) of the" *
                          " positive semidefinite constraint are almost" *
                          " identical but a constraint is added to ensure their" *
                          " equality because the largest difference between the" *
                          " coefficients is smaller than 1e-8 but larger than" *
                          " 1e-10."
                end
                push!(
                    sym,
                    (i, j) => MOIU.normalize_and_add_constraint(
                        model,
                        diff,
                        MOI.EqualTo(zero(T)),
                        allow_modify_function = true,
                    ),
                )
            end
        end
        k += dim - j
    end
    @assert length(upper_triangle_indices) == trilen
    triangle = MOI.add_constraint(
        model,
        f_scalars[upper_triangle_indices],
        MOI.triangular_form(s),
    )
    return SquareBridge{T,F,G,TT,ST}(s, triangle, sym)
end

function MOI.supports_constraint(
    ::Type{SquareBridge{T}},
    ::Type{<:MOI.AbstractVectorFunction},
    ::Type{<:MOI.AbstractSymmetricMatrixSetSquare},
) where {T}
    return true
end
function MOIB.added_constrained_variable_types(::Type{<:SquareBridge})
    return Tuple{DataType}[]
end
function MOIB.added_constraint_types(
    ::Type{SquareBridge{T,F,G,TT,ST}},
) where {T,F,G,TT,ST}
    return [(F, TT), (G, MOI.EqualTo{T})]
end
function concrete_bridge_type(
    ::Type{<:SquareBridge{T}},
    F::Type{<:MOI.AbstractVectorFunction},
    ST::Type{<:MOI.AbstractSymmetricMatrixSetSquare},
) where {T}
    S = MOIU.scalar_type(F)
    G = MOIU.promote_operation(-, T, S, S)
    TT = MOI.triangular_form(ST)
    return SquareBridge{T,F,G,TT,ST}
end

# Attributes, Bridge acting as a model
function MOI.get(
    ::SquareBridge{T,F,G,TT},
    ::MOI.NumberOfConstraints{F,TT},
) where {T,F,G,TT}
    return 1
end
function MOI.get(
    bridge::SquareBridge{T,F,G},
    ::MOI.NumberOfConstraints{G,MOI.EqualTo{T}},
) where {T,F,G}
    return length(bridge.sym)
end
function MOI.get(
    bridge::SquareBridge{T,F,G,TT},
    ::MOI.ListOfConstraintIndices{F,TT},
) where {T,F,G,TT}
    return [bridge.triangle]
end
function MOI.get(
    bridge::SquareBridge{T,F,G},
    ::MOI.ListOfConstraintIndices{G,MOI.EqualTo{T}},
) where {T,F,G}
    return map(pair -> pair.second, bridge.sym)
end

# Indices
function MOI.delete(model::MOI.ModelLike, bridge::SquareBridge)
    MOI.delete(model, bridge.triangle)
    for pair in bridge.sym
        MOI.delete(model, pair.second)
    end
end

# Attributes, Bridge acting as a constraint
function MOI.get(
    model::MOI.ModelLike,
    attr::MOI.ConstraintFunction,
    bridge::SquareBridge{T},
) where {T}
    tri = MOIU.eachscalar(MOI.get(model, attr, bridge.triangle))
    dim = MOI.side_dimension(bridge.square_set)
    sqr = Vector{eltype(tri)}(undef, dim^2)
    sqrmap(i, j) = (j - 1) * dim + i
    k = 0
    for j in 1:dim
        for i in 1:j
            k += 1
            sqr[sqrmap(i, j)] = tri[k]
            sqr[sqrmap(j, i)] = tri[k]
        end
    end
    for sym in bridge.sym
        i, j = sym.first
        diff = MOI.get(model, attr, sym.second)
        set = MOI.get(model, MOI.ConstraintSet(), sym.second)
        upper = sqr[sqrmap(i, j)]
        lower = MOIU.operate(-, T, upper, diff)
        lower = MOIU.operate!(-, T, lower, MOI.constant(set))
        sqr[sqrmap(j, i)] = MOIU.convert_approx(eltype(tri), lower)
    end
    return MOIU.vectorize(sqr)
end
function MOI.get(::MOI.ModelLike, ::MOI.ConstraintSet, bridge::SquareBridge)
    return bridge.square_set
end
function MOI.get(
    model::MOI.ModelLike,
    attr::MOI.ConstraintPrimal,
    bridge::SquareBridge{T},
) where {T}
    tri = MOI.get(model, attr, bridge.triangle)
    dim = MOI.side_dimension(bridge.square_set)
    sqr = Vector{eltype(tri)}(undef, dim^2)
    k = 0
    for j in 1:dim
        for i in 1:j
            k += 1
            sqr[i+(j-1)*dim] = sqr[j+(i-1)*dim] = tri[k]
        end
    end
    return sqr
end
function MOI.get(
    model::MOI.ModelLike,
    attr::MOI.ConstraintDual,
    bridge::SquareBridge,
)
    tri = MOI.get(model, attr, bridge.triangle)
    dim = MOI.side_dimension(bridge.square_set)
    sqr = Vector{eltype(tri)}(undef, dim^2)
    k = 0
    for j in 1:dim
        for i in 1:j
            k += 1
            # The triangle constraint uses only the upper triangular part
            if i == j
                sqr[i+(j-1)*dim] = tri[k]
            else
                sqr[i+(j-1)*dim] = 2tri[k]
                sqr[j+(i-1)*dim] = zero(eltype(sqr))
            end
        end
    end
    for pair in bridge.sym
        i, j = pair.first
        dual = MOI.get(model, attr, pair.second)
        sqr[i+(j-1)*dim] += dual
        sqr[j+(i-1)*dim] -= dual
    end
    return sqr
end


# Stricker Klaus 2020 - License is MIT: http://julialang.org/license
# ReadWriteDlm2 - rwd2_readutils.jl- https://github.com/strickek/ReadWriteDlm2.jl

"""

    parseothers(y::AbstractString, doparsetime::Bool, doparsecomplex::Bool, doparserational::Bool)

Parse string `y` for `Time`, `Complex` and `Rational` format and if match return the value.
Otherwise return the input string `y`.
"""
function parseothers(y, doparsetime, doparsecomplex, doparserational)

    if doparsetime # parse Time
        mt = match(r"^ *(0?\d|1\d|2[0-3])[:Hh]([0-5]?\d)(:([0-5]?\d)([\.,](\d{1,3})(\d{1,3})?(\d{1,3})?)?)? *$", y)
        if mt != nothing
            h = parse(Int, mt[1])
            mi = parse(Int, mt[2])
            (mt[4] == nothing) ? s = ms = us = ns = 0 :
            s  = parse(Int, lpad(string(mt[4]), 2, string(0))); (mt[6] == nothing) ? ms = us = ns = 0 :
            ms = parse(Int, rpad(string(mt[6]), 3, string(0))); (mt[7] == nothing) ? us = ns = 0 :
            us = parse(Int, rpad(string(mt[7]), 3, string(0))); (mt[8] == nothing) ? ns = 0 :
            ns = parse(Int, rpad(string(mt[8]), 3, string(0)))
            return Dates.Time(h, mi, s, ms, us, ns)
        end
    end

    if doparsecomplex # parse Complex
        mc = match(r"^ *(-?\d+(\.\d+)?([eE]-?\d+)?|(-?\d+)//(\d+)) ?([\+-]) ?(\d+(\.\d+)?([eE]-?\d+)?|(\d+)//(\d+))(\*im|\*i|\*j|im|i|j) *$", y)
        if mc != nothing
            real =
                ((mc[4] != nothing) && (mc[5] != nothing)) ? //(parse(Int, mc[4]), parse(Int, mc[5])) :
                ((mc[2] == nothing) && (mc[3] == nothing)) ? parse(Int, mc[1]) : parse(Float64, mc[1])
            imag =
                ((mc[10] != nothing) && (mc[11] != nothing)) ? //(parse(Int, mc[6]*mc[10]), parse(Int, mc[11])) :
                ((mc[8] == nothing) && (mc[9] == nothing)) ? parse(Int, mc[6]*mc[7]) : parse(Float64, mc[6]*mc[7])
            return complex(real, imag)
        end
    end

    if doparserational # parse Rational
        mr = match(r"^ *(-?\d+)//(-?\d+) *$", y)
        if mr != nothing
            nu = parse(Int, mr[1])
            de = parse(Int, mr[2])
            return //(nu, de)
        end
    end

    return y
end