[{"id":247014,"date":"2026-05-10T13:51:40","date_gmt":"2026-05-10T18:51:40","guid":{"rendered":"https:\/\/www.johndcook.com\/blog\/?p=247014"},"modified":"2026-05-10T13:59:11","modified_gmt":"2026-05-10T18:59:11","slug":"the-linear-algebra-of-bit-twiddling","status":"publish","type":"post","link":"https:\/\/www.johndcook.com\/blog\/2026\/05\/10\/the-linear-algebra-of-bit-twiddling\/","title":{"rendered":"The linear algebra of bit twiddling"},"content":{"rendered":"<p>The <a href=\"https:\/\/www.johndcook.com\/blog\/2026\/05\/10\/reverse-mersenne-twister\/\">previous post<\/a> looked at the tempering step of the Mersenne Twister, formulating a sequence of bit operations as multiplication by a matrix mod 2. This post will look at the components more closely.<\/p>\n<p>The theorems of linear algebra generally hold independent of the field of scalars. Typically the field is \u211d or \u2102, but most of basic linear algebra works the same over every field [1]. In particular, we can do linear algebra over a finite field, and we&#8217;re interested in the most finite of finite fields GF(2), the field with just two elements, 0 and 1.<\/p>\n<p>In GF(2), addition corresponds to XOR. We will denote this by \u2295 to remind us that although it&#8217;s addition, it&#8217;s not the usual addition, i.e. 1 \u2295 1 = 0. Similarly, multiplication corresponds to AND. We&#8217;ll work with 8-bit numbers to make the visuals easier to see.<\/p>\n<p>Shifting a number left one bit corresponds to multiplication by a matrix with 1&#8217;s below the diagonal main. Shifting left by <em>k<\/em> bits is the same as shifting left by 1 bit <em>k<\/em> times, so the the matrix representation for\u00a0<em>x<\/em> &lt;&lt; <em>k<\/em> is the\u00a0<em>k<\/em>th power of the matrix representation of shifting left once. This matrix has 1s on the\u00a0<em>k<\/em>th diagonal below the main diagonal. Below is the matrix for shifting left two bits,\u00a0<em>x<\/em> &lt;&lt;\u00a0<em>k<\/em>.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-medium\" src=\"https:\/\/www.johndcook.com\/left_shift_2_matrix.png\" width=\"360\" height=\"360\" \/><\/p>\n<p>Right shifts are the mirror image of left shifts. Here&#8217;s the matrix for shifting right two bits, <em>x<\/em> &gt;&gt; <em>k<\/em>.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-medium\" src=\"https:\/\/www.johndcook.com\/right_shift_2_matrix.png\" width=\"360\" height=\"360\" \/><\/p>\n<p>Shifts are not fully invertible because bits either fall off the left or the right end. The steps in the Mersenne Twister are invertible because shifts are always XOR&#8217;d with the original argument. For example, although the function that takes <em>x<\/em> to <em>x<\/em> &gt;&gt; 2 is not invertiable, the function that takes <em>x<\/em> to <em>x<\/em> \u2295 (<em>x<\/em> &gt;&gt; 2) is invertible. This operation corresponds to the matrix below.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-medium\" src=\"https:\/\/www.johndcook.com\/right_shift_xor.png\" width=\"360\" height=\"360\" \/><\/p>\n<p>This is an upper triangular matrix, so its determinant is the product of the diagonal elements. These are all 1s, so the determinant is 1, and the matrix is invertible.<\/p>\n<p>Bitwise AND multiplies each bit of the input by the corresponding bit in another number known as the mask. The bits aligned with a 1 are kept and the bits aligned with a 0 are cleared. This corresponds to multiplying by a diagonal matrix whose diagonal elements correspond to the bits in the mask. For example, here is the matrix that corresponds to taking the bitwise AND with 10100100.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.johndcook.com\/mask_matrix.png\" width=\"360\" height=\"360\" class=\"aligncenter size-medium\" \/><\/p>\n<p>Each of the steps in the Mersenne Twister tempering process are invertible because they all correspond to triangular matrices with all 1&#8217;s on the diagonal. For example, the line<\/p>\n<pre>y ^= (y <<  7) &#038; 0x9d2c5680 <\/pre>\n<p>says to shift the bits of <code>y<\/code> left 7 places, then zero out the elements corresponding to 0s in the mask, then XOR the result with <em>y<\/em>. In matrix terms, we multiply by a lower triangular matrix with zeros on the main diagonal, the multiply by a diagonal matrix that zeros out some of the terms, then add the identity matrix. So the matrix corresponding to the line of code above is lower triangular, with all 1s on the diagonal, so it is invertible. <\/p>\n<p>[1] Until you get to eigenvalues. Then it matters whether the field is algebraically complete, which no finite field is.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The previous post looked at the tempering step of the Mersenne Twister, formulating a sequence of bit operations as multiplication by a matrix mod 2. This post will look at the components more closely. The theorems of linear algebra generally hold independent of the field of scalars. Typically the field is \u211d or \u2102, but [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[5,9],"tags":[198],"class_list":["post-247014","post","type-post","status-publish","format-standard","hentry","category-computing","category-math","tag-linear-algebra"],"acf":[],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts\/247014","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/comments?post=247014"}],"version-history":[{"count":0,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts\/247014\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/media?parent=247014"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/categories?post=247014"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/tags?post=247014"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}},{"id":247013,"date":"2026-05-10T12:32:02","date_gmt":"2026-05-10T17:32:02","guid":{"rendered":"https:\/\/www.johndcook.com\/blog\/?p=247013"},"modified":"2026-05-10T13:52:53","modified_gmt":"2026-05-10T18:52:53","slug":"reverse-mersenne-twister","status":"publish","type":"post","link":"https:\/\/www.johndcook.com\/blog\/2026\/05\/10\/reverse-mersenne-twister\/","title":{"rendered":"Reverse engineering Mersenne Twister with Linear Algebra"},"content":{"rendered":"<p>The Mersenne Twister (MT) is a random number generator with good statistical properties but bad cryptographic properties. In buzzwords, it&#8217;s a PRNG but not a CSPRNG.<\/p>\n<p>This post will show how the internal state of a MT generator can be recovered from its output. We&#8217;ll do this using linear algebra. The bit twiddling approach is more common and more efficient, but the linear algebra approach is conceptually simpler.<\/p>\n<h2>How MT works<\/h2>\n<p>There are numerous variations on the Mersenne Twister. We&#8217;ll focus on the original version that had internal state consists of vector <code>x<\/code> of length 640 filled with 32-bit numbers. The ideas in this post would apply equally to all MT versions.<\/p>\n<p>The first call to MT returns a &#8220;tempered&#8221; version of\u00a0<code>x[0]<\/code>. The next call returned a tempered version of <code>x[1]<\/code>, and so on. After every 640 calls, the state is scrambled. This scrambling is where the &#8220;twist&#8221; in the name Mersenne Twister comes from. (The Mersenne part comes from the fact that the period of an MT generator is a <a href=\"https:\/\/www.johndcook.com\/blog\/2011\/09\/09\/five-interesting-things-about-mersenne-primes\/\">Mersenne prime<\/a>.)<\/p>\n<h2>Tempering<\/h2>\n<p>Here is Python code for performing the tempering step.<\/p>\n<pre>def temper(y):\r\n    y ^= (y &gt;&gt; 11) \r\n    y ^= (y &lt;&lt;  7) &amp; 0x9d2c5680 \r\n    y ^= (y &lt;&lt; 15) &amp; 0xefc60000 \r\n    y ^= (y &gt;&gt; 18)  \r\n    return y\r\n<\/pre>\n<p>Each step is reversible, and so the <code>temper<\/code> function is reversible.<\/p>\n<p>Because the tempering step is reversible, the first output can be used to infer the first element of the internal state, the second output to infer the second element, and so on. After 640 calls one can know the entire internal state and predict the rest of the generator&#8217;s output from then on.<\/p>\n<h2>Linear algebra<\/h2>\n<p>The bitwise operations above all correspond to linear operations over GF(2), the field with just two elements, 0 and 1. Addition in this field is XOR and multiplication is AND.<\/p>\n<p>Each step corresponds to multiplying a vector of 32 bits on the left by a 32 \u00d7 32 matrix with entries that are 0&#8217;s and 1&#8217;s, with the understanding that the sum of two bits is their XOR and the product of two bits is their AND. Equivalently, arithmetic is carried out mod 2. So you can compute the matrix-vector product as ordinary integers if you then reduce every integer mod 2.<\/p>\n<p>We will find the matrix <em>M<\/em> that corresponds to the temper operation and prove that it is invertible by finding its inverse. This proves that tempering is invertible, and one could compute the inverse of tempering by multiplying by <em>M<\/em><sup>\u22121<\/sup>, though it would be more efficient to undo temporing by bit twiddling.<\/p>\n<p>One way to recover a matrix is to multiplying by unit vectors <em>e<\/em><sub><em>i<\/em><\/sub> where the <em>i<\/em>th component of <em>e<\/em><sub><em>i<\/em><\/sub> is 1 and the rest of the components are zero. Then<\/p>\n<p style=\"padding-left: 40px;\"><em>M<\/em> <em>e<\/em><sub><em>i<\/em><\/sub><\/p>\n<p>is the\u00a0<em>i<\/em>th column of <em>M<\/em>.<\/p>\n<p>So we can find the\u00a0<em>n<\/em>th column of\u00a0<em>M<\/em> by tempering 1 &lt;&lt;\u00a0<em>n<\/em> = 2<sup><em>n<\/em><\/sup>.<\/p>\n<pre>M = np.zeros((32, 32), dtype=int)\r\nfor i in range(32):\r\n    t = temper(1 &lt;&lt; (31-i))\r\n    s = f\"{t:032b}\"\r\n    for j in range(32):\r\n        M[j, i] = int(s[j])\r\n<\/pre>\n<p>Let&#8217;s generate a random number and compute its tempered form two ways: directly and matrix multiplication.<\/p>\n<pre>x = random.getrandbits(32)\r\ny = temper(x)\r\nprint(f\"{y:032b}\")\r\n\r\nvx = np.array([int(b) for b in f\"{x:032b}\"]) # vector form of x\r\nvy = M @ vx % 2 # vector form of y\r\nprint(\"\".join(str(b) for b in vy))\r\n<\/pre>\n<p>Both produce the same bits:<\/p>\n<pre>10100101100101101100110101000110\r\n10100101100101101100110101000110\r\n<\/pre>\n<p>We can find the matrix representation of the untemper function by inverting the matrix <em>M<\/em>. However, we need to invert it over the field GF(2), not over the integers or reals.<\/p>\n<pre>import galois\r\nGF2 = galois.GF(2)\r\nMinv = np.linalg.inv(GF2(M))\r\n<\/pre>\n<p>Here are visualizations of <em>M<\/em> and its inverse using a black square for a 1 and a white square for a 0.<\/p>\n<p><em>M<\/em>:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-medium\" src=\"https:\/\/www.johndcook.com\/MT_M.png\" width=\"400\" height=\"400\" \/><\/p>\n<p><em>M<\/em><sup>\u22121<\/sup>:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-medium\" src=\"https:\/\/www.johndcook.com\/MT_U.png\" width=\"400\" height=\"400\" \/><\/p>\n<p>The <a href=\"https:\/\/www.johndcook.com\/blog\/2026\/05\/10\/the-linear-algebra-of-bit-twiddling\/\">next post<\/a> will back up and look at the linear algebra of the components that comprise the Mersenne Twister.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The Mersenne Twister (MT) is a random number generator with good statistical properties but bad cryptographic properties. In buzzwords, it&#8217;s a PRNG but not a CSPRNG. This post will show how the internal state of a MT generator can be recovered from its output. We&#8217;ll do this using linear algebra. The bit twiddling approach is [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[9],"tags":[198,118],"class_list":["post-247013","post","type-post","status-publish","format-standard","hentry","category-math","tag-linear-algebra","tag-rng"],"acf":[],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts\/247013","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/comments?post=247013"}],"version-history":[{"count":0,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts\/247013\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/media?parent=247013"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/categories?post=247013"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/tags?post=247013"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}},{"id":247012,"date":"2026-05-08T08:54:18","date_gmt":"2026-05-08T13:54:18","guid":{"rendered":"https:\/\/www.johndcook.com\/blog\/?p=247012"},"modified":"2026-05-08T08:54:18","modified_gmt":"2026-05-08T13:54:18","slug":"calculating-curvature","status":"publish","type":"post","link":"https:\/\/www.johndcook.com\/blog\/2026\/05\/08\/calculating-curvature\/","title":{"rendered":"Calculating curvature"},"content":{"rendered":"<p>Curvature is conceptually simple but usually difficult to calculate. For a level set curve <em>f<\/em>(<em>x<\/em>,\u00a0<em>y<\/em>) = c, such as in the previous couple posts, the equation for curvature is<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter\" style=\"background-color: white;\" src=\"https:\/\/www.johndcook.com\/curvature_implicit.svg\" alt=\"\\kappa = \\frac{\\left|f_y^2f_{xx}-2f_xf_yf_{xy}+f_x^2f_{yy}\\right|}{\\bigl(f_x^2+f_y^2\\bigr)^{3\/2}}\" width=\"238\" height=\"72\" \/><\/p>\n<p>Even when\u00a0<em>f<\/em> has a fairly simple expression, the expression for \u03ba can be complicated.<\/p>\n<p>If we define<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter\" style=\"background-color: white;\" src=\"https:\/\/www.johndcook.com\/equilateral_triangle_contours.svg\" alt=\"f(x, y) = y^3 - 3 x^2 - 3 y^2 - 3 x^2 y\" width=\"255\" height=\"22\" \/><\/p>\n<p>then the level set of <em>f<\/em>(<em>x<\/em>,\u00a0<em>y<\/em>) = c is an equilateral triangle when\u00a0<em>c<\/em> = \u22124. The level sets are smoothed triangles for \u22124 &lt;\u00a0<em>c<\/em> &lt; 0.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-medium\" src=\"https:\/\/www.johndcook.com\/triangle_contours.png\" width=\"400\" height=\"394\" \/><\/p>\n<p>The curvature of these level sets at any point is given by<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter\" style=\"background-color: white;\" src=\"https:\/\/www.johndcook.com\/equilateral_triangle_contours2.svg\" alt=\"\\frac{\\left|2 (y+1) \\left((y-2)^2-3 x^2\\right) \\left(x^2+y^2\\right)\\right|}{\\left(x^4+2 x^2 (y (y+6)+2)+(y-2)^2 y^2\\right)^{3\/2}}\" width=\"322\" height=\"59\" \/><\/p>\n<h2>Simplification<\/h2>\n<p>But there is one instance in which curvature is easy to calculate. For the graph of a function\u00a0<em>g<\/em>(<em>x<\/em>) =\u00a0<em>y<\/em>, the curvature is approximately the absolute value of the second derivative of\u00a0<em>g<\/em>, provided the first derivative is small.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter\" style=\"background-color: white;\" src=\"https:\/\/www.johndcook.com\/curvature_approx.svg\" alt=\" \\kappa = \\frac{|g^{\\prime\\prime}(x)|}{(1 + g^\\prime(x))^{3\/2}} \\approx |g^{\\prime\\prime}(x)|\" width=\"228\" height=\"47\" \/><\/p>\n<p>At a local maximum or local minimum of\u00a0<em>g<\/em>(<em>x<\/em>) the approximation is exact because the first derivative is zero.<\/p>\n<h2>Max and min curvature of smoothed triangles<\/h2>\n<p>This means that in the example above, we can calculate the maximum and minimum curvature of the level sets. The maximum curvature occurs in the corners and the minimum occurs in the middle of the sides. By symmetry there are three maxima and three minima, but we can take the ones corresponding to\u00a0<em>x<\/em> = 0 for convenience. There we find the curvature is simply<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter\" style=\"background-color: white;\" src=\"https:\/\/www.johndcook.com\/curvature_approx2.svg\" alt=\"|6 + 6y|\" width=\"59\" height=\"18\" \/><\/p>\n<p>When\u00a0<em>x<\/em> = 0, we have<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter\" style=\"background-color: white;\" src=\"https:\/\/www.johndcook.com\/curvature_approx3.svg\" alt=\"f(x,y) = c = y^3 - 3y^2\" width=\"175\" height=\"22\" \/><\/p>\n<p>and so the maximum and minimum curvature are the two roots of the cubic equation for\u00a0<em>y<\/em> that lie in the interval [\u22121, 2]. (There&#8217;s another root greater than 2.)<\/p>\n<p>For example, when <em>c<\/em> = \u22123, the roots are 0.8794, 1.3473, and 2.5321. The first root corresponds to the minimum curvature, the second to the maximum, and the third is outside our region of interest. It follows that the minimum curvature is 0.7237 and the maximum is 14.0838.<\/p>\n<p>When <em>c<\/em> = \u22121 the minimum and maximum curvature are 2.80747 and 9.91622 respectively,<\/p>\n<p>Since <em>c<\/em> = \u22124 corresponds to the triangle, the minimum curvature is 0 and the maximum is infinite. As <em>c<\/em> increases, the minimum and maximum curvature come together because the level set is becoming more round.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Curvature is conceptually simple but usually difficult to calculate. For a level set curve f(x,\u00a0y) = c, such as in the previous couple posts, the equation for curvature is Even when\u00a0f has a fairly simple expression, the expression for \u03ba can be complicated. If we define then the level set of f(x,\u00a0y) = c is [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[9],"tags":[48],"class_list":["post-247012","post","type-post","status-publish","format-standard","hentry","category-math","tag-differential-geometry"],"acf":[],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts\/247012","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/comments?post=247012"}],"version-history":[{"count":0,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts\/247012\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/media?parent=247012"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/categories?post=247012"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/tags?post=247012"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}},{"id":247009,"date":"2026-05-07T13:19:24","date_gmt":"2026-05-07T18:19:24","guid":{"rendered":"https:\/\/www.johndcook.com\/blog\/?p=247009"},"modified":"2026-05-08T08:48:09","modified_gmt":"2026-05-08T13:48:09","slug":"smoothed-polygons","status":"publish","type":"post","link":"https:\/\/www.johndcook.com\/blog\/2026\/05\/07\/smoothed-polygons\/","title":{"rendered":"Smoothed polygons"},"content":{"rendered":"<p>The previous post constructed a triangular analog of the squircle, the unit circle in the\u00a0<em>p<\/em>-norm where\u00a0<em>p<\/em> is typically around 4. The case\u00a0<em>p<\/em> = 2 is a Euclidean circle and the limit as <em>p<\/em> \u2192 \u221e is a Euclidean square.<\/p>\n<p>The previous post introduced three functions <em>L<\/em><sub><em>i<\/em><\/sub>(<em>x<\/em>,\u00a0<em>y<\/em>) such that the level set of each function<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter\" style=\"background-color: white;\" src=\"https:\/\/www.johndcook.com\/trisqr4.svg\" alt=\"\\{(x,y) \\mid L_i(x,y) = 1\\}\" width=\"170\" height=\"18\" \/><\/p>\n<p>forms a side of a triangle. Then it introduced a soft penalty for each <em>L<\/em> being away 1, and the level sets of that penalty function formed the rounded triangles we were looking for.<\/p>\n<p>Another approach would be to change the\u00a0<em>L<\/em>&#8216;s slightly so that the sides are the levels sets <em>L<\/em><sub><em>i<\/em><\/sub>(<em>x<\/em>,\u00a0<em>y<\/em>) = 0. The advantage to this formulation is that the product of three numbers is 0 if and only if one of the numbers is zero. That means if we define<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter\" style=\"background-color: white;\" src=\"https:\/\/www.johndcook.com\/trisqr5.svg\" alt=\"f(x,\u00a0y) = L_1(x,\u00a0y) L_2(x,\u00a0y) L_3(x,\u00a0y)\" width=\"284\" height=\"18\" \/><br \/>\nthen the set of points<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter\" style=\"background-color: white;\" src=\"https:\/\/www.johndcook.com\/trisqr6.svg\" alt=\"\\{(x, y) \\mid f(x,y) = c\\}\" width=\"165\" height=\"18\" \/><\/p>\n<p>corresponds to the three lines when <em>c<\/em> = 0 and the level sets\u00a0for small\u00a0<em>c<\/em> &gt; 0 are nearly triangles. The level sets will be smooth if the gradient is non-zero, i.e. <em>c<\/em> is not zero.<\/p>\n<p><strong>This approach is not unique to triangles<\/strong>. You could create smooth approximations any polygon by multiplying linear functions that define the sides. Or you could do something similar with curved arcs.<\/p>\n<p>If we define our\u00a0<em>L&#8217;<\/em>s by<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter\" style=\"background-color: white;\" src=\"https:\/\/www.johndcook.com\/trisqr7.svg\" alt=\"\\begin{align*} L_1(x,y) &amp;= y\\\\ L_2(x,y) &amp;= y - 2x - 2 \\\\ L_3(x,y) &amp;= 3y + x -3\\\\ \\end{align*}\" width=\"169\" height=\"77\" \/><\/p>\n<p>then our curves will be the level sets of<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter\" style=\"background-color: white;\" src=\"https:\/\/www.johndcook.com\/trisqr8.svg\" alt=\"f(x, y) = y(y - 2 x - 2)\u00a0 (3 y + x - 3)\" width=\"290\" height=\"18\" \/><\/p>\n<p>A few level sets are shown below. The level set for\u00a0<em>c<\/em> = 0 is the straight lines.<\/p>\n<p>Note the level sets are not connected. If you&#8217;re interested in approximate triangles, you want the components that are inside the triangle.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-medium\" src=\"https:\/\/www.johndcook.com\/trisqr3.png\" width=\"600\" height=\"320\" \/><\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The previous post constructed a triangular analog of the squircle, the unit circle in the\u00a0p-norm where\u00a0p is typically around 4. The case\u00a0p = 2 is a Euclidean circle and the limit as p \u2192 \u221e is a Euclidean square. The previous post introduced three functions Li(x,\u00a0y) such that the level set of each function forms [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[9],"tags":[],"class_list":["post-247009","post","type-post","status-publish","format-standard","hentry","category-math"],"acf":[],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts\/247009","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/comments?post=247009"}],"version-history":[{"count":0,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts\/247009\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/media?parent=247009"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/categories?post=247009"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/tags?post=247009"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}},{"id":247007,"date":"2026-05-06T14:53:29","date_gmt":"2026-05-06T19:53:29","guid":{"rendered":"https:\/\/www.johndcook.com\/blog\/?p=247007"},"modified":"2026-05-06T14:53:29","modified_gmt":"2026-05-06T19:53:29","slug":"triangular-analog-of-the-squircle","status":"publish","type":"post","link":"https:\/\/www.johndcook.com\/blog\/2026\/05\/06\/triangular-analog-of-the-squircle\/","title":{"rendered":"Triangular analog of the squircle"},"content":{"rendered":"<p>TimF left a comment on my <a href=\"https:\/\/www.johndcook.com\/blog\/2026\/05\/03\/guitar-pick\/\">guitar pick post<\/a> saying the image was a &#8220;squircle-ish analog for an isosceles triangle.&#8221; That made me wonder what a more direct analog of the squircle might be for a triangle.<\/p>\n<p>A squircle is not exactly a square with rounded corners. The sides are continuously curved, but curved most at the corners. See, for example, <a href=\"https:\/\/www.johndcook.com\/blog\/2018\/02\/13\/squircle-curvature\/\">this post<\/a>.<\/p>\n<p>Suppose the sides of our triangle are given by <em>L<\/em><sub>1<\/sub>(<em>x<\/em>, <em>y<\/em>) = 1 for <em>i<\/em> = 1, 2, 3. For example, <\/p>\n<p><img class='aligncenter' src='https:\/\/www.johndcook.com\/trisqr1.svg' alt='\\begin{align*}\nL_1(x, y) &#038;= y \\\\\nL_2(x, y) &#038;= \\phantom{-}\\frac{\\sqrt{3}}{2}x - \\frac{1}{2}y \\\\\nL_3(x, y) &#038;= -\\frac{\\sqrt{3}}{2}x - \\frac{1}{2}y \\\\\n\\end{align*}\n' style='background-color:white' height='127' width='182' \/><\/p>\n<p>We design a function <em>f<\/em>(<em>x<\/em>, <em>y<\/em>) as a soft penalty for points not being on one of the sides and look at the set of points <em>f<\/em>(<em>x<\/em>, <em>y<\/em>) = 1.<\/p>\n<p><img class='aligncenter' src='https:\/\/www.johndcook.com\/trisqr2.svg' alt='f(x, y) = \\left( \\sum_{i=1}^3 \\exp(p (L_i(x, y) - 1)) \\right)^{1\/p}' style='background-color:white' height='65' width='310' \/><\/p>\n<p>You might recognize this as a Lebesgue norm, analogous to the one used to define a squircle.<\/p>\n<p>The larger <em>p<\/em> is, the heavier the penalty for being far from a side and the closer the level set <em>f<\/em>(<em>x<\/em>, <em>y<\/em>) = 1 comes to being a triangle.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.johndcook.com\/triangular_squircle.png\" width=\"600\" height=\"600\" class=\"aligncenter size-medium\" \/><\/p>\n","protected":false},"excerpt":{"rendered":"<p>TimF left a comment on my guitar pick post saying the image was a &#8220;squircle-ish analog for an isosceles triangle.&#8221; That made me wonder what a more direct analog of the squircle might be for a triangle. A squircle is not exactly a square with rounded corners. The sides are continuously curved, but curved most [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[9],"tags":[],"class_list":["post-247007","post","type-post","status-publish","format-standard","hentry","category-math"],"acf":[],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts\/247007","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/comments?post=247007"}],"version-history":[{"count":0,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts\/247007\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/media?parent=247007"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/categories?post=247007"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/tags?post=247007"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}},{"id":247006,"date":"2026-05-06T11:32:20","date_gmt":"2026-05-06T16:32:20","guid":{"rendered":"https:\/\/www.johndcook.com\/blog\/?p=247006"},"modified":"2026-05-06T11:32:20","modified_gmt":"2026-05-06T16:32:20","slug":"unified-config-files","status":"publish","type":"post","link":"https:\/\/www.johndcook.com\/blog\/2026\/05\/06\/unified-config-files\/","title":{"rendered":"Unified config files"},"content":{"rendered":"<p>I try to maintain a consistent work environment across computers that I use. The computers differ for important reasons, but I&#8217;d rather they not differ for unimportant reasons.<\/p>\n<h2>Unified keys<\/h2>\n<p>One thing I do is remap keys so that the same key does the same thing everywhere, to the extent that&#8217;s practical. This requires <a href=\"https:\/\/www.johndcook.com\/blog\/2022\/08\/17\/cross-platform-muscle-memory\/\">remapping keys<\/a>. In particular, I want the key <b>functionality<\/b>, not the key <b>name<\/b>, to be the same across operating systems. For example, the Command key on a Mac does what the Control key does on Windows and Linux. I have my machines set up so the key in the lower left corner, whatever you call it, does things like copy, paste, and cut.<\/p>\n<h2>Unified config files<\/h2>\n<p>I use bash everywhere even though zshell is the default on Mac OS. But Linux and MacOS are sufficiently different that I have two <code>.bashrc<\/code> files, one for each OS. However, both <code>.bashrc<\/code> files source a common file <code>.bash_aliases<\/code> for aliases. You can set that up by putting the following code in your <code>.bashrc<\/code> file.<\/p>\n<pre>if [ -f ~\/.bash_aliases ]; then\r\n    . ~\/.bash_aliases\r\nfi\r\n<\/pre>\n<p>Sometimes you need some kind of branching logic even for two machines running the same OS. For example, if you have two computers, named Castor and Pollux, you might have code like the following in your <code>.bashrc<\/code>.<\/p>\n<pre>HOSTNAME_SHORT=$(hostname -s)\r\nif [ \"$HOSTNAME_SHORT\" = \"Castor\" ]; then\r\n...\r\nelif [ \"$HOSTNAME_SHORT\" = \"Pollux\" ]; then\r\n...\r\n<\/pre>\n<p>One problem with using hostname is that the OS can change the name on you. At least MacOS will do this; I don&#8217;t know whether other operating systems will. If you give your machine an uncommon name then this is unlikely to happen.<\/p>\n<p>I have one <code>init.el<\/code> file for Emacs. It contains some branching logic for OS:<\/p>\n<pre>(cond\r\n    ((string-equal system-type \"windows-nt\") ; Microsoft Windows\r\n     ...)\r\n    ((string-equal system-type \"gnu\/linux\") ; linux\r\n     ...)\r\n    ((string-equal system-type \"darwin\") ; Mac\r\n     ...)\r\n)\r\n<\/pre>\n<p>You can also branch on hostname.<\/p>\n<pre>(if (string-equal (system-name) \"Castor\")\r\n...)\r\n(if (string-equal (system-name) \"Pollux\")\r\n...)\r\n<\/pre>\n<p>This isn&#8217;t difficult, but in the short run it&#8217;s easier to just make one ad hoc edit to a config file at a time, letting the files drift apart. Along those lines, see <a href=\"https:\/\/www.johndcook.com\/blog\/2012\/08\/01\/bicycle-skills\/\">bicycle skills<\/a>. <\/p>\n","protected":false},"excerpt":{"rendered":"<p>I try to maintain a consistent work environment across computers that I use. The computers differ for important reasons, but I&#8217;d rather they not differ for unimportant reasons. Unified keys One thing I do is remap keys so that the same key does the same thing everywhere, to the extent that&#8217;s practical. This requires remapping [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[5],"tags":[],"class_list":["post-247006","post","type-post","status-publish","format-standard","hentry","category-computing"],"acf":[],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts\/247006","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/comments?post=247006"}],"version-history":[{"count":0,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts\/247006\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/media?parent=247006"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/categories?post=247006"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/tags?post=247006"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}},{"id":247005,"date":"2026-05-06T06:37:42","date_gmt":"2026-05-06T11:37:42","guid":{"rendered":"https:\/\/www.johndcook.com\/blog\/?p=247005"},"modified":"2026-05-06T06:37:42","modified_gmt":"2026-05-06T11:37:42","slug":"category-mythology","status":"publish","type":"post","link":"https:\/\/www.johndcook.com\/blog\/2026\/05\/06\/category-mythology\/","title":{"rendered":"The mythology of category theory"},"content":{"rendered":"<p>Yesterday a friend and I had a conversation about category theory, how it can be a useful pattern description language, but also about how people have unrealistic expectations for it, believing category theory can deliver something for nothing.<\/p>\n<p>Later I ran across the following <a href=\"https:\/\/x.com\/qiaochuyuan\/status\/2051833309962088537?s=46\">post<\/a> from Qiaochu Yuan. It felt as if he had overheard my conversation and summarized it in a tweet:<\/p>\n<blockquote><p>category theory is just some straightforwardly useful stuff for some purposes in some fields! you can elegantly simplify and streamline some proofs. then there is the mythology of category theory, which is some other thing entirely, mostly wishful thinking and projection afaict<\/p><\/blockquote>\n<p>His phrase &#8220;the mythology of category theory&#8221; gives a name to this idea that category theory can deliver specific outputs without specific inputs. It helps to distinguish CT as a scrapbook of patterns from CT as sorcery.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Yesterday a friend and I had a conversation about category theory, how it can be a useful pattern description language, but also about how people have unrealistic expectations for it, believing category theory can deliver something for nothing. Later I ran across the following post from Qiaochu Yuan. It felt as if he had overheard [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[9],"tags":[144],"class_list":["post-247005","post","type-post","status-publish","format-standard","hentry","category-math","tag-category-theory"],"acf":[],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts\/247005","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/comments?post=247005"}],"version-history":[{"count":0,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts\/247005\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/media?parent=247005"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/categories?post=247005"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/tags?post=247005"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}},{"id":247003,"date":"2026-05-05T17:45:26","date_gmt":"2026-05-05T22:45:26","guid":{"rendered":"https:\/\/www.johndcook.com\/blog\/?p=247003"},"modified":"2026-05-05T18:36:26","modified_gmt":"2026-05-05T23:36:26","slug":"changing-one-character-in-a-pdf","status":"publish","type":"post","link":"https:\/\/www.johndcook.com\/blog\/2026\/05\/05\/changing-one-character-in-a-pdf\/","title":{"rendered":"Changing one character in a PDF"},"content":{"rendered":"<p>I saw a <a href=\"https:\/\/x.com\/kjoshanssen\/status\/2051778800934150397?s=20\">post<\/a> on X saying<\/p>\n<p style=\"padding-left: 40px;\">Changing a hyphen to an en-dash increases your PDF file size by ~10 bytes.<\/p>\n<p>My first thought was that it had something to do with hyphen being an ASCII character and an en-dash not. Changing a hyphen to an en-dash would make a UTF-8 encoded text file a couple bytes longer. (See why <a href=\"https:\/\/www.johndcook.com\/blog\/2019\/09\/09\/how-utf-8-works\/\">here<\/a>.) Maybe adding one non-ASCII character could cause the file to include a glyph it didn&#8217;t before.<\/p>\n<p>I did a couple experiments. I made a minimal LaTeX file with only the text<\/p>\n<pre>    See pages 9-10.<\/pre>\n<p>and another with<\/p>\n<pre>    See pages 9--10.<\/pre>\n<p>(In LaTeX, a hyphen compiles to a hyphen and two hyphens compile to an en-dash.)<\/p>\n<p>I compiled both files using <code>pdflatex<\/code>. The PDF with the hyphen was 13172 bytes and the one with the en-dash was 13099 bytes. Replacing the hyphen with the en-dash made the file 73 bytes <b>smaller<\/b>.<\/p>\n<p>I repeated the experiment with a Libre Office ODT document. Changing the hyphen to an en-dash reduced the file size from 13548 bytes to 13514 bytes.<\/p>\n<p>Then I went back to LaTeX and pasted a paragraph of lorem ipsum text into both files. Now the file with the hyphen produced a 18131 byte PDF and the file with the en-dash produced a 18203 byte PDF. So in this instance changing the hyphen to an en-dash <b>increased<\/b> the size of the file by 72 bytes.<\/p>\n<p>After adding the lorem ipsum text to the ODT files, the PDF resulting from the file with the hyphen was 23 bytes larger, 17846 bytes versus 17823 bytes.<\/p>\n<p>PDFs are complicated. Changing one character can make the file bigger or smaller, by an unpredictable amount. It depends, among other things, on what software was used to create the PDF. Even changing one letter in an all-ASCII text can change the size of the PDF, I suppose due to some internal text compression and aesthetic control characters. I don&#8217;t pretend to understand what&#8217;s going on inside a PDF.<\/p>\n<h2>Related posts<\/h2>\n<ul>\n<li class=\"link\"><a href=\"https:\/\/www.johndcook.com\/blog\/2025\/10\/31\/smaller-qr-codes\/\">How text case can change QR code size<\/a><\/li>\n<li class=\"link\"><a href=\"https:\/\/www.johndcook.com\/blog\/2011\/12\/30\/handyman-lorem-ipsum\/\">Handyman lorem ipsum<\/a><\/li>\n<li class=\"link\"><a href=\"https:\/\/www.johndcook.com\/blog\/2024\/02\/08\/pdf-forensics\/\">PDF metadata<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>I saw a post on X saying Changing a hyphen to an en-dash increases your PDF file size by ~10 bytes. My first thought was that it had something to do with hyphen being an ASCII character and an en-dash not. Changing a hyphen to an en-dash would make a UTF-8 encoded text file a [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[],"class_list":["post-247003","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"acf":[],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts\/247003","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/comments?post=247003"}],"version-history":[{"count":0,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts\/247003\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/media?parent=247003"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/categories?post=247003"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/tags?post=247003"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}},{"id":246998,"date":"2026-05-03T15:49:41","date_gmt":"2026-05-03T20:49:41","guid":{"rendered":"https:\/\/www.johndcook.com\/blog\/?p=246998"},"modified":"2026-05-03T18:24:56","modified_gmt":"2026-05-03T23:24:56","slug":"guitar-pick","status":"publish","type":"post","link":"https:\/\/www.johndcook.com\/blog\/2026\/05\/03\/guitar-pick\/","title":{"rendered":"The shape of a guitar pick"},"content":{"rendered":"<p>I saw a <a href=\"https:\/\/x.com\/sculd_88\/status\/2050841316435841231\">post<\/a> on X that plotted the function<\/p>\n<p style=\"padding-left: 40px;\">(log\u00a0<em>x<\/em>)\u00b2 + (log\u00a0<em>y<\/em>)\u00b2 = 1.<\/p>\n<p>Of course the plot of<\/p>\n<p style=\"padding-left: 40px;\"><em>x<\/em>\u00b2 + <em>y<\/em>\u00b2 = 1<\/p>\n<p>is a circle, but I never thought what taking logs would do to the shape.<\/p>\n<p>Here&#8217;s what the contours look like setting the right hand side equal to 1, 2, 3, \u2026, 10.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-medium\" src=\"https:\/\/www.johndcook.com\/logcircle.png\" width=\"360\" height=\"363\" \/><\/p>\n<pre>ContourPlot[Log[x]^2 + Log[y]^2, {x, 0, 10}, {y, 0, 10}, \r\n    Contours -&gt; Range[10]]<\/pre>\n<p>The dark blue contour near the origin reminded me of a guitar pick, so I decided to take a stab at creating an equation for the shape of a guitar pick.<\/p>\n<p>I wanted to rotate the image so the axis of symmetry for the pick is vertical, so I replaced <em>x<\/em> and\u00a0<em>y<\/em> with\u00a0<em>x<\/em> +\u00a0<em>y<\/em> and\u00a0<em>x<\/em> \u2212\u00a0<em>y<\/em>.<\/p>\n<p>The aspect ratio was too wide, so I experimented with<\/p>\n<p style=\"padding-left: 40px;\">log(<em>y<\/em> +\u00a0<em>kx<\/em>)\u00b2 + log(<em>y<\/em> \u2212\u00a0<em>kx<\/em>)\u00b2 =\u00a0<em>r<\/em>\u00b2<\/p>\n<p>where increasing\u00a0<em>k<\/em> increases the height-to-width ratio. After a little experimentation I settled on\u00a0<em>k<\/em> = 1.5 and\u00a0<em>r<\/em> = 1.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-medium\" src=\"https:\/\/www.johndcook.com\/logcircle2.png\" width=\"360\" height=\"360\" \/><\/p>\n<p>This has an aspect ratio of roughly 5:4, which is about what I measured from a photo of a guitar pick.<\/p>\n<h2>Updating: refining the fit<\/h2>\n<p>After posting this article on X, Paul Graham <a href=\"https:\/\/x.com\/paulg\/status\/2051047606114005425?s=20\">replied<\/a> with a photo of a Fender guitar pick with the image above overlaid. The fit was fairly good, but the aspect ratio wasn&#8217;t quite right.<\/p>\n<p>So then I did a little research. The shape referred to in this post is known as the &#8220;351,&#8221; but even for the 351 shape the aspect ratio varies slightly between picks.<\/p>\n<p>Setting\u00a0<em>k<\/em> = 1.6 gives a better fit to Paul Graham&#8217;s pick.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-medium\" src=\"https:\/\/www.johndcook.com\/pgpick.png\" width=\"360\" height=\"422\" \/><\/p>\n<p>The blue line represents my fit using <em>k<\/em> = 1.5 and the red line represents my fit using <em>k<\/em> = 1.6.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I saw a post on X that plotted the function (log\u00a0x)\u00b2 + (log\u00a0y)\u00b2 = 1. Of course the plot of x\u00b2 + y\u00b2 = 1 is a circle, but I never thought what taking logs would do to the shape. Here&#8217;s what the contours look like setting the right hand side equal to 1, 2, [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[9],"tags":[],"class_list":["post-246998","post","type-post","status-publish","format-standard","hentry","category-math"],"acf":[],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts\/246998","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/comments?post=246998"}],"version-history":[{"count":0,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts\/246998\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/media?parent=246998"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/categories?post=246998"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/tags?post=246998"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}},{"id":246995,"date":"2026-04-30T19:06:12","date_gmt":"2026-05-01T00:06:12","guid":{"rendered":"https:\/\/www.johndcook.com\/blog\/?p=246995"},"modified":"2026-04-30T19:14:12","modified_gmt":"2026-05-01T00:14:12","slug":"burmanns-theorem","status":"publish","type":"post","link":"https:\/\/www.johndcook.com\/blog\/2026\/04\/30\/burmanns-theorem\/","title":{"rendered":"Approximating even functions by powers of cosine"},"content":{"rendered":"<p>A couple days ago I wrote a post about t<a href=\"https:\/\/www.johndcook.com\/blog\/2026\/04\/28\/even-series-trick\/\">urning a trick into a technique<\/a>, finding another use for a clever way to construct simple, accurate approximations. I used as my example approximating the Bessel function <em>J<\/em>(<em>x<\/em>) with (1 + cos(x))\/2. I learned via a helpful <a href=\"https:\/\/mathstodon.xyz\/@tpfto\/116484963022062150\">comment<\/a> on Mathstodon that my approximation was the first-order part of a more general series<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter\" style=\"background-color: white;\" src=\"https:\/\/www.johndcook.com\/burmann1.svg\" alt=\"J_0(x) = 1 + \\frac{\\cos(x) - 1}{2} - \\frac{(\\cos(x) - 1)^2}{48} + \\frac{7(\\cos(x) - 1)^3}{1440} + \\cdots\" width=\"504\" height=\"44\" \/><\/p>\n<p>The first-order approximation has error <em>O<\/em>(<em>x<\/em><sup>4<\/sup>), as shown in the earlier post. Adding the second-order term makes the error <em>O<\/em>(<em>x<\/em><sup>6<\/sup>), and adding the third-order term makes it <em>O<\/em>(<em>x<\/em><sup>8<\/sup>).<\/p>\n<p>I&#8217;ve written a few times about cosine approximations to the normal probability density. For example, see <a href=\"https:\/\/www.johndcook.com\/blog\/2023\/07\/05\/normal-approximation\/\">this post<\/a>. We could use the same idea as the series above to approximate the normal density with a series of powers of cosine. This gives us<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter\" style=\"background-color: white;\" src=\"https:\/\/www.johndcook.com\/burmann2.svg\" alt=\"\\exp(-x^2\/2) = 1 + (\\cos(x) - 1) + \\frac{(\\cos(x) - 1)^2}{3} + \\frac{2(\\cos(x) - 1)^3}{45} + \\cdots\" width=\"569\" height=\"44\" \/><\/p>\n<p>and as before, the first, second, and third order truncated series have error <em>O<\/em>(<em>x<\/em><sup>4<\/sup>), <em>O<\/em>(<em>x<\/em><sup>6<\/sup>), and <em>O<\/em>(<em>x<\/em><sup>8<\/sup>).<\/p>\n<p>The general theory behind what&#8217;s going on here is an extension of <a href=\"https:\/\/mathworld.wolfram.com\/BuermannsTheorem.html\">B\u00fcrmann&#8217;s theorem<\/a>. The original version of the theorem relies on a series inversion theorem that in turn relies on the approximating function, in our case cos(<em>x<\/em>) \u2212 1, not having zero derivative at the center of the series. But there is a more general form of B\u00fcrmann&#8217;s theorem based on a <a href=\"https:\/\/www.johndcook.com\/blog\/2025\/09\/21\/mollweide-for-high-latitudes\/\">more general form of series inversion<\/a>. We will always need a more general version of the theorem when working with even functions because even functions have zero derivative at zero.<\/p>\n<p>Here&#8217;s another example, this time using the Bessel function <em>J<\/em><sub>1<\/sub>, an odd function, which does use the original version of B\u00fcrmann&#8217;s theorem to approximate <em>J<\/em><sub>1<\/sub>\u00a0by powers of sine.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter\" style=\"background-color: white;\" src=\"https:\/\/www.johndcook.com\/burmann3.svg\" alt=\"J_1(x) = \\frac{1}{2} \\sin(x) + \\frac{1}{48} \\sin^3(x) + \\frac{17}{1920} \\sin^5(x) + \\cdots\" width=\"406\" height=\"41\" \/><\/p>\n<p>In this case truncating the series after sin<sup><em>k<\/em><\/sup>(<em>x<\/em>) gives an error <em>O<\/em>(<em>x<\/em><sup><em>k<\/em> + 2<\/sup>).<\/p>\n<p>You can find more on B\u00fcrmann&#8217;s theorem in <a href=\"https:\/\/www.johndcook.com\/blog\/2022\/07\/20\/whittaker-and-watson\/\">Whittker and Watson<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>A couple days ago I wrote a post about turning a trick into a technique, finding another use for a clever way to construct simple, accurate approximations. I used as my example approximating the Bessel function J(x) with (1 + cos(x))\/2. I learned via a helpful comment on Mathstodon that my approximation was the first-order [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[9],"tags":[129],"class_list":["post-246995","post","type-post","status-publish","format-standard","hentry","category-math","tag-special-functions"],"acf":[],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts\/246995","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/comments?post=246995"}],"version-history":[{"count":0,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts\/246995\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/media?parent=246995"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/categories?post=246995"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/tags?post=246995"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}]