2025-08-08 04:22:49 -05:00

16 lines
45 KiB
JSON

{
"hash": "a54f152d613af8d0723b2c5f1c17e58c",
"result": {
"engine": "jupyter",
"markdown": "---\ntitle: \"Algebraic Rotations and Stereography\"\ndescription: |\n How do you rotate in 2D and 3D without standard trigonometry?\nformat:\n html:\n html-math-method: katex\njupyter: python3\ndate: \"2021-09-26\"\ndate-modified: \"2025-06-28\"\ncategories:\n - geometry\n - symmetry\n---\n\n<style>\n.figure-img {\n max-width: 512px;\n object-fit: contain;\n height: 100%;\n}\n</style>\n\n\n\nExpressing rotation mathematically can be rather challenging, even in two dimensional space.\nTypically, the subject is approached from complicated-looking rotation matrices,\n with the occasional accompanying comment about complex numbers.\nThe challenge only increases when examining three dimensional space,\n where everyone has different ideas about the best implementations.\nIn what follows, I attempt to provide a derivation of 2D and 3D rotations based in intuition\n and without the use of trigonometry.\n\n\nComplex Numbers: 2D Rotations\n-----------------------------\n\nAs points in the plane, multiplication between complex numbers is frequently analyzed\n as a combination of scaling (a ratio of two scales) and rotation (a point on the complex unit circle).\nHowever, the machinery used in these analyses usually expresses complex numbers in a polar form\n involving the complex exponential.\nThis leverages prior knowledge of trigonometry and some calculus, and in fact obfuscates\n the algebraic properties of complex numbers.\nNeither concept is truly necessary to understand points on the complex unit circle,\n or complex number multiplication in general.\n\n\n### A review of complex multiplication\n\nA complex number $z = a + bi$ multiplies with another complex number\n $w = c + di$ in the obvious way: by distributing over addition.\n\n$$\nzw = (a + bi)(c + di) = ac + bci + adi + bdi^2 = (ac - bd) + i(ad + bc)\n$$\n\n*z* also has associated to it a conjugate $z^* = a - bi$.\nThe product $zz^*$ is the *norm* $a^2 + b^2$, a positive real quantity whose square root\n is typically called the *magnitude* of the complex number.\nTaking the square root is frequently unnecessary, as many of the properties of\n this quantity do not depend on this normalization.\n\nOne such property is that for two complex numbers *z* and *w*,\n the norm of the product is the product of the norm.\n\n$$\n\\begin{align*}\n (zw)(zw)^* &= (ac - bd)^2 + (ad + bc)^2\n \\\\\n &= a^2c^2 - 2abcd + b^2d^2 + a^2d^2 + 2abcd + b^2c^2\n \\\\\n &= a^2c^2 + b^2d^2 + a^2d^2 + b^2c^2\n = a^2(c^2 + d^2) + b^2(c^2 + d^2)\n \\\\\n &= (a^2 + b^2)(c^2 + d^2)\n\\end{align*}\n$$\n\nConjugation also distributes over multiplication, and complex numbers possess multiplicative inverses.\nAlso, the norm of the multiplicative inverse is the inverse of the norm.\n\n$$\n\\begin{gather*}\n z^*w^* = (a - bi)(c - di) = ac - bci - adi + bdi^2\n \\\\\n = (ac - bd) - i(ad + bc) = (zw)^{*}\n \\\\\n {1 \\over z} = {z^{*} \\over z z^{*}} = {a - bi \\over a^2 + b^2}\n \\\\[14pt]\n \\left({1 \\over z}\\right)\n \\left({1 \\over z}\\right)^{*}\n = {1 \\over zz^{*}} = {1 \\over a^2 + b^2}\n\\end{gather*}\n$$\n\nA final interesting note about complex multiplication is the product $z^{*} w$\n\n$$\nz^{*} w = (a - bi)(c + di) = ac - bci + adi - bdi^2 = (ac + bd) + i(ad - bc)\n$$\n\nThe real part is the same as the dot product $(a, c) \\cdot (b, d)$ and the imaginary part\n looks like the determinant of $\\begin{pmatrix}a & b \\\\ c & d\\end{pmatrix}$.\nThis shows the inherent value of complex arithmetic, as both of these quantities\n have important interpretations in vector algebra.\n\n\n### Onto the Circle\n\nIf $zz^{*} = a^2 + b^2 = 1$, then *z* lies on the complex unit circle.\nSince the norm is 1, a general complex number *w* has the same norm as its product with *z*, *zw*.\nSpecifically, if *w* is 1, multiplication by *z* can be seen as the rotation that maps 1 to *z*.\nBut how do you describe a point on the unit circle?\n\nSimple.\nFor any complex *w*, there are three other complex numbers that are guaranteed\n to share its norm: $-w, w^{*}$, and $-w^{*}$.\nDividing any one of these numbers by another results in a complex number on the unit circle.\nDividing *w* by -*w* is boring, since that just results in -1.\nHowever, division by $w^*$ turns out to be important:\n\n$$\n\\begin{align*}\n z &= {w \\over w^*} =\n \\left( {c + di \\over c - di} \\right)\n \\left( {c + di \\over c + di} \\right) = 1\n \\\\[8pt]\n &= {(c^2 - d^2) + (2cd)i \\over c^2 + d^2}\n\\end{align*}\n$$\n\nDividing the numerator and denominator of this expression through by $c^2$ yields an expression\n in $t = d/c$, where t can be interpreted as the slope of the line joining 0 and *w*.\nSlopes range from $-\\infty$ to $\\infty$, which means that the variable *t* does as well.\n\n$$\n\\begin{align*}\n {(c^2 - d^2) + (2cd)i \\over c^2 + d^2}\n &= {1/c^2 \\over 1/c^2} \\cdot {(c^2 - d^2) + (2cd)i \\over c^2 + d^2}\n \\\\\n &= {(1 - d^2/c^2) + (2d/c)i \\over 1 + d^2/c^2}\n = {(1 - t^2) + (2t)i \\over 1 + t^2}\n \\\\\n &= {1 - t^2 \\over 1 + t^2} + i{2t \\over 1 + t^2} = z(t)\n\\end{align*}\n$$\n\nThis gives us a point *z* on the unit circle.\nIts reciprocal is:\n\n$$\n{1 \\over z} = {z^{*} \\over zz^{*}} = z^{*}\n$$\n\nApplying the same operation to *z* as we did to *w* gives us $z / z^{*} = z^2$,\n or as an action on points, the rotation from 1 to $z^2$.\n\nThis demonstrates the decomposition of the rotation into two parts.\nFor a general complex number *w* which may not lie on the unit circle, multiplication by\n this number dilates the complex plane as well rotating 1 toward the direction of *w*.\nThen, division by $w^{*}$ undoes the dilation and performs the rotation again.\nGeneral (i.e., non-unit) rotations *must* occur in pairs.\n\n\n### Two Halves, Twice Over\n\nPlugging in -1, 0, and 1 for *t* reveals some interesting behavior:\n $z(0) = 1 + 0i$, $z(1) = 0 + 1i$, and $z(-1) = 0 - 1i$.\nAssuming continuity, this shows that $t \\in (-1, 1)$ plots the semicircle in the right half-plane.\nThe other half of the circle comes from *t* with magnitude greater than 1,\n and the point where $z(t) = -1$ exists if we admit the extra point $t = \\pm \\infty$.\n\nIs it possible to get the other half into a nicer interval?\nNo problem, just double the rotation.\n\n$$\n\\begin{align*}\n z(t)^2 &= (\\Re[z]^2 - \\Im[z]^2) + i(2\\Re[z]\\Im[z])\n \\\\[8pt]\n &= {(1 - t^2)^2 - (2t)^2 \\over (1 + t^2)^2} + i{2(1 - t^2)(2t) \\over (1 + t^2)^2}\n \\\\[8pt]\n &= {1 - 6t^2 + t^4 \\over 1 + 2t^2 + t^4}\n + i {4t - 4t^3 \\over 1 + 2t^2 + t^4}\n\\end{align*}\n$$\n\nNow $z(-1)^2 = z(1)^2 = -1$ and the entire circle fits in the interval \\[-1, 1\\].\nThis action of squaring *z* means that as *t* ranges from $-\\infty$ to $\\infty$, the point\n $z^2$ loops around the circle twice, since the unit rotation *z* is applied twice.\n\n\n### Plotting Transcendence\n\nLet's go on a brief tangent and compare these expressions to their\n transcendental trigonometric counterparts.\nGraphing the real and imaginary parts separately shows how much they resemble\n $\\cos(\\pi t)$ and $\\sin(\\pi t)$ around zero[^1].\n\n[^1]: An approximation of a function as the ratio of two polynomials is called a\n [Padé approximant](https://en.wikipedia.org/wiki/Pad%C3%A9_approximant).\n Specifically, the real part (which approximates cosine) has order \\[4/4\\] and the imaginary part\n (which approximates sine) has order \\[3/4\\].\n\n::: {#129c8e7f .cell layout-ncol='2' execution_count=3}\n\n::: {.cell-output .cell-output-display}\n![](index_files/figure-html/cell-3-output-1.png){}\n:::\n\n::: {.cell-output .cell-output-display}\n![](index_files/figure-html/cell-3-output-2.png){}\n:::\n:::\n\n\nIdeally, the zeroes of the real part should occur at $t = \\pm 1/2$, since $\\cos(\\pm \\pi/2) = 0$.\nInstead, they occur at\n\n$$\n\\begin{align*}\n 0 &= 1 - 6t^2 + t^4\n = (t^2 - 2t - 1)(t^2 + 2t - 1)\n \\\\\n &\\implies t = \\pm {1 \\over \\delta_s} = \\pm(\\sqrt 2 - 1) \\approx \\pm 0.414\n\\end{align*}\n$$\n\nWith a bit of polynomial interpolation, this can be rectified.\nA cubic interpolation between the expected and actual values is given by:\n\n$$\n\\begin{gather*}\n P(x) = ax^3 + bx^2 + cx + d\n \\\\\n \\begin{pmatrix}\n 1 & 0 & 0 & 0 \\\\\n 1 & 1/2 & 1/4 & 1/8 \\\\\n 1 & -1/2 & 1/4 & -1/8 \\\\\n 1 & 1 & 1 & 1\n \\end{pmatrix} \\begin{pmatrix}\n d \\\\\n c \\\\\n b \\\\\n a\n \\end{pmatrix} = \\begin{pmatrix}\n 0 \\\\\n \\sqrt 2 - 1 \\\\\n -\\sqrt 2 + 1 \\\\\n 1\n \\end{pmatrix}\n \\\\\n \\implies \\begin{pmatrix}\n d \\\\\n c \\\\\n b \\\\\n a\n \\end{pmatrix} = \\begin{pmatrix}\n 0 \\\\\n -3 + 8\\sqrt 2/3 \\\\\n 0 \\\\\n 4 - 8\\sqrt 2/3\n \\end{pmatrix}\n \\implies P(x) \\approx 0.229 x^3 + 0.771x\n\\end{gather*}\n$$\n\n::: {}\n\n::: {#92fceeac .cell layout-ncol='2' execution_count=4}\n\n::: {.cell-output .cell-output-display}\n![](index_files/figure-html/cell-4-output-1.png){}\n:::\n\n::: {.cell-output .cell-output-display}\n![](index_files/figure-html/cell-4-output-2.png){}\n:::\n:::\n\n\nThe error over the interval \\[-1, 1\\] in these approximations is around 1% (RMS).\n:::\n\nNotably, this interpolating polynomial is entirely odd, which gives it some symmetry about 0.\nAlong with being a very good approximation, it has the feature that a rotation by an\n *n*^th^ of a turn is about $z(P(1/n))^2$.\nThe approximation be improved further by taking *z* to higher powers and deriving a higher-order\n interpolating polynomial.\n\nIt is impossible to shrink this error to 0 because the derivative of the imaginary part at\n $t = 1$ would be π.\nBut the imaginary part is a rational expression, so its derivative is also a rational expression.\nSince π is transcendental, there must always be some error[^2].\n\n[^2]: Even with the interpolating polynomial, the quantity is a ratio of two algebraic numbers,\n which is also algebraic and not transcendental.\n\nEven though it is arguably easier to calculate, there probably isn't a definite benefit to this approximation.\nFor example,\n\n- Other accurate approximants for sine and cosine can be calculated directly from their power series.\n- There are no inverse functions like `acos` or `asin` to complement these approximations.\n - This isn't that bad, since I have refrained from describing rotations with an angle\n (i.e., the quantity returned by these functions).\n Even so, the inverse functions have their use cases.\n- Trigonometric functions can be hardware-accelerated (at least in some FPUs).\n- If no such hardware exists, approximations of `sin` and `cos` can be calculated by software beforehand\n and stored in a lookup table (which is also probably involved at some stage of the FPU).\n\nElaborating on the latter two points, using a lookup table means evaluation happens in roughly constant time.\nA best-case analysis of the above approximation, given some value *t* is...\n\n\n| Expression | Additions | Multiplications | Divisions |\n|-----------------------------------------------------------|-----------|-----------------|-----------|\n| $p = t \\cdot (0.228763834 \\cdot t \\cdot t + 0.771236166)$ | 1 | 3 | |\n| $q = p \\cdot p$ | | 1 | |\n| $r = 1 + q$ | 1 | | |\n| $c = { 1 - q \\over r}$ | 1 | | 1 |\n| $s = { p + p \\over r}$ | 1 | | 1 |\n| real = $c \\cdot c - s \\cdot s$ | 1 | 2 | |\n| imag = $c \\cdot s + c \\cdot s$ | 1 | 1 | |\n| Total | 6 | 7 | 2 |\n\n...or 15 FLOPs.\n\nOn a more optimistic note, a Monte Carlo test of the above approximation on my computer yields\n promising results when compared with GCC's `libm` implementations of `sin`, `cos`, and `cexp`.\n\n::: {#50127cec .cell execution_count=5}\n\n::: {.cell-output .cell-output-display}\n```{=html}\n<style>pre { line-height: 125%; }\ntd.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }\nspan.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }\ntd.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }\nspan.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }\n.output_html .hll { background-color: #ffffcc }\n.output_html { background: #f8f8f8; }\n.output_html .c { color: #3D7B7B; font-style: italic } /* Comment */\n.output_html .err { border: 1px solid #F00 } /* Error */\n.output_html .k { color: #008000; font-weight: bold } /* Keyword */\n.output_html .o { color: #666 } /* Operator */\n.output_html .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */\n.output_html .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */\n.output_html .cp { color: #9C6500 } /* Comment.Preproc */\n.output_html .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */\n.output_html .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */\n.output_html .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */\n.output_html .gd { color: #A00000 } /* Generic.Deleted */\n.output_html .ge { font-style: italic } /* Generic.Emph */\n.output_html .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */\n.output_html .gr { color: #E40000 } /* Generic.Error */\n.output_html .gh { color: #000080; font-weight: bold } /* Generic.Heading */\n.output_html .gi { color: #008400 } /* Generic.Inserted */\n.output_html .go { color: #717171 } /* Generic.Output */\n.output_html .gp { color: #000080; font-weight: bold } /* Generic.Prompt */\n.output_html .gs { font-weight: bold } /* Generic.Strong */\n.output_html .gu { color: #800080; font-weight: bold } /* Generic.Subheading */\n.output_html .gt { color: #04D } /* Generic.Traceback */\n.output_html .kc { color: #008000; font-weight: bold } /* Keyword.Constant */\n.output_html .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */\n.output_html .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */\n.output_html .kp { color: #008000 } /* Keyword.Pseudo */\n.output_html .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */\n.output_html .kt { color: #B00040 } /* Keyword.Type */\n.output_html .m { color: #666 } /* Literal.Number */\n.output_html .s { color: #BA2121 } /* Literal.String */\n.output_html .na { color: #687822 } /* Name.Attribute */\n.output_html .nb { color: #008000 } /* Name.Builtin */\n.output_html .nc { color: #00F; font-weight: bold } /* Name.Class */\n.output_html .no { color: #800 } /* Name.Constant */\n.output_html .nd { color: #A2F } /* Name.Decorator */\n.output_html .ni { color: #717171; font-weight: bold } /* Name.Entity */\n.output_html .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */\n.output_html .nf { color: #00F } /* Name.Function */\n.output_html .nl { color: #767600 } /* Name.Label */\n.output_html .nn { color: #00F; font-weight: bold } /* Name.Namespace */\n.output_html .nt { color: #008000; font-weight: bold } /* Name.Tag */\n.output_html .nv { color: #19177C } /* Name.Variable */\n.output_html .ow { color: #A2F; font-weight: bold } /* Operator.Word */\n.output_html .w { color: #BBB } /* Text.Whitespace */\n.output_html .mb { color: #666 } /* Literal.Number.Bin */\n.output_html .mf { color: #666 } /* Literal.Number.Float */\n.output_html .mh { color: #666 } /* Literal.Number.Hex */\n.output_html .mi { color: #666 } /* Literal.Number.Integer */\n.output_html .mo { color: #666 } /* Literal.Number.Oct */\n.output_html .sa { color: #BA2121 } /* Literal.String.Affix */\n.output_html .sb { color: #BA2121 } /* Literal.String.Backtick */\n.output_html .sc { color: #BA2121 } /* Literal.String.Char */\n.output_html .dl { color: #BA2121 } /* Literal.String.Delimiter */\n.output_html .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */\n.output_html .s2 { color: #BA2121 } /* Literal.String.Double */\n.output_html .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */\n.output_html .sh { color: #BA2121 } /* Literal.String.Heredoc */\n.output_html .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */\n.output_html .sx { color: #008000 } /* Literal.String.Other */\n.output_html .sr { color: #A45A77 } /* Literal.String.Regex */\n.output_html .s1 { color: #BA2121 } /* Literal.String.Single */\n.output_html .ss { color: #19177C } /* Literal.String.Symbol */\n.output_html .bp { color: #008000 } /* Name.Builtin.Pseudo */\n.output_html .fm { color: #00F } /* Name.Function.Magic */\n.output_html .vc { color: #19177C } /* Name.Variable.Class */\n.output_html .vg { color: #19177C } /* Name.Variable.Global */\n.output_html .vi { color: #19177C } /* Name.Variable.Instance */\n.output_html .vm { color: #19177C } /* Name.Variable.Magic */\n.output_html .il { color: #666 } /* Literal.Number.Integer.Long */</style><div class=\"highlight\"><pre><span></span><span class=\"nv\">Timing</span><span class=\"w\"> </span><span class=\"k\">for</span><span class=\"w\"> </span><span class=\"mi\">10000000</span><span class=\"w\"> </span><span class=\"nv\">math</span>.<span class=\"nv\">h</span><span class=\"w\"> </span><span class=\"nv\">sin</span><span class=\"w\"> </span><span class=\"nv\">and</span><span class=\"w\"> </span><span class=\"nv\">cos</span>:<span class=\"w\">\t</span><span class=\"mi\">2838358</span><span class=\"nv\">ns</span>\n<span class=\"nv\">Timing</span><span class=\"w\"> </span><span class=\"k\">for</span><span class=\"w\"> </span><span class=\"mi\">10000000</span><span class=\"w\"> </span><span class=\"nv\">approximations</span>:<span class=\"w\">\t</span><span class=\"mi\">1239784</span><span class=\"nv\">ns</span>\n<span class=\"nv\">Approximation</span><span class=\"w\"> </span><span class=\"nv\">faster</span>,<span class=\"w\"> </span><span class=\"nv\">speedup</span>:<span class=\"w\"> </span><span class=\"mi\">1598574</span><span class=\"nv\">ns</span><span class=\"w\"> </span><span class=\"ss\">(</span><span class=\"mi\">2</span>.<span class=\"mi\">29</span><span class=\"nv\">x</span><span class=\"ss\">)</span>\n<span class=\"nv\">Squared</span><span class=\"w\"> </span><span class=\"nv\">error</span><span class=\"w\"> </span><span class=\"nv\">in</span><span class=\"w\"> </span><span class=\"nv\">cosines</span><span class=\"w\"> </span><span class=\"ss\">(</span><span class=\"mi\">100000</span><span class=\"w\"> </span><span class=\"nv\">runs</span><span class=\"ss\">)</span>:<span class=\"w\"> </span>\n<span class=\"w\">\t</span><span class=\"nv\">Average</span>:<span class=\"w\"> </span><span class=\"mi\">0</span>.<span class=\"mi\">000051</span><span class=\"w\"> </span><span class=\"ss\">(</span><span class=\"mi\">0</span>.<span class=\"mi\">713743</span><span class=\"o\">%</span><span class=\"w\"> </span><span class=\"nv\">error</span><span class=\"ss\">)</span>\n<span class=\"w\">\t</span><span class=\"nv\">Largest</span>:<span class=\"w\"> </span><span class=\"mi\">0</span>.<span class=\"mi\">000174</span><span class=\"w\"> </span><span class=\"ss\">(</span><span class=\"mi\">1</span>.<span class=\"mi\">320551</span><span class=\"o\">%</span><span class=\"w\"> </span><span class=\"nv\">error</span><span class=\"ss\">)</span>\n<span class=\"w\">\t\t</span><span class=\"nv\">Input</span>:<span class=\"w\">\t\t</span><span class=\"mi\">0</span>.<span class=\"mi\">729202</span>\n<span class=\"w\">\t\t</span><span class=\"nv\">Value</span>:<span class=\"w\">\t\t</span><span class=\"o\">-</span><span class=\"mi\">0</span>.<span class=\"mi\">659428</span>\n<span class=\"w\">\t\t</span><span class=\"nv\">Approximation</span>:<span class=\"w\">\t</span><span class=\"o\">-</span><span class=\"mi\">0</span>.<span class=\"mi\">672634</span>\n<span class=\"nv\">Squared</span><span class=\"w\"> </span><span class=\"nv\">error</span><span class=\"w\"> </span><span class=\"nv\">in</span><span class=\"w\"> </span><span class=\"nv\">sines</span><span class=\"w\"> </span><span class=\"ss\">(</span><span class=\"mi\">100000</span><span class=\"w\"> </span><span class=\"nv\">runs</span><span class=\"ss\">)</span>:<span class=\"w\"> </span>\n<span class=\"w\">\t</span><span class=\"nv\">Average</span>:<span class=\"w\"> </span><span class=\"mi\">0</span>.<span class=\"mi\">000070</span><span class=\"w\"> </span><span class=\"ss\">(</span><span class=\"mi\">0</span>.<span class=\"mi\">835334</span><span class=\"o\">%</span><span class=\"w\"> </span><span class=\"nv\">error</span><span class=\"ss\">)</span>\n<span class=\"w\">\t</span><span class=\"nv\">Largest</span>:<span class=\"w\"> </span><span class=\"mi\">0</span>.<span class=\"mi\">000288</span><span class=\"w\"> </span><span class=\"ss\">(</span><span class=\"mi\">1</span>.<span class=\"mi\">698413</span><span class=\"o\">%</span><span class=\"w\"> </span><span class=\"nv\">error</span><span class=\"ss\">)</span>\n<span class=\"w\">\t\t</span><span class=\"nv\">Input</span>:<span class=\"w\">\t\t</span><span class=\"mi\">0</span>.<span class=\"mi\">842206</span>\n<span class=\"w\">\t\t</span><span class=\"nv\">Value</span>:<span class=\"w\">\t\t</span><span class=\"mi\">0</span>.<span class=\"mi\">475669</span>\n<span class=\"w\">\t\t</span><span class=\"nv\">Approximation</span>:<span class=\"w\">\t</span><span class=\"mi\">0</span>.<span class=\"mi\">458685</span>\n</pre></div>\n```\n:::\n\n::: {.cell-output .cell-output-display}\n```{=html}\n<style>pre { line-height: 125%; }\ntd.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }\nspan.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }\ntd.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }\nspan.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }\n.output_html .hll { background-color: #ffffcc }\n.output_html { background: #f8f8f8; }\n.output_html .c { color: #3D7B7B; font-style: italic } /* Comment */\n.output_html .err { border: 1px solid #F00 } /* Error */\n.output_html .k { color: #008000; font-weight: bold } /* Keyword */\n.output_html .o { color: #666 } /* Operator */\n.output_html .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */\n.output_html .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */\n.output_html .cp { color: #9C6500 } /* Comment.Preproc */\n.output_html .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */\n.output_html .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */\n.output_html .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */\n.output_html .gd { color: #A00000 } /* Generic.Deleted */\n.output_html .ge { font-style: italic } /* Generic.Emph */\n.output_html .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */\n.output_html .gr { color: #E40000 } /* Generic.Error */\n.output_html .gh { color: #000080; font-weight: bold } /* Generic.Heading */\n.output_html .gi { color: #008400 } /* Generic.Inserted */\n.output_html .go { color: #717171 } /* Generic.Output */\n.output_html .gp { color: #000080; font-weight: bold } /* Generic.Prompt */\n.output_html .gs { font-weight: bold } /* Generic.Strong */\n.output_html .gu { color: #800080; font-weight: bold } /* Generic.Subheading */\n.output_html .gt { color: #04D } /* Generic.Traceback */\n.output_html .kc { color: #008000; font-weight: bold } /* Keyword.Constant */\n.output_html .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */\n.output_html .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */\n.output_html .kp { color: #008000 } /* Keyword.Pseudo */\n.output_html .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */\n.output_html .kt { color: #B00040 } /* Keyword.Type */\n.output_html .m { color: #666 } /* Literal.Number */\n.output_html .s { color: #BA2121 } /* Literal.String */\n.output_html .na { color: #687822 } /* Name.Attribute */\n.output_html .nb { color: #008000 } /* Name.Builtin */\n.output_html .nc { color: #00F; font-weight: bold } /* Name.Class */\n.output_html .no { color: #800 } /* Name.Constant */\n.output_html .nd { color: #A2F } /* Name.Decorator */\n.output_html .ni { color: #717171; font-weight: bold } /* Name.Entity */\n.output_html .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */\n.output_html .nf { color: #00F } /* Name.Function */\n.output_html .nl { color: #767600 } /* Name.Label */\n.output_html .nn { color: #00F; font-weight: bold } /* Name.Namespace */\n.output_html .nt { color: #008000; font-weight: bold } /* Name.Tag */\n.output_html .nv { color: #19177C } /* Name.Variable */\n.output_html .ow { color: #A2F; font-weight: bold } /* Operator.Word */\n.output_html .w { color: #BBB } /* Text.Whitespace */\n.output_html .mb { color: #666 } /* Literal.Number.Bin */\n.output_html .mf { color: #666 } /* Literal.Number.Float */\n.output_html .mh { color: #666 } /* Literal.Number.Hex */\n.output_html .mi { color: #666 } /* Literal.Number.Integer */\n.output_html .mo { color: #666 } /* Literal.Number.Oct */\n.output_html .sa { color: #BA2121 } /* Literal.String.Affix */\n.output_html .sb { color: #BA2121 } /* Literal.String.Backtick */\n.output_html .sc { color: #BA2121 } /* Literal.String.Char */\n.output_html .dl { color: #BA2121 } /* Literal.String.Delimiter */\n.output_html .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */\n.output_html .s2 { color: #BA2121 } /* Literal.String.Double */\n.output_html .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */\n.output_html .sh { color: #BA2121 } /* Literal.String.Heredoc */\n.output_html .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */\n.output_html .sx { color: #008000 } /* Literal.String.Other */\n.output_html .sr { color: #A45A77 } /* Literal.String.Regex */\n.output_html .s1 { color: #BA2121 } /* Literal.String.Single */\n.output_html .ss { color: #19177C } /* Literal.String.Symbol */\n.output_html .bp { color: #008000 } /* Name.Builtin.Pseudo */\n.output_html .fm { color: #00F } /* Name.Function.Magic */\n.output_html .vc { color: #19177C } /* Name.Variable.Class */\n.output_html .vg { color: #19177C } /* Name.Variable.Global */\n.output_html .vi { color: #19177C } /* Name.Variable.Instance */\n.output_html .vm { color: #19177C } /* Name.Variable.Magic */\n.output_html .il { color: #666 } /* Literal.Number.Integer.Long */</style><div class=\"highlight\"><pre><span></span><span class=\"nv\">Timing</span><span class=\"w\"> </span><span class=\"k\">for</span><span class=\"w\"> </span><span class=\"mi\">10000000</span><span class=\"w\"> </span><span class=\"nv\">complex</span>.<span class=\"nv\">h</span><span class=\"w\"> </span><span class=\"nv\">cexp</span>:<span class=\"w\">\t</span><span class=\"mi\">4725615</span><span class=\"nv\">ns</span>\n<span class=\"nv\">Timing</span><span class=\"w\"> </span><span class=\"k\">for</span><span class=\"w\"> </span><span class=\"mi\">10000000</span><span class=\"w\"> </span><span class=\"nv\">approximations</span>:<span class=\"w\">\t</span><span class=\"mi\">1780325</span><span class=\"nv\">ns</span>\n<span class=\"nv\">Approximation</span><span class=\"w\"> </span><span class=\"nv\">faster</span>,<span class=\"w\"> </span><span class=\"nv\">speedup</span>:<span class=\"w\"> </span><span class=\"mi\">2945290</span><span class=\"nv\">ns</span><span class=\"w\"> </span><span class=\"ss\">(</span><span class=\"mi\">2</span>.<span class=\"mi\">65</span><span class=\"nv\">x</span><span class=\"ss\">)</span>\n<span class=\"nv\">Squared</span><span class=\"w\"> </span><span class=\"nv\">error</span><span class=\"w\"> </span><span class=\"nv\">in</span><span class=\"w\"> </span><span class=\"nv\">cosines</span><span class=\"w\"> </span><span class=\"ss\">(</span><span class=\"mi\">100000</span><span class=\"w\"> </span><span class=\"nv\">runs</span><span class=\"ss\">)</span>:<span class=\"w\"> </span>\n<span class=\"w\">\t</span><span class=\"nv\">Average</span>:<span class=\"w\"> </span><span class=\"mi\">0</span>.<span class=\"mi\">000051</span><span class=\"w\"> </span><span class=\"ss\">(</span><span class=\"mi\">0</span>.<span class=\"mi\">713743</span><span class=\"o\">%</span><span class=\"w\"> </span><span class=\"nv\">error</span><span class=\"ss\">)</span>\n<span class=\"w\">\t</span><span class=\"nv\">Largest</span>:<span class=\"w\"> </span><span class=\"mi\">0</span>.<span class=\"mi\">000174</span><span class=\"w\"> </span><span class=\"ss\">(</span><span class=\"mi\">1</span>.<span class=\"mi\">320551</span><span class=\"o\">%</span><span class=\"w\"> </span><span class=\"nv\">error</span><span class=\"ss\">)</span>\n<span class=\"w\">\t\t</span><span class=\"nv\">Input</span>:<span class=\"w\">\t\t</span><span class=\"mi\">0</span>.<span class=\"mi\">729202</span>\n<span class=\"w\">\t\t</span><span class=\"nv\">Value</span>:<span class=\"w\">\t\t</span><span class=\"o\">-</span><span class=\"mi\">0</span>.<span class=\"mi\">659428</span>\n<span class=\"w\">\t\t</span><span class=\"nv\">Approximation</span>:<span class=\"w\">\t</span><span class=\"o\">-</span><span class=\"mi\">0</span>.<span class=\"mi\">672634</span>\n<span class=\"nv\">Squared</span><span class=\"w\"> </span><span class=\"nv\">error</span><span class=\"w\"> </span><span class=\"nv\">in</span><span class=\"w\"> </span><span class=\"nv\">sines</span><span class=\"w\"> </span><span class=\"ss\">(</span><span class=\"mi\">100000</span><span class=\"w\"> </span><span class=\"nv\">runs</span><span class=\"ss\">)</span>:<span class=\"w\"> </span>\n<span class=\"w\">\t</span><span class=\"nv\">Average</span>:<span class=\"w\"> </span><span class=\"mi\">0</span>.<span class=\"mi\">000070</span><span class=\"w\"> </span><span class=\"ss\">(</span><span class=\"mi\">0</span>.<span class=\"mi\">835334</span><span class=\"o\">%</span><span class=\"w\"> </span><span class=\"nv\">error</span><span class=\"ss\">)</span>\n<span class=\"w\">\t</span><span class=\"nv\">Largest</span>:<span class=\"w\"> </span><span class=\"mi\">0</span>.<span class=\"mi\">000288</span><span class=\"w\"> </span><span class=\"ss\">(</span><span class=\"mi\">1</span>.<span class=\"mi\">698413</span><span class=\"o\">%</span><span class=\"w\"> </span><span class=\"nv\">error</span><span class=\"ss\">)</span>\n<span class=\"w\">\t\t</span><span class=\"nv\">Input</span>:<span class=\"w\">\t\t</span><span class=\"mi\">0</span>.<span class=\"mi\">842206</span>\n<span class=\"w\">\t\t</span><span class=\"nv\">Value</span>:<span class=\"w\">\t\t</span><span class=\"mi\">0</span>.<span class=\"mi\">475669</span>\n<span class=\"w\">\t\t</span><span class=\"nv\">Approximation</span>:<span class=\"w\">\t</span><span class=\"mi\">0</span>.<span class=\"mi\">458685</span>\n</pre></div>\n```\n:::\n:::\n\n\nFor the source which I used to generate this output, see the repository linked at the bottom\n of this article.\n\nEven though results can be inaccurate, this exercise in the algebraic manipulation\n of complex numbers is fairly interesting since it requires no calculus to define,\n unlike sine, cosine, and their Padé approximants (and to a degree, π).\n\n\nFrom Circles to Spheres\n-----------------------\n\nThe expression for complex points on the unit circle coincides with the stereographic projection of a circle.\nThis method is achieved by selecting a point on the circle, fixing *t* along a line\n (such as the *y*-axis), and letting *t* range over all possible values.\n\n![\n 2D stereographic projection\n](./stereo_circle.png)\n\nThe equations of the line and circle appear in the diagram above.\nThrough much algebra, expressions for *x* and *y* as functions of *t* can be formed.\n\n<!-- TODO: this is gross -->\n$$\n\\begin{align*}\n y &= t(x + 1)\n \\\\\n 1 &= x^2 + y^2 = x^2 + (tx + t)^2\n \\\\\n &= x^2 + t^2x^2 + 2t^2 x + t^2\n \\\\[14pt]\n {1 \\over t^2 + 1} -\\ {t^2 \\over t^2 + 1}\n &= x^2 + {t^2 \\over t^2 + 1} 2x\n \\\\\n {1 -\\ t^2 \\over t^2 + 1} + \\left( {t^2 \\over t^2 + 1} \\right)^2\n &= \\left(x + {t^2 \\over t^2 + 1} \\right)^2 + {t^2 \\over t^2 + 1}\n \\\\\n {1 -\\ t^4 \\over (t^2 + 1)^2} + {t^4 \\over (t^2 + 1)^2}\n &= \\left(x + {t^2 \\over t^2 + 1} \\right)^2\n \\\\\n {1 \\over (t^2 + 1)^2}\n &= \\left(x + {t^2 \\over t^2 + 1} \\right)^2\n \\\\\n {1 \\over t^2 + 1}\n &= x + {t^2 \\over t^2 + 1}\n \\\\\n x &= {1 -\\ t^2 \\over 1 + t^2}\n \\\\\n y &= t(x + 1)\n = t\\left( {1 -\\ t^2 \\over 1 + t^2} + {1 + t^2 \\over 1 + t^2} \\right)\n \\\\\n &= {2t \\over 1 + t^2}\n\\end{align*}\n$$\n\nThese are exactly the same expressions which appear in the real and imaginary components\n of the ratio between $1 + ti$ and its conjugate.\n\nCompared to the algebra using complex numbers, this is quite a bit more work.\nOne might ask whether, given a proper arithmetic setting, this can be extended to three dimensions.\nIn other words, we want to find the projection of a sphere using some new number system\n analogously to the circle with respect to complex numbers.\n\n![\n What we are *not* doing: intersecting the sphere with a line through a pole and a point in the xy plane\n](./stereo_sphere.png)\n\n\n### Algebraic Projection\n\nThe simplest thing to do is try it out.\nLet's say an extended number $h = 1 + si + tj$ has a conjugate of $h^{*} = 1 - si - tj$.\nThen, following our noses[^3]:\n\n[^3]: We don't know whether *i* and *j* form a\n [field](https://en.wikipedia.org/wiki/Field_%28mathematics%29)\n or [division ring](https://en.wikipedia.org/wiki/Division_ring)),\n so it's a bit of an assumption for division to be possible.\n We'll ignore that.\n\n$$\n\\begin{gather*}\n {h \\over h^{*}} = {1 + si + tj \\over 1 - si - tj}\n = \\left({1 + si + tj \\over 1 - si - tj}\\right)\n \\left({1 + si + tj \\over 1 + si + tj}\\right)\n \\\\[8pt]\n = {\n 1 + si + tj + si + s^2i^2 + stij + tj + stji + t^2j^2\n \\over 1 - si - tj + si - s^2 i^2 - stij + tj - stji - t^2j^2\n }\n\\end{gather*}\n$$\n\nWhile there is some cancellation in the denominator, both products are rather messy.\nTo get more cancellation, we can add some nice algebraic properties between *i* and *j*.\nIf we let i and j be *anticommutative* (meaning that $ij = -ji$) then $stij$ cancels with $stji$.\nSo that the denominator is totally real (and therefore can be guaranteed to divide),\n we can also assert that $i^2$ and $j^2$ are both real.\nThen the expression becomes\n\n$$\n{1 + si + tj \\over 1 - si - tj}\n = {1 + s^2i^2 + t^2j^2 \\over 1 - s^2 i^2 - t^2 j^2}\n + i{2s \\over 1 - s^2 i^2 - t^2 j^2}\n + j{2t \\over 1 - s^2 i^2 - t^2 j^2}\n$$\n\nIf it is chosen that $i^2 = j^2 = -1$, this produces the correct equations parametrizing the unit sphere\n (see [Wikipedia](https://en.wikipedia.org/wiki/Stereographic_projection#First_formulation)).\n\n$$\n{h \\over h^{*}} = {1 - s^2 - t^2 \\over 1 + s^2 + t^2}\n + i{2s \\over 1 + s^2 + t^2}\n + j{2t \\over 1 + s^2 + t^2}\n$$\n\nOne can also check that this makes the squares of the real, *i*, and *j* components sum to 1.\n\nIf both *i* and *j* anticommute, then their product also anticommutes with both *i* and *j*.\n\n$$\n\\textcolor{red}{ij}j = -j\\textcolor{red}{ij}\n ~,~ i\\textcolor{red}{ij} = -\\textcolor{red}{ij}i\n$$\n\nCalling this product *k* and noticing that $k^2 = (ij)(ij) = -ijji = i^2 = -1$\n completely characterizes the [quaternions](https://en.wikipedia.org/wiki/Quaternion).\n\n\n### Other projections\n\nChoosing different values for $i^2$ and $j^2$ yield different shapes than a sphere.\nIf you know a little group theory, you might know there are only two nonabelian\n (noncommutative) groups of order 8:\n\n- the [quaternion group](https://en.wikipedia.org/wiki/Quaternion_group)\n- and the [\n dihedral group of degree 4\n ](https://en.wikipedia.org/wiki/Examples_of_groups#dihedral_group_of_order_8).\n\nIn the latter group, *j* and *k* are both imaginary, but square to 1 (*i* still squares to -1)[^4].\n\n[^4]: I'm being a bit careless with the meanings of \"1\" and \"-1\" here.\n Properly, these are the group identity and another group element which commutes with all others.\n\nChanging the sign of one (or both) of the imaginary squares in the expression $h / h^{*}$ above\n switches the multiplicative structure from quaternions to the dihedral group.\nIn this group, picking $i^2 = -j^2 = -1$ parametrizes a hyperboloid of one sheet,\n and picking $i^2 = j^2 = 1$ parametrizes a hyperboloid of two sheets.\n\n\nQuaternions and Rotation\n------------------------\n\nWe've already established that complex numbers are useful for describing 2D rotations.\nThey also elegantly describe the stereographic projection of a circle.\nConsequently, since quaternions elegantly describe the stereographic projection of a sphere,\n they are useful for 3D rotations.\n\nSince the imaginary units *i*, *j*, and *k* are anticommutative, a general quaternion does not\n commute with other quaternions like complex numbers do.\nThis embodies a difficulty with 3D rotations in general: unlike 2D rotations, they do not commute.\n\nOnly three of the four components in a quaternion are necessary to describe a point in 2D space.\nThe *ijk* subspace is ideal since the three imaginary axes are symmetric (i.e., they all square to -1).\nQuaternions in this subspace are called *vectors*.\nAnother reason for using the *ijk* subspace comes from considering a point\n $u = ai + bj + ck$ on the unit sphere ($a^2 + b^2 + c^2 = 1$).\nThen the square of this point is:\n\n$$\n\\begin{align*}\n u^2 &= (ai + bj + ck)(ai + bj + ck)\n \\\\\n &= a^2i^2 + abij + acik + abji + b^2j^2 + bcjk + acki + bckj + c^2k^2\n \\\\\n &= (a^2 + b^2 + c^2)(-1) + ab(ij + ji) + ac(ik + ki) + bc(jk + kj)\n \\\\\n &= -1 + 0 + 0 + 0\n\\end{align*}\n$$\n\nThis property means that *u* behaves similarly to a typical imaginary unit.\nIf we form pseudo-complex numbers of the form $a + b u$, then ${1 + tu \\over 1 - tu} = \\alpha + \\beta u$\n specifies *some kind* of rotation in terms of the parameter *t*.\nAs with complex numbers, the inverse rotation is the conjugate $1 - tu \\over 1 + tu$.\n\nAnother useful feature of quaternion algebra involves symmetry transformations of the imaginary units.\nIf an imaginary unit (one of *i*, *j*, *k*) is left-multiplied by a quaternion $q$\n and right-multiplied by its conjugate $q^{*}$, then the result is still imaginary.\nIn other words, if *p* is a vector, then $qpq^{*}$ is also a vector since\n *i*, *j*, and *k* form a basis and multiplication distributes over addition.\nI will not demonstrate this fact, as it requires a large amount of algebra.\n\nThese two features combine to characterize a transformation for a vector quaternion.\nFor a vector *p*, if $q_u(t)$ is a \"rotation\" as above for a point *u* on the unit sphere,\n then another vector (the image of *p*) can be described by\n\n$$\n\\begin{gather*}\n qpq^* = qpq^{-1}\n = \\left({1 + tu \\over 1 - tu}\\right) p \\left({1 - tu \\over 1 + tu}\\right)\n = (\\alpha + \\beta u)p(\\alpha - \\beta u)\n \\\\[4pt]\n = (\\alpha + \\beta u)(\\alpha p - \\beta pu)\n = \\alpha^2 p - \\alpha \\beta pu + \\alpha \\beta up - \\beta^2 upu\n\\end{gather*}\n$$\n\n\n### Testing a Transformation\n\n3D space contains 2D subspaces.\nIf this transformation is a rotation, then a 2D subspace will be affected in a way\n which can be described more simply by complex numbers.\nFor example, if $u = k$ and $p = xi + yj$, then this can be expanded as:\n\n$$\n\\begin{align*}\n qpq^{-1} &= (\\alpha + \\beta k)(xi + yj)(\\alpha - \\beta k)\n \\\\[10pt]\n &= \\alpha^2(xi + yj) - \\alpha \\beta (xi + yj)k + \\alpha \\beta k(xi + yj) - \\beta^2 k(xi + yj)k\n \\vphantom{\\over}\n \\\\[10pt]\n &= \\alpha^2(xi + yj) - \\alpha \\beta(-xj + yi) + \\alpha \\beta(xj - yi) - \\beta^2(xi + yj)\n \\\\[10pt]\n &= (\\alpha^2 - \\beta^2)(xi + yj) + 2\\alpha \\beta(xj - yi)\n \\\\[10pt]\n &= [(\\alpha^2 - \\beta^2)x - 2\\alpha \\beta y]i + [(\\alpha^2 - \\beta^2)y + 2\\alpha \\beta x]j\n\\end{align*}\n$$\n\nIf *p* is rewritten as a vector (in the linear algebra sense), then this final expression\n can be rewritten as a linear transformation of *p*:\n\n$$\n\\begin{align*}\n \\begin{pmatrix}\n (\\alpha^2 - \\beta^2)x - 2 \\alpha \\beta y \\\\\n (\\alpha^2 - \\beta^2)y + 2 \\alpha \\beta x\n \\end{pmatrix}\n &= \\begin{pmatrix}\n \\alpha^2 - \\beta^2 & -2\\alpha \\beta \\\\\n 2\\alpha \\beta & \\alpha^2 - \\beta^2\n \\end{pmatrix}\n \\begin{pmatrix}\n x \\\\\n y\n \\end{pmatrix}\n \\\\\n &= \\begin{pmatrix}\n \\alpha & -\\beta \\\\\n \\beta & \\alpha\n \\end{pmatrix}^2\n \\begin{pmatrix}\n x \\\\\n y\n \\end{pmatrix}\n\\end{align*}\n$$\n\nThe complex number $\\alpha + \\beta i$ is isomorphic to the matrix on the second line[^5].\nSince $\\alpha$ and $\\beta$ were chosen so to lie on \"a\" complex unit circle,\n this means that the vector (*x*, *y*) is rotated by $\\alpha + \\beta i$ twice.\nThis also demonstrates that *u* specifies the axis of rotation, or equivalently,\n the plane of a great circle.\nThis means that the \"some kind of rotation\" specified by *q* is a rotation around\n the great circle normal to *u*.\n\n[^5]: Observe this by comparing the entries of the square of the matrix and the square of the complex number.\n\nThe double rotation resembles the half-steps in the complex numbers,\n where $1 + ti$ performs a dilation, ${1 \\over 1 - ti}$ reverses it, and together they describe a rotation.\nThe form $qpq^{-1}$ is similar to this -- the $q$ on the left performs some transformation which is (partially)\n undone by the $q^{-1}$ on the right.\nIn this case, since *q*'s conjugate is its inverse and quaternions do not commute, the only thing to do without\n the transformation being trivial is to sandwich *p* between them.\n\nAs shown above, the product of a vector with itself should be totally real\n and equal to the negative of the norm of *p*, the sum of the squares of its components.\nThe rotated *p* shares the same norm as *p*, so it should equal $p^2$.\nIndeed, this is the case, as\n\n$$\n(qpq^{-1})^2 = (qpq^{-1})(qpq^{-1}) = q p p q^{-1} = p^2 q q^{-1} = p^2\n$$\n\nAs a final remark, due to rotation through quaternions being doubled, the interpolating polynomial\n used above to smooth out double rotations can also be used here.\nThat is, $q_u(P(t))pq_u^{-1}(P(t))$ rotates *p* by (approximately) the fraction of a turn specified\n by *t* through the great circle which intersects the plane normal to *u*.\n\n\nClosing\n-------\n\nIt is difficult to find a treatment of rotations which does not hesitate\n to use the complex exponential $e^{ix} = \\cos(x) + i \\sin(x)$.\nThe true criterion for rotations is simply that a point lies on the unit circle.\nPerhaps this contributes to why understanding the 3D situation with quaternions is challenging for many.\nPast the barrier to entry, I believe them to be rather intuitive.\nI have outlined some futher benefits of this approach in this post.\n\nAs a disclaimer, this post was (at least subconsciously) inspired by\n [this video](https://www.youtube.com/watch?v=d4EgbgTm0Bg) by 3blue1brown on YouTube\n (though I had not seen the video in years before checking that I wasn't just plagiarizing it).\nIt *also* uses stereographic projections of the circle and sphere to describe rotations\n in the complex plane and quaternion vector space.\nHowever, I feel like it fails to provide an algebraic motivation for quaternions,\n or even stereography in the first place.\nHopefully, my remarks on the algebraic approach can be used to augment the information in the video.\n\n<!-- TODO: better repository link -->\nDiagrams created with GeoGebra and Matplotlib.\nRepository with approximations (as well as GeoGebra files) available [here](https://github.com/queue-miscreant/approx-trig).\n\n",
"supporting": [
"index_files"
],
"filters": [],
"includes": {
"include-in-header": [
"<script src=\"https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.min.js\" integrity=\"sha512-c3Nl8+7g4LMSTdrm621y7kf9v3SDPnhxLNhcjFJbKECVnmZHTdo+IRO05sNLTH/D3vA6u1X32ehoLC7WFVdheg==\" crossorigin=\"anonymous\"></script>\n<script src=\"https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js\" integrity=\"sha512-bLT0Qm9VnAYZDflyKcBaQ2gg0hSYNQrJ8RilYldYQ1FxQYoCLtUjuuRuZo+fjqhx/qtq/1itJ0C2ejDxltZVFg==\" crossorigin=\"anonymous\" data-relocate-top=\"true\"></script>\n<script type=\"application/javascript\">define('jquery', [],function() {return window.jQuery;})</script>\n"
]
}
}
}