1-- Cosines of angles, the law of cosines, and the isosceles triangle theorem.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 Passmore67module Grace.Cumulus.Geometry.Angle8import Distance9import Isosceles1011namespace Euclidean12exportopen Euclidean1314-- Section: The cosine of an angle1516-- The cosine of the angle at b between the rays to a and c. An unoriented17-- angle in [0, π] is determined by its cosine, so equal cosines are equal18-- angles; no inverse trigonometric function is needed to compare them.19def cosAngle (a b c : Point) : Real:= angleDot(a, b, c) / (distance(b, a) * distance(b, c))2021theorem cosAngle.real (a b c : Point)22showsReal(cosAngle(a, b, c))23proof24typecheck2526theorem rayProduct.positive (a b c : Point)27requires firstRay : a ≠ b28requires secondRay : c ≠ b29shows0 < distance(b, a) * distance(b, c)30proof31have firstDistinct : b ≠ a32intro equal33apply firstRay34cc35have secondDistinct : b ≠ c36intro equal37apply secondRay38cc39have first := distance.positive_of_ne at b, a with*40have second := distance.positive_of_ne at b, c with*41exact realMulValue.positive with*4243theorem cosAngle.mul_rays (a b c : Point)44requires firstRay : a ≠ b45requires secondRay : c ≠ b46shows cosAngle(a, b, c) * (distance(b, a) * distance(b, c)) = angleDot(a, b, c)47proof48have positive := rayProduct.positive at a, b, c with*49have nonzero : distance(b, a) * distance(b, c) ≠050intro zero51rw zero at positive52exact realLess.not_self with realZero.real, positive53have cancel := realDiv.mul_right at angleDot(a, b, c), distance(b, a) * distance(b, c) with*54unfold cosAngle55cc5657-- Section: The law of cosines5859theorem law_of_cosines (a b c : Point)60requires firstRay : a ≠ b61requires secondRay : c ≠ b62shows Euclidean.distanceSquared(a, c) =63 Euclidean.distanceSquared(a, b) + Euclidean.distanceSquared(b, c) - 2* (distance(a, b) * distance(b, c) * cosAngle(a, b, c))64proof65have product := cosAngle.mul_rays at a, b, c with*66have symmetric := distance.symmetric at b, a with*67rw symmetric at product68have identity := cosine_identity at a, b, c with*69linear_combination identity + product + product7071-- Section: Cosines lie in [-1, 1]7273theorem cosAngle.le_one (a b c : Point)74requires firstRay : a ≠ b75requires secondRay : c ≠ b76shows cosAngle(a, b, c) ≤177proof78have positive := rayProduct.positive at a, b, c with*79have product := cosAngle.mul_rays at a, b, c with*80have dotReal := angleDot.real at a, b, c with*81have productReal : Real(distance(b, a) * distance(b, c)) bytypecheck82have upper : angleDot(a, b, c) ≤ distance(b, a) * distance(b, c)83have productNonnegative := realLess.implies_le with*84have firstSquare := distance.square at b, a with*85have secondSquare := distance.square at b, c with*86have productSquare : (distance(b, a) * distance(b, c)) * (distance(b, a) * distance(b, c)) = Euclidean.distanceSquared(b, a) * Euclidean.distanceSquared(b, c)87rw [← firstSquare, ← secondSquare]88ring89have schwarz := angleDot.cauchy_schwarz at a, b, c with*90rw← productSquare at schwarz91have sign :=Real.sign_cases with dotReal92cases sign with nonpositive | nonnegative93 . have dotLe := realLe.of_subset at angleDot(a, b, c), realZero with realZero.real, nonpositive94exact realLe.transitive with*95 . have dotLe := realLe.of_subset with realZero.real, nonnegative96exact realMulValue.le_of_square_le with*97have scaled : cosAngle(a, b, c) * (distance(b, a) * distance(b, c)) ≤1* (distance(b, a) * distance(b, c))98rw product99have one := realMulValue.one_left with productReal100 rwa one101exact realLe.mul_right_cancel_positive with*102103theorem cosAngle.neg_one_le (a b c : Point)104requires firstRay : a ≠ b105requires secondRay : c ≠ b106shows -(1:R) ≤ cosAngle(a, b, c)107proof108have positive := rayProduct.positive at a, b, c with*109have product := cosAngle.mul_rays at a, b, c with*110have bound := angleDot.lower_bound at a, b, c with*111have lower : -(distance(b, a) * distance(b, c)) ≤ angleDot(a, b, c)112haverewrite : angleDot(a, b, c) - (-(distance(b, a) * distance(b, c))) = angleDot(a, b, c) + distance(b, a) * distance(b, c)113ring114have nonnegativeDifference : 0≤ angleDot(a, b, c) - (-(distance(b, a) * distance(b, c))) by rwa rewrite115exact realLe.of_nonnegative_difference with*116have scaled : (-(1:R)) * (distance(b, a) * distance(b, c)) ≤ cosAngle(a, b, c) * (distance(b, a) * distance(b, c))117rw product118have negOne : (-(1:R)) * (distance(b, a) * distance(b, c)) = -(distance(b, a) * distance(b, c)) byring119 rwa negOne120exact realLe.mul_right_cancel_positive with*121122-- Section: The isosceles triangle theorem123124-- Equal legs give equal base angles, stated as equality of their cosines.125theorem isosceles.cosines (a b c : Point)126requires base : a ≠ b127requires leg : a ≠ c128requires equalLegs : Euclidean.distanceSquared(a, c) = Euclidean.distanceSquared(b, c)129shows cosAngle(c, a, b) = cosAngle(c, b, a)130proof131have ca := Euclidean.distanceSquared.symmetric at c, a with*132have cb := Euclidean.distanceSquared.symmetric at c, b with*133have shared := Euclidean.distanceSquared.symmetric at a, b with*134have first : Euclidean.distanceSquared(c, a) = Euclidean.distanceSquared(c, b) bycc135have opposite : Euclidean.distanceSquared(c, b) = Euclidean.distanceSquared(c, a) bycc136have equalDots := angleDot.of_sss at c, a, b, c, b, a with*137have legA : distance(a, c) = distance(b, c)138unfold distance139cc140have baseSymmetric := distance.symmetric at a, b with*141unfold cosAngle142cc143144end Euclidean145