Як я намагався штурмувати P vs NP разом з Claude — і чому «код компілюється» це ще не «доведено»
Якщо ви бодай трохи цікавилися теорією складності, то знаєте: P vs NP — це та сама задача тисячоліття, за розв’язання якої Clay Institute обіцяє мільйон доларів. Мільйони розробників у якийсь момент думали: «а що як натравити на неї LLM?» Я теж подумав.
Спойлер: P vs NP досі не розв’язана. Але в процесі стався несподівано корисний урок про те, як (не) варто довіряти ані ChatGPT/Claude, ані навіть компілятору формального доказового асистента — і цей урок актуальний для будь-кого, хто працює з ШІ в продакшн-коді, а не тільки для математиків.
Задум
Ідея була така: взяти Lean 4 — мову й доказовий асистент, у якому кожен логічний крок перевіряється машиною без жодних припущень на слово, — і спробувати формалізувати щось із Geometric Complexity Theory (GCT), одного з найперспективніших підходів до P vs NP через алгебраїчну геометрію та теорію представлень груп.
Спойлер номер два: за кілька сесій з Claude я не наблизився до P≠NP ні на йоту. Але отримав дещо інше — маленький, чесний, повністю доведений шматочок математичної інфраструктури, і разом з ним — дуже наочний урок про природу «доведеності» в епоху ШІ-асистованої розробки.
Урок № 1: компілюється — не означає доведено
Перший файл, який ми з Claude написали, компілювався без жодної помилки. Теорема з назвою на кшталт VP_ne_VNP_target виводилась... з трьох аксіом (axiom у Lean — це твердження, яке приймається без доведення, «повірте на слово»). Формально Lean не бреше — він каже: «якщо прийняти ці три постулати, то ось цей висновок логічно з них випливає». Але самі постулати ми просто задекларували, не довівши.
Це як написати unit-тест, який мокає всю бізнес-логіку, а тоді перевіряє, що мок повертає те, що ти в нього і поклав. Тест зелений. Це нічого не означає.
У Lean є команда, яка рятує: #print axioms your_theorem. Вона показує повний список аксіом, на які спирається доведення. Якщо там тільки propext, Classical.choice, Quot.sound — це стандартна логіка самого Lean, все чисто. Якщо там з’являється щось із назвою типу irrepMultiplicity_semicontinuous — це ваша власна, недоведена аксіома, і весь «результат» тримається на довірі до неї.
Ми чесно прогнали #print axioms і побачили: 3 змістовні аксіоми, 0 реальних доведень. Синтаксична коректність, нуль математичного прогресу. Це стало правилом на весь подальший проєкт: жодна теорема не рахується «зробленою», доки #print axioms не покаже чистий список.
Ось як це виглядало в коді. Спочатку — сама «діра», замаскована під респектабельний математичний факт:
-- Постулюємо ключовий факт "на віру" — ось де живе вся дірка axiom irrepMultiplicity_semicontinuous (f g : PolyRing m) (lam : Partition) (h : g ∈ orbitClosure m A f) : multiplicity m g lam ≤ multiplicity m f lam -- А ця теорема виглядає солідно, і компілюється без жодної помилки... theorem VP_ne_VNP_target (n c : ℕ) (m : ℕ) (hm : m ≤ n ^ c) (f g : PolyRing m) (lam : Partition) (hpos : 0 < multiplicity m f lam) (hzero : multiplicity m g lam = 0) : f ∉ orbitClosure m A g := separation_from_obstruction m A f g lam hpos hzero
...і ось команда, яка миттєво знімає ілюзію:
#print axioms VP_ne_VNP_target -- [VP_ne_VNP_target] depends on axioms: -- [irrepMultiplicity_semicontinuous, irrepMultiplicity, zariski_topology, -- propext, Classical.choice, Quot.sound]
Перші три рядки виводу — наші власні, недоведені постулати. Останні три — стандартна логіка Lean, з ними все гаразд. Правило просте: якщо у виводі #print axioms є щось, крім propext, Classical.choice і Quot.sound, — перед вами не доведення, а домовленість вірити на слово.
Урок № 2: навіть ШІ-асистент помиляється в арифметиці — рахуйте вручну
Ми звузили амбіції до чогось реально доказового: hook length formula — класичної формули з комбінаторики теорії представлень, яка обчислює розмірність незвідних представлень симетричної групи Sₙ через «діаграми Юнга» (візуальне представлення розбиття числа на клітинки) і «крюки» — довжини специфічних куточків цих діаграм.
У якийсь момент Claude вивів формулу для конкретного класу діаграм (так званий hook shape) і помилився: замість правильної відповіді C(2k,k) видав просто k. Виглядало правдоподібно, компілятор би це навіть не спіймав (це arithmetic твердження, яке ще не було закодоване) — але ручна перевірка на маленькому прикладі (k=1, S₃) одразу показала розбіжність: k=1 давало відповідь 1, а правильна відповідь мала бути 2.
От як виглядала ручна перевірка на найменшому нетривіальному прикладі — діаграма λ=(2,1), тобто k=1:
| Клітинка | Рука | Нога | Довжина крюка |
|---|---|---|---|
| (1,1) — кут | 1 | 1 | 3 |
| (1,2) | 0 | 0 | 1 |
| (2,1) | 0 | 0 | 1 |
Добуток крюків = 3·1·1 = 3. Розмірність = n!/добуток = 3!/3 = 2. Формула Claude (k) для k=1 давала 1 — розбіжність одразу видно на найпростішому прикладі, ще до написання загального доведення.
Мораль: ШІ чудово генерує правдоподібний текст і код, включно з математичними твердженнями, які звучать переконливо. Єдиний надійний спосіб перевірки — незалежний перерахунок на маленькому конкретному прикладі, перш ніж кодувати загальний випадок. Це стосується не лише формальної математики — та сама логіка застосовна до будь-якого ШІ-згенерованого коду з нетривіальною логікою.
Що реально вдалося довести
Зрештою в проєкті github.com/illyar80/p_vs_np з’явилося:
- Формула крюків для «hook shapes» (діаграм виду одна довга перша строка + стовпчик під нею) — повністю доведена, нуль sorry (заглушок «доведу пізніше»), нуль змістовних аксіом.
- Формула крюків для двострокових діаграм — те саме, плюс вихід на класичну «ballot number formula».
- Формальний міст між нашим власним представленням діаграм і вбудованою структурою
YoungDiagramз Mathlib (стандартна математична бібліотека для Lean) — щоб довести, що обидва підходи рахують одне й те саме. - Комбінаторна оцінка (частина A програми Bürgisser—Ikenmeyer—Panova) — теж повністю доведена.
- І чесно позначена відкрита частина: повна no-go теорема BIP вимагає результатів з теорії коефіцієнтів Кронекера, які на сьогодні — нерозв’язана дослідницька задача світового рівня. Ми це не приховали під
sorry, а просто не стали формалізовувати, чітко описавши в README, чого бракує і чому.
Підсумковий стан проєкту виглядає так:
| Модуль | Що доведено | sorry | Власні аксіоми |
|---|---|---|---|
HookLength.lean | Формула крюків для hook shapes | 0 | 0 |
TwoRow.lean | Формула крюків для двострокових діаграм | 0 | 0 |
Bridge.lean | Еквівалентність з Mathlib.YoungDiagram | 0 | 0 |
BIP.lean (частина A) | Комбінаторна оцінка простору перешкод | 0 | 0 |
BIP.lean (частина B) | Повна no-go теорема Bürgisser—Ikenmeyer—Panova | не формалізовано | — |
Test.lean (чернетка) | Скелет GCT-аргументу | 0 | 3 (чесно позначені) |
Важливо чесно сказати: сама загальна формула крюків (для будь-якої діаграми Юнга) вже була доведена в Lean раніше іншим автором, іншим методом (імовірнісний «hook walk»). Наш внесок — незалежна перевірка іншим, елементарним методом для двох конкретних сімейств, плюс явні замкнуті формули та міст до стандартної бібліотеки. Це скромний, але чесний результат — і я свідомо не називаю це «проривом» чи «заповненням прогалини», бо це неправда.
Найкраща частина: як швидко ловлять брехню
Десь посеред роботи над проєктом мені трапився свіжий (червень 2026) препринт на arXiv: T.S. Артанарі заявив повне машинно-верифіковане в Lean 4 доведення P = NP через зведення симетричної задачі комівояжера до поліедральної membership problem. Стаття містила фрази на кшталт «Lean 4 rejects any gap... there is no possibility of a subtle error slipping through» і статистику «2968/2968 build targets clean».
Звучить переконливо, правда? Компілюється без помилок, нуль sorry в основному ланцюжку доведення.
За один день хтось відкрив у репозиторії Issue з назвою «Main theorem currently proves True, not P = NP». З’ясувалося: ключове визначення виглядало приблизно так:
-- Ось і вся "історична теорема P = NP": def P_equals_NP : Prop := True def SAT_in_P : Prop := True theorem p_equals_np (n : ℕ) (hn : 5 ≤ n) : P_equals_NP := by trivial -- ще б пак, це ж просто True
Теорема доводила «True = True» — тавтологію, яка ніяк не відповідала заявленому змісту. Компілятор це, звісно, пропустив: з погляду Lean, True — цілком коректний тип, і довести True — тривіально (trivial закриває будь-яку ціль True миттєво). Але саме твердження було підмінене на порожнє ще на етапі формулювання, до будь-якого доведення. «2968/2968 build targets clean» — і жодного стосунку до реальної проблеми P vs NP.
Це і є справжня мораль усієї цієї подорожі, актуальна для будь-кого, хто працює з формальними методами, тестами чи ШІ-згенерованим кодом: компілятор (чи test suite, чи CI) перевіряє внутрішню узгодженість відносно того, що ви йому сформулювали — а не те, чи ваше формулювання відповідає реальності. Помилка може ховатися не в логіці виведення, а в самій постановці задачі — і жоден зелений білд це не зловить.
Практичні висновки для розробників
Навіть якщо ви ніколи не відкриєте Lean:
- «Тести проходять» і «код працює правильно» — різні твердження. Завжди питайте: а що саме перевіряє цей тест? Чи не замокано випадково саму суть перевірки?
- ШІ-асистенти чудово генерують правдоподібні, синтаксично бездоганні артефакти — включно з тестами, доведеннями, специфікаціями — які можуть бути змістовно порожніми. Перевіряйте на маленьких конкретних прикладах вручну, перш ніж довіряти узагальненню.
- Явно розділяйте «доведено», «допущено на віру» (аксіома/mock/TODO) і «відкрито» — і в коді, і в документації. Наш README прямо перелічує, що саме доведено, що спирається на власні недоведені аксіоми, а що — чесно позначена нерозв’язана задача. Ця прозорість коштує дешево і рятує від пізнішого сорому.
- Якщо цікаво копнути глибше в саму математику — почніть з класичного огляду Скотта Ааронсона «P =? NP» (є у вільному доступі), а живі обговорення свіжих заявлених доведень шукайте на cstheory.stackexchange.com.
P vs NP, найімовірніше, не впаде найближчим часом ні від рук ШІ, ні від рук людини поодинці. Але дисципліна «довіряй, та перевіряй — на маленькому прикладі, руками» виявилась куди ціннішим сувеніром з цієї подорожі, ніж будь-яка окрема лема про довжини крюків.
8 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарівЗ нещодавного, діалог з Claude:
● Готово. Лему додамо в кінець destroy_decoder.v — погоджуєшся з таким формулюванням?
Lemma destroy_decoder_idempotent :
forall (gv : globals) (me bad : val),
mem_mgr gv *
data_at Tsh (Tstruct _decoder noattr) (bad, nullval) me
|—
mem_mgr gv *
data_at Tsh (Tstruct _decoder noattr) (bad, nullval) me *
(if eq_dec nullval nullval then emp
else malloc_token Ews (tarray tuchar 0) nullval *
data_at_ Ews (tarray tuchar 0) nullval).
❯ Давай робберемо
forall (gv : globals) (me bad : val), — тут зроз
mem_mgr gv * data_at Tsh (Tstruct _decoder noattr) (bad, nullval) me тут також зроз, це PRE
|— слідує
mem_mgr gv * — тут зроз
data_at Tsh (Tstruct _decoder noattr) (bad, nullval) me
Це повторує...
А далі... if eq_dec nullval nullval вишлядає як повна ***ня
Більше того я не бачу викликів destroy_decoder
● Ти абсолютно правий — та лема була погана з кількох причин:
1. if eq_dec nullval nullval — це завжди true, гілка else ніколи не спрацює, це безглуздо
2. Головне: destroy_decoder взагалі ніде не фігурує — це просто тривіальний ентайлмент Q |— Q * emp
Ну... методологічно не рекомендується поєднувати ролі розробника та тестувальника... Тут ті самі граблі: коли одна персона зацікавлена в результаті, вона може трохи підробити дані. Підлог неодноразово робили й люди. Ну а тут... просто більшість задач на яких обучалися нейромережі прості, от вони й шукають прості рішення. Тільки не завжди працює.
Правильно або самому сформулювати теорему, або попросити одного агента сформулювати, перевірити, (протестувати) а другого просили надати лише доказ. Знову, без контексту давати таку задачу Claude як на мене сенсу не має. Треба обов’язково побудувати процесс, щоб вона пропонувала одну конструкцію, а потім генерувати контекст та давайти його на вход.
Ну й... моя остання моя медитація була що докази можна викристовувати в блокчейні як proof of useful work (важко генерувати, легко перевірити). Там я перевинайшов ідею proofgold, коли після натхненної розробки концепту наприкинці дня вирішив загуглити що є... Звісно там багато граблей та технічної роботи також... Але в ідеалі знайдений доказ (через Claude або якось ще) має давати право генерацію нового блоку.
Дякую за змістовний коментар! Кілька уточнень з практики.
Я розумію що таке Lean 4, звісно що перевіряти докази не треба. Знову теоретично, бо в софті можуть бути вразливості, я чув про такі випадки в Rocq. Я більше акцентував авагу на конфлікті інтересів. Коли одина людина пише програму та перевіряє її то виникає спокуса заплющити очі на певні негаразди. Коли одна людина формулює твердження та доводить його то виникає спокуса десь спрямити кути.
Але «перевіряй на конкретному малому прикладі» це питання перевірки формулювання твердження, але це мінімальний мінімум. Ітак, у нас є... я не знаю що таке hook shapes, гуглити не хочеться, взагалі текст трохи перевантажений незагальновідомими математичними задачами, які в принципі не впливають на суть. Тому спробую щось більш відоме.
Припустимо є задача розташування N ферзів на шахівниці NxX яка більш-менш відома розробникам. Ми хочемо знайти формулу, вочевидь нічого простого немає, але припустимо... Тоді нам треба визначення правильного розташування ферзів. Це неелементарне визначення, в якому можна помилитися. Наприклад, якщо забути чи неправильно написати умову для діагоналей, тоді задача зведеться до розташування тур, яка вже має формулу. І це актуальна проблема.
Ви пропонуєте «перевіряй на конкретному малому прикладі, руками». Незрозуміло, навіщо руками, коли в тебе є мова програмування. Чому б не написати код? Наприлад, просто перебрати $5^5 = 3125$ варіантів розташування ферзів на дошці 5×5, для кожного викличе наш предікат та перевірить скільки розташувань існує.
Але «конкретному малому прикладі» це лише частина перевірки правильності формулювання (sanity checking). Певні властивості можуть виникати на більш складних прикладах. Маючи такий потужний арсенал, якось не дуже хочеться обмежуватися лише «конкретними малими прикладами». Ми можемо доводити більше складні властивості нашого визначення, й ми мусимо це робити. Наприклад, якщо брати isdiag sq1 sq2 (чи дві клітини розташовані на одній діагоналі) ми окрім конкретних прикладів ми можемо: (1) довести комутативність; (2) довести що зсув обох клітин на один і той самий вектор зберігає діагональність; (3) поворот дошки не змінює диагональність (4) довести що з клітинки a1 неможливо ланцюзком ходів дістатися до b1, більше того будь яка клітинка досягається ланцюждок ходів або з a1 або з b1. Далі, подвійне (dual spec) або навіть потрійне кодування: ми можемо визначити предикат через квантор існування (є таке число n, що або x1, y1 = x2 + n, y2 + n, або x1, y1 = x2 + n, y2 — n, або...) Можемо через математичну формулу abs(x1-x2) = abs(y1-y2). Потім довести еквівалентність. Також можна написати алгоритм (визначаємо напрямок руху, потім йдемо по одній клітинці доки не дійшли), й теж довести еквівалентність... Це кардинально звузить можливість помилитися...
Згоден — малий приклад це мінімум, а повна дисципліна — доводити властивості самого визначення. І тут ми вашу схему фактично застосували: у Bridge.lean наше представлення діаграм доведено еквівалентним незалежному представленню Mathlib (hookLen_eq_YD) — «друга сторона» тут зовнішня бібліотека, а не LLM, що частково знімає і ваш пункт про конфлікт інтересів.
Щодо «навіщо руками» — «руками» означало «не на слово ШІ», брутфорс так само годиться. Але перебір перевіряє скінченну кількість випадків, а ваші властивості isdiag — всі одразу, тобто це вже доведення, і саме цим займається Lean.
І чесно: soundness-баги в ядрах асистентів знаходили (Coq, Lean 3 у 2022) — «доведено» завжди «відносно коректності ядра й аксіом», що лише підсилює тезу статті.
Easy money. Доведи аксіому сам. Типу, може ти знайшов рішення вже (саме з допомогою LLM), але воно тобі таким не здається. Тобто LLM тобі відповіла як могла, але її обмеження завадили їй пояснити. Типу, вона дала тобі проекцію повної відповіді. Проекція 3D обжекту на площині щось та втрачає.
Влучна метафора, але вона працює в обидва боки — і в цьому суть.
«Доведи аксіому сам» — саме так, і це рівно та точка, де ми зупинилися. Але важливо, які це аксіоми: у проєкті лишилися не «LLM не змогла пояснити», а конкретні відкриті задачі з теорії коефіцієнтів Кронекера (зокрема для повної no-go теореми Бюржіссера—Ікенмаєра—Панової). Це відомі нерозв’язані проблеми — їх не довів ні людина, ні модель. Довести їх «самому» — це повноцінна дослідницька програма, а не один крок.
А щодо «проекції 3D на площину» — чесне занепокоєння, і я його не відкидаю. Так, плоска відповідь LLM може бути тінню чогось глибшого. Але рівно тому формальна перевірка й існує: інтуїцією неможливо відрізнити «проекцію повного рішення» від «переконливого шуму», який звучить упевнено. Гіпотеза проекції перевіряється єдиним способом — спробою довести аксіому до кінця. «Можливо, там щось є» — легітимна гіпотеза, але вона не зараховується як «доведено». Саме тому в статті й написано: компілятор перевіряє узгодженість формулювання, а не його відповідність реальності.
Тепер час від часу сам гуглю про P та NP. Заразив.