using Test, FinancialMonteCarlo, DualNumbers;
@show "HestonModel"
S0 = 100.0;
K = 100.0;
r = 0.02;
T = 1.0;
d = 0.01;
D = 90.0;

Nsim = 10000;
Nstep = 30;
sigma = 0.2;
sigma_zero = dual(0.2, 1.0);
kappa = 0.01;
theta = 0.03;
lambda = 0.01;
rho_ = 0.0;
mc = MonteCarloConfiguration(Nsim, Nstep);
mc1 = MonteCarloConfiguration(Nsim, Nstep, FinancialMonteCarlo.AntitheticMC());
toll = 0.8;

rfCurve = ZeroRate(r);

FwdData = Forward(T)
EUData = EuropeanOption(T, K)
AMData = AmericanOption(T, K)
BarrierData = BarrierOptionDownOut(T, K, D)
AsianData1 = AsianFloatingStrikeOption(T)
AsianData2 = AsianFixedStrikeOption(T, K)
Model = HestonProcess(sigma, sigma_zero, lambda, kappa, rho_, theta, Underlying(S0, d));

@show FwdPrice = pricer(Model, rfCurve, mc, FwdData);
@show EuPrice = pricer(Model, rfCurve, mc, EUData);
@show AmPrice = pricer(Model, rfCurve, mc, AMData);
@show BarrierPrice = pricer(Model, rfCurve, mc, BarrierData);
@show AsianPrice1 = pricer(Model, rfCurve, mc, AsianData1);
@show AsianPrice2 = pricer(Model, rfCurve, mc, AsianData2);

@test abs(FwdPrice - 98.8790717426047) < toll
@test abs(EuPrice - 7.9813557592924) < toll
@test abs(BarrierPrice - 7.006564636309922) < toll
@test abs(AsianPrice1 - 4.242256952013707) < toll

@show FwdPrice = pricer(Model, rfCurve, mc1, FwdData);
@show EuPrice = pricer(Model, rfCurve, mc1, EUData);
@show AmPrice = pricer(Model, rfCurve, mc1, AMData);
@show BarrierPrice = pricer(Model, rfCurve, mc1, BarrierData);
@show AsianPrice1 = pricer(Model, rfCurve, mc1, AsianData1);
@show AsianPrice2 = pricer(Model, rfCurve, mc1, AsianData2);
tollanti = 0.8
@test abs(FwdPrice - 98.8790717426047) < tollanti
@test abs(EuPrice - 7.9813557592924) < tollanti
@test abs(BarrierPrice - 7.006564636309922) < tollanti
@test abs(AsianPrice1 - 4.242256952013707) < tollanti


using PyCall, PyPlot

## Example 1
py"""
import numpy as np
def func(x, y):
    return x*(1-x)*np.cos(4*np.pi*x) * np.sin(4*np.pi*y**2)**2
"""

np = pyimport("numpy")
scipy = pyimport("scipy")
interpolate = pyimport("scipy.interpolate")
griddata = interpolate.griddata

s = pybuiltin(:slice)
XY = get(np.mgrid, (s(0,1,100im), s(0,1,200im)))
grid_x, grid_y = XY[1,:,:], XY[2,:,:]

points = np.random.rand(1000, 2)
values = py"func"(points[:,1], points[:,2])

grid_z0 = griddata(points, values, (grid_x, grid_y), method="nearest")
grid_z1 = griddata(points, values, (grid_x, grid_y), method="linear")
grid_z2 = griddata(points, values, (grid_x, grid_y), method="cubic")

plt.subplot(221)
plt.imshow(py"func"(grid_x, grid_y), extent=(0,1,0,1), origin="lower")
plt.plot(points[:,1], points[:,2], "k.", ms=1)
plt.title("Original")
plt.subplot(222)
plt.imshow(grid_z0, extent=(0,1,0,1), origin="lower")
plt.title("Nearest")
plt.subplot(223)
plt.imshow(grid_z1, extent=(0,1,0,1), origin="lower")
plt.title("Linear")
plt.subplot(224)
plt.imshow(grid_z2, extent=(0,1,0,1), origin="lower")
plt.title("Cubic")
plt.gcf().set_size_inches(6, 6)
plt.show()

## Example 2
py"""
def func(x, y):
    return x + y
"""

s = pybuiltin(:slice)
XY = get(np.mgrid, (s(0,1,5im),s(0,1,5im)))
grid_x, grid_y = XY[1,:,:], XY[2,:,:]
points = np.random.rand(25, 2)
values = py"func"(points[:, 1], points[:, 2])

griddata(points, values, (grid_x, grid_y), method="nearest")
griddata(points, values, (grid_x, grid_y), method="cubic")

using PyPlot
plt.plot(points[:,1], points[:,2], "k.", ms=1)

## test 1
py"""
def func(x, y, z):
    return x + y + z
"""
np = pyimport("numpy")
scipy = pyimport("scipy")
interpolate = pyimport("scipy.interpolate")
griddata = interpolate.griddata

s = pybuiltin(:slice)
XYZ = get(np.mgrid, (s(0,1,10im), s(0,1,20im), s(0,1,10im)))
grid_x, grid_y, grid_z = XYZ[1,:,:,:], XYZ[2,:,:,:], XYZ[3,:,:,:]

points = np.random.rand(1000, 3)
values = py"func"(points[:,1], points[:,2], points[:,3])

grid_z0 = griddata(points, values, (grid_x, grid_y, grid_z), method="nearest")
grid_z1 = griddata(points, values, (grid_x, grid_y, grid_z), method="linear")
grid_z2 = griddata(points, values, (grid_x, grid_y, grid_z), method="cubic")


export fourier_diff

"""
    fourier_diff([T=Float64,] N; order=1)

Create a Fourier differentiation matrix with numerical type T on the domain
`x = range(0, 2π, length=N+1)[1:end-1]`.
"""
function fourier_diff(T::Type{<:Number}, N::Integer; order=1)
    D = zeros(T, N, N)
    n1 = (N - 1) ÷ 2
    n2 = N ÷ 2
    x = LinRange{T}(0, π, N+1)
    if order == 1
        for i in 2:N
            sgn = (one(T)/2 - iseven(i))
            D[i, 1] = iseven(N) ? sgn*cot(x[i]) : sgn*csc(x[i])
        end
    elseif order == 2
        D[1, 1] = iseven(N) ? -N^2*one(T)/12 - one(T)/6 : -N^2*one(T)/12 + one(T)/12
        for i in 2:N
            sgn = -(one(T)/2 - iseven(i))
            D[i, 1] = iseven(N) ? sgn*csc(x[i]).^2 : sgn*cot(x[i])*csc(x[i])
        end
    else
        error("Not implemented")
    end
    for j in 2:N
        D[1, j] = D[N, j-1]
        D[2:N, j] .= D[1:N-1, j-1]
    end
    return D
end
fourier_diff(N::Integer; kwargs...) = fourier_diff(Float64, N; kwargs...)


### A Pluto.jl notebook ###
# v0.12.18

using Markdown
using InteractiveUtils

# ╔═╡ dcfde69e-20c7-11eb-0108-dbd2f5cb0098
begin
	using Pkg
    Pkg.activate(pwd())
	Pkg.instantiate()
end

# ╔═╡ 864115a0-20d4-11eb-16ff-71d198f9342e
begin
	using CSV
	using DataFrames
	using RDatasets
	using StatsBase
	using Plots
	using LinearAlgebra
	import Statistics: covm
end

# ╔═╡ a33c14a0-20c2-11eb-0bf8-837173d9d91c
md"""
# Assignment 1: Implement PCA
### Aadam (CS1945)
### CSE 662 - Deep Learning by Dr. Masroor Hussain

---

## Assignment Statement:
Write the code in C, Python or Matlab for $l=1$ PCA as discussed in class of Chapter no. 2. Five bonus points for $1 =< l < n$

---
"""

# ╔═╡ cf5c9eb0-20c2-11eb-05ba-2f5dd23fdaf4
md"""
# Principal Component Analysis (PCA)
PCA is a technique that is used to derive an orthogonal projection to convert a given set of observations to linearly uncorrelated variables, called **principal components**.

One of the most widely used application of PCA is **dimensionality reduction**, which helps us out, when training our models on large datasets, by minimizing noise and redundancy in the dataset. Basic idea of the dimensionality reduction is to represent an $M$-dimensional data into an $N$-dimensional subspace, where $N < M$. This would result in data loss, but our aim is to minimize it as much as possible. For that purposes, we try to find some **principal components** in the data that can represent the features as a linear combination, without loosing much information.

PCA uses **covariance matrix** to analyze:
- variance of each feature, showing if a feature is relevant or pure noise.
- linear relationship strength between pairs of features, spotting redundant features.

There are two main approaches to identify principal components:
1. Calculate the **eigenvectors** of the covariance matrix.
2. Calculate the **single value decomposition** of the covariance matrix.

Although **SVD** has higher numerical accuracy, it has lower running time, as compared to the **eigenvector decomposition** method.

For this assignment, we'll use **Eigendecomposition** method to find principal components.
"""

# ╔═╡ b6ed7820-20ce-11eb-0584-9130387e39ef
md"""
# Solution:
First, let's create a dummy dataset.
"""

# ╔═╡ 85bf8990-20d4-11eb-1a7b-e35d9e62f35a
gr();

# ╔═╡ f5d95832-20ea-11eb-2828-39ebb61c877f
md"""
### Utility functions
"""

# ╔═╡ 0024d7b0-20eb-11eb-2959-af3584eb7750
begin
	centralize(x::AbstractVector, m::AbstractVector) = (isempty(m) ? x : x - m)
	centralize(x::AbstractMatrix, m::AbstractVector) = (isempty(m) ? x : x .- m)

	decentralize(x::AbstractVector, m::AbstractVector) = (isempty(m) ? x : x + m)
	decentralize(x::AbstractMatrix, m::AbstractVector) = (isempty(m) ? x : x .+ m)

	transform(P, x, mv) = transpose(P) * centralize(x, mv)
	reconstruct(P, y, mv) = decentralize(P * y, mv)

	# extract k values/vectors
	function extract_kv(fac, ord, k)
		si = ord[1:k]
		vals = fac.values[si]
		vecs = fac.vectors[:, si]
		return (vals, vecs)
	end
end

# ╔═╡ 85976820-20d4-11eb-2afa-47a833fdccc4
md"""
## Input Data
For this example, we'll use **iris** dataset.
"""

# ╔═╡ dcce4b20-20c7-11eb-3cb1-852a46b75c31
iris = dataset("datasets", "iris") # DataFrame(rand(100, 10))

# ╔═╡ dcb4a8a2-20c7-11eb-3a1a-9fb620e84e57
size(iris)

# ╔═╡ dc975ca0-20c7-11eb-0a2b-b9e470c6e311
describe(iris)

# ╔═╡ 9241d120-20d2-11eb-0b30-25249995c00c
md"""
#### Let's create separate train and test datasets.
"""

# ╔═╡ b2a8ccc0-20d2-11eb-28eb-87dc018ae352
Xtrain = Array(iris[1:2:end,1:4])';

# ╔═╡ afb22c8e-20d3-11eb-339a-09f39705a02f
Ytrain = Array(iris[1:2:end,5]);

# ╔═╡ afe6aa10-20d3-11eb-03a4-89a46ade9feb
Xtest = Array(iris[2:2:end,1:4])';

# ╔═╡ b0004c90-20d3-11eb-2b38-452b177600d8
Ytest = Array(iris[2:2:end,5]);

# ╔═╡ b02fe810-20d3-11eb-21f4-f9939c117c12
md"""
Suppose `Xtrain` and `Xtest` are training and testing data matrix, with each observation in a column.

## Data Normalization
Now, we need to normalize the data.

"""

# ╔═╡ 399f2a20-20d4-11eb-1c3a-e76ac9707e29
mean_iris = vec(mean(Xtrain, dims=2))

# ╔═╡ c58d8a10-20e1-11eb-301a-eb715e5926d8
md"""
## Covariance Matrix Computation
Covariance matrix is an $N \times N$ symmetric matrix, where $N$ is the number of dimensions. This tells us about the relation between any two features, and the variation of data from the mean.
"""

# ╔═╡ 39b65ba2-20d4-11eb-11bd-7daa396810ac
C_iris = covm(Xtrain, mean_iris, 2);

# ╔═╡ 6493e090-20e3-11eb-0dec-0bdef8eb1c6a
DataFrame(C_iris)

# ╔═╡ c308e590-20e2-11eb-0ddb-055f8bdedb85
md"""
## Eigendecomposition of Covariance Matrix
We need to find the eigenvalues and eigenvectors of the covariance matrix in order to find the principal components of the data. These components are created in such a way that they are uncorrelated, and the first component contains the maximum amount of information possible, and the second contains the maximum remaining, and so on.
"""

# ╔═╡ 39e24da0-20d4-11eb-10e5-8320e876fb74
eg = eigen(Symmetric(C_iris));

# ╔═╡ b04fa510-20d3-11eb-0d40-21f2bf50e115
eg_values = eg.values

# ╔═╡ f010e210-20e5-11eb-1952-37e6ed84ea22
eg_vectors = eg.vectors

# ╔═╡ 35710110-20e5-11eb-0ad2-ef911c8d78bc
md"""
Let's sort the eigenvalues in reverse order, so the component which contains the most information is at the front.
"""

# ╔═╡ b1d8dc20-20de-11eb-1364-59fc5f488736
ord = sortperm(eg_values; rev=true)

# ╔═╡ 6f9d6dfe-20e5-11eb-2745-e95f930eaf88
md"""
Let's see how much information each component contains. For that, we need to find the total variance of the input, and then divide the principal component's variance with it. We'll plot the values for better visualization.
"""

# ╔═╡ b211ed80-20de-11eb-3abc-7f0b887915a5
tvar = sum(eg_values)

# ╔═╡ 9fefec80-20e6-11eb-3e16-e5f17e1d9560
eg_values[ord] ./ tvar * 100

# ╔═╡ b25a1a10-20de-11eb-3bcb-fbe70a5b974d
bar(eg_values[ord] ./ tvar * 100, title="Scree Plot")

# ╔═╡ b26da210-20de-11eb-026b-8fc4efce305c
md"""
By looking at the above graph, we can see that the first component, $PC 1$, contains more than $90 \%$ of the information, while $PC 2$ contains less than $5 \%$.

Even though many believe $PCA$ to be a dimensionality reduction algorithm, primarily it's a data transformation algorithm. It just makes our data amenable to data reduction, as shown above. Here, as most of the information is contained in the first couple of principal components, we can decide to keep only those componenets and discard the rest, resulting in a reduction in the dimensionality of the dataset.

### $l = 1$:
This means that we only take the first PC and discard the rest.
"""

# ╔═╡ d6429920-20cf-11eb-0bf1-8f74c17dc643
v, P = extract_kv(eg, ord, 1)

# ╔═╡ 72f60020-20eb-11eb-049a-d14c26e12ddb
md"""
This returns the eigen values, as well as the project matrix $P$. We can use $P$ to transform dataset. Transforming the `Xtest` using $P$ would result in:
"""

# ╔═╡ d628f6a0-20cf-11eb-11cc-01f1ca2b6f13
tr_data = transform(P, Xtest, mean_iris)

# ╔═╡ 6ae889c0-20eb-11eb-3ecb-4f13647511c3
md"""
which is a $1 \times 75$ array, instead of the original $4 \times 75$.

Let's try to reconstruct the original data from the transformed data using $P$.
"""

# ╔═╡ 1081c390-20f8-11eb-3e81-133df7444871
recon_data = reconstruct(P, tr_data, mean_iris);

# ╔═╡ 3313dc90-20f8-11eb-27eb-e9ea7e6a3805
md"""
Printing the first 5 observations from both the reconstructed data as well as the original data.
"""

# ╔═╡ d5e95590-20cf-11eb-0d5f-e3e5f2be14c0
DataFrame(recon_data)

# ╔═╡ d5b26710-20cf-11eb-0f4c-7f81055af3e3
DataFrame(Xtest[:, 1:5])

# ╔═╡ 4cd4fb30-20c3-11eb-0631-a9c8840a4cf9
md"""
As can be seen, the reconstructed data is fairly close to the original data, although not exactly the same because this is a lossy method. Even though we retained around $92\%$ information, we still lost some information by reducing the dimensions.

We can retain more information by increasing $l$, i.e. increasing the number of output dimensions.

## $1 <= l < n$
Instead of taking only $PC 1$, we will take the first three PCs.
"""

# ╔═╡ 740d6e20-20ec-11eb-1b12-1d614d3efb08
v3, P3 = extract_kv(eg, ord, 3)

# ╔═╡ 7466b1b2-20ec-11eb-1a71-e7061e6a7ad6
md"""
Again, let's first transform and reconstruct the data using `P3`.
"""

# ╔═╡ 747f1bb0-20ec-11eb-3cbf-a3a8d7a64563
tr3_data = transform(P3, Xtest, mean_iris);

# ╔═╡ 65ce7a10-20fc-11eb-1d02-f57de17db24e
size(tr3_data)

# ╔═╡ 7493b520-20ec-11eb-0e19-57cbd43d09af
recon3_data = reconstruct(P3, tr3_data, mean_iris);

# ╔═╡ 74a9ae20-20ec-11eb-1e5d-7984c0a399f3
DataFrame(recon3_data)

# ╔═╡ ff1b4e90-20f8-11eb-3924-2fa0c61677fd
DataFrame(Xtest[:, 1:5])

# ╔═╡ 053f9290-20f9-11eb-1cb6-6f5c7b26e279
md"""
We can see that using $l = 3$ gives a better approximation of the original data as compared to $l = 1$.

## Visualization
Let's visualize the first 3 principal components in a 3D plot.
"""

# ╔═╡ 9599eb10-20f9-11eb-044b-cd71da0c30a3
begin
	# group by labels, for color coding
	setosa = tr3_data[:,Ytest.=="setosa"];
	versicolor = tr3_data[:,Ytest.=="versicolor"];
	virginica = tr3_data[:,Ytest.=="virginica"];

	p = scatter(setosa[1,:],setosa[2,:],setosa[3,:],marker=:circle,linewidth=0)
	scatter!(versicolor[1,:],versicolor[2,:],versicolor[3,:],marker=:circle,linewidth=0)
	scatter!(virginica[1,:],virginica[2,:],virginica[3,:],marker=:circle,linewidth=0)
	plot!(p,xlabel="PC1",ylabel="PC2",zlabel="PC3")
end

# ╔═╡ 645934a0-20fb-11eb-1750-cf7ca28ab9e6
md"""
## Conclusion
In this notebook, we showed how we can use `PCA` to find principal components of some data, and then use those for dimensionality reduction without severly affecting and losing the information.
"""

# ╔═╡ 4d526890-20c3-11eb-082b-e912d7c9b3e2
md"""
# Resources

- [StatQuest: Principal Component Analysis (PCA), Step-by-Step](https://www.youtube.com/watch?v=FgakZw6K1QQ&t=458s&ab_channel=StatQuestwithJoshStarmer)
- [Data Analysis 6: Principal Component Analysis (PCA) - Computerphile](https://www.youtube.com/watch?v=TJdH6rPA-TI&ab_channel=Computerphile)
- [Making sense of principal component analysis, eigenvectors & eigenvalues](https://stats.stackexchange.com/questions/2691/making-sense-of-principal-component-analysis-eigenvectors-eigenvalues/140579)
- [A STEP BY STEP EXPLANATION OF PRINCIPAL COMPONENT ANALYSIS](https://builtin.com/data-science/step-step-explanation-principal-component-analysis)
- [Machine Learning — Singular Value Decomposition (SVD) & Principal Component Analysis (PCA)](https://jonathan-hui.medium.com/machine-learning-singular-value-decomposition-svd-principal-component-analysis-pca-1d45e885e491)
- [Relationship between SVD and PCA. How to use SVD to perform PCA?](https://stats.stackexchange.com/questions/134282/relationship-between-svd-and-pca-how-to-use-svd-to-perform-pca)
"""

# ╔═╡ Cell order:
# ╟─a33c14a0-20c2-11eb-0bf8-837173d9d91c
# ╟─cf5c9eb0-20c2-11eb-05ba-2f5dd23fdaf4
# ╟─dcfde69e-20c7-11eb-0108-dbd2f5cb0098
# ╟─b6ed7820-20ce-11eb-0584-9130387e39ef
# ╠═864115a0-20d4-11eb-16ff-71d198f9342e
# ╟─85bf8990-20d4-11eb-1a7b-e35d9e62f35a
# ╟─f5d95832-20ea-11eb-2828-39ebb61c877f
# ╠═0024d7b0-20eb-11eb-2959-af3584eb7750
# ╟─85976820-20d4-11eb-2afa-47a833fdccc4
# ╠═dcce4b20-20c7-11eb-3cb1-852a46b75c31
# ╠═dcb4a8a2-20c7-11eb-3a1a-9fb620e84e57
# ╠═dc975ca0-20c7-11eb-0a2b-b9e470c6e311
# ╟─9241d120-20d2-11eb-0b30-25249995c00c
# ╠═b2a8ccc0-20d2-11eb-28eb-87dc018ae352
# ╠═afb22c8e-20d3-11eb-339a-09f39705a02f
# ╠═afe6aa10-20d3-11eb-03a4-89a46ade9feb
# ╠═b0004c90-20d3-11eb-2b38-452b177600d8
# ╟─b02fe810-20d3-11eb-21f4-f9939c117c12
# ╠═399f2a20-20d4-11eb-1c3a-e76ac9707e29
# ╟─c58d8a10-20e1-11eb-301a-eb715e5926d8
# ╠═39b65ba2-20d4-11eb-11bd-7daa396810ac
# ╟─6493e090-20e3-11eb-0dec-0bdef8eb1c6a
# ╟─c308e590-20e2-11eb-0ddb-055f8bdedb85
# ╠═39e24da0-20d4-11eb-10e5-8320e876fb74
# ╠═b04fa510-20d3-11eb-0d40-21f2bf50e115
# ╠═f010e210-20e5-11eb-1952-37e6ed84ea22
# ╟─35710110-20e5-11eb-0ad2-ef911c8d78bc
# ╠═b1d8dc20-20de-11eb-1364-59fc5f488736
# ╟─6f9d6dfe-20e5-11eb-2745-e95f930eaf88
# ╠═b211ed80-20de-11eb-3abc-7f0b887915a5
# ╟─9fefec80-20e6-11eb-3e16-e5f17e1d9560
# ╠═b25a1a10-20de-11eb-3bcb-fbe70a5b974d
# ╟─b26da210-20de-11eb-026b-8fc4efce305c
# ╠═d6429920-20cf-11eb-0bf1-8f74c17dc643
# ╟─72f60020-20eb-11eb-049a-d14c26e12ddb
# ╠═d628f6a0-20cf-11eb-11cc-01f1ca2b6f13
# ╟─6ae889c0-20eb-11eb-3ecb-4f13647511c3
# ╠═1081c390-20f8-11eb-3e81-133df7444871
# ╟─3313dc90-20f8-11eb-27eb-e9ea7e6a3805
# ╠═d5e95590-20cf-11eb-0d5f-e3e5f2be14c0
# ╠═d5b26710-20cf-11eb-0f4c-7f81055af3e3
# ╟─4cd4fb30-20c3-11eb-0631-a9c8840a4cf9
# ╠═740d6e20-20ec-11eb-1b12-1d614d3efb08
# ╟─7466b1b2-20ec-11eb-1a71-e7061e6a7ad6
# ╠═747f1bb0-20ec-11eb-3cbf-a3a8d7a64563
# ╠═65ce7a10-20fc-11eb-1d02-f57de17db24e
# ╠═7493b520-20ec-11eb-0e19-57cbd43d09af
# ╠═74a9ae20-20ec-11eb-1e5d-7984c0a399f3
# ╠═ff1b4e90-20f8-11eb-3924-2fa0c61677fd
# ╟─053f9290-20f9-11eb-1cb6-6f5c7b26e279
# ╟─9599eb10-20f9-11eb-044b-cd71da0c30a3
# ╟─645934a0-20fb-11eb-1750-cf7ca28ab9e6
# ╟─4d526890-20c3-11eb-082b-e912d7c9b3e2


function axis_angle_to_quaternion(x)
    @assert length(x) == 3
    θ = norm(x)
    if θ > 0.0
        r = x ./ θ
        q = Quaternion(cos(0.5 * θ), sin(0.5 * θ) * r)
    else
        q = Quaternion(1.0, 0.0, 0.0, 0.0)
    end
    return q
end

function daxis_angle_to_quaterniondx(x)
    θ = norm(x)
    if θ > 0.0
        r = x ./ θ

        ∂qw∂x = -0.5 * sin(0.5 * θ) * transpose(x) ./ θ
        ∂qx∂x = 0.5 * cos(0.5 * θ) * transpose(x) ./ θ * r[1] + [sin(0.5 * θ) / θ 0.0 0.0] - sin(0.5 * θ) * x[1] / θ^2 * transpose(x) ./ θ
        ∂qy∂x = 0.5 * cos(0.5 * θ) * transpose(x) ./ θ * r[2] + [0.0 sin(0.5 * θ) / θ 0.0] - sin(0.5 * θ) * x[2] / θ^2 * transpose(x) ./ θ
        ∂qz∂x = 0.5 * cos(0.5 * θ) * transpose(x) ./ θ * r[3] + [0.0 0.0 sin(0.5 * θ) / θ] - sin(0.5 * θ) * x[3] / θ^2 * transpose(x) ./ θ

        return [
                ∂qw∂x;
                ∂qx∂x;
                ∂qy∂x;
                ∂qz∂x;
               ]
    else
        return [
                    0.0  0.0  0.0;
                    0.5  0.0  0.0;
                    0.0  0.5  0.0;
                    0.0  0.0  0.5;
                ]
    end
end

function axes_pair_to_quaternion(n1, n2)
	if norm(n1 + n2, Inf) < 1e-5
		n2 = n2 + 1e-5ones(3)
	end

	reg(x) = 1e-20 * (x == 0) + x
	# provides the quaternion that rotates n1 into n2, assuming n1 and n2 are normalized
	n1 ./= reg(norm(n1))
	n2 ./= reg(norm(n2))
	n3 = skew(n1)*n2
	cθ = n1' * n2 # cosine
	sθ = norm(n3) # sine
	axis = n3 ./ reg(sθ)
	tanθhalf = sθ / reg(1 + cθ)
	q = [1; tanθhalf * axis]
	q /= norm(q)
	return Quaternion(q...)
end


# p30.jl - spectral integration, ODE style (compare p12.jl)

# Computation: various values of N, four functions:
Nmax = 50; E = zeros(4,Nmax); clf();
for N = 1:Nmax
    i = 1:N; (D,x) = cheb(N);
    x = x[i]; Di = inv(D[i,i]); w = Di[1,:];
    f = @. abs(x)^3;     E[1,N] = abs(dot(w,f) - .5);
    f = @. exp(-x^(-2)); E[2,N] = abs(dot(w,f) - 2*(exp(-1)+sqrt(pi)*(erf(1)-1)));
    f = @. 1/(1+x^2);    E[3,N] = abs(dot(w,f) - pi/2);
    f = x.^10;           E[4,N] = abs(dot(w,f) - 2/11);
end

# Plot results:
labels = [L"|x|^3",L"\exp(-x^2)",L"1/(1+x^2)",L"x^{10}"];
for iplot = 1:4
    subplot(2,2,iplot)
    semilogy(E[iplot,:]+1e-100,".-",markersize=10);
    axis([0,Nmax,1e-18,1e3]); grid(true);
    xticks(0:10:Nmax); yticks((10.0).^(-15:5:0));
    ylabel("error"); text(32,.004,labels[iplot]);
end


# Copyright (c) 2021 Idiap Research Institute, http://www.idiap.ch/
#  Niccolò Antonello <nantonel@idiap.ch>

export BoolWeight

"""
`BoolWeight(x::Bool)`

| Set           |     ``\\oplus``      |  ``\\otimes``  | ``\\bar{0}`` | ``\\bar{1}`` |
|:-------------:|:--------------------:|:--------------:|:------------:|:------------:|
| ``\\{0,1\\}`` |       ``\\lor``      |  ``\\land``    |   ``0``      |    ``1``     |
"""
struct BoolWeight <: Semiring
  x::Bool
end

zero(::Type{BoolWeight}) = BoolWeight(false)
one(::Type{BoolWeight}) = BoolWeight(true)

*(a::BoolWeight, b::BoolWeight) = BoolWeight(a.x && b.x)
+(a::BoolWeight, b::BoolWeight) = BoolWeight(a.x || b.x)

#properties
isidempotent(::Type{W}) where {W <: BoolWeight} = true
iscommulative(::Type{W}) where {W <: BoolWeight} = true
isleft(::Type{W}) where {W <: BoolWeight}= true
isright(::Type{W}) where {W <: BoolWeight}= true
ispath(::Type{W}) where {W <: BoolWeight}= true
iscomplete(::Type{W}) where {W <: BoolWeight}= true


# ---
# title: 69. Sqrt(x)
# id: problem69
# author: Tian Jun
# date: 2020-10-31
# difficulty: Easy
# categories: Math, Binary Search
# link: <https://leetcode.com/problems/sqrtx/description/>
# hidden: true
# ---
#
# Given a non-negative integer `x`, compute and return _the square root of_ `x`.
#
# Since the return type is an integer, the decimal digits are **truncated** ,
# and only **the integer part** of the result is returned.
#
#
#
# **Example 1:**
#
#
#
#     Input: x = 4
#     Output: 2
#
#
# **Example 2:**
#
#
#
#     Input: x = 8
#     Output: 2
#     Explanation: The square root of 8 is 2.82842..., and since the decimal part is truncated, 2 is returned.
#
#
#
# **Constraints:**
#
#   * `0 <= x <= 231 - 1`
#
#
## @lc code=start
using LeetCode

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


using Distributions
using DataStructures

f(x) = x^2 - 5 * x + 6

function filter_best_solutions(solutions; pa=0.25)
    new_best_solutions=SortedDict(Dict{Float64, Float64}())
    println("***FILTER RCVD : ", length(solutions))
    println("Filterting : ", floor((length(solutions) - length(solutions)*pa)))
    let
        i = 1
        for (key, value) in solutions
            new_best_solutions[key] = value
            i = i + 1
            if i >= (length(solutions) - length(solutions)*pa)
                return merge(new_best_solutions, initialize_population(floor((length(solutions) - length(solutions)*pa))))
            end
        end
    end
end

function get_best_solution(soln)
    best = soln[1]
    for s in soln
        if f(s) < f(best)
            best = s
        end
    end
    return best
end

heaviside(x::AbstractFloat) = ifelse(x < 0, zero(x), ifelse(x > 0, one(x), oftype(x,0.5)))

function generate_new_population(population; pa=0.25)
    new_population=SortedDict(Dict{Float64, Float64}())
    for (key, value) in population
        new_value = value + heaviside(pa - rand()) * (rand(population)[2] - rand(population)[2])
        new_population[f(new_value)] = new_value
    end
    return new_population
end

function initialize_population(total)
    population = SortedDict(Dict{Float64,Float64}())
    for i=1:total
        soln = rand(Float64)
        population[f(soln)] = soln
    end
    return population
end

function cuckoo_search(f;total_cuckoos=25,pa = 0.25,α=0.01, λ=0.5, MaxGenerations = 15)
    cuckoos = initialize_population(total_cuckoos)
    println("Initial population of Cuckoos: ", length(cuckoos))
    L = Levy(λ)
    best = nothing
    t=1
    while t < MaxGenerations
        r_cuckoo = rand(cuckoos)
        new_best = r_cuckoo[2] + α * rand(L)
        if (f(new_best) < r_cuckoo[2])
            best = new_best
        end
        #
        cuckoos = filter_best_solutions(cuckoos, pa=pa)
        println("After FILTER Total Cuckoos: ", length(cuckoos))
        cuckoos = generate_new_population(cuckoos, pa=pa)
        best = first(cuckoos)[2]
        println("Current Best: ", best)
        println("Total Cuckoos: ", length(cuckoos))
        t = t +1
    end
    return best
end

global_minimum=cuckoo_search(f)

println("Final Global Minimum: ", global_minimum)


using Symata
using Test

@sym begin
  LagrangePolynomial(xi_, yi_) := Module([N, sum, num, den],
    begin
      N = Length(xi)
      sum = 0
      For( i=1, i <= N, Increment(i),
        begin
          num = 1.0
          den = 1.0
          For( j=1, j <= N, Increment(j),
            begin
              num = If(j != i, num = num * (x-xi[j]), num)
              den = If(j != i, den = den * (xi[i]-xi[j]), den)
            end
          )
          sum = sum + yi[i] * num/den
        end
      ),
      Return(Simplify(sum))
    end
  )
end

@sym begin
  CollocationWeightedResidualMethod(xi_, yi_, doprint_) := Module([Y, R],
    begin
      Y(x_) := LagrangePolynomial(xi, yi)
      If(doprint, Println("\nY(x) = ", Y(x), "\n"))
      #
      # Can be formulated as ytile(x) = F(x) + C1(a) * Ψ(x)
      #
      # F(x_) := 2*x^2 - x
      # C(a_) := -4a
      # Ψ(x_) := x^2 - x
      #
      #Y(x_) := F(x) + C(a)*Ψ(x)
      #
      R(x_) = Simplify(D(Y(x), x, 2) - 3*x - 4*Y(x))
      R(x_) = Simplify(R(x) ./ (a => -C1/4))
      If(doprint, Println("R(x) = ", R(x), "\n"))
      Return(R(x))
    end
  )
end

function wrm(x1,y1, doprint=true)
    setsymata(:xin, List(x1...))
    setsymata(:yin, List(y1...))
    setsymata(:doprint, doprint ? true : false)
    #symtranseval(Meta.parse("Compile(Evaluate(CollocationWeightedResidualMethod(xin,yin,doprint)))"))
    symparseeval("Compile(Evaluate(CollocationWeightedResidualMethod(xin,yin,doprint)))")
end

println("\nExample 7.13: y'' = 3x + 4y, y(0)=0, y(1)=1")
println("Residual for Weighted Residual Method using 1 point Lagragian Polynomial\n")

#
# A consequence of the Julia function based approach is that Y(x) and R(x) are local
# to the Symata function and are not available for examination, e.g. @sym R(x)
#

lp_eq = @sym ToString(LagrangePolynomial([0.0,0.5,1.0], [0.0,a,1.0]))
println(lp_eq)
@test lp_eq == "x*(-1.0 + 2.0*x + (-4.0)*a*(-1.0 + x))"
wrm_eq = @sym ToString(CollocationWeightedResidualMethod([0.0,0.5,1.0], [0.0,a,1.0], false))
println(wrm_eq)
@test wrm_eq == "4.0 + 2.0*C1 - 3x - 4x*(-1.0 + 2.0*x + C1*(-1.0 + x))"

#
# Another consideration is that below yt is yt(C1::Any, x::Any)
#

yt = wrm([0.0,0.5,1.0], [0.0,:a,1.0], true)

println("( Example 7.13 gives: R = -4x^2*(2 + C1) + x*(1 + 4C1) + 2*(2 + C1) )", "\n")

"
```
The function ytilde(x, C1) contains the 1-point Weighted Residual Method
polynomial with parameter C1.
```
Arguments to ytilde(x, C1):

x::Float64          : x value\n
C1::Float64         : Weighted Residual Method constant.

C1 can be estimated by e.g. collocation, subdomain, least squares or Galerkin.
See examples 7.15 through 7.18 and Fig.7.14.jl
"
ytilde(x, C1) = yt(C1, x)
println("ytilde(1.0, 0.0) = $(ytilde(1.0, 0.0))")
@test ytilde(1.0, 0.0) == -3.0

println("ytilde(0.0, 1.0) = $(ytilde(0.0, 1.0))")
@test ytilde(0.0, 1.0) == 6.0

#
# Tests with @symExpr
#

setsymata(:x1, [0.0,0.5,1.0])
wrm_1pt_col(x, C1) = @symExpr CollocationWeightedResidualMethod(x1, [0.0,a,1.0], false)

#
# The order of the arguments can be chosen. This is nice.
#

println("wrm_1pt_col(1.0, 0.0) = $(wrm_1pt_col(1.0, 0.0))")
@test wrm_1pt_col(1.0, 0.0) == ytilde(1.0, 0.0)

#
# Like @sym, I think @symExpr is not intended to return an expression from a Julia function.
#

function wrm_1(xin, xinval, C1inval)
  setsymata(:x1, xin)
  f(x, C1) = @symExpr CollocationWeightedResidualMethod(x1, [0.0,a,1.0], false)
  f(xinval, C1inval)
end

@test wrm_1([0.0,0.5,1.0], 1.0, 0.0) == ytilde(1.0, 0.0)


@testset "NTT 1D" begin
    g = 2
    q = 31
    x = [1:5;]
    @test intt(ntt(x, g, q), g, q) == x

    g = 3
    q = 257
    x = [1:256;]
    @test intt(ntt(x, g, q), g, q) == x
end

@testset "NTT 1D convolution" begin
    using DSP

    g = 9
    q = 271
    # padding inputs with zeros to get non circular convolution
    x = [1:8; zeros(Int64, 7);]
    h = [1:8; zeros(Int64, 7);]

    X = ntt(x, g, q)
    H = ntt(h, g, q)
    Y = X .* H

    y = intt(Y, g, q)

    @test y == DSP.conv(1:8, 1:8)
end

@testset "NTT 2D" begin
    g = 16
    q = 257
    x = reshape([1:16;] , (4,4))
    y = ntt(x, g, q)
    @test intt(y, g, q) == x

    g = 4
    q = 17
    x = reshape([1:16;] , (4,4))
    y = ntt(x, g, q)
    @test intt(y, g, q) == x

    g = 7
    q = 19
    x = reshape([1:9;] , (3,3))
    y = ntt(x, g, q)
    @test intt(y, g, q) == x
end

@testset "NTT 2D convolution" begin
    using DSP

    (g, q, n) = (7, 4733, 7)

    #zero padding to get non circular convolution
    x = reshape([1:16;] , (4,4))
    x_padded = zeros(Int64, 7, 7)
    x_padded[1:4, 1:4] = x

    h = reshape([4 3 2 0; 4 1 0 0; 0 0 1 2; 0 0 2 3] , (4,4))
    h_padded = zeros(Int64, 7, 7)
    h_padded[1:4, 1:4] = h

    X = ntt(x_padded, g, q)
    H = ntt(h_padded, g, q)
    Y = X .* H

    y = intt(Y, g, q)

    @test y == DSP.conv(x, h)
end


# ---
# title: 62. Unique Paths
# id: problem62
# author: AquaIndigo
# date: 2020-12-09
# difficulty: Medium
# categories: Array, Dynamic Programming
# link: <https://leetcode.com/problems/unique-paths/description/>
# hidden: true
# ---
#
# A robot is located at the top-left corner of a `m x n` grid (marked 'Start' in
# the diagram below).
#
# The robot can only move either down or right at any point in time. The robot
# is trying to reach the bottom-right corner of the grid (marked 'Finish' in the
# diagram below).
#
# How many possible unique paths are there?
#
#
#
# **Example 1:**
#
# ![](https://assets.leetcode.com/uploads/2018/10/22/robot_maze.png)
#
#
#
#     Input: m = 3, n = 7
#     Output: 28
#
#
# **Example 2:**
#
#
#
#     Input: m = 3, n = 2
#     Output: 3
#     Explanation:
#     From the top-left corner, there are a total of 3 ways to reach the bottom-right corner:
#     1. Right -> Down -> Down
#     2. Down -> Down -> Right
#     3. Down -> Right -> Down
#
#
# **Example 3:**
#
#
#
#     Input: m = 7, n = 3
#     Output: 28
#
#
# **Example 4:**
#
#
#
#     Input: m = 3, n = 3
#     Output: 6
#
#
#
#
# **Constraints:**
#
#   * `1 <= m, n <= 100`
#   * It's guaranteed that the answer will be less than or equal to `2 * 109`.
#
#
## @lc code=start
using LeetCode

unique_paths(m::Int, n::Int) = binomial(m + n - 2, n - 1)
## @lc code=end


"""
    create_upper_tri(vec,V)

Create an upper triangluar matrix from a vector of the form [12, ... 1V,23,...(V-1)V]
to the form [0 12 13  ... 1V]
            [0 0  23  ... 2V]
            [...............]
            [0 0  0...(V-1)V]
# Arguments
- `vec`: vector containing values to put into the upper triangluar matrix
- `V`  : dimension of output matrix

# Returns
Upper triangluar matrix containing values of `vec`
"""
function create_upper_tri(vec::Array{T,1},V::Int64) where T <: Union{Int64,Float64}
    mat = zeros(T,V,V)
    vec2 = deepcopy(vec)
    for k = 1:V
        for l = k+1:V
            mat[k,l] = popfirst!(vec2)
        end
    end
    return mat
end

"""
    upper_triangle(matrix)

Return the upper triangle (without the diagonal) of the matrix as a vector

# Arguments
- `matrix`: matrix of which to capture the upper triangle

# Returns
Vector of upper triangluar section of `matrix`
"""
function upper_triangle(matrix::Array{T,2}) where T <: Union{Int64,Float64}
    k = 1
    ret = zeros(T,convert(Int64, round(size(matrix,1)*(size(matrix,2) - 1)/2)))
    for i in 1:size(matrix,1)
        for j in (i+1):size(matrix,2)
            ret[k] = matrix[i,j]
            k = k + 1
        end
    end
    return ret
end


using RadauBVP, Plots
pyplot()

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

  if !isdefined(Main, :bc!)
    function bc!(y0, yn, r)
      r[1] = y0[1] - 0.0
      r[2] = yn[2] - (-1.0)
    end

    function dbc!(y0, yn, jac)
      jac[:,:] = 0.0
      jac[1,1] = 1.0
      jac[2,4] = 1.0
    end

    function odeeq!(t, y, dydt)
      dydt[1] = y[2]
      dydt[2] = -y[1]
    end

    function dodeeq!(t, y, jac)
      jac[1,1] = 0.0
      jac[1,2] = 1.0
      jac[2,1] = -1.0
      jac[2,2] = 0.0
    end
  end

  nt = 5
  const y0 = zeros(Float64, (2, 3*(nt-1)+1))

  for n=1:3*(nt-1)+1
    y0[:, n] = [0.0, 1.0]
  end

  @time (conv, ttot, ytot, result) =
    RadauBVP.radau3(bc!, odeeq!, dbc!, dodeeq!, y0, 0.0, convert(Float64, π))

  result |> display

  yres = ytot[1,:]
  vres = ytot[2,:]

  let t = linspace(0.0, π, 100)

    global possol = sin.(t)
    global vsol = cos.(t)
    global ysol = Array(Float64, 2*100)

    ysol[1:2:end-1] = possol
    ysol[2:2:end] = vsol
  end

  p1 = plot(
  linspace(0.0, π, 100),
  possol,
  title = "Coordinate (n = $nt)",
  label = "Exact")
  plot!(ttot, yres, label = "Numerical", linetype = :scatter)

  p2 = plot(
  linspace(0.0, π, 100),
  vsol,
  title = "Velocity (n = $nt)",
  label = "Exact")
  plot!(ttot, vres, label = "Numerical", linetype = :scatter)

  plot(p1,p2)
  savefig("radau.png")
  plot(p1,p2)

end


"LU Decomposition"
module LUDecomposition

using LinearAlgebra: dot

function get_n(A::Matrix)::Int
    (n_r, n_c) = size(A)
    @assert n_r == n_c "Input matrix is not square"
    n_r
end

"Row permutation"
struct Permutation
    indices::Vector{Int}
    n_pivots::Int
end

raw"""
LU factorization with partial pivoting.

`A` is changed to $(L - E) + U$ s.t. $P A = L U$.
"""
function lu_decompose!(A::Matrix{<:AbstractFloat})::Union{Permutation, Nothing}
    n = get_n(A)
    p = collect(1:n)
    n_pivots = n  # for computing the determinant

    @inbounds for c in 1:n
        # Using ArrayView here doesn't help.
        # See https://github.com/JuliaLang/julia/issues/19198#issuecomment-257986870.
        (max_value, c_max) = findmax(abs.(A[c:n, c]))
        if max_value < eps()
            return nothing
        end
        c_max += c - 1

        if c_max != c
            @views A[[c, c_max], :] .= A[[c_max, c], :]
            p[c], p[c_max] = p[c_max], p[c]
            n_pivots += 1
        end

        # `A[r, c]` is assigned to 0 by `A[r, c] -= A[c, c] * (A[r, c] / A[c, c])`, so the element
        # in `L` is `(A[r, c] / A[c, c])`.
        rg = (c + 1):n;
        @views A[rg, c] /= A[c, c]
        @. @views A[rg, rg] -= A[rg, c] * A[c, rg]'
    end

    Permutation(p, n_pivots)
end

raw"""
Solves $A x = b$.

**Precondition**: `LU` is obtained by appling `lu_decompose!` on `A`.
"""
function lu_solve(LU::Matrix{T}, perm::Permutation, b::Vector{T})::Vector{T} where T<:AbstractFloat
    n = get_n(LU)

    # transforms `b`, stored in `x`
    x = b[perm.indices]
    for r in 1:n
        @views x[r] -= dot(LU[r, 1:(r - 1)], x[1:(r - 1)])
    end

    # computes `x`
    for r in n:-1:1
        p = @views dot(LU[r, (r + 1):n], x[(r + 1):n])
        x[r] = (x[r] - p) / LU[r, r]
    end

    x
end

function lu_det(LU::Matrix{T}, n_pivots::Int)::T where T<:AbstractFloat
    n = get_n(LU)
    det = foldl(*, LU[i, i] for i in 1:n)
    if iseven(n - n_pivots)
        det
    else
        -det
    end
end

#===== Unit Tests =====#
using Test

using LinearAlgebra: Diagonal, I, LowerTriangular, UpperTriangular
using LinearAlgebra: det  # for reference implementation
using Random: seed!

@testset "LU decomposition" begin
    n = 32
    seed!()
    A = rand(n, n)
    b = rand(n)

    LU = copy(A)
    perm = lu_decompose!(LU)
    if perm != nothing
        L = LowerTriangular(LU) - Diagonal(LU) + I
        U = UpperTriangular(LU)
        @test L * U ≈ A[perm.indices[:], :]

        @test lu_det(LU, perm.n_pivots) ≈ det(A)

        x = lu_solve(LU, perm, b)
        @test A * x ≈ b
    end
end

end # module


# Laplacian Eigenmaps
# -------------------
# Laplacian Eigenmaps for Dimensionality Reduction and Data Representation,
# M. Belkin, P. Niyogi, Neural Computation, June 2003; 15 (6):1373-1396

"""
    LEM{NN <: AbstractNearestNeighbors, T <: Real} <: AbstractDimensionalityReduction

The `LEM` type represents a Laplacian eigenmaps model constructed for `T` type data with a help of the `NN` nearest neighbor algorithm.
"""
struct LEM{NN <: AbstractNearestNeighbors, T <: Real} <: AbstractDimensionalityReduction
    λ::AbstractVector{T}
    ɛ::T
    proj::Projection{T}
    nearestneighbors::NN
    component::AbstractVector{Int}
end

## properties
outdim(R::LEM) = size(R.proj, 1)
eigvals(R::LEM) = R.λ
neighbors(R::LEM) = R.nearestneighbors.k
vertices(R::LEM) = R.component

## show
summary(io::IO, R::LEM) = print(io, "Laplacian Eigenmaps(outdim = $(outdim(R)), neighbors = $(neighbors(R)), ɛ = $(R.ɛ))")

## interface functions
"""
    fit(LEM, data; k=12, maxoutdim=2, ɛ=1.0, nntype=BruteForce)

Fit a Laplacian eigenmaps model to `data`.

# Arguments
* `data`: a matrix of observations. Each column of `data` is an observation.

# Keyword arguments
* `k`: a number of nearest neighbors for construction of local subspace representation
* `maxoutdim`: a dimension of the reduced space.
* `nntype`: a nearest neighbor construction class (derived from `AbstractNearestNeighbors`)
* `ɛ`: a Gaussian kernel variance (the scale parameter)

# Examples
```julia
M = fit(LEM, rand(3,100)) # construct Laplacian eigenmaps model
R = transform(M)          # perform dimensionality reduction
```
"""
function fit(::Type{LEM}, X::AbstractMatrix{T};
        k::Int=12, maxoutdim::Int=2, ɛ::Real=1.0, nntype=BruteForce) where {T<:Real}
    # Construct NN graph
    NN = fit(nntype, X, k)
    D, E = knn(NN, X)
    G, C = largest_component(SimpleWeightedGraph(adjmat(D,E)))

    # Compute weights
    W = weights(G)
    W .^= 2
    W ./= maximum(W)

    W[W .> eps(T)] = exp.(-W[W .> eps(T)] ./ convert(T,ɛ))
    D = spdiagm(0=>vec(sum(W, dims=2)))
    L = D - W

    λ, V = decompose(L, D, maxoutdim)
    return LEM{nntype, T}(λ, ɛ, transpose(V), NN, C)
end

"""
    transform(R::LEM)

Transforms the data fitted to the Laplacian eigenmaps model `R` into a reduced space representation.
"""
transform(R::LEM) = R.proj


using Makie
using GeometryBasics

outtrig = Triangle(
    Point(0., 0.),
    Point(1., 0.),
    Point(0.5, 1.)
)

intrig = Triangle(
    Point(rand(2)...),
    Point(rand(2)...),
    Point(rand(2)...)
)

fig = Figure()
ax1 = Axis(fig[1, 1])
ax2 = Axis(fig[1, 2])
mesh!(ax1, outtrig.points)
mesh!(ax2, intrig.points)
using GeometryBasics
using Makie
using FractalTools

using Random
Random.seed!(0)

# Define the triangle
trig = Triangle(
    Point(-1., -1.),
    Point(1., -1.),
    Point(0., 2)
)

# Get valid points in the domain
pnts2d = disperse(trig, 10)

# Define function
f(x, y) = x^2 + y^2

# Evaluate function
pnts3d = [Point(pnt[1], pnt[2], f(pnt[1], pnt[2])) for pnt in pnts2d]  # Interpolation data

# Check transformations
_, msh3 = triangulate(pnts3d)
outtrig = findouttriangle(pnts3d)
outtrigpnts2d = project(outtrig.points)

i = Node(39)
ttrig = @lift(msh3[$i])

ttrigpnts2d = lift(ttrig) do ttrig
    project(ttrig.points)
end

mtrig = lift(ttrig) do ttrig
    tfm = gettransform(outtrig, ttrig, 0.01)
    L(x) = tfm.A[1:2, 1:2] * x + tfm.b[1:2]
    Triangle(map(pnt -> Point(L(pnt)...), outtrigpnts2d)...)
end

fig = Figure()
ls = LScene(fig[1, 1])
buttongrid = fig[1, 2] =  GridLayout(tellheight=false)
button1 = buttongrid[1, 1] = Button(fig, label = "Inc")
button2 = buttongrid[2, 1] = Button(fig, label = "Dec")
label = buttongrid[3, 1] = Label(fig, "Tri idx:$(string(i[]))")
mesh!(ls, outtrig)
wireframe!(outtrigpnts2d, color=:black, linewidth=3)
mesh!(ttrig, color=:blue)
wireframe!(ttrigpnts2d, color=:blue, linewidth=3)
mesh!(mtrig, color=:green)

on(button1.clicks) do n
    i[] += 1
    label.text[] = "Tri idx: " * string(i[])
end

on(button2.clicks) do n
    i[] -= 1
    label.text[] = "Tri idx: " * string(i[])
end

display(fig)

# # Interpolate the data
# itp = interpolate(pnts3d, maxiters=4)

# # Evaluat the itp
# tpnt = pnts2d
# # tpnt = [getpoint(trig) for i in 1 : 500]
# val = map(pnt -> itp(pnt...), tpnt)
# rval =  map(pnt -> f(pnt...), tpnt)
# relerr = abs.(val - rval) ./ abs.(rval) * 100

# @show findall(isnan, val)
# @show sum(relerr .≥ 1)

# # Plots
# tess, mesh3 = triangulate(pnts3d)
# mesh2 = GeometryBasics.Mesh(project(coordinates(mesh3)), faces(mesh3))
# fig, ax, plt = scatter(pnts3d, color=:red, markersize=20)
# wireframe!(trig.points, linewidth=3)
# wireframe!(mesh2)
# scatter!(pnts2d, markersize=8, color=:black)
# scatter!(tpnt, markersize=8, color=:green)
# # mesh!(mesh3, color=first.(mesh3.position))
# wireframe!(mesh3, color=:black)

# # Test point
# tpnt = getpoint(trig)
# idx = tess.find_simplex(tpnt)[1] + 1
# scatter!(tpnt, markersize=30, color=:orange)
# mesh!(mesh2[idx])

# tpnt2 = (trig.points[1] + trig.points[2]) / 2
# idx = tess.find_simplex(tpnt2)[1] + 1
# scatter!(tpnt2, markersize=30, color=:blue)
# mesh!(mesh2[idx], color=:green)

# tpnt3 = pnts2d[40]
# idx = tess.find_simplex(tpnt3)[1] + 1
# scatter!(tpnt3, markersize=30, color=:green)
# mesh!(mesh2[idx], color=:black)

# tpnt4 = pnts2d[39]
# scatter!(tpnt4, markersize=30, color=:blue)

# tpnt5 = (pnts2d[40] + pnts2d[39]) / 2
# scatter!(tpnt5, markersize=30, color=:blue)
# idx = tess.find_simplex(tpnt5)[1] + 1
# mesh!(mesh2[idx])

# display(fig)


@testset "collatz" begin

# A little puzzle inspired by the collatz conjecture, reach one by using the collatz conjecture moves (3n + 1 can be used even if the number is even)
neighbours(n) = n % 2 == 0 ? [n ÷ 2, 3n + 1] : [3n + 1]

# The best case is if the number is a power of 2, then the moves are just the log2, otherwise this is a very optimistic heuristic (it doesn't overestimate)
heuristic(n, goal) = floor(Int64, log2(n))

@testset "12" begin
  start = 12
  res = astar(neighbours, start, 1; heuristic)
  @test res.status == :success
  @test res.path == [12, 6, 3, 10, 5, 16, 8, 4, 2, 1]
  @test res.cost == 9
end

@testset "12 with maxcost" begin
  start = 12
  res = astar(neighbours, start, 1; heuristic, maxcost=3)
  @test res.status == :nopath
  @test res.path == [12, 6, 3]
  @test res.cost == 2
end

@testset "12 with cost function" begin
  start = 12

  # dividing costs 1, multiplying costs 4
  cost(state, neighbour) = state / neighbour == 2 ? 1 : 4

  res = astar(neighbours, start, 1; heuristic, cost)
  @test res.status == :success
  @test res.path == [12, 6, 3, 10, 5, 16, 8, 4, 2, 1]
  @test res.cost == 15
end

@testset "12 with maxdepth" begin
  start = 12
  res = astar(neighbours, start, 1; heuristic, maxdepth=3)
  @test res.status == :nopath
  @test res.path == [12, 6, 3]
  @test res.cost == 2
end

end


# #######################################
# Example 9.8 and 10.24 Grouped Multinomial Data of
# Robert, C. P., & Casella, G. (2010). Monte Carlo statistical methods (2. ed., softcover reprint of the hardcover 2. ed. 2004). New York, NY: Springer.
# #######################################

using Distributions
using Plots
using LaTeXStrings

# configure
a = [0.06, 0.14, 0.11, 0.09]
b = [0.17, 0.24, 0.19, 0.20]
x = [9, 15, 12, 7, 8]

# normal gibbs
function gibbs(M = 1000)
    Z = ones(M, 4)
    Params = ones(M, 2)
    Eparams = ones(M, 2)
    # prior
    alpha = [1/2, 1/2, 1/2]
    # initial
    mu = 0.1
    eta = 0.1
    for m in 1:M
        # sample zi
        zi = ones(4)
        for i = 1:2
            aimu = a[i] * mu
            zi[i] = rand(Binomial(x[i], aimu / (aimu + b[i])))
        end
        for i = 3:4
            aieta = a[i] * eta
            zi[i] = rand(Binomial(x[i], aieta / (aieta + b[i])))
        end
        # sample mu eta
        mu, eta, = rand(Dirichlet([ zi[1] + zi[2] + alpha[1], zi[3] + zi[4] + alpha[2], x[5] + alpha[3] ]))
        # store
        Z[m, :] = zi
        Params[m, :] = [mu, eta]
        #Eparams[m, :] = mean(Params[1:m, :], dims = 1)
    end
    Eparams[:, 1] = cumsum(Params[:, 1]) ./ Array(1:M)
    Eparams[:, 2] = cumsum(Params[:, 2]) ./ Array(1:M)
    return Z, Params, Eparams
end
Z, Params, Eparams = gibbs(10000)

plot(Eparams[:,1], label = L"\mu")
plot!(Eparams[:,2], label = L"\eta")
savefig("gibbs.png")

# Metropolization
function Metrogibbs(M = 1000)
    Z = ones(Int, M, 4)
    Params = ones(M, 2)
    Eparams = ones(M, 2)
    # prior
    alpha = [1/2, 1/2, 1/2]
    # initial
    mu = 0.1
    eta = 0.1
    for m in 1:M
        # sample zi
        zi = ones(Int, 4)
        for i = 1:4
            if i == 1 || i == 2
                aimu = a[i] * mu
            else
                aimu = a[i] * eta
            end
            p1 = aimu / (aimu + b[i])
            p2 = 1 - p1
            while true
                zi[i] = rand(Binomial(x[i], p1))
                if m == 1 || zi[i] != Z[m-1, i] # short-circuiting boolean
                    break
                end
            end
            # accept or not
            if m == 1
                rho = 1.1
            else
                num1 = 1 - binomial(Z[m-1, i], x[i]) * p1^Z[m-1,i] * p2^(x[i] - Z[m-1, i])
                num2 = 1 - binomial(zi[i], x[i]) * p1^zi[i] * p2^(x[i] - zi[i])
                rho = num1 / num2
            end
            if rand() > rho
                zi[i] = Z[m-1, i]
            end

        end
        # sample mu eta
        mu, eta, = rand(Dirichlet([ zi[1] + zi[2] + alpha[1], zi[3] + zi[4] + alpha[2], x[5] + alpha[3] ]))
        # store
        Z[m, :] = zi
        Params[m, :] = [mu, eta]
        #Eparams[m, :] = mean(Params[1:m, :], dims = 1)
    end
    Eparams[:, 1] = cumsum(Params[:, 1]) ./ Array(1:M)
    Eparams[:, 2] = cumsum(Params[:, 2]) ./ Array(1:M)
    return Z, Params, Eparams
end

Z, Params, Eparams = Metrogibbs(10000)
plot(Eparams[:,1], label = L"\mu")
plot!(Eparams[:,2], label = L"\eta")
savefig("metrogibbs.png")


"""
`sort_by_size(list)` returns a sorted copy of `list`
in which the elements are sorted by the `length` function
(from smallest to largest).
"""
function sort_by_size(list::Array)
    sort(list,lt=((x,y) -> length(x) < length(y)))
end

import Base.sqrt

function weave(c::Array{Int,1})::Array{Int,1}
    nc = length(c)
    if nc%2 == 0
        error("Attempting to weave $c, an even-length cycle, with itself")
    end
    half = Int((nc+1)/2)
    result = zeros(Int,nc)
    j = 1  # index into c
    jj = 1  # index into result
    while j <= nc
        result[j] = c[jj]
        j += 1
        jj = (jj+half) % nc
        if jj == 0
            jj = nc
        end
    end
    return result
end

function weave(c1::Array{Int,1}, c2::Array{Int,1})::Array{Int,1}
    n1 = length(c1)
    n2 = length(c2)
    @assert n1==n2 "Cycles $c1 and $c2 have different lengths"
    @assert n1%2==0 "Cycles must have even length to be weaved together"

    result = zeros(Int,2n1)
    for j=1:n1
        result[2j-1]=c1[j]
        result[2j]=c2[j]
    end
    return result
end
"""
`sqrt(p::Permutation)` returns a `Permutation` `q` such that
`q*q==p`. Note: There may be other square roots besides the one
returned. If `p` does not have a square root, an error is thrown.
"""
function sqrt(p::Permutation)::Permutation
    err_msg = "This permutation does not have a square root"
    n = length(p)
    clist = sort_by_size(cycles(p))
    nc = length(clist)
    result = zeros(Int,n)

    idx = 1
    while idx <= nc
        cyc = clist[idx]  # next cycle to process
        m = length(cyc)
        if m%2 == 1   # an odd cycle
            tmp = weave(cyc)
            for j=1:m-1
                result[tmp[j]] = tmp[j+1]
            end
            result[tmp[m]] = tmp[1]
            idx += 1
        else
            if idx == nc
                error(err_msg)
            end
            cyc2 = clist[idx+1]
            if length(cyc2) != length(cyc)
                error(err_msg)
            end
            tmp = weave(cyc,cyc2)
            for j=1:2m-1
                result[tmp[j]] = tmp[j+1]
            end
            result[tmp[2m]] = tmp[1]
            idx += 2
        end
    end
    return Permutation(result)
end

√(p::Permutation) = sqrt(p)


function maxadjacent(M, pattern)
	H, W = size(M)
	h, w = size(pattern)

	bestprod = 0

	for i = 1:H-h+1
		for j = 1:W-w+1
			m = M[i:i+h-1,j:j+w-1] .* pattern
			println(m)
			p = prod(m[pattern.==1])
			if p > bestprod
				bestprod = p
			end
		end
	end

	return bestprod
end

function main()
	M = [08 02 22 97 38 15 00 40 00 75 04 05 07 78 52 12 50 77 91 08;
49 49 99 40 17 81 18 57 60 87 17 40 98 43 69 48 04 56 62 00;
81 49 31 73 55 79 14 29 93 71 40 67 53 88 30 03 49 13 36 65;
52 70 95 23 04 60 11 42 69 24 68 56 01 32 56 71 37 02 36 91;
22 31 16 71 51 67 63 89 41 92 36 54 22 40 40 28 66 33 13 80;
24 47 32 60 99 03 45 02 44 75 33 53 78 36 84 20 35 17 12 50;
32 98 81 28 64 23 67 10 26 38 40 67 59 54 70 66 18 38 64 70;
67 26 20 68 02 62 12 20 95 63 94 39 63 08 40 91 66 49 94 21;
24 55 58 05 66 73 99 26 97 17 78 78 96 83 14 88 34 89 63 72;
21 36 23 09 75 00 76 44 20 45 35 14 00 61 33 97 34 31 33 95;
78 17 53 28 22 75 31 67 15 94 03 80 04 62 16 14 09 53 56 92;
16 39 05 42 96 35 31 47 55 58 88 24 00 17 54 24 36 29 85 57;
86 56 00 48 35 71 89 07 05 44 44 37 44 60 21 58 51 54 17 58;
19 80 81 68 05 94 47 69 28 73 92 13 86 52 17 77 04 89 55 40;
04 52 08 83 97 35 99 16 07 97 57 32 16 26 26 79 33 27 98 66;
88 36 68 87 57 62 20 72 03 46 33 67 46 55 12 32 63 93 53 69;
04 42 16 73 38 25 39 11 24 94 72 18 08 46 29 32 40 62 76 36;
20 69 36 41 72 30 23 88 34 62 99 69 82 67 59 85 74 04 36 16;
20 73 35 29 78 31 90 01 74 31 49 71 48 86 81 16 23 57 05 54;
01 70 54 71 83 51 54 69 16 92 33 48 61 43 52 01 89 19 67 48]

	N = 4

	patterns = {}
	push!(patterns, ones(eltype(M), N, 1))
	push!(patterns, ones(eltype(M), 1, N))
	push!(patterns, eye(eltype(M), N))
	push!(patterns, rotr90(eye(eltype(M), N)))

	bestprod = 0
	for pattern in patterns
		n = maxadjacent(M, pattern)
		if n > bestprod
			bestprod = n
		end
	end

	println(bestprod)
end

main()


# ------------------------------------------------------------------------------------------
# # Creating a dynamical system
#
# Topics:
# * What *is* a dynamical system in the context of **DynamicalSystems.jl**?
# * How can we define a discrete or continuous system?
# * What are the differences in handling large and small systems?
#     * Handy Dandy definition table!
# * Comment on DifferentialEquations.jl
# * Docstrings
#
# ---
#
# # Dynamical System
#
# A "dynamical system" is a law that describes how some variables should evolve in time.
# This law is described by the "equations of motion" function $\vec{f}$.
#
# There are two types of dynamical systems (in our case):
#
# 1. One is called a map, where time is a discrete quantity (like "steps" or "generations").
# The equations of motion then look something like $\vec{u}_{n+1} = \vec{f}(\vec{u}_n, p,
# n)$ where $n$ is an integer and $p$ are the parameters of the system.
#
# 2. The other type is called an Ordinary Differential Equation (ODE), where time is a
# continuous quantity. Then, the equations of motion give the time derivatives of the
# variables: $\frac{d\vec{u}}{dt} = \vec{f}(\vec{u}, p, t)$.
#
# In both cases $\vec{u}$ is the state of the system, a vector of the variables that define
# the system. For example, $\vec{u}$ may be defined as $(x, y, z)$, or as $(\theta, I)$, or
# as $(V, I, R, C_1, C_2)$, etc.
#
# ---
#
# ------------------------------------------------------------------------------------------

# ------------------------------------------------------------------------------------------
# # Creating a simple discrete system
#
#
# For all intents and purposes, a `DynamicalSystem` is a Julia `struct` that contains all
# the information of a dynamical system:
# 1. Equations of motion function $\vec{f}$
# 3. Initial state.
# 4. Parameter container (if the equations of motion are parameterized).
# 2. *Optional* : Jacobian of the equations of motion.
#
#
# For simplicity let's focus on creating a simple discrete chaotic system, the [Hénon
# map](https://en.wikipedia.org/wiki/H%C3%A9non_map).
#
# The constructor we need is:
#
# ```julia
# DiscreteDynamicalSystem(eom, state, p)
# ```
#
# In order to construct a Hénon map, let's discuss the input arguments we need to pass to
# this constructor!
#
# #### 1. Equations of motion
# The first argument of the above constructor --`eom` -- is a **function** representing the
# equations of motion.
#
# Therefore, the first step in creating a `DynamicalSystem` is to define the equations of
# motion. For the Hénon map they have the form:
#
# $$
# \begin{aligned}
# x_{n+1} &= 1 - ax^2_n+y_n \\
# y_{n+1} & = bx_n
# \end{aligned}
# $$
#
# Now we must make a function out of them. There is some strictness when writing this
# function in Julia. Specifically, it can only be one of two forms: out-of-place (oop) or
# in-place (iip). Here is how to define it:
#
# * **oop** : The `eom` function **must** be in the form `eom(x, p, t) -> SVector`
#   which means that given a state `x::SVector` and some parameter container
#   `p` it returns a
# [`SVector`](http://juliaarrays.github.io/StaticArrays.jl/stable/pages/api.html#SVector-1)
#   (from the [StaticArrays](https://github.com/JuliaArrays/StaticArrays.jl) module)
#   containing the next state.
# * **iip** : The `eom` function **must** be in the form `eom!(xnew, x, p, t)`
#   which means that given a state `x::Vector` and some parameter container `p`,
#   it writes in-place the new state in `xnew`.
#
# We advise to use **oop** for systems with dimension < 11, and use **iip** otherwise.
#
# *If you are familiar with [DifferentialEquations.jl](http://docs.juliadiffeq.org/latest/),
# then notice that the equations of motion are defined in an identical manner*
#
# Because the Hénon map is only 2-dimensional, we follow the advice of the documentation and
# take advantage of the [`StaticArrays`](https://github.com/JuliaArrays/StaticArrays.jl)
# module.
# ------------------------------------------------------------------------------------------

using DynamicalSystems
h_eom(x, p, t) = SVector{2}(1.0 - p[1]*x[1]^2 + x[2], p[2]*x[1])

# ------------------------------------------------------------------------------------------
# * Remember: *both* `p` (for parameters) and `t` (for time) must be included in the
# equations of motion function, irrespective of whether they are used or not!
#
# #### 2. State
#
# The second argument for the `DiscreteDynamicalSystem` constructor is a `state`, which
# represents the initial condition for the system:
# ------------------------------------------------------------------------------------------

state = zeros(2) # doesn't matter if we use Vector of SVector for the `state`

# ------------------------------------------------------------------------------------------
# #### 3. Parameters for our EOM
#
# Then, the last argument, `p`, is simply a parameter container.
# ------------------------------------------------------------------------------------------

p = [1.4, 0.3] # p = [a, b] from the equations of motion

# ------------------------------------------------------------------------------------------
# These 3 things are enough to make a `DynamicalSystem`:
# ------------------------------------------------------------------------------------------

henon = DiscreteDynamicalSystem(h_eom, state, p)

# ------------------------------------------------------------------------------------------
# ## Getting a trajectory
#
# Now that we have created the system, the first (and most basic) thing to do is to simply
# plot its time evolution and see what it looks like.
#
# The `trajectory` function is a convenient tool that evolves the system and returns the
# output at equally spaced time intervals. The call signature is simply:
#
# ```julia
# trajectory(ds::DynamicalSystem, T [, u]; kwargs...)
# ```
# which evolves a system for total time `T`, optionally starting from a different state `u`.
# ------------------------------------------------------------------------------------------

# trajectory from initial condition
tr = trajectory(henon, 100000)

# trajectory from a different starting point
tr2 = trajectory(henon, 100000, 0.01rand(2))

using PyPlot
figure(figsize=(6,4))
plot(tr[:, 1], tr[:, 2], lw = 0.0, marker = "o", ms = 0.1, alpha = 0.5);
plot(tr2[:, 1], tr2[:, 2], lw = 0.0, marker = "o", ms = 0.1, alpha = 0.5);
xlabel("x"); ylabel("y");

# ------------------------------------------------------------------------------------------
# ## Crash-course on `Dataset`
#
# `trajectory` returns an object that is a `Dataset`:
# ------------------------------------------------------------------------------------------

tr = trajectory(henon, 100000)
println(typeof(tr))
println(summary(tr))

# ------------------------------------------------------------------------------------------
# `Dataset` instances handle most data in **DynamicalSystems.jl**. `Dataset` is a wrapper of
# a `Vector` of `SVector`s (statically sized vectors).
# ------------------------------------------------------------------------------------------

typeof(tr.data) # this is where the `Dataset` has the data

# ------------------------------------------------------------------------------------------
# When accessed with one index, a `Dataset` behaves as a vector of vectors
# ------------------------------------------------------------------------------------------

tr[1] # variables at first time point

tr[1:5]

# ------------------------------------------------------------------------------------------
# When accessed with two indices, a `Dataset` behaves like a matrix with each column being
# the timeseries of each dynamic variable
# ------------------------------------------------------------------------------------------

tr[:, 1] # timeseries of first variable

tr[1:56, 2] # time points 1:56 of second variable

tr[12, 1] # value of first variable at 12th timepoint

tr[1:10, 1:2] # using two ranges returns a `Dataset`

# ------------------------------------------------------------------------------------------
# # Adding a Jacobian
#
# Let's see `henon` again:
# ------------------------------------------------------------------------------------------

henon

# ------------------------------------------------------------------------------------------
# * The last line, "`jacobian:  ForwardDiff`"
# says that the Jacobian function of the equations of motion was computed automatically
# using the module
# [`ForwardDiff`](http://www.juliadiff.org/ForwardDiff.jl/stable/user/api.html).
# * The Jacobian function is a crucial component of a dynamical system, and that is why if
# it is not given, it is computed automatically.
#
#
#
# Even though the automatic computation is very efficient, the best possible performance
# will come if you pass a "hard-coded" jacobian:
# ------------------------------------------------------------------------------------------

h_jacobian(x, p, t) = @SMatrix [-2*p[1]*x[1] 1.0; p[2] 0.0]

# ------------------------------------------------------------------------------------------
# * Notice that for **out-of-place** systems, the Jacobian must also have the same form as
# the equations of motion, namely to return an `SMatrix`.
#
# Now, we can pass this Jacobian function to the `DiscreteDynamicalSystem` constructor as a
# 4th argument:
# ------------------------------------------------------------------------------------------

henon_with_jac = DiscreteDynamicalSystem(h_eom, state, p, h_jacobian)

# ------------------------------------------------------------------------------------------
# To see the difference in performance, let's call this Jacobian function
# ------------------------------------------------------------------------------------------

using BenchmarkTools
a = rand(SVector{2})
@btime $(henon_with_jac.jacobian)($a, $henon.prob.p, 0);
@btime $(henon.jacobian)($a, $henon.prob.p, 0);

# ------------------------------------------------------------------------------------------
# You can see that even though `ForwardDiff` is truly performant, the hard-coded version is
# much faster.
# ------------------------------------------------------------------------------------------

# ------------------------------------------------------------------------------------------
# # Continuous System
# * The process of creating a continuous system is identical to that of a discrete system,
# except that the constructor `ContinuousDynamicalSystem` is used instead.
# * In this case the function `eom` returns the time derivatives and not a "next state".
#
#
# ---
#
#
#
# * We will take the opportunity to show the process of using in-place equations of motion
# for a continuous system, which is aimed to be used for large systems (dimensionality $\ge$
# 10).
#
# * In addition, the system we will use (Henon-Heiles) does not have any parameters.
# $$
# \begin{aligned}
# \dot{x} &= p_x \\
# \dot{y} &= p_y \\
# \dot{p}_x &= -x -2 xy \\
# \dot{p}_y &= -y - (x^2 - y^2)
# \end{aligned}
# $$
# ------------------------------------------------------------------------------------------

# Henon-heiles famous system
# in-place form of equations of motion
# du is the derivatives vector, u is the state vector
function hheom!(du, u, p, t)
    du[1] = u[3]
    du[2] = u[4]
    du[3] = -u[1] - 2u[1]*u[2]
    du[4] = -u[2] - (u[1]^2 - u[2]^2)
    return nothing
end

# pass `nothing` as the parameters, because the system doesn't have any
hh = ContinuousDynamicalSystem(hheom!, [0, -0.25, 0.42081, 0], nothing)

# ------------------------------------------------------------------------------------------
# Great, now we can get a trajectory of this system, by solving using
# DifferentialEquations.jl
# ------------------------------------------------------------------------------------------

tr = trajectory(hh, 100.0, dt = 0.05)

figure(figsize = (6,4))
plot(tr[:, 1], tr[:, 2]);
xlabel("\$q_1\$"); ylabel("\$q_2\$");

# ------------------------------------------------------------------------------------------
# # Handy Dandy definition table
#
# Depending on whether your system is small or large, you want to use out-of-place or in-
# place equations of motion. The Jacobian function (if you wish to provide it) must also be
# of the same form.
#
# Here is a handy table that summarizes what the definitions should look like:
#
# |          System Type         |    equations of motion    |            Jacobian
# |
# |:----------------------------:|:-------------------------:|:-----------------------------
# -:|
# | in-place (big systems)       | `eom!(du, u, p, t)`       | `jacobian!(J, u, p, t)`
# |
# | out-of-place (small systems) | `eom(u, p, t) -> SVector` | `jacobian(u, p, t) ->
# SMatrix` |
#
# ------------------------------------------------------------------------------------------

# ------------------------------------------------------------------------------------------
# # Time-evolution of systems & DifferentialEquations.jl
# Discrete systems are evolved using internal algorithms. However, all time evolution of
# continuous systems is done through the
# [DifferentialEquations.jl](http://docs.juliadiffeq.org/latest/) library. In fact,
# `trajectory` for continuous systems simply wraps `solve` with some extra arguments.
#
# Keep in mind that by default all continuous systems are evolved using the solver `Vern9()`
# (9th order Verner solver) with tolerances `abstol = reltol = 1e-9`. This is especially
# important when one wants to compare benchmarks with different packages.
#
# It is almost certain that if you use **DynamicalSystems.jl** you want to use also
# DifferentialEquations.jl, due to the huge list of available features.
#
#
# ## When to use **DynamicalSystems.jl**?
# At this point in the tutorial you might be thinking:
# > How is DynamicalSystems.jl different from DifferentialEquations.jl? Seems the same to me
# so far...
#
# That's because we haven't seen any special features of **DynamicalSystems.jl** yet!
# `trajectory` is nothing more than a "convenient shortcut".
#
# The [contents page](https://juliadynamics.github.io/DynamicalSystems.jl/latest/#contents)
# of the documentation does a good job describing what is possible through
# **DynamicalSystems.jl**.
#
# In addition, for usage with DifferentialEquations.jl, we can create specialized
# integrators that evolve the system and the tangent space, or many states of the system in
# parallel (at *exactly* same times):
# * `tangent_integrator`
# * `parallel_integrator`
#
# These functions work for both continuous and discrete systems and also work regardless of
# whether the system is in-place (large), out-of-place (small) or auto-differentiated.
# Special attention has also been given to the performance of the integrators.
#
# Also, keep in mind that regardless of whether you use a `DynamicalSystem` to produce a
# timeseries or not, more than half of the **DynamicalSystems.jl** library is using
# numerical data as an input. Most of the time this numerical data is expected in the form
# of an `AbstractDataset` instance, which is what `trajectory` returns.
#
# ## Using DifferentialEquations.jl from a `DynamicalSystem`
#
# It is *very* likely that other features of DifferentialEquations.jl will be useful to
# someone using **DynamicalSystems.jl**. However, you can still use DifferentialEquations.jl
# *after* you have defined a continuous dynamical system, because the field `prob` gives an
# `ODEProblem`:
# ------------------------------------------------------------------------------------------

hh.prob

# ------------------------------------------------------------------------------------------
# * **please be careful when using this problem directly, because as you can see the
# `tspan[end]` field is `Inf`!!!**
#
#
# * One final comment: using Callbacks is *not* possible with **DynamicalSystems.jl**,
# because the the equations of motion function has to be assumed differentiable
# "everywhere".
# ------------------------------------------------------------------------------------------

# ------------------------------------------------------------------------------------------
# # Docstrings
# ------------------------------------------------------------------------------------------

?DynamicalSystem

?trajectory

?Dataset

.nbexports/introductory-tutorials/broader-topics-and-ecosystem/introduction-to-dynamicalsystems.jl/1. Creating a dynamical system.jl


x =3965421
y = 569705405152896
#operadores
+x
-x
x + y
x - y
x * y
x / y
x ÷ y
x \ y
x ^ y
x % y
2 % 3
#not
!true
3*2/12
-x+2

#Bitwise
~x #bitwise not
bitstring(x)
bitstring(y)

bitstring(a)
bitstring(b)
bitstring(c)
a = x & y #bitwise and
b = x | y #bitwise or
c = x ⊻ y #bitwise xor
x >>> 5
bitstring(x >>> 5)  #logical shift right
(x >> 5) < x
bitstring(x >> 5)#arithmetic shift right
(x << 5) > x

bitstring(x << 5) #log/arithm shift left

123 ⊻ 234
xor(123, 234)
~UInt32(123)

#updating operator
x += 1
# += -= /= \= ÷= %= ^= &= |= ⊻= >>>= >>= <<=

#"dot" operators
#elemento por elemento
[1 2 3] ^ 3
[1 2 3] .^3
[1,2,3] .* [2,3,4]
⊗(A,B) = kron(A,B) #produto de Kronecker
[1 2 ; 3 4] ⊗ [0 5 ; 6 7]
[1 2 ; 3 4] .⊗ [0 5 ; 6 7]
#Comparações
# ==  !=,≠  <  <=,≤  >  >=,≥
1≤2
Inf>NaN
NaN == NaN
NaN < NaN
[1 NaN] == [1 NaN]
isequal(NaN, NaN)
isfinite(x)
isinf(x)
isnan(x)
-0.0 == 0.0
isequal(-0.0, 0.0)

v(x) = (println(x); x)
v(1) < v(2) ≤ v(3)

0 .< [0 .5 1.5 .8] .< 1
sin.([1 2 3])

#ordem de precedencia na pag 51(71)
Base.operator_precedence(:+), Base.operator_precedence(:*), Base.operator_precedence(:.)
Base.operator_precedence(:(=))
Base.operator_precedence(:sin)

Base.operator_associativity(:-), Base.operator_associativity(:+)
Base.operator_associativity(:^)
Base.operator_associativity(:sin)

#Note that symbols such as :sin return precedence 0.
#This value represents invalid operators and not operators
#of lowest precedence.
#Similarly, such operators are assigned associativity :none.

#Conversions
Int8(127.0)
Int8(128)
Int8(3.14)
127 % Int8
128 % Int8
round(Int8, 127.4)
round(Int8, 127.6)
 #ver Conversion and Promotion
typeof(round(2.5))
x=7.568

#Divisions
floor(x)
ceil(x)
trunc(x)
y=2.0
x/y
div(x,y) #truncated division
fld(x,y)
cld(x,y)
rem(x,y)
x == div(x,y)y + rem(x,y)
mod(x,y)
x == fld(x,y)y + mod(x,y)
mod1(x,y)
mod2pi(x)
0 <= mod2pi(x) < 2π
divrem(x,y)
fldmod(x,y)
x=49;y=56
gcd(x,y) #MDC
lcm(x,y) #MMC
x=-49
#Sinal abs
abs(x)
abs2(x)
sign(x)
signbit(x)
copysign(y,x)
flipsign(x,y)
#x com sinal de x*y

sin(x)
cos(x)
tan(x)
cot(x)
sec(x)
csc(x)

sqrt(abs(x))
√abs(x)
cbrt(abs(x))
∛abs(x)
hypot(3,4)
exp(1)
expm1(1) #e^x - 1
ldexp(1.0,8)
log(2)
log(ℯ,2)
log2(2)
log10(10)
log1p(0.0052)
#log(1+x) for x near zero
exponent(128.0)
significand(128.0)

#sinh cosh tanh coth sech csch
#asin acos atan acot asec acsc
#asinh acosh atanh acoth asech acsch
#sinc cosc
#These are all single-argument functions, with atan also accepting two
#arguments corresponding to a traditional atan2 function.

#Additionally, sinpi(x) and cospi(x) are provided for more accurate
#computations of sin(pi*x) and cos(pi*x) respectively.

#In order to compute trigonometric functions with degrees instead of radians,
#suffix the function with d. For example, sind(x) computes the sine of x
#where x is specified in degrees.

#Mais funções matemàticas especiais em SpecialFunctions.jl

#endcap


# Copyright 2017, Iain Dunning, Joey Huchette, Miles Lubin, and contributors    #src
# This Source Code Form is subject to the terms of the Mozilla Public License   #src
# v.2.0. If a copy of the MPL was not distributed with this file, You can       #src
# obtain one at https://mozilla.org/MPL/2.0/.                                   #src

# # The knapsack problem

# Formulate and solve a simple knapsack problem:
#
#     max sum(p_j x_j)
#      st sum(w_j x_j) <= C
#         x binary

using JuMP
import GLPK
import Test

function example_knapsack(; verbose = true)
    profit = [5, 3, 2, 7, 4]
    weight = [2, 8, 4, 2, 5]
    capacity = 10
    model = Model(GLPK.Optimizer)
    @variable(model, x[1:5], Bin)
    ## Objective: maximize profit
    @objective(model, Max, profit' * x)
    ## Constraint: can carry all
    @constraint(model, weight' * x <= capacity)
    ## Solve problem using MIP solver
    optimize!(model)
    if verbose
        println("Objective is: ", objective_value(model))
        println("Solution is:")
        for i in 1:5
            print("x[$i] = ", value(x[i]))
            println(", p[$i]/w[$i] = ", profit[i] / weight[i])
        end
    end
    Test.@test termination_status(model) == MOI.OPTIMAL
    Test.@test primal_status(model) == MOI.FEASIBLE_POINT
    Test.@test objective_value(model) == 16.0
    return
end

example_knapsack()


using Diagonalizations, LinearAlgebra, PosDefManifold, Test

# Method (1) real
n, t=10, 100
X=genDataMatrix(n, t)
Y=genDataMatrix(n, t)
Cx=Symmetric((X*X')/t)
Cy=Symmetric((Y*Y')/t)
Cxy=(X*Y')/t
mC=mca(Cxy, simple=true)
@test Cxy≈mC.F[1]*mC.D*mC.F[2]'
D=mC.F[1]'Cxy*mC.F[2]
@test norm(D-Diagonal(D))+1. ≈ 1.

# Method (1) complex
Xc=genDataMatrix(ComplexF64, n, t)
Yc=genDataMatrix(ComplexF64, n, t)
Cxc=Symmetric((Xc*Xc')/t)
Cyc=Symmetric((Yc*Yc')/t)
Cxyc=(Xc*Yc')/t
mCc=mca(Cxyc, simple=true)
@test Cxyc≈mCc.F[1]*mCc.D*mCc.F[2]'
Dc=mCc.F[1]'Cxyc*mCc.F[2]
@test norm(Dc-Diagonal(Dc))+1. ≈ 1.

# Method (2) real
mXY=mca(X, Y, simple=true)
D=mXY.F[1]'*Cxy*mXY.F[2]
@test norm(D-Diagonal(D))+1≈1.
@test mXY==mC

# Method (2) complex
mXYc=mca(Xc, Yc, simple=true)
Dc=mXYc.F[1]'*Cxyc*mXYc.F[2]
@test norm(Dc-Diagonal(Dc))+1. ≈ 1.
@test mXYc==mCc

# Method (3) real
# maximum covariance analysis of the average covariance and cross-covariance
k=10
Xset=[genDataMatrix(n, t) for i=1:k]
Yset=[genDataMatrix(n, t) for i=1:k]

m=mca(Xset, Yset)

# ... selecting subspace dimension allowing an explained variance = 0.5
m=mca(Xset, Yset; eVar=0.5)

# ... subtracting the mean from the matrices in Xset and Yset
m=mca(Xset, Yset; meanX=nothing, meanY=nothing, eVar=0.5)

# mca on the average of the covariance and cross-covariance matrices
# computed along dims 1
m=mca(Xset, Yset; dims=1, eVar=0.5)

# name of the filter
m.name

using Plots
# plot regularized accumulated eigenvalues
plot(m.arev)

# plot the original cross-covariance matrix and the rotated
# cross-covariance matrix
 Cmax=maximum(abs.(Cxy));
 h1 = heatmap(Cxy, clim=(-Cmax, Cmax), yflip=true, c=:bluesreds, title="Cxy");
 D=mC.F[1]'*Cxy*mC.F[2];
 Dmax=maximum(abs.(D));
 h2 = heatmap(D, clim=(0, Dmax), yflip=true, c=:amp, title="F[1]'*Cxy*F[2]");
 📈=plot(h1, h2, size=(700,300))
# savefig(📈, homedir()*"\\Documents\\Code\\julia\\Diagonalizations\\docs\\src\\assets\\FigMCA.png")

# Method (3) complex
# maximum covariance analysis of the average covariance and cross-covariance

k=10
Xcset=[genDataMatrix(ComplexF64, n, t) for i=1:k]
Ycset=[genDataMatrix(ComplexF64, n, t) for i=1:k]

mc=mca(Xcset, Ycset)

# ... selecting subspace dimension allowing an explained variance = 0.5
mc=mca(Xcset, Ycset; eVar=0.5)

# ... subtracting the mean from the matrices in Xset and Yset
mc=mca(Xcset, Ycset; meanX=nothing, meanY=nothing, eVar=0.5)

# mca on the average of the covariance and cross-covariance matrices
# computed along dims 1
mc=mca(Xcset, Ycset; dims=1, eVar=0.5)

# name of the filter
mc.name


"""
    bernoulli(n, x)

 Calculates Bernoulli polynomials ``B_n(x)``
 e.g., see

 + [https://en.wikipedia.org/wiki/Bernoulli_polynomials](https://en.wikipedia.org/wiki/Bernoulli_polynomials)
 + [http://dlmf.nist.gov/24](http://dlmf.nist.gov/24)

## Arguments
* ``n`` `::Integer`: the index into the series, ``n=0,1,2,3,...``
* ``x`` `::Real`: the point at which to calculate the polynomial

## Examples
```jldoctest; setup = :(using Polylogarithms)
julia> bernoulli(6, 1.2)
0.008833523809524735
```
"""
function bernoulli(n::Integer, x::Real)
    if n<0
        throw(DomainError(n))
    end
    if n == 0
        return 1 # zeta formula doesn't hold for n=0, so return explicit value
    elseif n == 1 # get some easy cases out of the way quickly
        return x-0.5
    elseif n == 2
        return x^2 - x + 1.0/6.0
    elseif n == 3
        return x^3 - 1.5*x^2 + 0.5*x
    elseif n == 4
        return x^4 - 2.0*x^3 +     x^2 - 1/30.0
    elseif n == 5
         return x^5 - 2.5*x^4 +(5.0/3.0)*x^3 - x/6.0
    end

 # from the Hurwitz-zeta function using

 # ``ζ(-n,x) = -B_{n+1}(x)/(n+1), for Re(x)>0,``

 # which is faster than direct calculation of the polynomial except for n<=4, but for
 # small n (n<=5) we use the exact polynomials. For negative x, we use the recursive
 # formula to push it into a postive range.
     # return -n*SpecialFunctions.zeta(1-n, x)
    # see https://carma.newcastle.edu.au/resources/jon/Preprints/Papers/Published-InPress/Oscillatory%20(Tapas%20II)/Papers/coffey-zeta.pdf, p.341
    # however, I am not really happy with the results I am getting out of this
    # so exploit symmetries in B_n(x) to compute recursively for x<=0
    # if x >= 0
    #      return -n*SpecialFunctions.zeta(1-n, x)
    # else
    #     return bernoulli(n, x+1) - n*x^(n-1)
    # end

    # total = 0.0
    # for k = 0:n-1
    #     total -= binomial(n+1, k) *  bernoulli(k,x)
    # end
    # total = total / (n+1)
    # total += x^n
    # return total

    # alternative, a little better, but relies on bernoulli numbers, which I only have to 35
    # and still has some non-negligable errors, so I still would like a better piece in here
    n = Int64(n)
    if n <= 35
        total = 0.0
        for k = 0:n
            k = Int64(k)
            total += binomial(n, k) *  bernoulli(n-k) * x^k
            # this will overflow for large n and k, but that migth be avoidable if we were more clever?
        end
        return total
    else
        # this is faster but less accurate, and runs into some weird problems, e.g.,
        # SpecialFunctions.zeta(-6, -1.0) = NaN (probably should be 2)
        return -n*SpecialFunctions.zeta(1-n, x)
    end
end


using NumericalMethodsforEngineers
using Test

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

  function f3(x::Float64, y::Vector{Float64})
    [y[2], -2y[2]/y[1]]
  end

  (iters, nsteps, converged, res) = shootingmethod(f3, [0.0, 1.0, 1.0, 2.0], [1.0, 3.0],
    10, 0.000001, 80)

  if !converged
    println("No conversion achieved.")
  elseif iters > 50
    println("Iter limit reached.")
  else
    @test round.(res[:, 2], digits=4)' ==
      [1.0  1.1742  1.321  1.4471  1.5569  1.6534  1.7389  1.8151  1.8833  1.9446  2.0]
    #println()
    #res |> display
  end

end


module paqueteFourier

import Base: +, -, *, /

import Gadfly

# Notar que los métodos de +, -, *, / y diff se exportan solos
export trigPoly, innerProduct, nodes, vals, tpfplot

# El objeto básico de este paquete es un trigPoly, cuyo campo coefs contiene
# los coeficientes de la representación de un polinomio trigonométrico como
# combinación lineal de 1, exp(-im θ), exp(im θ), exp(-2im θ), exp(2im θ), …,
# en ese orden.
# Junto a la definición de este 'type' Julia automáticamente deja disponible un
# constructor, que es una función que recibe un vector de números complejos y
# devuelve un trigPoly con ese vector en el campo coefs.
# A continuación justificaré mi elección de ordenamiento para los coeficientes
# con la ayuda de una tabla.  Denoto al orden elegido (0, -1, 1, -2, 2, …) por
# A, al orden visto en clase (-N/2, -N/2+1, …, -1, 0, 1, …, N/2-2, N/2) por B y
# al orden por defecto de la FFT (0, 1, …, N/2-1, -N/2, -N/2+1, …, -1) por C (B
# y C son ligeramente distintos si N es impar, pero la tabla sigue valiendo).
#                                 ┌───┬───┬───┐
#                                 │ A │ B │ C │
# ┌───────────────────────────────┼───┼───┼───┤
# │                          Suma │ ✓ │ ✗ │ ✗ │
# │Interpretación de coeficientes │ ✓ │ ✗ │ ~ │
# │     Vector de números de onda │ ~ │ ✓ │ ✗ │
# │           Interpolación (FFT) │ ✗ │ ~ │ ✓ │
# ├───────────────────────────────┴───┴───┴───┤
# │   ✓ = Fácil, ~ = Moderado, ✗ = Difícil    │
# └───────────────────────────────────────────┘
"""
    trigPoly(x)

Polinomio trigonométrico que es combinación lineal de 1, exp(-im θ), exp(im θ), exp(-2im θ), … con los coeficientes indicados en `x`.
"""
type trigPoly
	coefs::Vector{Complex128}
end

"""
    innerProduct(f,g)

Producto interior L² entre los trigPoly `f` y `g`.
"""
function innerProduct(f::trigPoly, g::trigPoly)
	ac = 0.0 + 0.0im
	for k = 1:min(length(f.coefs),length(g.coefs))
		ac += f.coefs[k] * conj(g.coefs[k])
	end
	2π*ac
end

# Sin cargar ningún paquete Julia ya tiene una función llamada norm; la nuestra
# será un método más
"""
    norm(f::trigPoly)

Norma L² de `f`.
"""
Base.norm(f::trigPoly) = sqrt(real(innerProduct(f,f)))

"""
    nodes(n)

Arreglo de `n` nodos de Fourier.
"""
function nodes(n::Integer)
	2π*(0:n-1)/n
end

# Función que transforma un vector del orden C al orden A; Julia reemplazará T
# con el tipo de número que contenga el vector con el cual se alimente a esta
# función
function reordering{T}(v::Vector{T})
	n = length(v)
	out = Vector{T}(n) # Reserva de memoria
	floorhalfn = n÷2
	for k = 1:floorhalfn
		out[2*k] = v[n+1-k]
	end
	for k = 1:n-floorhalfn
		out[2*k-1] = v[k]
	end
	out
end

# Función que transforma un vector del orden A al orden C
function reverse_reordering{T}(v::Vector{T})
	n = length(v)
	floorhalfn = n÷2
	out = Vector{T}(n)
	for k = 1:floorhalfn
		out[n+1-k] = v[2*k]
	end
	for k = 1:n-floorhalfn
		out[k] = v[2*k-1]
	end
	out
end

# Interpolador que asume que los valores recibidos corresponden a la evaluación
# de una función en los nodos de Fourier
function interp{T}(v::Vector{T})
	modes = fft(v)/length(v)
	trigPoly(reordering(modes))
end

"""
    trigPoly(f, n)

Polinomio trigonométrico que interpola a la función `f` en `n` nodos de Fourier.
"""
function trigPoly(f::Function, n::Integer)
	vals = map(f, nodes(n)) # Vector con evaluaciones de f
	interp(vals)
end

"""
    vals(f)

Evaluación del polinomio trigonométrico `f` en tantos nodos de Fourier como coeficientes almacena `f`.
"""
function vals(f::trigPoly)
	ifft(reverse_reordering(f.coefs*length(f.coefs)))
end

"""
    vals(f, x)

Evaluación del polinomio trigonométrico `f` en el arreglo o escalar real `x`.
"""
function vals(f::trigPoly, x)
	coefsplus = f.coefs[3:2:end]
	coefsminus = f.coefs[2:2:end]
	# Podría escribir exp(im*x), pero si x es escalar Julia emite una
	# advertencia (no un error) que invita a usar la versión vectorizada de
	# exp; usando map x puede ser escalar o vectorial indistintamente
	expplusix = map(exp, im*x)
	expminusix = conj(expplusix)
	# Arreglo con ceros o escalar cero según lo que sea x
	outplus = zero(expplusix)
	outminus = zero(expplusix)
	for val in reverse(coefsplus)
		outplus = expplusix.*(outplus + val)
	end
	for val in reverse(coefsminus)
		outminus = expminusix.*(outminus + val)
	end
	outplus + outminus + f.coefs[1]
end

# Evaluador por defecto de un trigPoly; permite evaluar un trigPoly f en x con
# la sintaxis clásica f(x)
(f::trigPoly)(x) = vals(f, x)

# Suma
function +(f::trigPoly, g::trigPoly)
	fl = length(f.coefs)
	gl = length(g.coefs)
	retl = max(fl, gl)
	retcoefs = zeros(Complex128, retl)
	retcoefs[1:fl] = f.coefs
	retcoefs[1:gl] += g.coefs
	trigPoly(retcoefs)
end

# Cambio de signo
-(f::trigPoly) = trigPoly(-f.coefs)

# Resta
-(f::trigPoly, g::trigPoly) = f + (-g)

# Suma, resta, multiplicación y división por escalar
function +(f::trigPoly, a::Number)
	retcoefs = f.coefs
	retcoefs[1] += a
	trigPoly(retcoefs)
end
-(f::trigPoly, a::Number) = f + (-a)
*(f::trigPoly, a::Number) = trigPoly(f.coefs*a)
/(f::trigPoly, a::Number) = trigPoly(f.coefs/a)
*(a::Number, f::trigPoly) = f*a
+(a::Number, f::trigPoly) = f+a
-(a::Number, f::trigPoly) = (-f) + a

# Multiplicación
function *(f::trigPoly, g::trigPoly)
	ndof = length(f.coefs) + length(g.coefs)
	paddedf = trigPoly([f.coefs; zeros(Complex128,ndof-length(f.coefs))])
	paddedg = trigPoly([g.coefs; zeros(Complex128,ndof-length(g.coefs))])
	productvals = vals(paddedf) .* vals(paddedg)
	interp(productvals)
end

"""
    diff(f::trigPoly)

Derivada de `f`.
"""
function Base.diff(f::trigPoly)
	retcoefs = Vector{Complex128}(length(f.coefs))
	for k = 1:length(f.coefs)
		#             ↓     modo     ↓
		retcoefs[k] = (-1)^(k+1)*(k÷2) * im * f.coefs[k]
	end
	trigPoly(retcoefs)
end

"""
    diff(f::trigPoly, k::Int64)

`k`-ésima derivada de `f`.
"""
function Base.diff(f::trigPoly, k::Int64)
	@assert k>=0
	if k == 0
		return f
	elseif k == 1
		return diff(f)
	else
		return diff(f, k-1)
	end
end

"""
    tpfplot(f)
    tpfplot(f1, f2, ...)
    tpfplot(f1, f2, ..., labels=[label1, label2, ...])

Grafica a un polinomio trigonométrico (trigPoly) o función (Function) `f` o a la colección de polinomios trigonométricos o funciones `f1`, `f2`, ...; el argumento opcional `labels`, de aparecer, debe especificar nombres para las curvas ingresadas como un arreglo de cadenas de tipo `String`.

La figura obtenida se puede guardar mediante la sintaxis

    fh = tpfplot(...)
    Gadfly.draw(Gadfly.SVG("myplot.svg", 15Gadfly.cm, 9Gadfly.cm), fh)
"""
function tpfplot(vf::Vararg{Union{Function,trigPoly}}; labels=[])
	defaultNamesFlag = isempty(labels)
	layersReal = []
	layersImag = []
	layerLabels = []
	layerColors = []
	for i = 1:length(vf)
		c = ["blue" "green" "red" "deepskyblue" "magenta" "gold" "lightcoral" "black"][(i-1)%8+1]
		if defaultNamesFlag
			push!(labels, "Curve $(i)")
		end
		if typeof(vf[i]) <: trigPoly
			nsamples = max(150, 6*length(vf[i].coefs))
			x = nodes(nsamples)
			vfix = vals(trigPoly([vf[i].coefs;zeros(Complex128,nsamples-length(vf[i].coefs))]))
			# Agrego el punto de abscisa 2π
			x = [x;2π]
			vfix = [vfix;vfix[1]]
			# Ahora los puntos con abscisas en la colección de
			# nodos de Fourier que corresponde a vf[i]
			xn = nodes(length(vf[i].coefs))
			vfin = vals(vf[i])
			push!(layersReal, Gadfly.layer(x=x, y=real(vfix), Gadfly.Geom.line, Gadfly.Theme(default_color=c)))
			push!(layersReal, Gadfly.layer(x=xn, y=real(vfin), Gadfly.Geom.point, Gadfly.Theme(default_color=c)))
			push!(layersImag, Gadfly.layer(x=x, y=imag(vfix), Gadfly.Geom.line, Gadfly.Theme(default_color=c)))
			push!(layersImag, Gadfly.layer(x=xn, y=imag(vfin), Gadfly.Geom.point, Gadfly.Theme(default_color=c)))
			push!(layerColors, c); push!(layerLabels, labels[i])
		else
			x = linspace(0,2π,2000)
			vfix = map(vf[i], x)
			push!(layersReal, Gadfly.layer(x=x, y=real(vfix), Gadfly.Geom.line, Gadfly.Theme(default_color=c)))
			push!(layersImag, Gadfly.layer(x=x, y=imag(vfix), Gadfly.Geom.line, Gadfly.Theme(default_color=c)))
			push!(layerColors, c); push!(layerLabels, labels[i])
		end
	end
	labelSpecification = Gadfly.Guide.manual_color_key("", layerLabels, layerColors)
	Gadfly.hstack(
	       Gadfly.plot(layersReal..., Gadfly.Guide.XLabel("θ"), Gadfly.Guide.YLabel(""), Gadfly.Guide.Title("Real part"), Gadfly.Coord.cartesian(xmin=0, xmax=2π), Gadfly.Theme(key_position=:bottom), labelSpecification),
	       Gadfly.plot(layersImag..., Gadfly.Guide.XLabel("θ"), Gadfly.Guide.YLabel(""), Gadfly.Guide.Title("Imaginary part"), Gadfly.Coord.cartesian(xmin=0, xmax=2π), Gadfly.Theme(key_position=:bottom), labelSpecification)
	       )
end

end # module


using Roots

f(x) = x^3 - x - 1
@show fzeros(f)
println()

@show fzero(f, [-2,2])
println()

@show fzero(f, 1.0)
println()

f(x) = exp(x) - x^4
fp(x) = exp(x) - 4x^3
fpp(x) = exp(x) - 12x^2
newton(f, fp, 8)        # 8.613169456441398
newton(f, 8)
halley(f, fp, fpp, 8)
halley(f, 8)
secant_method(f, 8, 8.5)


# Test for GaussLobatto()
n = 2
x,w = gausslobatto(n)
@test x[1] ≈ -1.
@test x[2] ≈ 1.
@test w[1] ≈ 1.
@test w[2] ≈ 1.

n = 3
x,w = gausslobatto(n)
@test x[1] ≈ -1.
@test abs(x[2])<1e-15
@test x[3] ≈ 1.
@test w[1] ≈ 1/3
@test w[2] ≈ 4/3
@test w[3] ≈ 1/3

tol = 1e-14
n = 42
x,w = gausslobatto(n)
@test ( length(x) == n) && ( length(w) == n )
@test abs(x[37] - 0.922259214258616) < tol
@test abs(w[37] - 0.029306411216166) < tol
@test ( x[1] == -1 && x[n] == 1 )

@test dot( w,(x.^2)) ≈ 2/3
@test dot( w,exp.(x)) ≈ exp(1)-exp(-1)


using NURBS
using Base.Test

@testset "Rbsurfaces Building" begin

	vec = [-100.0 -100.0 -100.0 -100.0 -100.0 -50.0 -50.0 -50.0 -50.0 -50.0 0.0 0.0 0.0 0.0 0.0 50.0 50.0 50.0 50.0 50.0 100.0 100.0 100.0 100.0 100.0;-100.0 -50.0 0.0 50.0 100.0 -100.0 -50.0 0.0 50.0 100.0 -100.0 -50.0 0.0 50.0 100.0 -100.0 -50.0 0.0 50.0 100.0 -100.0 -50.0 0.0 50.0 100.0;0.0 0.0 0.0 0.0 0.0 0.0 25.0 50.0 25.0 0.0 0.0 25.0 50.0 25.0 0.0 0.0 25.0 150.0 25.0 0.0 0.0 0.0 0.0 0.0 0.0; 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0]

	res = [-100.0 -100.0 -100.0 -100.0 -25.9259 -25.9259 -25.9259 -25.9259 25.9259 25.9259 25.9259 25.9259 100.0 100.0 100.0 100.0; -100.0 -25.9259 25.9259 100.0 -100.0 -25.9259 25.9259 100.0 -100.0 -25.9259 25.9259 100.0 -100.0 -25.9259 25.9259 100.0; 0.0 0.0 0.0 0.0 0.0 34.8422 34.8422 0.0 0.0 51.3032 51.3032 0.0 0.0 0.0 0.0 0.0]

    @testset "rational B-spline surface using an open uniform knot vector" begin
        @test typeof(rbspsurf(vec,4,4,5,5,100,100)) == Tuple{Array{Float64, 2}, Array{Array{Int64, 1}, 1}, Array{Array{Int64, 1}, 1}}
        @test isapprox(rbspsurf(vec,4,4,5,5,4,4)[1], res; atol=1e-3)
    end
end


module TestFunctions

"""
One dimensional Runge function defined over \$[-1, 1]\$
 (Taken from chapter 13 of "Approximation Theory and Approximation
 Practice" by Prof. Trefethen)
"""
1d_runge(x) = 1./(1 + 25x.*x)

"""
One dimensional Keane test function
    (Taken from "Engineering Design with Surrogate Modelling" by Prof. Keane
    et al.)
"""
1d_keane(x) = (6x - 2).^2.*sin.(12x-4)

"""
One dimensional Liu test function
 (A survey of adaptive sampling for global metamodeling (2017))
"""
1d_liu(x) = x.*x.*exp.(-4x).*(sin.(7x) + cos.(14x))

"""
Two dimensional Branin function from Schonolau's thesis.
"""
2d_branin(x) = (x[2] .- 5.1x[1].*x[1]./(4π*π) .+ 5x[1]/π .- 6).^2 + 10(1 - 1/(8π)).*cos.(x[1]) + 10

export 1d_runge, 1d_keane, 1d_liu,
       2d_branin

end

# vim:filetype=julia:foldmethod=syntax


function euler_step(f, t::Number, x, len)
    return len' .* f(t, x)
end

function mod_euler_step(f, t::Number, x, len)
    whole_step = len' .* f(t, x)
    return (whole_step + len' .* f(t .+ len, x .+ whole_step))/2
end

function heum_step(f, t::Number, x, len)
    whole_step = len' .* f(t, x)
    return (whole_step + 3*len' .* f(t .+ 2/3*len, x .+ 2/3*whole_step))/4
end

function RK4_step(f, t::Number, x, len)
    k1 = len' .* f(t, x)
    k2 = len' .* f(t .+ len/2, x .+ k1/2)
    k3 = len' .* f(t .+ len/2, x .+ k2/2)
    k4 = len' .* f(t .+ len, x .+ k3)
    return (k1 + 2*k2 + 2*k3 + k4)/6
end

macro define_order(f, ord::Integer)
    @eval function $f(s::Symbol)
        if s == :order
            return $ord
        end
        throw(ErrorException("Invalid symbol. Use :order"))
    end
end

@define_order(euler_step, 1)
@define_order(mod_euler_step, 2)
@define_order(heum_step, 2)
@define_order(RK4_step, 4)

function fixed_step_ode_solver(
    f,
    t0::Number,
    x0;
    method=RK4_step,
    step::Number = 1e-3,
    iter::Integer = 10000
)
    t = t0
    x = Array{typeof(x0 + step*f(t0, x0))}(undef, iter+1)
    x[1] = x0
    for j = 1:iter
        x[j+1] = x[j] + method(f, t, x[j], step)
        t += step
    end
    return x
end

function fixed_step_ode_solver(
    f,
    t0::Number,
    x0,
    stop_cond;
    method=RK4_step,
    step::Number = 1e-3,
    maxit::Integer = 10000
)
    t = t0
    x = Array{typeof(x0 + method(f, t, x0, step)), 1}()
    push!(x, x0)
    for j = 1:maxit
        push!(x, last(x) + method(f, t, last(x), step))
        t += step
        if stop_cond(t, x)
            break
        end
    end
    return x
end


module CoDa

export alpha, alphaTransform, Helmert
export ilr, clr

"""
    Helmert(n)

Calculate ``n×n`` Helmert matrix
"""
function Helmert(n)
    H = zeros(n,n)
  for i = 1:n, j = 1:n
      if i==1
        H[i,j] = 1.0/sqrt(n)
      elseif j < i
        H[i,j] = 1.0/sqrt(i*(i-1))
      elseif i==j
        H[i,j] = (1.0-i)/sqrt(i*(i-1))
      end
  end
    return -H
end

"""
    alphaTransform(X::Matrix, α::Float64)

Apply alpha-transform with Helmert product to ``X``
"""
function alphaTransform(X::Matrix{Float64}, α::Float64)
    D = size(X,2)
    H = Helmert(D)[2:end,:]
    Xᵃ = X.^α
    Xs = sum(Xᵃ, dims=2)
    U = (D*Xᵃ./Xs .- 1)./α
    return U*transpose(H)
end

"""
    alpha(X::Matrix, α::Float64)

Apply alpha-transform without Helmert product
"""
function alpha(X::Matrix{Float64}, α::Float64)
    D = size(X,2)
#    H = Helmert(D)[2:end,:]
    Xᵃ = X.^α
    Xs = sum(Xᵃ,dims=2)
    U = (D*Xᵃ./Xs .- 1)./α
    return U
end

"""
    ilr(X::Matrix)

Isometric logratio transform
"""
function ilr(X::Matrix{Float64})
    D = size(X,2)
    H = Helmert(D)[2:end,:]
    return log.(X) * transpose(H)
end

"""
    clr(X::Matrix)

Centered logratio transform
"""
function clr(X::Matrix{Float64})
    D = size(X,2)
    ratio = X ./ (prod(X,2).^(1/D))
    return log.(ratio)
end

end


using Test
using StaticArrays, IntervalSets
import ClimaCore.DataLayouts: IJFH
import ClimaCore:
    Fields, Domains, Meshes, Topologies, Spaces, Operators, Geometry
using StaticArrays, IntervalSets, LinearAlgebra

FT = Float64

radius = FT(1)
domain = Domains.SphereDomain(radius)

grad = Operators.Gradient()
wgrad = Operators.WeakGradient()

@testset "gradient" begin
    Ne = 5
    Nq = 6

    mesh = Meshes.EquiangularCubedSphere(domain, Ne)
    grid_topology = Topologies.Topology2D(mesh)

    quad = Spaces.Quadratures.GLL{Nq}()
    space = Spaces.SpectralElementSpace2D(grid_topology, quad)
    coords = Fields.coordinate_field(space)

    η = @. sind(coords.long) * cosd(coords.lat)
    ∇η = grad.(η)
    ∇η_w = wgrad.(η)
    ∇η_local = Geometry.transform.(Ref(Geometry.UVAxis()), ∇η)
    ∇ηw_local = Geometry.transform.(Ref(Geometry.UVAxis()), ∇η_w)
    Spaces.weighted_dss!(∇η_local)
    Spaces.weighted_dss!(∇ηw_local)

    ∇η_exact = @. Geometry.UVVector(
        cosd(coords.long) / radius,
        -sind(coords.long) * sind(coords.lat) / radius,
    )

    @test ∇η_local ≈ ∇η_exact rtol = 1e-2
    @test ∇ηw_local ≈ ∇η_exact rtol = 1e-2

end

"""
    convergence_rate(err, Δh)
Estimate convergence rate given vectors `err` and `Δh`
    err = C Δh^p+ H.O.T
    err_k ≈ C Δh_k^p
    err_k/err_m ≈ Δh_k^p/Δh_m^p
    log(err_k/err_m) ≈ log((Δh_k/Δh_m)^p)
    log(err_k/err_m) ≈ p*log(Δh_k/Δh_m)
    log(err_k/err_m)/log(Δh_k/Δh_m) ≈ p
"""
convergence_rate(err, Δh) =
    [log(err[i] / err[i - 1]) / log(Δh[i] / Δh[i - 1]) for i in 2:length(Δh)]

@testset "convergence tests for the gradient operator on the sphere" begin
    Nes = [3, 9, 27]
    Nqs = [4, 6]

    for (Iq, Nq) in enumerate(Nqs)
        err_sgrad = Array{FT}(undef, length(Nes))
        err_wgrad = Array{FT}(undef, length(Nes))
        Δh = Array{FT}(undef, length(Nes))

        for (Ie, Ne) in enumerate(Nes)
            mesh = Meshes.EquiangularCubedSphere(domain, Ne)
            grid_topology = Topologies.Topology2D(mesh)

            quad = Spaces.Quadratures.GLL{Nq}()
            space = Spaces.SpectralElementSpace2D(grid_topology, quad)
            coords = Fields.coordinate_field(space)

            η = @. sind(coords.long) * cosd(coords.lat)
            ∇η = grad.(η)
            ∇η_w = wgrad.(η)
            ∇η_local = Geometry.transform.(Ref(Geometry.UVAxis()), ∇η)
            ∇ηw_local = Geometry.transform.(Ref(Geometry.UVAxis()), ∇η_w)
            Spaces.weighted_dss!(∇η_local)
            Spaces.weighted_dss!(∇ηw_local)

            ∇η_exact = @. Geometry.UVVector(
                cosd(coords.long) / radius,
                -sind(coords.long) * sind(coords.lat) / radius,
            )

            err_sgrad[Ie] = norm(∇η_local .- ∇η_exact)
            err_wgrad[Ie] = norm(∇ηw_local .- ∇η_exact)
            Δh[Ie] = 1 / Ne

        end

        convergence_rate_sgrad = convergence_rate(err_sgrad, Δh)
        convergence_rate_wgrad = convergence_rate(err_wgrad, Δh)

        for Ie in range(1, length = length(Nes) - 1)
            @test convergence_rate_sgrad[Ie] ≈ (Nq - 1) atol = 0.1
            @test convergence_rate_wgrad[Ie] ≈ (Nq - 1) atol = 0.1
        end

    end

end