{"slug": "quaternion-rotations-claude-and-lean", "title": "Quaternion Rotations, Claude, and Lean", "summary": "John D. Cook used Anthropic's Claude (Sonnet 4.6 Medium) to find a typo in a blog post about quaternion rotations by asking it to write Lean code verifying the post's theorems. After four iterations, Claude produced correct Lean 4 code that proved the rotation matrix orthogonality and Chiaverini–Siciliano formulas, detecting a conflict between the post's Python code and LaTeX-generated SVG.", "body_md": "I got an email message this afternoon reporting a typo in a blog post from about a year ago on [converting between quaternions and rotation matrices](https://www.johndcook.com/blog/2025/05/07/quaternions-and-rotation-matrices/) [1]. The email said exactly where the typo was, but I decided to see whether Claude would find it. Specifically, I prompted Sonnet 4.6 Medium with the following.\n\nWrite Lean code to verify the two theorems at the top of this post:\n\n[https://www.johndcook.com/blog/2025/05/07/quaternions-and-rotation-matrices/](https://www.johndcook.com/blog/2025/05/07/quaternions-and-rotation-matrices/)\n\nThat is, prove that the expressions given in the two SVG files are correct.\n\nThe post included Python code to numerically verify the equations. However, the Python code differed from the LaTeX code for the image in one subscript [2]. Although I asked Claude to prove the expressions in the SVG file produced by the LaTeX code, it detected the conflict between the Python and LaTeX and correctly concluded that the former was correct.\n\nThe SVG is an image — let me rely on the Python code in the blog (which is the ground truth implementation) and the matrix entries in the alt text. … Note the alt text says row 1 col 2 is `2(q_1 q_3 - q_0 q_1)`\n\n— that’s a typo in the alt text; the Python code says `2*(q2*q3 - q0*q1)`\n\nwhich makes sense. I’ll use the Python code as the authoritative source.\n\nThe code did not run on first attempt. After four iterations of pasting in error messages and generating code again, it worked.\n\nHere is the final Lean 4 code.\n\n```\n/-\n  Lean 4 / Mathlib verification of the two theorems from:\n    \"Converting between quaternions and rotation matrices\"\n    ...\n\n  Matrix entries (0-based, matching the post's Python code):\n    R00 = 2(q0²+q1²)−1    R01 = 2(q1 q2−q0 q3)   R02 = 2(q1 q3+q0 q2)\n    R10 = 2(q1 q2+q0 q3)  R11 = 2(q0²+q2²)−1     R12 = 2(q2 q3−q0 q1)\n    R20 = 2(q1 q3−q0 q2)  R21 = 2(q2 q3+q0 q1)   R22 = 2(q0²+q3²)−1\n\n  THEOREM 1  (quaternion → rotation matrix)\n    If q0²+q1²+q2²+q3² = 1 then R is orthogonal (Rᵀ R = I),\n    proved via the 9 scalar dot-product identities.\n\n  THEOREM 2  (rotation matrix → quaternion, Chiaverini–Siciliano)\n    With rᵢⱼ as above:\n        1 + R00 + R11 + R22 = 4 q0²\n        1 + R00 − R11 − R22 = 4 q1²\n        1 − R00 + R11 − R22 = 4 q2²\n        1 − R00 − R11 + R22 = 4 q3²\n-/\n\nimport Mathlib.Tactic\n\nset_option linter.style.whitespace false\n\nvariable (q0 q1 q2 q3 : ℝ)\n\n/-! ## Theorem 1 : Rᵀ R = I -/\n\n-- ── Column norms = 1 ─────────────────────────────────────────────────────────\n\ntheorem col0_norm (h : q0 ^ 2 + q1 ^ 2 + q2 ^ 2 + q3 ^ 2 = 1) :\n    (2 * (q0 ^ 2 + q1 ^ 2) - 1) ^ 2 + (2 * (q1 * q2 + q0 * q3)) ^ 2 +\n    (2 * (q1 * q3 - q0 * q2)) ^ 2 = 1 := by\n  nlinarith [sq_nonneg (q0 ^ 2 + q1 ^ 2 - q2 ^ 2 - q3 ^ 2),\n             sq_nonneg (q0 * q2 + q1 * q3), sq_nonneg (q0 * q3 - q1 * q2),\n             sq_nonneg q0, sq_nonneg q1, sq_nonneg q2, sq_nonneg q3]\n\ntheorem col1_norm (h : q0 ^ 2 + q1 ^ 2 + q2 ^ 2 + q3 ^ 2 = 1) :\n    (2 * (q1 * q2 - q0 * q3)) ^ 2 + (2 * (q0 ^ 2 + q2 ^ 2) - 1) ^ 2 +\n    (2 * (q2 * q3 + q0 * q1)) ^ 2 = 1 := by\n  nlinarith [sq_nonneg (q0 ^ 2 - q1 ^ 2 + q2 ^ 2 - q3 ^ 2),\n             sq_nonneg (q0 * q1 + q2 * q3), sq_nonneg (q0 * q3 - q1 * q2),\n             sq_nonneg q0, sq_nonneg q1, sq_nonneg q2, sq_nonneg q3]\n\ntheorem col2_norm (h : q0 ^ 2 + q1 ^ 2 + q2 ^ 2 + q3 ^ 2 = 1) :\n    (2 * (q1 * q3 + q0 * q2)) ^ 2 + (2 * (q2 * q3 - q0 * q1)) ^ 2 +\n    (2 * (q0 ^ 2 + q3 ^ 2) - 1) ^ 2 = 1 := by\n  nlinarith [sq_nonneg (q0 ^ 2 - q1 ^ 2 - q2 ^ 2 + q3 ^ 2),\n             sq_nonneg (q0 * q1 - q2 * q3), sq_nonneg (q0 * q2 + q1 * q3),\n             sq_nonneg q0, sq_nonneg q1, sq_nonneg q2, sq_nonneg q3]\n\n-- ── Column orthogonality = 0 ─────────────────────────────────────────────────\n-- These need h (the residuals shown by Lean are multiples of (q0²+q1²+q2²+q3²-1)).\n-- We use linear_combination with the explicit witnesses.\n\ntheorem col0_col1_orth (h : q0 ^ 2 + q1 ^ 2 + q2 ^ 2 + q3 ^ 2 = 1) :\n    (2 * (q0 ^ 2 + q1 ^ 2) - 1) * (2 * (q1 * q2 - q0 * q3)) +\n    (2 * (q1 * q2 + q0 * q3)) * (2 * (q0 ^ 2 + q2 ^ 2) - 1) +\n    (2 * (q1 * q3 - q0 * q2)) * (2 * (q2 * q3 + q0 * q1)) = 0 := by\n  linear_combination 4 * q1 * q2 * h\n\ntheorem col0_col2_orth (h : q0 ^ 2 + q1 ^ 2 + q2 ^ 2 + q3 ^ 2 = 1) :\n    (2 * (q0 ^ 2 + q1 ^ 2) - 1) * (2 * (q1 * q3 + q0 * q2)) +\n    (2 * (q1 * q2 + q0 * q3)) * (2 * (q2 * q3 - q0 * q1)) +\n    (2 * (q1 * q3 - q0 * q2)) * (2 * (q0 ^ 2 + q3 ^ 2) - 1) = 0 := by\n  linear_combination 4 * q1 * q3 * h\n\ntheorem col1_col2_orth (h : q0 ^ 2 + q1 ^ 2 + q2 ^ 2 + q3 ^ 2 = 1) :\n    (2 * (q1 * q2 - q0 * q3)) * (2 * (q1 * q3 + q0 * q2)) +\n    (2 * (q0 ^ 2 + q2 ^ 2) - 1) * (2 * (q2 * q3 - q0 * q1)) +\n    (2 * (q2 * q3 + q0 * q1)) * (2 * (q0 ^ 2 + q3 ^ 2) - 1) = 0 := by\n  linear_combination 4 * q2 * q3 * h\n\n-- ── Row norms = 1 ────────────────────────────────────────────────────────────\n\ntheorem row0_norm (h : q0 ^ 2 + q1 ^ 2 + q2 ^ 2 + q3 ^ 2 = 1) :\n    (2 * (q0 ^ 2 + q1 ^ 2) - 1) ^ 2 + (2 * (q1 * q2 - q0 * q3)) ^ 2 +\n    (2 * (q1 * q3 + q0 * q2)) ^ 2 = 1 := by\n  nlinarith [sq_nonneg (q0 ^ 2 + q1 ^ 2 - q2 ^ 2 - q3 ^ 2),\n             sq_nonneg (q0 * q2 - q1 * q3), sq_nonneg (q0 * q3 + q1 * q2),\n             sq_nonneg q0, sq_nonneg q1, sq_nonneg q2, sq_nonneg q3]\n\ntheorem row1_norm (h : q0 ^ 2 + q1 ^ 2 + q2 ^ 2 + q3 ^ 2 = 1) :\n    (2 * (q1 * q2 + q0 * q3)) ^ 2 + (2 * (q0 ^ 2 + q2 ^ 2) - 1) ^ 2 +\n    (2 * (q2 * q3 - q0 * q1)) ^ 2 = 1 := by\n  nlinarith [sq_nonneg (q0 ^ 2 - q1 ^ 2 + q2 ^ 2 - q3 ^ 2),\n             sq_nonneg (q0 * q1 - q2 * q3), sq_nonneg (q0 * q3 + q1 * q2),\n             sq_nonneg q0, sq_nonneg q1, sq_nonneg q2, sq_nonneg q3]\n\ntheorem row2_norm (h : q0 ^ 2 + q1 ^ 2 + q2 ^ 2 + q3 ^ 2 = 1) :\n    (2 * (q1 * q3 - q0 * q2)) ^ 2 + (2 * (q2 * q3 + q0 * q1)) ^ 2 +\n    (2 * (q0 ^ 2 + q3 ^ 2) - 1) ^ 2 = 1 := by\n  nlinarith [sq_nonneg (q0 ^ 2 - q1 ^ 2 - q2 ^ 2 + q3 ^ 2),\n             sq_nonneg (q0 * q1 + q2 * q3), sq_nonneg (q0 * q2 - q1 * q3),\n             sq_nonneg q0, sq_nonneg q1, sq_nonneg q2, sq_nonneg q3]\n\n-- ── Row orthogonality = 0 ────────────────────────────────────────────────────\n\ntheorem row0_row1_orth (h : q0 ^ 2 + q1 ^ 2 + q2 ^ 2 + q3 ^ 2 = 1) :\n    (2 * (q0 ^ 2 + q1 ^ 2) - 1) * (2 * (q1 * q2 + q0 * q3)) +\n    (2 * (q1 * q2 - q0 * q3)) * (2 * (q0 ^ 2 + q2 ^ 2) - 1) +\n    (2 * (q1 * q3 + q0 * q2)) * (2 * (q2 * q3 - q0 * q1)) = 0 := by\n  linear_combination 4 * q1 * q2 * h\n\ntheorem row0_row2_orth (h : q0 ^ 2 + q1 ^ 2 + q2 ^ 2 + q3 ^ 2 = 1) :\n    (2 * (q0 ^ 2 + q1 ^ 2) - 1) * (2 * (q1 * q3 - q0 * q2)) +\n    (2 * (q1 * q2 - q0 * q3)) * (2 * (q2 * q3 + q0 * q1)) +\n    (2 * (q1 * q3 + q0 * q2)) * (2 * (q0 ^ 2 + q3 ^ 2) - 1) = 0 := by\n  linear_combination 4 * q1 * q3 * h\n\ntheorem row1_row2_orth (h : q0 ^ 2 + q1 ^ 2 + q2 ^ 2 + q3 ^ 2 = 1) :\n    (2 * (q1 * q2 + q0 * q3)) * (2 * (q1 * q3 - q0 * q2)) +\n    (2 * (q0 ^ 2 + q2 ^ 2) - 1) * (2 * (q2 * q3 + q0 * q1)) +\n    (2 * (q2 * q3 - q0 * q1)) * (2 * (q0 ^ 2 + q3 ^ 2) - 1) = 0 := by\n  linear_combination 4 * q2 * q3 * h\n\n/-! ## Theorem 2 : Chiaverini–Siciliano square-root arguments -/\n\ntheorem cs_arg0 (h : q0 ^ 2 + q1 ^ 2 + q2 ^ 2 + q3 ^ 2 = 1) :\n    1 + (2 * (q0 ^ 2 + q1 ^ 2) - 1) + (2 * (q0 ^ 2 + q2 ^ 2) - 1) +\n    (2 * (q0 ^ 2 + q3 ^ 2) - 1) = 4 * q0 ^ 2 := by linarith\n\ntheorem cs_arg1 (h : q0 ^ 2 + q1 ^ 2 + q2 ^ 2 + q3 ^ 2 = 1) :\n    1 + (2 * (q0 ^ 2 + q1 ^ 2) - 1) - (2 * (q0 ^ 2 + q2 ^ 2) - 1) -\n    (2 * (q0 ^ 2 + q3 ^ 2) - 1) = 4 * q1 ^ 2 := by linarith\n\ntheorem cs_arg2 (h : q0 ^ 2 + q1 ^ 2 + q2 ^ 2 + q3 ^ 2 = 1) :\n    1 - (2 * (q0 ^ 2 + q1 ^ 2) - 1) + (2 * (q0 ^ 2 + q2 ^ 2) - 1) -\n    (2 * (q0 ^ 2 + q3 ^ 2) - 1) = 4 * q2 ^ 2 := by linarith\n\ntheorem cs_arg3 (h : q0 ^ 2 + q1 ^ 2 + q2 ^ 2 + q3 ^ 2 = 1) :\n    1 - (2 * (q0 ^ 2 + q1 ^ 2) - 1) - (2 * (q0 ^ 2 + q2 ^ 2) - 1) +\n    (2 * (q0 ^ 2 + q3 ^ 2) - 1) = 4 * q3 ^ 2 := by linarith\n\n/-! ### Sign-correction identities (pure ring) -/\n\ntheorem sign_q1 : (2 * (q2 * q3 + q0 * q1)) - (2 * (q2 * q3 - q0 * q1)) = 4 * (q0 * q1) := by ring\ntheorem sign_q2 : (2 * (q1 * q3 + q0 * q2)) - (2 * (q1 * q3 - q0 * q2)) = 4 * (q0 * q2) := by ring\ntheorem sign_q3 : (2 * (q1 * q2 + q0 * q3)) - (2 * (q1 * q2 - q0 * q3)) = 4 * (q0 * q3) := by ring\n```\n\n[1] I very much appreciate error reports. Thank you to everyone who has helped improve this blog.\n\n[2] It is awkward that math is written in LaTeX and implemented in, say, Python. Lately I’ve been generating one or the other to reduce the chance of error. When I’m using Mathematica, for example, I’ll use `TeXForm[]`\n\nto convert the Mathematica code to LaTeX.", "url": "https://wpnews.pro/news/quaternion-rotations-claude-and-lean", "canonical_source": "https://www.johndcook.com/blog/2026/06/15/quaternions-claude-lean/", "published_at": "2026-06-16 18:03:02+00:00", "updated_at": "2026-06-16 18:19:47.396994+00:00", "lang": "en", "topics": ["large-language-models", "ai-tools", "ai-research"], "entities": ["Claude", "Sonnet 4.6 Medium", "Anthropic", "John D. Cook", "Lean", "Mathlib"], "alternates": {"html": "https://wpnews.pro/news/quaternion-rotations-claude-and-lean", "markdown": "https://wpnews.pro/news/quaternion-rotations-claude-and-lean.md", "text": "https://wpnews.pro/news/quaternion-rotations-claude-and-lean.txt", "jsonld": "https://wpnews.pro/news/quaternion-rotations-claude-and-lean.jsonld"}}