### A Pluto.jl notebook ###
# v0.17.7

using Markdown
using InteractiveUtils

# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error).
macro bind(def, element)
    quote
        local iv = try Base.loaded_modules[Base.PkgId(Base.UUID("6e696c72-6542-2067-7265-42206c756150"), "AbstractPlutoDingetjes")].Bonds.initial_value catch; b -> missing; end
        local el = $(esc(element))
        global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : iv(el)
        el
    end
end

# ╔═╡ 87e78056-fecc-4b15-a723-16f51fa19353
using DataFrames, PlutoUI,HypertextLiteral, Statistics

# ╔═╡ 0ebe3384-97aa-11ec-17b5-cbf23568df03
md" # 2 Mathematical characterization of systems

As a first approximatiomn we can focus on a steady state characterization.

## 2.1 Ecological Systems

In fact, diversity is related to robustness and adaptation, because if some part o the system perform different tasks, the sayste itself may respond better to sudden environmental changes and laugh changes, as occurs in ecosystems

How do we measure this diversity? is a system composed of parts that are almost identical a diverse system? Is a bag of apples diverse? Depends of what are we looking for, Therefore the measure of diversity is going to depend on the the of system we are dealing with.

Lets say we want to measure size diversity in a population of bacteria, We can just use standard deviation, Large std, very diverse, small std, no diversity. But lets say now that we focus on the genetic information of a system composed on foxes and  rabbits. ,, there is no way we can quantify the difference in the genes  based on standard deviation (standard deviation of what, the length of the genome,

"

# ╔═╡ 943c55a4-00d2-4cb7-bba0-a6c46b30e0fa
md"Suppose a biologist wants to measure the diversity of species in a local forest. She collects the following data: "

# ╔═╡ 8ad237fa-8a09-4bae-80f4-ba1298c09581
begin
	dog_slide = @bind 🐶 html"<input type=range min=0 max=100 step=1>"
	mouse_slide = @bind 🐭 html"<input type=range min=0 max=100 step=1>"
	cat_slide = @bind 😺 html"<input type=range min=0 max=100 step=1>"
	fox_slide = @bind 🦊 html"<input type=range min=0 max=100 step=1>"
	bear_slide = @bind 🐻 html"<input type=range min=0 max=100 step=1>"

	md"""
	**How many molecules do you have?**

	Amount of dogs: $(dog_slide)

	Amount of dogs: $(mouse_slide)

	Amount of cats: $(cat_slide)

	Amount of foxes: $(fox_slide)

	Amount of bears: $(bear_slide)
	"""
end

# ╔═╡ bee1dd58-c8e6-4fa6-8eae-7cd4cca0a466
values_animals=DataFrame("Species" => ["Dog", "Mouse", "Cat",  "Fox",  "Bear"],"Frequency" => [🐶, 🐭, 😺, 🦊, 🐻])

# ╔═╡ 68713a25-a962-47e2-8830-c5e5c0511b43
md"Next, she can calculate the total number of organisms"

# ╔═╡ 279b1402-f4ba-4a3b-9d7d-82ac81b515e4
 total_number_of_organisms=sum(values_animals.Frequency)

# ╔═╡ 4190734c-4c6e-4662-9be9-2c19c4956a38
md"A first measure is the Richness $R$, i.e., how many species do we have "

# ╔═╡ 9981448a-edee-435f-9d2b-ec3271c4016a
R=size(values_animals.Frequency,1)

# ╔═╡ 0dbc209e-91dd-4569-afe9-b6407bdab995
md" For ecosystems, or systems composed of a number of individuals from species, we often use the Dominance Index:

```math
D=\frac{\sum n_i(n_i-1)}{N(N-1)}
```

where $n_i$ is the number of organisms in a species i, and N is the total number of organisms

"

# ╔═╡ c3606f18-d301-44d8-8765-7746bb6493c0
N=sum(values_animals.Frequency)

# ╔═╡ 34a569b4-3f88-4b5f-928b-0edd40c4d6ef
md"the Dominace index is"

# ╔═╡ 3989ead3-2760-443f-80e8-21ac961e2ff5
D = (sum((values_animals.Frequency).* ((values_animals.Frequency) .- 1)) / (N*(N-1)))

# ╔═╡ 7085e737-82db-4879-9c2e-96c6f1f146f0
md"Another highly used idex is the Simpson’s Index of Diversity, which is simply

```math
S=1-D=1-\frac{\sum n_i(n_i-1)}{N(N-1)}
```

"

# ╔═╡ 7bfac697-d85b-4a60-b90a-2a69c1155f5c
S = 1 - D

# ╔═╡ c71435ea-4bca-4bf7-ba90-576563385b33
md" Species __evenness__ refers to how close in numbers each species in an environment is. Mathematically it is defined as a diversity index, a measure of biodiversity which quantifies how equal the community is numerically. So if there are 40 foxes and 1000 dogs, the community is not very even.

Simpson evenness is closely related to Simpson diversity. It is actually just the Inverse Simpson Diversity with the richness added to the denominator, to mitigate the effect of species count on the range of the metric.
```math
E = \frac{1}{D * R}
```
"

# ╔═╡ 8f31e31d-1080-4b22-b508-b71bf30c2ec0
E= 1/ D / R

# ╔═╡ aa33422d-a8cb-46ed-8e11-d70cfd7caf36
@htl("""

Exercise: Measure the Richness, the dominance, teh diversity and the evenness of the followin systems. Discuss and compare the values obtained between the three systems:

- Which one is more diverse?

- Which one is more even?

- which one is more rich?

""")

# ╔═╡ 721ad120-8999-4f9f-9bd3-b9d966246436
values_animals_2=DataFrame("Species" => ["A", "B", "C",  "D",  "E", "F", "G", "H", "I", "J"],"Frequency" => [10,9, 11, 10, 8, 12, 10,11,10,9])

# ╔═╡ 8de6461d-e9ba-46df-a347-a7648b6f6e6a
values_animals_3=DataFrame("Species" => ["A", "B", "C",  "D",  "E", "F", "G", "H", "I", "J"],"Frequency" => [72,9, 11, 10, 8, 12, 10,11,10,9])

# ╔═╡ 8a2c17b6-3f5c-4f9f-9714-09df2944dd50
values_animals_4=DataFrame("Species" => ["A", "B", "C",  "D",  "E", "F", "G", "H", "I", "J"],"Frequency" => [33,33, 34, 0, 0, 0, 0, 0,0,0])

# ╔═╡ b20cdd19-08c4-4837-947f-97594b8822d2
md" ## 2.2 Graph Theory
As we mentioned, the important feature of the system comes from the interactions between the parts. Now we will see some tools to study how the parts of a system are interacting and connected.

- Proteins can act as transcription factors for other genes, forming complex networks that ultimately set the identity of a cell.

- When some node of the network changes (for instance, due to mutations), the behavior of a cell can change (leading to genetic diseases, such as cancer).

- Therefore, to understand and treat genetic diseases, we need  to know the genes involved, how they interact with other genes, and how they ultimately affect cellular responses.

"

# ╔═╡ 73f4fd5b-4611-4d90-8f05-af7b279dd86a
md" ### - Type of Networks
The structure of a network determines many relevant aspects of its function.
Network architecture is established by topological and statistical analyses.
Networks can be studied based on its global properties, (size, connectivity, robustness…)

Depending of the nature of the links between nodes ewe can classify networks in:

- Undirected: links between nodes have no direction (Facebook)
- Directed: links between nodes have direction (Twitter)
- Directed with self-links: Feedback loops (Gene regulatory networks)

[![EC1wSj.md.png](https://iili.io/EC1wSj.md.png)](https://freeimage.host/i/EC1wSj)
"

# ╔═╡ bfa2aecf-b7a8-4d68-aa1b-82d29032bd70
tree_url = "https://er.yuvayana.org/wp-content/uploads/sites/11/2016/02/Binary-Tree-network-model.jpg"

# ╔═╡ 40f52fcb-4be1-40c1-8720-ee48433473f3
md" ### - Size of Networks
The size of a network can refer to the number of nodes N or the number of links E.
The number of links is minimal in a tree network

$(Resource(tree_url))

"

# ╔═╡ af0276a5-322a-4a7f-9457-7e58524c5cbd
md"The minimal number of links E in a network of N nodes $E_{min}$ = N-$1$
The maximum number of links depends of the type of network
For undirected

```math
E_{max}=\binom{N}{2}=\frac{N!}{2!(N-2)!)}=\frac{N(N-1)(N-2)...1}{2\cdot 1 \cdot (N-2)(N-3)...1}=\frac{N(N-1)}{2}
```

For directed

```math
\begin{align*}
E_{max}=2\binom{N}{2}=N+\frac{2 \cdot N!}{2!(N-2)!)}=\\
\\
E_{max}=\frac{2 \cdot N(N-1)(N-2)...1}{2\cdot 1 \cdot (N-2)(N-3)...1}=\\
\\
E_{max}=N(N-1)
\end{align*}
```

For directed with self-links

```math
\begin{align*}
E_{max}=N+2\binom{N}{2}=N+\frac{2 \cdot N!}{2!(N-2)!)}=\\
\\
E_{max}=N+\frac{2 \cdot N(N-1)(N-2)...1}{2\cdot 1 \cdot (N-2)(N-3)...1}=\\
\\
E_{max}=N+N(N-1)=N(1+N-1)=N^2
\end{align*}
```

"

# ╔═╡ d4c87e16-8dff-4818-9bfc-df89c372a498
md" ### - Density of Networks

The density D of a network of N nodes is defined as a ratio of the number of links E to the number of possible links $E_{max}$

```math
\begin{align*}
D=\frac{E-(N-1)}{E_{max}-(N-1)}
\end{align*}
```

"

# ╔═╡ 477ba0d8-1dd9-46c8-bff0-72e4bfa89238
md"Exercise, draw two networks (one directed and one undirected) and calculate the density, which one is mode dense? "

# ╔═╡ b5174a24-428e-4a41-9b85-fccc7bcf8d32
path_url = "https://cdncontribute.geeksforgeeks.org/wp-content/uploads/exampleFigure-1.png"

# ╔═╡ 6551021e-e6d4-4d34-af80-efd8a7b3010d
md" ### - Average shortest path

In a real network like the Internet, a short average path length facilitates the quick transfer of information and reduces costs

- $(Resource(path_url))

Input: source node = $0$ and destination node is = $7$.

Output: Shortest path length is:$2$ -> [0 3 7]

Input: source vertex is = $2$ and destination vertex is = $6$

Output: Shortest path length is:$5$ -> [2 1 0 3 4 6]
"

# ╔═╡ 7fd4ffe4-6143-43f6-b75c-c76eb831108e
@htl("""

Exercise: Measure the Richness, the dominance, teh diversity and the evenness of the followin systems. Discuss and compare the values obtained between the three systems:

""")

# ╔═╡ ac539912-861a-4779-bac6-79c001177faa
Distance_to_1=DataFrame("Species" => ["1->2", "1->0", "1->3",  "1->7",  "1->4", "1->6", "1->5"],"Distance" => [1,1,2,3,3,4,4])

# ╔═╡ f11db50b-6aa9-4b38-a1c5-7fc217c55e95
md"The average shortest distance to node 1 is $(mean(Distance_to_1.Distance))"

# ╔═╡ f5673e80-8f26-4e1b-adfc-e9ec84f18c5b
@htl("""

Exercise: Measure the Richness, the dominance, teh diversity and the evenness of the followin systems. Discuss and compare the values obtained between the three systems:

""")

# ╔═╡ 4ace57bb-d9be-4681-8f7e-61982bb34b1c
Distance_to_2=DataFrame("Species" => ["2->1", "2->0", "2->3",  "2->7",  "2->4", "2->5", "2->6"],"Distance" => [1,2,3,4,4,5,5])

# ╔═╡ d4d3ca6e-8a3b-42fd-afd3-0a6d1c40a58e
md"The average shortest distance to node 2 is $(mean(Distance_to_2.Distance))"

# ╔═╡ 45a3e3e1-53c0-4632-8eab-b67f691a0ac8
@htl("""

""")

# ╔═╡ 9179e620-b8ff-4559-b864-c9729a3a42ec
Distance_to_0=DataFrame("Species" => ["0->2", "0->1", "0->3",  "0->7",  "0->4", "0->5", "0->6"],"Distance" => [2,1,1,2,2,3,3])

# ╔═╡ 9c2a3452-8c7f-4f59-877f-a1c585107e0c
md"The average shortest distance to node 2 is $(mean(Distance_to_0.Distance)). Central nodes have a shortest distance to everyone else in teh network than nodes in the boundaries. It is likely that information travels through them."

# ╔═╡ 76ece02b-8170-487b-8e0b-8e32b579b131
md"## Adjacency Matrix: Matrix Representation of Networks

A = binary square matrix of $N$*$N$ elements.
$A_{ij}$ = 1 if there exists an interaction between $i$ and $j$
$A_{ij}$ = 0 if not.

- undirected graph
```math
A_{ij}  = A_{ji}, A_{ii} = 0
```
- directed graph
```math
A_{ij} \ne A_{ji}, A_{ii} = 0,
```

- directed graph with self-links
```math
A_{ij} \ne A_{ji}, A_{ii} \ne 0,
```

Example 1: Draw and characterize the network that correspodns to the following graph,:

```math
\begin{equation*}
A_{i,j} =
\begin{pmatrix}
0 & 1 & 0 & 1 \\
1 & 0 & 1 & 0 \\
0  & 1  & 0 & 1  \\
1 & 0 & 1 &0
\end{pmatrix}
\end{equation*}
```

Example 2: Draw and characterize the the network that correspodns to the following graph:

```math
\begin{equation*}
A_{i,j} =
\begin{pmatrix}
0 & 1 & 0 & 0 \\
0 & 0 & 0 & 1 \\
1  & 0  & 0 & 1  \\
0 & 1 & 0 & 0
\end{pmatrix}
\end{equation*}
```

Example 3: Draw and characterize the the network that correspodns to the following graph:

```math
\begin{equation*}
A_{i,j} =
\begin{pmatrix}
0 & 1 & 0 & 1 \\
1 & 0 & 1 & 1 \\
0  & 1  & 0 & 0  \\
1 & 1 & 0 & 1
\end{pmatrix}
\end{equation*}
```

"

# ╔═╡ ddd62a2f-b19d-41b7-8b56-3c279f9c61ab
md" ### - Degree of Networks
Another more direct measure of how connect are the different nodes is the number of links they received. The degree $k$ of a node is the number of connections the node has to other nodes. If a network is directed, such as GRN, nodes have two different degrees:
- in-degree $k_{in}$: the number of incoming edges, and the
- out-degree $k_{out}$:  the number of outgoing edges
- total-degree $k_{tot}$ = $k_{in}$ + $k_{out}$:

[![EC1wSj.md.png](https://iili.io/EC1wSj.md.png)](https://freeimage.host/i/EC1wSj)

_Undirected_:
- degree of node 1 is 2
- degree of node 2 is 2
- degree of node 3 is 3
- degree of node 4 is 1

In an undirected network the total number of links, E, can be expressed as the sum of the node degrees

```math
\begin{align*}
E=\frac{1}{2}\sum_{i=1}^{N} k_i
\end{align*}
```

the 1/2 factor corrects for the fact that in the sum (2.1) each link is counted twice. For example, the link connecting the nodes 2 and 3 will be counted once in the degree of node 2 and once in the degree of node 3

_Directed_:

- In-degree of node 1 is 0
- In-degree of node 2 is 2
- Out-degree of node 1 is 2
- Out degree of node 2 is 0

In a directed network the total number of links, E, can be expressed as the sum of all node in-degrees or out-degrees

```math
\begin{align*}
E=\sum_{i=1}^{N} k_{in} = \sum_{i=1}^{N} k_{out}
\end{align*}
```

The __in__ degrees are:

```math
\begin{align*}
k^{in}_1 = 0\\
k^{in}_2 = 2\\
k^{in}_3 = 2\\
k^{in}_4 = 1
\end{align*}
```

The _out_ degrees are:
```math
\begin{align*}
k^{out}_1 = 2\\
k^{out}_2 = 0\\
k^{out}_3 = 2\\
k^{out}_4 = 1
\end{align*}
```

So, for this particular network:
```math
\begin{align*}
E=\sum_{i=1}^{N} k_{in} = \sum_{i=1}^{N} k_{out} =5
\end{align*}
```
"

# ╔═╡ 2db33033-cb6c-4e5b-b952-c9f06c98def0
md"Using the adjacency matrix, the degree is the sum of the row

For undirected:
```math
\begin{align*}
k_1=\sum_{i=1}^{N} A_{ij}
\end{align*}
```

For directed
```math
\begin{align*}
k_{1}^{out}=\sum_{i=1}^{N} A_{ij}\\
k_{1}^{in}=\sum_{j=1}^{N} A_{ij}
\end{align*}
```

For weighted
```math
\begin{align*}
k_{1}^{out}=\sum_{i=1}^{N} A_{ij}\\
k_{1}^{in}=\sum_{j=1}^{N} A_{ij}
\end{align*}
```
"

# ╔═╡ 979d1f55-870f-424c-a488-71a369c1fd8b
degree_distribution_url = "https://www.researchgate.net/profile/Daniele-Condorelli/publication/242112667/figure/fig3/AS:669311584710678@1536587725639/An-important-graph-property-is-the-degree-distribution-function-P-k-that-describes_W640.jpg"

# ╔═╡ b41588c9-c52c-4e85-ad1d-2775d143bd06
md" ## Average degree

The average number of links per node. Gives you a basic measurement of how the network is connected in average.

In undirected networks:

```math
\begin{align*}
\left \langle k \right \rangle = \frac{k_1 + k_2+ k_3 + ... k_N}{N}=\frac{1}{N}\sum_{i=1}^{N}k_i=\frac{2E}{N}
\end{align*}
```

In directed networks:

```math
\begin{align*}
\left \langle k^{in}_i \right \rangle =\frac{1}{N}\sum_{i=1}^{N}k^{in}_i=\left \langle k^{out}_i \right \rangle =\frac{1}{N}\sum_{i=1}^{N}k^{out}_i=\frac{E}{N}
\end{align*}
```

using the notation of the adjacency matrix:
```math
\begin{align*}
\left \langle k \right \rangle =  \frac{ \sum_{i=j}^{N} \sum_{i=1}^{N} A_{ij}} {N}
\end{align*}
```

Exercise: find de average degree of the folliwng networks:

[![EC1wSj.md.png](https://iili.io/EC1wSj.md.png)](https://freeimage.host/i/EC1wSj)

For the undirected: $\left \langle k \right \rangle = (2+2+3+1)/ 4 = 2$

For the directed: $\left \langle k_{in} \right \rangle = (2+2+3+1)/ 4 = 2$

"

# ╔═╡ 010b6033-1546-478a-8a9f-c26cf3f194fe
@htl("""

""")

# ╔═╡ a44b48e7-5497-4d49-a4d7-64508ecb5c3a
adjacency_url="https://www.ebi.ac.uk/training/online/courses/network-analysis-of-protein-interaction-data-an-introduction/wp-content/uploads/sites/64/2020/08/new-fig-4.png"

# ╔═╡ f938eda7-cefc-4ea3-991a-094b918d0430
md" $(Resource(adjacency_url))"

# ╔═╡ f9f7a01d-fbcf-4e5d-9cab-152eff279e5a
types_of_networks_url="https://www.researchgate.net/publication/343839881/figure/fig2/AS:928266965774337@1598327499590/Four-types-of-networks-in-the-scale-free-network-the-white-and-striped-nodes-represent_W640.jpg"

# ╔═╡ d580711c-2aff-404c-9aa9-84d56759eac9
md" ## Degree Distribution

The degree distribution $p_k$ provides the probability that a randomly selected node in the network has degree $p_{k}$.
Since $p_{k}$ is a probability, it must be normalized, i.e.

```math
\begin{align*}
\sum_{k=1}^{N} p_k =1
\end{align*}
```

P(k) is the probability distribution of the degrees over the whole network.  pk of a network is defined as the fraction of nodes in the network with degree $k_i$. Thus if there are N nodes in total in a network and M of them have degree k, we have $p_{k}$ = M/N.

The degree distribution is probably the most important feature of a network, since it determines many properties of how the network acts. So, the number of nodes in a network with degree k can be obtained by:

```math
\begin{align*}
N_k = N \cdot p_k
\end{align*}
```

And the average degree can be also calculated as:

```math
\begin{align*}
\left \langle k \right \rangle =\frac{1}{N}\sum_{i=1}^{N}k_i=\sum_{k=0}^{\infty }k \cdot p_k
\end{align*}
```
We can characterize the networks based on the degree distribution:
$(Resource(types_of_networks_url))

- __Regular networks and trees__ (usually man-made, have the lowest heterogeneity (e.g. the number of connections each node has is more or less the same)

- __small-world networks__ higher clustering and almost the same average path than the random networks with the same number of nodes and edges. high modularity, high herogeneity

- __Scale-free networks__ a highly heterogeneous degree distribution (power-law degree distribution)

- __Random networks__, nodes paired with uniform probability (low heterogeneity , since most nodes have the same number of connections), the degree distribution will be a Gaussian bell-shaped curve.

$(Resource(degree_distribution_url))

The typical plot of the degree distribution of a random network as a maximum, which correspond to the characteristic scale of the network. For example, in this network, the characteristic scale is around 8, since there is a higher number of nodes with that number of connections.

Biological gene regulatory networks have a degree distribution very different from random. There is no characteristic scale (no node with a more common number of connections, no maximum in the degree distribution plot). Many nodes poorly connected, very few nodes (hubs) highly connected.

Degree distributions are often represented in log-log plot. These types of networks are called power-law or scale free networks

Some nonbiological networks also show a degree distribution that is close to scale free. Above we plot the degree distribution of some of them.

(A) Actor collaboration graph with N = 212,250,〈k〉 = 28.78.
(B) Small sample of the World Wide Web, N = 325,729, 〈k〉 = 5.46.
(C) Power grid data, N = 4941, 〈k〉 = 2.67

What does it mean that so many real networks have a power law (scale free) degree distribution?
As opposed to random networks, scale-free networks do not have a characteristic scale, meaning that there is no typical node in the network that represents the degree for the other nodes.

### - 2.3 Conclusion:
The study of Networks is a very big, active and interesting field. The characteristics of networks of interactions are quite useful to understand key properties of large systems where their parts are interacting (big data…). Biological networks are a perfect systems to study using this formalism of network theory. It is well known that most of biologically occurring networks  have a degree distribution close to scale free, characterized by the presence of large hubs:

These power law distributions have the same functional form at all scales.
Why so many different systems have the same scale free degree distribution?

This is quite difficult question to try to explain in such a basic course. But the current agreement is that Scale Free networks often apear in  in systems where you have two properties combined:

 They grow in size (new nodes and links added). They started from small networks and became large by adding new nodes (as oppose to networks were all the links are established at the same time, i.e., a group in WhatsApp)
There is some sort of “preferential attachment”, meaning that there is some popular nodes that every one wants to get information from (rich gets richer)

Both social networks and GRN have these two properties.

"

# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
HypertextLiteral = "ac1192a8-f4b3-4bfe-ba22-af5b92cd3ab2"
PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"

[compat]
DataFrames = "~1.3.2"
HypertextLiteral = "~0.9.3"
PlutoUI = "~0.7.38"
"""

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

julia_version = "1.7.1"
manifest_format = "2.0"

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

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

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

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

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

[[deps.Compat]]
deps = ["Base64", "Dates", "DelimitedFiles", "Distributed", "InteractiveUtils", "LibGit2", "Libdl", "LinearAlgebra", "Markdown", "Mmap", "Pkg", "Printf", "REPL", "Random", "SHA", "Serialization", "SharedArrays", "Sockets", "SparseArrays", "Statistics", "Test", "UUIDs", "Unicode"]
git-tree-sha1 = "96b0bc6c52df76506efc8a441c6cf1adcb1babc4"
uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
version = "3.42.0"

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

[[deps.Crayons]]
git-tree-sha1 = "249fe38abf76d48563e2f4556bebd215aa317e15"
uuid = "a8cc5b0e-0ffa-5ad4-8c14-923d3ee1735f"
version = "4.1.1"

[[deps.DataAPI]]
git-tree-sha1 = "cc70b17275652eb47bc9e5f81635981f13cea5c8"
uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a"
version = "1.9.0"

[[deps.DataFrames]]
deps = ["Compat", "DataAPI", "Future", "InvertedIndices", "IteratorInterfaceExtensions", "LinearAlgebra", "Markdown", "Missings", "PooledArrays", "PrettyTables", "Printf", "REPL", "Reexport", "SortingAlgorithms", "Statistics", "TableTraits", "Tables", "Unicode"]
git-tree-sha1 = "ae02104e835f219b8930c7664b8012c93475c340"
uuid = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
version = "1.3.2"

[[deps.DataStructures]]
deps = ["Compat", "InteractiveUtils", "OrderedCollections"]
git-tree-sha1 = "3daef5523dd2e769dad2365274f760ff5f282c7d"
uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
version = "0.18.11"

[[deps.DataValueInterfaces]]
git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6"
uuid = "e2d170a0-9d28-54be-80f0-106bbe20a464"
version = "1.0.0"

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

[[deps.DelimitedFiles]]
deps = ["Mmap"]
uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab"

[[deps.Distributed]]
deps = ["Random", "Serialization", "Sockets"]
uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b"

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

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

[[deps.Formatting]]
deps = ["Printf"]
git-tree-sha1 = "8339d61043228fdd3eb658d86c926cb282ae72a8"
uuid = "59287772-0a20-5a39-b81b-1366585eb4c0"
version = "0.4.2"

[[deps.Future]]
deps = ["Random"]
uuid = "9fa8497b-333b-5362-9e8d-4d0656e87820"

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

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

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

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

[[deps.InvertedIndices]]
git-tree-sha1 = "bee5f1ef5bf65df56bdd2e40447590b272a5471f"
uuid = "41ab1584-1d38-5bbf-9106-f11c6c58b48f"
version = "1.1.0"

[[deps.IteratorInterfaceExtensions]]
git-tree-sha1 = "a3f24677c21f5bbe9d2a714f95dcd58337fb2856"
uuid = "82899510-4779-5014-852e-03e436cf321d"
version = "1.0.0"

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

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

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

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

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

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

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

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

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

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

[[deps.Missings]]
deps = ["DataAPI"]
git-tree-sha1 = "bf210ce90b6c9eed32d25dbcae1ebc565df2687f"
uuid = "e1d29d7a-bbdc-5cf2-9ac0-f12de2c33e28"
version = "1.0.2"

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

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

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

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

[[deps.OrderedCollections]]
git-tree-sha1 = "85f8e6578bf1f9ee0d11e7bb1b1456435479d47c"
uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
version = "1.4.1"

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

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

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

[[deps.PooledArrays]]
deps = ["DataAPI", "Future"]
git-tree-sha1 = "28ef6c7ce353f0b35d0df0d5930e0d072c1f5b9b"
uuid = "2dfb63ee-cc39-5dd5-95bd-886bf059d720"
version = "1.4.1"

[[deps.PrettyTables]]
deps = ["Crayons", "Formatting", "Markdown", "Reexport", "Tables"]
git-tree-sha1 = "dfb54c4e414caa595a1f2ed759b160f5a3ddcba5"
uuid = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d"
version = "1.3.1"

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

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

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

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

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

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

[[deps.SharedArrays]]
deps = ["Distributed", "Mmap", "Random", "Serialization"]
uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383"

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

[[deps.SortingAlgorithms]]
deps = ["DataStructures"]
git-tree-sha1 = "b3363d7460f7d098ca0912c69b082f75625d7508"
uuid = "a2af1166-a08f-5f64-846c-94a0d3cef48c"
version = "1.0.1"

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

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

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

[[deps.TableTraits]]
deps = ["IteratorInterfaceExtensions"]
git-tree-sha1 = "c06b2f539df1c6efa794486abfb6ed2022561a39"
uuid = "3783bdb8-4a98-5b6b-af9a-565f29a5fe9c"
version = "1.0.1"

[[deps.Tables]]
deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "LinearAlgebra", "OrderedCollections", "TableTraits", "Test"]
git-tree-sha1 = "5ce79ce186cc678bbb5c5681ca3379d1ddae11a1"
uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
version = "1.7.0"

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

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

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

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

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

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

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

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

# ╔═╡ Cell order:
# ╟─0ebe3384-97aa-11ec-17b5-cbf23568df03
# ╟─943c55a4-00d2-4cb7-bba0-a6c46b30e0fa
# ╠═87e78056-fecc-4b15-a723-16f51fa19353
# ╟─8ad237fa-8a09-4bae-80f4-ba1298c09581
# ╟─bee1dd58-c8e6-4fa6-8eae-7cd4cca0a466
# ╟─68713a25-a962-47e2-8830-c5e5c0511b43
# ╟─279b1402-f4ba-4a3b-9d7d-82ac81b515e4
# ╟─4190734c-4c6e-4662-9be9-2c19c4956a38
# ╠═9981448a-edee-435f-9d2b-ec3271c4016a
# ╟─0dbc209e-91dd-4569-afe9-b6407bdab995
# ╠═c3606f18-d301-44d8-8765-7746bb6493c0
# ╟─34a569b4-3f88-4b5f-928b-0edd40c4d6ef
# ╠═3989ead3-2760-443f-80e8-21ac961e2ff5
# ╟─7085e737-82db-4879-9c2e-96c6f1f146f0
# ╠═7bfac697-d85b-4a60-b90a-2a69c1155f5c
# ╟─c71435ea-4bca-4bf7-ba90-576563385b33
# ╠═8f31e31d-1080-4b22-b508-b71bf30c2ec0
# ╠═aa33422d-a8cb-46ed-8e11-d70cfd7caf36
# ╟─721ad120-8999-4f9f-9bd3-b9d966246436
# ╟─8de6461d-e9ba-46df-a347-a7648b6f6e6a
# ╟─8a2c17b6-3f5c-4f9f-9714-09df2944dd50
# ╟─b20cdd19-08c4-4837-947f-97594b8822d2
# ╟─73f4fd5b-4611-4d90-8f05-af7b279dd86a
# ╟─40f52fcb-4be1-40c1-8720-ee48433473f3
# ╟─bfa2aecf-b7a8-4d68-aa1b-82d29032bd70
# ╟─af0276a5-322a-4a7f-9457-7e58524c5cbd
# ╟─d4c87e16-8dff-4818-9bfc-df89c372a498
# ╟─477ba0d8-1dd9-46c8-bff0-72e4bfa89238
# ╟─b5174a24-428e-4a41-9b85-fccc7bcf8d32
# ╟─6551021e-e6d4-4d34-af80-efd8a7b3010d
# ╟─7fd4ffe4-6143-43f6-b75c-c76eb831108e
# ╟─ac539912-861a-4779-bac6-79c001177faa
# ╟─f11db50b-6aa9-4b38-a1c5-7fc217c55e95
# ╟─f5673e80-8f26-4e1b-adfc-e9ec84f18c5b
# ╟─4ace57bb-d9be-4681-8f7e-61982bb34b1c
# ╟─d4d3ca6e-8a3b-42fd-afd3-0a6d1c40a58e
# ╟─45a3e3e1-53c0-4632-8eab-b67f691a0ac8
# ╟─9179e620-b8ff-4559-b864-c9729a3a42ec
# ╟─9c2a3452-8c7f-4f59-877f-a1c585107e0c
# ╟─76ece02b-8170-487b-8e0b-8e32b579b131
# ╟─ddd62a2f-b19d-41b7-8b56-3c279f9c61ab
# ╟─2db33033-cb6c-4e5b-b952-c9f06c98def0
# ╟─979d1f55-870f-424c-a488-71a369c1fd8b
# ╟─b41588c9-c52c-4e85-ad1d-2775d143bd06
# ╟─010b6033-1546-478a-8a9f-c26cf3f194fe
# ╟─a44b48e7-5497-4d49-a4d7-64508ecb5c3a
# ╟─f938eda7-cefc-4ea3-991a-094b918d0430
# ╟─f9f7a01d-fbcf-4e5d-9cab-152eff279e5a
# ╟─d580711c-2aff-404c-9aa9-84d56759eac9
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002


# reshape test
x = create_var(6, 4)
y = reshape(x, 8, 3)
z = reshape(x, 4, 6)
v = vec(x)
test_expr(y, (x,))
test_expr(z, (x,))
test_expr(v, (x,))

info("All reshape tests passed")


#Array test

## basics

@test length([1, 2, 3]) == 3
@test nfilled([1, 2, 3]) == 3

a = ones(4)
b = a+a
@test b[1]==2. && b[2]==2. && b[3]==2. && b[4]==2.

@test length((1,)) == 1
@test length((1,2)) == 2

@test isequal(1.+[1,2,3], [2,3,4])
@test isequal([1,2,3].+1, [2,3,4])
@test isequal(1.-[1,2,3], [0,-1,-2])
@test isequal([1,2,3].-1, [0,1,2])

@test isequal(5*[1,2,3], [5,10,15])
@test isequal([1,2,3]*5, [5,10,15])
@test isequal(1./[1,2,5], [1.0,0.5,0.2])
@test isequal([1,2,3]/5, [0.2,0.4,0.6])

a = ones(2,2)
a[1,1] = 1
a[1,2] = 2
a[2,1] = 3
a[2,2] = 4
b = a'
@test a[1,1] == 1. && a[1,2] == 2. && a[2,1] == 3. && a[2,2] == 4.
@test b[1,1] == 1. && b[2,1] == 2. && b[1,2] == 3. && b[2,2] == 4.

a = Array(Float64, 2, 2, 2, 2, 2)
a[1,1,1,1,1] = 10
a[1,2,1,1,2] = 20
a[1,1,2,2,1] = 30

@test a[1,1,1,1,1] == 10
@test a[1,2,1,1,2] == 20
@test a[1,1,2,2,1] == 30

b = reshape(a, (32,))
@test b[1]  == 10
@test b[19] == 20
@test b[13] == 30

b = rand(32)
a = reshape(b, (2, 2, 2, 2, 2))
@test ndims(a) == 5
@test a[2,1,2,2,1] == b[14]
@test a[2,2,2,2,2] == b[end]

sz = (5,8,7)
A = reshape(1:prod(sz),sz...)
@test A[2:6] == [2:6]
tmp = A[1:3,2,2:4]
@test tmp == cat(3,46:48,86:88,126:128)
@test A[:,7:-3:1,5] == [191 176 161; 192 177 162; 193 178 163; 194 179 164; 195 180 165]
tmp = A[:,3:9]
@test tmp == reshape(11:45,5,7)
rng = (2,2:3,2:2:5)
tmp = zeros(Int,map(maximum,rng)...)
tmp[rng...] = A[rng...]
@test  tmp == cat(3,zeros(Int,2,3),[0 0 0; 0 47 52],zeros(Int,2,3),[0 0 0; 0 127 132])

x = rand(2,2)
b = x[1,:]
@test isequal(size(b), (1, 2))
b = x[:,1]
@test isequal(size(b), (2,))

x = rand(5,5)
b = x[2:3,2]
@test b[1] == x[2,2] && b[2] == x[3,2]

B = zeros(4,5)
B[:,3] = 1:4
@test B == [0 0 1 0 0; 0 0 2 0 0; 0 0 3 0 0; 0 0 4 0 0]
B[2,:] = 11:15
@test B == [0 0 1 0 0; 11 12 13 14 15; 0 0 3 0 0; 0 0 4 0 0]
B[[3,1],[2,4]] = [21 22; 23 24]
@test B == [0 23 1 24 0; 11 12 13 14 15; 0 21 3 22 0; 0 0 4 0 0]
B[4,[2,3]] = 7
@test B == [0 23 1 24 0; 11 12 13 14 15; 0 21 3 22 0; 0 7 7 0 0]

@test isequal(reshape(1:27, 3, 3, 3)[1,:], [1  4  7  10  13  16  19  22  25])

a = [3, 5, -7, 6]
b = [4, 6, 2, -7, 1]
ind = findin(a, b)
@test ind == [3,4]

rt = Base.return_types(setindex!, (Array{Int32, 3}, Uint8, Vector{Int}, Float64, Range1{Int}))
@test length(rt) == 1 && rt[1] == Array{Int32, 3}

# sub
A = reshape(1:120, 3, 5, 8)
sA = sub(A, 2, 1:5, :)
@test parent(sA) == A
@test parentindexes(sA) == (2:2, 1:5, 1:8)
@test Base.parentdims(sA) == [1:3]
@test size(sA) == (1, 5, 8)
@test_throws sA[2, 1:8]
@test sA[1, 2, 1:8][:] == [5:15:120]
sA[2:5:end] = -1
@test all(sA[2:5:end] .== -1)
@test all(A[5:15:120] .== -1)
@test strides(sA) == (1,3,15)
@test stride(sA,3) == 15
@test stride(sA,4) == 120
sA = sub(A, 1:3, 1:5, 5)
@test Base.parentdims(sA) == [1:2]
sA[1:3,1:5] = -2
@test all(A[:,:,5] .== -2)
sA[:] = -3
@test all(A[:,:,5] .== -3)
@test strides(sA) == (1,3)
sA = sub(A, 1:3, 3, 2:5)
@test Base.parentdims(sA) == [1:3]
@test size(sA) == (3,1,4)
@test sA == A[1:3,3,2:5]
@test sA[:] == A[1:3,3,2:5][:]
sA = sub(A, 1:2:3, 1:3:5, 1:2:8)
@test Base.parentdims(sA) == [1:3]
@test strides(sA) == (2,9,30)
@test sA[:] == A[1:2:3, 1:3:5, 1:2:8][:]

# sub logical indexing #4763
A = sub([1:10], 5:8)
@test A[A.<7] == [5, 6]
B = reshape(1:16, 4, 4)
sB = sub(B, 2:3, 2:3)
@test sB[sB.>8] == [10, 11]

# slice
A = reshape(1:120, 3, 5, 8)
sA = slice(A, 2, :, 1:8)
@test parent(sA) == A
@test parentindexes(sA) == (2, 1:5, 1:8)
@test Base.parentdims(sA) == [2:3]
@test size(sA) == (5, 8)
@test strides(sA) == (3,15)
@test sA[2, 1:8][:] == [5:15:120]
@test sA[:,1] == [2:3:14]
@test sA[2:5:end] == [5:15:110]
sA[2:5:end] = -1
@test all(sA[2:5:end] .== -1)
@test all(A[5:15:120] .== -1)
sA = slice(A, 1:3, 1:5, 5)
@test Base.parentdims(sA) == [1:2]
@test size(sA) == (3,5)
@test strides(sA) == (1,3)
sA = slice(A, 1:2:3, 3, 1:2:8)
@test Base.parentdims(sA) == [1,3]
@test size(sA) == (2,4)
@test strides(sA) == (2,30)
@test sA[:] == A[sA.indexes...][:]

a = [5:8]
@test parent(a) == a
@test parentindexes(a) == (1:4,)

# issue #4335
@test_throws slice(A, 1:2)
@test_throws slice(A, 1:2, 3:4)
@test_throws slice(A, 1:2, 3:4, 5:6, 7:8)

# Out-of-bounds construction. See #4044
A = rand(7,7)
rng = 1:4
sA = sub(A, 2, rng-1)
@test_throws sA[1,1]
@test sA[1,2] == A[2,1]
sA = sub(A, 2, rng)
B = sub(sA, 1, rng-1)
C = sub(B, 1, rng+1)
@test C == sA
sA = slice(A, 2, rng-1)
@test_throws sA[1]
@test sA[2] == A[2,1]
sA = slice(A, 2, rng)
B = slice(sA, rng-1)
C = sub(B, rng+1)
@test C == sA

# issue #6218 - logical indexing
A = rand(2, 2, 3)
msk = ones(Bool, 2, 2)
msk[2,1] = false
sA = sub(A, :, :, 1)
sA[msk] = 1.0
@test sA[msk] == ones(countnz(msk))

# get
let
    A = reshape(1:24, 3, 8)
    x = get(A, 32, -12)
    @test x == -12
    x = get(A, 14, -12)
    @test x == 14
    x = get(A, (2,4), -12)
    @test x == 11
    x = get(A, (4,4), -12)
    @test x == -12
    X = get(A, -5:5, nan(Float32))
    @test eltype(X) == Float32
    @test isnan(X) == [trues(6),falses(5)]
    @test X[7:11] == [1:5]
    X = get(A, (2:4, 9:-2:-13), 0)
    Xv = zeros(Int, 3, 12)
    Xv[1:2, 2:5] = A[2:3, 7:-2:1]
    @test X == Xv
    X2 = get(A, Vector{Int}[[2:4], [9:-2:-13]], 0)
    @test X == X2
end

## arrays as dequeues
l = {1}
push!(l,2,3,8)
@test l[1]==1 && l[2]==2 && l[3]==3 && l[4]==8
v = pop!(l)
@test v == 8
v = pop!(l)
@test v == 3
@test length(l)==2
unshift!(l,4,7,5)
@test l[1]==4 && l[2]==7 && l[3]==5 && l[4]==1 && l[5]==2
v = shift!(l)
@test v == 4
@test length(l)==4

# concatenation
@test isequal([ones(2,2)  2*ones(2,1)], [1. 1 2; 1 1 2])
@test isequal([ones(2,2), 2*ones(1,2)], [1. 1; 1 1; 2 2])

# typed array literals
X = Float64[1 2 3]
Y = [1. 2. 3.]
@test size(X) == size(Y)
for i = 1:3 @test X[i] === Y[i] end
X = Float64[1;2;3]
Y = [1.,2.,3.]
@test size(X) == size(Y)
for i = 1:3 @test X[i] === Y[i] end
X = Float64[1 2 3; 4 5 6]
Y = [1. 2. 3.; 4. 5. 6.]
@test size(X) == size(Y)
for i = 1:length(X) @test X[i] === Y[i] end

# "end"
X = [ i+2j for i=1:5, j=1:5 ]
@test X[end,end] == 15
@test X[end]     == 15  # linear index
@test X[2,  end] == 12
@test X[end,  2] == 9
@test X[end-1,2] == 8
Y = [2, 1, 4, 3]
@test X[Y[end],1] == 5
@test X[end,Y[end]] == 11

## find, findfirst ##
a = [0,1,2,3,0,1,2,3]
@test find(a) == [2,3,4,6,7,8]
@test find(a.==2) == [3,7]
@test find(isodd,a) == [2,4,6,8]
@test findfirst(a) == 2
@test findfirst(a.==0) == 1
@test findfirst(a.==5) == 0
@test findfirst([1,2,4,1,2,3,4], 3) == 6
@test findfirst(isodd, [2,4,6,3,9,2,0]) == 4
@test findfirst(isodd, [2,4,6,2,0]) == 0

## findn ##

b = findn(ones(2,2,2,2))
@test (length(b[1]) == 16)
@test (length(b[2]) == 16)
@test (length(b[3]) == 16)
@test (length(b[4]) == 16)

#hand made case
a = ([2,1,2],[1,2,2],[2,2,2])
z = zeros(2,2,2)
for i = 1:3
    z[a[1][i],a[2][i],a[3][i]] = 10
end
@test isequal(a,findn(z))

#argmin argmax
@test indmax([10,12,9,11]) == 2
@test indmin([10,12,9,11]) == 3
@test findmin([NaN,3.2,1.8]) == (1.8,3)
@test findmax([NaN,3.2,1.8]) == (3.2,2)
@test findmin([NaN,3.2,1.8,NaN]) == (1.8,3)
@test findmax([NaN,3.2,1.8,NaN]) == (3.2,2)
@test findmin([3.2,1.8,NaN,2.0]) == (1.8,2)
@test findmax([3.2,1.8,NaN,2.0]) == (3.2,1)

## permutedims ##

#keeps the num of dim
p = randperm(5)
q = randperm(5)
a = rand(p...)
b = permutedims(a,q)
@test isequal(size(b), tuple(p[q]...))

#hand made case
y = zeros(1,2,3)
for i = 1:6
    y[i]=i
end

z = zeros(3,1,2)
for i = 1:3
    z[i] = i*2-1
    z[i+3] = i*2
end

#permutes correctly
@test isequal(z,permutedims(y,[3,1,2]))
@test isequal(z,permutedims(y,(3,1,2)))

# of a subarray
a = rand(5,5)
s = sub(a,2:3,2:3)
p = permutedims(s, [2,1])
@test p[1,1]==a[2,2] && p[1,2]==a[3,2]
@test p[2,1]==a[2,3] && p[2,2]==a[3,3]

## ipermutedims ##

tensors = {rand(1,2,3,4),rand(2,2,2,2),rand(5,6,5,6),rand(1,1,1,1)}
for i = tensors
    perm = randperm(4)
    @test isequal(i,ipermutedims(permutedims(i,perm),perm))
    @test isequal(i,permutedims(ipermutedims(i,perm),perm))
end

## unique across dim ##

# All rows and columns unique
A = ones(10, 10)
A[diagind(A)] = shuffle!([1:10])
@test unique(A, 1) == A
@test unique(A, 2) == A

# 10 repeats of each row
B = A[shuffle!(repmat(1:10, 10)), :]
C = unique(B, 1)
@test sortrows(C) == sortrows(A)
@test unique(B, 2) == B
@test unique(B.', 2).' == C

# Along third dimension
D = cat(3, B, B)
@test unique(D, 1) == cat(3, C, C)
@test unique(D, 3) == cat(3, B)

# With hash collisions
immutable HashCollision
    x::Float64
end
Base.hash(::HashCollision) = uint(0)
@test map(x->x.x, unique(map(HashCollision, B), 1)) == C

## reduce ##

z = zeros(2,2,2,2)
for i=1:16
    z[i] = i
end

@test sum(z) == sum(z,(1,2,3,4))[1] == 136

# check variants of summation for type-stability and other issues (#6069)
sum2(itr) = invoke(sum, (Any,), itr)
plus(x,y) = x + y
plus() = 0
sum3(A) = reduce(plus, A)
sum4(itr) = invoke(reduce, (Function, Any), plus, itr)
sum5(A) = reduce(plus, 0, A)
sum6(itr) = invoke(reduce, (Function, Int, Any), plus, 0, itr)
sum7(A) = mapreduce(x->x, plus, A)
sum8(itr) = invoke(mapreduce, (Function, Function, Any), x->x, plus, itr)
sum9(A) = mapreduce(x->x, plus, 0, A)
sum10(itr) = invoke(mapreduce, (Function, Function, Int, Any), x->x,plus,0,itr)
for f in (sum2, sum3, sum4, sum5, sum6, sum7, sum8, sum9, sum10)
    @test sum(z) == f(z)
    @test sum(Int[]) == f(Int[]) == 0
    @test sum(Int[7]) == f(Int[7]) == 7
    if f == sum3 || f == sum4 || f == sum7 || f == sum8
        @test typeof(f(Int8[])) == typeof(f(Int8[1 7]))
    else
        @test typeof(f(Int8[])) == typeof(f(Int8[1])) == typeof(f(Int8[1 7]))
    end
end
@test typeof(sum(Int8[])) == typeof(sum(Int8[1])) == typeof(sum(Int8[1 7]))

prod2(itr) = invoke(prod, (Any,), itr)
@test prod(Int[]) == prod2(Int[]) == 1
@test prod(Int[7]) == prod2(Int[7]) == 7
@test typeof(prod(Int8[])) == typeof(prod(Int8[1])) == typeof(prod(Int8[1 7])) == typeof(prod2(Int8[])) == typeof(prod2(Int8[1])) == typeof(prod2(Int8[1 7]))

v = cell(2,2,1,1)
v[1,1,1,1] = 28.0
v[1,2,1,1] = 36.0
v[2,1,1,1] = 32.0
v[2,2,1,1] = 40.0

@test isequal(v,sum(z,(3,4)))

z = rand(10^6)
let es = sum_kbn(z), es2 = sum_kbn(z[1:10^5])
    @test (es - sum(z)) < es * 1e-13
    cs = cumsum(z)
    @test (es - cs[end]) < es * 1e-13
    @test (es2 - cs[10^5]) < es2 * 1e-13
end
@test_approx_eq sum(sin(z)) sum(sin, z)

@test maximum([4, 3, 5, 2]) == 5
@test minimum([4, 3, 5, 2]) == 2
@test extrema([4, 3, 5, 2]) == (2, 5)

@test isnan(maximum([NaN]))
@test isnan(minimum([NaN]))
@test isequal(extrema([NaN]), (NaN, NaN))

@test maximum([4., 3., NaN, 5., 2.]) == 5.
@test minimum([4., 3., NaN, 5., 2.]) == 2.
@test extrema([4., 3., NaN, 5., 2.]) == (2., 5.)

@test extrema(1:5) == (1,5)

@test any([true false; false false], 2) == [true false]'
@test any([true false; false false], 1) == [true false]

@test all([true true; false true], 2) == [true false]'
@test all([true false; false true], 1) == [false false]

## large matrices transpose ##

for i = 1 : 3
    a = rand(200, 300)

    @test isequal(a', permutedims(a, [2, 1]))
end

## cumsum, cummin, cummax

@test isequal(cummin([1, 2, 5, -1, 3, -2]), [1, 1, 1, -1, -1, -2])
@test isequal(cummax([1, 2, 5, -1, 3, -2]), [1, 2, 5, 5, 5, 5])

@test isequal(cummax([1 0; 0 1], 1), [1 0; 1 1])
@test isequal(cummax([1 0; 0 1], 2), [1 1; 0 1])
@test isequal(cummin([1 0; 0 1], 1), [1 0; 0 0])
@test isequal(cummin([1 0; 0 1], 2), [1 0; 0 0])

@test sum_kbn([1,1e100,1,-1e100]) == 2

begin
    local A, A1, A2, A3, v, v2, cv, cv2, c, R, T
    A = ones(Int,2,3,4)
    A1 = reshape(repmat([1,2],1,12),2,3,4)
    A2 = reshape(repmat([1 2 3],2,4),2,3,4)
    A3 = reshape(repmat([1 2 3 4],6,1),2,3,4)
    @test isequal(cumsum(A),A1)
    @test isequal(cumsum(A,1),A1)
    @test isequal(cumsum(A,2),A2)
    @test isequal(cumsum(A,3),A3)

    R = repeat([1, 2], inner = [1], outer = [1])
    @test R == [1, 2]
    R = repeat([1, 2], inner = [2], outer = [1])
    @test R == [1, 1, 2, 2]
    R = repeat([1, 2], inner = [1], outer = [2])
    @test R == [1, 2, 1, 2]
    R = repeat([1, 2], inner = [2], outer = [2])
    @test R == [1, 1, 2, 2, 1, 1, 2, 2]
    R = repeat([1, 2], inner = [1, 1], outer = [1, 1])
    @test R == [1, 2]''
    R = repeat([1, 2], inner = [2, 1], outer = [1, 1])
    @test R == [1, 1, 2, 2]''
    R = repeat([1, 2], inner = [1, 2], outer = [1, 1])
    @test R == [1 1; 2 2]
    R = repeat([1, 2], inner = [1, 1], outer = [2, 1])
    @test R == [1, 2, 1, 2]''
    R = repeat([1, 2], inner = [1, 1], outer = [1, 2])
    @test R == [1 1; 2 2]

    R = repeat([1 2;
                3 4], inner = [1, 1], outer = [1, 1])
    @test R == [1 2;
                  3 4]
    R = repeat([1 2;
                3 4], inner = [1, 1], outer = [2, 1])
    @test R == [1 2;
                  3 4;
                  1 2;
                  3 4]
    R = repeat([1 2;
                3 4], inner = [1, 1], outer = [1, 2])
    @test R == [1 2 1 2;
                  3 4 3 4]
    R = repeat([1 2;
                3 4], inner = [1, 1], outer = [2, 2])
    @test R == [1 2 1 2;
                  3 4 3 4;
                  1 2 1 2;
                  3 4 3 4]
    R = repeat([1 2;
                3 4], inner = [2, 1], outer = [1, 1])
    @test R == [1 2;
                  1 2;
                  3 4;
                  3 4]
    R = repeat([1 2;
                3 4], inner = [2, 1], outer = [2, 1])
    @test R == [1 2;
                  1 2;
                  3 4;
                  3 4;
                  1 2;
                  1 2;
                  3 4;
                  3 4]
    R = repeat([1 2;
                3 4], inner = [2, 1], outer = [1, 2])
    @test R == [1 2 1 2;
                  1 2 1 2;
                  3 4 3 4;
                  3 4 3 4;]
    R = repeat([1 2;
                3 4], inner = [2, 1], outer = [2, 2])
    @test R == [1 2 1 2;
                  1 2 1 2;
                  3 4 3 4;
                  3 4 3 4;
                  1 2 1 2;
                  1 2 1 2;
                  3 4 3 4;
                  3 4 3 4]
    R = repeat([1 2;
                3 4], inner = [1, 2], outer = [1, 1])
    @test R == [1 1 2 2;
                  3 3 4 4]
    R = repeat([1 2;
                3 4], inner = [1, 2], outer = [2, 1])
    @test R == [1 1 2 2;
                  3 3 4 4;
                  1 1 2 2;
                  3 3 4 4]
    R = repeat([1 2;
                3 4], inner = [1, 2], outer = [1, 2])
    @test R == [1 1 2 2 1 1 2 2;
                  3 3 4 4 3 3 4 4]
    R = repeat([1 2;
                3 4], inner = [1, 2], outer = [2, 2])
    @test R == [1 1 2 2 1 1 2 2;
                  3 3 4 4 3 3 4 4;
                  1 1 2 2 1 1 2 2;
                  3 3 4 4 3 3 4 4]
    R = repeat([1 2;
                3 4], inner = [2, 2], outer = [1, 1])
    @test R == [1 1 2 2;
                  1 1 2 2;
                  3 3 4 4;
                  3 3 4 4]
    R = repeat([1 2;
                3 4], inner = [2, 2], outer = [2, 1])
    @test R == [1 1 2 2;
                  1 1 2 2;
                  3 3 4 4;
                  3 3 4 4;
                  1 1 2 2;
                  1 1 2 2;
                  3 3 4 4;
                  3 3 4 4]
    R = repeat([1 2;
                3 4], inner = [2, 2], outer = [1, 2])
    @test R == [1 1 2 2 1 1 2 2;
                  1 1 2 2 1 1 2 2;
                  3 3 4 4 3 3 4 4;
                  3 3 4 4 3 3 4 4]
    R = repeat([1 2;
                3 4], inner = [2, 2], outer = [2, 2])
    @test R == [1 1 2 2 1 1 2 2;
                  1 1 2 2 1 1 2 2;
                  3 3 4 4 3 3 4 4;
                  3 3 4 4 3 3 4 4;
                  1 1 2 2 1 1 2 2;
                  1 1 2 2 1 1 2 2;
                  3 3 4 4 3 3 4 4;
                  3 3 4 4 3 3 4 4]

    A = reshape([1:8], 2, 2, 2)
    R = repeat(A, inner = [1, 1, 2], outer = [1, 1, 1])
    T = reshape([1:4, 1:4, 5:8, 5:8], 2, 2, 4)
    @test R == T
    A = Array(Int, 2, 2, 2)
    A[:, :, 1] = [1 2;
                  3 4]
    A[:, :, 2] = [5 6;
                  7 8]
    R = repeat(A, inner = [2, 2, 2], outer = [2, 2, 2])
    @test R[1, 1, 1] == 1
    @test R[2, 2, 2] == 1
    @test R[3, 3, 3] == 8
    @test R[4, 4, 4] == 8
    @test R[5, 5, 5] == 1
    @test R[6, 6, 6] == 1
    @test R[7, 7, 7] == 8
    @test R[8, 8, 8] == 8

    A = rand(4,4)
    for s in {A[1:2:4, 1:2:4], sub(A, 1:2:4, 1:2:4)}
        c = cumsum(s, 1)
        @test c[1,1] == A[1,1]
        @test c[2,1] == A[1,1]+A[3,1]
        @test c[1,2] == A[1,3]
        @test c[2,2] == A[1,3]+A[3,3]

        c = cumsum(s, 2)
        @test c[1,1] == A[1,1]
        @test c[2,1] == A[3,1]
        @test c[1,2] == A[1,1]+A[1,3]
        @test c[2,2] == A[3,1]+A[3,3]
    end

    v   = [1,1e100,1,-1e100]*1000
    v2  = [1,-1e100,1,1e100]*1000

    cv  = [1,1e100,1e100,2]*1000
    cv2 = [1,-1e100,-1e100,2]*1000

    @test isequal(cumsum_kbn(v), cv)
    @test isequal(cumsum_kbn(v2), cv2)

    A = [v reverse(v) v2 reverse(v2)]

    c = cumsum_kbn(A, 1)

    @test isequal(c[:,1], cv)
    @test isequal(c[:,3], cv2)
    @test isequal(c[4,:], [2.0 2.0 2.0 2.0]*1000)

    c = cumsum_kbn(A, 2)

    @test isequal(c[1,:], cv2')
    @test isequal(c[3,:], cv')
    @test isequal(c[:,4], [2.0,2.0,2.0,2.0]*1000)

end

@test (1:5)[[true,false,true,false,true]] == [1,3,5]
@test [1:5][[true,false,true,false,true]] == [1,3,5]
@test_throws (1:5)[[true,false,true,false]]
@test_throws (1:5)[[true,false,true,false,true,false]]
@test_throws [1:5][[true,false,true,false]]
@test_throws [1:5][[true,false,true,false,true,false]]
a = [1:5]
a[[true,false,true,false,true]] = 6
@test a == [6,2,6,4,6]
a[[true,false,true,false,true]] = [7,8,9]
@test a == [7,2,8,4,9]
@test_throws (a[[true,false,true,false,true]] = [7,8,9,10])
A = reshape(1:15, 3, 5)
@test A[[true, false, true], [false, false, true, true, false]] == [7 10; 9 12]
@test_throws A[[true, false], [false, false, true, true, false]]
@test_throws A[[true, false, true], [false, true, true, false]]
@test_throws A[[true, false, true, true], [false, false, true, true, false]]
@test_throws A[[true, false, true], [false, false, true, true, false, true]]
A = ones(Int, 3, 5)
@test_throws A[2,[true, false, true, true, false]] = 2:5
A[2,[true, false, true, true, false]] = 2:4
@test A == [1 1 1 1 1; 2 1 3 4 1; 1 1 1 1 1]
@test_throws A[[true,false,true], 5] = [19]
@test_throws A[[true,false,true], 5] = 19:21
A[[true,false,true], 5] = 7
@test A == [1 1 1 1 7; 2 1 3 4 1; 1 1 1 1 7]

B = cat(3, 1, 2, 3)
@test B[:,:,[true, false, true]] == reshape([1,3], 1, 1, 2)  # issue #5454

# issue #2342
@test isequal(cumsum([1 2 3]), [1 2 3])

# set-like operations
@test isequal(union([1,2,3], [4,3,4]), [1,2,3,4])
@test isequal(union(['e','c','a'], ['b','a','d']), ['e','c','a','b','d'])
@test isequal(union([1,2,3], [4,3], [5]), [1,2,3,4,5])
@test isequal(union([1,2,3]), [1,2,3])
@test isequal(union([1,2,3], Int64[]), Int64[1,2,3])
@test isequal(union([1,2,3], Float64[]), Float64[1.0,2,3])
@test isequal(union(Int64[], [1,2,3]), Int64[1,2,3])
@test isequal(union(Int64[]), Int64[])
@test isequal(intersect([1,2,3], [4,3,4]), [3])
@test isequal(intersect(['e','c','a'], ['b','a','d']), ['a'])
@test isequal(intersect([1,2,3], [3,1], [2,1,3]), [1,3])
@test isequal(intersect([1,2,3]), [1,2,3])
@test isequal(intersect([1,2,3], Int64[]), Int64[])
@test isequal(intersect([1,2,3], Float64[]), Float64[])
@test isequal(intersect(Int64[], [1,2,3]), Int64[])
@test isequal(intersect(Int64[]), Int64[])
@test isequal(setdiff([1,2,3,4], [2,5,4]), [1,3])
@test isequal(setdiff([1,2,3,4], [7,8,9]), [1,2,3,4])
@test isequal(setdiff([1,2,3,4], Int64[]), Int64[1,2,3,4])
@test isequal(setdiff([1,2,3,4], [1,2,3,4,5]), Int64[])
@test isequal(symdiff([1,2,3], [4,3,4]), [1,2,4])
@test isequal(symdiff(['e','c','a'], ['b','a','d']), ['e','c','b','d'])
@test isequal(symdiff([1,2,3], [4,3], [5]), [1,2,4,5])
@test isequal(symdiff([1,2,3,4,5], [1,2,3], [3,4]), [3,5])
@test isequal(symdiff([1,2,3]), [1,2,3])
@test isequal(symdiff([1,2,3], Int64[]), Int64[1,2,3])
@test isequal(symdiff([1,2,3], Float64[]), Float64[1.0,2,3])
@test isequal(symdiff(Int64[], [1,2,3]), Int64[1,2,3])
@test isequal(symdiff(Int64[]), Int64[])

# mapslices
begin
    local a,h,i
    a = rand(5,5)
    h = mapslices(v -> hist(v,0:0.1:1)[2], a, 1)
    H = mapslices(v -> hist(v,0:0.1:1)[2], a, 2)
    s = mapslices(sort, a, [1])
    S = mapslices(sort, a, [2])
    for i = 1:5
        @test h[:,i] == hist(a[:,i],0:0.1:1)[2]
        @test vec(H[i,:]) == hist(vec(a[i,:]),0:0.1:1)[2]
        @test s[:,i] == sort(a[:,i])
        @test vec(S[i,:]) == sort(vec(a[i,:]))
    end

    # issue #3613
    b = mapslices(sum, ones(2,3,4), [1,2])
    @test size(b) === (1,1,4)
    @test all(b.==6)

    # issue #5141
    ## Update Removed the version that removes the dimensions when dims==1:ndims(A)
    c1 = mapslices(x-> maximum(-x), a, [])
    @test c1 == -a

    # other types than Number
    @test mapslices(prod,["1" "2"; "3" "4"],1) == ["13" "24"]

    # issue #5177

    c = ones(2,3,4)
    m1 = mapslices(x-> ones(2,3), c, [1,2])
    m2 = mapslices(x-> ones(2,4), c, [1,3])
    m3 = mapslices(x-> ones(3,4), c, [2,3])
    @test size(m1) == size(m2) == size(m3) == size(c)

    n1 = mapslices(x-> ones(6), c, [1,2])
    n2 = mapslices(x-> ones(6), c, [1,3])
    n3 = mapslices(x-> ones(6), c, [2,3])
    n1a = mapslices(x-> ones(1,6), c, [1,2])
    n2a = mapslices(x-> ones(1,6), c, [1,3])
    n3a = mapslices(x-> ones(1,6), c, [2,3])
    @test size(n1a) == (1,6,4) && size(n2a) == (1,3,6)  && size(n3a) == (2,1,6)
    @test size(n1) == (6,1,4) && size(n2) == (6,3,1)  && size(n3) == (2,6,1)
end

# single multidimensional index
let
    a = rand(6,6)
    I = [1 4 5; 4 2 6; 5 6 3]
    a2 = a[I]
    @test size(a2) == size(I)
    for i = 1:length(a2)
        @test a2[i] == a[I[i]]
    end
    a = [1,3,5]
    b = [1 3]
    a[b] = 8
    @test a == [8,3,8]
end

# assigning an array into itself
a = [1,3,5]
b = [3,1,2]
a[b] = a
@test a == [3,5,1]

# lexicographic comparison
@test lexcmp([1.0], [1]) == 0
@test lexcmp([1], [1.0]) == 0
@test lexcmp([1, 1], [1, 1]) == 0
@test lexcmp([1, 1], [2, 1]) == -1
@test lexcmp([2, 1], [1, 1]) == 1
@test lexcmp([1, 1], [1, 2]) == -1
@test lexcmp([1, 2], [1, 1]) == 1
@test lexcmp([1], [1, 1]) == -1
@test lexcmp([1, 1], [1]) == 1

# sort on arrays
begin
    local a = rand(3,3)

    asr = sortrows(a)
    @test lexless(asr[1,:],asr[2,:])
    @test lexless(asr[2,:],asr[3,:])

    asc = sortcols(a)
    @test lexless(asc[:,1],asc[:,2])
    @test lexless(asc[:,2],asc[:,3])

    asr = sortrows(a, rev=true)
    @test lexless(asr[2,:],asr[1,:])
    @test lexless(asr[3,:],asr[2,:])

    asc = sortcols(a, rev=true)
    @test lexless(asc[:,2],asc[:,1])
    @test lexless(asc[:,3],asc[:,2])

    as = sort(a, 1)
    @test issorted(as[:,1])
    @test issorted(as[:,2])
    @test issorted(as[:,3])

    as = sort(a, 2)
    @test issorted(as[1,:])
    @test issorted(as[2,:])
    @test issorted(as[3,:])
end

# fill
@test fill!(Array(Float64,1),-0.0)[1] === -0.0
A = ones(3,3)
S = sub(A, 2, 1:3)
fill!(S, 2)
S = sub(A, 1:2, 3)
fill!(S, 3)
@test A == [1 1 3; 2 2 3; 1 1 1]
rt = Base.return_types(fill!, (Array{Int32, 3}, Uint8))
@test length(rt) == 1 && rt[1] == Array{Int32, 3}

# splice!
for idx in {1, 2, 5, 9, 10, 1:0, 2:1, 1:1, 2:2, 1:2, 2:4, 9:8, 10:9, 9:9, 10:10,
            8:9, 9:10, 6:9, 7:10}
    for repl in {[], [11], [11,22], [11,22,33,44,55]}
        a = [1:10]; acopy = copy(a)
        @test splice!(a, idx, repl) == acopy[idx]
        @test a == [acopy[1:(first(idx)-1)], repl, acopy[(last(idx)+1):end]]
    end
end

# deleteat!
for idx in {1, 2, 5, 9, 10, 1:0, 2:1, 1:1, 2:2, 1:2, 2:4, 9:8, 10:9, 9:9, 10:10,
            8:9, 9:10, 6:9, 7:10}
    a = [1:10]; acopy = copy(a)
    @test deleteat!(a, idx) == [acopy[1:(first(idx)-1)], acopy[(last(idx)+1):end]]
end
a = [1:10]
@test deleteat!(a, [1,3,5,7:10...]) == [2,4,6]

# comprehensions
X = [ i+2j for i=1:5, j=1:5 ]
@test X[2,3] == 8
@test X[4,5] == 14
@test isequal(ones(2,3) * ones(2,3)', [3. 3.; 3. 3.])
@test isequal([ [1,2] for i=1:2, : ], [1 2; 1 2])
# where element type is a Union. try to confuse type inference.
foo32_64(x) = (x<2) ? int32(x) : int64(x)
boo32_64() = [ foo32_64(i) for i=1:2 ]
let a36 = boo32_64()
    @test a36[1]==1 && a36[2]==2
end
@test isequal([1,2,3], [a for (a,b) in enumerate(2:4)])
@test isequal([2,3,4], [b for (a,b) in enumerate(2:4)])

@test_throws (10.^[-1])[1] == 0.1
@test (10.^[-1.])[1] == 0.1

# reverse
@test reverse([2,3,1]) == [1,3,2]
@test reverse([1:10],1,4) == [4,3,2,1,5,6,7,8,9,10]
@test reverse([1:10],3,6) == [1,2,6,5,4,3,7,8,9,10]
@test reverse([1:10],6,10) == [1,2,3,4,5,10,9,8,7,6]
@test reverse(1:10,1,4) == [4,3,2,1,5,6,7,8,9,10]
@test reverse(1:10,3,6) == [1,2,6,5,4,3,7,8,9,10]
@test reverse(1:10,6,10) == [1,2,3,4,5,10,9,8,7,6]
@test reverse!([1:10],1,4) == [4,3,2,1,5,6,7,8,9,10]
@test reverse!([1:10],3,6) == [1,2,6,5,4,3,7,8,9,10]
@test reverse!([1:10],6,10) == [1,2,3,4,5,10,9,8,7,6]

# flipdim
@test isequal(flipdim([2,3,1], 1), [1,3,2])
@test isequal(flipdim([2,3,1], 2), [2,3,1])
@test isequal(flipdim([2 3 1], 1), [2 3 1])
@test isequal(flipdim([2 3 1], 2), [1 3 2])
@test_throws flipdim([2,3,1], -1)
@test isequal(flipdim(1:10, 1), 10:-1:1)
@test isequal(flipdim(1:10, 2), 1:10)
@test_throws flipdim(1:10, -1)
@test isequal(flipdim(Array(Int,0,0),1), Array(Int,0,0))  # issue #5872

# issue 4228
A = [[i i; i i] for i=1:2]
@test cumsum(A) == Any[[1 1; 1 1], [3 3; 3 3]]
@test cumprod(A) == Any[[1 1; 1 1], [4 4; 4 4]]

# PR #4627
A = [1,2]
@test append!(A, A) == [1,2,1,2]
@test prepend!(A, A) == [1,2,1,2,1,2,1,2]

# cases where shared arrays can/can't be grown
A = [1 3;2 4]
B = reshape(A, 4)
@test push!(B,5) == [1,2,3,4,5]
@test pop!(B) == 5
C = reshape(B, 1, 4)
@test_throws push!(C, 5)

A = [NaN]; B = [NaN]
@test !(A==A)
@test isequal(A,A)
@test A===A
@test !(A==B)
@test isequal(A,B)
@test A!==B

# complete testsuite for reducedim

include("reducedim.jl")
# Inferred types
Nmax = 3 # TODO: go up to CARTESIAN_DIMS+2 (currently this exposes problems)
for N = 1:Nmax
    #indexing with (Range1, Range1, Range1)
    args = ntuple(N, d->Range1{Int})
    @test Base.return_types(getindex, tuple(Array{Float32, N}, args...)) == {Array{Float32, N}}
    @test Base.return_types(getindex, tuple(BitArray{N}, args...)) == {BitArray{N}}
    @test Base.return_types(setindex!, tuple(Array{Float32, N}, Array{Int, 1}, args...)) == {Array{Float32, N}}
    # Indexing with (Range1, Range1, Float64)
    args = ntuple(N, d->d<N ? Range1{Int} : Float64)
    N > 1 && @test Base.return_types(getindex, tuple(Array{Float32, N}, args...)) == {Array{Float32, N-1}}
    N > 1 && @test Base.return_types(getindex, tuple(BitArray{N}, args...)) == {BitArray{N-1}}
    N > 1 && @test Base.return_types(setindex!, tuple(Array{Float32, N}, Array{Int, 1}, args...)) == {Array{Float32, N}}
end


# # Chapter 2 - Python..ではなくJulia入門

## #src
# Juliaでは、改行あり出力は`println`
println("hello")

## #src
# ## 2.2 変数と変数出力
# 変数や計算の埋め込みは`$()`で行う。
# 型を調べるのは`type`ではなく`typeof`を用いる。
num = 10
string = "Hello World"
println("num=$(num), typeof(num)=$(typeof(num))")
println("string=$(string), typeof(string)=$(typeof(string))")

## #src
# フォーマット書式ありの出力は標準ライブラリに含まれている`Printf.@sprintf`を使う。
# Pythonの`.format`に近い表記になる。
using Printf

num = 10.5678
println(@sprintf("小数2位:%.2f, 小数5位:%.5f, 小数2位の指数表記:%.2e", num, num, num))

## #src
# ## 2.3 データ構造
# ### 2.3.1 リスト
# Juliaではベクトル型が組み込まれているので、Pythonのようにリストとnumpy.ndarrayの相互変換
# を意識するする必要はない
price1 = [120, 180]
price2 = [300, 150]
all = [price1, price2]
all

## #src
# ### 2.3.2 リストの大きさ
length(price1)

## #src
# ### 2.3.3 リストの各要素へのアクセス
# インデックスが0から始まるPythonと違い、Juliaのインデックスは1から始まる
## 最初(1番目)の要素: 120
price1[1]

## #src
# 最初の要素はfirstでもアクセスできる
first(price1)

## #src
#-
## 2番目の要素
all[2]

## #src
#-
## 2行 2番目の要素: 150
all[2][2]

## #src
# ### 2.3.4 リストの拡張
# リストの拡張は`push!`を使う
## 空のリスト
price = []

## #src
#-
## 要素の追加: [120, 180]
push!(price, [120, 180])

## #src
#-
## 要素の追加: [[120, 180], [300, 150]]
push!(price, [300, 150])

## #src
# ### 2.3.5 Vector, Array
# Juliaではリストと行列、ベクトルの区別を意識しなくて良い
price1 = [120, 180]

## #src
#-
## Juliaはcolumn oriented
price2 = [200 300; 50 150.0]

## #src
# 要素の型は`eltype`を使って確認できる
eltype(price1)

## #src
#-
eltype(price2)

## #src
# ### 2.3.6 Arrayの大きさ
# `Array`のサイズは`size`で調べる
size(price1)

## #src
#-
size(price2)

## #src
# ### 2.3.7 Arrayのスライシング
price = [120, 180, 300, 150]

## 最初(1番目)の要素: 120
price[1]

## #src
#-
## 2番目の要素: 180
price[2]

## #src
#-
## 最後の要素: 150
price[end]

## #src
#-
## 最後から2番目の要素: 300
price[end-1]

## #src
#-
## 2番目から3番目の要素: [180, 300]
price[2:3]

## #src
#-
## 最初から2番目までの要素: [120, 180]
price[1:2]

## #src
#-
## 最初から3番目までの要素: [120. 180, 300]
price[1:end-1]

## #src
#-
## 最初から最後までの要素
price[:]

## #src
# ### 2.3.8 Arrayの簡単な計算
price = [120, 180, 300, 150]

## 和: 750
sum(price)

## #src
#-
## 積: 972000000
prod(price)

## #src
#-
## 二乗: [14400, 32400, 90000, 22500]
## `.`でbroadcastする
price .^ 2

## #src
#-
## 平方根: [10.9544515, 13.41640786, 17.32050808, 12.247448711]
sqrt.(price)

## #src
# 統計関連の計算には標準ライブラリであるStatisticsを使う
#-
using Statistics

## 平均値: 187.5
mean(price)

## #src
#-
## 分散: 4668.75
## Statistics.varはデフォルトで不偏分散を計算するので、標本分散は`corrected=false`とする
var(price, corrected=false)

## #src
#-
var(price)

## #src
#-
## 標準偏差
std(price, corrected=false)

## #src
#-
## 最小値: 120
minimum(price)

## #src
#-
## 最大値: 300
maximum(price)

## #src
#-
## 最小のインデックス: 1
argmin(price)

## #src
#-
## 最大のインデックス: 3
argmax(price)

## #src
# ### 2.3.9 Vectorのソート
price = [120, 180, 300, 150]

## 昇順に並び替えた値: [120, 150, 180, 300]
sort(price)

## #src
#-
## 降順に並び替えた値: [300, 180, 150, 120]
sort(price, rev=true)

## #src
#-
## 昇順に並び替えた値のインデックス: [1, 4, 2, 3]
sortperm(price)

## #src
#-
## 降順に並び替えた値のインデックス: [3, 2, 4, 1]
sortperm(price, rev=true)

## #src
# ### 2.3.10 Vectorのユニーク
price = [120, 180, 300, 150, 150]

## unique後: [120, 180, 300, 150]
## 順番は保存される
unique(price)

## #src
# ### 2.3.11 Vectorの条件を満たすインデックス
price = [120, 180, 300, 150]

## 条件を満たすインデックスを取得するにはfindall
findall(x -> x > 150, price)

## #src
#-
## マスクを作成するのはbroadcastを使えば簡単
indices = price .> 150

## #src
# ### 2.3.12 Arrayの結合
price1 = [120, 180]
price2 = [300, 150]

## horizontal方向への結合
hcat(price1, price2)

## #src
#-
## catを使っても書ける
cat(price1, price2; dims=2)

## #src
#-
## vertical方向への結合
vcat(price1, price2)

## #src
#-
## catを使っても書ける
cat(price1, price2; dims=1)

## #src
#-
price1 = reshape([120, 180], 1, 1, :)
price2 = reshape([300, 150], 1, 1, :)

## #src
#-
hcat(price1, price2)

## #src
#-
vcat(price1, price2)

## #src
#-
cat(price1, price2; dims=3)

## #src
# ### 2.3.13 Arrayの変形
price = [120, 180, 300, 150]

## #src
#-
## 2行 2列に変形
reshape(price, 2, 2)

## #src
#-
## 2行 2列に変形(自動)
reshape(price, 2, :)

## #src
#-
## 4行 1列に変形
reshape(price, 4, 1)

## #src
# ### 2.3.14 Arrayの繰り返し
price = [120, 180, 300, 150]

## 行方向に2回、列方向に1回繰り返す
repeat(price, 2, 1)

## #src
#-
## 行方向に1回、列方向に2回繰り返す
repeat(price, 1, 2)

## #src
#-
## 奥方向に2回、行・列方向に1回繰り返す
repeat(price, 1, 1, 2)

## #src
# ### 2.3.15 Arrayの次元拡張
price = [120, 180, 300, 150]

size(price)

## #src
#-
## 行方向に増やした後のsize
size(reshape(price, 1, :))

## #src
#-
## 列方向に増やした後のsize
size(reshape(price, :, 1))

## #src
# ### 2.3.16 Arrayの(軸方向の)回転
price = [120 180; 300 150]

## #src
#-
## 行方向に1つ回転する
circshift(price, (1, 0))

## #src
#-
## 列方向に1つ回転する
circshift(price, (0, 1))

## #src
#-
## 回転
rotr90(price, 1)

## #src
#-
rotl90(price, 2)

## #src
# ### 2.3.17 Arrayのインデックスの生成
price = [120, 180, 300, 150]

## 1:4
1:length(price)

## #src
#-
## [1, 2, 3, 4]
collect(1:length(price))

## #src
# ### 2.3.18 Arrayのインデックスのシャッフル
using Random

## シードを固定しておく
Random.seed!(1234)

price = [120, 180, 300, 150]

randperm(length(price))

## #src
# ### 2.3.19 単位行列の生成
using LinearAlgebra

Matrix(I, 5, 5)

## #src
# ### 2.3.20 CSV.jl & DataFrames.jl
# https://www.kaggle.com/c/house-prices-advanced-regression-techniques/data?select=train.csv
# からファイルをダウンロードしてdataフォルダに保存しているとする

## CSVファイルの読み込み
# pandas.DataFrameのようなDataFrameの構造を扱うにはDataFrames.jlを使う。
using CSV
using DataFrames

#-
data = CSV.read("../data/house-prices-advanced-regression-techniques/train.csv", DataFrame)

## #src
#-

## 5行目まで
first(data, 5)

## #src
#-
## カラムの一覧
names(data)

## #src
#-
## 建物の特徴(MSSubClass)
unique(data[!, :MSSubClass])

## #src
# ### 2.3.21 DataFrameからArrayへの変換
Array(data[!, [:GrLivArea, :SalePrice]])

## #src
# ### 2.3.22 DafaFrameのカラムの削除
length(names(data))

## #src
#-
## PoolAreaカラムを削除
select!(data, Not(:PoolArea))

## #src
#-
length(names(data))

## #src
# ## 2.4 グラフのプロット
# 別ファイル

## #src
# ### 2.5 if文とfor文
#-
values = [10, 3, 1, 5, 8, 6]

## 通常のfor文
## 空のリスト
passed_values = Int64[]
for v in values
    if v > 5
        push!(passed_values, v)
    end
end

## 結果を標準出力
println("5以上の値 $(passed_values)")

## #src
#-
## リスト内表記のforとif
passed_values = [v for v in values if v > 5]

## 結果を標準出力
println("5以上の値 $(passed_values)")

## #src
#-
## filterを使った表記
passed_values = filter(v -> v > 5, values)

## 結果を標準出力
println("5以上の値 $(passed_values)")

## #src
# ### 2.5.3 例外処理
#-
## Juliaでは1/0はInfになる
for i in [-1, 0, 1]
    println("$(1/i)")
end

## #src
#-
## 例外を発生させてみる
## for i in [-1, 0, 1]
##     println("$(i//i)")
##∂ end

## #src
#-
for i in [-1, 0, 1]
    try
        println("$(i//i)")
    catch
        continue
    end
end


using Test
using CartesianMesh

CM = CartesianMesh

function allequal(v1,v2)
    return all(v1 .≈ v2)
end

x0 = [5.,8.]
widths = [2.,3.]
nelements = [5,3]
mesh = CM.UniformMesh(x0,widths,nelements)

@test_throws BoundsError CM.linear_to_cartesian_index(mesh,0)
@test_throws BoundsError CM.linear_to_cartesian_index(mesh,16)

elx,ely = CM.linear_to_cartesian_index(mesh,8)
@test (elx,ely) == (3,2)
@test CM.linear_to_cartesian_index(mesh,6) == (2,3)
@test CM.linear_to_cartesian_index(mesh,1) == (1,1)
@test CM.linear_to_cartesian_index(mesh,15) == (5,3)

mesh = CM.UniformMesh(x0,widths,[1,1])
@test CM.linear_to_cartesian_index(mesh,1) == (1,1)

xL,xR = CM.element(mesh,1)
@test allequal(xL,[5.,8.])
@test allequal(xR,[7.,11.])

mesh = CM.UniformMesh(x0,widths,[5,3])
xL,xR = CM.element(mesh,9)
@test allequal(xL,[5.8,10.])
@test allequal(xR,[6.2,11.])

nbrs = CM.neighbors(mesh,1)
@test allequal(nbrs,[mesh.bdryflag,4,2,mesh.bdryflag])
nbrs = CM.neighbors(mesh,5)
@test allequal(nbrs,[4,8,6,2])
nbrs = CM.neighbors(mesh,3)
@test allequal(nbrs,[2,6,mesh.bdryflag,mesh.bdryflag])

@test CM.faces_per_cell(mesh) == 4


### A Pluto.jl notebook ###
# v0.18.0

using Markdown
using InteractiveUtils

# ╔═╡ 22a37ff4-3ae0-4338-86a2-3310b7773c1d
begin
    import Pkg
    # activate the shared project environment|
    Pkg.activate(Base.current_project())
    using Omega
	using Distributions
	using UnicodePlots
	using Random: MersenneTwister
end

# ╔═╡ 6b2fe98e-76cf-11eb-39f8-9ff52d23e688
md"# Bayesian Linear Regression"

# ╔═╡ a2fb35f7-8c48-4e70-aff8-d8ee431aa872
md"Bayesian linear regression is a method to infer the parameters of linear model between two variables.  The \"Bayesian\" part of Bayesian linear regression means means that it treats the problem of finding these parameters as one of Bayesian inference."

# ╔═╡ b583b52a-76ce-11eb-088b-dbeb7791729f
md"""## Data
First, let's create some fake data"""

# ╔═╡ ee2f8174-76ce-11eb-2c74-298d6072afee
rng = MersenneTwister(0);

# ╔═╡ d10a9c5a-76ce-11eb-0ece-bd1b6e946fe0
N = 100;

# ╔═╡ d83b52d0-76ce-11eb-0d3f-d1ddd64d6e08
xs = rand(rng, N)

# ╔═╡ e61b99a8-76ce-11eb-2d0e-1f8d2f31eba4
m = 0.8

# ╔═╡ 0af4f988-76cf-11eb-012e-cf2182fa826b
c = 1.2

# ╔═╡ ff7d2e7c-76ce-11eb-249b-cbf84db4fe9b
linear_model(x, m, c) = m * x + c

# ╔═╡ 2288d0d2-76d0-11eb-29e9-21a78bc370e8
obs_model(x) = linear_model(x, m, c) + randn(rng) * 0.1;

# ╔═╡ 107a8c36-76cf-11eb-1adf-43f11a9a4204
ys = obs_model.(xs);

# ╔═╡ 1dc0257e-76cf-11eb-3fa5-377113ba0a49
UnicodePlots.scatterplot(xs, ys)

# ╔═╡ 61f89028-76cf-11eb-1813-9179e3467f47
md"## Probabilistic Model"

# ╔═╡ 79cd6e26-76cf-11eb-1930-399093becba8
M = @~ Normal(0, 1);

# ╔═╡ a9765e1c-76cf-11eb-3495-79aa95e21afd
C = @~ Normal(0, 1);

# ╔═╡ 7fce6a74-8b68-45ce-bb9c-a4c4e39fcc1e
struct ϵ end

# ╔═╡ b2cc4db2-76cf-11eb-390d-f151ff4b939d
Y_class(i, ω) = linear_model(xs[i], M(ω), C(ω)) + (ϵ ∘ i ~ Normal(0, 0.1))(ω);

# ╔═╡ 93ed05dd-cc5a-4b77-af0d-7cca48777510
revise

# ╔═╡ 800049e2-f44b-4839-a2ae-7fb0beeb5c0e
Y⃗ = Mv(1:N, Y_class)

# ╔═╡ fa8b5cc2-7728-11eb-24b3-5db1bd7d0255
UnicodePlots.scatterplot(xs, randsample(Y⃗))

# ╔═╡ cf09331c-c5cd-49f0-8ad5-eced9cb56d37
(ω = defω(); Y⃗(ω); ω)

# ╔═╡ b3c0e452-fff8-419e-945c-27e2d21c9fe3
evidence = pw(==, Y⃗, ys)

# ╔═╡ 3d5b3ac2-bf70-4710-9c97-1cbef7be4c11
joint_posterior = @joint(M, C) |ᶜ evidence

# ╔═╡ aa781fbe-76d0-11eb-387a-3dc1cbf700f8
samples = randsample(joint_posterior |ᶜ evidence, 1000; alg = MH)

# ╔═╡ f7efb0d0-76d1-11eb-3440-47df94aeb74e
UnicodePlots.scatterplot(xs, samples)

# ╔═╡ Cell order:
# ╟─6b2fe98e-76cf-11eb-39f8-9ff52d23e688
# ╟─a2fb35f7-8c48-4e70-aff8-d8ee431aa872
# ╠═22a37ff4-3ae0-4338-86a2-3310b7773c1d
# ╟─b583b52a-76ce-11eb-088b-dbeb7791729f
# ╠═ee2f8174-76ce-11eb-2c74-298d6072afee
# ╠═d10a9c5a-76ce-11eb-0ece-bd1b6e946fe0
# ╠═d83b52d0-76ce-11eb-0d3f-d1ddd64d6e08
# ╠═e61b99a8-76ce-11eb-2d0e-1f8d2f31eba4
# ╠═0af4f988-76cf-11eb-012e-cf2182fa826b
# ╠═ff7d2e7c-76ce-11eb-249b-cbf84db4fe9b
# ╠═2288d0d2-76d0-11eb-29e9-21a78bc370e8
# ╠═107a8c36-76cf-11eb-1adf-43f11a9a4204
# ╠═1dc0257e-76cf-11eb-3fa5-377113ba0a49
# ╟─61f89028-76cf-11eb-1813-9179e3467f47
# ╠═79cd6e26-76cf-11eb-1930-399093becba8
# ╠═a9765e1c-76cf-11eb-3495-79aa95e21afd
# ╠═7fce6a74-8b68-45ce-bb9c-a4c4e39fcc1e
# ╠═b2cc4db2-76cf-11eb-390d-f151ff4b939d
# ╠═93ed05dd-cc5a-4b77-af0d-7cca48777510
# ╠═800049e2-f44b-4839-a2ae-7fb0beeb5c0e
# ╠═fa8b5cc2-7728-11eb-24b3-5db1bd7d0255
# ╠═cf09331c-c5cd-49f0-8ad5-eced9cb56d37
# ╠═b3c0e452-fff8-419e-945c-27e2d21c9fe3
# ╠═3d5b3ac2-bf70-4710-9c97-1cbef7be4c11
# ╠═aa781fbe-76d0-11eb-387a-3dc1cbf700f8
# ╠═f7efb0d0-76d1-11eb-3440-47df94aeb74e


using SumOfSquares
using DynamicPolynomials
using MosekTools


# Latafat, Patrinos, "Asymmetric forward–backward–adjoint splitting for
# solving monotone inclusions involving three operators", Computational
# Optimization and Applications, vol. 68, no. 1, pp. 57-93 (2017).
#
# Latafat, Patrinos, "Primal-dual proximal algorithms for structured convex
# optimization: a unifying framework", In Large-Scale and Distributed
# Optimization, Giselsson and Rantzer, Eds. Springer International Publishing,
# pp. 97–120 (2018).
#
# Chambolle, Pock, "A First-Order Primal-Dual Algorithm for Convex Problems
# with Applications to Imaging", Journal of Mathematical Imaging and Vision,
# vol. 40, no. 1, pp. 120-145 (2011).
#
# Condat, "A primal–dual splitting method for convex optimization
# involving Lipschitzian, proximable and linear composite terms",
# Journal of Optimization Theory and Applications, vol. 158, no. 2,
# pp 460-479 (2013).
#
# Vũ, "A splitting algorithm for dual monotone inclusions involving
# cocoercive operators", Advances in Computational Mathematics, vol. 38, no. 3,
# pp. 667-681 (2013).

using Base.Iterators
using ProximalAlgorithms.IterationTools
using ProximalCore: Zero, IndZero, convex_conjugate
using LinearAlgebra
using Printf

"""
    AFBAIteration(; <keyword-arguments>)

Iterator implementing the asymmetric forward-backward-adjoint algorithm (AFBA, see [1]).

This iterator solves convex optimization problems of the form

    minimize f(x) + g(x) + (h □ l)(L x),

where `f` is smooth, `g` and `h` are possibly nonsmooth and `l` is strongly
convex. Symbol `□` denotes the infimal convolution, and `L` is a linear mapping.

Points `x0` and `y0` are the initial primal and dual iterates, respectively.
If unspecified, functions `f`, `g`, and `h` default to the identically zero
function, `l` defaults to the indicator of the set `{0}`, and `L` defaults to
the identity. Important keyword arguments, in case `f` and `l` are set, are
the Lipschitz constants `beta_f` and `beta_l` (see below).

The iterator implements Algorithm 3 of [1] with constant stepsize (α_n=λ)
for several prominant special cases:
1) θ = 2          ==>   Corresponds to the Vu-Condat Algorithm [3, 4].
2) θ = 1, μ=1
3) θ = 0, μ=1
4) θ ∈ [0,∞), μ=0

See [2, Section 5.2] and [1, Figure 1] for stepsize conditions, special cases,
and relation to other algorithms.

See also: [`AFBA`](@ref).

# Arguments
- `x0`: initial primal point.
- `y0`: initial dual point.
- `f=Zero()`: smooth objective term.
- `g=Zero()`: proximable objective term.
- `h=Zero()`: proximable objective term.
- `l=IndZero()`: strongly convex function.
- `L=I`: linear operator (e.g. a matrix).
- `beta_f=0`: Lipschitz constant of the gradient of `f`.
- `beta_l=0`: Lipschitz constant of the gradient of `l` conjugate.
- `theta=1`: nonnegative algorithm parameter.
- `mu=1`: algorithm parameter in the range [0,1].
- `gamma1`: primal stepsize (see [1] for the default choice).
- `gamma2`: dual stepsize (see [1] for the default choice).

# References
1. Latafat, Patrinos, "Asymmetric forward-backward-adjoint splitting for solving monotone inclusions involving three operators", Computational Optimization and Applications, vol. 68, no. 1, pp. 57-93 (2017).
2. Latafat, Patrinos, "Primal-dual proximal algorithms for structured convex optimization: a unifying framework", In Large-Scale and Distributed Optimization, Giselsson and Rantzer, Eds. Springer International Publishing, pp. 97-120 (2018).
3. Condat, "A primal-dual splitting method for convex optimization involving Lipschitzian, proximable and linear composite terms", Journal of Optimization Theory and Applications, vol. 158, no. 2, pp 460-479 (2013).
4. Vũ, "A splitting algorithm for dual monotone inclusions involving cocoercive operators", Advances in Computational Mathematics, vol. 38, no. 3, pp. 667-681 (2013).
"""
Base.@kwdef struct AFBAIteration{R,Tx,Ty,Tf,Tg,Th,Tl,TL,Tbetaf,Tbetal,Ttheta,Tmu,Tlambda}
    f::Tf = Zero()
    g::Tg = Zero()
    h::Th = Zero()
    l::Tl = IndZero()
    L::TL = if isa(h, Zero) L = 0 * I else I end
    x0::Tx
    y0::Ty
    beta_f::Tbetaf = if isa(f, Zero)
        real(eltype(x0))(0)
    else
        error("argument beta_f must be specified together with f")
    end
    beta_l::Tbetal = if isa(l, IndZero)
        real(eltype(x0))(0)
    else
        error("argument beta_l must be specified together with l")
    end
    theta::Ttheta = real(eltype(x0))(1)
    mu::Tmu = real(eltype(x0))(1)
    lambda::Tlambda = real(eltype(x0))(1)
    gamma::Tuple{R, R} = if lambda != 1
        error("if lambda != 1, then you need to provide stepsizes manually")
    else
        T = real(eltype(x0))
        AFBA_default_stepsizes(L, h, T(theta), T(mu), T(beta_f), T(beta_l))
    end
end

Base.IteratorSize(::Type{<:AFBAIteration}) = Base.IsInfinite()

"""
    VuCondatIteration(; <keyword-arguments>)

Iterator implementing the Vũ-Condat primal-dual algorithm [1, 2].

This iterator solves convex optimization problems of the form

    minimize f(x) + g(x) + (h □ l)(L x),

where `f` is smooth, `g` and `h` are possibly nonsmooth and `l` is strongly
convex. Symbol `□` denotes the infimal convolution, and `L` is a linear mapping.

This iteration is equivalent to [`AFBAIteration`](@ref) with `theta=2`;
for all other arguments see [`AFBAIteration`](@ref).

See also: [`AFBAIteration`](@ref), [`VuCondat`](@ref).

# References
1. Condat, "A primal-dual splitting method for convex optimization involving Lipschitzian, proximable and linear composite terms", Journal of Optimization Theory and Applications, vol. 158, no. 2, pp 460-479 (2013).
2. Vũ, "A splitting algorithm for dual monotone inclusions involving cocoercive operators", Advances in Computational Mathematics, vol. 38, no. 3, pp. 667-681 (2013).
"""
VuCondatIteration(; kwargs...) = AFBAIteration(kwargs..., theta=2)

"""
    ChambollePockIteration(; <keyword-arguments>)

Iterator implementing the Chambolle-Pock primal-dual algorithm [1].

This iterator solves convex optimization problems of the form

    minimize g(x) + h(L x),

where `g` and `h` are possibly nonsmooth, and `L` is a linear mapping.

See also: [`AFBAIteration`](@ref), [`ChambollePock`](@ref).

This iteration is equivalent to [`AFBAIteration`](@ref) with `theta=2`, `f=Zero()`, `l=IndZero()`;
for all other arguments see [`AFBAIteration`](@ref).

# References
1. Chambolle, Pock, "A First-Order Primal-Dual Algorithm for Convex Problems with Applications to Imaging", Journal of Mathematical Imaging and Vision, vol. 40, no. 1, pp. 120-145 (2011).
"""
ChambollePockIteration(; kwargs...) = AFBAIteration(kwargs..., theta=2, f=Zero(), l=IndZero())

Base.@kwdef struct AFBAState{Tx,Ty}
    x::Tx
    y::Ty
    xbar::Tx = similar(x)
    ybar::Ty = similar(y)
    gradf::Tx = similar(x)
    gradl::Ty = similar(y)
    FPR_x::Tx = similar(x)
    FPR_y::Ty = similar(y)
    temp_x::Tx = similar(x)
    temp_y::Ty = similar(y)
end

function Base.iterate(iter::AFBAIteration, state::AFBAState = AFBAState(x=copy(iter.x0), y=copy(iter.y0)))
    # perform xbar-update step
    gradient!(state.gradf, iter.f, state.x)
    mul!(state.temp_x, iter.L', state.y)
    state.temp_x .+= state.gradf
    state.temp_x .*= -iter.gamma[1]
    state.temp_x .+= state.x
    prox!(state.xbar, iter.g, state.temp_x, iter.gamma[1])

    # perform ybar-update step
    gradient!(state.gradl, convex_conjugate(iter.l), state.y)
    state.temp_x .= iter.theta .* state.xbar .+ (1 - iter.theta) .* state.x
    mul!(state.temp_y, iter.L, state.temp_x)
    state.temp_y .-= state.gradl
    state.temp_y .*= iter.gamma[2]
    state.temp_y .+= state.y
    prox!(state.ybar, convex_conjugate(iter.h), state.temp_y, iter.gamma[2])

    # the residues
    state.FPR_x .= state.xbar .- state.x
    state.FPR_y .= state.ybar .- state.y

    # perform x-update step
    state.temp_y .= (iter.mu * (2 - iter.theta) * iter.gamma[1]) .* state.FPR_y
    mul!(state.temp_x, iter.L', state.temp_y)
    state.x .+= iter.lambda .* (state.FPR_x .- state.temp_x)

    # perform y-update step
    state.temp_x .= ((1 - iter.mu) * (2 - iter.theta) * iter.gamma[2]) .* state.FPR_x
    mul!(state.temp_y, iter.L, state.temp_x)
    state.y .+= iter.lambda .* (state.FPR_y .+ state.temp_y)

    return state, state
end

default_stopping_criterion(tol, ::AFBAIteration, state::AFBAState) = norm(state.FPR_x, Inf) + norm(state.FPR_y, Inf) <= tol
default_solution(::AFBAIteration, state::AFBAState) = (state.xbar, state.ybar)
default_display(it, ::AFBAIteration, state::AFBAState) = @printf("%6d | %7.4e\n", it, norm(state.FPR_x, Inf) + norm(state.FPR_y, Inf))

"""
    AFBA(; <keyword-arguments>)

Constructs the asymmetric forward-backward-adjoint algorithm (AFBA, see [1]).

This algorithm solves convex optimization problems of the form

    minimize f(x) + g(x) + (h □ l)(L x),

where `f` is smooth, `g` and `h` are possibly nonsmooth and `l` is strongly
convex. Symbol `□` denotes the infimal convolution, and `L` is a linear mapping.

The returned object has type `IterativeAlgorithm{AFBAIteration}`,
and can be called with the problem's arguments to trigger its solution.

See also: [`AFBAIteration`](@ref), [`IterativeAlgorithm`](@ref).

# Arguments
- `maxit::Int=10_000`: maximum number of iteration
- `tol::1e-5`: tolerance for the default stopping criterion
- `stop::Function`: termination condition, `stop(::T, state)` should return `true` when to stop the iteration
- `solution::Function`: solution mapping, `solution(::T, state)` should return the identified solution
- `verbose::Bool=false`: whether the algorithm state should be displayed
- `freq::Int=100`: every how many iterations to display the algorithm state
- `display::Function`: display function, `display(::Int, ::T, state)` should display a summary of the iteration state
- `kwargs...`: additional keyword arguments to pass on to the `AFBAIteration` constructor upon call

# References
1. Latafat, Patrinos, "Asymmetric forward-backward-adjoint splitting for solving monotone inclusions involving three operators", Computational Optimization and Applications, vol. 68, no. 1, pp. 57-93 (2017).
2. Latafat, Patrinos, "Primal-dual proximal algorithms for structured convex optimization: a unifying framework", In Large-Scale and Distributed Optimization, Giselsson and Rantzer, Eds. Springer International Publishing, pp. 97-120 (2018).
"""
AFBA(;
    maxit=10_000,
    tol=1e-5,
    stop=(iter, state) -> default_stopping_criterion(tol, iter, state),
    solution=default_solution,
    verbose=false,
    freq=100,
    display=default_display,
    kwargs...
) = IterativeAlgorithm(AFBAIteration; maxit, stop, solution, verbose, freq, display, kwargs...)

"""
    VuCondat(; <keyword-arguments>)

Constructs the Vũ-Condat primal-dual algorithm [1, 2].

This algorithm solves convex optimization problems of the form

    minimize f(x) + g(x) + (h □ l)(L x),

where `f` is smooth, `g` and `h` are possibly nonsmooth and `l` is strongly
convex. Symbol `□` denotes the infimal convolution, and `L` is a linear mapping.

The returned object has type `IterativeAlgorithm{AFBAIteration}`,
and can be called with the problem's arguments to trigger its solution.

See also: [`VuCondatIteration`](@ref), [`AFBAIteration`](@ref), [`IterativeAlgorithm`](@ref).

# Arguments
- `maxit::Int=10_000`: maximum number of iteration
- `tol::1e-5`: tolerance for the default stopping criterion
- `stop::Function`: termination condition, `stop(::T, state)` should return `true` when to stop the iteration
- `solution::Function`: solution mapping, `solution(::T, state)` should return the identified solution
- `verbose::Bool=false`: whether the algorithm state should be displayed
- `freq::Int=100`: every how many iterations to display the algorithm state
- `display::Function`: display function, `display(::Int, ::T, state)` should display a summary of the iteration state
- `kwargs...`: additional keyword arguments to pass on to the `AFBAIteration` constructor upon call

# References
1. Condat, "A primal-dual splitting method for convex optimization involving Lipschitzian, proximable and linear composite terms", Journal of Optimization Theory and Applications, vol. 158, no. 2, pp 460-479 (2013).
2. Vũ, "A splitting algorithm for dual monotone inclusions involving cocoercive operators", Advances in Computational Mathematics, vol. 38, no. 3, pp. 667-681 (2013).
"""
VuCondat(; kwargs...) = AFBA(; kwargs..., theta=2)

"""
    ChambollePock(; <keyword-arguments>)

Constructs the Chambolle-Pock primal-dual algorithm [1].

This algorithm solves convex optimization problems of the form

    minimize g(x) + h(L x),

where `g` and `h` are possibly nonsmooth, and `L` is a linear mapping.

The returned object has type `IterativeAlgorithm{AFBAIteration}`,
and can be called with the problem's arguments to trigger its solution.

See also: [`ChambollePockIteration`](@ref), [`AFBAIteration`](@ref), [`IterativeAlgorithm`](@ref).

# Arguments
- `maxit::Int=10_000`: maximum number of iteration
- `tol::1e-5`: tolerance for the default stopping criterion
- `stop::Function`: termination condition, `stop(::T, state)` should return `true` when to stop the iteration
- `solution::Function`: solution mapping, `solution(::T, state)` should return the identified solution
- `verbose::Bool=false`: whether the algorithm state should be displayed
- `freq::Int=100`: every how many iterations to display the algorithm state
- `display::Function`: display function, `display(::Int, ::T, state)` should display a summary of the iteration state
- `kwargs...`: additional keyword arguments to pass on to the `AFBAIteration` constructor upon call

# References
1. Chambolle, Pock, "A First-Order Primal-Dual Algorithm for Convex Problems with Applications to Imaging", Journal of Mathematical Imaging and Vision, vol. 40, no. 1, pp. 120-145 (2011).
"""
ChambollePock(; kwargs...) = AFBA(; kwargs..., f=Zero(), l=IndZero(), theta=2)

function AFBA_default_stepsizes(L, h::Zero, theta::R, mu::R, beta_f::R, beta_l::R) where R
    return R(1.99) / beta_f, R(1)
end

function AFBA_default_stepsizes(L, h, theta::R, mu::R, beta_f::R, beta_l::R) where R
    par = R(5) # scaling parameter for comparing Lipschitz constants and \|L\|
    par2 = R(100)   # scaling parameter for α
    alpha = R(1)
    nmL = R(opnorm(L))

    if theta ≈ 2 # default stepsize for Vu-Condat
        if nmL > par * max(beta_l, beta_f)
            alpha = R(1)
        elseif beta_f > par * beta_l
            alpha = par2 * nmL / beta_f
        elseif beta_l > par * beta_f
            alpha = beta_l / (par2 * nmL)
        end
        gamma1 = R(1) / (beta_f / 2 + nmL / alpha)
        gamma2 = R(0.99) / (beta_l / 2 + nmL * alpha)
    elseif theta ≈ 1 && mu ≈ 1 # SPCA
        if nmL > par2 * beta_l # for the case beta_f = 0
            alpha = R(1)
        elseif beta_l > par * beta_f
            alpha = beta_l / (par2 * nmL)
        end
        gamma1 = beta_f > 0 ? R(1.99) / beta_f : R(1) / (nmL / alpha)
        gamma2 = R(0.99) / (beta_l / 2 + gamma1 * nmL^2)
    elseif theta ≈ 0 && mu ≈ 1 # PPCA
        temp = R(3)
        if beta_f ≈ 0
            nmL *= sqrt(temp)
            if nmL > par * beta_l
                alpha = R(1)
            else
                alpha = beta_l / (par2 * nmL)
            end
            gamma1 = R(1) / (beta_f / 2 + nmL / alpha)
            gamma2 = R(0.99) / (beta_l / 2 + nmL * alpha)
        else
            if nmL > par * max(beta_l, beta_f)
                alpha = R(1)
            elseif beta_f > par * beta_l
                alpha = par2 * nmL / beta_f
            elseif beta_l > par * beta_f
                alpha = beta_l / (par2 * nmL)
            end
            xi = 1 + 2 * nmL / (nmL + alpha * beta_f / 2)
            gamma1 = R(1) / (beta_f / 2 + nmL / alpha)
            gamma2 = R(0.99) / (beta_l / 2 + xi * nmL * alpha)
        end
    elseif mu ≈ 0 # SDCA & PDCA
        temp = theta^2 - 3 * theta + 3
        if beta_l ≈ 0
            nmL *= sqrt(temp)
            if nmL > par * beta_f
                alpha = R(1)
            else
                alpha = par2 * nmL / beta_f
            end
            gamma1 = R(1) / (beta_f / 2 + nmL / alpha)
            gamma2 = R(0.99) / (beta_l / 2 + nmL * alpha)
        else
            if nmL > par * max(beta_l, beta_f)
                alpha = R(1)
            elseif beta_f > par * beta_l
                alpha = par2 * nmL / beta_f
            elseif beta_l > par * beta_f
                alpha = beta_l / (par2 * nmL)
            end
            eta = 1 + (temp - 1) * alpha * nmL / (alpha * nmL + beta_l / 2)
            gamma1 = R(1) / (beta_f / 2 + eta * nmL / alpha)
            gamma2 = R(0.99) / (beta_l / 2 + nmL * alpha)
        end
    elseif theta ≈ 0 && mu ≈ 0.5 # PPDCA
        if beta_l ≈ 0 || beta_f ≈ 0
            if nmL > par * max(beta_l, beta_f)
                alpha = R(1)
            elseif beta_f > par * beta_l
                alpha = par2 * nmL / beta_f
            elseif beta_l > par * beta_f
                alpha = beta_l / (par2 * nmL)
            end
        else
            alpha = sqrt(beta_l / beta_f) / 2
        end
        gamma1 = R(1) / (beta_f / 2 + nmL / alpha)
        gamma2 = R(0.99) / (beta_l / 2 + nmL * alpha)
    else
        error("this choice of theta and mu is not supported!")
    end

    return gamma1, gamma2
end


function AR_exit_T(MeName::String, MeOName::String, MeOFrac::Real, NMe::Real, NMeO::Real,
    InertName::String, Ts_in::Real, Tg_in = 300::Real)
    if MeOFrac > 1
        println("The metal fraction ($MeOFrac) must be smaller than 1! ")
    else
        # mole of metal and inert
        M_Me = 1000*MeOFrac/Mm(MeOName)*NMe/NMeO
        M_inert = 1000*(1 - MeOFrac)/Mm(InertName)
        # calculate needed air
        M_O2 = M_Me/NMe/0.95 # consider 95% O2 conversion
        M_N2 = M_O2*0.79/0.21
        #
        Hinlet = M_O2*GasEnthalpy("O2", Tg_in) + M_N2*GasEnthalpy("N2", Tg_in) +
        M_Me*SolidsEnthalpy(MeName, Ts_in) + M_inert*SolidsEnthalpy(InertName, Ts_in)
        T_exit = Ts_in # initial exit temperature
        ΔT = 1.0
        while abs(ΔT) > 0.1
            dH = Hinlet - (M_Me/NMe*NMeO*SolidsEnthalpy(MeOName, T_exit) +
            M_inert*SolidsEnthalpy(InertName, T_exit) +
            M_O2*0.05*GasEnthalpy("O2", T_exit) +
            M_N2*GasEnthalpy("N2", T_exit))
            if isnan(dH)
                println("error during calculation")
                return NaN
            end
            ΔT = dH/(M_Me/NMe*NMeO*SolidsCp(MeOName, T_exit) +
            M_inert*SolidsCp(InertName, T_exit) +
            M_O2*0.05*GasCp("O2", T_exit) +
            M_N2*GasCp("N2", T_exit) )

            T_exit = T_exit + ΔT
        end
        return T_exit
    end
end


# Shallow ice approximation (SIA) implicit solver for Greenland (steady state)
# Runs on both CPU (threaded) and GPU (Nvidia Cuda)

const USE_GPU = false # switch here to use GPU
# enable plotting & saving by default
if !@isdefined do_visu; do_visu = true end
if !@isdefined do_save; do_save = true end

using ParallelStencil
using ParallelStencil.FiniteDifferences2D
@static if USE_GPU
    @init_parallel_stencil(CUDA, Float64, 2)
else
    @init_parallel_stencil(Threads, Float64, 2)
end
using JLD, Plots, Printf, LinearAlgebra

# CPU functions: finite difference stencil operation support functions
@views av(A)  = 0.25*(A[1:end-1,1:end-1].+A[2:end,1:end-1].+A[1:end-1,2:end].+A[2:end,2:end])
@views inn(A) = A[2:end-1,2:end-1]

# GPU function
@parallel_indices (iy) function bc_x!(A::Data.Array)
    A[1  , iy] = A[2    , iy]
    A[end, iy] = A[end-1, iy]
    return
end

@parallel_indices (ix) function bc_y!(A::Data.Array)
    A[ix, 1  ] = A[ix, 2    ]
    A[ix, end] = A[ix, end-1]
    return
end

@parallel function compute_Err1!(Err, H)
    @all(Err) = @all(H)
    return
end

@parallel function compute_Err2!(Err, H)
    @all(Err) = @all(Err) - @all(H)
    return
end

@parallel function compute_M_dS!(M, dSdx, dSdy, S, z_ELA, grad_b, b_max, dx, dy)
    @all(M)    = min(@all(grad_b)*(@all(S) - @all(z_ELA)), b_max)
    @all(dSdx) = @d_xa(S)/dx
    @all(dSdy) = @d_ya(S)/dy
    return
end

@parallel function compute_D!(D, H, dSdx, dSdy, a, npow)
    @all(D) = a*@av(H)^(npow+2) * sqrt(@av_ya(dSdx)*@av_ya(dSdx) + @av_xa(dSdy)*@av_xa(dSdy))^(npow-1)
    return
end

@parallel function compute_flux!(qHx, qHy, D, S, dx, dy)
    @all(qHx)  = -@av_ya(D)*@d_xi(S)/dx
    @all(qHy)  = -@av_xa(D)*@d_yi(S)/dy
    return
end

@parallel function compute_dHdt!(dtau, ResH, dHdt, D, qHx, qHy, M, dtausc, cfl, epsi, damp, dx, dy)
    @all(dtau) = dtausc*min(10.0, cfl/(epsi + @av(D)))
    @all(ResH) = -(@d_xa(qHx)/dx + @d_ya(qHy)/dy) + @inn(M)
    @all(dHdt) = @all(dHdt)*damp + @all(ResH)
    return
end

@parallel function compute_H!(H, dHdt, dtau)
    @inn(H) = max(0.0, @inn(H) + @all(dtau)*@all(dHdt))
    return
end

@parallel_indices (ix,iy) function compute_Mask_S!(H, S, B, Mask)
    if (ix<=size(H,1) && iy<=size(H,2)) if (Mask[ix,iy]==0) H[ix,iy] = 0.0 end end
    if (ix<=size(S,1) && iy<=size(S,2)) S[ix,iy] = B[ix,iy] + H[ix,iy] end
    return
end

@parallel function compute_Vel!(Vx, Vy, D, H, dSdx, dSdy, epsi)
    @all(Vx) = -@all(D)/(@av(H) + epsi)*@av_ya(dSdx)
    @all(Vy) = -@all(D)/(@av(H) + epsi)*@av_xa(dSdy)
    return
end

@views function iceflow(dx, dy, Zbed, Hice, Mask, grad_b, z_ELA, b_max)
    println("Initialising ice flow model ... ")
    # physics
    s2y      = 3600*24*365.25  # seconds to years
    rho_i    = 910.0           # ice density
    g        = 9.81            # gravity acceleration
    npow     = 3.0             # Glen's power law exponent
    a0       = 1.5e-24         # Glen's law enhancement term
    # numerics
    @assert (dx>0 && dy>0) "dx and dy need to be positive"
    nx, ny   = size(Zbed,1), size(Zbed,2) # numerical grid resolution
    @assert (nx, ny) == size(Zbed) == size(Hice) == size(Mask) "Sizes don't match"
    itMax    = 1e5             # number of iteration (max)
    nout     = 200             # error check frequency
    tolnl    = 1e-6            # nonlinear tolerance
    epsi     = 1e-4            # small number
    damp     = 0.85            # convergence accelerator (this is a tuning parameter, dependent on e.g. grid resolution)
    dtausc   = 1.0/3.0         # iterative dtau scaling (this is a tuning parameter, dependent on e.g. grid resolution)
    # derived physics
    a        = 2.0*a0/(npow+2)*(rho_i*g)^npow*s2y
    # derived numerics
    cfl      = max(dx^2,dy^2)/4.1
    # array initialization
    Err      = @zeros(nx  , ny  )
    dSdx     = @zeros(nx-1, ny  )
    dSdy     = @zeros(nx  , ny-1)
    D        = @zeros(nx-1, ny-1)
    qHx      = @zeros(nx-1, ny-2)
    qHy      = @zeros(nx-2, ny-1)
    dtau     = @zeros(nx-2, ny-2)
    ResH     = @zeros(nx-2, ny-2)
    dHdt     = @zeros(nx-2, ny-2)
    Vx       = @zeros(nx-1, ny-1)
    Vy       = @zeros(nx-1, ny-1)
    M        = @zeros(nx  , ny  )
    S        = @zeros(nx  , ny  )
    grad_b   = Data.Array(grad_b)
    z_ELA    = Data.Array(z_ELA)
    Mask     = Data.Array(Mask)
    # initial conditions
    B        = Data.Array(Zbed)
    H        = Data.Array(Hice)
    S       .= B .+ H
    println(" starting iteration loop:")
    # iteration loop
    iter = 1; err = 2*tolnl
    while err>tolnl && iter<itMax
        @parallel compute_Err1!(Err, H)
        @parallel compute_M_dS!(M, dSdx, dSdy, S, z_ELA, grad_b, b_max, dx, dy)
        @parallel compute_D!(D, H, dSdx, dSdy, a, npow)
        @parallel compute_flux!(qHx, qHy, D, S, dx, dy)
        @parallel compute_dHdt!(dtau, ResH, dHdt, D, qHx, qHy, M, dtausc, cfl, epsi, damp, dx, dy)
        @parallel compute_H!(H, dHdt, dtau)
        @parallel compute_Mask_S!(H, S, B, Mask)
        # error check
        if mod(iter, nout)==0
            @parallel compute_Err2!(Err, H)
            err = norm(Err)/length(Err)
            @printf(" iter = %d, error = %1.2e \n", iter, err)
            if isnan(err)
                error("""NaNs encountered.  Try a combination of:
                             decreasing `damp` and/or `dtausc`, more smoothing steps""")
            end
        end
        iter += 1
    end
    @parallel compute_Vel!(Vx, Vy, D, H, dSdx, dSdy, epsi)
    # return as GeoArrays
    return  as_geoarray(Array(H),  Zbed, name=:thickness),
            as_geoarray(Array(S),  Zbed, name=:surface),
            as_geoarray(Array(M),  Zbed, name=:smb),
            as_geoarray(Array(Vx), Zbed, name=:vel_x, staggerd=true),
            as_geoarray(Array(Vy), Zbed, name=:vel_y, staggerd=true)
end
# ------------------------------------------------------------------------------
include(joinpath(@__DIR__, "helpers.jl"))

# load the data
print("Loading the data ... ")
Zbed, Hice, Mask, dx, dy, xc, yc = load_data(; nx=96) # nx=96,160 are included in the repo
                                                      # other numbers will trigger a 2GB download
println("done.")

# apply some smoothing
print("Applying some smoothing ... ")
for is=1:2 # two smoothing steps
    smooth!(Zbed)
    smooth!(Hice)
end
println("done.")

# calculate mass balance coefficients for given spatial grid
grad_b, z_ELA, b_max = mass_balance_constants(xc, yc)

# run the SIA flow model
H, S, M, Vx, Vy = iceflow(dx, dy, Zbed, Hice, Mask, grad_b, z_ELA, b_max)

# visualization and save
nx, ny = size(H)
if do_visu
    !ispath("../output") && mkdir("../output")

    H_v = copy(H); H_v[Mask.==0].=NaN
    Hice_v = copy(Hice); Hice_v[Mask.==0].=NaN
    S_v = copy(S); S_v[Mask.==0].=NaN
    M_v = copy(M); M_v[Mask.==0].=NaN
    V_v = sqrt.(Vx.^2 .+ Vy.^2)

    # outputs
    fontsize  = 7
    opts = (aspect_ratio=1, yaxis=font(fontsize, "Courier"), xaxis=font(fontsize, "Courier"),
            ticks=nothing, framestyle=:box, titlefontsize=fontsize, titlefont="Courier", colorbar_title="",
            xlabel="", ylabel="", xlims=(dims(H_v)[1][1],dims(H_v)[1][end]), ylims=(dims(H_v)[2][end],dims(H_v)[2][1]) )
    p1 = heatmap(S_v; c=:davos, title="Surface elev. [m]", opts...)
    p2 = heatmap(H_v; c=:davos, title="Ice thickness [m]", opts...)
    p3 = heatmap(log10.(V_v); clims=(0.1, 2.0), title="log10(vel) [m/yr]", opts...)
    p4 = heatmap(M_v; c=:devon, title="Mass Bal. rate [m/yr]", opts...)
    p = plot(p1, p2, p3, p4, size=(400,400), dpi=200) #background_color=:transparent, foreground_color=:white
    ## uncomment if you want a pop-up plot pane showing:
    # display(p)
    savefig("../output/iceflow_out1_xpu_$(nx)x$(ny).png")

    # error
    H_diff = Hice_v.-H_v
    fontsize = 7
    p1 = heatmap(Hice_v; c=:davos, title="H data [m]", opts...)
    p2 = heatmap(H_v; c=:davos, title="H model [m]", opts...)
    clim = max(abs.(extrema(H_diff[.!isnan.(H_diff)]))...)
    p3 = heatmap(H_diff; title="H (data-model) [m]",  clims=(-clim,clim), seriescolor=:balance, opts...)
    p = plot(p1, p2, p3, layout=(1, 3), size=(500,160), dpi=200) #background_color=:transparent, foreground_color=:white
    ## uncomment if you want a pop-up plot pane showing:
    # display(p)
    savefig("../output/iceflow_out2_xpu_$(nx)x$(ny).png")
end

if do_save
    save("../output/iceflow_xpu_$(nx)x$(ny).jld", "Hice", Hice,
                                                  "Mask", Mask,
                                                  "H"   , H,
                                                  "S"   , S,
                                                  "M"   , M,
                                                  "Vx"  , Vx,
                                                  "Vy"  , Vy,
                                                  "xc", xc, "yc", yc)
end

println("... done.")


### Quadrado: área e perímetro
# Faça um programa em que receba (entrada de dados)
# o valor correspondente ao lado de um quadrado,
# calcule e imprima (saída de dados) seu perímetro e sua área.

println("informe o valor do lado do quadrado")
square = parse(UInt8, readline())

println("Perímetro = ", (square*4) )
println("Área = ", (square^2) )


# This is code for computing a transit model integrated over
# a time step.

# First the version without derivatives:
function integrate_timestep(param::Array{T,1},trans::Transit_Struct{T},time::T,dt::T,tol::T,maxdepth::Int64) where {T <: Real}

  function solver(param::Array{T,1},time::T) where {T <: Real}
  # This predicts the impact parameter as a function of time in
  # terms of the transit_struct.
  # For now, transit is approximated as a straight line, where params are given as:
  # param[1] = t0 (mid-transit time; units of days/JD)
  # param[2] = v/R_* (sky velocity in terms of radius of the star - units are day^{-1}
  # param[3] = r (radius-ratio)
  # param[4] = b (impact parameter at time t0)
  btime = sqrt(param[4]^2+(param[2]*(time-param[1]))^2)
  return
  end

  # Now, adopt Dan Foreman-Mackey's recursive integration approach:
  function integrate(param::Array{T,1}, f1::T, f2::T, t1::T, t2::T, trans::Transit_Struct{T}) where {T <: Real}
  tmid = 0.5*(t1+t2);
  bmid = solver(param,tmid)
  trans.b = bmid
  fmid = transit_poly!(trans)
  fapprox = 0.5*(f1+f2)
  d = abs(fmid-fapprox)
  if d > tol && depth < maxdepth
     a = integrate(param,f1,fmid,t1,tmid,depth+1)
     b = integrate(param,fmid,f2,tmid,t2,depth+1)
     return a+b
  end
  return fapprox*(t2-t1)
  end

  t1 = time - 0.5*dt
  b1 = solver(param,t1)
  trans.b = b1
  f1 = transit_poly!(trans)
  t2 = time + 0.5*dt
  b2 = solver(param,t2)
  trans.b = b2
  f2 = transit_poly!(trans)
  fint = integrate(param,f1,f2,t1,t2,trans)
return fint
end