Jekyll2020-10-15T18:17:49+00:00http://kqueue.org/feed.xmlkqueue.orgCorrectness of a compiler for arithmetic expressions in Lean2020-10-15T00:00:00+00:002020-10-15T00:00:00+00:00http://kqueue.org/blog/2020/10/15/arithcc<p>“Correctness of a compiler for arithmetic expressions” (<a href="http://jmc.stanford.edu/articles/mcpain/mcpain.pdf">McCarthy and Painter 1967</a>) describes the first proof of compiler correctness. To make it easier to play with the proof, I coded it up using the <a href="https://leanprover-community.github.io/">Lean theorem prover</a>.</p> <p>The Lean code is self-explanatory, so I won’t go into the details here.</p> <ul> <li> <p><a href="https://github.com/xiw/arithcc/blob/master/src/arithcc.lean">Browse the Lean code</a> on GitHub.</p> </li> <li> <p><a href="https://leanprover-community.github.io/lean-web-editor/#url=https%3A%2F%2Fraw.githubusercontent.com%2Fxiw%2Farithcc%2Fmaster%2Fsrc%2Farithcc.lean">Run in the browser</a> (laptop or desktop recommended; tested with Lean/mathlib 3.21.0c).</p> </li> </ul> <p>Lean’s support for <a href="https://leanprover-community.github.io/extras/calc.html">calculational proofs</a> is particularly nice for mirroring the proof in the paper. For example, below is an excerpt of the proof in the paper:</p> \begin{aligned} \zeta_1 &amp; = \mathit{outcome}(\mathit{compile}(s1(e),t),\eta) \\ &amp; =_t a(\mathit{ac}, \upsilon_1, \eta) &amp; \textrm{Induction Hypothesis} \end{aligned} <p>Here’s the corresponding part in Lean:</p> <pre><code class="language-lean">calc ζ₁ = outcome (compile map e_s₁ t) η : by cc ... ≃[t] {ac := ν₁, ..η} : by apply e_ih_s₁; assumption </code></pre> <h2 id="typos">Typos</h2> <p>The <a href="http://jmc.stanford.edu/articles/mcpain/mcpain.pdf">reprint version</a> of the paper contains a number of typos (not present in the original version):</p> <ul> <li> <p>Theorem 1: “$$c(\mathit{loc}(v, \eta) = c(v, \xi)$$” should be “$$c(\mathit{loc}(v, \mathit{map}), \eta) = c(v, \xi)$$.”</p> </li> <li> <p>Section 5.III, under “$$\zeta_2 = \mathit{outcome}(\mathit{mksto}(t), \zeta_1)$$”: “$$a(t, \upsilon_1), \zeta_1)$$” should be “$$a(t, \upsilon_1, \zeta_1)$$.”</p> </li> <li> <p>Section 5.III, next to “$$c(\mathit{loc}(\upsilon, \mathit{map}), \zeta_2)$$”: “$$c(\mathit{loc}(\upsilon), \mathit{map})a(t, \upsilon_1, \eta))$$” should be “$$c(\mathit{loc}(\upsilon, \mathit{map}), a(t, \upsilon_1, \eta))$$.”</p> </li> </ul> <h2 id="links">Links</h2> <ul> <li> <p>Formalization (<a href="http://www.cs.umd.edu/~hjs/pubs/compilers/archive/mi72-mil-wey.pdf">Milner and Weyhrauch 1972</a>) using LCF.</p> </li> <li> <p>Xavier Leroy’s <a href="https://xavierleroy.org/courses/EUTypes-2019/">course</a> on proving the correctness of a compiler for IMP.</p> </li> <li> <p>The <a href="http://compcert.inria.fr/">CompCert</a> C verified compiler.</p> </li> <li> <p><a href="https://github.com/AliveToolkit/alive2">Alive</a> and <a href="https://github.com/google/souper">Souper</a> for verified LLVM optimizations.</p> </li> <li> <p><a href="https://unsat.cs.washington.edu/projects/jitterbug/">Jitterbug</a> for verifying BPF just-in-time compilers in the Linux kernel.</p> </li> </ul>Xi Wang“Correctness of a compiler for arithmetic expressions” (McCarthy and Painter 1967) describes the first proof of compiler correctness. To make it easier to play with the proof, I coded it up using the Lean theorem prover.Areas of Napoleon triangles2020-09-12T00:00:00+00:002020-09-12T00:00:00+00:00http://kqueue.org/blog/2020/09/12/areas-of-napoleon-triangles<p>NB: I came across a survey paper (<a href="#zhao-2011">Zhao, 2011</a>) on some plane geometry problems that a group of friends and I explored around 2000. It’s a nice trip down the memory lane. I played with <a href="/blog/2020/08/31/automated-geometry-bashing/">computer-aided proofs using Maxima</a> recently and thought it might be fun to write down the results.</p> <p>Consider triangle $$ABC$$. The centers of equilateral triangles constructed outward on the sides form the <em>outer</em> Napoleon triangle, $$DEF$$. Similarly, the centers of equilateral triangles constructed inward on the sides form the <em>inner</em> Napoleon triangle, $$D'E'F'$$. <a href="https://en.wikipedia.org/wiki/Napoleon%27s_theorem">Napoleon’s theorem</a> says that both $$DEF$$ and $$D'E'F'$$ are equilateral, one counterclockwise and the other clockwise.</p> <p>A particularly interesting property is that the sum of the (signed) areas of $$DEF$$ and $$D'E'F'$$ equals the area of $$ABC$$ (<a href="#coxeter-and-greitzer-1967">Coxeter and Greitzer, 1967</a>, §3.3). A signed area is positive if the vertices are ordered counterclockwise, and negative if clockwise.</p> <p> <div id="napoleon" title="Napoleon's theorem" class="cindy-canvas"></div> </p> <script id="napdraw" type="text/x-cindyscript"> xlegend = -9.5; ylegend = -8; fmt(x) := format(x, 4); abc = area(A, B, C); def = area(D, E, F); def' = area(D', E', F'); drawtext((xlegend, ylegend), "$\begin{array}{lllll}" + "\textrm{area}\ ABC \\" + fmt(abc) + " & & & = &" + fmt(abc) + "\\ \\" + "\textrm{area}\ DEF & & \textrm{area}\ D'E'F' \\" + fmt(def) + " & + & " + fmt(def') + " & = &" + fmt(def + def') + "\end{array}$", size->14); </script> <script type="text/javascript"> CindyJS({ ports: [{id: "napoleon"}], defaultAppearance: defaultAppearance, scripts: "nap*", geometry: [ {name: "A", type: "Free", pos: [-2, 3.5], color: [1, 1, 1], labeled: true, printname: "$A$"}, {name: "B", type: "Free", pos: [-3, -4.5], color: [1, 1, 1], labeled: true, printname: "$B$"}, {name: "C", type: "Free", pos: [10, -4.5], color: [1, 1, 1], labeled: true, printname: "$C$"}, {name: "l_BC", type: "Join", args: ["B", "C"], visible: false}, {name: "l_CA", type: "Join", args: ["C", "A"], visible: false}, {name: "l_AB", type: "Join", args: ["A", "B"], visible: false}, {name: "l_BD", type: "LineByFixedAngle", angle: -30 * Math.PI / 180, args: ["l_BC", "B"], visible: false}, {name: "l_CD", type: "LineByFixedAngle", angle: 30 * Math.PI / 180, args: ["l_BC", "C"], visible: false}, {name: "D", type: "Meet", args: ["l_BD", "l_CD"], labeled: true, printname: "$D$"}, {name: "tr_BC", type: "TrReflection", args: ["l_BC"]}, {name: "D'", type: "Transform", args: ["tr_BC", "D"], labeled: true, printname: "$D'$"}, {name: "l_CE", type: "LineByFixedAngle", angle: -30 * Math.PI / 180, args: ["l_CA", "C"], visible: false}, {name: "l_AE", type: "LineByFixedAngle", angle: 30 * Math.PI / 180, args: ["l_CA", "A"], visible: false}, {name: "E", type: "Meet", args: ["l_CE", "l_AE"], labeled: true, printname: "$E$"}, {name: "tr_CA", type: "TrReflection", args: ["l_CA"]}, {name: "E'", type: "Transform", args: ["tr_CA", "E"], labeled: true, printname: "$E'$"}, {name: "l_AF", type: "LineByFixedAngle", angle: -30 * Math.PI / 180, args: ["l_AB", "A"], visible: false}, {name: "l_BF", type: "LineByFixedAngle", angle: 30 * Math.PI / 180, args: ["l_AB", "B"], visible: false}, {name: "F", type: "Meet", args: ["l_AF", "l_BF"], labeled: true, printname: "$F$"}, {name: "tr_AB", type: "TrReflection", args: ["l_AB"]}, {name: "F'", type: "Transform", args: ["tr_AB", "F"], labeled: true, printname: "$F'$"}, {name: "l_EF", type: "Join", args: ["E", "F"], visible: false}, {name: "l_FD", type: "Join", args: ["F", "D"], visible: false}, {name: "l_DE", type: "Join", args: ["D", "E"], visible: false}, {name: "DD'", type: "Segment", args: ["D", "D'"], alpha: .2}, {name: "EE'", type: "Segment", args: ["E", "E'"], alpha: .2}, {name: "FF'", type: "Segment", args: ["F", "F'"], alpha: .2}, {name: "ABC", type: "Poly", args: ["A", "B", "C"], fillcolor: [1,0,0], fillalpha: .2}, {name: "DEF", type: "Poly", args: ["D", "E", "F"], fillcolor: [0,0,1], fillalpha: .2}, {name: "D'E'F'", type: "Poly", args: ["D'", "E'", "F'"], fillcolor: [0,0,1], fillalpha: .2}, {name: "l_A", type: "Orthogonal", args: ["l_EF", "A"], visible: false}, {name: "l_B", type: "Orthogonal", args: ["l_FD", "B"], visible: false}, {name: "X", type: "Meet", args: ["l_A", "l_B"]}, {name: "AX", type: "Segment", args: ["A", "X"], alpha: .2}, {name: "BX", type: "Segment", args: ["B", "X"], alpha: .2}, {name: "CX", type: "Segment", args: ["C", "X"], alpha: .2}, ] }); </script> <p>Note that in Napoleon triangle, $$A', B', C'$$, the reflections of $$A, B, C$$ across $$EF, FD, DE$$, respectively, coincide such that $$\mathrm{area}\ A'B'C' = 0$$. A general and symmetric form of the areal property is the following:</p> $\mathrm{area}\ ABC + \mathrm{area}\ A'B'C' = \mathrm{area}\ DEF + \mathrm{area}\ D'E'F'.$ <p>This post focuses on generalizations of the areal property.</p> <h2 id="antisimilitude-and-involution">Antisimilitude and involution</h2> <p>First, we would like to generalize how $$D, E, F$$ are chosen. What’s a necessary and sufficient condition for their reflections $$D', E', F'$$ across $$BC, CA, AB$$, respectively, to form a triangle inversely similar to $$DEF$$ (i.e., with the same shape and opposite rotations)?</p> <p><a href="#ye-and-cao-1997">Zhonghao Ye and Gang Cao (1997)</a> answered this question with the following theorem.</p> <div class="theorem"> <p><strong>Theorem 1</strong>: Consider non-degenerate triangles $$ABC$$ and $$DEF$$. Let $$A', B', C', D', E', F'$$ be the reflections of $$A, B, C, D, E, F$$ across $$EF, FD, DE, BC, CA, AB$$, respectively.</p> <p>Triangles $$DEF$$ and $$D'E'F'$$ ‘are inversely similar if and only if the Möbius transformation that maps $$A, B, C$$ to $$D, E, F$$ is an involution (or simply, $$A, B, C, D, E, F$$ are involutoric).</p> </div> <p> <div id="inv" title="The areal property (involution)" class="cindy-canvas"></div> </p> <script id="drawArea" type="text/x-cindyscript"> xlegend = -9.5; ylegend = -8; fmt(x) := format(x, 4); abc = area(A, B, C); def = area(D, E, F); abc' = area(A', B', C'); def' = area(D', E', F'); drawtext((xlegend, ylegend), "$\begin{array}{lllll}" + "\textrm{area}\ ABC & & \textrm{area}\ A'B'C' \\" + fmt(abc) + " & + & " + fmt(abc') + " & = &" + fmt(abc + abc') + "\\ \\" + "\textrm{area}\ DEF & & \textrm{area}\ D'E'F' \\" + fmt(def) + " & + & " + fmt(def') + " & = &" + fmt(def + def') + "\end{array}$", size->14); </script> <script id="invinit" type="text/x-cindyscript"> moveto(F, realF.xy); </script> <script id="invmousedrag" type="text/x-cindyscript"> if(mover()==A, moveto(F, realF.xy)); if(mover()==B, moveto(F, realF.xy)); if(mover()==C, moveto(F, realF.xy)); if(mover()==D, moveto(F, realF.xy)); if(mover()==E, moveto(F, realF.xy)); if(mover()==F, moveto(C, realC.xy)); </script> <script type="text/javascript"> CindyJS({ ports: [{id: "inv"}], scripts: "inv*", drawscript: "drawArea", defaultAppearance: defaultAppearance, geometry: [ {name: "A", type: "Free", pos: [ 1, 3], color: [1, 1, 1], labeled: true, printname: "$A$"}, {name: "B", type: "Free", pos: [-3, -4], color: [1, 1, 1], labeled: true, printname: "$B$"}, {name: "C", type: "Free", pos: [10, -5], color: [1, 1, 1], labeled: true, printname: "$C$"}, {name: "D", type: "Free", pos: [ 4, -8], color: [1, 1, 1], labeled: true, printname: "$D$"}, {name: "E", type: "Free", pos: [ 7, 1], color: [1, 1, 1], labeled: true, printname: "$E$"}, {name: "F", type: "Free", pos: [-2, 0], color: [1, 1, 1], labeled: true, printname: "$F$"}, {name: "trA", type: "TrMoebius", args: ["B", "E", "C", "F", "F", "C"]}, {name: "trB", type: "TrMoebius", args: ["C", "F", "A", "D", "D", "A"]}, {name: "trC", type: "TrMoebius", args: ["A", "D", "B", "E", "E", "B"]}, {name: "realA", type: "Transform", args: ["trA", "D"], visible: false}, {name: "realB", type: "Transform", args: ["trB", "E"], visible: false}, {name: "realC", type: "Transform", args: ["trC", "F"], visible: false}, {name: "trD", type: "TrMoebius", args: ["E", "B", "F", "C", "C", "F"]}, {name: "trE", type: "TrMoebius", args: ["F", "C", "D", "A", "A", "D"]}, {name: "trF", type: "TrMoebius", args: ["D", "A", "E", "B", "B", "E"]}, {name: "realD", type: "Transform", args: ["trD", "A"], visible: false}, {name: "realE", type: "Transform", args: ["trE", "B"], visible: false}, {name: "realF", type: "Transform", args: ["trF", "C"], visible: false}, {name: "l_BC", type: "Join", args: ["B", "C"], visible: false}, {name: "l_CA", type: "Join", args: ["C", "A"], visible: false}, {name: "l_AB", type: "Join", args: ["A", "B"], visible: false}, {name: "l_EF", type: "Join", args: ["E", "F"], visible: false}, {name: "l_FD", type: "Join", args: ["F", "D"], visible: false}, {name: "l_DE", type: "Join", args: ["D", "E"], visible: false}, {name: "tr_BC", type: "TrReflection", args: ["l_BC"]}, {name: "tr_CA", type: "TrReflection", args: ["l_CA"]}, {name: "tr_AB", type: "TrReflection", args: ["l_AB"]}, {name: "tr_EF", type: "TrReflection", args: ["l_EF"]}, {name: "tr_FD", type: "TrReflection", args: ["l_FD"]}, {name: "tr_DE", type: "TrReflection", args: ["l_DE"]}, {name: "A'", type: "Transform", args: ["tr_EF", "A"], labeled: true, printname: "$A'$"}, {name: "B'", type: "Transform", args: ["tr_FD", "B"], labeled: true, printname: "$B'$"}, {name: "C'", type: "Transform", args: ["tr_DE", "C"], labeled: true, printname: "$C'$"}, {name: "D'", type: "Transform", args: ["tr_BC", "D"], labeled: true, printname: "$D'$"}, {name: "E'", type: "Transform", args: ["tr_CA", "E"], labeled: true, printname: "$E'$"}, {name: "F'", type: "Transform", args: ["tr_AB", "F"], labeled: true, printname: "$F'$"}, {name: "ABC", type: "Poly", args: ["A", "B", "C"], fillcolor: [1,0,0], fillalpha: .2}, {name: "DEF", type: "Poly", args: ["D", "E", "F"], fillcolor: [0,0,1], fillalpha: .2}, {name: "A'B'C'", type: "Poly", args: ["A'", "B'", "C'"], fillcolor: [1,0,0], fillalpha: .2}, {name: "D'E'F'", type: "Poly", args: ["D'", "E'", "F'"], fillcolor: [0,0,1], fillalpha: .2}, {name: "AA'", type: "Segment", args: ["A", "A'"], alpha: .2}, {name: "BB'", type: "Segment", args: ["B", "B'"], alpha: .2}, {name: "CC'", type: "Segment", args: ["C", "C'"], alpha: .2}, {name: "DD'", type: "Segment", args: ["D", "D'"], alpha: .2}, {name: "EE'", type: "Segment", args: ["E", "E'"], alpha: .2}, {name: "FF'", type: "Segment", args: ["F", "F'"], alpha: .2}, ] }); </script> <p>Let’s start with some definitions. A <a href="https://en.wikipedia.org/wiki/M%C3%B6bius_transformation">Möbius transformation</a> is a linear fractional transformation in the following form:</p> $\varphi(z) = \frac{\alpha z + \beta}{\gamma z + \delta}.$ <p>Here $$\alpha, \beta, \gamma, \delta$$ are complex numbers such that $$\alpha\delta - \beta\gamma \neq 0$$. We use a lowercase letter $$z$$ to represent the complex number of the corresponding point $$Z$$.</p> <p>A transformation $$\varphi$$ is an involution if $$\varphi(\varphi(z)) = z$$. For example, if an involutoric transformation maps $$A$$ to $$D$$, then it also maps $$D$$ to $$A$$.</p> <p>A Möbius transformation is uniquely determined by three pairs of points (<a href="#deaux-1956">Deaux 1956</a>, article 74). When the transformation determined by $$(A, D), (B, E), (C, F)$$ is an involution, these points satisfy the following (<a href="#deaux-1956">Deaux 1956</a>, article 102):</p> $\begin{vmatrix} ad &amp; a + d &amp; 1 \\ be &amp; b + e &amp; 1 \\ cf &amp; c + f &amp; 1 \end{vmatrix} = 0.$ <p>We use the above definition in the proofs.</p> <p>A more intuitive way is to rewrite the determinant as:</p> $\frac{(a - f)(b - d)(c - e)}{(f - b)(d - c)(e - a)} = -1.$ <p>Therefore, one may view an involution as a hexagon that satisfy the following properties:</p> <ul> <li> <p>$$\dfrac{AF}{FB} \cdot \dfrac{BD}{DC} \cdot \dfrac{CE}{EA} = 1$$;</p> </li> <li> <p>$$\angle AFB + \angle BDC + \angle CEA = 0$$.</p> </li> </ul> <p>We use $$\angle XYZ$$ to denote the <a href="https://web.evanchen.cc/handouts/Directed-Angles/Directed-Angles.pdf">directed angle</a> from line $$XY$$ to line $$YZ$$.</p> <p>With this view, it’s easy to see that below are a few examples of involution:</p> <ul> <li>A triangle $$ABC$$ and its outer (or inner) Napoleon triangle $$DEF$$;</li> <li>A triangle $$ABC$$ and points $$D, E, F$$ on $$BC, AC, AB$$, respectively, such that $$AD, BE, CF$$ are concurrent (<a href="https://en.wikipedia.org/wiki/Ceva%27s_theorem">Ceva’s theorem</a>);</li> <li>A triangle $$ABC$$ and points $$D, E, F$$ on $$BC, AC, AB$$, respectively, such that $$D, E, F$$ are collinear (<a href="https://en.wikipedia.org/wiki/Menelaus%27s_theorem">Menelaus’s theorem</a>);</li> <li>The three pairs of opposite vertices of a <a href="https://mathworld.wolfram.com/CompleteQuadrilateral.html">complete quadrilateral</a> (<a href="#deaux-1956">Deaux 1956</a>, article 103).</li> </ul> <p><strong>Proof.</strong> $$DEF$$ inversely similar to $$D'E'F'$$ translates to:</p> $\begin{vmatrix} d &amp; \overline{d'} &amp; 1 \\ e &amp; \overline{e'} &amp; 1 \\ f &amp; \overline{f'} &amp; 1 \end{vmatrix} = 0.$ <p>Since $$D'$$ is the reflection of $$D$$ across $$BC$$, we have $$d' = \dfrac{(b - c)\overline{d} + \overline{b}c - b\overline{c}}{\overline{b} - \overline{c}}$$. We obtain $$e'$$ and $$f'$$ similarly. Plug $$d', e', f'$$ into the above equation, which simplifies to:</p> $\begin{vmatrix} ad &amp; a + d &amp; 1 \\ be &amp; b + e &amp; 1 \\ cf &amp; c + f &amp; 1 \end{vmatrix} \begin{vmatrix} a &amp; \overline{a} &amp; 1 \\ b &amp; \overline{b} &amp; 1 \\ c &amp; \overline{c} &amp; 1 \end{vmatrix} = 0.$ <p>Since $$ABC$$ is a non-degenerate triangle, the above reduces to:</p> $\begin{vmatrix} ad &amp; a + d &amp; 1 \\ be &amp; b + e &amp; 1 \\ cf &amp; c + f &amp; 1 \end{vmatrix} = 0.$ <p>Therefore, a necessary and sufficient condition for $$DEF$$ and $$D'E'F'$$ to be inversely similar is that $$A, B, C, D, E, F$$ are involutoric. <strong>Q.E.D.</strong></p> <p>Note that by symmetry, this is also the necessary and sufficient condition for triangles $$ABC$$ and $$A'B'C'$$ to be inversely similar.</p> <p>How are antisimilitude and involution related to the areal property? Below is my answer to this question in a letter to Zhonghao Ye (personal communication, 1999; <a href="#zhao-2011">Zhao, 2011</a>).</p> <div class="theorem"> <p><strong>Theorem 2</strong>: Consider points $$A, B, C, D, E, F$$ in the plane. Let $$A', B', C', D', E', F'$$ be the reflections of $$A, B, C, D, E, F$$ across $$EF, FD, DE, BC, CA, AB$$, respectively.</p> <p>If $$A, B, C, D, E, F$$ are involutoric, the sum of the areas of $$ABC$$ and $$A'B'C'$$ equals the sum of the areas of $$DEF$$ and $$D'E'F'$$:</p> $\mathrm{area}\ ABC + \mathrm{area}\ A'B'C' = \mathrm{area}\ DEF + \mathrm{area}\ D'E'F'.$ </div> <p><strong>Proof.</strong> Suppose the involution determined by $$A, B, C, D, E, F$$ is:</p> $\varphi(z) = \frac{\alpha z + \beta}{\gamma z + \delta}.$ <p>This boils down to three cases.</p> <p>If $$\gamma = 0$$, given $$\varphi(\varphi(z)) = z$$, this reduces to one of the following:</p> <ul> <li> <p>$$\alpha = \delta, \beta = 0$$. In this case, $$\varphi$$ reduces to the identity transformation $$\varphi(z) = z$$, where $$DEF$$ coincides with $$ABC$$. We have $$d = a, e = b, f = c$$.</p> </li> <li> <p>$$\alpha = -\delta$$. In this case, $$\varphi$$ reduces to $$\varphi(z) = -z + {\beta}/{\delta}$$, where $$DEF$$ is the symmetry of $$ABC$$ with respect to point $$\dfrac{\beta/\delta}{2}$$. Choose that point as the origin. We have $$d = -a, e = -b, f = -c$$.</p> </li> </ul> <p>If $$\gamma \neq 0$$, $$\varphi$$ has two fixed points $$p_i$$ such that $$\varphi(p_i) = p_i (i = 1, 2)$$:</p> <ul> <li>WLOG, choose the midpoint of $$p_1, p_2$$ as the origin and the unit such that $$p_1 = 1, p_2 = -1$$. In this case, $$\varphi$$ reduces to $$\varphi(z) = 1/z$$. We have $$d = 1/a, e = 1/b, f = 1/c$$.</li> </ul> <p>The rest is calculation for each of the three cases. See <a href="https://github.com/xiw/geometry/tree/master/napoleon/area.mac">area.mac</a> in Maxima. <strong>Q.E.D.</strong></p> <h2 id="concyclicity">Concyclicity</h2> <p>Involution is necessary and sufficient for antisimilitude (e.g., $$DEF$$ inversely similar to $$D'E'F'$$). It is also sufficient for the areal property, but not necessary, as evidenced by the following theorem.</p> <div class="theorem"> <p><strong>Theorem 3</strong>: Consider points $$A, B, C, D, E, F$$ in the plane. Let $$A', B', C', D', E', F'$$ be the reflections of $$A, B, C, D, E, F$$ across $$EF, FD, DE, BC, CA, AB$$, respectively.</p> <p>If $$A, B, C, D, E, F$$ are concyclic, the sum of the areas of $$ABC$$ and $$A'B'C'$$ equals the sum of the areas of $$DEF$$ and $$D'E'F'$$:</p> $\mathrm{area}\ ABC + \mathrm{area}\ A'B'C' = \mathrm{area}\ DEF + \mathrm{area}\ D'E'F'.$ </div> <p> <div id="cyclic" title="The areal property (concyclicity)" class="cindy-canvas"></div> </p> <script type="text/javascript"> CindyJS({ ports: [{id: "cyclic"}], drawscript: "drawArea", defaultAppearance: defaultAppearance, geometry: [ {name: "O", type: "Free", pos: [4, -1], color: [1, 1, 1], visible: false}, {name: "R", type: "Free", pos: [10, -1], color: [1, 1, 1], visible: false}, {name: "c0", type: "CircleMP", args: ["O", "R"], alpha: .2}, {name: "A", type: "PointOnCircle", args: ["c0"], pos: [ 2, 3], color: [1, 1, 1], labeled: true, printname: "$A$"}, {name: "B", type: "PointOnCircle", args: ["c0"], pos: [-1, -4], color: [1, 1, 1], labeled: true, printname: "$B$"}, {name: "C", type: "PointOnCircle", args: ["c0"], pos: [ 6, -3], color: [1, 1, 1], labeled: true, printname: "$C$"}, {name: "D", type: "PointOnCircle", args: ["c0"], pos: [ 4, -6], color: [1, 1, 1], labeled: true, printname: "$D$"}, {name: "E", type: "PointOnCircle", args: ["c0"], pos: [ 7, 1], color: [1, 1, 1], labeled: true, printname: "$E$"}, {name: "F", type: "PointOnCircle", args: ["c0"], pos: [-1, 1], color: [1, 1, 1], labeled: true, printname: "$F$"}, {name: "l_BC", type: "Join", args: ["B", "C"], visible: false}, {name: "l_CA", type: "Join", args: ["C", "A"], visible: false}, {name: "l_AB", type: "Join", args: ["A", "B"], visible: false}, {name: "l_EF", type: "Join", args: ["E", "F"], visible: false}, {name: "l_FD", type: "Join", args: ["F", "D"], visible: false}, {name: "l_DE", type: "Join", args: ["D", "E"], visible: false}, {name: "tr_BC", type: "TrReflection", args: ["l_BC"]}, {name: "tr_CA", type: "TrReflection", args: ["l_CA"]}, {name: "tr_AB", type: "TrReflection", args: ["l_AB"]}, {name: "tr_EF", type: "TrReflection", args: ["l_EF"]}, {name: "tr_FD", type: "TrReflection", args: ["l_FD"]}, {name: "tr_DE", type: "TrReflection", args: ["l_DE"]}, {name: "A'", type: "Transform", args: ["tr_EF", "A"], labeled: true, printname: "$A'$"}, {name: "B'", type: "Transform", args: ["tr_FD", "B"], labeled: true, printname: "$B'$"}, {name: "C'", type: "Transform", args: ["tr_DE", "C"], labeled: true, printname: "$C'$"}, {name: "D'", type: "Transform", args: ["tr_BC", "D"], labeled: true, printname: "$D'$"}, {name: "E'", type: "Transform", args: ["tr_CA", "E"], labeled: true, printname: "$E'$"}, {name: "F'", type: "Transform", args: ["tr_AB", "F"], labeled: true, printname: "$F'$"}, {name: "ABC", type: "Poly", args: ["A", "B", "C"], fillcolor: [1,0,0], fillalpha: .2}, {name: "DEF", type: "Poly", args: ["D", "E", "F"], fillcolor: [0,0,1], fillalpha: .2}, {name: "A'B'C'", type: "Poly", args: ["A'", "B'", "C'"], fillcolor: [1,0,0], fillalpha: .2}, {name: "D'E'F'", type: "Poly", args: ["D'", "E'", "F'"], fillcolor: [0,0,1], fillalpha: .2}, {name: "AA'", type: "Segment", args: ["A", "A'"], alpha: .2}, {name: "BB'", type: "Segment", args: ["B", "B'"], alpha: .2}, {name: "CC'", type: "Segment", args: ["C", "C'"], alpha: .2}, {name: "DD'", type: "Segment", args: ["D", "D'"], alpha: .2}, {name: "EE'", type: "Segment", args: ["E", "E'"], alpha: .2}, {name: "FF'", type: "Segment", args: ["F", "F'"], alpha: .2}, ] }); </script> <p><strong>Proof.</strong> Choose the circle on which $$A, B, C, D, E, F$$ all lie as the unit circle.</p> <p>We have $$\overline{a} = 1/a, \overline{b} = 1/b, \overline{c} = 1/c, \overline{d} = 1/d, \overline{e} = 1/e, \overline{f} = 1/f$$.</p> <p>The rest of the proof is similar to that of Theorem 2. See <a href="https://github.com/xiw/geometry/tree/master/napoleon/area.mac">area.mac</a>. <strong>Q.E.D.</strong></p> <p>Theorems 2 and 3 indicate that to unify the areal property for both involutoric and cyclic cases, a property “weaker” than antisimilitude is needed, as detailed next.</p> <h2 id="hexagons-with-opposite-sides-parallel">Hexagons with opposite sides parallel</h2> <p><a href="#zhao-2011">Yong Zhao (2011)</a> described several new results regarding the areal property.</p> <p>First, Yong Zhao considered the hexagon formed by $$A_1, B_1, C_1, D_1, E_1, F_1$$, the feet of the altitudes from $$A, B, C, D, E, F$$ to $$EF, FD, DE, BC, CA, AB$$, respectively: if $$A, B, C, D, E, F$$ are involutoric, the opposite sides of hexagon $$A_1F_1B_1D_1C_1E_1$$ are parallel (not necessarily equal in length): $$A_1F_1 \parallel D_1C_1, F_1B_1 \parallel C_1E_1, B_1D_1 \parallel E_1A_1$$.</p> <p> <div id="parinv" title="Hexagons with opposite sides parallel (involution)" class="cindy-canvas"></div> </p> <script type="text/javascript"> CindyJS({ ports: [{id: "parinv"}], scripts: "inv*", defaultAppearance: defaultAppearance, geometry: [ {name: "A", type: "Free", pos: [ 1, 3], color: [1, 1, 1], labeled: true, printname: "$A$"}, {name: "B", type: "Free", pos: [-3, -4], color: [1, 1, 1], labeled: true, printname: "$B$"}, {name: "C", type: "Free", pos: [10, -5], color: [1, 1, 1], labeled: true, printname: "$C$"}, {name: "D", type: "Free", pos: [ 3, -8], color: [1, 1, 1], labeled: true, printname: "$D$"}, {name: "E", type: "Free", pos: [ 7, 3], color: [1, 1, 1], labeled: true, printname: "$E$"}, {name: "F", type: "Free", pos: [-2, 0], color: [1, 1, 1], labeled: true, printname: "$F$"}, {name: "trA", type: "TrMoebius", args: ["B", "E", "C", "F", "F", "C"]}, {name: "trB", type: "TrMoebius", args: ["C", "F", "A", "D", "D", "A"]}, {name: "trC", type: "TrMoebius", args: ["A", "D", "B", "E", "E", "B"]}, {name: "realA", type: "Transform", args: ["trA", "D"], visible: false}, {name: "realB", type: "Transform", args: ["trB", "E"], visible: false}, {name: "realC", type: "Transform", args: ["trC", "F"], visible: false}, {name: "trD", type: "TrMoebius", args: ["E", "B", "F", "C", "C", "F"]}, {name: "trE", type: "TrMoebius", args: ["F", "C", "D", "A", "A", "D"]}, {name: "trF", type: "TrMoebius", args: ["D", "A", "E", "B", "B", "E"]}, {name: "realD", type: "Transform", args: ["trD", "A"], visible: false}, {name: "realE", type: "Transform", args: ["trE", "B"], visible: false}, {name: "realF", type: "Transform", args: ["trF", "C"], visible: false}, {name: "l_BC", type: "Join", args: ["B", "C"], visible: false}, {name: "l_CA", type: "Join", args: ["C", "A"], visible: false}, {name: "l_AB", type: "Join", args: ["A", "B"], visible: false}, {name: "l_EF", type: "Join", args: ["E", "F"], visible: false}, {name: "l_FD", type: "Join", args: ["F", "D"], visible: false}, {name: "l_DE", type: "Join", args: ["D", "E"], visible: false}, {name: "l_A", type: "Orthogonal", args: ["l_EF", "A"], visible: false}, {name: "A1", type: "Meet", args: ["l_A", "l_EF"], labeled: true, printname: "$A_1$"}, {name: "l_B", type: "Orthogonal", args: ["l_FD", "B"], visible: false}, {name: "B1", type: "Meet", args: ["l_B", "l_FD"], labeled: true, printname: "$B_1$"}, {name: "l_C", type: "Orthogonal", args: ["l_DE", "C"], visible: false}, {name: "C1", type: "Meet", args: ["l_C", "l_DE"], labeled: true, printname: "$C_1$"}, {name: "l_D", type: "Orthogonal", args: ["l_BC", "D"], visible: false}, {name: "D1", type: "Meet", args: ["l_D", "l_BC"], labeled: true, printname: "$D_1$"}, {name: "l_E", type: "Orthogonal", args: ["l_CA", "E"], visible: false}, {name: "E1", type: "Meet", args: ["l_E", "l_CA"], labeled: true, printname: "$E_1$"}, {name: "l_F", type: "Orthogonal", args: ["l_AB", "F"], visible: false}, {name: "F1", type: "Meet", args: ["l_F", "l_AB"], labeled: true, printname: "$F_1$"}, {name: "ABC", type: "Poly", args: ["A", "B", "C"]}, {name: "DEF", type: "Poly", args: ["D", "E", "F"]}, {name: "Poly1", type: "Poly", args: ["A1", "F1", "B1", "D1", "C1", "E1"]}, {name: "AA1", type: "Segment", args: ["A", "A1"], alpha: .2}, {name: "BB1", type: "Segment", args: ["B", "B1"], alpha: .2}, {name: "CC1", type: "Segment", args: ["C", "C1"], alpha: .2}, {name: "DD1", type: "Segment", args: ["D", "D1"], alpha: .2}, {name: "EE1", type: "Segment", args: ["E", "E1"], alpha: .2}, {name: "FF1", type: "Segment", args: ["F", "F1"], alpha: .2}, ] }); </script> <p>Second, it’s straightforward to show that if $$A, B, C, D, E, F$$ are concyclic, the opposite sides of hexagon $$A_1F_1B_1D_1C_1E_1$$ are also parallel.</p> <p> <div id="parcyclic" title="Hexagons with opposite sides parallel (concyclicity)" class="cindy-canvas"></div> </p> <script type="text/javascript"> CindyJS({ ports: [{id: "parcyclic"}], defaultAppearance: defaultAppearance, geometry: [ {name: "O", type: "Free", pos: [3, -2], color: [1, 1, 1], visible: false}, {name: "R", type: "Free", pos: [9, -1], color: [1, 1, 1], visible: false}, {name: "c0", type: "CircleMP", args: ["O", "R"], alpha: .2}, {name: "A", type: "PointOnCircle", args: ["c0"], pos: [ 2, 3], color: [1, 1, 1], labeled: true, printname: "$A$"}, {name: "B", type: "PointOnCircle", args: ["c0"], pos: [-1, -4], color: [1, 1, 1], labeled: true, printname: "$B$"}, {name: "C", type: "PointOnCircle", args: ["c0"], pos: [ 6, -3], color: [1, 1, 1], labeled: true, printname: "$C$"}, {name: "D", type: "PointOnCircle", args: ["c0"], pos: [ 3, -6], color: [1, 1, 1], labeled: true, printname: "$D$"}, {name: "E", type: "PointOnCircle", args: ["c0"], pos: [ 7, 1], color: [1, 1, 1], labeled: true, printname: "$E$"}, {name: "F", type: "PointOnCircle", args: ["c0"], pos: [-1, 1], color: [1, 1, 1], labeled: true, printname: "$F$"}, {name: "l_BC", type: "Join", args: ["B", "C"], visible: false}, {name: "l_CA", type: "Join", args: ["C", "A"], visible: false}, {name: "l_AB", type: "Join", args: ["A", "B"], visible: false}, {name: "l_EF", type: "Join", args: ["E", "F"], visible: false}, {name: "l_FD", type: "Join", args: ["F", "D"], visible: false}, {name: "l_DE", type: "Join", args: ["D", "E"], visible: false}, {name: "l_A", type: "Orthogonal", args: ["l_EF", "A"], visible: false}, {name: "A1", type: "Meet", args: ["l_A", "l_EF"], labeled: true, printname: "$A_1$"}, {name: "l_B", type: "Orthogonal", args: ["l_FD", "B"], visible: false}, {name: "B1", type: "Meet", args: ["l_B", "l_FD"], labeled: true, printname: "$B_1$"}, {name: "l_C", type: "Orthogonal", args: ["l_DE", "C"], visible: false}, {name: "C1", type: "Meet", args: ["l_C", "l_DE"], labeled: true, printname: "$C_1$"}, {name: "l_D", type: "Orthogonal", args: ["l_BC", "D"], visible: false}, {name: "D1", type: "Meet", args: ["l_D", "l_BC"], labeled: true, printname: "$D_1$"}, {name: "l_E", type: "Orthogonal", args: ["l_CA", "E"], visible: false}, {name: "E1", type: "Meet", args: ["l_E", "l_CA"], labeled: true, printname: "$E_1$"}, {name: "l_F", type: "Orthogonal", args: ["l_AB", "F"], visible: false}, {name: "F1", type: "Meet", args: ["l_F", "l_AB"], labeled: true, printname: "$F_1$"}, {name: "ABC", type: "Poly", args: ["A", "B", "C"]}, {name: "DEF", type: "Poly", args: ["D", "E", "F"]}, {name: "Poly1", type: "Poly", args: ["A1", "F1", "B1", "D1", "C1", "E1"]}, {name: "AA1", type: "Segment", args: ["A", "A1"], alpha: .2}, {name: "BB1", type: "Segment", args: ["B", "B1"], alpha: .2}, {name: "CC1", type: "Segment", args: ["C", "C1"], alpha: .2}, {name: "DD1", type: "Segment", args: ["D", "D1"], alpha: .2}, {name: "EE1", type: "Segment", args: ["E", "E1"], alpha: .2}, {name: "FF1", type: "Segment", args: ["F", "F1"], alpha: .2}, ] }); </script> <p>Third, the hexagon with opposite sides parallel has the following property:</p> $\mathrm{area}\ A_1B_1C_1 = \mathrm{area}\ D_1E_1F_1.$ <p>Zhouxing Mao further derived an alternative proof of Theorem 2 using the above property, demonstrating the connection between the areal property and the hexagon with opposite sides parallel.</p> <p>Therefore, the key is to find a necessary and sufficient condition for the opposite sides of hexagon $$A_1F_1B_1D_1C_1E_1$$ to be parallel.</p> <p>Libing Huang conjectured the following:</p> <div class="theorem"> <p><strong>Theorem 4.</strong> Consider distinct points $$A, B, C, D, E, F$$ in the plane. Let $$A_1, B_1, C_1, D_1, E_1, F_1$$ be the feet of the altitudes from $$A, B, C, D, E, F$$ to $$EF, FD, DE, BC, CA, AB$$, respectively.</p> <p>The opposite sides of hexagon $$A_1F_1B_1D_1C_1E_1$$ are parallel if and only if $$A, B, C, D, E, F$$ are either involutoric or concyclic.</p> </div> <p>Libing Huang mentioned that he had a computer-aided proof but didn’t provide details. Below I’ll describe a proof in Maxima.</p> <p><strong>Proof.</strong> The proof of the sufficient condition is similar to those of theorems 2 and 3. Let’s focus on the necessary condition.</p> <p>Since $$A_1$$ is the midpoint of $$A$$ and $$A'$$ (the reflection of $$A$$ across $$EF$$), we have:</p> $a_1 = \frac{a + a'}{2} = \frac{a(\overline{e} - \overline{f}) + \overline{a}(e - f) + \overline{e}f - e\overline{f}} {2(\overline{e} - \overline{f})}.$ <p>We obtain $$b_1, c_1, d_1, e_1, f_1$$ similarly.</p> <p>$$A_1F_1 \parallel D_1C_1$$ translates to:</p> $(a_1 - f_1)(\overline{d_1} - \overline{c_1}) - (\overline{a_1} - \overline{f_1})(d_1 - c_1) = 0.$ <p>Plug $$a_1, c_1, d_1, f_1$$ into the above equation, we have:</p> $\left[(a - b)(\overline{e} - \overline{f}) + (\overline{a} - \overline{b})(e - f)\right] \left[(b - c)(\overline{d} - \overline{e}) + (\overline{b} - \overline{c})(d - e)\right] p_1 = 0.$ <p>The detail of $$p_1$$ is omitted due to the complexity (120 subexpressions!). The other two expressions correspond to the degenerate cases, $$AB \bot EF$$ ($$A_1$$ coincides with $$F_1$$) and $$BC \bot DE$$ ($$D_1$$ coincides with $$C_1$$), which we don’t consider. Therefore, the above equation simplifies to $$p_1 = 0$$.</p> <p>Similarly, from $$F_1B_1 \parallel C_1E_1$$ we obtain $$p_2 = 0$$ (details omitted).</p> <p>Eliminating $$\overline{d}$$ from $$p_1 = 0, p_2 = 0$$ gives the following two cases:</p> $\begin{vmatrix} ad &amp; a + d &amp; 1 \\ be &amp; b + e &amp; 1 \\ cf &amp; c + f &amp; 1 \end{vmatrix} = 0$ $\left(c - a\right)\left(f - b\right)\left(\overline{c} - \overline{b}\right)\left(\overline{f} - \overline{a}\right) - \left(\overline{c} - \overline{a}\right)\left(\overline{f} - \overline{b}\right)\left(c - b\right)\left(f - a\right) = 0.$ <p>The first corresponds to the case where $$A, B, C, D, E, F$$ are involutoric. The second corresponds to the case where $$A, B, C, F$$ are concyclic; similarly, we obtain that $$A, B, C, D$$ and $$A, B, C, E$$ are concyclic, together implying that $$A, B, C, D, E, F$$ are concyclic. See <a href="https://github.com/xiw/geometry/tree/master/napoleon/parallel.mac">parallel.mac</a> in Maxima for the complete proof. <strong>Q.E.D.</strong></p> <h2 id="summary">Summary</h2> <p>Below is a list of the Maxima files used in this post.</p> <ul> <li>Common library: <a href="https://github.com/xiw/geometry/blob/master/lib/geometry.mac">geometry.mac</a> (see <a href="/blog/2020/08/31/automated-geometry-bashing/"><em>Automated geometry bashing</em></a>)</li> <li>Proof of Theorems 2 and 3: <a href="https://github.com/xiw/geometry/tree/master/napoleon/area.mac">area.mac</a></li> <li>Proof of Theorem 4: <a href="https://github.com/xiw/geometry/tree/master/napoleon/parallel.mac">parallel.mac</a></li> </ul> <p><strong>Acknowledgments</strong>: Zhonghao Ye provided a copy of my original proof of Theorems 2 and 3. Zhilei Xu provided feedback on a draft of this post.</p> <h2 id="references">References</h2> <ul> <li> <p><a id="coxeter-and-greitzer-1967"></a>H. S. M. Coxeter and S. L. Greitzer. (1967). Geometry Revisited. The Mathematical Association of America.</p> </li> <li> <p><a id="deaux-1956"></a>Roland Deaux. 1956. Introduction to the Geometry of Complex Numbers. Translated by Howard Eves.</p> </li> <li> <p><a id="ye-and-cao-1997"></a>Zhonghao Ye and Gang Cao. (1997, September). Solutions to Problem 165. Bulletin of Mathematics, no. 9. 问题征解栏: 165题的解答. 数学通讯.</p> </li> <li> <p><a id="zhao-2011"></a>Yong Zhao. (2011). A Survey of Involutoric Hexagons. 完美六边形研究综述 (<a href="https://bbs.cnool.net/5443327.html">draft</a>).</p> </li> </ul> <style> .theorem { padding-left: .5em; border-left: 1px solid; } </style>Xi WangNB: I came across a survey paper (Zhao, 2011) on some plane geometry problems that a group of friends and I explored around 2000. It’s a nice trip down the memory lane. I played with computer-aided proofs using Maxima recently and thought it might be fun to write down the results.Automated geometry bashing2020-08-31T00:00:00+00:002020-08-31T00:00:00+00:00http://kqueue.org/blog/2020/08/31/automated-geometry-bashing<p>The <a href="https://imo-grand-challenge.github.io/">IMO Grand Challenge</a> aims to “build an AI that can win a gold medal in the (International Mathematical Olympiad) competition.” Geometry problems are an attractive target (e.g., see <a href="https://mathoverflow.net/questions/337558/automatically-solving-olympiad-geometry-problems">this MathOverflow discussion</a> or <a href="https://leanprover-community.github.io/archive/stream/208328-IMO-grand-challenge/">this Lean’s Zulip stream</a>), given the rich literature on <a href="http://www.mat.uc.pt/~pedro/cientificos/presentationUrbino2019.pdf">automated theorem proving in geometry</a>.</p> <p>Olympiad participants have long been using well-known <em>computational</em> techniques (i.e., “bashing”) to solve geometry problems, such as complex numbers or barycentric coordinates. How much can we automate these techniques? As a case study, I write programs to solve the two geometry problems in <a href="https://www.imo2019.uk/">IMO 2019</a>. For this post, I use <a href="http://maxima.sourceforge.net/">Maxima</a>, a computer algebra system (easy to install via apt-get on Debian/Ubuntu or Homebrew on macOS). The focus is to explore how to encode these geometry problems such that they can be solved by Maxima within a reasonable amount of time.</p> <p>NB: I also tried several SMT solvers that support <a href="https://smt-comp.github.io/2020/results/qf-nra-single-query">nonlinear real arithmetic</a>. I was able to prove <a href="https://en.wikipedia.org/wiki/Napoleon%27s_theorem">Napoleon’s theorem</a> (the first example below), but not the other two IMO 2019 problems (with a 30-minute timeout). If you have success in finding the right SMT encodings, please let me know!</p> <h2 id="complex-numbers">Complex numbers</h2> <p>There are many good notes on encoding geometry problems using complex numbers. Search for “complex bashing” or refer to Evan Chen’s “<a href="https://web.evanchen.cc/handouts/cmplx/en-cmplx.pdf">Bashing Geometry with Complex Numbers</a>”. The high-level idea is to represent each point with Cartesian coordinate $$(x, y)$$ as complex number $$z = x + yi$$. This often leads to simpler calculation.</p> <p>The rest of the post uses $$a$$ (in lower case) to denote the complex number of the corresponding point $$A$$ (in upper case). Below are a few examples.</p> <ul> <li>The reflection of point $$A$$ over the real axis is: $$\overline{a}$$ (i.e., the conjugate of $$a$$).</li> <li>The rotation of point $$A$$ about point $$B$$ by counterclockwise angle $$\theta$$ is: $$(a - b) \exp(i\theta) + b$$.</li> <li>The centroid of triangle $$ABC$$ is: $${(a + b + c)}/{3}$$.</li> </ul> <p>See the file <a href="https://github.com/xiw/geometry/blob/master/lib/geometry.mac">geometry.mac</a> for more results (e.g., collinearity, concyclicity, and intersection) coded up in Maxima, which will be used for later proofs.</p> <p>As a simple example, let’s prove <a href="https://en.wikipedia.org/wiki/Napoleon%27s_theorem">Napoleon’s theorem</a>: the centers of equilateral triangles constructed outward on the sides of a triangle form an equilateral triangle (known as the <em>outer Napoleon triangle</em>).</p> <p> <div id="napoleon" title="Napoleon's theorem" class="cindy-canvas"></div> </p> <script type="text/javascript"> CindyJS({ ports: [{id: "napoleon"}], defaultAppearance: defaultAppearance, geometry: [ {name: "A", type: "Free", pos: [ 1, 3], color: [1, 1, 1], labeled: true, printname: "$A$"}, {name: "B", type: "Free", pos: [-1, -3], color: [1, 1, 1], labeled: true, printname: "$B$"}, {name: "C", type: "Free", pos: [ 5, -3], color: [1, 1, 1], labeled: true, printname: "$C$"}, {name: "BC", type: "Segment", args: ["B", "C"]}, {name: "CA", type: "Segment", args: ["C", "A"]}, {name: "AB", type: "Segment", args: ["A", "B"]}, {name: "l_BL", type: "LineByFixedAngle", angle: -60 * Math.PI / 180, args: ["BC", "B"], visible: false}, {name: "l_CL", type: "LineByFixedAngle", angle: 60 * Math.PI / 180, args: ["BC", "C"], visible: false}, {name: "L", type: "Meet", args: ["l_BL", "l_CL"], labeled: true, printname: "$M_1$"}, {name: "BL", type: "Segment", args: ["B", "L"]}, {name: "CL", type: "Segment", args: ["C", "L"]}, {name: "c_BCL", type: "CircleBy3", args: ["B", "C", "L"], visible: false}, {name: "D", type: "CenterOfConic", args: ["c_BCL"], labeled: true, printname: "$N_1$"}, {name: "l_CM", type: "LineByFixedAngle", angle: -60 * Math.PI / 180, args: ["CA", "C"], visible: false}, {name: "l_AM", type: "LineByFixedAngle", angle: 60 * Math.PI / 180, args: ["CA", "A"], visible: false}, {name: "M", type: "Meet", args: ["l_CM", "l_AM"], labeled: true, printname: "$M_2$"}, {name: "CM", type: "Segment", args: ["C", "M"]}, {name: "AM", type: "Segment", args: ["A", "M"]}, {name: "c_CAM", type: "CircleBy3", args: ["C", "A", "M"], visible: false}, {name: "E", type: "CenterOfConic", args: ["c_CAM"], labeled: true, printname: "$N_2$"}, {name: "l_AN", type: "LineByFixedAngle", angle: -60 * Math.PI / 180, args: ["AB", "A"], visible: false}, {name: "l_BN", type: "LineByFixedAngle", angle: 60 * Math.PI / 180, args: ["AB", "B"], visible: false}, {name: "N", type: "Meet", args: ["l_AN", "l_BN"], labeled: true, printname: "$M_3$"}, {name: "AN", type: "Segment", args: ["A", "N"]}, {name: "BN", type: "Segment", args: ["B", "N"]}, {name: "c_ABN", type: "CircleBy3", args: ["A", "B", "N"], visible: false}, {name: "F", type: "CenterOfConic", args: ["c_ABN"], labeled: true, printname: "$N_3$"}, {name: "DEF", type: "Poly", fillcolor: [0, 1, 0], fillalpha: 0.5, args: ["D", "E", "F"], alpha: 0.2} ] }); </script> <p>Let’s do the calculation manually first. $$A, B, C$$ are free points.</p> <p>$$M_1$$ is the rotation of $$B$$ about $$C$$ by $$60\degree$$ (i.e., $$\pi/3$$):</p> $m_1 = (b - c)\exp(i\pi/3) + c = \dfrac{(\sqrt{3}i + 1)b - (\sqrt{3}i - 1)c}{2}.$ <p>$$N_1$$ is the center (centroid) of equilateral triangle $$M_1BC$$, $$(m_1 + b + c) / 3$$. Therefore, we have:</p> $n_1 = \dfrac{(\sqrt{3}i + 3)b - (\sqrt{3}i - 3)c}{6}.$ <p>Similarly,</p> \begin{aligned} n_2 &amp; = \dfrac{(\sqrt{3}i + 3)c - (\sqrt{3}i - 3)a}{6} \\ n_3 &amp; = \dfrac{(\sqrt{3}i + 3)a - (\sqrt{3}i - 3)b}{6}. \end{aligned} <p>With all the points calculated, it’s straightforward to show that triangle $$N_1N_2N_3$$ is equilateral using any of the following encodings for equilaterality:</p> <ul> <li>$$(n_3 - n_2)\exp(i\pi/3) + n_2 = n_1$$,</li> <li>$$\lvert n_1 - n_2\rvert = \lvert n_2 - n_3\rvert = \lvert n_3 - n_1\rvert$$,</li> <li>$$n_1^2 + n_2^2 + n_3^2 = n_1n_2 + n_2n_3 + n_3n_1$$ (see <a href="https://proofwiki.org/wiki/Vertices_of_Equilateral_Triangle_in_Complex_Plane">proof</a>).</li> </ul> <p>Now let’s automate the calculation using the following Maxima program:</p> <pre><code class="language-maxima">load("geometry.mac"); /* Declare triangle ABC. */ declare([a, b, c], complex); /* Compute the outer Napoleon triangle. */ p(x, y) := centroid(x, y, rotate(x, y, %pi/3)); [n1, n2, n3] : [p(b, c), p(c, a), p(a, b)]; /* Prove that the outer Napoleon triangle is equilateral. */ prove(equilateral(n1, n2, n3)); quit(); </code></pre> <p>The <code class="language-plaintext highlighter-rouge">prove</code> function (defined in <a href="https://github.com/xiw/geometry/blob/master/lib/geometry.mac">geometry.mac</a>) calls Maxima’s built-in <code class="language-plaintext highlighter-rouge">is</code> function to determine whether a predicate is <em>provably</em> true. Below is the execution trace of this program:</p> <pre><code class="language-maxima-console">% maxima -b napoleon.mac Maxima 5.44.0 http://maxima.sourceforge.net using Lisp SBCL 2.0.5 Distributed under the GNU Public License. See the file COPYING. Dedicated to the memory of William Schelter. The function bug_report() provides bug reporting information. (%i1) batch("napoleon.mac") read and interpret napoleon.mac (%i2) load("geometry.mac") (%o2) geometry.mac (%i3) declare([a,b,c],complex) (%o3) done (%i4) p(x,y):=centroid(x,y,rotate(x,y,%pi/3)) %pi (%o4) p(x, y) := centroid(x, y, rotate(x, y, ---)) 3 (%i5) [n1,n2,n3]:[p(b,c),p(c,a),p(a,b)] sqrt(3) %i 1 sqrt(3) %i 1 2 c + (---------- + -) (b - c) + b (---------- + -) (c - a) + c + 2 a 2 2 2 2 (%o5) [----------------------------------, ----------------------------------, 3 3 sqrt(3) %i 1 2 b + (---------- + -) (a - b) + a 2 2 ----------------------------------] 3 (%i6) prove(equilateral(n1,n2,n3)) (%o6) true (%i7) quit() </code></pre> <p>If we attempt to prove something false, such as $$N_1N_2C$$ equilateral, the execution fails:</p> <pre><code class="language-maxima-console">(%i6) prove(equilateral(n1,n2,c)) prove(equilateral(n1,n2,c)) Unable to evaluate predicate errexp1 ... -- an error. To debug this try: debugmode(true); </code></pre> <p><strong>Exercise 1</strong>: Extend the above program to prove that the center of the outer Napoleon triangle $$N_1N_2N_3$$ corresponds to the centroid of triangle $$ABC$$.</p> <p><strong>Exercise 2</strong>: Prove Napoleon’s theorem using an SMT solver or Gröbner bases.</p> <ul> <li> <p>For SMT, you may find Leonardo de Moura’s “<a href="https://leodemoura.github.io/blog/2013/01/26/complex.html">Complex Numbers in Z3</a>” useful. To make the code work in Python 3, change <code class="language-plaintext highlighter-rouge">__div__</code> to <code class="language-plaintext highlighter-rouge">__truediv__</code> and <code class="language-plaintext highlighter-rouge">__rdiv__</code> to <code class="language-plaintext highlighter-rouge">__rtruediv__</code>.</p> </li> <li> <p>For Gröbner bases, you may find Dan Roozemond’s “<a href="http://magma.maths.usyd.edu.au/~danr/site/pubs/0310ProvingCinderella.pdf">Gröbner Bases in Practice</a>” chapter useful. The paper uses the <span style="font-variant:small-caps;"><a href="https://www.singular.uni-kl.de/">Singular</a></span> computer algebra system. <a href="http://maxima.sourceforge.net/">Maxima</a> and <a href="https://www.sympy.org/">SymPy</a> should also work.</p> </li> </ul> <h2 id="imo-2019-problem-2">IMO 2019, problem 2</h2> <p>In triangle $$ABC$$, point $$A_1$$ lies on side $$BC$$ and point $$B_1$$ lies on side $$AC$$. Let $$P$$ and $$Q$$ be points on segments $$AA_1$$ and $$BB_1$$, respectively, such that $$PQ$$ is parallel to $$AB$$. Let $$P_1$$ be a point on line $$PB_1$$, such that $$B_1$$ lies strictly between $$P$$ and $$P_1$$, and $$\angle PP_1C = \angle BAC$$. Similarly, let $$Q_1$$ be a point on line $$QA_1$$, such that $$A_1$$ lies strictly between $$Q$$ and $$Q_1$$, and $$\angle CQ_1Q = \angle CBA$$.</p> <p>Prove that points $$P$$, $$Q$$, $$P_1$$, and $$Q_1$$ are concyclic.</p> <p> <div id="imo2019p2" title="IMO 2019, problem 2" class="cindy-canvas"></div> </p> <script type="text/javascript"> CindyJS({ ports: [{id: "imo2019p2"}], defaultAppearance: defaultAppearance, geometry: [ // ABC {name: "A", type: "Free", pos: [-4, -8], color: [1, 1, 1], labeled: true, printname: "$A$"}, {name: "B", type: "Free", pos: [ 8, -8], color: [1, 1, 1], labeled: true, printname: "$B$"}, {name: "C", type: "Free", pos: [ 1, 0], color: [1, 1, 1], labeled: true, printname: "$C$"}, {name: "c0", type: "ArcBy3", args: ["B", "C", "A"], visible: false}, {name: "BC", type: "Segment", args: ["B", "C"]}, {name: "CA", type: "Segment", args: ["C", "A"]}, {name: "AB", type: "Segment", args: ["A", "B"]}, // A1 {name: "A1", type: "PointOnLine", args: ["BC"], pos: [6, -2.5], labeled: true, printname: "$A_1$"}, {name: "AA1", type: "Segment", args: ["A", "A1"]}, {name: "A2", type: "OtherIntersectionCL", args: ["c0", "AA1", "A"], visible: false}, // B1 {name: "B1", type: "PointOnLine", args: ["CA"], pos: [-2.5, -3.5], labeled: true, printname: "$B_1$"}, {name: "BB1", type: "Segment", args: ["B", "B1"]}, {name: "B2", type: "OtherIntersectionCL", args: ["c0", "BB1", "B"], visible: false}, // PQ {name: "K", type: "Free", pos: [ 1.6, -6.5], color: [1, 1, 1], labeled: true, printname: "$K$"}, {name: "l_PQ", type: "Parallel", args: ["AB", "K"], alpha: .2}, {name: "l_AA2", type: "Join", args: ["A", "A2"], visible: false}, {name: "l_BB2", type: "Join", args: ["B", "B2"], visible: false}, {name: "P", type: "Meet", args: ["l_AA2", "l_PQ"], labeled: true, printname: "$P$"}, {name: "Q", type: "Meet", args: ["l_BB2", "l_PQ"], labeled: true, printname: "$Q$"}, {name: "PQ", type: "Segment", args: ["P", "Q"]}, // P1 {name: "c_P", type: "CircleBy3", args: ["C", "B1", "B2"], visible: false}, {name: "l_PB1", type: "Join", args: ["P", "B1"], visible: false}, {name: "P1", type: "OtherIntersectionCL", args: ["c_P", "l_PB1", "B1"], labeled: true, printname: "$P_1$"}, {name: "PP1", type: "Segment", args: ["P", "P1"]}, {name: "CP1", type: "Segment", args: ["C", "P1"]}, // Q1 {name: "c_Q", type: "CircleBy3", args: ["C", "A1", "A2"], visible: false}, {name: "l_QA1", type: "Join", args: ["Q", "A1"], visible: false}, {name: "Q1", type: "OtherIntersectionCL", args: ["c_Q", "l_QA1", "A1"], labeled: true, printname: "$Q_1$"}, {name: "QP1", type: "Segment", args: ["Q", "Q1"]}, {name: "CQ1", type: "Segment", args: ["C", "Q1"]}, // P1PQQ1 {name: "c_PQ", type: "CircleBy3", args: ["P1", "P", "Q1"], alpha: .2}, ] }); </script> <p>See the solution in Maxima <a href="https://github.com/xiw/geometry/blob/master/imo2019/imo2019p2.mac">imo2019p2.mac</a>.</p> <p>Here’s the high-level flow. $$A, B, C$$ are free points, from which we derive $$A_1$$ and $$B_1$$. We derive $$P$$ and $$Q$$ by introducing another free point $$K$$, the line through which parallel to $$AB$$ intersects $$AA_1$$ and $$BB_1$$ at $$P$$ and $$Q$$, respectively. The remaining question is how to encode $$P_1$$ and $$Q_1$$.</p> <p>First, $$P_1$$ on $$PB_1$$ may translate to:</p> $\frac{p_1 - b_1}{b_1 - p} = \overline{\left(\frac{p_1 - b_1}{b_1 - p}\right)}.$ <p>$$\angle PP_1C = \angle BAC$$ may translate to:</p> $\frac{p_1 - c}{p_1 - p} / \frac{a - c}{a - b} = \overline{\left(\frac{p_1 - c}{p_1 - p} / \frac{a - c}{a - b}\right)}.$ <p>Since $$a, b, c, p$$ are known at this point, one may solve $$p_1$$ by eliminating $$\overline{p_1}$$ from the above two equations (by hand or using Maxima’s <code class="language-plaintext highlighter-rouge">solve</code> function). One may solve $$q_1$$ similarly.</p> <p>Next, it’s straightforward to prove that $$P, Q, P_1, Q_1$$ are concyclic by testing the following:</p> $\frac{p_1 - p}{p_1 - q} / \frac{q_1 - p}{q_1 - q} = \overline{\left(\frac{p_1 - p}{p_1 - q} / \frac{q_1 - p}{q_1 - q}\right)}.$ <p>Maxima reported the following error on my first attempt:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PQUOTIENT: Quotient by a polynomial of higher degree (case 2b) -- an error. To debug this try: debugmode(true); </code></pre></div></div> <p>I worked around this error by choosing $$C$$ as the origin (i.e., $$c = 0$$), with which Maxima is able to finish the proof.</p> <p>Notice that the problem description is not <em>constructive</em>: it asserts the existence of points $$P_1$$ and $$Q_1$$ but doesn’t say how to construct them—imagine how to draw the figure without solving polynomials. Below is one way to rephrase the problem (in fact, that’s how I drew the above figure), inspired by “solution 1” of this problem’s <a href="https://www.imo2019.uk/">reference solutions</a>.</p> <p>Problem 2*: Points $$A_2$$ and $$B_2$$ are on the circumcircle of triangle $$ABC$$. $$AA_2$$ intersects $$BC$$ at $$A_1$$. $$BB_2$$ intersects $$CA$$ at $$B_1$$. Let $$P$$ and $$Q$$ be points on segments $$AA_1$$ and $$BB_1$$, respectively, such that $$PQ$$ is parallel to $$AB$$. $$PB_1$$ intersects the circumcircle of triangle $$CB_1B_2$$ again at $$P_1$$. $$QA_1$$ intersects the circumcircle of triangle $$CA_1A_2$$ again at $$Q_1$$.</p> <p>Prove that points $$A_2, B_2, P, Q, P_1, Q_1$$ are concyclic.</p> <p> <div id="imo2019p2e3" title="IMO 2019, problem 2" class="cindy-canvas"></div> </p> <script type="text/javascript"> CindyJS({ ports: [{id: "imo2019p2e3"}], defaultAppearance: defaultAppearance, geometry: [ // ABC {name: "A", type: "Free", pos: [-4, -8], color: [1, 1, 1], labeled: true, printname: "$A$"}, {name: "B", type: "Free", pos: [ 8, -8], color: [1, 1, 1], labeled: true, printname: "$B$"}, {name: "C", type: "Free", pos: [ 1, 0], color: [1, 1, 1], labeled: true, printname: "$C$"}, {name: "c0", type: "ArcBy3", args: ["B", "C", "A"], alpha: .2}, {name: "BC", type: "Segment", args: ["B", "C"]}, {name: "CA", type: "Segment", args: ["C", "A"]}, {name: "AB", type: "Segment", args: ["A", "B"]}, // A1 {name: "A2", type: "PointOnCircle", args: ["c0"], pos: [7, -2], color: [1, 1, 1], labeled: true, printname: "$A_2$"}, {name: "AA2", type: "Segment", args: ["A", "A2"]}, {name: "A1", type: "Meet", args: ["BC", "AA2"], labeled: true, printname: "$A_1$"}, // B1 {name: "B2", type: "PointOnCircle", args: ["c0"], pos: [-3, -3], color: [1, 1, 1], labeled: true, printname: "$B_2$"}, {name: "BB2", type: "Segment", args: ["B", "B2"]}, {name: "B1", type: "Meet", args: ["CA", "BB2"], labeled: true, printname: "$B_1$"}, // PQ {name: "K", type: "Free", pos: [ 1.6, -6.5], color: [1, 1, 1]}, {name: "l_PQ", type: "Parallel", args: ["AB", "K"], alpha: .2}, {name: "l_AA2", type: "Join", args: ["A", "A2"], visible: false}, {name: "l_BB2", type: "Join", args: ["B", "B2"], visible: false}, {name: "P", type: "Meet", args: ["l_AA2", "l_PQ"], labeled: true, printname: "$P$"}, {name: "Q", type: "Meet", args: ["l_BB2", "l_PQ"], labeled: true, printname: "$Q$"}, {name: "PQ", type: "Segment", args: ["P", "Q"]}, // P1 {name: "c_P", type: "CircleBy3", args: ["C", "B1", "B2"], alpha: .2}, {name: "l_PB1", type: "Join", args: ["P", "B1"], visible: false}, {name: "P1", type: "OtherIntersectionCL", args: ["c_P", "l_PB1", "B1"], labeled: true, printname: "$P_1$"}, {name: "PP1", type: "Segment", args: ["P", "P1"]}, {name: "CP1", type: "Segment", args: ["C", "P1"]}, // Q1 {name: "c_Q", type: "CircleBy3", args: ["C", "A1", "A2"], alpha: .2}, {name: "l_QA1", type: "Join", args: ["Q", "A1"], visible: false}, {name: "Q1", type: "OtherIntersectionCL", args: ["c_Q", "l_QA1", "A1"], labeled: true, printname: "$Q_1$"}, {name: "QP1", type: "Segment", args: ["Q", "Q1"]}, {name: "CQ1", type: "Segment", args: ["C", "Q1"]}, // P1PQQ1 {name: "c_PQ", type: "CircleBy3", args: ["P1", "P", "Q1"], alpha: .2}, {name: "CA2", type: "Segment", args: ["C", "A2"], alpha: .2}, {name: "CB2", type: "Segment", args: ["C", "B2"], alpha: .2}, ] }); </script> <p>It’s easy to see that this constructive version is equivalent to the original problem, given $$\angle PP_1C = \angle B_1B_2C = \angle CAB$$ and $$\angle QQ_1C = \angle A_1A_2C = \angle CBA$$.</p> <p><strong>Exercise 3</strong>: Write a Maxima program to prove problem 2*.</p> <h2 id="imo-2019-problem-6">IMO 2019, problem 6</h2> <p>Let $$I$$ be the incenter of acute triangle $$ABC$$ with $$AB \neq AC$$. The incircle $$\omega$$ of $$ABC$$ is tangent to sides $$BC$$, $$CA$$, and $$AB$$ at $$D$$, $$E$$, and $$F$$, respectively. The line through $$D$$ perpendicular to $$EF$$ meets $$\omega$$ again at $$R$$. Line $$AR$$ meets $$\omega$$ again at $$P$$. The circumcircles of triangles $$PCE$$ and $$PBF$$ meet again at $$Q$$.</p> <p>Prove that lines $$DI$$ and $$PQ$$ meet on the line through $$A$$ perpendicular to $$AI$$.</p> <p> <div id="imo2019p6" title="IMO 2019, problem 6" class="cindy-canvas"></div> </p> <script type="text/javascript"> CindyJS({ ports: [{id: "imo2019p6"}], defaultAppearance: defaultAppearance, geometry: [ // ABC {name: "A", type: "Free", pos: [5.2, 3], color: [1, 1, 1], labeled: true, printname: "$A$"}, {name: "B", type: "Free", pos: [-4, -6], color: [1, 1, 1], labeled: true, printname: "$B$"}, {name: "C", type: "Free", pos: [ 8, -6], color: [1, 1, 1], labeled: true, printname: "$C$"}, {name: "BC", type: "Segment", args: ["B", "C"]}, {name: "CA", type: "Segment", args: ["C", "A"]}, {name: "AB", type: "Segment", args: ["A", "B"]}, // I {name: "ls_B", type: "AngularBisector", args: ["AB", "BC", "B"]}, {name: "l_B", type: "SelectL", args: ["ls_B"], index: 2, visible: false}, {name: "ls_C", type: "AngularBisector", args: ["BC", "CA", "C"]}, {name: "l_C", type: "SelectL", args: ["ls_C"], visible: false}, {name: "I", type: "Meet", args: ["l_B", "l_C"], labeled: true, printname: "$I$"}, // DEF {name: "l_ID", type: "Orthogonal", args: ["BC", "I"], visible: false}, {name: "D", type: "Meet", args: ["l_ID", "BC"], labeled: true, printname: "$D$"}, {name: "l_IE", type: "Orthogonal", args: ["CA", "I"], visible: false}, {name: "E", type: "Meet", args: ["l_IE", "CA"], labeled: true, printname: "$E$"}, {name: "l_IF", type: "Orthogonal", args: ["AB", "I"], visible: false}, {name: "F", type: "Meet", args: ["l_IF", "AB"], labeled: true, printname: "$F$"}, {name: "c0", type: "CircleBy3", args: ["D", "E", "F"]}, // R {name: "EF", type: "Segment", args: ["E", "F"]}, {name: "l_DR", type: "Orthogonal", args: ["EF", "D"], visible: false}, {name: "R", type: "OtherIntersectionCL", args: ["c0", "l_DR", "D"], labeled: true, printname: "$R$"}, {name: "DR", type: "Segment", args: ["D", "R"]}, // P {name: "l_AR", type: "Join", args: ["A", "R"], visible: false}, {name: "P", type: "OtherIntersectionCL", args: ["c0", "l_AR", "R"], labeled: true, printname: "$P$"}, {name: "AP", type: "Segment", args: ["A", "P"]}, // Q {name: "c_B", type: "CircleBy3", args: ["P", "B", "F"], alpha: .2}, {name: "c_C", type: "CircleBy3", args: ["P", "C", "E"], alpha: .2}, {name: "Q", type: "OtherIntersectionCC", args: ["c_B", "c_C", "P"], labeled: true, printname: "$Q$"}, // DI, PQ, AL {name: "l_DI", type: "Join", args: ["D", "I"], visible: false}, {name: "l_PQ", type: "Join", args: ["P", "Q"], visible: false}, {name: "L", type: "Meet", args: ["l_DI", "l_PQ"], labeled: true, printname: "$L$"}, {name: "IA", type: "Segment", args: ["A", "I"]}, {name: "LD", type: "Segment", args: ["L", "D"]}, {name: "LP", type: "Segment", args: ["L", "P"]}, {name: "l_AL", type: "Orthogonal", args: ["IA", "A"], alpha: .2}, ] }); </script> <p>See the solution in Maxima <a href="https://github.com/xiw/geometry/blob/master/imo2019/imo2019p6.mac">imo2019p6.mac</a>.</p> <p>The problem description is constructive, and it’s fairly straightforward to calculate each point. Two optimizations are worth mentioning.</p> <p>First, to avoid calculating the incenter, a standard approach is to choose $$I$$ as the origin and $$D, E, F$$ as points on a unit circle. Let’s introduce angle $$\theta_d$$ such that $$D$$ is $$\exp(i\theta_d)$$; $$E$$ and $$F$$ are calculated similarly. The rest of the points can be derived from $$D, E, F$$.</p> <p>Second, choose $$IA$$ as the real axis (i.e., $$\theta_f = -\theta_e$$), which exploits the symmetry of $$E$$ and $$F$$. Without this optimization, the program timed out (in 30 minutes).</p> <p>One may notice the complexity in calculating $$L$$, the intersection of $$DI$$ and $$PQ$$, which is due to the complexity in $$P$$ and $$Q$$. One possible optimization to calculate $$L$$ from points with simpler coordinates, $$DI$$ and the line through $$A$$ perpendicular to $$AI$$, and instead prove that $$P, Q, L$$ are collinear.</p> <p><strong>Exercise 4</strong>: Optimize the Maxima code using the above approach.</p> <h2 id="summary">Summary</h2> <p>Below is a list of the Maxima files used in this post.</p> <ul> <li>Common library: <a href="https://github.com/xiw/geometry/blob/master/lib/geometry.mac">geometry.mac</a></li> <li>IMO 2019, problem 2: <a href="https://github.com/xiw/geometry/blob/master/imo2019/imo2019p2.mac">imo2019p2.mac</a></li> <li>IMO 2019, problem 6: <a href="https://github.com/xiw/geometry/blob/master/imo2019/imo2019p6.mac">imo2019p6.mac</a></li> <li>Solution to exercise 3: <a href="https://github.com/xiw/geometry/blob/master/imo2019/imo2019p2e3.mac">imo2019p2e3.mac</a></li> <li>Solution to exercise 4: <a href="https://github.com/xiw/geometry/blob/master/imo2019p6e4.mac">imo2019p6e4.mac</a></li> </ul> <p>The proofs described in this post are close to those written by hand using complex numbers. The optimizations are fairly standard, such as choosing a point as the origin or choosing the real axis to exploit symmetry, which might be possible to automate in the future.</p> <p>The <a href="https://imo-grand-challenge.github.io/">IMO Grand Challenge</a> expects an AI to produce mechanical proofs that can be checked by the <a href="https://leanprover.github.io/">Lean</a> theorem prover. Some of the other IMO 2019 problems are already formalized in Lean (e.g., <a href="https://xenaproject.wordpress.com/2019/08/01/imo-2019-q1/">problem 1</a> by Kevin Buzzard and <a href="https://gist.github.com/fpvandoorn/e0bd9d116a59a5f01d1d661f3677b72f">problem 4</a> by Floris van Doorn). Maxima doesn’t produce Lean-checkable proofs, so it requires further work (e.g., building on efforts such as <a href="https://geocoq.github.io/GeoCoq/">GeoCoq</a>). Another important factor is <a href="https://github.com/IMO-grand-challenge/formal-encoding">how these problems will be encoded formally</a>.</p> <p><strong>Acknowledgments</strong>: Luke Nelson, Emina Torlak, and Zhilei Xu provided feedback on a draft of this post.</p>Xi WangThe IMO Grand Challenge aims to “build an AI that can win a gold medal in the (International Mathematical Olympiad) competition.” Geometry problems are an attractive target (e.g., see this MathOverflow discussion or this Lean’s Zulip stream), given the rich literature on automated theorem proving in geometry.The shifting range in RISC-V2020-08-22T00:00:00+00:002020-08-22T00:00:00+00:00http://kqueue.org/blog/2020/08/22/shifting-range<p>There are several commonly used RISC-V <a href="https://arxiv.org/pdf/1607.02318.pdf">instruction pairs</a> with 32-bit immediates. Below is an example of loading a 32-bit immediate into a register using <code class="language-plaintext highlighter-rouge">lui</code>/<code class="language-plaintext highlighter-rouge">addi</code>:</p> <pre><code class="language-riscvasm">lui rd,imm[31:12] addi rd,rd,imm[11:0] </code></pre> <p>Here <code class="language-plaintext highlighter-rouge">lui</code> places a (<em>sign</em>-extended) 20-bit immediate into register <code class="language-plaintext highlighter-rouge">rd</code> and fills the lowest 12 bits with zeros, and <code class="language-plaintext highlighter-rouge">addi</code> adds a <em>sign</em>-extended 12-bit immediate to register <code class="language-plaintext highlighter-rouge">rd</code>.</p> <p><strong>Question</strong>: does this work for any 32-bit immediate? It may be trickier than you think.</p> <p>Clearly, on <em>32-bit</em> systems, this instruction pair can be used to load any 32-bit immediate in the range [-2<sup>31</sup>, 2<sup>31</sup>-1]. For example, to load <code class="language-plaintext highlighter-rouge">0x7fffff00</code>, one may use <code class="language-plaintext highlighter-rouge">lui</code> with a 20-bit <code class="language-plaintext highlighter-rouge">0x80000</code> and <code class="language-plaintext highlighter-rouge">addi</code> with a 12-bit <code class="language-plaintext highlighter-rouge">0xf00</code>, as adding <code class="language-plaintext highlighter-rouge">0x8000000</code> and <code class="language-plaintext highlighter-rouge">0xffffff00</code> (sign-extended from <code class="language-plaintext highlighter-rouge">0xf00</code>) produces <code class="language-plaintext highlighter-rouge">0x7fffff00</code>.</p> <p>How about <em>64-bit</em> systems? Specifically, let’s look at loading the 32-bit immediate <code class="language-plaintext highlighter-rouge">0x7fffff00</code> again. Note that the same 20-bit and 12-bit values used on 32-bit systems won’t work, as adding <code class="language-plaintext highlighter-rouge">0xffffffff_8000000</code> (sign-extended from <code class="language-plaintext highlighter-rouge">0x80000000</code>) and <code class="language-plaintext highlighter-rouge">0xffffffff_ffffff00</code> (sign-extended from <code class="language-plaintext highlighter-rouge">0xf00</code>) produces <code class="language-plaintext highlighter-rouge">0x0fffffff_f7ffff00</code>. Does there exist any 20-bit and 12-bit values that make <code class="language-plaintext highlighter-rouge">lui</code>/<code class="language-plaintext highlighter-rouge">addi</code> work on 64-bit systems?</p> <p>Similar questions can be asked about other instruction pairs, such as <code class="language-plaintext highlighter-rouge">lui</code>/<code class="language-plaintext highlighter-rouge">ld</code> for loading a value at a 32-bit address, or <code class="language-plaintext highlighter-rouge">auipc</code>/<code class="language-plaintext highlighter-rouge">jalr</code> for jumping to a 32-bit pc-relative offset.</p> <p>The short answer is <em>no</em>. You may be interested in the <a href="https://groups.google.com/a/groups.riscv.org/forum/#!topic/isa-dev/bwWFhBnnZFQ">discussion in the RISC-V ISA Dev group</a> started by Luke Nelson, which prompted the RISC-V ISA specification to clarify the range in the “RV64I Base Integer Instruction Set” chapter:</p> <blockquote> <p>Note that the set of address offsets that can be formed by pairing LUI with LD, AUIPC with JALR, etc. in RV64I is [−2<sup>31</sup>−2<sup>11</sup>, 2<sup>31</sup>−2<sup>11</sup>−1].</p> </blockquote> <p>In other words, the 32-bit range reachable by such instruction pairs in 64-bit RISC-V is shifted by -2<sup>11</sup> from [-2<sup>31</sup>, 2<sup>31</sup>-1]. Therefore, <code class="language-plaintext highlighter-rouge">0x7fffff00</code> doesn’t fall in the range.</p> <p>Intuitively, the shifting is caused by the choice of the sign extension of immediates in the RISC-V ISA. See examples of issues reported in <a href="https://blogs.coreboot.org/blog/2016/06/13/gsoc-better-risc-v-support-week-3/">coreboot</a> and <a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=489553dd13a88d8a882db10622ba8b9b58582ce4">the BPF JIT for RV64 in the Linux kernel</a>, as well as our upcoming <a href="https://unsat.cs.washington.edu/papers/#nelson:jitterbug">Jitterbug paper</a>.</p> <p>To check the correctness of the range, I wrote a simple <a href="https://emina.github.io/rosette/">Rosette</a> program, as follows:</p> <div class="language-racket highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">#</span><span class="nv">lang</span> <span class="nv">rosette</span> <span class="c1">; integer register width in bits</span> <span class="p">(</span><span class="k">define</span> <span class="nv">XLEN</span> <span class="mi">64</span><span class="p">)</span> <span class="c1">; symbolic 20-bit and 12-bit values</span> <span class="p">(</span><span class="nf">define-symbolic</span> <span class="nv">imm20</span> <span class="p">(</span><span class="nf">bitvector</span> <span class="mi">20</span><span class="p">))</span> <span class="p">(</span><span class="nf">define-symbolic</span> <span class="nv">imm12</span> <span class="p">(</span><span class="nf">bitvector</span> <span class="mi">12</span><span class="p">))</span> <span class="c1">; mimic the result of an instruction pair</span> <span class="p">(</span><span class="k">define</span> <span class="nv">v</span> <span class="p">(</span><span class="nf">bvadd</span> <span class="p">(</span><span class="nf">sign-extend</span> <span class="p">(</span><span class="nf">concat</span> <span class="nv">imm20</span> <span class="p">(</span><span class="nf">bv</span> <span class="mi">0</span> <span class="mi">12</span><span class="p">))</span> <span class="p">(</span><span class="nf">bitvector</span> <span class="nv">XLEN</span><span class="p">))</span> <span class="p">(</span><span class="nf">sign-extend</span> <span class="nv">imm12</span> <span class="p">(</span><span class="nf">bitvector</span> <span class="nv">XLEN</span><span class="p">))))</span> <span class="c1">; lower and upper bounds</span> <span class="p">(</span><span class="nf">define-symbolic</span> <span class="nv">lower</span> <span class="nv">upper</span> <span class="p">(</span><span class="nf">bitvector</span> <span class="nv">XLEN</span><span class="p">))</span> <span class="c1">; find the lower and upper bounds via optimization</span> <span class="p">(</span><span class="nf">optimize</span> <span class="nt">#:maximize</span> <span class="p">(</span><span class="nb">list</span> <span class="nv">lower</span><span class="p">)</span> <span class="nt">#:minimize</span> <span class="p">(</span><span class="nb">list</span> <span class="nv">upper</span><span class="p">)</span> <span class="nt">#:guarantee</span> <span class="p">(</span><span class="nf">assert</span> <span class="p">(</span><span class="nf">forall</span> <span class="p">(</span><span class="nb">list</span> <span class="nv">imm12</span> <span class="nv">imm20</span><span class="p">)</span> <span class="p">(</span><span class="nf">&amp;&amp;</span> <span class="p">(</span><span class="nf">bvsge</span> <span class="nv">v</span> <span class="nv">lower</span><span class="p">)</span> <span class="p">(</span><span class="nf">bvsle</span> <span class="nv">v</span> <span class="nv">upper</span><span class="p">)))))</span> </code></pre></div></div> <p>Rosette invokes the <a href="https://rise4fun.com/Z3/tutorial/optimization">Z3 SMT solver</a> to find the lower and upper bounds of the reachable range (you may also use SMT or the Z3 API directly). The output of the above program is:</p> <div class="language-racket highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="nf">model</span> <span class="p">[</span><span class="nf">lower</span> <span class="p">(</span><span class="nf">bv</span> <span class="mh">#xffffffff7ffff800</span> <span class="mi">64</span><span class="p">)]</span> <span class="p">[</span><span class="nf">upper</span> <span class="p">(</span><span class="nf">bv</span> <span class="mh">#x000000007ffff7ff</span> <span class="mi">64</span><span class="p">)])</span> </code></pre></div></div> <p>This is consistent with the clarification in the RISC-V ISA specification.</p> <p>Exercise: if you were asked to make one change in the 64-bit RISC-V ISA (e.g., the semantics of an instruction) to make the range remain [-2<sup>31</sup>, 2<sup>31</sup>-1], what would you do? The above Rosette program may be helpful. You may also want to check the <code class="language-plaintext highlighter-rouge">addiw</code> instruction.</p> <p><strong>Acknowledgments</strong>: James Bornholt, Luke Nelson, and Emina Torlak provided feedback on a draft of this post.</p>Xi WangThere are several commonly used RISC-V instruction pairs with 32-bit immediates. Below is an example of loading a 32-bit immediate into a register using lui/addi: lui rd,imm[31:12] addi rd,rd,imm[11:0] Here lui places a (sign-extended) 20-bit immediate into register rd and fills the lowest 12 bits with zeros, and addi adds a sign-extended 12-bit immediate to register rd.Mars retrograde in 20202020-08-19T00:00:00+00:002020-08-19T00:00:00+00:00http://kqueue.org/blog/2020/08/19/mars-retrograde<p>There is a <a href="https://mars.nasa.gov/all-about-mars/night-sky/retrograde/">Mars retrograde</a> this year, from September 9 to November 13. Mars appears to move backwards as Earth overtakes Mars. Below is a plot of its positions in the sky using <a href="https://d3js.org/">d3.js</a>. It also includes Jupiter and Saturn retrogrades, both from mid May to mid September.</p> <p> <center> <div id="mars2020" title="Mars (2020)"></div> </center> </p> <p>As described in the earlier posts <a href="/blog/2016/05/15/mars-close-approach-in-2016/"><em>Mars close approach in 2016</em></a> and <a href="/blog/2013/08/21/mars-or-ufo/"><em>Mars or UFO</em></a>, the coordinates of the planets (<a href="/downloads/code/ti/mars2020.txt">Mars</a>, <a href="/downloads/code/ti/jupiter2020.txt">Jupiter</a>, and <a href="/downloads/code/ti/saturn2020.txt">Saturn</a>) are computed via a <a href="/downloads/code/ti/coordgen.c">small program</a> using NASA’s <a href="http://naif.jpl.nasa.gov/">SPICE toolkit</a>, and the coordinates of the <a href="/downloads/code/ti/stars.txt">stars</a> are retrieved from Wikipedia (e.g., <a href="https://en.wikipedia.org/wiki/List_of_stars_in_Pisces">Pisces</a>).</p> <script src="/downloads/code/ti/ti.js"></script> <script> ti.load("#mars2020", { width: 800, height: 450, margin: {top: 10, bottom: 10}, offset: {ra: 300}, stars: { src: "/downloads/code/ti/stars.txt", map: ["Sagittarius", "Capricornus", "Aquarius", "Pisces", "Aries", "Sculptor", "Pegasus", "Aquila", "Cetus"], }, planets: [{ src: "/downloads/code/ti/mars2020.txt", attr: ti.marsAttr, }, { src: "/downloads/code/ti/jupiter2020.txt", attr: ti.jupiterAttr, }, { src: "/downloads/code/ti/saturn2020.txt", attr: ti.saturnAttr, }], duration: 15, }); </script>Xi WangThere is a Mars retrograde this year, from September 9 to November 13. Mars appears to move backwards as Earth overtakes Mars. Below is a plot of its positions in the sky using d3.js. It also includes Jupiter and Saturn retrogrades, both from mid May to mid September.Mars close approach in 20162016-05-15T00:00:00+00:002016-05-15T00:00:00+00:00http://kqueue.org/blog/2016/05/15/mars-close-approach-in-2016<p>According to NASA, <a href="http://mars.nasa.gov/allaboutmars/nightsky/mars-close-approach/">Mars Close Approach</a> this year is May 30 (or wait until 2018). So I re-ran my program from the earlier post <a href="/blog/2013/08/21/mars-or-ufo/"><em>Mars or UFO</em></a> to compute the locations of Mars in the night sky in 2016, including retrograde from April to June, as shown below:</p> <center> <div id="mars2016" title="Mars (2016)"></div> </center> <p>One thing still missing is updating the size of Mars. Feel free to send me a patch.</p> <script src="/downloads/code/ti/ti.js"></script> <script> ti.load("#mars2016", { width: 800, height: 450, margin: {top: 10, left: 10}, stars: { src: "/downloads/code/ti/stars.txt", map: ["Libra", "Scorpius", "Sagittarius", "Capricornus"], }, planets: [{ src: "/downloads/code/ti/mars2016.txt", attr: ti.marsAttr, }], duration: 20, }); </script>Xi WangAccording to NASA, Mars Close Approach this year is May 30 (or wait until 2018). So I re-ran my program from the earlier post Mars or UFO to compute the locations of Mars in the night sky in 2016, including retrograde from April to June, as shown below:A mini symbolic execution engine2015-05-26T00:00:00+00:002015-05-26T00:00:00+00:00http://kqueue.org/blog/2015/05/26/mini-mc<p>It has been a while since I blogged last time. One change is that I started to have nightmares about me forgetting to prepare for lecture, rather than forgetting to turn in homework as in the past 20(?) years—life does get better.</p> <p>This post is about teaching. Last week in grad OS class (<a href="http://courses.cs.washington.edu/courses/cse551/15sp/">CSE 551</a>) we talked about symbolic execution, using the papers of <a href="http://klee.github.io/">KLEE</a> and <a href="http://research.microsoft.com/en-us/um/people/pg/public_psfiles/ndss2008.pdf">SAGE</a>. To illustrate the basic idea, I wrote a mini symbolic execution implementation (~20 lines of Python code), <a href="https://github.com/xiw/mini-mc">mini-mc</a>, using Z3.</p> <p>Consider a simple example, <a href="https://github.com/xiw/mini-mc/blob/master/test_me.py">test_me.py</a>:</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">mc</span> <span class="kn">import</span> <span class="o">*</span> <span class="k">def</span> <span class="nf">test_me</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">):</span> <span class="n">z</span> <span class="o">=</span> <span class="mi">2</span> <span class="o">*</span> <span class="n">x</span> <span class="k">if</span> <span class="n">z</span> <span class="o">==</span> <span class="n">y</span><span class="p">:</span> <span class="k">if</span> <span class="n">y</span> <span class="o">==</span> <span class="n">x</span> <span class="o">+</span> <span class="mi">10</span><span class="p">:</span> <span class="k">assert</span> <span class="bp">False</span> <span class="n">x</span> <span class="o">=</span> <span class="n">BitVec</span><span class="p">(</span><span class="s">"x"</span><span class="p">,</span> <span class="mi">32</span><span class="p">)</span> <span class="n">y</span> <span class="o">=</span> <span class="n">BitVec</span><span class="p">(</span><span class="s">"y"</span><span class="p">,</span> <span class="mi">32</span><span class="p">)</span> <span class="n">test_me</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">)</span> </code></pre></div></div> <p>Here <code class="language-plaintext highlighter-rouge">test_me()</code> looks like a normal Python function: the program would crash if the execution hits <code class="language-plaintext highlighter-rouge">assert False</code>. The two input variables <code class="language-plaintext highlighter-rouge">x</code> and <code class="language-plaintext highlighter-rouge">y</code> are initialized to symbolic 32-bit integers, through <a href="http://research.microsoft.com/en-us/um/redmond/projects/z3/z3.html">Z3’s python interface</a>.</p> <p>To find input that would trigger a crash with symbolic execution, one can fork at every if-branch, and explore each side of the branch. With <a href="https://github.com/xiw/mini-mc">mini-mc</a>, simply running the Python program will do so, producing the following output:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> assume (2*x == y)  assume ¬(2*x == y)  exit  assume (y == x + 10)  assume ¬(y == x + 10)  exit  Traceback (most recent call last): File "./test_me.py", line 11, in &lt;module&gt; test_me(x, y) File "./test_me.py", line 7, in test_me assert False AssertionError: x = 10, y = 20  exit </code></pre></div></div> <p>The output says that the execution starts with PID 3216, and forks a child process 3217 to explore the false branch of <code class="language-plaintext highlighter-rouge">if z == y</code>, while itself continues to explore the true branch. It does so similarly for a second branch <code class="language-plaintext highlighter-rouge">if y == x + 10</code>, and then hits <code class="language-plaintext highlighter-rouge">assert False</code> with input <code class="language-plaintext highlighter-rouge">x = 10, y = 20</code>.</p> <p>Here’s an almost complete implementation of the symbolic execution engine in <a href="https://github.com/xiw/mini-mc/blob/master/mc.py">mc.py</a>:</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">sched_fork</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> <span class="n">pid</span> <span class="o">=</span> <span class="n">os</span><span class="p">.</span><span class="n">fork</span><span class="p">()</span> <span class="k">if</span> <span class="n">pid</span><span class="p">:</span> <span class="n">solver</span><span class="p">.</span><span class="n">add</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="n">r</span> <span class="o">=</span> <span class="bp">True</span> <span class="n">mc_log</span><span class="p">(</span><span class="s">"assume (%s)"</span> <span class="o">%</span> <span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="bp">self</span><span class="p">),))</span> <span class="k">else</span><span class="p">:</span> <span class="n">solver</span><span class="p">.</span><span class="n">add</span><span class="p">(</span><span class="n">Not</span><span class="p">(</span><span class="bp">self</span><span class="p">))</span> <span class="n">r</span> <span class="o">=</span> <span class="bp">False</span> <span class="n">mc_log</span><span class="p">(</span><span class="s">"assume ¬(%s)"</span> <span class="o">%</span> <span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="bp">self</span><span class="p">),))</span> <span class="k">if</span> <span class="n">solver</span><span class="p">.</span><span class="n">check</span><span class="p">()</span> <span class="o">!=</span> <span class="n">sat</span><span class="p">:</span> <span class="n">mc_log</span><span class="p">(</span><span class="s">"unreachable"</span><span class="p">)</span> <span class="n">sys</span><span class="p">.</span><span class="nb">exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="k">return</span> <span class="n">r</span> <span class="c1"># intercept BoolRef -&gt; bool conversions </span><span class="nb">setattr</span><span class="p">(</span><span class="n">BoolRef</span><span class="p">,</span> <span class="s">"__bool__"</span><span class="p">,</span> <span class="n">sched_fork</span><span class="p">)</span> <span class="nb">setattr</span><span class="p">(</span><span class="n">BoolRef</span><span class="p">,</span> <span class="s">"__nonzero__"</span><span class="p">,</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">BoolRef</span><span class="p">,</span> <span class="s">"__bool__"</span><span class="p">))</span> </code></pre></div></div> <p>That’s it - just one function, to show the basic fork-explore-check idea.</p> <p>The trick is that, as values are symbolic (e.g., via Z3’s <code class="language-plaintext highlighter-rouge">BitVec</code>), the Python VM will try to convert them into bool at <code class="language-plaintext highlighter-rouge">if</code> statements; let’s intercept the conversion and replace it with <code class="language-plaintext highlighter-rouge">sched_fork()</code>, by rewriting <code class="language-plaintext highlighter-rouge">__bool__</code> (Python 3.x) or <code class="language-plaintext highlighter-rouge">__nonzero__</code> (Python 2.x).</p> <p>There are two more fun examples on equivalence checking:</p> <ul> <li><a href="https://github.com/xiw/mini-mc/blob/master/ffs_eqv.py">ffs_eqv.py</a>: check the equivalence of two find-first-set implementations, from the UC-KLEE paper (CAV 2011); and</li> <li><a href="https://github.com/xiw/mini-mc/blob/master/mod_eqv.py">mod_eqv.py</a>: check the equivalence of two modulo implementations, from the KLEE paper (OSDI 2008).</li> </ul> <p>Hope this is useful for classroom demonstration.</p> <p>BTW, <a href="https://github.com/xiw/mini-mc/blob/master/mc.py">mc.py</a> also contains a mini implementation of concolic execution. Run <a href="https://github.com/xiw/mini-mc/blob/master/bad.py">bad.py</a>, which resembles <a href="http://research.microsoft.com/en-us/um/people/pg/public_psfiles/ndss2008.pdf">Figure 1 of the SAGE paper</a>, and you will see 5 (out of 16) inputs that trigger the exception:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> ============================================================  #0: s = 0, s = 0, s = 0, s = 0  s == 98: False  s == 97: False  s == 100: False  s == 33: False  ============================================================  #1: s = 0, s = 0, s = 0, s = 33  s == 98: False  s == 97: False  s == 100: False  s == 33: True ...  ============================================================  #15: s = 98, s = 97, s = 100, s = 33  s == 98: True  s == 97: True  s == 100: True  s == 33: True  Traceback (most recent call last): File "./bad.py", line 32, in &lt;lambda&gt; mc_fuzz(lambda: top(s), s,  * n) File "./bad.py", line 26, in top assert False AssertionError: s = 98, s = 97, s = 100, s = 33  exit </code></pre></div></div> <p>You can modify <a href="https://github.com/xiw/mini-mc/blob/master/test_me.py">test_me.py</a> to do the same. See the <a href="https://github.com/xiw/mini-mc">mini-mc</a> repo on github for details.</p>Xi WangIt has been a while since I blogged last time. One change is that I started to have nightmares about me forgetting to prepare for lecture, rather than forgetting to turn in homework as in the past 20(?) years—life does get better.The cltq story2013-09-17T00:00:00+00:002013-09-17T00:00:00+00:00http://kqueue.org/blog/2013/09/17/cltq<p>Stare at the C code below and guess what could go wrong. It’s originally from a CPU simulator, implementing a 16×16⇒32 unsigned multiplication. Thanks to Mattias Engdegård at Intel for sharing the story.</p> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">uint64_t</span> <span class="nf">mul</span><span class="p">(</span><span class="kt">uint16_t</span> <span class="n">a</span><span class="p">,</span> <span class="kt">uint16_t</span> <span class="n">b</span><span class="p">)</span> <span class="p">{</span> <span class="kt">uint32_t</span> <span class="n">c</span> <span class="o">=</span> <span class="n">a</span> <span class="o">*</span> <span class="n">b</span><span class="p">;</span> <span class="k">return</span> <span class="n">c</span><span class="p">;</span> <span class="p">}</span> </code></pre></div></div> <p>To be more specific, let’s assume x86-64. Is it possible that for some <code class="language-plaintext highlighter-rouge">a</code> and <code class="language-plaintext highlighter-rouge">b</code>, the result of <code class="language-plaintext highlighter-rouge">mul(a, b)</code> changes when compiling the code with different optimization levels using gcc?</p> <p>Spoiler alert: here’s what will happen. When compiling the code with <code class="language-plaintext highlighter-rouge">gcc -O0</code>, you’ll get:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$./mul 65535 65535 65535 * 65535 = 4294836225 (0xfffe0001) </code></pre></div></div> <p>Hope you’re not expecting the multiplication to overflow and output <code class="language-plaintext highlighter-rouge">1</code> here, which would be the case in Go but not in C/C++. We’ll come back to that later.</p> <p>With <code class="language-plaintext highlighter-rouge">gcc -O2</code>, you’ll get this:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ./mul 65535 65535 65535 * 65535 = 18446744073709420545 (0xfffffffffffe0001) </code></pre></div></div> <p>This doesn’t look pretty, does it? And here’s what gcc 4.7.3/4.8.1 actually emits with <code class="language-plaintext highlighter-rouge">-O2</code>:</p> <div class="language-nasm highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">movzwl</span> <span class="o">%</span><span class="nb">di</span><span class="p">,</span> <span class="o">%</span><span class="nb">eax</span> <span class="c1">; zero-extend %di (a) to %eax</span> <span class="nf">movzwl</span> <span class="o">%</span><span class="nb">si</span><span class="p">,</span> <span class="o">%</span><span class="nb">esi</span> <span class="c1">; zero-extend %si (b) to %esi</span> <span class="nf">imull</span> <span class="o">%</span><span class="nb">esi</span><span class="p">,</span> <span class="o">%</span><span class="nb">eax</span> <span class="c1">; store their product in %eax</span> <span class="nf">cltq</span> <span class="c1">; sign-extend %eax to %rax</span> <span class="nf">ret</span> </code></pre></div></div> <p>Wait, the C code uses <em>unsigned</em> integers only. What’s this sign-extension instruction <code class="language-plaintext highlighter-rouge">cltq</code> (<code class="language-plaintext highlighter-rouge">cdqe</code> in Intel/AMD manuals) doing here? Is this a gcc bug?</p> <p>Actually, even though emitting <code class="language-plaintext highlighter-rouge">cltq</code> is odd, it doesn’t violate the C standard. There’s a very subtle bug in the <code class="language-plaintext highlighter-rouge">mul</code> function. I’ll show how gcc emits <code class="language-plaintext highlighter-rouge">cltq</code> by exploiting two odd C rules, <em>integer promotions</em> and <em>undefined behavior</em>, and discuss possible ways to avoid/detect such problems.</p> <h2 id="how-gcc-emits-cltq">How gcc emits cltq</h2> <p>To see what gcc does, invoke it with <code class="language-plaintext highlighter-rouge">-fdump-tree-all</code> to dump the IR after each pass. Here’s the (simplified) output after <em>vrp</em> (value range propagation).</p> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">mul</span> <span class="p">(</span><span class="kt">uint16_t</span> <span class="n">a</span><span class="p">,</span> <span class="kt">uint16_t</span> <span class="n">b</span><span class="p">)</span> <span class="c1">// a, b: VARYING</span> <span class="p">{</span> <span class="kt">uint32_t</span> <span class="n">c</span><span class="p">;</span> <span class="kt">int</span> <span class="n">_2</span><span class="p">;</span> <span class="kt">int</span> <span class="n">_4</span><span class="p">;</span> <span class="kt">int</span> <span class="n">_5</span><span class="p">;</span> <span class="kt">uint64_t</span> <span class="n">_7</span><span class="p">;</span> <span class="o">&lt;</span><span class="n">bb2</span><span class="o">&gt;:</span> <span class="n">_2</span> <span class="o">=</span> <span class="p">(</span><span class="kt">int</span><span class="p">)</span> <span class="n">a</span><span class="p">;</span> <span class="c1">// _2 : [0, 65535]</span> <span class="n">_4</span> <span class="o">=</span> <span class="p">(</span><span class="kt">int</span><span class="p">)</span> <span class="n">b</span><span class="p">;</span> <span class="c1">// _4 : [0, 65535]</span> <span class="n">_5</span> <span class="o">=</span> <span class="n">_2</span> <span class="o">*</span> <span class="n">_4</span><span class="p">;</span> <span class="c1">// _5 : [0, +INF(OVF)]</span> <span class="n">c</span> <span class="o">=</span> <span class="p">(</span><span class="kt">uint32_t</span><span class="p">)</span> <span class="n">_5</span><span class="p">;</span> <span class="c1">// c : [0, +INF]</span> <span class="n">_7</span> <span class="o">=</span> <span class="p">(</span><span class="kt">uint64_t</span><span class="p">)</span> <span class="n">_5</span><span class="p">;</span> <span class="c1">// _7 : [0, 4294967295]</span> <span class="k">return</span> <span class="n">_7</span><span class="p">;</span> <span class="p">}</span> </code></pre></div></div> <p>First of all, <code class="language-plaintext highlighter-rouge">a</code> and <code class="language-plaintext highlighter-rouge">b</code> are of type <code class="language-plaintext highlighter-rouge">uint16_t</code>, which is smaller than <code class="language-plaintext highlighter-rouge">int</code> (assuming 32-bit <code class="language-plaintext highlighter-rouge">int</code>). According to the rule of integer promotions (C11, 6.3.1.1/2), they are converted to <code class="language-plaintext highlighter-rouge">int</code> for multiplication. In other words, <code class="language-plaintext highlighter-rouge">a * b</code> is actually <code class="language-plaintext highlighter-rouge">(int)a * (int)b</code>, a <em>signed</em> multiplication.</p> <p>Second, since signed integer overflow is undefined behavior in C, gcc assumes the signed multiplication doesn’t overflow. This is shown in the range information: the product <code class="language-plaintext highlighter-rouge">_5</code> is considered non-negative. Therefore, <code class="language-plaintext highlighter-rouge">c</code> and <code class="language-plaintext highlighter-rouge">_5</code> are indistinguishable.</p> <p>Now the sign conversion <code class="language-plaintext highlighter-rouge">c = (uint32_t) _5</code> becomes dead code, and gcc’s next pass <em>dce</em> (dead code elimination) removes it. The return value <code class="language-plaintext highlighter-rouge">_7</code> is basically <code class="language-plaintext highlighter-rouge">(uint64_t)((int)a * (int)b)</code>. Note that this is a <em>sign</em> extension from <code class="language-plaintext highlighter-rouge">int</code> to <code class="language-plaintext highlighter-rouge">uint64_t</code>, which will be lowered to <code class="language-plaintext highlighter-rouge">cltq</code>. This is how <code class="language-plaintext highlighter-rouge">cltq</code> pops out.</p> <h2 id="fixes-and-workarounds">Fixes and workarounds</h2> <p>One way to fix the C code is to convert <code class="language-plaintext highlighter-rouge">a</code> and <code class="language-plaintext highlighter-rouge">b</code> to <code class="language-plaintext highlighter-rouge">uint32_t</code> before multiplication (assuming <code class="language-plaintext highlighter-rouge">int</code> is 32-bits).</p> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">uint64_t</span> <span class="nf">mul_fixed</span><span class="p">(</span><span class="kt">uint16_t</span> <span class="n">a</span><span class="p">,</span> <span class="kt">uint16_t</span> <span class="n">b</span><span class="p">)</span> <span class="p">{</span> <span class="kt">uint32_t</span> <span class="n">c</span> <span class="o">=</span> <span class="p">(</span><span class="kt">uint32_t</span><span class="p">)</span><span class="n">a</span> <span class="o">*</span> <span class="p">(</span><span class="kt">uint32_t</span><span class="p">)</span><span class="n">b</span><span class="p">;</span> <span class="k">return</span> <span class="n">c</span><span class="p">;</span> <span class="p">}</span> </code></pre></div></div> <p>Instead of fixing the code, another approach is to add a workaround compiler option. Here’s what gcc 4.7.3/4.8.1 emits with <code class="language-plaintext highlighter-rouge">-O2 -fwrapv</code>:</p> <div class="language-nasm highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">movzwl</span> <span class="o">%</span><span class="nb">si</span><span class="p">,</span> <span class="o">%</span><span class="nb">eax</span> <span class="nf">movzwl</span> <span class="o">%</span><span class="nb">di</span><span class="p">,</span> <span class="o">%</span><span class="nb">edi</span> <span class="nf">imull</span> <span class="o">%</span><span class="nb">edi</span><span class="p">,</span> <span class="o">%</span><span class="nb">eax</span> <span class="nf">ret</span> </code></pre></div></div> <p>Everything looks good now. However, if you use <code class="language-plaintext highlighter-rouge">-fno-strict-overflow</code> instead of <code class="language-plaintext highlighter-rouge">-fwrapv</code>, congratulations, you’ll get bitten by <code class="language-plaintext highlighter-rouge">cltq</code> again:</p> <div class="language-nasm highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">movzwl</span> <span class="o">%</span><span class="nb">di</span><span class="p">,</span> <span class="o">%</span><span class="nb">eax</span> <span class="nf">movzwl</span> <span class="o">%</span><span class="nb">si</span><span class="p">,</span> <span class="o">%</span><span class="nb">esi</span> <span class="nf">imull</span> <span class="o">%</span><span class="nb">esi</span><span class="p">,</span> <span class="o">%</span><span class="nb">eax</span> <span class="nf">cltq</span> <span class="nf">ret</span> </code></pre></div></div> <p>I never really understand the <a href="http://www.airs.com/blog/archives/120">difference</a> between <code class="language-plaintext highlighter-rouge">-fwrapv</code> and <code class="language-plaintext highlighter-rouge">-fno-strict-overflow</code>, and <a href="https://lkml.org/lkml/2009/7/12/90">which option to choose</a>. Looks like it’s safer to just fix the C code.</p> <p>As a comparison, here’s what clang 3.3 emits with <code class="language-plaintext highlighter-rouge">-O2</code>:</p> <div class="language-nasm highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">imulq</span> <span class="o">%</span><span class="nb">rdi</span><span class="p">,</span> <span class="o">%</span><span class="nb">rsi</span> <span class="nf">movq</span> <span class="o">%</span><span class="nb">rsi</span><span class="p">,</span> <span class="o">%</span><span class="nb">rax</span> <span class="nf">ret</span> </code></pre></div></div> <p>Don’t worry, no <code class="language-plaintext highlighter-rouge">cltq</code> here.</p> <h2 id="possible-detection-methods">Possible detection methods</h2> <p>In <a href="http://css.csail.mit.edu/stack/">our STACK paper</a>, we use the term <em>unstable code</em> to refer to program fragments being optimized away by the compiler due to undefined behavior. <a href="http://css.csail.mit.edu/stack/">STACK</a> is a static checker for detecting unstable code. While it works well for other cases, unfortunately, STACK doesn’t work here. The main reason is that STACK accepts <a href="http://llvm.org/docs/LangRef.html">LLVM IR</a>, which has no sign conversion instruction; the code being optimized away, the sign conversion <code class="language-plaintext highlighter-rouge">c = (uint32_t) _5</code> as in gcc, doesn’t exist in LLVM IR:</p> <div class="language-llvm highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">define</span> <span class="kt">i64</span> <span class="vg">@mul</span><span class="p">(</span><span class="kt">i16</span> <span class="k">zeroext</span> <span class="nv">%a</span><span class="p">,</span> <span class="kt">i16</span> <span class="k">zeroext</span> <span class="nv">%b</span><span class="p">)</span> <span class="vg">#0</span> <span class="p">{</span> <span class="nl">entry:</span> <span class="nv">%conv</span> <span class="p">=</span> <span class="k">zext</span> <span class="kt">i16</span> <span class="nv">%a</span> <span class="k">to</span> <span class="kt">i32</span> <span class="nv">%conv1</span> <span class="p">=</span> <span class="k">zext</span> <span class="kt">i16</span> <span class="nv">%b</span> <span class="k">to</span> <span class="kt">i32</span> <span class="nv">%mul</span> <span class="p">=</span> <span class="k">mul</span> <span class="k">nsw</span> <span class="kt">i32</span> <span class="nv">%conv</span><span class="p">,</span> <span class="nv">%conv1</span> <span class="nv">%conv2</span> <span class="p">=</span> <span class="k">zext</span> <span class="kt">i32</span> <span class="nv">%mul</span> <span class="k">to</span> <span class="kt">i64</span> <span class="k">ret</span> <span class="kt">i64</span> <span class="nv">%conv2</span> <span class="p">}</span> </code></pre></div></div> <p>Since in LLVM IR there’s no instruction to be optimized away, STACK doesn’t report any warning. One could port STACK to gcc to catch this case though.</p> <p>Another possible way is to extend STACK with an oracle that rewrites <code class="language-plaintext highlighter-rouge">zext</code> (zero extension) to <code class="language-plaintext highlighter-rouge">sext</code> (sign extension). The idea is that if the code after rewriting is equivalent to the original <em>only</em> under the assumption of undefined behavior, then this rewriting exploits undefined behavior, and the code may be unstable.</p> <p>I coded up a prototype and it did work for this <code class="language-plaintext highlighter-rouge">mul</code> case (see the <code class="language-plaintext highlighter-rouge">sign</code> branch in STACK’s git repository if interested). But it reported too many false positives. After all, the compiler being able to remove code often indicates a bug, while being able to flip between <code class="language-plaintext highlighter-rouge">zext</code> and <code class="language-plaintext highlighter-rouge">sext</code> doesn’t. Maybe we need better heuristics to reduce false positives.</p> <p>There are a few handy tools for finding undefined behavior. For example, run the C code with Frama-C’s <a href="http://frama-c.com/value.html">value analysis</a> and you’ll see the following warning:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$frama-c -val mul.c [value] Analyzing a complete application starting at main ... mul.c:3:[kernel] warning: signed overflow. assert (int)a*(int)b ≤ 2147483647; mul.c:3:[value] assigning non deterministic value for the first time ... </code></pre></div></div> <p>You can also compile it with clang’s <code class="language-plaintext highlighter-rouge">-fsanitize=undefined</code> (based on <a href="http://embed.cs.utah.edu/ioc/">IOC</a>). You need an input to trigger the undefined behavior, such as:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ./mul 65535 65535 mul.c:3:17: runtime error: signed integer overflow : 65535 * 65535 cannot be represented in type 'int' </code></pre></div></div> <p>Though these tools won’t tell you what code may be “miscompiled” as STACK does, fixing the warnings will reduce the chances of being bitten by the compiler.</p> <h2 id="summary">Summary</h2> <p>This is one of my favorite unstable code examples. The code is very short, uses unsigned integers only, and yet confusing. Fixing the problem, especially by using gcc’s workaround options, is tricky.</p> <p>Update from Sep 18th: Add results from Frama-C and clang’s <code class="language-plaintext highlighter-rouge">-fsanitize=undefined</code>. Thanks to David Mentré and John Regehr.</p>Xi WangStare at the C code below and guess what could go wrong. It’s originally from a CPU simulator, implementing a 16×16⇒32 unsigned multiplication. Thanks to Mattias Engdegård at Intel for sharing the story.Mars or UFO: The DIY night sky2013-08-21T00:00:00+00:002013-08-21T00:00:00+00:00http://kqueue.org/blog/2013/08/21/mars-or-ufo<p>Came across a funny book titled <em>5000 years of UFOs</em> (1997) last weekend. The author claims that a number of ancient astronomical records from Chinese history books are actually about UFOs. Here’s one example, where the author cites the chronicle <a href="http://en.wikipedia.org/wiki/Zizhi_Tongjian"><em>Zizhi Tongjian</em></a> (1084):</p> <blockquote> <p>In the 9th lunar month of the year 415, Mars looped the loop in the constellation Gemini, sometimes forward, sometime backward, sometimes left, sometimes right.</p> </blockquote> <p>The author questions “how can Mars move like this”<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote">1</a></sup> and suspects a UFO.</p> <p>Well, Mars can move like this. That’s why Mars is called a <a href="http://en.wikipedia.org/wiki/Planet"><em>planet</em></a> (“wandering stars” in Greek). Here’s the last retrograde motion of Mars in 2011–2012, plotted using <a href="http://d3js.org/">D3.js</a> (JavaScript+SVG required) and data from NASA.</p> <center> <div id="mars2011" title="Mars (2011-2012)"></div> </center> <p>Compare it with the <a href="http://apod.nasa.gov/apod/ap120809.html">actual photos from NASA</a>, and you can see how Mars brakes, changes its direction, and moves forward again. Mars appears to move backward in the sky when Earth overtakes Mars, since Earth moves faster around the sun (see more explanations from <a href="http://mars.nasa.gov/allaboutmars/nightsky/nightsky04/">NASA</a> and <a href="http://www.youtube.com/watch?v=kbynKfNfHk4">BBC</a>).</p> <p>However, according to either my plots or other historical records, Mars did <em>not</em> loop the loop in Gemini in late 415 at all. In fact, it is nowhere near Gemini at that time. I suspect that the actual date of this record is late 414 or early 415; the chronicle <a href="http://en.wikipedia.org/wiki/Zizhi_Tongjian"><em>Zizhi Tongjian</em></a> may make a mistake here. I’ll explain this later.</p> <h2 id="plotting-the-night-sky">Plotting the night sky</h2> <p>Here are some technical details. The x-y coordinates of my plots are <a href="http://en.wikipedia.org/wiki/Right_ascension">right ascension (RA)</a> and <a href="http://en.wikipedia.org/wiki/Declination">declination (dec)</a>, “longitude” and “latitude” of the celestial sphere, respectively. For stars, I got their RA/dec data (see <a href="/downloads/code/ti/stars.txt">stars.txt</a>) from Wikipedia (e.g., <a href="http://en.wikipedia.org/wiki/List_of_stars_in_Gemini">Gemini</a>), which are assumed not to change.</p> <p>The key part is to get the RA/dec of Mars over time. There are a few options:</p> <ul> <li>Use <a href="http://www.stellarium.org/">Stellarium</a>, but it’s hard to export the coordinates from there;</li> <li>Calculate the coordinates using <a href="http://ssd.jpl.nasa.gov/?planet_pos">lower accuracy formulae</a>;</li> <li>NASA’s <a href="http://ssd.jpl.nasa.gov/?horizons">HORIZONS</a> provides a nice web service for generating coordinates, but it doesn’t work for dates earlier than 1900.</li> </ul> <p>I ended up with writing a small program (see <a href="/downloads/code/ti/coordgen.c">coordgen.c</a>) using NASA’s <a href="http://naif.jpl.nasa.gov/">SPICE toolkit</a>, along with the ephemeris data <a href="http://naif.jpl.nasa.gov/pub/naif/generic_kernels/spk/planets/">DE431</a> that covers dates back to 13210BC.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>\$ ./coordgen "MARS BARYCENTER" 2011-SEP-01 2012-AUG-31 2011-SEP-01 00:00 110.18800 22.84568 2011-SEP-02 00:00 110.87321 22.77037 ... 2012-AUG-31 00:00 212.12717 -13.52374 </code></pre></div></div> <p>Now we have the RA/dec of Mars for that time span (see <a href="/downloads/code/ti/mars2011.txt">mars2011.txt</a>). Similarly, we can get those of Mars in the year 415 (and even those of other planets such as Saturn), as I’ll show later.</p> <p>Note that these coordinates may not be exactly the same as seen from Earth. They are of the Mars barycenter, not the mass center. Also, they don’t take into account atmospheric refraction nor the light time from Mars to an observer on Earth. They are accurate enough for our purpose though.</p> <p>I wrapped up my plotting code in <a href="/downloads/code/ti/ti.js">ti.js</a>. To generate a plot, simply call <code class="language-plaintext highlighter-rouge">ti.load()</code> with your data and configuration. See the source code of this webpage for details.</p> <h2 id="the-fall-of-a-kingdom">The fall of a kingdom</h2> <p>Mars in the year 415 played an interesting role in history. According to <a href="http://en.wikipedia.org/wiki/Zizhi_Tongjian"><em>Zizhi Tongjian</em></a>, in the 9th lunar month of 415, an astronomical officer of the <a href="http://en.wikipedia.org/wiki/Northern_Wei">Northern Wei</a> dynasty reported that Mars was “gone,” which was a serious issue at that time. The emperor then called for a meeting. A high-level official <a href="http://en.wikipedia.org/wiki/Cui_Hao">Cui Hao</a> predicted that another kingdom, <a href="http://en.wikipedia.org/wiki/Later_Qin">Later Qin</a>, would fall soon, as indicated by the motion of Mars, though others didn’t believe him. About 80 days later, Mars appeared looping the loop in Gemini. The emperor of Later Qin, <a href="http://en.wikipedia.org/wiki/Yao_Xing">Yao Xing</a>, died the next year (416), and Later Qin did collapse the year after (417).<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote">2</a></sup></p> <p>Let’s plot the motion of Mars from late 415 to early 416.</p> <center> <div id="mars415" title="Mars (415-416)"></div> </center> <p>Clearly, Mars was traveling between Virgo and Aquarius, way far away from Gemini.</p> <p>I also looked up the astronomical records from <a href="http://en.wikipedia.org/wiki/Book_of_Jin"><em>Book of Jin</em></a> (648), which mentioned the following:</p> <ul> <li>Jul 13th, 415, Mars moved to Virgo.<sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote">3</a></sup> (Note that all the dates in this article are converted from the original <a href="http://en.wikipedia.org/wiki/Chinese_calendar">lunar dates</a> and should be considered approximate.)</li> </ul> <p>We can confirm the accuracy of this record using the plot above. Based on this record, since Mars was in Virgo around July, we can also infer that it wouldn’t reach Gemini soon.</p> <h2 id="415-or-414">415 or 414?</h2> <p>So, what went wrong?</p> <p>Later, I found one record for the previous year 414, also from <a href="http://en.wikipedia.org/wiki/Book_of_Jin"><em>Book of Jin</em></a> (648):</p> <ul> <li>Aug 19th, 414, Mars approached Gemini and Saturn approached Cancer, looping the loop.<sup id="fnref:4" role="doc-noteref"><a href="#fn:4" class="footnote">4</a></sup></li> </ul> <p>Hmm, let’s plug in the data.</p> <center> <div id="mars414" title="Mars and Saturn (414-415)"></div> </center> <p>This plot shows Mars (in Gemini) and Saturn (in Cancer) in late 414 and early 415, both in retrograde motion. This is consistent with the ancient records.</p> <p>So, is it possible that <a href="http://en.wikipedia.org/wiki/Zizhi_Tongjian"><em>Zizhi Tongjian</em></a> (1084) made a mistake on the actual date?</p> <p>From <a href="http://en.wikipedia.org/wiki/Book_of_Wei">Book of Wei</a> (554), I found an earlier version of the story that <a href="http://en.wikipedia.org/wiki/Cui_Hao">Cui Hao</a> predicted the fall of <a href="http://en.wikipedia.org/wiki/Later_Qin">Later Qin</a> based on the motion of Mars. It is basically the same story, but the date recorded in that book is “qian sui” before <a href="http://en.wikipedia.org/wiki/Yao_Xing">Yao Xing</a>, the emperor of Later Qin, died in 416.<sup id="fnref:5" role="doc-noteref"><a href="#fn:5" class="footnote">5</a></sup></p> <p>Well, “qian sui” is a confusing term in Chinese. It could mean one year before (415), two years before (414), or even several years before. Based on the plot above, I suspect the actual date of the story is 414, while the author of <a href="http://en.wikipedia.org/wiki/Zizhi_Tongjian"><em>Zizhi Tongjian</em></a> misread the date, possibly due to the ambiguity of the term “qian sui.”</p> <p>To sum up, this is what I believe happened:</p> <ul> <li>Aug 414, Mars started retrograde motion in Gemini.</li> <li>Late 414, <a href="http://en.wikipedia.org/wiki/Cui_Hao">Cui Hao</a> predicted the fall of <a href="http://en.wikipedia.org/wiki/Later_Qin">Later Qin</a>.</li> <li>Early 415, Mars ended retrograde motion and moved out of Gemini.</li> <li>416, <a href="http://en.wikipedia.org/wiki/Yao_Xing">Yao Xing</a>, the emperor of Later Qin, died.</li> <li>417, Later Qin collapsed.</li> <li>554, <a href="http://en.wikipedia.org/wiki/Book_of_Wei"><em>Book of Wei</em></a> was published; it says the prediction was made “qian sui” before Yao Xing died.</li> <li>1084, <a href="http://en.wikipedia.org/wiki/Zizhi_Tongjian"><em>Zizhi Tongjian</em></a> was published; it says the prediction was made in late 415, which should have been late 414.</li> </ul> <h2 id="footnotes">Footnotes</h2> <script src="/downloads/code/ti/ti.js"></script> <script> ti.load("#mars2011", { width: 800, height: 450, margin: {top: 10, left: 10}, stars: { src: "/downloads/code/ti/stars.txt", map: ["Cancer", "Leo", "Virgo", "Sextans"], }, planets: [{ src: "/downloads/code/ti/mars2011.txt", attr: ti.marsAttr, }], duration: 20, }); ti.load("#mars415", { width: 800, height: 350, margin: {top: 10, bottom: 10}, offset: {ra: 200}, stars: { src: "/downloads/code/ti/stars.txt", map: ["Virgo", "Libra", "Scorpius", "Sagittarius", "Capricornus", "Aquarius"], }, planets: [{ src: "/downloads/code/ti/mars415.txt", attr: ti.marsAttr, }], duration: 10, font: { constellation: "font-size: 80%; font-family: sans-serif;", star: "font-size: 70%; font-family: sans-serif;", }, }); ti.load("#mars414", { width: 800, height: 450, margin: {top: 10, bottom: 10}, stars: { src: "/downloads/code/ti/stars.txt", map: ["Gemini", "Cancer"], }, planets: [{ src: "/downloads/code/ti/mars414.txt", attr: ti.marsAttr, }, { src: "/downloads/code/ti/saturn414.txt", attr: ti.saturnAttr, }], duration: 18, }); </script> <div class="footnotes" role="doc-endnotes"> <ol> <li id="fn:1" role="doc-endnote"> <p>Excerpts from <em>5000 years of UFOs</em>: “415年，《資治通鑑》「東晉安帝十一年九月，熒惑出東井（雙子座），留守句己，久之乃去。去而復來，乍前乍後，乍左乍右」，熒惑火星會如此飛來飛去嗎？” While the first half of the cited piece does appear in <a href="http://en.wikipedia.org/wiki/Zizhi_Tongjian"><em>Zizhi Tongjian</em></a>, the second half doesn’t. It is actually from <a href="http://en.wikipedia.org/wiki/Book_of_Jin"><em>Book of Jin</em></a> (<a href="http://zh.wikisource.org/wiki/%E6%99%89%E6%9B%B8/%E5%8D%B7012">晉書·志第二·天文中)</a>, talking about observing Mars in general. The author mixed them up, probably due to using a <a href="http://zh.wikisource.org/wiki/%E8%B3%87%E6%B2%BB%E9%80%9A%E9%91%92_(%E8%83%A1%E4%B8%89%E7%9C%81%E9%9F%B3%E6%B3%A8)/%E5%8D%B7117">commented version</a> of <em>Zizhi Tongjian</em>. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> <li id="fn:2" role="doc-endnote"> <p><a href="http://zh.wikisource.org/wiki/%E8%B3%87%E6%B2%BB%E9%80%9A%E9%91%92_(%E8%83%A1%E4%B8%89%E7%9C%81%E9%9F%B3%E6%B3%A8)/%E5%8D%B7117">資治通鑑·卷第一百一十七·晉紀三十九</a>: “義熙十一年…九月…魏太史奏…” <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> <li id="fn:3" role="doc-endnote"> <p><a href="http://zh.wikisource.org/wiki/%E6%99%89%E6%9B%B8/%E5%8D%B7013">晉書·志第三·天文下</a>: “義熙…十一年三月…己卯 熒惑入輿鬼… 五月癸卯 熒惑入太微…” <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> <li id="fn:4" role="doc-endnote"> <p><a href="http://zh.wikisource.org/wiki/%E6%99%89%E6%9B%B8/%E5%8D%B7013">晉書·志第三·天文下</a>: “義熙…十年…七月庚辰…熒惑犯井鉞 填星犯輿鬼 遂守之…” <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> <li id="fn:5" role="doc-endnote"> <p><a href="http://zh.wikisource.org/wiki/%E9%AD%8F%E6%9B%B8/%E5%8D%B735">魏書·列傳第二十三·崔浩</a>: “初 姚興死之前歲也…” <a href="#fnref:5" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> </ol> </div>Xi WangCame across a funny book titled 5000 years of UFOs (1997) last weekend. The author claims that a number of ancient astronomical records from Chinese history books are actually about UFOs. Here’s one example, where the author cites the chronicle Zizhi Tongjian (1084):IDIV DoS2012-12-31T00:00:00+00:002012-12-31T00:00:00+00:00http://kqueue.org/blog/2012/12/31/idiv-dos<p>A little fun for the last day of 2012: how to crash a program via division? x86’s IDIV instruction traps not only on division by zero, but also on <code class="language-plaintext highlighter-rouge">INT_MIN / -1</code> (signed integer overflow).</p> <h2 id="cc">C/C++</h2> <p>Try to compile this little function.</p> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">long</span> <span class="kt">long</span> <span class="nf">crash</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="p">(</span><span class="mi">1LL</span> <span class="o">&lt;&lt;</span> <span class="mi">63</span><span class="p">)</span> <span class="o">%</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span> <span class="p">}</span> </code></pre></div></div> <p>It will crash the <a href="http://bellard.org/tcc/">tcc</a> compiler and the <a href="https://sparse.wiki.kernel.org/">sparse</a> checker.</p> <h2 id="sql">SQL</h2> <p>You need a 64-bit PostgreSQL (older than 9.2.2/9.1.7/9.0.11) installed on Windows.</p> <p>Try the following SQL statement.</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="p">((</span><span class="o">-</span><span class="mi">9223372036854775808</span><span class="p">)::</span><span class="n">int8</span><span class="p">)</span> <span class="o">%</span> <span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">);</span> </code></pre></div></div> <p>Instead of producing 0 from modulo -1, it will crash your PostgreSQL server. The modulo trick also worked in <a href="http://www.firebirdsql.org/">Firebird</a>. This is <a href="http://git.postgresql.org/gitweb/?p=postgresql.git;a=commitdiff;h=a235b85a0ba06666dbbfdb9249a65dbfa9b42ebd">fixed</a> by adding a check on the divisor.</p> <p>Here goes more evil SQL.</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="p">((</span><span class="o">-</span><span class="mi">2147483648</span><span class="p">)::</span><span class="n">int4</span><span class="p">)</span> <span class="o">/</span> <span class="p">((</span><span class="o">-</span><span class="mi">1</span><span class="p">)::</span><span class="n">int2</span><span class="p">);</span> <span class="k">SELECT</span> <span class="p">((</span><span class="o">-</span><span class="mi">9223372036854775808</span><span class="p">)::</span><span class="n">int8</span><span class="p">)</span> <span class="o">/</span> <span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">);</span> <span class="k">SELECT</span> <span class="p">((</span><span class="o">-</span><span class="mi">9223372036854775808</span><span class="p">)::</span><span class="n">int8</span><span class="p">)</span> <span class="o">*</span> <span class="p">((</span><span class="o">-</span><span class="mi">1</span><span class="p">)::</span><span class="n">int8</span><span class="p">);</span> </code></pre></div></div> <p>The first two are straightforward. The third one (multiplication) crashes PostgreSQL because the overflow check is done via division.</p> <p>The <a href="http://git.postgresql.org/gitweb/?p=postgresql.git;a=commitdiff;h=1f7cb5c30983752ff8de833de30afcaee63536d0">fix</a> is simple: do the overflow check before the division, not after.</p> <p>It’s interesting that the developers <a href="http://archives.postgresql.org/pgsql-patches/2006-06/msg00104.php">fixed 32-bit division crashes</a>, but missed 64-bit cases. My guess is that the developers did the tests on a 32-bit Windows. In that case, since there’s no 64-bit IDIV instruction, the compiler instead generates a call to a runtime function <code class="language-plaintext highlighter-rouge">lldiv</code>, which doesn’t trap on <code class="language-plaintext highlighter-rouge">INT_MIN / -1</code>. This would lead to the incorrect conclusion that 64-bit division wouldn’t trap.</p> <h2 id="clamav-bytecode">ClamAV bytecode</h2> <p>The ClamAV engine accepts bytecode signatures as extensions to detect new viruses. You can write one to crash ClamAV’s interpreter, as follows.</p> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="nf">entrypoint</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span> <span class="kt">long</span> <span class="kt">long</span> <span class="n">x</span> <span class="o">=</span> <span class="mi">1LL</span> <span class="o">&lt;&lt;</span> <span class="mi">63</span><span class="p">;</span> <span class="kt">int</span> <span class="n">y</span> <span class="o">=</span> <span class="n">__is_bigendian</span><span class="p">()</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span> <span class="k">if</span> <span class="p">(</span><span class="n">x</span> <span class="o">/</span> <span class="n">y</span><span class="p">)</span> <span class="n">foundVirus</span><span class="p">(</span><span class="s">"idiv"</span><span class="p">);</span> <span class="k">return</span> <span class="mi">0</span><span class="p">;</span> <span class="p">}</span> </code></pre></div></div> <p>It basically does <code class="language-plaintext highlighter-rouge">INT_MIN / -1</code>, only to prevent ClamAV’s bytecode compiler from optimizing away the division. Then compile the function into bytecode, load it into <code class="language-plaintext highlighter-rouge">clamscan</code>, and it will crash the interpreter.</p> <p>Actually the interpreter does have a sanity check for signed division, which was <a href="https://github.com/vrtadmin/clamav-devel/commit/82ca2ab4209a46a9e7322777452000465070ffb6">introduced</a> in 2009.</p> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="n">always_inline</span> <span class="kt">int</span> <span class="nf">check_sdivops</span><span class="p">(</span><span class="kt">int64_t</span> <span class="n">op0</span><span class="p">,</span> <span class="kt">int64_t</span> <span class="n">op1</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="n">op1</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">||</span> <span class="p">(</span><span class="n">op0</span> <span class="o">==</span> <span class="o">-</span><span class="mi">1</span> <span class="o">&amp;&amp;</span> <span class="n">op1</span> <span class="o">==</span> <span class="p">(</span><span class="o">-</span><span class="mi">9223372036854775807LL</span><span class="o">-</span><span class="mi">1LL</span><span class="p">));</span> <span class="p">}</span> </code></pre></div></div> <p>The sanity check doesn’t work because, well, you really should swap <code class="language-plaintext highlighter-rouge">op0</code> and <code class="language-plaintext highlighter-rouge">op1</code> in the second half of the check.</p> <p>Let me know if you have more stories. Happy new year!</p>Xi WangA little fun for the last day of 2012: how to crash a program via division? x86’s IDIV instruction traps not only on division by zero, but also on INT_MIN / -1 (signed integer overflow).