1-- Every point of the unit sphere is carried to the north pole by a2-- rotation, written by coordinates3-- Part of Cumulus, the Grace17 mathematics library.4-- Copyright (c) 2026 Grant Passmore.5-- Released under the Apache License 2.0. See LICENSE in the repository root.6-- Authors: Grant Passmore78module Grace.Cumulus.Geometry.Sphere.Transitive9import Rotation.Freeness1011-- The half turn about the axis through q + e₃ for q = (x, y, z) on the12-- sphere with z ≠ -1 and w = 1 / (1 + z): the matrix13-- w (q + e₃)(q + e₃)ᵀ - 1.14def poleRotation (x y z w : Real) : Set:=15 mat3(x * x * w - 1, x * y * w, x, x * y * w, y * y * w - 1, y, x, y, z)1617-- The half turn about the x-axis.18def flipRotation : Set:=19 mat3((1:R), (0:R), (0:R), (0:R), -(1:R), (0:R), (0:R), (0:R), -(1:R))2021section PoleEntries22variable (x y z w : Real)2324instance poleRotation.matrix25shows poleRotation(x, y, z, w) : realRing ^ 3×326proof27unfold poleRotation28exact mat3.matrix with*2930theorem poleRotation.entries31shows poleRotation(x, y, z, w)[0, 0] = x * x * w - 1∧32 poleRotation(x, y, z, w)[0, 1] = x * y * w ∧33 poleRotation(x, y, z, w)[0, 2] = x ∧34 poleRotation(x, y, z, w)[1, 0] = x * y * w ∧35 poleRotation(x, y, z, w)[1, 1] = y * y * w - 1∧36 poleRotation(x, y, z, w)[1, 2] = y ∧37 poleRotation(x, y, z, w)[2, 0] = x ∧38 poleRotation(x, y, z, w)[2, 1] = y ∧39 poleRotation(x, y, z, w)[2, 2] = z40proof41have e00 := mat3.e00 at x * x * w - 1, x * y * w, x, x * y * w, y * y * w - 1,42 y, x, y, z with*43have e01 := mat3.e01 at x * x * w - 1, x * y * w, x, x * y * w, y * y * w - 1,44 y, x, y, z with*45have e02 := mat3.e02 at x * x * w - 1, x * y * w, x, x * y * w, y * y * w - 1,46 y, x, y, z with*47have e10 := mat3.e10 at x * x * w - 1, x * y * w, x, x * y * w, y * y * w - 1,48 y, x, y, z with*49have e11 := mat3.e11 at x * x * w - 1, x * y * w, x, x * y * w, y * y * w - 1,50 y, x, y, z with*51have e12 := mat3.e12 at x * x * w - 1, x * y * w, x, x * y * w, y * y * w - 1,52 y, x, y, z with*53have e20 := mat3.e20 at x * x * w - 1, x * y * w, x, x * y * w, y * y * w - 1,54 y, x, y, z with*55have e21 := mat3.e21 at x * x * w - 1, x * y * w, x, x * y * w, y * y * w - 1,56 y, x, y, z with*57have e22 := mat3.e22 at x * x * w - 1, x * y * w, x, x * y * w, y * y * w - 1,58 y, x, y, z with*59have unfolded : poleRotation(x, y, z, w) =60 mat3(x * x * w - 1, x * y * w, x, x * y * w, y * y * w - 1, y, x, y, z)61simp [poleRotation]62rw unfolded63exact⟨e00, e01, e02, e10, e11, e12, e20, e21, e22⟩6465-- Under x² + y² + z² = 1 and w (1 + z) = 1 the half turn is special66-- orthogonal: each entry condition is a combination of the two relations.67theorem poleRotation.so368requires sphere : x * x + y * y + z * z =169requires wz : w * (1+ z) =170shows poleRotation(x, y, z, w) ∈ so3Matrices71proof72obtain⟨e00, e01, e02, e10, e11, e12, e20, e21, e22⟩:=73 poleRotation.entries at x, y, z, w74have matrix := poleRotation.matrix at x, y, z, w75have orthogonal : Orthogonal(poleRotation(x, y, z, w))76apply Orthogonal.of_entries with matrix77 . rw [e00, e10, e20]78linear_combination (x * x * w * w) * sphere +79 (x * x * (w * (1 - z) - 1)) * wz80 . rw [e01, e11, e21]81linear_combination (y * y * w * w) * sphere +82 (y * y * (w * (1 - z) - 1)) * wz83 . rw [e02, e12, e22]84linear_combination sphere85 . rw [e00, e01, e10, e11, e20, e21]86linear_combination (x * y * w * w) * sphere +87 (x * y * (w * (1 - z) - 1)) * wz88 . rw [e00, e02, e10, e12, e20, e22]89linear_combination (x * w) * sphere +90 (x * (1 - z)) * wz91 . rw [e01, e02, e11, e12, e21, e22]92linear_combination (y * w) * sphere +93 (y * (1 - z)) * wz94 . rw [e00, e01, e02]95linear_combination (x * x * w * w) * sphere +96 (x * x * (w * (1 - z) - 1)) * wz97 . rw [e10, e11, e12]98linear_combination (y * y * w * w) * sphere +99 (y * y * (w * (1 - z) - 1)) * wz100 . rw [e20, e21, e22]101linear_combination sphere102 . rw [e00, e01, e02, e10, e11, e12]103linear_combination (x * y * w * w) * sphere +104 (x * y * (w * (1 - z) - 1)) * wz105 . rw [e00, e01, e02, e20, e21, e22]106linear_combination (x * w) * sphere +107 (x * (1 - z)) * wz108 . rw [e10, e11, e12, e20, e21, e22]109linear_combination (y * w) * sphere +110 (y * (1 - z)) * wz111have unit : determinant3(poleRotation(x, y, z, w)) = (1:R)112rw [determinant3.value, e00, e01, e02, e10, e11, e12, e20, e21, e22]113linear_combination (1 - w * z) * sphere - (z * (1 - z)) * wz114exact so3Matrices.mk with orthogonal, unit115116end PoleEntries117118-- The half turn carries q to the north pole e₃ = basis(2).119theorem poleRotation.pole (q : Set) (w : Real)120requires sphere : q ∈ S²121requires wz : w * ((1:R) + (q(2):R)) = (1:R)122shows poleRotation(q(0), q(1), q(2), w) · q = basis(2)123proof124have vector := S².function with sphere125have q0 := vector.component_real at q, 0with vector, index.zero_mem126have q1 := vector.component_real at q, 1with vector, index.one_mem127have q2 := vector.component_real at q, 2with vector, index.two_mem128obtain⟨inSpace, unit⟩:= S².membership.forward with sphere129rw normSq.value at unit130obtain⟨e00, e01, e02, e10, e11, e12, e20, e21, e22⟩:=131 poleRotation.entries at q(0), q(1), q(2), w132have matrix := poleRotation.matrix at q(0), q(1), q(2), w133have image := matrixVector.vector at poleRotation(q(0), q(1), q(2), w), q134with*135have pole := basis.vector at2with index.two_mem136apply FunctionBetween.ext with image, pole137intros i hi138have entry := matrixVector.entry at poleRotation(q(0), q(1), q(2), w), q, i139with*140rw entry141have alternatives := index.cases with hi142rcases alternatives with at0 | at1 | at2143 . have other := basis.other at0, 2144with index.zero_mem, index.two_mem, index.zero_ne_two145rw [at0, e00, e01, e02, other]146linear_combination ((q(0):R) * w) * unit +147 ((q(0):R) * (1 - (q(2):R))) * wz148 . have other := basis.other at1, 2149with index.one_mem, index.two_mem, index.one_ne_two150rw [at1, e10, e11, e12, other]151linear_combination ((q(1):R) * w) * unit +152 ((q(1):R) * (1 - (q(2):R))) * wz153 . have same := basis.same at2with index.two_mem154rw [at2, e20, e21, e22, same]155linear_combination unit156157-- The half turn about the x-axis.158instance flipRotation.matrix159shows flipRotation : realRing ^ 3×3160proof161unfold flipRotation162exact mat3.matrix with*163164theorem flipRotation.entries165shows flipRotation[0, 0] = (1:R) ∧ flipRotation[0, 1] = (0:R) ∧166 flipRotation[0, 2] = (0:R) ∧ flipRotation[1, 0] = (0:R) ∧167 flipRotation[1, 1] = -(1:R) ∧ flipRotation[1, 2] = (0:R) ∧168 flipRotation[2, 0] = (0:R) ∧ flipRotation[2, 1] = (0:R) ∧169 flipRotation[2, 2] = -(1:R)170proof171have negOne : -(1:R) ∈ ℝ bytypecheck172have e00 := mat3.e00 at (1:R), (0:R), (0:R), (0:R),173 -(1:R), (0:R), (0:R), (0:R), -(1:R) with*174have e01 := mat3.e01 at (1:R), (0:R), (0:R), (0:R),175 -(1:R), (0:R), (0:R), (0:R), -(1:R) with*176have e02 := mat3.e02 at (1:R), (0:R), (0:R), (0:R),177 -(1:R), (0:R), (0:R), (0:R), -(1:R) with*178have e10 := mat3.e10 at (1:R), (0:R), (0:R), (0:R),179 -(1:R), (0:R), (0:R), (0:R), -(1:R) with*180have e11 := mat3.e11 at (1:R), (0:R), (0:R), (0:R),181 -(1:R), (0:R), (0:R), (0:R), -(1:R) with*182have e12 := mat3.e12 at (1:R), (0:R), (0:R), (0:R),183 -(1:R), (0:R), (0:R), (0:R), -(1:R) with*184have e20 := mat3.e20 at (1:R), (0:R), (0:R), (0:R),185 -(1:R), (0:R), (0:R), (0:R), -(1:R) with*186have e21 := mat3.e21 at (1:R), (0:R), (0:R), (0:R),187 -(1:R), (0:R), (0:R), (0:R), -(1:R) with*188have e22 := mat3.e22 at (1:R), (0:R), (0:R), (0:R),189 -(1:R), (0:R), (0:R), (0:R), -(1:R) with*190have unfolded : flipRotation =191 mat3((1:R), (0:R), (0:R), (0:R), -(1:R), (0:R), (0:R), (0:R), -(1:R))192simp [flipRotation]193rw unfolded194exact⟨e00, e01, e02, e10, e11, e12, e20, e21, e22⟩195196theorem flipRotation.so3197shows flipRotation ∈ so3Matrices198proof199obtain⟨e00, e01, e02, e10, e11, e12, e20, e21, e22⟩:=200 flipRotation.entries201have orthogonal : Orthogonal(flipRotation)202apply Orthogonal.of_entries with flipRotation.matrix203 . rw [e00, e10, e20]204ring205 . rw [e01, e11, e21]206ring207 . rw [e02, e12, e22]208ring209 . rw [e00, e01, e10, e11, e20, e21]210ring211 . rw [e00, e02, e10, e12, e20, e22]212ring213 . rw [e01, e02, e11, e12, e21, e22]214ring215 . rw [e00, e01, e02]216ring217 . rw [e10, e11, e12]218ring219 . rw [e20, e21, e22]220ring221 . rw [e00, e01, e02, e10, e11, e12]222ring223 . rw [e00, e01, e02, e20, e21, e22]224ring225 . rw [e10, e11, e12, e20, e21, e22]226ring227have unit : determinant3(flipRotation) = (1:R)228rw [determinant3.value, e00, e01, e02, e10, e11, e12, e20, e21, e22]229ring230exact so3Matrices.mk with orthogonal, unit231232theorem flipRotation.pole (q : Set)233requires sphere : q ∈ S²234requires first : q(0) = (0:R)235requires second : q(1) = (0:R)236requires third : q(2) = -(1:R)237shows flipRotation · q = basis(2)238proof239have vector := S².function with sphere240have matrix := flipRotation.matrix241obtain⟨e00, e01, e02, e10, e11, e12, e20, e21, e22⟩:=242 flipRotation.entries243have image := matrixVector.vector at flipRotation, q with*244have pole := basis.vector at2with index.two_mem245apply FunctionBetween.ext with image, pole246intros i hi247have entry := matrixVector.entry at flipRotation, q, i with*248rw [entry, first, second, third]249have alternatives := index.cases with hi250rcases alternatives with at0 | at1 | at2251 . have other := basis.other at0, 2252with index.zero_mem, index.two_mem, index.zero_ne_two253rw [at0, e00, e01, e02, other]254ring255 . have other := basis.other at1, 2256with index.one_mem, index.two_mem, index.one_ne_two257rw [at1, e10, e11, e12, other]258ring259 . have same := basis.same at2with index.two_mem260rw [at2, e20, e21, e22, same]261ring262263-- Every point of the sphere is carried to the north pole by a rotation.264theorem so3.transitive_pole (q : Set)265requires sphere : q ∈ S²266shows∃ σ ∈ so3Matrices, σ · q = basis(2)267proof268have vector := S².function with sphere269have q0 := vector.component_real at q, 0with vector, index.zero_mem270have q1 := vector.component_real at q, 1with vector, index.one_mem271have q2 := vector.component_real at q, 2with vector, index.two_mem272obtain⟨inSpace, unit⟩:= S².membership.forward with sphere273rw normSq.value at unit274have alternatives : (q(2):R) = -(1:R) ∨ (q(2):R) ≠ -(1:R) bytauto275cases alternatives with south | elsewhere276 . have squares : (q(0):R) * (q(0):R) + (q(1):R) * (q(1):R) =0277rw south at unit278linear_combination unit279have nonnegativeX := realMulValue.square_nonnegative at q(0)280have nonnegativeY := realMulValue.square_nonnegative at q(1)281have squareX := realAddValue.nonnegative_eq_zero at (q(0):R) * (q(0):R),282 (q(1):R) * (q(1):R) with nonnegativeX, nonnegativeY, squares283have squareY : (q(1):R) * (q(1):R) =0bylinarith284have first := realMulValue.square_eq_zero_iff.forward with squareX285have second := realMulValue.square_eq_zero_iff.forward with squareY286have rotation := flipRotation.so3287have pole := flipRotation.pole with sphere, first, second, south288exact⟨flipRotation, rotation, pole⟩289 . have nonzero : (1:R) + (q(2):R) ≠0290intro zero291apply elsewhere292linarith293have wReal := realInvValue.real at (1:R) + (q(2):R)294have wz := realInvValue.mul_left at (1:R) + (q(2):R) with nonzero295have rotation := poleRotation.so3 at q(0), q(1), q(2),296 realInvValue((1:R) + (q(2):R)) with unit, wz297have pole := poleRotation.pole at q, realInvValue((1:R) + (q(2):R))298with sphere, wz299witness poleRotation(q(0), q(1), q(2), realInvValue((1:R) + (q(2):R)))300exact⟨rotation, pole⟩301