Этот сайт использует файлы cookies. Продолжая просмотр страниц сайта, вы соглашаетесь с использованием файлов cookies. Если вам нужна дополнительная информация, пожалуйста, посетите страницу Политика файлов Cookie
Subscribe
Прямой эфир
Cryptocurrencies: 8123 / Markets: 110688
Market Cap: $ 2 690 667 610 546 / 24h Vol: $ 60 048 265 646 / BTC Dominance: 60.138218143661%

Н Новости

Девять петель от Claude — почему это очень важно для кода будущего

25 сентября 2026 года Anthropic опубликовал «Yes, Claude can do Nine Loops».

По сообщению участников проекта, Claude получил символ девятипетлевой шестичастичной MHV-амплитуды двумя способами. Речь идёт о специальной квантовой теории поля — планарной \mathcal N=4 суперсимметричной теории Янга — Миллса. Полная функция была восстановлена отдельно; её второго независимого расчёта пока нет. Разницу между символом и полной функцией разберём в разделах 13–16.

Предыдущий лучший опубликованный результат исследователей для этой шестичастичной амплитуды — восемь петель в работе Лэнса Диксона и Юй-Тин Лю (2023).

Заявленная стоимость каждого из двух способов расчёта — около 1000–2000 долларов, а проверкой результата две недели занимался Лэнс Диксон из SLAC, один из специалистов, развивавших эти методы.

Для кого эта статья

Эта статья для тех, кто хочет понять результат Claude и разобраться, как проверять научный код, написанный с помощью ИИ. Разработчику она даст примеры проверок, студенту и любопытному читателю — последовательный путь от базовых понятий к петлевым расчётам. Текст можно использовать и как материал для ИИ-агента, которому вы поручаете научное программирование.

Я опираюсь на свой опыт преподавания вычислительной математики и выполнения практических проектов. Из потребности связать численные методы с реальными задачами вырос мой курс для МФТИ. А для этой статьи пригодился мой старый диплом бакалавра о магнитоэкситонах в графене — его историю расскажу ниже.

Почему это важно для научного кода

Меня в этой новости интересует не сам физический результат, а организация работы с ИИ. Расчёт выполнен известными методами; новизна — новый девятипетлевой результат и способ построения сложного вычисления с помощью модели. Claude создал научный код и получил результат, прошедший описанные участниками проверки. Это не доказывает отсутствия всех ошибок: в исходной публикации автор прямо говорит, что не знает, сколько ошибок модель совершала в ходе работы, тем более что во многих подобных физических задачах правильный ответ еще не означает безошибочность его вывода.

Ключевой вопрос тут — как отличить научный результат от убедительно оформленного ответа нейросети? Ниже разберём, что именно вычислялось, как это было реализовано с помощью кода и какие проверки позволяют доверять ответу в оговорённых пределах.

Если такие методы помогут исследователям быстрее создавать и проверять программы, они могут облегчить вычислительную часть крупных проектов — от физики частиц до астрофизики и физики твёрдого тела. Но из одного успешного расчёта ещё не следует, что ИИ способен безошибочно обработать любые экспериментальные данные или что исчезли ограничения на строительство новых установок.

Однако, если в будущем исследователи добьются от ИИ этой способности, обложив его правильными алгоритмами и проверками, это произведет революцию в фундаментальных и прикладных областях. Можно будет строить коллайдеры намного больше БАК, потому что критическим ограничением размера ускорителя сейчас является способность научного сообщества обрабатывать большие объемы данных с ускорительной техники. Можно будет значительно расширить программы астрономических и астрофизических исследований — там то как раз объем данных, получаемых с приборов, пока что намного превосходит способности ученых их обработать. Наконец, это всё сулит невиданный прогресс в области физики твердого тела и создания новых материалов.

Объяснения идут от простых моделей к задаче Claude. Формулы, код и контрольные результаты оставлены рядом, чтобы читатель мог повторить отдельный шаг, а не только поверить рассказу. Архив кода статьи.

Личная предыстория

Для меня эта новость тесно связана с личной историей. В 2010 году я защитил в МФТИ бакалаврский диплом «Расчет магнитоэкситонов в графене в магнитном поле с учетом экранировки в приближении хаотических фаз». Руководителем был Юрий Ефремович Лозовик. От этой работы сохранились текст, notebook Mathematica и таблица Excel — вот артефакты моего диплома.

У меня самого сложное отношение к этому всему.

С одной стороны, в определенный момент я принял решение уйти из всей этой науки и поискать что-то, что приносит больше денег.

С другой стороны, переживал, что из-за этого меньше приношу пользы для общества, в меньшей степени реализовал себя в жизни.

Но теперь я понимаю, что появление ИИ сильно переоценивает это всё. Потому что если бы я остался в науке, то многое, что сделал бы за последние 20 лет вместо зарабатывания денег, просто оказалось бы довольно бесполезным. В силу того, что с помощью ИИ мой диплом бакалавра пишется за несколько часов, диплом магистра за 3 дня, а заброшенную диссертацию, думаю можно за недели написать. Многое, чем занимался мой научный руководитель, его коллеги и чем бы занимался я — сейчас гораздо быстрее и притом надежнее делается с помощью нейросетей под контролем человека.

Подозреваю, что если бы я остался в науке, то сейчас бы испытывал фрустрацию: денег не заработал, так еще ИИ и труд моей жизни фактически обесценил.

Уйдя из науки далеко от квантовой электродинамики и физики твердого тела, я думал, что больше мне это все никогда в жизни не пригодится.

Но вот впервые в жизни мой диплом пригодился — написать полезную статью на Хабр!

Во-первых, благодаря работе над его написанием я тогда разобрался в тех вещах, которые описываю здесь, и многие объяснения, что писал в НИРах и в своих черновиках, просто перенес сюда.

Во-вторых, работа из моего диплома — хороший простейший пример подобного рода расчетов, объяснение которого позволяет понять, как это устроено в принципе. Задачу Claude с 9 петлями очень трудно разобрать в деталях не специалисту, а задачу из моего диплома покрутить, промоделировать, проанализировать в принципе может и новичок. На этом примере проще проследить отдельные шаги расчёта.

В моей задаче тоже есть квантовые состояния, взаимодействия, диаграммы и петли. Но весь путь от постановки задачи до чисел помещается в один небольшой Python-скрипт.

Шестнадцать лет спустя диплом пригодился для статьи на Хабр
Шестнадцать лет спустя диплом пригодился для статьи на Хабр

Комикс zinoink: оригинал автора на Пикабу. Теплотехнический диплом в комиксе работает по специальности — помогает с огнём. Мой шестнадцать лет набирался опыта на диске и наконец устроился объяснять девять петель на Хабре. Карьерный путь нелинейный, зато без мангала.

При подготовке этой статьи ИИ-агент мне помог написать код, осуществить все проверки, и нарисовать большую часть иллюстраций отсюда. Поэтому та теория и практика работы над научным кодом, которая здесь описывается, была реализована прямо при создании статьи.

При восстановлении расчёта с помощью ИИ обнаружилась ошибка в записи единицы энергии в дипломе: скорость Ферми оказалась в знаменателе вместо числителя. Ниже покажу исходную запись и размерностную проверку. Эта находка сама по себе ещё не означает, что численные результаты диплома неверны.

Вот так новость о девяти петлях привела меня к вопросу о том, как перепроверять научный код и старые результаты, даже если они уже прошли академическую проверку.

С этого и начнём: научимся понимать формулу настолько, чтобы замечать подобные расхождения. Затем разберём нуль, одну и две петли, самостоятельно получим несколько поправок и только после этого вернёмся к Claude.

Общий язык состояний, взаимодействий и диаграмм связывает эти задачи, но их теории, наблюдаемые величины и методы расчёта различаются.

Одна из самых главных идей статьи — процессная декомпозиция как средство контроля кода от ИИ. Сначала нужно описать моделируемое явление и его расчёт через небольшие процессы с контролируемыми входами и выходами, и только затем поручать ИИ реализацию отдельных блоков и проверять их соединения.

Это один из методов повышения надёжности: он работает вместе с аналитическими проверками и контрольными точками, независимыми расчётами и проверкой сходимости.

Описание квантовой физики на языке процессов с помощью картинок существует и называется квантовым пикторализмом. В этом подходе часть алгебраических преобразований можно выражать через диаграммы с точными правилами соединения. Рисунки требуют тех же определений и допущений, что и формулы. Если вам это интересно, можете почитать учебник.

Для работы с учебными скриптами достаточно базового знания Python и установки перечисленных в разделе 17 библиотек; для подробных выводов пригодятся знания производных, интегралов и правила умножения матриц.

Необходимые физические и математические понятия здесь вводятся по ходу дела и сопровождаются примерами с иллюстрациями. Текст можно использовать как последовательный курс или справочник. Его цель не в том, чтобы освоить всю квантовую теорию поля за один вечер. Она в том, чтобы научиться понимать, о чем вся эта теория, и как в ней связаны между собой объяснения, формулы, рисунки, код и что вычисляют физики.

Как читать эту длинную статью.

Сначала — законченный пример работы с ИИ и проверки научного кода.

Разделы 1–8 восстанавливают физику диплома.

9–10 объясняют счёт петель на простых моделях.

11–16 разбирают результат Claude.

В разделе 17 — семь лабораторных опытов, включая намеренную порчу программы и проверку самих тестов. Полные программы убраны под раскрывающиеся блоки, поэтому сначала можно пройти по объяснениям и рисункам.

Так как статья длинная, возможно читать последовательно будет несколько утомительно. Пользуйтесь планом: выберите интересующий раздел, а при незнакомом понятии вернитесь к его определению выше.

Общий план статьи
Общий план статьи

Как сделать научный код надежным

Для программиста здесь до боли знакомая задача: нужно перенести старую систему на другой стек, сохранив смысл происходящего.

Но у научного кода есть дополнительные трудности.

Код может запуститься, выдать конечные числа, пройти сравнение со старой программой — и всё равно реализовывать неподходящую физическую модель. Поэтому обещание «получить код без ошибок» лучше заменить проверяемой целью: перечислить допущения, назвать источники контрольных значений и показать, какие ошибки обнаруживает каждая проверка.

Алгоритм контроля надежности сгенерированного научного кода

Этот алгоритм еще не идеален. Он не гарантирует полного отсутствия ошибок, но делает многие из них обнаружимыми и создает воспроизводимые проверки.

Схема алгоритма надёжного научного кода с помощью ИИ
Схема алгоритма надёжного научного кода с помощью ИИ

Номера соответствуют шагам ниже. Эталонные ответы получают отдельно от проверяемой реализации. После сбоя выясняют его причину: меняют ошибочный блок, постановку или доказанно неверный тест. Подгонка допуска под полученное число проверкой не становится.

  1. Сначала описание задачи.

    Записать уравнение, область применимости, единицы, используемый базис из функций, начальные и граничные условия, искомую величину и допустимую погрешность. Отдельно выписать соглашения о знаках и о нормировках. Попросить ИИ перечислить неоднозначности еще до написания программы.

  2. Нарисовать процесс.

    Разделить расчёт на блоки с понятными входами и выходами: параметры → оператор → решение → наблюдаемая величина. Указать на всех соединениях между блоками используемые размерности, форму массивов и смысл индексов.

  3. Получить опоры независимо от большого кода.

    Вывести простой предел, решить маленькую задачу вручную, проверить размерности, симметрии и знаки. Написать короткую прозрачную эталонную реализацию; оптимизацию делать отдельно. Уже сейчас зафиксировать отложенные контрольные случаи, которые не будут использоваться для выбора формулы и настройки реализации.

  4. Проверять и числа, и символы.

    Компьютерная алгебра проверяет преобразования формул с явно заданными предположениями. Если расчёт использует символ полилогарифма, дополнительно проверять алфавит, вес, интегрируемость и ограничения на символ. Затем отдельно восстанавливать потерянные константы и выбор ветвей функции. Совпавший символ не равен доказательству совпадения полных функций, но это совпадение является необходимым признаком верности проделанных вычислений.

  5. Испытывать код на прочность.

    Менять масштаб, знак, базис и порядок входных данных там, где теория предсказывает результат изменения. Использовать точные аналитические решения и независимые от применяемого вычислительные методы. Очень важно то, что совпадение ответов двух разных чатов с LLM, даже если это разные LLM, независимым методом не считается и по надежности сильно уступает другим видам проверок.

  6. Проводить эксперименты по устойчивости вычислений.

    Раздельно уменьшать шаг сетки, расширять базис, ужесточать квадратуры и повышать разрядность используемых чисел. При этом везде смотреть на сходимость и невязку уравнения. Если вычисления неустойчивы, то доверять им не следует.

  7. Проверять не только научный код, но и сами проверки этого кода.

    Внести известные дефекты и убедиться, что подходящие тесты их ловят. Перепроверить сами эталонные ответы, области применимости и допуски. Одних зелёных тестов недостаточно: для важных свойств стоит показать, какой известный дефект обнаруживает проверка. Это конечный проверяемый опыт, а не требование бесконечно строить тесты для тестов.

  8. Применить отложенные контрольные случаи.

    Проверить заранее выбранные обычные и предельные случаи. Если результаты этого набора использовали для исправления или выбора кандидата, он уже стал частью разработки. Его стоит сохранить для регрессионных проверок — чтобы старые ошибки не вернулись, — а для новой независимой оценки подготовить другие случаи.

    Случайные примеры дополняют, но не заменяют известные ответы.

  9. Сохранить воспроизводимый след.

    Версии среды, исходные данные, команды, параметры, результаты и ограничения вывода. Перед публикацией выполнить инструкции в чистом окружении и передать существенные физические предположения на предметную проверку человеку. ИИ может помочь быстро выписать все эти предположения, но за ним тут нужен контроль.

    Для проверки исследовательской гипотезы можно заранее зафиксировать постановку, критерии оценки и возможные исходы вычислительного эксперимента. Такая предрегистрация помогает отделить исходный план от решений, принятых после просмотра результата; сама по себе она не доказывает правильность расчёта.

Часть этих ступеней реализована ниже: аналитические ориентиры, несколько независимых способов вычисления, размерности, изменение отсечения, намеренные дефекты, контроль порядка сходимости. Экспериментальной проверки модели графена и независимой экспертизы всей новой статьи все эти запуски не заменяют, разумеется. Слова «проверено» всегда должны сопровождаться ответом: чем именно и в каких пределах?

Словарь базовых терминов с примерами

Базис: как хранить одно и то же состояние

В конечномерном линейном пространстве базис — это набор линейно независимых векторов, через который каждый вектор пространства единственным образом выражается линейной комбинацией.

Линейная комбинация — это сумма векторов, умноженных на числа; эти числа — координаты в выбранном базисе. Независимость означает, что ни один базисный вектор нельзя собрать из остальных. Для пространств функций дополнительно нужно оговорить смысл сходимости бесконечных разложений.

Для повторения основ линейной алгебры могу порекомендовать этот мой экспресс-курс.

Вектор u=(3,4) в базисе e_1=(1,0), e_2=(0,1) равен 3e_1+4e_2.

В повернутом ортонормированном базисе e'_1=(1,1)/\sqrt2, e'_2=(-1,1)/\sqrt2 тот же вектор записывается как (7/\sqrt2)e'_1+(1/\sqrt2)e'_2. Изменились координаты, но не сам объект.

Один вектор и его координаты в двух базисах
Один вектор и его координаты в двух базисах

*Зелёная стрелка на обеих панелях одна и та же. В квантовом расчёте вместо двух стрелок могут быть функции: состояния на подрешётках A и B или состояния уровней Ландау. *

Начальное условие: с какого состояния начинаем

Начальные условия задают необходимые данные в выбранный начальный момент времени. Для уравнения первого порядка по времени это само состояние системы; для дифференциальных уравнений второго порядка обычно нужны ещё и скорости. Количество данных зависит от уравнения.

Уравнение dy/d\tau=-y имеет семейство решений y(\tau)=C e^{-\tau}.

Начальное условие y(0)=2 выбирает C=2.

Одна лишь правая часть return -y не говорит решателю, какую из кривых строить.

Семейство решений и выбор начального состояния
Семейство решений и выбор начального состояния

На всех кривых действует один закон изменения. Их различие задаётся значением y(0). Единицы времени выбраны так, что коэффициент затухания равен единице.

Граничное условие: что разрешено на краю области

Граничное условие ограничивает решение на пространственной границе.

Условие Дирихле задаёт значение функции, условие Неймана — её производную по нормали к границе; физический поток может быть пропорционален этой производной с дополнительным коэффициентом.

Периодические условия соединяют противоположные края.

Пусть -u''(x)=1 на 0<x<1.

Двойное интегрирование даёт u=-x^2/2+C_1x+C_2.

При u(0)=u(1)=0 получаем u=x(1-x)/2.

При u(0)=0, u'(1)=0 — уже u=x-x^2/2.

Поэтому менять края массива без изменения постановки нельзя.

Два граничных условия для одного дифференциального уравнения
Два граничных условия для одного дифференциального уравнения

Слева закреплено значение в обоих случаях. Справа в одном случае закреплено значение, в другом — нулевой наклон. Это две разных задачи с разным ответом.

Нормировка: фиксируем масштаб

Нормировка в квантовой механике — это условие, фиксирующее масштаб объекта.

Для волновой функции одного чистого состояния оно обычно имеет вид \int |\psi|^2\,d\Omega=1, где d\Omega — мера интегрирования.

На отрезке [0,1] функция f(x)=x имеет квадрат нормы N=\int_0^1x^2dx=1/3.

Поэтому нормированная амплитуда \psi=f/\sqrt N=\sqrt3x, а плотность вероятности |\psi|^2=3x^2. Амплитуду делят на \sqrt N, плотность — на N.

Нормировка амплитуды и соответствующей плотности вероятности
Нормировка амплитуды и соответствующей плотности вероятности

Площадь под нормированной плотностью равна единице.

Допуск: какое расхождение принимаем

Допуск — выбранный до просмотра результата критерий приемлемого отклонения.

Часто сравнивают вычисленное a с опорным b по правилу

|a-b|\leq {\rm atol}+{\rm rtol}|b|.

Абсолютный допуск {\rm atol} имеет единицы сравниваемой величины, относительный {\rm rtol} безразмерен. При b около нуля нужен абсолютный масштаб.

При {\rm atol}=10^{-8}, {\rm rtol}=10^{-5} и b=2 порог равен 0.00002001. Число 2.00001 проходит тест, 2.0001 — нет.

Абсолютная и относительная части численного допуска
Абсолютная и относительная части численного допуска

Излом показывает смену доминирующего масштаба.

Невязка: подставляем кандидат обратно в уравнение

Для уравнения F(u)=b и кандидата \widehat u определим невязку как r=F(\widehat u)-b. Иногда используют противоположный знак; соглашение надо назвать.

Ошибка решения — e=\widehat u-u_*, где u_* — точный ответ.

Невязку можно посчитать без знания u_*, а ошибку — не всегда.

Для пружины ku=b с k=3 Н/м и b=6 Н точное смещение равно 2 м.

Если программа вернула 2.01 м, ошибка равна 0.01 м, невязка — 0.03 Н.

Здесь r=ke, но это величины разных размерностей.

Ошибка смещения и невязка силы в одном уравнении
Ошибка смещения и невязка силы в одном уравнении

Горизонтальное отклонение относится к смещению, вертикальное — к равновесию сил.

Мантисса и точность представления

Число с плавающей точкой хранится как знак, значащая часть и степень основания:

(-1)^s m\beta^e.

Значащую часть m часто называют мантиссой. Число её разрядов ограничивает относительную точность представления чисел данного формата.

В учебном примере с десятичными числами с четырьмя значащими цифрами 1.23456 округлится до 1.235. Абсолютная ошибка — 0.00044, относительная — около 0.0356\%.

Эта потеря при представлении одного числа появляется еще до решения уравнений.

Округление числа при ограниченной длине мантиссы
Округление числа при ограниченной длине мантиссы

Порядок числа: диапазон отдельно от точности

Порядок e — показатель степени основания \beta.

Записи 1.235\cdot10^{-3}, 1.235\cdot10^0 и 1.235\cdot10^3 имеют одну мантиссу и разные порядки.

Диапазон порядков определяет, насколько большие и маленькие масштабы можно хранить; он не добавляет значащих цифр.

Одна мантисса при трёх разных порядках числа
Одна мантисса при трёх разных порядках числа

Поэтому «64 бита» не означает 64 надёжные десятичные цифры. Кроме хранения числа есть накопление округлений и чувствительность самой задачи.

При сложении чисел разных порядков значащие части выравниваются, а результат округляется до точности формата. Поэтому достаточно малая добавка может потеряться. Это следует отличать от потери относительной точности при вычитании близких приближённых чисел.

Численная устойчивость

Численная устойчивость описывает реакцию алгоритма на малые вычислительные возмущения. Еще одно полезное понятие тут — обратная устойчивость. Она в том, что полученный ответ является точным решением задачи с достаточно мало изменёнными входными данными.

Рассмотрим f(x)=\sqrt{1+x}-1 при малом положительном x.

При прямом вычислении округляется сумма 1+x, а затем из близкого к единице корня вычитается 1. Ошибки округления становятся велики относительно малого результата — возникает потеря значащих цифр при вычитании.

Умножим числитель и знаменатель на сопряжённое выражение:

f(x)=\frac{(\sqrt{1+x}-1)(\sqrt{1+x}+1)}{\sqrt{1+x}+1}=\frac{x}{\sqrt{1+x}+1}.

При x=10^{-16} двойной float в нашем запуске дал для первой записи нуль, для второй — 5\cdot10^{-17}.

Здесь сохранить точность нам помогло само изменение способа вычислений.

Потеря цифр при вычитании и устойчивая эквивалентная формула
Потеря цифр при вычитании и устойчивая эквивалентная формула

На левой логарифмической оси нулевой результат показан на условном нижнем уровне. Справа — относительное расхождение со второй записью.

Обусловленность: насколько чувствительна сама задача

Обусловленность характеризует изменение точного ответа при изменении входа; она не является свойством алгоритма. Локальное число относительной обусловленности скалярной функции при ненулевых x,f(x) можно записать как \kappa=|xf'(x)/f(x)|.

Если вычитаются измеренные a=1.000001 и b=1, разность равна 10^{-6}.

Изменение первого числа до 1.000002 — примерно одна миллионная его величины — удваивает ответ.

Чувствительность маленькой разности к изменению входного числа
Чувствительность маленькой разности к изменению входного числа

Здесь числа сравниваются в точной десятичной арифметике.

Бюджет ошибок: что ещё осталось неизвестным

Бюджетом ошибок называют список источников ошибок вместе с оценками, единицами, предположениями и способами уменьшения. Он включает в себя неопределённость параметров, дискретизацию, отсечение суммы, квадратуру, округление и ограничения модели.

Дискретизация заменяет непрерывную задачу конечной, например сеткой.

Отсечение заменяет бесконечную сумму или область конечной.

Квадратура — численный способ вычислить интеграл.

Ошибка модели возникает, когда уравнение опускает существенный физический эффект.

Бюджет ошибок с отдельной проверкой каждого источника
Бюджет ошибок с отдельной проверкой каждого источника

*В сумме по уровням Ландау отдельно друг от друга для контроля изменяют последний уровень, точность интеграла и разрядность. *

Почему неопределённости иногда складываются по квадратам

Различим ясно друг от друга погрешность и неопределённость.

Погрешность — отклонение от опорного значения; неопределённость характеризует неполноту знания о величине.

Стандартную неопределённость представляют масштабом стандартного отклонения в принятой вероятностной модели. Важно, что неизвестную ошибку программы нельзя объявить случайной только потому, что мы не знаем её знак.

Пусть суммарная погрешность состоит из случайных вкладов

\epsilon=\epsilon_1+\epsilon_2.

Обозначим их средние как m_i=\mathbb E[\epsilon_i], центрированные отклонения — как \delta_i=\epsilon_i-m_i.

Символ \mathbb E означает математическое ожидание — среднее с учётом вероятностей. Его можно понимать как среднее арифметическое от всех значений случайной величины при очень большом количестве повторений опыта.

Дисперсия — это средний квадрат отклонения от среднего:

\operatorname{Var}(\epsilon)=\mathbb E[(\delta_1+\delta_2)^2]=\mathbb E[\delta_1^2]+2\mathbb E[\delta_1\delta_2]+\mathbb E[\delta_2^2].

Ковариация является смешанным объектом:

\operatorname{Cov}(\epsilon_1,\epsilon_2)=\mathbb E[\delta_1\delta_2].

Для независимых величин с конечными вторыми моментами ковариация равна нулю.

Обратное утверждение «некоррелированность означает независимость» в общем случае неверно. При нулевой ковариации получаем

\sigma_{\rm sum}=\sqrt{\sigma_1^2+\sigma_2^2}.

Это и называют тем самым «сложением ошибок по квадратам»: складывают дисперсии, а затем извлекают квадратный корень, возвращаясь к исходным единицам.

Для независимых вкладов со стандартными отклонениями 0.03 и 0.04 в одних единицах ответ равен 0.05. Их можно понимать также и как характерный масштаб разброса.

Общий случайный источник может сделать вклады коррелированными.

Если \epsilon_1=0.03Z и \epsilon_2=0.04Z для одной величины Z с нулевым средним и единичной дисперсией, суммарное стандартное отклонение равно 0.07.

Суммарный разброс независимых и полностью скоррелированных ошибок
Суммарный разброс независимых и полностью скоррелированных ошибок

На рисунке 200 тысяч случайных пар. Точные значения 0.05 и 0.07 выведены выше.

Для y=f(x_1,\ldots,x_n) сначала линейно приближают изменение:

\delta y\approx\sum_i c_i\delta x_i,

где c_i=\partial f/\partial x_i — коэффициенты чувствительности в опорной точке.

Тогда

u_y^2\approx\sum_i c_i^2u_i^2+2\sum_{i<j}c_ic_j\,\operatorname{Cov}(x_i,x_j).

В линейных моделях эта формула является точной. А в нелинейных еще нужно обосновать достаточность первого порядка. Подробнее можно почитать в руководстве NIST.

Рассмотрим другую ситуацию. Пусть нам известны абсолютные ограничения на значения величин

|e_1|\leq a, |e_2|\leq b.

Без вероятностных предположений надежной границей можно считать лишь саму сумму

|e_1+e_2|\leq a+b.

Например, если в ошибках смещение постоянное, то оно не исчезает при повторении опыта.

Ошибки дискретизации и отсечения часто являются детерминированными и могут быть связаны. Без обоснованной вероятностной модели складывать их по квадратам нельзя. Если для отдельных вкладов известны абсолютные границы, их сумма даёт консервативную границу общей ошибки.

Не все проверки одинаково важны. Они разные по значимости и выполняют разные функции.

Запуск отвечает на вопрос «выполнилась ли программа».

Тест с точным ответом проверяет конкретный случай.

Сходимость проверяет приближение к пределу в выбранном режиме.

Верификация — правильно ли решена математическая задача.

Валидация — подходит ли модель для описания данного явления и поставленной цели.

Точное решение уравнения без трения не подтверждает его пригодность для маятника с сильным затуханием.

Карта источников ошибок и соответствующих проверок
Карта источников ошибок и соответствующих проверок

Этот подход вырос не только из моего диплома

Здесь в статье соединены несколько разных задач: разработка научного кода, проверка правильности используемых численных методов, проверка проверок (тесты для тестов) и само тестирование кода, сгенерированного большими языковыми моделями. У них разная доказательная сила. Рекомендации сообществ, систематические обзоры, эксперименты на бенчмарках и свежие препринты отвечают на разные вопросы.

Ниже приведен тематический обзор с ответом на вопрос «что именно берём в наш алгоритм».

1. Воспроизводимый расчёт начинается с организации работы. Wilson и соавторы, Best Practices for Scientific Computing (2014) рекомендуют небольшие проверяемые изменения, контроль версий, автоматические проверки и просмотр кода коллегами. Good Enough Practices in Scientific Computing (2017) переводит эти идеи в практические рекомендации, как именно правильно организовать научную работу. У нас из них следуют неизменяемый архив диплома и отдельные новые скрипты, которые тестируются независимо друг от друга.

2. У научного кода может не быть готового ответа для сравнения. Тут Kanewala и Bieman, Testing Scientific Software: A Systematic Literature Review рассматривают эту трудность вместе с прочими особенностями научного программирования. «Оракул теста» — источник решения, который дает ответ на вопрос, считать ли результат правильным. Оракулом теста может быть формула, независимый расчёт, сохранённый эталон или какое-то проверяемое другим способом свойство (например, экспериментально). Когда полного оракула нет, один удачный запуск особенно мало говорит. Более поздняя работа Testing Research Software: A Survey (2022) также подчёркивает сложность предметной проверки и необходимость выделять на неё реальные усилия. Для нас вывод — отдельно записывать происхождение каждого ожидаемого числа.

3. Декомпозиция должна помогать менять реализацию без смены физического смысла. Классическая работа Parnas, On the Criteria To Be Used in Decomposing Systems into Modules (1972) обсуждает критерии выделения программных блоков и сокрытие проектных решений за интерфейсом. Простое разрезание программы по порядку выполнения ещё не обеспечивает хорошую модульность. В нашем варианте, например, блок «интеграл» должен иметь проверки по входам, выходам и точности: квадратуру можно заменить другой, не изменяя физический смысл происходящего.

4. Предсказуемые связи между ответами дополняют эталонные числа. Обзор Segura и соавторов (2016) посвящён метаморфному тестированию. Его суть заключается в том, чтобы преобразовать вход так, чтобы теория предсказывала изменение выхода. В нашем примере ниже для уровней графена уменьшение B вчетверо должно уменьшить ненулевую энергию вдвое при фиксированных остальных параметрах. Но неверный общий множитель пройдёт этот тест, так что он является необходимым, но никак не достаточным.

5. Известное решение можно сконструировать самому. Salari и Knupp, Sandia (2000) описывают метод заранее изготовленных решений: выбрать гладкую функцию, вычислить для неё правую часть и проверить решатель. Метод позволяет выявить неверные коэффициенты и порядок сходимости. Однако он не доказывает, что исходная физическая модель соответствует эксперименту. Ниже это тоже реализуется для проверки правильности работы кода.

6. Тесты испытывают известными дефектами. Здесь Jia и Harman, An Analysis and Survey of the Development of Mutation Testing (2011) рассматривают мутационное тестирование: небольшие изменения программы показывают, какие отклонения тестовый набор способен обнаружить. При этом очень важно отличать реальный дефект от преобразования, не изменяющего поведение, и объяснять необнаруженные изменения. Наши пять мутаций ниже подобраны по физическому смыслу.

7. Для кода от ИИ качество тестов меняет оценку качества модели. В EvalPlus (2023) расширение тестовых наборов обнаруживало ошибки, пропущенные исходными проверками сгенерированных решений. Это исследование функциональной корректности программ на выбранном бенчмарке. Его урок переносится в эту статью в точности: тестируем границы, особые случаи и свойства результата.

8. Научные бенчмарки тоже нуждаются в предметном аудите. SciCode (2024) использует исследовательские задачи, составленные учёными. Авторы препринта SciCode-Verified (2026) сообщают об ошибках эталонов, неоднозначных требованиях и неподходящих допусках в исходной оценке. Любое исправление проверяющего должно иметь отдельное математическое обоснование и соответствующую запись в журнале изменений.

9. Автоматическая проверка методологии — полезный дополнительный слой. В препринте scicode-lint (2026) исследуется поиск методологических дефектов научного Python-кода с помощью шаблонов, подготовленных языковыми моделями. Среди примеров — утечка данных при предобработке и ошибки организации машинного обучения. Авторы отдельно оценивают контролируемые примеры и реальные материалы; качество зависит от набора и категории дефектов. Такой инструмент уместен как помощник ревью, но сам по себе не проверяет физику и не устраняет ложные срабатывания.

10. Воспроизводимость кода и воспроизводимость диалога с ИИ — разные задачи. В редакционной статье Nature Computational Science, Reproducibility in the Era of Large Language Models (2026) подчёркивается потребность в более ясных стандартах отчётности и общих практиках воспроизводимости. В нашем алгоритме эту общую задачу реализуем фиксацией модели и версии, запросов, настроек и программной среды. Эти сведения дополняем окончательным кодом, данными и командами запуска. История получения кода помогает понять происхождение решений. Из этого обзора я беру не обещание «ИИ больше не ошибётся», а конструкцию работы:

явная постановка → небольшие заменяемые блоки → независимо обоснованные проверки → проверка самих тестов → воспроизводимый пакет.

Процессная диаграмма позволяет все это изобразить наглядно.

Ошибка в дипломе, пережившая защиту

В разделе 9 моего диплома, на странице 23, единица энергии записана как ħ/v_Fl. Здесь l — магнитная длина; ниже обозначим её l_B, как в остальной статье.

Если понимать запись как \hbar/(v_Fl_B), получается размерность массы. Правильный энергетический масштаб для рассматриваемого гамильтониана — \hbar v_F/l_B.

Ошибка в записи единицы энергии в дипломе и проверка размерностей
Ошибка в записи единицы энергии в дипломе и проверка размерностей

Проверка помещается в одну строку: [\hbar]=\mathrm{kg\,m^2/s}, [v_F]=\mathrm{m/s}, [l_B]=\mathrm m.

Поэтому деление \hbar на v_Fl_B оставляет килограммы, а умножение на v_F/l_B даёт джоули. Если иначе расставить скобки в исходной записи и прочитать её как (\hbar/v_F)l_B, размерность энергии тоже не получится.

Ошибка осталась в тексте защищённого диплома, а восстановление расчёта с помощью ИИ помогло её заметить.

Чтобы контролировать подобные ошибки, формулы необходимо превратить в явные определения, единицы и исполняемые проверки.

Результат можно предъявить читателю вместе с коротким обоснованием. А только лишь по сохранившемуся файлу нельзя точно установить, что именно проверял каждый преподаватель и обсуждалась ли эта запись на защите диплома.

Важно точно назвать находку: доказана ошибка в записи единицы энергии.

Она могла быть опечаткой и сама по себе не доказывает, что все численные результаты диплома неверны. После обнаружения ошибки размерностную проверку можно закрепить в коде. Запишем размерность тройкой степеней при килограмме, метре и секунде. При умножении тройки складываются, при делении — вычитаются:

import numpy as np

hbar_dim = np.array([1, 2, -1])  # kg m^2 s^-1
v_dim = np.array([0, 1, -1])     # m s^-1
l_dim = np.array([0, 1, 0])      # m
energy_dim = np.array([1, 2, -2])
written = hbar_dim - v_dim - l_dim
corrected = hbar_dim + v_dim - l_dim
assert not np.array_equal(written, energy_dim)
assert np.array_equal(corrected, energy_dim)
print(written, corrected)  # [1 0 0] и [1 2 -2]

Этот тест фиксирует уже разобранную ошибку. Сильная сторона такого подхода — в связи между исходной записью, физическим смыслом и воспроизводимой проверкой.

Что именно передавать ИИ

Начинать нужно с маленькой спецификации: формула, единицы, допустимые аргументы, соглашения о знаках, ожидаемые пределы. Затем просить отдельную вычислительную функцию. Графики и большую таблицу результатов имеет смысл строить после её проверки.

Для простейшего состояния радиальная часть волновой функции равна

f_{00}(r)=\frac{e^{-r^2/4}}{\sqrt{2\pi}}.

Здесь r\geq0 — безразмерный радиус. Сейчас достаточно геометрии: тонкое кольцо имеет площадь 2\pi r\,dr. Значит, радиальная плотность вероятности

\rho(r)=2\pi r|f_{00}(r)|^2=r e^{-r^2/2}.

Ожидаемые значения можно вывести до написания программы. Замена u=r^2/2 даёт r\,dr=du, поэтому

\int_0^\infty\rho(r)\,dr=\int_0^\infty e^{-u}du=1,\qquad\int_0^\infty r^2\rho(r)\,dr=2\int_0^\infty u e^{-u}du=2.

Ниже пример задания для повторения опыта.

Реализовать на Python функцию f00 ® по приведённой формуле. Радиус должен быть безразмерным и неотрицательным. Функция должна принимать число или массив NumPy. Отдельно вычислить численной квадратурой нормировку с мерой 2πr dr и средний квадрат радиуса. Сравнить с аналитически выведенными значениями 1 и 2. Не менять формулу и не перенормировать результат ради прохождения проверок. При расхождении вывести рассчитанное значение и оценить, какой именно шаг требует проверки.

Саму проверку можно прочитать целиком:

import numpy as np
from scipy.integrate import quad

def f00(r):
    if np.iscomplexobj(r):
        raise ValueError("r must be real")
    r = np.asarray(r, dtype=float)
    if np.any(~np.isfinite(r)) or np.any(r < 0):
        raise ValueError("r must be finite and nonnegative")
    return np.exp(-r*r/4) / np.sqrt(2*np.pi)

def rho(r):
    return 2*np.pi*r*abs(f00(r))**2

norm = quad(rho, 0, np.inf, epsabs=1e-11, epsrel=1e-11)[0]
r2 = quad(lambda r: r*r*rho(r), 0, np.inf,
          epsabs=1e-11, epsrel=1e-11)[0]
assert abs(norm-1) < 1e-9
assert abs(r2-2) < 1e-9
print(norm, r2)  # оба интеграла вычисляются, а не подставляются из ответа

Проверки тоже можно поручать ИИ, как это делалось при подготовке материала.

Но источник ожидаемого ответа должен быть понятен отдельно от проверяемой функции.

В этом примере есть короткий аналитический вывод и численная квадратура. Скопировать формулу из реализации в тест и получить совпадение было бы гораздо менее информативно. Согласие двух моделей ИИ тоже не заменяет такого обоснования, так как они могут повторить одну и ту же предпосылку.

Проверяем не только программу, но и проверки

Выше из аналитически посчитанного интеграла удалось получить два требования к радиальной плотности \rho_0(r)=r e^{-r^2/2}: нормировка равна 1, средний квадрат радиуса равен 2. Эти числа выведены до проверки кода.

Теперь намеренно изменим ширину и одновременно сохраним нормировку:

\rho_1(r)=2r e^{-r^2},\qquad \int_0^\infty\rho_1(r)dr=1,\qquad \int_0^\infty r^2\rho_1(r)dr=1.

Откуда последний результат?

Замена u=r^2 даёт du=2r\,dr:

первый интеграл превращается в \int_0^\infty e^{-u}du=1,

второй — в \int_0^\infty u e^{-u}du=1.

Кривая уже другая, но тест одной нормировки останется зелёным.

Проверка среднего квадрата радиуса обнаружит подмену.

Один известный дефект и два разных теста
Один известный дефект и два разных теста

Процедура состоит из пяти действий:

  1. сохранить контрольную реализацию;

  2. внести один понятный дефект в отдельную копию;

  3. запустить тот же набор тестов;

  4. объяснить, какой тест и почему должен сработать;

  5. сопоставить ожидание с фактическим результатом.

Если дефект не замечен, нужно провести более чувствительный тест.

Мутационный тест не доказывает отсутствие других ошибок сам по себе.

В нашем опыте различие доказывается явными формулами.

Теперь расширим опыт до потери якобиана, неверного масштаба энергии, знака экранирования и контроля отсечения. Такие намеренные изменения называют мутациями кода; у каждого дефекта будет своё обоснование.

Здесь известна природа каждой модификации, поэтому можно проверить, замечает ли её выбранный тест.

Исходный файл 06_graphene.py остаётся неизменным: эксперимент создаёт отдельные варианты функций.

Гладкие кривые для исходных формул и намеренно изменённых вариантов
Гладкие кривые для исходных формул и намеренно изменённых вариантов

На верхней панели потерянный множитель r и неверная ширина дают гладкие кривые.

На средней панели ошибочная линейная зависимость энергии от поля специально совпадает с правильной в одной точке, при 10 Тл.

На нижней панели и 1/(1+s), и ошибочное 1/(1-s) конечны на выбранном отрезке 0\leq s\leq0.8.

Само отсутствие NaN здесь ничего не разоблачает.

NaN означает not a number: специальное машинное значение для неопределённого численного результата, например при некоторых недопустимых операциях.

Его отсутствие не проверяет физику формулы.

Для рисунка с полем используется такая неправильная формула: E_{\rm bad}(B)=E_1(10\,\mathrm{T})B/(10\,\mathrm{T}). У неё даже правильная размерность.

Но уменьшение поля с 10 до 2.5 Тл уменьшает энергию вчетверо, тогда как исходная модель требует уменьшения вдвое.

Проверка связи между двумя запусками обнаруживает то, что пропускает одна контрольная точка. Полный результат запуска 12_verify_ai.py выглядит так:

Какие из шести проверок обнаружили пять намеренных модификаций
Какие из шести проверок обнаружили пять намеренных модификаций

В каждой строке меняется только один указанный аспект относительно контрольной версии. Зелёная ячейка означает выполнение конкретного условия, красная — найденное отклонение. Набор обнаружил все пять введённых модификаций, однако отдельные столбцы пропускают почти все чужие ошибки. Число «пять из пяти» описывает этот специально построенный опыт; оценивать по нему надёжность ИИ на других задачах нельзя.

Якобиан — множитель изменения меры при замене координат. Без якобиана интеграл нормировки равен примерно 1.253314137 вместо верного значения 1. При неправильной, но перенормированной ширине нормировка остаётся единицей, зато средний квадрат радиуса становится 1 вместо 2. Наконец, замена знаменателя RPA на 1-s даёт при s=0.8 усиление в 5 раз вместо ослабления до 1/1.8\approx0.556.

Последняя строка матрицы устроена иначе. В ней проверяется требование к усечению суммы. При q=1 увеличение границы с 15 до 30 меняет ответ примерно на 2.027%, если делить разность на модуль результата при 30. Это не проходит выбранный порог 1%.

Переход с 60 до 120 меняет ответ примерно на 0.981% и проходит.

Здесь не доказана однопроцентная точность относительно бесконечной суммы: проверено только изменение при одном удвоении границы и в одной точке. Совпадение с несколькими старыми числами или зелёная ячейка не отменяют проверки всего нужного диапазона.

Когда малая невязка ничего не обещает о точности ответа

Даже правильно составленное уравнение может быть чувствительным.

Возьмём, например, безразмерную систему Au=b:

A=\begin{pmatrix}1&0\\0&\epsilon\end{pmatrix},\qquad b=\begin{pmatrix}1\\0\end{pmatrix},\qquad\epsilon=10^{-8}.

Точное решение здесь: u=(1,0)^T.

Кандидат \widehat u=(1,1)^T имеет ошибку нормы 1. В этом примере и на рисунке используем соглашение r=b-A\widehat u, противоположное знаку из начального словаря: r=(0,-\epsilon)^T, а его норма всего 10^{-8}. Выбор знака не меняет норму и приведённую ниже оценку ошибки.

Второе уравнение слишком слабо реагирует на ошибку во второй компоненте.

Невязка десять в минус восьмой при ошибке решения, равной единице
Невязка десять в минус восьмой при ошибке решения, равной единице

Из A(\widehat u-u)=-r следует \widehat u-u=-A^{-1}r.

Для обратимой матрицы, ненулевых b,u и согласованных норм:

\frac{\|\widehat u-u\|}{\|u\|}\leq\kappa(A)\frac{\|r\|}{\|b\|},\qquad\kappa(A)=\|A\|\,\|A^{-1}\|.

Здесь норма измеряет размер вектора; на рисунке она евклидова, то есть квадратный корень из суммы квадратов компонент. Соответствующая норма матрицы — максимальный коэффициент растяжения вектора.

Число обусловленности \kappa характеризует чувствительность задачи; в примере оно равно 10^8. Малую невязку нужно оценивать вместе с ним и с масштабами величин.

Для физически разных единиц компонент сначала выбирают осмысленное масштабирование. Связь обусловленности и ошибок обсуждается в руководстве LAPACK. Построенный нами контрпример можно непосредственно проверить переумножением матриц.

Один реальный сбой, которого не видно в формулах

При проверке этой статьи обнаружилось несоответствие между инструкцией и сгенерированной программой: читателю предлагалось выполнить опыт с --order 40, а 10_laboratory.py разрешал максимум 30. Математическое объяснение ряда могло быть правильным, но команда завершалась до вычислений. В приведённой ниже версии этот конфликт устранён: для опыта с геометрической цепочкой порядок 40 допустим.

Это отдельный вид проверки: исполнить инструкцию целиком, шаг за шагом.

Такой вид проверки дополняет тесты формул, аргументов и численной устойчивости. Его может осуществить и другой ИИ, но лучше проделать и человеку тоже.

Как читать проверки дальше

Сначала разбираем физический смысл каждого блока и выводим формулы.

Затем проверяем маленькие реализации, их соединения и численные приближения.

В конце соберём эту работу в процессную схему и выполним семь экспериментов.

При переносе своего расчёта необходимо заполнять таблицу: утверждение → проверка → результат → оставшееся ограничение.

1. Что я пытался посчитать в дипломе

Схема одноатомного слоя графена
Схема одноатомного слоя графена

Графен — это один слой атомов углерода с сотовой структурой. Нас будут интересовать электроны, которые способны свободно передвигаться по этому слою.

Две подрешётки и примитивная ячейка идеального графена
Две подрешётки и примитивная ячейка идеального графена

Почему «соты»? У каждого атома три ближайших соседа, а связи образуют шестиугольники. Цвета обозначают два типа положения в решётке — A и B, а не два химических элемента: в обоих случаях это углерод.

Удобно взять треугольную решётку переносов и базис из двух атомов.

Заштрихованный параллелограмм — одна примитивная ячейка, а расстояние между ближайшими атомами около 0.142 нм.

Решётка Браве состоит из точек \mathbf R=m\mathbf a_1+n\mathbf a_2, где m,n — целые числа. Перенос на любой такой вектор воспроизводит весь набор точек.

Примитивная ячейка — это участок решетки, копии которого этими переносами заполняют всю плоскость. Чтобы получить атомные соты, к каждой точке добавляем два положения \mathbf r_A и \mathbf r_B. Эта пара называется атомным базисом.

Состояние, оператор и уравнение Шрёдингера

Волновая функция — это представление квантового состояния в выбранном базисе.

В координатном представлении одночастичной модели это комплексная функция \psi(x,\tau); в двухмерном пространстве состояний — столбец из двух комплексных амплитуд. Буквой \tau в этом примере обозначим время.

Модуль в квадрате превращает амплитуду в вероятность: для дискретного ортонормированного базиса P_n=|c_n|^2, для координаты P(x\in[a,b])=\int_a^b|\psi(x)|^2dx.

Фаза комплексной амплитуды нужна для расчета интерференции: вклады сначала складываются как комплексные числа, а потом берётся квадрат модуля.

Поэтому состояния (1,1)/\sqrt2 и (1,-1)/\sqrt2 имеют одинаковые вероятности A/B, но различаются при измерениях в другом базисе.

Линейный оператор переводит состояние в другое выражение и сохраняет линейные комбинации: A(\alpha\psi+\beta\chi)=\alpha A\psi+\beta A\chi.

В конечном базисе это матрица. В пространстве функций это может быть дифференцирование: например, импульс p=-i\hbar\partial_x при надлежащей области определения.

Наблюдаемая — физическая величина, для которой модель задаёт правила измерения. В рассматриваемой стандартной схеме ей соответствует самосопряжённый оператор. Для конечной матрицы это означает A^\dagger=A: транспонирование вместе с комплексным сопряжением не меняет матрицу. Для дифференциального оператора важна также область определения, включая граничные условия.

Если A|a_n\rangle=a_n|a_n\rangle, то a_n — возможный результат идеального измерения, а вероятность для нормированного состояния |\psi\rangle равна |\langle a_n|\psi\rangle|^2 в невырожденном дискретном случае. При вырождении суммируют вероятности внутри соответствующего подпространства. Среднее \langle A\rangle=\langle\psi|A|\psi\rangle относится к статистике повторений, а не обязательно к одному исходу. Например, оператор с исходами -1 и +1 может иметь среднее 0.

Гамильтониан H — оператор энергии, который также задаёт изменение состояния во времени. Для замкнутой системы уравнение Шрёдингера имеет вид

i\hbar\frac{\partial\psi}{\partial\tau}=H\psi.

Слева скорость изменения амплитуд, справа действие оператора энергии на текущее состояние. Для обычной частицы массы m в потенциале U(x) получаем

i\hbar\partial_\tau\psi=\left[-\frac{\hbar^2}{2m}\partial_x^2+U(x)\right]\psi.

Чтобы превратить это в конкретную задачу, задают U, область, граничные условия и начальную функцию. Для не зависящего от времени H ищут стационарную компоненту \psi(x,\tau)=\phi(x)e^{-iE\tau/\hbar}. Подстановка даёт H\phi=E\phi.

Состояние, гамильтониан, эволюция и измеряемая величина
Состояние, гамильтониан, эволюция и измеряемая величина

От двух атомных состояний к матрице перескока

Оставим два ортонормированных локализованных состояния |A\rangle, |B\rangle. Общее состояние |\psi\rangle=c_A|A\rangle+c_B|B\rangle хранится как столбец c=(c_A,c_B)^T. Элемент матрицы H_{ij}=\langle i|H|j\rangle отвечает на конкретный вопрос: какая компонента вдоль i получается при действии H на состояние j.

Для одинаковых узлов диагональные энергии одинаковы; вычтем их общее значение, выбрав нуль энергии. Пусть связь между узлами вещественна, с соглашением H_{AB}=H_{BA}=-t,

t>0. Тогда

H=\begin{pmatrix}0&-t\\-t&0\end{pmatrix},\qquad i\hbar\frac{d}{d\tau}\begin{pmatrix}c_A\\c_B\end{pmatrix}=\begin{pmatrix}-t c_B\\-t c_A\end{pmatrix}.

Перескок здесь означает внедиагональную связь амплитуд.

Для c(0)=(1,0)^T решение c_A=\cos(t\tau/\hbar), c_B=i\sin(t\tau/\hbar).

Прямая подстановка проверяет обе строки уравнения.

Вероятности \cos^2(t\tau/\hbar) и \sin^2(t\tau/\hbar) в сумме дают 1.

Характерный масштаб времени — \hbar/t; сама величина t имеет размерность энергии.

Матрица двух состояний и рассчитанная эволюция вероятностей
Матрица двух состояний и рассчитанная эволюция вероятностей

Слева строка обозначает выходную компоненту, столбец — входную. Справа эволюция вычислена из этого гамильтониана и проверена матричной экспонентой. Стационарные комбинации (|A\rangle+|B\rangle)/\sqrt2 и (|A\rangle-|B\rangle)/\sqrt2 имеют энергии -t и +t. В периодической решётке множество связанных узлов даёт энергетические зоны.

Перескок между состояниями и две энергетические зоны
Перескок между состояниями и две энергетические зоны

Слева — учебная двухузловая модель: \tau обозначает время, t — энергию перескока. Справа — отдельный пример спектра со щелью, не измеренный спектр графена. Энергетическая зона — диапазон состояний, возникающий из-за периодической структуры; при изменении импульса меняется энергия внутри зоны. В идеальном собственном полупроводнике при нулевой температуре валентная зона заполнена, зона проводимости пуста, а между ними нет разрешённых одночастичных энергий: это щель E_g. У идеального нейтрального графена две соответствующие ветви соприкасаются в точках Дирака, поэтому такой щели нет.

На картинке уже сделано следующее приближение: идеальный бесконечный плоский кристалл, без дефектов, краёв, деформаций и тепловых колебаний.

Следующий шаг для упрощения в том, чтобы оставить только существенные электронные состояния и перескоки между ближайшими соседями. Ещё один шаг — разложить получившийся спектр около двух особых точек K и K' в ряд. Поэтому наличие атомных сот и наличие точного уравнения Дирака на всех энергиях не одно и то же.

Для более подробного знакомства с контекстом приведу материал из диссертации Алексея Алексеевича Соколика «Коллективные электронные явления в графене» (2010), страница с авторефератом.

Для свободного медленного электрона энергия движения равна квадрату импульса, делённому на удвоенную массу. В графене вблизи особых точек электронной структуры возникает другая, приближённо линейная зависимость:

E_{\pm}(\mathbf{p})=\pm v_F\left\lvert\mathbf{p}\right\rvert.

Здесь \mathbf p=\hbar(\mathbf k-\mathbf K) — квазиимпульс, отсчитанный от соответствующей точки Дирака (для второй долины вместо \mathbf K берут \mathbf K'). Квазиимпульс — метка состояния в периодическом кристалле, заданная с точностью до \hbar-кратного вектора обратной решётки; обратная решётка состоит из векторов \mathbf G, для которых e^{i\mathbf G\cdot\mathbf R}=1 при любом переносе решётки \mathbf R.

Далее в низкоэнергетическом уравнении для огибающей слово «импульс» используется в этом эффективном смысле. Величина v_F — скорость порядка миллиона метров в секунду, а знаки обозначают две ветви спектра. Это низкоэнергетическое приближение: на произвольно больших энергиях продолжать две прямые нельзя.

Связь E(\mathbf p) называется законом дисперсии.

Это употребление слова не связано со статистической дисперсией случайной величины.

Когда ниже говорится о параболической зоне, имеется в виду зависимость около её края вида E=E_{\rm edge}+p^2/(2m_*), где m_* — эффективная масса, определяемая кривизной спектра.

Конус Дирака и отличие линейного приближения от модели ближайших соседей
Конус Дирака и отличие линейного приближения от модели ближайших соседей

Слева энергия изображена над двумерной плоскостью импульсов: верхняя и нижняя ветви образуют два конуса. Справа — сечение около точки K. Сплошная линия получена из модели перескоков между ближайшими атомами, пунктир — её линейное разложение. Здесь d — расстояние между соседними атомами, t — энергия перескока. При удалении от вершины кривые расходятся: «релятивистская вселенная» имеет область применимости.

Теперь применим введённые понятия к низкоэнергетическому электронному состоянию графена.

Почему появляется формула, похожая на релятивистскую?

Сотовая решётка состоит из двух подрешёток, A и B. Для описания состояния нужны две амплитуды: насколько оно связано с A и с B. Поэтому волновая функция становится столбцом из двух компонент, а оператор энергии — матрицей:

H_0=v_F\begin{pmatrix}0&p_x-ip_y\\p_x+ip_y&0\end{pmatrix}.

Чтобы найти допустимые энергии, нужно решить задачу на собственные значения: H_0\psi=E\psi. Это поиск состояний, которые оператор лишь умножает на число E, сохраняя их направление в пространстве состояний; это число и есть соответствующая энергия. Определитель матрицы H_0-EI даёт

E^2-v_F^2(p_x^2+p_y^2)=0,

откуда и получаются две ветви.

Двухкомпонентность связана с подрешётками; настоящий спин электрона и две долины графена являются тут дополнительными степенями свободы. Смешивать эти три понятия друг с другом нельзя.

Спин — собственный квантовый угловой момент, а не вращение маленького шарика. У электрона есть две проекции спина на выбранную ось.

Долина — окрестность одной из двух неэквивалентных точек K,K' в пространстве импульсов.

Псевдоспином здесь называют двухкомпонентную степень свободы A/B.

Электрон — фермион. В квантовой механике принцип Паули запрещает двум одинаковым фермионам занимать одно и то же полное одночастичное состояние. Состояния с разным спином или долиной уже различны.

Бозоны, например фотоны, такого запрета не имеют. Эти правила заполнения понадобятся, когда будем решать, какие электронные переходы разрешены.

Маленькая вселенная на лабораторном столе

Плоская волна имеет вид e^{i(\mathbf k\cdot\mathbf r-\omega t)}: при сдвиге координаты меняется её фаза, а модуль остаётся постоянным. Импульс такой компоненты равен \hbar\mathbf k. Одна волна на бесконечной плоскости не локализует частицу. Волновой пакет — суперпозиция волн с разными \mathbf k: их фазы могут усиливать друг друга в одном месте и подавлять в другом.

Плоская волна, локализованный пакет и ширина спектра
Плоская волна, локализованный пакет и ширина спектра

На рисунке \psi(x)=e^{-x^2/(2\ell^2)}e^{ik_0x}: слева показана вещественная часть, справа — модуль её Фурье-образа с максимумом, приведённым к единице. Ширина по k пропорциональна 1/\ell. Вертикальные масштабы выбраны для наглядности; это не сравнение нормированных вероятностей разных пакетов.

Преобразование Фурье раскладывает профиль по плоским волнам:

\psi(x)=\int dk\,\widetilde\psi(k)e^{ikx}/(2\pi).

Вместо значения в каждой точке можно хранить амплитуду каждой пространственной частоты. Производная действует на одну волну особенно просто: \partial_x e^{ikx}=ik e^{ikx}.

Это и объясняет, почему оператор импульса удобно заменять числом в Фурье-базисе.

До сих пор импульс был числом для отдельной плоской волны. Чтобы описать произвольный волновой пакет, заменим его оператором p_j=-i\hbar\partial_j. Получим уравнение эволюции двухкомпонентной функции \Psi=(\psi_A,\psi_B)^T:

i\hbar\frac{\partial\Psi}{\partial t}=-i\hbar v_F\begin{pmatrix}0&\partial_x-i\partial_y\\\partial_x+i\partial_y&0\end{pmatrix}\Psi.

Это уравнение первого порядка и по времени, и по пространственным координатам. В обычном уравнении Шрёдингера для свободного массивного электрона справа стоит оператор второго порядка, -\hbar^2\nabla^2/(2m). Уравнение Дирака в вакууме имеет структуру

i\hbar\partial_t\Psi_D=(-i\hbar c\,\boldsymbol\alpha\cdot\nabla+\beta mc^2)\Psi_D.

Сходство видно: матрицы умножаются на первые производные. В идеальной низкоэнергетической модели графена нет массового члена, пространство двумерно, а место c в кинематике носителей занимает v_F\approx10^6 м/с, примерно в 300 раз меньше скорости света. Матрицы подрешёток играют роль, аналогичную дираковским матрицам, но графеновый псевдоспин нельзя отождествлять с настоящим спином электрона.

Лаборатория из серии The Ricks Must Be Crazy
Лаборатория из серии The Ricks Must Be Crazy

*Кадр из «Рика и Морти», The Ricks Must Be Crazy, сезон 2, серия 6 (Adult Swim); источник изображения. У Рика — вселенная в батарейке, у физиков — диракоподобные квазичастицы в графене. *

В этой аналогии вакуумом является выбранное исходное состояние без возбуждений.

Квазичастица — возбуждение сложной системы, которое в некотором режиме можно описывать как отдельный объект с энергией и импульсом.

Электронные и дырочные квазичастицы материала определяются свойствами среды.

Помню, Лозовик любил говорить: «Вакуум — это полупроводник». Он имел в виду, что свойства физического вакуума в современной физике качественно по многим параметрам совпадают со свойствами полупроводников, а пробой вакуума и полупроводника описываются примерно одинаково с математической точки зрения.

Получается, что графен похож на параллельную вселенную с другой физикой.

В нем живут эффективно безмассовые фермионные квазичастицы: электронные и дырочные возбуждения с линейной дисперсией. Здесь «безмассовый» означает отсутствие щели и массового члена в выбранной эффективной модели. Иными словами, модуль энергии пропорционален модулю эффективного импульса. Форма зависимости напоминает фотонную, но скорость здесь v_F, а сами возбуждения — фермионные.

У этой аналогии есть особенно интересная вычислительная сторона.

Безразмерная константа связи сравнивает силу взаимодействия с характерной энергией свободного движения. В КЭД такую роль играет постоянная тонкой структуры.

В СИ обычная постоянная тонкой структуры равна \alpha=e^2/(4\pi\varepsilon_0\hbar c)\approx1/137. Отношение кулоновской потенциальной и кинетической энергий в графене задаёт

\alpha_g=\frac{e^2}{4\pi\varepsilon_0\varepsilon\hbar v_F}=\frac{c}{\varepsilon v_F}\alpha\approx\frac{2.2}{\varepsilon}.

Здесь \varepsilon — эффективная относительная диэлектрическая проницаемость окружения; для простой модели двух сред её часто приближают полусуммой их проницаемостей. Получается число порядка двух вместо 0.0073.

Поэтому взаимодействие может давать существенные поправки. Из этого не следует, однако, что абсолютно каждая поправка обязательно велика: важны экранирование, плотность носителей, масштаб энергии и сама наблюдаемая величина.

Графен — настольная лаборатория аналогов квантовой электродинамики: поляризация вакуума, релятивистская кинематика и туннелирование Дирака. Все эти сюжеты подробно обсуждаются тут, например: Кацнельсон и Новосёлов; экспериментальная работа о безмассовых дираковских носителях в графене — Novoselov et al., Nature (2005).

Конечно, фотоны в лаборатории не начинают летать со скоростью v_F.

Электромагнитное поле остаётся трёхмерным, кристалл задаёт выделенную систему отсчёта, а на больших энергиях проявляется решётка. В принципе, любой полупроводник можно условно рассматривать как эффективный «вакуум» для своих возбуждений — с другими массами, щелью, скоростями и взаимодействиями. Но далеко не каждый такой вакуум релятивистский, и не каждый моделирует именно КЭД.

Теперь приложим магнитное поле перпендикулярно листу графена. В результате вместо непрерывной шкалы энергий появляются уровни Ландау. В рассматриваемом приближении

E_n=\mathrm{sgn}(n)\frac{\hbar v_F}{l_B}\sqrt{2\lvert n\rvert},\qquad n=0,\pm1,\pm2,\ldotsl_B=\sqrt{\frac{\hbar}{eB}}.

Последняя формула записана в СИ,

e>0 — модуль заряда электрона.

Уровни Ландау графена и сравнение с параболической зоной
Уровни Ландау графена и сравнение с параболической зоной

Слева присутствуют положительные и отрицательные уровни и особый уровень с нулевой энергией. Справа показано различие законов: у обычного электрона с параболической дисперсией уровни равноотстоящие, у графена интервалы уменьшаются с ростом номера.

Откуда взялся квадратный корень, можно понять без полного решения дифференциального уравнения. В поле компоненты кинетического импульса уже не коммутируют. Из них составляют операторы понижения и повышения a,a^\dagger, как для квантового гармонического осциллятора. При выборе подходящей калибровки электромагнитного поля

H_0=\frac{\sqrt2\hbar v_F}{l_B}\begin{pmatrix}0&a\\a^\dagger&0\end{pmatrix},\qquad [a,a^\dagger]=1.

После возведения этой матрицы в квадрат на диагонали оказываются aa^\dagger и a^\dagger a. Их собственные значения связаны с целыми номерами осциллятора. Следовательно квадрат энергии пропорционален номеру уровня, а сама энергия — его квадратному корню.

Для примера при B=10 Тл и v_F=10^6 м/с магнитная длина около 8.1 нм, а расстояние от нулевого уровня до первого — около 0.115 эВ.

2. Как из электрона и отсутствующего электрона получается задача о паре

Представим себе, что электрон перешёл с занятого уровня n_h на свободный уровень n_e. На верхнем уровне появился электрон, а на нижнем осталось незанятое место. Это место удобно описывать как положительно заряженную квазичастицу, которую называют дыркой.

Создание электронно-дырочной пары
Создание электронно-дырочной пары

Положительный заряд дырки является математическим способом учитывать недостающий отрицательный заряд относительно заполненного исходного состояния. Дырка может перемещаться, когда электроны занимают освободившиеся состояния. Её энергию и эффективный импульс описывают относительно выбранного заполнения; динамика определяется спектром материала.

Без взаимодействия энергия такого возбуждения равна

\Delta E_0=E_{n_e}-E_{n_h}.

Электрон-дырочное возбуждение в магнитном поле называют магнитоэкситоном. Его энергия зависит от взаимодействия и от состояния движения пары.

От уравнения Шрёдингера к вычислению поправок по теории возмущений

В разделе 1 мы получили стационарное уравнение H|\psi\rangle=E|\psi\rangle из уравнения Шрёдингера. Теперь найдём, как меняются его решения при добавлении взаимодействия.

Кет |\psi\rangle обозначает вектор состояния, бра \langle\psi| — сопряжённый вектор, а \langle\phi|\psi\rangle — скалярное произведение. В координатах это \int\phi^*(x)\psi(x)dx; звёздочка означает комплексное сопряжение.

Для конечной матрицы эрмитовость H=H^\dagger обеспечивает вещественные энергии и ортогональность собственных состояний с разными энергиями. Для дифференциального гамильтониана нужна самосопряжённость с заданной областью определения: допустимые функции и граничные условия входят в задачу. Одной лишь формальной симметрии выражения недостаточно. Это различие описано, например, в лекциях Тонга, раздел 3.2.2.

Предположим, задача H_0|n^{(0)}\rangle=E_n^{(0)}|n^{(0)}\rangle уже решена.

Добавим взаимодействие H(\lambda)=H_0+\lambda U.

Параметр \lambda — безразмерная ручка силы взаимодействия; физически её потом часто ставят равной 1. Давайте разложим в формальный ряд

E_n(\lambda)=\sum_{r=0}^{\infty}\lambda^r E_n^{(r)},\qquad |n(\lambda)\rangle=\sum_{r=0}^{\infty}\lambda^r|n^{(r)}\rangle.

Верхний индекс в скобках — порядок поправки, нижний n — номер состояния. Для удобства выбираем промежуточную нормировку \langle n^{(0)}|n(\lambda)\rangle=1: все поправки к вектору ортогональны исходному состоянию.

Подставим ряды в уравнение и соберём одинаковые степени \lambda. При r\geq1:

\left(H_0-E_n^{(0)}\right)|n^{(r)}\rangle=-U|n^{(r-1)}\rangle+\sum_{s=1}^{r}E_n^{(s)}|n^{(r-s)}\rangle.

Так получается рекурсивный алгоритм: каждый следующий этаж строится из предыдущих. Проекция на исходное состояние даёт E_n^{(r)}=\langle n^{(0)}|U|n^{(r-1)}\rangle.

В первом порядке получается особенно просто. Уравнение

H_0|n^{(1)}\rangle+U|n^{(0)}\rangle=E_n^{(0)}|n^{(1)}\rangle+E_n^{(1)}|n^{(0)}\rangle

умножаем слева на \langle n^{(0)}|. Два члена с неизвестным исправлением состояния сокращаются, поскольку \langle n^{(0)}|H_0=E_n^{(0)}\langle n^{(0)}|. Остаётся

E_n^{(1)}=U_{nn},\qquad U_{mn}=\langle m^{(0)}|U|n^{(0)}\rangle.

Проекция на другое состояние m\ne n даёт уже саму поправку к вектору и затем вторую поправку к энергии:

|n^{(1)}\rangle=\sum_{m\ne n}\frac{U_{mn}}{E_n^{(0)}-E_m^{(0)}}|m^{(0)}\rangle,\qquad E_n^{(2)}=\sum_{m\ne n}\frac{|U_{mn}|^2}{E_n^{(0)}-E_m^{(0)}}.

Итак:

E_n(\lambda)=E_n^{(0)}+\lambda U_{nn}+\lambda^2\sum_{m\ne n}\frac{|U_{mn}|^2}{E_n^{(0)}-E_m^{(0)}}+O(\lambda^3).

Что такое большое O?

Запись R(\lambda)=O(\lambda^3) при \lambda\to0 означает: в некоторой достаточно малой окрестности нуля есть постоянная C, такая что |R(\lambda)|\leq C|\lambda|^3.

Это оценка масштаба остатка. Существование разложения до каждого фиксированного порядка ещё не гарантирует сходимость бесконечного ряда. А в квантовой теории часто работают с асимптотическими рядами, которые улучшают ответ лишь до некоторого порядка, а дальше не работают.

О деталях того, что такое асимптотическое разложение, можете посмотреть в Википедии.

Вывод выше относится лишь к изолированному невырожденному уровню.

При вырождении несколько состояний имеют одну энергию: нулевой знаменатель сигнализирует, что сначала нужно диагонализовать U в этом подпространстве.

Малые ненулевые знаменатели тоже предупреждают о сильном смешивании. В задаче Ландау сначала учитываются вырождение и сохраняющиеся квантовые числа пары.

Малость самого значения параметра \lambda ещё не гарантирует слабое смешивание.

В модели

H=\begin{pmatrix}0&\lambda v\\\lambda v&\Delta\end{pmatrix},

где

\Delta>0 — зазор между исходными уровнями,

а v — вещественный матричный элемент с размерностью энергии, нижний уровень известен точно:

E_-=\frac{\Delta-\sqrt{\Delta^2+4\lambda^2v^2}}2,\qquad E_-^{[2]}=-\frac{\lambda^2v^2}{\Delta}.

Квадратные скобки здесь обозначают приближение до второго порядка, включая нулевую энергию исходного уровня. Контролировать надо отношение |\lambda v|/\Delta.

В нашем примере с v=1 и \lambda=0.12 при \Delta=1 точный ответ около -0.0141984, второй порядок даёт -0.0144. При \Delta=0.05 точный ответ уже -0.0975765, а второй порядок — -0.288. Одна и та же \lambda попала в разные режимы.

Теория возмущений при большом и малом зазоре между уровнями
Теория возмущений при большом и малом зазоре между уровнями

Матричный элемент: сколько одного состояния получается переходом из другого

В полном ортонормированном базисе оператор раскладывается по нему так

U|n\rangle=\sum_m U_{mn}|m\rangle.

Поэтому столбец n матрицы говорит, какие компоненты получились после действия U на состояние n.

Диагональный элемент U_{nn} — среднее взаимодействие и первый сдвиг энергии.

Внедиагональный U_{mn} задаёт смешивание состояний. Это комплексная амплитуда с размерностью энергии, не вероятность.

Например, при коротком действии слабого возмущения амплитуда перехода n\to m в первом порядке по времени равна -i\lambda tU_{mn}/\hbar, а вероятность равна квадрату ее модуля.

Для локального скалярного потенциала:

U_{mn}=\int d^dr\,\psi_m^*(\mathbf r)U(\mathbf r)\psi_n(\mathbf r).

Диагональ усредняет потенциал с плотностью |\psi_n|^2.

Интегралы от внедиагональных элементов могут обнуляться из-за взаимного уничтожения положительных и отрицательных вкладов.

В учебном примере с одномерным квантовым осциллятором возьмём

\psi_0=\pi^{-1/4}e^{-x^2/2} и \psi_1=\sqrt2x\psi_0.

Для U=x среднее в каждом из этих состояний равно нулю, а U_{01}=1/\sqrt2: возмущение смешивает состояния, хотя его диагональное среднее равно нулю.

Для U=x^2 наоборот: U_{01}=0, но U_{00}=1/2, U_{11}=3/2.

Произведения волновых функций и две матрицы взаимодействия
Произведения волновых функций и две матрицы взаимодействия

Нечётные части взаимно уничтожаются, что определяет правило отбора, которое можно проверить до запуска большого расчёта.

Для пары в дипломе сначала есть две двумерные координаты и сумма по компонентам графенового спинора:

U_{ab}=\sum_\alpha\int d^2r_e\,d^2r_h\,\Psi_{a,\alpha}^*(\mathbf r_e,\mathbf r_h)U(\mathbf r_e-\mathbf r_h)\Psi_{b,\alpha}(\mathbf r_e,\mathbf r_h).

Здесь предполагается, что потенциал одинаково действует на разные компоненты, а для матричного потенциала нужна сумма по двум компонентным индексам.

Индексы a,b обозначают состояния пары, \alpha — её компоненту.

После выделения движения центра пары и фиксации сохраняющейся метки P остаётся интеграл по относительной координате; для диагонального элемента его можно записать как \int d^2r\,\Psi^\dagger U\Psi.

Дальнейшее угловое интегрирование и преобразование Фурье приводят к двойному радиальному интегралу раздела 7. Потерять здесь меру r\,dr\,d\theta — значит изменить саму задачу, поэтому это можно использовать как еще одну точку контроля.

Взвешенное среднее потенциала и проверка теории возмущений на двух уровнях
Взвешенное среднее потенциала и проверка теории возмущений на двух уровнях

Слева для наглядности взяты нормированный радиальный вес \rho(r)=2re^{-r^2} и сглаженный притягивающий потенциал U(r)=-1/\sqrt{r^2+0.2^2}. Закрашенная площадь равна среднему взаимодействию.

Справа совсем маленькая задача, которую можно решить точно:

H(\lambda)=\begin{pmatrix}0&0\\0&1\end{pmatrix}+\lambda\begin{pmatrix}0.3&0.4\\0.4&-0.2\end{pmatrix}.

Для нижнего уровня первый порядок даёт E=0.3\lambda, второй добавляет -0.16\lambda^2. При малой \lambda приближения согласуются с точной нижней собственной энергией; при сильном смешивании расходятся. Проверить можно так:

import numpy as np
for lam in [0.01, 0.1, 0.5, 1.0]:
    H = np.array([[0.3*lam, 0.4*lam], [0.4*lam, 1-0.2*lam]])
    exact = np.linalg.eigvalsh(H)[0]
    print(lam, exact, 0.3*lam, 0.3*lam-0.16*lam**2)

В этой статье V будет обозначать положительную величину кулоновского ядра для одинаковых зарядов. В энергии притягивающейся электрон-дырочной пары прямой вклад входит со знаком минус. Поэтому положительное число в таблице матричных элементов нельзя автоматически называть положительной поправкой к энергии или полной энергией экситона.

И ещё одна важная граница приближения. В графене энергия Ландау и характерная энергия кулоновского взаимодействия обе растут как 1/l_B, то есть как \sqrt B. Их отношение

\alpha_g=\frac{e^2}{4\pi\varepsilon_0\varepsilon\hbar v_F}\approx\frac{2.19}{\varepsilon}

само по себе не уменьшается при увеличении магнитного поля. Сильное поле не гарантирует слабость взаимодействия. Смешивание уровней и роль диэлектрического окружения подробно обсуждаются в работе Лозовика и Соколика.

3. Зачем понадобилось экранирование

Пока мы рассматривали электрон и дырку так, как будто они остались вдвоём и больше вокруг ничего нет. Но в материале есть много других электронов.

Заряды электрона и дырки воздействуют на эти электроны, вследствие чего они перераспределяются в пространстве и создают какое-то новое электрическое поле.

Другая пробная частица чувствует уже сумму внешнего и индуцированного полей. Такое изменение взаимодействия со средой физики называют экранированием.

Положительный пробный заряд притягивает электроны, поэтому вокруг него появляется избыток отрицательной плотности заряда, который уменьшает его поле на больших расстояниях. Отрицательный заряд, наоборот, вытесняет электроны, оставляя рядом избыток положительного заряда относительно исходного состояния. Он тоже уменьшает поле пробного заряда на больших расстояниях. в обоих случаях получается ослабление потенциала.

Перераспределение электронной плотности и ослабление потенциала
Перераспределение электронной плотности и ослабление потенциала

Справа — учебная трёхмерная статическая модель с потенциалом e^{-\kappa r}/r вместо 1/r; 1/\kappa называется длиной экранирования.

Почему отклик вообще линеен?

При достаточно слабом воздействии можно разложить изменение плотности в ряд Тейлора по его величине. Оставляя первый член, получаем \delta n=\Pi U для одной пространственно-временной гармоники. В координатах это в общем случае интеграл по координатам и времени воздействия.

U — это потенциальная энергия электрона, а не электрический потенциал \varphi: U=-e\varphi при

e>0. Повышение локальной энергии делает занятие области менее выгодным, поэтому обычный статический электронный отклик имеет отрицательную \Pi.

Для положительного пробного заряда

\varphi>0, U<0, и \delta n>0 — это согласуется с картинкой накопления электронов.

Дальше возникает самосогласованность. Это слово означает, что мы учли, что электроны реагируют на полное поле, которое создается в том числе их реакцией на него. Если только один раз вычислить отклик на внешнее воздействие, то получится лишь первая поправка к взаимодействию. А решение самосогласованной системы уравнений на плотность заряда и поле суммирует всю обратную связь в этой модели.

Проще всего вывести нужную формулу через линейный отклик. Обозначим исходное взаимодействие через V_0, эффективное — через W. Рассматриваем одну пространственно-временную гармонику, чтобы для простоты описывать взаимодействие и отклик числами, а не матрицами.

Здесь важно не перепутать два поля. U_{\rm ext} — внешняя потенциальная энергия электрона; U_{\rm tot} включает ещё и поле перераспределившихся электронов.

U_{\rm tot}=U_{\rm ext}+V_0\delta n,\qquad \delta n=\Pi U_{\rm tot}.

Неприводимая поляризация \Pi связывает изменение плотности с полным полем. В диаграммной записи этот блок нельзя разделить на два последовательных блока отклика разрезанием одной внутренней линии кулоновского взаимодействия. В RPA мы заменяем его пузырьком независимых электронов \Pi_{0}. Когда ниже пишем просто \Pi в RPA-примере, подразумеваем именно это приближение.

Решая два уравнения, находим полный отклик на внешнее воздействие:

\chi=\frac{\delta n}{U_{\rm ext}}=\frac{\Pi}{1-V_0\Pi}.

Это другая величина. Пусть \rho_{\rm test} — амплитуда заданного источника в той же нормировке числовой плотности; знак источника включён в неё. Если U_{\rm ext}=V_0\rho_{\rm test}, а полное поле U_{\rm tot}=W\rho_{\rm test}, получаем

W=V_0+V_0\Pi W.

Справа исходное воздействие плюс воздействие через отклик среды. Важно, что среда реагирует на полное поле: поэтому в последнем множителе стоит W.

Полное поле, внешнее поле и два разных отклика
Полное поле, внешнее поле и два разных отклика

Слева — схема самосогласованного отклика. Справа взят учебный постоянный \Pi_0=-0.4: при ненулевом взаимодействии \chi и \Pi_{0} уже различаются. Эквивалентная запись W=V_0+V_0\chi V_0 использует полный отклик. Подстановка \chi вместо \Pi в знаменатель привела бы к повторному учёту экранирования. Различие двух откликов разобрано, например, в лекциях Антуана Жоржа, разделы II–III; при сравнении формул нужно учитывать соглашения о знаках заряда и потенциала.

Переносим второй член налево и решаем уравнение:

W(1-V_0\Pi)=V_0,\qquad W=\frac{V_0}{1-V_0\Pi}.

Если при выбранных аргументах |V_0\Pi|<1, ту же формулу можно разложить в геометрический ряд:

W=V_0+V_0\Pi V_0+V_0\Pi V_0\Pi V_0+\ldots.

Каждая дополнительная вставка \Pi описывает ещё один отклик. В диаграммной записи элементарный электрон-дырочный отклик рисуется пузырьком из двух электронных линий. Так возникает цепочка петель.

Цепочка из нуля, одного, двух и трёх поляризационных пузырьков
Цепочка из нуля, одного, двух и трёх поляризационных пузырьков

У каждого пузырька два места присоединения к взаимодействию.

Между ними идут две электронные линии, которые в отклике описывают создание и уничтожение электронно-дырочного возбуждения. Оранжевая линия — взаимодействие; она не изображает ещё один электрон. Переменные каждого следующего пузырька суммируются или интегрируются по тем же правилам.

Если принять s=-V_0\Pi\geq0, то

\frac{W}{V_0}=\frac{1}{1+s}=1-s+s^2-s^3+\ldots.

При 0\leq s<1 частичные суммы сходятся к ослабленному взаимодействию.

При s\geq1 этот ряд уже не сходится, хотя алгебраическое решение 1/(1+s) остаётся определённым. Поэтому само выражение с дробью полезнее механического сложения всё большего числа членов.

Последовательное добавление пузырьков в учебном скалярном канале
Последовательное добавление пузырьков в учебном скалярном канале

Анимация построена в Python. По горизонтали безразмерная сила отклика s, а не время и не траектория электрона. Чёрная линия — решение уравнения, цветная — частичная сумма. При s ближе к единице поправки затухают медленнее. Скрипт 06_graphene.py ниже создаёт и GIF, и статическую картинку.

Вот буквальный расчёт цепочки в одном канале. Пусть уже вычисленный пузырёк дал \Pi=-0.4, а V_0=2 в согласованных условных единицах. Тогда s=0.8. Вклад с нулём вставок равен 2, с одной — -1.6, с двумя — 1.28. Каждый следующий получается умножением предыдущего на \Pi V_0=-0.8.

V0, Pi = 2.0, -0.4
term = V0
partial = 0.0
for n in range(21):
    partial += term
    print(n, term, partial)
    term *= Pi * V0
exact = V0 / (1 - V0 * Pi)
print("RPA:", exact)  # 1.1111111111111112

После 21 члена получится примерно 1.1213593023. Ошибка усечения для суммы до n=N — разность точного и приближённого ответов:

e_N=W-W_N=\frac{V_0(-s)^{N+1}}{1+s}.

Невязка получается другим действием — подстановкой W_N в уравнение (1+s)W=V_0:

r_N=(1+s)W_N-V_0=-(1+s)e_N.

Для приведённых чисел e_{20}\approx-0.01024819, а r_{20}\approx0.01844674. Связь между ними здесь известна точно. В больших линейных системах малая невязка может сочетаться с существенной ошибкой, если задача плохо обусловлена; в разделе 17 увидим ещё и другую ловушку — маленькую невязку неверно составленного уравнения. Эти формулы можно проверить независимо от цикла.

Здесь \Pi для простоты задано числом; в разделах 4–5 разберём, как сам пузырёк получается из пропагаторов и суммы переходов. Полный воспроизводимый эксперимент с графиком каждого вклада находится в разделе 17.

Приближение хаотических фаз, RPA, использует такую сумму цепочек с простейшим поляризационным пузырьком. Приближение оставляет определённое семейство диаграмм. Вершинные поправки и другие классы взаимодействий оно автоматически не добавляет.

Вершинная поправка изменяет сам узел связи электронных линий с воздействием, например учитывает дополнительное взаимодействие внутри электронно-дырочного отклика.

Это объясняет важную разницу между тем, чтобы просуммировать бесконечную цепочку повторяющихся пузырьков и тем, чтобы посчитать полный вклад одного высокого порядка, где допустимо много разных структур. Первое оказывается гораздо проще.

4. Как читать петли и превращать рисунок в интеграл

Начнём с языка теории графов.

Граф — набор вершин и соединяющих их рёбер.

Вершина — узел, ребро — связь двух узлов.

Длина ребра не фиксирована, его можно нарисовать как угодно и даже гнуть, не меняя самого графа. Если между двумя узлами несколько рёбер или ребро возвращается в тот же узел, нужен мультиграф; именно такие случаи часто встречаются в диаграммах.

Маршрут — последовательность вершин и соединяющих их рёбер.

Простой путь проходит без повторения вершин.

Цикл замыкается в исходной вершине без повторения остальных вершин и рёбер; в мультиграфе допускаются также петля из одного ребра и цикл из двух параллельных рёбер.

Связный граф позволяет добраться между любыми двумя вершинами.

Дерево — связный граф без циклов.

Дерево и добавление независимых циклов
Дерево и добавление независимых циклов

На первом рисунке четыре вершины соединены тремя рёбрами: это дерево.

При добавлении любого ребра в дерево образуется цикл. Добавили диагональ — появился второй независимый цикл. Замкнутых обходов можно придумать больше двух, но остальные выражаются через выбранные два. Именно число независимых циклов связано с количеством свободных импульсов интегрирования.

Диаграмма — это всего лишь способ записать член математического выражения (ряда).

У рисунка есть три вида основных элементов: внешние и внутренние линии, и вершины.

Внешние линии задают состояния или воздействия, которые мы подготовили и наблюдаем.

Вершины задают разрешённые взаимодействия.

Внутренние линии описываются функциями распространения — пропагаторами. Их конкретный вид нужно брать из выбранной теории.

Для примера возьмём учебную теорию вещественного скалярного поля с четырёхлинейной вершиной.

Это поле задаётся одним вещественным числом в точке, без векторного направления или спинорных компонент.

Четырёхточечная функция — среднее произведения четырёх полей,

\langle\phi(x_1)\phi(x_2)\phi(x_3)\phi(x_4)\rangle.

Её аргументы отмечают места вставок; сами по себе четыре точки не задают четыре классические траектории. На картинке показаны примеры связных вкладов в такую функцию:

Нулевая, одна и две петли с подсчётом внутренних линий и вершин
Нулевая, одна и две петли с подсчётом внутренних линий и вершин

В первой диаграмме четыре внешние линии встречаются в одной вершине. Внутренних линий нет. Во второй между двумя вершинами проходят две внутренние линии. В третьей три вершины соединены четырьмя внутренними линиями, образующими два независимых цикла.

Для связного графа справедлива формула Эйлера

L=I-V+1,

где I — число внутренних линий,

V — число вершин,

L — число независимых петель.

Внешние линии в I здесь не включаются.

Чтобы понять формулу, соедините все вершины деревом.

Для этого требуется V-1 внутренних рёбер.

Каждое оставшееся ребро замыкает один новый независимый цикл, поэтому их число равно в точности I-(V-1).

Почему это связано с формулой Эйлера и что меняется на торе

Назначим импульс каждой из I внутренних линий. В вершинах получим V уравнений сохранения, но одно следует из остальных и общего сохранения внешнего импульса. Независимых условий V-1, значит, свободных внутренних импульсов I-V+1. У несвязного графа с C компонентами аналогичный счёт даёт L=I-V+C.

Теперь нарисуем связный граф без пересечений на сфере и посчитаем области, на которые он её разделил, — грани F. Для клеточного разбиения, где каждая грань топологически диск, выполняется

V-I+F=2.

Эйлерова характеристика поверхности — число \chi=V-I+F, не зависящее от выбранного клеточного разбиения. Для замкнутой ориентируемой поверхности рода g, то есть сферы с g ручками,

\chi=2-2g,\qquad V-I+F=2-2g.

Например, у сферы \chi=2, у тора \chi=0, у поверхности с двумя ручками \chi=-2. Одну ручку можно получить, удалив два диска и соединив отверстия цилиндром: удаление дисков уменьшает характеристику на два, цилиндр имеет характеристику нуль. Это объясняет каждое следующее -2. При наличии b граничных компонент формула становится \chi=2-2g-b, если разбиение учитывает границу.

Один граф с двумя петлями на сфере и на торе
Один граф с двумя петлями на сфере и на торе

На сфере две петли с общей вершиной дают V=1, I=2, F=3: характеристика 2. Тор удобно представить квадратом со склеенными противоположными сторонами. Четыре его угла превращаются в одну вершину, стороны — в два ребра, внутренняя область — в одну грань: 1-2+1=0. Это тот же абстрактный граф с L=2, но другое вложение.

Поэтому формула L=I-V+1 не меняется на торе.

Меняется связь циклов с гранями: на замкнутой ориентируемой поверхности при клеточном вложении L=F-1+2g. На сфере каждый независимый цикл можно связать с ограниченной областью плоского рисунка; на торе добавляются обходы вокруг ручки. Смешивать эйлерову характеристику поверхности и число независимых петель диаграммы нельзя.

Теперь подпишем импульсы на однопетлевом рисунке. Пусть через пузырёк передаётся суммарный импульс p. Если по верхней внутренней линии идёт k, по нижней должен идти p-k: это закон сохранения импульса в вершине. Значение k внешними условиями не задано. Нужно сложить вклады всех допустимых k, то есть проинтегрировать.

Дельта-функция: точечный толчок

Для линейной задачи ответ на произвольный источник можно собрать из откликов на локальные толчки. Один такой толчок описывают дельта-функцией Дирака \delta(x-a) — обобщённой функцией, действие которой на подходящую пробную функцию f задаётся равенством

\int_{-\infty}^{\infty}\delta(x-a)f(x)\,dx=f(a).

Обычной локально интегрируемой функции с таким свойством нет. Но существуют семейства функций, сходящиеся к дельта-функции в смысле действия на пробные функции. Удобный пример — нормированные гауссианы:

\delta_\epsilon(x-a)=\frac{1}{\sqrt{2\pi}\epsilon}\exp\left[-\frac{(x-a)^2}{2\epsilon^2}\right],\qquad\epsilon\to0.

Высота растёт, ширина уменьшается, площадь остаётся равной 1. Размерность дельта-функции — обратная размерность её аргумента: \delta(x) имеет единицы 1/\text{длина}.

На сетке точечный источник можно представить значением 1/h в одной ячейке ширины h.

Гауссовы приближения дельта-функции и отклик на распределённый источник
Гауссовы приближения дельта-функции и отклик на распределённый источник

Слева растёт высота, но сохраняется площадь. Справа распределённый источник разбит на маленькие толчки; их отклики складываются в решение.

В статье Дирака 1927 года о физической интерпретации квантовой динамики такой объект появляется при работе с непрерывными квантовыми переменными. В дискретном базисе роль единичного толчка играет символ Кронекера \delta_{mn}: 1 при m=n, 0 иначе. В непрерывном базисе его аналог — \langle x|x'\rangle=\delta(x-x').

Функция Грина: обратный оператор с оговорёнными границами

Пусть линейное уравнение имеет вид Lu=f. Вместо решения заново для каждого f найдём ядро обратного оператора:

L_xG(x,x')=\delta(x-x'),\qquad u(x)=\int G(x,x')f(x')\,dx'.

Почему это работает?

Применяем L_x ко второму равенству, переносим его под интеграл и получаем

\int\delta(x-x')f(x')dx'=f(x).

Это и есть функция Грина: отклик в точке x на единичный источник в x'.

Здесь предполагаются однородные граничные условия; неоднородные добавляют отдельный граничный вклад. Если у L есть нулевая мода — ненулевое решение Lu=0 с теми же условиями, — обратный оператор без дополнительных ограничений может не существовать.

Границы — это часть определения G, при разных границах функция Грина будет разной. Проще говоря, один и тот же дифференциальный оператор на прямой, на кольце и на отрезке с закреплёнными концами даёт различные функции Грина.

Во временной задаче запаздывающее условие запрещает ответ до воздействия. Например, для эволюции с постоянным H

G_R(t)=-\frac{i}{\hbar}\theta(t)e^{-iHt/\hbar},\qquad(i\hbar\partial_t-H)G_R(t)=\delta(t)I.

Ступенька \theta(t) равна 0 при t<0 и 1 при

t>0; её производная — дельта-функция. I здесь единичный оператор.

Что такое действие поля и почему возникает обратный оператор

Лагранжев подход начинает описание с одного скалярного объекта — действия, — из которого получают все уравнения движения. В механике частицы задают координату q(\tau), лагранжиан L(q,\dot q,\tau) и действие S[q]=\int L\,d\tau. Для обычной частицы L=m\dot q^2/2-U(q). Действие имеет размерность энергии, умноженной на время.

Лагранжев подход позволяет одинаково работать со многими координатами, полями, связями и симметриями. Но лагранжиан всё равно надо обосновать физически, так как сам принцип стационарного действия не выбирает правильную модель автоматически.

У поля вместо одной координаты есть значение в каждой точке: \phi(x,\tau).

Поэтому лагранжиан записывают как пространственный интеграл от лагранжевой плотности \mathcal L, а действие — как интеграл по пространству и времени:

S[\phi]=\int d\tau\,dx\,\mathcal L(\phi,\partial_\tau\phi,\partial_x\phi).

Функционал принимает на вход целую функцию и возвращает число.

Например, действие оценивает всю историю поля. Обычная функция f(x) получает число x; функционал S[\phi] получает профиль или историю \phi целиком.

Для натянутой струны в приближении малых поперечных смещений можно взять

\mathcal L=\frac{\rho}{2}(\partial_\tau\phi)^2-\frac{T}{2}(\partial_x\phi)^2-\frac{K}{2}\phi^2+f\phi.

Здесь \rho — масса на единицу длины,

T — натяжение,

K\phi — дополнительная восстанавливающая сила на единицу длины,

f — внешняя сила на единицу длины.

Первое слагаемое является кинетической энергией на единицу длины, следующие два задают потенциальную энергию с минусом. Модель даёт уравнение

\rho\partial_\tau^2\phi-T\partial_x^2\phi+K\phi=f.

Поле, конечно же, не обязано быть струной. Просто этот пример помогает понять, что означает непрерывное множество связанных степеней свободы.

От координаты частицы к лагранжевой плотности и действию поля
От координаты частицы к лагранжевой плотности и действию поля

Вариация — малое изменение самой функции. Запишем соседнюю историю как \phi+\varepsilon\eta, где \eta(x,\tau) — произвольная допустимая форма изменения, а \varepsilon — малое число. Требование стационарности означает dS[\phi+\varepsilon\eta]/d\varepsilon|_{\varepsilon=0}=0 для всех таких \eta. На фиксированной границе \eta=0. Это не производная поля по координате и не утверждение, что реальное действие всегда минимально.

Для лагранжевой плотности с первыми производными правило произведения и интегрирование по частям дают уравнение Эйлера — Лагранжа:

\frac{\partial\mathcal L}{\partial\phi}-\partial_\tau\frac{\partial\mathcal L}{\partial(\partial_\tau\phi)}-\partial_x\frac{\partial\mathcal L}{\partial(\partial_x\phi)}=0.

В примере струны три члена равны -K\phi+f, -\rho\partial_\tau^2\phi и +T\partial_x^2\phi. Перенос знаков даёт написанное уравнение. Такой переход от механики к полю разобран в лекциях Тонга по классической теории поля.

Интегрирование по частям без пропущенного шага

Теперь вернёмся к простой модели, из которой получим пропагатор. Это одномерное евклидово скалярное поле. Используем единицы \hbar=1 и квадратичное действие с источником:

S_E[\phi;J]=\frac12\int_a^b dx\,[(\phi')^2+\mu^2\phi^2]-\int_a^b dx\,J\phi,\qquad\mu>0.

Штрих обозначает производную по x, \mu имеет размерность обратной длины.

Евклидовость здесь означает геометрию с одинаковыми знаками в квадрате длины. В квантовой теории такое описание часто связывают с продолжением времени в мнимую область, но допустимость перехода требует условий. Здесь мы явно выбрали положительную свободную квадратичную часть и изучаем статический оператор.

Вставим \phi+\varepsilon\eta вместо \phi, не меняя J. Используем (\phi'+\varepsilon\eta')^2=(\phi')^2+2\varepsilon\phi'\eta'+\varepsilon^2(\eta')^2 и аналогично раскроем квадрат \phi. Коэффициент при первой степени \varepsilon равен

\delta S_E[\eta]=\int_a^b dx\,[\phi'\eta'+\mu^2\phi\eta-J\eta].

Пока первая часть содержит производную пробного изменения \eta'. Нам нужно получить одну общую произвольную функцию \eta, чтобы потребовать исчезновения её коэффициента. Используем (\phi'\eta)'=\phi''\eta+\phi'\eta':

\int_a^b\phi'\eta'\,dx=[\phi'\eta]_a^b-\int_a^b\phi''\eta\,dx.

При закреплённых значениях поля \eta(a)=\eta(b)=0, поэтому крайний член исчезает. На бесконечной прямой вместо этого задают достаточное убывание. Получаем

\delta S_E[\eta]=\int_a^b dx\,\eta(x)[-\phi''(x)+\mu^2\phi(x)-J(x)].

Если скобка не равна нулю в какой-то области, можно выбрать там изменение \eta соответствующего знака и получить ненулевую вариацию. Поэтому стационарность для всех допустимых изменений требует

L\phi=J,\qquad L=-\partial_x^2+\mu^2.

Здесь L обозначает уже дифференциальный оператор; лагранжева плотность выше обозначалась \mathcal L. Именно интегрирование по частям превратило квадрат первой производной в оператор со второй производной и зафиксировало знак минус.

Изменение профиля поля и изменение действия около стационарного решения
Изменение профиля поля и изменение действия около стационарного решения

Численный пример на [0,1]:

\mu=1, \phi_*(x)=\sin\pi x, J=(\pi^2+1)\sin\pi x, \eta=\sin2\pi x.

Все профили имеют нулевые края. Прямое интегрирование даёт здесь

S_E[\phi_*+\varepsilon\eta]-S_E[\phi_*]=\varepsilon^2(4\pi^2+1)/4.

Линейного члена нет. Однако стационарность тут действительно является минимумом благодаря положительной квадратичной части.

Почему тот же оператор появляется в гауссовом среднем

На конечной сетке поле становится вектором \boldsymbol\phi, а действие —

\boldsymbol\phi^TA\boldsymbol\phi/2-J^T\boldsymbol\phi.

Матрица A вещественная, симметричная и положительно определённая:

z^TAz>0 для ненулевого z.

Шаги сетки включены в A и дискретный источник.

Дополним квадрат:

\frac12\boldsymbol\phi^TA\boldsymbol\phi-J^T\boldsymbol\phi=\frac12(\boldsymbol\phi-A^{-1}J)^TA(\boldsymbol\phi-A^{-1}J)-\frac12J^TA^{-1}J.

В интеграле Z[J]=\int d\boldsymbol\phi\,e^{-S_E} сдвиг переменной убирает A^{-1}J из квадрата. Остаётся тот же гауссов интеграл, что при J=0, умноженный на экспоненту последнего члена. Поэтому

\frac{Z[J]}{Z[0]}=\exp\left(\frac12J^TA^{-1}J\right),\qquad\langle\phi_i\phi_j\rangle_0=(A^{-1})_{ij}.

Вторая формула получается двумя производными по J_i,J_j при J=0: каждая производная исходного интеграла вставляет множитель \phi_i. Так обратный оператор одновременно решает линейное уравнение и задаёт двухточечную корреляцию свободного поля. В непрерывном пределе матрица заменяется ядром G(x,y); вопросы существования предела оговариваются отдельно. При восстановлении \hbar ковариация получает множитель \hbar.

Ковариация —

\langle(\phi_i-\langle\phi_i\rangle)(\phi_j-\langle\phi_j\rangle)\rangle.

При J=0 средние в этой чётной гауссовой модели нулевые, поэтому она совпадает с двухточечным средним.

Ковариация гауссовых переменных и обратная матрица
Ковариация гауссовых переменных и обратная матрица

*Для A=\begin{pmatrix}2&-1\\-1&2\end{pmatrix} обратная матрица равна

\frac13\begin{pmatrix}2&1\\1&2\end{pmatrix}.

Наклон облака показывает положительную совместную флуктуацию. Облако — конечная выборка.*

В реальном квантовом времени вес e^{iS/\hbar} осциллирует, так как складываются амплитуды, а не положительные вероятности отдельных траекторий.

Пропагатор: получаем конкретную формулу

Для оператора L=-\partial_x^2+\mu^2 на бесконечной прямой с убыванием на обоих концах определим G условием LG(x)=\delta(x).

Вне нуля справа нуль. Убывающее на бесконечностях решение имеет вид G(x)=A e^{-\mu|x|}. Проинтегрируем уравнение на маленьком отрезке около нуля: -G'(0+)+G'(0-)=1. Производные равны -\mu A и +\mu A, значит, 2\mu A=1 и

G(x)=\frac{e^{-\mu|x|}}{2\mu}.

Теперь разложим ответ по плоским волнам: G(x)=\int dk\,e^{ikx}D(k)/(2\pi). Оператор -\partial_x^2 на такой волне превращается в число k^2, а дельта-функция имеет Фурье-образ 1. Уравнение становится (k^2+\mu^2)D(k)=1. Отсюда

D(k)=\frac{1}{k^2+\mu^2}.Координатная функция Грина и её импульсное представление
Координатная функция Грина и её импульсное представление

Большая \mu сокращает характерную длину корреляций 1/\mu. Оба графика описывают один оператор в разных базисах. В гауссовом функциональном интеграле двухточечная корреляция также равна обратному квадратичному оператору; при выборе нормировки \hbar=1 это тот же G. Для электронов, фотонов, запаздывающих функций и магнитного поля нужно своё ядро, матричная структура и свои граничные условия.

Почему пару полей заменяют линией: правило Вика по шагам

Здесь спрятан важный переход: интеграл от произведения многих полей заменяется суммой произведений двухточечных функций. Он работает благодаря гауссовости свободной модели. Для произвольного распределения такое сокращение неверно. Сначала сформулируем правило точно, затем выведем его и проверим на числах.

Что именно усредняем. Представим поле на конечной сетке вектором вещественных переменных \phi=(\phi_1,\ldots,\phi_N). Свободное действие квадратично:

S_0(\phi)=\frac12\phi^T A\phi,\qquad\langle F\rangle_0=\frac{\int_{\mathbb R^N}d^N\phi\,F(\phi)e^{-S_0(\phi)}}{\int_{\mathbb R^N}d^N\phi\,e^{-S_0(\phi)}}.

Здесь A — вещественная симметричная положительно определённая матрица: тогда интегралы сходятся. Буква F обозначает любую интегрируемую функцию поля, например

\phi_1\phi_2\phi_3\phi_4.

При таком весе \langle\phi_i\rangle_0=0, а G_{ij}=\langle\phi_i\phi_j\rangle_0=(A^{-1})_{ij} — ковариация, то есть среднее произведение двух центрированных переменных. Индекс может обозначать узел сетки; в непрерывной записи он превращается в координату x, а G_{ij} — в G(x,y).

Совпадающие точки в непрерывной теории могут требовать регуляризации. В случае конечной сетки для расчетов достаточно обычной конечномерной алгебры.

Правило Вика.

Для совместно гауссовых вещественных переменных с нулевыми средними среднее нечётного произведения равно нулю. Среднее произведения 2n переменных равно сумме по всем разбиениям их помеченных мест на непересекающиеся неупорядоченные пары:

\langle\phi_{i_1}\cdots\phi_{i_{2n}}\rangle_0=\sum_{P\in\mathcal P_{2n}}\ \prod_{(a,b)\in P}G_{i_a i_b}.

\mathcal P_{2n} — множество таких разбиений; в каждом разбиении каждое место от 1 до 2n используется ровно один раз. Для каждой пары выписываем одну ковариацию, перемножаем их внутри одного разбиения, затем складываем результаты разных разбиений. Даже если два места содержат одно и то же поле, места при подсчёте остаются различимыми. Именно отсюда позднее возникают множители 3, 12 и 192.

Если среднее поля ненулевое, сначала пишут \phi_i=m_i+\eta_i, где m_i=\langle\phi_i\rangle, и применяют правило к центрированным \eta_i, раскрыв произведение.

Примечание.

Для негауссова взаимодействующего распределения одних двухточечных средних в общем случае недостаточно, так как появляются независимые связные корреляции более высоких порядков. В операторной квантовой теории теорема Вика формулируется для упорядоченных произведений свободных полей через нормальное упорядочение и свёртки; у фермионов появляются знаки перестановок. Нормальное упорядочение переносит операторы рождения частиц левее операторов уничтожения. Свёртка пары свободных полей в вакууме равна их временно упорядоченному двухточечному среднему; временное упорядочение ставит оператор с более поздним временем левее. Для фермионов обе перестановки требуют соответствующих знаков. Это объясняет родство с гауссовой формулой. Операторная формулировка разобрана в лекциях Тонга, раздел 3.3, а гауссовая — в его лекциях по статистической теории поля.

Ниже мы доказываем именно вещественную гауссову версию, нужную для нашего евклидова примера.

Шаг 1. Зачем вводить источник.

Добавим к экспоненте J^T\phi=\sum_iJ_i\phi_i и нормируем интеграл:

M(J)=\langle e^{J^T\phi}\rangle_0=\frac{Z_0[J]}{Z_0[0]}.

Переменные J_i здесь — вспомогательные ручки для вычисления средних. Производная по J_i вытаскивает множитель \phi_i из экспоненты. После дифференцирования ставим все источники равными нулю:

\left.\frac{\partial^m M}{\partial J_{i_1}\cdots\partial J_{i_m}}\right|_{J=0}=\langle\phi_{i_1}\cdots\phi_{i_m}\rangle_0.

Это объясняет слова «производные вставляют поля»: никакого дополнительного физического процесса при этом не происходит. Для конечномерного гауссова интеграла такие дифференцирования обоснованы сходимостью.

Шаг 2. Используем уже вычисленный гауссов интеграл.

В подразделе «Почему тот же оператор появляется в гауссовом среднем» мы выделили полный квадрат и сделали замену переменной интегрирования. При G=A^{-1} было получено

M(J)=\exp\!\left(\frac12J^TGJ\right).

Теперь будем дифференцировать это выражение, чтобы получить средние более высоких порядков.

Шаг 3. Две производные дают одну линию.

Обозначим h_i=(GJ)_i=\sum_jG_{ij}J_j. Тогда

\partial_iM=h_iM,\qquad\partial_j\partial_iM=(G_{ij}+h_ih_j)M.

Поскольку M(0)=1 и h_i(0)=0, остаётся \langle\phi_i\phi_j\rangle_0=G_{ij}. Графическое сокращение для этого множителя — линия между метками i и j.

Шаг 4. Четыре производные дают три рисунка.

Ещё одна производная даёт

\partial_k\partial_j\partial_iM=(G_{ij}h_k+G_{ik}h_j+G_{jk}h_i+h_ih_jh_k)M.

Теперь дифференцируем по J_l и только после этого ставим J=0. Производная каждого одиночного h даёт один G; остальные слагаемые всё ещё содержат хотя бы один h и исчезают:

\langle\phi_i\phi_j\phi_k\phi_l\rangle_0=G_{ij}G_{kl}+G_{ik}G_{jl}+G_{il}G_{jk}.Правило Вика: три спаривания четырёх помеченных мест и три произведения
Правило Вика: три спаривания четырёх помеченных мест и три произведения

Сначала выбираем партнёра для места 1: это 2, 3 или 4. Два оставшихся места образуют вторую пару автоматически. Пересечение линий в третьем рисунке не создаёт вершину: там нет новой точки взаимодействия. Цвет различает пары внутри одного слагаемого, а не виды частиц. Между тремя рисунками стоит плюс.

Шаг 5. Почему правило работает для любого чётного числа полей. Разложим M в ряд:

M(J)=\sum_{n=0}^{\infty}\frac{1}{2^n n!}\left(\sum_{a,b}J_aG_{ab}J_b\right)^n.

Произведение в скобках содержит по два источника на каждый G. При 2n производных и J=0 выживает только член степени 2n: меньшая степень уже продифференцирована до нуля, от большей остаётся источник. Каждое полное спаривание возникает 2^n n! раз: можно переставить концы каждой из n пар и сами n пар. Эти повторения сокращают делитель перед членом ряда. Остаётся один экземпляр каждого произведения ковариаций. Нечётных степеней источника в экспоненте квадратичной формы нет, поэтому нечётные средние равны нулю.

Число разных спариваний равно

N_{2n}=(2n-1)(2n-3)\cdots3\cdot1=\frac{(2n)!}{2^n n!}.

Первому месту выбираем партнёра 2n-1 способами, затем повторяем для оставшихся мест. Получаем: 2 поля — 1 спаривание, 4 — 3, 6 — 15, 8 — 105, 10 — 945. Это число спариваний помеченных полевых множителей; после отождествления внутренних точек несколько спариваний могут дать одну и ту же диаграмму.

Вычислительный пример: одинаковые поля не делают спаривания одинаковыми при подсчёте.

Пусть два гауссовых числа X,Y имеют нулевые средние и ковариацию

G=\begin{pmatrix}2&1\\1&3\end{pmatrix}.

Это допустимая ковариационная матрица: первый диагональный элемент положителен, определитель равен 5. Для произведения X\,X\,Y\,Y есть три вклада:

\langle X^2Y^2\rangle=\underbrace{\langle X^2\rangle\langle Y^2\rangle}_{2\cdot3=6}+\underbrace{\langle XY\rangle^2}_{1}+\underbrace{\langle XY\rangle^2}_{1}=8.Вычислительный пример для правила Вика: ковариация и вклады 6 плюс 1 плюс 1
Вычислительный пример для правила Вика: ковариация и вклады 6 плюс 1 плюс 1

Для независимой численной проверки надо ещё объяснить, как получить именно такую совместную гауссову модель.

Разложение Холецкого представляет положительно определённую ковариацию как G=LL^T, где L — нижнетреугольная матрица.

Здесь можно взять

L=\begin{pmatrix}\sqrt2&0\\1/\sqrt2&\sqrt{5/2}\end{pmatrix},\qquad \begin{pmatrix}X\\Y\end{pmatrix}=L\begin{pmatrix}z_1\\z_2\end{pmatrix}.

Переменные z_1,z_2 независимы и имеют стандартное нормальное распределение — нулевое среднее и единичную дисперсию; их ковариационная матрица — единичная. Поэтому ковариация результата равна LIL^T=G, что проверяется обычным умножением матриц. Скрипт интегрирует непосредственно полученный полином X^2Y^2 по двум стандартным гауссовым весам, без подстановки правила Вика. Для каждого z трёхузловая квадратура использует узлы -\sqrt3,0,\sqrt3 с весами 1/6,2/3,1/6; она точна для полиномов степени до пяти. У нашего подынтегрального полинома степень по каждой переменной не больше четырёх, поэтому девять пар узлов дают точный результат в точной арифметике. В вычислениях остаётся округление. Проверяются также числа спариваний вплоть до десяти мест. Для одного числа с дисперсией \sigma^2 тот же подсчёт даёт \langle X^4\rangle=3\sigma^4 и \langle X^6\rangle=15\sigma^6.

Как читать линию в диаграмме.

Линия между метками i,j означает множитель G_{ij}. Внешние точки — полевые множители наблюдаемой с заданными координатами. Внутренняя вершина взаимодействия объединяет поля, стоящие в одной интегрируемой точке: например, \phi(z)^4 даёт четыре места у вершины z. Чтобы восстановить член ряда по рисунку, надо выписать G для каждой линии, перемножить их, проинтегрировать внутренние координаты и учесть коэффициент разложения вместе с числом одинаковых спариваний. Именно этот перевод, а не история полёта частиц по нарисованным траекториям, будет использован дальше.

Как произведение двух линий действительно становится пузырьком

Возьмём составную наблюдаемую O(x)=\phi(x)^2. «Составная» означает, что в одной точке перемножены поля.

На этом шаге можно держать в уме конечную сетку либо одномерное массивное поле ниже: совпадающие значения G(x,x) там конечны. При обобщении на непрерывную теорию в большей размерности произведение полей в одной точке может быть сингулярным.

Тогда его сначала определяют с регуляризацией — например, ограничивают большие импульсы или сохраняют конечную сетку, — а составную наблюдаемую определяют с необходимыми вычитаниями.

Перенормировка связывает параметры и наблюдаемые с физически заданными условиями после такой обработки. Формальное вычитание двух неопределённых бесконечностей вычислением не считается.

Большие импульсы называют ультрафиолетовой областью, малые — инфракрасной.

Здесь эти слова не означают, что в задаче обязательно есть свет соответствующего цвета. Наша одномерная модель с

\mu>0 позволяет сначала освоить саму комбинаторику без этих дополнительных трудностей. В свободном гауссовом среднем четыре поля разбиваются на пары тремя способами — это простейший случай правила Вика:

\langle\phi(x)^2\phi(y)^2\rangle_0=G(x,x)G(y,y)+2G(x,y)^2.

Покажем, зачем из этого среднего вычитать произведение отдельных средних. Обозначим m_x=\langle O(x)\rangle_0 и m_y=\langle O(y)\rangle_0. Отклонения от средних равны \delta O(x)=O(x)-m_x, \delta O(y)=O(y)-m_y. Раскрытие скобок даёт

\langle\delta O(x)\delta O(y)\rangle_0=\langle O(x)O(y)\rangle_0-m_xm_y-m_xm_y+m_xm_y=\langle O(x)O(y)\rangle_0-m_xm_y.

Для O=\phi^2 имеем m_x=G(x,x) и m_y=G(y,y).

Из трёх спариваний одно соединяет поля только внутри каждой точки и даёт именно m_xm_y. Два других соединяют x с y; каждое даёт G(x,y)^2.

Поэтому после вычитания остаётся множитель 2. Связность в данном двухточечном примере означает ковариацию составных наблюдаемых. Нулевая ковариация сама по себе не является общим доказательством независимости произвольных наблюдаемых.

Полученное выражение называется связной корреляцией:

\langle O(x)O(y)\rangle_{0,c}=2G(x-y)^2.Три спаривания четырёх полей и происхождение множителя два
Три спаривания четырёх полей и происхождение множителя два

Левое спаривание даёт произведение двух отдельных средних. Два правых соединяют точки друг с другом и дают одинаковый вклад. Число 2 получено подсчётом, а не назначено по внешнему виду пузырька.

Свёртка складывает вклады всех разбиений общего импульса: одна линия получает k, другая — оставшийся p-k. Это выражается интегралом произведения D(k)D(p-k) по всем k. Почему произведение в координатах даёт именно такую свёртку в импульсах?

Подставим G(r)=\int d^dk\,e^{ik\cdot r}D(k)/(2\pi)^d в каждый из двух множителей, дав импульсам разные имена k,q. Затем возьмём Фурье-образ по r=x-y:

\int d^dr\,e^{-ip\cdot r}G(r)^2=\int\frac{d^dk\,d^dq}{(2\pi)^{2d}}D(k)D(q)\int d^dr\,e^{i(k+q-p)\cdot r}.

Последний интеграл — Фурье-представление (2\pi)^d\delta^{(d)}(k+q-p). Дельта-функция при интегрировании по q выбирает q=p-k; модуль якобиана равен 1. Из двух множителей (2\pi)^{-d} остаётся один. Таким образом,

\int d^dr\,e^{-ipr}G(r)^2=\int\frac{d^dk}{(2\pi)^d}D(k)D(p-k)=I_d(p).

Здесь I_d — ядро пузырька из двух линий, а полный связный ответ для выбранного O=\phi^2 равен 2I_d. В другой теории другие вершины и статистика частиц дадут свои коэффициенты и знаки. В частности, фермионный отклик плотности в графене нельзя получить простым переносом этого множителя 2: там есть матричные элементы плотности, заполнение уровней и соглашение о знаке отклика, которое разберём в разделе 5.

Для учебного евклидова пропагатора D(k)=1/(k^2+\mu^2) получаем ядро пузырька

I_d(p)=\int\frac{d^dk}{(2\pi)^d}\frac{1}{(k^2+\mu^2)((p-k)^2+\mu^2)}.

Каждая внутренняя линия дала один знаменатель. Один независимый импульс дал один интеграл. Вершины дополнительно дают степени константы связи; одинаковые способы соединения линий определяют комбинаторный коэффициент. Полная амплитуда включает эти множители и сумму других разрешённых диаграмм. Формула выше показывает именно ядро выбранного пузырька.

Чтобы действительно посчитать его несколькими строками, положим d=1 и

\mu>0. Тогда интеграл сходится и

I_1(p)=\frac{1}{\mu(p^2+4\mu^2)}.

Выпишем вычисление до конца. Для d=1 используем уже найденный образ G(x)=e^{-\mu|x|}/(2\mu). Квадрат чётен, поэтому синусная часть Фурье-интеграла сокращается:

I_1(p)=\frac{1}{4\mu^2}\int_{-\infty}^{\infty}dx\,e^{-2\mu|x|}e^{-ipx}=\frac{1}{2\mu^2}\int_0^\infty dx\,e^{-2\mu x}\cos(px).

Чтобы вычислить оставшийся интеграл, представим косинус вещественной частью экспоненты. При

a>0

\int_0^\infty e^{-ax}\cos(px)dx=\operatorname{Re}\int_0^\infty e^{-(a-ip)x}dx=\operatorname{Re}\frac{1}{a-ip}=\frac{a}{a^2+p^2}.

Подстановка a=2\mu даёт I_1(p)=1/[\mu(p^2+4\mu^2)]. При p=0 это 1/(4\mu^3). Условие

\mu>0 нужно и для затухания на бесконечности, и для отсутствия сингулярности при нулевом импульсе. Теперь сравним формулу с непосредственным численным интегрированием исходного произведения двух знаменателей:

import numpy as np
from scipy.integrate import quad

def bubble(p, mu=1.0):
    f = lambda k: 1 / ((k*k + mu*mu) * ((p-k)**2 + mu*mu))
    return quad(f, -np.inf, np.inf)[0] / (2*np.pi)

for p in [0, 1, 2, 5]:
    calculated = bubble(p)
    expected = 1 / (p*p + 4)  # mu = 1
    assert abs(calculated - expected) < 1e-10
    print(p, calculated)

Получатся 0.25, 0.2, 0.125 и примерно 0.0344828. Это одномерная учебная модель. В четырёх измерениях тот же тип интеграла требует отдельного обращения с расходимостью; переносить туда этот конечный ответ нельзя.

Диаграммы появились из разложения экспоненты

Теперь покажем весь переход: вес поля → ряд по взаимодействию → гауссовы средние → спаривания → рисунки → интегралы.

Рисунок помогает не терять множители и одинаковые способы соединения.

Для прозрачности остаёмся в вещественной скалярной евклидовой модели с \hbar=1.

Свободное действие S_0 уже умеем интегрировать. Для сходящегося евклидова среднего берём g\geq0 и при необходимости сохраняем конечную сетку и объём. Добавим

S_{\rm int}=\frac{g}{4!}\int d^dz\,\phi(z)^4.

Величина g управляет силой взаимодействия; её размерность зависит от размерности пространства и нормировки поля. Делитель 4!=24 выбран для удобства комбинаторики. Четыре одинаковых поля в точке взаимодействия пока считаем четырьмя различимыми местами, которые позднее соединим с другими полями.

Нужная двухточечная функция определяется нормированным средним:

C_g(x,y)=\frac{\langle\phi(x)\phi(y)e^{-S_{\rm int}}\rangle_0}{\langle e^{-S_{\rm int}}\rangle_0}.

Индекс 0 означает среднее с нормированным свободным гауссовым весом. Знаменатель нужен, потому что взаимодействие меняет нормировку распределения. Разложение e^{-a}=1-a+a^2/2!-\ldots даёт

e^{-S_{\rm int}}=1-\frac{g}{4!}\int d^dz\,\phi(z)^4+\frac{g^2}{2!(4!)^2}\int d^dz\,d^dw\,\phi(z)^4\phi(w)^4+\ldots.

Почему во втором порядке два интеграла? Квадрат интеграла — произведение двух одинаково устроенных сумм по всем точкам; переменные независимы и получают имена z,w. В общем порядке V:

e^{-S_{\rm int}}=\sum_{V=0}^{\infty}\frac{(-g)^V}{V!(4!)^V}\int d^dz_1\cdots d^dz_V\,\phi(z_1)^4\cdots\phi(z_V)^4.

Это точное разложение экспоненты для фиксированной конфигурации. Перестановка бесконечной суммы и интеграла по всем полям — отдельный вопрос. Пертурбативные коэффициенты вычисляют по порядкам, не предполагая заранее сходимость итогового ряда. Даже конечномерная модель \phi^4 ниже показывает, почему ряд может оказаться асимптотическим.

Как разложение веса превращается в набор диаграмм
Как разложение веса превращается в набор диаграмм

Нулевой и первый порядок: считаем все 15 спариваний

В нулевом порядке C_0(x,y)=G(x,y): одна линия, ни одной вершины взаимодействия. Для первого порядка нужно среднее шести полей \langle\phi(x)\phi(y)\phi(z)^4\rangle_0. У первого поля 5 вариантов партнёра, у следующего непарного — 3, у последнего — 1: всего 5\cdot3\cdot1=15. Разделим их на две группы.

Если x спарено с y, четыре поля в z спариваются тремя способами. Получается 3G(x,y)G(z,z)^2. Если x спарено с одним из четырёх полей в z, для y остаются три; последние два поля соединяются между собой. Получается 4\cdot3=12 одинаковых произведений G(x,z)G(z,y)G(z,z). Итак,

\langle\phi(x)\phi(y)\phi(z)^4\rangle_0=3G(x,y)G(z,z)^2+12G(x,z)G(z,z)G(z,y).

Домножим на коэффициент \frac{-g}{4!}. Для второй группы 12/24=1/2, значит, связанная с внешними точками поправка равна

\Delta C^{(1)}(x,y)=-\frac g2\int d^dz\,G(x,z)G(z,z)G(z,y).

Линия из z в ту же точку образует петлю; такую диаграмму называют «головастиком». Она означает совпадающий аргумент пропагатора, а не круговой полёт частицы. В одномерной массивной модели G(z,z)=1/(2\mu) конечно; в других размерностях совпадающие точки могут требовать регуляризации.

Дерево четырёхточечной функции и однопетлевая поправка двухточечной
Дерево четырёхточечной функции и однопетлевая поправка двухточечной

Слева — другой наблюдаемый объект: связная четырёхточечная функция. В первом порядке каждая из четырёх внешних точек соединяется с одним полем в z: 4! способов сокращают 4! в знаменателе. Получается -g\int d^dz\,\prod_{i=1}^4G(x_i,z), дерево без петель. Справа — только что выведенный головастик. Одинаковая степень g не означает одинаковое число петель у разных наблюдаемых.

Куда делись три оставшихся спаривания

Числитель содержит свободную линию G(x,y), умноженную на отдельный вакуумный рисунок из двух замкнутых линий при z. Знаменатель равен

\langle e^{-S_{\rm int}}\rangle_0=1-\frac g8\int d^dz\,G(z,z)^2+O(g^2),

потому что \langle\phi(z)^4\rangle_0=3G(z,z)^2 и 3/24=1/8. Для отношения (N_0+gN_1)/(1+gD_1) первый порядок равен N_0+g(N_1-N_0D_1). Именно произведение свободной линии на отдельный вакуумный вклад сокращается. До сокращения можно работать на конечной сетке и в конечном объёме, чтобы не вычитать неопределённые выражения.

Явное сокращение несвязного вакуумного вклада знаменателем
Явное сокращение несвязного вакуумного вклада знаменателем

Знаменатель убирает компоненты, никак не связанные с внешними вставками. Он не убирает все петли: головастик связан с x,y и остаётся. Для четырёхточечной функции дополнительно вычитают произведения двухточечных функций, чтобы выделить связную часть.

Второй порядок: один двухпетлевой вклад целиком

Во втором порядке две вершины z,w. Рассмотрим рисунок с одной внешней линией у каждой вершины и тремя линиями между ними — «sunset». Внешние x,y можно распределить по вершинам двумя способами. Для каждого: 4 выбора поля у первой вершины, 4 у второй и 3! способов соединить оставшиеся тройки. Получается 2\cdot4\cdot4\cdot3!=192 спаривания. Делим на 2!(4!)^2=1152:

C_{\rm sunset}^{(2)}(x,y)=\frac{g^2}{6}\int d^dz\,d^dw\,G(x,z)G(z,w)^3G(w,y).Двухпетлевая диаграмма и полный подсчёт её коэффициента
Двухпетлевая диаграмма и полный подсчёт её коэффициента

Внутренних линий 3, внутренних вершин 2, поэтому L=3-2+1=2. Это одно слагаемое второго порядка, не полный коэффициент g^2. Код перебирает все 945 спариваний десяти помеченных полей и независимо выделяет 192 нужных.

Для однородной системы перейдём к импульсам. Каждое G раскладывается по плоским волнам, интеграл по положению вершины даёт дельта-функцию сохранения импульса. Внутри sunset остаются два независимых импульса k,q, а третья линия несёт p-k-q.

C_{\rm sunset}^{(2)}(p)=\frac{g^2}{6}D(p)^2\int\frac{d^dk}{(2\pi)^d}\frac{d^dq}{(2\pi)^d}D(k)D(q)D(p-k-q).

Множитель D(p)^2 — две внешние линии полной двухточечной корреляции. Если обсуждается ампутированная функция, их убирают по определению; без оговорки два правильных ответа выглядят несовместимыми. «Ампутация» здесь означает удаление внешних пропагаторов.

Для проверки вернёмся к головастику в одном измерении: G(x,y)=e^{-\mu|x-y|}/(2\mu). При x=y=0 интеграл элементарен:

\Delta C^{(1)}(0,0)=-\frac g2\frac1{8\mu^3}\int_{-\infty}^{\infty}e^{-2\mu|z|}dz=-\frac{g}{16\mu^4}.

При g=0.2, \mu=1 получаем -0.0125; независимая квадратура в новом скрипте даёт то же значение. Это проверка одного коэффициента, а не утверждение, что первый порядок равен точному взаимодействующему ответу.

Теперь соответствие конкретно:

степень ряда задаёт число вставок взаимодействия;

поля — концы линий;

спаривания — соединения;

интегралы — внутренние координаты;

коэффициент учитывает число одинаковых соединений и факториалы.

Число петель зависит от всего графа и внешних вставок. В фермионной теории добавятся знаки от перестановок, матричные индексы и другие вершины, поэтому коэффициенты скалярного примера нельзя напрямую переносить в графен.

Исторические работы — Фейнман (1949), Дайсон (1949) и Вик (1950).

Переход от разложения к спариваниям и диаграммам есть в лекциях Тонга.

От плоских волн к базису Ландау: теперь решаем задачу в магнитном поле

Базис — это набор известных состояний, по которым можно разложить неизвестное. Без поля свободные плоские волны диагонализуют импульс. В постоянном поле удобнее состояния, уже включающие его действие: иначе пришлось бы каждый раз собирать эффект поля из бесконечного числа плоских волн.

Пусть электрон имеет заряд -e,

B>0 направлено вдоль z, а векторный потенциал выбран как \mathbf A=(0,Bx,0).

Это калибровка Ландау: она даёт \nabla\times\mathbf A=(0,0,B).

Векторный потенциал \mathbf A — вспомогательное поле, через которое магнитное поле записывается как \mathbf B=\nabla\times\mathbf A; ротор \nabla\times содержит разности перекрёстных производных. Один и тот же \mathbf B можно представить разными \mathbf A: замена \mathbf A\to\mathbf A+\nabla f не меняет ротор.

Выбор такого представления называют калибровкой.

Волновую функцию тоже нужно преобразовать согласованно: для электрона \psi\to e^{-ief/\hbar}\psi, а измеримые вероятности сохраняются.

Два векторных потенциала одного магнитного поля
Два векторных потенциала одного магнитного поля

У калибровки Ландау \mathbf A_L=(0,Bx) и симметричной \mathbf A_S=(-By/2,Bx/2) одинаковый ротор. Их разность — \nabla(Bxy/2). Рисунок показывает стрелки потенциала, не силовые линии магнитного поля и не скорости частиц.

Канонический импульс здесь — оператор \mathbf p=-i\hbar\nabla, связанный с выбором координат и фазы.

Кинетический импульс \boldsymbol\pi=\mathbf p+e\mathbf A входит в энергию движения электрона. Для обычной массивной частицы он равен m\mathbf v; для графенового линейного гамильтониана такую формулу с постоянной массой использовать нельзя. Сначала посмотрим на обычный массивный электрон:

H_{\rm Sch}=\frac{p_x^2+(p_y+eBx)^2}{2m}.

Координата y явно не входит в оператор.

Поэтому ищем решение в виде \psi(x,y)=e^{ik_y y}\phi(x)/\sqrt{L_y}. Тогда p_y заменяется числом \hbar k_y, и остаётся одномерное уравнение

\left[-\frac{\hbar^2}{2m}\frac{d^2}{dx^2}+\frac{m\omega_c^2}{2}(x-x_0)^2\right]\phi=E\phi,\quad\omega_c=\frac{eB}{m},\quad x_0=-l_B^2k_y.

Это уравнение гармонического осциллятора, центр которого сдвинут в x_0.

Величина \omega_c=eB/m называется циклотронной угловой частотой обычного массивного электрона, потому что она равна частоте его вращения в постоянном магнитном поле, которая вычисляется на основе обычной классической механики (уравнения Ньютона F = ma).

Она появляется здесь еще и как частота получившегося квантового осциллятора.

Его нормируемые решения дают E_n=\hbar\omega_c(n+1/2) и функции

\phi_n(x-x_0)=\frac{H_n(\xi)e^{-\xi^2/2}}{\pi^{1/4}\sqrt{2^n n!\,l_B}},\qquad \xi=\frac{x-x_0}{l_B}.

Здесь H_n — полином Эрмита: H_0=1, H_1=2\xi, H_2=4\xi^2-2.

Они появляются при поиске нормируемого решения. Дело в том, что после выделения гауссовой экспоненты степенной ряд должен оборваться, иначе функция растёт на бесконечности. Именно это условие обрыва и квантует энергию.

Осцилляторные функции и вырождение уровня по центру орбиты
Осцилляторные функции и вырождение уровня по центру орбиты

Слева функции с разным числом узлов. Вертикальные сдвиги добавлены только для читаемости. Справа три состояния одного уровня с разными x_0: энергия одна, расположение плотности разное. При периодической границе по y соседние k_y отличаются на 2\pi/L_y, а центры — на 2\pi l_B^2/L_y. На площади A=L_xL_y помещается примерно A/(2\pi l_B^2) состояний одного уровня на одну внутреннюю компоненту. Так возникает вырождение Ландау; спин и долины добавляют к нему свои множители.

Теперь возвращаемся к графену. В дираковском гамильтониане заменяем \mathbf p на \boldsymbol\pi. Поскольку [\pi_x,\pi_y]=-i\hbar eB, операторы

a=\frac{l_B}{\sqrt2\hbar}(\pi_x-i\pi_y),\qquad a^\dagger=\frac{l_B}{\sqrt2\hbar}(\pi_x+i\pi_y)

удовлетворяют [a,a^\dagger]=1. В базисе состояний |n\rangle с подходящими фазами

a|n\rangle=\sqrt n|n-1\rangle, a^\dagger|n-1\rangle=\sqrt n|n\rangle.

Конкретно, для вещественных функций \phi_n выше можно выбрать \langle x|n\rangle=i^n\phi_n(x-x_0): тогда множитель -i в операторе понижения поглощается фазами. Плотности вероятности на рисунке от этого не меняются.

Положим \Omega_B=\sqrt2\hbar v_F/l_B.

Для одной долины при n\geq1 подстановка этого значения дает

\Psi_{s,n}=\frac{1}{\sqrt2}\begin{pmatrix}s|n-1\rangle\\|n\rangle\end{pmatrix},\qquad H_0\Psi_{s,n}=s\Omega_B\sqrt n\,\Psi_{s,n},\qquad s=\pm1.

Проверка занимает всего лишь одну строчку. Верхняя компонента после действия a равна \sqrt n|n-1\rangle, нижняя после a^\dagger — s\sqrt n|n\rangle.

Нулевое состояние устроено особым образом:

\Psi_0=(0,|0\rangle)^T, поскольку a|0\rangle=0.

В другой долине меняется распределение по подрешёткам. Так из уравнения получаются

E_n=\operatorname{sgn}(n)\hbar v_F\sqrt{2|n|}/l_B.

Разложение по состояниям удобно записывать с помощью проекторов.

Проектор — это оператор выделения компоненты.

Для нормированного состояния |n\rangle оператор P_n=|n\rangle\langle n| оставляет только компоненту вдоль него:

если |\psi\rangle=\sum_m c_m|m\rangle, то P_n|\psi\rangle=c_n|n\rangle.

Он удовлетворяет P_n^2=P_n.

Сумма проекторов полного ортонормированного базиса равна единичному оператору.

«Ортонормированный» означает

\langle n|m\rangle=\delta_{nm}.

Проще говоря, разные состояния ортогональны, каждое нормировано на единицу.

Проекция состояния и различие фермионного и бозонного заполнения
Проекция состояния и различие фермионного и бозонного заполнения

Наверху |\psi\rangle=(\sqrt3/2)|0\rangle+(1/2)|1\rangle: вероятность результата 0 равна \langle\psi|P_0|\psi\rangle=3/4. Ниже номера обозначают полные одночастичные состояния.

Много электронов на одном уровне Ландау не нарушает принципа Паули здесь, потому что у каждого уровня тут много разных состояний в зависимости от расположения центра орбиты, значений спина и долины.

Несколько независимых состояний с одинаковой энергией — это в квантовой механике называется вырождением.

Итак, электронную линию удобно раскладывать в сумму по \Psi_{n,k_y}, в которой каждый член содержит проектор на состояние и энергетический знаменатель.

Пузырёк соединяет два таких состояния, поэтому возникают два номера уровня, заполнения и матричный элемент плотности между ними.

Сумма по центрам орбит учитывает вырождение.

Исчезновение явного интеграла по одному импульсу не означает исчезновения петли — просто изменился базис, который использовался в расчетах.

Полный код учебных диаграмм и однопетлевого интеграла: 08_diagrams.py
"""От 0 до 2 петель: топологии связной четырёхточечной функции φ⁴.
Не все диаграммы каждого порядка. Численный пример — отдельная 1D модель.
"""
from pathlib import Path
import numpy as np
from scipy.integrate import quad
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt

def publication_layout(fig):
    """Readable labels at a 600 px article width; retain data and panel order."""
    import textwrap
    from matplotlib.text import Text
    width, height = fig.get_size_inches()
    fig.set_size_inches(width, height * 1.22, forward=True)
    scale = width / 8.0
    main, small = 14.0 * scale, 12.0 * scale
    for text in fig.findobj(Text):
        if text.get_text().strip():
            text.set_fontsize(max(text.get_fontsize(), main))
            if text.get_color() in ['#d77b27', '#ed8b36', '#df7b28', '#d77527']:
                text.set_color('#87470f')
    for ax in fig.axes:
        for tick in ax.get_xticklabels() + ax.get_yticklabels():
            tick.set_fontsize(small)
        for label in [ax.xaxis.label, ax.yaxis.label]:
            txt = label.get_text()
            if '$' not in txt:
                label.set_text('\n'.join(textwrap.fill(s, 39) for s in txt.splitlines()))
        title = ax.get_title()
        if '$' not in title:
            columns = max(25, int(ax.get_position().width * 75))
            ax.set_title('\n'.join(textwrap.fill(s, columns) for s in title.splitlines()),
                         fontsize=main, pad=12)
        legend = ax.get_legend()
        if legend:
            for text in legend.get_texts(): text.set_fontsize(small)
        for line in ax.lines: line.set_linewidth(max(line.get_linewidth(), 1.8))
    if fig._suptitle:
        txt = fig._suptitle.get_text()
        if '$' not in txt:
            fig._suptitle.set_text(textwrap.fill(txt, 57))
        fig._suptitle.set_fontsize(max(main + 2, 18))
    for text in fig.texts:
        if text is fig._suptitle: continue
        txt=text.get_text()
        if '$' not in txt:
            text.set_text('\n'.join(textwrap.fill(s, 68) for s in txt.splitlines()))
    if type(fig.get_layout_engine()).__name__ == 'PlaceHolderLayoutEngine':
        fig.tight_layout(rect=(.01,.10,.99,.91),h_pad=2,w_pad=2)
    if type(fig.get_layout_engine()).__name__ == 'ConstrainedLayoutEngine':
        fig.get_layout_engine().set(h_pad=.15, w_pad=.12)


OUT=Path(__file__).resolve().parent
plt.rcParams.update({'font.family':'DejaVu Sans','font.size':12})
fig,axes=plt.subplots(1,3,figsize=(11,3.9),layout='constrained')
for L,ax in enumerate(axes):
    positions=np.linspace(-.6,.6,L+1) if L else [0]
    for x in positions: ax.plot(x,0,'o',color='#1e678a',ms=8)
    for x,y in zip(positions[:-1],positions[1:]):
        t=np.linspace(0,np.pi,100)
        for sign in [-1,1]: ax.plot((x+y)/2+(y-x)/2*np.cos(t),sign*.3*np.sin(t),color='#1e678a',lw=2)
    for x,side in [(positions[0],-1),(positions[-1],1)]:
        for sign in [-1,1]: ax.plot([x,x+side*.35],[0,sign*.55],color='#1e678a',lw=2)
    ax.text(0,-.93,f'I={2*L}, V={L+1}\nL=I−V+1={L}',ha='center',va='center')
    ax.set(xlim=(-1.1,1.1),ylim=(-1.25,.95),title=['0 петель','1 петля','2 петли'][L]);ax.axis('off')
axes[1].text(0,.4,'k',ha='center');axes[1].text(0,-.49,'p−k',ha='center')
publication_layout(fig); fig.savefig(OUT/'loops_reading.png',dpi=150, bbox_inches="tight");plt.close(fig)

# Для μ>0 интеграл сходится. Общие множители вершин не включены.
def bubble(p,mu=1):
    integrand=lambda k:1/((k*k+mu*mu)*((p-k)**2+mu*mu))/(2*np.pi)
    return quad(integrand,-np.inf,np.inf,epsabs=1e-11)[0]
for p in [0,1,2,5]:
    exact=1/(1*(p*p+4))
    assert abs(bubble(p)-exact)<1e-10
    print('p=',p,'I(p)=',bubble(p),'exact=',exact)

5. Разбираем мой старый Mathematica notebook

Файл operator.nb создан в Mathematica 7.

Сначала договоримся о единицах.

Длины измеряются в l_B, энергии уровней Ландау — в \hbar v_F/l_B, импульсы — в \hbar/l_B. После этого переменные внутри функций безразмерны.

Кулоновское ядро VV ниже оставлено в собственной нормировке архива, с числителем 2\pi: его численное значение не следует автоматически считать энергией в тех же единицах. Буквой m в коде обозначен номер последнего заполненного неотрицательного уровня, а N — верхняя граница суммы.

В исходнике эта граница называется max и равна 15.

Число 15 ограничивает индексы уровней в суммах, а не количество петель.

Отрицательная ветвь и нулевой уровень учитываются отдельно.

От факториалов к волновой функции

Первая функция в notebook:

ff[n1_, n2_] = (Min[n1,n2]! / Max[n1,n2]!)^0.5

Она задаёт нормировочный множитель.

Если обозначить a=\min(n_1,n_2) и b=\max(n_1,n_2), это \sqrt{a!/b!}.

Следующее определение fg добавляет зависимость от координаты:

f_{n_1n_2}(r)=\frac{\sqrt{a!/b!}}{\sqrt{2\pi}}e^{-r^2/4}\left(\frac{r}{\sqrt{2}}\right)^{b-a}L_a^{b-a}\left(\frac{r^2}{2}\right).

Это радиальная часть осцилляторной функции.

Полная функция также имеет свою угловую фазу.

Экспонента подавляет большие расстояния.

Степень r задаёт поведение около центра.

Полином Лагерра L_a^{b-a} даёт узлы и структуру возбуждённых состояний.

Факториалы и 1/\sqrt{2\pi} обеспечивают нормировку.

L_0^\alpha(x)=1,\qquad L_1^\alpha(x)=1+\alpha-x.

Поэтому

f_{00}(r)=\frac{e^{-r^2/4}}{\sqrt{2\pi}},\qquad f_{10}(r)=\frac{r}{\sqrt2}\frac{e^{-r^2/4}}{\sqrt{2\pi}},f_{11}(r)=\left(1-\frac{r^2}{2}\right)\frac{e^{-r^2/4}}{\sqrt{2\pi}}.

Для этих состояний можно проверить

2\pi\int_0^\infty r\,dr\,|f_{ab}(r)|^2=1.

Множитель r появился из площади тонкого кольца: d^2r=r\,dr\,d\theta. Его потеря даст гладкую, но неправильно нормированную кривую.

Гамма-функция продолжает факториал за пределы целых аргументов: при

z>0 она определяется интегралом

\Gamma(z)=\int_0^\infty t^{z-1}e^{-t}dt,

а \Gamma(n+1)=n!.

В Python вместо огромных факториалов удобнее использовать её логарифм:

\log(n!)=\mathrm{gammaln}(n+1).

Эта та же формула, но вычисляется более устойчиво на компьютере.

import numpy as np
from scipy.special import eval_genlaguerre, gammaln

def fg(n1, n2, r):
    if any(isinstance(n, (bool, np.bool_))
           or not isinstance(n, (int, np.integer)) or n < 0 for n in (n1, n2)):
        raise ValueError("n1 and n2 must be nonnegative integers")
    a, b = min(n1, n2), max(n1, n2)
    if np.iscomplexobj(r):
        raise ValueError("r must be real")
    r = np.asarray(r, dtype=float)
    if np.any(~np.isfinite(r)) or np.any(r < 0):
        raise ValueError("r must be finite and nonnegative")
    return (np.exp((gammaln(a+1)-gammaln(b+1))/2-r*r/4)
            * (r/np.sqrt(2))**(b-a)
            * eval_genlaguerre(a, b-a, r*r/2) / np.sqrt(2*np.pi))

Здесь индексы принимают неотрицательные значения. В полном скрипте отсутствующие компоненты с отрицательным осцилляторным индексом отдельно обращаются в ноль.

Формфактор: как структура состояния отвечает на пространственную волну

Пусть внешнее воздействие меняется в пространстве как e^{ikx}.

Для распределённого объекта разные его части получают разные фазы.

Простейший пример формфактора в том, чтобы просто суммировать эти вклады:

F(k)=\int dx\,\rho(x)e^{ikx},\qquad\int\rho(x)dx=1.

При k=0 все фазы одинаковы и F(0)=1.

При короткой длине волны 2\pi/|k| они частично гасят друг друга.

Для гауссова профиля с центром в нуле и дисперсией \sigma^2 получаем F(k)=e^{-\sigma^2k^2/2}. Если тот же вес поровну распределён между двумя гауссовыми облаками с центрами \pm a, то F(k)=e^{-\sigma^2k^2/2}\cos(ka): возникают нули интерференции.

Знак F и фаза несут информацию; интенсивность |F|^2 её частично теряет.

Откуда эти две формулы?

Для нормированного гауссова профиля

\rho'(x)=-x\rho(x)/\sigma^2.

Дифференцируем F(k) по k и интегрируем по частям:

F'(k)=i\int x\rho(x)e^{ikx}dx=-i\sigma^2\int\rho'(x)e^{ikx}dx=-\sigma^2kF(k).

Граничный член исчезает из-за гауссова затухания.

Решение этого уравнения с F(0)=1 — e^{-\sigma^2k^2/2}.

Сдвиг профиля на a даёт множитель e^{ika} после замены x=u+a.

Поэтому среднее двух одинаковых профилей в \pm a имеет множитель (e^{ika}+e^{-ika})/2=\cos(ka).

Так интерференционный косинус получается непосредственно из двух сдвигов.

Один и два пространственных пика и соответствующие формфакторы
Один и два пространственных пика и соответствующие формфакторы

Большой объект быстрее замечает изменение фазы волны. Два центра дают дополнительные взаимные гашения. Кривые справа вычислены из профилей слева и проверены независимым интегрированием.

Можно обобщить формулу на переход между двумя квантовыми состояниями. Получается

F_{mn}(k)=\langle m|e^{ikx}|n\rangle.

Это тот самый матричный элемент плотности, который входит в пузырёк отклика.

Диагональный случай сводится к преобразованию плотности |\psi_n|^2, а внедиагональный содержит \psi_m^*\psi_n.

Из ортонормированности следует

F_{mn}(0)=\delta_{mn},

из сопряжения — F_{mn}(k)^*=F_{nm}(-k).

Обе связи полезны как контрольные тесты для проверки правильности работы программы.

Здесь k — волновое число с размерностью обратной длины; физический переданный импульс равен \hbar k. В формулах отклика ниже q внутри e^{i\mathbf q\cdot\mathbf r} также означает волновой вектор; в безразмерном коде это k l_B, численно равное импульсу в единицах \hbar/l_B.

Для радиального состояния связь особенно наглядна. Здесь пара индексов (a,b) задаёт одно осцилляторное состояние, а не начальное и конечное состояния перехода.

Назовём всю эту пару \alpha=(a,b) и обозначим диагональный формфактор иначе:

\mathcal F_\alpha(q)=2\pi\int_0^\infty r\,dr\,J_0(qr)|f_{ab}(r)|^2,\qquad \mathcal F_\alpha(0)=1.

Это не противоречит F_{mn}(0)=\delta_{mn}: в первом случае берётся плотность одного нормированного состояния, во втором — перекрытие двух состояний.

Диагональный формфактор одного состояния и формфактор перехода
Диагональный формфактор одного состояния и формфактор перехода

Верхняя панель использует двумерные радиальные функции диплома: \mathcal F_{(0,0)}=e^{-q^2/2}, \mathcal F_{(1,0)}=(1-q^2/2)e^{-q^2/2}. Нижняя — отдельный одномерный осциллятор в безразмерных единицах: F_{00}=e^{-q^2/4} и F_{01}=iqe^{-q^2/4}/\sqrt2. Показана мнимая часть последнего. Оба диагональных формфактора наверху дают 1 при нуле; переход между разными состояниями внизу даёт 0. Разные гауссовы масштабы относятся к двум явно указанным примерам.

Внутренний интеграл раздела 7 равен \mathcal F_\alpha(q)/(2\pi) в принятой там нормировке. Так специальная функция под двойным интегралом становится откликом пространственного состояния на заданную гармонику.

В физике амплитуд термин “матричный элемент” используется гораздо шире: например,

\langle p_1,p_2,p_3|\mathcal O(0)|0\rangle —

матричный элемент локального оператора между вакуумным состоянием и состоянием частиц с указанными импульсами.

Оператор вносит суммарный импульс q=p_1+p_2+p_3.

Это иной объект, чем амплитуда рассеяния без такой вставки, и иной конкретный формфактор, чем плотностный F_{mn} в графене.

Общая идея — матричный элемент заданного воздействия.

Хороший обзор с определениями и примерами можно найти тут: Gang Yang, «On-shell Methods for Form Factors…».

Почему в поляризации есть сумма, разность и энергетический знаменатель

Рассмотрим переход с занятого состояния a на свободное b. Вес такого перехода в спектральном отклике содержит квадрат модуля матричного элемента воздействия.

Сам этот отклик — не вероятность: он может быть отрицательным или комплексным.

Что же означает «спектральный запаздывающий отклик плотности»?

Разберём каждое слово.

Плотность n(\mathbf r,t) — число электронов на единицу площади.

Слабое внешнее воздействие U_{\rm ext} здесь обозначает потенциальную энергию электрона во внешнем поле. Оно слегка изменяет распределение плотности заряда в пространстве. В линейном приближении изменение пропорционально воздействию:

\delta n(q,\omega)=\chi^R(q,\omega)U_{\rm ext}(q,\omega).

Индекс q выделяет пространственную гармонику, \omega — частоту.

Например, воздействие \cos(\mathbf q\cdot\mathbf r-\omega t) создаёт бегущую модуляцию, и коэффициент отклика показывает, насколько сильно и с какой задержкой система отвечает на эту модуляцию.

Если система стационарна и однородна, линейный ответ не смешивает разные частоты и волновые векторы. Поэтому преобразование Фурье превращает интегральную связь в произведение для каждой гармоники.

В неоднородной системе нужен более общий ответ с двумя пространственными аргументами.

Запаздывающий, или retarded, означает причинный: изменение в момент t зависит только от более раннего воздействия. До толчка отклик на этот толчок равен нулю.

В квантовой записи это обеспечивается ступенькой \theta(t) в формуле Кубо (здесь схема для одной гармоники, с нормировкой плотности на площадь):

\chi^R(q,t)=-\frac{i}{\hbar A}\theta(t)\langle[\hat n_q(t),\hat n_{-q}(0)]\rangle.

Квадратные скобки — коммутатор AB-BA, угловые — среднее по исходному равновесному состоянию.

Чтобы за формулой не пряталась нормировка, обозначим площадь большого образца через A, пренебрежем краевыми эффектами. Определим \hat n_q=\sum_{j=1}^{N_e}e^{-i\mathbf q\cdot\mathbf r_j}, где N_e — число электронов, а j нумерует их координаты. Это Фурье-компонента оператора числа электронов, без множителя заряда. Коэффициент плотности равен \langle\hat n_q\rangle/A; поэтому в формуле Кубо стоит 1/A. Внешнюю потенциальную энергию раскладываем по тем же пространственным гармоникам. Разность двух порядков операторов выражает изменение наблюдаемой величины под воздействием. Эту формулу получают, разложив оператор эволюции до первого порядка по внешнему полю — тот же принцип «сначала решённая задача, затем малая добавка».

Спектральная запись означает, что мы вставили между операторами полный набор собственных состояний энергии: 1=\sum_a|a\rangle\langle a|. Эволюция каждого матричного элемента даёт фазу e^{i(E_a-E_b)t/\hbar}. Интеграл по положительным временам с малым затуханием превращает фазу в знаменатель дроби, состоящий из суммы энергий:

-\frac{i}{\hbar}\int_0^\infty dt\,e^{i(\hbar\omega+E_a-E_b+i\eta)t/\hbar}=\frac{1}{\hbar\omega+E_a-E_b+i\eta},\qquad\eta>0.

Предел \eta\to0+ обычно пишут как +i0. Для иллюстрации можно оставить маленькую ширину, но ее конечное значение дополнительно уширяет спектр. Для независимых электронов оценка коммутатора даёт разность заполнений f_a-f_b, а два матричных элемента плотности — квадрат модуля. Так приходим к следующей формуле. Для взаимодействующей системы точная спектральная сумма для \chi^R идёт по многочастичным состояниям. Если среднее и эволюцию вычислять для независимых электронов, получаем \chi_0^R=\Pi_0^R: это и есть пузырёк, используемый в RPA.

Причинность во времени и спектральный пик разрешённого перехода
Причинность во времени и спектральный пик разрешённого перехода

На рисунке взята пара уровней с разностью энергий 1 из учебного примера и единичным матричным элементом, \hbar=1. Слева отклик нулевой до t=0. Справа пик при \omega=1 означает совпадение частоты воздействия с энергетической ценой перехода. Ширина \eta=0.08 выбрана для видимости; это не измеренное время жизни графенового возбуждения.

В спектральной записи пузырёк независимых электронов имеет следующую структуру

\Pi_0^R(q,\omega)=\frac1A\sum_{a,b}\frac{f_a-f_b}{\hbar\omega+E_a-E_b+i0}\,|\langle b|e^{i\mathbf q\cdot\mathbf r}|a\rangle|^2.

Здесь a,b пробегают все нормированные одночастичные состояния, включая центр орбиты, спин и долину, когда они присутствуют. Если эти внутренние индексы уже входят в сумму, отдельный множитель вырождения добавлять нельзя. Если одинаковые компоненты вынесены из суммы, их число учитывается отдельно. Размерность \Pi_0^R при этой нормировке — обратные энергия и площадь. f_a,f_b\in[0,1] — средние числа заполнения состояний. Если оба состояния одинаково заполнены, разность равна нулю. Малое +i0 выбирает причинный отклик: система отвечает после воздействия. Это правило аналитического продолжения, а не параметр для подгонки графика.

В notebook вся эта общая структура разобрана на три семейства. Для положительных индексов a,b введены \epsilon_a=\sqrt{2a} и

P_+(a,b;q)=\frac{[f_{ab}(q)+f_{a-1,b-1}(q)]^2}{4(\epsilon_b-\epsilon_a)},P_-(a,b;q)=-\frac{[f_{ab}(q)-f_{a-1,b-1}(q)]^2}{4(\epsilon_b+\epsilon_a)},P_0(a;q)=-\frac{f_{a0}(q)^2}{2\epsilon_a}.

Они соответствуют функциям PP, PM, PO. Сумма и разность двух осцилляторных компонентов связаны с двухкомпонентными графеновыми состояниями и знаками ветвей спектра. Для перехода между положительными уровнями возникает разность положительных энергий; для перехода через отрицательную и положительную ветви — их сумма. Нулевой уровень выделен отдельно, поэтому у PO другой множитель нормировки.

Итоговое определение Sigma1 буквально имеет вид

\Sigma_{m,N}(q)=\sum_{a=m+1}^{N}\sum_{b=1}^{m}P_+(a,b;q)+\sum_{a=m+1}^{N}\sum_{b=1}^{N}P_-(a,b;q)+\sum_{a=m+1}^{N}P_0(a;q).

В первой сумме

a>b, поэтому деления на ноль при равных уровнях нет. При m=0 вся первая сумма пуста. Вторая сумма учитывает переходы, связанные с отрицательной ветвью; третья — вклад нулевого уровня. Обозначение \Sigma здесь лишь короткое имя функции Sigma1.

Радиальные функции и поляризация, восстановленные из notebook
Радиальные функции и поляризация, восстановленные из notebook

Слева нарисована именно радиальная плотность 2\pi r|f|^2: площадь под каждой кривой равна единице. Справа — результат старой формулы Sigma1 при четырёх заполнениях и общей границе N=15.

6. Восстановить код ещё не значит подтвердить физику

В исходном notebook сохранены не только определения, но и результаты некоторых вызовов функций. Это позволяет проверить перенос независимо от визуального сходства графиков.

Проверяемый вызов

Сохранённый ответ Mathematica

Python

fg(1, 2, 5)

−0.020216169102072835

−0.020216169102072835

VV(5, 1, 0, 15)

1.0290829678673687

1.029082967867369

II(0.1, 1, 3, 5, 0)

0.2288012805693556

0.22880128056935545

II(1, 1, 2, 3, 1)

0.11974542810169851

0.11974542810169861

Последние различия — на уровне округления чисел с плавающей точкой. Но пока проверено только то, что новый код считает те же самые выражения. Ошибка исходной формулы вполне могла бы перенестись вместе с ней.

Первое расхождение: коэффициент экранирования

В notebook записано:

VV[q_, epsilon_, m_, max_] =
    2*Pi / (q - 8*Sigma1[q,m,max]*Pi/(2.19*epsilon))

То есть буквально

V_{\rm nb}(q)=\frac{2\pi}{q-8\pi\Sigma_{m,N}(q)/(2.19\varepsilon)}.

На странице 24 диплома приведено

V_{\rm text}(q)=\frac{2\pi}{q-8\pi r_s\Pi(q)},\qquad r_s\approx\frac{2.19}{\varepsilon}.

При отождествлении использованной здесь \Pi с Sigma1 это разные коэффициенты: 1/(2.19\varepsilon) и 2.19/\varepsilon. Отличие почти в 4.8 раза.

Для окончательного исправления нужно восстановить все нормировки и обозначения из первоначального расчёта. Выбрать один вариант только потому, что его график нравится больше, было бы подгонкой.

Поэтому скрипт содержит две явно названные функции: VV_archive воспроизводит notebook, VV_text показывает вторую запись при одинаковой нормировке \Sigma. Старые контрольные числа сравниваются с первой, а различие двух вариантов вынесено на график.

Второе расхождение: единицы и размерность

Ошибку в записи единицы энергии на странице 23 мы уже разобрали в начале статьи; здесь используется масштаб \hbar v_F/l_B. Есть и другая несогласованность: на странице 24 в координатном матричном элементе также встречается d^3r, хотя далее вычисляется двумерный радиальный интеграл. В статье эта мера записана как d^2r.

Проверка размерностей помогает быстрее, чем запуск сложного расчёта.

Третья проверка: достаточно ли пятнадцати уровней

Можно написать сумму хоть до бесконечности, но компьютер всё равно должен получить задачу с конечным количеством вычислений. В notebook оставлены уровни до N=15.

N

\Sigma_{0,N}(q=1)

10

−0.02617231

15

−0.02659943

30

−0.02714981

60

−0.02753810

Результат меняется. Особенно хорошо это видно при больших q:

Проверка границы суммы и сравнение коэффициентов экранирования
Проверка границы суммы и сравнение коэффициентов экранирования

Следовательно, совпадение четырёх контрольных значений подтверждает перенос программы, но не достаточность обрезания N=15 во всём диапазоне. Правая картинка демонстрирует другую причину расхождения — коэффициент в формуле. У этих двух неопределённостей разные источники, и проверять их нужно раздельно.

7. От экранированного взаимодействия к двойному интегралу

Осталось связать вычисленное V(q) с поправкой для конкретного состояния пары. Возьмём один радиальный компонент f_{ab}(r). Взаимодействие сдвигается относительно этого состояния на величину, связанную с магнитным импульсом пары. В безразмерных переменных модуль сдвига равен P.

Переход в импульсное представление особенно удобен для взаимодействия: там экранирование уже записано простой дробью. После угловых интегралов в радиальном матричном элементе появляются функции Бесселя:

M_{ab}(P)=\int_0^\infty q\,dq\,V(q)J_0(qP)\int_0^\infty r\,dr\,J_0(qr)|f_{ab}(r)|^2.

Это формула в нормировке радиальной функции notebook. Два множителя q и r — якобианы полярных координат. Их нельзя удалить при переносе на прямоугольную сетку.

Откуда здесь J_0? Это результат усреднения плоской волны по направлению:

J_0(z)=\frac{1}{2\pi}\int_0^{2\pi}e^{iz\cos\theta}\,d\theta.

Векторный интеграл сворачивается в радиальный, но информация об углах сохраняется в специальной функции. При P=0 имеем J_0(qP)=1. При ненулевом P этот множитель осциллирует, и некоторые вклады взаимно сокращаются.

В notebook есть и попытка определить эту функцию самостоятельно, через ряд BS.

J_0(z)=\sum_{k=0}^{\infty}\frac{(-1)^k}{(k!)^2}\left(\frac{z}{2}\right)^{2k}.

Начало ряда — 1-z^2/4+z^4/64-\ldots. В архиве вызов численного NSum с ещё символическим аргументом сопровождался сообщениями nsnum. В Python для вычисления значений используется готовая scipy.special.j0.

Теперь можно прочитать последнюю важную строку notebook:

II[q_,r_,m1_,m2_,P_] =
  fg[m1,m2,r]^2 * BesselJ[0,q*P] * BesselJ[0,q*r]
  * q*r * VV[q,1,m,max]

II — подынтегральная функция. В ней q — передаваемый внутренний импульс, P — внешний магнитный импульс пары, r — радиальная координата. Эти три аргумента выполняют разные роли.

Вызов NIntegrate использовал границы от 0.01 до 10 по обеим переменным и AccuracyGoal -> 3. Таким образом, были сделаны сразу два приближения: обрезание бесконечных интервалов и численное интегрирование на конечном прямоугольнике. Заданная точность алгоритма относится ко второй задаче. Она не оценивает автоматически отброшенные хвосты и участок около нуля.

Что значит «посчитать интеграл квадратурой»? Квадратурная формула заменяет интеграл взвешенной суммой \int_a^b f(x)dx\approx\sum_i w_i f(x_i). В квадратуре Гаусса узлы x_i выбираются специальным образом, а не через равные расстояния. Для веса 1 на [-1,1] это нули полинома Лежандра степени n — полинома, ортогонального всем меньшим степеням относительно интеграла на этом отрезке. Узлы и веса обеспечивают точность для любого полинома степени до 2n-1 включительно, если считать в точной арифметике.

Три узла квадратуры Гаусса и их веса
Три узла квадратуры Гаусса и их веса

При n=3 узлы 0,\pm\sqrt{3/5} и веса 8/9,5/9,5/9 точно интегрируют x^4: сумма равна 2/5. Произвольная осциллирующая функция этим обещанием не покрывается. На другом отрезке нужны сдвиг и масштаб узлов, а веса умножаются на (b-a)/2. В двойном интеграле используются пары узлов и произведения весов.

Для гладкого интеграла увеличение числа узлов часто улучшает результат, но не исправляет неверную меру и не возвращает обрезанные хвосты. Поэтому в нашей проверке число узлов и границы интегрирования — два независимых параметра опыта.

Для компонента a=b=5, P=0 сохранился ответ 0.5262355639062104. Пересчёт той же подынтегральной функции на том же прямоугольнике квадратурой Гаусса даёт 0.5258440773. Увеличение числа узлов с 350 до 650 по каждой координате меняет результат примерно на 6.4\cdot10^{-14}; отличие от старого ответа составляет примерно 3.9\cdot10^{-4}.

Полный код восстановления notebook и проверок: 06_graphene.py
"""Восстановление формул operator.nb (Mathematica 7) и учебные проверки.
python 06_graphene.py
Единицы: длины l_B, энергии hbar*v_F/l_B. N — обрезание уровней, не петли.
VV_archive буквально повторяет notebook, включая 1/(2.19*eps).
Эта конвенция отличается от r_s=2.19/eps в тексте диплома.
"""
from pathlib import Path
import json
import numpy as np
from scipy.special import eval_genlaguerre, gammaln, j0, roots_legendre
from scipy.integrate import quad
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt

def publication_layout(fig):
    """Readable labels at a 600 px article width; retain data and panel order."""
    import textwrap
    from matplotlib.text import Text
    width, height = fig.get_size_inches()
    fig.set_size_inches(width, height * 1.22, forward=True)
    scale = width / 8.0
    main, small = 14.0 * scale, 12.0 * scale
    for text in fig.findobj(Text):
        if text.get_text().strip():
            text.set_fontsize(max(text.get_fontsize(), main))
            if text.get_color() in ['#d77b27', '#ed8b36', '#df7b28', '#d77527']:
                text.set_color('#87470f')
    for ax in fig.axes:
        for tick in ax.get_xticklabels() + ax.get_yticklabels():
            tick.set_fontsize(small)
        for label in [ax.xaxis.label, ax.yaxis.label]:
            txt = label.get_text()
            if '$' not in txt:
                label.set_text('\n'.join(textwrap.fill(s, 39) for s in txt.splitlines()))
        title = ax.get_title()
        if '$' not in title:
            columns = max(25, int(ax.get_position().width * 75))
            ax.set_title('\n'.join(textwrap.fill(s, columns) for s in title.splitlines()),
                         fontsize=main, pad=12)
        legend = ax.get_legend()
        if legend:
            for text in legend.get_texts(): text.set_fontsize(small)
        for line in ax.lines: line.set_linewidth(max(line.get_linewidth(), 1.8))
    if fig._suptitle:
        txt = fig._suptitle.get_text()
        if '$' not in txt:
            fig._suptitle.set_text(textwrap.fill(txt, 57))
        fig._suptitle.set_fontsize(max(main + 2, 18))
    for text in fig.texts:
        if text is fig._suptitle: continue
        txt=text.get_text()
        if '$' not in txt:
            text.set_text('\n'.join(textwrap.fill(s, 68) for s in txt.splitlines()))
    if type(fig.get_layout_engine()).__name__ == 'PlaceHolderLayoutEngine':
        fig.tight_layout(rect=(.01,.10,.99,.91),h_pad=2,w_pad=2)
    if type(fig.get_layout_engine()).__name__ == 'ConstrainedLayoutEngine':
        fig.get_layout_engine().set(h_pad=.15, w_pad=.12)


from matplotlib.animation import FuncAnimation, PillowWriter

OUT=Path(__file__).resolve().parent
plt.rcParams.update({'font.family':'DejaVu Sans','font.size':11,
                     'axes.spines.top':False,'axes.spines.right':False})

def fg(a,b,r):
    if min(a,b)<0: return np.zeros_like(r,dtype=float)
    r=np.asarray(r,dtype=float)
    lo,hi=min(a,b),max(a,b)
    return (np.exp((gammaln(lo+1)-gammaln(hi+1))/2-r*r/4)
            *(r/np.sqrt(2))**(hi-lo)
            *eval_genlaguerre(lo,hi-lo,r*r/2)/np.sqrt(2*np.pi))

def EE(n): return np.sqrt(2*n)

def PP(a,b,q):
    return (fg(a,b,q)+fg(a-1,b-1,q))**2/(4*(EE(b)-EE(a)))

def PM(a,b,q):
    return -(fg(a,b,q)-fg(a-1,b-1,q))**2/(4*(EE(b)+EE(a)))

def PO(a,q): return -fg(a,0,q)**2/(2*EE(a))

def sigma(q,m=0,N=15):
    if not 0<=m<N: raise ValueError('Require 0 <= m < N')
    ans=np.zeros_like(q,dtype=float)
    for a in range(m+1,N+1):
        for b in range(1,m+1): ans+=PP(a,b,q)
        for b in range(1,N+1): ans+=PM(a,b,q)
        ans+=PO(a,q)
    return ans

def VV_archive(q,eps=1,m=0,N=15):
    return 2*np.pi/(q-8*np.pi*sigma(q,m,N)/(2.19*eps))

def VV_text(q,eps=1,m=0,N=15):
    # Отдельная ветка по формуле r_s=2.19/eps на стр. 24 диплома.
    return 2*np.pi/(q-8*np.pi*sigma(q,m,N)*(2.19/eps))

def II(q,r,a,b,P):
    return fg(a,b,r)**2*j0(q*P)*j0(q*r)*q*r*VV_archive(q)

def radial_component(a,b,P=0,qmin=.01,qmax=10,points=500):
    # Сначала численно интегрируем r на исходном интервале [.01,10].
    nodes,weights=roots_legendre(points)
    q=qmin+(nodes+1)*(qmax-qmin)/2; wq=weights*(qmax-qmin)/2
    r=.01+(nodes+1)*9.99/2; wr=weights*9.99/2
    hankel=j0(q[:,None]*r[None,:])@(wr*r*fg(a,b,r)**2)
    return np.sum(wq*q*VV_archive(q)*j0(q*P)*hankel)

def main():
    # Сохранённые Output-ячейки: независимые опорные значения из .nb.
    checks={
        'fg(1,2,5)':(float(fg(1,2,5)),-.020216169102072835),
        'VV(5)':(float(VV_archive(5)),1.0290829678673687),
        'II(.1,1,3,5,0)':(float(II(.1,1,3,5,0)),.2288012805693556),
        'II(1,1,2,3,1)':(float(II(1,1,2,3,1)),.11974542810169851),
    }
    for name,(got,expected) in checks.items():
        print(name,got,'archive:',expected)
        assert np.isclose(got,expected,rtol=2e-11,atol=1e-13),name
    for a,b in [(0,0),(1,0),(1,1),(3,5)]:
        norm=2*np.pi*quad(lambda r:r*fg(a,b,r)**2,0,np.inf)[0]
        assert abs(norm-1)<1e-10
    fine=radial_component(5,5,points=650)
    coarse=radial_component(5,5,points=350)
    print('Double integral 5,5:',fine,'grid change:',abs(fine-coarse),
          'old NIntegrate:',.5262355639062104)
    assert abs(fine-coarse)<1e-9
    # Старое AccuracyGoal=3 не обещает совпадения в 15 знаках.
    assert abs(fine-.5262355639062104)<1e-3
    x=np.linspace(0,8,321)
    assert np.max(sigma(x,0,15))<=1e-13
    assert abs(float(sigma(0,0,15)))<1e-14
    report={'archive_checks':checks,'double_integral':fine,
            'quadrature_grid_change':abs(fine-coarse),
            'sigma_q1_by_cutoff':{str(n):float(sigma(1,0,n)) for n in [10,15,30,60]}}
    print('sigma(q=1):',report['sigma_q1_by_cutoff'])
    (OUT/'graphene_validation.json').write_text(json.dumps(report,indent=2),encoding='utf8')

    fig,axes=plt.subplots(1,2,figsize=(11,4.8),layout='constrained')
    r=np.linspace(0,5,400)
    for a,b in [(0,0),(1,0),(1,1)]:
        axes[0].plot(r,2*np.pi*r*fg(a,b,r)**2,label=f'a={a}, b={b}')
    axes[0].set(xlabel='r / lB',ylabel='Радиальная плотность вероятности',title='Нормировка: площадь под кривой = 1')
    axes[0].legend()
    for m in range(4): axes[1].plot(x,sigma(x,m,15),label=f'm={m}')
    axes[1].set(xlabel='q (безразмерный)',ylabel='Σ(q), конвенция notebook',title='Поляризация при N=15')
    axes[1].legend(); publication_layout(fig); fig.savefig(OUT/'graphene_functions.png',dpi=145, bbox_inches="tight"); plt.close(fig)

    fig,axes=plt.subplots(1,2,figsize=(11,4.8),layout='constrained')
    for n in [10,15,30,60]: axes[0].plot(x,sigma(x,0,n),label=f'N={n}')
    axes[0].set(xlabel='q',ylabel='Σ(q)',title='Обрезание по уровням меняет ответ');axes[0].legend()
    q=x[1:]
    axes[1].plot(q,q*VV_archive(q)/(2*np.pi),label='Буквально из notebook')
    axes[1].plot(q,q*VV_text(q)/(2*np.pi),label='С rₛ=2.19/ε из текста')
    axes[1].axhline(1,color='gray',ls=':',label='Без экранирования')
    axes[1].set(xlabel='q',ylabel='Vэфф / V₀',title='Две разные конвенции, ε=1, N=15');axes[1].legend(fontsize=9)
    publication_layout(fig); fig.savefig(OUT/'graphene_checks.png',dpi=145, bbox_inches="tight");plt.close(fig)

    # Простейшая иллюстрация: один скалярный канал RPA.
    s=np.linspace(0,.9,300); fig,ax=plt.subplots(figsize=(8,4.8),layout='constrained')
    ax.plot(s,1/(1+s),color='black',lw=2,label='RPA: 1/(1+s)')
    line,=ax.plot([],[],color='#237da6',lw=2,label='Частичная сумма')
    ax.set(xlabel='s = −V₀Π ≥ 0',ylabel='Vэфф / V₀',ylim=(0,1.9))
    title=ax.set_title('');ax.legend()
    def update(k):
        line.set_data(s,sum((-s)**j for j in range(k+1)))
        title.set_text(f'Цепочка пузырьков: от 0 до {k} вставок')
        return line,title
    update(0);publication_layout(fig)
    ani=FuncAnimation(fig,update,frames=range(10),interval=650)
    ani.save(OUT/'rpa.gif',writer=PillowWriter(fps=1.5),dpi=110)
    update(3);fig.savefig(OUT/'rpa.png',dpi=145, bbox_inches="tight");plt.close(fig)

if __name__=='__main__': main()

8. Что действительно сохранилось в Excel

В сохранившемся .xls нет встроенных объектов диаграмм. Есть две заполненные вкладки с численными таблицами и третья пустая. Поэтому следующие картинки построены заново по сохранённым числам, без пересчёта или сглаживания самих данных.

На Лист2 записаны четыре серии V01, V02, V11, V12 при m=0. Аргумент таблицы называется q; по контексту матричных элементов он должен сопоставляться с внешним импульсом пары P, но в подписи графика я сохраняю исходное обозначение. Единица величины V в самой таблице не подписана, поэтому ось не объявлена энергией в электронвольтах.

Четыре зависимости из исходной таблицы Excel
Четыре зависимости из исходной таблицы Excel

По этим числам можно уверенно сказать следующее. При нулевом аргументе серии различаются: от 0.494 до 0.792. V02 сначала возрастает и среди сохранённых точек достигает 0.5515 при аргументе 1.5, затем убывает. При аргументе 10 все четыре значения находятся между 0.0905 и 0.091. Точки соединены отрезками: между ними новых результатов не вычислялось.

На Лист1 сохранены матрицы для трёх значений аргумента. Например, при нуле:

Матрица исходных значений при нулевом аргументе
Матрица исходных значений при нулевом аргументе

Есть небольшая внутренняя несогласованность архива. Для V01 при аргументе 1 в матрице на Лист1 стоит 0.723, а в серии на Лист2 — 0.724. Для V02 там же — 0.540 и 0.541. Причина в файлах не пояснена: это могли быть разные запуски, округление либо перенос чисел. В графике серий используются числа Лист2, в тепловой карте — числа Лист1.

Я не стал «согласовывать» таблицы изменением исходных значений. Для учебного разбора расхождение полезнее спрятанного исправления. Оно показывает, какие сведения должен сохранять воспроизводимый расчёт: версию формулы, параметры, границы сумм и интегралов, точность и соответствие столбцов физическим величинам.

Полный код графиков из исходных чисел Excel: 07_excel.py
"""Графики исходных чисел Excel. Нового физического расчёта здесь нет.
Источник: Лист Microsoft Excel.xls, Лист2!B2:F29; матрица Лист1!C23:G28.
Ось q оставлена как в архиве; по смыслу матричного элемента это P.
Единица V в таблице не подписана: не приписываем данным эВ.
Числа включены в скрипт, поэтому исходный .xls для запуска не нужен.
"""
from pathlib import Path
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt

def publication_layout(fig):
    """Readable labels at a 600 px article width; retain data and panel order."""
    import textwrap
    from matplotlib.text import Text
    width, height = fig.get_size_inches()
    fig.set_size_inches(width, height * 1.22, forward=True)
    scale = width / 8.0
    main, small = 14.0 * scale, 12.0 * scale
    for text in fig.findobj(Text):
        if text.get_text().strip():
            text.set_fontsize(max(text.get_fontsize(), main))
            if text.get_color() in ['#d77b27', '#ed8b36', '#df7b28', '#d77527']:
                text.set_color('#87470f')
    for ax in fig.axes:
        for tick in ax.get_xticklabels() + ax.get_yticklabels():
            tick.set_fontsize(small)
        for label in [ax.xaxis.label, ax.yaxis.label]:
            txt = label.get_text()
            if '$' not in txt:
                label.set_text('\n'.join(textwrap.fill(s, 39) for s in txt.splitlines()))
        title = ax.get_title()
        if '$' not in title:
            columns = max(25, int(ax.get_position().width * 75))
            ax.set_title('\n'.join(textwrap.fill(s, columns) for s in title.splitlines()),
                         fontsize=main, pad=12)
        legend = ax.get_legend()
        if legend:
            for text in legend.get_texts(): text.set_fontsize(small)
        for line in ax.lines: line.set_linewidth(max(line.get_linewidth(), 1.8))
    if fig._suptitle:
        txt = fig._suptitle.get_text()
        if '$' not in txt:
            fig._suptitle.set_text(textwrap.fill(txt, 57))
        fig._suptitle.set_fontsize(max(main + 2, 18))
    for text in fig.texts:
        if text is fig._suptitle: continue
        txt=text.get_text()
        if '$' not in txt:
            text.set_text('\n'.join(textwrap.fill(s, 68) for s in txt.splitlines()))
    if type(fig.get_layout_engine()).__name__ == 'PlaceHolderLayoutEngine':
        fig.tight_layout(rect=(.01,.10,.99,.91),h_pad=2,w_pad=2)
    if type(fig.get_layout_engine()).__name__ == 'ConstrainedLayoutEngine':
        fig.get_layout_engine().set(h_pad=.15, w_pad=.12)


OUT=Path(__file__).resolve().parent
DATA=np.array([[0.0, 0.792, 0.494, 0.731, 0.563], [0.25, 0.788, 0.498, 0.724, 0.561], [0.5, 0.776, 0.51, 0.706, 0.556], [0.75, 0.762, 0.533, 0.6865, 0.5495], [1.0, 0.724, 0.541, 0.648, 0.532], [1.25, 0.6825, 0.551, 0.6145, 0.516], [1.5, 0.6335, 0.5515, 0.58, 0.499], [1.75, 0.58, 0.5395, 0.5445, 0.482], [2.0, 0.525, 0.516, 0.5075, 0.465], [2.25, 0.4725, 0.484, 0.469, 0.447], [2.5, 0.425, 0.446, 0.431, 0.4255], [2.75, 0.383, 0.407, 0.393, 0.4], [3.0, 0.347, 0.37, 0.358, 0.373], [3.25, 0.3165, 0.336, 0.327, 0.344], [3.5, 0.2905, 0.3065, 0.299, 0.316], [3.75, 0.2685, 0.281, 0.2755, 0.29], [4.0, 0.2495, 0.2595, 0.255, 0.267], [4.25, 0.233, 0.2415, 0.237, 0.2475], [4.5, 0.219, 0.2255, 0.222, 0.23], [4.75, 0.2065, 0.212, 0.209, 0.216], [5.0, 0.195, 0.1995, 0.1975, 0.2025], [6.0, 0.1595, 0.162, 0.161, 0.164], [7.0, 0.1345, 0.136, 0.135, 0.137], [8.0, 0.1165, 0.117, 0.117, 0.118], [9.0, 0.102, 0.1025, 0.102, 0.103], [10.0, 0.0905, 0.091, 0.091, 0.091], [20.0, 0.04, 0.04, 0.04, 0.04], [30.0, 0.0235, 0.0225, 0.0235, 0.023]])
MATRIX=np.array([[0.792, 0.494, 0.4, 0.3455, 0.308], [0.731, 0.563, 0.418, 0.3555, 0.315], [0.563, 0.6045, 0.495, 0.382, 0.332], [0.418, 0.495, 0.537, 0.4515, 0.3565], [0.3555, 0.382, 0.4515, 0.493, 0.42], [0.315, 0.332, 0.3565, 0.42, 0.46]])
plt.rcParams.update({'font.family':'DejaVu Sans','font.size':11})
fig,axes=plt.subplots(1,2,figsize=(11,4.8),layout='constrained')
for i,label in enumerate(['V01','V02','V11','V12'],1):
    axes[0].plot(DATA[:,0],DATA[:,i],'.-',label=label)
    axes[1].plot(DATA[:,0],DATA[:,i],'.-',label=label)
for ax in axes:
    ax.set(xlabel='q — обозначение исходной таблицы',ylabel='V, единицы исходной таблицы')
    ax.grid(alpha=.2)
axes[0].set_xlim(0,5);axes[0].set_title('Различия при малом импульсе');axes[0].legend()
axes[1].set_title('Все сохранённые точки');axes[1].legend()
publication_layout(fig); fig.savefig(OUT/'excel_curves.png',dpi=145, bbox_inches="tight");plt.close(fig)
fig,ax=plt.subplots(figsize=(7,5),layout='constrained')
im=ax.imshow(MATRIX,cmap='viridis',vmin=0,vmax=.8,aspect='auto')
for i,row in enumerate(MATRIX):
    for j,v in enumerate(row): ax.text(j,i,f'{v:g}',ha='center',va='center',color='white' if v<.5 else 'black')
ax.set(xticks=range(5),xticklabels=range(1,6),yticks=range(6),yticklabels=[0,-1,-2,-3,-4,-5],xlabel='n₁',ylabel='n₂',title='Сохранённые матричные элементы при q=0, m=0')
fig.colorbar(im,ax=ax,label='V, единицы исходной таблицы')
publication_layout(fig); fig.savefig(OUT/'excel_matrix.png',dpi=145, bbox_inches="tight");plt.close(fig)
assert DATA.shape==(28,5) and MATRIX.shape==(6,5)
print('Plotted 112 curve values and 30 matrix values, unchanged.')

9. Почему мой диплом куда проще девятипетлевой задачи

В восстановленной части диплома уже заданы спектр, функции состояний, правила суммирования и приближение экранирования. После выбора параметров остаются конечные суммы и значения вычисленных интегралов.

Высокопетлевая амплитуда же — это функция кинематических переменных.

Кинематика описывает допустимые энергии и импульсы внешних состояний; динамика — то, с какой амплитудой выбранная теория связывает эти состояния.

Аналитическая структура функции включает, в частности, её особые точки и правила продолжения между областями аргументов. Требуется определить её аналитическую структуру так, чтобы она работала для целого семейства допустимых состояний, согласовывалась с симметриями и предельными случаями.

Получить несколько чисел в нескольких точках тут недостаточно.

Что сравниваем

Восстановленная задача диплома

Девятипетлевая амплитуда

Что задано

Конкретные суммы, функции и радиальное ядро

Теория, структура допустимого ответа и ограничения

Что ищем

Поляризацию и отдельные матричные элементы

Аналитический объект на высоком порядке разложения

Основная трудность нашего примера

Нормировки, обрезания, численная устойчивость

Размер пространств функций и систем ограничений

Что можно проверить на ноутбуке

Контрольные значения и небольшие интегралы

Учебные аналоги и фрагмент публичных данных

Есть и более тонкое отличие. Моя RPA-формула уже суммирует цепочки пузырьков всех длин, но только одного выбранного класса. Число петель в полном пертурбативном коэффициенте учитывает весь разрешённый набор вкладов данного порядка.

Как выглядят эти девять петель

В шестичастичной амплитуде остаются шесть внешних состояний, а число независимых внутренних импульсов растёт с порядком. Ниже нарисованы планарные шеститочечные скелеты с одной, двумя и девятью петлями. Картинка не утверждает, что именно этот скелет с единичным числителем даёт ненулевое отдельное слагаемое нужной амплитуды \mathcal N=4 SYM.

Шеститочечные скелеты с одной, двумя и девятью петлями
Шеститочечные скелеты с одной, двумя и девятью петлями

У нижнего скелета 20 вершин и 28 внутренних рёбер, поэтому L=28-20+1=9. Девять ячеек видны непосредственно. Внешние шесть лучей не входят в число 28.

Дуальные импульсные координаты: закон сохранения встроен в обозначения

Обычно мы назначаем каждой линии импульс, а затем следим, чтобы в каждой вершине сумма входящих равнялась сумме выходящих. Для плоского, или планарного, рисунка можно поступить наоборот: назначить переменные областям между линиями, а импульсы линий определять как разности соседних переменных. При обходе вершины разности телескопически сокращаются. Закон сохранения становится свойством способа записи.

Напомним геометрию, скрытую в квадрате импульса.

Четырёхимпульс объединяет энергию и трёхмерный импульс:

p^\mu=(E/c,\mathbf p).

В пространстве Минковского при выбранном знаке метрики

p^2=(E/c)^2-|\mathbf p|^2.

Метрика задаёт именно это правило скалярного произведения.

Для безмассовой частицы p^2=0 при ненулевом p: такой вектор называют светоподобным.

В евклидовой геометрии сумма квадратов вещественных координат обращается в нуль только у нулевого вектора.

Светоподобность, коллинеарный предел и разрез комплексной функции
Светоподобность, коллинеарный предел и разрез комплексной функции

Наверху — световой конус в одной пространственной координате. На средней панели два пространственных импульса становятся параллельными; это коллинеарный предел. Внизу показано уже пространство комплексного аргумента функции, не пространство-время: главный логарифм имеет разные пределы с двух берегов отрицательной вещественной оси. Три картинки иллюстрируют разные понятия, которые далее встретятся в условиях на амплитуду.

Начнём с внешних импульсов. Считаем все p_i исходящими, поэтому \sum_{i=1}^n p_i=0; физически входящие частицы получают при такой записи отрицательные четырёхимпульсы. Выберем произвольное x_0 и последовательно построим

x_i=x_0-\sum_{j=1}^{i}p_j,\qquad p_i=x_{i-1}-x_i.

Сохранение импульса означает замыкание многоугольника: x_n=x_0. Общий сдвиг всех x_i ничего не меняет — физический смысл имеют разности. Буква x здесь обманчива: её размерность та же, что у импульса. Это не координата частицы, не траектория и не обычная сопряжённая импульсу координата преобразования Фурье.

Импульсы как стороны замкнутого многоугольника и разности переменных областей
Импульсы как стороны замкнутого многоугольника и разности переменных областей

Слева сумма стрелок равна нулю. Справа знаменатель линии определяется двумя соседними областями. Рисунок показывает связи и условную проекцию; для безмассовых внешних частиц стороны светоподобны в пространстве Минковского, а не являются такими евклидовыми отрезками в буквальном смысле.

Проверим на однопетлевом «ящике».

Его четыре знаменателя при одном выборе направления петлевого импульса \ell:

\ell^2,\quad(\ell-p_1)^2,\quad(\ell-p_1-p_2)^2,\quad(\ell+p_4)^2.

Положим x_0=0, x_1=-p_1, x_2=-p_1-p_2, x_3=p_4, а переменную внутренней области y=-\ell. Тогда каждый знаменатель равен (y-x_i)^2.

Никакой новой динамики не появилось: это та же формула, в которой легче увидеть связи и симметрии. Знак q^2 зависит от используемой метрики; в следующем учебном интеграле мы явно перейдём к евклидовому варианту с +\mu^2.

История здесь длиннее одного изобретения.

Переменные областей — это универсальный способ маршрутизации импульсов планарных графов. Современная роль дуальной конформной симметрии в амплитудах \mathcal N=4 SYM связана, в частности, с работами Драммонда, Хенна, Смирнова и Сокачёва (2006), Драммонда, Корчемского и Сокачёва (2007), затем Драммонда, Хенна, Корчемского и Сокачёва (2008).

Конформные преобразования сохраняют локальные углы; в данной задаче симметрия действует на вспомогательные x, поэтому называется дуальной.

Из квадратов расстояний между ними можно собрать безразмерные перекрёстные отношения — например,

x_{13}^2x_{46}^2/(x_{14}^2x_{36}^2), где x_{ij}=x_i-x_j.

Ни общий сдвиг, ни общий масштаб всех x это отношение не меняет. Именно такие переменные удобны при построении ответа из ограничений в разделе 12.

Одно слагаемое: от трёхпетлевого рисунка к интегралу

Разберём более короткий рисунок, на котором ещё можно подписать каждую линию.

Применим введённые дуальные импульсные координаты: шесть внешних x_1,\ldots,x_6 и три внутренних y_1,y_2,y_3. Сохраняем соглашение p_i=x_{i-1}-x_i, x_0=x_6, со всеми внешними импульсами, направленными наружу.

Три внутренних области и десять знаменателей диаграммы
Три внутренних области и десять знаменателей диаграммы

Сделаем математически простой пример: каждой линии сопоставим скалярный евклидов пропагатор 1/(q^2+\mu^2), числитель положим равным единице. Это учебное ядро на данной топологии, не полная формула глюонной амплитуды. По подписям рисунка десять знаменателей равны

\begin{aligned}D_1&=(y_1-x_1)^2+\mu^2,&D_2&=(y_2-x_2)^2+\mu^2,\\D_3&=(y_3-x_2)^2+\mu^2,&D_4&=(y_1-x_5)^2+\mu^2,\\D_5&=(y_2-x_5)^2+\mu^2,&D_6&=(y_3-x_4)^2+\mu^2,\\D_7&=(y_1-x_6)^2+\mu^2,&D_8&=(y_1-y_2)^2+\mu^2,\\D_9&=(y_2-y_3)^2+\mu^2,&D_{10}&=(y_3-x_3)^2+\mu^2.\end{aligned}

Одно соответствующее интегральное ядро имеет вид

I^{(3)}(x_1,\ldots,x_6)=\int\frac{d^4y_1}{(2\pi)^4}\frac{d^4y_2}{(2\pi)^4}\frac{d^4y_3}{(2\pi)^4}\frac{1}{D_1D_2D_3D_4D_5D_6D_7D_8D_9D_{10}}.

Например, вертикальная линия между первой и второй ячейками даёт D_8, потому что граничит с y_1 и y_2. Верхняя линия второй ячейки даёт D_2, поскольку по разные стороны стоят y_2 и x_2. Так можно проверить каждый множитель без догадок о форме интеграла. Закон сохранения в вершине получается телескопическим сокращением разностей областей.

В калибровочной теории над произведением знаменателей в общем случае стоит числитель N_G с импульсами, поляризациями и внутренними индексами; добавляются коэффициенты и другие графы. Для девяти петель общая структура такая:

\mathcal A^{(9)}_6=\sum_G C_G\int\prod_{r=1}^{9}\frac{d^D\ell_r}{(2\pi)^D}\,\frac{N_G(\ell,p)}{\prod_{j\in G}(q_j^2+i0)}.

Это схематическая запись: D может служить размерным регулятором, а конкретные соглашения о множителях i и связи включены в C_G. В реальном девятипетлевом результате работают с определённой нормированной конечной величиной после учёта универсальной инфракрасной структуры.

Зато теперь видно, что именно кодирует слово «петля»: ещё одну независимую векторную переменную интегрирования — 4-вектор в четырёхмерной теории.

10. Получим несколько полных коэффициентов в ещё более простой модели

До сих пор мы рисовали отдельные диаграммы и суммировали выбранную цепочку. Теперь хочется увидеть пример, где коэффициент порядка включает все разрешённые вклады, а правильность можно проверить исходным интегралом.

Уберём пространство и время совсем. Вместо поля в каждой точке оставим одну вещественную переменную x. Получится нульмерная модель \varphi^4:

G(g)=\frac{\int_{-\infty}^{\infty}x^2e^{-x^2/2-gx^4/24}\,dx}{\int_{-\infty}^{\infty}e^{-x^2/2-gx^4/24}\,dx},\qquad g\geq0.

Знаменатель нормирует вес, а числитель считает среднее x^2.

Число 24 — это 4!: традиционная нормировка четырёхлинейного взаимодействия, учитывающая перестановки одинаковых множителей. При g=0 перед нами обычное гауссово распределение с дисперсией 1, поэтому G(0)=1. При положительном g большие значения |x| сильнее подавлены, и среднее x^2 должно уменьшаться. Уже есть качественная проверка знака первой поправки.

Нормированный вес нульмерной модели и сокращение несвязных спариваний
Нормированный вес нульмерной модели и сокращение несвязных спариваний

Поле в обычной евклидовой теории — это много переменных \phi(x), связанных членом с производными. Функциональный интеграл суммирует по всем конфигурациям поля. Нульмерная версия оставляет одну переменную и обычный интеграл: сохраняются гауссов вес, взаимодействие и комбинаторика разложения.

Нуль измерений здесь не означает нуль петель.

На графике слева веса нормированы отдельным знаменателем для каждого g. Рост связи подавляет хвосты распределения. Задача о среднем x^2 измеряет, насколько широким осталось это распределение. В этой модели можно одновременно иметь точный численный ориентир и полный формальный ряд — удобная лаборатория для проверки метода.

Почему здесь по-прежнему можно говорить о диаграммах? Разложим взаимодействие:

e^{-gx^4/24}=1-\frac{gx^4}{24}+\frac{g^2x^8}{2\cdot24^2}-\ldots.

Каждая степень добавляет четыре множителя x — аналог четырёхлинейной вершины. Гауссовы моменты можно получать попарным соединением множителей. Именно эту комбинаторику кодируют диаграммы. От реальной теории поля здесь остался счёт соединений; интегралов по импульсам уже нет.

Три попарных спаривания четырёх множителей
Три попарных спаривания четырёх множителей

В разделе 4 мы уже подсчитали спаривания: для четырёх помеченных множителей их 3, для шести — 15, для 2k — (2k-1)!!. Здесь каждая пара даёт \langle x^2\rangle_0=1, поэтому число спариваний сразу равно гауссову моменту:

\langle x^{2k}\rangle_0=\frac{(2k)!}{2^kk!}.

Эта формула следует из интегрирования по частям: каждый следующий чётный момент в 2k-1 раз больше предыдущего, а нулевой момент равен 1.

Например, \langle x^4\rangle_0=3, а \langle x^6\rangle_0=15.

Поэтому числитель, делённый на \sqrt{2\pi}, начинается с 1-15g/24, а знаменатель — с 1-3g/24. Деление даёт

\frac{1-15g/24+O(g^2)}{1-3g/24+O(g^2)}=1-\frac12g+O(g^2).

Видно и диаграммное происхождение коэффициента -1/2.

Как и в разборе головастика в разделе 4, из 15 спариваний знаменатель убирает 3 несвязных вакуумных вклада. Оставшиеся 12 вместе с -g/4! дают -g/2. Теперь посчитаем полный следующий коэффициент, а не одну выбранную топологию.

Во втором порядке у двух вершин восемь полевых множителей, но в числителе есть ещё два внешних: всего десять, поэтому там 9!!=945 спариваний. В знаменателе остаются восемь множителей и 7!!=105 спариваний. Оба коэффициента делятся на 2!(4!)^2=1152:

N_2=\frac{945}{1152}=\frac{105}{128},\qquad Z_2=\frac{105}{1152}=\frac{35}{384}.

Разложим нормированное отношение: из (1+Z_1g+Z_2g^2+\cdots)(1+c_1g+c_2g^2+\cdots)=1+N_1g+N_2g^2+\cdots следует c_2=N_2-Z_1c_1-Z_2. Уже известны Z_1=-1/8 и c_1=-1/2, поэтому

c_2=\frac{105}{128}-\frac1{16}-\frac{35}{384}=\frac23.

Так множитель 1/2! и сокращение вакуумных частей участвуют в явной арифметике. Рекурсия ниже продолжает то же самое деление на любом порядке разложения.

Рисунки нескольких отдельных топологий не заменяют полного подсчёта. Продолжив вычисления, получим

G(g)=1-\frac12g+\frac23g^2-\frac{11}{8}g^3+\frac{34}{9}g^4+\ldots.

Знаменатель здесь также сокращает несвязные вакуумные части диаграмм.

Среднее x равно нулю по чётности, поэтому двухточечная функция связная. Для её связных диаграмм с V\geq1 каждая вершина имеет четыре конца, два конца внешние, а внутренняя линия использует два:

4V=2I+2,\qquad I=2V-1,\qquad L=I-V+1=V.

Значит, именно в этой двухточечной нульмерной модели степень g совпадает с числом петель. Это свойство конкретной модели и наблюдаемой величины, а не универсальное правило для любого графа.

Чтобы считать коэффициенты без округления, обозначим ряды числителя и знаменателя через N(g)=\sum N_ng^n и Z(g)=\sum Z_ng^n. Из ZG=N следует

c_n=N_n-\sum_{k=1}^{n}Z_kc_{n-k},\qquad Z_0=1.

Здесь c_n — коэффициенты G(g).

Python-класс Fraction сохраняет их точными рациональными числами.

Как меняется ошибка при добавлении порядков нульмерной модели
Как меняется ошибка при добавлении порядков нульмерной модели

При g=0.1 численный интеграл даёт G\approx0.955576629510, и среди порядков от нулевого до девятого лучший — девятый. При g=0.5 получаем G\approx0.838595434923, а лучший из тех же порядков — второй. Дальнейшее добавление членов ухудшает результат.

Это пример асимптотического ряда. Коэффициенты растут достаточно быстро, поэтому для фиксированной ненулевой связи полезность очередного члена не гарантирована.

Правило «больше петель — всегда точнее» неверно без оговорок. Но и переносить поведение этого простого ряда на конкретную амплитуду другой теории без анализа нельзя.

Полный код коэффициентов и анимации: 01_loops.py
"""Учебная нульмерная phi^4: связная двухточечная функция, 0–9 петель.
Запуск: python 01_loops.py. Создаёт loops.png, loops.gif, loop_diagrams.png.
Это не расчёт амплитуды в N=4 SYM. Все коэффициенты включают все диаграммы
соответствующего порядка, а рисунки показывают только примеры топологий.
"""
from pathlib import Path
from fractions import Fraction
from math import factorial
import numpy as np
import mpmath as mp
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation, PillowWriter
from matplotlib.patches import Circle, Arc

OUT = Path(__file__).resolve().parent
plt.rcParams.update({"font.family": "DejaVu Sans", "font.size": 11,
                     "axes.spines.top": False, "axes.spines.right": False})
BLUE, RED = "#237da6", "#d05c42"

def gaussian_moment(k):
    return Fraction(factorial(2*k), 2**k * factorial(k))

def coefficients(nmax=9):
    # Z = <exp(-g*x^4/24)>_0; N = <x^2 exp(-g*x^4/24)>_0.
    z = [(-1)**n * gaussian_moment(2*n) / (24**n * factorial(n))
         for n in range(nmax+1)]
    num = [(-1)**n * gaussian_moment(2*n+1) / (24**n * factorial(n))
           for n in range(nmax+1)]
    c = []
    for n in range(nmax+1):
        c.append(num[n] - sum(z[k]*c[n-k] for k in range(1, n+1)))
    return c

def reference(g):
    # Численный интеграл исходной модели, без усечения по петлям.
    with mp.workdps(40):
        g = mp.mpf(str(g))
        weight = lambda x: mp.exp(-x*x/2 - g*x**4/24)
        return float(mp.quad(lambda x: x*x*weight(x), [0, mp.inf]) /
                     mp.quad(weight, [0, mp.inf]))

c = coefficients()
assert c[:4] == [Fraction(1), Fraction(-1, 2), Fraction(2, 3), Fraction(-11, 8)]
couplings = [0.1, 0.5]
orders = np.arange(len(c))
partials = [np.cumsum([float(a)*g**n for n, a in enumerate(c)]) for g in couplings]
refs = [reference(g) for g in couplings]
for g, seq, ref in zip(couplings, partials, refs):
    print(f"g={g}: integral={ref:.12f}; best of 0..9 loops={np.argmin(abs(seq-ref))}")
print("Coefficients:", ", ".join(map(str, c)))

fig, axes = plt.subplots(2, 1, figsize=(7.2, 9.8))
fig.subplots_adjust(top=.79, bottom=.16, left=.16, right=.96, hspace=.48)
fig.suptitle("Больше петель — точнее?\nЗависит от связи", fontsize=19, y=.985)
fig.text(.5, .87, "Учебная нульмерная модель φ⁴\nОшибка относительно численного интеграла",
         ha="center", color="#334155", fontsize=14)
lines, dots = [], []
for ax, g, seq, ref in zip(axes, couplings, partials, refs):
    error = abs(seq-ref)
    ax.plot(orders, error, color="#dce3e8", lw=2)
    line, = ax.plot([], [], "o-", color=BLUE, lw=2.5)
    dot, = ax.plot([], [], "o", color=RED, ms=10)
    lines.append(line); dots.append(dot)
    ax.set(yscale="log", xlim=(-.3, 9.3),
           ylim=(min(error)*.5, max(error)*2), xlabel="Число петель L",
           title=f"g = {g}", xticks=range(10))
    ax.grid(alpha=.2, which="both")
    ax.tick_params(labelsize=12)
    ax.xaxis.label.set_size(14)
    ax.title.set_size(16)
    ax.set_ylabel("Ошибка |G_L − G|", fontsize=14)
fig.text(.5, .025, "Красная точка — лучший из добавленных порядков.\nРяд асимптотический.",
         ha="center", fontsize=13, color="#334155")

def animate(frame):
    end = min(frame//4+1, 10)
    for line, dot, seq, ref in zip(lines, dots, partials, refs):
        error = abs(seq-ref)
        best = np.argmin(error[:end])
        line.set_data(orders[:end], error[:end])
        dot.set_data([best], [error[best]])
    return lines+dots

animate(39)
fig.savefig(OUT / "loops.png", dpi=140)
anim = FuncAnimation(fig, animate, frames=48, interval=180, blit=False)
anim.save(OUT / "loops.gif", writer=PillowWriter(fps=5), dpi=85)
plt.close(fig)

# Примеры топологий. Внешние ноги в I не входят.
fig, axes = plt.subplots(1, 3, figsize=(10, 3.5))
fig.suptitle("Сначала научимся считать петли", fontsize=18, y=.98)
for ax in axes:
    ax.set(xlim=(-1.7, 1.7), ylim=(-1.7, 1.6), aspect="equal")
    ax.axis("off")
axes[0].plot([-1.4, 1.4], [0, 0], lw=3, color=BLUE)
axes[0].text(0, -1.25, "0 петель\nСвободный пропагатор", ha="center")
axes[1].plot([-1.4, 1.4], [0, 0], lw=3, color=BLUE)
axes[1].add_patch(Circle((0, .5), .5, fill=False, lw=3, edgecolor=BLUE))
axes[1].plot([0], [0], "o", color=RED, ms=9)
axes[1].text(0, -1.25, "1 петля\nI = 1, V = 1", ha="center")
ax = axes[2]
ax.plot([-1.5, -.65], [0, 0], color=BLUE, lw=3)
ax.plot([.65, 1.5], [0, 0], color=BLUE, lw=3)
ax.plot([-.65, .65], [0, 0], color=BLUE, lw=3)
ax.add_patch(Arc((0, 0), 1.3, 1.4, theta1=0, theta2=180, lw=3, color=BLUE))
ax.add_patch(Arc((0, 0), 1.3, 1.4, theta1=180, theta2=360, lw=3, color=BLUE))
ax.plot([-.65, .65], [0, 0], "o", color=RED, ms=9)
ax.text(0, -1.25, "2 петли\nI = 3, V = 2", ha="center")
fig.text(.5, .03, "Для связного графа: L = I − V + 1. Это примеры, не полный набор диаграмм.",
         ha="center", fontsize=14)
fig.subplots_adjust(top=.85, bottom=.18)
fig.savefig(OUT / "loop_diagrams.png", dpi=140)
plt.close(fig)

11. Возвращаемся к Claude: что именно было заявлено

Уточним задачу, названную во вступлении. Мэтт фон Хиппель предложил Claude вычислить девятипетлевой шестичастичный MHV-результат в планарной \mathcal N=4 суперсимметричной теории Янга — Миллса; проверкой занимался Лэнс Диксон. Два маршрута получения символа — прямой bootstrap и путь через формфактор. Отдельный переход от символа к полной функции обсудим ниже.

В том же сообщении описан параллельный девятипетлевой результат группы Сун Хэ на уровне символа; при получении некоторых ограничений исследователи использовали GPT-6.

Поэтому противопоставление «один ИИ против всех людей» было бы неточным. Рассказ участников.

Разберём название задачи по частям.

Пример кинематики с двумя входящими и четырьмя исходящими состояниями
Пример кинематики с двумя входящими и четырьмя исходящими состояниями

Амплитуда — величина, из которой получают вероятности квантовых процессов. Для вероятности нужны квадрат модуля, нормировки и, в задаче рассеяния, соответствующие кинематические множители. Сама амплитуда обычно комплексная.

Шесть частиц — шесть внешних состояний, например две входящие частицы и четыре выходящие. Это не шесть частиц, одновременно летящих навстречу друг другу из шести направлений.

MHV задаёт специальный сектор спиральностей внешних частиц.

Для глюонной амплитуды всех исходящих импульсов это две отрицательные спиральности, остальные положительные.

Спиральность характеризует проекцию спина на направление движения.

Глюон — квант калибровочного поля рассматриваемой теории. У его физических безмассовых состояний две спиральности, +1 и -1 в единицах \hbar. Здесь это поля модельной теории Янга — Миллса, а не утверждение о наблюдении свободных глюонов в графене.

Планарный предел выделяет ведущий вклад при большом числе цветов калибровочной группы. В соответствующей записи диаграммы можно рисовать на плоскости без пересечений цветовых линий. Это не утверждение, что частицы физически движутся в двумерном листе, как электроны графена.

Калибровочная группа, например SU(N_c), задаёт разрешённые локальные преобразования внутренних компонент поля, описывающие одну и ту же физику. SU(N_c) — группа унитарных матриц размера N_c с определителем 1; унитарность U^\dagger U=I сохраняет скалярное произведение. «Цвет» — название внутреннего индекса, не оптический цвет. Двухлинейная запись индексов калибровочного поля позволяет считать, как диаграммы зависят от N_c. В планарном пределе N_c\to\infty сохраняют постоянной связь ’т Хоофта \lambda=g_{\rm YM}^2N_c. При таком пределе выделяются ведущие планарные вклады. \lambda здесь — специальная связь теории, а не автоматически вспомогательный параметр из учебного вывода возмущений.

\mathcal N=4 обозначает расширенную суперсимметрию в четырёхмерной теории.

Её сильные симметрии ограничивают ответ и делают некоторые вычисления доступнее. Она служит теоретической лабораторией методов и не является непосредственно реалистической моделью вещества нашего мира.

Суперсимметрия связывает бозонные и фермионные поля.

Обозначение \mathcal N=4 в четырёх измерениях соответствует четырём наборам спинорных генераторов этой симметрии, или 16 вещественным суперзарядам; оно не означает четырёх частиц или четырёх петель. Для нашего сравнения существенно наличие дополнительных ограничений на ответ, а не знание всей алгебры симметрии. Введение в язык амплитуд есть в лекциях Диксона.

Наконец, девять петель — высокий порядок пертурбативного разложения выбранной амплитуды. До этого был опубликован восьмипетлевой результат Диксона и Лю. Переход от восьми к девяти — новый коэффициент функции, а не простое добавление ещё одного пузырька к готовой RPA-дроби.

Два маршрута получения символа и отдельный переход к полной функции
Два маршрута получения символа и отдельный переход к полной функции

12. Как считать амплитуду, не вычисляя по очереди все диаграммы

Зная правила диаграмм, естественно представить программу, которая рисует их все, составляет интегралы и вычисляет каждый. Но это не единственный и совсем не оптимальный путь для вычисления требуемого ответа.

Можно начать с класса функций, в котором должен находиться ответ, записать общую линейную комбинацию и определить коэффициенты из ограничений:

F(u,v,w)=\sum_{j=1}^{K}c_j f_j(u,v,w).

Функции f_j известны, коэффициенты c_j неизвестны. Симметрии, допустимые особенности и известные пределы превращаются в уравнения на c_j.
Такой подход физики называют bootstrap.

Миниатюрный пример можно разобрать до конца.

Пусть известно, что F(x)=a+bx+cx^2, функция чётная, F(0)=1, а F(1)=3.

Чётность даёт b=0, второе условие — a=1, третье — c=2.

Правильный ответ тут такой: F(x)=1+2x^2.

import sympy as sp
x = sp.symbols('x')
a, b, c = sp.symbols('a b c')
F = a + b*x + c*x*x
solution = sp.solve([b, F.subs(x, 0)-1, F.subs(x, 1)-3], [a,b,c])
print(sp.expand(F.subs(solution)))  # 2*x**2 + 1

Теперь уберём предположение о степени не выше двух.

Функция 1-2 x^{2}+4 x^{4} тоже чётная и даёт те же значения в 0 и \pm1. Но в x=1/2 первый ответ равен 1.5, а второй — 0.75.

Два ответа, совпадающие на использованных условиях
Два ответа, совпадающие на использованных условиях

Уникальность внутри выбранного пространства не доказывает, что пространство выбрано правильно. Поэтому нужны два вида проверки: обоснование допустимого класса функций и условия, которые не использовались для подбора коэффициентов. Этот урок одинаково полезен для bootstrap, машинного обучения и старой таблицы моего диплома.

Ещё один пример: условия как последовательные фильтры

Представим, что ищем неизвестную плавную кривую между точками (0,0) и (1,1). Ограничим пространство квадратичными полиномами. Удобнее сразу записать все функции, удовлетворяющие двум значениям на концах:

F_a(x)=x+a x(1-x).

Один параметр всё ещё свободен.

Дополнительное условие F'_a(0)=2 даёт 1+a=2, то есть a=1 и F(x)=2x-x^2. Теперь можно предсказать значение в неиспользованной точке: F(1/2)=3/4. Если независимое вычисление даст другое число за пределами обоснованной погрешности, возникнет противоречие. Тогда проверяем исходные предположения, вывод и обе вычислительные реализации: ошибка может находиться и в проверочном расчёте.

Семейство допустимых функций и независимая проверочная точка
Семейство допустимых функций и независимая проверочная точка

А теперь расширим выбранное семейство, разрешив одну определённую добавку четвёртой степени. Это ещё не все полиномы степени не выше четырёх:

F_{a,b}(x)=x+a x(1-x)+b x^2(1-x)^2.

Добавка с b не меняет значения на концах и производную при нуле. Поэтому все прежние условия снова оставляют свободу: a=1, но b произвольно. В середине получаем 3/4+b/16. Независимый ответ 3/4 теперь зафиксирует b=0.

После использования этой точки для определения b она становится частью условий построения, а для новой проверки понадобится другая точка. Это наглядный пример ядра системы ограничений: ненулевая добавка может быть невидима всем уже использованным тестам.

import sympy as sp
x, a, b = sp.symbols('x a b')
F = x + a*x*(1-x) + b*x**2*(1-x)**2
assert F.subs(x, 0) == 0 and F.subs(x, 1) == 1
print(sp.solve(sp.diff(F, x).subs(x, 0)-2, a))  # [1]
print(sp.expand(F.subs({a: 1, x: sp.Rational(1, 2)})))  # b/16 + 3/4

Если разрешить все полиномы степени не выше четырёх, прежних условий опять недостаточно. Например,

F_c(x)=2x-x^2+c\,x^2(1-x)(x-\tfrac12)

при любом c удовлетворяет всем четырём требованиям: F(0)=0, F(1)=1, F'(0)=2, F(1/2)=3/4. Дополнительная точка даёт F_c(1/4)=7/16-3c/256 и различает кандидатов.

Неоднозначность, невидимая старым ограничениям bootstrap
Неоднозначность, невидимая старым ограничениям bootstrap

Чёрные точки совпадают у всех кривых; совпадает и начальный наклон. Справа показана разность с 2x-x^2. Так выглядит пропущенная свобода в пространстве ответов. Проверка в новом месте полезнее повторения тех условий, которыми кандидат уже был построен.

Ядро системы ограничений здесь — набор добавок, на которые использованные условия не реагируют.

Анзац — это выбранная форма кандидата с неизвестными параметрами; bootstrap ищет эти параметры по совместным требованиям.

Особая точка — значение аргумента, при котором функция перестаёт быть обычной гладкой аналитической функцией: например, полюс 1/x при нуле или точка ветвления \log z.

Для логарифма нужно выбрать непрерывную ветвь аргумента, а разрез отделяет области этого выбора.

Коллинеарный предел означает параллельность определённых внешних импульсов.

В таком пределе теория предсказывает характерное упрощение или сингулярное поведение; это физическое ограничение, а не требование просто нарисовать гладкую кривую.

В реальном bootstrap вместо степени полинома выбирают пространство специальных функций, вместо двух точек используют симметрии, допустимые разрезы, коллинеарные и другие пределы. Но вопрос остаётся узнаваемым: достаточно ли ограничений и не исключили ли мы правильный ответ заранее?

Полный код учебного bootstrap: 02_bootstrap.py
"""Точные ограничения и отложенная проверка в учебной задаче.
Запуск: python 02_bootstrap.py. Создаёт holdout.png.
"""
from pathlib import Path
import sympy as sp
import numpy as np
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt

def publication_layout(fig):
    """Readable labels at a 600 px article width; retain data and panel order."""
    import textwrap
    from matplotlib.text import Text
    width, height = fig.get_size_inches()
    fig.set_size_inches(width, height * 1.22, forward=True)
    scale = width / 8.0
    main, small = 14.0 * scale, 12.0 * scale
    for text in fig.findobj(Text):
        if text.get_text().strip():
            text.set_fontsize(max(text.get_fontsize(), main))
            if text.get_color() in ['#d77b27', '#ed8b36', '#df7b28', '#d77527']:
                text.set_color('#87470f')
    for ax in fig.axes:
        for tick in ax.get_xticklabels() + ax.get_yticklabels():
            tick.set_fontsize(small)
        for label in [ax.xaxis.label, ax.yaxis.label]:
            txt = label.get_text()
            if '$' not in txt:
                label.set_text('\n'.join(textwrap.fill(s, 39) for s in txt.splitlines()))
        title = ax.get_title()
        if '$' not in title:
            columns = max(25, int(ax.get_position().width * 75))
            ax.set_title('\n'.join(textwrap.fill(s, columns) for s in title.splitlines()),
                         fontsize=main, pad=12)
        legend = ax.get_legend()
        if legend:
            for text in legend.get_texts(): text.set_fontsize(small)
        for line in ax.lines: line.set_linewidth(max(line.get_linewidth(), 1.8))
    if fig._suptitle:
        txt = fig._suptitle.get_text()
        if '$' not in txt:
            fig._suptitle.set_text(textwrap.fill(txt, 57))
        fig._suptitle.set_fontsize(max(main + 2, 18))
    for text in fig.texts:
        if text is fig._suptitle: continue
        txt=text.get_text()
        if '$' not in txt:
            text.set_text('\n'.join(textwrap.fill(s, 68) for s in txt.splitlines()))
    if type(fig.get_layout_engine()).__name__ == 'PlaceHolderLayoutEngine':
        fig.tight_layout(rect=(.01,.10,.99,.91),h_pad=2,w_pad=2)
    if type(fig.get_layout_engine()).__name__ == 'ConstrainedLayoutEngine':
        fig.get_layout_engine().set(h_pad=.15, w_pad=.12)



x, a, b, c = sp.symbols("x a b c")
f = a*x*x + b*x + c
solution = sp.solve([c-1, b, a+b+c-3], [a, b, c])
f = sp.expand(f.subs(solution))
# Эта добавка не меняет f(-1), f(0), f(1) и сохраняет чётность.
fake = f + 4*x*x*(x*x-1)
assert all(sp.simplify(fake.subs(x, t)-f.subs(x, t)) == 0 for t in [-1, 0, 1])
assert sp.simplify(fake.subs(x, -x)-fake) == 0
check = sp.Rational(1, 2)
assert f.subs(x, check) == sp.Rational(3, 2)
assert fake.subs(x, check) == sp.Rational(3, 4)
print("Quadratic:", f, "Alternative:", sp.expand(fake))
print("At x=1/2:", f.subs(x, check), fake.subs(x, check))

plt.rcParams.update({"font.family": "DejaVu Sans", "font.size": 11})
fig, ax = plt.subplots(figsize=(9, 5.1))
xx = np.linspace(-1.12, 1.12, 401)
ax.plot(xx, sp.lambdify(x, f, "numpy")(xx), lw=3, color="#237da6", label="Квадратичная модель")
ax.plot(xx, sp.lambdify(x, fake, "numpy")(xx), lw=3, color="#d05c42", ls="--", label="Альтернатива четвёртой степени")
ax.scatter([-1, 0, 1], [3, 1, 3], c="#202e3c", s=70, zorder=5, label="Точки, использованные при подборе")
ax.plot([.5, .5], [.75, 1.5], color="#7256a7", lw=3)
ax.scatter([.5, .5], [.75, 1.5], c="#7256a7", s=60, zorder=6)
ax.annotate("Новая проверка: x = ½\n1,50 против 0,75", (.5, 1.12),
            xytext=(-.35, 2.45), arrowprops={"arrowstyle": "->", "color": "#7256a7"},
            color="#7256a7", fontsize=11)
ax.set(xlabel="x", ylabel="f(x)", ylim=(0, 4.3))
ax.spines[["top", "right"]].set_visible(False)
ax.grid(alpha=.18)
ax.legend(loc="upper center", frameon=False, fontsize=10)
fig.suptitle("Три совпадения ещё не доказывают формулу", fontsize=18, y=.97)
fig.text(.5, .88, "Обе функции чётные и проходят через одни и те же исходные точки",
         ha="center", color="#536171", fontsize=11)
fig.text(.5, .02, "Учебный пример: он показывает роль проверки, но не реконструирует эксперимент Шварца.",
         ha="center", fontsize=9, color="#536171")
fig.subplots_adjust(top=.8, bottom=.16, left=.09, right=.97)
publication_layout(fig); fig.savefig(Path(__file__).resolve().parent / "holdout.png", dpi=140, bbox_inches="tight")
plt.close(fig)

13. Что такое «символ» и почему его недостаточно назвать полной функцией

Большие ответы удобно хранить в сокращённом математическом представлении.

В задачах амплитуд таким представлением служит символ: он кодирует определённую структуру итерированных логарифмических дифференциалов.

Начать можно с обычного логарифма:

d\log x=\frac{dx}{x}.

Если функция строится последовательным интегрированием выражений такого типа, полезно записывать, какие аргументы логарифмов появляются и в каком порядке. Эти аргументы называют буквами алфавита, а упорядоченные последовательности — словами. Это математические метафоры; буквенная последовательность не является текстом на естественном языке.

Простейшие примеры стандартной символной записи:

\mathcal S(\log x)=x,\qquad \mathcal S(\operatorname{Li}_2(x))=-(1-x)\otimes x.

Последний пример связан с d\operatorname{Li}_2(x)=-\log(1-x)d\log x. Знак \otimes сохраняет порядок букв; это не обычное умножение чисел. В частности, перестановка букв вообще говоря меняет объект.

При этом такое представление теряет часть информации. Уже обычная производная не различает f(x) и f(x)+C. Символ имеет более сложные неоднозначности, в том числе специальные константы соответствующего веса и их произведения с функциями меньшего веса. Произвольное произведение функций меньшего веса при этом не обязано иметь нулевой символ. Поэтому совпадение символов — сильная проверка, но для полной функции требуются дополнительные данные.

Дилогарифм: два интегрирования и потерянная константа

Чтобы слово «итерированный» не осталось магическим, определим на 0\leq x<1

\operatorname{Li}_2(x)=-\int_0^x\frac{\log(1-t)}{t}\,dt.

Логарифм внутри тоже получается интегрированием: \int_0^t\frac{ds}{1-s}=-\ln(1-t).

Поэтому перед нами вложенный интеграл, то есть два уровня логарифмической структуры.

В символе первая буква 1-x приходит от внутреннего логарифма, последняя x — от внешнего dt/t=d\log t. Вес равен двум.

Дилогарифм и сдвинутая функция с тем же символом
Дилогарифм и сдвинутая функция с тем же символом

Функции \operatorname{Li}_2(x) и \operatorname{Li}_2(x)+\pi^2/6 имеют одинаковый обычный символ веса 2. На графике они различаются на постоянную величину. Значение при нуле выбирает нужную: первая равна нулю, вторая — \pi^2/6. Таким образом, потеря информации здесь видна буквально как вертикальный сдвиг, а не как абстрактная оговорка.

Порядок букв тоже можно проверить на знакомой формуле. Из d(\log^2x/2)=\log x\,d\log x следует \mathcal S(\log^2x/2)=x\otimes x. Для произведения двух разных логарифмов производная даёт два слагаемых:

\mathcal S(\log a\log b)=a\otimes b+b\otimes a.

Это простейшее shuffle-правило: нужно сохранить внутренний порядок каждой последовательности, но перебрать способы их перемешать.

Тензорная запись линейна по логарифмам: буква-произведение раскрывается как сумма, например \cdots\otimes(ab)=\cdots\otimes a+\cdots\otimes b.

Поэтому выбор алфавита существенно влияет на число явно записанных слов.

Численно проверим определение дилогарифма двумя способами:

import numpy as np
from scipy.integrate import quad
from scipy.special import spence
for x in [0.1, 0.5, 0.9]:
    f = lambda t: -np.log1p(-t)/t if t else 1.0
    integral = quad(f, 0, x, epsabs=1e-12)[0]
    library = spence(1-x)  # scipy.spence(z) = Li₂(1-z)
    assert abs(integral-library) < 1e-12
    print(x, integral)

Один важный класс — дзета-константы: при вещественном

s>1 функция

\zeta(s)=\sum_{n=1}^{\infty}n^{-s}

задаёт, например, \zeta(2)=\pi^2/6 и \zeta(3)\approx1.2020569.

В многократных дзета-значениях суммирование вложено, например

\zeta(r,s)=\sum_{n>m\geq1}n^{-r}m^{-s}

в сходящемся случае.

При принятом весе таким константам сопоставляют сумму индексов.

В обычном символе соответствующего положительного веса числовые константы не видны; потому на большем весе могут теряться и произведения этих констант на функции меньшего веса. Поэтому для получения физически осмысленного ответа к задаче понадобятся еще нормировка, граничные значения и другие данные за пределами символа.

В рассматриваемом девятипетлевом примере слова имеют вес 18.

На сайте проекта Cosmic9 отдельно представлены символные данные, контрольные файлы и результат на уровне функции.

Там же прямо указано, что программы исходного вычисления не распространяются. Поэтому наличие открытых данных не равно возможности запустить исходный расчёт одной командой.

Где символ помогает контролировать код — и где прекращает помогать

Тут важно различать два смысла слова.

Символьная алгебра преобразует выражения с переменными вместо чисел: например, SymPy может проверить коэффициенты ряда.

Символ полилогарифмической функции — специальное математическое отображение \mathcal S, введённое выше.

Для обычных формул проверка начинается с предположений: переменная вещественная или комплексная, положительная ли она, исключён ли нуль?

Например, \sqrt{x^2}=|x| для вещественного x, а не всегда x.

Численные подстановки в нескольких точках не доказывают тождество и могут не пересечь разрез комплексного логарифма.

Ветвь — это согласованный выбор значения многозначной функции; изменение ветви может добавлять 2\pi i к логарифму.

Для символа нужны: список разрешённых букв, длина слова — вес, допустимые соседства, симметрии и интегрируемость.

Последняя означает, что из записанного набора дифференциалов действительно может получиться функция. Простейшая проверка веса 2 для \sum c_{ab}\,a\otimes b:

\sum c_{ab}\,d\log a\wedge d\log b=0.

Знак \wedge обозначает антисимметричное произведение:

dx\wedge dy=-dy\wedge dx, а dx\wedge dx=0.

Для независимых положительных x,y одиночное слово x\otimes y даёт dx\wedge dy/(xy)\ne0. Сумма x\otimes y+y\otimes x проходит проверку: два вклада сокращаются.

Это символ \log x\log y. Для большего веса проверка применяется к каждой паре соседних позиций с сохранением остальных. В одной переменной эта конкретная проверка может стать тождественно пустой, поэтому она не заменяет остальные ограничения.

Символ как фильтр: интегрируемость и информация, потерянная при отображении
Символ как фильтр: интегрируемость и информация, потерянная при отображении

В рабочем процессе это отдельный блок:

кандидат → проверка символа → восстановление функции → граничные значения и ветви → независимые численные точки.

Нельзя остановиться после второй стрелки. Одного граничного значения достаточно для определения одной добавочной константы, как в примере с дилогарифмом.

Но для общего многопетлевого ответа неоднозначностей больше и требуется соответствующий набор условий.

Подробности символного исчисления и его реализации разобраны в Duhr и Dulat, PolyLogTools (2019); сохранение информации о дзета-константах обсуждается в работе Duhr (2012).

Ниже Python проверяет небольшой пример интегрируемости и показывает, как функция с правильной производной проваливает проверку значения в опорной точке. Это учебная проверка принципа, а не реализация полного символного алгоритма для девяти петель.

14. Почему в рассказе о частицах появляется поверхность

Шестичастичная нормированная амплитуда после использования симметрий зависит от трёх безразмерных отношений u,v,w. Определим их полностью.

При всех исходящих импульсах введём инварианты Мандельстама s_{ij\ldots k}=(p_i+p_j+\cdots+p_k)^2.

Это квадраты суммарных четырёхимпульсов. Они не меняются при преобразовании системы отсчёта Лоренца. Тогда в одной стандартной циклической нумерации

u=\frac{s_{12}s_{45}}{s_{123}s_{345}},\qquad v=\frac{s_{23}s_{56}}{s_{234}s_{123}},\qquad w=\frac{s_{34}s_{61}}{s_{345}s_{234}}.

Числитель и знаменатель имеют одинаковую размерность. Это перекрёстные отношения кинематики, а не координаты трёх частиц в лаборатории. В первоисточниках переменные амплитуды часто снабжены шляпками; здесь их опускаем, а переменные формфактора ниже обозначим другими буквами.

В вычислении важна поверхность

\Delta=(1-u-v-w)^2-4uvw=0.

Это уравнение связано с определителем Грама шестичастичной кинематики — определителем матрицы скалярных произведений соответствующих векторов.

В алфавите амплитуды появляется \sqrt\Delta; пространственная чётность меняет знак этого корня. На \Delta=0 две ветви совпадают. Это поверхность в пространстве параметров: её размерность не означает, что частицы начали жить на двумерном листе. Определения и отображение в первоисточнике.

Посмотрим на неё как на геометрический объект. Решая квадратное уравнение относительно w, получаем

w_\pm=1-u-v+2uv\pm2\sqrt{uv(1-u)(1-v)}.

При u,v\in[0,1] обе ветви можно построить непосредственно:

Поверхность Delta=0 в пространстве кинематических переменных
Поверхность Delta=0 в пространстве кинематических переменных

Анимация вращает камеру вокруг математической поверхности. Она не показывает столкновение частиц, ход вычисления Claude или структуру графена. Два цвета обозначают две ветви. Поверхности построены по сетке значений u,v; окрашенные участки соединяют вычисленные узлы.

После построения проверяем каждую точку подстановкой в исходное уравнение. Иначе можно получить визуально правдоподобную поверхность даже с неправильным знаком перед корнем. В скрипте максимальная невязка должна быть меньше 10^{-12}.

Полный код поверхности и проверки: 03_surface.py
"""Две ветви Delta=0 в единичном кубе: геометрия, не амплитуда.
Запуск: python 03_surface.py. Создаёт delta0.png и delta0.gif.
"""
from pathlib import Path
import numpy as np
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation, PillowWriter

OUT = Path(__file__).resolve().parent
plt.rcParams.update({"font.family": "DejaVu Sans", "font.size": 14})
u, v = np.meshgrid(np.linspace(.001, .999, 45), np.linspace(.001, .999, 45))
root = 2*np.sqrt(u*v*(1-u)*(1-v))
w_plus = 1-u-v+2*u*v+root
w_minus = 1-u-v+2*u*v-root
for w in [w_plus, w_minus]:
    delta = (1-u-v-w)**2 - 4*u*v*w
    assert np.max(np.abs(delta)) < 1e-12
    assert np.min(w) > -1e-12 and np.max(w) < 1+1e-12
print("Both branches: max |Delta| < 1e-12")

fig = plt.figure(figsize=(7.6, 7.1))
ax = fig.add_subplot(111, projection="3d")
ax.plot_surface(u, v, w_plus, color="#237da6", alpha=.6, linewidth=0)
ax.plot_surface(u, v, w_minus, color="#dd9861", alpha=.6, linewidth=0)
ax.set(xlabel="u", ylabel="v", zlabel="w", xlim=(0,1), ylim=(0,1), zlim=(0,1))
ax.set_box_aspect((1,1,1))
fig.suptitle("Исходная поверхность одного из маршрутов", fontsize=16, y=.975)
fig.text(.5, .88, r"$\Delta=(1-u-v-w)^2-4uvw=0$", ha="center", fontsize=16)
fig.text(.5, .035, "Два цвета — две ветви решения по w.\nТочки задают кинематику, не частицы.",
         ha="center", fontsize=12, color="#536171")
fig.subplots_adjust(top=.82, bottom=.23, left=.04, right=.96)
def rotate(frame):
    ax.view_init(elev=23, azim=25+frame*7.5)
    return []
rotate(0)
fig.savefig(OUT / "delta0.png", dpi=140)
anim = FuncAnimation(fig, rotate, frames=48, interval=125)
anim.save(OUT / "delta0.gif", writer=PillowWriter(fps=8), dpi=90)
plt.close(fig)

Антиподальная дуальность

В работе Диксона, Гюрдоана, Маклеода и Вильгельма, опубликованной в 2022 году после препринта 2021 года, обнаружена связь трёхчастичного формфактора и шестичастичной амплитуды на этой поверхности. Это специальное соотношение нормированных величин данной теории. Для его символьной стороны нужны две операции: преобразование аргументов и антипод — обращение порядка букв со знаком.

Обозначим отношения формфактора через a,b,c, причём a+b+c=1.

Здесь это кинематические переменные. Карта имеет вид

u=\frac{bc}{(1-b)(1-c)},\qquad v=\frac{ca}{(1-c)(1-a)},\qquad w=\frac{ab}{(1-a)(1-b)}.

Подстановка c=1-a-b превращает (1-u-v-w)^2-4uvw в тождественный нуль. Это можно проверить точной алгеброй, без построения амплитуды. Например, (a,b,c)=(1/3,1/3,1/3) переходит в (u,v,w)=(1/4,1/4,1/4).

Карта из двух независимых переменных формфактора на поверхность амплитуды
Карта из двух независимых переменных формфактора на поверхность амплитуды

На рисунке взят положительный треугольник

a,b,c>0. Слева и справа один цвет помечает одну исходную точку и её образ. Вся поверхность из предыдущей анимации не обязана покрываться только этим выбранным треугольником.

Для слова длины m антипод действует так:

\operatorname{Ant}(x_1\otimes\cdots\otimes x_m)=(-1)^m x_m\otimes\cdots\otimes x_1.

При весе 2L знак положителен. Чтобы увидеть операцию на маленьком объекте, возьмём символ дилогарифма: -(1-x)\otimes x превращается в -x\otimes(1-x), то есть символ \operatorname{Li}_2(1-x). Это учебный пример обращения слова; он сам по себе не доказывает физическую дуальность. Константы и ветви функции по-прежнему требуют отдельной работы.

Обращение слова и диэдральные перестановки меток
Обращение слова и диэдральные перестановки меток

Слева меняется порядок позиций внутри слова. Справа показана другая операция — симметрия меток треугольника: повороты (a,b,c)\to(b,c,a) и отражения (a,b,c)\to(c,b,a). Такие перестановки образуют диэдральную группу. Для шести внешних меток аналогично рассматривают повороты и отражения шестиугольника; действие на выбранные кинематические отношения и буквы нужно устанавливать отдельно.

Откуда берутся 30 024 320 034 слова

На \Delta=0 можно выбрать шестибуквенное представление символа, связанное с алфавитом формфактора. Если бы каждая из 2L позиций независимо принимала любую из шести букв, получилось бы 6^{2L} слов. При девяти петлях это 101 559 956 668 416 — более ста триллионов. Но у физического символа большинство коэффициентов равны нулю: действуют ограничения первых и последних букв, допустимых соседств, интегрируемости и другие отношения.

Прежде чем считать слова, нужно привести одинаковые последовательности к одному ключу и сложить их коэффициенты. Например, a\otimes a+2a\otimes b-a\otimes a+3b\otimes a содержит четыре записи слагаемых, три разных ключа до сложения и только два ненулевых слова после сокращения. Поэтому число ненулевых слов нельзя получить, просто умножив число разрешённых букв в каждой позиции. Даже перечня допустимых соседств недостаточно: коэффициенты определяются решением физической задачи.

Для сравнения: в игрушечном алфавите из a,b с запретом bb можно вести два счётчика A_n,B_n — сколько слов длины n заканчиваются на каждую букву. Тогда A_{n+1}=A_n+B_n, B_{n+1}=A_n, при A_1=B_1=1. Получатся 2, 3, 5, 8, 13… слов. Это понятная комбинаторика локального ограничения; она не является формулой для девятипетлевой амплитуды.

Что значит «отношения последних индексов»? Символ можно сгруппировать по последней букве:

\mathcal S(F)=\sum_{\ell}\mathcal S(F^{\ell})\otimes\ell.

Надстрочная метка \ell здесь не степень. Она означает коэффициент при d\log\ell в дифференциале dF=\sum_\ell F^\ell d\log\ell. Повторное разложение выделяет две, три и больше конечных букв. Это компоненты сокращённого представления, которое в источниках называют копроизведением; это не обычное перемножение функций.

Физические ограничения и интегрируемость дают линейные отношения между такими компонентами. Например, в используемой записи амплитуды компонент F^{a,f,d} равен нулю, поэтому слов с таким хвостом нет. Если два ненулевых блока переводятся друг в друга симметрией, их числа слов равны, хотя сами слова различны. Так возникают множители 2 и 3 ниже. Здесь нельзя считать каждую позицию независимой или выводить эти множители только из количества букв: конкретные отношения взяты из выбранной амплитудной задачи, раздел 3 восьмипетлевой работы.

В реальном опубликованном счёте используют более крупные блоки: собирают слова по последним трём буквам, затем по двум, затем по одной. Назовём n_{cdd} число ненулевых слов в блоке, заканчивающемся на последовательность c,d,d. Это счётчик слов, не коэффициент самого символа и не число независимых функций. В файле Cosmic9 буквы помечены дополнительным h: например, chchdh означает c,c,d. Ниже для читаемости метку опускаю.

Из публичного JSON берём десять исходных счётчиков:

Последние три буквы

Ненулевых слов в блоке

c c d

370 248 149

b d d

370 242 639

d c d

599 066 605

e c d

599 066 605

d f d

598 959 997

f f d

598 959 997

d d d

598 879 864

e f d

598 866 214

e d d

598 959 483

b f d

370 243 718

Блоки с разными последними буквами не пересекаются как множества полных слов. После использования отношений последних индексов и диэдральной симметрии получаем тот же способ сборки, который подробно выписан в восьмипетлевой работе, формулы (3.5)–(3.7):

n_{cd}=n_{ccd}+n_{dcd}+n_{ecd}=1\,568\,381\,359,n_{fd}=n_{bfd}+n_{dfd}+n_{efd}+n_{ffd}=2\,167\,029\,926,n_{dd}=2n_{bdd}+n_{ddd}+2n_{edd}=2\,537\,284\,108.

Двойки учитывают симметричные блоки с разными окончаниями, а не удвоение одного и того же слова. Затем

n_d=2n_{cd}+n_{dd}+2n_{fd}=10\,008\,106\,678,n_{\rm total}=3n_d=30\,024\,320\,034.

Последняя тройка — три симметричных класса конечной буквы. Это конкретная арифметика, которую можно повторить. Самые трудные исходные десять чисел уже получены из вычисленного символа; эти пять равенств не заменяют его вычисление. В девятипетлевом файле ненулевость считалась по простому модулю 2 147 483 647. Ненулевой остаток гарантирует ненулевой рациональный коэффициент при допустимом знаменателе; нулевой остаток сам по себе не доказывает рациональный нуль. Поэтому сохраняю формулировку «по опубликованному модульному счёту».

Все меньшие порядки — в сопоставимом представлении

Для 1–8 петель счётчики опубликованы в таблице II работы об антиподальной дуальности. Там они записаны для связанного трёхчастичного формфактора. На уровне символа его антиподальное отображение в выбранном алфавите меняет порядок и названия букв (а также ненулевые числовые множители), сохраняя число ненулевых слов; на стороне амплитуды речь идёт именно об ограничении на \Delta=0. Для восьми петель этот счёт отдельно проверен в работе Диксона и Лю. Таким образом, таблица не смешивает полную трёхпеременную амплитуду с её поверхностью.

Петли L

Вес слова 2L

Ненулевых слов

1

2

6

2

4

12

3

6

636

4

8

11 208

5

10

263 880

6

12

4 916 466

7

14

92 954 568

8

16

1 671 656 292

9

18

30 024 320 034 (модульный счёт)

Нулевой порядок нормированной величины равен единице.

Если расширить соглашение до веса 0, ему соответствует одно пустое слово; в таблице положительных весов оно обычно не записывается.

Число ненулевых слов от одной до девяти петель и комбинаторная верхняя граница
Число ненулевых слов от одной до девяти петель и комбинаторная верхняя граница

Вертикальная шкала логарифмическая. Даже после огромного сокращения пространства ответ быстро растёт. От восьми к девяти число слов увеличилось примерно в 17.96 раза. Это не множитель точности, не число диаграмм и не обещание такого же роста времени расчёта. Компактные представления используют общие блоки и отношения, поэтому тридцать миллиардов слов не обязательно хранятся в памяти как тридцать миллиардов отдельных строк.

Полный код сборки числа слов и учебной комбинаторики: 11_symbol_count.py
"""Проверка сборки опубликованного числа слов. Это не новый bootstrap.
Нужен E9_Delta0_counts.json: python 04_public_data.py скачивает его.
"""
from pathlib import Path
from collections import defaultdict
import json

root=Path(__file__).resolve().parent
data=json.loads((root/'E9_Delta0_counts.json').read_text(encoding='utf8'))
n=data['amplitude_triple_final_entries_Delta0_terms']
cd=n['chchdh']+n['dhchdh']+n['ehchdh']
fd=n['bhfhdh']+n['dhfhdh']+n['ehfhdh']+n['fhfhdh']
dd=2*n['bhdhdh']+n['dhdhdh']+2*n['ehdhdh']
d=2*cd+dd+2*fd
total=3*d
assert [cd,fd,dd,d,total]==[1568381359,2167029926,2537284108,10008106678,30024320034]
assert total==data['derived_counts']['total']
print('cd, fd, dd, d, total:',cd,fd,dd,d,total)
counts=[6,12,636,11208,263880,4916466,92954568,1671656292,total]
for L,number in enumerate(counts,1):
    print(f'L={L}: weight={2*L}, all six-letter words={6**(2*L)}, nonzero={number}')

# Учебная комбинаторика: слова из a,b без соседних bb.
# Это не ограничения реальной амплитуды!
ending={'a':1,'b':1}
for length in range(1,9):
    print('toy length',length,'words',sum(ending.values()))
    ending={'a':ending['a']+ending['b'],'b':ending['a']}

# При подсчёте ненулевых слов сначала складывают коэффициенты.
coeff=defaultdict(int)
for word,value in [('aa',1),('ab',2),('aa',-1),('ba',3)]:coeff[word]+=value
nonzero={word:value for word,value in coeff.items() if value!=0}
assert nonzero=={'ab':2,'ba':3}
print('После сокращения одинаковых слов:',nonzero)

15. Маленькая проверка настоящих данных

Теперь можно прикоснуться к опубликованному результату. В файле выборки содержатся 20 630 слов длины 18. Для каждого записаны рациональный коэффициент и два остатка по простым модулям. Ненулевых коэффициентов 20 400, нулевых — 230.

Зачем остатки? При работе с большими системами рациональная арифметика может порождать огромные числители и знаменатели. Вычисление по простому модулю удерживает числа в конечном диапазоне. Затем результат восстанавливают и проверяют дополнительными условиями. Аналогия с последними цифрами числа годится лишь частично: здесь используются простые модули и деление через обратный элемент.

Если рациональный коэффициент равен a/b, а b не делится на простое p, его образ вычисляется как

a\,b^{-1}\pmod p.

Обратный элемент b^{-1} — число, для которого bb^{-1} даёт остаток 1. В Python его находит pow(b, -1, p). Таким образом, проверка сравнивает два способа записать один коэффициент:

from fractions import Fraction

coefficient = Fraction('-105757/65536')
for p in [2147483647, 2147483629]:
    residue = (coefficient.numerator
               * pow(coefficient.denominator, -1, p)) % p
    print(p, residue)
# 2147483647 829521918
# 2147483629 1173913588

Полный скрипт читает строки, проверяет их длину, алфавит, диапазон остатков и согласованность обеих модульных записей с дробью. Все 20 630 строк проходят эти проверки.

У результата строго ограниченный смысл: файл внутренне согласован в проверенной части.

Если ошибочный коэффициент был согласованно записан и дробью, и двумя остатками, наш тест его не обнаружит.

Полный код проверки публичной выборки: 04_public_data.py
"""Проверка внутренней согласованности публичной выборки Cosmic9.
НЕ независимый расчёт коэффициентов или проверка физической правильности.
Загружает около 1.8 МБ; создаёт локальные копии данных и counts.png.
Запуск: python 04_public_data.py
"""
from pathlib import Path
from urllib.request import urlopen
from fractions import Fraction
import json
import hashlib
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt

OUT = Path(__file__).resolve().parent
BASE = "https://smsharma.io/cosmic-nine-loops/"
def fetch(relative, filename):
    raw = urlopen(BASE+relative, timeout=60).read()
    (OUT / filename).write_bytes(raw)
    print(filename, "sha256:", hashlib.sha256(raw).hexdigest())
    return raw

data = json.loads(fetch("delta0/E9_Delta0_counts.json", "E9_Delta0_counts.json"))
n8, n9 = 1_671_656_292, data["derived_counts"]["total"]
assert data["L"] == 9 and data["prime"] == 2_147_483_647
assert n9 == 3*data["derived_counts"]["n_{dh}"]
print(f"8 to 9 loops: {n8:,} -> {n9:,}; ratio={n9/n8:.3f}")

raw = fetch("samples/E9_sample_coefficients.txt", "E9_sample_coefficients.txt")
primes = (2_147_483_647, 2_147_483_629)
checked, nonzero = 0, 0
for line in raw.decode("utf-8").splitlines():
    if not line.strip() or line.startswith("#"):
        continue
    cols = line.split()
    assert len(cols) == 21, f"Unexpected format: {line[:80]}"
    word, residues = cols[:18], list(map(int, cols[18:20]))
    assert set(word) <= {"ah", "bh", "ch", "dh", "eh", "fh", "yu", "yv", "yw"}
    q = Fraction(cols[20])
    for p, expected in zip(primes, residues):
        assert 0 <= expected < p
        actual = q.numerator * pow(q.denominator, -1, p) % p
        assert actual == expected, f"Mismatch for {' '.join(word)} modulo {p}"
    checked += 1
    nonzero += q != 0
assert (checked, nonzero) == (20_630, 20_400)
print(f"Passed: {checked} rows, {nonzero} nonzero; 2 primes per row.")

plt.rcParams.update({"font.family": "DejaVu Sans", "font.size": 12})
fig, ax = plt.subplots(figsize=(9, 4.8))
bars = ax.barh(["8 петель", "9 петель"], [n8/1e9, n9/1e9], color=["#8aa5b5", "#237da6"], height=.48)
ax.invert_yaxis()
for bar, n in zip(bars, [n8, n9]):
    ax.text(bar.get_width()+.4, bar.get_y()+bar.get_height()/2,
            f"{n/1e9:.2f}".replace(".", ","), va="center", fontsize=14)
ax.set(xlim=(0, 35), xlabel="Миллиарды ненулевых слагаемых символа на Δ = 0")
ax.spines[["top", "right", "left"]].set_visible(False)
ax.grid(axis="x", alpha=.15)
ax.set_axisbelow(True)
fig.suptitle("Одна дополнительная петля: примерно в 18 раз больше слагаемых", fontsize=15, y=.95)
fig.text(.5, .03, "8 петель: Dixon & Liu, (3.7). 9 петель: Cosmic9, подсчёт по модулю 2 147 483 647.\nЭто размер развёрнутого символа на поверхности, не время расчёта и не прирост точности.",
         ha="center", fontsize=9, color="#536171")
fig.subplots_adjust(top=.82, bottom=.24, left=.13, right=.96)
fig.savefig(OUT / "counts.png", dpi=140)
plt.close(fig)

Как из остатков восстановить дробь и что означает «сертифицирована»

Проверить данную дробь по модулям проще, чем восстановить её. Например, 3/5 даёт остаток 5 по модулю 11 и 11 по модулю 13. Китайская теорема об остатках объединяет эти два условия в одно: остаток 115 по модулю M=11\cdot13=143. Но без ограничений на числитель и знаменатель этому условию удовлетворяет бесконечно много дробей.

Заранее зададим |a|\leq A=5, 1\leq b\leq B=7, взаимную простоту a,b и обратимость b по модулю M. Среди таких кандидатов получается единственная дробь 3/5. Почему это уже аргумент об однозначности? Если две допустимые дроби a/b и c/d дают один остаток, то ad-bc делится на M, а |ad-bc|\leq2AB. При 2AB<M единственный возможный кратный M — нуль. Значит, дроби равны.

Восстановление рационального коэффициента с явными границами
Восстановление рационального коэффициента с явными границами

Один модуль оставляет пять сокращённых дробей в выбранных границах; два оставляют одну. Границы — часть условия задачи, а не числа, подобранные после желаемого ответа. Если неизвестно, укладывается ли настоящий коэффициент в эти границы, сама картинка ещё не удостоверяет его восстановление.

Это учебный пример рациональной реконструкции. В крупных вычислениях используют эффективные алгоритмы вместо перебора, дополнительные простые модули и проверки исходных соотношений. Поэтому при слове «сертифицировано» нужно смотреть, что именно подтверждено: в каком представлении, при каких границах и на каких модулях. Наш перебор не воспроизводит весь протокол Cosmic9; он показывает, почему одного совпадения остатков недостаточно и какие условия превращают совпадение в доказательство однозначности внутри заданного класса.

16. Где заканчивается проверка и начинается доверие

В техническом описании метода, раздел 6.2 сравниваются два символных представления на 107 053 определяющих ненулевых коэффициентах. Для 3401 из них исходная проверка ограничивалась совпадением по двум простым модулям: стандартная симметричная граница реконструкции не восстанавливала нужную дробь. Авторы отдельно показали, что при другой форме границ восстанавливаются все 107 053 точных значения. Однако эти границы выбраны по размерам коэффициентов прямого bootstrap, поэтому это проверка согласованности двух вычислений, а не независимое восстановление из одних остатков. Она не сертифицирует автоматически 3821 базисную координату, о которых говорится ниже.

Авторы также описывают предположения о пространстве допустимых функций и проверки на меньших порядках. Уникальность остаётся уникальностью внутри принятого пространства. Полная функция получена отдельно; совпадение двух символных маршрутов не означает двух независимых вычислений всей функции.

Для функционального результата указано ещё одно существенное предположение: отношения между семикратными конечными компонентами, верные на уровне символа, перенесены на уровень функции. «Семикратные» означает семь фиксированных последних букв, а не семь петель. Отношения, следующие из интегрируемости и соответствующего уравнения суперсимметрии, имеют основания для такого переноса, но эмпирические отношения более высокого уровня не обязаны сохраняться автоматически. На странице результата это отмечено явно. Значит, независимое подтверждение этих предположений — отдельная задача, которую наша маленькая лабораторная не решает.

На странице данных Cosmic9 отдельно отмечено: в одном из базисных представлений рациональная реконструкция сертифицирована для 99.62% ненулевых координат, а для 3821 координаты — нет. Эти координаты не нужно путать со словами нашей маленькой выборки: это разные уровни представления результата.

Мне кажется полезным различать четыре вопроса. Правильно ли перенесена формула в код? Устойчив ли численный ответ? Корректна ли сама модель и её приближения? Действительно ли получен ранее неизвестный результат? Один успешный тест редко отвечает сразу на все четыре.

В нашем разборе это видно на конкретных шагах. Нормировка функции проверяется интегралом. Перенос notebook проверяется сохранёнными числами. Сетка квадратуры проверяется сгущением. Обрезание уровней — увеличением N. Область применимости RPA требует физического анализа. Проверка публичного файла — ещё один отдельный уровень.

Неприятная история о Claude, которая здесь действительно уместна

В марте 2026 года Мэтью Шварц опубликовал рассказ о работе с Claude Opus 4.5 над другой задачей по квантовой хромодинамике. По его описанию, модель подбирала параметры ради совпадения графиков, исключала неудобные вклады в погрешность и переносила ключевую формулу из другой физической системы. После вмешательства специалиста расчёт удалось исправить. Источник — сам Шварц.

На Хабре эпизод уже пересказывали в новости о Claude-аспиранте. Есть и более общая критическая статья о «вайб-физике».

Этот эпизод не доказывает, что новый результат ошибочен: задачи, запуски и версии модели различаются. Его роль в нашей истории другая. Теперь читатель уже знает несколько способов, которыми красивый ответ может оказаться недостаточным: неправильная нормировка, неверная формула, неподходящий класс функций, неустойчивое обрезание, проверка только тех условий, под которые ответ строился.

И мой старый архив оказался хорошим напоминанием, что проблема возникла задолго до LLM. Гладкий график из Mathematica тоже не объясняет, какой коэффициент должен стоять в знаменателе. Напечатанные пятнадцать цифр не превращаются сами собой в пятнадцать достоверных цифр. Совпавшие таблицы не заменяют понятной цепочки вывода.

Поэтому интерес девятипетлевой истории для меня — в возможности обсуждать конкретный вычислительный результат и конкретные проверки. Что подали на вход? В каком классе искали ответ? Чем проверили известные случаи? Что подтвердили независимо? Какую неопределённость оставили открытой? На эти вопросы можно отвечать по файлам и формулам, а затем спорить уже о содержании.

Квантовый пикторализм и код: сначала процесс, потом программа

У этого подхода есть близкий мне математический язык. В книге Боба Коке и Алекса Киссинджера «Изображение квантовых процессов. Начальный курс квантовой теории и рассуждений на диаграммах» (русский перевод А. А. Слинкина, ДМК Пресс, 2019; оригинал Picturing Quantum Processes, 2017) квантовая теория строится через процессы и правила их соединения. Экземпляр книги, который мы обсуждаем. Ниже использованы небольшие фрагменты рисунков именно из этого издания; номера страниц относятся к печатной нумерации.

Блоки, провода и смысл соединения

В главе 3 процесс изображается блоком, системы на его входах и выходах — проводами. У проводов есть типы. Функция двух чисел, сортировка списка и физическое преобразование получают общий язык, но сохраняют разные входы и выходы.

Примеры типизированных процессов из книги Коке и Киссинджера
Примеры типизированных процессов из книги Коке и Киссинджера

Фрагмент с. 46 книги Коке и Киссинджера, ДМК Пресс, 2019. Рисунки читаются снизу вверх: два вещественных числа поступают в функцию, одно число выходит. Подпись провода определяет, что именно разрешено соединить с блоком.

Слово композиция означает соединение процессов. Последовательная композиция g\circ f сначала выполняет f, затем передаёт результат в g. Параллельная f\otimes g действует на две системы рядом. Для линейных процессов это обычное произведение матриц и тензорное произведение соответственно. Матрица B A означает «сначала A, затем B». Переставить скобки разрешено по ассоциативности; переставить сами операции обычно нельзя.

Тензорное произведение строит пространство совместной системы. Если у каждой из двух систем есть базис |0\rangle,|1\rangle, то у пары четыре базисных состояния: |00\rangle,|01\rangle,|10\rangle,|11\rangle. Первая метка относится к первой системе, вторая — ко второй. Для матриц (A\otimes B)_{(i,k),(j,l)}=A_{ij}B_{kl}. Значит, в каждой клетке A_{ij} как будто стоит целый блок A_{ij}B; именно это вычисляет np.kron(A, B).

Блоки тензорного произведения и преобразование одной подсистемы
Блоки тензорного произведения и преобразование одной подсистемы

Слева A=\begin{pmatrix}1&2\\3&4\end{pmatrix} и X=\begin{pmatrix}0&1\\1&0\end{pmatrix}. Матрица A выбрана для демонстрации блоков и не объявляется физической квантовой операцией. Справа I\otimes X оставляет первую подсистему неизменной, а во второй меняет 0 и 1 местами: |10\rangle\to|11\rangle.

В разделе о символах знак \otimes сохранял порядок логарифмических букв. Здесь он соединяет пространства и линейные операторы физических подсистем. Обе записи имеют тензорную природу, но их элементы и правила вычисления различны: массив букв нельзя автоматически трактовать как матрицу квантовой операции.

Последовательное соединение процессов из книги
Последовательное соединение процессов из книги

Фрагмент с. 63 того же издания. Выходные типы первого процесса должны совпасть со входными типами следующего. Порядок проводов тоже часть определения: одинаковое число входов ещё не означает правильное соединение.

Есть и равенства диаграмм: некоторые изменения рисунка не меняют описываемый процесс. Это математическое утверждение с определёнными правилами, а не разрешение произвольно перерисовывать физику.

Эквивалентные диаграммы при сохранении соединений
Эквивалентные диаграммы при сохранении соединений

Фрагмент примера 3.6, с. 52. Геометрию проводов можно менять в рамках правил, сохраняя соединения. Для кода это подсказка: одну композицию полезно вычислить двумя эквивалентными способами и сравнить результат.

Например, при совместимых размерах матриц

(A\otimes B)(C\otimes D)=(AC)\otimes(BD)

Левая программа сначала создаёт большие матрицы, правая сначала умножает маленькие. Равенство задаёт проверку двух реализаций одного процесса. Но точное равенство формул в машинной арифметике превращается в сравнение с обоснованным допуском. В лабораторной части проверим его на комплексных матрицах. Заодно увидим, что забытое комплексное сопряжение может испортить квантовое среднее, сохранив все размеры массивов.

Связь с программами существует не только в виде аналогии: PyZX использует диаграммное ZX-исчисление для работы с квантовыми схемами. Это конкретная область с формальными правилами преобразований.

Квантовая схема описывает последовательность операций над квантовыми подсистемами и измерений. ZX-исчисление — графический язык для представления и преобразования таких схем с двумя семействами узлов, связанными с Z- и X-базисами. Здесь достаточно знать его назначение; его специальные правила нельзя переносить на произвольную блок-схему. Наши произвольные научные блок-схемы не получают автоматически ни этих правил, ни доказательства физической корректности.

Как применить процессную декомпозицию к коду от ИИ

Я предлагаю использовать её как один из методов повышения надёжности и улучшения контроля, рядом с аналитикой, численными проверками и предметной экспертизой. Вместо задания «напиши расчёт магнитоэкситона» сначала зафиксируем диаграмму вычисления:

Процессная схема расчёта диплома с контрактами и контрольными точками
Процессная схема расчёта диплома с контрактами и контрольными точками

Стрелка передаёт здесь не просто сам массив: вместе с ним передаются единицы, смысл индексов, соглашения о знаке и область применимости. Блок не считается проверенным только потому, что его выход подошёл по размеру следующей функции.

В этой схеме нужно различать физический процесс и процесс вычисления.

Материал отвечает на внешнее поле — это физическое явление.

Программа сначала строит базис, затем суммирует переходы и интегрирует матричный элемент — это организация расчёта модели физического явления.

Между ними есть соответствие, но вычислительная стрелка не обязана изображать причинное движение частицы во времени.

Диаграммы Фейнмана, процессные диаграммы книги и блок-схема программы имеют разные правила чтения; объединяет их возможность явно показывать структуру операции.

Для каждого блока пишем контракт: что он принимает, что возвращает, какие условия проверяет и на какие гарантии предыдущего блока опирается.

Блок

Контролируемый вход и выход

Независимая опора

Параметры → базис Ландау

B>0, v_F, окружение, индексы; энергии в оговорённых единицах, порядок компонент

E\propto\sqrt B, нормировка состояний, особый нулевой уровень

Базис → матричные элементы плотности

Нормированные состояния и волновой вектор; комплексный F_{ab}(q)

F_{ab}(0)=\delta_{ab}, сопряжение, правила отбора

Переходы → статический отклик

Заполнения и отсечение N; \Pi(q) в конкретной нормировке

Знак при выбранном определении, пустые суммы, изменение N

V,\Pi → экранированное W

Одинаковая сетка и совместимые единицы; знаменатель 1-V\Pi

\Pi=0\Rightarrow W=V; в рассматриваемой статике 0<W\leq V

W, состояние пары → матричный элемент

Внешний P, меры q\,dq и r\,dr, границы интеграла

Аналитический предел, другой порядок интегрирования, хвосты и число узлов

Компоненты → энергия возбуждения

Веса спинора, знак притяжения, вычитание исходного состояния

Проверка сборки и единиц, физическое сравнение в области модели

Для старого notebook есть дополнительная сложность:

Sigma1 — архивная величина со своей нормировкой.

Её нельзя просто объявить физической \Pi и подключить к современной формуле.

Нужен явный переход между соглашениями; расхождение коэффициентов из раздела 6 показывает, почему этот переход сам заслуживает отдельного блока и проверки.

Практический запрос к ИИ можно сформулировать так:

Реализуй только блок статического экранирования. Вход: положительное кулоновское ядро V и неположительный отклик Pi на одной сетке, уже приведённые к совместимой нормировке; произведение VPi безразмерно. Выход: W = V/(1 - VPi) в единицах V. Проверяй сетку, конечность значений, размерности и область применимости. Не исправляй знаки автоматически. Проверь нулевой отклик, аналитический пример и неверно соединённые входы. Не меняй контракт ради прохождения теста.

Это задание заметно проще проверить, чем весь диплом сразу. Следующий запрос получает зафиксированный контракт, а не свободный пересказ предыдущего ответа. В интерфейсе полезно хранить и происхождение массива: формулу, единицы, диапазон, параметры отсечения. Если ИИ меняет нормировку, это должно выглядеть как явное изменение интерфейса, затрагивающее соседние блоки.

Что ловит схема, а что остаётся за её пределами

В новом учебном коде входы экранирования оформлены объектами с сеткой и метками единиц. Перемножить массивы разных сеток или несовместимых размерностей программа откажется. Правильная реализация проходит проверки нулевого отклика и допустимого диапазона. Реализация с ошибкой в знаменателе проваливает их; неверная сетка останавливается ещё до физического вычисления. Это конкретный выигрыш от контролируемых соединений.

Но две неверные формулы могут случайно удовлетворить одному контракту.

Метку единиц можно подписать неправильно. Все блоки могут использовать одно ошибочное физическое предположение. Поэтому поверх локальных проверок нужны

интеграционные — проверка соединённых блоков

и сквозные: от исходных параметров до известного результата.

Нужны независимые ориентиры и проверка сходимости, а после них — проверка соответствия модели реальному явлению. Декомпозиция дает возможность локализовать ошибки, но сама по себе истинность модели она не производит.

Здесь для меня находится содержательная связь с «кодом будущего».

ИИ быстро предлагает реализации, человек задаёт язык процессов, интерфейсы и критерии опровержения. Ответ чата превращается в проверяемый компонент только после прохождения этих критериев. Роль человека смещается к постановке эксперимента над программой, но ответственность за смысл результата никуда не исчезает.

Процессуальное мышление при этом гораздо полезнее и шире этого.

Всё научное знание можно рассматривать как воспроизводимую цепочку преобразований — от подготовки системы и измерения до формулы, кода и вывода.

Тогда вопрос «что это за число?» дополняется вопросами «каким процессом оно получено, что считалось входом и какое изменение входа должно изменить выход?».

Это моя личная методологическая интерпретация науки, созвучная книге про квантовый пикторализм. Её достоинство чисто практическое: появляется больше мест, в которых красивое, но неверное объяснение может встретиться с проверкой и быть опровергнуто.

Обозначения, которые меняют смысл между разделами

Определения терминов даны по ходу вывода. Перед запуском кода полезно отдельно сверить локальные соглашения:

Обозначение

Как различать контексты

U, V, W

U — возмущение в задаче об энергии или потенциальная энергия в отклике; V, W — исходное и экранированное ядра. В теории графов V обозначает число вершин.

L, I

В диаграмме L — число независимых петель, I — число внутренних линий. В других формулах L — дифференциальный оператор, I — единичный оператор или интеграл.

g, \lambda

В учебных моделях — параметры взаимодействия; в топологии g — род поверхности. Связь ’т Хоофта \lambda=g_{\rm YM}^2N_c определена отдельно.

q, P

В отклике q — волновой вектор; в безразмерном notebook масштаб поглощён в аргумент. P — внешний магнитный импульс пары. В Excel аргумент назван q, хотя сопоставляется с P.

\Sigma, \Pi, \chi

Sigma1 — архивная сумма со своей нормировкой; \Pi — неприводимая поляризация, \chi — полный отклик. Их нельзя подменять без преобразования соглашений.

\otimes

В символе — упорядоченная запись логарифмических букв; в процессной схеме — тензорное произведение пространств или операторов.

Такие соглашения стоит хранить рядом с кодом: одинаковая буква или размер массива ещё не означают одинакового физического объекта.

17. Лабораторная: что запускать, что менять и что должно получиться

Код рассчитан на Python 3.12.

Запускайте учебные скрипты в обычном режиме, без флага -O и без включённой переменной окружения PYTHONOPTIMIZE, иначе проверки assert будут отключены.

Сохраните листинги из раскрывающихся блоков в одну папку под указанными именами.

Установите библиотеки:

python -m pip install numpy scipy matplotlib sympy mpmath pillow

На Windows вместо python можно использовать py -3.12.

Все вычислительные примеры и графики работают локально.

Только 04_public_data.py скачивает открытые файлы Cosmic9 — около 1.8 МБ.

Старые Mathematica и Excel для воспроизведения не нужны: исходные определения и исходные таблицы из них уже включены в код.

Ниже отдельный драйвер 10_laboratory.py: его аргументы — это “ручки” эксперимента.

Он сохраняет PNG и JSON с числами рядом с собой. Имена файлов включают параметры: например, lab_loops_g0.1_L9.png и lab_loops_g0.1_L15.png можно открыть рядом и сравнить. Команда запуска напечатана и внизу каждой картинки.

Картинки в этом разделе получены запуском этих команд.

Опыт 1. Когда добавление пузырьков помогает, а когда мешает

Сначала запускаем сходящуюся геометрическую цепочку:

python 10_laboratory.py --case rpa --s 0.8 --order 20
Вклады пузырьков и частичные суммы при s=0.8
Вклады пузырьков и частичные суммы при s=0.8

Слева знакопеременные вклады, справа их накопленная сумма.

Она колеблется вокруг 1/(1+0.8)=0.55555556 и приближается к ней.

При --order 20 включены члены от нулевого до двадцатого, то есть 21 член, и сумма равна 0.56067965. Измените только --order на 40: амплитуда колебаний уменьшится, а сумма станет примерно 0.55561463.

Теперь измените только силу отклика:

python 10_laboratory.py --case rpa --s 1.2 --order 20
Расходящиеся частичные суммы при s=1.2
Расходящиеся частичные суммы при s=1.2

Столбики растут, частичные суммы всё сильнее отклоняются от конечного решения 1/2.2\approx0.45454545; её двадцатый порядок около 21.36596.

Геометрическое разложение неприменимо при |s|\geq1, хотя уравнение самосогласования по-прежнему имеет конечное решение.

Попробуйте предсказать чётность отклонения до запуска.

Опыт 2. Уменьшаем магнитное поле в четыре раза

python 10_laboratory.py --case landau --B 10
python 10_laboratory.py --case landau --B 2.5
Уровни Ландау при 10 Тл и зависимость первого уровня от поля
Уровни Ландау при 10 Тл и зависимость первого уровня от поля

При 10 Тл должны получиться l_B\approx8.113 нм и E_1\approx114.736 мэВ. При 2.5 Тл магнитная длина удвоится до 16.226 нм, а первая энергия уменьшится вдвое до 57.368 мэВ.

Это проверка двух степенных законов:

l_B\propto B^{-1/2} и E_1\propto\sqrt B.

В скрипте v_F=10^6 м/с; если поменять его в коде, энергии изменятся пропорционально, а магнитная длина останется прежней.

Опыт 3. Девять петель против двух

python 10_laboratory.py --case loops --g 0.1 --order 9
python 10_laboratory.py --case loops --g 0.5 --order 9
Слабая связь: девять порядков улучшают учебный результат
Слабая связь: девять порядков улучшают учебный результат
Более сильная связь: после оптимального порядка ошибка увеличивается
Более сильная связь: после оптимального порядка ошибка увеличивается

В первом запуске исходный интеграл равен примерно 0.955576629510, и среди порядков 0–9 наименьшую ошибку даёт девятый. Во втором интеграл равен 0.838595434923, а лучший из тех же порядков — второй. На правом графике ошибка показана в логарифмическом масштабе; оранжевая точка отмечает минимум среди проверенных порядков.

Поставьте --order 15 и сначала предположите, продолжится ли улучшение при g=0.1. Затем попробуйте --g 0.2. Цель опыта — найти полезный диапазон усечения и увидеть, что номер последнего добавленного члена сам по себе не измеряет точность.

Численный интеграл — это отдельный ориентир для этой простой модели.

Контрольные ответы для продолжения опыта

При --g 0.1 --order 15 лучший среди порядков 0–15 — четырнадцатый: ошибка около 1.29\cdot10^{-6}. Следующий член уже слегка ухудшает ответ. При --g 0.2 --order 15 ориентир равен 0.919366349957, а лучший среди тех же порядков — шестой.

Сравниваем ошибки всех частичных сумм, а не только размер последнего слагаемого: это разные критерии усечения.

Опыт 4. Воспроизводим одну проверку моего диплома

Для этого опыта рядом должен лежать 06_graphene.py, приведённый в разделе 7:

python 10_laboratory.py --case cutoff --N 15 30 60
Как граница суммы по уровням меняет поляризацию
Как граница суммы по уровням меняет поляризацию

Это увеличение количества уровней в пузырьке, а не количества петель. При q=1 программа должна напечатать примерно −0.02659943, −0.02714981 и −0.02753810. Измените список на --N 30 60 120 и сравните изменение ответа с требуемой вам точностью.

Не делайте вывод о сходимости только по одной точке: слева построена зависимость по целому диапазону q.

Чтобы восстановить все исходные проверки, выполните python 06_graphene.py.

Для старых Excel-данных — python 07_excel.py. Чтобы сравнить публичные записи настоящих коэффициентов — python 04_public_data.py, а затем проверить сборку числа слов — python 11_symbol_count.py. Последние проверки устанавливают согласованность опубликованных данных; они не выводят амплитуду заново из физических законов.

Полный код лабораторных опытов с параметрами: 10_laboratory.py
"""Лабораторная: меняйте аргументы команд, а не скрытые настройки.
python 10_laboratory.py --case loops --g 0.1 --order 9
python 10_laboratory.py --case rpa --s 0.8 --order 20
python 10_laboratory.py --case landau --B 10
python 10_laboratory.py --case cutoff --N 15 30 60
Зависимости: numpy scipy matplotlib. Для cutoff нужен 06_graphene.py рядом.
"""
from pathlib import Path
from fractions import Fraction
from math import factorial
import argparse, importlib.util, json
import numpy as np
from scipy.integrate import quad
from scipy.constants import hbar, elementary_charge as e
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt

def publication_layout(fig):
    """Readable labels at a 600 px article width; retain data and panel order."""
    import textwrap
    from matplotlib.text import Text
    width, height = fig.get_size_inches()
    fig.set_size_inches(width, height * 1.22, forward=True)
    scale = width / 8.0
    main, small = 14.0 * scale, 12.0 * scale
    for text in fig.findobj(Text):
        if text.get_text().strip():
            text.set_fontsize(max(text.get_fontsize(), main))
            if text.get_color() in ['#d77b27', '#ed8b36', '#df7b28', '#d77527']:
                text.set_color('#87470f')
    for ax in fig.axes:
        for tick in ax.get_xticklabels() + ax.get_yticklabels():
            tick.set_fontsize(small)
        for label in [ax.xaxis.label, ax.yaxis.label]:
            txt = label.get_text()
            if '$' not in txt:
                label.set_text('\n'.join(textwrap.fill(s, 39) for s in txt.splitlines()))
        title = ax.get_title()
        if '$' not in title:
            columns = max(25, int(ax.get_position().width * 75))
            ax.set_title('\n'.join(textwrap.fill(s, columns) for s in title.splitlines()),
                         fontsize=main, pad=12)
        legend = ax.get_legend()
        if legend:
            for text in legend.get_texts(): text.set_fontsize(small)
        for line in ax.lines: line.set_linewidth(max(line.get_linewidth(), 1.8))
    if fig._suptitle:
        txt = fig._suptitle.get_text()
        if '$' not in txt:
            fig._suptitle.set_text(textwrap.fill(txt, 57))
        fig._suptitle.set_fontsize(max(main + 2, 18))
    for text in fig.texts:
        if text is fig._suptitle: continue
        txt=text.get_text()
        if '$' not in txt:
            text.set_text('\n'.join(textwrap.fill(s, 68) for s in txt.splitlines()))
    if type(fig.get_layout_engine()).__name__ == 'PlaceHolderLayoutEngine':
        fig.tight_layout(rect=(.01,.18,.99,.91),h_pad=2,w_pad=2)
    if type(fig.get_layout_engine()).__name__ == 'ConstrainedLayoutEngine':
        fig.get_layout_engine().set(h_pad=.15, w_pad=.12)



OUT=Path(__file__).resolve().parent
plt.rcParams.update({'font.family':'DejaVu Sans','font.size':12,
                     'axes.spines.top':False,'axes.spines.right':False})

def coefficients(nmax):
    moment=lambda k:Fraction(factorial(2*k),2**k*factorial(k))
    z=[(-1)**n*moment(2*n)/(24**n*factorial(n)) for n in range(nmax+1)]
    num=[(-1)**n*moment(2*n+1)/(24**n*factorial(n)) for n in range(nmax+1)]
    c=[]
    for n in range(nmax+1):c.append(num[n]-sum(z[k]*c[n-k] for k in range(1,n+1)))
    return c

def run(a):
    fig,axes=plt.subplots(1,2,figsize=(11,5.5)); result={'case':a.case}
    if a.case=='loops':
        if a.g<=0:raise ValueError('Нужна g>0')
        c=coefficients(a.order); n=np.arange(a.order+1)
        weight=lambda x:np.exp(-x*x/2-a.g*x**4/24)
        exact=quad(lambda x:x*x*weight(x),0,np.inf,epsabs=1e-12,epsrel=1e-12)[0]/quad(weight,0,np.inf,epsabs=1e-12,epsrel=1e-12)[0]
        partial=np.cumsum([float(v)*a.g**k for k,v in enumerate(c)])
        err=abs(partial-exact); best=int(np.argmin(err))
        axes[0].plot(n,partial,'o-',label='Частичная сумма');axes[0].axhline(exact,color='black',ls='--',label='Численный интеграл');axes[0].set(ylabel='G',xlabel='Порядок L');axes[0].legend(fontsize=10)
        axes[1].semilogy(n,np.maximum(err,1e-16),'o-');axes[1].plot(best,max(err[best],1e-16),'o',color='#ed8b36',ms=12);axes[1].set(xlabel='Порядок L',ylabel='Абсолютная ошибка')
        fig.suptitle(f'g={a.g:g}: G={exact:.12f}; лучший из 0…{a.order} — L={best}',fontsize=16)
        command=f'python 10_laboratory.py --case loops --g {a.g:g} --order {a.order}'
        name=f'lab_loops_g{a.g:g}_L{a.order}';result.update(g=a.g,order=a.order,reference=exact,best_order=best,partial=partial.tolist())
    elif a.case=='rpa':
        if a.s<0:raise ValueError('Для примера экранирования нужна s>=0')
        n=np.arange(a.order+1); terms=(-a.s)**n; partial=np.cumsum(terms); exact=1/(1+a.s)
        axes[0].bar(n,terms,color=['#147d92' if v>=0 else '#ed8b36' for v in terms]);axes[0].set(xlabel='Число вставок n',ylabel='Вклад (−s)ⁿ')
        axes[1].plot(n,partial,'o-',label='Сумма до n');axes[1].axhline(exact,color='black',ls='--',label='1/(1+s)');axes[1].set(xlabel='Максимальное n',ylabel='W / V₀');axes[1].legend(fontsize=10)
        assert np.allclose(partial,(1-(-a.s)**(n+1))/(1+a.s))
        fig.suptitle(f's={a.s:g}: решение RPA={exact:.9f}; ряд '+('сходится' if a.s<1 else 'не сходится'),fontsize=17)
        command=f'python 10_laboratory.py --case rpa --s {a.s:g} --order {a.order}'
        name=f'lab_rpa_s{a.s:g}_N{a.order}';result.update(s=a.s,order=a.order,exact=exact,last=float(partial[-1]))
    elif a.case=='landau':
        if a.B<=0:raise ValueError('Нужно B>0')
        lb=np.sqrt(hbar/(e*a.B)); n=np.arange(-5,6); en=np.sign(n)*hbar*1e6/lb*np.sqrt(2*abs(n))/e*1000
        for j,E in zip(n,en):axes[0].hlines(E,0,1,color='#147d92');axes[0].text(1.03,E,str(j),va='center',fontsize=10)
        axes[0].set(xlim=(0,1.4),ylabel='Энергия, мэВ',title=f'Уровни при B={a.B:g} Тл');axes[0].set_xticks([])
        bs=np.linspace(.1,max(12,a.B),250);energy=1e6*np.sqrt(2*e*hbar*bs)/e*1000
        axes[1].plot(bs,energy);axes[1].scatter([a.B],[en[6]],color='#ed8b36',s=90);axes[1].set(xlabel='B, Тл',ylabel='E₁, мэВ',title='Предсказание: E₁ ∝ √B')
        fig.suptitle(f'lB={lb*1e9:.3f} нм, E₁={en[6]:.3f} мэВ, vF=10⁶ м/с',fontsize=17)
        command=f'python 10_laboratory.py --case landau --B {a.B:g}'
        name=f'lab_landau_B{a.B:g}';result.update(B=a.B,lB_nm=lb*1e9,E1_meV=float(en[6]))
    else:
        spec=importlib.util.spec_from_file_location('graphene',OUT/'06_graphene.py')
        module=importlib.util.module_from_spec(spec);spec.loader.exec_module(module)
        if min(a.N)<1:raise ValueError('N>=1')
        q=np.linspace(.03,5,100); vals=[]
        for N in a.N:
            y=module.sigma(q,0,N)
            assert np.isfinite(y).all(), 'Поляризация должна оставаться конечной'
            axes[0].plot(q,y,label=f'N={N}');vals.append(float(module.sigma(1.,0,N)))
        axes[0].set(xlabel='q',ylabel='Σ₀,N(q)',title='Граница суммы — параметр вычисления');axes[0].legend(fontsize=10)
        axes[1].plot(a.N,vals,'o-');axes[1].set(xlabel='N',ylabel='Σ₀,N(1)',title='Значение при q=1 ещё меняется')
        command='python 10_laboratory.py --case cutoff --N '+' '.join(map(str,a.N))
        name='lab_cutoff_N'+'_'.join(map(str,a.N));result.update(N=a.N,sigma_q1=vals)
    for ax in axes:ax.grid(alpha=.18)
    fig.text(.5,.025,command,ha='center',fontsize=11,family='DejaVu Sans Mono',color='#147d92')
    fig.tight_layout(rect=(0,.08,1,.93));publication_layout(fig); fig.savefig(OUT/(name+'.png'),dpi=135, bbox_inches="tight");plt.close(fig)
    (OUT/(name+'.json')).write_text(json.dumps(result,ensure_ascii=False,indent=2),encoding='utf8')
    print(json.dumps(result,ensure_ascii=False))
    print('Картинка:',OUT/(name+'.png'))

if __name__=='__main__':
    p=argparse.ArgumentParser();p.add_argument('--case',choices=['loops','rpa','landau','cutoff'],required=True)
    p.add_argument('--g',type=float,default=.1);p.add_argument('--s',type=float,default=.8)
    p.add_argument('--order',type=int,default=9);p.add_argument('--B',type=float,default=10.)
    p.add_argument('--N',type=int,nargs='+',default=[15,30,60]);a=p.parse_args()
    max_order=200 if a.case=='rpa' else 30
    if not 0<=a.order<=max_order:p.error(f'--order должен быть от 0 до {max_order}')
    run(a)

Опыт 5. Обнаружит ли проверка намеренно испорченную программу

Сохраните следующий листинг как 12_verify_ai.py рядом с 06_graphene.py и выполните:

python 12_verify_ai.py

На Windows можно использовать py -3.12 12_verify_ai.py. Скачивание данных не требуется. Программа создаст три картинки из начала статьи — ai_diploma_units.png, ai_mutation_matrix.png и ai_smooth_wrong.png, таблицу ai_verification_matrix.csv и подробный отчёт ai_verification_report.json. В отчёте отдельно сохранены размерностная проверка записи из диплома и результаты опыта с намеренными модификациями.

Все варианты вычисляются отдельно, файл с восстановлением notebook не переписывается.

Ожидаемый результат: контрольная строка проходит шесть условий; пять намеренных модификаций нарушают хотя бы одно условие каждая.

Надписи FAIL у испорченных вариантов — ожидаемый результат опыта, поэтому сам сценарий при правильной работе завершится с кодом 0. Скрипт отдельно проверяет, что получился именно заявленный рисунок обнаруженных отклонений.

В коде найдите список cases. У варианта wrong_width замените radial_scale=np.sqrt(2) на radial_scale=1.1. Нормировка должна остаться равной 1, а средний квадрат радиуса станет 2/1.1^2\approx1.65289256. Это можно предсказать заменой переменной в интеграле, до запуска Python. Изменённая функция снова пройдёт нормировку и не пройдёт проверку момента. Перед изменением сохраните копию исходного скрипта, чтобы сравнить оба отчёта.

Ещё один опыт можно провести без редактирования: откройте CSV и представьте, что у вас есть только столбцы «Конечные значения» и «Нормировка».

Четыре из пяти модификаций останутся незамеченными. Восстановите остальные столбцы и для каждого красного результата объясните, из какого свойства модели следует ожидание. Цель — научиться обосновывать проверки, а не накапливать число assert.

Результат последнего столбца зависит от выбранного порога, номера уровня и аргумента q. Прикладная задача должна задавать собственную требуемую точность и диапазон. Единственный порог 1% при q=1 в этом опыте не является универсальным критерием достаточного обрезания.

Полный код опыта с намеренными ошибками: 12_verify_ai.py
"""Учебный эксперимент: способны ли проверки обнаружить известные дефекты?
python 12_verify_ai.py
Рядом нужен 06_graphene.py. Зависимости: numpy scipy matplotlib.
Дефекты внесены намеренно; это НЕ частота ошибок ИИ и НЕ история его ответов.
Исходный 06_graphene.py не изменяется. Результаты: PNG, CSV и JSON.
"""
from pathlib import Path
from dataclasses import dataclass, replace
import csv, hashlib, importlib.util, json, platform
import numpy as np
from scipy.integrate import quad
from scipy.constants import hbar, elementary_charge as e
import scipy
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt

def publication_layout(fig):
    """Readable labels at a 600 px article width; retain data and panel order."""
    import textwrap
    from matplotlib.text import Text
    width, height = fig.get_size_inches()
    fig.set_size_inches(width, height * 1.22, forward=True)
    scale = width / 8.0
    main, small = 14.0 * scale, 12.0 * scale
    for text in fig.findobj(Text):
        if text.get_text().strip():
            text.set_fontsize(max(text.get_fontsize(), main))
            if text.get_color() in ['#d77b27', '#ed8b36', '#df7b28', '#d77527']:
                text.set_color('#87470f')
    for ax in fig.axes:
        for tick in ax.get_xticklabels() + ax.get_yticklabels():
            tick.set_fontsize(small)
        for label in [ax.xaxis.label, ax.yaxis.label]:
            txt = label.get_text()
            if '$' not in txt:
                label.set_text('\n'.join(textwrap.fill(s, 39) for s in txt.splitlines()))
        title = ax.get_title()
        if '$' not in title:
            columns = max(25, int(ax.get_position().width * 75))
            ax.set_title('\n'.join(textwrap.fill(s, columns) for s in title.splitlines()),
                         fontsize=main, pad=12)
        legend = ax.get_legend()
        if legend:
            for text in legend.get_texts(): text.set_fontsize(small)
        for line in ax.lines: line.set_linewidth(max(line.get_linewidth(), 1.8))
    if fig._suptitle:
        txt = fig._suptitle.get_text()
        if '$' not in txt:
            fig._suptitle.set_text(textwrap.fill(txt, 57))
        fig._suptitle.set_fontsize(max(main + 2, 18))
    for text in fig.texts:
        if text is fig._suptitle: continue
        txt=text.get_text()
        if '$' not in txt:
            text.set_text('\n'.join(textwrap.fill(s, 68) for s in txt.splitlines()))
    if type(fig.get_layout_engine()).__name__ == 'PlaceHolderLayoutEngine':
        fig.tight_layout(rect=(.01,.10,.99,.91),h_pad=2,w_pad=2)
    if type(fig.get_layout_engine()).__name__ == 'ConstrainedLayoutEngine':
        fig.get_layout_engine().set(h_pad=.15, w_pad=.12)


from matplotlib.colors import ListedColormap

OUT=Path(__file__).resolve().parent
spec=importlib.util.spec_from_file_location('graphene',OUT/'06_graphene.py')
graphene=importlib.util.module_from_spec(spec)
spec.loader.exec_module(graphene)
plt.rcParams.update({'font.family':'DejaVu Sans','font.size':12,
                     'axes.spines.top':False,'axes.spines.right':False})

@dataclass(frozen=True)
class Case:
    key: str
    label: str
    jacobian: bool=True
    radial_scale: float=1.0
    field_power: float=0.5
    rpa_sign: int=1
    cutoff: int=60

baseline=Case('control','Контрольная версия\nN=60')
cases=[baseline,
       replace(baseline,key='no_r',label='Потерян якобиан r',jacobian=False),
       replace(baseline,key='wrong_width',label='Неверная ширина,\nнормировка сохранена',radial_scale=np.sqrt(2)),
       replace(baseline,key='wrong_B',label='E₁ ∝ B вместо √B;\nпри 10 Тл совпадает',field_power=1.0),
       replace(baseline,key='wrong_rpa_sign',label='Неверный знак\nв знаменателе RPA',rpa_sign=-1),
       replace(baseline,key='small_N',label='N=15 при требовании\nизменения < 1%',cutoff=15)]

def density(r,case):
    # a*f(a*r) сохраняет двумерную нормировку при положительном a.
    # a=sqrt(2) намеренно меняет ширину относительно формулы архива.
    a=case.radial_scale
    psi=a*graphene.fg(0,0,a*np.asarray(r))
    measure=np.asarray(r) if case.jacobian else np.ones_like(r,dtype=float)
    return 2*np.pi*measure*abs(psi)**2

def energy_meV(B,case):
    # Все варианты проходят одну опорную точку при 10 Тл.
    # Неверный закон сохраняет размерность за счёт отношения B/Bref.
    Bref=10.0
    Eref=1e6*np.sqrt(2*e*hbar*Bref)/e*1000
    return Eref*(np.asarray(B)/Bref)**case.field_power

def screened_ratio(s,case):
    return 1/(1+case.rpa_sign*np.asarray(s))

def cutoff_change(N):
    coarse=float(graphene.sigma(1.,0,N))
    fine=float(graphene.sigma(1.,0,2*N))
    return abs(coarse-fine)/abs(fine),coarse,fine

TESTS=['Конечные\nзначения','Нормировка\n= 1','Момент\n⟨r²⟩ = 2',
       'Масштаб\nпо полю','Статическое\nослабление','Удвоение N:\nизменение < 1%']

def evaluate(case):
    norm=quad(lambda r:density(r,case),0,np.inf,epsabs=1e-11,epsrel=1e-11)[0]
    moment=quad(lambda r:r*r*density(r,case),0,np.inf,epsabs=1e-11,epsrel=1e-11)[0]
    B_ratio=float(energy_meV(2.5,case)/energy_meV(10.,case))
    s=np.array([.1,.5,.8]);wr=screened_ratio(s,case)
    delta,coarse,fine=cutoff_change(case.cutoff)
    finite=bool(np.isfinite(np.r_[density(np.linspace(0,5,101),case),
                                 energy_meV(np.array([2.5,10.]),case),wr,
                                 [norm,moment,delta,coarse,fine]]).all())
    passed=[finite,abs(norm-1)<1e-9,abs(moment-2)<1e-9,
            abs(B_ratio-.5)<1e-12,bool(np.all((wr>0)&(wr<=1))),delta<.01]
    return {'case':case.key,'label':case.label.replace('\n',' '),
            'passed':list(map(bool,passed)),'normalization':float(norm),
            'radial_second_moment':float(moment),'E1_2.5T_over_10T':B_ratio,
            'W_over_V0_at_s0.8':float(wr[-1]),'N':case.cutoff,
            'sigma_N':coarse,'sigma_2N':fine,'relative_change_on_doubling':delta}

def main():
    # Показатели степеней базовых единиц (кг, м, с).
    # Это проверка записи из диплома, отдельно от намеренных модификаций ниже.
    dim_hbar=np.array([1,2,-1]);dim_v=np.array([0,1,-1]);dim_l=np.array([0,1,0])
    dim_energy=np.array([1,2,-2])
    dim_archive=dim_hbar-dim_v-dim_l
    dim_correct=dim_hbar+dim_v-dim_l
    assert not np.array_equal(dim_archive,dim_energy)
    assert np.array_equal(dim_correct,dim_energy)
    rows=[evaluate(case) for case in cases]
    assert all(rows[0]['passed']), 'Контрольная версия не прошла выбранные проверки'
    # Проверяем сам демонстрационный эксперимент: каждая известная модификация
    # должна нарушить заранее выписанное условие. Матрица включает все проверки,
    # в том числе те, которые данную ошибку не замечают.
    expected_failures={'no_r':{1,2},'wrong_width':{2},'wrong_B':{3},
                       'wrong_rpa_sign':{4},'small_N':{5}}
    for row in rows[1:]:
        failed={i for i,ok in enumerate(row['passed']) if not ok}
        assert failed==expected_failures[row['case']],row
    report={'experiment':'intentionally introduced defects; not model benchmark',
            'environment':{'python':platform.python_version(),'numpy':np.__version__,
                           'scipy':scipy.__version__,'matplotlib':matplotlib.__version__},
            'source_sha256':hashlib.sha256((OUT/'06_graphene.py').read_bytes()).hexdigest(),
            'tests':[s.replace('\n',' ') for s in TESTS],'rows':rows,
            'diploma_units':{'base_units':['kg','m','s'],
                             'hbar_div_vF_lB':dim_archive.tolist(),
                             'hbar_times_vF_div_lB':dim_correct.tolist(),
                             'energy':dim_energy.tolist(),
                             'scope':'error in the written energy unit; not proof that original numerical results are wrong'},
            'limitations':['cutoff check is change at one q=1, not an error bound to N=infinity',
                           'synthetic defects do not estimate error frequency of any AI model',
                           'normalization does not validate the physical model',
                           'passing all selected checks does not prove absence of other errors']}
    (OUT/'ai_verification_report.json').write_text(json.dumps(report,ensure_ascii=False,indent=2),encoding='utf8')
    with (OUT/'ai_verification_matrix.csv').open('w',encoding='utf-8-sig',newline='') as f:
        writer=csv.writer(f);writer.writerow(['Вариант']+report['tests'])
        for row in rows:writer.writerow([row['label']]+['PASS' if v else 'FAIL' for v in row['passed']])

    fig,axes=plt.subplots(1,2,figsize=(10,4.6))
    for ax in axes:ax.axis('off')
    axes[0].set_title('Скорость в знаменателе',fontsize=16,color='#a33d34')
    axes[0].text(.5,.65,r'$\hbar/(v_F l_B)$',ha='center',fontsize=31)
    axes[0].text(.5,.37,r'$[\hbar/(v_F l_B)]=\mathrm{kg}$',ha='center',fontsize=23)
    axes[0].text(.5,.1,'Не имеет размерности энергии',ha='center',fontsize=14,color='#a33d34')
    axes[1].set_title('Масштаб энергии из гамильтониана',fontsize=16,color='#217455')
    axes[1].text(.5,.65,r'$\hbar v_F/l_B$',ha='center',fontsize=31)
    axes[1].text(.5,.37,r'$[\hbar v_F/l_B]=\mathrm{kg}\,\mathrm{m}^2/\mathrm{s}^2$',ha='center',fontsize=21)
    axes[1].text(.5,.1,'Имеет размерность энергии',ha='center',fontsize=14,color='#217455')
    fig.suptitle('Ошибка в записи единицы энергии: проверка по СИ',fontsize=18,y=.96)
    fig.text(.5,.025,'Формулы перенабраны для сравнения; исходная запись — в разделе 9 диплома.\n'
             'Ошибка в тексте не устанавливает автоматически ошибку всех старых численных результатов.',
             ha='center',fontsize=10)
    fig.subplots_adjust(left=.02,right=.98,top=.76,bottom=.2,wspace=.08)
    publication_layout(fig); fig.savefig(OUT/'ai_diploma_units.png',dpi=150, bbox_inches="tight");plt.close(fig)

    matrix=np.array([row['passed'] for row in rows],dtype=int)
    fig,ax=plt.subplots(figsize=(12,7.1))
    ax.imshow(matrix,cmap=ListedColormap(['#eaa49d','#b3dbca']),vmin=0,vmax=1,aspect='auto')
    display_tests=['Конечные\nчисла','Норма\n= 1','Момент\n⟨r²⟩ = 2',
                   'Масштаб\nE(B)','Статика:\n0 < W ≤ V','N → 2N\n< 1%']
    ax.set_xticks(range(len(TESTS)),display_tests,fontsize=10)
    ax.xaxis.tick_top();ax.tick_params(length=0,pad=9)
    ax.set_yticks(range(len(cases)),[c.label for c in cases],fontsize=11)
    for y in range(len(cases)):
        for x in range(len(TESTS)):
            ax.text(x,y,'PASS' if matrix[y,x] else 'FAIL',ha='center',va='center',fontsize=12)
    ax.set_xticks(np.arange(-.5,len(TESTS),1),minor=True)
    ax.set_yticks(np.arange(-.5,len(cases),1),minor=True)
    ax.grid(which='minor',color='white',linewidth=3)
    ax.tick_params(which='minor',length=0)
    fig.suptitle('Что обнаруживают проверки, а что проходит незамеченным',fontsize=17,y=.97)
    fig.text(.5,.06,'PASS — выполнено данное условие; FAIL — обнаружено отклонение.\n'
             'Все дефекты внесены намеренно. Зелёная строка не доказывает правильность всей физики.',
             ha='center',fontsize=11)
    fig.subplots_adjust(left=.25,right=.98,top=.79,bottom=.25)
    publication_layout(fig); fig.savefig(OUT/'ai_mutation_matrix.png',dpi=150, bbox_inches="tight");plt.close(fig)

    fig,axes=plt.subplots(3,1,figsize=(8,10))
    rs=np.linspace(0,4,300)
    for case,label in [(cases[0],'По исходной формуле'),(cases[1],'Без якобиана r'),(cases[2],'Другая ширина')]:
        axes[0].plot(rs,density(rs,case),label=label)
    axes[0].set(xlabel='r / lB',ylabel='Радиальная плотность',title='Гладкость не проверяет меру')
    Bs=np.linspace(.2,12,300)
    for case,label in [(cases[0],'E₁ ∝ √B'),(cases[3],'E₁ ∝ B')]:
        axes[1].plot(Bs,energy_meV(Bs,case),label=label)
    axes[1].scatter([10],[energy_meV(10,baseline)],color='black',zorder=5)
    axes[1].set(xlabel='B, Тл',ylabel='E₁, мэВ',title='Одна точка не проверяет закон')
    ss=np.linspace(0,.8,300)
    axes[2].plot(ss,screened_ratio(ss,baseline),label='1 / (1+s)')
    axes[2].plot(ss,screened_ratio(ss,cases[4]),label='1 / (1−s)')
    axes[2].axhline(1,color='gray',ls=':',label='Без экранирования')
    axes[2].set(xlabel='s ≥ 0',ylabel='W / V₀',title='Оба графика конечны на отрезке')
    for ax in axes:ax.grid(alpha=.18);ax.legend(fontsize=9)
    fig.suptitle('Неверная формула тоже умеет рисовать красивую кривую',fontsize=17)
    fig.tight_layout(rect=(0,.02,1,.9));publication_layout(fig); fig.savefig(OUT/'ai_smooth_wrong.png',dpi=150, bbox_inches="tight");plt.close(fig)
    for row in rows:
        print(row['case'],['PASS' if v else 'FAIL' for v in row['passed']],
              f"norm={row['normalization']:.9f}",
              f"moment={row['radial_second_moment']:.9f}",
              f"delta_N={100*row['relative_change_on_doubling']:.6f}%")
    print('5 намеренных модификаций обнаружены; исходный файл не менялся.')
    print('Размерности записи диплома:',dim_archive.tolist(),'; размерности энергии:',dim_energy.tolist())

if __name__=='__main__':main()

Опыт 6. Контракты процессов и решатель, который убедительно решает не ту задачу

Запуск нового файла:

python 13_process_checks.py

Он не требует архивного notebook. Нужны NumPy, SciPy, SymPy и Matplotlib; зависимости устанавливаются той же командой из начала лабораторной.

Рядом с программой появятся восемь рисунков deep_*.png и process_checks_report.json с результатами и версиями среды. Скрипт использует обычные assert, поэтому запускайте его без флага -O, отключающего эти проверки.

Сначала проверяем соединения.

В игрушечном примере V(q)=2/q, \Pi(q)=-0.2q, поэтому V\Pi=-0.4 и W/V=1/1.4\approx0.7142857 на всей положительной сетке. Точный ответ известен до написания кода. Смените сетку только у \Pi или её метку единиц: блок обязан отказаться от вычисления. Отдельные случаи в программе уже проверяют эти отказы.

Метки обозначают степени одной заранее согласованной единицы ядра; это демонстрация контракта, а не полноценная библиотека физических единиц.

Замените в копии screened знаменатель 1 - v.values * pi.values на 1 + v.values * pi.values. В этом примере получится

W/V=1/0.6>1: проверка аналитического ответа должна остановить программу. После опыта верните исходную формулу. Контракт не скрывает ошибку автоматическим модулем или сменой знака.

Затем испытываем численный решатель. На отрезке 0<x<1 поставим задачу

-u''+\mu^2u=f(x),\qquad u(0)=u(1)=0.

Не будем гадать правильный ответ. Сами назначим u_*(x)=\sin(\pi x)+0.1\sin(3\pi x) и вычислим правую часть:

f(x)=(\pi^2+\mu^2)\sin(\pi x)+0.1(9\pi^2+\mu^2)\sin(3\pi x).

Теперь дадим решателю только f, границы и коэффициенты.

В коде вторая производная заменяется центральной разностью [u(x+h)-2u(x)+u(x-h)]/h^2. Разложение Тейлора показывает, что её ошибка на гладкой функции имеет порядок h^2. Следовательно, в подходящем режиме уменьшение шага вдвое должно уменьшать ошибку решения примерно вчетверо. При \mu=2 получено:

Внутренних узлов

Шаг

Максимальная ошибка относительно u_*

31

1/32

9.98786\cdot10^{-4}

63

1/64

2.49168\cdot10^{-4}

127

1/128

6.22589\cdot10^{-5}

255

1/256

1.55686\cdot10^{-5}

Измеренный порядок p=\log(e_h/e_{h/2})/\log2 близок к 2. Это сильнее одного совпадения на одной сетке, но всё ещё проверка выбранного семейства гладких задач.

Точное решение, порядок сходимости и ошибка при неправильном операторе
Точное решение, порядок сходимости и ошибка при неправильном операторе

Что менять: в manufactured увеличивайте n по последовательности 31, 63, 127, 255; затем сравните laplace_factor=1.0 и 1.1. Правую часть оставьте прежней. Нижняя панель показывает ожидаемую ошибку после такой порчи.

Главная ловушка: с laplace_factor=1.1 код решает уже -1.1u''+\mu^2u=f. На 127 узлах невязка его собственной дискретной системы около 5.2\cdot10^{-12} — на вид прекрасный результат. Но ошибка относительно требуемого u_* примерно 0.058. Невязка — разность левой и правой частей проверяемого уравнения; маленькая невязка неверно составленного уравнения не доказывает решение исходной задачи. Поэтому правую часть и точное решение мы получили отдельно от сборки численного оператора.

Оставшиеся проверки связывают опыт с теорией статьи: численное интегрирование матричных элементов сравнивается с 1/\sqrt2, 1/2 и 3/2; Фурье-образы профилей — с аналитическими формфакторами; две композиции комплексных матриц — друг с другом.

Для |\psi\rangle=(1,i)/\sqrt2 правильное среднее единичного оператора равно 1, а ошибочное psi.T @ psi даёт 0. Размеры массивов совпадают в обоих случаях: нужен семантический тест комплексного сопряжения.

Программа также проверяет символный пример веса 2 и потерю константы у дилогарифма. Она не воспроизводит девятипетлевое доказательство.

Все эти проверки — небольшие независимые опоры, которые помогают понять, что должен контролировать каждый блок большого расчёта.

Полный код контрактов, изготовленного решения и восьми рисунков: 13_process_checks.py
"""Independent teaching checks and figures; run: python 13_process_checks.py.

This is a verification laboratory, not a validated graphene model.
Requires numpy, scipy, sympy, matplotlib. Writes beside this file.
"""
from dataclasses import dataclass
from pathlib import Path
import json
import platform
import numpy as np
import scipy
from scipy.integrate import quad
from scipy.linalg import solve_banded
from scipy.special import spence
import sympy as sp
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt

def publication_layout(fig):
    """Readable labels at a 600 px article width; retain data and panel order."""
    import textwrap
    from matplotlib.text import Text
    width, height = fig.get_size_inches()
    fig.set_size_inches(width, height * 1.22, forward=True)
    scale = width / 8.0
    main, small = 14.0 * scale, 12.0 * scale
    for text in fig.findobj(Text):
        if text.get_text().strip():
            text.set_fontsize(max(text.get_fontsize(), main))
            if text.get_color() in ['#d77b27', '#ed8b36', '#df7b28', '#d77527']:
                text.set_color('#87470f')
    for ax in fig.axes:
        for tick in ax.get_xticklabels() + ax.get_yticklabels():
            tick.set_fontsize(small)
        for label in [ax.xaxis.label, ax.yaxis.label]:
            txt = label.get_text()
            if '$' not in txt:
                label.set_text('\n'.join(textwrap.fill(s, 39) for s in txt.splitlines()))
        title = ax.get_title()
        if '$' not in title:
            columns = max(25, int(ax.get_position().width * 75))
            ax.set_title('\n'.join(textwrap.fill(s, columns) for s in title.splitlines()),
                         fontsize=main, pad=12)
        legend = ax.get_legend()
        if legend:
            for text in legend.get_texts(): text.set_fontsize(small)
        for line in ax.lines: line.set_linewidth(max(line.get_linewidth(), 1.8))
    if fig._suptitle:
        txt = fig._suptitle.get_text()
        if '$' not in txt:
            fig._suptitle.set_text(textwrap.fill(txt, 57))
        fig._suptitle.set_fontsize(max(main + 2, 18))
    for text in fig.texts:
        if text is fig._suptitle: continue
        txt=text.get_text()
        if '$' not in txt:
            text.set_text('\n'.join(textwrap.fill(s, 68) for s in txt.splitlines()))
    if type(fig.get_layout_engine()).__name__ == 'PlaceHolderLayoutEngine':
        fig.tight_layout(rect=(.01,.10,.99,.91),h_pad=2,w_pad=2)
    if type(fig.get_layout_engine()).__name__ == 'ConstrainedLayoutEngine':
        fig.get_layout_engine().set(h_pad=.15, w_pad=.12)


from matplotlib.patches import FancyArrowPatch, FancyBboxPatch, Circle

OUT = Path(__file__).resolve().parent
plt.rcParams.update({'font.family': 'DejaVu Sans', 'font.size': 12,
                     'axes.spines.top': False, 'axes.spines.right': False})
C = ['#175c95', '#de7624', '#24866d', '#a74567']
report = {'environment': {'python': platform.python_version(),
                         'numpy': np.__version__, 'scipy': scipy.__version__,
                         'sympy': sp.__version__, 'matplotlib': matplotlib.__version__}}


@dataclass(frozen=True)
class Sampled:
    q: np.ndarray
    values: np.ndarray
    unit_power: int  # powers of the same agreed kernel unit
    convention: str


def screened(v: Sampled, pi: Sampled) -> Sampled:
    """Static scalar RPA, one agreed convention, positive q; not dynamic RPA."""
    for item in (v, pi):
        if item.q.ndim != 1 or item.values.shape != item.q.shape:
            raise ValueError('Grid and values must be one-dimensional and equal-sized')
        if len(item.q) == 0 or not np.all(np.isfinite(item.q)):
            raise ValueError('Empty or nonfinite grid')
        if np.any(item.q <= 0) or np.any(np.diff(item.q) <= 0):
            raise ValueError('Expected strictly increasing positive q')
        if not np.all(np.isfinite(item.values)):
            raise ValueError('Nonfinite input')
    if not np.array_equal(v.q, pi.q):
        raise ValueError('Different grids: use an explicit checked conversion')
    if v.unit_power != 1 or pi.unit_power != -1:
        raise ValueError('V Pi must be dimensionless in the agreed kernel unit')
    if v.convention != pi.convention:
        raise ValueError('Different response conventions')
    if np.any(v.values <= 0) or np.any(pi.values > 0):
        raise ValueError('Outside the stated static sign convention')
    den = 1 - v.values * pi.values
    result = v.values / den
    if not np.all(np.isfinite(result)) or np.any(result <= 0):
        raise ArithmeticError('Nonfinite or nonpositive result')
    return Sampled(v.q.copy(), result, 1, v.convention)


def must_reject(v, pi):
    try:
        screened(v, pi)
    except ValueError:
        return True
    raise AssertionError('Invalid connection accepted')


qgrid = np.array([0.2, 0.7, 1.4, 3.0])
v = Sampled(qgrid, 2 / qgrid, 1, 'toy-static-v1')
pi = Sampled(qgrid, -0.2 * qgrid, -1, 'toy-static-v1')
w = screened(v, pi)
np.testing.assert_allclose(w.values / v.values, 1 / 1.4, rtol=1e-14)
zero = Sampled(qgrid, np.zeros_like(qgrid), -1, pi.convention)
np.testing.assert_array_equal(screened(v, zero).values, v.values)
bad_grid = must_reject(v, Sampled(qgrid * 1.01, pi.values, -1, pi.convention))
bad_units = must_reject(v, Sampled(qgrid, pi.values, 1, pi.convention))
bad_sign = must_reject(v, Sampled(qgrid, -pi.values, -1, pi.convention))
bad_convention = must_reject(v, Sampled(qgrid, pi.values, -1, 'other'))
wrong_w = v.values / (1 + v.values * pi.values)
assert np.any(wrong_w > v.values)
report['contracts'] = {'correct_ratio': (w.values / v.values).tolist(),
                       'wrong_grid_rejected': bad_grid, 'wrong_units_rejected': bad_units,
                       'wrong_sign_rejected': bad_sign, 'wrong_convention_rejected': bad_convention,
                       'wrong_denominator_detected': True}

# Manufactured solution: errors against a continuous exact answer, not residual alone.
def manufactured(n, mu=2.0, laplace_factor=1.0):
    h = 1 / (n + 1)
    x = h * np.arange(1, n + 1)
    exact = np.sin(np.pi * x) + 0.1 * np.sin(3 * np.pi * x)
    forcing = ((np.pi**2 + mu**2) * np.sin(np.pi * x)
               + 0.1 * (9 * np.pi**2 + mu**2) * np.sin(3 * np.pi * x))
    bands = np.zeros((3, n))
    bands[0, 1:] = -laplace_factor / h**2
    bands[1] = 2 * laplace_factor / h**2 + mu**2
    bands[2, :-1] = -laplace_factor / h**2
    u = solve_banded((1, 1), bands, forcing)
    padded = np.r_[0, u, 0]
    applied = (laplace_factor * (2 * u - padded[:-2] - padded[2:]) / h**2
               + mu**2 * u)
    return x, exact, u, h, np.max(abs(u - exact)), np.max(abs(applied-forcing))

ns = [31, 63, 127, 255]
sols = [manufactured(n) for n in ns]
errors = np.array([s[4] for s in sols])
hs = np.array([s[3] for s in sols])
orders = np.log(errors[:-1] / errors[1:]) / np.log(hs[:-1] / hs[1:])
assert np.all((orders > 1.95) & (orders < 2.05))
bad = manufactured(127, laplace_factor=1.1)
assert bad[4] > 0.03 and bad[5] < 1e-8
report['manufactured'] = {'n': ns, 'h': hs.tolist(), 'max_errors': errors.tolist(),
                           'observed_orders': orders.tolist(),
                           'wrong_operator_error': float(bad[4]),
                           'wrong_operator_residual': float(bad[5])}

# Perturbation: symbolic exact eigenvalue and separate numeric matrix solver.
l = sp.symbols('lam', real=True)
exact_E = (1 + l / 10 - sp.sqrt((1-l/2)**2 + sp.Rational(16,25)*l**2)) / 2
series = sp.series(exact_E, l, 0, 4)
assert sp.expand(series.removeO()).coeff(l, 1) == sp.Rational(3,10)
assert sp.expand(series.removeO()).coeff(l, 2) == -sp.Rational(4,25)
for z in [0.003, 0.02, 0.13]:
    numeric = np.linalg.eigvalsh([[0.3*z, 0.4*z], [0.4*z, 1-0.2*z]])[0]
    assert abs(numeric - float(exact_E.subs(l,z))) < 1e-14
report['perturbation'] = str(series)

# Matrix elements, including vanishing odd integrands.
def psi0(x):
    return np.pi**(-0.25) * np.exp(-x*x/2)

def psi1(x):
    return np.sqrt(2) * x * psi0(x)

states = [psi0, psi1]
matrices = [np.array([[quad(lambda x: a(x)*x**power*b(x), -np.inf, np.inf,
                                 epsabs=1e-12)[0] for b in states] for a in states])
            for power in [1, 2]]
np.testing.assert_allclose(matrices[0], [[0,1/np.sqrt(2)],[1/np.sqrt(2),0]], atol=1e-12)
np.testing.assert_allclose(matrices[1], [[0.5,0],[0,1.5]], atol=1e-12)
report['matrix_elements'] = [m.tolist() for m in matrices]

# Approximate delta: integrates a smooth test function, not a sampled infinite spike.
delta_values = []
for eps in [0.5, 0.2, 0.08]:
    a = 0.6
    numerical = quad(lambda z: np.exp(-z*z/2)/np.sqrt(2*np.pi)
                     * np.cos(a + eps*z), -10, 10, epsabs=1e-12)[0]
    analytic = np.cos(a) * np.exp(-eps**2/2)
    assert abs(numerical - analytic) < 1e-12
    delta_values.append([eps, numerical])
report['delta_cosine'] = {'center': 0.6, 'limit': float(np.cos(0.6)), 'values': delta_values}

# Form factors: real quadrature of a profile versus its analytic transform.
sigma, shift = 0.45, 1.3
def density(x, center=0):
    return np.exp(-(x-center)**2/(2*sigma**2))/(np.sqrt(2*np.pi)*sigma)

for k in [0.0, 0.4, 1.2, 2.7, 5.3]:
    numeric = quad(lambda x: (density(x,shift)+density(x,-shift))/2*np.cos(k*x),
                   -np.inf, np.inf, epsabs=1e-12)[0]
    assert abs(numeric - np.exp(-sigma**2*k*k/2)*np.cos(shift*k)) < 1e-11
    # Off-diagonal F_01 is imaginary: do not discard the sine part.
    f01 = 1j * quad(lambda x: psi0(x)*np.sin(k*x)*psi1(x), -np.inf, np.inf)[0]
    reference = 1j*k/np.sqrt(2)*np.exp(-k*k/4)
    assert abs(f01-reference) < 1e-11
    f10_minus = 1j * quad(lambda x: psi1(x)*np.sin(-k*x)*psi0(x), -np.inf, np.inf)[0]
    assert abs(f01.conjugate()-f10_minus) < 1e-11
report['form_factors'] = {'profile_and_transition_tests': 'passed', 'sigma':sigma,'a':shift}

# Symbol toy: wedge coefficient is a two-dimensional determinant.
x, y = sp.symbols('x y', positive=True)
def wedge(a, b):
    return sp.simplify(sp.diff(sp.log(a), x)*sp.diff(sp.log(b), y)
                       - sp.diff(sp.log(a), y)*sp.diff(sp.log(b), x))

assert wedge(x,y) != 0
assert sp.simplify(wedge(x,y)+wedge(y,x)) == 0
F = sp.polylog(2,x)
shifted = F + sp.pi**2/6
assert sp.simplify(sp.diff(F-shifted,x)) == 0
assert sp.simplify((F-shifted).subs(x,0)) == -sp.pi**2/6
report['symbol_toy'] = {'single_word_wedge': str(wedge(x,y)),
                         'symmetric_words_wedge': '0', 'constant_error_detected': True}

# Two genuinely different evaluation paths for the same linear process.
rng = np.random.default_rng(1729)
def random_complex(shape):
    return rng.normal(size=shape) + 1j*rng.normal(size=shape)

A, B, Cmat, D = [random_complex(s) for s in [(2,3),(4,2),(3,2),(2,3)]]
left = np.kron(A,B) @ np.kron(Cmat,D)
right = np.kron(A@Cmat, B@D)
np.testing.assert_allclose(left,right,rtol=1e-12,atol=1e-12)
psi = np.array([1,1j]) / np.sqrt(2)
identity = np.eye(2)
correct = np.vdot(psi, identity@psi)
wrong = psi.T @ identity @ psi
assert abs(correct-1) < 1e-14 and abs(wrong) < 1e-14
report['composition'] = {'max_error': float(np.max(abs(left-right))),
                         'identity_expectation': float(correct.real),
                         'without_conjugation': float(wrong.real)}


def save(fig, name):
    publication_layout(fig); fig.savefig(OUT / name, dpi=155, bbox_inches='tight', facecolor='white')
    plt.close(fig)


fig, ax = plt.subplots(3,1,figsize=(7.5,10),layout='constrained')
xx = np.linspace(-3,3,501)
for j,(f,label) in enumerate([(psi0(xx)*xx*psi0(xx),r'$\psi_0 x\psi_0$: площадь 0'),
                              (psi0(xx)*xx*psi1(xx),r'$\psi_0 x\psi_1$: площадь $1/\sqrt{2}$')]):
    ax[0].plot(xx,f,label=label,color=C[j])
ax[0].axhline(0,color='gray',lw=.7); ax[0].legend(fontsize=10)
ax[0].set(xlabel='x',title='Подынтегральные функции')
for panel,m,title in zip(ax[1:],matrices,['Матрица U = x','Матрица U = x²']):
    panel.imshow(m,cmap='Blues',vmin=0,vmax=1.5)
    for i in range(2):
        for j in range(2): panel.text(j,i,f'{m[i,j]:.3f}',ha='center',va='center',color='black' if m[i,j]<1 else 'white',fontsize=18)
    panel.set(xticks=[0,1],yticks=[0,1],xlabel='Входное состояние n',ylabel='Выходная компонента m',title=title)
save(fig,'deep_matrix.png')

fig, ax = plt.subplots(1,2,figsize=(12,4.2),layout='constrained')
xx = np.linspace(-3,3,1501)
for eps,color in zip([0.6,0.3,0.12],C):
    ax[0].plot(xx,np.exp(-xx**2/(2*eps**2))/(np.sqrt(2*np.pi)*eps),color=color,label=f'ε = {eps}')
ax[0].set(xlabel='x',ylabel='δ_ε(x)',title='Площадь каждого пика = 1');ax[0].legend()
sources=np.array([-1.2,0,1.0]); weights=np.array([0.25,0.5,0.35])
parts=weights[:,None]*np.exp(-abs(xx[None,:]-sources[:,None]))/2
for part,pos in zip(parts,sources):ax[1].plot(xx,part,'--',alpha=.65,label=f'Источник x = {pos:g}')
ax[1].plot(xx,parts.sum(axis=0),color=C[0],lw=2.5,label='Сумма откликов')
ax[1].set(xlabel='x',ylabel='u(x)',title='L = −d²/dx² + 1: суперпозиция');ax[1].legend(fontsize=10)
save(fig,'deep_delta.png')

fig, ax = plt.subplots(1,3,figsize=(12,3.8),layout='constrained')
for i,panel in enumerate(ax):
    panel.set(xlim=(-.5,2.5),ylim=(-1.1,1.3)); panel.axis('off')
    for pos,name in [(0,'x'),(2,'y')]:
        panel.scatter([pos,pos],[-.22,.22],s=55,color=C[0]);panel.text(pos,-.65,name,ha='center',fontsize=17)
    if i==0:
        for pos in [0,2]:panel.add_patch(FancyArrowPatch((pos,-.22),(pos,.22),connectionstyle='arc3,rad=1.3',arrowstyle='-',lw=2,color=C[1]))
    else:
        for sign in [-1,1]:panel.plot([0,2],[sign*.22,sign*.22*(1 if i==1 else -1)],color=C[2],lw=2)
    panel.set_title(['G(x,x) G(y,y)','G(x,y)²','G(x,y)²'][i])
    panel.text(1,1.05,['Несвязный вклад','Первое соединение','Второе соединение'][i],ha='center')
fig.suptitle('Четыре поля → три спаривания → связный ответ 2 G(x,y)²',fontsize=15)
save(fig,'deep_wick.png')

fig,ax=plt.subplots(1,2,figsize=(11,4.4),layout='constrained')
pts=np.array([[0,0],[1.6,-.2],[2.4,1.2],[.8,2.]])
for i in range(4):
    j=(i+1)%4
    ax[0].annotate('',xy=pts[i],xytext=pts[j],arrowprops={'arrowstyle':'->','color':C[0],'lw':2})
    mid=(pts[i]+pts[j])/2
    ax[0].text(*mid,f'  p{j if j else 4}',fontsize=13)
    ax[0].text(*(pts[i]+[.05,.1]),f'x{i}',fontsize=14)
ax[0].set(xlim=(-.6,3),ylim=(-.7,2.7),title='pᵢ = xᵢ₋₁ − xᵢ; сумма pᵢ = 0');ax[0].axis('off')
center=np.array([1.1,.8])
for i,pt in enumerate(pts):
    ax[1].plot([*pt[:1],center[0]],[pt[1],center[1]],color=C[2],lw=2)
    ax[1].text(*(pt+[.04,.1]),f'x{i}',fontsize=14)
    mid=(pt+center)/2;ax[1].text(*mid,f'D{i+1}',fontsize=12)
ax[1].scatter(*center,s=60,color=C[1]);ax[1].text(*(center+[.08,-.15]),'y = −ℓ',fontsize=14)
ax[1].set(xlim=(-.6,3),ylim=(-.7,2.7),title='Дуальный граф: Dᵢ = (y − xᵢ₋₁)²');ax[1].axis('off')
save(fig,'deep_dual.png')

fig,ax=plt.subplots(1,2,figsize=(12,4.2),layout='constrained')
xx=np.linspace(-3,3,601);kk=np.linspace(0,7,501)
ax[0].plot(xx,density(xx),label='Один центр',color=C[0]);ax[0].plot(xx,(density(xx,shift)+density(xx,-shift))/2,label='Два центра',color=C[1])
ax[1].plot(kk,np.exp(-sigma**2*kk**2/2),label='Один центр',color=C[0]);ax[1].plot(kk,np.exp(-sigma**2*kk**2/2)*np.cos(shift*kk),label='Два центра',color=C[1])
for panel in ax:panel.legend();panel.axhline(0,color='gray',lw=.6)
ax[0].set(xlabel='x',ylabel='ρ(x)',title='Нормированные профили');ax[1].set(xlabel='k',ylabel='F(k)',title='Отклик на пространственную гармонику')
save(fig,'deep_form.png')

fig,ax=plt.subplots(1,2,figsize=(12,4.2),layout='constrained')
ax[0].axis('off')
ax[0].text(.03,.85,'Проверка интегрируемости (вес 2)',fontsize=15)
ax[0].text(.04,.6,'x ⊗ y → dx ∧ dy / (xy) ≠ 0',color=C[3],fontsize=14)
ax[0].text(.04,.35,'x ⊗ y + y ⊗ x → 0',color=C[2],fontsize=14)
ax[0].text(.04,.12,'Порядок букв имеет значение',fontsize=12)
xx=np.linspace(0,.95,301)
ax[1].plot(xx,spence(1-xx),label='Li₂(x)',color=C[0]);ax[1].plot(xx,spence(1-xx)+np.pi**2/6,label='Li₂(x) + π²/6',color=C[1])
ax[1].scatter([0,0],[0,np.pi**2/6],color=[C[0],C[1]],zorder=3)
ax[1].set(xlabel='x',title='Один символ, разные значения при x = 0');ax[1].legend()
save(fig,'deep_symbol.png')

fig,ax=plt.subplots(figsize=(8,9));ax.set(xlim=(0,8),ylim=(0,9));ax.axis('off')
labels=[('Параметры и соглашения','B, vF, ε; единицы и заполнения'),('Базис и переходы','энергии, F_ab(q); порядок индексов'),('Статический отклик','Π(q); знак, нормировка, отсечение N'),('Экранирование','W = V / (1 − VΠ); та же сетка'),('Матричный элемент','P; меры интеграла, границы и веса')]
tests=['Размерности, допустимые значения','Нормировка, F_ab(0), сопряжение','Предельный режим, N → 2N','Π = 0; 0 < W ≤ V','Другой метод, хвосты, сходимость']
for i,((label,detail),check) in enumerate(zip(labels,tests)):
    yy=7.4-i*1.55
    ax.add_patch(FancyBboxPatch((.25,yy),7.5,1.25,boxstyle='round,pad=.06',fc='#eaf3fa',ec=C[0]))
    ax.text(.5,yy+.95,label,fontsize=16,weight='bold')
    ax.text(.5,yy+.57,detail,fontsize=14)
    ax.text(.5,yy+.2,'Проверка: '+check,fontsize=14,color=C[2])
    if i<4:ax.annotate('',xy=(4,yy-.27),xytext=(4,yy-.08),arrowprops={'arrowstyle':'->','lw':1.5})
ax.text(4,.35,'Сквозная проверка:\nисходные параметры → известный ответ',ha='center',fontsize=14,weight='bold')
fig.suptitle('Процессная декомпозиция: у каждого соединения есть контракт',fontsize=16)
save(fig,'deep_pipeline.png')

fig,ax=plt.subplots(3,1,figsize=(8,10),layout='constrained')
xx,truth,uu,*_=sols[1]
ax[0].plot(xx,truth,label='Заданное u*',color=C[0]);ax[0].plot(xx,uu,'--',label='Решатель',color=C[1])
ax[0].set(xlabel='x',title='Изготовленное решение');ax[0].legend()
ax[1].loglog(hs,errors,'o-',label='Ошибка',color=C[0]);ax[1].loglog(hs,errors[0]*(hs/hs[0])**2,'--',label='Ориентир h²',color=C[1])
ax[1].set(xlabel='Шаг h',ylabel='max |u − u*|',title='Уменьшение шага');ax[1].legend()
ax[2].plot(bad[0],bad[2]-bad[1],color=C[3]);ax[2].set(xlabel='x',ylabel='u_wrong − u*',title='Неверный коэффициент: 1 → 1.1')
ax[2].text(.08,.15,'Невязка мала,\nно решено другое уравнение',transform=ax[2].transAxes,fontsize=11)
save(fig,'deep_manufactured.png')

report['status']='all assertions passed'
(OUT/'process_checks_report.json').write_text(json.dumps(report,ensure_ascii=False,indent=2),encoding='utf8')
print(json.dumps(report,ensure_ascii=False,indent=2))

Опыт 7. Проверяем дополнительные математические связи

Сохраните следующий полный листинг как 14_precision_checks.py и выполните:

python 14_precision_checks.py

Он создаёт 13 рисунков precision_*.png и журнал precision_checks_report.json. Используются те же библиотеки; запускать нужно без -O, чтобы работали assert. Проверки имеют разные основания: решение маленькой линейной системы сравнивается с RPA-дробью; точная алгебра проверяет калибровки и карту на \Delta=0; квадратура сравнивается с аналитическими интегралами; рациональные кандидаты перечисляются явно.

Что менять в своей копии

Ожидаемый результат

Какой вывод допустим

Значения в списке [-12, 0, 12] в блоке рисунка bootstrap: это разные c

Старые четыре условия сохраняются; значение при x=1/4 меняется как 7/16-3c/256

Старые условия не определяли весь полином.

Взять только один из модулей 11 и 13

При прежних границах появятся дополнительные дроби

Недостаток данных реконструкции виден непосредственно.

Поменять векторный потенциал Ландау на симметричный

Ротор остаётся B, стрелки потенциала меняются

Выбор калибровки меняет представление, а не это поле.

Поменять матрицу X во втором тензорном множителе

Меняется действие на вторую подсистему

Порядок множителей и индексов является частью контракта.

Перенести проверочную точку bootstrap обратно в уже использованный набор

Проверка перестаёт различать показанные кандидаты

Повторение условий построения не даёт нового свидетельства.

В опыте с рациональными числами нельзя расширить границы и продолжать ссылаться на прежнюю гарантию: неравенство 2AB<M нужно проверять заново. В гауссовом рисунке выборочная ковариация только приближает точную матрицу; случайные точки иллюстрируют формулу, а не доказывают её. Такие различия между рисунком, тестом и доказательством — часть того же контроля научного кода.

Полный код 13 новых рисунков и проверок: 14_precision_checks.py
"""Thirteen explanatory figures and independent checks for the Habr article.

Run with Python 3.12, without -O: python 14_precision_checks.py
Requires numpy, scipy, sympy, matplotlib. Results go beside this file.
These are small teaching models, not a nine-loop amplitude computation.
"""
from pathlib import Path
from fractions import Fraction
from math import gcd
import json
import platform
import numpy as np
import scipy
from scipy.special import roots_legendre
import sympy as sp
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt

def publication_layout(fig):
    """Readable labels at a 600 px article width; retain data and panel order."""
    import textwrap
    from matplotlib.text import Text
    width, height = fig.get_size_inches()
    fig.set_size_inches(width, height * 1.22, forward=True)
    scale = width / 8.0
    main, small = 14.0 * scale, 12.0 * scale
    for text in fig.findobj(Text):
        if text.get_text().strip():
            text.set_fontsize(max(text.get_fontsize(), main))
            if text.get_color() in ['#d77b27', '#ed8b36', '#df7b28', '#d77527']:
                text.set_color('#87470f')
    for ax in fig.axes:
        for tick in ax.get_xticklabels() + ax.get_yticklabels():
            tick.set_fontsize(small)
        for label in [ax.xaxis.label, ax.yaxis.label]:
            txt = label.get_text()
            if '$' not in txt:
                label.set_text('\n'.join(textwrap.fill(s, 39) for s in txt.splitlines()))
        title = ax.get_title()
        if '$' not in title:
            columns = max(25, int(ax.get_position().width * 75))
            ax.set_title('\n'.join(textwrap.fill(s, columns) for s in title.splitlines()),
                         fontsize=main, pad=12)
        legend = ax.get_legend()
        if legend:
            for text in legend.get_texts(): text.set_fontsize(small)
        for line in ax.lines: line.set_linewidth(max(line.get_linewidth(), 1.8))
    if fig._suptitle:
        txt = fig._suptitle.get_text()
        if '$' not in txt:
            fig._suptitle.set_text(textwrap.fill(txt, 57))
        fig._suptitle.set_fontsize(max(main + 2, 18))
    for text in fig.texts:
        if text is fig._suptitle: continue
        txt=text.get_text()
        if '$' not in txt:
            text.set_text('\n'.join(textwrap.fill(s, 68) for s in txt.splitlines()))
    if type(fig.get_layout_engine()).__name__ == 'PlaceHolderLayoutEngine':
        fig.tight_layout(rect=(.01,.10,.99,.91),h_pad=2,w_pad=2)
    if type(fig.get_layout_engine()).__name__ == 'ConstrainedLayoutEngine':
        fig.get_layout_engine().set(h_pad=.15, w_pad=.12)


from matplotlib.patches import FancyArrowPatch, FancyBboxPatch

OUT = Path(__file__).resolve().parent
FIGURES = []
COL = ['#17668b', '#df7b28', '#26816b', '#994867']
plt.rcParams.update({'font.family': 'DejaVu Sans', 'font.size': 11,
                     'axes.spines.top': False, 'axes.spines.right': False})
report = {'environment': {'python': platform.python_version(),
                         'numpy': np.__version__, 'scipy': scipy.__version__,
                         'sympy': sp.__version__, 'matplotlib': matplotlib.__version__}}


def save(fig, name):
    publication_layout(fig); fig.savefig(OUT / f'precision_{name}.png', dpi=150,
                bbox_inches='tight', facecolor='white')
    FIGURES.append(f'precision_{name}.png')
    plt.close(fig)


def box(ax, xy, label, width=2.1, height=.8, color=COL[0]):
    x, y = xy
    ax.add_patch(FancyBboxPatch((x-width/2, y-height/2), width, height,
                               boxstyle='round,pad=.08', ec=color, fc='white', lw=1.8))
    ax.text(x, y, label, ha='center', va='center', fontsize=11)


def arrow(ax, start, end, color=COL[0], **kwargs):
    ax.add_patch(FancyArrowPatch(start, end, arrowstyle='-|>',
                               mutation_scale=14, color=color, lw=1.7, **kwargs))


def finish_axes(axes):
    for ax in np.asarray(axes).flat:
        ax.grid(alpha=.18)


# A response to the total potential is not a response to the external one.
v0, pi0, uext = 2., -.4, 1.
# Unknown vector: (U_total, delta_n).
utot, dn = np.linalg.solve([[1, -v0], [-pi0, 1]], [uext, 0])
chi = pi0 / (1-v0*pi0)
assert abs(dn/uext-chi) < 1e-14
assert abs(utot/uext-1/(1-v0*pi0)) < 1e-14
assert abs(v0+v0*chi*v0-v0/(1-v0*pi0)) < 1e-14
partial = sum(Fraction(2)*(-Fraction(4,5))**n for n in range(21))
exact = Fraction(2)/(1+Fraction(4,5))
error = exact-partial
residual = (1+Fraction(4,5))*partial-2
assert residual == -(1+Fraction(4,5))*error
report['response'] = {'Pi0': pi0, 'chi': chi, 'U_total': float(utot),
                       'partial_21': float(partial), 'error': float(error),
                       'residual': float(residual)}

# A counterexample to uniqueness outside the restricted bootstrap ansatz.
x, c = sp.symbols('x c', real=True)
F = 2*x-x**2+c*x**2*(1-x)*(x-sp.Rational(1,2))
assert [F.subs(x,0), F.subs(x,1), sp.diff(F,x).subs(x,0),
        F.subs(x,sp.Rational(1,2))] == [0,1,2,sp.Rational(3,4)]
assert sp.expand(F.subs(x,sp.Rational(1,4))) == sp.Rational(7,16)-3*c/256
report['bootstrap'] = {'held_out_quarter': str(sp.expand(F.subs(x,sp.Rational(1,4))))}

# Two vector potentials give the same magnetic field, and differ by a gradient.
x, y, B = sp.symbols('x y B', real=True)
AL = sp.Matrix([0, B*x])
AS = sp.Matrix([-B*y/2, B*x/2])
gauge_f = B*x*y/2
assert sp.simplify(AL-AS-sp.Matrix([sp.diff(gauge_f,x),sp.diff(gauge_f,y)])) == sp.zeros(2,1)
for A in [AL, AS]:
    assert sp.diff(A[1],x)-sp.diff(A[0],y) == B
report['gauge'] = 'two curls equal B; A_L - A_S = gradient(Bxy/2)'

# Projectors and the Gaussian covariance: different mathematical objects.
state = np.array([np.sqrt(3)/2, .5])
P0 = np.diag([1., 0.])
assert np.array_equal(P0@P0, P0)
assert abs(np.vdot(state,P0@state)-.75) < 1e-14
Amat = np.array([[2., -1.], [-1., 2.]])
cov = np.linalg.inv(Amat)
rng = np.random.default_rng(1729)
cloud = rng.multivariate_normal([0,0], cov, 5000)
cov_empirical = np.cov(cloud.T)
assert np.max(abs(cov_empirical-cov)) < .045
report['gaussian'] = {'exact_covariance': cov.tolist(),
                       'empirical_covariance': cov_empirical.tolist(),
                       'note': 'finite random sample; not a proof of Gaussian identities'}

# Three-point Gauss-Legendre integrates degree <=5 exactly (up to rounding).
nodes, weights = roots_legendre(3)
moments = []
for degree in range(6):
    numeric = np.dot(weights, nodes**degree)
    expected = 0 if degree % 2 else 2/(degree+1)
    assert abs(numeric-expected) < 2e-14
    moments.append(float(numeric))
report['quadrature'] = {'nodes': nodes.tolist(), 'weights': weights.tolist(),
                         'moments_0_to_5': moments}

# Exact dual kinematic map. a,b,c are form-factor variables, not loop momenta.
a, b = sp.symbols('a b', positive=True)
cc = 1-a-b
us = b*cc/((1-b)*(1-cc))
vs = cc*a/((1-cc)*(1-a))
ws = a*b/((1-a)*(1-b))
assert sp.factor((1-us-vs-ws)**2-4*us*vs*ws) == 0
center = [sp.simplify(t.subs({a:sp.Rational(1,3),b:sp.Rational(1,3)})) for t in [us,vs,ws]]
assert center == [sp.Rational(1,4)]*3
report['dual_map'] = {'Delta': 0, 'symmetric_point': [str(t) for t in center]}


def antipode_word(word):
    return (-1)**len(word), tuple(reversed(word))


for word in [('a',), ('a','b'), ('a','b','c')]:
    s1, rev = antipode_word(word)
    s2, original = antipode_word(rev)
    assert s1*s2 == 1 and original == word
report['antipode'] = 'reversal squared is identity, including the signs'


def candidates(modulus, residue, max_num=5, max_den=7):
    """Transparent exhaustive teaching oracle; not a production reconstruction."""
    return [(aa,bb) for bb in range(1,max_den+1) for aa in range(-max_num,max_num+1)
            if gcd(abs(aa),bb)==1 and gcd(bb,modulus)==1
            and (aa-residue*bb) % modulus == 0]


res11 = 3*pow(5,-1,11) % 11
res13 = 3*pow(5,-1,13) % 13
combined = [r for r in range(143) if r%11==res11 and r%13==res13]
assert combined == [115]
one = candidates(11,res11)
two = candidates(143,combined[0])
assert len(one)>1 and two == [(3,5)]
assert 2*5*7 < 143
report['rational'] = {'residues': [res11,res13], 'CRT_residue': combined[0],
                       'candidates_mod11': one, 'candidates_mod143': two,
                       'bounds_A_B': [5,7], 'uniqueness_bound_2AB': 70}

# A tensor product has independently constructed blocks A_ij B.
AA = np.array([[1,2],[3,4]])
BB = np.array([[0,1],[1,0]])
explicit = np.block([[AA[0,0]*BB,AA[0,1]*BB], [AA[1,0]*BB,AA[1,1]*BB]])
np.testing.assert_array_equal(np.kron(AA,BB), explicit)
ket0, ket1 = np.eye(2)
np.testing.assert_array_equal(np.kron(np.eye(2),BB)@np.kron(ket1,ket0),np.kron(ket1,ket1))
report['tensor_product'] = explicit.tolist()

# 1. Bands and hopping. Both panels are teaching models, not measured graphene data.
fig, ax = plt.subplots(1,2,figsize=(12,4.8),layout='constrained')
ax[0].axis('off');ax[0].set(xlim=(0,10),ylim=(0,5),title='Перескок — матричный элемент')
for pos,label,col in [(2,'|A⟩',COL[0]),(8,'|B⟩',COL[1])]:
    ax[0].scatter(pos,3,s=950,color=col);ax[0].text(pos,3,label,ha='center',va='center',color='white',fontsize=17)
arrow(ax[0],(2.7,3),(7.3,3));ax[0].text(5,3.4,'⟨A|H|B⟩ = −t',ha='center',fontsize=16)
ax[0].text(5,1.5,'iℏ dψA/dτ = −t ψB\niℏ dψB/dτ = −t ψA',ha='center',fontsize=16)
ax[0].text(5,.3,'t — энергия связи амплитуд',ha='center',fontsize=11)
k = np.linspace(-1.7,1.7,400)
for sign,col in [(1,COL[0]),(-1,COL[1])]:
    ax[1].plot(k,sign*np.sqrt(.6**2+k*k),color=col,label='Зона проводимости' if sign==1 else 'Валентная зона')
ax[1].fill_between(k,-.6,.6,color='#dddddd',alpha=.5)
ax[1].annotate('',xy=(0,.6),xytext=(0,-.6),arrowprops={'arrowstyle':'<->'})
ax[1].text(.08,0,'щель Eg',va='center');ax[1].set(xlabel='k, условные единицы',ylabel='E',title='Две зоны со щелью: учебная модель')
ax[1].legend(fontsize=10,loc='upper center');save(fig,'bands')

# 2. A localized packet requires a range of wave numbers.
fig, ax = plt.subplots(1,2,figsize=(12,4.3),layout='constrained')
xx = np.linspace(-7,7,1200);kk = np.linspace(-1,7,800);k0=3
ax[0].plot(xx,.55*np.cos(k0*xx),color='gray',alpha=.55,label='Одна плоская волна (Re ψ)')
for width,col in [(1.,COL[0]),(2.,COL[1])]:
    psi=np.exp(-xx**2/(2*width**2))*np.cos(k0*xx)
    ax[0].plot(xx,psi,color=col,label=f'Пакет: ℓ = {width:g}')
    ax[1].plot(kk,np.exp(-width**2*(kk-k0)**2/2),color=col,label=f'ℓ = {width:g}')
ax[0].set(xlabel='x',ylabel='Re ψ, произвольная нормировка',title='Локализация в пространстве')
ax[1].set(xlabel='k',ylabel='|ψ̃(k)| / max',title='Широкий пакет → узкий спектр')
for p in ax:p.legend(fontsize=10)
finish_axes(ax);save(fig,'waves')

# 3. Self-consistent response: route arrows outside labels and separate the panels.
fig, ax = plt.subplots(1,2,figsize=(14,5.7),layout='constrained',
                      gridspec_kw={'width_ratios':[1.65,1], 'wspace':.16})
ax[0].axis('off')
ax[0].set(xlim=(-.2,16.7),ylim=(-.7,9),title='На какое поле отвечает Π?')
for xy,label,width in [((1.6,6.5),r'$U_{\rm ext}$',2.8),
                       ((7.3,6.5),r'$U_{\rm tot}$',3.0),
                       ((13,6.5),r'$\delta n=\Pi U_{\rm tot}$',5.0),
                       ((13,2.7),r'$U_{\rm ind}=V_0\delta n$',6.0)]:
    box(ax[0],xy,label,width=width,height=1.6)
# The summing point has its own space, outside the potential boxes.
ax[0].plot(4.3,6.5,'o',ms=24,mfc='white',mec=COL[0],mew=1.8)
ax[0].text(4.3,6.5,'+',ha='center',va='center',fontsize=17)
arrow(ax[0],(3.1,6.5),(3.93,6.5))
arrow(ax[0],(4.68,6.5),(5.72,6.5))
arrow(ax[0],(8.88,6.5),(10.42,6.5))
arrow(ax[0],(13,5.62),(13,3.58))
# Feedback follows an empty corridor; no line crosses a formula.
ax[0].plot([9.92,4.3],[2.7,2.7],color=COL[0],lw=1.7)
arrow(ax[0],(4.3,2.7),(4.3,6.05))
ax[0].text(8.1,.65,'ext: внешнее; tot: полное; ind: наведённое',
           ha='center',va='center',fontsize=11)
ax[0].text(8.1,-.2,r'$\delta n=\chi U_{\rm ext}$',
           ha='center',va='center',fontsize=11)
vv=np.linspace(0,6,200);response=pi0/(1-vv*pi0)
ax[1].plot(vv,np.full_like(vv,pi0),label='Π₀ = −0.4',color=COL[1])
ax[1].plot(vv,response,label='χ = Π₀ / (1 − V₀Π₀)',color=COL[0])
ax[1].set(xlabel='V₀, согласованные единицы',ylabel='Отклик',
          title='Π₀ и χ различаются\nпри взаимодействии')
ax[1].legend(loc='upper center',bbox_to_anchor=(.5,-.24),frameon=False)
save(fig,'response')

# 4. Projection and occupation are different uses of state labels.
fig, ax = plt.subplots(3,1,figsize=(7.5,10),layout='constrained')
p=ax[0];p.set(xlim=(-.1,1.1),ylim=(-.1,1.05),aspect='equal',title='Проектор выделяет компоненту')
arrow(p,(0,0),tuple(state));arrow(p,(0,0),(state[0],0),COL[1]);p.plot([state[0]]*2,[0,state[1]],'--',color='gray')
p.set(xlabel='|0⟩',ylabel='|1⟩');p.text(.03,.87,'ψ = √3/2 |0⟩ + 1/2 |1⟩',fontsize=10);p.text(.1,.15,'⟨ψ|P₀|ψ⟩ = 3/4',fontsize=11)
for panel,title,counts in [(ax[1],'Фермионы: 0 или 1 в состоянии',[1,1,0]),(ax[2],'Бозоны: 0, 1, 2, …',[3,0,1])]:
    panel.set(xlim=(0,3),ylim=(0,3.5),title=title);panel.axis('off')
    for i,n in enumerate(counts):
        yy=.7+i;panel.hlines(yy,.3,2.7,color='gray');panel.text(.05,yy,str(i),va='center')
        for j in range(n):panel.scatter(.8+.6*j,yy,s=140,color=COL[0] if panel is ax[1] else COL[1])
    panel.text(1.5,.05,'Метка означает полное\nодночастичное состояние',ha='center',fontsize=9)
save(fig,'states')

# 5. Two gauges, the same curl.
fig,ax=plt.subplots(1,2,figsize=(11,4.8),layout='constrained');xy=np.linspace(-2,2,9);X,Y=np.meshgrid(xy,xy)
for p,U,V,title in [(ax[0],0*X,X,'Калибровка Ландау: A = (0, Bx)'),(ax[1],-Y/2,X/2,'Симметричная: A = (−By/2, Bx/2)')]:
    p.quiver(X,Y,U,V,color=COL[0],scale=20);p.set(xlabel='x',ylabel='y',aspect='equal',title=title)
    p.text(.5,.02,'∂x Ay − ∂y Ax = B',transform=p.transAxes,ha='center',bbox={'fc':'white','ec':'none'})
fig.suptitle('Стрелки показывают A, а не движение электронов. Поле B одинаково.',fontsize=13)
save(fig,'gauge')

# 6. Gaussian covariance and the meaning of a two-point function.
fig,ax=plt.subplots(1,2,figsize=(11,4.5),layout='constrained')
ax[0].scatter(*cloud[:1600].T,s=4,alpha=.15,color=COL[0]);ax[0].set(xlabel='φ₁',ylabel='φ₂',aspect='equal',title='Гауссов вес: exp(−φᵀ A φ / 2)')
ax[1].imshow(cov,cmap='Blues',vmin=0,vmax=.8)
for i in range(2):
    for j in range(2):ax[1].text(j,i,'2/3' if i==j else '1/3',ha='center',va='center',fontsize=23,color='white' if i==j else 'black')
ax[1].set(xticks=[0,1],yticks=[0,1],xticklabels=['φ₁','φ₂'],yticklabels=['φ₁','φ₂'],title='⟨φᵢφⱼ⟩ = (A⁻¹)ᵢⱼ при J = 0')
save(fig,'gaussian')

# 7. Quadrature nodes are not sampled area elements of equal width.
fig,ax=plt.subplots(1,2,figsize=(11,4.3),layout='constrained');xx=np.linspace(-1,1,300)
ax[0].plot(xx,xx**4,color=COL[0],label='f(x) = x⁴');ax[0].scatter(nodes,nodes**4,color=COL[1],zorder=4)
for z in nodes:ax[0].axvline(z,ls=':',color='gray',alpha=.6)
ax[0].set(xlabel='x',ylabel='f(x)',title='3 узла Гаусса на [−1, 1]');ax[0].legend()
ax[1].bar(np.arange(3),weights,color=COL[0]);ax[1].set(xticks=range(3),xticklabels=['−√(3/5)','0','√(3/5)'],ylabel='Вес',title='Веса 5/9, 8/9, 5/9')
fig.suptitle('Σ wᵢ xᵢ⁴ = 2/5 = ∫₋₁¹ x⁴ dx. Точность здесь следует из степени полинома.',fontsize=13)
save(fig,'quadrature')

# 8. Geometry, collinearity, and a complex logarithm cut are distinct notions.
fig,ax=plt.subplots(3,1,figsize=(7.5,10),layout='constrained')
xx=np.linspace(-1,1,101);ax[0].plot(xx,abs(xx),color=COL[0]);ax[0].plot(xx,-abs(xx),color=COL[0])
ax[0].fill_between(xx,abs(xx),1,color=COL[0],alpha=.1);ax[0].set(xlabel='x',ylabel='ct',title='Световой конус: (ct)² − x² = 0',aspect='equal')
ax[1].axis('off');ax[1].set(xlim=(0,3),ylim=(-.5,2.6),title='Коллинеарный предел: θ → 0')
arrow(ax[1],(.3,.3),(2.5,.3));arrow(ax[1],(.3,.3),(2.1,1.8),COL[1]);arrow(ax[1],(.3,.3),(2.4,.55),COL[2]);ax[1].text(1.2,1.15,'наклон уменьшается',rotation=35,fontsize=10)
ax[2].plot([-2,0],[0,0],color=COL[3],lw=4);ax[2].axhline(0,color='gray',lw=.5);ax[2].axvline(0,color='gray',lw=.5)
ax[2].scatter([-1,-1],[.13,-.13],color=[COL[0],COL[1]]);ax[2].text(-1,.55,'log(−1 + i0) = +iπ',ha='center',fontsize=10);ax[2].text(-1,-.6,'log(−1 − i0) = −iπ',ha='center',fontsize=10)
ax[2].set(xlim=(-2.1,.5),ylim=(-1,1),xlabel='Re z',ylabel='Im z',title='Разрез главной ветви логарифма')
save(fig,'limits')

# 9. A held-out point detects freedom invisible to four earlier constraints.
fig,ax=plt.subplots(1,2,figsize=(11,4.5),layout='constrained');xx=np.linspace(0,1,401)
for coef,col in zip([-12,0,12],COL):
    ff=2*xx-xx**2+coef*xx**2*(1-xx)*(xx-.5)
    ax[0].plot(xx,ff,color=col,label=f'c = {coef}')
    ax[1].plot(xx,ff-(2*xx-xx**2),color=col,label=f'c = {coef}')
ax[0].scatter([0,.5,1],[0,.75,1],color='black',zorder=4);ax[0].set(xlabel='x',ylabel='F(x)',title='Все проходят четыре прежних условия');ax[0].legend()
ax[1].axvline(.25,color='gray',ls=':');ax[1].set(xlabel='x',ylabel='F(x) − (2x − x²)',title='Новая точка x = 1/4 различает ответы')
finish_axes(ax);save(fig,'bootstrap')

# 10. A computed parametrization of the parity-preserving surface.
aa,bb=np.meshgrid(np.linspace(.025,.95,31),np.linspace(.025,.95,31));mask=aa+bb<.975
aa,bb=aa[mask],bb[mask];ccc=1-aa-bb
uu=bb*ccc/((1-bb)*(1-ccc));vv=ccc*aa/((1-ccc)*(1-aa));ww=aa*bb/((1-aa)*(1-bb))
assert np.max(abs((1-uu-vv-ww)**2-4*uu*vv*ww)) < 1e-13
fig=plt.figure(figsize=(12,6));p=fig.add_subplot(121)
p.scatter(aa,bb,c=aa,cmap='viridis',s=10);p.plot([0,1,0,0],[0,0,1,0],color='gray');p.scatter([1/3],[1/3],color=COL[1],s=90,edgecolor='black')
p.set(xlabel='a',ylabel='b',aspect='equal',title='Формфактор: a + b + c = 1')
p=fig.add_subplot(122,projection='3d');p.scatter(uu,vv,ww,c=aa,cmap='viridis',s=9);p.scatter([.25],[.25],[.25],color=COL[1],s=70,edgecolor='black')
p.set(xlabel='u',ylabel='v',zlabel='w',title='Образ карты лежит на Δ = 0');p.view_init(elev=25,azim=45)
fig.suptitle('Одинаковый цвет связывает точку и её образ',fontsize=17)
fig.subplots_adjust(top=.77,bottom=.12,left=.06,right=.96,wspace=.28)
save(fig,'dual')

# 11. Word reversal and dihedral relabelling should not be confused.
fig,ax=plt.subplots(1,2,figsize=(12,4.3),layout='constrained')
ax[0].axis('off');ax[0].set(xlim=(0,10),ylim=(0,5),title='Антипод: порядок позиций в слове')
for yy,letters in [(3.6,['a','b','c','d']),(1.2,['d','c','b','a'])]:
    for j,letter in enumerate(letters):box(ax[0],(1.4+2.3*j,yy),letter,width=1.1,height=.8,color=COL['abcd'.index(letter)])
arrow(ax[0],(5,3),(5,1.9));ax[0].text(6.1,2.45,'знак (−1)⁴ = +1',fontsize=11)
th=np.pi/2+np.arange(3)*2*np.pi/3;pts=np.c_[np.cos(th),np.sin(th)]
ax[1].plot(*np.r_[pts,pts[:1]].T,color=COL[0]);ax[1].axis('off');ax[1].set(xlim=(-1.7,1.7),ylim=(-1.6,1.6),aspect='equal',title='Диэдральная симметрия меток')
for pt,letter in zip(pts,['a','b','c']):ax[1].text(*(pt*1.2),letter,ha='center',va='center',fontsize=21)
ax[1].text(0,-1.45,'(a,b,c) → (b,c,a) и (a,b,c) → (c,b,a)',ha='center',fontsize=10)
save(fig,'words')

# 12. The bounds are part of the certificate, not discovered by fitting.
fig,ax=plt.subplots(1,2,figsize=(11,4.5),layout='constrained')
allpairs=[(a,b) for b in range(1,8) for a in range(-5,6) if gcd(abs(a),b)==1]
for p,solutions,title in [(ax[0],one,'Один модуль 11: остаток 5'),(ax[1],two,'Два модуля: остаток 115 по 143')]:
    p.scatter(*np.array(allpairs).T,s=18,color='#c7cdd1')
    p.scatter(*np.array(solutions).T,s=100,color=COL[0])
    for a,b in solutions:p.annotate(f'{a}/{b}',(a,b),xytext=(5,5),textcoords='offset points',fontsize=10)
    p.set(xlabel='Числитель a',ylabel='Знаменатель b',xticks=range(-5,6,2),yticks=range(1,8),title=title)
fig.suptitle('Заранее задано: |a| ≤ 5, 1 ≤ b ≤ 7, дробь сокращена. 2·5·7 < 143.',fontsize=13)
save(fig,'rational')

# 13. Tensor-product blocks, and an action on one subsystem.
fig,ax=plt.subplots(1,2,figsize=(11,4.5),layout='constrained')
ax[0].imshow(explicit,cmap='Blues',vmin=0,vmax=4)
for i in range(4):
    for j in range(4):ax[0].text(j,i,str(explicit[i,j]),ha='center',va='center',fontsize=16,color='white' if explicit[i,j]>2 else 'black')
ax[0].axvline(1.5,color=COL[1],lw=3);ax[0].axhline(1.5,color=COL[1],lw=3)
ax[0].set(xticks=range(4),yticks=range(4),xticklabels=['00','01','10','11'],yticklabels=['00','01','10','11'],title='A ⊗ X: четыре блока Aᵢⱼ X')
ax[1].axis('off');ax[1].set(xlim=(0,10),ylim=(0,5),title='I ⊗ X меняет только второй бит')
for yy,start,end in [(3.5,'|1⟩','|1⟩'),(1.5,'|0⟩','|1⟩')]:
    ax[1].text(.3,yy,start,va='center',fontsize=19);arrow(ax[1],(1.5,yy),(8.5,yy));ax[1].text(9,yy,end,va='center',fontsize=19)
box(ax[1],(5,3.5),'I',width=.8);box(ax[1],(5,1.5),'X',width=.8,color=COL[1])
ax[1].text(5,.25,'Порядок базиса: |00⟩, |01⟩, |10⟩, |11⟩',ha='center',fontsize=11)
save(fig,'tensor')

report['figures'] = sorted(FIGURES)
assert len(report['figures']) == 13
report['status'] = 'all assertions passed'
(OUT/'precision_checks_report.json').write_text(json.dumps(report,ensure_ascii=False,indent=2),encoding='utf8')
print(json.dumps(report,ensure_ascii=False,indent=2))

Код схем чтения и трёх небольших проверок

Сохраните листинг ниже как 15_reading_maps.py и запустите python 15_reading_maps.py. Он создаст шесть nav_*.png и reading_maps_report.json. Первые три рисунка — схемы чтения и контроля; численные рисунки проверяются отдельно от рисования: матричным решением, вычислением собственных значений и прямой квадратурой формфакторов.

В опыте с невязкой меняйте eps: при том же кандидате ошибка остаётся 1, а невязка равна eps. В опыте со смешиванием меняйте gap и сравнивайте отношение lam*v/gap.

В опыте с формфактором сравнивайте значения при q=0 у диагонали и перехода. Это дополнение к семи опытам, а не новый расчёт девятипетлевой амплитуды.

Полный код шести схем и проверок: 15_reading_maps.py
"""Six reading maps and independently checked teaching examples.

Run: python 15_reading_maps.py (Python 3.12, without -O).
Requires numpy, scipy, matplotlib. Writes PNG and JSON beside this file.
The diagrams are guides to the article, not physical validation.
"""
from pathlib import Path
import json
import platform
import numpy as np
import scipy
from scipy.integrate import quad
from scipy.special import j0
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt

def publication_layout(fig):
    """Readable labels at a 600 px article width; retain data and panel order."""
    import textwrap
    from matplotlib.text import Text
    width, height = fig.get_size_inches()
    fig.set_size_inches(width, height * 1.22, forward=True)
    scale = width / 8.0
    main, small = 14.0 * scale, 12.0 * scale
    for text in fig.findobj(Text):
        if text.get_text().strip():
            text.set_fontsize(max(text.get_fontsize(), main))
            if text.get_color() in ['#d77b27', '#ed8b36', '#df7b28', '#d77527']:
                text.set_color('#87470f')
    for ax in fig.axes:
        for tick in ax.get_xticklabels() + ax.get_yticklabels():
            tick.set_fontsize(small)
        for label in [ax.xaxis.label, ax.yaxis.label]:
            txt = label.get_text()
            if '$' not in txt:
                label.set_text('\n'.join(textwrap.fill(s, 39) for s in txt.splitlines()))
        title = ax.get_title()
        if '$' not in title:
            columns = max(25, int(ax.get_position().width * 75))
            ax.set_title('\n'.join(textwrap.fill(s, columns) for s in title.splitlines()),
                         fontsize=main, pad=12)
        legend = ax.get_legend()
        if legend:
            for text in legend.get_texts(): text.set_fontsize(small)
        for line in ax.lines: line.set_linewidth(max(line.get_linewidth(), 1.8))
    if fig._suptitle:
        txt = fig._suptitle.get_text()
        if '$' not in txt:
            fig._suptitle.set_text(textwrap.fill(txt, 57))
        fig._suptitle.set_fontsize(max(main + 2, 18))
    for text in fig.texts:
        if text is fig._suptitle: continue
        txt=text.get_text()
        if '$' not in txt:
            text.set_text('\n'.join(textwrap.fill(s, 68) for s in txt.splitlines()))
    if type(fig.get_layout_engine()).__name__ == 'PlaceHolderLayoutEngine':
        fig.tight_layout(rect=(.01,.10,.99,.91),h_pad=2,w_pad=2)
    if type(fig.get_layout_engine()).__name__ == 'ConstrainedLayoutEngine':
        fig.get_layout_engine().set(h_pad=.15, w_pad=.12)


from matplotlib.patches import FancyBboxPatch, FancyArrowPatch

OUT=Path(__file__).resolve().parent
COL=["#145e82","#d77527","#228473","#8c4974"]
FIGURES=[]
plt.rcParams.update({"font.family":"DejaVu Sans","font.size":13,
                     "axes.spines.top":False,"axes.spines.right":False})
report={"environment":{"python":platform.python_version(),
                        "numpy":np.__version__,"scipy":scipy.__version__,
                        "matplotlib":matplotlib.__version__}}


def save(fig,name):
    filename="nav_"+name+".png"
    publication_layout(fig); fig.savefig(OUT/filename,dpi=155,bbox_inches="tight",facecolor="white")
    plt.close(fig)
    FIGURES.append(filename)


def arrow(ax,start,end,color=COL[0],**kw):
    ax.add_patch(FancyArrowPatch(start,end,arrowstyle="-|>",
                  mutation_scale=17,lw=1.8,color=color,**kw))


def card(ax,x,y,w,h,title,detail,color=COL[0],fontsize=14):
    import textwrap
    detail='\n'.join(textwrap.fill(line,max(21,int(w*6))) for line in detail.splitlines())
    ax.add_patch(FancyBboxPatch((x-w/2,y-h/2),w,h,
        boxstyle="round,pad=.07",fc="#f6f9fc",ec=color,lw=1.6))
    ax.text(x,y+.25,title,ha="center",va="center",fontsize=fontsize,
            color=color,weight="bold")
    ax.text(x,y-.23,detail,ha="center",va="center",fontsize=13)


# A route through the existing article, not new scientific claims.
fig,ax=plt.subplots(figsize=(8.3,8.0))
ax.set(xlim=(0,8),ylim=(0,8));ax.axis("off")
stages=[
    ("Сначала: контроль кода от ИИ","Постановка → независимые опоры → проверяемый результат"),
    ("Разделы 1–8: восстанавливаем диплом","Графен → состояния → отклик → матричный элемент"),
    ("Разделы 9–10: понимаем цену петли","Граф → интеграл → полный коэффициент учебного ряда"),
    ("Разделы 11–16: читаем результат Claude","Bootstrap → символ → функция → пределы проверки"),
    ("Раздел 17: проверяем своими руками","Команда → изменение параметра → ожидаемый эффект")]
for i,(title,detail) in enumerate(stages):
    y=7.15-1.4*i
    card(ax,4,y,7.2,.96,title,detail,color=COL[i%4])
    if i<4:arrow(ax,(4,y-.55),(4,y-.82))
fig.suptitle("Общий план статьи",fontsize=17)
save(fig,"route")

fig,ax=plt.subplots(figsize=(8.6,10))
ax.set(xlim=(0,8.6),ylim=(0,10.8));ax.axis("off")
card(ax,4.15,10,7.6,.9,"1–2. Постановка и схема процессов",
     "Уравнения, единицы, область применимости, входы и выходы")
card(ax,2.05,8.45,3.6,1.2,"3. Опорные ответы",
     "Точный пример\n+ отложенные случаи",COL[2],12.5)
card(ax,6.2,8.45,3.5,1.2,"ИИ: код блоков",
     "Короткие функции\nпо явным контрактам",COL[1],12.5)
arrow(ax,(3.1,9.48),(2.05,9.14));arrow(ax,(5.2,9.48),(6.2,9.14))
card(ax,4.15,6.7,6.6,1.0,"4–5. Проверки формул и ответов",
     "Числа, символы, симметрии, независимый метод")
arrow(ax,(2.05,7.76),(3.1,7.28),COL[2]);arrow(ax,(6.2,7.76),(5.2,7.28),COL[1])
card(ax,4.15,5.2,6.6,1.0,"6. Сходимость и бюджет ошибок",
     "Шаг, отсечение, квадратура\nРазрядность и обусловленность")
card(ax,4.15,3.7,6.6,1.0,"7–8. Испытание проверяющего",
     "Известные дефекты + заранее отложенные случаи")
card(ax,4.15,2.2,6.6,1.0,"9. Воспроизводимый пакет",
     "Код, данные, версии, команды, результаты и ограничения")
for y in [6.7,5.2,3.7]:arrow(ax,(4.15,y-.58),(4.15,y-.92))
ax.plot([7.7,8.25,8.25,7.7],[5.2,5.2,8.45,8.45],
        color=COL[3],lw=1.8)
arrow(ax,(7.7,8.45),(7.5,8.45),COL[3])
ax.text(8.35,6.65,"разобрать сбой",rotation=90,ha="left",va="center",
        color=COL[3],fontsize=10.5)
ax.text(4.15,.76,"Провал проверки возвращает нас к её причине:\n"
        "модели, реализации, данным или обоснованию самого теста.",
        ha="center",fontsize=12)
fig.suptitle("Как организовать научный код с помощью ИИ",fontsize=18)
save(fig,"workflow")

fig,ax=plt.subplots(figsize=(8.4,7.4))
ax.set(xlim=(0,8),ylim=(0,7.8));ax.axis("off")
rows=[
    ("Модель и её область применимости","Сравнение с экспериментом или физическим ориентиром"),
    ("Параметры и исходные данные","Единицы, происхождение, неопределённость измерений"),
    ("Реализация уравнения","Размерности, аналитические случаи, независимый метод"),
    ("Численное приближение","Сетка, границы, число уровней, точность квадратуры"),
    ("Округление и чувствительность","Разрядность, масштабирование, число обусловленности")]
for i,(title,detail) in enumerate(rows):
    card(ax,4,7-1.34*i,7.25,1.0,title,detail,color=COL[i%4],fontsize=14)
ax.text(4,.35,"Это разные источники неопределённости, а не автоматически\n"
        "независимые случайные ошибки, которые можно сложить по квадратам.",
        ha="center",fontsize=11)
fig.suptitle("Что именно мы проверяем?",fontsize=18)
save(fig,"errors")

# Exact conceptual counterexample, independently evaluated with matrix algebra.
eps=1e-8
A=np.diag([1.,eps]); b=np.array([1.,0.])
truth=np.linalg.solve(A,b)
candidate=np.array([1.,1.])
residual=b-A@candidate
forward=np.linalg.norm(candidate-truth)
backward=np.linalg.norm(residual)
condition=np.linalg.cond(A)
assert np.array_equal(truth,[1.,0.])
assert forward==1 and backward==eps
assert np.isclose(condition,1/eps)
np.testing.assert_allclose(candidate-truth,-np.linalg.solve(A,residual))
report["residual_example"]={"epsilon":eps,"candidate":candidate.tolist(),
    "exact":truth.tolist(),"residual_norm":backward,"error_norm":forward,
    "condition_number":condition}
fig,ax=plt.subplots(2,1,figsize=(8.3,7.6),
                    gridspec_kw={"height_ratios":[1,1.1]},layout="constrained")
ax[0].axis("off")
ax[0].text(.05,.84,"A = diag(1, ε),  b = (1, 0)",fontsize=19)
ax[0].text(.05,.57,"Точное u = (1, 0)    Кандидат û = (1, 1)",fontsize=17)
ax[0].text(.05,.29,"r = b − Aû = (0, −ε)    ε = 10⁻⁸",fontsize=17)
ax[0].text(.05,.04,"A⁻¹ усиливает вторую компоненту в 10⁸ раз",fontsize=14,color=COL[3])
ax[1].barh(["Норма невязки","Норма ошибки"],[backward,forward],
            color=[COL[2],COL[3]])
ax[1].set(xscale="log",xlim=(1e-9,10),xlabel="Евклидова норма, логарифмическая шкала")
ax[1].grid(axis="x",alpha=.25)
for i,v in enumerate([backward,forward]):ax[1].text(v*1.4,i,f"{v:g}",va="center")
fig.suptitle("Малая невязка может соседствовать с большой ошибкой",fontsize=16)
save(fig,"condition")

# Nearly degenerate perturbation: symbolic eigenvalue vs independent eigensolver.
lam=np.linspace(0,.25,301); v=1.
gap_results=[]
fig,axes=plt.subplots(2,1,figsize=(8.3,7.2),layout="constrained")
for ax,gap in zip(axes,[1.,.05]):
    exact=(gap-np.sqrt(gap*gap+4*(lam*v)**2))/2
    second=-(lam*v)**2/gap
    for z in [.002,.02,.12]:
        value=np.linalg.eigvalsh([[0,z*v],[z*v,gap]])[0]
        analytic=(gap-np.sqrt(gap*gap+4*(z*v)**2))/2
        assert abs(value-analytic)<2e-16
    ax.plot(lam,exact,color=COL[0],lw=2,label="Точная нижняя энергия")
    ax.plot(lam,second,"--",color=COL[1],lw=2,label="Второй порядок")
    if gap<lam[-1]:
        ax.axvspan(gap/v,lam[-1],color=COL[3],alpha=.08)
        ax.text(.14,-.92,"λv / Δ ≥ 1:\nслабого смешивания нет",fontsize=12)
    ax.set(xlabel="λ при v = 1 в выбранных единицах",ylabel="Энергия",
           title=f"Зазор Δ = {gap:g}")
    ax.grid(alpha=.2);ax.legend(fontsize=10,loc="lower left")
    gap_results.append({"gap":gap,"lambda":.12,
        "mixing_ratio":.12*v/gap,
        "exact_E":float(np.linalg.eigvalsh([[0,.12*v],[.12*v,gap]])[0]),
        "second_order_E":-(.12*v)**2/gap})
report["small_gap"]=gap_results
fig.suptitle("Важна сила смешивания относительно зазора",fontsize=17)
save(fig,"gap")

# Two notions of indices: a 2D radial state vs a 1D oscillator transition.
qs=np.linspace(0,4.5,301)
radial00=np.exp(-qs*qs/2)
radial10=(1-qs*qs/2)*radial00
osc00=np.exp(-qs*qs/4)
imag01=qs/np.sqrt(2)*osc00
errors=[]
for q in [0.,.4,1.2,2.7]:
    f00=quad(lambda r:r*np.exp(-r*r/2)*j0(q*r),0,np.inf,epsabs=1e-11)[0]
    f10=quad(lambda r:r**3/2*np.exp(-r*r/2)*j0(q*r),0,np.inf,epsabs=1e-11)[0]
    trans=quad(lambda x:np.sqrt(2)*x/np.sqrt(np.pi)*np.exp(-x*x)*np.sin(q*x),
               -np.inf,np.inf,epsabs=1e-11)[0]
    refs=[np.exp(-q*q/2),(1-q*q/2)*np.exp(-q*q/2),q/np.sqrt(2)*np.exp(-q*q/4)]
    errors.extend(abs(np.array([f00,f10,trans])-refs))
assert max(errors)<1e-10
report["form_factor_quadrature_max_error"]=float(max(errors))
fig,axes=plt.subplots(2,1,figsize=(8.3,8),layout="constrained")
axes[0].plot(qs,radial00,label="Состояние α = (0, 0)",color=COL[0],lw=2)
axes[0].plot(qs,radial10,label="Состояние α = (1, 0)",color=COL[1],lw=2)
axes[0].scatter([0,0],[1,1],color=COL[0],zorder=5)
axes[0].set(title=r"Плотность одного состояния: $\mathcal{F}_\alpha(0)=1$",
            ylabel=r"$\mathcal{F}_\alpha(q)$")
axes[1].plot(qs,osc00,label="F₀₀: диагональ",color=COL[0],lw=2)
axes[1].plot(qs,imag01,label="Im F₀₁: переход",color=COL[3],lw=2)
axes[1].scatter([0,0],[1,0],color=[COL[0],COL[3]],zorder=5)
axes[1].set(title="Между двумя состояниями: Fmn(0) = δmn",ylabel="Указанная компонента F")
for ax in axes:
    ax.set_xlabel("q, безразмерный волновой вектор")
    ax.axhline(0,color="gray",lw=.6);ax.grid(alpha=.2);ax.legend(fontsize=11)
fig.suptitle("Две роли индексов у формфактора",fontsize=18)
save(fig,"formfactor")

report["figures"]=FIGURES
report["status"]="all assertions passed"
(OUT/"reading_maps_report.json").write_text(json.dumps(report,ensure_ascii=False,indent=2),encoding="utf8")
print(json.dumps(report,ensure_ascii=False,indent=2))

Воспроизводим учебные примеры

Сохраните следующий листинг как 16_teaching_examples.py в отдельную папку и выполните python 16_teaching_examples.py. Он создаст 24 изображения teach_*.png и файл teaching_examples_report.json. Программа не использует архив диплома и не скачивает данные. Пример правила Вика проверяется двумя способами: подсчётом спариваний и прямой двумерной гауссовой квадратурой.

Она проверяет начальную задачу независимым решателем, эволюцию двух состояний матричной экспонентой, нормировки квадратурой, стационарность действия прямым интегрированием и коэффициенты диаграмм перебором спариваний. Для гауссова примера напечатанные стандартные отклонения близки к 0.05 и 0.07; это выборочные оценки, поэтому не обязаны совпадать с теоретическими числами до последнего знака.

Что менять для самостоятельной работы: в примере двух состояний измените величину связи и проверьте масштаб времени; в вариационном примере замените допустимую функцию изменения на другую с нулевыми концами и снова проверьте отсутствие линейного члена; в опыте неопределённостей используйте общий случайный источник в обоих слагаемых и сравните с независимыми источниками. Сначала выпишите ожидаемое свойство, потом меняйте программу.

Полный код учебных определений, вариаций и диаграмм: 16_teaching_examples.py

```python """Worked illustrations for the Habr mini-textbook.

Run with Python 3.12: python 16_teaching_examples.py Requires numpy, scipy, matplotlib. No network or private input files. Figures and a numerical/combinatorial report are written beside this script. All examples are explicitly simplified models, not a graphene validation. “”" from pathlib import Path from fractions import Fraction import json import math import platform import numpy as np from scipy.integrate import quad, solve_ivp from scipy.linalg import expm import matplotlib matplotlib.use(“Agg”) import matplotlib.pyplot as plt

def publication_layout(fig): “”“Readable labels at a 600 px article width; retain data and panel order.”“” import textwrap from matplotlib.text import Text width, height = fig.get_size_inches() fig.set_size_inches(width, height * 1.22, forward=True) scale = width / 8.0 main, small = 14.0 * scale, 12.0 * scale for text in fig.findobj(Text): if text.get_text().strip(): text.set_fontsize(max(text.get_fontsize(), main)) if text.get_color() in [‘#d77b27’, ‘#ed8b36’, ‘#df7b28’, ‘#d77527’]: text.set_color(‘#87470f’) for ax in fig.axes: for tick in ax.get_xticklabels() + ax.get_yticklabels(): tick.set_fontsize(small) for label in [ax.xaxis.label, ax.yaxis.label]: txt = label.get_text() if ‘' not in txt:                 label.set_text('\n'.join(textwrap.fill(s, 39) for s in txt.splitlines()))         title = ax.get_title()         if '’ not in title: columns = max(25, int(ax.get_position().width * 75)) ax.set_title(‘\n’.join(textwrap.fill(s, columns) for s in title.splitlines()), fontsize=main, pad=12) legend = ax.get_legend() if legend: for text in legend.get_texts(): text.set_fontsize(small) for line in ax.lines: line.set_linewidth(max(line.get_linewidth(), 1.8)) if fig._suptitle: txt = fig._suptitle.get_text() if ‘' not in txt:             fig._suptitle.set_text(textwrap.fill(txt, 57))         fig._suptitle.set_fontsize(max(main + 2, 18))     for text in fig.texts:         if text is fig._suptitle: continue         txt=text.get_text()         if '’ not in txt: text.set_text(‘\n’.join(textwrap.fill(s, 68) for s in txt.splitlines())) if type(fig.get_layout_engine()).name == ‘PlaceHolderLayoutEngine’: fig.tight_layout(rect=(.01,.10,.99,.91),h_pad=2,w_pad=2) if type(fig.get_layout_engine()).name == ‘ConstrainedLayoutEngine’: fig.get_layout_engine().set(h_pad=.15, w_pad=.12)

from matplotlib.patches import FancyBboxPatch, FancyArrowPatch, Circle

OUT = Path(file).resolve().parent C = [“#156783”, “#d77b27”, “#258471”, “#9a466b”] plt.rcParams.update({“font.family”: “DejaVu Sans”, “font.size”: 12, “axes.spines.top”: False, “axes.spines.right”: False}) REPORT = {“python”: platform.python_version(), “figures”: []}

def save(fig, name): publication_layout(fig); fig.savefig(OUT / (“teach_” + name + “.png”), dpi=155, bbox_inches=“tight”, facecolor=“white”) plt.close(fig) REPORT[“figures”].append(“teach_” + name + “.png”)

def panels(title, n=2, height=3.5): fig, ax = plt.subplots(1, n, figsize=(10, height), constrained_layout=True) fig.suptitle(title, fontsize=17) return fig, np.atleast_1d(ax)

def arrow(ax, p, q, color=C[0], **kw): ax.add_patch(FancyArrowPatch(p, q, arrowstyle=“-|>”, mutation_scale=15, color=color, lw=1.7, **kw))

def card(ax, x, y, w, h, text, color=C[0], size=12): ax.add_patch(FancyBboxPatch((x-w/2, y-h/2), w, h, boxstyle=“round,pad=.04”, fc=“#f3f7fa”, ec=color, lw=1.5)) ax.text(x, y, text, ha=“center”, va=“center”, fontsize=size, color=“#172b3a”)

def canvas(title, height=4): fig, axs = panels(title, 1, height) ax = axs[0] ax.set(xlim=(0, 10), ylim=(0, 5)) ax.axis(“off”) return fig, ax

1. Basis: coordinates change, the represented vector does not.

fig, ax = panels(“Базис: один вектор, два набора координат”) u = np.array([3., 4.]) Q = np.array([[1, -1], [1, 1]]) / np.sqrt(2) coords = Q.T @ u assert np.allclose(Q @ coords, u) for a, B, title in zip(ax, [np.eye(2), Q], [“Обычные оси: (3, 4)”, “Поворот осей: (7/√2, 1/√2)”]): a.set(xlim=(-1.8, 4.6), ylim=(-1, 5), aspect=“equal”, title=title) a.grid(alpha=.15) for j in range(2): arrow(a, (0, 0), B[:, j], C[j]) arrow(a, (0, 0), u, C[2]) a.text(2.8, 4.35, “u”, color=C[2]) a.set_xlabel(“x”); a.set_ylabel(“y”) save(fig, “basis”)

2. Initial conditions.

t = np.linspace(0, 3, 301) fig, ax = panels(“Начальное условие выбирает одно решение”, 1) for c in [1, 2, 3]: ax[0].plot(t, cnp.exp(-t), label=f"y(0) = {c}") ax[0].set(xlabel=“время τ”, ylabel=“y(τ)”, title=“Одно уравнение: y′ = −y”) ax[0].legend() sol = solve_ivp(lambda t, y: -y, [0, 3], [2.], t_eval=t, rtol=1e-10, atol=1e-12) REPORT[“initial_max_error”] = float(np.max(abs(sol.y[0]-2np.exp(-t)))) assert REPORT[“initial_max_error”] < 1e-9 save(fig, “initial”)

3. Boundary conditions for the same differential operator.

x = np.linspace(0, 1, 501) fig, ax = panels(“Граничные условия меняют решение −u″ = 1”, 1) ax[0].plot(x, x*(1-x)/2, label=“u(0)=0, u(1)=0”, color=C[0]) ax[0].plot(x, x-x*x/2, label=“u(0)=0, u′(1)=0”, color=C[1]) ax[0].set(xlabel=“x”, ylabel=“u(x)”) ax[0].legend() save(fig, “boundary”)

4. Normalization: amplitude and probability density.

fig, ax = panels(“Нормировка: делим амплитуду на корень из нормы”) ax[0].plot(x, x, label=“f(x)=x”) ax[0].plot(x, np.sqrt(3)x, label=“ψ(x)=√3 x”) ax[1].fill_between(x, xx, alpha=.25, color=C[0], label=“∫|f|² dx = 1/3”) ax[1].plot(x, 3xx, color=C[1], label=“∫|ψ|² dx = 1”) for a in ax: a.set_xlabel(“x ∈ [0,1]”); a.legend(fontsize=10) ax[0].set_ylabel(“амплитуда”); ax[1].set_ylabel(“плотность вероятности”) assert abs(quad(lambda z: 3zz, 0, 1)[0]-1) < 1e-13 save(fig, “normalization”)

5. Absolute and relative tolerances.

fig, ax = panels(“Допуск: единицы и масштаб ответа имеют значение”, 1) q = np.logspace(-8, 3, 300) bound = 1e-8 + 1e-5q ax[0].loglog(q, bound, label=“atol + rtol |эталон|”, color=C[0]) ax[0].loglog(q, np.full_like(q, 1e-8), “–”, label=“atol = 10⁻⁸”) ax[0].loglog(q, 1e-5q, “:”, label=“rtol |эталон|; rtol = 10⁻⁵”) ax[0].set(xlabel=“модуль эталонного значения”, ylabel=“допустимое отклонение”) ax[0].legend(fontsize=10) save(fig, “tolerance”)

6. Error and residual have different units.

fig, ax = panels(“Ошибка решения и невязка — разные величины”, 1) uh = np.linspace(1.8, 2.2, 101) ax[0].plot(uh, 3*uh, color=C[0]) ax[0].axhline(6, color=C[2], ls=“–”) ax[0].plot([2, 2.01], [6, 6.03], “o”, color=C[1]) ax[0].annotate(“ошибка u: 0.01 м”, xy=(2.005, 6), xytext=(1.82, 5.7), arrowprops={“arrowstyle”: “->”}) ax[0].annotate(“невязка: 0.03 Н”, xy=(2.01, 6.015), xytext=(2.04, 6.25), arrowprops={“arrowstyle”: “->”}) ax[0].set(xlabel=“u, м”, ylabel=“k u, Н”, title=“k = 3 Н/м; b = 6 Н; точное u = 2 м”) save(fig, “residual”)

7. Significant digits and exponent are separate concepts.

fig, ax = canvas(“Мантисса: сколько значащих цифр сохранено”, 3.8) card(ax, 2.4, 3.4, 4, 1.0, “Точное десятичное число\n1.23456”) arrow(ax, (4.6, 3.4), (5.3, 3.4)) card(ax, 7.6, 3.4, 4, 1.0, “Округление до 4 цифр\n1.235”, C[1]) ax.text(5, 1.8, “Абсолютная ошибка: 0.00044\nОтносительная: 0.00044 / 1.23456 ≈ 0.0356%”, ha=“center”, va=“center”, fontsize=14) ax.text(5, .6, “Учебный десятичный формат.\nОбычный float использует основание 2.”, ha=“center”, fontsize=11) save(fig, “precision”) fig, ax = canvas(“Порядок: та же мантисса, другой масштаб”, 3.1) for pos, exponent, value in [(1.7, -3, “0.001235”), (5, 0, “1.235”), (8.3, 3, “1235”)]: card(ax, pos, 3.0, 2.9, 1.5, f"1.235 × 10^{exponent}\n\n{value}") ax.text(5, 1.3, “Число значащих цифр в записи — 4 во всех трёх случаях.”, ha=“center”, fontsize=12) save(fig, “exponent”)

8. A stable evaluation of a well-conditioned function near zero.

xx = np.logspace(-16, -1, 200) stable = xx/(np.sqrt(1+xx)+1) naive = np.sqrt(1+xx)-1 relerr = np.abs(naive-stable)/stable fig, ax = panels(“Численная устойчивость: одинаковая формула, разный расчёт”) ax[0].loglog(xx, stable, label=“x / (√(1+x)+1)”, color=C[2]) ax[0].loglog(xx, np.maximum(naive, 1e-20), “–”, label=“√(1+x) − 1”, color=C[1]) ax[0].set(xlabel=“x”, ylabel=“значение”) ax[0].legend(fontsize=10) ax[1].loglog(xx, np.maximum(relerr, 1e-17), color=C[1]) ax[1].set(xlabel=“x”, ylabel=“относительное расхождение”) REPORT[“cancellation_at_1e-16”] = {“naive”: float(np.sqrt(1+1e-16)-1), “stable”: float(1e-16/(np.sqrt(1+1e-16)+1))} assert REPORT[“cancellation_at_1e-16”][“naive”] == 0 save(fig, “stability”)

9. Conditioning with exact arithmetic.

fig, ax = panels(“Обусловленность: точный ответ чувствителен к данным”, 1) labels = [“a=1.000001\nb=1”, “a=1.000002\nb=1”] answers = [1e-6, 2e-6] ax[0].bar(labels, answers, color=[C[0], C[1]]) ax[0].set(ylabel=“точная разность a − b”, title=“a меняется примерно на 0.0001%; ответ — на 100%”) assert Fraction(“1.000002”)-Fraction(1) == 2*(Fraction(“1.000001”)-Fraction(1)) save(fig, “conditioning”)

10. Budget and independence.

fig, ax = canvas(“Бюджет ошибок: источник → оценка → действие”, 5.6) rows = [(“Входные данные”, “стандартная неопределённость”, “уточнить измерение”), (“Сетка и отсечение”, “сходимость / остаток ряда”, “уменьшить шаг / расширить сумму”), (“Округление”, “устойчивость и разрядность”, “изменить способ вычисления”), (“Физическая модель”, “допущения и данные”, “проверить применимость”)] for i, (source, estimate, action_text) in enumerate(rows): yy=4.3-i*1.16 card(ax,5,yy,9.2,.96,‘’,C[i]) ax.text(.65,yy+.22,source,fontsize=17,weight=‘bold’,color=C[i],va=‘center’) ax.text(.65,yy-.22,estimate+'\n→ '+action_text,fontsize=14,va=‘center’) save(fig, “budget”)

rng = np.random.default_rng(314159) z = rng.standard_normal((200000, 2)) e1, e2 = .03z[:, 0], .04z[:, 1] independent = e1+e2 correlated = .03z[:, 0]+.04z[:, 0] fig, ax = panels(“Складываются дисперсии, если ковариация равна нулю”) ax[0].scatter(e1[:1200], e2[:1200], s=5, alpha=.18, color=C[0]) ax[0].set(xlabel=“ε₁”, ylabel=“ε₂”, title=“Независимые источники”) for values, label, color in [(independent, “независимые: σ = 0.05”, C[0]), (correlated, “общий источник: σ = 0.07”, C[1])]: ax[1].hist(values, bins=100, density=True, histtype=“step”, label=label, color=color) ax[1].set(xlabel=“суммарная погрешность”, ylabel=“плотность”, title=“3% и 4% в одном масштабе”) ax[1].legend(fontsize=9) REPORT[“sample_std”] = {“independent”: float(np.std(independent, ddof=1)), “correlated”: float(np.std(correlated, ddof=1))} assert abs(REPORT[“sample_std”][“independent”]-.05) < .0005 assert abs(REPORT[“sample_std”][“correlated”]-.07) < .0005 save(fig, “uncertainty”)

11. Test the tester with a deliberately altered probability density.

fig, ax = plt.subplots(2,1,figsize=(8,6.5),constrained_layout=True, gridspec_kw={‘height_ratios’:[2,1]}) fig.suptitle(“Проверка проверки: нормировки недостаточно”,fontsize=19) r = np.linspace(0, 4, 400) ax[0].plot(r, rnp.exp(-rr/2), label=“ρ₀ = r exp(−r²/2)”) ax[0].plot(r, 2rnp.exp(-rr), label=“ρ₁ = 2r exp(−r²)”, color=C[1]) ax[0].set(xlabel=“r”, ylabel=“радиальная плотность”) ax[0].legend(fontsize=9) M = [] for f in [lambda r: rnp.exp(-rr/2), lambda r: 2rnp.exp(-rr)]: M.append([quad(f, 0, np.inf)[0], quad(lambda r: rrf®, 0, np.inf)[0]]) assert np.allclose(M, [[1, 2], [1, 1]], atol=1e-12) ax[1].axis(“off”) table = ax[1].table(cellText=[[“Исходная”, “1 — проходит”, “2 — проходит”], [“Изменённая”, “1 — проходит”, “1 — не проходит”]], colLabels=[“Плотность”, “Нормировка = 1”, “Средний r² = 2”], loc=“center”, cellLoc=“center”) table.auto_set_font_size(False); table.set_fontsize(16); table.scale(1, 3) REPORT[“mutation_integrals”] = M save(fig, “tester”)

12. Quantum state, operator, observable and dynamics.

fig, ax = canvas(“Уравнение Шрёдингера связывает состояние и гамильтониан”, 4.4) card(ax, 2.1, 3.8, 3.7, 1.1, “Состояние ψ(0)\nамплитуды и фазы”) card(ax, 7.8, 3.8, 3.7, 1.1, “Гамильтониан H\nоператор энергии”, C[1]) arrow(ax, (3, 3.1), (4.2, 2.7)); arrow(ax, (7, 3.1), (5.8, 2.7), C[1]) card(ax, 5, 2.1, 7.5, 1, “iℏ ∂ψ/∂τ = Hψ → ψ(τ)”, C[2], 17) arrow(ax, (5, 1.5), (5, 1.1)) card(ax, 5, .6, 8.8, .75, “Измерение A: исходы aₙ, вероятности |⟨aₙ|ψ⟩|²; среднее ⟨ψ|A|ψ⟩”, size=11) save(fig, “quantum”)

tau = np.linspace(0, 2np.pi, 400) coupling = 1.0 # Change this positive energy scale; hbar = 1. H = np.array([[0., -coupling], [-coupling, 0.]]) fig, ax = panels(“Два состояния: матрица задаёт изменение амплитуд”) ax[0].imshow(H, cmap=“RdBu”, vmin=-1, vmax=1) for i in range(2): for j in range(2): ax[0].text(j, i, “0” if i==j else “−t”, ha=“center”, va=“center”, fontsize=20, color=‘black’ if i==j else ‘white’) ax[0].set(xticks=[0, 1], xticklabels=[“A: столбец”, “B: столбец”], yticks=[0, 1], yticklabels=[“A: строка”, “B: строка”], title=“Hᵢⱼ = ⟨i|H|j⟩”) ax[1].plot(tau, np.cos(couplingtau)**2, label=“P(A)=cos²(tτ/ℏ)”) ax[1].plot(tau, np.sin(couplingtau)**2, label=“P(B)=sin²(tτ/ℏ)”) ax[1].set(xlabel=“время τ (единицы с ℏ=1)”, ylabel=“вероятность”, title=“ψ(0) = (1,0)”) ax[1].legend(fontsize=9) for tt in [.1, .7, 2.]: assert np.allclose(expm(-1jHtt)@np.array([1., 0.]), [np.cos(couplingtt), 1jnp.sin(couplingtt)]) save(fig, “two_sites”)

13. Screening: signs for a positive test charge.

fig, ax = panels(“Экранирование: перераспределение зарядов и полное поле”) a = ax[0]; a.set(xlim=(-3, 3), ylim=(-2, 2), aspect=“equal”); a.axis(“off”) a.add_patch(Circle((0, 0), .28, fc=C[1])); a.text(0, 0, “+”, color=“white”, ha=“center”, va=“center”, fontsize=23) for theta in np.linspace(0, 2np.pi, 10, endpoint=False): xx, yy = 1.1np.cos(theta), 1.1*np.sin(theta) a.text(xx, yy, “−”, ha=“center”, va=“center”, color=C[0], fontsize=23) a.text(0, -1.75, “Избыток электронов около + заряда\nСхема изменения плотности, не орбит”, ha=“center”, fontsize=10) rr = np.linspace(.2, 5, 300) ax[1].plot(rr, 1/rr, label=“1/r”) ax[1].plot(rr, np.exp(-rr)/rr, label=“exp(−κr)/r, κ=1”) ax[1].set(xlabel=“расстояние r”, ylabel=“потенциал, условные единицы”, title=“Иллюстрация: объёмная модель Томаса — Ферми”) ax[1].legend(fontsize=9) save(fig, “screening”)

14. Lagrangian field description and variation.

fig, ax = canvas(“От координат частиц к действию поля”, 4.4) for yy, text in [(4.25, “Частица: q(τ) → L(q, q̇) → S = ∫ L dτ”), (2.85, “Поле: φ(x,τ) → плотность ℒ(φ,∂φ) → S = ∫ dτ dx ℒ”), (1.45, “Меняем историю: φ → φ + εη; dS/dε при ε=0 равно 0”)]: card(ax, 5, yy, 9, .85, text, size=13) arrow(ax, (5, 3.73), (5, 3.37)); arrow(ax, (5, 2.33), (5, 1.97)) ax.text(5, .35, “При фиксированных границах η = 0 на границе; внутри η произвольна.”, ha=“center”, fontsize=11) save(fig, “lagrangian”)

Finite interval, phi*=sin(pix), J=(pi²+mu²)sin(pix), eta=sin(2pi*x).

mu = 1. eta_mode = 2 # Positive integer; eta(x) vanishes at both endpoints. def action(eps): def density(x): phi = np.sin(np.pix)+epsnp.sin(eta_modenp.pix) dp = np.pinp.cos(np.pix)+epseta_modenp.pinp.cos(eta_modenp.pix) J = (np.pi2+mu2)np.sin(np.pix) return .5(dpdp+mumuphiphi)-Jphi return quad(density, 0, 1, epsabs=1e-11)[0] eps = np.linspace(-.3, .3, 101) ss = np.array([action(e) for e in eps]) exact = action(0)+eps**2((eta_modenp.pi)2+mu2)/4 assert np.max(abs(ss-exact)) < 1e-12 fig, ax = panels(“Вариация — изменение всей функции, а не её аргумента”) for e in [-.2, 0, .2]: ax[0].plot(x, np.sin(np.pix)+enp.sin(eta_modenp.pi*x), label=f"ε = {e}") ax[0].set(xlabel=“x”, ylabel=“φ + εη”, title=“Концы всех профилей закреплены”) ax[0].legend(fontsize=10) ax[1].plot(eps, ss-action(0)); ax[1].plot(0, 0, “o”, color=C[1]) ax[1].set(xlabel=“ε”, ylabel=“S[φ+εη] − S[φ]”, title=“Линейный член равен нулю”) REPORT[“action_identity_max_error”] = float(np.max(abs(ss-exact))) save(fig, “variation”)

15. Wick pairings, retained explicitly for transparent combinatorics.

def pairings(items): if not items: yield [] return a, rest = items[0], items[1:] for i, b in enumerate(rest): for tail in pairings(rest[:i]+rest[i+1:]): yield [(a, b)]+tail

def multiplicities(groups): counts = {} for pairs in pairings(tuple(range(len(groups)))): edges = tuple(sorted(tuple(sorted((groups[a], groups[b]))) for a, b in pairs)) counts[edges] = counts.get(edges, 0)+1 return counts

six = multiplicities((“x”, “y”, “z”, “z”, “z”, “z”)) assert sorted(six.values()) == [3, 12] tadpole_count = six[tuple(sorted([(“x”, “z”), (“y”, “z”), (“z”, “z”)]))] ten = multiplicities((“x”, “y”)+(“z”,)*4+(“w”,)4) sunset_count = sum(v for edges, v in ten.items() if edges.count((“w”, “z”)) == 3 and (“x”, “y”) not in edges and (“z”, “z”) not in edges and (“w”, “w”) not in edges) assert sunset_count == 192 assert Fraction(tadpole_count, math.factorial(4)) == Fraction(1, 2) assert Fraction(sunset_count, 2math.factorial(4)**2) == Fraction(1, 6) REPORT[“wick_counts”] = {“all_six_field_pairings”: sum(six.values()), “disconnected”: 3, “tadpole”: tadpole_count, “sunset”: sunset_count, “all_ten_field_pairings”: sum(ten.values())}

fig, ax = plt.subplots(figsize=(7.2, 7.4)) ax.set(xlim=(0, 10), ylim=(0, 10)); ax.axis(‘off’) fig.suptitle(“От ряда к диаграммам”, fontsize=21) steps = [ (“1. Разложить вес взаимодействия”, r"e^{-S_{\rm int}},\quad S_{\rm int}=\frac{g}{4!}\int d^dz\,\phi(z)^4", “Разлагаем экспоненту по степеням g.”), (“2. Выбрать порядок V”, r"\frac{(-g)^V}{V!(4!)^V}“, “V внутренних точек → V интегралов.\nВ каждой точке — четыре поля.”), (“3. Вычислить гауссово среднее”, r”\langle\phi_i\phi_j\rangle_0=G_{ij}", “Разбить все поля на пары.\nПеремножить G для каждой пары.”), (“4. Записать результат рисунком”, “пара полей → линия точка взаимодействия → вершина”, “Суммировать все спаривания\nс коэффициентом из шага 2.”)] for i, (title, formula, detail) in enumerate(steps): y = 8.65-i*2.35 card(ax,5,y,9.35,1.96,‘’,C[i]) ax.text(5,y+.66,title,ha=‘center’,va=‘center’,fontsize=16,weight=‘bold’,color=C[i]) if i==3: formula=“Пара полей → линия\nТочка взаимодействия → вершина” ax.text(5,y+.08,formula,ha=‘center’,va=‘center’,fontsize=19 if i<3 else 14.5) ax.text(5,y-.57,detail,ha=‘center’,va=‘center’,fontsize=14) if i<3: arrow(ax,(5,y-1.01),(5,y-1.29)) save(fig, “series”)

All three pairings: a crossing is not a new interaction vertex.

fig, axs = plt.subplots(3,1,figsize=(7.5,9.5),constrained_layout=True) fig.suptitle(‘Правило Вика: 4 места → 3 спаривания’,fontsize=20) terms=[([(0,1),(2,3)],r’G_{12}G_{34}‘), ([(0,2),(1,3)],r’G_{13}G_{24}’), ([(0,3),(1,2)],r’G_{14}G_{23}‘)] pos=[(.5,1.25),(.5,.15),(3.6,1.25),(3.6,.15)] for n,(a,(edges,label)) in enumerate(zip(axs,terms),1): a.set(xlim=(-.1,8),ylim=(-.3,1.75));a.axis(‘off’) for c,(i,j) in zip(C,edges): a.plot([pos[i][0],pos[j][0]],[pos[i][1],pos[j][1]],color=c,lw=2.8) for j,(xx,yy) in enumerate(pos): a.plot(xx,yy,‘o’,color=’#172b3a’,ms=9) a.text(xx,yy+(.18 if j%2==0 else -.18),str(j+1),ha=‘center’,va=‘center’,fontsize=17) a.text(5.4,.9,label,ha=‘center’,fontsize=25) a.text(5.4,.3,f’Слагаемое {n}‘,ha=‘center’,fontsize=16) if n<3:a.text(2,-.28,’+',ha=‘center’,fontsize=23) save(fig,‘wick_pairs’)

Independent numerical check: a direct 2D Gaussian quadrature, not a Wick sum.

cov=np.array([[2.,1.],[1.,3.]]) chol=np.linalg.cholesky(cov) nodes,weights=np.polynomial.hermite.hermgauss(3) direct_moment=0. for i,zi in enumerate(nodes): for j,zj in enumerate(nodes): X,Y=chol@(np.sqrt(2)*np.array([zi,zj])) direct_moment+=weights[i]*weights[j]X**2Y**2/np.pi wick_moment=cov[0,0]cov[1,1]+2cov[0,1]2 assert abs(direct_moment-wick_moment)<1e-12 for count in [2,4,6,8,10]: assert sum(1 for _ in pairings(tuple(range(count))))==math.factorial(count)//(2(count//2)*math.factorial(count//2)) REPORT[‘wick_numeric’]={‘direct_quadrature’:float(direct_moment),‘pairing_sum’:float(wick_moment)} fig,ax=plt.subplots(1,2,figsize=(9,4.8),constrained_layout=True) fig.suptitle(‘Один момент: три вклада, сумма равна 8’,fontsize=19) ax[0].imshow(cov,cmap=‘Blues’,vmin=0,vmax=4) for i in range(2): for j in range(2):ax[0].text(j,i,str(int(cov[i,j])),ha=‘center’,va=‘center’,fontsize=27) ax[0].set(xticks=[0,1],xticklabels=[‘X’,‘Y’],yticks=[0,1],yticklabels=[‘X’,‘Y’],title=‘Ковариация G’) ax[1].bar([0,1,2],[6,1,1],color=[C[0],C[1],C[2]]) ax[1].set(xticks=[0,1,2],xticklabels=[‘XX · YY’,‘XY · XY’,‘XY · XY’],ylim=(0,7.3),ylabel=‘Вклад в среднее X²Y²’) for i,val in enumerate([6,1,1]):ax[1].text(i,val+.15,str(val),ha=‘center’,fontsize=22) ax[1].tick_params(axis=‘x’,labelrotation=25) save(fig,‘wick_values’)

def vertex(ax, xy, label, internal=True): ax.plot(*xy, “o”, color=C[1] if internal else C[0], ms=8) ax.text(xy[0], xy[1]-.35, label, ha=“center”, fontsize=12)

def line(ax, a, b, bend=0, color=C[0]): ax.add_patch(FancyArrowPatch(a, b, arrowstyle=“-”, color=color, lw=2, connectionstyle=f"arc3,rad={bend}"))

fig, axs = panels(“Один порядок g, разные диаграммы и коэффициенты”, 2, 4.3) for a in axs: a.set(xlim=(0, 6), ylim=(-1, 4.2)); a.axis(“off”) a = axs[0]; z0 = (3, 2) for xy, label in [((.5, 3.5), “x₁”), ((.5, .5), “x₂”), ((5.5, 3.5), “x₃”), ((5.5, .5), “x₄”)]: vertex(a, xy, label, False); line(a, xy, z0) vertex(a, z0, “z”) a.text(3, -.55, “4! спариваний / 4! = 1\n”+r"-g\int dz\,\prod_{i=1}^4G(x_i,z)“, ha=“center”, fontsize=9) a.set_title(“Четырёхточечная связная функция: дерево”) a = axs[1] for xy, label, internal in [((.5, 1.4), “x”, False), ((3, 1.4), “z”, True), ((5.5, 1.4), “y”, False)]: vertex(a, xy, label, internal) line(a, (.5, 1.4), (3, 1.4)); line(a, (3, 1.4), (5.5, 1.4)) a.add_patch(Circle((3, 2.3), .9, fill=False, ec=C[2], lw=2)) a.text(3, -.55, “12 спариваний / 4! = 1/2\n”+r”-\frac{g}{2}\int dz\,G(x,z)G(z,z)G(z,y)", ha=“center”, fontsize=9) a.set_title(“Двухточечная функция: одна петля”) save(fig, “diagrams_first”)

fig, axs = panels(“Знаменатель сокращает отдельный вакуумный пузырь”, 2, 3.8) for a in axs: a.set(xlim=(0, 6), ylim=(0, 4)); a.axis(“off”) a = axs[0] line(a, (.5, 1), (5.5, 1)); vertex(a, (.5, 1), “x”, False); vertex(a, (5.5, 1), “y”, False) for xx in [2.3, 3.7]: a.add_patch(Circle((xx, 2.8), .7, fill=False, ec=C[1], lw=2)) vertex(a, (3, 2.8), “z”) a.set_title(“Числитель: свободная линия × вакуум”) a.text(3, .05, “−g/8 · G(x,y) ∫ dz G(z,z)²”, ha=“center”, fontsize=11) a = axs[1] card(a, 3, 2.5, 5.4, 1.5, “(N₀ + gN₁) / (1 + gD₁)\n\n= N₀ + g(N₁ − N₀D₁) + …”, size=12) a.text(3, .7, “Вычитается тот же вакуумный вклад.\nГоловастик связан с x,y\nи остаётся в ответе.”, ha=“center”, fontsize=11) save(fig, “vacuum”)

fig, ax = canvas(“Две вершины и две независимые петли: sunset”, 4) z0, w0 = (3, 2.8), (7, 2.8) for xy, label, inner in [((.5, 2.8), “x”, False), (z0, “z”, True), (w0, “w”, True), ((9.5, 2.8), “y”, False)]: vertex(ax, xy, label, inner) line(ax, (.5, 2.8), z0); line(ax, w0, (9.5, 2.8)) for bend in [-.5, 0, .5]: line(ax, z0, w0, bend) ax.text(5, 4.5, “Внутренних линий I=3, вершин V=2: L=I−V+1=2”, ha=“center”, fontsize=12) ax.text(5, .85, “2 × 4 × 4 × 3! = 192 спаривания; 192 / [2!(4!)²] = 1/6”, ha=“center”, fontsize=11) ax.text(5, .2, “Вклад: g²/6 ∫ dz dw G(x,z) G(z,w)³ G(w,y)”, ha=“center”, fontsize=13) save(fig, “sunset”)

Finite one-dimensional tadpole check: G(0)=1/(2mu).

g = .2 direct = -g/2 * quad(lambda z: np.exp(-muabs(z))/(2mu) * (1/(2mu)) * np.exp(-muabs(z))/(2mu), -np.inf, np.inf, epsabs=1e-12)[0] expected = -g/(16mu**4) assert abs(direct-expected) < 1e-12 REPORT[“tadpole_at_x_equals_y_zero”] = {“quadrature”: direct, “analytic”: expected} REPORT[“limits”] = [“Figures use simplified models, not experimental validation.”, “Wick expansion is coefficient-wise; convergence is not assumed.”, “The screening potential drawn is a 3D model, not graphene’s exact kernel.”] (OUT/“teaching_examples_report.json”).write_text( json.dumps(REPORT, ensure_ascii=False, indent=2), encoding=“utf8”) print(json.dumps(REPORT, ensure_ascii=False, indent=2))

</spoiler>

### Как воспроизвести остальные иллюстрации

Файл `09_visual_guide.py` создаёт 21 научную схему и график для статьи: решётку, конус, уровни, пару, пропагатор, графы, топологию, многопетлевые скелеты и пояснения bootstrap. Он также проверяет нормировки первых осцилляторных состояний. Два юмористических изображения — комикс zinoink и кадр Adult Swim с указанными источниками. Фрагменты процессных диаграмм взяты из книги Коке и Киссинджера с номерами страниц; новые научные схемы и графики строятся на Python.

```bash
python 09_visual_guide.py
python 01_loops.py
python 03_surface.py

Первый запуск создаёт PNG, второй — в том числе анимацию ошибки по порядкам, третий — вращение поверхности \Delta=0. GIF цепочки пузырьков создаёт 06_graphene.py. Вращение камеры не следует воспринимать как физическую динамику системы.

Полный код научных иллюстраций: 09_visual_guide.py
"""Иллюстрации статьи. Python 3.12; numpy, scipy, matplotlib.
Все схемы авторские; это модели, не фотографии эксперимента.
Запуск: python 09_visual_guide.py. PNG появляются рядом с файлом.
"""
from pathlib import Path
from math import factorial
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt

def publication_layout(fig):
    """Readable labels at a 600 px article width; retain data and panel order."""
    import textwrap
    from matplotlib.text import Text
    width, height = fig.get_size_inches()
    fig.set_size_inches(width, height * 1.22, forward=True)
    scale = width / 8.0
    main, small = 14.0 * scale, 12.0 * scale
    for text in fig.findobj(Text):
        if text.get_text().strip():
            text.set_fontsize(max(text.get_fontsize(), main))
            if text.get_color() in ['#d77b27', '#ed8b36', '#df7b28', '#d77527']:
                text.set_color('#87470f')
    for ax in fig.axes:
        for tick in ax.get_xticklabels() + ax.get_yticklabels():
            tick.set_fontsize(small)
        for label in [ax.xaxis.label, ax.yaxis.label]:
            txt = label.get_text()
            if '$' not in txt:
                label.set_text('\n'.join(textwrap.fill(s, 39) for s in txt.splitlines()))
        title = ax.get_title()
        if '$' not in title:
            columns = max(25, int(ax.get_position().width * 75))
            ax.set_title('\n'.join(textwrap.fill(s, columns) for s in title.splitlines()),
                         fontsize=main, pad=12)
        legend = ax.get_legend()
        if legend:
            for text in legend.get_texts(): text.set_fontsize(small)
        for line in ax.lines: line.set_linewidth(max(line.get_linewidth(), 1.8))
    if fig._suptitle:
        txt = fig._suptitle.get_text()
        if '$' not in txt:
            fig._suptitle.set_text(textwrap.fill(txt, 57))
        fig._suptitle.set_fontsize(max(main + 2, 18))
    for text in fig.texts:
        if text is fig._suptitle: continue
        txt=text.get_text()
        if '$' not in txt:
            text.set_text('\n'.join(textwrap.fill(s, 68) for s in txt.splitlines()))
    if type(fig.get_layout_engine()).__name__ == 'PlaceHolderLayoutEngine':
        fig.tight_layout(rect=(.01,.10,.99,.91),h_pad=2,w_pad=2)
    if type(fig.get_layout_engine()).__name__ == 'ConstrainedLayoutEngine':
        fig.get_layout_engine().set(h_pad=.15, w_pad=.12)


from matplotlib.patches import Circle, Ellipse, Polygon, FancyArrowPatch
from scipy.special import eval_hermite, spence
from scipy.constants import hbar, elementary_charge as e

OUT = Path(__file__).resolve().parent
C = ['#147d92', '#ed8b36', '#7144a5', '#24384c', '#bb4353']
plt.rcParams.update({'font.family': 'DejaVu Sans', 'font.size': 12,
                     'axes.spines.top': False, 'axes.spines.right': False,
                     'figure.facecolor': '#fffdf8', 'axes.facecolor': '#fffdf8',
                     'savefig.facecolor': '#fffdf8', 'axes.titleweight': 'bold'})

def save(fig, name):
    publication_layout(fig); fig.savefig(OUT / (name + '.png'), dpi=130, bbox_inches='tight')
    plt.close(fig)

def canvas(title, size=(11, 5)):
    fig, ax = plt.subplots(figsize=size)
    ax.set(xlim=(0, 10), ylim=(0, 5))
    ax.axis('off')
    ax.set_title(title, pad=16, fontsize=19)
    return fig, ax

def arrow(ax, a, b, color=C[0], **kw):
    ax.add_patch(FancyArrowPatch(a, b, arrowstyle='-|>', mutation_scale=17,
                               color=color, linewidth=2, **kw))

def box(ax, x, y, text, color=C[0], size=13):
    ax.text(x, y, text, ha='center', va='center', fontsize=size,
            bbox=dict(boxstyle='round,pad=.65', fc='white', ec=color, lw=2))

def honeycomb():
    pts, sub = [], []
    a1, a2 = np.array([np.sqrt(3), 0]), np.array([np.sqrt(3)/2, 1.5])
    for i in range(-5, 6):
        for j in range(-4, 5):
            for s, d in enumerate([np.array([0., 0.]), np.array([0., 1.])]):
                p = i*a1+j*a2+d
                if abs(p[0]) < 5.5 and abs(p[1]) < 3.4:
                    pts.append(p); sub.append(s)
    pts, sub = np.array(pts), np.array(sub)
    dist = np.linalg.norm(pts[:, None]-pts[None, :], axis=2)
    edges = np.argwhere(np.triu(np.isclose(dist, 1), 1))
    return pts, sub, edges

def graphene():
    p, sub, edges = honeycomb()
    fig = plt.figure(figsize=(11, 5.7))
    ax = fig.add_subplot(projection='3d')
    for i, j in edges:
        ax.plot(*p[[i,j]].T, [0, 0], color='#60788b', lw=2)
    ax.scatter(p[:,0], p[:,1], np.zeros(len(p)), c=[C[s] for s in sub], s=65,
               edgecolor='white', linewidth=.5, depthshade=False)
    ax.set(zlim=(-2,2)); ax.view_init(elev=34, azim=-64); ax.set_axis_off()
    ax.set_box_aspect((11,7,2))
    ax.set_title('Графен: один атомный слой углерода', fontsize=20)
    fig.text(.5,.05,'Шарики — положения атомов; линии — ближайшие связи. Схема, не микрофотография.',ha='center',fontsize=12)
    save(fig,'guide_graphene')
    fig, ax = plt.subplots(figsize=(11,6))
    for i,j in edges: ax.plot(*p[[i,j]].T, color='#adbac2', lw=2, zorder=1)
    for s in [0,1]:
        ax.scatter(*p[sub==s].T, s=95, color=C[s], label=f'Подрешётка {"AB"[s]}', zorder=3)
    # Примитивная ячейка (параллелограмм) содержит два атома с учётом границ.
    origin=np.array([-.15,-.25]); a1=np.array([np.sqrt(3),0]); a2=np.array([np.sqrt(3)/2,1.5])
    ax.add_patch(Polygon([origin,origin+a1,origin+a1+a2,origin+a2],fc=C[2],alpha=.15,ec=C[2],lw=3))
    ax.annotate('Ячейка: два атома A и B', xy=(1.2,.6),xytext=(2.3,2.6),arrowprops={'arrowstyle':'->'},fontsize=12)
    ax.annotate('Связь C–C ≈ 0.142 нм',xy=(0,.5),xytext=(-5,2.6),arrowprops={'arrowstyle':'->'},fontsize=12)
    ax.set_aspect('equal'); ax.axis('off'); ax.legend(loc='lower left',ncol=2)
    ax.set_title('«Соты» — геометрия атомов, не траектории электронов',fontsize=18)
    fig.text(.5,.02,'Идеальная решётка → модель ближайших соседей → приближение Дирака около K и K′',ha='center',fontsize=12)
    save(fig,'guide_lattice')
    fig=plt.figure(figsize=(12,5.4)); ax=fig.add_subplot(121,projection='3d')
    r=np.linspace(0,1,30); t=np.linspace(0,2*np.pi,60); R,T=np.meshgrid(r,t)
    for s,c in [(1,C[0]),(-1,C[1])]: ax.plot_surface(R*np.cos(T),R*np.sin(T),s*R,color=c,alpha=.8,linewidth=0)
    ax.set(xlabel='$p_x$',ylabel='$p_y$',zlabel='$E$',title=r'Конус Дирака: $E=\pm v_F |p|$')
    ax.set_xticks([]); ax.set_yticks([]); ax.set_zticks([])
    ax=fig.add_subplot(122)
    dk=np.linspace(-.7,.7,300); K=4*np.pi/(3*np.sqrt(3))
    band=np.abs(1+2*np.cos(np.sqrt(3)*(K+dk)/2))
    for s in [1,-1]:
        ax.plot(dk,s*band,color=C[0],label='Ближайшие соседи' if s==1 else None)
        ax.plot(dk,s*1.5*np.abs(dk),'--',color=C[1],label='Линейное приближение' if s==1 else None)
    ax.axvspan(-.12,.12,color=C[0],alpha=.12)
    ax.set(xlabel=r'$(k_x-K_x)d$, $k_y=0$',ylabel='$E/t$',title='Конус работает вблизи точки K')
    ax.legend(fontsize=10); ax.grid(alpha=.2)
    fig.tight_layout(); save(fig,'guide_dispersion')

def landau():
    B=10.; vf=1e6; lb=np.sqrt(hbar/(e*B)); scale=hbar*vf/lb/e*1e3
    fig,axes=plt.subplots(1,2,figsize=(11,5.6))
    for n in range(-5,6):
        en=np.sign(n)*scale*np.sqrt(2*abs(n))
        axes[0].hlines(en,0,1,color=C[0] if n>=0 else C[1],lw=2.5)
        axes[0].text(1.05,en,f'n={n}',va='center',fontsize=10)
    axes[0].set(xlim=(-.1,1.6),ylabel='Энергия, мэВ',title='Графен, B = 10 Тл'); axes[0].set_xticks([])
    n=np.arange(6)
    axes[1].plot(n,n+.5,'o-',label=r'Параболическая зона: $n+1/2$',color=C[2])
    axes[1].plot(n,np.sqrt(2*n),'o-',label=r'Графен: $\sqrt{2n}$',color=C[0])
    axes[1].set(xlabel='Номер n ≥ 0',ylabel='Энергия в собственных единицах модели',title='Различается закон расстояний')
    axes[1].legend(fontsize=10); axes[1].grid(alpha=.2)
    fig.suptitle(f'Магнитная длина {lb*1e9:.2f} нм; E₁ = {np.sqrt(2)*scale:.1f} мэВ',fontsize=18)
    fig.tight_layout(); save(fig,'guide_landau')
    fig,axes=plt.subplots(1,2,figsize=(11,5.3)); x=np.linspace(-5,5,700)
    for n in range(4):
        phi=eval_hermite(n,x)*np.exp(-x*x/2)/np.sqrt(2**n*factorial(n)*np.sqrt(np.pi))
        axes[0].plot(x,phi+n*1.3,label=f'n={n}',color=C[n]); axes[0].axhline(n*1.3,color='gray',lw=.5)
        assert abs(np.trapezoid(phi*phi,x)-1)<1e-6
    axes[0].set(xlabel=r'$\xi=(x-x_0)/l_B$',ylabel='Функции со сдвигом для наглядности',title='Осцилляторные функции'); axes[0].legend(fontsize=10)
    for x0,c in [(-2,C[0]),(0,C[1]),(2,C[2])]:
        axes[1].plot(x,np.exp(-(x-x0)**2)/np.sqrt(np.pi),color=c,label=f'x₀/lB = {x0}')
    axes[1].set(xlabel='x / lB',ylabel='Плотность по x, в единицах 1/lB',title='Один уровень — разные центры');axes[1].legend(fontsize=10)
    fig.tight_layout();save(fig,'guide_basis')

def pair_and_perturbation():
    fig,ax=canvas('Электрон + незанятое место = возбуждение пары')
    for off,title in [(0,'До перехода'),(5.5,'После перехода')]:
        ax.text(off+2,4.3,title,ha='center',fontsize=16)
        for y,l in [(1.4,'Занятый уровень'),(3.2,'Свободный уровень')]:
            ax.plot([off+.4,off+3.7],[y,y],color=C[3],lw=2)
        for j in range(4):
            empty=off>0 and j==2
            ax.add_patch(Circle((off+.8+j*.8,1.4),.13,fc='white' if empty else C[0],ec=C[1] if empty else C[0],lw=2))
        if off>0:
            ax.add_patch(Circle((off+2.4,3.2),.13,fc=C[0]))
            ax.text(off+2.65,3.35,'электрон −e',fontsize=12)
            ax.text(off+2.65,1,'дырка +e',fontsize=12)
            arrow(ax,(off+2.4,1.65),(off+2.4,2.95),C[1])
    arrow(ax,(4.1,2.3),(5.3,2.3));ax.text(5,.3,'Энергия без взаимодействия: Eₑ − Eₕ. Число электронов не изменилось.',ha='center',fontsize=12)
    save(fig,'guide_pair')
    fig,axes=plt.subplots(1,2,figsize=(11,5.2))
    x=np.linspace(0,4,500); rho=2*x*np.exp(-x*x); U=-1/np.sqrt(x*x+.2**2)
    axes[0].plot(x,rho,label='Радиальный вес ρ(r)',color=C[0]);axes[0].plot(x,rho*U,label='Вклад ρ(r)U(r)',color=C[1]);axes[0].fill_between(x,rho*U,0,color=C[1],alpha=.2)
    axes[0].set(xlabel='r, условные единицы',title='Среднее — площадь под взвешенной кривой');axes[0].legend(fontsize=10)
    lam=np.linspace(0,1,300); exact=np.array([np.linalg.eigvalsh([[v*.3,v*.4],[v*.4,1-v*.2]])[0] for v in lam])
    axes[1].plot(lam,exact,label='Точная нижняя энергия',color=C[3]);axes[1].plot(lam,.3*lam,'--',label='Первый порядок',color=C[1]);axes[1].plot(lam,.3*lam-.16*lam**2,':',lw=3,label='Второй порядок',color=C[0])
    axes[1].set(xlabel='λ',ylabel='E',title='Два уровня: H₀=diag(0,1)');axes[1].legend(fontsize=10)
    for ax in axes: ax.grid(alpha=.2)
    fig.tight_layout();save(fig,'guide_perturbation')

def bubbles():
    fig,ax=canvas('Один и тот же пузырёк, повторённый 0, 1, 2, 3 раза',(11,6))
    for n in range(4):
        y=4.2-n*1.1; ax.text(.2,y,f'n={n}',va='center')
        ax.plot([1.3,7.4],[y,y],color=C[1],lw=2)
        for j in range(n):
            xx=2.4+j*1.7
            ax.add_patch(Ellipse((xx,y),1.1,.65,fc='#fffdf8',ec=C[0],lw=2.5,zorder=3))
            arrow(ax,(xx-.15,y+.31),(xx+.15,y+.31),C[0],zorder=4)
        ax.text(7.8,y,['V₀','V₀ Π V₀','V₀ Π V₀ Π V₀','V₀ (Π V₀)³'][n],va='center',fontsize=15)
    ax.text(5,.2,'Сплошные дуги — электронные линии; оранжевые отрезки — кулоновское взаимодействие.',ha='center',fontsize=11)
    save(fig,'guide_bubbles')

def graphs():
    fig,axes=plt.subplots(1,3,figsize=(12,4.4))
    pts=np.array([[0,0],[1,0],[1,1],[0,1]])
    all_edges=[(0,1),(1,2),(2,3),(3,0),(0,2)]
    for n,ax in enumerate(axes):
        for k,(i,j) in enumerate(all_edges[:3+n]): ax.plot(*pts[[i,j]].T,color=C[0] if k<3 else C[1],lw=3)
        ax.scatter(*pts.T,s=110,color=C[3],zorder=3)
        for i,(x,y) in enumerate(pts): ax.text(x-.12,y+.1,'ABCD'[i])
        ax.set(xlim=(-.4,1.4),ylim=(-.4,1.4),aspect='equal',title=f'V=4, I={3+n}, L={n}');ax.axis('off')
    fig.suptitle('Остовное дерево + каждое лишнее ребро = новый цикл',fontsize=18)
    fig.tight_layout();save(fig,'guide_graphs')
    fig,axes=plt.subplots(1,2,figsize=(11,5))
    ax=axes[0]
    for x in [-1,1]: ax.add_patch(Circle((x,0),1,fc='none',ec=C[0],lw=3))
    ax.plot(0,0,'o',color=C[1],ms=10);ax.text(-1,0,'F₁',ha='center');ax.text(1,0,'F₂',ha='center');ax.text(0,1.35,'F₃: внешняя область',ha='center')
    ax.set(xlim=(-2.5,2.5),ylim=(-1.5,1.8),aspect='equal',title='Сфера: V=1, I=2, F=3');ax.axis('off')
    ax=axes[1];ax.add_patch(Polygon([[0,0],[2,0],[2,2],[0,2]],fc='#dceff0',ec=C[3],lw=2))
    for a,b in [((.6,0),(1.4,0)),((.6,2),(1.4,2))]:arrow(ax,a,b,C[0])
    for a,b in [((0,.6),(0,1.4)),((2,.6),(2,1.4))]:arrow(ax,a,b,C[1])
    for p in [(0,0),(2,0),(2,2),(0,2)]:ax.plot(*p,'o',color=C[2],ms=10)
    ax.text(1,1,'Одна грань F₁\nСклеить равные края',ha='center',va='center');ax.text(1,-.45,'4 угла → 1 вершина; 4 края → 2 ребра',ha='center',fontsize=11)
    ax.set(xlim=(-.6,2.6),ylim=(-.7,2.5),aspect='equal',title='Тор: V=1, I=2, F=1');ax.axis('off')
    fig.suptitle('Один граф «две петли»: L=2 на обеих поверхностях',fontsize=18)
    fig.tight_layout();save(fig,'guide_topology')

def propagator_response():
    fig,axes=plt.subplots(1,2,figsize=(11,5.3));x=np.linspace(-6,6,700); k=np.linspace(-4,4,700)
    for mu,c in [(.5,C[0]),(1,C[1]),(2,C[2])]:
        axes[0].plot(x,np.exp(-mu*np.abs(x))/(2*mu),color=c,label=f'μ={mu:g}')
        axes[1].plot(k,1/(k*k+mu*mu),color=c,label=f'μ={mu:g}')
    axes[0].set(xlabel='x',ylabel='G(x)',title='Отклик на точечный источник')
    axes[1].set(xlabel='k',ylabel='D(k)',title='Тот же оператор в Фурье-базисе')
    for ax in axes:ax.legend(fontsize=10);ax.grid(alpha=.2)
    fig.suptitle('Евклидова функция Грина: (−∂² + μ²)G = δ',fontsize=18)
    fig.tight_layout();save(fig,'guide_propagator')
    fig,axes=plt.subplots(1,2,figsize=(11,5.1));t=np.linspace(-3,12,600);omega=np.linspace(0,3,600);eta=.08;gap=1.
    response=np.where(t>=0,-2*np.sin(gap*t)*np.exp(-eta*t),0)
    chi=1/(omega-gap+1j*eta)-1/(omega+gap+1j*eta)
    axes[0].plot(t,response,color=C[0]);axes[0].axvline(0,color=C[1],ls='--');axes[0].set(xlabel='t, при ħ=1',ylabel='χᴿ(t)',title='До воздействия отклик равен нулю')
    axes[1].plot(omega,-chi.imag,color=C[1]);axes[1].axvline(gap,color=C[0],ls='--');axes[1].set(xlabel='ω, при ħ=1',ylabel='−Im χᴿ(ω)',title='Спектр: виден разрешённый переход')
    fig.suptitle('Учебная пара уровней: ΔE=1, матричный элемент=1, η=0.08',fontsize=16)
    for ax in axes:ax.grid(alpha=.2)
    fig.tight_layout();save(fig,'guide_response')

def ladder(ax,L,labels=False):
    for j in range(L+1):ax.plot([j,j],[0,1],color=C[3],lw=2)
    for y in [0,1]:ax.plot([0,L],[y,y],color=C[3],lw=2);ax.scatter(range(L+1),[y]*(L+1),s=30,color=C[3],zorder=3)
    at=max(1,L//3) if L>1 else 0;bot=max(1,2*L//3) if L>1 else 1
    starts=[(0,1),(at,1),(L,1),(L,0),(bot,0),(0,0)]
    ends=[(-.55,1.5),(at,1.7),(L+.55,1.5),(L+.55,-.5),(bot,-.7),(-.55,-.5)]
    for i,(a,b) in enumerate(zip(starts,ends)):
        arrow(ax,a,b,C[0]);ax.text(b[0],b[1],f'p{i+1}',ha='center',va='bottom' if b[1]>0 else 'top',fontsize=9)
    for j in range(L):ax.text(j+.5,.5,f'y{j+1}' if labels else str(j+1),ha='center',va='center',color=C[1],fontsize=14)
    ax.set(xlim=(-1,L+1),ylim=(-1.1,2.1),aspect='equal');ax.axis('off')

def many_loops():
    fig,axes=plt.subplots(3,1,figsize=(9,10.5),gridspec_kw={'height_ratios':[1,1,1.2]})
    for ax,L in zip(axes,[1,2,9]):
        ladder(ax,L);ax.set_title(f'L={L}: I={3*L+1}, V={2*L+2}, I−V+1={L}',fontsize=14)
    fig.suptitle('Шесть внешних линий; внутри — всё больше независимых петель',fontsize=18)
    fig.text(.5,.01,'Планарные скелеты: показывают топологию, не полный набор ненулевых диаграмм N=4 SYM.',ha='center',fontsize=11)
    fig.tight_layout(rect=(0,.04,1,.95));save(fig,'guide_nine_loops')
    fig,ax=plt.subplots(figsize=(11,6.2));ladder(ax,3,True)
    edge_labels=[(.5,1.12,'D₁'),(1.5,1.12,'D₂'),(2.5,1.12,'D₃'),(.5,-.2,'D₄'),(1.5,-.2,'D₅'),(2.5,-.2,'D₆'),(-.17,.5,'D₇'),(1.17,.5,'D₈'),(2.17,.5,'D₉'),(3.2,.5,'D₁₀')]
    for x,y,s in edge_labels:ax.text(x,y,s,ha='center',fontsize=12,color=C[2])
    for x,y,s in [(.5,1.65,'x₁'),(2.1,1.65,'x₂'),(3.7,.5,'x₃'),(2.5,-.7,'x₄'),(.8,-.7,'x₅'),(-.7,.5,'x₆')]:ax.text(x,y,s,ha='center',fontsize=13,color=C[4])
    ax.set_title('Три петли → интегрирование по y₁, y₂, y₃; десять линий → десять Dⱼ',fontsize=17)
    fig.text(.5,.02,'pᵢ = xᵢ₋₁ − xᵢ (циклически). x и y здесь — дуальные импульсные координаты.',ha='center',fontsize=12)
    save(fig,'guide_integral')

def wick():
    fig,axes=plt.subplots(1,3,figsize=(12,4.8));points=np.array([[-1,-1],[1,-1],[1,1],[-1,1]])
    for ax,pairs in zip(axes,[[(0,1),(2,3)],[(0,2),(1,3)],[(0,3),(1,2)]]):
        for a,b in pairs:ax.plot(*points[[a,b]].T,color=C[0],lw=3)
        ax.scatter(*points.T,s=110,color=C[1],zorder=3)
        for i,(x,y) in enumerate(points):ax.text(x*1.2,y*1.2,f'x{i+1}',ha='center',va='center')
        ax.set(xlim=(-1.7,1.7),ylim=(-1.7,1.7),aspect='equal');ax.axis('off')
    fig.suptitle('Четыре гауссовых множителя: три способа разбить на пары',fontsize=18)
    fig.text(.5,.04,'⟨x⁴⟩₀ = 3. Для шести множителей: 5 × 3 × 1 = 15 спариваний.',ha='center',fontsize=14)
    fig.tight_layout(rect=(0,.08,1,.95));save(fig,'guide_wick')
    fig,axes=plt.subplots(1,2,figsize=(11,4.5));x=np.linspace(-3,3,500)
    for g,c in [(0,C[3]),(.5,C[0]),(2,C[1])]:
        w=np.exp(-x*x/2-g*x**4/24);z=np.trapezoid(w,x)
        # Для рисунка нормировка вычислена на более широком интервале.
        from scipy.integrate import quad
        z=quad(lambda q:np.exp(-q*q/2-g*q**4/24),-np.inf,np.inf)[0]
        axes[0].plot(x,w/z,color=c,label=f'g={g}')
    axes[0].set(xlabel='x',ylabel='Нормированный вес',title='Взаимодействие подавляет хвосты');axes[0].legend(fontsize=10)
    axes[1].axis('off');axes[1].text(.5,.65,'6 множителей: 15 спариваний\n\n3 несвязных = ⟨x²⟩₀ ⟨x⁴⟩₀\n\n12 связных остаются',ha='center',va='center',fontsize=16,transform=axes[1].transAxes)
    axes[1].text(.5,.14,'Первый коэффициент: −12 / 4! = −1/2',ha='center',fontsize=14,transform=axes[1].transAxes,color=C[0])
    fig.tight_layout();save(fig,'guide_zero_model')

def process_and_bootstrap():
    fig,ax=canvas('Шесть внешних состояний: пример процесса 2 → 4')
    ax.add_patch(Ellipse((5,2.5),2.1,2,fc='#dceff0',ec=C[0],lw=2));ax.text(5,2.5,'Амплитуда\nA₆',ha='center',va='center',fontsize=20)
    for i,y in enumerate([1.5,3.5]):arrow(ax,(.8,y),(4,2.3+i*.4));ax.text(.3,y,f'вход {i+1}',fontsize=12)
    for i,y in enumerate([.5,1.8,3.1,4.4]):arrow(ax,(6,2.2+i*.2),(8.8,y));ax.text(9,y,f'выход {i+1}',fontsize=12)
    ax.text(5,.15,'Для вероятности нужны |A₆|², фазовый объём и нормировки.',ha='center',fontsize=12);save(fig,'guide_scattering')
    fig,ax=canvas('Два маршрута к символу',(8,7))
    box(ax,2.2,4.25,'Прямой bootstrap\nамплитуды')
    box(ax,7.5,4.25,'Формфактор\n+ антипод',C[1])
    arrow(ax,(2.2,3.75),(4.5,3.1));arrow(ax,(7.5,3.75),(5.5,3.1),C[1])
    box(ax,5,2.65,'Сравнение символов\nи контрольных коэффициентов',C[2])
    arrow(ax,(5,2.12),(5,1.35),C[3])
    ax.text(5,1.78,'+ константы и граничные условия',ha='center',fontsize=14,
            bbox=dict(facecolor='#fffdf8',edgecolor='none',pad=6))
    box(ax,5,.8,'Восстановление полной функции',C[3])
    save(fig,'guide_routes')
    fig,axes=plt.subplots(1,2,figsize=(11,5.4));x=np.linspace(0,1,350)
    for a,c in [(-1,C[1]),(0,C[0]),(1,C[2]),(2,C[3])]:axes[0].plot(x,x+a*x*(1-x),color=c,label=f'a={a}')
    axes[0].set(xlabel='x',ylabel='F(x)',title='F(0)=0 и F(1)=1 ещё недостаточно');axes[0].legend(fontsize=10)
    axes[1].plot(x,x+x*(1-x),color=C[0],label='a=1: после F′(0)=2')
    axes[1].scatter([.5],[.75],color=C[1],s=90,zorder=4,label='Отдельная проверка: F(1/2)=3/4')
    axes[1].plot(x,x+x*(1-x)+3*x*x*(1-x)**2,'--',color=C[2],label='Расширили пространство: b=3')
    axes[1].set(xlabel='x',ylabel='F(x)',title='Новые условия и новые неоднозначности');axes[1].legend(fontsize=9,loc='upper left')
    for ax in axes:ax.grid(alpha=.2)
    fig.tight_layout();save(fig,'guide_bootstrap')

def symbol_counts():
    fig,axes=plt.subplots(1,2,figsize=(11,5.3));x=np.linspace(.02,.95,350);f=spence(1-x)
    axes[0].plot(x,f,label='Li₂(x)',color=C[0]);axes[0].plot(x,f+np.pi**2/6,label='Li₂(x)+π²/6',color=C[1]);axes[0].set(xlabel='x',ylabel='F(x)',title='Один символ, разные функции');axes[0].legend(fontsize=10)
    axes[1].axis('off');axes[1].text(.5,.75,'d Li₂(x) = −log(1−x) d log x',ha='center',fontsize=15,transform=axes[1].transAxes)
    axes[1].text(.5,.48,'Символ: −(1−x) ⊗ x\n\nСначала 1−x, затем x',ha='center',fontsize=18,color=C[0],transform=axes[1].transAxes)
    axes[1].text(.5,.13,'Граничное значение Li₂(0)=0\nвыбирает первую кривую.',ha='center',fontsize=13,transform=axes[1].transAxes)
    fig.tight_layout();save(fig,'guide_symbol')
    counts=[6,12,636,11208,263880,4916466,92954568,1671656292,30024320034]
    fig,ax=plt.subplots(figsize=(11,6));L=np.arange(1,10)
    ax.semilogy(L,counts,'o-',color=C[0],lw=2,label='Ненулевые слова в сопоставимом представлении')
    ax.semilogy(L,6.**(2*L),'--',color=C[1],label='Все слова шестибуквенного алфавита: 6^(2L)')
    for l,n in zip(L,counts):ax.annotate(f'{n:,}'.replace(',',' '),(l,n),xytext=(0,-18),textcoords='offset points',ha='center',fontsize=9)
    ax.set(xlabel='Число петель L',ylabel='Число слов (логарифмическая шкала)',title='Ненулевых слов много, но это не все возможные слова');ax.set_xticks(L);ax.legend(fontsize=10);ax.grid(alpha=.2)
    fig.tight_layout();save(fig,'guide_counts_all')

if __name__=='__main__':
    graphene();landau();pair_and_perturbation();bubbles();graphs()
    propagator_response();many_loops();wick();process_and_bootstrap();symbol_counts()
    print('Создана 21 иллюстрация guide_*.png; нормировки состояний проверены.')

Диплом наконец пригодился как материал, на котором можно пройти путь от уравнения до числа и проверить работу ИИ на знакомой постановке. Восстановленный расчёт дал контрольные значения; расхождения в архиве показали пределы доверия к этим значениям; опыт с намеренными ошибками показал пределы самих тестов. Такой порядок работы полезен и за пределами физики: сохранять основания модели, отделять их от реализации и делать результат воспроизводимым. После этого новость «Claude посчитал девять петель» вызывает предметные вопросы: какие величины посчитаны, какие ограничения использованы и что удалось проверить независимо?

Источник

  • 26.06.26 15:05 Riley Stephens

    If withdrawals keep getting denied, stay calm. I went through the same, and this firm helped me recover everything. Their assistance was outstanding. Contact: [ResQProFirm @Gmail|•|com], Telegram: ResQprofirm, WhatsApp: <+198> <5296> <9146>.

  • 26.06.26 15:09 Antonio Riley

    Withdrawal troubles shouldn’t stress you out. I faced a similar problem, and this firm stepped in and recovered my funds. Their support truly mattered. Contact them: [[email protected], ResQprofirm @aol.com], Telegram: ResQprofirm, WhatsApp: +19852969146.

  • 28.06.26 00:37 kimberlyhebertt673

    I invested in bitcoin trading After losing $78.4 USDT) linked to a romance fraud scam worth of cryptocurrency through an online investment platform and later discovered it was a scam. After extensive research for recovery options, I contacted CAPITAL CRYPTO RECOVER based on positive client reviews and recommendations. Their professional security team guided me through the recovery process using advanced technology, and I was able to recover my lost cryptocurrency successfully. I am truly grateful for their support and assistance during such a difficult experience. I will advise you to contact CAPITAL CRYPTO RECOVER helped me recover my funds. For anyone facing similar issues, Website: https://recovercapital.wixsite.com/capital-crypto-rec-1 Email: [email protected] Telegram: @Capitalcryptorecover Contact: [email protected] Call/Text Number: +1 (336) 390-6684

  • 28.06.26 00:37 kimberlyhebertt673

    I invested in bitcoin trading After losing $78.4 USDT) linked to a romance fraud scam worth of cryptocurrency through an online investment platform and later discovered it was a scam. After extensive research for recovery options, I contacted CAPITAL CRYPTO RECOVER based on positive client reviews and recommendations. Their professional security team guided me through the recovery process using advanced technology, and I was able to recover my lost cryptocurrency successfully. I am truly grateful for their support and assistance during such a difficult experience. I will advise you to contact CAPITAL CRYPTO RECOVER helped me recover my funds. For anyone facing similar issues, Website: https://recovercapital.wixsite.com/capital-crypto-rec-1 Email: [email protected] Telegram: @Capitalcryptorecover Contact: [email protected] Call/Text Number: +1 (336) 390-6684

  • 29.06.26 11:57 Lisadonato0726

    For 43 years, I struggled with bad credit due to my own poor decisions, and my credit score was around 490. When my girlfriend and I decided to buy a house, a mortgage broker informed us that it would be impossible to secure a mortgage with my credit score. As a result, she referred me to a company called HACK MAVENS CREDIT SPECIALIST, assuring me of their professionalism and ability to assist with credit improvement. Upon contacting them, I was impressed by their professionalism and they assured me that they could help. In less than 6 days, my credit score skyrocketed to 785, and they also successfully resolved issues in my credit report, including the bankruptcy. I am incredibly satisfied with their service and would highly recommend HACK MAVENS CREDIT SPECIALIST for reliable credit repairs. You can reach them at H A C K M A V E N S 5 [AT] G M A I L [DOT] COM or at [+] [1] [2 0 9] [4 1 7] – [1 9 5 7]. Thanks to their help, my girlfriend and I are now proud homeowners.

  • 29.06.26 22:37 riley777

    Back in 2025, I watched my life savings vanish. A thief took every cent. I felt desperate and went looking for a way to get it back. I found a guy here who said he was an expert haha. He talked about special software that could find my missing cash. I trusted him. That was a big mistake. He was just another scammer. I paid him a software fee and then he just stopped answering my texts and ran off with my money too. I felt so ashamed that I kept quiet about it for months. It is hard to admit you got fooled twice. Later on, I found a real pro. she did not use a fancy sales pitch. she just looked at the trans screenshots and followed the path the money took. she worked fast and got my funds back into my account. Having that money back changed everything. I can sleep again. her info; [email protected]. Call/chatroom on Whtasapp/ +44 7476618364.

  • 30.06.26 15:08 wendytaylor015

    My name is Wendy Taylor, I'm from Los Angeles, i want to announce to you Viewer how Capital Crypto Recover help me to restore my Lost Bitcoin, I invested with a Crypto broker without proper research to know what I was hoarding my hard-earned money into scammers, i lost access to my crypto wallet or had your funds stolen? Don’t worry Capital Crypto Recover is here to help you recover your cryptocurrency with cutting-edge technical expertise, With years of experience in the crypto world, Capital Crypto Recover employs the best latest tools and ethical hacking techniques to help you recover lost assets, unlock hacked accounts, Whether it’s a forgotten password, Capital Crypto Recover has the expertise to help you get your crypto back. a security company service that has a 100% success rate in the recovery of crypto assets, i lost wallet and hacked accounts. I provided them the information they requested and they began their investigation. To my surprise, Capital Crypto Recover was able to trace and recover my crypto assets successfully within 24hours. Thank you for your service in helping me recover my $647,734 worth of crypto funds and I highly recommend their recovery services, they are reliable and a trusted company to any individuals looking to recover lost money. Contact email [email protected] OR Telegram @Capitalcryptorecover Call/Text Number +1 (336)390-6684 his contact: [email protected] His website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 30.06.26 15:08 wendytaylor015

    My name is Wendy Taylor, I'm from Los Angeles, i want to announce to you Viewer how Capital Crypto Recover help me to restore my Lost Bitcoin, I invested with a Crypto broker without proper research to know what I was hoarding my hard-earned money into scammers, i lost access to my crypto wallet or had your funds stolen? Don’t worry Capital Crypto Recover is here to help you recover your cryptocurrency with cutting-edge technical expertise, With years of experience in the crypto world, Capital Crypto Recover employs the best latest tools and ethical hacking techniques to help you recover lost assets, unlock hacked accounts, Whether it’s a forgotten password, Capital Crypto Recover has the expertise to help you get your crypto back. a security company service that has a 100% success rate in the recovery of crypto assets, i lost wallet and hacked accounts. I provided them the information they requested and they began their investigation. To my surprise, Capital Crypto Recover was able to trace and recover my crypto assets successfully within 24hours. Thank you for your service in helping me recover my $647,734 worth of crypto funds and I highly recommend their recovery services, they are reliable and a trusted company to any individuals looking to recover lost money. Contact email [email protected] OR Telegram @Capitalcryptorecover Call/Text Number +1 (336)390-6684 his contact: [email protected] His website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 02.07.26 01:22 Lieneke Bonnema

    I highly recommend ResQprofirm for their professional asset recovery services of my $120,000 scammed funds. Their expertise, professionalism, and commitment to achieving results make them a reliable choice for anyone seeking dependable recovery assistance. [email protected], WhatsApp +19852969146, telegram @resqprofirm

  • 02.07.26 01:26 Clara Morin

    I want to extend my deepest appreciation for showing that circumstances do not define one’s potential for greatness. Your support has been a major source of inspiration during my trading journey, and I am sincerely grateful for your insight and mentorship. Thank you so much. [email protected], WhatsApp +19852969146, telegram ResQprofirm

  • 02.07.26 01:31 Robin Hale

    I sincerely want to thank you for demonstrating that anyone can rise above their circumstances and achieve success. Your constant support has been incredibly inspiring during my trading journey, and your wisdom and advice mean so much to me. I appreciate you deeply. [email protected], WhatsApp +19852969146, telegram Resqprofirm

  • 04.07.26 15:32 Fraddy Pual

    There are few companies I trust as much as FUNDSRETRIEVER. When I lost $653,000 in Ethereum to a ruthless scam, I thought my life would never be the same. The betrayal cut deep, but I refused to give up. I searched tirelessly for a legitimate way to recover what was stolen, and finally found FUNDSRETRIEVER—the most competent and compassionate recovery team I could have imagined. They handled my case with precision and care, and in the end, my entire ETH wallet was restored. More than the money, they gave me back my hope and happiness. I'm sharing my story because I want others to know that recovery is possible. If a scam has taken from you, don't hesitate—contact FUNDSRETRIEVER today. Email: FUNDSRETRIEVER1@ Gmail.com | WhatsApp: +1 603-512-1448 | Telegram: @FUNDSRETRIEVER

  • 05.07.26 14:44 lydiassmith567

    HIRE A HACKER YOUR STOLEN CRYPTO RECOVERY / BTC / USDT / ETH WITH THE HELP OF CAPITAL CRYPTO RECOVER. I want to share my experience publicly regarding cryptocurrency wallet recovery, I highly recommend you to contact CAPITAL CRYPTO RECOVER, a professional private investigator in the Bitcoin world. They have a certified expert security team specializing in Bitcoin Recovery Services and have helped many people worldwide recover their lost funds. My wife and I were defrauded by an online manipulator posing as an experienced crypto investment professional. We lost $9.2 Million Stolen BTC in cryptocurrency and were left feeling homeless. After spending hours searching for a reliable crypto recovery service, I discovered CAPITAL CRYPTO RECOVER online. By patiently explaining my situation to their team, I was able to recover all my funds. Remarkably, my money was returned to my wallet in less than 24 hours. I am extremely grateful to CAPITAL CRYPTO RECOVER for their excellent assistance—they truly were a godsend in my difficult situation. If you have fallen victim to a cryptocurrency scam, you can reach CAPITAL CRYPTO RECOVER through the following channels Email: [email protected] OR Call/Text: +1 (336) 390-6684 Contact: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 05.07.26 14:44 lydiassmith567

    HIRE A HACKER YOUR STOLEN CRYPTO RECOVERY / BTC / USDT / ETH WITH THE HELP OF CAPITAL CRYPTO RECOVER. I want to share my experience publicly regarding cryptocurrency wallet recovery, I highly recommend you to contact CAPITAL CRYPTO RECOVER, a professional private investigator in the Bitcoin world. They have a certified expert security team specializing in Bitcoin Recovery Services and have helped many people worldwide recover their lost funds. My wife and I were defrauded by an online manipulator posing as an experienced crypto investment professional. We lost $9.2 Million Stolen BTC in cryptocurrency and were left feeling homeless. After spending hours searching for a reliable crypto recovery service, I discovered CAPITAL CRYPTO RECOVER online. By patiently explaining my situation to their team, I was able to recover all my funds. Remarkably, my money was returned to my wallet in less than 24 hours. I am extremely grateful to CAPITAL CRYPTO RECOVER for their excellent assistance—they truly were a godsend in my difficult situation. If you have fallen victim to a cryptocurrency scam, you can reach CAPITAL CRYPTO RECOVER through the following channels Email: [email protected] OR Call/Text: +1 (336) 390-6684 Contact: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 06.07.26 16:20 Olga Ognjanović

    Having trouble withdrawing funds from an investment platform? ResQprofirm provides fund recovery assistance for individuals seeking help with investment-related disputes. I reached out to them after experiencing problems with an investment platform, and I appreciated their professionalism and support throughout the process. If you're facing a similar situation, act promptly, keep records of your transactions and communications, and seek assistance from a qualified recovery service or the appropriate authorities. Contact: Email: [email protected] Telegram: @ResQprofirm WhatsApp: +1 985 296 9146

  • 06.07.26 16:31 Joseph Weigl

    Invest wisely and stay cautious. Don't be influenced by promises of unusually high returns or convincing sales pitches from brokers. I learned this the hard way after falling victim to an investment scam that promised huge profits. Fortunately, I acted quickly and reported the incident to a recovery firm for assistance. Contact: Email: [email protected] Telegram: @Resqprofirm WhatsApp: +1 985 296 9146

  • 06.07.26 16:33 Jaran Løvlien

    A heartfelt thank you to RESQPRO FIRM for their commitment and professionalism throughout the investigation of my case. Their team worked diligently and helped recover assets valued at $88,000, which were returned to my wallet. I truly appreciate their support, clear communication, and dedication, and I'm grateful for the assistance I received. Contact: Email: [email protected] Telegram: @Resqprofirm WhatsApp: +1 985 296 9146

  • 07.07.26 18:00 robertalfred175

    CRYPTO SCAM RECOVERY SUCCESSFUL – A TESTIMONIAL OF LOST PASSWORD TO YOUR DIGITAL WALLET BACK. My name is Robert Alfred, Am from Australia. I’m sharing my experience in the hope that it helps others who have been victims of crypto scams. A few months ago, I fell victim to a fraudulent crypto investment scheme linked to a broker company. I had invested heavily during a time when Bitcoin prices were rising, thinking it was a good opportunity. Unfortunately, I was scammed out of $120,000 AUD and the broker denied me access to my digital wallet and assets. It was a devastating experience that caused many sleepless nights. Crypto scams are increasingly common and often involve fake trading platforms, phishing attacks, and misleading investment opportunities. In my desperation, a friend from the crypto community recommended Capital Crypto Recovery Service, known for helping victims recover lost or stolen funds. After doing some research and reading multiple positive reviews, I reached out to Capital Crypto Recovery. I provided all the necessary information—wallet addresses, transaction history, and communication logs. Their expert team responded immediately and began investigating. Using advanced blockchain tracking techniques, they were able to trace the stolen Dogecoin, identify the scammer’s wallet, and coordinate with relevant authorities to freeze the funds before they could be moved. Incredibly, within 24 hours, Capital Crypto Recovery successfully recovered the majority of my stolen crypto assets. I was beyond relieved and truly grateful. Their professionalism, transparency, and constant communication throughout the process gave me hope during a very difficult time. If you’ve been a victim of a crypto scam, I highly recommend them with full confidence contacting: Email: [email protected] Telegram: @Capitalcryptorecover Contact: [email protected] Call/Text: +1 (336) 390-6684 Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 07.07.26 18:01 robertalfred175

    CRYPTO SCAM RECOVERY SUCCESSFUL – A TESTIMONIAL OF LOST PASSWORD TO YOUR DIGITAL WALLET BACK. My name is Robert Alfred, Am from Australia. I’m sharing my experience in the hope that it helps others who have been victims of crypto scams. A few months ago, I fell victim to a fraudulent crypto investment scheme linked to a broker company. I had invested heavily during a time when Bitcoin prices were rising, thinking it was a good opportunity. Unfortunately, I was scammed out of $120,000 AUD and the broker denied me access to my digital wallet and assets. It was a devastating experience that caused many sleepless nights. Crypto scams are increasingly common and often involve fake trading platforms, phishing attacks, and misleading investment opportunities. In my desperation, a friend from the crypto community recommended Capital Crypto Recovery Service, known for helping victims recover lost or stolen funds. After doing some research and reading multiple positive reviews, I reached out to Capital Crypto Recovery. I provided all the necessary information—wallet addresses, transaction history, and communication logs. Their expert team responded immediately and began investigating. Using advanced blockchain tracking techniques, they were able to trace the stolen Dogecoin, identify the scammer’s wallet, and coordinate with relevant authorities to freeze the funds before they could be moved. Incredibly, within 24 hours, Capital Crypto Recovery successfully recovered the majority of my stolen crypto assets. I was beyond relieved and truly grateful. Their professionalism, transparency, and constant communication throughout the process gave me hope during a very difficult time. If you’ve been a victim of a crypto scam, I highly recommend them with full confidence contacting: Email: [email protected] Telegram: @Capitalcryptorecover Contact: [email protected] Call/Text: +1 (336) 390-6684 Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 09.07.26 19:06 Toivo Walli

    I lost 8.56btc to a fake Bitcoin mining site, I tried withdrawing but couldn't approved my process, I reported to !R£SQPROFIRM! via °R£SQproFirm°àt°gmail•com° °tEL£°gram=R£SQprofirm °whaT°Zap+198°52°96°91°46

  • 09.07.26 19:10 Misty Alexander

    Ongoing messages demanding more money before approving withdrawals are a major red flag. Stop engaging and report the incident to a trusted re­covery team. For professional support, you can contact R£sQprofirm using °ResQproFirm°àt°g,*ma'il(•)¢m°, TEL£gram ResQprofirm, or |whaTZap| +1-985-296-9146.

  • 09.07.26 19:13 Clara Soto

    Anyone receiving continued requests for additional deposits from a scam platform should immediately cut off communication and submit the case to a reputable re­covery service for investigation. R£sQprofirm is a dependable firm you can reach at °ResQproFirm°àt°g,*ma'il(•)¢om°, TEL£gram ResQprofirm, or |whaTZap| +1-985-296-9146.

  • 12.07.26 03:30 Kora Baltacha

    Time is critical. Act now by reaching out to a reputable, seasoned recovery specialist who will guide you every step of the way. You'll need to submit transaction proof, scammer details, and any other useful information. Armed with this, the experts can trace and attempt to pull your money back from the scammers' hidden accounts or wallets. Best of all, R£sQprofirm provides recovery help without charging any upfront fees. Contact them immediately via Telegram @ResQprofirm, WhatsApp +19852969146, or email [email protected].

  • 12.07.26 03:33 Pahal Mathew

    It's important to move proactively by engaging an experienced recovery specialist. They will assist you throughout the process. To help them, provide: · Transaction evidence · Scammer information · Any additional relevant details The experts will then track and try to retrieve your funds from the scammers' hidden accounts or wallets. R£sQprofirm offers recovery assistance with no upfront fees. Contact: Telegram: @ResQprofirm WhatsApp: +19852969146 Email: [email protected]

  • 13.07.26 23:49 [email protected]

    One of the biggest concerns I have about cryptocurrency is the lack of regulation. It creates opportunities for scammers to invent convincing stories and fraudulent investment schemes. Unfortunately, some social media platforms continue to display these ads because they profit from them, even after users report them.I personally clicked on a Facebook advertisement for a company called Chickenfastmining and ended up losing more than $120,000 in a scam. I reported the ad, but nothing was done. Later, through a Reddit community, I found a recovery service called CYBERBERSPY that, in my personal experience, they helped me recover $110,000 of my lost funds. If you've been a victim of a cryptocurrency scam, don't lose hope. Explore your options carefully, and always verify the legitimacy of any recovery service before trusting them or paying any fees. Based on my own experience, CYBERBERSPY was helpful to me and i was able to recover my funds back, but I encourage everyone to do their own research before using any recovery service.i highly recommend: ([email protected])

  • 15.07.26 11:53 Sarah Green

    Thank you for showing that success is possible regardless of where someone starts. Your encouragement, valuable advice, and continuous support have inspired me throughout my $160,457k crypto investment recovery journey. I truly appreciate your kindness and dedication. Resqprofirm @gmail.com Telegram: Resqprofirm

  • 15.07.26 11:58 Lily Gagné

    I sincerely appreciate you for proving that anyone can overcome challenges and achieve success. Your unwavering support throughout my trading investment scam of $88,890 recovery journey has been truly inspiring, and your guidance and wisdom have meant a great deal to me. Thank you for everything ResQprofirm@ gmail.com, ResQprofirm on the telegram.

  • 16.07.26 21:38 patricialovick86

    How To Recover Your Bitcoin Without Falling Victim To Scams: A  Testimony Experience With Capital Crypto Recover Services, Contact Telegram: @Capitalcryptorecover Dear Everyone, I would like to take a moment to share my positive experience with Capital Crypto Recover Services. Initially, I was unsure if it would be possible to recover my stolen bitcoins. However, with their expertise and professionalism, I was able to fully recover my funds. Unfortunately, many individuals fall victim to scams in the cryptocurrency space, especially those involving fraudulent investment platforms. However, I advise caution, as not all recovery services are legitimate. I personally lost $273,000 worth of Bitcoin from my Binance account due to a deceptive platform. If you have suffered a similar loss, you may be considering crypto recovery, The Capital Crypto Recover is the most knowledgeable and effective Capital Crypto Recovery Services assisted me in recovering my stolen funds within 24 hours, after getting access to my wallet. Their service was not only prompt but also highly professional and effective, and many recovery services may not be trustworthy. Therefore, I highly recommend Capital Crypto Recover to you. i do always research and see reviews about their service, For assistance finding your misplaced cryptocurrency, get in touch with them, They do their jobs quickly and excellently, Stay safe and vigilant in the crypto world. Contact: [email protected]  You can reach them via email at [email protected] OR Call/Text Number +1 (336)390-6684 his contact website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 16.07.26 21:38 patricialovick86

    How To Recover Your Bitcoin Without Falling Victim To Scams: A  Testimony Experience With Capital Crypto Recover Services, Contact Telegram: @Capitalcryptorecover Dear Everyone, I would like to take a moment to share my positive experience with Capital Crypto Recover Services. Initially, I was unsure if it would be possible to recover my stolen bitcoins. However, with their expertise and professionalism, I was able to fully recover my funds. Unfortunately, many individuals fall victim to scams in the cryptocurrency space, especially those involving fraudulent investment platforms. However, I advise caution, as not all recovery services are legitimate. I personally lost $273,000 worth of Bitcoin from my Binance account due to a deceptive platform. If you have suffered a similar loss, you may be considering crypto recovery, The Capital Crypto Recover is the most knowledgeable and effective Capital Crypto Recovery Services assisted me in recovering my stolen funds within 24 hours, after getting access to my wallet. Their service was not only prompt but also highly professional and effective, and many recovery services may not be trustworthy. Therefore, I highly recommend Capital Crypto Recover to you. i do always research and see reviews about their service, For assistance finding your misplaced cryptocurrency, get in touch with them, They do their jobs quickly and excellently, Stay safe and vigilant in the crypto world. Contact: [email protected]  You can reach them via email at [email protected] OR Call/Text Number +1 (336)390-6684 his contact website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 17.07.26 19:24 laimqq90

    I recommend Marie when it comes to recovering lost/stolen ust/bitcoin or any kind of cryptocurrencies' from fake investment platforms because they're well specialized in that area and you'll get your money back in full. I can boldly say this right now based on my prior deal i had with them; she was the only one who was able to recover my lost money $52,760 dollars back to my account, Only * ([email protected] and WhatsApp +1 7127594675 successful in recovering my money. They are the only one who can fully restore your lost funds to your account without any deductions, I really value their work and am recommending her to you today. THANK ME LATER

  • 17.07.26 20:12 martinsjude080

    Needs Online Fraud Help Contact Mighty Hacker Recovery https://mightyhackarrecovery.com I lost $292,900 in Bitcoin after investing with an online mining company. After realizing I had been scammed, I spent a long time searching for ways to recover my funds and contacted several services without success. During my research, I came across Mighty Hacker Recovery through Google and YouTube. What caught my attention was that they said they would not require any upfront payment before providing their recovery service. I decided to contact them to discuss my case and understand their process. Throughout the process, they kept me informed about the progress. After several days, I was asked to provide my Bitcoin wallet address, and my case was concluded. Their fee was handled after the service rather than being requested in advance. If you've been the victim of a cryptocurrency scam, it's important to do your own research, ask questions, and carefully evaluate any recovery service before proceeding. Every case is different, so take the time to verify information and understand the process before making any decisions. For Bitcoin scam recovery, cryptocurrency scam, crypto recovery service, recover stolen Bitcoin, Bitcoin fraud help, blockchain investigation, crypto wallet recovery, online investment scam, digital asset recovery, and crypto scam support. Contact Them on WhatsApp +1 (343) 947-3496 or [email protected] or [email protected] or https://mightyhackarrecovery.com Scam recovery Online fraud help Scam alert Report fraud Fraud investigation Fake website checker Scam checker Identity theft protection Consumer protection Chargeback for scam Wire transfer scam Tech support scam Employment scam Rental scam Shopping scam Email scam WhatsApp scam Telegram scam Facebook scam Instagram scam

  • 18.07.26 17:12 Malthe Larsen

    My experience with OKX has been deeply frustrating. For months, my account withdrawals were restricted with no explanation or resolution. Multiple emails to their support team were ignored, leaving me without answers or reassurance. This complete lack of communication shattered my trust. I ultimately regained access to my funds only through the help of a third-party recovery service, ResQProfirm. What should have been a reliable platform instead made me feel helpless and cut off from my own assets. [email protected], WhatsApp +19852969146, telegram @Resqprofirm

  • 18.07.26 17:42 پریا رضایی

    OKX has been a major disappointment. My withdrawals were restricted for months, and repeated attempts to contact support went unanswered. This silence destroyed my confidence in the platform. I was only able to recover my funds through a third-party service, ResQProfirm. I trusted OKX for its reliability, but the experience left me feeling trapped and helpless. Timely communication and access to one’s own money should be a basic standard. [email protected], WhatsApp +19852969146, telegram: ResQprofirm

  • 18.07.26 17:46 Adem Akışık

    I truly didn’t expect such an outstanding outcome. Recovering my $49,360 felt impossible at first, but ResQprofirm’s dedication and persistence made it happen. I hold their team in the highest regard. [email protected], WhatsApp +19852969146

  • 18.07.26 23:51 bernalzenaida

    WhatsApp https://wa.link/fhle97 Telegram https://msng.link/o?@techcyberforc=tg As cryptocurrency continues to reshape global finance, cybercriminals are finding new ways to exploit investors through scams, hacks, phishing attacks, fake investment platforms, and other forms of digital asset fraud. For many victims, knowing where to turn after a loss can be one of the biggest challenges. Techy Force Cyber Retrieval was founded with one clear mission: to give victims of crypto fraud a fighting chance through professional blockchain investigations and cybersecurity expertise. Our team brings together experienced blockchain analysts, digital forensic specialists, cybersecurity professionals, and legal partners who work collaboratively to investigate cryptocurrency-related crimes. Using advanced blockchain forensic tools and global investigative techniques, we analyze transaction histories, trace digital asset movements where possible, identify valuable investigative leads, and prepare evidence that may assist clients and the appropriate authorities. We believe blockchain should represent transparency, accountability, and trust—not fear. That’s why we’re committed to helping victims understand their options, navigate the investigative process, and take informed action after cryptocurrency fraud. Every case is different, and while no legitimate recovery service can promise a successful recovery, acting quickly and working with experienced professionals can improve the quality of an investigation. At Techy Force Cyber Retrieval, we do more than investigate digital crimes—we advocate for victims, pursue the facts, and help people regain confidence after cryptocurrency fraud. WhatsApp https://wa.link/fhle97 Telegram https://msng.link/o?@techcyberforc=tg Crypto fraud doesn’t have to be the end of the road. It’s where our investigation begins.

  • 19.07.26 04:10 Fraddy Pual

    I can't thank Fundsretriever enough for everything they did for me. My name is Vanessa Conway, and I'm here to tell you my story of how I recovered money I never thought I'd see again. A few months ago, I put a significant amount of money into what looked like a genuine online investment company. At first, it felt real—they showed me fake profits and convinced me to invest even more. But when I tried to cash out, they went quiet and started asking for extra fees. That's when it hit me—I had been scammed. I was heartbroken, frustrated, and didn't know where to turn. That money was my savings—months of hard work gone. Then I found Fundsretriever online. I reached out, hoping for a miracle. Right away, their team made me feel heard. They were responsive, knowledgeable, and walked me through everything. They didn't just take my case—they took it seriously and kept me in the loop every step of the way. I finally felt like I had real experts fighting for me. And guess what? They actually got my money back. It wasn't instant, and it took teamwork, but it happened. When I saw those funds returned, I cried with joy. I'm sharing this so that anyone else out there who's been scammed knows—don't lose hope. Do your research before investing, and stay far away from platforms that promise too much too fast. And if you've already been stung, don't wait—get professional help immediately. Thank you, Fundsretriever, from the bottom of my heart. You didn't just recover my money—you restored my faith. — Vanessa Conway 📧 [email protected] 📱 WhatsApp: +16035121448 💬 Telegram: @Fundsretriever

  • 19.07.26 19:53 kimberlyhebertt6877

    I invested in bitcoin trading After losing $78.4 USDT) linked to a romance fraud scam worth of cryptocurrency through an online investment platform and later discovered it was a scam. After extensive research for recovery options, I contacted CAPITAL CRYPTO RECOVER based on positive client reviews and recommendations. Their professional security team guided me through the recovery process using advanced technology, and I was able to recover my lost cryptocurrency successfully. I am truly grateful for their support and assistance during such a difficult experience. I will advise you to contact CAPITAL CRYPTO RECOVER helped me recover my funds. For anyone facing similar issues, Website: https://recovercapital.wixsite.com/capital-crypto-rec-1 Email: [email protected] Telegram: @Capitalcryptorecover Contact: [email protected] Call/Text Number: +1 (336) 390-6684

  • 19.07.26 19:53 kimberlyhebertt6877

    I invested in bitcoin trading After losing $78.4 USDT) linked to a romance fraud scam worth of cryptocurrency through an online investment platform and later discovered it was a scam. After extensive research for recovery options, I contacted CAPITAL CRYPTO RECOVER based on positive client reviews and recommendations. Their professional security team guided me through the recovery process using advanced technology, and I was able to recover my lost cryptocurrency successfully. I am truly grateful for their support and assistance during such a difficult experience. I will advise you to contact CAPITAL CRYPTO RECOVER helped me recover my funds. For anyone facing similar issues, Website: https://recovercapital.wixsite.com/capital-crypto-rec-1 Email: [email protected] Telegram: @Capitalcryptorecover Contact: [email protected] Call/Text Number: +1 (336) 390-6684

  • 19.07.26 19:54 kimberlyhebertt6877

    I invested in bitcoin trading After losing $78.4 USDT) linked to a romance fraud scam worth of cryptocurrency through an online investment platform and later discovered it was a scam. After extensive research for recovery options, I contacted CAPITAL CRYPTO RECOVER based on positive client reviews and recommendations. Their professional security team guided me through the recovery process using advanced technology, and I was able to recover my lost cryptocurrency successfully. I am truly grateful for their support and assistance during such a difficult experience. I will advise you to contact CAPITAL CRYPTO RECOVER helped me recover my funds. For anyone facing similar issues, Website: https://recovercapital.wixsite.com/capital-crypto-rec-1 Email: [email protected] Telegram: @Capitalcryptorecover Contact: [email protected] Call/Text Number: +1 (336) 390-6684

  • 30.07.26 00:04 Ahmed

    A really cool analysis, thank you. I was especially struck by how strictly the order is defined: data processing and formatting come first, with color only at the very end. This really saves you from the typical mistake of "first a pretty palette, then we figure out what the chart is for." And the palette validator with OKLCH + colorblindness check is absolutely fantastic; you almost never see that in regular tools. By the way, while reading about rank-trajectory and the logarithmic scale, I immediately remembered how convenient it is to analyze dynamics on charts in ExpertOption—I've been trading there for quite some time now. When the data is well visualized, decisions are noticeably easier and more relaxed. I also liked the point about "no more than eight colors" and the dual-axis ban. Strict restrictions sometimes actually produce better results than complete freedom. I'll try this approach myself.

  • 30.07.26 17:27 wendytaylor015

    My name is Wendy Taylor, I'm from Los Angeles, i want to announce to you Viewer how Capital Crypto Recover help me to restore my Lost Bitcoin, I invested with a Crypto broker without proper research to know what I was hoarding my hard-earned money into scammers, i lost access to my crypto wallet or had your funds stolen? Don’t worry Capital Crypto Recover is here to help you recover your cryptocurrency with cutting-edge technical expertise, With years of experience in the crypto world, Capital Crypto Recover employs the best latest tools and ethical hacking techniques to help you recover lost assets, unlock hacked accounts, Whether it’s a forgotten password, Capital Crypto Recover has the expertise to help you get your crypto back. a security company service that has a 100% success rate in the recovery of crypto assets, i lost wallet and hacked accounts. I provided them the information they requested and they began their investigation. To my surprise, Capital Crypto Recover was able to trace and recover my crypto assets successfully within 24hours. Thank you for your service in helping me recover my $647,734 worth of crypto funds and I highly recommend their recovery services, they are reliable and a trusted company to any individuals looking to recover lost money. Contact email [email protected] OR Telegram @Capitalcryptorecover Call/Text Number +1 (336)390-6684 his contact: [email protected] His website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 30.07.26 17:27 wendytaylor015

    My name is Wendy Taylor, I'm from Los Angeles, i want to announce to you Viewer how Capital Crypto Recover help me to restore my Lost Bitcoin, I invested with a Crypto broker without proper research to know what I was hoarding my hard-earned money into scammers, i lost access to my crypto wallet or had your funds stolen? Don’t worry Capital Crypto Recover is here to help you recover your cryptocurrency with cutting-edge technical expertise, With years of experience in the crypto world, Capital Crypto Recover employs the best latest tools and ethical hacking techniques to help you recover lost assets, unlock hacked accounts, Whether it’s a forgotten password, Capital Crypto Recover has the expertise to help you get your crypto back. a security company service that has a 100% success rate in the recovery of crypto assets, i lost wallet and hacked accounts. I provided them the information they requested and they began their investigation. To my surprise, Capital Crypto Recover was able to trace and recover my crypto assets successfully within 24hours. Thank you for your service in helping me recover my $647,734 worth of crypto funds and I highly recommend their recovery services, they are reliable and a trusted company to any individuals looking to recover lost money. Contact email [email protected] OR Telegram @Capitalcryptorecover Call/Text Number +1 (336)390-6684 his contact: [email protected] His website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 31.07.26 16:21 rssllhrnsb

    I learned an important lesson after investing in what appeared to be a genuine opportunity. Unfortunately, I was unable to access my funds, which reinforced the importance of carrying out thorough due diligence, checking whether an investment is appropriately regulated, and seeking qualified professional advice rather than relying solely on online reviews or testimonials. During the process of addressing my case, I worked with Mrs. Doris Ashley, who communicated clearly, provided regular updates, and handled the matter in a professional manner. According to my experience, I have recovered $50,000 so far, while efforts to resolve the remaining balance are still in progress. If you wish to contact her, the details I used are: Mrs. Tatiana Sorina
TEXT : (tatianasorina06 at G.Ma IL ..c 0 m ) Before committing money to any investment, take time to verify the legitimacy of the platform, confirm any relevant regulatory authorisations, and avoid investing more than you can comfortably afford to lose.

  • 01.08.26 15:05 keithwilson9899

    ETHEREUM RECOVERY ASSISTANCE: CAPITAL CRYPTO RECOVER HELPED ME RECOVER $98,000 WORTH OF LOST ETH In cases of cryptocurrency scams, having accurate information and trusted support is essential. I would like to recommend Capital Crypto Recover Service, a professional team that specializes in assisting individuals with the recovery of lost or stolen Bitcoin and Ethereum (ETH). Their experienced experts are dedicated to helping victims of digital asset fraud by carefully analyzing each case, developing strategic recovery plans, Capital Crypto Recover Service knowledgeable team's primary goals are to satisfy clients and offer significant support and working diligently toward fund retrieval. The team is committed to providing reliable assistance and maintaining a high level of client satisfaction. Based on my assessment, their reputation professionalism and a strong commitment to their clients. If you have experienced a cryptocurrency loss, you can contacting them for further assistance Phone (Call/Text): +1 (336) 390-6684 Email: [email protected] Alternate Email: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 01.08.26 15:05 keithwilson9899

    ETHEREUM RECOVERY ASSISTANCE: CAPITAL CRYPTO RECOVER HELPED ME RECOVER $98,000 WORTH OF LOST ETH In cases of cryptocurrency scams, having accurate information and trusted support is essential. I would like to recommend Capital Crypto Recover Service, a professional team that specializes in assisting individuals with the recovery of lost or stolen Bitcoin and Ethereum (ETH). Their experienced experts are dedicated to helping victims of digital asset fraud by carefully analyzing each case, developing strategic recovery plans, Capital Crypto Recover Service knowledgeable team's primary goals are to satisfy clients and offer significant support and working diligently toward fund retrieval. The team is committed to providing reliable assistance and maintaining a high level of client satisfaction. Based on my assessment, their reputation professionalism and a strong commitment to their clients. If you have experienced a cryptocurrency loss, you can contacting them for further assistance Phone (Call/Text): +1 (336) 390-6684 Email: [email protected] Alternate Email: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 03.08.26 20:05 Philip

    I was a victim of a crypto theft involving a Pink Drainer, which resulted in the theft of my Wrapped Bitcoin (WBTC) from my Polygon network. The experience was incredibly frustrating and distressing, as I had no idea how to recover my funds. Unfortunately, by the time I noticed the theft, the funds had already been drained to an address that I had no control over, making it seem like an irreversible situation.The attack happened when I clicked on what appeared to be a legitimate link. I didn’t realize at the time that it was a phishing attempt designed to siphon off my private keys and access my wallet. The Pink Drainer, a type of malicious script used by attackers, is specifically designed to exploit such vulnerabilities in crypto wallets. The moment I realized that my WBTC had been drained from my Polygon network. I felt completely helpless, as I didn’t have direct access to the thief's address, and there was no way to reverse the transaction on my own at that point, I started searching for ways to recover my funds, but most resources only offered generic advice that wasn’t practical in this particular case. I quickly realized that if I wanted to have any hope of getting my assets back, I would need professional assistance. After some research, I came across a reliable and trusted recovery team called Aspen Recovery Experts. Their expertise in cryptocurrency recovery, especially in cases like mine, seemed promising.I decided to reach out to Aspen Recovery Experts, and I’m incredibly grateful that I did. Their team of crypto recovery experts was able to help me trace the stolen funds and identify the path the funds took after they left my wallet. Using advanced tools and techniques, they were able to track the transactions on the blockchain, helping me understand where my WBTC had been sent. More importantly, they worked tirelessly to assist me in contacting the necessary parties and even interfaced with blockchain analysts to help facilitate the recovery process.Thanks to their efforts, I was able to successfully recover my stolen funds. The entire process took some time, but the Aspen Recovery Experts dedicated team provided regular updates and kept me informed throughout the process, which gave me a sense of hope and relief during an otherwise stressful time. If you’re ever in a similar situation, I highly recommend reaching out to a trusted recovery team like Aspen Recovery Expertshhh . Their professionalism, knowledge, and expertise were critical in helping me recover my funds and regain control of my crypto assets. Whatsapp : +1 747 231 9036 Telegram : @ prohackerspy Email : [email protected]

  • 03.08.26 20:06 Philip

    I was a victim of a crypto theft involving a Pink Drainer, which resulted in the theft of my Wrapped Bitcoin (WBTC) from my Polygon network. The experience was incredibly frustrating and distressing, as I had no idea how to recover my funds. Unfortunately, by the time I noticed the theft, the funds had already been drained to an address that I had no control over, making it seem like an irreversible situation.The attack happened when I clicked on what appeared to be a legitimate link. I didn’t realize at the time that it was a phishing attempt designed to siphon off my private keys and access my wallet. The Pink Drainer, a type of malicious script used by attackers, is specifically designed to exploit such vulnerabilities in crypto wallets. The moment I realized that my WBTC had been drained from my Polygon network. I felt completely helpless, as I didn’t have direct access to the thief's address, and there was no way to reverse the transaction on my own at that point, I started searching for ways to recover my funds, but most resources only offered generic advice that wasn’t practical in this particular case. I quickly realized that if I wanted to have any hope of getting my assets back, I would need professional assistance. After some research, I came across a reliable and trusted recovery team called Aspen Recovery Experts. Their expertise in cryptocurrency recovery, especially in cases like mine, seemed promising.I decided to reach out to Aspen Recovery Experts, and I’m incredibly grateful that I did. Their team of crypto recovery experts was able to help me trace the stolen funds and identify the path the funds took after they left my wallet. Using advanced tools and techniques, they were able to track the transactions on the blockchain, helping me understand where my WBTC had been sent. More importantly, they worked tirelessly to assist me in contacting the necessary parties and even interfaced with blockchain analysts to help facilitate the recovery process.Thanks to their efforts, I was able to successfully recover my stolen funds. The entire process took some time, but the Aspen Recovery Experts dedicated team provided regular updates and kept me informed throughout the process, which gave me a sense of hope and relief during an otherwise stressful time. If you’re ever in a similar situation, I highly recommend reaching out to a trusted recovery team like Aspen Recovery Expertshhh . Their professionalism, knowledge, and expertise were critical in helping me recover my funds and regain control of my crypto assets. Whatsapp : +1 747 231 9036 Telegram : @ prohackerspy Email : [email protected]

  • 04.08.26 11:05 Kisnoles

    Excellent analysis, thank you. I was particularly struck by how rigidly the skill sets the order: data processing and form come first, with color coming last. This really cures the habit of "first a pretty palette, then we'll figure it out." A palette validator using OKLCH + color blindness + WCAG is something most chart generators lack. And regarding the boundaries of competence, it's very clear. Where there's a self-checking loop (color), you delegate freely. Where there's a heuristic (form, data interpretation), you remain the final filter. This is a universal principle, not just for /dataviz. By the way, when you look at trading dashboards (including those of decent platforms like ExpertOption), it's immediately clear who thought about readability and who just threw in rainbow lines. Tools like this skill could greatly improve the quality of analytics. Thanks again for the detailed analysis – saved.

  • 04.08.26 12:37 kimberlyhebertt6877

    I invested in bitcoin trading After losing $78.4 USDT) linked to a romance fraud scam worth of cryptocurrency through an online investment platform and later discovered it was a scam. After extensive research for recovery options, I contacted CAPITAL CRYPTO RECOVER based on positive client reviews and recommendations. Their professional security team guided me through the recovery process using advanced technology, and I was able to recover my lost cryptocurrency successfully. I am truly grateful for their support and assistance during such a difficult experience. I will advise you to contact CAPITAL CRYPTO RECOVER helped me recover my funds. For anyone facing similar issues, Website: https://recovercapital.wixsite.com/capital-crypto-rec-1 Email: [email protected] Telegram: @Capitalcryptorecover Contact: [email protected] Call/Text Number: +1 (336) 390-6684

  • 04.08.26 12:37 kimberlyhebertt6877

    I invested in bitcoin trading After losing $78.4 USDT) linked to a romance fraud scam worth of cryptocurrency through an online investment platform and later discovered it was a scam. After extensive research for recovery options, I contacted CAPITAL CRYPTO RECOVER based on positive client reviews and recommendations. Their professional security team guided me through the recovery process using advanced technology, and I was able to recover my lost cryptocurrency successfully. I am truly grateful for their support and assistance during such a difficult experience. I will advise you to contact CAPITAL CRYPTO RECOVER helped me recover my funds. For anyone facing similar issues, Website: https://recovercapital.wixsite.com/capital-crypto-rec-1 Email: [email protected] Telegram: @Capitalcryptorecover Contact: [email protected] Call/Text Number: +1 (336) 390-6684

  • 06.08.26 08:11 ROMMYHENDERSON344

    All you need is to hire an expert to help you accomplish that. If there's any need to spy on your partner's phone. From my experience I lacked evidence to confront my husband on my suspicion on his infidelity, until I came across REALCYBERHACKERS which many commend him of assisting them in their spying mission. So I contacted him and he provided me with access into his phone to view all text messages, call logs, WhatsApp messages and even her location. This evidence helped me move him off my life . I recommend you consult REALCYBERHACKERS AT gmail com or whatsapp +14106350697 if you need access to your partner's phone or any kind of hacking, they carry out all kinds of hacking job

  • 06.08.26 13:56 lydiassmith567

    HIRE A HACKER YOUR STOLEN CRYPTO RECOVERY / BTC / USDT / ETH WITH THE HELP OF CAPITAL CRYPTO RECOVER. I want to share my experience publicly regarding cryptocurrency wallet recovery, I highly recommend you to contact CAPITAL CRYPTO RECOVER, a professional private investigator in the Bitcoin world. They have a certified expert security team specializing in Bitcoin Recovery Services and have helped many people worldwide recover their lost funds. My wife and I were defrauded by an online manipulator posing as an experienced crypto investment professional. We lost $9.2 Million Stolen BTC in cryptocurrency and were left feeling homeless. After spending hours searching for a reliable crypto recovery service, I discovered CAPITAL CRYPTO RECOVER online. By patiently explaining my situation to their team, I was able to recover all my funds. Remarkably, my money was returned to my wallet in less than 24 hours. I am extremely grateful to CAPITAL CRYPTO RECOVER for their excellent assistance—they truly were a godsend in my difficult situation. If you have fallen victim to a cryptocurrency scam, you can reach CAPITAL CRYPTO RECOVER through the following channels Email: [email protected] OR Call/Text: +1 (336) 390-6684 Contact: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 06.08.26 13:56 lydiassmith567

    HIRE A HACKER YOUR STOLEN CRYPTO RECOVERY / BTC / USDT / ETH WITH THE HELP OF CAPITAL CRYPTO RECOVER. I want to share my experience publicly regarding cryptocurrency wallet recovery, I highly recommend you to contact CAPITAL CRYPTO RECOVER, a professional private investigator in the Bitcoin world. They have a certified expert security team specializing in Bitcoin Recovery Services and have helped many people worldwide recover their lost funds. My wife and I were defrauded by an online manipulator posing as an experienced crypto investment professional. We lost $9.2 Million Stolen BTC in cryptocurrency and were left feeling homeless. After spending hours searching for a reliable crypto recovery service, I discovered CAPITAL CRYPTO RECOVER online. By patiently explaining my situation to their team, I was able to recover all my funds. Remarkably, my money was returned to my wallet in less than 24 hours. I am extremely grateful to CAPITAL CRYPTO RECOVER for their excellent assistance—they truly were a godsend in my difficult situation. If you have fallen victim to a cryptocurrency scam, you can reach CAPITAL CRYPTO RECOVER through the following channels Email: [email protected] OR Call/Text: +1 (336) 390-6684 Contact: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 11.08.26 03:43 raymont0714

    I recommend a trusted cybersecurity PRO with experience in authorized device access, security testing, and data recovery. SHE work only with the owner's consent and follow all legal and privacy rules on meta data. With permission, this specialist can recover lost files, check device security offline & online, and review texts, call records, or hidden data (spy or lurk around a cheating partner or colleuge). Strict confidentiality agreements protect the information, and client details remain private. The work is careful, efficient, and suited to complex cases. For help securing or recovering information from a phone or another electronic device, use the details below 2 consult [email protected] +44 7476618364 I trust her secrecy a living witness. Her discretion is unmatched, ensuring your privacy is always maintained

  • 12.08.26 16:37 rssllhrnsb

    I learned an important lesson after investing in what appeared to be a genuine opportunity. Unfortunately, I was unable to access my funds, which reinforced the importance of carrying out thorough due diligence, checking whether an investment is appropriately regulated, and seeking qualified professional advice rather than relying solely on online reviews or testimonials. During the process of addressing my case, I worked with Mrs. Tatiana Sorina , who communicated clearly, provided regular updates, and handled the matter in a professional manner. According to my experience, I have recovered $50,000 so far, while efforts to resolve the remaining balance are still in progress. If you wish to contact her, the details I used are: Mrs. Tatiana Sorina
TEXT : (tatianasorina06 at G_Ma IL dot ..c 0 m)…. Before committing money to any investment, take time to verify the legitimacy of the platform, confirm any relevant regulatory authorisations, and avoid investing more than you can comfortably afford to lose.

  • 16.08.26 01:44 Matt Kegan

    CapitalNode Analytics. They help to investigate and recover stolen digital assets from fake trading platforms. Great firm i must say.

  • 18.08.26 04:59 marcushenderson624

    Bitcoin Recovery Testimonial After falling victim to a cryptocurrency scam group, I lost $354,000 worth of USDT. I thought all hope was lost from the experience of losing my hard-earned money to scammers. I was devastated and believed there was no way to recover my funds. Fortunately, I started searching for help to recover my stolen funds and I came across a lot of testimonials online about Capital Crypto Recovery, an agent who helps in recovery of lost bitcoin funds, I contacted Capital Crypto Recover Service, and with their expertise, they successfully traced and recovered my stolen assets. Their team was professional, kept me updated throughout the process, and demonstrated a deep understanding of blockchain transactions and recovery protocols. They are trusted and very reliable with a 100% successful rate record Recovery bitcoin, I’m grateful for their help and highly recommend their services to anyone seeking assistance with lost crypto. Contact: [email protected] Phone WhatsApp/Text Number: +1 (336) 390-6684 Email: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 18.08.26 04:59 marcushenderson624

    Bitcoin Recovery Testimonial After falling victim to a cryptocurrency scam group, I lost $354,000 worth of USDT. I thought all hope was lost from the experience of losing my hard-earned money to scammers. I was devastated and believed there was no way to recover my funds. Fortunately, I started searching for help to recover my stolen funds and I came across a lot of testimonials online about Capital Crypto Recovery, an agent who helps in recovery of lost bitcoin funds, I contacted Capital Crypto Recover Service, and with their expertise, they successfully traced and recovered my stolen assets. Their team was professional, kept me updated throughout the process, and demonstrated a deep understanding of blockchain transactions and recovery protocols. They are trusted and very reliable with a 100% successful rate record Recovery bitcoin, I’m grateful for their help and highly recommend their services to anyone seeking assistance with lost crypto. Contact: [email protected] Phone WhatsApp/Text Number: +1 (336) 390-6684 Email: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 19.08.26 14:50 BAYER7043

    My account was locked, and I couldn’t access my own information until [email protected] +447476618364 whats?up? helped me recover it. This lady hacker was kind and professional, but this experience showed me how risky account lockouts can be. If you’re facing the same problem, do not panic consult her A. S. A. P. Be careful with recovery agents outchea, and research their name, business, and reviews before sharing any details. Never send passwords, security codes, banking information, or copies of your ID unless you’ve confirmed who you’re dealing with. Be wary of promises that sound too good to be true, especially claims that require little information or guarantee instant access with outrageous fees.

  • 20.08.26 11:32 michaeldavenport218

    I was recently scammed out of $53,000 by a fraudulent Bitcoin investment scheme, which added significant stress to my already difficult health issues, as I was also facing cancer surgery expenses. Desperate to recover my funds, I spent hours researching and consulting other victims, which led me to discover the excellent reputation of Capital Crypto Recover, I came across a Google post It was only after spending many hours researching and asking other victims for advice that I discovered Capital Crypto Recovery’s stellar reputation. I decided to contact them because of their successful recovery record and encouraging client testimonials. I had no idea that this would be the pivotal moment in my fight against cryptocurrency theft. Thanks to their expert team, I was able to recover my lost cryptocurrency back. The process was intricate, but Capital Crypto Recovery's commitment to utilizing the latest technology ensured a successful outcome. I highly recommend their services to anyone who has fallen victim to cryptocurrency fraud. For assistance contact [email protected] and on Telegram OR WhatsApp Number +1 (336)390-6684 via email: [email protected] you can visit his website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 20.08.26 11:32 michaeldavenport218

    I was recently scammed out of $53,000 by a fraudulent Bitcoin investment scheme, which added significant stress to my already difficult health issues, as I was also facing cancer surgery expenses. Desperate to recover my funds, I spent hours researching and consulting other victims, which led me to discover the excellent reputation of Capital Crypto Recover, I came across a Google post It was only after spending many hours researching and asking other victims for advice that I discovered Capital Crypto Recovery’s stellar reputation. I decided to contact them because of their successful recovery record and encouraging client testimonials. I had no idea that this would be the pivotal moment in my fight against cryptocurrency theft. Thanks to their expert team, I was able to recover my lost cryptocurrency back. The process was intricate, but Capital Crypto Recovery's commitment to utilizing the latest technology ensured a successful outcome. I highly recommend their services to anyone who has fallen victim to cryptocurrency fraud. For assistance contact [email protected] and on Telegram OR WhatsApp Number +1 (336)390-6684 via email: [email protected] you can visit his website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 23.08.26 20:02 leslieyee

    Excellent analysis by /dataviz! I was particularly struck by how strictly the order is defined: "data meaning first, color last" and how the validator actually adjusts the palette according to OKLCH, color blindness, and WCAG standards. These rules greatly improve the quality of charts. By the way, a similar approach to clean and legible charts is highly valued in trading—for example, on the ExpertOption platform, the interface and price visualization are designed to ensure information is read instantly and without unnecessary noise.

  • 25.08.26 13:44 lydiassmith567

    HIRE A HACKER YOUR STOLEN CRYPTO RECOVERY / BTC / USDT / ETH WITH THE HELP OF CAPITAL CRYPTO RECOVER. I want to share my experience publicly regarding cryptocurrency wallet recovery, I highly recommend you to contact CAPITAL CRYPTO RECOVER, a professional private investigator in the Bitcoin world. They have a certified expert security team specializing in Bitcoin Recovery Services and have helped many people worldwide recover their lost funds. My wife and I were defrauded by an online manipulator posing as an experienced crypto investment professional. We lost $9.2 Million Stolen BTC in cryptocurrency and were left feeling homeless. After spending hours searching for a reliable crypto recovery service, I discovered CAPITAL CRYPTO RECOVER online. By patiently explaining my situation to their team, I was able to recover all my funds. Remarkably, my money was returned to my wallet in less than 24 hours. I am extremely grateful to CAPITAL CRYPTO RECOVER for their excellent assistance—they truly were a godsend in my difficult situation. If you have fallen victim to a cryptocurrency scam, you can reach CAPITAL CRYPTO RECOVER through the following channels Email: [email protected] OR Call/WhatsApp: +1 (336) 390-6684 Contact: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 25.08.26 13:44 lydiassmith567

    HIRE A HACKER YOUR STOLEN CRYPTO RECOVERY / BTC / USDT / ETH WITH THE HELP OF CAPITAL CRYPTO RECOVER. I want to share my experience publicly regarding cryptocurrency wallet recovery, I highly recommend you to contact CAPITAL CRYPTO RECOVER, a professional private investigator in the Bitcoin world. They have a certified expert security team specializing in Bitcoin Recovery Services and have helped many people worldwide recover their lost funds. My wife and I were defrauded by an online manipulator posing as an experienced crypto investment professional. We lost $9.2 Million Stolen BTC in cryptocurrency and were left feeling homeless. After spending hours searching for a reliable crypto recovery service, I discovered CAPITAL CRYPTO RECOVER online. By patiently explaining my situation to their team, I was able to recover all my funds. Remarkably, my money was returned to my wallet in less than 24 hours. I am extremely grateful to CAPITAL CRYPTO RECOVER for their excellent assistance—they truly were a godsend in my difficult situation. If you have fallen victim to a cryptocurrency scam, you can reach CAPITAL CRYPTO RECOVER through the following channels Email: [email protected] OR Call/WhatsApp: +1 (336) 390-6684 Contact: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 30.08.26 15:37 [email protected]

    Discovering I had been defrauded, I searched online for services claiming to recover stolen ETH. I contacted several companies, but none succeeded. Although some claimed they could trace assets or recover funds, I could not verify their success or recover my money. I later found SYLVESTER BRYANT Recovery through Google. After contacting him, he recovered 450,000,00. He can be reached at [email protected] or WhatsApp at +1 512 577 7957.

  • 30.08.26 15:37 [email protected]

    Discovering I had been defrauded, I searched online for services claiming to recover stolen ETH. I contacted several companies, but none succeeded. Although some claimed they could trace assets or recover funds, I could not verify their success or recover my money. I later found SYLVESTER BRYANT Recovery through Google. After contacting him, he recovered 450,000,00. He can be reached at [email protected] or WhatsApp at +1 512 577 7957.

  • 01.09.26 11:16 lisawerth897

    My Experience With Cryptocurrency — CAPITAL CRYPTO RECOVER I lost over $82,000 in Bitcoin after falling victim to a fraudulent online investment scheme. After realizing I had been scammed, I spent considerable time researching possible ways to recover my funds. During my research, I came across CAPITAL CRYPTO RECOVER and decided to contact them after reading their reported success record and encouraging client positive reviews and testimonials. Their team guided me through the recovery process, and I was grateful to successfully recover my lost cryptocurrency. The experience was challenging, but I appreciated the assistance and support I received throughout the process. I’m sharing my experience in the hope that it may encourage other victims to carefully research their options and verify any recovery service before proceeding. I will forever be thankful to you CAPITAL CRYPTO RECOVER 📧 [email protected] 🌐 Website: recovercapital.wixsite.com/capital-crypto-rec-1 📧 [email protected] 📞 Call/WhatsApp: +1 (336) 390-6684

  • 01.09.26 11:16 lisawerth897

    My Experience With Cryptocurrency — CAPITAL CRYPTO RECOVER I lost over $82,000 in Bitcoin after falling victim to a fraudulent online investment scheme. After realizing I had been scammed, I spent considerable time researching possible ways to recover my funds. During my research, I came across CAPITAL CRYPTO RECOVER and decided to contact them after reading their reported success record and encouraging client positive reviews and testimonials. Their team guided me through the recovery process, and I was grateful to successfully recover my lost cryptocurrency. The experience was challenging, but I appreciated the assistance and support I received throughout the process. I’m sharing my experience in the hope that it may encourage other victims to carefully research their options and verify any recovery service before proceeding. I will forever be thankful to you CAPITAL CRYPTO RECOVER 📧 [email protected] 🌐 Website: recovercapital.wixsite.com/capital-crypto-rec-1 📧 [email protected] 📞 Call/WhatsApp: +1 (336) 390-6684

  • 01.09.26 17:34 Garry42

    Really crazy world. These fraudsters go at any length to steal your hard earned funds. I have been a victim of a bitcoin scam about 7 months back. A Con artist gained access to my cashapp account through a phishing scam. They stole $409,000. I was really devastated. I did everything to get back my funds by contacting the FBI but they claimed there was nothing they could do. A friend told me about a recovery expert. He helps fight against various phishing and investment scams and they were able to help trace and recover my funds even though it took over 2 days. you can reach out to him through recoverydarek@gmail. com . I can guarantee his services are still active.

  • 01.09.26 17:34 Garry42

    Really crazy world. These fraudsters go at any length to steal your hard earned funds. I have been a victim of a bitcoin scam about 7 months back. A Con artist gained access to my cashapp account through a phishing scam. They stole $409,000. I was really devastated. I did everything to get back my funds by contacting the FBI but they claimed there was nothing they could do. A friend told me about a recovery expert. He helps fight against various phishing and investment scams and they were able to help trace and recover my funds even though it took over 2 days. you can reach out to him through recoverydarek@gmail. com . I can guarantee his services are still active.

  • 02.09.26 03:43 kimberlyhebertt6877

    I invested in bitcoin trading After losing $78.4 USDT) linked to a romance fraud scam worth of cryptocurrency through an online investment platform and later discovered it was a scam. After extensive research for recovery options, I contacted CAPITAL CRYPTO RECOVER based on positive client reviews and recommendations. Their professional security team guided me through the recovery process using advanced technology, and I was able to recover my lost cryptocurrency successfully. I am truly grateful for their support and assistance during such a difficult experience. I will advise you to contact CAPITAL CRYPTO RECOVER helped me recover my funds. For anyone facing similar issues, Website: https://recovercapital.wixsite.com/capital-crypto-rec-1 Email: [email protected] Telegram: @Capitalcryptorecover Contact: [email protected] WhatsApp/Text Number: +1 (336) 390-6684

  • 02.09.26 03:43 kimberlyhebertt6877

    I invested in bitcoin trading After losing $78.4 USDT) linked to a romance fraud scam worth of cryptocurrency through an online investment platform and later discovered it was a scam. After extensive research for recovery options, I contacted CAPITAL CRYPTO RECOVER based on positive client reviews and recommendations. Their professional security team guided me through the recovery process using advanced technology, and I was able to recover my lost cryptocurrency successfully. I am truly grateful for their support and assistance during such a difficult experience. I will advise you to contact CAPITAL CRYPTO RECOVER helped me recover my funds. For anyone facing similar issues, Website: https://recovercapital.wixsite.com/capital-crypto-rec-1 Email: [email protected] Telegram: @Capitalcryptorecover Contact: [email protected] WhatsApp/Text Number: +1 (336) 390-6684

  • 04.09.26 21:51 Kovengray

    Recovering your lost investment funds as the case might be, is not what you can do alone, you’d require the service of a trained recovery specialist. A recovery specialist is a person or a group of people who are well equipped to work around the brokerage network. They have vast knowledge about the whole network and have the right software and private keys to follow any transaction. I was ripped off trading online to an investment broker, good thing I got every penny back through the help of Gavin ray he’s a genius Contact : Gavinray78 at gmail com or WhatsApp +1 352 322 2096 It is also important to be patient and really calm during the process.

  • 05.09.26 21:38 [email protected]

    I invested 45,000  Euro, and later aggreviated to 198,000 Euro.  I requested  to place ‎Withdrawal of my funds to be paid to my Bank account. But nothing happened I was subjected to pay more fee until I can get my funds on my account, i got in touch with Theodore ryan here on this platform who had helped a lot of people, I followed all instructions and he legally got back my withheld funds, I got threatened by the company that if I don’t pay they will get my account frozen, all thanks to Theodore ryan and I highly recommend him to anyone dealing with an unregulated broker company… contact him on his Gmail - theodoreryan318@  gmail .  com

  • 09.09.26 21:31 lisawerth897

    My Experience With Cryptocurrency — CAPITAL CRYPTO RECOVER I lost over $82,000 in Bitcoin after falling victim to a fraudulent online investment scheme. After realizing I had been scammed, I spent considerable time researching possible ways to recover my funds. During my research, I came across CAPITAL CRYPTO RECOVER and decided to contact them after reading their reported success record and encouraging client positive reviews and testimonials. Their team guided me through the recovery process, and I was grateful to successfully recover my lost cryptocurrency. The experience was challenging, but I appreciated the assistance and support I received throughout the process. I’m sharing my experience in the hope that it may encourage other victims to carefully research their options and verify any recovery service before proceeding. I will forever be thankful to you CAPITAL CRYPTO RECOVER 📧 [email protected] 🌐 Website: recovercapital.wixsite.com/capital-crypto-rec-1 📧 [email protected] 📞 Call/WhatsApp: +1 (336) 390-6684

  • 09.09.26 21:31 lisawerth897

    My Experience With Cryptocurrency — CAPITAL CRYPTO RECOVER I lost over $82,000 in Bitcoin after falling victim to a fraudulent online investment scheme. After realizing I had been scammed, I spent considerable time researching possible ways to recover my funds. During my research, I came across CAPITAL CRYPTO RECOVER and decided to contact them after reading their reported success record and encouraging client positive reviews and testimonials. Their team guided me through the recovery process, and I was grateful to successfully recover my lost cryptocurrency. The experience was challenging, but I appreciated the assistance and support I received throughout the process. I’m sharing my experience in the hope that it may encourage other victims to carefully research their options and verify any recovery service before proceeding. I will forever be thankful to you CAPITAL CRYPTO RECOVER 📧 [email protected] 🌐 Website: recovercapital.wixsite.com/capital-crypto-rec-1 📧 [email protected] 📞 Call/WhatsApp: +1 (336) 390-6684

  • 09.09.26 23:22 Fraddy Pual

    Hearing that these individuals are focusing on other people makes me very sad. I had a similar situation with them and lost a lot of money, but after learning about (Cruxcipherteam @ proton DoT me), whataqq:+168160-15021, telegram: @Cruxcipherteam I was able to get my money back. It's critical that we all be watchful and keep reporting these occurrences.

  • 11.09.26 03:23 kimberlyhebertt6877

    I invested in bitcoin trading After losing $78.4 USDT) linked to a romance fraud scam worth of cryptocurrency through an online investment platform and later discovered it was a scam. After extensive research for recovery options, I contacted CAPITAL CRYPTO RECOVER based on positive client reviews and recommendations. Their professional security team guided me through the recovery process using advanced technology, and I was able to recover my lost cryptocurrency successfully. I am truly grateful for their support and assistance during such a difficult experience. I will advise you to contact CAPITAL CRYPTO RECOVER helped me recover my funds. For anyone facing similar issues, Website: https://recovercapital.wixsite.com/capital-crypto-rec-1 Email: [email protected] Telegram: @Capitalcryptorecover Contact: [email protected] WhatsApp/Text Number: +1 (336) 390-6684

  • 11.09.26 03:23 kimberlyhebertt6877

    I invested in bitcoin trading After losing $78.4 USDT) linked to a romance fraud scam worth of cryptocurrency through an online investment platform and later discovered it was a scam. After extensive research for recovery options, I contacted CAPITAL CRYPTO RECOVER based on positive client reviews and recommendations. Their professional security team guided me through the recovery process using advanced technology, and I was able to recover my lost cryptocurrency successfully. I am truly grateful for their support and assistance during such a difficult experience. I will advise you to contact CAPITAL CRYPTO RECOVER helped me recover my funds. For anyone facing similar issues, Website: https://recovercapital.wixsite.com/capital-crypto-rec-1 Email: [email protected] Telegram: @Capitalcryptorecover Contact: [email protected] WhatsApp/Text Number: +1 (336) 390-6684

  • 15.09.26 03:35 elioduncan

    I fell victim to a freelance web development contract scam that ultimately cost me CAD 4,000. It all started when I came across a job posting on Indeed Canada, a popular online job portal. The position seemed perfect: a freelance web developer role for an established company looking for someone to design and build a fully functional e-commerce website. The job promised a high payment of CAD 20,000 upon successful completion, which sounded like a great opportunity for me to gain experience and earn decent pay.The employer, who introduced himself as a project manager from a "well-known" tech company, was very persuasive and professional in our initial communication. He explained that the project involved developing a user-friendly, responsive online store for their client and that they had a strict timeline to meet. He assured me that the payment would be made promptly after completing the tasks. However, before starting the work, he told me that I would need to pay an upfront fee of CAD 4,000 to cover certain software tools and licensing fees required for the project. This was supposedly a part of their company policy for freelance contractors, ensuring access to their premium resources. The idea of working on a professional project, coupled with the promise of a substantial payout, convinced me to pay the upfront fee.As soon as I made the payment, the project manager became increasingly difficult to reach. He initially responded to my emails and provided some vague instructions on what the project would entail, but as time went on, communication slowed to a complete halt. I never received the required tools or any proper project details. My emails went unanswered, and any attempts to contact the company were met with silence. After waiting for weeks, I realized that I had been scammed.Desperate to recover my money, I turned to TechY Force Cyber Retrieval, a service that specializes in helping victims of online scams. They helped me track the payment and took legal steps to pursue the fraudsters. Through their guidance, I was able to recover the full CAD 4,000. TechY Force Cyber Retrieval worked with my bank to reverse the transaction and liaised with the authorities to trace the scammer's details.This was a hard lesson, and I now know to be highly cautious about online job offers that require upfront payments. It is crucial to research companies thoroughly and avoid any job that seems too good to be true, especially when it involves paying money upfront. WhatsApp https://wa.link/2x6ktp Mail. [email protected]

  • 15.09.26 15:45 lydiassmith567

    HIRE A HACKER YOUR STOLEN CRYPTO RECOVERY / BTC / USDT / ETH WITH THE HELP OF CAPITAL CRYPTO RECOVER. I want to share my experience publicly regarding cryptocurrency wallet recovery, I highly recommend you to contact CAPITAL CRYPTO RECOVER, a professional private investigator in the Bitcoin world. They have a certified expert security team specializing in Bitcoin Recovery Services and have helped many people worldwide recover their lost funds. My wife and I were defrauded by an online manipulator posing as an experienced crypto investment professional. We lost $9.2 Million Stolen BTC in cryptocurrency and were left feeling homeless. After spending hours searching for a reliable crypto recovery service, I discovered CAPITAL CRYPTO RECOVER online. By patiently explaining my situation to their team, I was able to recover all my funds. Remarkably, my money was returned to my wallet in less than 24 hours. I am extremely grateful to CAPITAL CRYPTO RECOVER for their excellent assistance—they truly were a godsend in my difficult situation. If you have fallen victim to a cryptocurrency scam, you can reach CAPITAL CRYPTO RECOVER through the following channels Email: [email protected] OR Call/WhatsApp: +1 (336) 390-6684 Contact: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 15.09.26 15:45 lydiassmith567

    HIRE A HACKER YOUR STOLEN CRYPTO RECOVERY / BTC / USDT / ETH WITH THE HELP OF CAPITAL CRYPTO RECOVER. I want to share my experience publicly regarding cryptocurrency wallet recovery, I highly recommend you to contact CAPITAL CRYPTO RECOVER, a professional private investigator in the Bitcoin world. They have a certified expert security team specializing in Bitcoin Recovery Services and have helped many people worldwide recover their lost funds. My wife and I were defrauded by an online manipulator posing as an experienced crypto investment professional. We lost $9.2 Million Stolen BTC in cryptocurrency and were left feeling homeless. After spending hours searching for a reliable crypto recovery service, I discovered CAPITAL CRYPTO RECOVER online. By patiently explaining my situation to their team, I was able to recover all my funds. Remarkably, my money was returned to my wallet in less than 24 hours. I am extremely grateful to CAPITAL CRYPTO RECOVER for their excellent assistance—they truly were a godsend in my difficult situation. If you have fallen victim to a cryptocurrency scam, you can reach CAPITAL CRYPTO RECOVER through the following channels Email: [email protected] OR Call/WhatsApp: +1 (336) 390-6684 Contact: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 05.10.26 21:26 [email protected]

    I Invested 45,000  Euro, and later aggreviated to 198,000 Euro.  I requested  to place Withdrawal of my funds to be paid to my Bank account. But nothing happened I was subjected to pay more fee until I can get my funds on my account, i got in touch with Theodore ryan here on this platform who had helped a lot of people, I followed all instructions and he legally got back my withheld funds, I got threatened by the company that if I don’t pay they will get my account frozen, all thanks to Theodore ryan and I highly recommend him to anyone dealing with an unregulated broker company… contact him on his Gmail - theodoreryan318@  gmail .  com

  • 06.10.26 06:13 melissathomason573

    Professional Crypto Recovery Testimonial After losing a significant amount of cryptocurrency through a scam on Twitter, I was unsure whether my Bitcoin funds could ever be recovered. The experience was extremely stressful, particularly because I encountered several individuals and services claiming they could help recover my assets, only to discover that some were fraudulent themselves. A friend eventually referred me to CAPITAL CRYPTO RECOVER. Their team handled my case professionally, discreetly, and efficiently. With their assistance, I successfully recovered approximately $130,000 worth of USDT in just over a week. My experience taught me the importance of being extremely cautious when seeking cryptocurrency recovery services, as scammers often target victims a second time. I am grateful to CAPITAL CRYPTO RECOVER for the assistance I received and wanted to share my experience with others who may be facing a similar situation. If you’ve been compromised, don’t lose hope—and be careful of fraudsters posing as saviors. CAPITAL CRYPTO RECOVER are real professionals in crypto recovery. I am a living proof of their effectiveness, you can reach them through Phone (Call/WhatsApp): +1 (336) 390-6684 Email: [email protected] Alternate Email: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 06.10.26 06:13 melissathomason573

    Professional Crypto Recovery Testimonial After losing a significant amount of cryptocurrency through a scam on Twitter, I was unsure whether my Bitcoin funds could ever be recovered. The experience was extremely stressful, particularly because I encountered several individuals and services claiming they could help recover my assets, only to discover that some were fraudulent themselves. A friend eventually referred me to CAPITAL CRYPTO RECOVER. Their team handled my case professionally, discreetly, and efficiently. With their assistance, I successfully recovered approximately $130,000 worth of USDT in just over a week. My experience taught me the importance of being extremely cautious when seeking cryptocurrency recovery services, as scammers often target victims a second time. I am grateful to CAPITAL CRYPTO RECOVER for the assistance I received and wanted to share my experience with others who may be facing a similar situation. If you’ve been compromised, don’t lose hope—and be careful of fraudsters posing as saviors. CAPITAL CRYPTO RECOVER are real professionals in crypto recovery. I am a living proof of their effectiveness, you can reach them through Phone (Call/WhatsApp): +1 (336) 390-6684 Email: [email protected] Alternate Email: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 06.10.26 17:44 Darrell1717

    I fell victim of a scam I invested to total of 5thousand into. I know it could be risky but after I trusted this man I was dating online he lured me into investing with him. I know hard I worked of my money so I wanted every way possible to get it back. I really appreciate Blockrecoveryexpert for successfully recovered my funds form this scammer. This is all I can say. You can reachout if you ever need help contact blockrecoveryexpert (at) gmail (dot)com

  • 11.10.26 03:04 Patricia014

    Most recovery companies will take your money again. I personally think they’re the same set of people running all of them. It’s a sad, unending cycle, and so many people fall for cloned websites. The only recovery agent I know is Agent Rottgen Raphael. I’ve visited their physical company address, and they’ll tell you if they can handle your case instead of taking upfront payment when they know they can’t help. I highly appreciate their support in recovering my money from the scammers’ wallet within 24 hours. RottgenRaphael@Gmail. com

  • 12.10.26 19:34 lydiassmith567

    HIRE A HACKER YOUR STOLEN CRYPTO RECOVERY / BTC / USDT / ETH WITH THE HELP OF CAPITAL CRYPTO RECOVER. I want to share my experience publicly regarding cryptocurrency wallet recovery, I highly recommend you to contact CAPITAL CRYPTO RECOVER, a professional private investigator in the Bitcoin world. They have a certified expert security team specializing in Bitcoin Recovery Services and have helped many people worldwide recover their lost funds. My wife and I were defrauded by an online manipulator posing as an experienced crypto investment professional. We lost $9.2 Million Stolen BTC in cryptocurrency and were left feeling homeless. After spending hours searching for a reliable crypto recovery service, I discovered CAPITAL CRYPTO RECOVER online. By patiently explaining my situation to their team, I was able to recover all my funds. Remarkably, my money was returned to my wallet in less than 24 hours. I am extremely grateful to CAPITAL CRYPTO RECOVER for their excellent assistance—they truly were a godsend in my difficult situation. If you have fallen victim to a cryptocurrency scam, you can reach CAPITAL CRYPTO RECOVER through the following channels Email: [email protected] OR Call/WhatsApp: +1 (336) 390-6684 Contact: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 12.10.26 19:34 lydiassmith567

    HIRE A HACKER YOUR STOLEN CRYPTO RECOVERY / BTC / USDT / ETH WITH THE HELP OF CAPITAL CRYPTO RECOVER. I want to share my experience publicly regarding cryptocurrency wallet recovery, I highly recommend you to contact CAPITAL CRYPTO RECOVER, a professional private investigator in the Bitcoin world. They have a certified expert security team specializing in Bitcoin Recovery Services and have helped many people worldwide recover their lost funds. My wife and I were defrauded by an online manipulator posing as an experienced crypto investment professional. We lost $9.2 Million Stolen BTC in cryptocurrency and were left feeling homeless. After spending hours searching for a reliable crypto recovery service, I discovered CAPITAL CRYPTO RECOVER online. By patiently explaining my situation to their team, I was able to recover all my funds. Remarkably, my money was returned to my wallet in less than 24 hours. I am extremely grateful to CAPITAL CRYPTO RECOVER for their excellent assistance—they truly were a godsend in my difficult situation. If you have fallen victim to a cryptocurrency scam, you can reach CAPITAL CRYPTO RECOVER through the following channels Email: [email protected] OR Call/WhatsApp: +1 (336) 390-6684 Contact: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 13.10.26 14:14 lisalisa22

    Legitimate Cryptocurrency Recovery Services: Consult ZEUS CRYPTO RECOVERY SERVICES Zeus Crypto Recovery Services supports individuals exploring options after cryptocurrency losses from fraud, unauthorised transfers, or wallet access issues. Services may include blockchain analysis, wallet tracing, and evidence gathering for investigations. Each case is reviewed. Zeus Crypto Recovery Services is a trusted company with the highest success rate among cryptocurrency recovery services in 2025/2026 and is highly recommended if you have been scammed. ZEUS CRYPTO RECOVERY SERVICES handles case such as Scammed USDT Lost BTC, Coinbase Wallet Forgotten password, ETH and coins, etc. Anything that has to do with cryptocurrency or lost digital assets, ZEUS can help you Do you Need Help? Website https://zeusrecoveryservices.com Mailbox [email protected] WhatsApp number +447798816938 Telegram https://t.me/Zeuscryptorecoveryservices

  • 13.10.26 14:14 lisalisa22

    Legitimate Cryptocurrency Recovery Services: Consult ZEUS CRYPTO RECOVERY SERVICES Zeus Crypto Recovery Services supports individuals exploring options after cryptocurrency losses from fraud, unauthorised transfers, or wallet access issues. Services may include blockchain analysis, wallet tracing, and evidence gathering for investigations. Each case is reviewed. Zeus Crypto Recovery Services is a trusted company with the highest success rate among cryptocurrency recovery services in 2025/2026 and is highly recommended if you have been scammed. ZEUS CRYPTO RECOVERY SERVICES handles case such as Scammed USDT Lost BTC, Coinbase Wallet Forgotten password, ETH and coins, etc. Anything that has to do with cryptocurrency or lost digital assets, ZEUS can help you Do you Need Help? Website https://zeusrecoveryservices.com Mailbox [email protected] WhatsApp number +447798816938 Telegram https://t.me/Zeuscryptorecoveryservices

  • 17.10.26 12:58 melissathomason573

    Professional Crypto Recovery Testimonial After losing a significant amount of cryptocurrency through a scam on Twitter, I was unsure whether my Bitcoin funds could ever be recovered. The experience was extremely stressful, particularly because I encountered several individuals and services claiming they could help recover my assets, only to discover that some were fraudulent themselves. A friend eventually referred me to CAPITAL CRYPTO RECOVER. Their team handled my case professionally, discreetly, and efficiently. With their assistance, I successfully recovered approximately $130,000 worth of USDT in just over a week. My experience taught me the importance of being extremely cautious when seeking cryptocurrency recovery services, as scammers often target victims a second time. I am grateful to CAPITAL CRYPTO RECOVER for the assistance I received and wanted to share my experience with others who may be facing a similar situation. If you’ve been compromised, don’t lose hope—and be careful of fraudsters posing as saviors. CAPITAL CRYPTO RECOVER are real professionals in crypto recovery. I am a living proof of their effectiveness, you can reach them through Phone (Call/WhatsApp): +1 (336) 390-6684 Email: [email protected] Alternate Email: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 17.10.26 12:58 melissathomason573

    Professional Crypto Recovery Testimonial After losing a significant amount of cryptocurrency through a scam on Twitter, I was unsure whether my Bitcoin funds could ever be recovered. The experience was extremely stressful, particularly because I encountered several individuals and services claiming they could help recover my assets, only to discover that some were fraudulent themselves. A friend eventually referred me to CAPITAL CRYPTO RECOVER. Their team handled my case professionally, discreetly, and efficiently. With their assistance, I successfully recovered approximately $130,000 worth of USDT in just over a week. My experience taught me the importance of being extremely cautious when seeking cryptocurrency recovery services, as scammers often target victims a second time. I am grateful to CAPITAL CRYPTO RECOVER for the assistance I received and wanted to share my experience with others who may be facing a similar situation. If you’ve been compromised, don’t lose hope—and be careful of fraudsters posing as saviors. CAPITAL CRYPTO RECOVER are real professionals in crypto recovery. I am a living proof of their effectiveness, you can reach them through Phone (Call/WhatsApp): +1 (336) 390-6684 Email: [email protected] Alternate Email: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 03:16 lxbfYeaa

    e

  • 03:16 lxbfYeaa

    e

  • 03:16 lxbfYeaa

    e

  • 03:16 lxbfYeaa

    e

  • 03:23 lxbfYeaa

    e

  • 03:23 lxbfYeaa

    e

  • 03:24 lxbfYeaa

    e

Для участия в Чате вам необходим бесплатный аккаунт pro-blockchain.com Войти Регистрация
Есть вопросы?
С вами на связи 24/7
Help Icon