Snapshot.jl
← Notebooks/Convolutions🏝️ 9/9 cells interactive
.jl source

Convolutions Explained

Introduction

Hello there! If you ever came across convolutions in some certain science context, you know they can be VERY confusing. This notebook will help you get a better intuition for convolutions. Let's go!

A new disease appears - oh no

Let's start with this great example from the better explained blog : Suppose there is a pandemic going on and more people are getting sick every day. So, you are a doctor and you have the following new patients coming in each day (yes, sadly even elves and fairies can get this disease):

The number of patients increases as the disease spreads.

Try it: How many days should the disease go on: 5 day(s)

But we have a treatment, puh!

The patients all have the same disease that requires the following treatment:

  • On the 1st day, the patient receives 1 pill

  • On the 2nd day, the patient receives 2 pills

  • On the nth day, the patient receives n pills

So something that looks like this:

Try it: Choose your own treatment plan (you can also chose 0 pills for a certain day):

But, how many pills do we need to stockpile?

Now as a doctor, you want to be ready: each day you total how many pills your patients need.

  • On day 1 you treat 1 patient group(s) with 1 new pills.

  • On day 2 those patients are still there, plus 2 new ones.

  • Each new day you multiply pills-per-patient and add across every day so far.

Notice how we keep multiplying and adding each day? That running total is exactly a convolution!


Now to make things more visual, move the slider around to see how many pills you need each day!

Hint: In order to administer the treatment in the right order, we need to flip patients lists, so they get admitted to the hospital in the right order! So now it looks like this:

# Numeric per-day patient counts (= the lengths of the emoji groups above). The
# figure and the day-by-day text below run on these flat Int counts so they
# compile to live WebAssembly islands β€” no emoji string-length work in the kernel.
patient_counts = (exponential ? [1, 2, 5, 8, 16, 32, 65, 0] : [1, 2, 5, 8, 4, 2, 1, 0])[1:len]

Try it:: calculate up to day: 1

Test your understanding: On what day do you require most pills?

begin
	# A SIMPLER but faithful WebAssembly-compilable figure of the SAME data the
	# notebook is about: the 1D CONVOLUTION of the two daily signals (new pills
	# per day ⋆ new patients per day) = the stockpile of pills you must hold each
	# day, with the current day highlighted. Everything is built from flat
	# Float64 vectors with explicit loops so it compiles to a live island, and
	# the convolution is the very `simple_conv` the notebook teaches.

	# the convolution result: the stockpile required each day β€” the same
	# explicit kernel the whole notebook is about
	y3 = simple_conv(y1_pills, reverse(y2_patients))
	n3 = length(y3)
	xs3 = Float64[]; ys3 = Float64[]
	for i in 1:n3
		push!(xs3, Float64(i))
		push!(ys3, Float64(y3[i]))
	end

	# clamp the day slider into a valid index so NO sampled bond value can trap
	# (the differential oracle samples Slider possible_values = POSITION INDICES)
	day = k_conv
	if day < 1
		day = 1
	elseif day > n3
		day = n3
	end
	day_x = Float64(day)
	day_y = Float64(y3[day])

	# build the figure with the proven-minimal WasmMakie API (bare Axis + bare
	# lines!, exactly like the known-good figure island): the convolution curve,
	# plus a vertical stem at the current day so it stays visible as you scrub.
	f = Figure(size = (640, 380))
	ax = Axis(f[1, 1])
	lines!(ax, xs3, ys3)
	lines!(ax, [day_x, day_x], [0.0, day_y])
	f
end

Generating Code

The following code sets up our initial figure. If you're interested in drawing graphs in computer science, you can take a look but you don't need to understand every line.

begin
	# Number of time steps
	nb = 8 

	# Slider for how long the pandemic should go 
	len_slider = @bind len PlutoUI.Slider(3:1:nb-1, show_value=true, default=5)

	# Checkboxes for more options
	show_stairs = @bind stairs CheckBox(default=true)
	make_exponential = @bind exponential CheckBox()
		
end;
begin
	# Number of patients per day (First function) β€” flipped + left-padded to 8,
	# exactly as `patients_flipped` is, but built from flat Int counts.
	numbers_patients = append!([0 for i in 1:8-length(patient_counts)], reverse(patient_counts))
	y2_patients = append!([0 for i in 1:9], numbers_patients, [0 for i in 1:16-length(numbers_patients)])

	# Number of pills per day (Second function) β€” the scrubbable counts directly.
	numbers_pills = treatment_in
	y1_pills = append!([0 for i in 1:9], numbers_pills, [0 for i in 1:16-length(numbers_pills)])

end;
# (the figure is built and displayed by the single cell below β€” keeping all the
# WasmMakie drawing in the one cell whose VALUE is the Figure lets it ship as a
# live WebAssembly canvas island)

Appendix

begin
	# plotting:
	using WasmMakie, ColorSchemes, Colors

	# widgets and layout:
	using PlutoUI
	using PlutoUI.ExperimentalLayout: grid, vbox, hbox, Div
	using HypertextLiteral
end
# (the emoji-pill PNG download is gone β€” WasmMakie markers are plain shapes,
# so the visualization is fully self-contained and wasm-compilable)
treatment_in = [a1_s, a2_s, a3_s, a4_s, a5_s, a6_s, a7_s, a8_s];
draw_all (generic function with 2 methods)
draw_point (generic function with 2 methods)
draw_grey_points (generic function with 2 methods)
simple_conv (generic function with 1 method)
4
begin
	_rgba(c) = (Float64(red(c)), Float64(green(c)), Float64(blue(c)), 1.0)
	color_grey = (0.5, 0.5, 0.5, 1.0)
	colors = [_rgba(ColorSchemes.viridis[i]) for i in 1:40:256]
	col_length = length(colors)
	color_blue = _rgba(ColorSchemes.viridis[116])
end;
Dict{Symbol, Any}(:attributes => Dict{String, Any}("class" => "plutoui-sidebar aside", "style" => ""), :tag => "div", :children => Any[("<header>\n<span class=\"sidebar-toggle open-sidebar\">πŸ•Ή</span>\n<span class=\"sidebar-toggle closed-sidebar\">πŸ•Ή</span>\nInteractive Sliders\n</header>\n", MIME type text/html), ("<div class=\"markdown\"><p>Here are all interactive bits of the notebook at one place. Feel free to change them&#33;</p>\n</div>", MIME type text/html)])
sidebar = Div([
	@htl("""
	<header>
	<span class="sidebar-toggle open-sidebar">πŸ•Ή</span>
	<span class="sidebar-toggle closed-sidebar">πŸ•Ή</span>
	Interactive Sliders
	</header>
	"""),
	md"""
	Here are all interactive bits of the notebook at one place. Feel free to change them!
	"""
], class="plutoui-sidebar aside")
Dict{Symbol, Any}(:attributes => Dict{String, Any}("class" => "plutoui-sidebar aside third", "style" => ""), :tag => "div", :children => Any[("<div class=\"markdown\"><p><strong>Choose a day:</strong></p>\n</div>", MIME type text/html), ("<bond def=\"k_conv\" unique_id=\"fkaigdjdpovu\"><input type='range' min='1' max='17' value='1'><script>\n\t\t\t\t\tconst input_el = currentScript.previousElementSibling\n\t\t\t\t\tconst output_el = currentScript.nextElementSibling\n\t\t\t\t\tconst displays = [\"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\", \"10\", \"11\", \"12\", \"13\", \"14\", \"15\", \"16\", \"17\"]\n\n\t\t\t\t\tlet update_output = () => {\n\t\t\t\t\t\toutput_el.value = displays[input_el.valueAsNumber - 1]\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tinput_el.addEventListener(\"input\", update_output)\n\t\t\t\t\t// We also poll for changes because the `input_el.value` can change from the outside, e.g. https://github.com/JuliaPluto/PlutoUI.jl/issues/277\n\t\t\t\t\tlet id = setInterval(update_output, 200)\n\t\t\t\t\tinvalidation.then(() => {\n\t\t\t\t\t\tclearInterval(id)\n\t\t\t\t\t\tinput_el.removeEventListener(\"input\", update_output)\n\t\t\t\t\t})\n\t\t\t\t\t</script><output style='\n\t\t\t\t\t\tfont-family: system-ui;\n\t\t\t\t\t\tfont-variant-numeric: tabular-nums;\n \t\t\t\t\tfont-size: 15px;\n \t\t\t\t\tmargin-left: 3px;\n \t\t\t\t\ttransform: translateY(-4px);\n \t\t\t\t\tdisplay: inline-block;'>1</output></bond>", MIME type text/html), ("<div class=\"markdown\"><p><strong>Choose how long the pandemic is:</strong></p>\n</div>", MIME type text/html), ("<bond def=\"len\" unique_id=\"xzniitvrbiky\"><input type='range' min='1' max='5' value='3'><script>\n\t\t\t\t\tconst input_el = currentScript.previousElementSibling\n\t\t\t\t\tconst output_el = currentScript.nextElementSibling\n\t\t\t\t\tconst displays = [\"3\", \"4\", \"5\", \"6\", \"7\"]\n\n\t\t\t\t\tlet update_output = () => {\n\t\t\t\t\t\toutput_el.value = displays[input_el.valueAsNumber - 1]\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tinput_el.addEventListener(\"input\", update_output)\n\t\t\t\t\t// We also poll for changes because the `input_el.value` can change from the outside, e.g. https://github.com/JuliaPluto/PlutoUI.jl/issues/277\n\t\t\t\t\tlet id = setInterval(update_output, 200)\n\t\t\t\t\tinvalidation.then(() => {\n\t\t\t\t\t\tclearInterval(id)\n\t\t\t\t\t\tinput_el.removeEventListener(\"input\", update_output)\n\t\t\t\t\t})\n\t\t\t\t\t</script><output style='\n\t\t\t\t\t\tfont-family: system-ui;\n\t\t\t\t\t\tfont-variant-numeric: tabular-nums;\n \t\t\t\t\tfont-size: 15px;\n \t\t\t\t\tmargin-left: 3px;\n \t\t\t\t\ttransform: translateY(-4px);\n \t\t\t\t\tdisplay: inline-block;'>5</output></bond>", MIME type text/html), ("<div class=\"markdown\"><p><strong>Change the treatment:</strong></p>\n</div>", MIME type text/html), ("<div class=\"markdown\"><p><bond def=\"a1_s\" unique_id=\"xjfbujqtwhcx\"><script>\n\t\t\t// weird import to make it faster. The `await import` can still delay execution by one frame if it is already loaded...\n\t\t\twindow.d3format = window.d3format ?? await import(\"https://cdn.jsdelivr.net/npm/d3-format@2/+esm\")\n\n\t\t\tconst argmin = xs => xs.indexOf(Math.min(...xs))\n\t\t\tconst closest_index = (xs, y) => argmin(xs.map(x => Math.abs(x-y)))\n\n\t\t\tconst values = [0, 1, 2, 3, 4, 5, 6]\n\n\t\t\tconst el = html`\n\t\t\t<span title=\"Click and drag this number left or right!\" style=\"cursor: col-resize;\n\t\t\ttouch-action: none;\n\t\t\tbackground: rgb(252, 209, 204);\n\t\t\tcolor: black;\n\t\t\tpadding: 0em .2em;\n\t\t\tborder-radius: .3em;\n\t\t\tfont-weight: bold;\n\t\t\tfont-family: system-ui, sans-serif;\n \t\tfont-variant-numeric: tabular-nums;\n\t\t\t\">1</span>\n\t\t\t`\n\n\n\n\t\t\tlet old_x = 0\n\t\t\tlet old_index = 0\n\t\t\tconst initial_index = closest_index(values, 1)\n\t\t\tlet current_index = initial_index\n\n\t\t\tconst formatter = s => \"\" + d3format.format(\"\")(s) + \"\"\n\n\n\t\t\tObj
sidebar2 = Div([
	md""" **Choose a day:**""",
	k_slider,
	md"""**Choose how long the pandemic is:**""",
	len_slider, 
	md"""**Change the treatment:**""",
	md""" $(a1) $(a2) $(a3) $(a4) $(a5) $(a6) $(a7) $(a8)""",
	md"""**Make the pandemic exponential:** $(make_exponential)""",
	md"""**Show the stairs** $(show_stairs)"""
], class="plutoui-sidebar aside third")
html"""
<style>
	div.plutoui-sidebar.aside {
		position: fixed;
		right: 1rem;
		top: 10rem;
		width: min(80vw, 300px);
		padding: 10px;
		border: 3px solid rgba(0, 0, 0, 0.15);
		border-radius: 10px;
		box-shadow: 0 0 11px 0px #00000010;
		max-height: calc(100vh - 5rem - 56px);
		overflow: auto;
		z-index: 40;
		background: white;
		transition: transform 300ms cubic-bezier(0.18, 0.89, 0.45, 1.12);
		color: var(--pluto-output-color);
		background-color: var(--main-bg-color);
	}

	.second {
		top: 17.5rem !important;
	}

	.third {
		top: 17.5rem !important;
	}
	
	div.plutoui-sidebar.aside.hide {
		transform: translateX(calc(100% - 28px));
	}
	
	.plutoui-sidebar header {
		display: block;
		font-size: 1.5em;
		margin-top: -0.1em;
		margin-bottom: 0.4em;
		padding-bottom: 0.4em;
		margin-left: 0;
		margin-right: 0;
		font-weight: bold;
		border-bottom: 2px solid rgba(0, 0, 0, 0.15);
	}
	
	.plutoui-sidebar.aside.hide .open-sidebar, .plutoui-sidebar.aside:not(.hide) .closed-sidebar, .plutoui-sidebar:not(.aside) .closed-sidebar {
		display: none;
	}

	.sidebar-toggle {
		cursor: pointer;
	}
	
</style>
<script>
	let listener = event => {
		if (event.target.classList.contains("sidebar-toggle")) {
			document.querySelectorAll('.plutoui-sidebar').forEach(function(el) {
				el.classList.toggle("hide");
			});
		}
	}
	document.addEventListener('click', listener);
	invalidation.then(() => {
		document.removeEventListener('click', listener);
	})
</script>
"""

Pluto notebooks as lean Therapy components β€” interactive WebAssembly, no server.