Initial commit.
authorGreg Burri <greg.burri@gmail.com>
Thu, 8 Jul 2021 09:41:10 +0000 (11:41 +0200)
committerGreg Burri <greg.burri@gmail.com>
Thu, 8 Jul 2021 09:41:10 +0000 (11:41 +0200)
eth_staking.jl [new file with mode: 0644]
fourier.jl [new file with mode: 0644]

diff --git a/eth_staking.jl b/eth_staking.jl
new file mode 100644 (file)
index 0000000..7a42f56
--- /dev/null
@@ -0,0 +1,61 @@
+### A Pluto.jl notebook ###
+# v0.14.8
+
+using Markdown
+using InteractiveUtils
+
+# ╔═╡ 701a0e50-df22-11eb-2eea-a56013a4ffdd
+html"""<style>
+main {
+    max-width: 90% !important;
+    margin-right: 100px !important;
+}
+</style>
+"""
+
+# ╔═╡ 6605e130-e014-4de3-8837-baf4eb77418c
+md"""
+**Ethereum staking project**
+
+The goal is to set up a passive income for the beginning of year 2025.
+"""
+
+# ╔═╡ 9a96995c-89be-4fa9-97f6-81b7bd5898b8
+price_eth_today = 2380
+
+# ╔═╡ ff389da3-6761-4d78-873e-94f1b5319a05
+price_btc_today = 34_800
+
+# ╔═╡ 52becaeb-d325-4b17-a4ba-de19d6eb2cf8
+yield = 0.05 # 5% per year.
+
+# ╔═╡ 5bd92f00-0b9d-4217-92dc-ed3f7234666f
+usd_per_month_to_live = 9_000
+
+# ╔═╡ 3a5fdd52-6445-4b10-ab77-de5b2f833009
+#=
+This is a prediction of eth growth by the year 2025.
+=#
+grow_factor_of_eth = 6
+
+# ╔═╡ 8d251250-a9be-4689-9318-106eb240f4e6
+let
+       usd_per_year_per_eth = (price_eth_today * grow_factor_of_eth) * yield
+       eth_needed = usd_per_month_to_live * 12 / usd_per_year_per_eth
+       bitcoin_needed = eth_needed * price_eth_today / price_btc_today
+       md"""
+       Number of ETH needed today: $eth_needed
+       
+       Number of bitcoin needed today: $bitcoin_needed
+       """
+end
+
+# ╔═╡ Cell order:
+# ╟─701a0e50-df22-11eb-2eea-a56013a4ffdd
+# ╟─6605e130-e014-4de3-8837-baf4eb77418c
+# ╟─9a96995c-89be-4fa9-97f6-81b7bd5898b8
+# ╟─ff389da3-6761-4d78-873e-94f1b5319a05
+# ╟─52becaeb-d325-4b17-a4ba-de19d6eb2cf8
+# ╟─5bd92f00-0b9d-4217-92dc-ed3f7234666f
+# ╟─3a5fdd52-6445-4b10-ab77-de5b2f833009
+# ╟─8d251250-a9be-4689-9318-106eb240f4e6
diff --git a/fourier.jl b/fourier.jl
new file mode 100644 (file)
index 0000000..6530094
--- /dev/null
@@ -0,0 +1,661 @@
+### A Pluto.jl notebook ###
+# v0.14.8
+
+using Markdown
+using InteractiveUtils
+
+# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error).
+macro bind(def, element)
+    quote
+        local el = $(esc(element))
+        global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : missing
+        el
+    end
+end
+
+# ╔═╡ 799b8d42-e5ff-11ea-37b7-ede54edaf352
+begin
+       using PlutoUI, Plots, LinearAlgebra, Statistics, CSV, DataFrames, StatsPlots
+       plotly()
+       default(legend = false, minorgrid = true)       
+end
+
+# ╔═╡ 60408320-3f7b-11eb-1773-e706096dc457
+html"""<style>
+main {
+    max-width: 90% !important;
+    margin-right: 100px !important;
+}
+</style>
+"""
+
+# ╔═╡ 7f284800-e606-11ea-0e36-a5e6934dbcf1
+md"## Fourier series"
+
+# ╔═╡ 404bafa0-e7aa-11ea-0158-55902780a54e
+md"""
+The following code will parse a path from a SVG file (see <https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths>)
+Then will compute the fourier series of the path with a number of coefficients to compute as a parameter.
+
+Great explanation about the Fourier series: <https://www.youtube.com/watch?v=r6sGWTCMz2k>
+"""
+
+# ╔═╡ 34294d10-e785-11ea-373a-8f17483f4468
+# An SVG path command.
+struct Command
+       command # Can be :move_to_rel :move_to_abs :vertical_rel :vertical_abs :horizontal_rel :horizontal_abs :line_rel :quadratic_bézier_rel :cubic_bézier_rel :cubic_bézier_abs :close_path
+       points :: Vector{Vector{Float64}}
+end
+
+# ╔═╡ c90fa10e-e7ab-11ea-1dd1-7b5d60129245
+md"**`parse_parth`** take a path as a string and return a list of commands, see the struct `Command`"
+
+# ╔═╡ 2ae11130-e6df-11ea-3a92-55d4d69ef9a8
+function parse_path(path :: String) :: Vector{Command}
+       commands_str = split(path, " ")
+       commands = []
+       for word ∈ commands_str
+               if word == "m"
+                       push!(commands, Command(:move_to_rel, []))
+               elseif word == "M"
+                       push!(commands, Command(:move_to_abs, []))
+               elseif word == "v"
+                       push!(commands, Command(:vertical_rel, []))
+               elseif word == "V"
+                       push!(commands, Command(:vertical_abs, []))
+               elseif word == "h"
+                       push!(commands, Command(:horizontal_rel, []))
+               elseif word == "H"
+                       push!(commands, Command(:horizontal_abs, []))
+               elseif word == "l"
+                       push!(commands, Command(:line_rel, []))
+               elseif word == "q"
+                       push!(commands, Command(:quadratic_bézier_rel, []))
+               elseif word == "c"
+                       push!(commands, Command(:cubic_bézier_rel, []))
+               elseif word == "C"
+                       push!(commands, Command(:cubic_bézier_abs, []))
+               elseif word == "z" || word == "Z"
+                       push!(commands, Command(:close_path, []))
+               elseif word[1] >= 'A' && word[1] <= 'z'
+                       throw(ErrorException("Command not supported: $(word[1])"))
+               else    
+                       current = last(commands)
+                       if current.command == :vertical_rel || current.command == :vertical_abs
+                               push!(current.points, [0.0, parse(Float64, word)])                              
+                       elseif current.command == :horizontal_rel || current.command == :horizontal_abs
+                               push!(current.points, [parse(Float64, word), 0.0])
+                       else
+                               push!(current.points, parse.(Float64, split(word, ",")))
+                       end
+               end                             
+       end
+       commands
+end
+
+# ╔═╡ 077ff940-e7ac-11ea-3461-5f0c5a2b38b8
+md"Each bézier curve is converted in a number of points given by **`points_per_bézier`**"
+
+# ╔═╡ 28287c70-e785-11ea-37e0-8b6c4f8d31ef
+points_per_bézier = 20
+
+# ╔═╡ 1cde9fc0-e785-11ea-0644-7d58bfd03a5e
+function commands_to_points(commands, reverse_y = true) :: Vector{Vector{Float64}}
+       points = []
+       
+       B2(t, P₀, P₁, P₂) = (1 - t) * ((1 - t) * P₀ + t * P₁) + t * ((1 - t) * P₁ + t * P₂)
+       
+       B3(t, P₀, P₁, P₂, P₃) = (1 - t)^3 * P₀ + 3 * (1 - t)^2 * t * P₁ + 3 * (1 - t) * t^2 * P₂ + t^3 * P₃
+       
+       for c ∈ commands
+               if c.command == :move_to_rel                    
+                       push!(points, c.points[1] + (length(points) > 0 ? last(points) : [0.0, 0.0]))
+               elseif c.command == :move_to_abs
+                       push!(points, c.points[1])
+               elseif c.command == :vertical_rel || c.command == :horizontal_rel ||  c.command == :line_rel
+                       for p ∈ c.points
+                               push!(points, last(points) + p)
+                       end
+               elseif c.command == :vertical_abs
+                       push!(points, [last(points)[1], c.points[1][2]])
+               elseif c.command == :horizontal_abs
+                       push!(points, [c.points[1][1], last(points)[2]])
+               elseif c.command == :quadratic_bézier_rel
+                       for i ∈ 1:2:length(c.points)-1
+                               P₀ = last(points)
+                               P₁ = P₀ + c.points[i]
+                               P₂ = P₀ + c.points[i + 1]
+                               
+                               for t ∈ 0:1/points_per_bézier:1
+                                       push!(points, B2(t, P₀, P₁, P₂))
+                               end
+                       end
+               elseif c.command == :cubic_bézier_rel
+                       for i ∈ 1:3:length(c.points)-2
+                               P₀ = last(points)
+                               P₁ = P₀ + c.points[i]
+                               P₂ = P₀ + c.points[i + 1]
+                               P₃ = P₀ + c.points[i + 2]
+                               for t ∈ 0:1/points_per_bézier:1
+                                       push!(points, B3(t, P₀, P₁, P₂, P₃))
+                               end
+                       end
+                       
+               elseif c.command == :cubic_bézier_abs
+                       for i ∈ 1:3:length(c.points)-2
+                               P₀ = last(points)
+                               P₁ = c.points[i]
+                               P₂ = c.points[i + 1]
+                               P₃ = c.points[i + 2]                          
+                               for t ∈ 0:1/points_per_bézier:1
+                                       push!(points, B3(t, P₀, P₁, P₂, P₃))
+                               end
+                       end
+               elseif c.command == :close_path
+                       push!(points, copy(points[1]))
+               else
+                       throw(ErrorException("command not supported: $c"))
+               end
+       end
+
+       # Reverse y coordinate of each points because the y axis is to the bottom in .
+       if reverse_y
+               for p ∈ points
+                       p[2] = -p[2]
+               end
+       end
+
+       points
+end
+
+# ╔═╡ 140a7e00-e785-11ea-2599-6fa3e0e8191e
+function position_on_points(points, k :: Float64) :: Vector{Float64}
+       if !(0 <= k <= 1)
+               throw(ArgumentError("k must be in [0, 1]"))
+       end
+
+       total_len = 0
+       lengths = []
+       for i ∈ 2:length(points)
+               l = norm(points[i] - points[i-1])
+               total_len += l
+               push!(lengths, l)
+       end
+
+       current_len = 0
+       for i ∈ 1:length(lengths)
+               next_len = current_len + lengths[i]     
+               
+               if current_len == next_len
+                       continue
+               end
+
+               if k >= current_len / total_len && k <= next_len / total_len            
+                       len_in_segment = k * total_len - current_len                            
+                       return normalize(points[i+1] - points[i]) * len_in_segment + points[i]
+               end
+               current_len = next_len
+       end
+end
+
+# ╔═╡ 50395b50-e82d-11ea-2da6-3baecb6100de
+md"Some vecorial drawings (path):"
+
+# ╔═╡ a59ac5ce-e782-11ea-09ac-295f9e2292b4
+begin
+       path_box = "m 40.82143,73.327377 h 71.05952 V 136.07142 H 40.82143 Z"
+       commands_box = parse_path(path_box)
+       points_box = commands_to_points(commands_box)
+               
+       plot(points_box .|> Tuple, aspect_ratio = true)
+end
+
+# ╔═╡ 62f97bb0-e762-11ea-0561-c75618e97061
+begin
+       path_letter_g = "m 65.054135,67.883872 v -1.007821 l 3.638702,-0.0059 v 3.187527 q -0.837898,0.667974 -1.72853,1.007821 -0.890633,0.333987 -1.82814,0.333987 -1.265636,0 -2.302754,-0.539067 -1.031259,-0.544886 -1.558607,-1.570286 -0.527348,-1.025399 -0.527348,-2.291034 0,-1.253917 0.521489,-2.33791 0.527348,-1.089853 1.511731,-1.617201 0.984383,-0.527349 2.267597,-0.527349 0.931649,0 1.681655,0.30469 0.755866,0.298831 1.183604,0.837898 0.427738,0.539067 0.650396,1.406262 l -1.0254,0.281252 q -0.193361,-0.656255 -0.480472,-1.031258 -0.287112,-0.375004 -0.82032,-0.597662 -0.533207,-0.228517 -1.183603,-0.228517 -0.779304,0 -1.347668,0.240236 -0.568364,0.234377 -0.919929,0.621099 -0.345706,0.386722 -0.539067,0.849617 -0.328128,0.796881 -0.328128,1.72853 0,1.148447 0.392581,1.921891 0.398441,0.773444 1.154307,1.148447 0.755866,0.375003 1.605482,0.375003 0.738287,0 1.441418,-0.281252 0.703131,-0.287112 1.066415,-0.609381 v -1.599622 z"
+       commands_letter_g = parse_path(path_letter_g)
+       points_letter_g = commands_to_points(commands_letter_g) 
+       
+       plot(points_letter_g .|> Tuple, aspect_ratio = true)
+end
+
+# ╔═╡ ede054d0-e86e-11ea-1002-6f4e3d3c517c
+md"## 🐱"
+
+# ╔═╡ 5ddbafc0-e7a5-11ea-113a-232748354da7
+begin
+       path_cat = "M 1607.2,4071.3 C 1196.4,3961.8 1036.6,3831.7 865.4,3480.2 680.5,3099 612.1,2742.9 657.7,2416.6 726.2,1957.8 892.8,1542.5 1242,955.9 l 255.6,-429.1 -16,-189.5 c -9.1,-105 -43.4,-310.4 -75.3,-454.2 -34.2,-143.8 -68.5,-362.9 -82.2,-483.9 -13.7,-130.1 -41.1,-264.7 -68.5,-328.7 -79.9,-180.3 -239.6,-378.9 -449.6,-561.4 l -203.1,-175.8 -146.1,-360.6 c -226,-561.5 -253.3,-641.3 -301.3,-901.5 -22.8,-132.4 -47.9,-255.6 -54.8,-273.9 -13.7,-34.2 146.1,-230.5 216.8,-269.3 57.1,-29.7 146.1,11.4 198.6,89 27.4,43.3 38.8,111.8 41.1,253.3 0,175.8 11.4,216.8 107.3,454.2 59.3,143.8 132.4,287.6 164.3,319.5 125.5,130.1 289.9,203.1 698.4,308.1 219.1,54.8 429.1,114.1 467.9,127.8 57.1,20.5 70.8,18.3 95.9,-13.7 41.1,-54.8 36.5,-116.4 -16,-392.5 -25.1,-127.8 -45.6,-278.4 -45.6,-333.2 0,-205.4 255.6,-595.7 680.1,-1040.7 l 175.7,-184.9 271.6,6.8 c 262.5,6.8 271.6,9.1 317.3,66.2 38.8,54.8 41.1,68.5 13.7,123.3 -45.6,93.6 -136.9,150.6 -267,166.6 -111.8,16 -125.5,25.1 -310.4,226 -203.1,219.1 -235.1,278.4 -210,401.7 18.3,95.9 235.1,413.1 417.7,614 102.7,111.8 175.7,219.1 235.1,351.5 79.9,171.2 95.9,191.7 152.9,194 728.1,20.5 938,38.8 1063.6,84.5 l 130.1,47.9 200.8,-57.1 c 538.7,-148.4 495.3,-127.8 502.1,-225.9 13.7,-157.5 -86.7,-787.4 -157.5,-995.1 -59.4,-175.7 -66.2,-228.2 -66.2,-470.2 0,-244.2 4.6,-280.7 47.9,-335.5 45.7,-57.1 54.8,-59.3 280.7,-59.3 262.5,0 287.6,11.4 287.6,139.2 0,66.2 -16,98.2 -68.5,141.5 -36.5,32 -89,66.2 -114.1,73 -93.6,29.7 -38.8,189.4 248.8,739.5 367.5,707.5 415.4,755.5 725.8,755.5 109.6,0 219.1,13.7 283,38.8 l 107.3,36.5 146.1,-105 c 159.8,-111.8 292.1,-248.8 292.1,-299 0,-43.3 152.9,-178 730.4,-643.6 269.3,-219.1 547.8,-445 616.2,-502.1 68.5,-59.4 166.6,-125.5 219.1,-148.3 50.2,-22.8 139.2,-84.5 196.3,-134.7 l 105,-91.3 H 9527 c 237.4,0 244.2,2.3 310.4,66.2 86.7,89 84.5,141.5 -13.7,228.2 -73,63.9 -95.9,70.8 -226,70.8 H 9454 l -182.6,189.4 c -399.4,410.8 -582,634.5 -732.6,899.3 -237.4,413.1 -228.2,365.2 -237.4,1116.1 l -6.8,661.9 77.6,152.9 c 84.4,166.6 225.9,312.7 397.1,413.1 86.7,52.5 148.3,66.2 335.5,82.2 125.5,11.4 237.4,25.1 248.8,31.9 9.1,6.9 66.2,95.9 125.5,198.6 77.6,130.1 105,200.8 95.8,237.4 -6.8,29.7 -79.9,134.6 -159.8,235.1 -82.2,100.4 -155.2,210 -164.3,241.9 -20.6,84.5 -210,248.8 -362.9,312.7 l -127.8,54.8 6.8,228.2 c 6.8,223.7 4.6,228.2 -43.4,223.7 -31.9,-4.6 -52.5,9.1 -57.1,34.2 -6.8,29.7 -77.6,-27.4 -271.6,-216.8 -230.5,-226 -283,-267 -477,-356 -166.6,-79.9 -264.7,-146.1 -415.4,-283 -109.4,-98.4 -271.5,-217.1 -360.5,-265 -148.3,-82.2 -173.4,-89 -344.6,-86.7 -100.4,0 -303.6,20.5 -449.6,45.6 -146.1,25.1 -369.7,57.1 -495.3,70.8 -125.5,13.7 -511.2,68.5 -855.9,125.5 -951.7,150.6 -1024.8,159.8 -1517.8,157.5 -326.4,0 -575.2,-16 -876.4,-52.5 l -420,-50.2 -123.3,63.9 c -152.9,77.6 -415.4,315 -493,447.3 -130.1,221.4 -312.7,887.8 -312.7,1136.6 2.3,248.8 118.7,620.8 235.1,746.3 107.3,116.4 374.3,198.6 529.5,164.3 82.2,-18.3 178,-79.9 248.8,-157.5 66.2,-73 164.3,-63.9 207.7,20.6 43.4,84.5 43.4,235.1 2.3,317.2 -43.4,84.5 -216.8,239.6 -330.9,296.7 -109.5,57.1 -358.3,64 -538.6,13.8 z"
+       
+       commands_cat = parse_path(path_cat)
+       points_cat = commands_to_points(commands_cat, false) .* 1/100 # Here we down scale the cat to have more readable coordinates.
+       
+       plot(points_cat .|> Tuple, aspect_ratio = true)
+end    
+
+# ╔═╡ 71520a10-e785-11ea-3aea-19575bbadbfc
+function to_complexes(points, n :: Integer) :: Vector{Complex{Float64}}
+       f(t) = let
+               pos = position_on_points(points, t)
+               complex(pos[1], pos[2])
+       end
+       [f(t) for t ∈ LinRange(0, 1-1/n, n)]
+end
+
+# ╔═╡ 3d763860-e771-11ea-0edc-094a80f44f74
+function plot_scatter(points, nb_points = 100)
+       scatter(to_complexes(points, nb_points), aspect_ratio = true)
+end
+
+# ╔═╡ 762ceed0-e82d-11ea-34ec-e92e32a51815
+md"Same drawings sampled and transformed in complex numbers:"
+
+# ╔═╡ 88f3eb30-e79d-11ea-0df4-cd4ab90ae8cb
+plot_scatter(points_box, 100)
+
+# ╔═╡ 724535f2-e780-11ea-1a67-69aceba56c81
+plot_scatter(points_letter_g, 100)
+
+# ╔═╡ 45da7c50-e7a8-11ea-2e48-35182730df7a
+plot_scatter(points_cat, 200)
+
+# ╔═╡ 0b84ca5e-e785-11ea-3c6d-f75c72b7b825
+# Also known as 'Fourier descriptor'.
+function nth_coefficient(complex_points, nth) :: Complex{Float64}              
+       n = length(complex_points)
+       (complex_points .* (exp(-nth * 2π * im * t) for t ∈ LinRange(0, 1-1/n, n)) |> sum) / n
+end
+
+# ╔═╡ 869d9bd0-eb92-11ea-2e0e-05306004cd45
+function fft(complex_points, nb_coef_positive)
+        [nth_coefficient(complex_points, k) for k ∈ -nb_coef_positive:nb_coef_positive]
+end
+
+# ╔═╡ c1e999ee-e7aa-11ea-2a8a-83ae8f10dffc
+begin
+       drawings = 
+               Dict(
+                       "box" => points_box,
+                       "g_letter" => points_letter_g,
+                       "cat" => points_cat
+               )
+
+       @bind drawing Select(["box" => "Box", "g_letter" => "G letter", "cat" => "Cat"])
+end
+
+# ╔═╡ 27aaf490-e770-11ea-3579-61a1d791051d
+@bind nb_coef Slider(3:2:101; default = 25)
+
+# ╔═╡ 866c0140-e770-11ea-0e97-eb2d15bf29d6
+md"Number of coefficients: **$nb_coef**"
+
+# ╔═╡ 2593e000-eb84-11ea-3019-2775c11bb903
+begin
+       N = 500
+       md"Number of complex numbers: **$N**"
+end
+
+# ╔═╡ a9b906e0-eb83-11ea-21eb-f763fbf6ec1d
+complexes = to_complexes(drawings[drawing], N)
+
+# ╔═╡ 47fe4880-e76d-11ea-307e-69f5f9f4cb3e
+let    
+       nb_coef_positive = nb_coef ÷ 2
+               
+       coefficients = fft(complexes, nb_coef_positive)
+                                        
+       coefficient(freq) = let
+               i = freq + nb_coef_positive + 1
+               if i >= 1 && i <= nb_coef
+                       coefficients[i]
+               else
+                       0im
+               end
+       end
+       
+       t = LinRange(0, 1, N)
+       f(t) = (coefficient(freq) * exp(freq * 2π * im * t) for freq in -nb_coef_positive:nb_coef_positive) |> sum
+       
+       plot(f.(t), aspect_ratio = true)
+end
+
+# ╔═╡ 111a7f50-eb7d-11ea-3a2e-8328258daddf
+ md"## Matching scores"
+
+# ╔═╡ b7175880-ec2f-11ea-3275-9b2c99a431e9
+begin
+       limits_real = real(complexes) |> extrema
+       limits_imag = imag(complexes) |> extrema
+       
+       real_size = abs(limits_real[2] - limits_real[1])
+       imag_size = abs(limits_imag[2] - limits_imag[1])
+       
+       md"""**real limits**: $limits_real
+       
+       **imaginary limits**: $limits_imag"""
+end
+
+# ╔═╡ b8db0c70-3f7c-11eb-2441-9b0bf3b848fe
+@bind reset Button("Reset to default values")
+
+# ╔═╡ 42062500-eb88-11ea-3443-c32b192a16a5
+begin
+       reset
+       md"θ: $(@bind θ Slider(range(0, 2π; length = 100); default = 0, show_value = true))"
+end
+
+# ╔═╡ b0e10d00-eb88-11ea-379e-e1f03cf9d8c0
+begin
+       reset
+       md"Δx: $(@bind Δx Slider(range(-real_size, real_size; length = 100); default = 0, show_value = true))"
+end
+
+# ╔═╡ c4ca66e0-eb88-11ea-1b3b-279a2ce5cabb
+begin
+       reset
+       md"Δy: $(@bind Δy Slider(range(-imag_size, imag_size; length = 100); default = 0, show_value = true))"
+end
+
+# ╔═╡ ca636560-eb89-11ea-0906-d1892d1d514c
+begin
+       reset
+       md"Scale: $(@bind α Slider(range(5^-1, 5; length = 25); default = 1, show_value = true))"
+end
+
+# ╔═╡ 664a3a20-eb90-11ea-099a-2fd33e5daf89
+begin
+       reset
+       md"noise: $(@bind noise Slider(range(0, .1; length = 100); default = 0, show_value = true))"
+end
+
+# ╔═╡ cfb46240-92ea-11eb-06ee-5d71c9bfc4f5
+let
+       c = complexes[1:100]
+       nth_coefficient(c, 1), nth_coefficient(reverse(c), 1)
+end
+
+# ╔═╡ 96980942-eb87-11ea-25e9-f710357f69c3
+function rotate(points, θ)
+       points * exp(θ * im)
+end
+
+# ╔═╡ 1941bf40-eb7d-11ea-0624-ffe3a941cfd2
+function translate(points, Δxy)
+       points .+ Δxy
+end
+
+# ╔═╡ 9eaf1c90-eb87-11ea-16af-4d85ca6c35eb
+function scale(points, α)
+       points .* α
+end
+
+# ╔═╡ a7cb1cc0-eb87-11ea-2f53-9339f8433bcc
+function add_noise(points)     
+       noise_factor = mean([real_size, imag_size]) * noise
+       points + noise_factor * randn(Complex{Float64}, length(points))
+end
+
+# ╔═╡ d59fbf70-eb87-11ea-0183-8de6a0137ee0
+begin                  
+       using Random
+       Random.seed!(1234)
+       
+       noised = add_noise(complexes)
+       translated = translate(noised, complex(Δx, Δy))
+       rotated = rotate(translated, θ)
+       scaled = scale(rotated, α)
+       
+       scatter(scaled, aspect_ratio = true)
+end
+
+# ╔═╡ 76ccc310-9228-11eb-063f-432008968caf
+function relative_fft(complex_points, n)
+       coefficients = fft(complex_points, n ÷ 2)
+       
+       # Remove the frequency 0 to avoid to take in account the position.
+       coefficients[n ÷ 2 + 1] = 0
+       
+       coefficients
+end
+
+# ╔═╡ 0ff985fe-92e1-11eb-266d-e777af1ab6fd
+a = norm.(relative_fft(complexes, 7))
+
+# ╔═╡ 25cf94b2-92e1-11eb-2c86-6f980f7a5d29
+b = norm.(relative_fft(reverse(complexes), 7))
+
+# ╔═╡ db88b910-eb90-11ea-3b93-054a0e9d1a10
+function score(coefficients₁, coefficients₂)
+       nb_coef = length(coefficients₁)       
+       sum(abs.(norm.(coefficients₁) .- norm.(coefficients₂))) / (nb_coef - 1)
+end
+
+# ╔═╡ 0abdd950-eb95-11ea-16a6-d19923afaabb
+let                    
+       coefficients_ref = relative_fft(complexes, nb_coef)             
+       coefficients_measured = relative_fft(scaled, nb_coef)
+       
+       s = score(coefficients_ref, coefficients_measured)
+       if abs(s) < 0.00001
+               s = 0.0
+       end
+       md"Score = **$s**"
+end
+
+# ╔═╡ 02fac600-92e4-11eb-16e1-9f2a7322b8cb
+score(a, b)
+
+# ╔═╡ 07c48fe0-ec2c-11ea-33be-1f9d4a6a2721
+md"""
+## Finding a part of the image
+
+Input:
+* A pattern to find (points)
+* A curve: list of consecutive points
+
+Output:
+* A score for each given point
+"""
+
+# ╔═╡ 44923ef0-920c-11eb-3306-e51c06ba41df
+begin
+       profile_filename = "ProfileRail.csv"
+       profile_nominal_filename = "NominalHead.csv"
+       
+       md"""
+       ### Example with real data
+
+       * Rail profile: $profile_filename
+       * Nominal profile: $profile_nominal_filename"""
+end
+
+# ╔═╡ 5280ed20-921d-11eb-148b-d582dabbfb45
+begin
+       profile = CSV.File(profile_filename; header=["X", "Y"]) |> DataFrame
+       transform!(profile, :, :Y => (-) => :Y, :X => (-) => :X)
+       nominal = CSV.File(profile_nominal_filename; header=["X", "Y"]) |> DataFrame
+end
+
+# ╔═╡ 3381b020-9223-11eb-1198-434e98bcdf31
+@df nominal scatter(:X, :Y, aspect_ratio = true)
+
+# ╔═╡ 91f5dbb0-922b-11eb-05d2-e5799199c053
+begin
+       # Parameters.
+       search_box_enlarge_factor = 2
+       nb_of_coefficients = 61
+       md"""
+       #### Parameters
+       
+       *Search box enlarge factor* = $search_box_enlarge_factor
+       """
+end
+
+# ╔═╡ 3972e7e0-92c0-11eb-3df0-67b830fbc023
+rect((x1, y1), (x2, y2)) = Shape([(x1, y1), (x1, y2), (x2, y2), (x2, y1)])
+
+# ╔═╡ 55f9daa0-92f6-11eb-1c48-6587d126d413
+md"Current point: $(@bind current_point NumberField(0:200, default = 0))"
+
+# ╔═╡ c45c2dc0-9226-11eb-2ac1-252cb75d7c12
+begin
+       local x_min, x_max = extrema(nominal[!, :X])
+       local y_min, y_max = extrema(nominal[!, :Y])
+       local x_mean = mean(nominal[!, :X])
+       local y_mean = mean(nominal[!, :Y])
+
+       local nearest_point = 400
+       #=
+       nearest_d = typemax(Float64)
+       for (i, (x, y)) ∈ enumerate(eachrow(nominal))
+               d = norm([x - x_mean, y - y_mean])
+               if d < nearest_d
+                       nearest_point = i
+                       nearest_d = d
+               end
+       end
+       =#
+
+       @debug nominal[nearest_point, [:X, :Y]]
+
+       search_box_width = (x_max - x_min) * search_box_enlarge_factor 
+       search_box_height = (y_max - y_min) * search_box_enlarge_factor
+
+       # Shift from the center.
+       δx = x_min + (x_max - x_min) / 2 - nominal[nearest_point, :X]
+       δy = y_min + (y_max - y_min) / 2 - nominal[nearest_point, :Y]          
+
+       @debug "------"
+       
+       @debug "width = $search_box_width, height = $search_box_height"
+       
+       #fft_profile = select(profile, :, :Y => ((y) -> -y) => :Y, :X => ((x) -> -x) => :X)
+       fft_nominal = relative_fft([Complex(x, y) for (x, y) ∈ eachrow(nominal)], nb_of_coefficients)
+       
+       local best_point = 1
+       local best_score = typemax(Float64)     
+       local windows = Vector{Shape}()
+       
+       scores = Vector{Tuple{Int64, Float64}}()
+       
+       for (i, (x, y)) ∈ enumerate(eachrow(profile)) 
+               x_min = x + δx - search_box_width / 2
+               x_max = x + δx + search_box_width / 2
+               y_min = y + δy - search_box_height / 2
+               y_max = y + δy + search_box_height / 2         
+               
+               surrounding_points = [(x, y) for (x, y) ∈ eachrow(profile) if x_min ≤ x ≤ x_max && y_min ≤ y ≤ y_max]                         
+               
+               push!(windows, rect((x_min, y_min), (x_max, y_max)))
+               
+               if length(surrounding_points) ≥ nb_of_coefficients            
+                                               
+                       fft_profile = 
+                               relative_fft(
+                                       map(surrounding_points) do (x, y)
+                                               Complex(x, y)
+                                       end,
+                                       nb_of_coefficients
+                               )
+                       
+                       s = score(fft_nominal, fft_profile)
+                       
+                       push!(scores, (i, s))
+                       
+                       if s < best_score 
+                               best_score = s
+                               best_point = i                          
+                       end
+                       
+                       #@debug "score: $s"
+                       
+                       #=
+                       @debug "fft_profile: $fft_profile"
+                       @debug "surrounding_points: $surrounding_points"
+                       @debug "current point: ($x, $y)"
+                       @debug "x limits: ($x_min, $x_max)"
+                       @debug "y limits: ($y_min, $y_max)"                             
+                       =#
+                
+                       #break
+               end
+       end
+       
+       function highlight_point_with_window(i, color = :red)   
+               point_value = profile[i, [:X, :Y]]
+               plot!((point_value.X, point_value.Y), aspect_ratio = true, seriestype = :scatter, markershape = :hexagon, markersize = 6, seriescolor = color)
+               plot!(windows[i], fillcolor = :transparent, linecolor = color, linealpha = 0.5) 
+       end
+       
+       @df profile plot(:X, :Y, aspect_ratio = true, seriestype = :scatter, size = (1200, 800))
+               
+       highlight_point_with_window(best_point, :orange)        
+       highlight_point_with_window(current_point)
+       #highlight_point_with_window(537)
+       
+end
+
+# ╔═╡ 4e116eb0-92c5-11eb-1449-ad3ef2e3cbde
+let
+       scatter(scores, size = (1200, 800))
+end
+
+# ╔═╡ Cell order:
+# ╟─60408320-3f7b-11eb-1773-e706096dc457
+# ╠═799b8d42-e5ff-11ea-37b7-ede54edaf352
+# ╟─7f284800-e606-11ea-0e36-a5e6934dbcf1
+# ╟─404bafa0-e7aa-11ea-0158-55902780a54e
+# ╠═34294d10-e785-11ea-373a-8f17483f4468
+# ╟─c90fa10e-e7ab-11ea-1dd1-7b5d60129245
+# ╟─2ae11130-e6df-11ea-3a92-55d4d69ef9a8
+# ╟─077ff940-e7ac-11ea-3461-5f0c5a2b38b8
+# ╟─28287c70-e785-11ea-37e0-8b6c4f8d31ef
+# ╟─1cde9fc0-e785-11ea-0644-7d58bfd03a5e
+# ╟─140a7e00-e785-11ea-2599-6fa3e0e8191e
+# ╟─50395b50-e82d-11ea-2da6-3baecb6100de
+# ╠═a59ac5ce-e782-11ea-09ac-295f9e2292b4
+# ╠═62f97bb0-e762-11ea-0561-c75618e97061
+# ╟─ede054d0-e86e-11ea-1002-6f4e3d3c517c
+# ╟─5ddbafc0-e7a5-11ea-113a-232748354da7
+# ╠═71520a10-e785-11ea-3aea-19575bbadbfc
+# ╠═3d763860-e771-11ea-0edc-094a80f44f74
+# ╟─762ceed0-e82d-11ea-34ec-e92e32a51815
+# ╟─88f3eb30-e79d-11ea-0df4-cd4ab90ae8cb
+# ╟─724535f2-e780-11ea-1a67-69aceba56c81
+# ╟─45da7c50-e7a8-11ea-2e48-35182730df7a
+# ╠═0b84ca5e-e785-11ea-3c6d-f75c72b7b825
+# ╠═869d9bd0-eb92-11ea-2e0e-05306004cd45
+# ╠═c1e999ee-e7aa-11ea-2a8a-83ae8f10dffc
+# ╟─27aaf490-e770-11ea-3579-61a1d791051d
+# ╟─866c0140-e770-11ea-0e97-eb2d15bf29d6
+# ╟─2593e000-eb84-11ea-3019-2775c11bb903
+# ╠═a9b906e0-eb83-11ea-21eb-f763fbf6ec1d
+# ╠═47fe4880-e76d-11ea-307e-69f5f9f4cb3e
+# ╟─111a7f50-eb7d-11ea-3a2e-8328258daddf
+# ╟─b7175880-ec2f-11ea-3275-9b2c99a431e9
+# ╟─42062500-eb88-11ea-3443-c32b192a16a5
+# ╟─b0e10d00-eb88-11ea-379e-e1f03cf9d8c0
+# ╟─c4ca66e0-eb88-11ea-1b3b-279a2ce5cabb
+# ╟─ca636560-eb89-11ea-0906-d1892d1d514c
+# ╟─664a3a20-eb90-11ea-099a-2fd33e5daf89
+# ╠═b8db0c70-3f7c-11eb-2441-9b0bf3b848fe
+# ╟─d59fbf70-eb87-11ea-0183-8de6a0137ee0
+# ╠═0abdd950-eb95-11ea-16a6-d19923afaabb
+# ╠═0ff985fe-92e1-11eb-266d-e777af1ab6fd
+# ╠═25cf94b2-92e1-11eb-2c86-6f980f7a5d29
+# ╠═cfb46240-92ea-11eb-06ee-5d71c9bfc4f5
+# ╠═02fac600-92e4-11eb-16e1-9f2a7322b8cb
+# ╟─96980942-eb87-11ea-25e9-f710357f69c3
+# ╟─1941bf40-eb7d-11ea-0624-ffe3a941cfd2
+# ╟─9eaf1c90-eb87-11ea-16af-4d85ca6c35eb
+# ╟─a7cb1cc0-eb87-11ea-2f53-9339f8433bcc
+# ╠═76ccc310-9228-11eb-063f-432008968caf
+# ╠═db88b910-eb90-11ea-3b93-054a0e9d1a10
+# ╟─07c48fe0-ec2c-11ea-33be-1f9d4a6a2721
+# ╠═44923ef0-920c-11eb-3306-e51c06ba41df
+# ╠═5280ed20-921d-11eb-148b-d582dabbfb45
+# ╠═3381b020-9223-11eb-1198-434e98bcdf31
+# ╠═91f5dbb0-922b-11eb-05d2-e5799199c053
+# ╠═3972e7e0-92c0-11eb-3df0-67b830fbc023
+# ╟─55f9daa0-92f6-11eb-1c48-6587d126d413
+# ╠═c45c2dc0-9226-11eb-2ac1-252cb75d7c12
+# ╠═4e116eb0-92c5-11eb-1449-ad3ef2e3cbde