Viviani, formalized

Viviani’stheorem

Pick any point inside an equilateral triangle and measure straight to each of the three sides. The three distances always add up to the same length: the triangle’s height.

This page proves that fact in , a language in which a computer checks every step, and then follows it as far as it goes: to every triangle, to the whole plane, and up into higher dimensions. Every figure can be dragged.

6 Lean files, 883 lines. Source on GitHub.

ABC height P drag the point
0.543+ 0.652+ 0.537= 1.732
Side length 2, so the height is √3 ≈ 1.732. Drag P anywhere in the triangle.

Chapter 1

The theorem

Let A, B, C be three points with all three sides of equal length, |AB| = |BC| = |CA|, and let P be any point of the closed triangle, that is, of the of {A, B, C}. Write d(P, BC) for the distance from P to the side BC. Then

d(P, BC)+d(P, CA)+d(P, AB)=(√3 / 2) · |BC|

The right-hand side is the of the triangle. Nothing on that side mentions P: that is the whole surprise.

The same statement in Lean

theorem viviani_segment (hAB : dist A B = dist B C) (hCA : dist C A = dist B C)    (hP : P ∈ convexHull ℝ {A, B, C}) :    infDist P (segment ℝ B C) + infDist P (segment ℝ C A) + infDist P (segment ℝ A B) =      Real.sqrt 3 / 2 * dist B C

Here segment ℝ B C is the side itself, as in the textbook. A companion theorem, viviani, says the same for the full line through each side, line[ℝ, B, C], which is the form most of the proofs use. There is no assumption that the triangle has positive size: if the three points coincide, both sides of the equation are zero.

Chapter 2

Why it’s true

Cut, lay flat, stackThe classical argument, in motion

Join P to the corners and the triangle falls into three pieces, each standing on one side. Follow the pieces. Every move below keeps areas exactly: first a rigid slide and turn, then a shear, where the tip of a triangle slides parallel to its base.

ABC s h P
  1. Cut. Join P to the three corners. Three triangles, each with a full side s as its base and one of the distances as its height.
  2. Lay flat. Slide and turn each piece, without changing its shape, until its base lies level in the frame below, one on top of the other.
  3. Shear. Slide each tip sideways, parallel to its base. Base and height stay the same, so the area does too.
  4. Double. Each piece is half of a rectangle s wide and one distance tall. The three rectangles fill a frame s wide and h tall, which is twice the big triangle.
Press Start. You can drag P at any step.
Three triangles: PBC, PCA and PAB.
Their heights are d₁, d₂ and d₃; their bases are all s.
Area of each piece: ½·s·d₁, ½·s·d₂, ½·s·d₃.
s·(d₁ + d₂ + d₃) = s·h, so d₁ + d₂ + d₃ = h.
Step 0 of 4

The only place the argument uses “all sides equal” is the last step, where the same width s appears in all three rectangles. In Lean the area identity is the theorem sum_infDist_facetSpan_mul_volume, and it holds for any triangle: each distance times its own side, added up, is twice the area.

Weigh the cornersThe idea behind the Lean proof

The formal proof takes a second route, which uses no areas at all, only distances and averages. It is the idea everything else on this page grows from.

Every point of the triangle is a weighted average of the corners: P = aA + bB + cC, where the weights are never negative and add up to 1.

ABC P
weight of A0.36
weight of B0.20
weight of C0.44

Drag P. The distance to BC is always the weight of A times the height: 0.357 × 1.732 = 0.619.

  1. Distance scales with weight. Distance to the line BC, taken with a sign, is an of the point, and it is zero at B and at C. So at P it is exactly a times its value at A: the distance from P to BC is a · hA, where hA is the height from A.infDist_facetSpan_affineCombination
  2. Add the three sides. The three distances add up to ahA + bhB + chC. This is true in every triangle.sum_infDist_facetSpan_affineCombination
  3. Equal sides, equal heights. Height times opposite side is twice the area, whichever corner you start from. So if the sides are equal, the heights are equal, to some h.equilateral_iff_height_eq
  4. Collect. ah + bh + ch = (a + b + c)h = h.viviani

Chapter 3

Every triangle, and only one of them

Step 2 above never asked for equal sides. In any triangle at all, the three distances from P = aA + bB + cC add up to a weighted average of the three heights:

d₁+d₂+d₃=a hA + b hB + c hC

Move the corners and see what that means. The bar on the right stacks the three distances at their true lengths; the ticks mark the three heights.

ABC hChAhB P
0.972+1.241+1.079=3.292
0.30 × 3.24+0.42 × 2.96+0.28 × 3.85=3.292

Not equilateral. The sum slides between the shortest height, 2.955, and the tallest, 3.854. The faint lines join points with equal sums.

Drag the hollow corners and the point P. First line: the three distances and their sum. Second line: weight × height for each corner. The two always agree. Thin dashed lines are the three heights.

Between the shortest and the tallest height

An average cannot leave the range of what it averages. So on the closed triangle the distance sum is at least the shortest height and at most the tallest, and it reaches both, at corners.le_sum_infDist_facetSpan, sum_infDist_facetSpan_le

The faint lines in the figure join points with the same sum. They are straight and parallel, because the sum is an affine function of P. This picture of a general triangle is due to Elias Abboud and to Burkard Polster; see related work.

The converse

If the sum is the same everywhere in a triangle, then in particular it is the same at the three corners, where it equals the three heights. Equal heights force equal sides, because height times side is always twice the area. So:

theorem viviani_iff (h : AffineIndependent ℝ ![A, B, C]) :    (∃ c : ℝ, ∀ P ∈ convexHull ℝ {A, B, C},      infDist P line[ℝ, B, C] + infDist P line[ℝ, C, A] + infDist P line[ℝ, A, B] = c) ↔      dist A B = dist B C ∧ dist C A = dist B C

A triangle has a constant distance sum if and only if it is equilateral. The hypothesis AffineIndependent says the three points are not on one line. It is needed: for three points on a line every distance is zero, so the sum is constant, yet the “triangle” need not be equilateral.

Chapter 4

Sides, lines, and the rest of the plane

To the side, or to its line?

The textbook says “distance to the side”. Proofs prefer the whole line through the side, which is easier to handle. For a point inside an equilateral triangle the two are the same, because the perpendicular dropped onto each line lands on the side itself, never on its extension. That needs an argument, and the Lean development gives one: the foot of the perpendicular from P is the same weighted average of the feet from A, B and C, and those three feet are the midpoint of the side and its two ends.orthogonalProjection_mem_segment, viviani_segment

In an obtuse triangle it can fail: try the “Obtuse” button in the figure above and look for the dashed extensions.

Outside the triangle

Carry P across a side and the theorem seems to break: the three distances now add up to more than the height. It is repaired by a sign. Count a distance as negative when P is on the far side of that line from the triangle, and the sum is the height again, everywhere in the plane.

ABC 0 h P
1.061−0.809+1.480=1.732

P is outside: one distance is measured on the far side of a side line and counts as negative (dashed). Without signs the three would add to 3.350.

Drag P anywhere. The arrows on the right are the three signed distances laid end to end, up for positive and down for negative. They always finish level with h. Side length 2, so h = √3 ≈ 1.732.

theorem sum_signedInfDist_eq_of_height_eq {s : Simplex ℝ P n} {h : ℝ}    (hh : ∀ i, s.height i = h) {p : P} (hp : p ∈ affineSpan ℝ (Set.range s.points)) :    ∑ i, s.signedInfDist i p = h

This is stated for a of any dimension whose heights all equal h; an equilateral triangle is the case n = 2. The signed distance signedInfDist is Mathlib’s.

Chapter 5

Why ternary plots work

Chemists, geologists and soil scientists draw mixtures of three ingredients as a point in an equilateral triangle. The share of each ingredient is read off as the distance to one side. That only makes sense if the three readings always total 100%, and Viviani’s theorem is exactly that guarantee.

The key lemma says more: distance to a side, divided by the height, is the weight of the opposite corner. So the readings are not just three numbers that happen to add up; they are the proportions in which the corners are mixed.signedInfDist_div_height

204060802040608020406080 ABC
share of A50%
share of B30%
share of C20%

Drag the point. The blue distance, to the side opposite A, measures the share of A; the blue grid lines mark 20%, 40%, 60% and 80% of A. The same goes for red and B, ochre and C.

Chapter 6

Up a dimension

Nothing in the weights argument is special to the plane. Replace the triangle by a tetrahedron, its three sides by four faces, and distance to a line by distance to a plane. The distance from P to a face is still the weight of the opposite corner times the height from that corner, and the four distances still add up to an average of the four heights.

For a regular tetrahedron with edge r, every height is √(2/3) · r, so:

theorem viviani_tetrahedron {s : Simplex ℝ P 3} {r : ℝ}    (hr : ∀ i j, i ≠ j → dist (s.points i) (s.points j) = r) {p : P}    (hp : p ∈ s.closedInterior) :    ∑ i, infDist p (facetSpan s i) = Real.sqrt (2 / 3) * r
ABCD h P
0.327+0.490+0.359+0.457=1.633

Regular tetrahedron, edge 2. Every height is √(2/3) × 2 ≈ 1.633, and so is the sum.

Drag P to move it; drag the empty space to turn the solid. Each perpendicular has the colour of the face it meets (the fourth face is ink). The bar shows the four distances at true length.

The converse is different in space

In the plane, a constant sum singles out the equilateral triangle. In space it does not single out the regular tetrahedron. The same reasoning as before shows the sum is constant exactly when the four heights are equal, and since height times face area is three times the volume, that happens exactly when the four faces have equal area.sum_infDist_facetSpan_const_iff_volume

A , a tetrahedron whose opposite edges are equal in pairs, has four congruent faces, so it qualifies without being regular. The Lean proof uses Heron’s formula to see that the four faces have the same area.viviani_disphenoid By a classical theorem of Bang (1897), these are in fact the only tetrahedra with four faces of equal area; that last step is not formalized here.

Every dimension

For a regular simplex of dimension n and edge r, the distances from any point of the closed simplex to its n + 1 facets add up to the height:

theorem viviani_simplex {s : Simplex ℝ P n} {r : ℝ}    (hr : ∀ i j, i ≠ j → dist (s.points i) (s.points j) = r) {p : P}    (hp : p ∈ s.closedInterior) :    ∑ i, infDist p (facetSpan s i) = Real.sqrt ((n + 1) / (2 * n)) * r
DimensionShapeSum of distances
1Segmentr
2Equilateral triangle√(3/4) · r = (√3 / 2) · r ≈ 0.866 r
3Regular tetrahedron√(2/3) · r ≈ 0.816 r
4Regular 5-cell√(5/8) · r ≈ 0.791 r
nRegular n-simplex√((n + 1) / (2n)) · r, tending to r / √2

Chapter 7

The Lean, line by line

Seven statements carry the whole development. Choose one and the figure shows what it says about the triangle. Each box quotes the statement exactly as it stands in the source.

0.360.260.38 ABC h P

P = 0.36·A + 0.26·B + 0.38·C

1 of 7
1A point of the triangle is a mixture of the corners
theorem exists_weights_of_mem_closedInterior {s : Simplex ℝ P n} {p : P}    (hp : p ∈ s.closedInterior) :    ∃ w : Fin (n + 1) → ℝ, ∑ j, w j = 1 ∧ (∀ j, 0 ≤ w j) ∧      p = univ.affineCombination ℝ s.points w

Every point of the closed shape is a weighted average of the corners, with weights that are not negative and add up to 1. The numbers in the figure are the three weights.

exists_weights_of_mem_closedInterior

2Distance is weight times height
theorem infDist_facetSpan_affineCombination (s : Simplex ℝ P n) (i : Fin (n + 1))    {w : Fin (n + 1) → ℝ} (hw : ∑ j, w j = 1) :    infDist (univ.affineCombination ℝ s.points w) (facetSpan s i) = |w i| * s.height i

The distance from the point to the side opposite corner i is its weight at i times the height from i. In the figure: the blue distance, against the dashed height from A.

infDist_facetSpan_affineCombination

3Add up the sides
theorem sum_infDist_facetSpan_affineCombination (s : Simplex ℝ P n)    {w : Fin (n + 1) → ℝ} (hw : ∑ j, w j = 1) (h0 : ∀ j, 0 ≤ w j) :    ∑ i, infDist (univ.affineCombination ℝ s.points w) (facetSpan s i) =      ∑ i, w i * s.height i

Inside the shape, the distances to all the sides add up to the weights times the heights: an average of the heights.

sum_infDist_facetSpan_affineCombination

4Equal sides, equal heights
theorem equilateral_iff_height_eq (t : Triangle ℝ P) :    t.Equilateral ↔ ∀ i j, t.height i = t.height j

A triangle has three equal sides exactly when it has three equal heights, because height times side is twice the area from every corner.

equilateral_iff_height_eq

5Viviani’s theorem
theorem viviani (hAB : dist A B = dist B C) (hCA : dist C A = dist B C)    (hP : P ∈ convexHull ℝ {A, B, C}) :    infDist P line[ℝ, B, C] + infDist P line[ℝ, C, A] + infDist P line[ℝ, A, B] =      Real.sqrt 3 / 2 * dist B C

With equal heights the average is just the height. The three distances stack up to fill it exactly.

viviani

6To the sides themselves
theorem viviani_segment (hAB : dist A B = dist B C) (hCA : dist C A = dist B C)    (hP : P ∈ convexHull ℝ {A, B, C}) :    infDist P (segment ℝ B C) + infDist P (segment ℝ C A) + infDist P (segment ℝ A B) =      Real.sqrt 3 / 2 * dist B C

Each perpendicular lands on its side, not on an extension of it. So the theorem also holds for distance to the sides as segments, the literal textbook statement.

viviani_segment

7And only then
theorem viviani_iff (h : AffineIndependent ℝ ![A, B, C]) :    (∃ c : ℝ, ∀ P ∈ convexHull ℝ {A, B, C},      infDist P line[ℝ, B, C] + infDist P line[ℝ, C, A] + infDist P line[ℝ, A, B] = c) ↔      dist A B = dist B C ∧ dist C A = dist B C

Skew the triangle and the heights differ, so the sum moves with the point. Constant sum happens exactly for equilateral triangles.

viviani_iff

The files

883 lines of Lean 4 in 6 files, built on . Two facts are taken from Mathlib and deserve credit by name: Affine.Simplex.signedInfDist_affineCombination, which is the signed form of the key lemma, and Affine.Simplex.volume_eq, base times height for simplices. What is built here is what follows from them.

  • The textbook statements, for three points A, B, C: Viviani with side lines and with the sides themselves, the converse, and the formula for every triangle. 271 lines.
  • The general theory, in any dimension: distance to a facet is weight times height; the sum is an average of the heights; it is constant exactly when the heights, or the facet volumes, are equal; signed distances on the whole space; the area identity. 247 lines.
  • Triangles: equilateral exactly when the three heights are equal; Viviani and its converse. 85 lines.
  • Regular simplices in every dimension: the height is √((n + 1) / (2n)) times the edge. 123 lines.
  • The regular tetrahedron, and the disphenoid. 90 lines.
  • A concrete triangle, (0, 0), (2, 0), (1, √3), on which the theorem gives √3. 67 lines.
Download
1/-2Copyright (c) 2026 redzwn. All rights reserved.3Released under Apache 2.0 license as described in the file LICENSE.4Authors: redzwn, Claude (Anthropic)5-/6module78public import Viviani.Triangle910/-!11# Viviani's theorem, as in the textbook1213Statements about three points `A B C` of a real inner product space `V` (for instance the plane14`EuclideanSpace ℝ (Fin 2)`), with1516* the closed triangle written `convexHull ℝ {A, B, C}`,17* the side lines written `line[ℝ, B, C]`, the sides themselves `segment ℝ B C`,18* distance from a point to a set written `Metric.infDist`.1920## Main results2122* `viviani`: if `dist A B = dist B C = dist C A` and `P` lies in the closed triangle, the23  distances from `P` to the three side lines add up to `√3 / 2 * dist B C`. There is no24  non-degeneracy hypothesis: if the three points coincide both sides are `0`.25* `viviani_segment`: the same with distances to the three sides (segments), which is the26  literal textbook wording.27* `viviani_iff`: for a genuine (affinely independent) triangle, the distance sum is constant on28  the closed triangle if and only if the triangle is equilateral.29* `sum_infDist_eq_of_affineIndependent`: in any triangle the distance sum at30  `a • A + b • B + c • C` is `a * h_A + b * h_B + c * h_C`.31* `infDist_eq_mul_of_affineIndependent`: the distance to side line `BC` is `a * h_A`.32* `infDist_segment_eq_infDist_line`: when the foot of the altitude from `A` lies on the side33  `BC`, distance to the side and distance to its line agree on the closed triangle.34-/3536public section3738open EuclideanGeometry Metric Affine Finset3940namespace Viviani4142section image4344variable {α : Type*} (A B C : α)4546/-- The vertices other than the first. -/47theorem image_compl_zero : (![A, B, C] : Fin 3 → α) '' {0}ᶜ = {B, C} := by48  ext x; simp [Fin.exists_fin_succ, eq_comm]4950/-- The vertices other than the second. -/51theorem image_compl_one : (![A, B, C] : Fin 3 → α) '' {1}ᶜ = {C, A} := by52  ext x; simp [Fin.exists_fin_succ, eq_comm, or_comm]5354/-- The vertices other than the third. -/55theorem image_compl_two : (![A, B, C] : Fin 3 → α) '' {2}ᶜ = {A, B} := by56  ext x; simp [Fin.exists_fin_succ, eq_comm]5758/-- The set of vertices. -/59theorem range_eq : Set.range (![A, B, C] : Fin 3 → α) = {A, B, C} := by60  ext x; simp [eq_comm]; tauto6162end image6364variable {V : Type*} [NormedAddCommGroup V] [InnerProductSpace ℝ V] {A B C P : V}6566section bridge6768variable (h : AffineIndependent ℝ ![A, B, C])6970/-- The facet opposite `A` spans the line `BC`. -/71theorem facetSpan_zero : facetSpan (⟨![A, B, C], h⟩ : Triangle ℝ V) 0 = line[ℝ, B, C] := by72  rw [facetSpan, image_compl_zero]7374/-- The facet opposite `B` spans the line `CA`. -/75theorem facetSpan_one : facetSpan (⟨![A, B, C], h⟩ : Triangle ℝ V) 1 = line[ℝ, C, A] := by76  rw [facetSpan, image_compl_one]7778/-- The facet opposite `C` spans the line `AB`. -/79theorem facetSpan_two : facetSpan (⟨![A, B, C], h⟩ : Triangle ℝ V) 2 = line[ℝ, A, B] := by80  rw [facetSpan, image_compl_two]8182/-- The closed triangle is the convex hull of the three vertices. -/83theorem closedInterior_eq_convexHull :84    (⟨![A, B, C], h⟩ : Triangle ℝ V).closedInterior = convexHull ℝ {A, B, C} := by85  rw [← Simplex.convexHull_eq_closedInterior, range_eq]8687/-- The sum over the three facets, written out. -/88theorem sum_facetSpan_eq (P : V) :89    ∑ i, infDist P (facetSpan (⟨![A, B, C], h⟩ : Triangle ℝ V) i) =90      infDist P line[ℝ, B, C] + infDist P line[ℝ, C, A] + infDist P line[ℝ, A, B] := by91  rw [Fin.sum_univ_three, facetSpan_zero, facetSpan_one, facetSpan_two]9293end bridge9495/-- Three points at equal positive distances from each other form a genuine triangle. -/96theorem affineIndependent_of_dist_eq (hAB : dist A B = dist B C) (hCA : dist C A = dist B C)97    (hne : A ≠ B) : AffineIndependent ℝ ![A, B, C] := by98  have hpos : 0 < dist A B := dist_pos.2 hne99  rw [affineIndependent_iff_not_collinear_set]100  intro hcol101  rcases hcol.wbtw_or_wbtw_or_wbtw with h | h | h <;>102  · have := h.dist_add_dist103    simp only [dist_comm] at *104    linarith105106107/-! ### Every triangle -/108109/-- **Key lemma, for three points.** The distance from `a • A + b • B + c • C` to the line `BC`110is `a` times the distance from `A` to that line. -/111theorem infDist_eq_mul_of_affineIndependent (h : AffineIndependent ℝ ![A, B, C]) {a b c : ℝ}112    (ha : 0 ≤ a) (habc : a + b + c = 1) :113    infDist (a • A + b • B + c • C) line[ℝ, B, C] = a * infDist A line[ℝ, B, C] := by114  have hw : ∑ i, ![a, b, c] i = 1 := by simpa [Fin.sum_univ_three] using habc115  have key := infDist_facetSpan_affineCombination (⟨![A, B, C], h⟩ : Triangle ℝ V) 0 hw116  have hcomb : univ.affineCombination ℝ ![A, B, C] ![a, b, c] = a • A + b • B + c • C := by117    rw [affineCombination_eq_linear_combination _ _ _ hw]118    simp [Fin.sum_univ_three]119  rw [height_eq_infDist, facetSpan_zero] at key120  rw [← hcomb]121  simpa [abs_of_nonneg ha] using key122123/-- **Viviani for every triangle.** At the point `a • A + b • B + c • C` of a closed triangle,124the distances to the three side lines add up to `a * h_A + b * h_B + c * h_C`, where `h_A` is125the distance from `A` to the line `BC` and so on: a weighted average of the three altitudes. -/126theorem sum_infDist_eq_of_affineIndependent (h : AffineIndependent ℝ ![A, B, C]) {a b c : ℝ}127    (ha : 0 ≤ a) (hb : 0 ≤ b) (hc : 0 ≤ c) (habc : a + b + c = 1) :128    infDist (a • A + b • B + c • C) line[ℝ, B, C] +129      infDist (a • A + b • B + c • C) line[ℝ, C, A] +130      infDist (a • A + b • B + c • C) line[ℝ, A, B] =131    a * infDist A line[ℝ, B, C] + b * infDist B line[ℝ, C, A] + c * infDist C line[ℝ, A, B] := by132  set t : Triangle ℝ V := ⟨![A, B, C], h⟩133  have hw : ∑ i, ![a, b, c] i = 1 := by simpa [Fin.sum_univ_three] using habc134  have h0 : ∀ i, 0 ≤ ![a, b, c] i := by135    intro i; fin_cases i <;> simpa136  have key := sum_infDist_facetSpan_affineCombination t hw h0137  have hcomb : univ.affineCombination ℝ t.points ![a, b, c] = a • A + b • B + c • C := by138    rw [affineCombination_eq_linear_combination _ _ _ hw]139    simp [t, Fin.sum_univ_three]140  rw [hcomb, sum_facetSpan_eq h] at key141  rw [key, Fin.sum_univ_three, height_eq_infDist, height_eq_infDist, height_eq_infDist,142    facetSpan_zero, facetSpan_one, facetSpan_two]143  rfl144145/-! ### The classical theorem -/146147/-- **Viviani's theorem.** For an equilateral triangle `ABC` and any point `P` of the closed148triangle, the distances from `P` to the three side lines add up to `√3 / 2` times the side,149which is the altitude of the triangle. -/150theorem viviani (hAB : dist A B = dist B C) (hCA : dist C A = dist B C)151    (hP : P ∈ convexHull ℝ {A, B, C}) :152    infDist P line[ℝ, B, C] + infDist P line[ℝ, C, A] + infDist P line[ℝ, A, B] =153      Real.sqrt 3 / 2 * dist B C := by154  by_cases hne : A = B155  · subst hne156    have hBC : A = C := by157      rw [dist_self] at hAB158      exact dist_eq_zero.1 hAB.symm159    subst hBC160    simp only [Set.insert_eq_of_mem, Set.mem_singleton_iff, convexHull_singleton] at hP161    subst hP162    simp163  · have h := affineIndependent_of_dist_eq hAB hCA hne164    set t : Triangle ℝ V := ⟨![A, B, C], h⟩165    have ht : t.Equilateral := by166      rw [Triangle.equilateral_iff_dist_01_eq_02_and_dist_01_eq_12]167      refine ⟨?_, hAB⟩168      change dist A B = dist A C169      rw [hAB, ← hCA, dist_comm]170    rw [← closedInterior_eq_convexHull h] at hP171    have key := viviani_triangle ht hP172    rw [sum_facetSpan_eq h] at key173    rw [key, ← hAB]174    rfl175176/-- **Viviani's theorem with its converse.** For a genuine triangle `ABC`, the sum of the177distances to the three side lines is the same at every point of the closed triangle if and only178if the triangle is equilateral. -/179theorem viviani_iff (h : AffineIndependent ℝ ![A, B, C]) :180    (∃ c : ℝ, ∀ P ∈ convexHull ℝ {A, B, C},181      infDist P line[ℝ, B, C] + infDist P line[ℝ, C, A] + infDist P line[ℝ, A, B] = c) ↔182      dist A B = dist B C ∧ dist C A = dist B C := by183  have key := sum_infDist_const_iff_equilateral (⟨![A, B, C], h⟩ : Triangle ℝ V)184  simp_rw [sum_facetSpan_eq h, closedInterior_eq_convexHull h] at key185  rw [key, Triangle.equilateral_iff_dist_01_eq_02_and_dist_01_eq_12]186  change dist A B = dist A C ∧ dist A B = dist B C ↔ _187  rw [dist_comm A C]188  constructor189  · rintro ⟨h1, h2⟩190    exact ⟨h2, h1 ▸ h2⟩191  · rintro ⟨h1, h2⟩192    exact ⟨h1.trans h2.symm, h1⟩193194195/-! ### Distances to the sides themselves -/196197/-- A side is contained in its line. -/198theorem segment_subset_line (B C : V) : segment ℝ B C ⊆ line[ℝ, B, C] := by199  rw [← convexHull_pair]200  exact convexHull_subset_affineSpan _201202/-- If the foot of the altitude from `A` lies on the side `BC`, then so does the foot of the203perpendicular from any point of the closed triangle. -/204theorem orthogonalProjection_mem_segment205    (hA : (orthogonalProjection line[ℝ, B, C] A : V) ∈ segment ℝ B C)206    (hP : P ∈ convexHull ℝ {A, B, C}) :207    (orthogonalProjection line[ℝ, B, C] P : V) ∈ segment ℝ B C := by208  let π : V →ᵃ[ℝ] V :=209    (line[ℝ, B, C]).subtype.comp (orthogonalProjection line[ℝ, B, C]).toAffineMap210  have hπ : π '' convexHull ℝ {A, B, C} ⊆ segment ℝ B C := by211    rw [AffineMap.image_convexHull]212    refine convexHull_min ?_ (convex_segment B C)213    rintro _ ⟨x, hx, rfl⟩214    rcases hx with rfl | rfl | rfl215    · exact hA216    · have : π x = x :=217        orthogonalProjection_eq_self_iff.2 (left_mem_affineSpan_pair ℝ x C)218      rw [this]219      exact left_mem_segment ℝ x C220    · have : π x = x :=221        orthogonalProjection_eq_self_iff.2 (right_mem_affineSpan_pair ℝ B x)222      rw [this]223      exact right_mem_segment ℝ B x224  exact hπ ⟨P, hP, rfl⟩225226/-- If the foot of the altitude from `A` lies on the side `BC`, then for points of the closed227triangle the distance to the side `BC` equals the distance to the line `BC`. -/228theorem infDist_segment_eq_infDist_line229    (hA : (orthogonalProjection line[ℝ, B, C] A : V) ∈ segment ℝ B C)230    (hP : P ∈ convexHull ℝ {A, B, C}) :231    infDist P (segment ℝ B C) = infDist P line[ℝ, B, C] := by232  refine le_antisymm ?_233    (infDist_le_infDist_of_subset (segment_subset_line B C) ⟨B, left_mem_segment ℝ B C⟩)234  rw [← dist_orthogonalProjection_eq_infDist]235  exact infDist_le_dist_of_mem (orthogonalProjection_mem_segment hA hP)236237/-- In an isosceles triangle the foot of the altitude from the apex is the midpoint of the base. -/238theorem orthogonalProjection_eq_midpoint (h : dist A B = dist A C) :239    (orthogonalProjection line[ℝ, B, C] A : V) = midpoint ℝ B C := by240  rw [coe_orthogonalProjection_eq_iff_mem]241  refine ⟨AffineMap.lineMap_mem_affineSpan_pair _ _ _, ?_⟩242  rw [direction_affineSpan, vectorSpan_pair, Submodule.mem_orthogonal_singleton_iff_inner_right,243    ← neg_vsub_eq_vsub_rev, inner_neg_left, neg_eq_zero]244  exact AffineSubspace.mem_perpBisector_iff_inner_eq_zero'.1245    (AffineSubspace.mem_perpBisector_iff_dist_eq.2 h)246247/-- In an isosceles triangle with apex `A`, distance to the base and distance to its line agree248on the closed triangle. -/249theorem infDist_segment_eq_of_dist_eq (h : dist A B = dist A C)250    (hP : P ∈ convexHull ℝ {A, B, C}) :251    infDist P (segment ℝ B C) = infDist P line[ℝ, B, C] :=252  infDist_segment_eq_infDist_line253    (by rw [orthogonalProjection_eq_midpoint h]; exact midpoint_mem_segment B C) hP254255/-- **Viviani's theorem, literally.** For an equilateral triangle `ABC` and any point `P` of256the closed triangle, the distances from `P` to the three sides (as segments) add up to `√3 / 2`257times the side. -/258theorem viviani_segment (hAB : dist A B = dist B C) (hCA : dist C A = dist B C)259    (hP : P ∈ convexHull ℝ {A, B, C}) :260    infDist P (segment ℝ B C) + infDist P (segment ℝ C A) + infDist P (segment ℝ A B) =261      Real.sqrt 3 / 2 * dist B C := by262  have hP₁ : P ∈ convexHull ℝ {B, C, A} := by263    convert hP using 2; ext x; simp; tauto264  have hP₂ : P ∈ convexHull ℝ {C, A, B} := by265    convert hP using 2; ext x; simp; tauto266  rw [infDist_segment_eq_of_dist_eq (by rw [hAB, ← hCA, dist_comm]) hP,267    infDist_segment_eq_of_dist_eq (by rw [← hAB, dist_comm]) hP₁,268    infDist_segment_eq_of_dist_eq (by rw [hCA, dist_comm]) hP₂]269  exact viviani hAB hCA hP270271end Viviani

Chapter 8

How it was checked

A proof assistant is only as trustworthy as the program that checks it, and a check is only worth something if it can fail. So the proofs were checked several ways, including by a checker written by different people in a different language, and the checking itself was tested by planting defects.

What is still taken on trust: the three standard axioms, the correctness of Lean’s kernel and of the second checker, and that the Lean statements say what the English says. The last is why every statement on this page is shown next to its plain reading.

CheckWhat it doesResult
Lean 4 compiler and kernelElaborates every file and checks every proof term in Lean’s small trusted . Warnings are treated as failures.No errors, no warnings
Axiom auditWalks over every declaration in the project, including the automatically generated ones, and lists the axioms each depends on.85 declarations; only propext, Classical.choice, Quot.sound
Statement fingerprintsPrints the 22 main statements as Lean understands them and compares them with a recorded copy, so a statement cannot be weakened unnoticed.Identical
leancheckerReplays each compiled module through the kernel again, from the stored proof terms.All 6 modules passed
nanodaAn independent type checker for Lean, written in Rust. It re-checks the main theorems together with every declaration they depend on, back to the axioms.48,926 declarations, no errors
Tamper testsThe same nanoda setup is handed a proof that uses sorry, one that adds its own axiom, and one that uses native_decide; and the fingerprint check is handed a swapped statement.All four rejected
A concrete triangleThe triangle (0, 0), (2, 0), (1, √3) is shown to satisfy the hypotheses, and the theorem is applied to it: the sum is √3.Proved
Lean
4.36.0-rc2 (commit b35cb10)
Mathlib
7bcc5e95ffc0d80d94adc6f17715f224d59d209b
Checked on
13 November 2026
Full output of scripts/verify.sh, including the SHA-256 of every source file
== 1. Versions
Lean (version 4.36.0-rc2, x86_64-unknown-linux-gnu, commit b35cb10cf2ced51a2fd1880cfdbf75c71ab7a35a, Release)
mathlib 7bcc5e95ffc0d80d94adc6f17715f224d59d209b

== 2. Build (the Lean kernel checks every proof); warnings count as failures
Build completed successfully (18252 jobs).

== 3. No sorry, no axiom, no native_decide in the sources
none

== 4. Axiom audit of every declaration in the project
audit: 85 declarations (81 theorems) in the Viviani modules; every one depends only on axioms among [propext, Classical.choice, Quot.sound]
'Viviani.viviani' depends on axioms: [propext, Classical.choice, Quot.sound]
'Viviani.viviani_segment' depends on axioms: [propext, Classical.choice, Quot.sound]
'Viviani.viviani_iff' depends on axioms: [propext, Classical.choice, Quot.sound]
'Viviani.sum_infDist_eq_of_affineIndependent' depends on axioms: [propext, Classical.choice, Quot.sound]
'Viviani.sum_infDist_facetSpan_const_iff_volume' depends on axioms: [propext, Classical.choice, Quot.sound]
'Viviani.viviani_simplex' depends on axioms: [propext, Classical.choice, Quot.sound]
'Viviani.viviani_disphenoid' depends on axioms: [propext, Classical.choice, Quot.sound]

== 5. Statements are the ones on record (scripts/statements.expected)
identical (22 statements)

== 6. leanchecker: replay every module of the project in a fresh kernel
ok  Viviani.Simplex
ok  Viviani.Triangle
ok  Viviani.Regular
ok  Viviani.Tetrahedron
ok  Viviani.Classical
ok  Viviani.Examples

== 7. nanoda: an independent type checker (written in Rust) re-checks the main theorems
      and everything they depend on, down to the axioms
Checked 48926 declarations with no typechecker errors, 1 pretty printer errors: [
    "Unable to print axioms",
]

== 8. Tamper tests: each planted defect must be rejected
rejected  Sorry: unpermitted axiom "sorryAx"
rejected  Axiom: unpermitted axiom "convenient"
rejected  NativeDecide: unpermitted axiom "bad._native.native_decide.ax_1_1"
rejected  Statement: a swapped statement no longer matches scripts/statements.expected

== 9. SHA-256 of the sources
8d3c5c5c21a5d29f021eda2aeacdf5b087471cb7eb917bf4aeaca04819e3ca76  Viviani.lean
839274d92ecaef5603f923718e0067361ba8bafd8093e86432596044f541dcc0  Viviani/Classical.lean
fd801abdcd57b22cc5d62c47796c31c8f3175b089b7ccf1b23a6240677d2a0db  Viviani/Examples.lean
7ec0b75d97d13054389128f7a31e2fbaf85cfb24aa4cf2b8945c5019dfefda79  Viviani/Regular.lean
e65fa22f838b02ff3ead1916f89d476c423ea23da79abc460fa11d90e779ab10  Viviani/Simplex.lean
af88584e6020ca5d64c454466737e8d62fd009cb6776dc8b1949d150bf7940a9  Viviani/Tetrahedron.lean
54b32367cd9efa717de663ad71c6da9f9c364bc8d57498528489fbfa72136a88  Viviani/Triangle.lean
1b0e320aacfb6e4d7c3364280fe758f7e3a49d3506735cfd57f883fac1576701  lakefile.toml
fbc69eb9d6e8aa76067e87309793e962e664e83650bd65a26f0880e0cb073dc9  lake-manifest.json
11c697aebe5b539d74f5069e497a588e7dfab1c4eeb74313f28357e351c55ca3  lean-toolchain

ALL CHECKS PASSED

nanoda prints “1 pretty printer errors: Unable to print axioms”. That message is about displaying the axioms at the end of the run, not about checking; the count of type-checking errors is zero.

Chapter 9

Galileo’s last student

Vincenzo Viviani was a Florentine mathematician who, as a teenager, became Galileo’s assistant in his final years. He spent much of his life protecting Galileo’s legacy, and is remembered in geometry for a curve and for this theorem.

Viviani stated the theorem in the seventeenth century. It has kept attracting new proofs and extensions ever since, most of them short enough to fit on a page.

  1. 1622Born in Florence on 5 April.
  2. 1639Moves into Galileo’s house at Arcetri to assist him. Torricelli joins them in 1641.
  3. 1642Galileo dies. Viviani was his last disciple.
  4. 1647After Torricelli’s death, is appointed to Torricelli’s position at the Accademia dell’Arte del Disegno in Florence.
  5. 1654Writes his account of Galileo’s life, which stays unpublished until 1717.
  6. 1655Edits the first edition of Galileo’s collected works, completed in 1656.
  7. 1703Dies in Florence on 22 September, aged 81.
  8. ·Three centuries later, the theorem is still finding new proofs.
  9. 2003Hans Samelson publishes a proof without words using vectors.
  10. 2005Ken-Ichiroh Kawasaki gives a proof without words using only rotations. With Yagi and Yanagawa he treats the regular tetrahedron the same year.
  11. 2006Zhibo Chen and Tian Liang prove the converse: a triangle with constant distance sum is equilateral.
  12. 2010Elias Abboud describes what happens in every convex polygon: the sum is constant along parallel segments.
  13. 2012Li Zhou generalizes the idea to “Viviani polytopes”.
  14. 2014Burkard Polster shows that in any triangle the sum lies between the shortest and the tallest height.
  15. 2026The theorem is formalized in Lean, more than once: in the lean-genius collection (June), by Joshua Bald (September), and in the development on this page (November).

Check it yourself

You don’t need to trust this page. Any computer can re-run the check; no special hardware is needed.

  1. Install Lean with elan, the Lean version manager.
  2. Clone the repository.
    git clone https://github.com/redzwn/viviani-lean
    cd viviani-lean
  3. Fetch Mathlib’s prebuilt files and build. A successful build prints no errors and no warnings.
    lake exe cache get
    lake build
  4. Run every check described above, tamper tests included.
    scripts/verify.sh
  5. Or ask Lean one question directly: what does the main theorem depend on?
    echo 'import Viviani
    #print axioms Viviani.viviani_segment' > Check.lean
    lake env lean Check.lean

The source files are also available here: Classical.lean, Simplex.lean, Triangle.lean, Regular.lean, Tetrahedron.lean, Examples.lean.