{"id":918,"date":"2022-03-14T07:00:30","date_gmt":"2022-03-14T12:00:30","guid":{"rendered":"https:\/\/wollen.org\/blog\/?p=918"},"modified":"2024-04-23T17:54:28","modified_gmt":"2024-04-23T22:54:28","slug":"estimating-pi-on-pi-day","status":"publish","type":"post","link":"https:\/\/wollen.org\/blog\/2022\/03\/estimating-pi-on-pi-day\/","title":{"rendered":"Estimating Pi on Pi Day"},"content":{"rendered":"<p>You&#8217;ve probably heard that March 14 is unofficially (or maybe officially in some circles) recognized as Pi Day. That&#8217;s because the digits 3-1-4 match the first three digits in the number pi (\u03c0 = 3.14159265&#8230;). I&#8217;ve always said there&#8217;s no better way to celebrate made-up holidays than by doing math, so let&#8217;s get started.<\/p>\n<hr \/>\n<p>One way to estimate pi is to analyze the area-ratio of a square and a circle inscribed within it. There&#8217;s a bit to unpack from that statement so let me add a visual aid:<\/p>\n<p><a href=\"https:\/\/wollen.org\/blog\/wp-content\/uploads\/2022\/03\/pi_dimension_diagram.gif\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-963 size-full\" src=\"https:\/\/wollen.org\/blog\/wp-content\/uploads\/2022\/03\/pi_dimension_diagram.gif\" alt=\"\" width=\"400\" height=\"400\" \/><\/a><\/p>\n<p>Here we have a circle circumbscribed by a square. That&#8217;s a fancy way of saying the square&#8217;s overall width is the same as the circle&#8217;s overall width. In more formal terms, the circle&#8217;s radius is length R and the square&#8217;s sides are length 2R.<\/p>\n<p>A circle&#8217;s area is defined as \u03c0*r\u00b2, where r is the radius. Notice that this expression includes pi, so if we&#8217;re trying to estimate pi then we&#8217;re starting to get somewhere. A square&#8217;s area is even easier to calculate: [side]*[side].<\/p>\n<p>Now look at what happens if we divide the above circle&#8217;s area by the area of its square counterpart:<\/p>\n<p><a href=\"https:\/\/wollen.org\/blog\/wp-content\/uploads\/2022\/03\/area_equation-2.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-1428 size-full\" src=\"https:\/\/wollen.org\/blog\/wp-content\/uploads\/2022\/03\/area_equation-2.png\" alt=\"\" width=\"281\" height=\"476\" srcset=\"https:\/\/wollen.org\/blog\/wp-content\/uploads\/2022\/03\/area_equation-2.png 281w, https:\/\/wollen.org\/blog\/wp-content\/uploads\/2022\/03\/area_equation-2-177x300.png 177w\" sizes=\"auto, (max-width: 281px) 100vw, 281px\" \/><\/a><\/p>\n<div style=\"height: 2px;\"><\/div>\n<p>The R\u00b2 terms cancel and we&#8217;re left with a constant: \u03c0\/4. We can then simply multiply by 4 to solve for pi.<\/p>\n<hr \/>\n<p>So where does Python come in? Well, assuming for some reason that we can&#8217;t draw the shapes and measure them, the next-best approach might be to randomly generate points within the square and check how many fall within the circle.<\/p>\n<p>If the points are uniformly distributed across the x and y axes, the probability of a point falling within a shape is proportional to the shape&#8217;s area. If, as we saw above, [Area of Circle] \/ [Area of Square] is equal to \u03c0\/4, then \u03c0\/4 percent of the points will fall within the circle. That&#8217;s about 78.5%. We can run a simulation to randomly generate thousands of points, calculate this ratio, and then multiply by 4 to estimate the value of pi.<\/p>\n<hr \/>\n<p>Begin by randomly generating 10,000 points within a square of size 2R. This should be enough data to provide a reasonable estimate.<\/p>\n<p>We&#8217;ll locate the center of the figure at (0, 0) and set R equal to 1.0. This will keep the code simple, but the final result would be no different if we used a larger square centered at (8, -4.5), for example.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\">import matplotlib.pyplot as plt\r\nfrom random import uniform\r\nfrom math import sqrt, pi\r\n\r\nR = 1.0\r\n\r\nscatter_x, scatter_y = [], []\r\n\r\nfor _ in range(10000):\r\n    scatter_x.append(uniform(-R, R))\r\n    scatter_y.append(uniform(-R, R))<\/pre>\n<p>With data in hand, check how many points fall within an inscribed circle of radius R. The easiest approach is to <a href=\"https:\/\/www.cuemath.com\/euclidean-distance-formula\/\" target=\"_blank\" rel=\"noopener\">test<\/a> whether a point lies within 1.0 units of the center (0, 0).<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\">inside_circle = 0\r\n\r\nfor x, y in zip(scatter_x, scatter_y):\r\n    distance = sqrt(x**2 + y**2)\r\n    if distance &lt; R:\r\n        inside_circle += 1<\/pre>\n<p>Now we know how many points are within the circle. And we know <em>all<\/em> 10,000 points fall within the square because that&#8217;s how we designed the simulation.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\">inside_square = len(scatter_x)<\/pre>\n<p>These numbers are analogous to the area of each shape, since the probability of a point landing within a shape is equal to its share of the entire figure.<\/p>\n<p>So we have estimates of each shape&#8217;s area, and we know their ratio should be equal to \u03c0\/4. We&#8217;re almost there!<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\">ratio = inside_circle \/ inside_square\r\n\r\npi_estimate = ratio * 4<\/pre>\n<p>And we&#8217;re there! Just like that.<\/p>\n<p>Before moving on we should calculate how close the simulation came to pi&#8217;s true value.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\">error_percent = (pi_estimate - pi) \/ pi * 100<\/pre>\n<p>Rather than printing the estimate to screen, let&#8217;s display everything on a nice plot.<\/p>\n<p>Because the <em>Matplotlib<\/em> code is so simple I&#8217;ll skip the commentary. But as always, the full code will be available at the end of this post.<\/p>\n<p>Without further ado, our estimate of pi:<\/p>\n<p><a href=\"https:\/\/wollen.org\/blog\/wp-content\/uploads\/2022\/03\/pi_estimate.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-1490 size-full\" src=\"https:\/\/wollen.org\/blog\/wp-content\/uploads\/2022\/03\/pi_estimate.png\" alt=\"\" width=\"1600\" height=\"1600\" srcset=\"https:\/\/wollen.org\/blog\/wp-content\/uploads\/2022\/03\/pi_estimate.png 1600w, https:\/\/wollen.org\/blog\/wp-content\/uploads\/2022\/03\/pi_estimate-300x300.png 300w, https:\/\/wollen.org\/blog\/wp-content\/uploads\/2022\/03\/pi_estimate-1024x1024.png 1024w, https:\/\/wollen.org\/blog\/wp-content\/uploads\/2022\/03\/pi_estimate-150x150.png 150w, https:\/\/wollen.org\/blog\/wp-content\/uploads\/2022\/03\/pi_estimate-768x768.png 768w, https:\/\/wollen.org\/blog\/wp-content\/uploads\/2022\/03\/pi_estimate-1536x1536.png 1536w, https:\/\/wollen.org\/blog\/wp-content\/uploads\/2022\/03\/pi_estimate-800x800.png 800w\" sizes=\"auto, (max-width: 1600px) 100vw, 1600px\" \/><\/a><\/p>\n<p>With 10,000 randomly generated points we came within 0.42% of the actual value (3.14159265&#8230;). That&#8217;s not bad! Maybe we couldn&#8217;t build a particle accelerator with that sort of precision but we could certainly write blog posts all day.<\/p>\n<p>Obviously with more data comes more precision. I put together an animated GIF to show how an estimate emerges from a larger simulation. Error generally trends toward zero but it&#8217;s not a straight path!<\/p>\n<p><a href=\"https:\/\/wollen.org\/blog\/wp-content\/uploads\/2022\/03\/pi_estimate_gif-1.gif\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-1491 size-full\" src=\"https:\/\/wollen.org\/blog\/wp-content\/uploads\/2022\/03\/pi_estimate_gif-1.gif\" alt=\"\" width=\"800\" height=\"800\" \/><\/a><\/p>\n<hr \/>\n<p><a href=\"https:\/\/wollen.org\/misc\/pi_estimate.zip\"><strong>Download the Matplotlib style.<\/strong><\/a><\/p>\n<p><strong>Full code:<\/strong><\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\">import matplotlib.pyplot as plt\r\nfrom random import uniform\r\nfrom math import sqrt, pi\r\n\r\n\r\nR = 1.0\r\n\r\nscatter_x, scatter_y = [], []\r\n\r\nfor _ in range(10000):\r\n    scatter_x.append(uniform(-R, R))\r\n    scatter_y.append(uniform(-R, R))\r\n\r\ninside_circle = 0\r\n\r\nfor x, y in zip(scatter_x, scatter_y):\r\n    distance = sqrt(x**2 + y**2)\r\n    if distance &lt; R:\r\n        inside_circle += 1\r\n\r\ninside_square = len(scatter_x)\r\n\r\nratio = inside_circle \/ inside_square    # = (pi*R\u00b2) \/ (4*R\u00b2) = pi\/4\r\n\r\npi_estimate = ratio * 4\r\n\r\nerror_percent = (pi_estimate - pi) \/ pi * 100\r\n\r\nplt.style.use(\"wollen_dark.mplstyle\")\r\nfig, ax = plt.subplots(figsize=(8, 8))\r\n\r\nax.scatter(scatter_x, scatter_y, alpha=0.3, zorder=1)\r\n\r\nax.add_patch(plt.Circle((0, 0), R, facecolor=\"None\", edgecolor=\"white\", linewidth=3.0))\r\nax.add_patch(plt.Rectangle((-R, -R), R*2, R*2, facecolor=\"None\", edgecolor=\"white\", linewidth=3.0))\r\n\r\nresults_message = f\"n = {len(scatter_x):,}\\n\" \\\r\n                  f\"pi (est.):  {pi_estimate:.4f}\\n\" \\\r\n                  f\"error = {error_percent:+.2f}%\"\r\nax.text(0, 0, results_message,\r\n        font=\"Ubuntu Condensed\", size=13, ha=\"center\", va=\"center\",\r\n        bbox={\"boxstyle\": \"round\", \"facecolor\": \"#303030\", \"alpha\": 0.9, \"pad\": 0.35})\r\n\r\nplt.savefig(\"pi_estimate.png\", dpi=200)<\/pre>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>You&#8217;ve probably heard that March 14 is unofficially (or maybe officially in some circles) recognized as Pi Day. That&#8217;s because the digits 3-1-4 match the first three digits in the number pi (\u03c0 = 3.14159265&#8230;). I&#8217;ve always said there&#8217;s no<\/p>\n","protected":false},"author":1,"featured_media":1527,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[40,41],"tags":[184,178,180,22,183,179,177,47,24,64,181,174,175,182,142,25,42,65,140,176],"class_list":["post-918","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-math","category-probability","tag-3-14","tag-area","tag-circumscribed","tag-data","tag-holiday","tag-inscribed","tag-march-14","tag-math","tag-matplotlib","tag-monte-carlo","tag-monte-carlo-method","tag-pi","tag-pi-day","tag-probability","tag-pyplot","tag-python","tag-random","tag-simulation","tag-style","tag-uniform"],"_links":{"self":[{"href":"https:\/\/wollen.org\/blog\/wp-json\/wp\/v2\/posts\/918","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/wollen.org\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/wollen.org\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/wollen.org\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/wollen.org\/blog\/wp-json\/wp\/v2\/comments?post=918"}],"version-history":[{"count":40,"href":"https:\/\/wollen.org\/blog\/wp-json\/wp\/v2\/posts\/918\/revisions"}],"predecessor-version":[{"id":1493,"href":"https:\/\/wollen.org\/blog\/wp-json\/wp\/v2\/posts\/918\/revisions\/1493"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/wollen.org\/blog\/wp-json\/wp\/v2\/media\/1527"}],"wp:attachment":[{"href":"https:\/\/wollen.org\/blog\/wp-json\/wp\/v2\/media?parent=918"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/wollen.org\/blog\/wp-json\/wp\/v2\/categories?post=918"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/wollen.org\/blog\/wp-json\/wp\/v2\/tags?post=918"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}