1-- Equal sides give equal base angles.2-- Part of Cumulus, the Grace17 mathematics library.3-- Copyright (c) 2026 Grant Passmore.4-- Released under the Apache License 2.0. See LICENSE in the repository root.5-- Authors: Grant Passmore67-- Adapted from Grant Passmore's Imandra Top 100 isosceles theorem:8-- https://github.com/imandra-ai/imandra-100-theorems/blob/main/src/isosceles.iml910module Grace.Cumulus.Geometry.Isosceles11import Plane12import Real.Metric1314namespace Euclidean15exportopen Euclidean1617-- Equality of unoriented angles, encoded without arccos or roots.18-- Positivity excludes zero rays. The final sign condition distinguishes19-- equal angles from supplementary angles with the same squared cosine.20predicate EqualAngle (a b c d e f : Set) :=210 < Euclidean.distanceSquared(a, b) ∧220 < Euclidean.distanceSquared(b, c) ∧230 < Euclidean.distanceSquared(d, e) ∧240 < Euclidean.distanceSquared(e, f) ∧25 (angleDot(a, b, c) * angleDot(a, b, c)) *26 (Euclidean.distanceSquared(d, e) * Euclidean.distanceSquared(e, f)) =27 (angleDot(d, e, f) * angleDot(d, e, f)) *28 (Euclidean.distanceSquared(a, b) * Euclidean.distanceSquared(b, c)) ∧290≤ angleDot(a, b, c) * angleDot(d, e, f)3031-- The coordinate calculation is confined to cosine_identity. Everything32-- from here on reasons about sides, their lengths, and angles.33theorem angleDot.of_sss (a b c d e f : Point)34requires firstSide : Euclidean.distanceSquared(a, b) = Euclidean.distanceSquared(d, e)35requires secondSide : Euclidean.distanceSquared(b, c) = Euclidean.distanceSquared(e, f)36requires opposite : Euclidean.distanceSquared(a, c) = Euclidean.distanceSquared(d, f)37shows angleDot(a, b, c) = angleDot(d, e, f)38proof39have first := cosine_identity at a, b, c with*40have second := cosine_identity at d, e, f with*41have doubled : angleDot(a, b, c) + angleDot(a, b, c) =42 angleDot(d, e, f) + angleDot(d, e, f)43linear_combination first - second + firstSide + secondSide - opposite44exact realAddValue.double_cancel with doubled4546theorem equalAngle.sss (a b c d e f : Point)47requires firstRay : a ≠ b48requires secondRay : b ≠ c49requires firstSide : Euclidean.distanceSquared(a, b) = Euclidean.distanceSquared(d, e)50requires secondSide : Euclidean.distanceSquared(b, c) = Euclidean.distanceSquared(e, f)51requires opposite : Euclidean.distanceSquared(a, c) = Euclidean.distanceSquared(d, f)52shows EqualAngle(a, b, c, d, e, f)53proof54have positiveFirst := Euclidean.distanceSquared.positive at a, b with*55have positiveSecond := Euclidean.distanceSquared.positive at b, c with*56have equalDots := angleDot.of_sss at a, b, c, d, e, f with*57have dotReal := angleDot.real at a, b, c with*58have sameSign := realMulValue.square_nonnegative with dotReal59unfold EqualAngle60refine⟨positiveFirst, positiveSecond, _, _, _, _⟩61 . rw← firstSide62exact positiveFirst63 . rw← secondSide64exact positiveSecond65 . rw [← firstSide, ← secondSide, ← equalDots]66rfl67 . rw← equalDots68exact sameSign6970-- Compare triangle CAB with triangle CBA: the legs are equal, the base71-- is shared, and the opposite sides are the same equal legs.72theorem isosceles (a b c : Point)73requires base : a ≠ b74requires leg : a ≠ c75requires equalLegs : Euclidean.distanceSquared(a, c) = Euclidean.distanceSquared(b, c)76shows EqualAngle(c, a, b, c, b, a)77proof78have ca := Euclidean.distanceSquared.symmetric at c, a with*79have cb := Euclidean.distanceSquared.symmetric at c, b with*80have shared := Euclidean.distanceSquared.symmetric at a, b with*81have first : Euclidean.distanceSquared(c, a) = Euclidean.distanceSquared(c, b) bycc82have opposite : Euclidean.distanceSquared(c, b) = Euclidean.distanceSquared(c, a) bycc83have distinct : c ≠ a84intro equal85apply leg86cc87exact equalAngle.sss with*8889end Euclidean90