### A Pluto.jl notebook ###
# v0.16.1

using Markdown
using InteractiveUtils

# ╔═╡ 2db004b0-7c45-11eb-2c3a-3f0e111cf63a
using LinearAlgebra, PlutoUI

# ╔═╡ 55f8789f-5361-421f-9f37-98c154fd40cf
begin
	using Dates
	Dates.today()
end

# ╔═╡ 5ea33223-4dad-4869-8724-93b7cb91771f
TableOfContents(title="📚 Table of Contents", aside=true)

# ╔═╡ 8ccdfcf9-d5d1-4b69-a854-15f2715bfafa
md"""
# Julia is Open

`Julia` is an open-source project, source being entirely hosted on [github](https://github.com/JuliaLang/julia).

The code consists of (these are old numbers, actual numbers may differ):

- 29K lines of `C/C++`
- 6K lines of `scheme`
- 68K lines of `julia`

Julia uses [LLVM](http://llvm.org) which itself has 680K lines of code. Therefore, `Julia` is very compact, compared to other languages, like LLVM's `C` compiler `clang` (513K lines of code) or `gcc`  (3,530K lines). This makes it easy to read the actuall code and get full information, in spite the fact that some parts of the documentation are insufficient. `Julia`'s "navigating" system,
consisting of commands `varinfo()`, `methods()` and `@which`, makes this even easier.

Further, the `Base` (core) of `Julia` is kept small, and the rest of the functionality is obtained through packages.
Since packages are written in `Julia`, they are navigated on the same way.

In this notebook, we demonstrate how to get help and navigate the source code.

__Prerequisites__

Basic knowledge of programming in any language.

Read [Methods](https://docs.julialang.org/en/v1/manual/methods/#Methods) section of the `Julia` manual. (5 min)

__Competences__

The reader should be able to read the code and be able to find and understand calling sequences and outputs of any function.

__Credits__

Some examples are taken from [The Julia Manual](https://docs.julialang.org/en/v1/).

"""

# ╔═╡ 4440847c-db90-44a5-be48-08c5794ee5b7
md"""
# Operators `+`, `*` and `⋅`

Consider operators `+`, `*` and `⋅`, the first two of them seem rather basic in any language. The `⋅` symbol is typed as LaTeX command `\cdot` + `Tab`.

`?+` gives some information, which is vary sparse. We would expect more details, and we also suspect that `+` can be used in more ways that just hose two.

`?*` explaind more instances where `*` can be used, but the text itself is vague and not sufficient.

`?⋅` appears to be what we expect fro the dot product off two vectors.
"""

# ╔═╡ 7a55f84e-1439-45ec-b428-5976cac084cd
#?+

# ╔═╡ 7514d88c-d0fa-45e5-947d-10fe0ce47f7d
md"""
# methods()

`Julia` functions have a feature called _multiple dispatch_, which means that the method depends on the name __AND__ the input.
Full range of existing methods for certain function name is given by the `methods()` command.
> Running `methods(+)` sheds a completely differfent light on `+`.

The great Julia feature is that the links to the source code where the respective version of the function is defined, are readily provided.
"""

# ╔═╡ 611770e1-4b5b-47a6-be3a-e0806990796d
md"""
## The `"+"` operator
"""

# ╔═╡ 9c208420-7cdf-11eb-2b99-e32e66b46e85
# methods(+)

# ╔═╡ bfb87460-7cdf-11eb-38ca-0961560c093b
# methodswith(Tridiagonal)

# ╔═╡ cb3e314e-68e0-4fc0-8ece-c5c8bc279f55
md"""
Following the third link, we get the following code snippet:
```
+(x::Bool) = int(x)
-(x::Bool) = -int(x)
+(x::Bool, y::Bool) = int(x) + int(y)
-(x::Bool, y::Bool) = int(x) - int(y)
*(x::Bool, y::Bool) = x & y
```

Therefore:
"""

# ╔═╡ 0f3c49cd-00c7-4654-9dc9-674a89e19a30
+(true), +(false),-(true),-(false)

# ╔═╡ 595878a2-30ab-4a19-a5ad-e621dd37f6c7
xb, yb = BitArray([0,1,0,1,0,1]), BitArray([0,1,1,1,1,0])

# ╔═╡ 2a255a09-3c3f-4fcb-99f2-b31adfb536b5
md"""
The above command is equivalent to
```
xb = bitpack([0,1,0,1,0,1]); yb = bitpack([0,1,1,1,1,0])
```
except that only the last result would be displayed
"""

# ╔═╡ 4f6d3190-45c9-433d-b7f9-c42ed01dc04b
+xb,-(xb)

# ╔═╡ 60a3f371-d9b2-47a8-be8f-fab7cfb3fb48
xb+yb, +(xb,yb)

# ╔═╡ 74d98ae5-0641-4eea-9f8c-8e663a405f32
md"""
## Manipulating dates

We see that one of the `+` methods is adding days to time:

```
 +(x::Date,y::Base.Dates.Day) at dates/arithmetic.jl:77
```
Therefore, the 135-th day from today is:
"""

# ╔═╡ 48783355-31c8-4159-8dd5-8bea047c742c
dd=Dates.today()+Dates.Day(135)

# ╔═╡ c9a0df42-1368-4e5d-a21d-d924f7ffbb75
typeof(dd)

# ╔═╡ 79ca20e7-446e-4264-ae24-c46fbb4b15c1
md"""
More information about the two types can be obtained by `methods(Dates.Date)` and `methods(Dates.Day)`, respectively.
"""

# ╔═╡ 7e6013ea-1e66-4a81-86a8-a69e22cde29a
md"""
## Adding tridiagonal matrices

In the above output of `methods(+)`,  we see that we can add tridiagonal matrices:
```
+(A::Tridiagonal{T}, B::Tridiagonal{T}) at linalg/tridiag.jl:624
```
Following the link, we see that the method separately adds lower, main and upper diagonals, denoted by `dl`, `d` and `du`, respectively:
```
624: +(A::Tridiagonal, B::Tridiagonal) = Tridiagonal(A.dl+B.dl, A.d+B.d, A.du+B.du)
```
Let us see how exactly is the type `Tridiagonal` defined:
"""

# ╔═╡ 8591bb73-12e5-474e-b24b-7acfab2e3c5f
# methods(Tridiagonal)

# ╔═╡ 8c0b2316-26fa-4748-b047-3d2c6bf7acc0
md"""
This output seems confusing, but from the second line we conclude that we can define three diagonals, lower, main and upper diagonal, denoted as above. We also know that that the lower and upper diagonals are of size $n-1$. Let us try it out:
"""

# ╔═╡ 4163dd6e-7b5c-42f4-a9f4-c1e1a7faecba
T₁ = Tridiagonal(rand(6),rand(7),rand(6))

# ╔═╡ 89bf9619-7ee7-4ccd-99c0-48583e93bbe1
Matrix(T₁)

# ╔═╡ 684b3f4a-f83b-440d-9b13-d5dae159e77a
T₂ = Tridiagonal(rand(-5.0:5,6),randn(7),rand(-9.0:0,6))

# ╔═╡ 243854d4-495a-43de-88cb-1442275c8a8a
T₃=T₁+T₂

# ╔═╡ d79e7c32-ad00-4373-ae0e-04eeb8f6e333
md"""
This worked as expected, the result is again a `Tridiagonal`. We can access each diagonal by:
"""

# ╔═╡ 22af8627-6713-40a5-b716-81a55a6b7aad
# Output is in the console
println(T₃.dl, T₃.d, T₃.du)

# ╔═╡ 98142e4f-c703-4ea9-b893-5e2c831afa42
md"""
## `@which`

Let us take a closer look at what happens. The `@which` command gives the link to the part of the code which is actually invoked. The argument should be only function, without assignment.
"""

# ╔═╡ dac4c688-cd5f-4ef5-8ba6-3709f945d3f0
@which T₁ + T₂

# ╔═╡ 60469aa5-8d5b-4996-a02d-100a65e0915f
@which Tridiagonal(rand(6),rand(7),rand(6))

# ╔═╡ c4bbf3a7-9497-474a-8174-db9530853486
md"""
In the code, we see that there is a type definition in the `struct` block:
```
## Tridiagonal matrices ##
struct Tridiagonal{T} <: AbstractMatrix{T}
    dl::Vector{T}    # sub-diagonal
    d::Vector{T}     # diagonal
    du::Vector{T}    # sup-diagonal
    du2::Vector{T}   # supsup-diagonal for pivoting
end
```
The `Tridiagonal` structure (or type) consists of __four__ vectors.
In our case, we actually called the function `Tridiagonal()` with __three__ vector arguments. The function creates the type of the same name, setting the fourth reqired vector `du2` to `zeros(T,n-2)`.

The next function with the same name is invoked when the input vectors have different types, in which case the types arer promoted to a most general one, if possible.
"""

# ╔═╡ 88ee9b5a-a1f1-49a1-b6cf-64ebac21634a
md"""
# `size()` and `Matrix()`

For each matrix type we need to define the function which returns the size of a matrix, and the function which converts the matrix of a given type to a full array. These function are listed after the second `Tridiagonal()` function.
"""

# ╔═╡ 5e2c532c-49f3-411c-b110-70c54263f007
T₄ = Tridiagonal([1,2,3],[2,3,4,5],[-1,1,2])

# ╔═╡ 7f026e3e-bf1c-49e5-b815-a9370eb5e3f5
size(T₄)

# ╔═╡ 20f20bfa-9aff-46a0-b358-5fe42640d21a
Matrix(T₄)

# ╔═╡ bd0a3a98-4311-41b0-9209-5e09c4ecd9ad
md"""
## `sizeof()`

Of course, using special types can leasd to much more efficient programs. For example, for `Tridiagonal` type, onlt four diagonals are stored, in comparison to storing full matrix when $n^2$ elements are stored. The storage used is obtained by the `sizeof()` function.
"""

# ╔═╡ e0a4eeaf-c259-453e-82aa-6813538ab536
T₁

# ╔═╡ 41daa15d-9b1f-47df-ad24-b124161ce25a
T₁f=Matrix(T₁)

# ╔═╡ cbb653fc-6ec1-408b-b683-7d6821724051
sizeof(T₁f)  #   392 =  7 * 7 * 8 bytes

# ╔═╡ e6a068a5-cb01-40f8-8f0c-19e84f8adbc0
# This is not yet implemented for Tridiagonal - only the storage
# required for 4 vector variables' names is displayed
sizeof(T₁)

# ╔═╡ 7622b55d-49ba-4f21-8d85-4bfb95f0fe37
md"""
# `struct` is immutable

This means that we can change individual elements of defined parts, but not the parts as a whole (an alternative is to use the `type` construtor). For example:
"""

# ╔═╡ 3e9cf5d9-f97f-468d-ba27-b6dc217418d8
begin
	@show T₄
	T₄.d[2]=123
	@show T₄
	# T₄.dl = [-1, -1 ,1]
end

# ╔═╡ ec7be6ff-07ff-4b6d-bf00-2c48a1535582
md"""
# `methodswith()`

This is the reverse of `methods()` - which methods exist for the given type. For example, what can we do with `Tridiagonal` matrices, or with `Dates.Day`:
"""

# ╔═╡ e54408ea-e984-42b8-926e-74806ffb5aec
# methodswith(Tridiagonal)

# ╔═╡ 017d94a3-c9cb-481f-b3ec-0dc687cb8a46
# methodswith(Dates.Day)

# ╔═╡ 9397e3da-f0b7-43da-9fd5-341a679e46c5
md"""
## The `"*"` operator
"""

# ╔═╡ 32050704-3017-4995-a054-5180f6e6e8d0
# methods(*)

# ╔═╡ 313b771e-95b0-4a6f-add9-e017ae6712a7
md"""
We can multiply various types of numbers and matrices. Notice, however, that there is no multiplication specifically defined for `Tridiagonal` matrices. This would not make much sense, since the product of two tridiagonal matrices is a pentadiagonal matrix, the product of three tridiagonal matrices is septadiagonal matrix, ...

Therefore, two tridiagonal matrices are first converted to full matrices, and then multiplied, as is seen in the source code.
"""

# ╔═╡ e1471fce-4bf3-43d7-8cfc-19e5d4f5b8ec
T₁*T₂

# ╔═╡ 3c8ae987-8cb5-43b4-bda8-bf528e3675af
@which T₁*T₂

# ╔═╡ a04fc04a-c446-4528-abfd-91be7edf0944
T₁*T₂*T₁

# ╔═╡ 8b29ea27-05ef-4b5e-8123-80667c385b70
md"""
## The ⋅ operator
"""

# ╔═╡ 391b705d-0b2d-42b9-86a6-94416d2135cf
# methods(⋅)

# ╔═╡ 0b1557b6-2142-49db-8180-d7d17e1128cf
md"""
By inspecting the source, we see that the `scalar` or the `dot` product of two vectors (1-dimensional arrays) is
computed via `BLAS` function `dot` for real arguments, and the function `dotc` for complex arguments.
"""

# ╔═╡ 76edd9e2-e2c7-4048-b6a4-59f6ec93684a
begin
	x = rand(1:5,5)
	y  = rand(-5:0,5)
	z = rand(5)
	w = rand(5) + im*rand(5)
	@show x, y, z, w
	x⋅y, x⋅z, z⋅x, x⋅w, z⋅w, w⋅z
end

# ╔═╡ b48fd083-d317-4195-8500-c8fa5c5ac4ed
md"""
# `varinfo()`

The command `varinfo()` reveals the content of the specified package or module. It can be invoked either with the package name, or with the package name and a regular expression.
"""

# ╔═╡ 9f93e309-458e-45c8-9a20-dd2179b7b744
# varinfo(Dates)

# ╔═╡ 16ed9927-e764-434a-bbfb-f51fcd339a1f
# varinfo(LinearAlgebra)

# ╔═╡ 7e16a343-d2b6-44c0-9864-46703bb757e5
# Now with a regular expression - we are looking for
# 'eigenvalue' related stuff.
varinfo(LinearAlgebra, Regex("eig"))

# ╔═╡ c084e244-419a-46c1-8bb8-92f58324f7b1
md"""
Funally, let us list all we have in `Julia`'s `Base` module. __It is a long list!__
Notice that `Dates` and `LinearAlgebra` are modules themselves.
"""

# ╔═╡ 198883c4-3f6c-4725-a638-c3be9f7c0c12
# varinfo(Base)

# ╔═╡ a7de4d00-7c4a-11eb-1c76-cd159c6fdf9c
md"
# `code_llvm()` and `code_native()`

It is easy to see the LLVM or assebler code which is executed. The code is displayed in the console.
"

# ╔═╡ d399376e-7c4a-11eb-02e4-936801012749
f(a,b)=2a+b^2

# ╔═╡ e1aa988e-7c4a-11eb-2de6-7b7d5c2fc54c
@code_llvm f(1,2)

# ╔═╡ ef51c630-7c4a-11eb-3331-49602357198f
@code_native f(1,2)

# ╔═╡ 0521934e-7c4b-11eb-37f3-631715a07f3a
@code_native f(1.0,2)

# ╔═╡ 0ba1da00-7c4b-11eb-0abc-c395efc9700e
@code_native f(1,2+2.0im)

# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8"

[compat]
PlutoUI = "~0.7.10"
"""

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

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

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

[[HypertextLiteral]]
git-tree-sha1 = "72053798e1be56026b81d4e2682dbe58922e5ec9"
uuid = "ac1192a8-f4b3-4bfe-ba22-af5b92cd3ab2"
version = "0.9.0"

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

[[JSON]]
deps = ["Dates", "Mmap", "Parsers", "Unicode"]
git-tree-sha1 = "8076680b162ada2a031f707ac7b4953e30667a37"
uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
version = "0.21.2"

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

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

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

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

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

[[Parsers]]
deps = ["Dates"]
git-tree-sha1 = "438d35d2d95ae2c5e8780b330592b6de8494e779"
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
version = "2.0.3"

[[PlutoUI]]
deps = ["Base64", "Dates", "HypertextLiteral", "InteractiveUtils", "JSON", "Logging", "Markdown", "Random", "Reexport", "Suppressor"]
git-tree-sha1 = "26b4d16873562469a0a1e6ae41d90dec9e51286d"
uuid = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
version = "0.7.10"

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

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

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

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

[[Suppressor]]
git-tree-sha1 = "a819d77f31f83e5792a76081eee1ea6342ab8787"
uuid = "fd094767-a336-5f1f-9728-57cf17d0bbfb"
version = "0.2.0"

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

# ╔═╡ Cell order:
# ╠═2db004b0-7c45-11eb-2c3a-3f0e111cf63a
# ╠═5ea33223-4dad-4869-8724-93b7cb91771f
# ╟─8ccdfcf9-d5d1-4b69-a854-15f2715bfafa
# ╟─4440847c-db90-44a5-be48-08c5794ee5b7
# ╠═7a55f84e-1439-45ec-b428-5976cac084cd
# ╟─7514d88c-d0fa-45e5-947d-10fe0ce47f7d
# ╠═611770e1-4b5b-47a6-be3a-e0806990796d
# ╠═9c208420-7cdf-11eb-2b99-e32e66b46e85
# ╠═bfb87460-7cdf-11eb-38ca-0961560c093b
# ╟─cb3e314e-68e0-4fc0-8ece-c5c8bc279f55
# ╠═0f3c49cd-00c7-4654-9dc9-674a89e19a30
# ╠═595878a2-30ab-4a19-a5ad-e621dd37f6c7
# ╟─2a255a09-3c3f-4fcb-99f2-b31adfb536b5
# ╠═4f6d3190-45c9-433d-b7f9-c42ed01dc04b
# ╠═60a3f371-d9b2-47a8-be8f-fab7cfb3fb48
# ╟─74d98ae5-0641-4eea-9f8c-8e663a405f32
# ╠═55f8789f-5361-421f-9f37-98c154fd40cf
# ╠═48783355-31c8-4159-8dd5-8bea047c742c
# ╠═c9a0df42-1368-4e5d-a21d-d924f7ffbb75
# ╟─79ca20e7-446e-4264-ae24-c46fbb4b15c1
# ╟─7e6013ea-1e66-4a81-86a8-a69e22cde29a
# ╠═8591bb73-12e5-474e-b24b-7acfab2e3c5f
# ╟─8c0b2316-26fa-4748-b047-3d2c6bf7acc0
# ╠═4163dd6e-7b5c-42f4-a9f4-c1e1a7faecba
# ╠═89bf9619-7ee7-4ccd-99c0-48583e93bbe1
# ╠═684b3f4a-f83b-440d-9b13-d5dae159e77a
# ╠═243854d4-495a-43de-88cb-1442275c8a8a
# ╟─d79e7c32-ad00-4373-ae0e-04eeb8f6e333
# ╠═22af8627-6713-40a5-b716-81a55a6b7aad
# ╟─98142e4f-c703-4ea9-b893-5e2c831afa42
# ╠═dac4c688-cd5f-4ef5-8ba6-3709f945d3f0
# ╠═60469aa5-8d5b-4996-a02d-100a65e0915f
# ╟─c4bbf3a7-9497-474a-8174-db9530853486
# ╟─88ee9b5a-a1f1-49a1-b6cf-64ebac21634a
# ╠═5e2c532c-49f3-411c-b110-70c54263f007
# ╠═7f026e3e-bf1c-49e5-b815-a9370eb5e3f5
# ╠═20f20bfa-9aff-46a0-b358-5fe42640d21a
# ╟─bd0a3a98-4311-41b0-9209-5e09c4ecd9ad
# ╠═e0a4eeaf-c259-453e-82aa-6813538ab536
# ╠═41daa15d-9b1f-47df-ad24-b124161ce25a
# ╠═cbb653fc-6ec1-408b-b683-7d6821724051
# ╠═e6a068a5-cb01-40f8-8f0c-19e84f8adbc0
# ╟─7622b55d-49ba-4f21-8d85-4bfb95f0fe37
# ╠═3e9cf5d9-f97f-468d-ba27-b6dc217418d8
# ╟─ec7be6ff-07ff-4b6d-bf00-2c48a1535582
# ╠═e54408ea-e984-42b8-926e-74806ffb5aec
# ╠═017d94a3-c9cb-481f-b3ec-0dc687cb8a46
# ╟─9397e3da-f0b7-43da-9fd5-341a679e46c5
# ╠═32050704-3017-4995-a054-5180f6e6e8d0
# ╟─313b771e-95b0-4a6f-add9-e017ae6712a7
# ╠═e1471fce-4bf3-43d7-8cfc-19e5d4f5b8ec
# ╠═3c8ae987-8cb5-43b4-bda8-bf528e3675af
# ╠═a04fc04a-c446-4528-abfd-91be7edf0944
# ╟─8b29ea27-05ef-4b5e-8123-80667c385b70
# ╠═391b705d-0b2d-42b9-86a6-94416d2135cf
# ╟─0b1557b6-2142-49db-8180-d7d17e1128cf
# ╠═76edd9e2-e2c7-4048-b6a4-59f6ec93684a
# ╟─b48fd083-d317-4195-8500-c8fa5c5ac4ed
# ╠═9f93e309-458e-45c8-9a20-dd2179b7b744
# ╠═16ed9927-e764-434a-bbfb-f51fcd339a1f
# ╠═7e16a343-d2b6-44c0-9864-46703bb757e5
# ╟─c084e244-419a-46c1-8bb8-92f58324f7b1
# ╠═198883c4-3f6c-4725-a638-c3be9f7c0c12
# ╟─a7de4d00-7c4a-11eb-1c76-cd159c6fdf9c
# ╠═d399376e-7c4a-11eb-02e4-936801012749
# ╠═e1aa988e-7c4a-11eb-2de6-7b7d5c2fc54c
# ╠═ef51c630-7c4a-11eb-3331-49602357198f
# ╠═0521934e-7c4b-11eb-37f3-631715a07f3a
# ╠═0ba1da00-7c4b-11eb-0abc-c395efc9700e
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002

src/Module A - Short Julia Course/L02a Julia is Open.jl


module TestMPBWrapper

using Ipopt
using LinearAlgebra
using MathProgBase
using SparseArrays
using Test

function test_mpb()
    solver = IpoptSolver()
    sol = linprog([-1, 0], [2 1], '<', 1.5, solver)
    @test sol.status == :Optimal
    @test abs(sol.objval - -0.75) <= 1e-6
    @test norm(sol.sol - [0.75, 0.0]) <= 1e-6

    sol = linprog([-1, 0], sparse([2 1]), '<', 1.5, solver)
    @test sol.status == :Optimal
    @test abs(sol.objval - -0.75) <= 1e-6
    @test norm(sol.sol - [0.75, 0.0]) <= 1e-6

    # test infeasible problem:
    # min x
    # s.t. 2x+y <= -1
    # x,y >= 0
    sol = linprog([1, 0], [2 1], '<', -1, solver)
    #    @test sol.status == :Infeasible

    # test unbounded problem:
    # min -x-y
    # s.t. -x+2y <= 0
    # x,y >= 0
    return sol = linprog([-1, -1], [-1 2], '<', [0], solver)
    #    @test sol.status == :Unbounded
end

function mathprogbase_file(file::String)
    return joinpath(dirname(dirname(pathof(MathProgBase))), "test", file)
end

include(mathprogbase_file("nlp.jl"))
include(mathprogbase_file("quadprog.jl"))

function test_mathprogbase_nlp()
    nlptest(IpoptSolver())
    nlptest_nohessian(IpoptSolver())
    convexnlptest(IpoptSolver())
    return rosenbrocktest(IpoptSolver())
end

function test_mathprogbase_quadprog()
    quadprogtest(IpoptSolver())
    return qpdualtest(IpoptSolver())
end

function test_restoration_options()
    # Test retoration only options
    #
    # Warm start with infeasible solution, force restoration on initial iteration.
    # But limit to 0 iterations. Forces :UserLimit exit.
    m = MathProgBase.NonlinearModel(
        IpoptSolver(start_with_resto = "yes", resto_max_iter = 0),
    )
    l = [1, 1, 1, 1]
    u = [5, 5, 5, 5]
    lb = [25, 40]
    ub = [Inf, 40]
    MathProgBase.loadproblem!(m, 4, 2, l, u, lb, ub, :Min, HS071())
    MathProgBase.setwarmstart!(m, [0, 15, 15, 11])
    MathProgBase.optimize!(m)
    @test MathProgBase.status(m) == :UserLimit
end

end  # TestMPBWrapper

runtests(TestMPBWrapper)


@testset "minimal_sum_test" begin

    @test sum([1.0, 2.0, 3.0]) === 6.0

    @test isnan(sum([1.0, 2.0, NaN, 3.0]))

    @test isnan(sum([1.0, -Inf, 2.0, Inf, 3.0]))

end

@testset "minimal_sum_abs_test" begin

    @test sum(abs.([1.0, -2.0, 3.0])) === 6.0

    @test isnan(sum(abs.([1.0, -2.0, NaN, 3.0])))

    @test sum(abs.([1.0, -Inf, 2.0, Inf, 3.0])) === Inf

end

@testset "minimal_sum_sqr_test" begin

    @test sum([1.0, 2.0, 3.0].^2) === 14.0

    @test isnan(sum([1.0, 2.0, NaN, 3.0].^2))

    @test sum([1.0, -Inf, 2.0, Inf, 3.0].^2) === Inf

end

@testset "minimal_dot_test" begin

    @test dot([1.0, 2.0, 3.0], [1.0, 2.0, 3.0]) === 14.0

    @test_broken dot([0x10000000000001p0, 0x1p104], [0x0fffffffffffffp0, -1.0]) === -1.0

    @test isnan(dot([1.0, 2.0, NaN, 3.0], [1.0, 2.0, 3.0, 4.0]))

    @test isnan(dot([1.0, 2.0, 3.0, 4.0], [1.0, 2.0, NaN, 3.0]))

    @test isnan(dot([1.0, 2.0, 0.0, 4.0], [1.0, 2.0, Inf, 3.0]))

    @test isnan(dot([1.0, 2.0, -Inf, 4.0], [1.0, 2.0, 0.0, 3.0]))

end


"""
    StatisticalModel

Abstract supertype for all statistical models.
"""
abstract type StatisticalModel end

"""
    coef(model::StatisticalModel)

Return the coefficients of the model.
"""
function coef end

"""
    coefnames(model::StatisticalModel)

Return the names of the coefficients.
"""
function coefnames end

"""
    coeftable(model::StatisticalModel; level::Real=0.95)

Return a table with coefficients and related statistics of the model.
`level` determines the level for confidence intervals (by default, 95%).

The returned `CoefTable` object implements the
[Tables.jl](https://github.com/JuliaData/Tables.jl/) interface, and can be
converted e.g. to a `DataFrame` via `using DataFrames; DataFrame(coeftable(model))`.
"""
function coeftable end

"""
    confint(model::StatisticalModel; level::Real=0.95)

Compute confidence intervals for coefficients, with confidence level `level` (by default 95%).
"""
function confint end

"""
    deviance(model::StatisticalModel)

Return the deviance of the model relative to a reference, which is usually when applicable
the saturated model. It is equal, *up to a constant*, to ``-2 \\log L``, with ``L``
the likelihood of the model.
"""
function deviance end

"""
    islinear(model::StatisticalModel)

Indicate whether the model is linear.
"""
function islinear end

"""
    nulldeviance(model::StatisticalModel)

Return the deviance of the null model, that is the one including only the intercept.
"""
function nulldeviance end

"""
    loglikelihood(model::StatisticalModel)
    loglikelihood(model::StatisticalModel, observation)

Return the log-likelihood of the model.

With an `observation` argument, return the contribution of `observation` to the
log-likelihood of `model`.

If `observation` is a `Colon`, return a vector of each observation's contribution
to the log-likelihood of the model. In other words, this is the vector of the
pointwise log-likelihood contributions.

In general, `sum(loglikehood(model, :)) == loglikelihood(model)`.
"""
function loglikelihood end

"""
    nullloglikelihood(model::StatisticalModel)

Return the log-likelihood of the null model corresponding to `model`.
This is usually the model containing only the intercept.
"""
function nullloglikelihood end

"""
    score(model::StatisticalModel)

Return the score of the model, that is the gradient of the
log-likelihood with respect to the coefficients.
"""
function score end

"""
    nobs(model::StatisticalModel)

Return the number of independent observations on which the model was fitted. Be careful
when using this information, as the definition of an independent observation may vary
depending on the model, on the format used to pass the data, on the sampling plan
(if specified), etc.
"""
function nobs end

"""
    dof(model::StatisticalModel)

Return the number of degrees of freedom consumed in the model, including
when applicable the intercept and the distribution's dispersion parameter.
"""
function dof end

"""
    mss(model::StatisticalModel)

Return the model sum of squares.
"""
function mss end

"""
    rss(model::StatisticalModel)

Return the residual sum of squares of the model.
"""
function rss end

"""
    informationmatrix(model::StatisticalModel; expected::Bool = true)

Return the information matrix of the model. By default the Fisher information matrix
is returned, while the observed information matrix can be requested with `expected = false`.
"""
function informationmatrix end

"""
    stderror(model::StatisticalModel)

Return the standard errors for the coefficients of the model.
"""
function stderror end

"""
    vcov(model::StatisticalModel)

Return the variance-covariance matrix for the coefficients of the model.
"""
function vcov end

"""
    weights(model::StatisticalModel)

Return the weights used in the model.
"""
function weights end

"""
    isfitted(model::StatisticalModel)

Indicate whether the model has been fitted.
"""
function isfitted end

"""
Fit a statistical model.
"""
function fit end

"""
Fit a statistical model in-place.
"""
function fit! end

"""
    aic(model::StatisticalModel)

Akaike's Information Criterion, defined as ``-2 \\log L + 2k``, with ``L`` the likelihood
of the model, and `k` its number of consumed degrees of freedom
(as returned by [`dof`](@ref)).
"""
aic(model::StatisticalModel) = -2loglikelihood(model) + 2dof(model)

"""
    aicc(model::StatisticalModel)

Corrected Akaike's Information Criterion for small sample sizes (Hurvich and Tsai 1989),
defined as ``-2 \\log L + 2k + 2k(k-1)/(n-k-1)``, with ``L`` the likelihood of the model,
``k`` its number of consumed degrees of freedom (as returned by [`dof`](@ref)),
and ``n`` the number of observations (as returned by [`nobs`](@ref)).
"""
function aicc(model::StatisticalModel)
    k = dof(model)
    n = nobs(model)
    -2loglikelihood(model) + 2k + 2k*(k+1)/(n-k-1)
end

"""
    bic(model::StatisticalModel)

Bayesian Information Criterion, defined as ``-2 \\log L + k \\log n``, with ``L``
the likelihood of the model,  ``k`` its number of consumed degrees of freedom
(as returned by [`dof`](@ref)), and ``n`` the number of observations
(as returned by [`nobs`](@ref)).
"""
bic(model::StatisticalModel) = -2loglikelihood(model) + dof(model)*log(nobs(model))

"""
    r2(model::StatisticalModel)
    r²(model::StatisticalModel)

Coefficient of determination (R-squared).

For a linear model, the R² is defined as ``ESS/TSS``, with ``ESS`` the explained sum of squares
and ``TSS`` the total sum of squares.
"""
function r2 end

const r² = r2

"""
    adjr2(model::StatisticalModel)
    adjr²(model::StatisticalModel)

Adjusted coefficient of determination (adjusted R-squared).

For linear models, the adjusted R² is defined as ``1 - (1 - (1-R^2)(n-1)/(n-p))``, with ``R^2``
the coefficient of determination, ``n`` the number of observations, and ``p`` the number of
coefficients (including the intercept). This definition is generally known as the Wherry Formula I.
"""
function adjr2 end

const adjr² = adjr2


function equindex2pass(data::Array)
    rst = Vector{Int}(0)
    suml, sumr, ddelayed = 0, sum(data), 0
    for (i, d) in enumerate(data)
        suml += ddelayed
        sumr -= d
        ddelayed = d
        if suml == sumr
            push!(rst, i)
        end
    end
    return rst
end

@show equindex2pass([1, -1, 1, -1, 1, -1, 1])
@show equindex2pass([1, 2, 2, 1])
@show equindex2pass([-7, 1, 5, 2, -4, 3, 0])


## gaussian_random_fields.jl : Gaussian random field types and generator types

abstract type GaussianRandomFieldGenerator end

struct GaussianRandomField{G<:GaussianRandomFieldGenerator,C<:AbstractCovarianceFunction,
                           P,M,D}
    mean::M
    cov::C
    pts::P
    data::D
end

"""
	GaussianRandomField(mean,cov,method,pts...)
	GaussianRandomField(cov,method,pts...)
	GaussianRandomField(mean,cov,method,nodes,elements)
	GaussianRandomField(cov,method,nodes,elements)

Compute a Gaussian random field with mean `mean` and covariance structure `cov` defined in the points `pts`, and computed using the method `method`.

# Examples
```jldoctest
julia> m = Matern(0.1,1.0)
Matérn (λ=0.1, ν=1.0, σ=1.0, p=2.0)

julia> c = CovarianceFunction(2,m)
2d Matérn covariance function (λ=0.1, ν=1.0, σ=1.0, p=2.0)

julia> pts1 = 0:0.02:1; pts2 = 0:0.02:1
0.0:0.02:1.0

julia> mn = ones(length(pts1),length(pts2))

julia> grf = GaussianRandomField(mn,c,Cholesky(),pts1,pts2)
Gaussian random field with 2d Matérn covariance function (λ=0.1, ν=1.0, σ=1.0, p=2.0) on a 51x51 structured grid, using a Cholesky decomposition

```
If no `mean` is specified, a zero-mean Gaussian random field is assumed.
```jldoctest
julia> grf = GaussianRandomField(c,Cholesky(),pts1,pts2)
Gaussian random field with 2d Matérn covariance function (λ=0.1, ν=1.0, σ=1.0, p=2.0) on a 51x51 structured grid, using a Cholesky decomposition

```
The  Gaussian random field sampler `method` can be `Cholesky()`, `Spectral()`, `KarhunenLoeve(n)` (where `n` is the number of terms in the expansion), or `CirculantEmbedding()`. The dimension of the points must match the dimension of the covariance function `cov`. The points can be specified as arguments of type `AbstractVector`, in which case a tensor (Kronecker) product is assumed, or as a Finite Element mesh with node table `nodes` and element table `elements`.
```jldoctest
julia> grf = GaussianRandomField(c,KarhunenLoeve(500),pts1,pts2)
Gaussian random field with 2d Matérn covariance function (λ=0.1, ν=1.0, σ=1.0, p=2.0) on a 51x51 structured grid, using a KL expansion with 500 terms

```
For separable Gaussian random fields with a KL expansion, provide a [`SeparableCovarianceFunction`](@ref). Note that the number of terms `n` in `KarhunenLoeve(n)` refers to the total number of terms.
```jldoctest
julia> e1 = Exponential(0.1); e2 = Exponential(0.01);

julia> scov = SeparableCovarianceFunction(e1,e2)
2d separable covariance function [ exponential (λ=0.1, σ=1.0, p=2.0), exponential (λ=0.01, σ=1.0, p=2.0) ]

julia> grf = GaussianRandomField(scov,KarhunenLoeve(500),pts1,pts2)
Gaussian random field with 2d separable covariance function [ exponential (λ=0.1, σ=1.0, p=2.0), exponential (λ=0.01, σ=1.0, p=2.0) ] on a 51x51 structured grid, using a KL expansion with 500 terms

julia> plot_eigenfunction(grf,3); show()
[...]

```
Also anisotropic Gaussian random fields can be computed. For example, the anisotropic exponential covariance function needs a positive definite matrix `A`. The size of the off-diagonal elements of `A` determine the degree of anisotropy.
```jldoctest
julia> a = AnisotropicExponential([1 0.8;0.8 1])
anisotropic exponential (A=[1.0 0.8; 0.8 1.0], σ=1.0)

julia> acov = CovarianceFunction(2,a)
2d anisotropic covariance function exponential (A=[1.0 0.8; 0.8 1.0], σ=1.0)

julia> pts = range(0,stop = 10,length = 128)
0.0:0.07874015748031496:10.0

julia> grf = GaussianRandomField(acov,CirculantEmbedding(),pts,pts)
WARNING: negative eigenvalue -1.2245106889248049e-18 detected, Gaussian random field will be approximated (ignoring all negative eigenvalues)
WARNING: increase padding if possible
Gaussian random field with 2d anisotropic covariance function exponential (A=[1.0 0.8; 0.8 1.0], σ=1.0) on a 128x128 structured grid, using a circulant embedding

julia> contourf(grf)
[...]

```
Samples from the random field can be computed using the `sample` function.
```jldoctest
julia> sample(grf)
[...]

```
See also: [`Cholesky`](@ref), [`Spectral`](@ref), [`KarhunenLoeve`](@ref), [`CirculantEmbedding`](@ref), [`sample`](@ref)
"""
function GaussianRandomField(mean::Array{<:Real}, cov::CovarianceFunction{d}, method::GaussianRandomFieldGenerator, pts::Vararg{AbstractVector,d}; kwargs...) where d
    size(mean) == length.(pts) || throw(DimensionMismatch("size of the mean does not correspond to the dimension of the points"))

    method isa CirculantEmbedding && !(pts isa NTuple{d,AbstractRange}) &&
        throw(ArgumentError("can only use circulant embedding on a regular grid, supply ranges for pts"))

    method isa Union{CirculantEmbedding,KarhunenLoeve} && any(pt -> length(pt) < 2, pts) &&
        throw(ArgumentError("must have at least 2 points in each direction to use circulant embedding or KL expansion"))

    _GaussianRandomField(mean, cov, method, pts...; kwargs...)
end

# zero-mean GRF
GaussianRandomField(cov::CovarianceFunction{d}, method::GaussianRandomFieldGenerator, pts::Vararg{AbstractVector,d}; kwargs...) where d = GaussianRandomField(zeros(eltype(cov), length.(pts)), cov, method, pts...; kwargs...)

# constant mean GRF
GaussianRandomField(mean::Real, cov::CovarianceFunction{d}, method::GaussianRandomFieldGenerator, pts::Vararg{AbstractVector,d}; kwargs...) where d = GaussianRandomField(fill(convert(eltype(cov), mean), length.(pts)), cov, method, pts...; kwargs...)

# obtain generator used to compute random field
generator(::GaussianRandomField{G}) where G = G()

"""
	sample(grf)
	sample(grf, xi=randn(randdim(grf)))

Take a sample from the Gaussian Random Field `grf` using the (optional) random numbers `xi`. The vector`xi` must have appropriate length. The default value is `randn(randdim(grf))`.

# Examples
```
julia> m = Matern(0.1,1.0)
Matérn (λ=0.1, ν=1.0, σ=1.0, p=2.0)

julia> c = CovarianceFunction(2,m)
2d Matérn covariance function (λ=0.1, ν=1.0, σ=1.0, p=2.0)

julia> pts1 = 0:0.02:1; pts2 = 0:0.02:1
0.0:0.02:1.0

julia> grf = GaussianRandomField(c,KarhunenLoeve(300),pts1,pts2)
Gaussian random field with 2d Matérn covariance function (λ=0.1, ν=1.0, σ=1.0, p=2.0) on a 51x51 structured grid, using a KL expansion with 300 terms

julia> sample(grf)
[...]

julia> sample(grf,xi=2*rand(randdim(grf))-1)
[...]

```
"""
function sample(grf::GaussianRandomField; xi::AbstractArray{<:Real}=randn(randdim(grf)))
    length(xi) == prod(randdim(grf)) || throw(DimensionMismatch("length of random points vector must be equal to $(randdim(grf))"))
    _sample(grf,xi)
end

function Base.show(io::IO, grf::GaussianRandomField)
    print(io, "Gaussian random field with ", grf.cov, " on a ")
    showpoints(io, grf.pts)
    print(io, ", using a ", generator(grf))
end

function showpoints(io::IO, points)
    d = length(points)
    if d == 1
        print(io, length(points[1]), "-point")
    else
        print(io, length(points[1]))
        @inbounds for i in 2:d
            print(io, "×", length(points[i]))
        end
    end
    print(io, " structured grid")
end


#=
  https://dtai.cs.kuleuven.be/problog/tutorial/basic/03_dice.html
  """
  The following example illustrates the use of a logic program with recursion and lists.
  We start by rolling the first die. Every roll determines the next die to roll, but we stop
  if we have used that die before. We query for the possible sequences of rolled dice.
  We use three-sided dice instead of the regular six-sided ones simply to restrict the number
  of possible outcomes (and thus inference time).
  """

  Cf ~/blog/rolling_dice5.blog
     ~/webppl/rolling_dice5.wppl
=#

using Turing, StatsPlots, DataFrames
include("jl_utils.jl")

@model function rolling_dice5(n=3)

    function roll(a)
        # t ~ DiscreteUniform(1,n)
        t = rand(DiscreteUniform(1,n))
        if t in a
            # We have used this die before -> end this sequence
            # Note: In the WebPPL model the duplicate value is included
            # return vcat(a,t)
            return a
        else
            return roll(vcat(a,t))
        end
    end

    a = roll([])
    len ~ DiscreteUniform(1,n)
    len = length(a)
    # println("a:$a len:$len")

    # In the example, the first roll is 1.
    true ~ Dirac(a[1] == 1)

    return len
    # return a

end

function run_model(len=0)
    model = rolling_dice5(len)

    num_chains = 4

    # chains = sample(model, Prior(), 10_000)

    # chains = sample(model, MH(), MCMCThreads(), 100_000, num_chains)
    # chains = sample(model, MH(), MCMCThreads(), 10_000, num_chains)
    # chains = sample(model, MH(), 10_000)
    chains = sample(model, MH(), 1_000)

    # chains = sample(model, PG(15), MCMCThreads(), 1_000, num_chains)

    # chains = sample(model, SMC(1000), MCMCThreads(), 10_000, num_chains)
    # chains = sample(model, SMC(1000), 10_000)
    # chains = sample(model, IS(), 10_000)
    #
    # display(chains)
    # show_var_dist_pct(chains,:len,1000)

    println("prob return value:")
    genq = generated_quantities(model, chains)
    show_var_dist_pct(genq,1000)
    println("mean: $(mean(genq))")
    return genq
end

genq = undef
for val in 1:30
    global genq
    println("\nval:$val")
    @time genq = run_model(val)
end

println("\nval=10")
println("mean:", mean(run_model(10)))

println("\nval=100")
println("mean:", mean(run_model(100)))


"""
    distance_profile(dist, Q, T; kwargs...)

Calculate the distance profile corresponding to sliding `Q` over `T` and measuring distances between time windows using `dist`

The output is a vector of length `length(T) - length(Q) + 1`.
If inputs are matrices or higher-dimensional arrays, time is considered to be the last axis, and windows on the form `T[:, t:t+lastlength(Q)-1]`, where `lastlength` measures the length along the last axis, are compared.

Keyword arguments are sent to `dist`, which is called like this `dist(Q,T; kwargs...)`. `dist` can be a function or a distance from Distances.jl.

`distance_profile!(D, dist, Q, T; kwargs...)` is an in-place version.
"""
function distance_profile(dist, Q::AbstractArray{S}, T::AbstractArray{S}; kwargs...) where S
    m = lastlength(Q)
    n = lastlength(T)
    n >= m || throw(ArgumentError("Q cannot be longer than T"))
    l = n-m+1
    D = map(1:l) do i
        dist(Q, getwindow(T, m, i); kwargs...)
    end
end

"$(SIGNATURES)"
function distance_profile!(D::AbstractArray, dist, Q::AbstractArray{S}, T::AbstractArray{S}; kwargs...) where S
    m = lastlength(Q)
    n = lastlength(T)
    n >= m || throw(ArgumentError("Q cannot be longer than T"))
    l = n-m+1
    length(D) >= l || throw(ArgumentError("D is too short"))
    map!(D, 1:l) do i
        dist(Q, getwindow(T, m, i); kwargs...)
    end
end

"$(SIGNATURES)"
function distance_profile!(D::AbstractVector{S},::ZEuclidean, QT::AbstractVector{S}, μ, σ, m::Int, i::Int) where S <: Number
    @assert i <= length(D)
    mμ = m*μ[i]
    mσ = m*σ[i]
    O = zero(S)
    # @inbounds @fastmath @simd for j = eachindex(D)
    @avx unroll=4 for j = eachindex(D)
        # frac = (QT[j] - mμ*μ[j]) / (mσ*σ[j])
        # frac = (QT[j] - mμ*μ[j]) * Base.FastMath.inv_fast(mσ*σ[j])
        frac = (QT[j] - mμ*μ[j]) * LoopVectorization.VectorizationBase.vinv_fast(mσ*σ[j])
        D[j] = sqrt(max(2m*(1-frac), O))
    end
    D[i] = typemax(eltype(D))
    D
end

"$(SIGNATURES)"
distance_profile(
    ::ZEuclidean,
    QT::AbstractVector{S},
    μ::AbstractVector{S},
    σ::AbstractVector{S},
    m::Int,
) where {S<:Number} = distance_profile!(similar(μ), ZEuclidean(), QT, μ, σ, m, 1)

"""
    $(SIGNATURES)

Accepts precomputed sliding mean and std of the input arrays. `QT` is the windowed dot product and `i` is the index into `T` (the longer time series).
"""
function distance_profile!(D::AbstractVector{S},::ZEuclidean, QT::AbstractVector{S}, μA, σA, μT, σT, m::Int, i::Int) where S <: Number
    @assert i <= length(μA)
    @avx for j = eachindex(D,QT,μT,σT)
        frac = (QT[j] - m*μA[i]*μT[j]) / (m*σA[i]*σT[j])
        D[j] = sqrt(max(2m*(1-frac), 0))
    end
    D
end

"$(SIGNATURES)"
distance_profile(::ZEuclidean, QT::AbstractVector{S}, μA, σA, μT, σT, m::Int) where {S<:Number} =
    distance_profile!(similar(μT), ZEuclidean(), QT, μA, σA, μT, σT, m, 1)

"""
    distance_profile(::ZEuclidean, Q, T)

Compute the z-normalized Euclidean distance profile corresponding to sliding `Q` over `T`
"""
function distance_profile!(
    D::AbstractVector{S},
    ::ZEuclidean,
    Q::AbstractVector{S},
    T::AbstractVector{S},
) where {S<:Number}
    m = length(Q)
    μ, σ = sliding_mean_std(T, m)
    QT = window_dot(znorm(Q), T) # TODO: allocates a new znorm(Q) each time
    @avx for j in eachindex(D,QT,σ)
        frac = QT[j] / (m * σ[j])
        D[j] = sqrt(max(2m * (1 - frac), 0))
    end
    D
end

"$(SIGNATURES)"
distance_profile(::ZEuclidean, Q::AbstractArray{S}, T::AbstractArray{S}) where {S} =
    distance_profile!(similar(T, length(T) - length(Q) + 1), ZEuclidean(), Q, T)

"""
The dot product between query Q and all subsequences of the same length as Q in time series T
"""
function window_dot(Q, T)
    n   = length(T)
    m   = length(Q)
    QT  = conv(reverse(Q), T)
    return QT[m:n]
end

"""
    $(SIGNATURES)

return vectors with mean and std of sliding windows of length `m`
"""
function sliding_mean_std(x::AbstractVector{T}, m) where T
    @assert length(x) >= m
    n = length(x)-m+1
    s = ss = zero(float(T))
    μ = Vector{float(T)}(undef, n)
    σ = Vector{float(T)}(undef, n)
    @avx for i = 1:m
        s  += x[i]
        ss += x[i]^2
    end
    μ[1] = s/m
    σ[1] = sqrt(max(ss/m - μ[1]^2, 0))
    @fastmath @inbounds for i = 1:n-1 # fastmath making it more accurate here as well, but not faster
        s -= x[i]
        ss -= x[i]^2
        s += x[i+m]
        ss += x[i+m]^2
        μ[i+1] = s/m
        σ[i+1] = sqrt(max(ss/m - μ[i+1]^2, 0))
    end
    μ,σ
end

@inbounds @fastmath function sliding_mean_std(x::AbstractMatrix{T}, m) where T
    @assert lastlength(x) >= m
    n = lastlength(x)-m+1
    s  = zeros(T, size(x,1))
    ss = zeros(T, size(x,1))
    μ = Matrix{float(T)}(undef, size(x,1), n)
    σ = Matrix{float(T)}(undef, size(x,1), n)
    for i = 1:m
        @avx s  .+= view(x, :, i)
        @avx ss .+= view(x, :, i).^2
    end
    @avx μ[:,1] .= s./m
    @avx σ[:,1] .= sqrt.(max.(ss./m .- view(μ, :, 1).^2, 0))
    for i = 1:n-1 # fastmath making it more accurate here as well, but not faster
        @avx s .-= view(x, :, i)
        @avx ss .-= view(x, :, i).^2
        @avx s .+= view(x, :, i+m)
        @avx ss .+= view(x, :, i+m).^2
        @avx μ[:,i+1] .= s./m
        @avx σ[:,i+1] .= sqrt.(max.(ss./m .- view(μ, :, i+1).^2, 0))
    end
    μ,σ
end

"""
    $(SIGNATURES)

return mean of sliding windows of length `m`. Operates in-place and stores result in first argument
"""
function sliding_mean!(μ,x::AbstractVector{T}, m) where T
    n = length(x)-m+1
    @assert length(x) >= m
    @assert length(μ) >= n

    s = zero(T)
    @avx for i = 1:m
        s += x[i]
    end
    μ[1] = s/m
    @fastmath @inbounds for i = 1:n-1 # fastmath making it more accurate here as well, but not faster
        s -= x[i]
        s += x[i+m]
        μ[i+1] = s/m
    end
    μ
end

"""
$(SIGNATURES)

Returns the sliding-window entropy
"""
function sliding_entropy(x::AbstractVecOrMat{T}, m=actuallastlength(x)) where T
    N = actuallastlength(x)
    n = N-m+1
    e = Vector{T}(undef, n)
    if x isa AbstractVector
        ent = vmap(x->x*log(x), x)
    else
        ent = zeros(T, N)
        @avx for i = 1:N
            for j = 1:size(x,1)
                ent[i] += x[j,i]*log(x[j,i])
            end
        end
    end
    e0 = zero(T)
    @avx for i = 1:m
        e0 += ent[i]
    end
    e[1] = e0
    @fastmath @inbounds for i = 1:n-1
        e0 -= ent[i]
        e0 += ent[i+m]
        e[i+1] = e0
    end
    e .= .-e
end

"""
$(SIGNATURES)

Returns the sliding-window entropy, normalized to have unit sum for each feature channel.
"""
function sliding_entropy_normalized(x::AbstractArray{T}, m=actuallastlength(x)) where T
    sums = sum(x, dims=ndims(x))
    sliding_entropy(x ./ sums, m)
end

"""
    $(SIGNATURES)

Sliding-window PCA. Returns `U1 ∈ 𝐑(N)` the main principal-component loading scaled with the singular value, and `V1 ∈ 𝐑(d × N)` the main principal direction.

# Arguments:
- `X`: Signal ∈ 𝐑(d × N)
- `fs`: Sampling frequency
- `w`: Window length in seconds
- `cutoff_freq`: Frequencies below this will be removed. Set to 0 to avoid filtering.
- `pad`: if true, the return value will be padded with zeros to account for the fact that the analysis uses sliding windows and produce a slightly shorter output. If `pad = true`, the output will have the same length as the input.
- `normalize_win`: remove the mean from each window before calculating svd.
- `center`: Index into `X` at the center of the window (default) or at a position in the window proportional to how far along in the signal the window is taken.
"""
function sliding_pca(X::AbstractMatrix{A}; fs, w = 0.3, cutoff_freq = 0, pad = true, normalize_win=false, center = true) where A
    w = round(Int, w*fs) # from seconds to samples
    d,N = size(X)
    mX = mean(X, dims=2)
    X = X .- mX
    if cutoff_freq > 0
        X = filtfilt(digitalfilter(Highpass(cutoff_freq; fs=fs), Butterworth(6)), X')'
    end

    win = 1:w
    dominant_mode = Vector{A}(undef, N-w+1)
    directions = Matrix{A}(undef, d, N-w+1)
    d1 = zeros(A, d); d1[1] = 1
    wininds = range(1, stop = w, length = N-w+1)
    for i = 1:N-w+1
        v = _maindir!(X[:, win], i == 1 ? d1 : @view(directions[:, i-1]), normalize_win)
        directions[:,i] .= v
        if center
            ii = (win[1] + win[end]) ÷ 2 + 1 # integer index from middle of window
            @views dominant_mode[i] = v'X[:, ii]
        else
            ii = i + wininds[i] - 1
            ib = floor(Int, ii)
            ia = ceil(Int, ii)
            α = ia - ii
            @views dominant_mode[i] = v'*((1-α) .* X[:, ib] .+ (α) .* X[:,ia]) # interpolate
        end
        win = win .+ 1
    end
    if cutoff_freq > 0
        directions = filtfilt(digitalfilter(Lowpass(cutoff_freq; fs=fs), Butterworth(4)), directions')'
    end
    for i = axes(directions, 2)
        @views directions[:,i] ./= norm(directions[:,i])
    end
    if pad
        dominant_mode = [zeros(w÷2); dominant_mode; zeros(w÷2-1)]
        directions = [repeat(directions[:,1], 1, w÷2) directions repeat(directions[:,1], 1, w÷2-1)]
        directions
    end
    dominant_mode, directions
end

function _maindir!(x, last, normalize_win)
    if normalize_win
        x .-= mean(x, dims = 1)
    end
    s = svd!(x, alg = LinearAlgebra.QRIteration()) # alg choice important for stability of singular vectors
    V = s.U
    v = V[1,:]
    if dot(last, v) < 0
        v .*= -1
    end
    v
end

# function sliding_pca2(X::AbstractMatrix{A}; dout = 1, l=2) where A
#     d, N = size(X)
#     λ   = zeros(A, dout)
#     V   = zeros(A, d, dout, N)
#     μ   = zeros(A, d)
#     xi  = zeros(A, d)
#     v   = zeros(A, d)
#     U   = zeros(A, dout, N)
#     n   = 0

#     for x in eachcol(X)

#         n += 1
#         @. μ += (x - μ) / n
#         xi = (n > 1) ? (x .- μ) : deepcopy(x)
#         f = (one(A)+l)/n
#         @views if n > 1
#             V[:, :, n] .= V[:, :, n-1]
#         end
#         @views @inbounds for i in 1:dout
#             if i == n
#                 λ[i] = norm(xi)
#                 @. V[:, i, n] = xi / (λ[i] + eps())
#                 break
#             end
#             v .= ((1-f) * λ[i]) .* V[:, i, n] .+ (f * dot(V[:, i, n], xi)) .* xi
#             λ[i] = norm(v)
#             @. V[:, i, n] = v/(λ[i] + eps())
#             xi .= xi .- dot(V[:, i, n], xi) .* V[:, i]
#         end
#         @views mul!(U[:, n], V[:, :, n]', xi)
#         U[:, n] .*= λ
#     end
#     U, V

# end


using Markov
using Catlab
using Catlab.Theories
using Test

X = Ob(FreeCartesianCategory, :Float64)
u = Hom(x->x*rand(), X, X)

u₀ = Hom(()->rand(), munit(FreeCartesianCategory.Ob), X)
crand(u₀⊗u₀)

meantest(f::Function, μ::Real, n::Int, ϵ::Real) = begin
    μ̂ = sum(map(f, 1:n))/n
    @test μ - ϵ < μ̂
    @test μ̂ < μ + ϵ
end

meantest(1/1, 10000, 1e-1) do i
    crand(((u⊗u)⋅braid(X,X)), 1,2)[1]
end
meantest(1/2, 10000, 1e-1) do i
    crand(((u⊗u)⋅braid(X,X)), 1,2)[2]
end
meantest(1/1, 10000, 1e1) do i
    crand(mcopy(X),2)[1]
end
meantest(1/1, 10000, 1e1) do i
    crand(mcopy(X),2)[2]
end

@test crand(delete(X), 1) == ()
@test crand(u⋅delete(X), 1) == ()
meantest(1/2, 1000, 1e-1) do i
    crand(u⋅mcopy(X)⋅(id(X)⊗delete(X)), 1)[1][1]
end

@testset "Uniforms" begin
  u₀ = Hom(()->rand(), munit(FreeCartesianCategory.Ob), X)
  @test 0 <= crand(u, 1.0) <= 1.0
  @test 0 <= crand(u, 2) <= 2
  @test compose(u,u).args |> length == 2
  @test dom(compose(u,u)) == X
  @test codom(compose(u,u)) == X
  @test 0 <= crand(compose(u,u), 0.5) <= 1/2
  @test 0 <= crand(u₀) <= 1
  @test crand(u₀⊗u₀) |> length == 2
  @test crand(u⊗u₀, 1.0) |> length == 2
  @test crand(u₀⊗u, 1.0) |> length == 2
  @test crand(otimes(u₀,u₀,u), 2.0) |> length == 3
  @test crand(otimes(u₀,u,u₀), 2.0) |> length == 3
  @test crand(otimes(u₀,u,u), 1.0, 2.0) |> length == 3
  @test crand(otimes(u₀,u,u)⋅otimes(u,u,u), 1.0, 2.0) |> length == 3
  @test crand(otimes(u,u,u), 1.0, 2.0, 3.0) |> length == 3
end

@test crand(compose(id(X), id(X), id(X)), 1)[1] == 1


@info "---------- Basic cantilever test ----------"
st=Structure()
lcset=LoadCaseSet()

add_uniaxial_metal!(st,"steel",2e11,0.2,7849.0474)
add_general_section!(st,"frame",4.26e-3,3.301e-6,6.572e-5,9.651e-8,1e-3,1e-3,0,0)

add_node!(st,1,0,0,0)
add_node!(st,2,18,12,12)
add_beam!(st,1,1,2,"steel","frame")

set_nodal_restraint!(st,1,true,true,true,true,true,true)

add_static_case!(lcset,"DL",1.1)

assembly=assemble!(st,lcset,path=PATH)

solve(assembly)

r=result_nodal_reaction(assembly,"DL",1)
@test r≈[-2.83076e-10,-9.04954e-10,8926.15,53556.9,-80335.3,-3.29473e-8] atol=1e-1
r=result_nodal_displacement(assembly,"DL",2)
@test r≈[0.454025, 0.302683, -0.98385, -0.0336002, 0.0504003, -5.77725e-13] atol=1e-3

@info "---------- Modal test ----------"
clear_result!(assembly)
lcset=LoadCaseSet()
add_static_case!(lcset,"DL",1.0)
add_modal_case!(lcset,"modal")
set_modal_params!(lcset,"modal",modal_type="eigen",n=4)
assembly=assemble!(st,lcset,path=PATH)
# set_mass_source!(assembly,"DL")
solve(assembly)
T=result_modal_period(assembly,"modal")
@info "To be examined" T=T

@info "----------Node spring test----------"
clear_result!(assembly)

set_nodal_restraint!(st,1,false,false,false,false,false,false)
set_nodal_spring!(st,1,1e3,2e3,3e3,4e3,5e3,6e3)

lcset=LoadCaseSet()
add_static_case!(lcset,"DL",1.1)

assembly=assemble!(st,lcset,path=PATH)
solve(assembly)
r=result_nodal_displacement(assembly,"DL",2)
@test r≈[193.258,160.973,-453.836,-13.4228,16.1175,3.14657e-9] atol=1e-2


#!/usr/bin/env julia

#
# A test script that checks correctness of algorithms that evaluate incomplete
# gamma and beta functions and their inverses.
# All input arguments have been carefully selected to cover all branches
# of evaluation algorithms.
#
# For more information how to reproduce exact (i.e. unrounded) results, see
# https://github.com/jkovacic/math/blob/master/scripts/test/specfun.mac and
# https://github.com/jkovacic/math/blob/master/scripts/test/specfun.py
#

using IncGammaBeta
using Base.Test

# Round all results to 'i' decimal places:
function my_round(i::Integer)
    return x -> round(x, i)
end

mrd = my_round(6)

print_with_color(:blue, "Running tests...\n")

@test mrd( inc_gamma_upper(2.0, 0.5) ) == 0.909796
@test mrd( inc_gamma_upper(2.0, 5.0) ) == 0.040428
@test mrd( inc_gamma_lower(3.0, 2.5) ) == 0.912374
@test mrd( inc_gamma_lower(1.5, 4.0) ) == 0.84545
@test mrd( inc_gamma_upper_reg(3.0, 0.5) ) == 0.985612
@test mrd( inc_gamma_lower_reg(1.5, 3.2) ) == 0.906309
@test mrd( inc_gamma_upper(2.0+im, 1.0-im) ) == 0.294741 + 1.123545im
@test mrd( inc_gamma_lower(1.4-0.7im, 0.3+0.1im) ) == -0.016291 + 0.130522im
@test mrd( inc_gamma_upper_reg(-1.1+0.8im, 2.4-1.3im) ) == 0.001972 + 0.016253im
@test mrd( inc_gamma_lower_reg(-3.2-1.4im, -2.0-0.5im) ) == 0.239716 + 0.435784im

@test mrd( inc_beta_lower(2.0, 5.0, 0.2) ) == 0.011488
@test mrd( inc_beta_lower(2.0, 5.0, 0.7) ) == 0.032969
@test mrd( inc_beta_upper(1.0, 2.0, 0.15) ) == 0.36125
@test mrd( inc_beta_upper(1.0, 2.0, 0.82) ) == 0.0162
@test mrd( inc_beta_lower_reg(3.0, 2.0, 0.12) ) == 0.00629
@test mrd( inc_beta_upper_reg(4.0, 1.0, 0.32) ) == 0.989514
@test mrd( inc_beta_lower(2.0-im, 3.0+im, 0.1+0.1im) ) == -0.01428 - 0.010563im
@test mrd( inc_beta_upper(-1.2+im, -3.0-im, 0.1-0.2im) ) == 35.01302 + 12.573969im
@test mrd( inc_beta_lower_reg(2.2-0.2im, 1.4+0.7im, -0.3+0.4im) ) ==  0.82213 - 0.401782im
@test mrd( inc_beta_upper_reg(-3.8-0.7im, -3.4+2.7im, -0.6-0.1im) ) == 0.001099 + 0.002128im

@test mrd( inc_gamma_lower_reg_inv(0.2, 0.3) ) == 0.001588
@test mrd( inc_gamma_lower_reg_inv(3.0, 0.7) ) == 3.615568
@test mrd( inc_gamma_upper_reg_inv(0.3, 0.4) ) == 0.141253
@test mrd( inc_gamma_upper_reg_inv(5.2, 0.82) ) == 3.129677
@test mrd( inc_gamma_lower_inv(0.24, 0.94) ) == 0.002024
@test mrd( inc_gamma_lower_inv(2.8, 0.17) ) == 0.987392
@test mrd( inc_gamma_upper_inv(0.65, 0.86) ) == 0.217367
@test mrd( inc_gamma_upper_inv(3.5, 0.43) ) == 5.609014

@test mrd( inc_beta_lower_reg_inv(0.3, 0.2, 0.7) ) == 0.978553
@test mrd( inc_beta_lower_reg_inv(2.4, 3.5, 0.6) ) == 0.449428
@test mrd( inc_beta_upper_reg_inv(0.9, 1.5, 0.7) ) == 0.181633
@test mrd( inc_beta_upper_reg_inv(1.9, 2.7, 0.25) ) == 0.56483
@test mrd( inc_beta_lower_inv(2.8, 0.3, 2.0) ) == 0.999734
@test mrd( inc_beta_lower_inv(1.1, 1.3, 0.4) ) == 0.519011
@test mrd( inc_beta_upper_inv(0.4, 0.5, 1.8) ) == 0.410869
@test mrd( inc_beta_upper_inv(1.7, 1.1, 0.2) ) == 0.720553

print_with_color(:blue, "Tests completed successfully.\n")


## --- Load the StatGeochem package which has the resampling functions we'll want

    using StatGeochem
    using Plots; gr();
    using Statistics, DelimitedFiles, SpecialFunctions

## --- Download and unzip Keller and Schoene (2012) dataset

    if ~isfile("ign.h5") # Unless it already exists
        download("https://storage.googleapis.com/statgeochem/ign.h5.gz","./ign.h5.gz")
        download("https://storage.googleapis.com/statgeochem/err2srel.csv","./err2srel.csv")
        run(`gunzip -f ign.h5.gz`) # Unzip file
    end

    # Read HDF5 file
    using HDF5
    ign = h5read("ign.h5","vars")

## --- Compute proximity coefficients (inverse weights)

    # # Compute inverse weights
    # k = invweight(ign["Latitude"] .|> Float32, ign["Longitude"] .|> Float32, ign["Age"] .|> Float32)

    # Since this is somewhat computatually intensive, let's load a precomputed version instead
    k = ign["k"]

    # Probability of keeping a given data point when sampling
    p = 1.0 ./ ((k .* median(5.0 ./ k)) .+ 1.0) # Keep rougly one-fith of the data in each resampling
    p[vec(ign["Elevation"].<-100)] .= 0 # Consider only continental crust

    # Set absolute uncertainties for each element where possible, using errors defined inerr2srel.csv
    err2srel = importdataset("err2srel.csv", ',')
    for e in ign["elements"]
        # If there's an err2srel for this variable, create a "_sigma" if possible
        if haskey(err2srel, e) && !haskey(ign, e*"_sigma")
            ign[e*"_sigma"] = ign[e] .* (err2srel[e] / 2);
        end
    end

    # Special cases: age uncertainty
    ign["Age_sigma"] = (ign["Age_Max"]-ign["Age_Min"])/2;
    t = (ign["Age_sigma"] .< 50) .| isnan.(ign["Age_sigma"]) # Find points with < 50 Ma absolute uncertainty
    ign["Age_sigma"][t] .= 50 # Set 50 Ma minimum age uncertainty (1-sigma)

    # Special cases: location uncertainty
    ign["Latitude_sigma"] = ign["Loc_Prec"]
    ign["Longitude_sigma"] = ign["Loc_Prec"]

## --- Single element differentiation example

    xelem = "SiO2"
    xmin = 45
    xmax = 75
    nbins = 8
    elem = "K2O"

    h = plot(xlabel=xelem, ylabel="$(elem)",xlims=(xmin,xmax),framestyle=:box,grid=:off,fg_color_legend=:white) # Format plot

    rt = [0,1,2,3,4] # Time range (Ga)
    colors = reverse(resize_colormap(viridis[1:end-20],length(rt)-1))
    for i=1:length(rt)-1
        t = rt[i]*1000 .< ign["Age"] .< rt[i+1]*1000

        # Resample, returning binned means and uncertainties
        # (c = bincenters, m = mean, el = lower 95% CI, eu = upper 95% CI)
        (c,m,el,eu) = bin_bsr_means(ign[xelem][t],ign[elem][t],xmin,xmax,nbins, p=p[t],
                        x_sigma=ign[xelem*"_sigma"][t], y_sigma=ign[elem*"_sigma"][t])

        # Plot results
        plot!(h, c,m,yerror=(el,eu),color=colors[i],mscolor=colors[i],seriestype=:scatter,label="$(rt[i])-$(rt[i+1]) Ga")
        plot!(h, c,m,style=:dot,color=colors[i],mscolor=colors[i],label="")
    end
    display(h)
## --- Ratio differentiation example

    xelem = "SiO2"
    xmin = 45
    xmax = 75
    nbins = 8
    num = "Rb" # Numerator
    denom = "Sr" # Denominator

    h = plot(xlabel=xelem, ylabel="$(num) / $(denom)",xlims=(xmin,xmax),framestyle=:box,grid=:off,legend=:topleft,fg_color_legend=:white) # Format plot

    rt = [0,1,2,3,4]
    colors = reverse(resize_colormap(viridis[1:end-20],length(rt)-1))
    for i=1:length(rt)-1
        t = rt[i]*1000 .< ign["Age"] .< rt[i+1]*1000

        # Resample, returning binned means and uncertainties
        # (c = bincenters, m = mean, el = lower 95% CI, eu = upper 95% CI)
        (c,m,el,eu) = bin_bsr_ratios(nanbinmedian!, ign[xelem][t],ign[num][t],ign[denom][t],xmin,xmax,nbins, p=p[t],
                        x_sigma=ign[xelem*"_sigma"][t], num_sigma=ign[num*"_sigma"][t], denom_sigma=ign[denom*"_sigma"][t])

        # Plot results
        plot!(h, c,m,yerror=(el,eu),color=colors[i],mscolor=colors[i],seriestype=:scatter,label="$(rt[i])-$(rt[i+1]) Ga")
        plot!(h, c,m,style=:dot,color=colors[i],mscolor=colors[i],label="")
    end
    display(h)

## --- High-level functions for calculating combined averages over a set or silica ranges

    function constprop(binbsrfunction::Function, dataset::Dict, elem, xmin, xmax, nbins, p; xelem="Age", norm_by="SiO2", norm_bins=[43,55,65,78], nresamplings=1000)
        c = zeros(nbins)
        m = zeros(nbins)
        el = zeros(nbins)
        eu = zeros(nbins)
        for i=1:length(norm_bins)-1
            # Find the samples we're looking for
            t = (norm_bins[i] .< dataset[norm_by] .< norm_bins[i+1]) .& (dataset[elem] .> 0)

            # See what proportion of the modern crust falls in this norm_bin
            prop = sum((norm_bins[i] .< dataset[norm_by] .< norm_bins[i+1]) .& (p .> 0)) / sum((norm_bins[1] .< dataset[norm_by] .< norm_bins[end]) .& (p .> 0))

            # Resample, returning binned means and uncertainties
            # (c = bincenters, m = mean, el = lower 95% CI, eu = upper 95% CI)
            (c[:],m1,el1,eu1) = binbsrfunction(dataset[xelem][t], dataset[elem][t], xmin, xmax, nbins, x_sigma=dataset["$(xelem)_sigma"][t], nresamplings=nresamplings, p=p[t])

            m .+= prop.*m1
            el .+= prop.*el1
            eu .+= prop.*eu1
        end
        el ./= sqrt(length(norm_bins)-1) # Standard error
        eu ./= sqrt(length(norm_bins)-1) # Standard error

        return c, m, el, eu
    end

    function constprop(binbsrfunction::Function, dataset::Dict, num, denom, xmin, xmax, nbins, p; xelem="Age", norm_by="SiO2", norm_bins=[43,55,65,78], nresamplings=1000)
        c = zeros(nbins)
        m = zeros(nbins)
        el = zeros(nbins)
        eu = zeros(nbins)
        for i=1:length(norm_bins)-1
            # Find the samples we're looking for
            t = (norm_bins[i] .< dataset[norm_by] .< norm_bins[i+1]) .& (dataset[num].>0) .& (dataset[denom].>0)

            # See what proportion of the modern crust falls in this norm_bin
            prop = sum((norm_bins[i] .< dataset[norm_by] .< norm_bins[i+1]) .& (p .> 0)) / sum((norm_bins[1] .< dataset[norm_by] .< norm_bins[end]) .& (p .> 0))

            # Resample, returning binned means and uncertainties
            # (c = bincenters, m = mean, el = lower 95% CI, eu = upper 95% CI)
            (c[:],m1,el1,eu1) = binbsrfunction(dataset[xelem][t],dataset[num][t],dataset[denom][t],xmin,xmax,nbins,x_sigma=dataset["$(xelem)_sigma"][t],num_sigma=dataset["$(num)_sigma"][t],denom_sigma=dataset["$(denom)_sigma"][t],nresamplings=nresamplings,p=p[t])

            m .+= prop.*m1
            el .+= prop.*el1
            eu .+= prop.*eu1
        end
        el ./= sqrt(length(norm_bins)-1) # Standard error
        eu ./= sqrt(length(norm_bins)-1) # Standard error

        return c, m, el, eu
    end

## --- Single element constant-silica reference model

    tmin = 0
    tmax = 3900
    nbins = 39

    plotmin = 0
    plotmax = 4000

    elem = "MgO"

    (c, m, el, eu) = constprop(bin_bsr_means, ign, elem, tmin, tmax, nbins, p)

    # Plot results
    h = plot(c,m,yerror=(el,eu),seriestype=:scatter,color=:darkblue,mscolor=:darkblue,label="")
    plot!(h, c,m,style=:dot,color=:darkblue,mscolor=:darkblue,label="")
    plot!(h, xlabel="Age (Ma)", ylabel="$(elem)",xlims=(plotmin,plotmax),framestyle=:box,grid=:off,xflip=true) # Format plot
    savefig(h,"Constant Silica $(elem).pdf")
    display(h)

## --- Ratio constant-silica reference model

    tmin = 0
    tmax = 4000
    nbins = 8

    plotmin = 0
    plotmax = 4000

    num = "Rb"
    denom = "Sr"

    (c, m, el, eu) = constprop(bin_bsr_ratio_medians, ign, num, denom, tmin, tmax, nbins, p)

    # Plot results
    h = plot(c,m,yerror=(el,eu),seriestype=:scatter,color=:darkblue,mscolor=:darkblue,label="")
    plot!(h, c,m,style=:dot,color=:darkblue,mscolor=:darkblue,label="")
    plot!(h, xlabel="Age (Ma)", ylabel="$(num) / $(denom)",xlims=(plotmin,plotmax),framestyle=:box,grid=:off,xflip=true) # Format plot
    savefig(h,"Constant Silica $(num)_$(denom).pdf")
    display(h)

## --- End of File


using DelimitedFiles
f_1 = "1_00__0__0__acopf__1_05"
f_2 = "1_00__0__0__scacopf__1_05"
f_3 = "1_00__0__0__exitrates__1e_15__1_05"
c_1 = "data/"*f_1*"/"
c_2 = "data/"*f_2*"/"
c_3 = "data/"*f_3*"/"
Vm_1 = readdlm(c_1*"Vm.csv",','); Va_1 = readdlm(c_1*"Va.csv",','); V_1 = Vm_1 .* exp.(im .* Va_1)
Vm_2 = readdlm(c_2*"Vm.csv",','); Va_2 = readdlm(c_2*"Va.csv",','); V_2 = Vm_2 .* exp.(im .* Va_2)
Vm_3 = readdlm(c_3*"Vm.csv",','); Va_3 = readdlm(c_3*"Va.csv",','); V_3 = Vm_3 .* exp.(im .* Va_3)
Im_1 = zeros(186)
Im_2 = zeros(186)
Im_3 = zeros(186)

Vm = Vm_1
Va = Va_1
Im = Im_1

for i in eachindex(Im)
f = line.from; t = line.to
f_idx = first(findall(opfmodeldata[:buses].bus_i .== line.from)); t_idx = first(findall(opfmodeldata[:buses].bus_i .== line.to))
Y_tf = Y[t_idx,f_idx]
Y_ft = Y[f_idx,t_idx]
Vm_f = Vm[f_idx]; Va_f = Va[f_idx]
Vm_t = Vm[t_idx]; Va_t = Va[t_idx]
# Yabs2 = max(abs2(Y_tf), abs2(Y_ft))
if options[:lossless] == true
  Yabs2 = abs2(1.0 / line.x)
else
  Yabs2 = abs2(line.r / (line.r^2 + line.x^2) - im * (line.x / (line.r^2 + line.x^2)))
end
if options[:remove_tap] == false
  t   = (line.ratio == 0.0 ? 1.0 : line.ratio) * exp(im * line.angle)
  a   = real(t)
  b   = imag(t)
  Tik = abs(t)
  φik = angle(t)
else
  Tik = 1.0
  φik = 0.0
  a   = 1.0
  b   = 0.0
end
## NOTE: current from Frank & Rebennack OPF primer eq 4.6; turns/tap ratios ARE accounted for; eq 5.11 + Remark 5.1 looks wrong
F_l = @NLexpression(opfmodel, current2, (Vm_f^2 + (a^2 + b^2) * Vm_t^2 - 2 * Vm_f * Vm_t * ( a * cos(Va_f - Va_t) + b * sin(Va_f - Va_t) ))*(Yabs2/(a^2 + b^2)^2) - flowmax)


module DarcyCubedSphereTestsSeq

using Test
using Gridap
using GridapGeosciences
using Plots
using FillArrays

include("../ConvergenceAnalysisTools.jl")
include("../DarcyCubedSphereTests.jl")

# Testing cubed sphere mesh with analytical geometric mapping
n_values=generate_n_values(2)
hs=[2.0/n for n in n_values]
model_args_series=[(n,) for n in n_values]
@time hs0,k0errors,s0=convergence_study(solve_darcy,hs,model_args_series,0,2)
@test round(s0,digits=3) ≈ 1.0

n_values=generate_n_values(2,n_max=50)
hs=[2.0/n for n in n_values]
model_args_series=[(n,) for n in n_values]
@time hs1,k1errors,s1=convergence_study(solve_darcy,hs,model_args_series,1,4)
@test round(s1,digits=3) ≈ 1.932

n_values=generate_n_values(2)
hs=[2.0/n for n in n_values]
model_args_series=zip(n_values,Fill(2,length(n_values)))

# Testing cubed sphere mesh with polynomial geometric mapping
@time hs0,k0errors,s0=convergence_study(solve_darcy,hs,model_args_series,0,2)
@test round(s0,digits=3) ≈ 1.0

n_values=generate_n_values(2,n_max=50)
hs=[2.0/n for n in n_values]
model_args_series=zip(n_values,Fill(2,length(n_values)))

@time hs1,k1errors,s1=convergence_study(solve_darcy,hs,model_args_series,1,4)
@test round(s1,digits=3) ≈ 1.936

# plot([hs0,hs1],[k0errors,k1errors],
#      xaxis=:log, yaxis=:log,
#      label=["L2 k=0","L2 k=1"],
#      shape=:auto,
#      xlabel="h",ylabel="L2 error norm")

end # module


#Test functional Integrate and adjoint for euler equation.

@doc """
Euler Equation -- test_adjoint

The function tests for the correctness of objective and non-objective functional
computation and then tests if the adjoint vector is being computed correctly.
Adjoint vector computation is based on an objective function. This is a serial
test and uses the input file called

`input_vals_vortex_adjoint_DG.jl`

"""->

function test_adjoint()

  @testset "Checking complete derivative of a functional using adjoint vector" begin
    # println("testing adjoint functions\n")
    #fname = "input_vals_vortex_adjoint_DG.jl"
    fname = "input_vals_airfoil.jl"
    mesh, sbp, eqn, opts = solvePDE(fname)
    @assert opts["aoa"] == 2.0

    lift = createFunctional(mesh, sbp, eqn, opts, 1)

    h = 1e-20
    pert = Complex128(0, h)

    pc, lo = getNewtonPCandLO(mesh, sbp, eqn, opts)
    ls = StandardLinearSolver(pc, lo, eqn.comm, opts)
    adjoint_vec = zeros(Complex128, mesh.numDof)
    calcAdjoint(mesh, sbp, eqn, opts, ls, lift, adjoint_vec, recalc_jac=true, recalc_pc=true)

    # compute dR/daoa and dJ/daoa
    eqn.params.aoa += pert
    evalResidual(mesh, sbp, eqn, opts)
    array3DTo1D(mesh, sbp, eqn, opts, eqn.res, eqn.res_vec)
    dRdaoa = imag(eqn.res_vec)/h

    val = evalFunctional(mesh, sbp, eqn, opts, lift)
    dJdaoa = imag(val)/h
    eqn.params.aoa -= pert

    DJDaoa = dJdaoa + dot(adjoint_vec, dRdaoa)

    # save value for parallel tests
    fname = "BoundaryForceData_values.dat"
    file = open(fname, "w")
    println(file, val)
    close(file)

    # Check complete derivatives w.r.t alpha using finite difference
    pert = 1e-6
    eqn.params.aoa += pert
    solvePDE(mesh, sbp, eqn, opts, mesh)
    val2 = evalFunctional(mesh, sbp, eqn, opts, lift)
    eqn.params.aoa -= pert

    DJDaoa_fd = (val2 - real(val))/pert
    err_val = norm(DJDaoa - DJDaoa_fd, 2)

    println("dJDaoa = ", DJDaoa)
    println("dJdaoa_fd = ", DJDaoa_fd)
    println("diff = ", DJDaoa - DJDaoa_fd)
    @test isapprox( err_val, 0.0) atol= 1e-6

  end # End  testset("Checking complete derivative of a functional using adjoint vector")

end # End function test_adjoint

add_func1!(EulerTests, test_adjoint, [TAG_ADJOINT, TAG_LONGTEST])


# # Analog forecasting

using Plots, DifferentialEquations, AnalogDataAssimilation

σ = 10.0
ρ = 28.0
β = 8.0 / 3

dt_integration = 0.01
dt_states = 1
dt_obs = 8
var_obs = [1]
nb_loop_train = 100
nb_loop_test = 10
sigma2_catalog = 0.0
sigma2_obs = 2.0

ssm = StateSpaceModel(
    lorenz63,
    dt_integration,
    dt_states,
    dt_obs,
    [σ, ρ, β],
    var_obs,
    nb_loop_train,
    nb_loop_test,
    sigma2_catalog,
    sigma2_obs,
)

# # compute u0 to be in the attractor space

u0 = [8.0; 0.0; 30.0]
tspan = (0.0, 5.0)
prob = ODEProblem(ssm.model, u0, tspan, ssm.params)
u0 = last(solve(prob, reltol = 1e-6, save_everystep = false))

# # Generate the data

xt, yo, catalog = generate_data(ssm, u0)

# # Create the forecasting function

af = AnalogForecasting(50, xt, catalog; regression = :local_linear, sampling = :multinomial)

# # Data assimilation

np = 100
DA = DataAssimilation(af, xt, ssm.sigma2_obs)
x̂ = forecast(DA, yo, AnEnKS(np), progress = false);
println(RMSE(xt, x̂))

# # Plot results

plot(xt.t, xt[1], label = "true")
plot!(xt.t, x̂[1], label = "forecasted")
scatter!(yo.t, yo[1], markersize = 2, label = "observed")


module QCurlGradMonomialBasesTests

using Test
using Gridap.TensorValues
using Gridap.Fields
using Gridap.Polynomials

xi = Point(2,3)
np = 5
x = fill(xi,np)

order = 0
D = 2
T = Float64
V = VectorValue{D,T}
G = gradient_type(V,xi)
b = QCurlGradMonomialBasis{D}(T,order)

@test num_terms(b) == 4
@test get_order(b) == 0

xi = Point(2,3,5)
np = 5
x = fill(xi,np)

order = 0
D = 3
T = Float64
V = VectorValue{D,T}
G = gradient_type(V,xi)
b = QCurlGradMonomialBasis{D}(T,order)

v = V[
  (1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 1.0),
  (2.0, 0.0, 0.0), (0.0, 3.0, 0.0), (0.0, 0.0, 5.0)]

g = G[
  (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
  (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
  (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
  (1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
  (0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0),
  (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0)]

bx = repeat(permutedims(v),np)
∇bx = repeat(permutedims(g),np)
test_field(b,x,bx,grad=∇bx)

xi = Point(2,3)
np = 5
x = fill(xi,np)

order = 1
D = 2
T = Float64
V = VectorValue{D,T}
G = gradient_type(V,xi)
b = QCurlGradMonomialBasis{D}(T,order)

v = V[
  (1.0, 0.0), (0.0, 1.0), (2.0, 0.0), (0.0, 3.0),
  (4.0, 0.0), (0.0, 9.0), (3.0, 0.0), (0.0, 2.0),
  (6.0, 0.0), (0.0, 6.0), (12.0, 0.0), (0.0, 18.0)]

g = G[
  (0.0, 0.0, 0.0, 0.0), (0.0, 0.0, 0.0, 0.0),
  (1.0, 0.0, 0.0, 0.0), (0.0, 0.0, 0.0, 1.0),
  (4.0, 0.0, 0.0, 0.0), (0.0, 0.0, 0.0, 6.0),
  (0.0, 1.0, 0.0, 0.0), (0.0, 0.0, 1.0, 0.0),
  (3.0, 2.0, 0.0, 0.0), (0.0, 0.0, 3.0, 2.0),
  (12.0, 4.0, 0.0, 0.0),(0.0, 0.0, 9.0, 12.0)]

bx = repeat(permutedims(v),np)
∇bx = repeat(permutedims(g),np)
test_field(b,x,bx,grad=∇bx)

end # module


#
# Version 0.1 (IJCNN submission preparation)
#
#     06 Nov 2016: - Added function to add noise to the generated signal
#
# (A lot of functions are copied from Pccf/ repository)
module SignalGenerator

include("recmidpoints.jl")

export genSig, addNoise!, addNoiseRec!

function bisect(v, x, imin::Int, imax::Int)
    # https://docs.python.org/2/library/bisect.html
    # The returned insertion point i partitions the sorted vector v into two halves so that
    # all(val <= x for val in v[1:i]) for the left side and
    # all(val > x  for val in v[i:hi]) for the right side.
    if imin == imax
        x <= v[imin] ? imin : imin + 1
    else
        imid = div(imin+imax, 2)
        if x == v[imid]
            return imid
        elseif x > v[imid]
            bisect(v, x, imid + 1, imax)
        else
            bisect(v, x, imin, max(imid - 1, imin))
        end
    end
end
function bisect(v, x)
    bisect(v,x,1,length(v))
end

"""
Function to generate sequence of elements according to the probabilities proportional to the weights.
"""
function genSamples(values, weights::Vector{Float64}, n::Int64)
    @assert length(values) == length(weights)
    if sum(weights) == 1.0
        probs = weights
    else
        probs = weights/sum(weights)
    end
    cdf = cumsum(probs)
    samples = []
    for i in 1:n
        u   = rand()
        i   = bisect(cdf, u)
        push!(samples, values[i])
    end
    return samples
end

## ;;;;;;;;;;;;;;;;;Pccf/v1.0/src/recurrency_modelling2/sim3.clj;;;;;;;
## (defn gen-vals-in-interval-uniform [n from-v to-v]
##   "Function to generate values within interval inclusive"
##   (let [rng (inc (- to-v from-v))]
##     (map #(+ % from-v) (repeatedly n #(rand-int rng)))))
##
## (defn gen-vals-in-interval-gauss [n from-v to-v]
##   (let [mu (/ (+ to-v from-v) 2.0) sigma (inc (- to-v from-v))]
##     (utils/rnorm n mu sigma)))
## ;; (gen-vals-in-interval 5 7 8)
## ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

# """
# Probably it if naive experiments V.1.1 (After https://github.com/av-maslov/Pccf)
# Function to generate random observations.
# bootom_level, upper_level - boundaries of the interval defining mean and sigma
# Boundaries seemed to be more convenient than setting mu and sigma directly.
# """
# function genValsInIntervalGauss(n, bootom_level, upper_level)
#     # old: mu = (n+st)/2
#     mu = (bootom_level + upper_level)/2
#     sigma = upper_level - bootom_level + 1.0
#     randn(n) * sigma + mu
# end

function calcWidth(mu)
    1 + round(Int, 1.33 * mu)
end

function gaussPdf(x, mu, sigma)
    k = 1.0/(sigma * sqrt(2*pi))
    p = k * exp(-((x-mu)^2/(2*sigma^2)))
end

"Change's locations, all > 0 except the first"
function changesLocations(n, mu, sigma)
    locations = vcat(0, filter(x -> x > 0, cumsum(randn(n)*sigma + mu)))
    locations = map(x -> convert(Int, x), round(locations))
end

"Length of the signal to be generated."
function calcTmax(mu, n)
    round(Int,(mu * (n+1)))
end

function errorsLocations(n,mu,errRate)
    tMax = calcTmax(mu, n)
    avgNumOfErrors = errRate * n
    rand(1:tMax, avgNumOfErrors)
end

"""
Function to generate the signal with changes and noise.
n          : length of the signal
mu         : avg distance between changes
sigma      : std. dev. of the distance
changeStep : absolute value of the signal's level change after each changepoint
sigStd     : signals standard deviation
"""
function genSig(n, mu, sigma, changeStep, sigStd)
    tMax    = calcTmax(mu, n)
    changes = changesLocations(n, mu, sigma)
    n_chps  = length(changes)
    levels  = cumsum(rand([-1, 1], n_chps) * changeStep/2.0)
    @assert length(levels) == n_chps
    sig = []
    for i in 2:n_chps
        sub_len = changes[i] - changes[i-1]
        sig     = vcat(sig, randn(sub_len) * sigStd + levels[i])
    end
    Dict("sig"=>sig, "changes"=>changes)
end

function updVec(vec, ids::Array{Int,1}, newVals)
    v=copy(vec)
    for i in 1:length(ids)
        v[ids[i]]=newVals[i]
    end
    return v
end

"""
Add noise between changepoinst in the artificially generated signal.
Just in the middle between changes.
sig[i] = sig[i] + changeStep
"""
function addNoise!(sig, changes, changeStep; width = 5)
    idx_noise = Array{Int64,1}()
    n_chp = length(changes)
    n     = length(sig)
    @assert n_chp > 3
    for i in 2 : n_chp
        c_j = changes[i-1]
        c_i = changes[i]
        m = convert(Int, div(c_i+c_j, 2))
        idx_noise = vcat(idx_noise, collect(m-div(width,2) : m+div(width,2)))
    end
    for k in idx_noise
        sig[k] = sig[k] + changeStep
    end
    idx_noise
end

"""
Use recursive function to add more noise than just in the middle
"""
function addNoiseRec!(sig, changes, changeStep; width = 5, min_len = 30)
    idx_noise = Array{Int64,1}()
    n_chp = length(changes)
    n     = length(sig)
    @assert n_chp > 3
    for i in 2 : n_chp
        c_j = changes[i-1]
        c_i = changes[i]
        # m = convert(Int, div(c_i+c_j, 2))
        midpoints = recmidpoints(c_j, c_i, min_len)
        for m in midpoints
            idx_noise = vcat(idx_noise, collect(m-div(width,2) : m+div(width,2)))
        end
    end
    sort!(idx_noise)
    for k in idx_noise
        sig[k] = sig[k] + changeStep
    end
    idx_noise
end

end

# """
# Function to generate the signal with changes and noise.
# For changes values are from tvf-tvt; For errors in rvf-evt
# tvf: 'true values from' / tvt: `true values to`
# evf: 'error's values from' / evt: 'error's values to'
# """
# function genSig_old(n, mu, sigma, errRate, tvf, tvt, evf, evt)
#     tMax        = calcTmax(mu,n)
#     changesLoc  = changesLocations(n,mu,sigma)
#     nChps       = length(changesLoc)
#     valsChanges = genValsInIntervalGauss(nChps, tvf, tvt)
#     # ???
#     vals        = zeros(tMax)
#
#     if errRate > 0
#         errorsLoc   = errorsLocations(n,mu,errRate)
#         nErrors     = length(errorsLoc)
#         valsErrors  = genValsInIntervalGauss(nErrors, evf, evt)
#     end
#     changes1    = updVec(vals, errorsLoc, valsErrors)
#     changes2    = updVec(changes1, changesLoc, valsChanges)
#     Dict("changes"=>changesLoc, "sig"=>changes2)
# end


"""
    forward_filter(Z, δ, c₀)

Performs the forward filtering algorithm for a Dirichlet Evolutional Process, as
described in Fonseca & Ferreira (2017), and returns the online Dirichlet
parameters `c[1], …, c[T]`.
"""
function forward_filter(Z::Matrix{<: Integer},
                        δ::RT,
                        c₀::Vector{RT} = fill(RT(0.1), size(Z,2))) where RT <: Real

    T, k = size(Z)
    c = Matrix{RT}(undef, T, k)

    c[1,:] = δ * c₀ + Z[1,:]
    for t = 2:T
        c[t,:] = δ * c[t-1,:] + Z[t,:]
    end

    return c
end

"""
    backwards_sampler(c, δ)

Performs the backwards sampling algorithm for a Dirichlet Evolutional Process,
as described in Fonseca & Ferreira (2017), and returns samples `η[1], …, η[T]`
from the posterior distribution.
"""
function backwards_sampler(c::Matrix{RT}, δ::RT) where RT <: Real

    T = size(c, 1)
    η = similar(c)

    η[T,:] = rand(Dirichlet(c[T,:]))
    for t = T-1:-1:1
        cₛ = sum(c[t,:])
        S = rand(Beta(δ * cₛ, (1 - δ) * cₛ))
        u = rand(Dirichlet((1 - δ) * c[t,:]))
        η[t,:] = S * η[t+1,:] + (1 - S) * u
    end

    return η
end

"""
    moddirichlet_mean(c, a, b)

Internal function computing the mean for a Mod-Dirichlet.
"""
@inline function moddirichlet_mean(c::Vector{RT},
                                   a::Vector{RT},
                                   b::Vector{RT}) where RT <: Real

    return (b - a) .* (c / sum(c)) + a
end

"""
    moddirichlet_params(c, δ, η)

Internal function to return the parameters for the Mod-Dirichlet distribution.
"""
function moddirichlet_params(c::Vector{RT},
                             δ::RT,
                             η::Vector{RT}) where RT <: Real

    k = size(η, 1)

    # Compute mode for S

    cₛ = sum(c)
    α = δ * cₛ
    β = (1 - δ) * cₛ

    if (α < 1.) & (β < 1.)
        throw(ArgumentError("Invalid mode for S"))
    elseif α <= 1.
        S = 0
    elseif β <= 1.
        S = 1
    else
        S = (α - 1) / (α + β - 2)
    end

    # Compute parameters as function of the mode of S

    c = (1 - δ) * c
    a = S * η
    b = (1 - S) * ones(k) + S * η

    return c, a, b
end

"""
    backwards_estimator(c, δ)

Similar to `backwards_sampler`, but obtains modes in a backwards step instead
of samples.
"""
function backwards_estimator(c::Matrix{RT}, δ::RT) where RT <: Real

    T, k = size(c)
    η = similar(c)

    # For the last eta's distribution we can do it directly since it's a known
    # Dirichlet(c[T]).

    η[T,:] = moddirichlet_mean(c[T,:], zeros(k), ones(k))

    # For the other time instants we need to find the Mod-Dirichlet parameters
    # conditional to the previous mode being a known value.

    for t = T-1:-1:1
        cₜ, aₜ, bₜ = moddirichlet_params(c[t,:], δ, η[t+1,:])
        η[t,:] = moddirichlet_mean(cₜ, aₜ, bₜ)
    end

    return η
end


@testset "competing Poisson" begin
    r = [0.2, 0.7, 3.9]
    cp = CompetingPoisson(r; events = 2:4)

    # printing
    @test repr(cp) == "Competing Poisson with total rate 4.8; P(2)=0.0417 P(3)=0.146 P(4)=0.812"

    # properties
    @test cp.rates ≈ r
    @test cp.total_rate ≈ sum(r)
    @test cp.probabilities ≈ normalize(r, 1)
    @test cp.events == 2:4
    @test Set(propertynames(cp)) == Set((:total_rate, :rates, :probabilities, :events))
    @test Set(propertynames(cp, true)) == Set((propertynames(cp)..., :index_distribution))

    # empirical count
    c = zeros(Int, 4)           # event counts
    d = zeros(10000)            # durations
    for i in axes(d, 1)
        x = @inferred rand(cp)
        d[i] = x.duration
        c[x.event] += 1
    end
    e = ecdf(d)
    p = (1:9)./10
    q = quantile.(Exponential(1 / sum(r)), p)
    @test mean(d) ≈ (1 ./ sum(r)) atol = 0.01
    @test all(isapprox.(e.(q), p; atol = 0.02))
    @test normalize(c, 1) ≈ vcat([0], normalize(r, 1)) atol = 0.02
end


using ForestBiometrics
using DelimitedFiles
using Test
using Plots

datapath = joinpath(@__DIR__, "data")
data = readdlm(joinpath(datapath,"StandExam_data.csv"),',',header=true)

tl = Tree[]

for i in eachrow(data[1])
    push!(tl,Tree(i[7], i[8], i[6], i[9]))
end

stand = Stand(tl)

@test isapprox(basal_area(stand), 90.58823529)

basal_area!(stand)

@test isapprox(stand.basal_area, 90.58823529)

@test isapprox(trees_per_area(stand), 147.8593658)

trees_per_area!(stand)

@test isapprox(stand.trees_per_area, 147.8593658)

qmd!(stand)

@test isapprox(stand.qmd, 10.5985824)

@test isapprox(sdi(stand),162.3198113)

@test typeof(gingrich_chart(400, 80)) == Plots.Plot{Plots.GRBackend}
@test typeof(gingrich_chart(stand)) == Plots.Plot{Plots.GRBackend}

@test typeof(reineke_chart(500, 8)) == Plots.Plot{Plots.GRBackend}
@test typeof(reineke_chart(stand))  == Plots.Plot{Plots.GRBackend}
@test typeof(reineke_chart(500,8;maxsdi=600)) == Plots.Plot{Plots.GRBackend}


# 3D Grid examples
# ===============
#

# ## Quadrilateral
function quadrilateral()
    X=collect(0:0.25:1)
    Y=collect(0:0.2:1)
    Z=collect(0:0.1:1)
    simplexgrid(X,Y,Z)
end
# ![](quadrilateral.svg)


using Test, AutoARIMA, Statistics

@testset "Time Series Analysis Forecasting and Control" begin
    @testset "Table6.5" begin
        arma = fit(ARMAParams(true, 1,1), seriesA)
        @test arma.μ ≈ 2.45 rtol = 0.2
        @test arma.ϕ ≈ [0.87] rtol = 0.05
        @test arma.θ ≈ [0.48] rtol = 0.05
        @test arma.σ2 ≈ 0.098 rtol = 0.05

        θ, σ2 = innovations(diff(seriesA), 1, n=10)
        @test θ ≈ [0.53] rtol = 0.01
        @test σ2 ≈ 0.107 rtol = 0.01

        θ, σ2 = innovations(diff(seriesB), 1)
        @test θ ≈ [-0.09] rtol = 0.05
        @test σ2 ≈ 52.2 rtol = 0.05

        ar = fit(ARParams(false, 1), diff(seriesC))
        @test ar.μ ≈ 0.0 atol = 0.01
        @test ar.ϕ ≈ [0.81] rtol = 0.05
        @test ar.σ2 ≈ 0.019 rtol = 0.05

        θ, σ2 = innovations(diff(diff(seriesC)), 2, n=30)
        @test θ ≈ [0.09,0.07] rtol = 0.05
        @test σ2 ≈ 0.020 rtol = 0.05

        ar = fit(ARParams(1), seriesD)
        @test ar.μ ≈ 1.32 rtol = 0.05
        @test ar.ϕ ≈ [0.86] rtol = 0.05
        @test ar.σ2 ≈ 0.093 rtol = 0.05

        θ, σ2 = innovations(diff(seriesD), 1)
        @test θ ≈ [0.05] rtol = 0.05
        @test σ2 ≈ 0.096 rtol = 0.01

        ar = fit(ARParams(2), seriesE)
        @test ar.μ ≈ 14.9 rtol = 0.05
        @test ar.ϕ ≈ [1.32,-0.63] rtol = 0.05
        @test ar.σ2 ≈ 289.0 rtol = 0.05

        ar = fit(ARParams(3), seriesE)
        @test ar.μ ≈ 13.7 rtol = 0.05
        @test ar.ϕ ≈ [1.37,-0.74,0.08] rtol = 0.05
        @test ar.σ2 ≈ 287.0 rtol = 0.05

        ar = fit(ARParams(2), seriesF)
        @test ar.μ ≈ 58.3 rtol = 0.05
        @test ar.ϕ ≈ [-0.32,0.18] rtol = 0.05
        @test ar.σ2 ≈ 115.0 rtol = 0.05
    end

    @testset "Table3.1" begin
        ϕ = map(i -> partial_autocorrelation(seriesF, i), 1:15)
        @test ϕ ≈ [-0.39,0.18,0.00,-0.04,-0.07,-0.12,0.02,0.00,-0.06,0.00,0.14,-0.01,0.09,0.17,0.00] rtol = 0.05
    end

    @testset "Table2.1" begin
        ϕ = map(i -> autocorrelation(seriesF, i), 1:15)
        @test ϕ ≈ [-0.39,0.30,-0.17,0.07,-0.10,-0.05,0.04,-0.04,0.00,0.01,0.11,-0.07,0.15,0.04,0.01] rtol = 0.05
    end

    @testset "Section9.1" begin
        sarima = fit(MSARIMAParams([0,0], [1,1], [1,1], [1,12]), log.(seriesG))
        @test sarima.ϕ ≈ [1,0,0,0,0,0,0,0,0,0,0,1,-1] rtol = 0.05
        # @test sarima.θ[1] ≈ 0.4 rtol = 0.05
        # @test sarima.θ[12] ≈ 0.6 rtol = 0.05
        @test sarima.θ[13] ≈ -sarima.θ[1] * sarima.θ[12] rtol = 0.05
    end

    # @testset "Section10.1" begin
    #     arma = MA∞(fit(ARMAParams(1, 1), seriesA), 30)
    #     @test arma.θ ≈ [0.34,0.32,0.29,0.265,0.24,0.22,0.21,0.19,0.17,0.16,0.15,0.14,0.12,0.11,0.10,0.10,0.09,0.082,0.075,0.07,0.065,0.06,0.054,0.05,0.046,0.043,0.039,0.36,0.033,0.030] rtol = 0.05
    #     arima = MA∞(fit(ARIMAParams(0, 1, 1), seriesA), 30)
    #     @test arima.θ ≈ fill(0.29, 30)
    # end

    @testset "Table4.2" begin
        @test AR∞(toarma(ARIMAParams(1, 1, 1), ARMAModel{1,1}(1.0, [-0.3], [0.5], 1.0)), 7).ϕ ≈ [0.2,0.4,0.2,0.1,0.05,0.025,0.0125]
    end

end


using PyPlot

N = 100
chord = 1.0
ds = .26
halfspan = 26
Vinf = 25.85 #(props are removed, so constant Vinfeff = Vinf)
Vinfeff = Vinf
Sref = 26*2

y = linspace(0,halfspan,N)
ds_ell = y[2]-y[1]
cl0 = 1.2
cl = cl0*sqrt.(1-(y/halfspan).^2)

clave_ell = mean(cl)

clopt = [1.18335, 1.18348, 1.18367, 1.1839, 1.18416, 1.18444, 1.18473, 1.18503, 1.18534, 1.18566, 1.18597, 1.18629, 1.18661, 1.18693, 1.18725, 1.18756, 1.18787, 1.18818, 1.18849, 1.18879, 1.18909, 1.18938, 1.18966, 1.18994, 1.19022, 1.19048, 1.19074, 1.191, 1.19124, 1.19148, 1.19171, 1.19193, 1.19214, 1.19234, 1.19253, 1.19271, 1.19287, 1.19303, 1.19317, 1.1933, 1.19342, 1.19352, 1.19361, 1.19368, 1.19373, 1.19376, 1.19378, 1.19377, 1.19375, 1.1937, 1.19362, 1.19352, 1.1934, 1.19324, 1.19305, 1.19283, 1.19257, 1.19228, 1.19194, 1.19156, 1.19113, 1.19065, 1.19011, 1.18951, 1.18885, 1.18811, 1.18729, 1.18639, 1.18539, 1.18429, 1.18307, 1.18172, 1.18023, 1.17857, 1.17674, 1.17471, 1.17246, 1.16994, 1.16713, 1.16399, 1.16046, 1.15648, 1.15198, 1.14687, 1.14104, 1.13435, 1.12662, 1.11765, 1.10713, 1.09472, 1.07991, 1.06206, 1.04025, 1.01321, 0.979061, 0.934965, 0.876372, 0.795405, 0.676626, 0.481338]
y2 = linspace(0,halfspan,length(clopt))
clopt_ave = mean(clopt)

CL_ell = 2*sum(cl.*Vinfeff.*chord.*ds_ell)/(Vinf.*Sref)
CL_opt = 2*sum(clopt.*Vinfeff.*chord.*ds)/(Vinf.*Sref)

PyPlot.figure()
PyPlot.plot(y,cl,"b-",label = "Elliptical cl: CL = $(round.(CL_ell,3))")
PyPlot.plot(y,ones(cl)*clave_ell,"b--",label = "Elliptical cl Average")
PyPlot.plot(y2,clopt,"r-",label = "FB results cl: CL = $(round.(CL_opt,3))")
PyPlot.plot(y2,ones(clopt)*clopt_ave,"r--",label = "FB results cl Average")
PyPlot.xlabel("Halfspan")
PyPlot.ylabel("cl")
PyPlot.legend(loc = "best")


# Squares building

W,(WW,EW,FW) = Lar.cuboid([1,1], true)
square = (W,EW)

squares = Lar.Struct([  square,  Lar.t(-1, -1),  Lar.s(3, 3),  square  ])

V,EV = Lar.struct2lar(squares)

model = CAGD.Model(V);
CAGD.addModelCells!(model, 1, EV);

# Lines adding
lines = CAGD.Model([
    -2.0  1.0  0.0  3.0
     0.0  3.0  3.0  0.0
]);
CAGD.addModelCells!(lines, 1, [[1, 2], [3, 4]]);

# Merge the models
CAGD.uniteModels!(model, lines)

# Planar Arrangment

A_model = CAGD.planar_arrangement(model, sparse(Int8[0,0,0,0,1,1,1,1,0,0]));

A_model.G
Matrix(A_model.T[1])
Matrix(A_model.T[2])

# Display Models

if todisplay
    VV = [[k] for k=1:size(A_model.G ,2)];
    triangulated_faces = CAGD.triangulate2D(A_model);
    FVs = convert(Array{Lar.Cells}, triangulated_faces);

    GL.VIEW(GL.GLExplode(A_model.G, FVs, 1.0, 1.0, 1.0, 99, 1));
    GL.VIEW(GL.GLExplode(A_model.G, FVs, 1.2, 1.2, 1.2, 99, 1));
end;


#Función de inflación de exclusión dinámica

#InflationDynamicExclusion.jl - Definición de exclusión dinámica

"""
    InflationDynamicExclusion <: InflationFunction

Función para computar la inflación de exclusión dinámica dado el factor inferior (`lower_factor`) y factor superior (`upper_factor`).

## Utilización

    function (inflfn::InflationDynamicExclusion)(base::VarCPIBase)

Define cómo opera `InflationDynamicExclusion` sobre un objeto de tipo VarCPIBase.

    function (inflfn::InflationDynamicExclusion)(cs::CountryStructure)

Define cómo opera `InflationDynamicExclusion` sobre un objeto de tipo CountryStructure.

### Ejemplo
Cálculo el recorte dinámico simétrico (2, 2) la distribución de variaciones intermensuales ponderadas.

```julia-repl
julia> dynExfn = InflationDynamicExclusion(2, 2)
(::InflationDynamicExclusion) (generic function with 5 methods)
julia>dynExfn(gtdata) #gtdata es de tipo UniformCountryStructure
231×1 Matrix{Float32}:
 7.0480227
 7.3734045
 7.6767564
 7.6933146
 7.7317834
 ⋮
 1.0896564
 1.1332393
 1.0305643
 1.1091232
```
"""
Base.@kwdef struct InflationDynamicExclusion <: InflationFunction
    lower_factor::Float32
    upper_factor::Float32
end

# Método para recibir argumentos como par en una tupla.
InflationDynamicExclusion(factors::Tuple{Real, Real}) = InflationDynamicExclusion(
    convert(Float32, factors[1]),
    convert(Float32, factors[2])
)

# Método para recibir argumentos como par en una lista.
function (InflationDynamicExclusion)(factor_vec::Vector{<:Real})
    length(factor_vec) != 2 && return @error "Dimensión incorrectal del vector"
    InflationDynamicExclusion(
        convert(Float32, factor_vec[1]),
        convert(Float32, factor_vec[2])
    )
end

"""
    measure_name(inflfn::InflationDynamicExclusion)

Indica qué medida se utiliza para una instancia de una función de inflación.

# Ejemplo
```julia-repl
julia> dynExfn = InflationDynamicExclusion(2, 2)
julia> measure_name(dynExfn)
"Inflación de exclusión dinámica (2.0, 2.0)"
```
"""
function CPIDataBase.measure_name(inflfn::InflationDynamicExclusion)
    round_lower_factor, round_upper_factor = string.(
        round.(
            [inflfn.lower_factor, inflfn.upper_factor], digits = 2
        )
    )
    "Inflación de exclusión dinámica ($(round_lower_factor), $(round_upper_factor))"
end

"""
    measure_tag(inflfn::InflationDynamicExclusion)

Indica qué medida se utiliza para una instancia de una función de inflación.

# Ejemplo
```julia-repl
julia> dynExfn = InflationDynamicExclusion(2, 2)
julia> measure_tag(dynExfn)
"DynEx(2.0,2.0)"
```
"""
# Extendemos `params`, que devuelve los parámetros de la medida de inflación
CPIDataBase.params(inflfn::InflationDynamicExclusion) = (inflfn.lower_factor, inflfn.upper_factor)

# Las funciones sobre VarCPIBase resumen en variaciones intermensuales
function (inflfn::InflationDynamicExclusion)(base::VarCPIBase)
    lower_factor = inflfn.lower_factor
    upper_factor = inflfn.upper_factor

    std_v = std(base.v, dims = 2)
    mean_v = mean(base.v, dims = 2)

    dynEx_filter = (mean_v - (lower_factor .* std_v)) .<=
        base.v .<=
        (mean_v + (upper_factor .* std_v))

    dynEx_w = base.w' .* dynEx_filter
    dynEx_w = dynEx_w ./ sum(dynEx_w, dims = 2)

    dynEx_v = sum(
        base.v .* dynEx_w,
        dims = 2
    ) |> vec
end


function smacof(Δ::AbstractMatrix{T}; p = 2, wt = nothing, Xinit = nothing, anchors = nothing, ε = 1e-6, itmax = 100, verbose = false, return_hist::Bool = false) where T
    n = size(Δ, 1)

    # Check if Δ is a dissimilarity matrix.
    !issymmetric(Δ) && error("First argument is not a dissimilarity matrix.")

    # Check if anchors is correct dims
    if !isnothing(anchors)
        if isa(anchors, NamedTuple)
            if size(anchors.pos) != (n, p)
                error("`anchors` has incorrect dimensions. $(size(anchors.pos)) != ($n, $p)")
            end
        else
            if size(anchors) != (n, p)
                error("`anchors` has incorrect dimensions. $(size(anchors)) != ($n, $p)")
            end
        end
    end

    # Normalize weights
    if isnothing(wt)
        wt = ones(n, n)
        Vinv = I(n) / n
    else
        V = - copy(wt)
        for i in 1:size(V, 1)
            V[i, i] = -sum(view(V, :, i))
        end
        Vinv = pinv(V)
    end

    # Use classical scaling to get initial mapping if Xinit is not provided
    X = isnothing(Xinit) ? classical_scaling(Δ, p) : Xinit
    return_hist && (Xhist = [X])
    D = dist(X)
    B = zeros(T, size(Δ))
    update!(B, Δ, D, wt)
    σ = zeros(T, itmax + 1)
    σ[1] = stress(Δ, D, wt)

    Xcache = zeros(size(X))
    for i in 1:itmax
        log_output(i, σ, verbose)
        # Gutman transform
        mul!(Xcache, B, X)
        mul!(X, Vinv, Xcache)
        return_hist && push!(Xhist, copy(X))
        dist!(D, X)
        σ[i + 1] = stress(Δ, D, wt)
        abs(σ[i] - σ[i + 1]) < ε * σ[i] && break
        update!(B, Δ, D, wt)
    end

    # Use anchors if provided
    align!(X, anchors)
    return_hist ? (hist = Xhist, σ = σ) : X
end

function update!(B, Δ, D, W)
    for ij in eachindex(B)
        B[ij] = W[ij] * Δ[ij] / (D[ij] + eps())
    end

    for i in 1:size(B, 1)
        B[i, i] = 0.0
        B[i, i] = -sum(view(B, :, i))
    end
end


export particlefilter

function particlefilter(hmm::HMM, observations::Matrix{Float}, N::Int,
                        proposal::Proposal;
                        resampling::Function=multinomialresampling,
                        essthresh::Float=0.5
                        )::Tuple{ParticleSet,Vector{Float}}

    K   = size(observations, 2)
    # particle set filter (storage)
    psf = ParticleSet(N, hmm.dimx, K)
    ess = zeros(K)

    (p1,e1) = resample( Particles(
                            [proposal.mu0 + proposal.noise() for i in 1:N],
                            ones(N)/N),
                        essthresh )
    # store
    psf.p[1] = p1
    ess[1]   = e1

    for k=2:K
        pkm1 = psf.p[k-1]
        obsk = observations[:,k]

        logak = zeros(N)
        xk    = similar(pkm1.x)
        # sample (BOOTSTRAP)
        for i in 1:N
            xk[i]    = proposal.mean(k, pkm1.x[i], obsk) + proposal.noise()
            logak[i] = hmm.transloglik(k, pkm1.x[i], xk[i]) +
                        hmm.obsloglik(k, obsk, xk[i]) -
                        proposal.loglik(k, pkm1.x[i], obsk, xk[i])
        end
        Wk  = log.(pkm1.w) + logak
        Wk -= minimum(Wk) # try to avoid underflows
        wk  = exp.(Wk)
        wk /= sum(wk)

        (pk, ek) = resample(Particles(xk,wk), essthresh, resampling)

        psf.p[k] = pk
        ess[k]   = ek
    end
    (psf, ess)
end


## Utilities for working with the bases.

"""
    num_vectors(N::Int, K::Int)

Compute the number of vectors in a basis with `N` particles and `K` sites.
"""
num_vectors(N::Int, K::Int) = binomial(N+K-1, K-1)

# Global cache of generalized Pascal's triangles for restricted num_vectors.
const triangles = Dict{Int, Matrix{Int}}()

"""
    num_vectors(N::Int, K::Int, M::Int)

Compute the number of vectors in a basis with `N` particles and `K` sites, and
a limit of `M` particles per site.
"""
function num_vectors(N::Int, K::Int, M::Int)
    0 <= N <= M * K || return 0
    N == K == 0 && return 1

    # Create a new triangle.
    if !haskey(triangles, M)
        triangles[M] = zeros(Int, 1, 1)
        triangles[M][1, 1] = 1
    end

    # Extend an existing triangle.
    if size(triangles[M], 1) < K + 1
        t_old = triangles[M]
        K_old = size(t_old, 1) - 1
        t = zeros(Int, K+1, M*K+1)
        for k in 0:K_old
            for n in 0:(M*k)
                t[k+1, n+1] = t_old[k+1, n+1]
            end
        end
        for k in (K_old+1):K
            for n in 0:(M*k)
                for m in 0:min(M, n)
                    t[k+1, n+1] += t[k, n+1-m]
                end
            end
        end
        triangles[M] = t
    end

    triangles[M][K+1, N+1]
end

"""
    num_vectors(basis::AbstractSzbasis, N::Int, K::Int)

Compute the number of vectors in a basis with `N` particles and `K` sites, and
a limit of as many particles per site as in `basis`.
"""
num_vectors(::Szbasis, N::Int, K::Int) = num_vectors(N, K)
num_vectors(basis::RestrictedSzbasis, N::Int, K::Int) = num_vectors(N, K, basis.M)

"""
    site_max(basis::AbstractSzbasis)

Get the maximum number of particles on a site in `basis`.
"""
site_max(basis::Szbasis) = basis.N
site_max(basis::RestrictedSzbasis) = basis.M

"""
    serial_num(K::Int, N::Int, v::AbstractVector{Int})

Compute the serial number of occupation vector `v` in a basis with `K` sites
and `N` particles.
"""
function serial_num(K::Int, N::Int, v::AbstractVector{Int})
    I = 1

    for mu in 1:K
        s = 0
        for nu in (mu+1):K
            s += v[nu]
        end
        for i in 0:(v[mu]-1)
            I += num_vectors(N-s-i, mu-1)
        end
    end

    I
end

"""
    serial_num(K::Int, N::Int, M::Int, v::AbstractVector{Int})

Compute the serial number of occupation vector `v` in a basis with `K` sites
and `N` particles, and a limit of `M` particles per site.
"""
function serial_num(K::Int, N::Int, M::Int, v::AbstractVector{Int})
    I = 1

    for mu in 1:K
        s = 0
        for nu in (mu+1):K
            s += v[nu]
        end
        for i in 0:(v[mu]-1)
            I += num_vectors(N-s-i, mu-1, M)
        end
    end

    I
end

"""
    serial_num(basis::AbstractSzbasis, v::AbstractVector{Int})

Compute the serial number of occupation vector `v` in `basis`.
"""
serial_num(basis::Szbasis, v::AbstractVector{Int}) = serial_num(basis.K, basis.N, v)
serial_num(basis::RestrictedSzbasis, v::AbstractVector{Int}) = serial_num(basis.K, basis.N, basis.M, v)

"""
    serial_num(basis::AbstractSzbasis, K::Int, N::Int, v::AbstractVector{Int})

Compute the serial number of occupation vector `v` in a basis with `N`
particles and `K` sites, and a limit of as many particles per site as in
`basis`.
"""
serial_num(::Szbasis, K::Int, N::Int, v::AbstractVector{Int}) = serial_num(K, N, v)
serial_num(basis::RestrictedSzbasis, K::Int, N::Int, v::AbstractVector{Int}) = serial_num(K, N, basis.M, v)

"""
    sub_serial_num(basis::AbstractSzbasis, v::AbstractVector{Int})

Compute the serial number of the reduced occupation vector `v`, which has only
a subset of the sites present in `basis`.
"""
function sub_serial_num(::Szbasis, v::AbstractVector{Int})
    K = length(v)
    N = sum(v)

    # Only one way to have no sites.
    K >= 1 || return 1
    # Only one way to have no particles.
    N >= 1 || return 1

    # Count the zero-particle case.
    I = 1

    for n in 1:(N-1)
        I += num_vectors(n, K)
    end

    I + serial_num(K, N, v)
end

function sub_serial_num(basis::RestrictedSzbasis, v::AbstractVector{Int})
    K = length(v)
    N = sum(v)

    # Only one way to have no sites.
    K >= 1 || return 1
    # Only one way to have no particles.
    N >= 1 || return 1

    # Count the zero-particle case.
    I = 1

    for n in 1:(N-1)
        I += num_vectors(n, K, basis.M)
    end

    I + serial_num(K, N, basis.M, v)
end