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

using Markdown
using InteractiveUtils

# ╔═╡ b0020dc0-78a7-483f-877f-719f42b0c044
using Plots, PlutoUI, DataStructures

# ╔═╡ aec0517c-01e7-4d3f-b9b4-3e4ec8acdfc3
begin

	using HTTP

	hamming(s,t) = sum((si != ti for (si, ti) in zip(s, t)))

	"""Get all English words"""
	function getwords()
		io = HTTP.get("https://github.com/dwyl/english-words/raw/master/words_alpha.txt")
		words = split(String(io.body), "\n") .|> rstrip .|> String
		return words
	end

	"""Get all English words of length `wordsize`"""
	function getwords(wordsize::Integer)
		return filter(w -> length(w) == wordsize, getwords())
	end

	"""
		getwordedges(words; cutoff=5)

	Returns all the weigthed edges between a list of `words` where the Hamming
	distance is less than `cutoff`.
	"""
	function getwordedges(words; cutoff=5)
		edges = Tuple{Int,String,String}[]
		for (i, w1) in enumerate(words[1:end-1])
			for w2 in words[(i+1):end]
				d = hamming(w1, w2)
				d ≤ cutoff && push!(edges, (d, w1, w2))
			end
		end
		return edges
	end

end #Words

# ╔═╡ 9803aaf0-08c3-4501-8865-318a988210c9
module Solution
	export reconstruct_path, dijkstra, a_star

	using DataStructures

	WeightedEdgeList{R,T} = Array{Tuple{R,T,T},1}
	AdjList{R,T} = Dict{T,Array{Tuple{R,T},1}}

	"""
		reconstruct_path(previous::Dict{T,T}, source::T, sink::T) where {T}

	Reconstruct the path from the output of the Dijkstra algorithm.

	Inputs:
			- previous : a Dict with the previous node in the path
			- source : the source node
			- sink : the sink node
	Ouput:
			- the shortest path from source to sink
	"""
	function reconstruct_path(previous::Dict{T,T}, source::T, sink::T) where {T}
		path = T[sink]
		current = sink
		while current != source
			current = previous[current]
			push!(path, current)
		end
		return reverse!(path)
	end

	"""
		dijkstra(graph::AdjList{R,T}, source::T) where {R<:Real,T}

	Dijkstra without a specified sink. Give the `graph` and a `source` and this
	function uses Dijkstra's algorithm to compute all distances between the nodes
	and the source. Returns
		- `distances`: a dictionary with the distances
		- `previous`: a dictionary representing the tree of the shortest paths.
	"""
	function dijkstra(graph::AdjList{R,T}, source::T) where {R<:Real,T}
		# initialize the tentative distances
		distances = Dict(v => Inf for v in keys(graph))
		distances[source] = 0.0
		previous = Dict{T,T}()
		vertices_to_check = [(0.0, source)]
		while length(vertices_to_check) > 0
			dist, u = heappop!(vertices_to_check)
			for (dist_u_v, v) in graph[u]
				new_dist = dist + dist_u_v
				if distances[v] > new_dist
					distances[v] = new_dist
					previous[v] = u
					heappush!(vertices_to_check, (new_dist, v))
				end
			end
		end
		return distances, previous
	end

	"""
		dijkstra(graph::AdjList{R,T}, source::T, sink::T) where {R<:Real,T}

	Dijkstra's shortest path algorithm.

	Inputs:
		- `graph` : adjacency list representing a weighted directed graph
		- `source`
		- `sink`

	Outputs:
		- the shortest path
		- the cost of this shortest path
	"""
	function dijkstra(graph::AdjList{R,T}, source::T, sink::T) where {R<:Real,T}
		# initialize the tentative distances
		distances = Dict(v => Inf for v in keys(graph))
		distances[source] = 0.0
		previous = Dict{T,T}()
		vertices_to_check = [(0.0, source)]
		while length(vertices_to_check) > 0
			dist, u = heappop!(vertices_to_check)
			if u == sink
				return reconstruct_path(previous, source, sink), dist
			end
			for (dist_u_v, v) in graph[u]
				new_dist = dist + dist_u_v
				if distances[v] > new_dist
					distances[v] = new_dist
					previous[v] = u
					heappush!(vertices_to_check, (new_dist, v))
				end
			end
		end
	end

	"""
		a_star(graph::AdjList{R,T}, source::T, sink::T, heuristic) where {R<:Real,T}

	A* shortest path algorithm.

	Inputs:
		- `graph` : adjacency list representing a weighted directed graph
		- `source`
		- `sink`
		- `heuristic` : a function that inputs a node and returns an lower bound
				for the distance to the source. Note that a distance can be turned into
				a heuristic using `n -> d(n, sink)`

	Outputs:
		- the shortest path
		- the cost of this shortest path
	"""
	function a_star(graph::AdjList{R,T}, source::T, sink::T, heuristic) where {R<:Real,T}
		# initialize the tentative distances
		distances = Dict(v => Inf for v in keys(graph))
		distances[source] = 0.0
		previous = Dict{T,T}()
		vertices_to_check = [(heuristic(source), source)]
		while length(vertices_to_check) > 0
			dist, u = heappop!(vertices_to_check)
			if u == sink
				return reconstruct_path(previous, source, sink), distances[sink]
			end
			for (dist_u_v, v) in graph[u]
				new_dist = dist + dist_u_v
				if distances[v] > new_dist
					distances[v] = new_dist
					min_dist_to_sink = new_dist + heuristic(v)
					previous[v] = u
					heappush!(vertices_to_check, (min_dist_to_sink, v))
				end
			end
		end
	end
end

# ╔═╡ 2ab0b80e-1e02-11ec-1596-8bd8e81d347c
md"""
# Shortest path problems

*STMO*

**Michiel Stock**
"""

# ╔═╡ b4977f9f-2bcc-405f-b875-99cde2ccb77d
md"## Dijkstra's shortest path algorithm

Dijkstra's algorithm is a popular algorithm to find the shortest path between the nodes of a graph. The algorithm can be used in two ways:

- when both a source and a sink node are provided, the algorithm gives the list of nodes of the shortest path, together with the length (distance of the path).
- when only a source is given, the shortest distance between the source and all (accessable) nodes is returned together with a dictionary representing the tree of the shortest paths between the source and other nodes.

The pseudocode can be found below."

# ╔═╡ 2492a904-77da-4a36-baf4-65d40aa89d74
md"""
### Pseudocode of Dijkstra's shortest path algorithm
Source: Wikipedia.org
```
1  function Dijkstra(Graph, source, sink (optional)):
2      distance[source] := 0  // initialize a dictionary with distance
3                             // to source
4      for each vertex V in Graph:
5          if V ≠ source
6              distance[V] := infinity
7      previous := empty dict   // stores the previous node in path
8
10     make empty priority queue Q for vertices to check
11     Q.add_with_priority(source, 0)  # first check the source
12
13
14     while Q is not empty:                     // The main loop
15         get node U with smallest distance from Q
16         if U is the sink (if provided)
17              break the loop
17         for every neighbor V of U:
18             alternative := distance[U] + dist(U, V)
18             if alternative < distance[V]
19                 distance[V] := alternative
20                 previous[V] := U
21                 Q.add_with_priority(V, alternative)
22
23     if sink is given
24         reconstruct shortest path
25         return shortest path and length of path
26     else
27         return distance, previous
```
"""

# ╔═╡ 194250f8-c94d-4732-b179-0ebead51110b
md"""
### Pseudocode of path reconstruction  algorithm
```
1 function reconstruct_path(previous, source, sink)
2    if the source is not in previous
3          return empty path
4    V := sink   \\ path is reconstructed backwards
5    path := [V]  \\ path is a list of nodes
6    while V is not source
7         V := previous node in path
8         add V in beginning of the path
9    return the path
```
"""

# ╔═╡ 40b7b2f4-ec2b-4b89-b2f0-adc368ee15bc
"""
    reconstruct_path(previous::Dict{T,T}, source::T, sink::T) where {T}

Reconstruct the path from the output of the Dijkstra algorithm.

Inputs:
        - previous : a Dict with the previous node in the path
        - source : the source node
        - sink : the sink node
Ouput:
        - the shortest path from source to sink
"""
function reconstruct_path(previous::Dict{T,T}, source::T, sink::T) where {T}
    path = T[sink]
    missing
    return missing
end

# ╔═╡ 67729a4c-df55-4fdb-8962-25b133eaaaf5
md"""

**Assignment 1: Dijkstra**

1. Complete the implementation for Dijstra's algorithm and the path reconstruction function
2. Find the shortest path from node A to I in the example graph below.
3. Find the shortest path from Portland to Nashville on the Ticket to Ride graph.
3. (optional, medium hard) Modify `dijkstra` such that the sink is optional. When no sink is given, the dictionary `previous` (where `previous[u] = v` indicates that the shortest path from `v` is via `u`) and `distances`, a dictionary containing the minimal distance from every node to the sink.

![Example network](https://github.com/MichielStock/STMO/blob/master/chapters/08.ShortestPath/Figures/example_graph.png?raw=true)
"""

# ╔═╡ a47b09eb-7441-4441-98de-d48111e172a7
# the above graph as an adjacency list
graph = Dict('A' => [(2, 'B'), (3, 'D')],
        'B' => [(1, 'C'), (5, 'E')],
        'C' => [(2, 'D'), (1, 'E')],
        'D' => [(3, 'E')],
        'E' => [(2, 'B'), (3, 'F')],
        'F' => [(5, 'G'), (8, 'I')],
        'G' => [(2, 'H'), (5, 'I')],
        'H' => [(3, 'I')],
        'I' => [(7, 'G')]);

# ╔═╡ c12360cc-5208-417c-9003-5ce1308e8081
# find the shortest path

# ╔═╡ 47e0c111-83c1-4c3f-b293-a337b19feee9

# ╔═╡ a7cb520d-d221-45e9-83ac-e1927aafd0ab
md"""
## A* shortest path algorithm

It is hard to explain the algorithm into more depth than [here](http://theory.stanford.edu/~amitp/GameProgramming/) and [here](http://www.redblobgames.com/pathfinding/a-star/introduction.html).

The A\* algorithm is exact (like Dijkstra's algorithm), but it can also use a **heuristic** to speed up the search. In each iteration, the next neighbor $v$ of the current vertex that the algorithm considers is chosen based on the heuristic

$$f(v) = g(v) + h(v)$$

with $g(v)$ the cost of the path so far and $h(v)$ a heuristic that estimates the cost of the cost of the shortest path from $v$ to the goal. The heuristic $h(v)$ should satify two properties:
1. To guaranty that a shortest path is found, the heuristic should be **admissible**, i.e. it should never overestimate the true distance to goal.
2. Evaluating the heuristic should be cheap (it is either pre-computed or is a function).

So the A\* algorithm is basically the same as Dijkstra's algorithm, but with the main difference that the the latter chooses nodes to explore based on the distance from the starting node, while the latter chooses nodes **based on an estimate of distance to the goal**.

When using A\* to find the shortest path between two physical locations, each vertex corresponds to a point in space. A good heuristic is the (Euclidian) distance between the vertexs, as this will always be a lower bound for the actual distance to travel to the goal.

Consider the following example of finding the path from vertex $a$ to vertex $h$.

![Example how A\* uses the heuristics](https://github.com/MichielStock/STMO/blob/master/chapters/08.ShortestPath/Figures/astarexample.png?raw=true)

The shortest path so far is $[a, e]$ and the algorithm needs to choose to explore vertex $d$ or vertex $g$. Since the path of $ed$ is shorter than the path $eg$, Dijkstra will choose this vertex, even though it is farther removed from the goal $h$ (Dijkstra chooses a neighbor $n$ only based on the current path length form the starting vertex $g(n)$. The A\* algorithm will chose vertex $g$ to explore, because the estimated path length $f(e) = g(e) + h(e) > f(d)$ and hence will approach the goal.
"""

# ╔═╡ a2906480-383f-4456-9a80-04d172f85c41
md"""
### Pseudocode of A\* shortest path algorithm
Source from Wikipedia (modified)
```
1  function Astar(Graph, source, sink, heuristic):
2      make empty priority queue Q for vertices to check
3
4      add source to Q with priority f(source)
5          // use the estimated path length for priority
6
7      distance[V] := 0   // initialize a dictionary with distance
8                           // to source
9      previous := empty dict   // stores the previous node in path
10
11     while Q is not empty:                     // The main loop
12         pop node U with lowest estimated path length to sink
13         if U is the sink
14              break the loop
15         for every neighbor V of U:
16             distance_source_U := distance[U] + dist(U, V)
17             if V not in distance or distance_source_U < distance[V]
18                 distance[V] := distance_source_U
19                 previous[V] := U
20                 heuristic_V_sink := distance[V] + heuristic(V)
21                 Q.add_with_priority(V, heuristic_V_sink)
22
23     reconstruct shortest path
24     return shortest path and length of path
```
"""

# ╔═╡ f8bc5419-054e-4b8f-807e-b594784661f6
md"""
**Assignment 2: A**\*

1. Complete the implementation for the A\* algorithm.
2. Compute the shortest path between Portland to Nashville. The function `tickettoride_dist` returns the shortest distance (as the bird flies) between two cities.
3. Compare running time of the two shortest path algorithms using `@time`.
"""

# ╔═╡ 74b62c2b-58b5-4c37-9399-266855086f9b

# ╔═╡ 54b185a7-7b4e-4756-9663-db0b6419de27
md"""
## Word problem

Let us use shortest path for some word problems? What is the shortest chain from one word to another if you can only change 5 letters at a time? We will explore this on all words of length 12.
"""

# ╔═╡ db00faa4-1d04-47aa-a062-4cf9b8cbe208
words12 = getwords(12)

# ╔═╡ fddb2f51-e5dc-47eb-81d9-9485435c60ea
md"We have the function `hamming` to compute the hamming distance between two stings."

# ╔═╡ 6a1dae01-b78b-4e8b-9a4d-d20bc6a025a5
w1, w2 = rand(words12, 2)

# ╔═╡ 4a867566-14eb-4d8a-ae17-a267054840fd
hamming(w1, w2)

# ╔═╡ cf659c75-b8b9-488d-b937-36ea1e0cadbe
md"We can compute the weighted graph where two words are connected if they have a Hamming distance of at most 5."

# ╔═╡ 94a53663-6dba-4e9b-8c30-bc1f2be3a43e
words_edges = getwordedges(words12, cutoff=5);

# ╔═╡ 9fd3c33d-275d-420b-a03b-8ceb81a56ad3
md"""
**Assignment 3: word salad**

1. Use Dijkstra and A* to find the distance between two randomly chosen words.
2. Can you find the longest word chain?
"""

# ╔═╡ 9d467594-8a33-4e1d-a272-744379bf2722

# ╔═╡ 63e2620a-6897-4664-81e3-479a7a5af9ff

# ╔═╡ eb9dfc6d-b9d8-4771-9b71-e4d0fba38cb7
md"## Appendix"

# ╔═╡ 89941271-8a9d-4e1c-94b0-77b35ebd8816
begin
	myblue = "#304da5"
	mygreen = "#2a9d8f"
	myyellow = "#e9c46a"
	myorange = "#f4a261"
	myred = "#e76f51"
	myblack = "#50514F"

	mycolors = [myblue, myred, mygreen, myorange, myyellow]
end;

# ╔═╡ ecde4baa-0aff-434c-b34a-59b0938a100f
const tickettoride_edges = [(1, "Vancouver", "Seattle"),
          (1, "Seattle", "Portland"),
          (3, "Vancouver", "Calgary"),
          (6, "Calgary", "Winnipeg"),
          (6, "Winnipeg", "Sault Ste. Marie"),
          (4, "Winnipeg", "Helena"),
          (4, "Calgary", "Helena"),
          (6, "Seattle", "Helena"),
          (4, "Seattle", "Calgary"),
          (6, "Portland", "Salt Lake City"),
          (5, "Portland", "San Francisco"),
          (5, "San Francisco", "Salt Lake City"),
          (3, "San Francisco", "Los Angeles"),
          (2, "Los Angeles", "Las Vegas"),
          (3, "Los Angeles", "Phoenix"),
          (6, "Los Angeles", "El Paso"),
          (3, "Phoenix", "El Paso"),
          (3, "Phoenix", "Santa Fe"),
          (3, "Las Vegas", "Salt Lake City"),
          (5, "Phoenix", "Denver"),
          (3, "Salt Lake City", "Denver"),
          (3, "Helena", "Salt Lake City"),
          (6, "Helena", "Duluth"),
          (4, "Winnipeg", "Duluth"),
          (4, "Helena", "Denver"),
          (5, "Helena", "Omaha"),
          (4, "Denver", "Omaha"),
          (4, "Denver", "Kansas City"),
          (2, "Denver", "Santa Fe"),
          (2, "Santa Fe", "El Paso"),
          (3, "Santa Fe", "Oklahoma City"),
          (4, "Denver", "Oklahoma City"),
          (6, "El Paso", "Houston"),
          (4, "El Paso", "Dallas"),
          (5, "El Paso", "Oklahoma City"),
          (1, "Dallas", "Houston"),
          (2, "Dallas", "Oklahoma City"),
          (2, "Kansas City", "Oklahoma City"),
          (1, "Omaha", "Kansas City"),
          (2, "Omaha", "Duluth"),
          (3, "Duluth", "Chicago"),
          (4, "Omaha", "Chicago"),
          (6, "Duluth", "Toronto"),
          (3, "Duluth", "Sault Ste. Marie"),
          (5, "Sault Ste. Marie", "Montreal"),
          (2, "Montreal", "Boston"),
          (2, "Boston", "New York"),
          (3, "Montreal", "New York"),
          (3, "Montreal", "Toronto"),
          (4, "Toronto", "Chicago"),
          (3, "Chicago", "Pittsburgh"),
          (2, "Chicago", "Saint Louis"),
          (2, "Pittsburgh", "Toronto"),
          (2, "Toronto", "Sault Ste. Marie"),
          (2, "Pittsburgh", "New York"),
          (2, "Pittsburgh", "Washington"),
          (2, "Washington", "New York"),
          (2, "Washington", "Raleigh"),
          (2, "Pittsburgh", "Raleigh"),
          (5, "Pittsburgh", "Saint Louis"),
          (2, "Kansas City", "Saint Louis"),
          (2, "Nashville", "Saint Louis"),
          (2, "Little Rock", "Saint Louis"),
          (2, "Oklahoma City", "Little Rock"),
          (2, "Little Rock", "Dallas"),
          (3, "Little Rock", "Nashville"),
          (2, "Houston", "New Orleans"),
          (3, "Little Rock", "New Orleans"),
          (4, "New Orleans", "Atlanta"),
          (1, "Atlanta", "Nashville"),
          (4, "Nashville", "Pittsburgh"),
          (2, "Atlanta", "Raleigh"),
          (3, "Nashville", "Raleigh"),
          (2, "Raleigh", "Charleston"),
          (2, "Charleston", "Atlanta"),
          (6, "New Orleans", "Miami"),
          (5, "Atlanta", "Miami"),
          (4, "Charleston", "Miami")
          ]

# ╔═╡ 30651af8-ad5d-4cd2-89be-8283d381da5b
const cities_coordinates = Dict("Atlanta" => (-84.3901849, 33.7490987),
                               "Boston" => (-71.0595678, 42.3604823),
                               "Calgary" => (-114.0625892, 51.0534234),
                               "Charleston" => (-79.9402728, 32.7876012),
                               "Chicago" => (-87.6244212, 41.8755546),
                               "Dallas" => (-96.7968559, 32.7762719),
                               "Denver" => (-104.9847034, 39.7391536),
                               "Duluth" => (-92.1251218, 46.7729322),
                               "El Paso" => (-106.501349395577, 31.8111305),
                               "Helena" => (-112.036109, 46.592712),
                               "Houston" => (-95.3676974, 29.7589382),
                               "Kansas City" => (-94.5630298, 39.0844687),
                               "Las Vegas" => (-115.149225, 36.1662859),
                               "Little Rock" => (-92.2895948, 34.7464809),
                               "Los Angeles" => (-118.244476, 34.054935),
                               "Miami" => (-80.1936589, 25.7742658),
                               "Montreal" => (-73.6103642, 45.4972159),
                               "Nashville" => (-86.7743531, 36.1622296),
                               "New Orleans" => (-89.9750054503052, 30.03280175),
                               "New York" => (-73.9866136, 40.7306458),
                               "Oklahoma City" => (-97.5170536, 35.4729886),
                               "Omaha" => (-95.9378732, 41.2587317),
                               "Phoenix" => (-112.0773456, 33.4485866),
                               "Pittsburgh" => (-79.99, 40.42),
                               "Portland" => (-122.6741949, 45.5202471),
                               "Raleigh" => (-78.6390989, 35.7803977),
                               "Saint Louis" => (-90.12954315, 38.60187637),
                               "Salt Lake City" => (-111.8904308, 40.7670126),
                               "San Francisco" => (-122.49, 37.75),
                               "Santa Fe" => (-105.9377997, 35.6869996),
                               "Sault Ste. Marie" => (-84.320068, 46.52391),
                               "Seattle" => (-122.3300624, 47.6038321),
                               "Toronto" => (-79.387207, 43.653963),
                               "Vancouver" => (-123.1139529, 49.2608724),
                               "Washington" => (-77.0366456, 38.8949549),
                               "Winnipeg" => (-97.168579, 49.884017))

# ╔═╡ 1e332d32-7846-4bdb-935e-505dd290e1f0
begin
	p = plot(xaxis="longitude", yaxis="lattitude")

	# add edges
	for (w, c1, c2) in tickettoride_edges
		x1, y1 = cities_coordinates[c1]
		x2, y2 = cities_coordinates[c2]
		plot!(p, [x1, x2], [y1, y2], color=myred, alpha=0.8,
			lw=w, label="")
	end
	# plot cities
	for (city, (x, y)) in cities_coordinates
		println("$city: $x, $y")
		scatter!(p, [x], [y], label="", color=mygreen, markersize=10, alpha=0.8
		, annotations=[(x, y, city, 8)]  # comment this for clarity
		)
	end
	p
end

# ╔═╡ d69435c1-c19a-4a94-8778-cea2ffcdfaa6
const cities = [k for k in keys(cities_coordinates)]

# ╔═╡ 8c64f65f-b610-40b5-b9a1-6abb39a6f0bb
tickettoride_dist(c1, c2) = sqrt(sum((cities_coordinates[c1] .- cities_coordinates[c2]).^2))

# ╔═╡ 76b4809a-3fed-4bea-bfe8-66582cbb3fc9
tickettoride_edges_dists = [(tickettoride_dist(u, v), u, v) for (w, u, v) in tickettoride_edges]

# ╔═╡ 427428ab-1978-4317-86f3-ea211141e52f
WeightedEdgeList{R,T} = Array{Tuple{R,T,T},1}

# ╔═╡ 9d754d22-2a09-4458-8fa0-589ae4f75d76
AdjList{R,T} = Dict{T,Array{Tuple{R,T},1}}

# ╔═╡ 38cb6e91-b7bb-4000-b883-f24c17ad827a
"""
    dijkstra(graph::AdjList{R,T}, source::T, sink::T) where {R<:Real,T}

Dijkstra's shortest path algorithm.

Inputs:
    - `graph` : adjacency list representing a weighted directed graph
    - `source` : node to start with
	- `sink` : node to end with

Outputs:
    - the shortest path
    - the cost of this shortest path
"""
function dijkstra(graph::AdjList{R,T}, source::T, sink) where {R<:Real,T}
    # initialize the tentative distances
    distances = Dict(v => Inf for v in keys(graph))
    #distances[source] = missing
    return missing
end

# ╔═╡ 6b6ede88-b4b8-4dc7-8f27-bc2228512e22
dijkstra(graph, 'A', 'I')

# ╔═╡ 0c280cb9-9f30-4570-91a1-19538b56f957
"""
    a_star(graph::AdjList{R,T}, source::T, sink::T, heuristic) where {R<:Real,T}

A* shortest path algorithm.

Inputs:
    - `graph` : adjacency list representing a weighted directed graph
    - `source`
    - `sink`
    - `heuristic` : a function that inputs a node and returns an lower bound
            for the distance to the source. Note that a distance can be turned into
            a heuristic using `n -> d(n, sink)`

Outputs:
    - the shortest path
    - the cost of this shortest path
"""
function a_star(graph::AdjList{R,T}, source::T, sink::T, heuristic) where {R<:Real,T}
    # initialize the tentative distances
    distances = Dict(v => Inf for v in keys(graph))
    missing
    while missing
        missing
	end
	return missing
end

# ╔═╡ 7654100f-86ca-481f-bfb7-bc9b9ee20639
"""
Turns a list of weighted edges in an adjacency matrix (implemented as a Dict).
If the keyword `double` is set to `true`, every edge is added twice: `(w, u, v)`
and `(w, v, u)`. This is the default behaviour.
"""
function edges2adjlist(edges::WeightedEdgeList{R,T}; double=true) where {R<:Real,T}
    adjlist = AdjList{R,T}()
    for (w, i, j) in edges
        if !haskey(adjlist, i); adjlist[i] = [] end
        if !haskey(adjlist, j); adjlist[j] = [] end
        push!(adjlist[i], (w, j))
        double && push!(adjlist[j], (w, i))
    end
    return adjlist
end

# ╔═╡ 1a2a55a5-424c-4522-a4bd-9c657f9f8565
words_graph = edges2adjlist(words_edges)

# ╔═╡ 80c1d9d2-f8d7-4a16-90a3-a0af2ce96ced
const tickettoride_graph = edges2adjlist(tickettoride_edges_dists)

# ╔═╡ a130184f-06af-4767-ad6a-d8d8c1bbf6ca
tickettoride_graph

# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3"
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8"

[compat]
DataStructures = "~0.18.10"
HTTP = "~0.9.14"
Plots = "~1.22.2"
PlutoUI = "~0.7.11"
"""

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

[[Adapt]]
deps = ["LinearAlgebra"]
git-tree-sha1 = "84918055d15b3114ede17ac6a7182f68870c16f7"
uuid = "79e6a3ab-5dfb-504d-930d-738a2a938a0e"
version = "3.3.1"

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

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

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

[[Bzip2_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "19a35467a82e236ff51bc17a3a44b69ef35185a2"
uuid = "6e34b625-4abd-537c-b88f-471c36dfa7a0"
version = "1.0.8+0"

[[Cairo_jll]]
deps = ["Artifacts", "Bzip2_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "JLLWrappers", "LZO_jll", "Libdl", "Pixman_jll", "Pkg", "Xorg_libXext_jll", "Xorg_libXrender_jll", "Zlib_jll", "libpng_jll"]
git-tree-sha1 = "f2202b55d816427cd385a9a4f3ffb226bee80f99"
uuid = "83423d85-b0ee-5818-9007-b63ccbeb887a"
version = "1.16.1+0"

[[ColorSchemes]]
deps = ["ColorTypes", "Colors", "FixedPointNumbers", "Random"]
git-tree-sha1 = "9995eb3977fbf67b86d0a0a0508e83017ded03f2"
uuid = "35d6a980-a343-548e-a6ea-1d62b119f2f4"
version = "3.14.0"

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

[[Colors]]
deps = ["ColorTypes", "FixedPointNumbers", "Reexport"]
git-tree-sha1 = "417b0ed7b8b838aa6ca0a87aadf1bb9eb111ce40"
uuid = "5ae59095-9a9b-59fe-a467-6f913c188581"
version = "0.12.8"

[[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 = "31d0151f5716b655421d9d75b7fa74cc4e744df2"
uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
version = "3.39.0"

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

[[Contour]]
deps = ["StaticArrays"]
git-tree-sha1 = "9f02045d934dc030edad45944ea80dbd1f0ebea7"
uuid = "d38c429a-6771-53c6-b99e-75d170b6e991"
version = "0.5.7"

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

[[DataStructures]]
deps = ["Compat", "InteractiveUtils", "OrderedCollections"]
git-tree-sha1 = "7d9d316f04214f7efdbb6398d545446e246eff02"
uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
version = "0.18.10"

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

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

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

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

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

[[EarCut_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "3f3a2501fa7236e9b911e0f7a588c657e822bb6d"
uuid = "5ae413db-bbd1-5e63-b57d-d24a61df00f5"
version = "2.2.3+0"

[[Expat_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "b3bfd02e98aedfa5cf885665493c5598c350cd2f"
uuid = "2e619515-83b5-522b-bb60-26c02a35a201"
version = "2.2.10+0"

[[FFMPEG]]
deps = ["FFMPEG_jll"]
git-tree-sha1 = "b57e3acbe22f8484b4b5ff66a7499717fe1a9cc8"
uuid = "c87230d0-a227-11e9-1b43-d7ebe4e7570a"
version = "0.4.1"

[[FFMPEG_jll]]
deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "JLLWrappers", "LAME_jll", "Libdl", "Ogg_jll", "OpenSSL_jll", "Opus_jll", "Pkg", "Zlib_jll", "libass_jll", "libfdk_aac_jll", "libvorbis_jll", "x264_jll", "x265_jll"]
git-tree-sha1 = "d8a578692e3077ac998b50c0217dfd67f21d1e5f"
uuid = "b22a6f82-2f65-5046-a5b2-351ab43fb4e5"
version = "4.4.0+0"

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

[[Fontconfig_jll]]
deps = ["Artifacts", "Bzip2_jll", "Expat_jll", "FreeType2_jll", "JLLWrappers", "Libdl", "Libuuid_jll", "Pkg", "Zlib_jll"]
git-tree-sha1 = "21efd19106a55620a188615da6d3d06cd7f6ee03"
uuid = "a3f928ae-7b40-5064-980b-68af3947d34b"
version = "2.13.93+0"

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

[[FreeType2_jll]]
deps = ["Artifacts", "Bzip2_jll", "JLLWrappers", "Libdl", "Pkg", "Zlib_jll"]
git-tree-sha1 = "87eb71354d8ec1a96d4a7636bd57a7347dde3ef9"
uuid = "d7e528f0-a631-5988-bf34-fe36492bcfd7"
version = "2.10.4+0"

[[FriBidi_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "aa31987c2ba8704e23c6c8ba8a4f769d5d7e4f91"
uuid = "559328eb-81f9-559d-9380-de523a88c83c"
version = "1.0.10+0"

[[GLFW_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Libglvnd_jll", "Pkg", "Xorg_libXcursor_jll", "Xorg_libXi_jll", "Xorg_libXinerama_jll", "Xorg_libXrandr_jll"]
git-tree-sha1 = "dba1e8614e98949abfa60480b13653813d8f0157"
uuid = "0656b61e-2033-5cc2-a64a-77c0f6c09b89"
version = "3.3.5+0"

[[GR]]
deps = ["Base64", "DelimitedFiles", "GR_jll", "HTTP", "JSON", "Libdl", "LinearAlgebra", "Pkg", "Printf", "Random", "Serialization", "Sockets", "Test", "UUIDs"]
git-tree-sha1 = "c2178cfbc0a5a552e16d097fae508f2024de61a3"
uuid = "28b8d3ca-fb5f-59d9-8090-bfdbd6d07a71"
version = "0.59.0"

[[GR_jll]]
deps = ["Artifacts", "Bzip2_jll", "Cairo_jll", "FFMPEG_jll", "Fontconfig_jll", "GLFW_jll", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Libtiff_jll", "Pixman_jll", "Pkg", "Qt5Base_jll", "Zlib_jll", "libpng_jll"]
git-tree-sha1 = "ef49a187604f865f4708c90e3f431890724e9012"
uuid = "d2c73de3-f751-5644-a686-071e5b155ba9"
version = "0.59.0+0"

[[GeometryBasics]]
deps = ["EarCut_jll", "IterTools", "LinearAlgebra", "StaticArrays", "StructArrays", "Tables"]
git-tree-sha1 = "58bcdf5ebc057b085e58d95c138725628dd7453c"
uuid = "5c1252a2-5f33-56bf-86c9-59e7332b4326"
version = "0.4.1"

[[Gettext_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Libiconv_jll", "Pkg", "XML2_jll"]
git-tree-sha1 = "9b02998aba7bf074d14de89f9d37ca24a1a0b046"
uuid = "78b55507-aeef-58d4-861c-77aaff3498b1"
version = "0.21.0+0"

[[Glib_jll]]
deps = ["Artifacts", "Gettext_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Libiconv_jll", "Libmount_jll", "PCRE_jll", "Pkg", "Zlib_jll"]
git-tree-sha1 = "7bf67e9a481712b3dbe9cb3dac852dc4b1162e02"
uuid = "7746bdde-850d-59dc-9ae8-88ece973131d"
version = "2.68.3+0"

[[Graphite2_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "344bf40dcab1073aca04aa0df4fb092f920e4011"
uuid = "3b182d85-2403-5c21-9c21-1e1f0cc25472"
version = "1.3.14+0"

[[Grisu]]
git-tree-sha1 = "53bb909d1151e57e2484c3d1b53e19552b887fb2"
uuid = "42e2da0e-8278-4e71-bc24-59509adca0fe"
version = "1.0.2"

[[HTTP]]
deps = ["Base64", "Dates", "IniFile", "Logging", "MbedTLS", "NetworkOptions", "Sockets", "URIs"]
git-tree-sha1 = "60ed5f1643927479f845b0135bb369b031b541fa"
uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3"
version = "0.9.14"

[[HarfBuzz_jll]]
deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "Graphite2_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Pkg"]
git-tree-sha1 = "8a954fed8ac097d5be04921d595f741115c1b2ad"
uuid = "2e76f6c2-a576-52d4-95c1-20adfe4de566"
version = "2.8.1+0"

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

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

[[IniFile]]
deps = ["Test"]
git-tree-sha1 = "098e4d2c533924c921f9f9847274f2ad89e018b8"
uuid = "83e8ac13-25f8-5344-8a64-a9f2b223428f"
version = "0.5.0"

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

[[IterTools]]
git-tree-sha1 = "05110a2ab1fc5f932622ffea2a003221f4782c18"
uuid = "c8e1da08-722c-5040-9ed9-7db0dc04731e"
version = "1.3.0"

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

[[JLLWrappers]]
deps = ["Preferences"]
git-tree-sha1 = "642a199af8b68253517b80bd3bfd17eb4e84df6e"
uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210"
version = "1.3.0"

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

[[JpegTurbo_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "d735490ac75c5cb9f1b00d8b5509c11984dc6943"
uuid = "aacddb02-875f-59d6-b918-886e6ef4fbf8"
version = "2.1.0+0"

[[LAME_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "f6250b16881adf048549549fba48b1161acdac8c"
uuid = "c1c5ebd0-6772-5130-a774-d5fcae4a789d"
version = "3.100.1+0"

[[LZO_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "e5b909bcf985c5e2605737d2ce278ed791b89be6"
uuid = "dd4b983a-f0e5-5f8d-a1b7-129d4a5fb1ac"
version = "2.10.1+0"

[[LaTeXStrings]]
git-tree-sha1 = "c7f1c695e06c01b95a67f0cd1d34994f3e7db104"
uuid = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f"
version = "1.2.1"

[[Latexify]]
deps = ["Formatting", "InteractiveUtils", "LaTeXStrings", "MacroTools", "Markdown", "Printf", "Requires"]
git-tree-sha1 = "a4b12a1bd2ebade87891ab7e36fdbce582301a92"
uuid = "23fbe1c1-3f47-55db-b15f-69d7ec21a316"
version = "0.15.6"

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

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

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

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

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

[[Libffi_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "761a393aeccd6aa92ec3515e428c26bf99575b3b"
uuid = "e9f186c6-92d2-5b65-8a66-fee21dc1b490"
version = "3.2.2+0"

[[Libgcrypt_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Libgpg_error_jll", "Pkg"]
git-tree-sha1 = "64613c82a59c120435c067c2b809fc61cf5166ae"
uuid = "d4300ac3-e22c-5743-9152-c294e39db1e4"
version = "1.8.7+0"

[[Libglvnd_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libX11_jll", "Xorg_libXext_jll"]
git-tree-sha1 = "7739f837d6447403596a75d19ed01fd08d6f56bf"
uuid = "7e76a0d4-f3c7-5321-8279-8d96eeed0f29"
version = "1.3.0+3"

[[Libgpg_error_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "c333716e46366857753e273ce6a69ee0945a6db9"
uuid = "7add5ba3-2f88-524e-9cd5-f83b8a55f7b8"
version = "1.42.0+0"

[[Libiconv_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "42b62845d70a619f063a7da093d995ec8e15e778"
uuid = "94ce4f54-9a6c-5748-9c1c-f9c7231a4531"
version = "1.16.1+1"

[[Libmount_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "9c30530bf0effd46e15e0fdcf2b8636e78cbbd73"
uuid = "4b2f31a3-9ecc-558c-b454-b3730dcb73e9"
version = "2.35.0+0"

[[Libtiff_jll]]
deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Pkg", "Zlib_jll", "Zstd_jll"]
git-tree-sha1 = "340e257aada13f95f98ee352d316c3bed37c8ab9"
uuid = "89763e89-9b03-5906-acba-b20f662cd828"
version = "4.3.0+0"

[[Libuuid_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "7f3efec06033682db852f8b3bc3c1d2b0a0ab066"
uuid = "38a345b3-de98-5d2b-a5d3-14cd9215e700"
version = "2.36.0+0"

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

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

[[MacroTools]]
deps = ["Markdown", "Random"]
git-tree-sha1 = "5a5bc6bf062f0f95e62d0fe0a2d99699fed82dd9"
uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
version = "0.5.8"

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

[[MbedTLS]]
deps = ["Dates", "MbedTLS_jll", "Random", "Sockets"]
git-tree-sha1 = "1c38e51c3d08ef2278062ebceade0e46cefc96fe"
uuid = "739be429-bea8-5141-9913-cc70e7f3736d"
version = "1.0.3"

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

[[Measures]]
git-tree-sha1 = "e498ddeee6f9fdb4551ce855a46f54dbd900245f"
uuid = "442fdcdd-2543-5da2-b0f3-8c86c306513e"
version = "0.3.1"

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

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

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

[[NaNMath]]
git-tree-sha1 = "bfe47e760d60b82b66b61d2d44128b62e3a369fb"
uuid = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3"
version = "0.3.5"

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

[[Ogg_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "7937eda4681660b4d6aeeecc2f7e1c81c8ee4e2f"
uuid = "e7412a2a-1a6e-54c0-be00-318e2571c051"
version = "1.3.5+0"

[[OpenSSL_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "15003dcb7d8db3c6c857fda14891a539a8f2705a"
uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95"
version = "1.1.10+0"

[[Opus_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "51a08fb14ec28da2ec7a927c4337e4332c2a4720"
uuid = "91d4177d-7536-5919-b921-800302f37372"
version = "1.3.2+0"

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

[[PCRE_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "b2a7af664e098055a7529ad1a900ded962bca488"
uuid = "2f80f16e-611a-54ab-bc61-aa92de5b98fc"
version = "8.44.0+0"

[[Parsers]]
deps = ["Dates"]
git-tree-sha1 = "9d8c00ef7a8d110787ff6f170579846f776133a9"
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
version = "2.0.4"

[[Pixman_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "b4f5d02549a10e20780a24fce72bea96b6329e29"
uuid = "30392449-352a-5448-841d-b1acce4e97dc"
version = "0.40.1+0"

[[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"

[[PlotThemes]]
deps = ["PlotUtils", "Requires", "Statistics"]
git-tree-sha1 = "a3a964ce9dc7898193536002a6dd892b1b5a6f1d"
uuid = "ccf2f8ad-2431-5c83-bf29-c5338b663b6a"
version = "2.0.1"

[[PlotUtils]]
deps = ["ColorSchemes", "Colors", "Dates", "Printf", "Random", "Reexport", "Statistics"]
git-tree-sha1 = "2537ed3c0ed5e03896927187f5f2ee6a4ab342db"
uuid = "995b91a9-d308-5afd-9ec6-746e21dbc043"
version = "1.0.14"

[[Plots]]
deps = ["Base64", "Contour", "Dates", "Downloads", "FFMPEG", "FixedPointNumbers", "GR", "GeometryBasics", "JSON", "Latexify", "LinearAlgebra", "Measures", "NaNMath", "PlotThemes", "PlotUtils", "Printf", "REPL", "Random", "RecipesBase", "RecipesPipeline", "Reexport", "Requires", "Scratch", "Showoff", "SparseArrays", "Statistics", "StatsBase", "UUIDs"]
git-tree-sha1 = "457b13497a3ea4deb33d273a6a5ea15c25c0ebd9"
uuid = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
version = "1.22.2"

[[PlutoUI]]
deps = ["Base64", "Dates", "HypertextLiteral", "IOCapture", "InteractiveUtils", "JSON", "Logging", "Markdown", "Random", "Reexport", "UUIDs"]
git-tree-sha1 = "0c3e067931708fa5641247affc1a1aceb53fff06"
uuid = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
version = "0.7.11"

[[Preferences]]
deps = ["TOML"]
git-tree-sha1 = "00cfd92944ca9c760982747e9a1d0d5d86ab1e5a"
uuid = "21216c6a-2e73-6563-6e65-726566657250"
version = "1.2.2"

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

[[Qt5Base_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "Fontconfig_jll", "Glib_jll", "JLLWrappers", "Libdl", "Libglvnd_jll", "OpenSSL_jll", "Pkg", "Xorg_libXext_jll", "Xorg_libxcb_jll", "Xorg_xcb_util_image_jll", "Xorg_xcb_util_keysyms_jll", "Xorg_xcb_util_renderutil_jll", "Xorg_xcb_util_wm_jll", "Zlib_jll", "xkbcommon_jll"]
git-tree-sha1 = "ad368663a5e20dbb8d6dc2fddeefe4dae0781ae8"
uuid = "ea2cea3b-5b76-57ae-a6ef-0a8af62496e1"
version = "5.15.3+0"

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

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

[[RecipesBase]]
git-tree-sha1 = "44a75aa7a527910ee3d1751d1f0e4148698add9e"
uuid = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
version = "1.1.2"

[[RecipesPipeline]]
deps = ["Dates", "NaNMath", "PlotUtils", "RecipesBase"]
git-tree-sha1 = "7ad0dfa8d03b7bcf8c597f59f5292801730c55b8"
uuid = "01d81517-befc-4cb6-b9ec-a95719d0359c"
version = "0.4.1"

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

[[Requires]]
deps = ["UUIDs"]
git-tree-sha1 = "4036a3bd08ac7e968e27c203d45f5fff15020621"
uuid = "ae029012-a4dd-5104-9daa-d747884805df"
version = "1.1.3"

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

[[Scratch]]
deps = ["Dates"]
git-tree-sha1 = "0b4b7f1393cff97c33891da2a0bf69c6ed241fda"
uuid = "6c6a2e73-6563-6170-7368-637461726353"
version = "1.1.0"

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

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

[[Showoff]]
deps = ["Dates", "Grisu"]
git-tree-sha1 = "91eddf657aca81df9ae6ceb20b959ae5653ad1de"
uuid = "992d4aef-0814-514b-bc4d-f2e9a6c4116f"
version = "1.0.3"

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

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

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

[[StaticArrays]]
deps = ["LinearAlgebra", "Random", "Statistics"]
git-tree-sha1 = "3240808c6d463ac46f1c1cd7638375cd22abbccb"
uuid = "90137ffa-7385-5640-81b9-e52037218182"
version = "1.2.12"

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

[[StatsAPI]]
git-tree-sha1 = "1958272568dc176a1d881acb797beb909c785510"
uuid = "82ae8749-77ed-4fe6-ae5f-f523153014b0"
version = "1.0.0"

[[StatsBase]]
deps = ["DataAPI", "DataStructures", "LinearAlgebra", "Missings", "Printf", "Random", "SortingAlgorithms", "SparseArrays", "Statistics", "StatsAPI"]
git-tree-sha1 = "8cbbc098554648c84f79a463c9ff0fd277144b6c"
uuid = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
version = "0.33.10"

[[StructArrays]]
deps = ["Adapt", "DataAPI", "StaticArrays", "Tables"]
git-tree-sha1 = "2ce41e0d042c60ecd131e9fb7154a3bfadbf50d3"
uuid = "09ab397b-f2b6-538f-b94a-2f83cf4a842a"
version = "0.6.3"

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

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

[[Tables]]
deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "LinearAlgebra", "TableTraits", "Test"]
git-tree-sha1 = "1162ce4a6c4b7e31e0e6b14486a6986951c73be9"
uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
version = "1.5.2"

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

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

[[URIs]]
git-tree-sha1 = "97bbe755a53fe859669cd907f2d96aee8d2c1355"
uuid = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4"
version = "1.3.0"

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

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

[[Wayland_jll]]
deps = ["Artifacts", "Expat_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Pkg", "XML2_jll"]
git-tree-sha1 = "3e61f0b86f90dacb0bc0e73a0c5a83f6a8636e23"
uuid = "a2964d1f-97da-50d4-b82a-358c7fce9d89"
version = "1.19.0+0"

[[Wayland_protocols_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Wayland_jll"]
git-tree-sha1 = "2839f1c1296940218e35df0bbb220f2a79686670"
uuid = "2381bf8a-dfd0-557d-9999-79630e7b1b91"
version = "1.18.0+4"

[[XML2_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Libiconv_jll", "Pkg", "Zlib_jll"]
git-tree-sha1 = "1acf5bdf07aa0907e0a37d3718bb88d4b687b74a"
uuid = "02c8fc9c-b97f-50b9-bbe4-9be30ff0a78a"
version = "2.9.12+0"

[[XSLT_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Libgcrypt_jll", "Libgpg_error_jll", "Libiconv_jll", "Pkg", "XML2_jll", "Zlib_jll"]
git-tree-sha1 = "91844873c4085240b95e795f692c4cec4d805f8a"
uuid = "aed1982a-8fda-507f-9586-7b0439959a61"
version = "1.1.34+0"

[[Xorg_libX11_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libxcb_jll", "Xorg_xtrans_jll"]
git-tree-sha1 = "5be649d550f3f4b95308bf0183b82e2582876527"
uuid = "4f6342f7-b3d2-589e-9d20-edeb45f2b2bc"
version = "1.6.9+4"

[[Xorg_libXau_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "4e490d5c960c314f33885790ed410ff3a94ce67e"
uuid = "0c0b7dd1-d40b-584c-a123-a41640f87eec"
version = "1.0.9+4"

[[Xorg_libXcursor_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libXfixes_jll", "Xorg_libXrender_jll"]
git-tree-sha1 = "12e0eb3bc634fa2080c1c37fccf56f7c22989afd"
uuid = "935fb764-8cf2-53bf-bb30-45bb1f8bf724"
version = "1.2.0+4"

[[Xorg_libXdmcp_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "4fe47bd2247248125c428978740e18a681372dd4"
uuid = "a3789734-cfe1-5b06-b2d0-1dd0d9d62d05"
version = "1.1.3+4"

[[Xorg_libXext_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libX11_jll"]
git-tree-sha1 = "b7c0aa8c376b31e4852b360222848637f481f8c3"
uuid = "1082639a-0dae-5f34-9b06-72781eeb8cb3"
version = "1.3.4+4"

[[Xorg_libXfixes_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libX11_jll"]
git-tree-sha1 = "0e0dc7431e7a0587559f9294aeec269471c991a4"
uuid = "d091e8ba-531a-589c-9de9-94069b037ed8"
version = "5.0.3+4"

[[Xorg_libXi_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libXext_jll", "Xorg_libXfixes_jll"]
git-tree-sha1 = "89b52bc2160aadc84d707093930ef0bffa641246"
uuid = "a51aa0fd-4e3c-5386-b890-e753decda492"
version = "1.7.10+4"

[[Xorg_libXinerama_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libXext_jll"]
git-tree-sha1 = "26be8b1c342929259317d8b9f7b53bf2bb73b123"
uuid = "d1454406-59df-5ea1-beac-c340f2130bc3"
version = "1.1.4+4"

[[Xorg_libXrandr_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libXext_jll", "Xorg_libXrender_jll"]
git-tree-sha1 = "34cea83cb726fb58f325887bf0612c6b3fb17631"
uuid = "ec84b674-ba8e-5d96-8ba1-2a689ba10484"
version = "1.5.2+4"

[[Xorg_libXrender_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libX11_jll"]
git-tree-sha1 = "19560f30fd49f4d4efbe7002a1037f8c43d43b96"
uuid = "ea2f1a96-1ddc-540d-b46f-429655e07cfa"
version = "0.9.10+4"

[[Xorg_libpthread_stubs_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "6783737e45d3c59a4a4c4091f5f88cdcf0908cbb"
uuid = "14d82f49-176c-5ed1-bb49-ad3f5cbd8c74"
version = "0.1.0+3"

[[Xorg_libxcb_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "XSLT_jll", "Xorg_libXau_jll", "Xorg_libXdmcp_jll", "Xorg_libpthread_stubs_jll"]
git-tree-sha1 = "daf17f441228e7a3833846cd048892861cff16d6"
uuid = "c7cfdc94-dc32-55de-ac96-5a1b8d977c5b"
version = "1.13.0+3"

[[Xorg_libxkbfile_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libX11_jll"]
git-tree-sha1 = "926af861744212db0eb001d9e40b5d16292080b2"
uuid = "cc61e674-0454-545c-8b26-ed2c68acab7a"
version = "1.1.0+4"

[[Xorg_xcb_util_image_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_xcb_util_jll"]
git-tree-sha1 = "0fab0a40349ba1cba2c1da699243396ff8e94b97"
uuid = "12413925-8142-5f55-bb0e-6d7ca50bb09b"
version = "0.4.0+1"

[[Xorg_xcb_util_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libxcb_jll"]
git-tree-sha1 = "e7fd7b2881fa2eaa72717420894d3938177862d1"
uuid = "2def613f-5ad1-5310-b15b-b15d46f528f5"
version = "0.4.0+1"

[[Xorg_xcb_util_keysyms_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_xcb_util_jll"]
git-tree-sha1 = "d1151e2c45a544f32441a567d1690e701ec89b00"
uuid = "975044d2-76e6-5fbe-bf08-97ce7c6574c7"
version = "0.4.0+1"

[[Xorg_xcb_util_renderutil_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_xcb_util_jll"]
git-tree-sha1 = "dfd7a8f38d4613b6a575253b3174dd991ca6183e"
uuid = "0d47668e-0667-5a69-a72c-f761630bfb7e"
version = "0.3.9+1"

[[Xorg_xcb_util_wm_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_xcb_util_jll"]
git-tree-sha1 = "e78d10aab01a4a154142c5006ed44fd9e8e31b67"
uuid = "c22f9ab0-d5fe-5066-847c-f4bb1cd4e361"
version = "0.4.1+1"

[[Xorg_xkbcomp_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libxkbfile_jll"]
git-tree-sha1 = "4bcbf660f6c2e714f87e960a171b119d06ee163b"
uuid = "35661453-b289-5fab-8a00-3d9160c6a3a4"
version = "1.4.2+4"

[[Xorg_xkeyboard_config_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_xkbcomp_jll"]
git-tree-sha1 = "5c8424f8a67c3f2209646d4425f3d415fee5931d"
uuid = "33bec58e-1273-512f-9401-5d533626f822"
version = "2.27.0+4"

[[Xorg_xtrans_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "79c31e7844f6ecf779705fbc12146eb190b7d845"
uuid = "c5fb5394-a638-5e4d-96e5-b29de1b5cf10"
version = "1.4.0+3"

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

[[Zstd_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "cc4bf3fdde8b7e3e9fa0351bdeedba1cf3b7f6e6"
uuid = "3161d3a3-bdf6-5164-811a-617609db77b4"
version = "1.5.0+0"

[[libass_jll]]
deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "HarfBuzz_jll", "JLLWrappers", "Libdl", "Pkg", "Zlib_jll"]
git-tree-sha1 = "5982a94fcba20f02f42ace44b9894ee2b140fe47"
uuid = "0ac62f75-1d6f-5e53-bd7c-93b484bb37c0"
version = "0.15.1+0"

[[libfdk_aac_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "daacc84a041563f965be61859a36e17c4e4fcd55"
uuid = "f638f0a6-7fb0-5443-88ba-1cc74229b280"
version = "2.0.2+0"

[[libpng_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Zlib_jll"]
git-tree-sha1 = "94d180a6d2b5e55e447e2d27a29ed04fe79eb30c"
uuid = "b53b4c65-9356-5827-b1ea-8c7a1a84506f"
version = "1.6.38+0"

[[libvorbis_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Ogg_jll", "Pkg"]
git-tree-sha1 = "c45f4e40e7aafe9d086379e5578947ec8b95a8fb"
uuid = "f27f6e37-5d2b-51aa-960f-b287f2bc3b7a"
version = "1.3.7+0"

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

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

[[x264_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "4fea590b89e6ec504593146bf8b988b2c00922b2"
uuid = "1270edf5-f2f9-52d2-97e9-ab00b5d0237a"
version = "2021.5.5+0"

[[x265_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "ee567a171cce03570d77ad3a43e90218e38937a9"
uuid = "dfaa095f-4041-5dcd-9319-2fabd8486b76"
version = "3.5.0+0"

[[xkbcommon_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Wayland_jll", "Wayland_protocols_jll", "Xorg_libxcb_jll", "Xorg_xkeyboard_config_jll"]
git-tree-sha1 = "ece2350174195bb31de1a63bea3a41ae1aa593b6"
uuid = "d8fb68d0-12a3-5cfd-a85a-d49703b185fd"
version = "0.9.1+5"
"""

# ╔═╡ Cell order:
# ╟─2ab0b80e-1e02-11ec-1596-8bd8e81d347c
# ╠═b0020dc0-78a7-483f-877f-719f42b0c044
# ╟─b4977f9f-2bcc-405f-b875-99cde2ccb77d
# ╟─2492a904-77da-4a36-baf4-65d40aa89d74
# ╟─194250f8-c94d-4732-b179-0ebead51110b
# ╠═38cb6e91-b7bb-4000-b883-f24c17ad827a
# ╠═40b7b2f4-ec2b-4b89-b2f0-adc368ee15bc
# ╟─67729a4c-df55-4fdb-8962-25b133eaaaf5
# ╠═a47b09eb-7441-4441-98de-d48111e172a7
# ╠═6b6ede88-b4b8-4dc7-8f27-bc2228512e22
# ╠═a130184f-06af-4767-ad6a-d8d8c1bbf6ca
# ╠═c12360cc-5208-417c-9003-5ce1308e8081
# ╟─1e332d32-7846-4bdb-935e-505dd290e1f0
# ╠═47e0c111-83c1-4c3f-b293-a337b19feee9
# ╟─a7cb520d-d221-45e9-83ac-e1927aafd0ab
# ╟─a2906480-383f-4456-9a80-04d172f85c41
# ╟─f8bc5419-054e-4b8f-807e-b594784661f6
# ╠═0c280cb9-9f30-4570-91a1-19538b56f957
# ╠═74b62c2b-58b5-4c37-9399-266855086f9b
# ╟─54b185a7-7b4e-4756-9663-db0b6419de27
# ╠═db00faa4-1d04-47aa-a062-4cf9b8cbe208
# ╟─fddb2f51-e5dc-47eb-81d9-9485435c60ea
# ╠═6a1dae01-b78b-4e8b-9a4d-d20bc6a025a5
# ╠═4a867566-14eb-4d8a-ae17-a267054840fd
# ╟─cf659c75-b8b9-488d-b937-36ea1e0cadbe
# ╠═94a53663-6dba-4e9b-8c30-bc1f2be3a43e
# ╠═1a2a55a5-424c-4522-a4bd-9c657f9f8565
# ╟─9fd3c33d-275d-420b-a03b-8ceb81a56ad3
# ╠═9d467594-8a33-4e1d-a272-744379bf2722
# ╠═63e2620a-6897-4664-81e3-479a7a5af9ff
# ╟─eb9dfc6d-b9d8-4771-9b71-e4d0fba38cb7
# ╟─89941271-8a9d-4e1c-94b0-77b35ebd8816
# ╟─ecde4baa-0aff-434c-b34a-59b0938a100f
# ╟─30651af8-ad5d-4cd2-89be-8283d381da5b
# ╟─d69435c1-c19a-4a94-8778-cea2ffcdfaa6
# ╟─8c64f65f-b610-40b5-b9a1-6abb39a6f0bb
# ╟─76b4809a-3fed-4bea-bfe8-66582cbb3fc9
# ╟─80c1d9d2-f8d7-4a16-90a3-a0af2ce96ced
# ╟─427428ab-1978-4317-86f3-ea211141e52f
# ╟─9d754d22-2a09-4458-8fa0-589ae4f75d76
# ╟─7654100f-86ca-481f-bfb7-bc9b9ee20639
# ╟─aec0517c-01e7-4d3f-b9b4-3e4ec8acdfc3
# ╟─9803aaf0-08c3-4501-8865-318a988210c9
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002


## Load the packages.
using LoopVectorization
using Interpolations
using Statistics
using BenchmarkTools
using PyPlot
using Printf

## Define the upsample functions.
"""
function upsample_nn!(hi, lo, itot, jtot, ktot, ifac, jfac, kfac, ioff, joff, koff)
    @tturbo for k in 0:ktot-1, j in 0:jtot-1, i in 0:itot-1
        i_lo = i + 2; j_lo = j + 2; k_lo = k + 2
        i_hi = ifac*i + 2; j_hi = jfac*j + 2; k_hi = kfac*k + 2
        for kk in 0:kfac-1, jj in 0:jfac-1, ii in 0:ifac-1
            iii = floor(Int, ioff + 1/ifac * ii)
            jjj = floor(Int, joff + 1/jfac * jj)
            kkk = floor(Int, koff + 1/kfac * kk)
            hi[i_hi+ii, j_hi+jj, k_hi+kk] = lo[i_lo+iii, j_lo+jjj, k_lo+kkk]
        end
    end
end

function upsample_nn_ref!(hi, lo, x_hi, y_hi, z_hi, x_lo, y_lo, z_lo)
    interp = interpolate((x_lo, y_lo, z_lo), lo, (Gridded(Constant()), Gridded(Constant()), Gridded(Constant())))
    hi[2:end-1, 2:end-1, 2:end-1] .= interp(x_hi[2:end-1], y_hi[2:end-1], z_hi[2:end-1])
end
"""

macro upsample_lin_fast(suffix, ifac, jfac, kfac, ioff, joff, koff)
    ex_inner = []
    for kk in 0:kfac-1, jj in 0:jfac-1, ii in 0:ifac-1
        push!(ex_inner, :(i_hi = $ifac*i+$ii+is_hi), :(j_hi = $jfac*j+$jj+js_hi), :(k_hi = $kfac*k+$kk+ks_hi))

        i_pos = -1/2 + ioff + (1/2 - ioff + ii)/ifac
        j_pos = -1/2 + joff + (1/2 - joff + jj)/jfac
        k_pos = -1/2 + koff + (1/2 - koff + kk)/kfac

        i_lo_off = floor(Int, i_pos) + is_lo
        j_lo_off = floor(Int, j_pos) + js_lo
        k_lo_off = floor(Int, k_pos) + ks_lo
        push!(ex_inner, :(i_lo = i + $i_lo_off), :(j_lo = j + $j_lo_off), :(k_lo = k + $k_lo_off))

        fi = mod(i_pos, 1)
        fj = mod(j_pos, 1)
        fk = mod(k_pos, 1)

        coef = zeros(2, 2, 2)
        coef[1, 1, 1] = (((1-fi)*fj+fi-1)*fk+(fi-1)*fj-fi+1)
        coef[2, 1, 1] = ((fi*fj-fi)*fk-fi*fj+fi)
        coef[1, 2, 1] = ((fi-1)*fj*fk+(1-fi)*fj)
        coef[2, 2, 1] = (fi*fj-fi*fj*fk)
        coef[1, 1, 2] = ((fi-1)*fj-fi+1)*fk
        coef[2, 1, 2] = (fi-fi*fj)*fk
        coef[1, 2, 2] = (1-fi)*fj*fk
        coef[2, 2, 2] = fi*fj*fk

        rhs_list = []
        for kc in 1:2, jc in 1:2, ic in 1:2
            if !(coef[ic, jc, kc] ≈ 0)
                push!(rhs_list, :( $(coef[ic, jc, kc])*lo[i_lo+$(ic-1), j_lo+$(jc-1), k_lo+$(kc-1)] ))
            end
        end

        rhs = Expr(:call, :+, rhs_list...)

        push!(ex_inner, :(hi[i_hi, j_hi, k_hi] = $rhs))
    end

    ex_inner_block = Expr(:block, ex_inner...)

    name = Symbol(@sprintf "upsample_lin_%s!" suffix)
    ex = quote
        function $name(hi, lo, itot, jtot, ktot, is_hi, js_hi, ks_hi, is_lo, js_lo, ks_lo)
            Threads.@threads for k in 0:ktot-1
                for j in 0:jtot-1
                    @inbounds @simd for i in 0:itot-1
                        $ex_inner_block
                    end
                end
            end
        end
    end
    return esc(ex)
end

function upsample_lin!(
        hi, lo,
        itot, jtot, ktot,
        is_hi, js_hi, ks_hi, is_lo, js_lo, ks_lo,
        ifac, jfac, kfac, ioff, joff, koff)

    Threads.@threads for k in 0:ktot-1
        for j in 0:jtot-1
            @simd for i in 0:itot-1
                for kk in 0:kfac-1, jj in 0:jfac-1, ii in 0:ifac-1
                    i_hi = ifac*i+ii+is_hi; j_hi = jfac*j+jj+js_hi; k_hi = kfac*k+kk+ks_hi;

                    # Determine fractional distance and west, south, and bot point.
                    i_pos = -1/2 + ioff + (1/2 - ioff + ii)/ifac
                    j_pos = -1/2 + joff + (1/2 - joff + jj)/jfac
                    k_pos = -1/2 + koff + (1/2 - koff + kk)/kfac

                    fi = mod(i_pos, 1)
                    fj = mod(j_pos, 1)
                    fk = mod(k_pos, 1)

                    coef_111 = (((1-fi)*fj+fi-1)*fk+(fi-1)*fj-fi+1)
                    coef_211 = ((fi*fj-fi)*fk-fi*fj+fi)
                    coef_121 = ((fi-1)*fj*fk+(1-fi)*fj)
                    coef_221 = (fi*fj-fi*fj*fk)
                    coef_112 = ((fi-1)*fj-fi+1)*fk
                    coef_212 = (fi-fi*fj)*fk
                    coef_122 = (1-fi)*fj*fk
                    coef_222 = fi*fj*fk

                    i_lo = i + floor(Int, i_pos) + is_lo
                    j_lo = j + floor(Int, j_pos) + js_lo
                    k_lo = k + floor(Int, k_pos) + ks_lo

                    @inbounds hi[i_hi, j_hi, k_hi] = (
                        + coef_111 * lo[i_lo  , j_lo  , k_lo  ]
                        + coef_211 * lo[i_lo+1, j_lo  , k_lo  ]
                        + coef_121 * lo[i_lo  , j_lo+1, k_lo  ]
                        + coef_221 * lo[i_lo+1, j_lo+1, k_lo  ]
                        + coef_112 * lo[i_lo  , j_lo  , k_lo+1]
                        + coef_212 * lo[i_lo+1, j_lo  , k_lo+1]
                        + coef_122 * lo[i_lo  , j_lo+1, k_lo+1]
                        + coef_222 * lo[i_lo+1, j_lo+1, k_lo+1] )
                end
            end
        end
    end
end

function upsample_lin_ref!(hi, lo, x_hi, y_hi, z_hi, x_lo, y_lo, z_lo, is_hi, js_hi, ks_hi, ie_hi, je_hi, ke_hi)
    interp = interpolate((x_lo, y_lo, z_lo), lo, (Gridded(Linear()), Gridded(Linear()), Gridded(Linear())))
    hi[is_hi:ie_hi, js_hi:je_hi, ks_hi:ke_hi] .= interp(x_hi[is_hi:ie_hi], y_hi[js_hi:je_hi], z_hi[ks_hi:ke_hi])
end

## Set up the grids.
itot_lo = 256; jtot_lo = 192; ktot_lo = 128
itot_hi = 512; jtot_hi = 384; ktot_hi = 256
igc_lo = 1; jgc_lo = 1; kgc_lo = 1
igc_hi = 1; jgc_hi = 1; kgc_hi = 1

# Derive dimensions
ifac = itot_hi÷itot_lo; jfac = jtot_hi÷jtot_lo; kfac = ktot_hi÷ktot_lo;
icells_lo = itot_lo + 2igc_lo; jcells_lo = jtot_lo + 2jgc_lo; kcells_lo = ktot_lo + 2kgc_lo
icells_hi = itot_hi + 2igc_hi; jcells_hi = jtot_hi + 2jgc_hi; kcells_hi = ktot_hi + 2kgc_hi
is_lo = igc_lo+1; js_lo = jgc_lo+1; ks_lo = kgc_lo+1
is_hi = igc_hi+1; js_hi = jgc_hi+1; ks_hi = kgc_hi+1
ie_lo = igc_lo+itot_lo; je_lo = jgc_lo+jtot_lo; ke_lo = kgc_lo+ktot_lo
ie_hi = igc_hi+itot_hi; je_hi = jgc_hi+jtot_hi; ke_hi = kgc_hi+ktot_hi

a_lo = rand(icells_lo, jcells_lo, kcells_lo)
a_hi_ref = zeros(icells_hi, jcells_hi, kcells_hi)
a_hi = zeros(icells_hi, jcells_hi, kcells_hi)
a_hi_fast = zeros(icells_hi, jcells_hi, kcells_hi)

dx_lo = 1/itot_lo; dy_lo = 1/jtot_lo; dz_lo = 1/ktot_lo
x_lo = collect(range(0.5-igc_lo, length=icells_lo)) .* dx_lo
y_lo = collect(range(0.5-jgc_lo, length=jcells_lo)) .* dy_lo
z_lo = collect(range(0.5-kgc_lo, length=kcells_lo)) .* dz_lo
xh_lo = collect(range(-igc_lo, length=icells_lo)) .* dx_lo
yh_lo = collect(range(-jgc_lo, length=jcells_lo)) .* dy_lo

dx_hi = 1/itot_hi; dy_hi = 1/jtot_hi; dz_hi = 1/ktot_hi
x_hi = collect(range(0.5-igc_hi, length=icells_hi)) .* dx_hi
y_hi = collect(range(0.5-jgc_hi, length=jcells_hi)) .* dy_hi
z_hi = collect(range(0.5-kgc_hi, length=kcells_hi)) .* dz_hi
xh_hi = collect(range(-igc_hi, length=icells_hi)) .* dx_hi
yh_hi = collect(range(-jgc_hi, length=jcells_hi)) .* dy_hi

## Compute a reference using Interpolations.jl
@btime upsample_lin_ref!(
    a_hi_ref, a_lo, x_hi, y_hi, z_hi, x_lo, y_lo, z_lo,
    is_hi, js_hi, ks_hi, ie_hi, je_hi, ke_hi)
@btime upsample_lin!(a_hi, a_lo, itot_lo, jtot_lo, ktot_lo,
    is_hi, js_hi, ks_hi, is_lo, js_lo, ks_lo,
    ifac, jfac, kfac, 0, 0, 0)
@upsample_lin_fast("222", 2, 2, 2, 0, 0, 0)
@btime upsample_lin_222!(
    a_hi_fast, a_lo, itot_lo, jtot_lo, ktot_lo,
    is_hi, js_hi, ks_hi, is_lo, js_lo, ks_lo)

a_lo_int = @view a_lo[is_lo:ie_lo, js_lo:je_lo, ks_lo:ke_lo]
a_hi_ref_int = @view a_hi_ref[is_hi:ie_hi, js_hi:je_hi, ks_hi:ke_hi]
a_hi_int = @view a_hi[is_hi:ie_hi, js_hi:je_hi, ks_hi:ke_hi]
a_hi_fast_int = @view a_hi_fast[is_hi:ie_hi, js_hi:je_hi, ks_hi:ke_hi]

println("Standard equal to ref: ", a_hi_int ≈ a_hi_ref_int)
println("Fast equal to ref: ", a_hi_fast_int ≈ a_hi_ref_int)

## Plot the output.
x_lo_int = @view x_lo[is_lo:ie_lo]
x_hi_int = @view x_hi[is_hi:ie_hi]

figure()
plot(x_hi_int, a_hi_int[:, 1, 1], "C0-o")
plot(x_hi_int, a_hi_ref_int[:, 1, 1], "C1-^")
plot(x_hi_int, a_hi_fast_int[:, 1, 1], "C2-*")
plot(x_lo_int, a_lo_int[:, 1, 1], "k:+")
tight_layout()
display(gcf())

## Compute a reference using Interpolations.jl
u_lo = rand(icells_lo, jcells_lo, kcells_lo)
u_hi_ref = zeros(icells_hi, jcells_hi, kcells_hi)
u_hi = zeros(icells_hi, jcells_hi, kcells_hi)
u_hi_fast = zeros(icells_hi, jcells_hi, kcells_hi)

@btime upsample_lin_ref!(
     u_hi_ref, u_lo, xh_hi, y_hi, z_hi, xh_lo, y_lo, z_lo,
     is_hi, js_hi, ks_hi, ie_hi, je_hi, ke_hi)
@btime upsample_lin!(
    u_hi, u_lo, itot_lo, jtot_lo, ktot_lo,
    is_hi, js_hi, ks_hi, is_lo, js_lo, ks_lo,
    ifac, jfac, kfac, 0.5, 0, 0)
@upsample_lin_fast("222_u", 2, 2, 2, 0.5, 0, 0)
@btime upsample_lin_222_u!(
    u_hi_fast, u_lo, itot_lo, jtot_lo, ktot_lo,
    is_hi, js_hi, ks_hi, is_lo, js_lo, ks_lo)

u_lo_int = @view a_lo[is_lo:ie_lo, js_lo:je_lo, ks_lo:ke_lo]
u_hi_ref_int = @view a_hi_ref[is_hi:ie_hi, js_hi:je_hi, ks_hi:ke_hi]
u_hi_int = @view a_hi[is_hi:ie_hi, js_hi:je_hi, ks_hi:ke_hi]
u_hi_fast_int = @view a_hi_fast[is_hi:ie_hi, js_hi:je_hi, ks_hi:ke_hi]

println("Standard equal to ref: ", u_hi_int ≈ u_hi_ref_int)
println("Fast equal to ref: ", u_hi_fast_int ≈ u_hi_ref_int)

## Plot the output.
xh_lo_int = @view xh_lo[is_lo:ie_lo]
xh_hi_int = @view xh_hi[is_hi:ie_hi]

figure()
plot(xh_hi_int, u_hi_int[:, 1, 1], "C0-o")
plot(xh_hi_int, u_hi_ref_int[:, 1, 1], "C1-^")
plot(xh_hi_int, u_hi_fast_int[:, 1, 1], "C2-*")
plot(xh_lo_int, u_lo_int[:, 1, 1], "k:+")
tight_layout()
display(gcf())

show()


using ProgressBars
using SparseArrays

struct Graph
    transition_matrix::SparseMatrixCSC{Int64, Int64}
    nodes::Vector{Int64}
    edges::Vector{Tuple{Int64, Int64}}
    degree_out::Vector{Int64}
    degree_in::Vector{Int64}
    n::Int32
    function Graph(T, nodes, edges, degree_out, degree_in, n)
        new(T, nodes, edges, degree_out, degree_in, n)
    end
    function Graph(filename)
        # Read edges and nodes from filename
        io = open(filename)
        lines = readlines(io)

        edges = Vector{Tuple{Int64, Int64}}()

        I = Vector{Int64}()
        J = Vector{Int64}()

        println("Loading graph in memory")
        maxI = 0
        maxJ = 0
        for (index, line) in ProgressBar(enumerate(lines))
            if !startswith(line, "#") && line != ""
                r = r"(\w+)[\t\s](\w+)"
                i1, i2 = match(r, line).captures
                i1 = parse(Int64, i1)
                i2 = parse(Int64, i2)
                push!(I, i1)
                push!(J, i2)

                push!(edges, (i1, i2))

                if i1 > maxI
                    maxI = i1
                end

                if i2 > maxJ
                    maxJ = i2
                end
            end
        end

        n = max(maxI, maxJ)

        nodes = Vector{Int64}(1:n)

        degree_out = zeros(n)
        degree_in = zeros(n)
        for i in 1:length(I)
            u = I[i]
            v = J[i]
            degree_out[u] += 1
            degree_in[v] += 1
        end

        V = ones(Int64, length(I))
        transition_matrix = sparse(I, J, V, n, n)
        new(transition_matrix, nodes, edges, degree_out, degree_in, n)
    end

end

function remove_node(graph::Graph, node::Int64)

end


"A box in R^n."
struct BoxSpace{T <: Number,N} <: AbstractSpace
    low::Array{T,N}
    high::Array{T,N}
end

size(s::BoxSpace) = size(s.low)

"""
    BoxSpace(low::Number, high::Number, size::Tuple{Vararg{Int}}=(1,))

```julia
BoxSpace(-1, 1)
BoxSpace(-1, 1, (2,3))
```
"""
BoxSpace(low::Number, high::Number, size::Tuple{Vararg{Int}}=(1,)) = BoxSpace(fill(low, size), fill(high, size))

"""
    BoxSpace(low::Array{<:Number}, high::Array{<:Number})

```julia
BoxSpace([0, 0, 0], [1, 2, 3])
```
"""
BoxSpace(low::Array{<:Number}, high::Array{<:Number}) = size(low) == size(high) && BoxSpace(low, high)

==(x::BoxSpace, y::BoxSpace) = x.low == y.low && x.high == y.high

sample(s::BoxSpace) = map((l, h) -> l + rand() * (h - l), s.low, s.high)

occursin(xs::Array{<:Number}, s::BoxSpace) = size(s) == size(xs) &&
    all(map((a, b, c) -> a ≤ b ≤ c, s.low, xs, s.high))
occursin(x::Number, s::BoxSpace) = all(map((l, h) -> l ≤ x ≤ h, s.low, s.high))


# This file is a part of BAT.jl, licensed under the MIT License (MIT).

using BAT
using Test

using Random
using Distributions, StatsBase

@testset "BAT.HistogramAsMvDistribution" begin
    Random.seed!(123)
    μ = [1.23, -0.67]
    Σ = [0.45 0.32; 0.32 0.76]' * [0.45 0.32; 0.32 0.76]
    true_dist = MvNormal(μ, Σ)
    h = Histogram((μ[1]-10Σ[1]:Σ[1]/10:μ[1]+10Σ[1], μ[2]-10Σ[4]:Σ[4]/10:μ[2]+10Σ[4]))
    n = 10^6
    r = rand(true_dist, n)
    append!(h, (r[1, :], r[2, :]))
    d = BAT.HistogramAsMvDistribution(h)
    @test all(isapprox.(μ, d.μ,   atol = 0.01))
    @test all(isapprox.(Σ, d.cov, atol = 0.01))
    rand!(d, r)
    fit_dist = fit(MvNormal, r)
    @test all(isapprox.(μ, fit_dist.μ,     atol = 0.01))
    @test all(isapprox.(Σ, fit_dist.Σ.mat, atol = 0.01))
end


using Random, Distributions

Random.seed!(1234)

description = "Reduced four-state model (C_A, O_R, O_A)"

# indices of states below are relative to the reduced, 3-state model.
# open index
oi = 2
# activated states
ai = [1,3]
# indices to save when solving
states_to_save = [2, 3]
# parameter names
parnames = ["a0", "sa", "b0", "sb", "c0", "sc", "d0", "sd", "g1", "g2", "h1", "h2"]

"""
Notes on how performance can be improved:
* Modify the objective function so that `db_pars` is called only once per evoluation of the objective function.
    i.e. `tm(...)` should not have to call `db_pars`
* Modify `tm(...)` to update a pre-allocated array in-place
* Use a static vector `SVector` for parameters, e.g. the output of `db_pars`
* Use views of the transition matrix to define the ODE, i.e. argument `Q` in `rhs!(...)`

Similar performance tips are available in `main_v4.jl`.
The above apply equally to `six_hum.jl`.
"""

function db_pars(pars::AbstractVector{}, db; fmt="x")::Vector{}
    #pars are ln-transformed
    if db == "x"
        a0, sa, b0, sb, c0, sc, d0, sd, g1, g2, h1, h2 = exp.(pars)
    else
        if db in ["g1", "h1", "g2", "h2"]
            a0, sa, b0, sb, c0, sc, d0, sd = exp.(pars[1:8])

            if db == "g1"
                g2, h1, h2 = exp.(pars[9:end])
                g1 = (h1*b0*g2*c0)/(d0*h2*a0)
            elseif db == "h1"
                g1, g2, h2 = exp.(pars[9:end])
                h1 = (g1*d0*h2*a0)/(b0*g2*c0)
            elseif db == "g2"
                g1, h1, h2 = exp.(pars[9:end])
                g2 = (g1*d0*h2*a0)/(h1*b0*c0)
            elseif db == "h2"
                g1, g2, h1 = exp.(pars[9:end])
                h2 = (g2*c0*h1*b0)/(a0*g1*d0)
            end

        else
            if db == "c"
                a0, sa, b0, sb, d0, sd, g1, g2, h1, h2 = exp.(pars)
                c0 = (a0*d0*g1*h2)/(b0*g2*h1)
                sc = 1/( (1/sb) - (1/sd) + (1/sa) )

            elseif db == "b"
                a0, sa, c0, sc, d0, sd, g1, g2, h1, h2 = exp.(pars)
                b0 = (a0*g1*d0*h2)/(g2*c0*h1)
                sb = 1/( (1/sc) + (1/sd) - (1/sa) )
            elseif db == "d"
                a0, sa, b0, sb, c0, sc, g1, g2, h1, h2 = exp.(pars)
                d0 = (b0*g2*c0*h1)/(a0*g1*h2)
                sd = 1/( (1/sb) + (1/sa) - (1/sc) )
            elseif db == "a"
                b0, sb, c0, sc, d0, sd, g1, g2, h1, h2 = exp.(pars)
                a0 = (b0*g2*c0*h1)/(g1*d0*h2)
                sa = 1/( (1/sc) - (1/sb) + (1/sd) )
            else
                error("   $db not understood as detailed balance condition.")
            end
        end
    end

    if fmt == "log"
        return log.([a0, sa, b0, sb, c0, sc, d0, sd, g1, g2, h1, h2])
    else
        return [a0, sa, b0, sb, c0, sc, d0, sd, g1, g2, h1, h2]
    end
end

function tm(v, p::AbstractVector{}, db)
    a0, sa, b0, sb, c0, sc, d0, sd, g1, g2, h1, h2 = db_pars(p, db)

    # hyperpolarization-activated
    a1 = a0*exp(-v/sa)
    b1 = b0*exp(v/sb)
    a2 = c0*exp(-v/sc)
    b2 = d0*exp(v/sd)

    Q = [a1  -(b1 + g1 + a1)  -a1  h1-a1;
        g2  -g2  -(a2+h2+g2)  b2-g2;
        0  g1  a2  -(b2 + h1)]

    return Q
end

function get_vpars(v, p::AbstractVector{}, db)
    a0, sa, b0, sb, c0, sc, d0, sd, g1, g2, h1, h2 = db_pars(p, db)

    a1 = a0*exp(-v/sa)
    b1 = b0*exp(v/sb)
    a2 = c0*exp(-v/sc)
    b2 = d0*exp(v/sd)

    return [a1, b1, a2, b2, g1, g2, h1, h2, v]
end

function rhs!(du, u, Q, t)
    du .= Q[:,2:end] * u + Q[:,1]
end

function prepare_BoundsAndParams(pars::Array{Float64,1}, bounds, db)
    # remove entries from parameter and bound arrays given detailed balance
    if db == true || db == "x"
        return pars, bounds
    else
        ps = copy(pars)
        bds = copy(bounds)
        if db == "a"
            deleteat!(ps, 1:2)
            deleteat!(bds, 1:2)
        elseif db == "b"
            deleteat!(ps, 3:4)
            deleteat!(bds, 3:4)
        elseif db == "c"
            deleteat!(ps, 5:6)
            deleteat!(bds, 5:6)
        elseif db == "d"
            deleteat!(ps, 7:8)
            deleteat!(bds, 7:8)
        else
            println("$db not understood. Available are: 'a', 'b', 'c', 'd'.")
        end
    end
    return ps, bds
end

bds = [
        (1e-9, 1), #b0
        (7, 200), #sb

        (1e-9, 1e3), #a0
        (10, 4000), #sa

        (1e-12, 1e-1), #d0
        (50, 200), #sd

        (1e-12, 1e-1), #c0
        (10, 200), #sc,

        (1e-9, 10.), #g1
        (1e-8, 10), #g2

        (1e-9, 10), #h1
        (1e-9, 1e3), #h2
]

hnc2_bds = [
        (1e-7, 1), #a0
        (8, 300), #sa

        (20, 1000), #b0
        (25, 500), #sb

        (1e-8, 1), #c0
        (10, 100), #sc,

        (1e-8, 1), #d0
        (30, 100), #sd

        (1e-6, 1.), #g1
        (1e-12, 1e-3), #g2

        (1e-9, 1e-1), #h1
        (1e-9, 1e-1), #h2

]

ln_par_bds = [log.(x) for x in hnc2_bds]


"""
    true_positives(targets, outputs, [encoding]) -> Union{Int, Dict}

Count how many positive predicted outcomes in `outputs` are also
marked as positive outcomes in `targets`. Which value denotes
"positive" depends on the given (or inferred) `encoding`.
$SCALAR_DESC

$COUNT_ENCODING_DESCR

# Arguments

$COUNT_ARGS

# See also

[`predicted_positive`](@ref),
[`condition_positive`](@ref),
[`true_positive_rate`](@ref)

# Examples

```jldoctest
julia> true_positives(1, 1, LabelEnc.ZeroOne()) # single observation
1

julia> true_positives([1,0,1,1,0], [1,1,1,0,0]) # multiple observations
2

julia> true_positives([:a,:a,:b,:b,:c,:c], [:a,:b,:b,:b,:a,:a]) # multi-class
Dict{Symbol,$Int} with 3 entries:
  :a => 1
  :b => 2
  :c => 0
```
"""
true_positives(target, output, encoding::BinaryLabelEncoding) =
    Int(isposlabel(target, encoding) & isposlabel(output, encoding))

# --------------------------------------------------------------------

"""
    true_negatives(targets, outputs, [encoding]) -> Union{Int, Dict}

Count how many negative predicted outcomes in `outputs` are also
marked as negative outcomes in `targets`. Which value(s) denote
"negative" depends on the given (or inferred) `encoding`.
$SCALAR_DESC

$COUNT_ENCODING_DESCR

# Arguments

$COUNT_ARGS

# See also

[`predicted_negative`](@ref),
[`condition_negative`](@ref),
[`true_negative_rate`](@ref)

# Examples

```jldoctest
julia> true_negatives(0, 0, LabelEnc.ZeroOne()) # single observation
1

julia> true_negatives([1,0,1,1,0], [1,1,1,0,0]) # multiple observations
1

julia> true_negatives([:a,:a,:b,:b,:c,:c], [:a,:b,:b,:b,:a,:a]) # multi-class
Dict{Symbol,$Int} with 3 entries:
  :a => 2
  :b => 3
  :c => 4
```
"""
true_negatives(target, output, encoding::BinaryLabelEncoding) =
    Int(isneglabel(target, encoding) & isneglabel(output, encoding))

# --------------------------------------------------------------------

"""
    false_positives(targets, outputs, [encoding]) -> Union{Int, Dict}

Count how many positive predicted outcomes in `outputs` are
actually marked as negative outcomes in `targets`. These
occurrences are also known as `type_1_errors`. Which value
denotes "positive" depends on the given (or inferred) `encoding`.
$SCALAR_DESC

$COUNT_ENCODING_DESCR

# Arguments

$COUNT_ARGS

# See also

[`predicted_positive`](@ref),
[`condition_negative`](@ref),
[`false_positive_rate`](@ref)

# Examples

```jldoctest
julia> false_positives(0, 1, LabelEnc.ZeroOne()) # single observation
1

julia> false_positives([1,0,1,1,0], [1,1,1,0,0]) # multiple observations
1

julia> false_positives([:a,:a,:b,:b,:c,:c], [:a,:b,:b,:b,:a,:a]) # multi-class
Dict{Symbol,$Int} with 3 entries:
  :a => 2
  :b => 1
  :c => 0
```
"""
false_positives(target, output, encoding::BinaryLabelEncoding) =
    Int(isneglabel(target, encoding) & isposlabel(output, encoding))

const type_1_errors = false_positives

# --------------------------------------------------------------------

"""
    false_negatives(targets, outputs, [encoding]) -> Union{Int, Dict}

Count how many negative predicted outcomes in `outputs` are
actually marked as positive outcomes in `targets`. These
occurrences are also known as `type_2_errors`. Which value
denotes "positive" depends on the given (or inferred) `encoding`.
$SCALAR_DESC

$COUNT_ENCODING_DESCR

# Arguments

$COUNT_ARGS

# See also

[`predicted_negative`](@ref),
[`condition_positive`](@ref),
[`false_negative_rate`](@ref)

# Examples

```jldoctest
julia> false_negatives(1, 0, LabelEnc.ZeroOne()) # single observation
1

julia> false_negatives([1,0,1,1,0], [1,1,1,0,0]) # multiple observations
1

julia> false_negatives([:a,:a,:b,:b,:c,:c], [:a,:b,:b,:b,:a,:a]) # multi-class
Dict{Symbol,$Int} with 3 entries:
  :a => 1
  :b => 0
  :c => 2
```
"""
false_negatives(target, output, encoding::BinaryLabelEncoding) =
    Int(isposlabel(target, encoding) & isneglabel(output, encoding))

const type_2_errors = false_negatives

# --------------------------------------------------------------------

"""
    condition_positive(targets, outputs, [encoding]) -> Union{Int, Dict}

Count the number of positive outcomes in `targets`. Which value
denotes "positive" depends on the given (or inferred) `encoding`.
$SCALAR_DESC

$COUNT_ENCODING_DESCR

# Arguments

$COUNT_ARGS

# See also

[`predicted_positive`](@ref),
[`condition_negative`](@ref),
[`prevalence`](@ref)

# Examples

```jldoctest
julia> condition_positive(1, 0, LabelEnc.ZeroOne()) # single observation
1

julia> condition_positive([1,0,1,1,0], [1,1,1,0,0]) # multiple observations
3

julia> condition_positive([:a,:a,:b,:b,:c,:c], [:a,:b,:b,:b,:a,:a]) # multi-class
Dict{Symbol,$Int} with 3 entries:
  :a => 2
  :b => 2
  :c => 2
```
"""
condition_positive(target, output, encoding::BinaryLabelEncoding) =
    Int(isposlabel(target, encoding))

# --------------------------------------------------------------------

"""
    condition_negative(targets, outputs, [encoding]) -> Union{Int, Dict}

Count the number of negative outcomes in `targets`. Which values
denote "negative" depends on the given (or inferred) `encoding`.
$SCALAR_DESC

$COUNT_ENCODING_DESCR

# Arguments

$COUNT_ARGS

# See also

[`predicted_negative`](@ref),
[`condition_positive`](@ref)

# Examples

```jldoctest
julia> condition_negative(0, 1, LabelEnc.ZeroOne()) # single observation
1

julia> condition_negative([1,0,1,1,0], [1,1,1,0,0]) # multiple observations
2

julia> condition_negative([:a,:a,:b,:b,:c,:c], [:a,:b,:b,:b,:a,:a]) # multi-class
Dict{Symbol,$Int} with 3 entries:
  :a => 4
  :b => 4
  :c => 4
```
"""
condition_negative(target, output, encoding::BinaryLabelEncoding) =
    Int(isneglabel(target, encoding))

# --------------------------------------------------------------------

"""
    predicted_positive(targets, outputs, [encoding]) -> Union{Int, Dict}

Count the number of positive predicted outcomes in `outputs`.
Which value denotes "positive" depends on the given (or inferred)
`encoding`.
$SCALAR_DESC

$COUNT_ENCODING_DESCR

# Arguments

$COUNT_ARGS

# See also

[`predicted_negative`](@ref),
[`condition_positive`](@ref)

# Examples

```jldoctest
julia> predicted_positive(0, 1, LabelEnc.ZeroOne()) # single observation
1

julia> predicted_positive([1,0,1,1,0], [1,1,1,0,0]) # multiple observations
3

julia> predicted_positive([:a,:a,:b,:b,:c,:c], [:a,:b,:b,:b,:a,:a]) # multi-class
Dict{Symbol,$Int} with 3 entries:
  :a => 3
  :b => 3
  :c => 0
```
"""
predicted_positive(target, output, encoding::BinaryLabelEncoding) =
    Int(isposlabel(output, encoding))

# --------------------------------------------------------------------

"""
    predicted_negative(targets, outputs, [encoding]) -> Union{Int, Dict}

Count the number of negative predicted outcomes in `outputs`.
Which values denote "negative" depends on the given (or inferred)
`encoding`.
$SCALAR_DESC

$COUNT_ENCODING_DESCR

# Arguments

$COUNT_ARGS

# See also

[`predicted_positive`](@ref),
[`condition_negative`](@ref)

# Examples

```jldoctest
julia> predicted_negative(1, 0, LabelEnc.ZeroOne()) # single observation
1

julia> predicted_negative([1,0,1,1,0], [1,1,1,0,0]) # multiple observations
2

julia> predicted_negative([:a,:a,:b,:b,:c,:c], [:a,:b,:b,:b,:a,:a]) # multi-class
Dict{Symbol,$Int} with 3 entries:
  :a => 3
  :b => 3
  :c => 6
```
"""
predicted_negative(target, output, encoding::BinaryLabelEncoding) =
    Int(isneglabel(output, encoding))

# --------------------------------------------------------------------

"""
    correctly_classified(targets, outputs, [encoding]) -> Union{Int, Dict}

Count the number of predicted outcomes in `outputs` that agree
with the expected outcomes in `targets` under a two-class
interpretation. Which value(s) denote "positive" or "negative"
depends on the given (or inferred) `encoding`.
$SCALAR_DESC

$COUNT_ENCODING_DESCR

# Arguments

$COUNT_ARGS

# See also

[`incorrectly_classified`](@ref),
[`accuracy`](@ref)

# Examples

```jldoctest
julia> correctly_classified(0, 0, LabelEnc.ZeroOne()) # single observation
1

julia> correctly_classified([1,0,1,1,0], [1,1,1,0,0]) # multiple observations
3

julia> correctly_classified([:a,:a,:b,:b,:c,:c], [:a,:b,:b,:b,:a,:a]) # multi-class
Dict{Symbol,$Int} with 3 entries:
  :a => 3
  :b => 5
  :c => 4
```
"""
correctly_classified(target, output, encoding::BinaryLabelEncoding) =
    Int(isposlabel(target, encoding) == isposlabel(output, encoding))

# --------------------------------------------------------------------

"""
    incorrectly_classified(targets, outputs, [encoding]) -> Union{Int, Dict}

Count the number of predicted outcomes in `outputs` that are
misclassified according to the expected outcomes in `targets`
under a two-class interpretation. Which value(s) denote
"positive" or "negative" depends on the given (or inferred)
`encoding`.
$SCALAR_DESC

$COUNT_ENCODING_DESCR

# Arguments

$COUNT_ARGS

# See also

[`correctly_classified`](@ref),
[`accuracy`](@ref)

# Examples

```jldoctest
julia> incorrectly_classified(0, 1, LabelEnc.ZeroOne()) # single observation
1

julia> incorrectly_classified([1,0,1,1,0], [1,1,1,0,0]) # multiple observations
2

julia> incorrectly_classified([:a,:a,:b,:b,:c,:c], [:a,:b,:b,:b,:a,:a]) # multi-class
Dict{Symbol,$Int} with 3 entries:
  :a => 3
  :b => 1
  :c => 2
```
"""
incorrectly_classified(target, output, encoding::BinaryLabelEncoding) =
    Int(xor(isposlabel(target, encoding), isposlabel(output, encoding)))

const misclassified = incorrectly_classified

# --------------------------------------------------------------------

# only for internal use to define `prevalence`
_length_targets(target, output, encoding) = 1
_length_targets(target::AbstractArray, output, encoding) = length(targets)

# --------------------------------------------------------------------
# Generate common fallback functions
for fun in (:true_positives,  :true_negatives,
            :false_positives, :false_negatives,
            :condition_positive, :condition_negative,
            :predicted_positive, :predicted_negative,
            :correctly_classified, :incorrectly_classified)

    # Convenience syntax for using native labels
    @eval function ($fun)(targets, outputs, encoding::AbstractVector)
        ($fun)(targets, outputs, LabelEnc.NativeLabels(encoding))
    end

    # Generic fallback. Tries to infer label encoding
    @eval function ($fun)(targets, outputs)
        encoding = _labelenc(targets, outputs)
        ($fun)(targets, outputs, encoding)
    end

    # BinaryLabelEncoding: Generate shared accumulator
    @eval function ($fun)(
            targets::AbstractArray,
            outputs::AbstractArray,
            encoding::BinaryLabelEncoding)
        @_dimcheck length(targets) == length(outputs)
        result::Int = 0
        @inbounds for I in eachindex(targets, outputs)
            result += ($fun)(targets[I], outputs[I], encoding)
        end
        result
    end

    # Multiclass LabelEncoding: Implementation for scalar
    @eval function ($fun)(target, output, encoding::LabelEncoding)
        labels = label(encoding)
        result = Dict{eltype(labels),Int}()
        for l in labels
            result[l] = ($fun)(target, output, LabelEnc.OneVsRest(l))
        end
        result
    end

    # Multiclass LabelEncoding: Generate shared accumulator
    @eval function ($fun)(
            targets::AbstractArray,
            outputs::AbstractArray,
            encoding::LabelEncoding)
        @_dimcheck length(targets) == length(outputs)
        labels = label(encoding)
        results = zeros(Int, length(labels))
        ovr = [LabelEnc.OneVsRest(l) for l in labels]
        @inbounds for I in eachindex(targets, outputs)
            target = targets[I]
            output = outputs[I]
            for j in eachindex(results, ovr)
                results[j] += ($fun)(target, output, ovr[j])
            end
        end
        Dict(Pair.(labels, results))
    end

    # Disable support for OneOfK (otherwise behaviour is misleading)
    @eval function ($fun)(target, output, encoding::LabelEnc.OneOfK)
        throw(ArgumentError("encoding LabelEnc.OneOfK not yet supported"))
    end

    @eval function ($fun)(targets::AbstractArray, outputs::AbstractArray, encoding::LabelEnc.OneOfK)
        throw(ArgumentError("encoding LabelEnc.OneOfK not yet supported"))
    end
end


using Mdp.PricingProblem
using Mdp.PricingAlgorithmFH

"""
Model of test problem:

3 nodes, 2 edges: graph:      A---B---C
capacity:     1   1
sell end:     3   5
"""
P = Problem(
    [0:1, 0:1],             # Capacity of edges
    [3, 5],                 # Selling period end of edges
    5,                      # Number of timesteps (Start at 1)
    [10, 20],               # Actions (prices)
    [(1,), (2,), (1, 2)],   # Products (seqeuences of edge indeces)
    Dict((1,) => 0.5,       # λ: Dictionary of demand intensities for products
        (2,) => 0.5,
        (1, 2) => 0.4),
)

"""
Model of test problem 2:

2 nodes, 1 edge: graph:      A---B
capacity:     1
sell end:     5
"""
P_2 = Problem(
    [0:1,],                 # Capacity of edges
    [5,],                   # Selling period end of edges
    5,                      # Number of timesteps (Start at 1)
    [10, 20],               # Actions (prices)
    [(1,),],                # Products (seqeuences of edge indeces)
    Dict((1,) => 0.5)       # λ: Dictionary of demand intensities for products
)

@testset "Q, π, V" begin
    @testset "Test Problem 1" begin
        # See docs/FH_mdp_testcase.jpg
        @testset "V_next=0" begin
            V_next = zeros(Float64, map(length, P.C)...)
            k = 3#P.N
            Q = get_Q(k, V_next, P)
            # Q array dimensions are [c1, a, c2]
            @test Q == cat([0.0 1.3333333333333333;
                            0.0 1.3333333333333333;
                            0.0 0.0],
                            [0.8000000000000002 3.2;
                            0.8000000000000002 3.2;
                            0.0 0.0], dims=3)
            @test get_V(Q) == [0.0 0.8000000000000002;
                           1.3333333333333333 3.2]
            @test get_π(Q) == [1 1; 1 1]

            k=5 # difference from selling period end for products (1,) and (1,2)
            Q = get_Q(k, V_next, P)
            @test Q == cat([0.0 0.0;
                            0.0 0.0;
                            0.0 0.0],
                            [0.8000000000000002 0.8000000000000002;
                            0.8000000000000002 0.8000000000000002;
                            0.0 0.0], dims=3)
            @test get_V(Q) == [0.0 0.8000000000000002;
                          0.0 0.8000000000000002]
            @test get_π(Q) == [1 1; 1 1]
        end

        @testset "V_next=10" begin
            V_next = ones(Float64, map(length, P.C)...).*10
            k = 3
            #TODO: The action 2 is caused by numerical instability!
            Q = get_Q(k, V_next, P)
            @test Q == cat([10.0 11.333333333333334;
                            10.0 11.333333333333334;
                            10.0 10.0],
                            [10.799999999999999 13.199999999999998;
                            10.8 13.200000000000001;
                            10.0 10.0], dims=3)
            @test get_V(Q) == [0.0 0.8000000000000002;
                          1.3333333333333333 3.200000000000001].+10.
            @test get_π(Q) == [1 2; 1 2]

            k = 5
            Q = get_Q(k, V_next, P)
            @test Q == cat([10.0 10.0;
                            10.0 10.0;
                            10.0 10.0],
                            [10.799999999999999 10.799999999999999;
                            10.8 10.8;
                            10.0 10.0], dims=3)
            @test get_V(Q) == [0.0 0.8000000000000002;
                          0.0 0.8000000000000002].+10.
            @test get_π(Q) == [1 2; 1 2]
        end
    end
    @testset "Test Problem 2" begin
    end
end

@testset "state2id" begin
    @test state2id([0,1])==[1,2]
    @test state2id([0,0])==[1,1]
end

@testset "get_R" begin
    R_s = state_rewards(P.A)
    @test get_R([0,1], [0,1], R_s)==[0.,0.,0.]
    @test get_R([0,0], [0,0], R_s)==[0.,0.,0.]
    @test get_R([0,1], [0,0], R_s)==[P.A...,0.]
    @test get_R([1,0], [0,0], R_s)==[P.A...,0.]
    @test get_R([1,1], [0,1], R_s)==[P.A...,0.]
end

@testset "state_transitions" begin
    """
    These probabilities are used to set up the following testcases.
    """
    k = 3
    @test state_transitions([0,0],k, P.A, P.products, P) == ([[0,0]],
        reshape([1,1,1], 3,1))
    @test state_transitions([0,1],k, P.A, P.products, P) == ([[0,1],[0,0]],
        [0.9199999999999999 0.08000000000000002;
        0.96 0.04000000000000001;
        1.0 0.0])
    @test state_transitions([1,0],k, P.A, P.products, P) == ([[1,0],[0,0]],
        [0.8666666666666667 0.13333333333333333;
        0.9333333333333333 0.06666666666666667;
        1.0 0.0])
    @test state_transitions([1,1],k, P.A, P.products, P) == ([[1,1],[0,1],[1,0],[0,0]],
        [0.6799999999999999 0.13333333333333333 0.08000000000000002 0.10666666666666667;
        0.84 0.06666666666666667 0.04000000000000001 0.05333333333333334;
        1.0 0.0 0.0 0.0])

    k = 4
    @test state_transitions([0,0],k, P.A, P.products, P) == ([[0,0]],
        reshape([1,1,1], 3,1))
    @test state_transitions([0,1],k, P.A, P.products, P) == ([[0,1],[0,0]],
        [0.9199999999999999 0.08000000000000002;
        0.96 0.04000000000000001;
        1.0 0.0])
    @test state_transitions([1,0],k, P.A, P.products, P) == ([[1,0],[0,0]],
        [1. 0.0;
        1. 0.0;
        1.0 0.0])
    @test state_transitions([1,1],k, P.A, P.products, P) == ([[1,1],[0,1],[1,0],[0,0]],
        [0.91999999999999998 0.0 0.08000000000000002 0.0;
        0.95999999999999999 0.0 0.04000000000000001 0.0;
        1.0 0.0 0.0 0.0])
end

@testset "get_next_states" begin
    @test get_next_states([0,0], P.products) == ([()],
        [[0,0]])
    @test get_next_states([0,1], P.products) == ([(), (2,)],
        [[0,1],[0,0]])
    @test get_next_states([1,0], P.products) == ([(), (1,)],
        [[1,0],[0,0]])
    @test get_next_states([1,1], P.products) == ([(), (1,), (2,), (1,2)],
        [[1,1],[0,1],[1,0],[0,0]])
end


function sameSpan(aVecs::Array{Float64,2},bVecs::Array{Float64,2})
    aRank=rank(aVecs)
    bRank=rank(bVecs)
    bothRank=rank(vcat(aVecs,bVecs),10e-12)
    aRank==bRank==bothRank
end


################################################################################
#
#  Unit rank
#
################################################################################

@doc Markdown.doc"""
    unit_group_rank(O::NfOrd) -> Int

Returns the unit rank of $\mathcal O$, that is, the rank of the unit group
$\mathcal O^\times$.
"""
function unit_group_rank(O::NfOrd)
  return unit_group_rank(nf(O))
end

################################################################################
#
#  Test if units are independent
#
################################################################################

@doc Markdown.doc"""
    isindependent{T}(x::Vector{T})

Given an array of non-zero units in a number field, returns whether they
are multiplicatively independent.
"""
function isindependent(x::Vector{T}, p::Int = 32) where T
  return _isindependent(x, p)
end

function _isindependent(x::Vector{T}, p::Int = 32) where T
  K = _base_ring(x[1])

  deg = degree(K)
  r1, r2 = signature(K)
  rr = r1 + r2
  r = rr - 1 # unit rank

  conlog = Vector{Vector{arb}}(undef, length(x))

  # This can be made more memory friendly
  while true
    @assert p != 0

    q = 2
    for i in 1:length(x)
      @vprint :UnitGroup 3 "Computing conjugates with precision $p of ($i) $(length(x[i].fac))...\n"
      conlog[i] = conjugates_arb_log(x[i], p)
      for j in 1:rr
        q = max(q, bits(conlog[i][j]))
      end
    end

    A = zero_matrix(ArbField(q, cached = false), length(x), rr)

    for i in 1:length(x)
      for j in 1:rr
        A[i, j] = conlog[i][j]
        @assert radiuslttwopower(A[i, j], -p)
      end
    end

    Ar = base_ring(A)

    B = A*transpose(A)
    @vprint :UnitGroup 1 "Computing det of $(nrows(B))x$(ncols(B)) matrix with precision $(p) ... \n"
    d = det(B)

    y = (Ar(1)//Ar(r))^r * (Ar(21)//Ar(128) * log(Ar(deg))//(Ar(deg)^2))^(2*r)
    if isfinite(d) && ispositive(y - d)
      return false, p
    elseif isfinite(d) && ispositive(d)
      return true, p
    end
    p = 2*p
  end
end


module AppendedTriangulationsTests

using Test
using Gridap.ReferenceFEs
using Gridap.Arrays
using Gridap.Geometry
using Gridap.Fields
using Gridap.Integration
using LinearAlgebra: ⋅
using Gridap.CellData
using Gridap.FESpaces

domain = (0,1,0,1)
partition = (10,10)
model = CartesianDiscreteModel(domain,partition)

ncells = num_cells(model)
nin = ceil(Int,2*ncells/3)
cell_to_mask = fill(false,ncells)
cell_to_mask[1:nin] .= true

grid = get_grid(model)

trian_in = RestrictedTriangulation(grid,cell_to_mask)
trian_out = RestrictedTriangulation(grid,collect(Bool, .! cell_to_mask))

trian = lazy_append(trian_out,trian_in)
test_triangulation(trian)

order = 1
quad = CellQuadrature(trian,2*order)
quad_in = CellQuadrature(trian_in,2*order)
quad_out = CellQuadrature(trian_out,2*order)

q = get_coordinates(quad)
w = get_weights(quad)
@test isa(q,AppendedArray)
@test isa(w,AppendedArray)

V = TestFESpace(model=model,valuetype=Float64,order=order,reffe=:Lagrangian,conformity=:H1)

u(x) = x[1]+x[2]

_v = interpolate(u,V)
v = restrict(_v,trian)

e = u - v
el2 = sqrt(sum(integrate(e*e,trian,quad)))
@test el2 < 1.0e-8

_dv = get_cell_basis(V)
dv = restrict(_dv,trian)

cellmat =  integrate(∇(dv)⋅∇(dv),trian,quad)
@test isa(cellmat,AppendedArray)
@test isa(cellmat.a,CompressedArray)
@test isa(cellmat.b,CompressedArray)

end # module


using ADCME
using PyCall
using LinearAlgebra
using PyPlot
using SparseArrays
using Random
Random.seed!(233)

function scatter_update(A::Union{SparseTensor, SparseMatrixCSC{Float64,Int64}},
            ii,jj,B::Union{SparseTensor, SparseMatrixCSC{Float64,Int64}})
    !isa(A, SparseTensor) && (A=SparseTensor(A))
    !isa(B, SparseTensor) && (B=SparseTensor(B))
    ii1, jj1, vv1 = find(A)
    m1_, n1_ = size(A)
    ii2, jj2, vv2 = find(B)

    sparse_scatter_update_ = load_op_and_grad("./build/libSparseScatterUpdate","sparse_scatter_update", multiple=true)
    ii1,jj1,vv1,m1,n1,ii2,jj2,vv2,ii,jj = convert_to_tensor([ii1,jj1,vv1,m1_,n1_,ii2,jj2,vv2,ii,jj], [Int64,Int64,Float64,Int64,Int64,Int64,Int64,Float64,Int64,Int64])
    ii, jj, vv = sparse_scatter_update_(ii1,jj1,vv1,m1,n1,ii2,jj2,vv2,ii,jj)
    SparseTensor(ii, jj, vv, m1_, n1_)
end

# TODO: specify your input parameters
A = sprand(10,10,0.3)
B = sprand(3,3,0.6)
ii = [1;4;5]
jj = [2;4;6]
u = scatter_update(A, ii, jj, B)
C = copy(A)
C[ii,jj] = B
sess = Session(); init(sess)
@show run(sess, u)-C

# uncomment it for testing gradients
# error()

# TODO: change your test parameter to `m`
#       in the case of `multiple=true`, you also need to specify which component you are testings
# gradient check -- v
function scalar_function(A, B, m)
    ii1, jj1, vv1 = find(A)
    ii2, jj2, vv2 = find(B)
    if length(m)==length(vv1)
        A = SparseTensor(ii1, jj1, m, size(A)...)
    else
        B = SparseTensor(ii2, jj2, m, size(B)...)
    end
    C = scatter_update(A, ii, jj, B)
    return sum(C)^2
end

# TODO: change `m_` and `v_` to appropriate values

A = sprand(10,10,0.3)|>SparseTensor
B = sprand(3,3,0.6)|>SparseTensor
ii = [1;4;5]
jj = [2;4;6]

m_ = constant(rand(length(values(A))))
v_ = rand(length(values(A)))
y_ = scalar_function(A, B, m_)
dy_ = gradients(y_, m_)
ms_ = Array{Any}(undef, 5)
ys_ = Array{Any}(undef, 5)
s_ = Array{Any}(undef, 5)
w_ = Array{Any}(undef, 5)
gs_ =  @. 1 / 10^(1:5)

for i = 1:5
    g_ = gs_[i]
    ms_[i] = m_ + g_*v_
    ys_[i] = scalar_function(A, B, ms_[i])
    s_[i] = ys_[i] - y_
    w_[i] = s_[i] - g_*sum(v_.*dy_)
end

sess = Session(); init(sess)
sval_ = run(sess, s_)
wval_ = run(sess, w_)
close("all")
loglog(gs_, abs.(sval_), "*-", label="finite difference")
loglog(gs_, abs.(wval_), "+-", label="automatic differentiation")
loglog(gs_, gs_.^2 * 0.5*abs(wval_[1])/gs_[1]^2, "--",label="\$\\mathcal{O}(\\gamma^2)\$")
loglog(gs_, gs_ * 0.5*abs(sval_[1])/gs_[1], "--",label="\$\\mathcal{O}(\\gamma)\$")

plt.gca().invert_xaxis()
legend()
xlabel("\$\\gamma\$")
ylabel("Error")