Skip to content

Instantly share code, notes, and snippets.

@dermesser
Last active May 23, 2021 11:53
Show Gist options
  • Save dermesser/efea9d85538739c8105a8249bf8bd09a to your computer and use it in GitHub Desktop.
Save dermesser/efea9d85538739c8105a8249bf8bd09a to your computer and use it in GitHub Desktop.
Calculate Kirchhoff-Fresnel diffraction integrals with visualization. Explanation: https://lewinb.net/posts/diffraction/ - example: http://borgac.net/~lbo/doc/kirchhoff.pdf
### A Pluto.jl notebook ###
# v0.14.3
using Markdown
using InteractiveUtils
# ╔═╡ 6d796395-5024-4129-a250-df2401327711
using Setfield
# ╔═╡ 87c80fda-9eda-11eb-2ac5-b7b5006b19b7
import CairoMakie as CM
# ╔═╡ 836ad30f-20cd-4f1b-9142-e47bc6e90aa0
import FileIO
# ╔═╡ 27295f24-fd52-4608-865b-8d77a02762f7
CM.activate!(type="png")
# ╔═╡ b6a6faa5-684d-4d77-82a1-ae5e1618b12f
md"""**Utility functions...**"""
# ╔═╡ af3b9d5e-385f-4b22-b140-468f094e1424
# Make this use cos() if your angles are not very small.
function fastcos(x)
1 - x^2/2
end
# ╔═╡ f218f7bf-b88b-4f30-91b3-51aa4e3eb373
function fastatan(x)
x-x^3/3
end
# ╔═╡ 6574c144-ef7e-467f-ba52-2b6beb7672fd
md"""# Kirchhoff-Fresnel diffraction integral"""
# ╔═╡ c81b24c9-9f7b-48a1-bb2e-28fd314ee0d7
md"""## Aperture functions
Shape function for apertures. You can test the different functions at the end of this section.
Shape functions are used in the Setup struct; kwargs can be given as a dictionary."""
# ╔═╡ fe40f98f-7344-497a-b965-436ea0380efd
function circular(x, y; radius=1000e-6)::Float16
(x^2+y^2) <= radius^2 ? 1 : 0
end
# ╔═╡ db008850-db81-4c85-a46d-04fea3f04837
function double_circle(x, y; radius=1000e-6, dist=1000e-6)::Float16
d = dist/2
rsq = radius^2
ysq = y^2
(((x-d)^2+ysq) <= rsq || ((x+d)^2+ysq) <= rsq) ? 1 : 0
end
# ╔═╡ 54feae63-2bce-4e22-b41e-461bf3056275
function quadratic(x, y; side=1000e-6)::Float16
(-side <= x && x <= side && -side <= y && y <= side) ? 1 : 0
end
# ╔═╡ ea0d82b8-b040-4aa3-a8e1-fad9eec948e2
function slit(x, y; width=800e-6, height=1000)
(-width/2 <= x && x <= width/2 && -height <= y && y <= height) ? 1 : 0
end
# ╔═╡ 12afa06a-4893-40ac-8692-1366ae8dcdda
function double_slit(x, y; width=800e-6, off=1000e-6, height=1000)
(-width/2-off <= x && x <= width/2-off && -height <= y && y <= height) || (-width/2+off <= x && x <= width/2+off && -height <= y && y <= height) ? 1 : 0
end
# ╔═╡ 126adaa2-a420-4d58-b121-0c97a9eca83a
function smallgrate(x, y; width=100e-6, off=200e-6, height=1000)
s = abs(rem(x, off+width))
((x < 0 && s <= width) || (x >= 0 && s >= off)) ? 1 : 0
end
# ╔═╡ b76a6f05-dd85-4c64-9ab7-fe17d7e11774
function cross(x, y; width=300e6)
(abs(x) <= width/2 || abs(y) <= width/2) ? 0 : 1
end
# ╔═╡ 5b7646f9-7935-4aa8-8d81-b236dfdc5c9a
function spikes(x, y; width=100e-6, radius=1000e-6)
circular(x, y, radius=radius) == 1 && cross(x, y, width=width) == 1
end
# ╔═╡ 1b974614-3748-43b2-bd28-9933aec8d860
#circular_fft = convert.(Float16, FileIO.load("circle.png"))
# ╔═╡ d2b812f6-cbd7-407e-a0e4-eea53a73dd76
function from_raster(shape, maxdim)
maxix = size(shape, 1)
off = convert(Int, trunc((maxix)/2))
return function(x,y)
i, j = convert(Int, trunc(maxix*x/maxdim)), convert(Int, trunc(maxix*y/maxdim))
shape[min(i+off+1, maxix), min(j+off+1, maxix)]
end
end
# ╔═╡ 40f819d3-4a86-4b2c-9b18-b9b4143e3a96
function grating2d(x, y; width=100e-6, off=200e-6)
s = abs(rem(x, off+width))
a = ((x < 0 && s <= width) || (x >= 0 && s >= off))
t = abs(rem(y, off+width))
b = ((y < 0 && t <= width) || (y >= 0 && t >= off))
a && b
end
# ╔═╡ 64bad78e-e34a-4562-9168-d7e27ecd3158
function invertshape(s)
if s <= 0.1
1
else
0
end
end
# ╔═╡ 06ffee0e-1189-4645-bab2-9e938db0620d
begin
# These constants are just used for testing the aperture functions.
const OUTER_DIM = .002
const SCREEN_DIM = 0.3
end
# ╔═╡ 5b11a45e-4430-4db7-8909-cd08c8bd142f
function sample_shape(f; dim=OUTER_DIM, res=100)::Tuple{LinRange{Float64}, Matrix{Float16}}
field = zeros(Float16, res, res)
xs = ys = LinRange(-dim/2, dim/2, res)
for (i, x) = enumerate(xs)
for (j, y) = enumerate(ys)
field[i,j] = f(x, y)
end
end
xs, field
end
# ╔═╡ 1488f2ee-1741-4676-9e14-93dd4e22fbb4
function show_shape(f; dim=OUTER_DIM, scan=OUTER_DIM/10)
xs, field = sample_shape(f, dim=dim, res=convert(Int, div(dim,scan)))
fig = CM.Figure(resolution=(440,400))
CM.Axis(fig[1,1])
CM.heatmap!(xs, xs, field)
fig
end
# ╔═╡ a23a6d00-4d4e-4868-86a7-fa8c638ee8ea
show_shape((x,y) -> grating2d(x, y), dim=OUTER_DIM, scan=OUTER_DIM/200)
# ╔═╡ a22f16ca-14a9-4a69-bf0f-c84fba39a836
md"""## Integral calculation
We integrate across the aperture, sampling both aperture and screen area.
Before doing that, we add some structs for configuring our calculations."""
# ╔═╡ 0765cbcc-f0f1-4dfd-94e2-14358e6e6b28
Base.@kwdef struct Source
x::Float64 = 0
y::Float64 = 0
z::Float64
end
# ╔═╡ 0839fb51-ba56-4f70-8e69-bbb41685c2b2
struct Setup
# (negative) position of source, left of aperture
source::Vector{Source}
# aperture shape function
aperture::Function
# aperture config
aperture_kwargs::Dict{Symbol, Float64}
# distance to screen
screen_pos::Float64
# wavelength
lambda::Float64
end
# ╔═╡ c0393e2b-28fd-4d9b-869d-ffeb0fdb8d24
# Distance of source, shape function, shape parameters, distance to screen, wavelength
default_setup = Setup([Source(z=-30, y=-.0)],
double_slit, Dict(),
30, 500e-9)
# ╔═╡ 65cd7931-4dcf-4644-94d7-149effce4cce
struct ScanParam
aperture_size::Float64
aperture::Float64
screen_size::Float64
screen::Float64
end
# ╔═╡ 1ccd1e55-fe40-4683-af90-8b15617d63b1
default_scan_param = ScanParam(
# Aperture scan size and scan step, in m, depends on aperture function
0.005, .005/100,
# Screen size and scan step in m.
.04, .04/50)
# ╔═╡ d2a3d02f-7eb3-42d6-9765-16e5bbc99d8b
md"""### Point source to screen
`calculate_integral()` assumes one or more point sources and a screen orthogonal to the (geometric) beam. It calculates the image on the screen, which is shown below."""
# ╔═╡ 1c3bfcfd-1332-49d4-9b98-ceecc8c2926a
function calculate_integral(setup::Setup; scan_param::ScanParam=default_scan_param)
screendim = convert(Int, div(scan_param.screen_size, scan_param.screen))
apdim = convert(Int, div(scan_param.aperture_size, scan_param.aperture))
screen = zeros(Complex{Float64}, screendim, screendim)
delta = scan_param.aperture_size / apdim
all_aperture_coords = ((x,y)
for x = LinRange(
-scan_param.aperture_size/2, scan_param.aperture_size/2, apdim)
, y = LinRange(
-scan_param.aperture_size/2, scan_param.aperture_size/2, apdim))
all_screen_coords = ((i, x, j, y)
for (i, x) = enumerate(LinRange(
-scan_param.screen_size/2, scan_param.screen_size/2, screendim))
, (j, y) = enumerate(LinRange(
-scan_param.screen_size/2, scan_param.screen_size/2, screendim)))
k = 2pi/setup.lambda
all_screen_coords = collect(all_screen_coords)
count = 0
@time begin for (apx, apy) = all_aperture_coords
weight = setup.aperture(apx, apy; setup.aperture_kwargs...)
if weight == 0
continue
end
count += length(all_screen_coords)
cdist = sqrt(apx^2+apy^2)
dists = ((atan(sum(((s.x, s.y) .- (apx, apy)).^2)/abs(s.z)),
sqrt(sum(((apx,apy,0) .- (s.x, s.y, s.z)).^2)))
for s = setup.source)
sourceterms = [(fastcos(alpha), exp(-im * k * R)/R) for (alpha, R) = dists]
Threads.@threads for (i, scx, j, scy) = all_screen_coords
pointdist = sqrt((apx-scx)^2 + (apy-scy)^2)
r = sqrt(pointdist^2 + setup.screen_pos^2)
beta = fastatan(pointdist / abs(setup.screen_pos))
fcb = fastcos(beta)
term = exp(-im*k*r)/r * sum(
((fcb+fca)/2 * t
for (fca, t) = sourceterms))
screen[i, j] += weight * term * delta^2/(im*setup.lambda)
end
end
end
println("calculate_integral: $count iterations")
abs.(screen).^2, LinRange(-scan_param.screen_size/2, scan_param.screen_size/2, screendim)
end
# ╔═╡ e0abeb58-afd8-42d9-b0fc-fb55cc383323
md"""**Check the actual aperture used:**"""
# ╔═╡ 6c0b1f38-f692-4b3e-ac08-c0f71734ed05
show_shape((x,y) -> default_setup.aperture(x, y; default_setup.aperture_kwargs...), dim=default_scan_param.aperture_size, scan=default_scan_param.aperture)
# ╔═╡ 92147968-94e2-41d1-ad15-857eb5f06f60
function plot_diffraction()
screen, coords = calculate_integral(default_setup, scan_param=default_scan_param);
fig = CM.Figure(resolution=(1300, 650))
CM.Axis(fig[1,1])
CM.heatmap!(coords, coords, (screen))
axh = CM.Axis(fig[1, 2])
axv = CM.Axis(fig[1,2], xaxisposition=:top, yaxisposition=:right)
mid = div(size(screen)[1], 2)
CM.lines!(axh, coords, view(screen, :, mid)/maximum(view(screen, :, mid)),
color=:green)
CM.lines!(axv, view(screen, mid, :)/maximum(view(screen, mid, :)), coords,
color=:red)
fig
end
# ╔═╡ 1f538236-9883-4c44-a3ab-5cf5a4b01ee1
md"""**Show diffraction image as a heatmap with a crosssection (by default: along the middle, in x-orientation)**"""
# ╔═╡ 1aada718-2e07-48fa-b794-708e2ea23d8b
plot_diffraction()
# ╔═╡ 1b0e2b99-a65d-441d-b038-159ccc48e6f0
md"""## Fraunhofer Integral"""
# ╔═╡ e134d03f-3771-4056-acd7-d4a8d74f2cf7
function calculate_fraunhofer_integral(setup::Setup; scan_param::ScanParam=default_scan_param)
screendim = convert(Int, div(scan_param.screen_size, scan_param.screen))
apdim = convert(Int, div(scan_param.aperture_size, scan_param.aperture))
screen = zeros(Complex{Float64}, screendim, screendim)
delta = scan_param.aperture_size / apdim
all_aperture_coords = ((x,y)
for x = LinRange(
-scan_param.aperture_size/2, scan_param.aperture_size/2, apdim)
, y = LinRange(
-scan_param.aperture_size/2, scan_param.aperture_size/2, apdim))
all_screen_coords = ((i, x, j, y)
for (i, x) = enumerate(LinRange(
-scan_param.screen_size/2, scan_param.screen_size/2, screendim))
, (j, y) = enumerate(LinRange(
-scan_param.screen_size/2, scan_param.screen_size/2, screendim)))
k = 2pi/setup.lambda
all_screen_coords = collect(all_screen_coords)
count = 0
@time begin for (apx, apy) = all_aperture_coords
if setup.aperture(apx, apy; setup.aperture_kwargs...) < 0.1
continue
end
count += length(all_screen_coords)
Threads.@threads for (i, scx, j, scy) = all_screen_coords
#l, m = (sin(fastatan((scx-apx)/setup.screen_pos)),
# sin(fastatan((scy-apy)/setup.screen_pos)))
l, m = scx/setup.screen_pos, scy/setup.screen_pos
term = exp(-im*k*(l*apx + m*apy))
screen[i, j] += term * delta^2
end
end
end
println("calculate_fraunhofer_integral: $count iterations")
abs.(screen).^2, LinRange(-scan_param.screen_size/2, scan_param.screen_size/2, screendim)
end
# ╔═╡ 04f4e258-749c-4ac4-8fad-f1c15370a1d7
function plot_fraunhofer_diffraction()
screen, coords = calculate_fraunhofer_integral(default_setup,
scan_param=default_scan_param);
fig = CM.Figure(resolution=(1300, 650))
CM.Axis(fig[1,1])
CM.heatmap!(coords, coords, (screen))
axh = CM.Axis(fig[1, 2])
axv = CM.Axis(fig[1,2], xaxisposition=:top, yaxisposition=:right)
mid = div(size(screen)[1], 2)
CM.lines!(axh, coords, view(screen, :, mid)/maximum(view(screen, :, mid)),
color=:green)
CM.lines!(axv, view(screen, mid, :)/maximum(view(screen, mid, :)), coords,
color=:red)
fig
end
# ╔═╡ 8502868f-c309-4391-9ef5-a83d5979ac77
plot_fraunhofer_diffraction()
# ╔═╡ c83fc930-87c3-4b99-b947-6d06ac516429
md"""### Diffraction pattern on plane
Here we calculate the intensities along a plane containing the (geometric) beam as it travels towards the screen.
The plane is configured in the `Plane` struct, which determines the rotation angle of the plane around the geometric beam axis, as well as the width and length of the plane.
Below we link these values with the aperture and screen settings used above, so that we always see the pattern on the way to the screen.
"""
# ╔═╡ 114b3dc2-39e3-4d97-b9f1-af60419bd0a8
struct Plane
# 0 degrees = x plane
angle::Float64
width::Float64
wscan::Float64
length::Float64
lscan::Float64
end
# ╔═╡ fdfc21ca-ea64-4726-9533-8694a27d40df
function calculate_plane_integral(setup::Setup, plane::Plane;
scan_param::ScanParam=default_scan_param)
screendim = (round(Int, div(plane.width, plane.wscan)),
round(Int, div(plane.length, plane.lscan)))
apdim = convert(Int, div(scan_param.aperture_size, scan_param.aperture))
screen = zeros(Complex{Float64}, screendim)
delta = scan_param.aperture_size / apdim
k = 2pi / setup.lambda
all_aperture_coords = ((x,y)
for x = LinRange(
-scan_param.aperture_size/2, scan_param.aperture_size/2, apdim)
, y = LinRange(
-scan_param.aperture_size/2, scan_param.aperture_size/2, apdim))
# For stepping, it is important to have a very slight angle at the least.
ang = plane.angle != 0 ? plane.angle : 0.1
plane_n, plane_l_n = screendim
cosine = cos(ang/180*pi)
sine = sin(ang/180*pi)
all_trans_coords = enumerate(zip(
LinRange(-cosine*plane.width/2, cosine*plane.width/2, plane_n),
LinRange(-sine*plane.width/2, sine*plane.width/2, plane_n)))
all_plane_coords = ((i, x, y, j, z)
for (i, (x, y)) = all_trans_coords
for (j, z) = enumerate(LinRange(0, plane.length, plane_l_n))
if i < plane_n && j < plane_l_n)
all_plane_coords = collect(all_plane_coords)
count = 0
@time begin for (apx, apy) = all_aperture_coords
if setup.aperture(apx, apy; setup.aperture_kwargs...) < 0.1
continue
end
count += length(all_plane_coords)
cdist = sqrt(apx^2+apy^2)
dists = [(fastatan(sum(((s.x, s.y) .- (apx, apy)).^2)/abs(s.z)),
sqrt(sum(((apx,apy,0) .- (s.x, s.y, s.z)).^2)))
for s = setup.source]
sourceterms = [(alpha, exp(-im * k * R)/R) for (alpha, R) = dists]
Threads.@threads for (i, x, y, j, z) = all_plane_coords
pointdist = sqrt((apx-x)^2 + (apy-y)^2)
r = sqrt(pointdist^2 + z^2)
beta = atan(pointdist / abs(setup.screen_pos))
term = exp(-im*k*r)/r * sum(
((fastcos(beta)+fastcos(alpha))/2 * t
for (alpha, t) = sourceterms))
# cos terms are only sometimes required
K = 1/(im*setup.lambda)
screen[i, j] += K * term * delta^2
end
end
end
println("calculate_plane_integral: $count iterations")
(abs.(screen).^2,
LinRange(-plane.width/2, plane.width/2, plane_n),
LinRange(0, plane.length, plane_l_n))
end
# ╔═╡ 79480cf1-834d-4458-80a6-c5bb246ec39d
begin
#plane = Plane(0, 0.025, 0.025/200, 20, 20/100)
plane = Plane(90, default_scan_param.screen_size, default_scan_param.screen/2,
default_setup.screen_pos, default_setup.screen_pos/100)
end
# ╔═╡ 1efe51d4-1edf-4772-b91a-ca04475e31c5
function plot_diffraction_on_plane()
screen, wcoords, lcoords = calculate_plane_integral(default_setup, plane);
fig = CM.Figure(resolution=(1300, 1300))
CM.Axis(fig[1,1])
CM.heatmap!(wcoords, lcoords, log.(screen))
CM.Axis(fig[1, 2])
mid = div(size(screen)[1], 2)
CM.lines!(-log.(screen[mid, :]), lcoords)
fig
end
# ╔═╡ 5308c06f-c14b-4d3c-8283-a833a008a532
plot_diffraction_on_plane()
# ╔═╡ a9d05389-9d58-4d7c-a3ed-3ea7a1c5ab38
md"""### Babinet's principle
[Babinet's Principle](https://en.wikipedia.org/wiki/Babinet%27s_principle)
Show diffraction for inverted aperture.
Consider that the used aperture is square and usually of very limited length: Major artifacts will stem from that fact.
"""
# ╔═╡ 2de1ca1a-8741-4364-ae91-4819e6447927
function plot_inverse_diffraction()
setup = default_setup
aperture = setup.aperture
setup = @set setup.aperture = (x, y; kwargs...) -> (invertshape(aperture(x, y; kwargs...)))
screen, coords = calculate_integral(setup);
fig = CM.Figure(resolution=(1300, 650))
CM.Axis(fig[1,1])
CM.heatmap!(coords, coords, (screen))
axh = CM.Axis(fig[1, 2])
axv = CM.Axis(fig[1,2], xaxisposition=:top, yaxisposition=:right)
mid = div(size(screen)[1], 2)
CM.lines!(axh, coords, view(screen, :, mid)/maximum(view(screen, :, mid)),
color=:green)
CM.lines!(axv, view(screen, mid, :)/maximum(view(screen, mid, :)), coords,
color=:red)
fig
end
# ╔═╡ b7e30300-1bd3-4eab-a0d6-93d4902e9b9d
plot_inverse_diffraction()
# ╔═╡ db083fe4-b5ba-4502-8258-2aceb84e6e42
md"""### Plane Wave diffraction
In the limit of far distance of the point source to the screen, we get plane waves.
By directly assuming plane waves, we can calculate faster. D different setup and scan configurations are used here, too. However, they are linked to the configuration above by default to allow a 1:1 comparison of point source images to plane wave images.
"""
# ╔═╡ 202e0940-2fd2-4f6e-983a-83d7025c91de
begin
# Distance of source, shape function, shape parameters,
# distance to screen, wavelength
plane_setup = Setup([Source(z=-80)], double_slit, Dict(), 20, 500e-9)
plane_setup = default_setup
end
# ╔═╡ 1ff2be95-ecc6-4a90-9205-f425535039d7
begin
plane_scan_param = ScanParam(
# Aperture scan size and scan step, in m, depends on aperture function
0.005, .005/100,
# Screen size and scan step in m.
.025, .025/100)
# Override: use identical plane as above
plane_scan_param = default_scan_param
end
# ╔═╡ 352c281e-11d7-4158-9528-e05a4717cf7d
function calculate_integral_planewave(setup::Setup;
scan_param::ScanParam=plane_scan_param)
screendim = convert(Int, div(scan_param.screen_size, scan_param.screen))
apdim = convert(Int, div(scan_param.aperture_size, scan_param.aperture))
screen = zeros(Complex{Float64}, screendim, screendim)
delta = scan_param.aperture_size / apdim
all_aperture_coords = ((x,y)
for x = LinRange(
-scan_param.aperture_size/2, scan_param.aperture_size/2, apdim)
for y = LinRange(
-scan_param.aperture_size/2, scan_param.aperture_size/2, apdim))
all_screen_coords = ((i, x, j, y)
for (i, x) = enumerate(LinRange(
-scan_param.screen_size/2, scan_param.screen_size/2, screendim))
for (j, y) = enumerate(LinRange(
-scan_param.screen_size/2, scan_param.screen_size/2, screendim)))
all_screen_coords = collect(all_screen_coords)
k = 2pi/setup.lambda
count = 0
@time begin for (apx, apy) = all_aperture_coords
if setup.aperture(apx, apy; setup.aperture_kwargs...) < 0.1
continue
end
count += length(all_screen_coords)
alpha = 0
Threads.@threads for (i, scx, j, scy) = all_screen_coords
pointdist = sqrt((apx-scx)^2 + (apy-scy)^2)
r = sqrt(pointdist^2 + setup.screen_pos^2)
beta = atan(pointdist / abs(setup.screen_pos))
term = exp(-im*(k*r))/(r)
K = 1/(im*setup.lambda) * (fastcos(alpha)+fastcos(beta))/2
screen[i, j] += K * term * delta^2
end
end
end
println("calculate_integral_planewave: $count iterations")
(abs.(screen).^2,
LinRange(-scan_param.screen_size/2, scan_param.screen_size/2, screendim))
end
# ╔═╡ 5488a918-315f-4b0d-bf0b-d26d873583fe
function plot_diffraction_planewave()
screen, coords = calculate_integral_planewave(plane_setup);
fig = CM.Figure(resolution=(1300, 650))
CM.Axis(fig[1,1])
CM.heatmap!(coords, coords, (screen))
axh = CM.Axis(fig[1, 2])
axv = CM.Axis(fig[1,2], xaxisposition=:top, yaxisposition=:right)
mid = div(size(screen)[1], 2)
CM.lines!(axh, coords, view(screen, :, mid)/maximum(view(screen, :, mid)),
color=:green)
CM.lines!(axv, view(screen, mid, :)/maximum(view(screen, :, mid)), coords,
color=:red)
fig
end
# ╔═╡ b3cd8248-2e5c-4730-ba9c-fe99d2c79e78
plot_diffraction_planewave()
# ╔═╡ Cell order:
# ╠═87c80fda-9eda-11eb-2ac5-b7b5006b19b7
# ╠═836ad30f-20cd-4f1b-9142-e47bc6e90aa0
# ╠═27295f24-fd52-4608-865b-8d77a02762f7
# ╠═b6a6faa5-684d-4d77-82a1-ae5e1618b12f
# ╠═af3b9d5e-385f-4b22-b140-468f094e1424
# ╠═f218f7bf-b88b-4f30-91b3-51aa4e3eb373
# ╠═6574c144-ef7e-467f-ba52-2b6beb7672fd
# ╠═c81b24c9-9f7b-48a1-bb2e-28fd314ee0d7
# ╠═fe40f98f-7344-497a-b965-436ea0380efd
# ╠═db008850-db81-4c85-a46d-04fea3f04837
# ╠═54feae63-2bce-4e22-b41e-461bf3056275
# ╠═ea0d82b8-b040-4aa3-a8e1-fad9eec948e2
# ╠═12afa06a-4893-40ac-8692-1366ae8dcdda
# ╠═126adaa2-a420-4d58-b121-0c97a9eca83a
# ╠═b76a6f05-dd85-4c64-9ab7-fe17d7e11774
# ╠═5b7646f9-7935-4aa8-8d81-b236dfdc5c9a
# ╠═1b974614-3748-43b2-bd28-9933aec8d860
# ╠═d2b812f6-cbd7-407e-a0e4-eea53a73dd76
# ╠═40f819d3-4a86-4b2c-9b18-b9b4143e3a96
# ╠═64bad78e-e34a-4562-9168-d7e27ecd3158
# ╠═5b11a45e-4430-4db7-8909-cd08c8bd142f
# ╠═1488f2ee-1741-4676-9e14-93dd4e22fbb4
# ╠═06ffee0e-1189-4645-bab2-9e938db0620d
# ╠═a23a6d00-4d4e-4868-86a7-fa8c638ee8ea
# ╠═a22f16ca-14a9-4a69-bf0f-c84fba39a836
# ╠═0765cbcc-f0f1-4dfd-94e2-14358e6e6b28
# ╠═0839fb51-ba56-4f70-8e69-bbb41685c2b2
# ╠═c0393e2b-28fd-4d9b-869d-ffeb0fdb8d24
# ╠═65cd7931-4dcf-4644-94d7-149effce4cce
# ╠═1ccd1e55-fe40-4683-af90-8b15617d63b1
# ╠═d2a3d02f-7eb3-42d6-9765-16e5bbc99d8b
# ╠═1c3bfcfd-1332-49d4-9b98-ceecc8c2926a
# ╠═e0abeb58-afd8-42d9-b0fc-fb55cc383323
# ╠═6c0b1f38-f692-4b3e-ac08-c0f71734ed05
# ╠═92147968-94e2-41d1-ad15-857eb5f06f60
# ╠═1f538236-9883-4c44-a3ab-5cf5a4b01ee1
# ╠═1aada718-2e07-48fa-b794-708e2ea23d8b
# ╠═1b0e2b99-a65d-441d-b038-159ccc48e6f0
# ╠═e134d03f-3771-4056-acd7-d4a8d74f2cf7
# ╠═04f4e258-749c-4ac4-8fad-f1c15370a1d7
# ╠═8502868f-c309-4391-9ef5-a83d5979ac77
# ╠═c83fc930-87c3-4b99-b947-6d06ac516429
# ╠═114b3dc2-39e3-4d97-b9f1-af60419bd0a8
# ╠═fdfc21ca-ea64-4726-9533-8694a27d40df
# ╠═79480cf1-834d-4458-80a6-c5bb246ec39d
# ╠═1efe51d4-1edf-4772-b91a-ca04475e31c5
# ╠═5308c06f-c14b-4d3c-8283-a833a008a532
# ╠═a9d05389-9d58-4d7c-a3ed-3ea7a1c5ab38
# ╠═6d796395-5024-4129-a250-df2401327711
# ╠═2de1ca1a-8741-4364-ae91-4819e6447927
# ╠═b7e30300-1bd3-4eab-a0d6-93d4902e9b9d
# ╠═db083fe4-b5ba-4502-8258-2aceb84e6e42
# ╠═352c281e-11d7-4158-9528-e05a4717cf7d
# ╠═5488a918-315f-4b0d-bf0b-d26d873583fe
# ╠═202e0940-2fd2-4f6e-983a-83d7025c91de
# ╠═1ff2be95-ecc6-4a90-9205-f425535039d7
# ╠═b3cd8248-2e5c-4730-ba9c-fe99d2c79e78
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment