1
0
Fork 0
mirror of https://gitlab.rlp.net/mobitar/julia_course.git synced 2024-09-14 12:47:20 +00:00
Julia_Course/day-4/main.jl
2024-05-15 02:54:36 +02:00

851 lines
27 KiB
Julia

### A Pluto.jl notebook ###
# v0.19.42
using Markdown
using InteractiveUtils
# ╔═╡ 7a9ccfbc-bd2e-41d0-be5d-dea04b90d397
using BenchmarkTools
# ╔═╡ 8b1dfee2-bd8b-4d23-b9f8-9406002e0eaa
# Builtin package with useful functions for dealing with randomness
# Imported for the function `shuffle`
using Random
# ╔═╡ 1fb7d9af-333e-44f2-b693-09ff97937d4c
# Oh, no, you found my secret! 😱
# Don't change this hidden cell!
begin
using PlutoUI
TableOfContents()
end
# ╔═╡ 899a86c4-89e5-4779-8191-2b38ead6d567
md"""
# Workflow
"""
# ╔═╡ 81b6211e-b065-11ec-0a86-375721c85b07
md"""
## Jupyter notebooks
Although Pluto notebooks are very interactive and revolutionary, there are some cases where they provide you with too much interactivity. This is especially a problem when you are mutating a variable in a different cell. You can always use `begin` blocks, but sometimes, this is not practical.
Jupyter notebooks don't rerun every cell depending on a variable that was just updated. Cells are only run manually.
Jupyter supports Julia! Indeed, Jupyter is named after **Ju**lia + **Pyt**hon (+ **e**) + **R**.
To use Jupyter notebooks with Julia, you just have to install the `IJulia` package globally (not in an environment).
To install the package globally, open a terminal and launch Julia:
julia
In Julia, press `]` to switch to the package manager prompt (see `README.adoc`).
You should NOT activate an environment!
Now, run the following:
add IJulia
After the installation is done, you can verify that the package is installed by running the following (still in the package manager prompt):
status
Then you should see `IJulia` listed.
Now, you can exit Julia. You have now the Julia kernel for Jupyter.
If you don't already have JupyterLab (recommended) or Jupyter installed, install JupyterLab.
Launch JupyterLab or Jupyter. You should see the Julia kernel listed now! 🎉
"""
# ╔═╡ 7f45c502-0909-42df-b93d-384f743df6a9
md"""
## VS Code/Codium
If you are working on a big project, then splitting code up into different files does help maintaining it. Therefore, notebooks should not be used for big projects.
Because you should avoid using global variables in Julia and instead only call functions, you will not achieve the maximum performance using a notebook because of its workflow that does not support working only with functions. See the section about performance in this notebook.
Julia has a very good integration in VS Code. VS Codium is the free open source version of VS Code without proprietary code and telemetry. Both will do the job. Just start one of them after installation, go the extensions tab on the sidebar, type in Julia and install the Julia extension.
Now, you can edit Julia files with autocomplition, real-time feedback, formatting (you might need to set up formatting on save) and other features that you can find about in [the documentation of the extension](https://www.julia-vscode.org/docs/stable/).
Code written in normal Julia files can be run using one of these three methods:
1. You can let the VS Code/Codium extension run a line, a code section or an entire file.
2. You can run it using the following command in the terminal:
julia --project FILENAME.jl
The `--project` argument tells Julia to use the environment in the current directory. See section about environments in this notebook.
3. You can launch Julia, activate the project environment and run the functions defined in the Julia file after importing using the following in Julia:
include("FILENAME.jl")
I you are using the last method, consider using [Revise](https://timholy.github.io/Revise.jl/stable/)! It does reinclude functions of an included file if they where updated in this file. This way, you don't have to reinclude the file after every change or restart your Julia session (remember the startup time because of compilation on running code for the first time).
"""
# ╔═╡ f23ad33d-af1d-40c2-9efc-17ef8c4d1fb8
md"""
## Environments
If you are working on a project and not using Pluto notebooks, you should be using environments.
Environments separate project dependencies (packages). Therefore, you are less likely to have any conflicts between two packages. They also allow you to use a different version of a package for every project.
Each Pluto notebook has its own environment. After running
using PACKAGENAME
in a Pluto notebook, Pluto takes care of downloading and adding the package to the environment of the notebook. Therefore, if you are not working with Pluto notebooks, you should manually create and use a project environment and you have to add Packages manually to your environment.
To enter a project environment, you create a project directory and `cd` into it in the terminal. Then, you launch julia
julia --project
The argument `--project` activates a project environment in your current directory (project directory). To add packages to your project environment, press `]` to activate the package manager prompt and then run the following:
add PACKAGENAME
where you replace `PACKAGENAME` with the name of the package that you want to use.
In the package manager prompt, run the following to see a list of the packages added to your environment:
status
In the package manager prompt, run the following to update (or install if not already installed) all the packages in your environment:
update
In the package manager prompt, run the following to remove a package from your environment:
remove PACKAGENAME
"""
# ╔═╡ 6340aec8-6f77-4a30-8815-ce76ddecd6e8
md"""
## REPL
The Julia REPL is what you get when you launch Julia in the terminal.
The REPL is very useful if you want to quickly experiment with something or test if a function works how you do imagine.
It is also helpful if you want to look up documentation. To do so, press `?` in the normal prompt. This will cause the prompt to change from the normal prompt `julia>` to the help prompt `help?>`. Now you can enter what you are looking for and you will get its documentation.
The REPL does support tab autocompletion. Just press tab one time to autocomplete. Press tab two times to see what Julia would suggest to you. This also works in the package manager prompt.
It REPL also supports history. If you press arrow up or down, you can navigate the execution history. You can also press `CTRL+R` to see the a list of all last commands. You can search in this history and then press enter.
If you want syntax highlighting in your REPL, add the package `OhMyREPL` to your **global** environment and let this package be imported always on startup as described in [the documentation](https://kristofferc.github.io/OhMyREPL.jl/latest/installation/).
"""
# ╔═╡ 0e53e4ee-16a7-47ef-9992-77cbfd1ed258
md"""
# Benchmarking
Benchmarking is a tool to evaluate the performance of code.
Although you can just measure the time a function call takes, it is not a good idea to use only one measurement. Therefore, the package `BenchmarkTools.jl` runs a function multiple times and evaluates it statistically.
"""
# ╔═╡ d86e4a2f-e737-49a4-bc16-a149e81785bd
# Calculating factorials
function normal_for_loop(N)
factorials = zeros(BigInt, N)
for i in 1:N
factorials[i] = factorial(big(i))
end
return
end
# ╔═╡ bc777c73-9573-41c3-8ab5-843335539f96
N = 5000
# ╔═╡ 55d1d616-b6ee-40dd-a0ed-6274a98b1e73
# This macro shows a lot of information
@benchmark normal_for_loop(N)
# ╔═╡ 00c27927-9c72-417a-862f-9b66318d9751
# This macro only shows the most important values
@btime normal_for_loop(N)
# ╔═╡ d21771de-272e-4d57-8c76-c75be709ad0a
md"""
# Multithreading
Julia is built as a modern language with parallel and asynchronous programming in mind.
You can use run Julia code on the GPU, on multiple CPU threads (multithreading) and with multiple processes (multiprocessing).
In this course, only multithreading will be presented.
The easiest way to use multithreading in Julia is by letting a `for` loop run on multiple threads. This can be achieved by only adding the macro `@threads` before the `for` loop.
Before trying to use multithreading, make sure that you launch Julia with multiple threads. To do so, run the following to launch Julia:
julia -t auto
You can append `--project` and other arguments to it.
`-t` is an alias to `--threads`. `auto` automatically determines how many threads your PC supports. You can also replace `auto` by the number of threads that you want to use. This number can not be bigger than the number of threads that your computer supports.
"""
# ╔═╡ 2cf5f891-8d8f-477d-8ef1-9b4b9480d477
# Show available number of threads
# If this is 1, then you can not use multithreading!
# If your PC has only one core and one thread, then you can do nothing about it.
# Otherwise, you have to tell Julia how many threads to use with the `-t` argument.
Threads.nthreads()
# ╔═╡ 76866972-0290-4e33-93de-b6128ba11994
md"""
## `@threads`
"""
# ╔═╡ cacc94b4-21e0-410e-acaa-80e37b447f94
# Using multiple threads for calculating factorials
function multithreaded_for_loop(N)
factorials = zeros(BigInt, N)
Threads.@threads for i in 1:N
factorials[i] = factorial(big(i))
end
return
end
# ╔═╡ a3029480-bcdc-44f3-b504-8bd3bf3aa14d
# Magic 🪄
@btime multithreaded_for_loop(N)
# ╔═╡ 3fcdf8ff-224c-4616-acd6-d8062f3a7af0
# Demonstration of shuffling
shuffle(1:10)
# ╔═╡ f141dbb4-bdc5-4f16-8d97-fc0a3d5981f2
# Shuffle to change the order of calculating factorials
function shuffle_multithreaded_for_loop(N)
factorials = zeros(BigInt, N)
Threads.@threads for i in shuffle(1:N)
factorials[i] = factorial(big(i))
end
return
end
# ╔═╡ e9dcda88-1eef-4c0a-99b2-12eaec56186b
# It did boost the performance even further 🤯
@btime shuffle_multithreaded_for_loop(N)
# ╔═╡ 768203a6-345d-4fa8-89a3-91e227579a38
md"""
The macro `@threads` splits the elements to iterate over evenly and gives every available thread a portion of these elements.
Example:
You have 2 threads and a loop over 1:10.
Then, one thread will get the values 1:5.
The other thread will get the values 6:10.
---
For our function above, calculating the factorial of the first elements does not take that much time as calculating the factorial of the last elements.
Therefore, the threads taking the first elements finish their job and wait for the thread that did get the portion with the biggest numbers.
With `shuffle`, we spread the hard job with big numbers over all threads. Therefore, the work is more evenly distributed and threads will finish (almost) together.
If you don't want the static work allocation of the macro `@threads` and do want to have more control about threads and what they do and when (dynamic thread allocation), take a look at the macro `Threads.@spawn`.
"""
# ╔═╡ 009bb2e8-f03e-40f7-a66b-166dc6a1962d
md"""
## Thread safety
Using threads is easy in Julia, but you have to pay attention when using threads!
If multiple threads try to modify a variable or an element in a container at the same time, weird dangerous things happen! 😵‍💫
"""
# ╔═╡ dd5e5073-be29-47e7-91c5-9e47c35f905c
function thread_unsafe()
N = 100000
var = 0
Threads.@threads for i in 1:N
var += 1
end
return var
end
# ╔═╡ 4554cbf0-36f5-45c6-a966-ad18b1592a60
# Run this cell multiple times.
# The output is random 🤯
thread_unsafe()
# ╔═╡ 7520326f-5576-42c7-aefd-29bc7d2c6b56
md"""
Lets see how to avoid such situations using another example.
"""
# ╔═╡ ec08a80c-8886-4312-9481-5c89951681e1
# Calculating the sum of sums
function thread_unsafe_sum(N)
sum_of_sums = 0
Threads.@threads for i in 1:N
sum_of_sums += sum(1:i)
end
return sum_of_sums
end
# ╔═╡ 3b5d9f7c-1fc9-4e85-8c07-8b5709895a10
N2 = 1000000
# ╔═╡ fbd2423b-aaea-47a7-a3cf-537860e11a93
# Also random!
thread_unsafe_sum(N2)
# ╔═╡ 73c86a45-d7f7-4d65-a588-1f5ff3adcf6f
md"""
The problem can be solved by using a vector with `N` elements. After calculating a sum, the thread that did calculate it places the result on a unique place in the vector that is reserved for this specific result.
At the end, the sum of sums is calculated by summing over the vector of sums.
"""
# ╔═╡ e65ad214-33ba-4d08-81f0-5f98022a9f78
function thread_safe_sum(N)
sums = zeros(Int64, N)
Threads.@threads for i in 1:N
sums[i] = sum(1:i)
end
return sum(sums)
end
# ╔═╡ 8ad3daa6-d221-4ff7-9bc2-8e8a66bdd8c7
# Stable!
sum_of_sums = @btime thread_safe_sum(N2)
# ╔═╡ 95dffc7f-3393-487e-8521-c96291cdc7bf
# Verify that we did not exceed the limit!
typemax(Int64) > sum_of_sums
# ╔═╡ ebd3a9d9-7a12-4001-9b53-913f664fb1c8
# Lets try shuffling again
function shuffle_safe_thread_sum(N)
sums = zeros(Int64, N)
Threads.@threads for i in shuffle(1:N)
sums[i] = sum(1:i)
end
return sum(sums)
end
# ╔═╡ ddd2409e-de34-4eb9-a0b7-e10cc6c0ce9f
# This is worse than the version without shuffling.
# In this case, shuffling is too expensive compared with its benefit
# Especially for multithreading, there is no silver bullet.
# Always benchmark! This is the only method to make sure that an "optimization" indeed an optimization is
@btime shuffle_safe_thread_sum(N2)
# ╔═╡ 24ad64f9-b0a4-48ac-b6dc-06a5d1c7b072
# Function to determine the shuffling cost
function shuffling_cost(N)
shuffle(1:N)
return
end
# ╔═╡ fe0b18c0-cbf0-421d-b6a0-987321a0b09d
# The shuffling itself is too expensive compared to the calculation in the loop
@btime shuffling_cost(N2)
# ╔═╡ 09f71a9e-6798-492f-98df-45087d0c4c8b
md"""
# Performance optimization
Julia has a focus on high performance. But if you don't pay attention, you might produce code that either can not be optimized by the compiler or results in a lot of allocations. In both cases, your code will be slow.
In this section, some tools and tips for performance optimization in Julia are presented.
"""
# ╔═╡ 491e077c-fbf0-4ae7-b54b-9f9c68f8f1b0
md"""
## Tips
Most important performance tips:
- Don't use global variables! 🌐
- Don't use containers with abstract type! 🆎
- Don't write long functions! 🦒
- Don't change the type of a variable! 🥸
- Preallocate when possible! 🕰️
- Reuse containers when possible! 🔄
- Use views instead of copies when possible! 🧐
- Benchmark, benchmark, benchmark! Also pay attention to allocations. ⌚️
These are some of the tips in the official documentation of Julia.
If you are writing performance critical code in Julia, make sure you have a **very good relationship** with [all performance tips in the documentation](https://docs.julialang.org/en/v1/manual/performance-tips/).
"""
# ╔═╡ 32981b03-edb9-417f-b5e0-c652e3ac715c
md"""
## Demo
"""
# ╔═╡ 6509dddd-ff17-49db-8e5e-fcea1ef0026c
N3 = 500000
# ╔═╡ 2a24aebc-0654-4d00-bdab-627a8e1a75f2
# Bad usage of a global array of type Any (container with abstract type)
begin
sin_vals = []
function global_allocating_access(N)
# First, empty the array because @btime runs the function multiple times
empty!(sin_vals)
for i in 1:N
push!(sin_vals, sin(i))
end
return
end
@btime global_allocating_access(N3)
end
# ╔═╡ 56058ab1-4ea2-479d-88f9-5da6ac8c39c2
# Not using an array of type Any
begin
typed_sin_vals = Float64[]
function typed_global_allocating_access(N)
# First, empty the array because @btime runs the function multiple times
empty!(typed_sin_vals)
for i in 1:N
push!(typed_sin_vals, sin(i))
end
return
end
@btime typed_global_allocating_access(N3)
end
# ╔═╡ ef164e7c-668a-4312-83f1-687ca7d4c8f9
# Preallocation
begin
preallocated_sin_vals = zeros(Float64, N3)
function global_preallocated_access(N)
for i in 1:N
preallocated_sin_vals[i] = sin(i)
end
return
end
@btime global_preallocated_access(N3)
end
# ╔═╡ ebc621b5-3aa3-4cf7-bcdf-e4c5fbb79f50
# The difference of not using global variables
# Never use global variables!
begin
passed_preallocated_sin_vals = zeros(Float64, N3)
function local_preallocated_access(N, sin_vals)
for i in 1:N
sin_vals[i] = sin(i)
end
return
end
@btime local_preallocated_access(N3, passed_preallocated_sin_vals)
end
# ╔═╡ afcc15de-81e0-484f-80cf-3d805517c6e8
# Here, the difference of preallocation is clearer
begin
passed_sin_vals = Float64[]
function local_access(N, sin_vals)
# First, empty the array because @btime runs the function multiple times
empty!(passed_sin_vals)
for i in 1:N
push!(sin_vals, sin(i))
end
return
end
@btime local_access(N3, passed_sin_vals)
end
# ╔═╡ 43d2cbda-a21b-46ae-8433-7a9ef30c536b
md"""
## `StaticArrays`
If you are dealing with small arrays with less than 100 elements, then take a look at the package [`StaticArrays.jl`](https://github.com/JuliaArrays/StaticArrays.jl). Especially if you are dealing with 2D or 3D coordinates, using `StaticArrays` will make a big performance difference.
"""
# ╔═╡ f0b634a5-19a9-4c61-932f-7ae357e13be2
md"""
## Profiling
Of course, you can profile your code in Julia. Check out the package [ProfileView](https://github.com/timholy/ProfileView.jl) for example.
"""
# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
[compat]
BenchmarkTools = "~1.3.2"
PlutoUI = "~0.7.59"
"""
# ╔═╡ 00000000-0000-0000-0000-000000000002
PLUTO_MANIFEST_TOML_CONTENTS = """
# This file is machine-generated - editing it directly is not advised
julia_version = "1.10.3"
manifest_format = "2.0"
project_hash = "b790568b1825bfdc3aa4caa97bcfe041482b08cd"
[[deps.AbstractPlutoDingetjes]]
deps = ["Pkg"]
git-tree-sha1 = "6e1d2a35f2f90a4bc7c2ed98079b2ba09c35b83a"
uuid = "6e696c72-6542-2067-7265-42206c756150"
version = "1.3.2"
[[deps.ArgTools]]
uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f"
version = "1.1.1"
[[deps.Artifacts]]
uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"
[[deps.Base64]]
uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
[[deps.BenchmarkTools]]
deps = ["JSON", "Logging", "Printf", "Profile", "Statistics", "UUIDs"]
git-tree-sha1 = "d9a9701b899b30332bbcb3e1679c41cce81fb0e8"
uuid = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
version = "1.3.2"
[[deps.ColorTypes]]
deps = ["FixedPointNumbers", "Random"]
git-tree-sha1 = "b10d0b65641d57b8b4d5e234446582de5047050d"
uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f"
version = "0.11.5"
[[deps.CompilerSupportLibraries_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae"
version = "1.1.1+0"
[[deps.Dates]]
deps = ["Printf"]
uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"
[[deps.Downloads]]
deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"]
uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6"
version = "1.6.0"
[[deps.FileWatching]]
uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee"
[[deps.FixedPointNumbers]]
deps = ["Statistics"]
git-tree-sha1 = "05882d6995ae5c12bb5f36dd2ed3f61c98cbb172"
uuid = "53c48c17-4a7d-5ca2-90c5-79b7896eea93"
version = "0.8.5"
[[deps.Hyperscript]]
deps = ["Test"]
git-tree-sha1 = "179267cfa5e712760cd43dcae385d7ea90cc25a4"
uuid = "47d2ed2b-36de-50cf-bf87-49c2cf4b8b91"
version = "0.0.5"
[[deps.HypertextLiteral]]
deps = ["Tricks"]
git-tree-sha1 = "7134810b1afce04bbc1045ca1985fbe81ce17653"
uuid = "ac1192a8-f4b3-4bfe-ba22-af5b92cd3ab2"
version = "0.9.5"
[[deps.IOCapture]]
deps = ["Logging", "Random"]
git-tree-sha1 = "8b72179abc660bfab5e28472e019392b97d0985c"
uuid = "b5f81e59-6552-4d32-b1f0-c071b021bf89"
version = "0.2.4"
[[deps.InteractiveUtils]]
deps = ["Markdown"]
uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
[[deps.JSON]]
deps = ["Dates", "Mmap", "Parsers", "Unicode"]
git-tree-sha1 = "31e996f0a15c7b280ba9f76636b3ff9e2ae58c9a"
uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
version = "0.21.4"
[[deps.LibCURL]]
deps = ["LibCURL_jll", "MozillaCACerts_jll"]
uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21"
version = "0.6.4"
[[deps.LibCURL_jll]]
deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"]
uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0"
version = "8.4.0+0"
[[deps.LibGit2]]
deps = ["Base64", "LibGit2_jll", "NetworkOptions", "Printf", "SHA"]
uuid = "76f85450-5226-5b5a-8eaa-529ad045b433"
[[deps.LibGit2_jll]]
deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll"]
uuid = "e37daf67-58a4-590a-8e99-b0245dd2ffc5"
version = "1.6.4+0"
[[deps.LibSSH2_jll]]
deps = ["Artifacts", "Libdl", "MbedTLS_jll"]
uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8"
version = "1.11.0+1"
[[deps.Libdl]]
uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"
[[deps.LinearAlgebra]]
deps = ["Libdl", "OpenBLAS_jll", "libblastrampoline_jll"]
uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
[[deps.Logging]]
uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"
[[deps.MIMEs]]
git-tree-sha1 = "65f28ad4b594aebe22157d6fac869786a255b7eb"
uuid = "6c6e2e6c-3030-632d-7369-2d6c69616d65"
version = "0.1.4"
[[deps.Markdown]]
deps = ["Base64"]
uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"
[[deps.MbedTLS_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1"
version = "2.28.2+1"
[[deps.Mmap]]
uuid = "a63ad114-7e13-5084-954f-fe012c677804"
[[deps.MozillaCACerts_jll]]
uuid = "14a3606d-f60d-562e-9121-12d972cd8159"
version = "2023.1.10"
[[deps.NetworkOptions]]
uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"
version = "1.2.0"
[[deps.OpenBLAS_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"]
uuid = "4536629a-c528-5b80-bd46-f80d51c5b363"
version = "0.3.23+4"
[[deps.Parsers]]
deps = ["Dates", "PrecompileTools", "UUIDs"]
git-tree-sha1 = "8489905bcdbcfac64d1daa51ca07c0d8f0283821"
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
version = "2.8.1"
[[deps.Pkg]]
deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"]
uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
version = "1.10.0"
[[deps.PlutoUI]]
deps = ["AbstractPlutoDingetjes", "Base64", "ColorTypes", "Dates", "FixedPointNumbers", "Hyperscript", "HypertextLiteral", "IOCapture", "InteractiveUtils", "JSON", "Logging", "MIMEs", "Markdown", "Random", "Reexport", "URIs", "UUIDs"]
git-tree-sha1 = "ab55ee1510ad2af0ff674dbcced5e94921f867a9"
uuid = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
version = "0.7.59"
[[deps.PrecompileTools]]
deps = ["Preferences"]
git-tree-sha1 = "5aa36f7049a63a1528fe8f7c3f2113413ffd4e1f"
uuid = "aea7be01-6a6a-4083-8856-8a6e6704d82a"
version = "1.2.1"
[[deps.Preferences]]
deps = ["TOML"]
git-tree-sha1 = "9306f6085165d270f7e3db02af26a400d580f5c6"
uuid = "21216c6a-2e73-6563-6e65-726566657250"
version = "1.4.3"
[[deps.Printf]]
deps = ["Unicode"]
uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"
[[deps.Profile]]
deps = ["Printf"]
uuid = "9abbd945-dff8-562f-b5e8-e1ebf5ef1b79"
[[deps.REPL]]
deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"]
uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
[[deps.Random]]
deps = ["SHA"]
uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
[[deps.Reexport]]
git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b"
uuid = "189a3867-3050-52da-a836-e630ba90ab69"
version = "1.2.2"
[[deps.SHA]]
uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"
version = "0.7.0"
[[deps.Serialization]]
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
[[deps.Sockets]]
uuid = "6462fe0b-24de-5631-8697-dd941f90decc"
[[deps.SparseArrays]]
deps = ["Libdl", "LinearAlgebra", "Random", "Serialization", "SuiteSparse_jll"]
uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
version = "1.10.0"
[[deps.Statistics]]
deps = ["LinearAlgebra", "SparseArrays"]
uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
version = "1.10.0"
[[deps.SuiteSparse_jll]]
deps = ["Artifacts", "Libdl", "libblastrampoline_jll"]
uuid = "bea87d4a-7f5b-5778-9afe-8cc45184846c"
version = "7.2.1+1"
[[deps.TOML]]
deps = ["Dates"]
uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76"
version = "1.0.3"
[[deps.Tar]]
deps = ["ArgTools", "SHA"]
uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e"
version = "1.10.0"
[[deps.Test]]
deps = ["InteractiveUtils", "Logging", "Random", "Serialization"]
uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
[[deps.Tricks]]
git-tree-sha1 = "eae1bb484cd63b36999ee58be2de6c178105112f"
uuid = "410a4b4d-49e4-4fbc-ab6d-cb71b17b3775"
version = "0.1.8"
[[deps.URIs]]
git-tree-sha1 = "67db6cc7b3821e19ebe75791a9dd19c9b1188f2b"
uuid = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4"
version = "1.5.1"
[[deps.UUIDs]]
deps = ["Random", "SHA"]
uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
[[deps.Unicode]]
uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"
[[deps.Zlib_jll]]
deps = ["Libdl"]
uuid = "83775a58-1f1d-513f-b197-d71354ab007a"
version = "1.2.13+1"
[[deps.libblastrampoline_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "8e850b90-86db-534c-a0d3-1478176c7d93"
version = "5.8.0+1"
[[deps.nghttp2_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d"
version = "1.52.0+1"
[[deps.p7zip_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0"
version = "17.4.0+2"
"""
# ╔═╡ Cell order:
# ╟─899a86c4-89e5-4779-8191-2b38ead6d567
# ╟─81b6211e-b065-11ec-0a86-375721c85b07
# ╟─7f45c502-0909-42df-b93d-384f743df6a9
# ╟─f23ad33d-af1d-40c2-9efc-17ef8c4d1fb8
# ╟─6340aec8-6f77-4a30-8815-ce76ddecd6e8
# ╟─0e53e4ee-16a7-47ef-9992-77cbfd1ed258
# ╠═7a9ccfbc-bd2e-41d0-be5d-dea04b90d397
# ╠═d86e4a2f-e737-49a4-bc16-a149e81785bd
# ╠═bc777c73-9573-41c3-8ab5-843335539f96
# ╠═55d1d616-b6ee-40dd-a0ed-6274a98b1e73
# ╠═00c27927-9c72-417a-862f-9b66318d9751
# ╟─d21771de-272e-4d57-8c76-c75be709ad0a
# ╠═2cf5f891-8d8f-477d-8ef1-9b4b9480d477
# ╟─76866972-0290-4e33-93de-b6128ba11994
# ╠═cacc94b4-21e0-410e-acaa-80e37b447f94
# ╠═a3029480-bcdc-44f3-b504-8bd3bf3aa14d
# ╠═8b1dfee2-bd8b-4d23-b9f8-9406002e0eaa
# ╠═3fcdf8ff-224c-4616-acd6-d8062f3a7af0
# ╠═f141dbb4-bdc5-4f16-8d97-fc0a3d5981f2
# ╠═e9dcda88-1eef-4c0a-99b2-12eaec56186b
# ╟─768203a6-345d-4fa8-89a3-91e227579a38
# ╟─009bb2e8-f03e-40f7-a66b-166dc6a1962d
# ╠═dd5e5073-be29-47e7-91c5-9e47c35f905c
# ╠═4554cbf0-36f5-45c6-a966-ad18b1592a60
# ╟─7520326f-5576-42c7-aefd-29bc7d2c6b56
# ╠═ec08a80c-8886-4312-9481-5c89951681e1
# ╠═3b5d9f7c-1fc9-4e85-8c07-8b5709895a10
# ╠═fbd2423b-aaea-47a7-a3cf-537860e11a93
# ╟─73c86a45-d7f7-4d65-a588-1f5ff3adcf6f
# ╠═e65ad214-33ba-4d08-81f0-5f98022a9f78
# ╠═8ad3daa6-d221-4ff7-9bc2-8e8a66bdd8c7
# ╠═95dffc7f-3393-487e-8521-c96291cdc7bf
# ╠═ebd3a9d9-7a12-4001-9b53-913f664fb1c8
# ╠═ddd2409e-de34-4eb9-a0b7-e10cc6c0ce9f
# ╠═24ad64f9-b0a4-48ac-b6dc-06a5d1c7b072
# ╠═fe0b18c0-cbf0-421d-b6a0-987321a0b09d
# ╟─09f71a9e-6798-492f-98df-45087d0c4c8b
# ╟─491e077c-fbf0-4ae7-b54b-9f9c68f8f1b0
# ╟─32981b03-edb9-417f-b5e0-c652e3ac715c
# ╠═6509dddd-ff17-49db-8e5e-fcea1ef0026c
# ╠═2a24aebc-0654-4d00-bdab-627a8e1a75f2
# ╠═56058ab1-4ea2-479d-88f9-5da6ac8c39c2
# ╠═ef164e7c-668a-4312-83f1-687ca7d4c8f9
# ╠═ebc621b5-3aa3-4cf7-bcdf-e4c5fbb79f50
# ╠═afcc15de-81e0-484f-80cf-3d805517c6e8
# ╟─43d2cbda-a21b-46ae-8433-7a9ef30c536b
# ╟─f0b634a5-19a9-4c61-932f-7ae357e13be2
# ╟─1fb7d9af-333e-44f2-b693-09ff97937d4c
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002