Skip to content

Commit

Permalink
rearrange doodles
Browse files Browse the repository at this point in the history
  • Loading branch information
cnrrobertson committed Apr 3, 2024
1 parent f8b6137 commit 06a0eba
Show file tree
Hide file tree
Showing 29 changed files with 364 additions and 97 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"hash": "bedea6bcfab5ecec06e10dce0137384b",
"result": {
"engine": "jupyter",
"markdown": "---\ntitle: Heliocentrism vs Geocentrism\nauthor: Connor Robertson\nexecute:\n daemon: true\n---\n\n## Overview\nAnother fun little animation that I saw recently was the difference in the dynamics of the solar system when approaching the equations from a heliocentric (sun-centered) or geocentric (Earth-centered) perspective.\nAlthough it's clear now that we orbit the sun and a sun-centered model of the solar system is more correct, it wasn't always obvious.\nThis concept extends to systems that we have not yet nailed down completely.\nThis [simple animation](https://www.youtube.com/watch?v=7n7xaviepKM&t=556s) demonstrates that identifying the correct perspective when modeling a system can have a huge pay off in the simplicity of your result.\n\nI thought I'd try to recreate this little animation with `Makie.jl` as I did previously with the [polygon GIF](polygon_angles.qmd).\n\n## Setting up\nIn this animation, we'll be rotating some circular shapes around the point representing either the sun or the Earth and tracing their paths as they progress.\nFirst, let's plot the initial frame of the animation using a `Figure` with 2 `axes`:\n\n::: {#d9148c82 .cell execution_count=1}\n``` {.julia .cell-code}\nusing Pkg;\nPkg.activate(\".\");\nusing CairoMakie;\n```\n:::\n\n\n::: {#1572df4e .cell execution_count=2}\n``` {.julia .cell-code}\nf = Figure(resolution=(800,400));\naxes = [Axis(f[1,1]);Axis(f[1,2])]\nfor ax in axes ax.limits=(-22,22,-22,22) end\nfunction remove_axis_decor!(ax)\n ax.topspinevisible = false; ax.bottomspinevisible = false\n ax.leftspinevisible = false; ax.rightspinevisible = false\n ax.xgridvisible = false; ax.ygridvisible = false\n ax.xticksvisible = false; ax.yticksvisible = false\n ax.xticklabelsvisible = false; ax.yticklabelsvisible = false\nend\nremove_axis_decor!.(axes)\n```\n:::\n\n\nWe can now layout the different planets via a simple scatter plot in each `axis`.\nOf course, we cannot use the correct proportions or distances or the plot would be hard to understand.\nInstead, I'll settle for simple size differences between the planets and the sun and a somewhat uniform distance between each.\n\n::: {#fa44f437 .cell execution_count=3}\n``` {.julia .cell-code}\nnum_bodies = 9\nbody_locs1 = [(float(i),0.0) for i in 0:2:2(num_bodies-1)]\nbody_locs2 = [(float(i),0.0) for i in -6:2:2(num_bodies-1)-6]\nbody_sizes = 3 .* [9,3,3,4,2,5,6,4,4]\nbody_colors = [:yellow,:red,:red,:blue,:red,:red,:red,:red,:red]\ns1 = scatter!(axes[1], body_locs1, markersize=body_sizes, color=body_colors)\ns2 = scatter!(axes[2], body_locs2, markersize=body_sizes, color=body_colors)\ndisplay(f)\n```\n\n::: {.cell-output .cell-output-display}\n![](heliocentric_geocentric_files/figure-html/cell-4-output-1.svg){}\n:::\n\n::: {.cell-output .cell-output-display execution_count=4}\n```\nCairoMakie.Screen{IMAGE}\n```\n:::\n:::\n\n\n## Animation\nOkay!\nEasy as that.\nNow, we can move on to animating the rotation of the bodies.\nEach planet will rotate at a different speed and will go until again lining up as they started.\n\n::: {#7e52a51a .cell execution_count=4}\n``` {.julia .cell-code}\nbody_speeds = [0.0,47.87,35.02,29.78,24.077,13.07,9.69,6.81,5.43] ./ 200\nsun_speed2 = body_speeds[4]\norbit_radii1 = [bl[1] for bl in body_locs1]\norbit_radii2 = [bl[1] for bl in body_locs2]\n\n# Use Observable to add time dependence to planet locations\ntime_i = Observable(0.0)\nbody_xs1 = @lift(orbit_radii1 .* cos.(-1 .* body_speeds .* $time_i))\nbody_ys1 = @lift(orbit_radii1 .* sin.(-1 .* body_speeds .* $time_i))\nbody_xs2 = @lift(vcat(\n orbit_radii2[1]*cos(-sun_speed2*$time_i),\n orbit_radii2[1]*cos(-sun_speed2*$time_i) + orbit_radii1[2]*cos(-body_speeds[2]*$time_i),\n orbit_radii2[1]*cos(-sun_speed2*$time_i) + orbit_radii1[3]*cos(-body_speeds[3]*$time_i),\n 0.0,\n orbit_radii2[1]*cos(-sun_speed2*$time_i) .+ orbit_radii1[5:end] .* cos.(-1 .* body_speeds[5:end] .* $time_i)\n))\nbody_ys2 = @lift(vcat(\n orbit_radii2[1]*sin(-sun_speed2*$time_i),\n orbit_radii2[1]*sin(-sun_speed2*$time_i) + orbit_radii1[2]*sin(-body_speeds[2]*$time_i),\n orbit_radii2[1]*sin(-sun_speed2*$time_i) + orbit_radii1[3]*sin(-body_speeds[3]*$time_i),\n 0.0,\n orbit_radii2[1]*sin(-sun_speed2*$time_i) .+ orbit_radii1[5:end] .* sin.(-1 .* body_speeds[5:end] .* $time_i)\n))\n\nempty!(axes[1].scene.plots)\nempty!(axes[2].scene.plots)\ns1 = scatter!(axes[1], body_xs1, body_ys1, markersize=body_sizes, color=body_colors)\ns2 = scatter!(axes[2], body_xs2, body_ys2, markersize=body_sizes, color=body_colors)\n\n# Create GIF by iterating time\nsteps = 300\nrecord(f, \"gifs/heliocentric_geocentric1.gif\", 1:steps) do t\n time_i[] = t\nend\n```\n:::\n\n\n![](gifs/heliocentric_geocentric1.gif)\n\nNice!\nWe've got the two animations moving well.\nNote that since the animation was fairly straightforward and only required updating the scatter plot locations, we were able to use an `Observable` for time in `Makie`.\nThis object allows us to create the initial scatter plots where the scatter locations are wrapped with the `@lift` macro with the interpolating `$time_i`.\nNow, when our `Observable`, `time_i` is updated, the scatter points and subsequently the scatter plots are updated.\nUsing this nifty tool, our recording loop is very straightforward.\nHowever, using the `@lift` macro is not particularly intuitive and it took some trial and error to get the definition of the scatter points correctly wrapped in an `Observable`.\nHence, the definitions of `body_xs2` and `body_ys2` are so messy..\n\nOur next step is to add the path tracing of the planets to each plot.\nAgain, this is a fairly simple procedure that could be completed with an `Observable`.\n\n::: {#74178def .cell execution_count=5}\n``` {.julia .cell-code}\n# Line observables\nline_locs1 = [Observable([body_locs1[i]]) for i in 1:num_bodies]\nline_locs2 = [Observable([body_locs2[i]]) for i in 1:num_bodies]\n\nempty!(axes[1].scene.plots)\nempty!(axes[2].scene.plots)\nfor i in 1:num_bodies\n lines!(axes[1], line_locs1[i], color=body_colors[i])\n lines!(axes[2], line_locs2[i], color=body_colors[i])\nend\ns1 = scatter!(axes[1], body_xs1, body_ys1, markersize=body_sizes, color=body_colors)\ns2 = scatter!(axes[2], body_xs2, body_ys2, markersize=body_sizes, color=body_colors)\n\n# Create GIF by iterating time\nsteps = 300\nrecord(f, \"gifs/heliocentric_geocentric.gif\", 1:steps) do t\n time_i[] = t\n for i in 1:num_bodies\n line_locs1[i][] = push!(line_locs1[i][], (body_xs1[][i], body_ys1[][i]))\n line_locs2[i][] = push!(line_locs2[i][], (body_xs2[][i], body_ys2[][i]))\n end\nend\n```\n:::\n\n\n![](gifs/heliocentric_geocentric.gif)\n\nAlright!\nOur animation is now complete.\n\n",
"supporting": [
"heliocentric_geocentric_files"
],
"filters": [],
"includes": {}
}
}
12 changes: 12 additions & 0 deletions _freeze/other/fun/doodles/polygon_angles/execute-results/html.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"hash": "087b6dbe91a5f955ebee90f62b186fc3",
"result": {
"engine": "jupyter",
"markdown": "---\ntitle: The exterior angles of a polygon make a circle\nauthor: Connor Robertson\nexecute:\n daemon: true\n---\n\n## Overview\nI recently saw a [fun little GIF](https://gfycat.com/bluesecondblackwidowspider-geometry-math) from a weekly news email I get called the New Paper.\nIt shows a simple plot of the exterior angles of a few polygons.\nAs the polygons shrink, the exterior angles combine to eventually make a circle, which shows a simple graphical example of how the exterior angles of any polygon add to $2\\pi$.\n<!-- The GIF can be seen below: -->\n\n<!-- <div style='position:relative; padding-bottom:calc(100.00% + 44px)'><iframe src='https://gfycat.com/ifr/BlueSecondBlackwidowspider' frameborder='0' scrolling='no' width='100%' height='100%' style='position:absolute;top:0;left:0;' allowfullscreen></iframe></div> -->\n\n\nI thought I'd try to recreate this little GIF with my favorite plotting library `Makie.jl`.\n\n## Setting up\nBasically, we can start by getting the plots of each polygon set.\nWe can then animate the sides of the polygons shrinking.\n\nTo start we are going to need a `Figure` with 4 `axes`:\n\n::: {#a2322d40 .cell execution_count=1}\n``` {.julia .cell-code}\nusing Pkg;\nPkg.activate(\".\");\nusing CairoMakie;\n```\n:::\n\n\n::: {#b7cf19c9 .cell execution_count=2}\n``` {.julia .cell-code}\nf = Figure(resolution=(800,800));\naxes = [\n Axis(f[1,1]) Axis(f[1,2]);\n Axis(f[2,1]) Axis(f[2,2])\n]\nfor ax in axes ax.limits=(-6,6,-6,6) end\n```\n:::\n\n\nWe can now list the vertices for each polygon:\n\n::: {#f55fac85 .cell execution_count=3}\n``` {.julia .cell-code}\npoly11 = [(-2.0,3.0),(3.0,-3.0),(-4.0,-2.0),(-2.0,3.0)];\npoly12 = [(-3.0,2.0),(1.0,1.0),(3.0,-2.0),(-4.0,-1.0),(-3.0,2.0)];\npoly21 = [(-1.0,3.0),(1.0,3.0),(3.0,-1.0),(1.0,-3.0),(-2.0,-2.0),(-3.0,1.0),(-1.0,3.0)];\npoly22 = [(-1.0,2.0),(1.0,2.0),(4.0,-1.0),(2.0,-3.0),(-4.0,-1.0),(-1.0,2.0)];\n```\n:::\n\n\nwhere `poly11` is the polygon in the 1st row and 1st column.\nPlotting these lines on each respective axis, we get:\n\n::: {#8ff94bbe .cell execution_count=4}\n``` {.julia .cell-code}\nlines!(axes[1,1],poly11,color=:black);\nlines!(axes[1,2],poly12,color=:black);\nlines!(axes[2,1],poly21,color=:black);\nlines!(axes[2,2],poly22,color=:black);\npoly!(axes[1,1],poly11,transparency=true,color=RGBAf(1.0,0.6,0.0,0.2));\npoly!(axes[1,2],poly12,transparency=true,color=RGBAf(0.0,0.0,1.0,0.2));\npoly!(axes[2,1],poly21,transparency=true,color=RGBAf(0.5,0.0,0.5,0.2));\npoly!(axes[2,2],poly22,transparency=true,color=RGBAf(0.0,0.5,0.0,0.2));\ndisplay(f)\n```\n\n::: {.cell-output .cell-output-display}\n![](polygon_angles_files/figure-html/cell-5-output-1.svg){}\n:::\n\n::: {.cell-output .cell-output-display execution_count=5}\n```\nCairoMakie.Screen{IMAGE}\n```\n:::\n:::\n\n\nThese are obviously not exactly the polygons in the GIF, but they are generally similar and use nice easy vertex coordinates.\nNow, in order to accentuate the exterior angles, the GIF uses lines which extend beyond the vertices.\nTo achieve this, we can consider each line segment and shift the first vertex some distance in the opposite direction of the second vertex.\nTo do so, we should shift adjust our polygon representation to separate each line segment:\n\n::: {#d1f3a9e5 .cell execution_count=5}\n``` {.julia .cell-code}\nlpoly11 = [[poly11[i],poly11[i+1]] for i in 1:length(poly11)-1];\nlpoly12 = [[poly12[i],poly12[i+1]] for i in 1:length(poly12)-1];\nlpoly21 = [[poly21[i],poly21[i+1]] for i in 1:length(poly21)-1];\nlpoly22 = [[poly22[i],poly22[i+1]] for i in 1:length(poly22)-1];\ndisplay(lpoly11)\n```\n\n::: {.cell-output .cell-output-display}\n```\n3-element Vector{Vector{Tuple{Float64, Float64}}}:\n [(-2.0, 3.0), (3.0, -3.0)]\n [(3.0, -3.0), (-4.0, -2.0)]\n [(-4.0, -2.0), (-2.0, 3.0)]\n```\n:::\n:::\n\n\nWe can now the vector between the first and second indices of each line segment and shift our first vertex by the negative of that vector.\nThat is a mouthful but more easily written mathematically.\nIf we consider a single line segment with vertices $v_1$ and $v_2$, we can calculate the distance between them $d = v_2 - v_1$ such that $v_1 + d = v_2$ and then redefine our first vertex in the opposite direction as $v_1^* = v_1 - l\\frac{d}{\\|d\\|}$ where $l$ is the length of the external line.\nThis boils down to the following:\n\n::: {#a06c4970 .cell execution_count=6}\n``` {.julia .cell-code}\nfunction shift_first_vertices!(lpoly, l=2)\n for line in lpoly\n v1 = collect(line[1]); v2 = collect(line[2])\n d = v2 - v1\n v1star = v1 - l*d/sqrt(d[1]^2+d[2]^2)\n line[1] = tuple(v1star...)\n end\nend\nshift_first_vertices!(lpoly11)\nshift_first_vertices!(lpoly12)\nshift_first_vertices!(lpoly21)\nshift_first_vertices!(lpoly22)\nfunction plot_line_segments!(ax,lpoly)\n lines = []\n for line in lpoly push!(lines,lines!(ax,line,color=:black)) end\n return lines\nend\nplot_line_segments!(axes[1,1],lpoly11)\nplot_line_segments!(axes[1,2],lpoly12)\nplot_line_segments!(axes[2,1],lpoly21)\nplot_line_segments!(axes[2,2],lpoly22)\ndisplay(f)\n```\n\n::: {.cell-output .cell-output-display}\n![](polygon_angles_files/figure-html/cell-7-output-1.svg){}\n:::\n\n::: {.cell-output .cell-output-display execution_count=7}\n```\nCairoMakie.Screen{IMAGE}\n```\n:::\n:::\n\n\nOnce we have these lines in place, we can add the external angles.\nIronically, the best tool in Makie for these angle drawings is the `poly!` function which plots a filled polygon from some given vertices.\nThus, we need to compute the vertices of the arc for each angle of each polygon.\n\nThis computation can be done by taking two connected line segments $d_1$ and $d_2$, identifying the angle between them using the law of cosines $\\arccos(d_1 \\cdot d_2)$, and sampling points along the arc of given radius $l$.\nSampling the arc requires a little change of coordinates to center the points around the vertex connecting the two line segments and to rotate the standard $x$ and $y$ coordinates to align $x$ with $d_1$ and $y$ with $d_1^\\perp$.\nThis is, in my opinion, the most challenging part of the plot.\n\n::: {#9398063a .cell execution_count=7}\n``` {.julia .cell-code}\nfunction angle_vertices(line1,line2,l=1)\n v1 = collect(line1[1])\n v2 = collect(line1[2]) # Shared vertex\n v3 = collect(line2[2])\n d1 = v2-v1\n d2 = v3-v2\n # Line segment directions (normalized\n d1 ./= sqrt(d1[1]^2+d1[2]^2)\n d2 ./= sqrt(d2[1]^2+d2[2]^2)\n d1perp = [d1[2],-d1[1]]\n vertex = tuple(v2...)\n # Computing angle between lines, then sampling arc points\n angle = acos(d1'*d2)\n angle = isnan(angle) ? 0.0 : angle\n angles = range(0, angle, length=10)\n # arc has radius l, origin at v2, \"x\"-direction is d1, \"y\"-direction is d1perp\n arc_points = [tuple(@.( v2 - l*(d1*cos(a) + d1perp*sin(a)))...) for a in angles]\n vertices = vcat(vertex,arc_points,vertex)\n return vertices\nend\nfunction plot_arcs!(ax,lpoly)\n arcs = []\n colors = to_colormap(:seaborn_colorblind)\n for i in 1:length(lpoly)\n if i+1 > length(lpoly) # The angle between the last line segment and first\n color = colors[i+1]\n arc_vertices = angle_vertices(lpoly[i],lpoly[1])\n else\n color = colors[i]\n arc_vertices = angle_vertices(lpoly[i],lpoly[i+1])\n end\n push!(arcs,poly!(ax,arc_vertices,color=color))\n end\n return arcs\nend\nplot_arcs!(axes[1,1],lpoly11)\nplot_arcs!(axes[1,2],lpoly12)\nplot_arcs!(axes[2,1],lpoly21)\nplot_arcs!(axes[2,2],lpoly22)\ndisplay(f)\n```\n\n::: {.cell-output .cell-output-display}\n![](polygon_angles_files/figure-html/cell-8-output-1.svg){}\n:::\n\n::: {.cell-output .cell-output-display execution_count=8}\n```\nCairoMakie.Screen{IMAGE}\n```\n:::\n:::\n\n\nNow, removing the axes decorations, we have a clean plot of (almost) the first frame of the GIF:\n\n::: {#efca1bba .cell execution_count=8}\n``` {.julia .cell-code}\nfunction remove_axis_decor!(ax)\n ax.topspinevisible = false; ax.bottomspinevisible = false\n ax.leftspinevisible = false; ax.rightspinevisible = false\n ax.xgridvisible = false; ax.ygridvisible = false\n ax.xticksvisible = false; ax.yticksvisible = false\n ax.xticklabelsvisible = false; ax.yticklabelsvisible = false\nend\nremove_axis_decor!.(axes)\ndisplay(f)\n```\n\n::: {.cell-output .cell-output-display}\n![](polygon_angles_files/figure-html/cell-9-output-1.svg){}\n:::\n\n::: {.cell-output .cell-output-display execution_count=9}\n```\nCairoMakie.Screen{IMAGE}\n```\n:::\n:::\n\n\n## Animating\n\nWith the initial plot now done, to complete the animation, it remains to shrink each polygon until the angles come together to form a circle.\nThis can be simply done (with slight error) by computing the center of each polygon via averaging, centering the vertices around that center, then shrinking the vertices proportional to the number of steps in the animation.\nPutting everything together:\n\n::: {#4d6c6f6a .cell execution_count=9}\n``` {.julia .cell-code}\n# Initialize\nf = Figure(resolution=(800,800));\naxes = [\n Axis(f[1,1]) Axis(f[1,2]);\n Axis(f[2,1]) Axis(f[2,2])\n]\nfor ax in axes ax.limits = (-6,6,-6,6) end\nremove_axis_decor!.(axes)\npoly11 = [(-2.0,3.0),(3.0,-3.0),(-4.0,-2.0),(-2.0,3.0)];\npoly12 = [(-3.0,2.0),(1.0,1.0),(3.0,-2.0),(-4.0,-1.0),(-3.0,2.0)];\npoly21 = [(-1.0,3.0),(1.0,3.0),(3.0,-1.0),(1.0,-3.0),(-2.0,-2.0),(-3.0,1.0),(-1.0,3.0)];\npoly22 = [(-1.0,2.0),(1.0,2.0),(4.0,-1.0),(2.0,-3.0),(-4.0,-1.0),(-1.0,2.0)];\n# Polygon average centers\nfunction compute_center(poly)\n vec(sum(hcat(collect.(poly)...),dims=2)./length(poly))\nend\nc11 = compute_center(poly11)\nc12 = compute_center(poly12)\nc21 = compute_center(poly21)\nc22 = compute_center(poly22)\nfunction shrink_polygon(poly,c,step,steps)\n new_vertices = similar(poly)\n for i in eachindex(poly)\n vertex = collect(poly[i]) - c\n new_vertex = @. vertex*((steps-step)/(steps))\n new_vertices[i] = tuple((new_vertex + c)...)\n end\n return new_vertices\nend\n\n# Animation (somewhat inefficient since it doesn't use Observables)\nsteps = 120\nrecord(f, \"gifs/angle_gif.gif\", vcat(1:(steps-1),fill(steps-1,steps÷4),(steps-1):-1:1)) do t\n empty!(axes[1,1].scene.plots)\n empty!(axes[1,2].scene.plots)\n empty!(axes[2,1].scene.plots)\n empty!(axes[2,2].scene.plots)\n npoly11 = shrink_polygon(poly11,c11,t,steps)\n npoly12 = shrink_polygon(poly12,c12,t,steps)\n npoly21 = shrink_polygon(poly21,c21,t,steps)\n npoly22 = shrink_polygon(poly22,c22,t,steps)\n lpoly11 = [[npoly11[i],npoly11[i+1]] for i in 1:length(npoly11)-1];\n lpoly12 = [[npoly12[i],npoly12[i+1]] for i in 1:length(npoly12)-1];\n lpoly21 = [[npoly21[i],npoly21[i+1]] for i in 1:length(npoly21)-1];\n lpoly22 = [[npoly22[i],npoly22[i+1]] for i in 1:length(npoly22)-1];\n shift_first_vertices!(lpoly11)\n shift_first_vertices!(lpoly12)\n shift_first_vertices!(lpoly21)\n shift_first_vertices!(lpoly22)\n poly!(axes[1,1],npoly11,transparency=true,color=RGBAf(1.0,0.6,0.0,0.2));\n poly!(axes[1,2],npoly12,transparency=true,color=RGBAf(0.0,0.0,1.0,0.2));\n poly!(axes[2,1],npoly21,transparency=true,color=RGBAf(0.5,0.0,0.5,0.2));\n poly!(axes[2,2],npoly22,transparency=true,color=RGBAf(0.0,0.5,0.0,0.2));\n plot_arcs!(axes[1,1],lpoly11)\n plot_arcs!(axes[1,2],lpoly12)\n plot_arcs!(axes[2,1],lpoly21)\n plot_arcs!(axes[2,2],lpoly22)\n plot_line_segments!(axes[1,1],lpoly11)\n plot_line_segments!(axes[1,2],lpoly12)\n plot_line_segments!(axes[2,1],lpoly21)\n plot_line_segments!(axes[2,2],lpoly22)\nend\n```\n:::\n\n\n![](gifs/angle_gif.gif)\n\n",
"supporting": [
"polygon_angles_files"
],
"filters": [],
"includes": {}
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions _quarto.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ website:
href: other/fun/fun.qmd
contents:
- text: "Polygon angles"
href: other/fun/polygon_angles.qmd
href: other/fun/doodles/polygon_angles.qmd
- text: "Geocentrism"
href: other/fun/heliocentric_geocentric.qmd
href: other/fun/doodles/heliocentric_geocentric.qmd
- title: "Machine Learning and Optimization Seminar"
style: "floating"
collapse-level: 1
Expand Down
File renamed without changes
Loading

0 comments on commit 06a0eba

Please sign in to comment.