[{"data":1,"prerenderedAt":4},["ShallowReactive",2],{"A6iwyXrzlK":3},"# Sendov's conjecture in Lean\n\nA formalization of **Sendov's conjecture** and the **Phelps–Rodriguez conjecture**, in full\ngenerality.\n\n```lean\n/-- Sendov's conjecture. -/\ntheorem Sendov.sendov {n : ℕ} (hn : 2 ≤ n) {p : ℂ[X]} (hdeg : p.natDegree = n)\n    (hroots : ∀ w ∈ p.roots, ‖w‖ ≤ 1) {a : ℂ} (hpa : p.eval a = 0) :\n    ∃ ζ : ℂ, (derivative p).eval ζ = 0 ∧ ‖ζ - a‖ ≤ 1\n\n/-- The Phelps–Rodriguez conjecture. -/\ntheorem Sendov.phelps_rodriguez {n : ℕ} (hn : 2 ≤ n) {p : ℂ[X]} (hdeg : p.natDegree = n)\n    (hroots : ∀ w ∈ p.roots, ‖w‖ ≤ 1) {a : ℂ} (hpa : p.eval a = 0) :\n    (∃ ζ : ℂ, (derivative p).eval ζ = 0 ∧ ‖ζ - a‖ \u003C 1)\n      ∨ (‖a‖ = 1 ∧ ∃ c : ℂ, c ≠ 0 ∧ p = C c * (X ^ n - C (a ^ n)))\n```\n\nIf every zero of a complex polynomial of degree `n ≥ 2` lies in the closed unit disk, then\nevery zero `a` has a critical point within distance `1` — and within distance *strictly* less\nthan `1`, unless `a` is on the unit circle and `p` is a scalar multiple of `zⁿ - aⁿ`.\n\nThe informal proof being formalized is\n[*A digestion of the proof of Sendov's conjecture*](https://terrytao.wordpress.com/2026/08/12/a-digestion-of-the-proof-of-sendovs-conjecture/).\n\nBoth are in [`Sendov/Conjecture.lean`](Sendov/Conjecture.lean#L83), and both depend on\n`propext`, `Classical.choice` and `Quot.sound` only.\n\n[`Challenge.lean`](Challenge.lean) is the statement of record: 87 lines, importing only\nMathlib, declaring no definitions of its own. A reader who wants to check *what* has been\nproved should read that file and need not read anything else.\n\n## Roadmap\n\nThe proof is by contradiction throughout. Suppose `p` has degree `n ≥ 2`, all zeroes in the\nclosed unit disk, `p(a) = 0`, and *no* critical point within distance `1` of `a`.\n\n### 0. Normalize, and split into two multisets\n\nA rotation `p(ωz)` moves `a` to the real point `‖a‖`, so `a` may be assumed real and in\n`[0,1]` — [`Sendov.rotate`](Sendov/Conjecture.lean#L43). The counterexample is then\nrepackaged as two multisets of `n-1` points of the closed unit disk:\n\n| | |\n|---|---|\n| the other zeroes `zⱼ` | [`exists_root_multiset`](Sendov/Counterexample/Factor.lean#L46) |\n| the critical points, as `qⱼ = 1/(a - wⱼ)` | [`exists_crit_multiset`](Sendov/Counterexample/Factor.lean#L74) |\n\nThe hypothesis \"no critical point within distance 1\" is exactly `‖qⱼ‖ ≤ 1`.\n\n### 1. Four identities linking zeroes to critical points\n\nAll in [`Sendov/Counterexample/Identities.lean`](Sendov/Counterexample/Identities.lean), all\ndivision-free, so that no `p'(a) ≠ 0` side condition is ever needed:\n\n| identity | meaning | |\n|---|---|---|\n| [`centroid_identity`](Sendov/Counterexample/Identities.lean#L60) | centroid of zeroes = centroid of critical points | |\n| [`prod_sub_mul_prod`](Sendov/Counterexample/Identities.lean#L322) | `p'(a)` two ways: `(∏qⱼ)(∏(a-zⱼ)) = n` | |\n| [`polar_identity`](Sendov/Counterexample/Identities.lean#L355) | `∏(1-azⱼ)/(a-zⱼ)` as an integral | |\n| [`first_origin_identity`](Sendov/Counterexample/Identities.lean#L241) | `∫₀¹F` in terms of `∏zⱼ` | `F(t) = ∏(1 - a t qⱼ)` |\n| [`second_origin_identity`](Sendov/Counterexample/Identities.lean#L165) | `F(1)` in terms of `∏zⱼ` and `∑1/zⱼ` | |\n\n### 2. The branch point `(⋆)`\n\nThe polar identity together with `p'(a)` two ways gives\n\n> [`one_le_integral_prod_norm`](Sendov/Analytic/Polar.lean#L115) : `1 ≤ ∫₀¹ ∏ⱼ ‖a + t(1-a²)qⱼ‖ dt`\n\nThis is where the argument forks. The Möbius estimate `|1-az|/|a-z| ≥ 1` is the one step that\nneeds `a` real.\n\n### 3a. Low degrees, `2 ≤ n ≤ 5`\n\nBounding each factor of `(⋆)` by the *scalar* `X(t) = a + (1-a²)t` already contradicts itself:\n`1 ≤ Jₘ(a) = ∫₀¹X(t)ᵐ dt` with `m = n-1`, while `Jₘ(a) \u003C 1` for `0 \u003C a \u003C 1` and `m ≤ 4`. The\ncomputation is done once, at `m = 4`, where `1 - J₄(a) = ((1-a)³(1+a)/5)(a⁴-3a³+3a+4)`.\n\n> [`low_degree_contradiction`](Sendov/Analytic/LowDegree.lean#L210), from\n> [`one_le_lowJ`](Sendov/Analytic/LowDegree.lean#L93) and\n> [`lowJ_lt_one`](Sendov/Analytic/LowDegree.lean#L185)\n\nNo origin channel, no `Real.rpow`, no certificates.\n\n### 3b. High degrees, `n ≥ 5`: two channels that cannot both hold\n\nWrite `x + iy = (∑ⱼqⱼ)/(n-1)` and `α = (n-1)(1-a²)/2`.\n\n**The polar channel.** AM–GM relaxes `(⋆)` to a scalar inequality in `x` alone:\n\n> [`one_le_integral_Ppolar`](Sendov/Analytic/Polar.lean#L274) — the raw polar inequality `(1Q)`\n\n**The origin channel.** Differentiating `F(t) = ∏ⱼ(1 - a t qⱼ)` and integrating against the\nfundamental theorem of calculus:\n\n| step | |\n|---|---|\n| `∑ⱼ∏_{k≠j}‖1-atq_k‖ ≤ (n-1)β(t)^{(n-2)/2}` | [`sumEraseProdMap_norm_le`](Sendov/Analytic/Origin.lean#L158) |\n| `‖F'(t) + (n-1)a(x+iy)F(t)‖ ≤ …` | [`norm_deriv_add_le`](Sendov/Analytic/Origin.lean#L329) |\n| integrate, using `F(0) = 1` | [`one_le_tri`](Sendov/Analytic/Origin.lean#L388) |\n| estimate `J∑1/zⱼ`, paying the defect `1-\\|J\\|²` | [`Jsum_estimate`](Sendov/Analytic/Jsum.lean#L122) |\n| `‖W‖ ≥ 2an/(n-1)`, collapsing `\\|J\\|` to `1` | [`grow`](Sendov/Analytic/OriginExact.lean#L92) |\n| | [`origin_exact`](Sendov/Analytic/OriginExact.lean#L179) |\n\n`grow` reduces to `(n-1)²a² - (3n-1)a + (n-1) > 0`, whose discriminant `(3n-1)² - 4(n-1)³` is\nnegative exactly from `n ≥ 5`. **This is the only place `n ≥ 5` is used.**\n\n**The contradiction.** The two inequalities force `α ≤ 17` and then contradict each other:\n\n> [`polar_origin_incompatible`](Sendov/Reduction/Main.lean#L60)\n\nby way of the chain `(1Q) ⟹ (lt)` ([`polar_exp`](Sendov/Reduction/Polar.lean#L74)),\n`(lt) ⟹ (beta-bound)` ([`beta_le`](Sendov/Reduction/BetaBound.lean#L108)),\n[`alpha_le_seventeen`](Sendov/Reduction/Alpha17.lean#L106), and a numerical claim `stat`:\n\n> [`stat_lt_one`](Sendov/Main.lean#L40), from\n> [`finite_range_le_100`](Sendov/FiniteRange/Cover.lean#L58) (degrees 5–100, by Bernstein\n> certificates) and [`large_degree`](Sendov/LargeDegree/Endgame.lean#L251) (degrees ≥ 101,\n> analytically)\n\nSee [`docs/finite-range.md`](docs/finite-range.md) for that component on its own.\n\n### 3c. The boundary, `‖a‖ = 1`\n\nThe polar identity degenerates at the boundary — the reflected point `1/a` coincides with `a`\nand `1-a² = 0` — so it is replaced by one identity from `p''(1)/p'(1)`:\n\n> `∑ⱼqⱼ = 2∑ⱼ1/(1-zⱼ)` — [`boundary_reciprocal`](Sendov/Boundary.lean#L151)\n\nBoth sides are then pinned: `Re qⱼ ≤ ‖qⱼ‖ ≤ 1` caps the left at `n-1`, while\n`Re 1/(1-z) - 1/2 = (1-‖z‖²)/(2‖1-z‖²) ≥ 0` floors the right at `n-1`. Equality term by term\nforces `qⱼ = 1`, so every critical point is `0` and `p = c(zⁿ-1)`.\n\n> [`all_q_eq_one`](Sendov/Boundary.lean#L168) → [`rubinstein_one`](Sendov/Boundary.lean#L210)\n\nThe boundary case is Rubinstein's theorem, and it is where the Phelps–Rodriguez equality case\ncomes from. The argument is **not new**: the identity and the half-plane bound above are\nequation (5.1) and Remark 5.1 of [Tang and Zhang](https://arxiv.org/abs/2508.10341), where the\nsame two steps give the `a = 1` case of Sendov's conjecture. What is done here in addition is\nto run the sandwich to equality, recovering the classification — which is Rubinstein's 1968\ntheorem, and so not new either. See [Provenance](#provenance-and-novelty).\n\n### 3d. The centre, `a = 0`\n\n`p'(a)` two ways is already enough: `(∏qⱼ)(∏zⱼ) = n` with both factors of norm at most one\nforces `n ≤ 1`.\n\n> [`sendov_center`](Sendov/Interior.lean#L125)\n\n### 4. Assembling\n\n> [`sendov_interior`](Sendov/Interior.lean#L53) (real `0 \u003C a \u003C 1`) →\n> [`sendov_interior_real`](Sendov/Interior.lean#L162) (with `a = 0`) →\n> [`phelps_rodriguez`](Sendov/Conjecture.lean#L83) (rotation, and the boundary) →\n> [`sendov`](Sendov/Conjecture.lean#L158)\n\n## Two ingredients absent from Mathlib\n\n* **Maclaurin's inequality**, top case — [`Multiset.esymm_card_pred_le`](Sendov/Analytic/Maclaurin.lean#L166),\n  by multiset induction reducing to Bernoulli. The same induction gives multiset AM–GM,\n  [`Multiset.prod_le_mean_pow`](Sendov/Analytic/Maclaurin.lean#L107).\n* **The defect lemma** — [`defect`](Sendov/Analytic/Defect.lean#L93):\n  `∏|wⱼ| · ∑ⱼ|1/wⱼ - conj wⱼ| ≤ 1 - ∏|wⱼ|²` on the closed unit disk.\n\nA third piece worth naming is [`log_sinh_div_le`](Sendov/Common/Sinh.lean#L245),\n`log(sinh h / h) ≤ √(h²+9) - 3`, which is sharp to three orders at `h = 0` and is the crux of\nthe `(beta-bound)` step.\n\n## Trust\n\n* no `sorry` and no project-defined `axiom`, with one deliberate exception: `Challenge.lean`\n  *states* the two theorems without proving them, which is what makes it the statement of\n  record for Comparator to check `Solution.lean` against. `scripts/audit.sh` requires exactly\n  those two holes and no others, and forbids `sorry` everywhere else including `Solution.lean`;\n* no `native_decide`, no `unsafe`, no floating point in any statement or proof;\n* `maxHeartbeats` / `maxRecDepth` appear only as deliberate resource knobs, each with an\n  explanatory comment.\n\n`scripts/audit.sh` checks all of this and prints the axiom dependencies of the top-level\nresults; `scripts/mutation_test.sh` checks that the numerical certificates are actually\nload-bearing.\n\n## Building\n\n```\nlake exe cache get\nlake build\nbash scripts/audit.sh\n```\n\nMemory, not time, is the binding constraint on a full build; `scripts/staged_build.sh` builds\nthe certificate files in batches.\n\n## Documents\n\n| | |\n|---|---|\n| [`docs/making-of.md`](docs/making-of.md) | how this formalization happened: a curated transcript of the conversation that produced it |\n| [`docs/getting-started.md`](docs/getting-started.md) | how to set up to do something like this, for someone new to Lean, VS Code, GitHub or coding agents |\n| [`docs/design.md`](docs/design.md) | design record: status table, the measurements behind each decision, and the traps encountered |\n| [`docs/finite-range.md`](docs/finite-range.md) | the finite-range check on its own |\n| [`docs/proof-large-degree.md`](docs/proof-large-degree.md) | informal proof for degrees ≥ 101 |\n| [`docs/plan-*.md`](docs/) | the staged hand-off plans, with notes on where they were superseded |\n| [the blog post](https://terrytao.wordpress.com/2026/08/12/a-digestion-of-the-proof-of-sendovs-conjecture/) | the informal proof being formalized |\n\n## Provenance and novelty\n\n**No novelty is claimed anywhere in this repository, and no literature or formalization search\nwas performed.** Where this document notes that something here differs from earlier work, that\nis a description of this development, not a priority claim: whether any of it is new is\nunknown.\n\nThe mathematics descends from **Lech Mazur's** proof. Sendov's conjecture was first proved, and\nfirst formalized in Lean, by that work at [ProofAtlas](https://www.proofatlas.ai/formalizations/sendov-conjecture/) — priority for both belongs there.\nThe informal proof formalized here is [Tao's digestion](https://terrytao.wordpress.com/2026/08/12/a-digestion-of-the-proof-of-sendovs-conjecture/) of Mazur's argument, described in\nthe post as an effort \"to place the proof in proper context with previous literature and to\nsimplify and streamline the argument to highlight the main ideas\". This repository is therefore\nthe **second** formalization of Sendov's conjecture, and its mathematical content is a\nstreamlined descendant of the first rather than an independent route to the same theorem.\n\nAs far as we are aware it is the first formalization of the **Phelps–Rodriguez** form: the\nearlier formalization reports a single main theorem, Sendov's `‖ζ - a‖ ≤ 1`, and does not state\nthe equality classification. That is an observation about the one earlier formalization we know\nof, not the result of a search.\n\nThe two developments are separately checkable evidence for the same theorem, and they differ in\nwhat they state and in size:\n\n| | this repository | the earlier formalization |\n|---|---|---|\n| statement | Sendov **and** Phelps–Rodriguez | Sendov |\n| Lean | 80 files, 15,152 lines | 1,160 files, 92,816 lines *(as reported there)* |\n| shared code | none | |\n\nThe boundary argument in [`Sendov/Boundary.lean`](Sendov/Boundary.lean) is likewise not new.\nIt was reached here from the blog post rather than from the literature, but its two ingredients\nare equation (5.1) and Remark 5.1 of Quanyu Tang and Teng Zhang, *Sharp Schoenberg type\ninequalities and the de Bruin–Sharma problem*, [arXiv:2508.10341](https://arxiv.org/abs/2508.10341):\ntheir `∑ₖ 1/wₖ = 2 ∑ⱼ 1/zⱼ` is `boundary_reciprocal` after moving the distinguished zero from\n`0` to `1`, and their `ℜ(1/zⱼ) ≤ -1/2` is `half_le_re_inv_one_sub`. Remark 5.1 stops at the\nnon-strict conclusion `min|wₖ| ≤ 1`; the extra step here, extracting equality term by term to\nget the classification, recovers Rubinstein's 1968 theorem and is not claimed as new either.\n\nOne difference is worth naming because it is checkable here: this development uses **no\ninterval arithmetic and no floating point at all**. The single computational step — a numerical\ninequality for degrees 5 to 100 — is discharged by exact rational Bernstein certificates\nre-verified inside Lean, and `scripts/audit.sh` fails the build if `Float` appears anywhere in\na statement or proof. No comparison of the two proofs' internal arguments has been carried out.\n\n## How this was produced\n\nEssentially all of the Lean source in this repository was written by **Claude Opus 5**\n(Anthropic), working interactively in Claude Code under the direction and review of the\nauthor: choosing the Lean formulations, finding and repairing proofs, and designing the\ncertificate machinery. The author set the targets, supplied the informal proof and the staged\nplans in `docs/`, made the mathematical decisions, and reviewed the output as it was produced.\nNo external or independent review has been performed.\n\nThe Bernstein certificates in `Sendov/FiniteRange/` are emitted by the Python scripts in\n`scripts/`. Those scripts are outside the trusted base: they write Lean source that Lean\nre-verifies from scratch, so a bug in them causes a build failure, not an unsound theorem.\n\nTwo documents record the process rather than the result.\n[`docs/making-of.md`](docs/making-of.md) is a curated transcript of the conversation that\nproduced the development — the stages it went through, and the dead ends and mistakes along\nthe way. [`docs/getting-started.md`](docs/getting-started.md) describes the setup this\nrepository started from and the working habits that mattered, for someone new to Lean, VS Code,\nGitHub or coding agents.\n\n## Licence\n\nApache-2.0; see [`LICENSE`](LICENSE).\n",1787169594357]