Я попробовал создать вселенную. Ночью, лежа в кровати, думая о том, что могло бы стать ее фундаментальной основой. Самым базовым кирпичиком, так что бы проще уже некуда. Может ли базовый строительный элемент быть онтологически сложным? Скажем, Теория Струн и ее развитие М‑теория, постулируют, что базовым кирпичиком лежащим в основе мироздания, является многомерная брана. Неужели все должно быть так сложно?

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

Бра́на (от мембрана) в теории струн (М-теории) — гипотетический фундаментальный многомерный физический объект размерности меньшей, чем размерность пространства, в котором он находится…

Пространство в котором он находится?.. Стоп! У нас уже возникла онтологическая сложность - пространство, как контейнер. Откуда оно взялось?.. Ладно, допустим, есть и пространство-контейнер. Тогда что более фундаментально? Брана? Пространство? А как брана помещается в пространство и как вообще такое отношение — объект(!) помещенный в контейнер(!)  — можно редуцировать до базовых свойств, которые еще даже не определены?

Супруга, на вопрос “что на твой взгляд могло бы стать базовым кирпичиком”, после некоторых раздумий тоже предположила, что это могло бы быть пространство. Но пространство тоже бывает разное. Как минимум оно имеет характеристику, описываемое его метрикой. А уж возможность помещать объекты в это самое пространство, назвать тривиальным свойством — уж простите!..

Глава 1. Введение: За пределами объектов и свойств

1.1. На стыке миров

Традиционный способ описывать Вселенную основан на объектах, обладающих свойствами. Электрон имеет массу, заряд и спин. Стол имеет длину, ширину и цвет. Кажется, что реальность состоит из таких «кирпичиков». Однако квантовая механика, самая точная из существующих научных теорий, рисует иную картину.

Хрестоматийный пример — феномен квантовой запутанности. Две частицы, рожденные вместе и разлетевшиеся на световые годы, ведут себя как единое целое. Измерение свойства одной (например, спина) мгновенно находит свое отражение в свойстве другой. Альберт Эйнштейн назвал это «жутким действием на расстоянии».

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

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

Если исходить из этого, тогда мы должны были бы наблюдать свойства по-отдельности, чего в реальности не происходит. Мы не наблюдаем спин в отрыве от кортежа всех остальных свойств — всегда соблюдается их, простите за терминологическую вольность, «конфайнмент».

Строго говоря, мы и не можем наблюдать свойства по-отдельности; они всегда идут приложением друг к другу. Например, для фотона мы измеряем спин поляризации или поляризацию спина. Мы не можем измерить «просто спин» сам по себе, как не можем сказать, что нечто является «мягким» безотносительно того, что именно является мягким. Это всегда взаимно обусловленные аспекты единого целого.

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

Этот взгляд находит отклик в реляционной интерпретации квантовой механики (Карло Ровелли, 1996), где свойства системы также определяются не абсолютно, а только через её отношения с другими системами. Однако мы делаем следующий шаг: постулируем, что эти отношения (Корреляционные Паттерны) являются не просто эпистемологическим инструментом описания, но онтологически первичной тканью реальности, существующей до и независимо от акта измерения.

…Ну хорошо, если я хочу построить вселенную из базовых единиц, с чего начать то? Радикальный редукционизм твой выход. Начну с «ничто». С фундаментального «ничто». Такого, которое экзистенциальное, атеистическое  «ничто», возникающее только после смерти.

А что дальше? А дальше, что бы оно ни было, станет полным противопоставлением «ничто», поскольку как только возникает «нечто», «ничто» перестает существовать.

Минимальный квант смысла, возникающий в самом сердце «ничто» и тут же отрицающий его  — Relationis Fundamentum…

1.2. Атомы смысла: онтологический фундамент

Интуитивный выход из запутанности

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

Однако это не просто «связь» в привычном смысле, ведь физическая связь может прерваться, изменить характер или вовсе исчезнуть. Здесь же мы имеем дело с чем-то иным: с фундаментально сохраняющейся корреляцией, которая остается неизменной, несмотря на все расстояния и временные интервалы.

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

Эта мысль ключевая. После того как измеряется первая частица, исход второй оказывается уже строго определён. Но важно: это не распространение какой-то «волны статистики» или сигнала. Исходы не вычисляются и не предугадываются во времени — они согласованы заранее. В этой интерпретации заранее согласованность не означает, что каждая частица «несёт» внутри себя предзаданное скрытое значение. Напротив, само значение спина нельзя приписать отдельной частице до измерения. Оно хранится не в ней самой, а в общей сети связей, в окружении, в котором рождается пара. То есть параметры состояния как бы «распределены» в контексте, а не локализованы в носителях.

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

Но что именно определено заранее? Не само числовое значение, ведь до измерения вторая частица ещё не «выбрала» конкретный результат. Заранее определена только статистика исхода — редуцированная до бинарного выбора вероятность.

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

Эта идея фундаментальна и минимальна: придумать что-либо проще бинарного отношения («0» или «1», «да» или «нет») практически невозможно. Она является не частным свойством системы, а первичной формой согласованности. Особенность такой идеи в том, что до момента измерения она способна сохранять себя без изменений. Она существует как чистый коррелят — возможность, распределенная между двумя носителями. Лишь акт измерения актуализирует её, сводя к конкретному результату. В этот момент идея реализуется полностью, переходя из потенциального состояния в фактическое.

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

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

Фундаментальность

Может возникнуть ощущение, что мы тут занимаемся пустой болтологией, играем словами и смыслами, мимикрируя под античную натурфилософию. Я совершенно волюнтаристски, под маской некой интуитивности, выше утверждаю, что на фундаментальном уровне вселенная обязана быть онтологически простой. Струны-браны, дескать, штуки сложные, а по сему на роль кирпичиков вселенной не годятся. Особенно спекулятивно выглядит постулирование неких «атомов смысла».

Я приведу три довода в пользу такого подхода.

Во-первых, эпистемологически подобные шаги присутствуют во всех фундаментальных теориях, включая и теорию струн: любая онтология начинает с некоторого выбора «кирпичика» реальности. Разница лишь в том, что на струнах (или бранах) остановились, а мы идём дальше, к еще более простому уровню.

Во-вторых, методологически постулирование атомов смысла не произвольно: оно выведено из феномена квантовой запутанности. Может пока это и не так очевидно, но по мере дальнейшей формализации их вывод будет выглядеть более естественно. Запутанное состояние уже демонстрирует факт существования устойчивой бинарной корреляции, которая не зависит ни от расстояния, ни от времени. Атом смысла есть лишь абстрагирование этого наблюдаемого свойства в онтологический принцип. Этот подход находит поддержку в QBism (Квантовом Байесианизме) Кристофера Фукса (Fuchs, 2010), который также смещает акцент с объективных свойств системы на отношения, в нашем случае — на фундаментальные бинарные корреляции. В то время как QBism интерпретирует вероятности как степень уверенности агента, наша модель предлагает онтологическое обоснование для самих этих отношений в виде атомов смысла.

В-третьих, это стратегия прямой «онтологической простоты». Если мы ищем минимальный кирпич реальности, он обязан быть проще, чем струна или брана. Бинарная связь (0/1) — это крайний предел упрощения. Всё сложное должно быть построено из таких простейших бинарных узлов.

Таким образом, постулирование атомов смысла — не метафизическая игра, а продолжение стандартного пути физики: от сложного к простому, от наблюдаемого к фундаментальному. Этот подход следует принципу онтологической простоты, аналогичному бритве Оккама, и эхом отзывается в Causal Set Theory Рафаэля Соркина (Sorkin, 2005), где пространство-время эмерджируют из дискретных причинных отношений.

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

Атомы смысла принципиально иного рода. Они задают не объект и не свойство, а минимальное отношение — бинарную корреляцию. Иными словами, они фиксируют не то, что «есть», а то, как связано. Такая перспектива близка реляционистским подходам в философии физики (relationism), где фундаментальными признаются пространственно-временные и причинные отношения, а также современным сетевым онтологиям (network ontology), в которых мир описывается как система узлов и связей. Однако отличие атомов смысла в том, что они трактуются не как абстрактные математические связи, а как онтологически реальные минимальные элементы структуры реальности, через которые впоследствии я попробую развернуть и объекты, и их свойства.

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

…Окей, у меня в руках какая-то бинарная штука. Что я могу с ней сделать? В нашей вселенной одной переменной пока даже нет математики, чтобы с ней вступать в какие-то операбельные отношения. А мне надо из этой неопределившейся булевой переменной построить целую вселенную.

Из очевидного, это акт какого-то первичного взаимодействия с ней, буквально воспринять ее. То есть определить ее значение. И в этот момент происходит удивительная вещь у нас появляется эмерджентное усложнение: первое значение, второе и суперпозиция!…

Глава 2. Язык корреляций

Атом смысла — это ключевая идея модели.
Он представляет собой минимальную, фундаментальную единицу реальности, которая несет не объект и не свойство, а минимальное различие. Более строго: атом смысла — это бинарная корреляция. Он существует только как отношение между двумя носителями и выражает идею противоположности: если у одного значение «0», то у другого обязательно «1».

Таким образом, атом смысла — это не «частица», а элементарная связь, минимальная кванта упорядоченной информации о мире. Минимальный смысл.

Теперь, когда интуитивная картина ясна, давайте подкрепим ее минимальной математикой.

2.1 Пространство всех возможных свойств

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

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

H=\bigotimes_{k} H_k

Каждое подпространствоH_kсоответствует отдельному возможному свойству (например, «спин», «заряд», «цвет» и т.д.). Узлы сети будут не «вещами», а носителями атрибутов, которые могут актуализироваться из этого пространства.

2.2 Формальное определение атома смысла

Пусть s — это бинарная переменная, принимающая значения

s\in0,1

Эта запись фиксирует сам факт минимального выбора: «да» или «нет», «0» или «1». Но важно: атом смысла никогда не существует сам по себе. Он проявляется только в составе контекста — то есть как элемент различения внутри некоторой совокупности атомов. Его „носитель“ — это не отдельная вещь, а сама структура отношений, в которых этот выбор имеет значение.

Пусть у нас есть два абстрактных индекса (позиции в контексте), A и B, каждый из которых может актуализировать значение атома: s_A, s_B = 1 \in \{ 0, 1 \}.
Тогда условие атома смысла задаётся правилом:

s_A ⊕ s_B = 1

где ⊕ — операция XOR (сложение по модулю 2).

Это означает: если у одного носителя зафиксировано значение «0», то у другого обязательно «1», и наоборот. Атом смысла выражает не объект, а идею различия, которая сохраняется сквозь любые расстояния и времена.

Статистика и потенциал

До момента измерения система не имеет определённого значения ни для S_A, ни для S_B Вместо этого существует вероятность, распределённая по двум исходам. Формально это можно записать как состояние:

\Psi = \frac{1}{\sqrt{2}}\big( |0\rangle_A |1\rangle_B \;+\; |1\rangle_A |0\rangle_B \big)

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

2.3 Бинарные отношения и корреляционные паттерны

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

Для описания таких более общих случаев вводится Корреляционный Паттерн (КП):

CP\left(A,B\right)=rule:B=f\left(A\right),s\in\left[0,1\right],p\in\left[0,1\right]
  • rule — логическое или функциональное правило (например, противоположность XOR, тождество, более сложные зависимости).

  • strength — сила корреляции (насколько связь «жёсткая»). При  это абсолютный атом смысла, при — статистическое ограничение.

  • persistence — мера устойчивости корреляции во времени и в разных контекстах.

Таким образом, атом смысла — это минимальный случай КП, когда rule=XOR,\ s=1,\ p=1, а правило максимально простое (например, противоположность). Корреляционные паттерны формализуются через матрицы, подобные ковариационным матрицам в многомерной статистике (Pearson, 1901), но с акцентом на онтологическую роль, как в моделях Изинга для магнетизма (Ising, 1925), где корреляции определяют фазовые переходы.
При этом важно четко понять, что Атом смысла всегда остаётся бинарным. Однако в составе Корреляционных Паттернов (КП) мы работаем не с одиночными атомами, а с распределениями на множествах атомов. Именно в этих распределениях проявляются дробные вероятности и корреляции, которые можно интерпретировать как «нестрогие» отношения между атомами. Но это относится не к самим атомам, а к их совместной структуре внутри КП.

2.4 Корреляционная матрица: «партитура» реальности

Несколько атомов смысла или КП могут объединяться в сеть.
Формально её можно описать как граф:

G = (V,E)

где V — множество носителей, а E — множество связей (атомов смысла или КП).

Каждое ребро в таком графе задаёт условие вида:

f(A_i, A_j) = 0

Сеть образует корреляционную матрицу, то есть полное описание возможных отношений в системе. Если говорить не математическим, а простым языком — это таблица всех связей между элементами. Чем сильнее связь, тем «ближе» они друг к другу в структуре модели. Такая корреляционная матрица является прямым аналогом матрицы плотности (density matrix) в квантовой механике и матрицы когерентности в статистической физике.

Формально сеть КП описывается как граф, где узлы — носители свойств, а рёбра — корреляции между ними. Такой подход перекликается с теорией сложных сетей (Барабаши-Альберт, 1999), где изучение топологии связей позволяет объяснить возникновение масштабно-инвариантных структур. В нашей модели именно динамика корреляционных связей способна эмерджентно порождать сложные устойчивые паттерны — от элементарных частиц до макроскопических объектов.

Корреляционная матрица  размером N\times N описывает состояние всей сети из узлов:

C = (C_{ij}),  \ \ \ \ i, j = 1, ..., N

Элемент C_{ij} характеризует связь между узлом и узлом. Чтобы эта матрица имела смысл как «сетевая совокупность правил», на неё накладываются особые свойства:

  • C – эрмитова и неотрицательно определенная матрица. Это математический способ сказать, что связи согласованы и не содержат внутренних противоречий. Эрмитовость C_{ij} = C_{ji} и положительная полуопределенность аналогичны свойствам ковариационных или плотностных матриц в квантовой механике. Проще говоря, эти условия гарантируют, что мы не сможем из наших корреляций вывести заведомо нелогичный или отрицательно вероятный результат.

  • Нормировка на диагонали: C_{ij} = 1 для любого узла. Каждый узел полностью «коррелирован с самим собой». Это естественно: мера самосогласованности максимальна, любая вещь на 100% совпадает с собой. Этот единичный диагональный элемент играет роль эталона для силы связи.

  • Диапазон значений: C_{ij}∈[-1,1]. Элемент матрицы может быть положительным, отрицательным или нулевым. Его модуль |C_{ij}| показывает силу связи между i и j, а знак – тип связи. Положительный C_{ij}=+1 означает полную прямую зависимость (если i в состоянии X, то j тоже в таком же состоянии X; можно сказать, они тождественны в своих значениях). Отрицательный C_{ij}=-1 – обратная зависимость (когда i = X, j=-X; как спины в синглетной паре – всегда противоположны). Ноль C_{ij}=0 – полное отсутствие корреляции, независимость (знание состояния i ничего не говорит о j).

Корреляционная матрица — это полное описание состояния всей системы. Она заменяет собой традиционный вектор состояния (волновую функцию) в квантовой механике.

2.5 Эмерджентные объекты: устойчивые кластеры корреляций

Мы подошли к ключевому моменту: как из голых отношений рождаются привычные нам устойчивые объекты — электроны, протоны, яблоки, планеты?

Основная идея: объекты возникают как устойчивые кластеры внутри сети корреляционных паттернов (КП).

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

Формализация устойчивости

Пусть U\subset V — множество узлов (носителей), претендующее на статус объекта.
Определим его устойчивость через суммарную силу и долговечность внутренних корреляций:

\mathrm{stability}(U) = \sum_{i < j \in U} s_{ij} \cdot p_{ij}

Здесь:

  • s_{ij}\in\left[0,1\right] — сила корреляционной связи между узлами i и j;

  • p_{ij}\in\left[0,1\right] — её устойчивость (persistence), т.е. насколько долго и в скольких контекстах сохраняется данный паттерн.

Если

Stability\left(U\right)>\theta

где θ — некоторый порог, то кластер U можно считать самостоятельным образованием — объектом.

Элементарные объекты

Элементарные частицы в этой модели — это минимальные устойчивые кластеры, для которых корреляционная структура максимально проста, но обладает высокой устойчивостью {\ p}_{ij}\approx1. Это означает, что внутренние корреляции практически не разрушаются при взаимодействии с окружением, обеспечивая сохранение идентичности объекта.

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

Идентичность такого эмерджентного объекта задаётся корреляционной сигнатурой – набором всех C_{ij}      внутри кластера. Это как геном или «ДНК» объекта: он определяет, как части объекта взаимосвязаны, а значит, как объект будет вести себя, каковы его свойства. Если два объекта имеют разную внутреннюю корреляционную сигнатуру, они различаются по свойствам (как два разных вида частиц или два разных понятия). Если сигнатура одинакова – по сути, это один и тот же объект (или два экземпляра одного типа).

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

Например, электрон может мыслиться как кластер определённых корреляций между свойствами заряда, спина, лептонного числа и т.д. – все электроны будут иметь сходную «схему связей», поэтому неотличимы в рамках этой структуры. А протон – другой набор корреляций между квантовыми числами, поэтому это другой объект.

2.6 Динамика сети: как эволюционируют корреляции

Имея подобие «структуры состояния» (матрицу C), нужно описать, как эта структура меняется. Для того, что бы «оживить» статичные связи, мы вводим функционал действия S\left[C\right] – аналог принципа наименьшего действия в физике, но применительно к нашей сети корреляций. Экстремумы этого функционала (минимумы/стационарные точки) будут соответствовать устойчивым состояниям сети (равновесиям или закономерным эволюциям).

В общем виде можно записать довольно громоздко:

S[C] = - \sum_{i < j} J_{ij} C_{ij}^2 + \lambda\sum_{i,j,k} C_{ij} C_{jk} C_{ki} + \mu\sum_{i < j < k < l}f(C_{ij}, C_{jk}, C_{kl}, C_{li}) + \ldots

В функционале есть несколько вкладов:

  • Первый член - \sum\nolimits_{i < j} J_{ij} |C_{ij}|^2это сумма квадратов всех связей (с весовыми коэффициентами J_{ij}). Этот член отвечает за стремление усилить или ослабить конкретные связи. Коэффициенты J_{ij}                можно считать некими внешними параметрами или “предпочтениями” сети: где J положительно, сеть выгодно усиливать соответствующую корреляцию (увеличивать  |C_{ij}|); где отрицательно – наоборот, гасить связь. В отсутствии других эффектов такой член сам по себе пытался бы настроить силу каждой связи на какой-то оптимум.

  • Второй член + \; \lambda\sum\nolimits_{i,j,k} C_{ij} C_{jk} C_{ki} : это сумма по всем тройкам узлов i,j,k произведений корреляций, то есть фактически замкнутые треугольники корреляций. Такой член поощряет формирование циклов в сети. Замкнутый треугольник – признак, что узлы i,j,k все попарно связаны между собой. Наличие многих таких корреляционных циклов – это уже зачатки локальной структуры наподобие геометрии. Именно эти циклы необходимы для возникновения чего-то похожего на пространственные отношения внутри сети, а также способствуют устойчивости кластеров. Коэффициент \lambda регулирует значимость таких триад.

  • Последующие члены (суммы по 4 и более узлам, обозначено функцией f и т.д.): это ещё более высокие порядки взаимодействий. Они могут описывать формирование сложных топологических структур – например, ячеек симплициального комплекса (четырёхугольники, объёмные тетраэдры корреляций и т.д.). Эти термы нужны, чтобы учесть очень сложные корреляционные узоры, возможно отвечающие за высокоуровневые структуры вроде пространственной топологии, размерности, и прочих тонкостей.

Важнейшее уравнение, которое получается из принципа наименьшего действия – уравнение эволюции корреляционной матрицы. По аналогии с квантомеханическим уравнением (вспомним уравнение Шрёдингера i \hbar \frac{\partial \rho}{\partial t} = [H, \rho] для плотностной матрицы), вводится схожее уравнение:

i\hbar\frac{\partial C_{mn}}{\partial t}=\frac{\delta S\left[C\right]}{\delta C_{mn}}+\sum_{k,l}V_{mnkl} C_{kl}

Не будем вдаваться глубоко в математику, отметим главное: эволюция всей сети унитарна. Это значит, что при развитии корреляций сохраняется общая информация, нет потерь – подобно тому, как замкнутая квантовая система развивается детерминистски и сохраняет свою полную вероятность (волновая функция нормирована, энтропия сохраняется). Локальные же процессы, которые мы интерпретируем как «случайные события», «измерение», «декогеренция» – в этой картине объясняются не как нарушение общей детерминированности, а как перераспределение корреляций внутри сети.

Я обязан сделать оговорку о том, что конкретный вид функционала действия S[C] и потенциалов V_{mnkl} в текущей версии модели постулируется на основе общих физических соображений, а не выводится из первых принципов и носит иллюстративный характер. Задача нахождения точного вида S[C], из которого эмерджировали бы все известные физические законы, является ключевой для дальнейшего развития теории и пока остается открытой.

Что происходит при измерении? В классическом понимании волновая функция коллапсирует, информация теряется, неопределённость исчезает. В нашей модели корреляции перестраиваются: измеряемая система сильнее связывается с прибором и/или мозгом наблюдателя, образуя новый, более широкий корреляционный кластер, а прежние тонкие связи внутри неё размываются. Важно подчеркнуть, что если учитывать всю сеть целиком (систему + прибор), то общий процесс остаётся обращаемым и информационно полным. Никакой мистики — просто перераспределение связей.

2.7 Меры информации: от корреляций к смыслу

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

Энтропия узла H\left(v_i\right):

H\left(v_i\right)=\ -\sum_{x}{P\left(x\right)log P\left(x\right)\ }

где P\left(x\right) — вероятность того, что узел v_i примет значение x.

Энтропия измеряет неопределённость состояния узла. Высокое значение H\left(v_i\right)   означает, что узел может реализовать много возможных исходов; низкое — что исход фактически заранее определён.

В модели: энтропия узла указывает, насколько данный элемент сети «плавает» в пространстве возможностей. Низкая энтропия соответствует «замороженному» состоянию (узел строго определён), высокая — множеству потенциальных вариантов.

Взаимная информация I\left(v_i;v_j\right)

Формула:

I\left(i;j\right)=H\left(v_i\right)+H\left(v_j\right)-H\left(v_i,v_j\right)

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

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

Интегрированная информация \Phi(U)

Формула:

\Phi(U)=\sum_{i∈U}H(v_i) - H(U)

где H(U) — энтропия всего кластера U.

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

В модели: положительное значение \Phi указывает на то, что кластер узлов образует новое образование. При больших \Phi можно говорить о появлении эмерджентного объекта, устойчивого к распаду. А очень большие значения \Phi — признак систем, способных к целостной обработке информации, то есть потенциально обладающих свойствами сознания. Мера \Phi(U) аналогична схожему параметру из Теории Интегрированной Информации Джулио Тонони (Integrated Information Theory, IIT) для оценки неразложимости кластера.

Таким образом, три меры образуют естественную лестницу смыслов:

  1. Энтропия — степень открытости одного узла к возможным состояниям.

  2. Взаимная информация — смысл, разделяемый двумя узлами.

  3. Интегрированная информация — целостность кластера, неразложимого на части.

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

Несмотря на известную спекулятивность, данный инструментарий уже дает возможность увидеть в структуре корреляций не просто математические зависимости, а формирующийся пласт смыслов — путь от простейших бинарных отношений к эмерджентным объектам и, в пределе, к сознательным системам. Однако перед нами встает следующий вызов: каким образом из этой чисто реляционной, аморфной сети эмерджируют привычные нам категории — пространство, время и физические законы. Интуиция подсказывает, что ответ следует искать в анализе свойств самой матрицы C и функционала действия S[C].

...Так можно ли из бинанрности построить простраснтво? И как это вообще должно выглядеть? А с чего я взял, что следующим шагом должно быть обязательно построение пространства? Это вообще ниразу нетривиальная вещь!

Супруга (архитектурный дизайнер), на вопрос что по-твоему есть простарнство ответила – интерьер. Чистый, выверенный. Наша интуиция требует «интерьера» — упорядоченной, метрической структуры, где есть близко и далеко, внутри и снаружи.

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

Если я возьму две такие бинарности, это не будет усложнением? А там где две, там же и столько, сколько необходимо. Могут ли они между собой как-то взаимодействовать?..

Возникает ощущение, что я пытюсь где-то тут ухватится за метрику…

Глава 3. Пространство-время, Материя и Силы

Субстанция, которую невозможно представить, можно только восприять. Это касается и пространства, и времени. Мы привыкли, что пространство — это фон, сцена, «контейнер», в котором расположены объекты. Но если мы начинаем «собирать вселенную с нуля», то такой контейнер нам даром никто не даёт.

Одно из следствий модели: пространство-время – не фундаментально, а возникает из структуры корреляций. То, что мы считаем «сценной», на которой разворачиваются события, в более глубоком смысле является следствием отношений между самими событиями.

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

3.1 Пространство как сила корреляций

Мы постулируем (следствие): расстояние между двумя элементами реальности – это просто удобный способ описать слабость или силу их связи. Если два узла сильно коррелируют (например, C_{ij} близко к 1 по модулю), мы будем говорить, что они находятся близко друг к другу в некотором эмерджентном пространстве. Если связи нет (C_{ij}\approx0)     , узлы “далеки”.

Можно даже ввести определение расстояния через величину корреляции. Например:

d\left(i,j\right)=-\lambda ln|C_{ij}|

Тогда: если C_{ij}=1 (максимальная связь), d\left(i,j\right)=-ln1=0 – узлы практически в одном месте, сливаются. Если связь слабая, скажем C_{ij} = 0.1, то d\left(i,j\right)\approx2.3 (некоторое расстояние). При нулевой корреляции можно считать расстояние бесконечным или очень большим (формально -ln0 стремится к бесконечности). Такая логарифмическая мера похожа на понятие расстояния в информационных пространствах (Kullback-Leibler дивергенция и т.п.). Так же, похожие идеи встречаются и в современной теоретической физике. Например, в работах Ван Рамсдонка запутанность рассматривается как источник геометрии и гравитации. Здесь используется упрощённая версия той же интуиции.

С помощью подобного определения сама сеть корреляций превращается в «пространственную» структуру: узлы формируют некое метрическое пространство. Метрика эта, правда, не обязана строго подчиняться всем классическим аксиомам (например, могут быть случаи, когда d\left(i,j\right) + d\left(j,k\right) < d\left(i,k\right)  – как если есть “кратчайший путь” через промежуточный узел, а прямой связи i-k нет). Тем не менее в крупном масштабе, если корреляционная сеть богата циклами (вспомним вклад треугольников в функционал действия), она будет вести себя примерно как привычное геометрическое пространство: с локальной близостью-дальностью и даже со своим изгибом (кривизной).

Кривизна этого эмерджентного пространства зависит от структуры связей. Если вокруг некоторого узла связи равномерные во всех направлениях, пространство плоское. Если же в какой-то области узлы имеют особенно много сильных связей между собой (плотный кластер), это эквивалентно тому, что метрика там деформирована, «сжата». В геометрии такое сжатие коррелирует с положительной кривизной (как поверхность сферы). В сетевых терминах можно измерять кривизну через спектр лапласиана графа связей или через отклонения от метрических соотношений (например, нарушается неравенство треугольника). Наши симуляции модели показывают, что плотные кластеры корреляций действительно вызывают искривление эмерджентного пространства – аналогично тому, как масса искривляет пространство-время в Общей теории относительности.

Сама идея о том, что пространство есть проекция корреляционных паттернов, созвучна голографическому принципу AdS/CFT, где гравитация в объеме кодируется данными на границе. Кроме того, идея о том, что пространство-время является производным от запутанности, была впервые чётко сформулирована Ван Рамсдонком, который показал, что изменение запутанности между регионами эквивалентно изменению метрики. Наша модель развивает эту идею, заменяя «запутанность» на более общее понятие корреляционного паттерна (КП).

3.2 Время как порядок актуализаций

Физика привыкла рассматривать время как фон. Есть некая ось, вдоль которой течёт мир, а мы фиксируем моменты «сейчас». В нашей модели всё иначе: время — это не фон, а порядок изменений, возникающий из последовательности перестроек корреляционной сети.

Формально: пусть C_{mn}\left(t\right) — элементы корреляционной матрицы, описывающие связи между узлами m и n. Тогда уравнение эволюции можно записать в общем виде:

i\hbar\frac{\partial C_{mn}}{\partial t}=\mathcal{F}\left[C\right]

где \mathcal{F}\left[C\right] — функционал, описывающий, как текущая конфигурация сети влияет на её следующее состояние.

Здесь параметр t не следует понимать как абсолютное «внешнее» время. Он лишь задаёт последовательность изменений: шаг за шагом сеть перестраивает свои корреляции.

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

Для кластера узлов U естественно ввести понятие собственного времени , связанного со стабильностью внутренних корреляций. Пусть

\Delta C_{ij}\left(t\right)=\frac{\partial C_{ij}\left(t\right)}{\partial t},\ \ \ \ \ \ i,j\in U

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

\sigma_U\left(t\right)=\frac{1}{\left|U\right|^2}\sum_{i,j\in U}\left|\Delta C_{ij}\left(t\right)\right|^2

Чем больше \sigma_U​, тем сильнее колеблются внутренние связи, и тем быстрее течёт его собственное время.

В простейшей аппроксимации:

\frac{d\tau_U}{dt}\propto\frac{1}{1+\sigma_U\left(t\right)}

То есть, чем устойчивее кластер, тем медленнее идут его «часы».

Релятивистская метафора

Можно сказать так: время субъекта сети течёт тем быстрее, чем интенсивнее меняется его внутренняя структура.

  • Если кластер почти неизменен, его «собственное время» замедляется.

  • Если он активно участвует во внешних взаимодействиях, его время ускоряется, так как каждая перестройка внутренней структуры создаёт «метку» последовательности.

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

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

Константы и предельные скорости

Здесь появляется приятный бонус. В традиционной физике константы вроде просто «даются» экспериментом. В нашей модели они естественно присутствуют как свойства самой сети:

  • c — предел скорости переноса изменений в сети.

  • \hbar — мера квантованности, то есть минимального «шага» актуализации в эволюции корреляций.

  • G — мера того, насколько плотные кластеры корреляций искривляют сеть.

3.3 Рождение материи: Автоморфизмы и физические инварианты

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

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

Масса как плотность связей

Что такое масса в этой картине?
Масса эмерджентно возникает как мера плотности внутренних корреляций кластера.

m\left(U\right)\;\propto\;\sum_{i,j\in U}\left|C_{ij}\right|^2

где U — множество узлов (кластер), а C_{ij} — корреляция между узлами.

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

Автоморфизмы: источник квантовых чисел

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

Автоморфизм сети g\in Aut(G) — это преобразование, которое переставляет узлы, но сохраняет все связи:

C_{ij}=C_{g(i)g(j)}

Можно сравнить с паутиной. Вы можете перетащить её узлы, но если натяжение (в модели это сила связей) всех нитей осталось прежним, структура сети не изменилась.

Инварианты таких преобразований и есть квантовые числа частиц:

  • Заряд (q): соответствует симметрии U(1) (фазовое вращение).
    Инвариант под этим преобразованием = электрический заряд.

  • Спин (s): связан с симметрией SU(2) .
    Это внутреннее «вращение» кластера в абстрактном пространстве состояний.

  • Цвет (QCD): возникает из симметрии SU(3).
    Кварки — кластеры, «чувствительные» к этой симметрии.
    Адроны (например, протоны) — это уже комбинации, инвариантные относительно SU(3), то есть «бесцветные».

Законы сохранения как следствие

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

В нашей модели весь “физический мир” – это производная реальность, возникающая из динамики сети C. Поэтому симметрии сети корреляций проявятся как знакомые законы сохранения. Можно показать, что если функционал действия S[C] и граничные условия инвариантны относительно некоторого преобразования корреляционной матрицы (например, одновременного вращения всех корреляций по определённой оси в пространстве свойств), то найдётся соответствующая величина, которая не изменяется при эволюции.

Например:

  • Если наша сеть (а точнее, уравнения для неё) не меняются при “вращении” узлов в эмерджентном пространстве (то есть сеть в среднем изотропна, нет выделенного направления в корреляциях) – то возникает закон сохранения момента импульса. В реальном мире именно из изотропности пространства следует сохранение углового момента; у нас – аналогично, но речь о симметрии в возникающем пространстве связей.

  • Если существует некая фаза/калибровка свойств узлов, которая не влияет на корреляции (то есть инвариантность при определённом комплексном повороте фаз связей, скажем) – появится закон сохранения заряда или другой квантовой числовой величины. (В квантовой теории заряд сохраняется из-за симметрии фазовой инвариантности волновой функции.)

Идея, что материальные объекты могли бы являются устойчивыми кластерами в сети, была математически обоснована в работе Криукова и др., где показано, что поведение сетей с определённой топологией может воспроизводить уравнения Эйнштейна. Формализм корреляционных паттернов (КП) предлагает альтернативную онтологию, где кластеры формируются не по геометрическому признаку, а по силе корреляционных связей.

3.4 Фотон как переносчик согласованности

В предыдущем разделе мы показали, что фундаментальные свойства частиц (заряд, спин, цвет) возникают как инварианты автоморфизмов сети корреляционных паттернов (КП).
Для заряда соответствующая симметрия — это группа фазовых преобразований U(1).

Формально:

Пусть g\in U(1) действует на узлы v_i сети как фазовый сдвиг:

v_i\mapsto e^{i\theta}v_i,\ \ \ \ \ \theta\in\left[0,2\pi\right)

Если корреляционная матрица C_{ij} инвариантна относительно такого преобразования:

C_{ij}=C_{g\left(i\right)g\left(j\right)},

то величина, сохраняющаяся при всех g\in U(1), интерпретируется как электрический заряд узла или кластера.

Необходимость носителя симметрии

Классическая теорема Нётер утверждает: всякой непрерывной симметрии соответствует закон сохранения. Но в сетевой интерпретации этого недостаточно.

Если у нас есть инвариант (заряд), то любая перестройка корреляций должна сопровождаться согласованием: изменение локального состояния узла обязано «передаться» во всей сети, чтобы сохранить U(1) - инвариантность.

Эта передача не может быть мгновенной. Она ограничена топологией сети и максимальной скоростью перераспределения корреляций. Здесь и возникает предельная скорость c.

Возбудитель согласования

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

Формально:

  • Пусть кластер U имеет заряд q.

  • Изменение его состояния описывается оператором \delta C_{ij}​​, нарушающим локальную U(1) - симметрию.

  • Чтобы восстановить глобальную инвариантность, сеть возбуждает переносчик \gamma (фотон), который распространяет согласование.

Можно записать схематично:

\delta Cijлокально→γ→δCklглобально,

где \gamma есть возбудитель сети, минимизирующий функционал действия S[C] при условии сохранения U(1) - инвариантности.

Свойства фотона в модели

  • Безмассовость:
    У фотона нет устойчивого кластера узлов (он не объект), он — чистый паттерн согласования. Поэтому у него нет инерции, и он всегда движется с предельной скоростью c.

  • Отсутствие собственного времени:
    В модели время — это порядок изменений корреляций внутри кластера.
    У фотона нет собственного кластера → нет и собственного времени.

  • Две поляризации:
    В терминах сети это два возможных способа реализации бинарного паттерна в подпространстве U(1).

  • Переносчик взаимодействия:
    Взаимодействие заряженных объектов = согласование их корреляционных паттернов.
    Фотон = минимальная единица такого согласования.

Представьте, что два заряженных кластера находятся в сети.
Если один изменил своё состояние, то глобальная U(1) - симметрия «требует», чтобы другой также согласовал свои связи.
Фотон в этой картине — это «пакет согласования», который бежит по сети с максимальной скоростью и проявляется как частица только в моменты эмиссии и абсорбции.
Он не несёт «вещество», но несёт идею корреляционной совместимости.

В стандартной квантовой электродинамике (КЭД) фотон — калибровочный бозон U(1). В нашей модели фотон — это возбудитель согласования U(1) - инвариантности в сети корреляций. Обе картины совпадают по сути: фотон существует не как материальный объект, а как необходимое следствие симметрии.

Глава 4. Численные эксперименты (симуляции модели)

От абстрактной модели — к видимой реальности

В предыдущих главах мы ввели онтологический фундамент: мир состоит не из объектов, а из отношений — Корреляционных Паттернов (КП). Мы описали их математически через корреляционную матрицу и функционал действия. Но ключевой вопрос остается: как из этой абстрактной сети чистых отношений возникает привычная нам реальность с ее пространством, временем и устойчивыми объектами?

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

Представленные ниже численные эксперименты носят иллюстративный и модельный характер. Они призваны продемонстрировать принципиальную возможность эмерджентного возникновения структур из сети отношений, а не являться точным моделированием физической реальности. Используемые симуляции упрощены и служат доказательством концепции (proof-of-concept), но не являются строгим доказательством

4.1. Суперкластер: Фаза единства

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

Корреляционная матрица имеет повсеместно умеренно высокие значения — на тепловой карте видна яркая диагональ и заметные внедиагональные элементы, образующие единый размытый «блок» корреляций
Корреляционная матрица имеет повсеместно умеренно высокие значения — на тепловой карте видна яркая диагональ и заметные внедиагональные элементы, образующие единый размытый «блок» корреляций
Скрытый текст
# ----------------------------------------------
# Симуляция 4.1: «Суперкластер»
# ----------------------------------------------
# Что делает скрипт:
# 1) Генерирует временные ряды на графе с экспоненциальной связностью,
#    считает корреляционную матрицу C.
# 2) Строит kNN-граф по |C| (показатель глобальной связности) и
#    «пороговый» граф по перцентилю |C| (для выделения компонент).
# 3) Считает второе собственное значение лапласиана (λ2) как метрику связности.
# 4) Рисует три рисунка и СОХРАНЯЕТ их в папку ./out РЯДОМ СО СКРИПТОМ:
#    - Теплокарта |C|
#    - PCA-раскладка узлов + рёбра kNN
#    - Спектры лапласианов (kNN и |C|)
# 5) Печатает в ТЕРМИНАЛ ключевые метрики.
#
# Зависимости: numpy, matplotlib
# ----------------------------------------------
import os
import numpy as np
import matplotlib.pyplot as plt

# ---------- Вспомогательные функции ----------
def ensure_outdir(path=""):
    script_dir = os.path.dirname(os.path.abspath(__file__))
    outdir = os.path.join(script_dir, path)
    os.makedirs(outdir, exist_ok=True)
    return outdir

def set_seed(seed=42):
    """Фиксируем зерно генератора для воспроизводимости."""
    np.random.seed(seed)

def build_W(N, xi=8.0, aniso=0.1):
    """
    Строим матрицу связей W (симметричную):
    - Базовая часть: экспоненциальное убывание связи по |i-j| с характерным радиусом xi.
    - Добавочная слабая анизотропия (две «полосы»), чтобы разрушить идеальную симметрию.
    - Нормируем по спектральному радиусу, чтобы значения были устойчивыми численно.
    """
    i = np.arange(N)[:, None]
    j = np.arange(N)[None, :]
    D = np.abs(i - j).astype(float)
    W = np.exp(-D / xi)
    np.fill_diagonal(W, 0.0)
    if aniso > 0:
        W += aniso * np.exp(-np.abs((i - j) - N/3) / (max(xi/2,1e-6)))
        W += aniso * np.exp(-np.abs((i - j) + N/3) / (max(xi/2,1e-6)))
        np.fill_diagonal(W, 0.0)
    # Нормировка по спектральному радиусу
    s = np.max(np.abs(np.linalg.eigvalsh(W)))
    if s > 1e-9:
        W = W / s
    return W

def integrate(N=80, T=20.0, dt=0.05, beta=1.0, gamma=0.8, noise=0.05, xi=8.0, aniso=0.1, seed=42):
    """
    Простейшая нелинейная интеграция на графе:
    x(t+dt) = (1 - gamma*dt) * x + beta*dt * tanh(W @ x) + noise * sqrt(dt) * N(0, I)
    Возвращает:
      X — траектории (steps x N),
      C — матрица корреляций (N x N),
      W — матрица связей (N x N).
    """
    set_seed(seed)
    steps = int(T / dt)
    W = build_W(N, xi=xi, aniso=aniso)
    x = 0.1 * np.random.randn(N)
    X = np.zeros((steps, N))
    sqrt_dt = np.sqrt(dt)
    for t in range(steps):
        x = (1 - gamma*dt) * x + beta*dt * np.tanh(W @ x) + noise * sqrt_dt * np.random.randn(N)
        X[t] = x
    # Матрица корреляций по времени
    Xc = X - X.mean(axis=0, keepdims=True)
    std = Xc.std(axis=0, keepdims=True) + 1e-12
    Xn = Xc / std
    C = (Xn.T @ Xn) / (Xn.shape[0] - 1)
    C = np.clip(C, -1.0, 1.0)
    return X, C, W

def laplacian_from_affinity(A):
    """
    Строим нормированный лапласиан графа L = I - D^{-1/2} A D^{-1/2},
    где A — матрица «родства» (аффинности), D — диагональ степеней.
    """
    A = np.maximum(A, 0.0)
    np.fill_diagonal(A, 0.0)
    d = A.sum(axis=1)
    dinv2 = 1.0 / np.sqrt(d + 1e-12)
    L = np.eye(A.shape[0]) - (dinv2[:,None] * A) * dinv2[None,:]
    return L

def fiedler_lambda(L):
    """Второе собственное значение лапласиана (λ2) — индикатор связности графа."""
    vals = np.linalg.eigvalsh(L)
    vals = np.sort(np.real(vals))
    if len(vals) >= 2:
        return float(vals[1])
    return float(vals[0])

def knn_affinity_from_C(C, k=6):
    """
    Строим kNN-граф по метрике d = 1 - |C| (т.е. «ближе» = выше корреляция).
    Веса рёбер = |C|. Симметризуем по максимуму.
    kNN обычно остаётся связным при умеренно больших k и высоких средних |C|.
    """
    S = np.abs(C)
    N = S.shape[0]
    D = 1.0 - S
    A = np.zeros_like(S)
    for i in range(N):
        order = np.argsort(D[i] + np.eye(N)[i]*1e9)  # исключаем самих себя
        nbrs = order[:k]
        A[i, nbrs] = S[i, nbrs]
    A = np.maximum(A, A.T)
    np.fill_diagonal(A, 0.0)
    return A

def connected_components_binary(A_bin):
    """Компоненты связности для НЕвзвешенного графа (BFS)."""
    N = A_bin.shape[0]
    visited = np.zeros(N, dtype=bool)
    labels = -np.ones(N, dtype=int)
    comp = 0
    for start in range(N):
        if not visited[start]:
            queue = [start]
            visited[start] = True
            labels[start] = comp
            while queue:
                u = queue.pop(0)
                neighbors = np.where(A_bin[u] > 0)[0]
                for v in neighbors:
                    if not visited[v]:
                        visited[v] = True
                        labels[v] = comp
                        queue.append(v)
            comp += 1
    return comp, labels

def auto_percentile_threshold(C, target_range=(2,6), start=85, minp=50, maxp=98, step=3):
    """
    Подбор перцентиля по |C|, чтобы число компонент было в target_range.
    Возвращает лучший найденный вариант (даже если не идеально попали):
      (p, thr, A_bin, n_comp, labels, sizes)
    """
    absC = np.abs(C)
    N = C.shape[0]
    best = None
    best_gap = 10**9
    best_balance = 10**9
    idx = np.triu_indices(N, 1)
    for p in range(start, maxp+1, step):
        thr = np.percentile(absC[idx], p)
        A = (absC >= thr).astype(float)
        np.fill_diagonal(A, 0.0)
        n_comp, labels = connected_components_binary(A)
        sizes = np.bincount(labels, minlength=n_comp)
        # Насколько далеко от желаемого диапазона по числу компонент
        gap = 0
        if n_comp < target_range[0]:
            gap = target_range[0] - n_comp
        elif n_comp > target_range[1]:
            gap = n_comp - target_range[1]
        # Насколько «несбалансирован» разбиение (стандартное отклонение размеров)
        balance = sizes.std() if n_comp > 1 else 1e9
        if (gap < best_gap) or (gap == best_gap and balance < best_balance):
            best_gap = gap
            best_balance = balance
            best = (p, thr, A, n_comp, labels, sizes)
        # Если попали в коридор и относительно сбалансировано — хватит
        if target_range[0] <= n_comp <= target_range[1] and balance < 0.25 * N:
            break
    return best

def cluster_metrics(C, labels):
    """
    Простейшие метрики по каждому кластеру (по меткам labels):
    - size: размер кластера
    - mass: сумма квадратов внутренних корреляций |C|^2 (интенсивность/«масса»)
    - charge: сумма внешних связей (с другими кластерами)
    - spin_like: мера асимметрии C_U (≈0 — почти симметрично)
    - lambda2: связность внутреннего подграфа (λ2 лапласиана по |C|)
    """
    uniq = np.unique(labels)
    out = []
    for u in uniq:
        idx = np.where(labels == u)[0]
        n = len(idx)
        Cin = C[np.ix_(idx, idx)]
        Cout = C[np.ix_(idx, np.where(labels != u)[0])]
        mass = float(np.sum(np.abs(Cin)**2))
        charge = float(np.sum(Cout))
        spin_like = float(np.linalg.norm(Cin - Cin.T) / (np.linalg.norm(Cin) + 1e-12))
        Ain = np.abs(Cin)
        Lin = laplacian_from_affinity(Ain)
        lam2 = fiedler_lambda(Lin)
        out.append(dict(id=int(u), size=int(n), mass=mass, charge=charge, spin_like=spin_like, lambda2=lam2))
    out.sort(key=lambda d: d["mass"], reverse=True)
    return out

def pca_2d_from_rows(M):
    """PCA до 2D (через SVD) для отображения узлов как точек в плоскости."""
    X = M - M.mean(axis=0, keepdims=True)
    U, S, Vt = np.linalg.svd(X, full_matrices=False)
    Z = X @ Vt[:2].T  # N x 2
    return Z

def spectrum(L):
    """Возвращает отсортированный спектр собственных значений лапласиана."""
    vals = np.linalg.eigvalsh(L)
    return np.sort(np.real(vals))

# ---------- Основной запуск (пресет «Суперкластер») ----------
def run_supercluster_preset():
    outdir = ensure_outdir()

    # Параметры, дающие глобальную связность (суперкластер в kNN)
    params = dict(
        N=300, T=20.0, dt=0.05, beta=1.0, gamma=0.8, noise=0.05, xi=9.0, aniso=0.10,
        seed=42, k=6
    )

    print("=== Симуляция: «Суперкластер» (пресет) ===")
    for k,v in params.items():
        if k!='k':
            print(f"{k} = {v}")
    print(f"k (kNN) = {params['k']}")

    # Интеграция и корреляции
    X, C, W = integrate(N=params['N'], T=params['T'], dt=params['dt'], beta=params['beta'],
                        gamma=params['gamma'], noise=params['noise'], xi=params['xi'],
                        aniso=params['aniso'], seed=params['seed'])

    # Граф kNN и λ2
    A_knn = knn_affinity_from_C(C, k=params['k'])
    L_knn = laplacian_from_affinity(A_knn)
    lam2_knn = fiedler_lambda(L_knn)

    # Пороговый граф по перцентилю |C| (для компонент)
    p_val, thr_val, A_bin, n_comp, labels, sizes = auto_percentile_threshold(
        C, target_range=(2,6), start=85, minp=50, maxp=98, step=3
    )

    # Лапласиан по |C| (как «глобальная жёсткость»)
    L_absC = laplacian_from_affinity(np.abs(C))
    lam2_absC = fiedler_lambda(L_absC)

    # --- Вывод метрик в терминал ---
    print("\n--- Глобальные метрики ---")
    print(f"Второе собственное λ2 (kNN-граф): {lam2_knn:.4f}  → чем больше, тем связнее граф")
    print(f"Второе собственное λ2 (аффинность |C|): {lam2_absC:.4f}")
    print(f"Перцентиль p={p_val}% (порог={thr_val:.3f}) → число компонент: {n_comp}")
    print(f"Размеры компонент: {sizes.tolist()}")

    # Метрики по кластерам (на основе компонент перцентильного графа)
    clusters = cluster_metrics(C, labels)
    print("\n--- Метрики кластеров (по пороговому графу) ---")
    for cl in clusters:
        print(f"Кластер {cl['id']:>2d}: "
              f"размер={cl['size']:>3d}, "
              f"масса={cl['mass']:.3f}, "
              f"связи-наружу={cl['charge']:.3f}, "
              f"анизо(spin_like)={cl['spin_like']:.5f}, "
              f"λ2-внутр={cl['lambda2']:.4f}")

    # --- Изображения ---

    # (1) Теплокарта |C|
    plt.figure()
    plt.imshow(np.abs(C), interpolation="nearest")
    plt.title("|C| — теплокарта (режим «Суперкластер»)")
    plt.xlabel("Узел")
    plt.ylabel("Узел")
    plt.colorbar(label="|C|")
    fig1_path = os.path.join(outdir, "supercluster_heatmap_absC.png")
    plt.savefig(fig1_path, dpi=180, bbox_inches="tight")
    plt.close()

    # (2) PCA + рёбра kNN
    coords = pca_2d_from_rows(C)
    plt.figure()
    rows, cols = np.where(A_knn > 0)
    for i, j in zip(rows, cols):
        if i < j:
            plt.plot([coords[i,0], coords[j,0]], [coords[i,1], coords[j,1]], alpha=0.3)
    for u in range(n_comp):
        idx = np.where(labels == u)[0]
        plt.scatter(coords[idx,0], coords[idx,1], label=f"комп. {u} (n={len(idx)})", s=20)
    plt.title("PCA узлов C + рёбра kNN")
    plt.xlabel("ПК1")
    plt.ylabel("ПК2")
    plt.legend(loc="best", fontsize=8)
    fig2_path = os.path.join(outdir, "supercluster_pca_knn.png")
    plt.savefig(fig2_path, dpi=180, bbox_inches="tight")
    plt.close()

    # (3) Спектры лапласианов (kNN и |C|)
    def spectrum(L):
        vals = np.linalg.eigvalsh(L)
        return np.sort(np.real(vals))

    spec_knn = spectrum(L_knn)
    spec_absC = spectrum(L_absC)

    plt.figure()
    plt.plot(spec_knn, marker='o', linestyle='None', label="спектр L (kNN)")
    plt.plot(spec_absC, marker='x', linestyle='None', label="спектр L (|C|)")
    plt.title("Спектры лапласианов")
    plt.xlabel("индекс собственного значения")
    plt.ylabel("значение")
    plt.legend()
    fig3_path = os.path.join(outdir, "supercluster_laplacian_spectra.png")
    plt.savefig(fig3_path, dpi=180, bbox_inches="tight")
    plt.close()

    print("\nСохранено:")
    print(f"- Теплокарта: {fig1_path}")
    print(f"- PCA + kNN: {fig2_path}")
    print(f"- Спектры:   {fig3_path}")

if __name__ == "__main__":
    run_supercluster_preset()

Результат: система быстро пришла в состояние глобальной когерентности. Корреляционная матрица превратилась в почти сплошной яркий «блин» — знак того, что любой элемент сети знает о состоянии любого другого. Математически, это моделирует перколяционные переходы, впервые описанные Бродбентом и Хаммерсли (Broadbent & Hammersley, 1957), где гигантская компонента эмерджирует при критической плотности связей, аналогично фазовым переходам в конденсированной материи.

Вывод: модель допускает существование фазы предельного единства, где различия стерты, а реальность едина.

4.2. Многокластерность: Рождение множественности

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

Теплокарта корреляций отчётливо демонстрирует блоковую структуру: на диаграмме корреляций заметны несколько ярких квадратных областей вдоль диагонали, разделённых более тёмными промежутками. Это означает, что внутри каждого предполагаемого кластера узлы коррелируют между собой сильнее (ярче цвет), а между разными кластерами корреляционные связи ослаблены.
Теплокарта корреляций отчётливо демонстрирует блоковую структуру: на диаграмме корреляций заметны несколько ярких квадратных областей вдоль диагонали, разделённых более тёмными промежутками. Это означает, что внутри каждого предполагаемого кластера узлы коррелируют между собой сильнее (ярче цвет), а между разными кластерами корреляционные связи ослаблены.
Скрытый текст
# --------------------------------------------------------------
# Симуляция 4.2c: «Сбалансированная многокластерность (спектральная)»
# --------------------------------------------------------------
# Честная часть: kNN по |C| + спектральная кластеризация Лапласиана.
# K выбирается АВТОМАТИЧЕСКИ по eigengap нормализованного лапласиана.
# Визуализация: кластерный layout (только для картинки) — группы узлов
# компактны и разведены друг от друга; можно управлять радиусом/джиттером.
# --------------------------------------------------------------

import os
import numpy as np
import matplotlib.pyplot as plt

# ---------- утилиты ----------
def ensure_outdir(path=""):
    script_dir = os.path.dirname(os.path.abspath(__file__))
    outdir = os.path.join(script_dir, path)
    os.makedirs(outdir, exist_ok=True)
    return outdir

def set_seed(seed=2024):
    np.random.seed(seed)

# ---------- генерация W с мягкой блочностью ----------
def build_W_grain(N, xi=3.0, aniso=0.15, n_groups=5, grain_strength=0.30, inter_atten=0.55):
    i = np.arange(N)[:, None]
    j = np.arange(N)[None, :]
    D = np.abs(i - j).astype(float)
    W = np.exp(-D / xi)
    np.fill_diagonal(W, 0.0)

    if aniso > 0:
        W += aniso * np.exp(-np.abs((i - j) - N/3) / (max(xi/2,1e-6)))
        W += aniso * np.exp(-np.abs((i - j) + N/3) / (max(xi/2,1e-6)))
        np.fill_diagonal(W, 0.0)

    sizes = [N // n_groups + (1 if r < (N % n_groups) else 0) for r in range(n_groups)]
    offs = np.cumsum([0] + sizes)
    group_id = np.zeros(N, dtype=int)
    for g in range(n_groups):
        lo, hi = offs[g], offs[g+1]
        group_id[lo:hi] = g
        W[lo:hi, lo:hi] *= (1.0 + grain_strength)

    G = (group_id[:,None] == group_id[None,:]).astype(float)
    W = W * (G + inter_atten*(1.0-G))
    np.fill_diagonal(W, 0.0)

    s = np.max(np.abs(np.linalg.eigvalsh(W)))
    if s > 1e-9:
        W = W / s
    return W

# ---------- динамика и корреляции ----------
def integrate(N=84, T=14.0, dt=0.05, beta=1.0, gamma=0.8, noise=0.10,
              xi=3.0, aniso=0.15, n_groups=5, grain_strength=0.30, inter_atten=0.55, seed=2024):
    set_seed(seed)
    steps = int(T / dt)
    W = build_W_grain(N, xi=xi, aniso=aniso, n_groups=n_groups,
                      grain_strength=grain_strength, inter_atten=inter_atten)
    x = 0.1 * np.random.randn(N)
    X = np.zeros((steps, N))
    sqrt_dt = np.sqrt(dt)
    for t in range(steps):
        x = (1 - gamma*dt) * x + beta*dt * np.tanh(W @ x) + noise * sqrt_dt * np.random.randn(N)
        X[t] = x

    Xc = X - X.mean(axis=0, keepdims=True)
    std = Xc.std(axis=0, keepdims=True) + 1e-12
    Xn = Xc / std
    C = (Xn.T @ Xn) / (Xn.shape[0] - 1)
    C = np.clip(C, -1.0, 1.0)
    return X, C, W

# ---------- граф и лапласиан ----------
def knn_affinity_from_C(C, k=3):
    S = np.abs(C)
    N = S.shape[0]
    D = 1.0 - S
    A = np.zeros_like(S)
    for i in range(N):
        order = np.argsort(D[i] + np.eye(N)[i]*1e9)
        nbrs = order[:k]
        A[i, nbrs] = S[i, nbrs]
    A = np.maximum(A, A.T)
    np.fill_diagonal(A, 0.0)
    return A

def laplacian_from_affinity(A):
    A = np.maximum(A, 0.0)
    np.fill_diagonal(A, 0.0)
    d = A.sum(axis=1)
    dinv2 = 1.0 / np.sqrt(d + 1e-12)
    L = np.eye(A.shape[0]) - (dinv2[:,None] * A) * dinv2[None,:]
    return L

def normalized_laplacian(A):
    d = A.sum(axis=1)
    dinv2 = 1.0/np.sqrt(d + 1e-12)
    return np.eye(A.shape[0]) - (dinv2[:,None]*A)*dinv2[None,:]

def fiedler_lambda(L):
    vals = np.linalg.eigvalsh(L)
    vals = np.sort(np.real(vals))
    return float(vals[1]) if len(vals) >= 2 else float(vals[0])

def spectrum(L):
    vals = np.linalg.eigvalsh(L)
    return np.sort(np.real(vals))

def select_K_by_eigengap(A, Kmin=2, Kmax=10):
    """
    Автовыбор числа кластеров по eigengap нормализованного лапласиана.
    Возвращает K в [Kmin, Kmax], где разрыв λ_k+1 - λ_k максимален.
    """
    Lsym = normalized_laplacian(A)
    vals = np.linalg.eigvalsh(Lsym)
    vals = np.sort(np.real(vals))
    gaps = []
    cand_K = []
    for K in range(Kmin, min(Kmax, len(vals)-1) + 1):
        gaps.append(vals[K] - vals[K-1])
        cand_K.append(K)
    if not cand_K:
        return max(2, min(5, len(vals)))
    return int(cand_K[int(np.argmax(gaps))])

# ---------- спектральная кластеризация ----------
def spectral_kmeans(A, K, iters=30, seed=123):
    d = A.sum(axis=1)
    dinv2 = 1.0/np.sqrt(d+1e-12)
    Lsym = np.eye(A.shape[0]) - (dinv2[:,None]*A)*dinv2[None,:]
    vals, vecs = np.linalg.eigh(Lsym)
    X = vecs[:, :K]
    X = X / (np.linalg.norm(X, axis=1, keepdims=True) + 1e-12)
    rng = np.random.default_rng(seed)
    N = X.shape[0]
    centers = X[rng.choice(N, K, replace=False)]
    labels = np.zeros(N, dtype=int)
    for _ in range(iters):
        dists = ((X[:,None,:] - centers[None,:,:])**2).sum(axis=2)
        labels = dists.argmin(axis=1)
        new_centers = np.stack([X[labels==k].mean(axis=0) if np.any(labels==k) else centers[k] for k in range(K)], axis=0)
        if np.allclose(new_centers, centers):
            break
        centers = new_centers
    return labels

def balance_score(sizes, N):
    sizes = np.array(sizes, dtype=float)
    std = sizes.std()
    max_frac = sizes.max()/N
    return - (std + 10.0*max_frac)

# ---------- метрики по кластерам ----------
def cluster_metrics(C, labels):
    uniq = np.unique(labels)
    out = []
    for u in uniq:
        idx = np.where(labels == u)[0]
        n = len(idx)
        Cin = C[np.ix_(idx, idx)]
        mass = float(np.sum(np.abs(Cin)**2))
        Ain = np.abs(Cin)
        Lin = laplacian_from_affinity(Ain)
        lam2 = fiedler_lambda(Lin)
        out.append(dict(id=int(u), size=int(n), mass=mass, lambda2=lam2))
    out.sort(key=lambda d: d["size"], reverse=True)
    return out

# ---------- базовое 2D-встраивание (опционально) ----------
def embed_2d_from_rows(M, seed=42):
    X = M - M.mean(axis=0, keepdims=True)
    try:
        import umap
        reducer = umap.UMAP(n_neighbors=25, min_dist=0.15, random_state=seed, metric="euclidean")
        Z = reducer.fit_transform(X)
    except Exception:
        try:
            from sklearn.manifold import TSNE
            Z = TSNE(n_components=2, perplexity=30, learning_rate="auto", init="pca",
                     random_state=seed).fit_transform(X)
        except Exception:
            U, S, Vt = np.linalg.svd(X, full_matrices=False)
            Z = X @ Vt[:2].T
    return Z

# ---------- ТОЛЬКО ДЛЯ ВИЗУАЛИЗАЦИИ: кластерный layout ----------
def spectral_embed_2d(A):
    d = A.sum(axis=1)
    dinv2 = 1.0/np.sqrt(d+1e-12)
    Lsym = np.eye(A.shape[0]) - (dinv2[:,None]*A)*dinv2[None,:]
    vals, vecs = np.linalg.eigh(Lsym)
    if vecs.shape[1] >= 3:
        X = vecs[:, 1:3]
    else:
        X = vecs[:, :2]
    X = X / (np.linalg.norm(X, axis=1, keepdims=True) + 1e-12)
    X = X - X.mean(axis=0, keepdims=True)
    return X

def clustered_layout(A, labels, gap=7.0, cluster_scale=3.0,
                     jitter_std=1.12, radial_noise=1.10,
                     anisotropy=0.20, seed=2024):
    """
    Делает координаты ТОЛЬКО для визуализации:
      • кластеры разводит по окружности (gap);
      • внутри кластера точки образуют облако (джиттер, радиальная «лохматость»,
        слабая анизотропия), масштаб контролируется cluster_scale.
    """
    rng = np.random.default_rng(seed)
    labels = np.asarray(labels)
    uniq = np.unique(labels)
    K = len(uniq)
    N = A.shape[0]

    # центры кластеров на окружности
    angles = np.linspace(0, 2*np.pi, K, endpoint=False)
    centers = np.c_[np.cos(angles), np.sin(angles)] * gap

    coords = np.zeros((N, 2))
    for ci, u in enumerate(uniq):
        idx = np.where(labels == u)[0]
        if len(idx) == 1:
            # одиночная точка — прямо в центр кластера
            coords[idx[0]] = centers[ci] + rng.normal(0, jitter_std, size=2)
            continue

        # компактная форма кластера из его внутрикластерного графа
        A_sub = A[np.ix_(idx, idx)]
        X = spectral_embed_2d(A_sub)                 # (m x 2)

        # нормируем на радиус 1
        rmax = np.max(np.linalg.norm(X, axis=1)) + 1e-9
        X = X / rmax

        # 1) радиальная «лохматость»: растягиваем/сжимаем каждую точку по её радиусу
        radii = np.linalg.norm(X, axis=1, keepdims=True)
        rad_scale = 1.0 + radial_noise * rng.normal(0, 1, size=(len(idx), 1))
        X = X * (rad_scale * (0.85 + 0.15*radii))    # чуть сильнее у внешних

        # 2) локальная случайная анизотропия и поворот
        theta = rng.uniform(0, 2*np.pi)
        c, s = np.cos(theta), np.sin(theta)
        R = np.array([[c, -s], [s, c]])             # поворот
        # лёгкое неравное масштабирование по осям
        sx = 1.0 + anisotropy * rng.normal(0, 0.6)
        sy = 1.0 + anisotropy * rng.normal(0, 0.6)
        S = np.diag([sx, sy])
        X = X @ (R @ S)

        # 3) мелкий изотропный джиттер
        X += rng.normal(0, jitter_std, size=X.shape)

        # держим кластер компактным и пропорциональным размеру
        rmax = np.max(np.linalg.norm(X, axis=1)) + 1e-9
        X = X / rmax
        X = X / (1.0 + 0.006*len(idx))
        X = X * cluster_scale

        # перенос в центр кластера на окружности
        coords[idx] = X + centers[ci]

    return coords

# ---------- основной пресет ----------
def run_balanced_multicluster_preset():
    outdir = ensure_outdir("")

    params = dict(
        N=300, T=14.0, dt=0.05,
        beta=1.0, gamma=0.8, noise=0.10,
        xi=3.0, aniso=0.15,
        n_groups=5, grain_strength=0.30, inter_atten=0.55,
        k=3,                # kNN (локальная аффинность)
        seed=2024
    )

    print("=== Симуляция: «Сбалансированная многокластерность» (спектральная) ===")
    for k,v in params.items():
        print(f"{k} = {v}")

    # (1) интеграция и корреляции
    X, C, W = integrate(N=params['N'], T=params['T'], dt=params['dt'], beta=params['beta'],
                        gamma=params['gamma'], noise=params['noise'], xi=params['xi'],
                        aniso=params['aniso'], n_groups=params['n_groups'],
                        grain_strength=params['grain_strength'], inter_atten=params['inter_atten'],
                        seed=params['seed'])

    # (2) kNN-граф и метрики связности
    A_knn = knn_affinity_from_C(C, k=params['k'])
    L_knn = laplacian_from_affinity(A_knn)
    lam2_knn = fiedler_lambda(L_knn)
    L_absC = laplacian_from_affinity(np.abs(C))
    lam2_absC = fiedler_lambda(L_absC)

    # (3) авт. выбор числа кластеров по eigengap
    K = select_K_by_eigengap(A_knn, Kmin=2, Kmax=10)
    labels = spectral_kmeans(A_knn, K, iters=40, seed=params['seed'])
    sizes = np.array(sorted([int(np.sum(labels==u)) for u in range(K)], reverse=True))
    n_comp = len(sizes)

    print("\n--- Глобальные метрики ---")
    print(f"λ2 (kNN): {lam2_knn:.4f}  → ближе к 0 — легче распадается")
    print(f"λ2 (|C|): {lam2_absC:.4f}")
    print(f"Автовыбранное число кластеров K = {K} (eigengap) → размеры: {sizes.tolist()}")

    # (4) кластерные метрики
    clusters = cluster_metrics(C, labels)
    print("\n--- Метрики кластеров (спектральное разбиение) ---")
    for cl in clusters:
        print(f"Кластер {cl['id']:>2d}: размер={cl['size']:>3d}, масса={cl['mass']:.1f}, λ2-внутр={cl['lambda2']:.4f}")

    # --- рисунки ---
    # (1) теплокарта |C|
    plt.figure()
    plt.imshow(np.abs(C), origin='lower', interpolation='nearest')
    plt.title("Теплокарта |C|")
    plt.colorbar(fraction=0.046, pad=0.04)
    fig1_path = os.path.join(outdir, "balanced_multicluster_heatmap.png")
    plt.savefig(fig1_path, dpi=180, bbox_inches="tight")
    plt.close()

    # (2) кластерная раскладка узлов + рёбра kNN (только визуализация)
    coords = clustered_layout(A_knn, labels, gap=7.0, cluster_scale=3.0)
    plt.figure()
    N = params['N']
    for i in range(N):
        for j in range(i+1, N):
            if A_knn[i,j] <= 0:
                continue
            same = (labels[i] == labels[j])
            a = float(max(0.03, min(0.6, A_knn[i,j])))
            if not same:
                a *= 0.12
            plt.plot([coords[i,0], coords[j,0]], [coords[i,1], coords[j,1]], alpha=a)
    for u in range(n_comp):
        idx = np.where(labels == u)[0]
        plt.scatter(coords[idx,0], coords[idx,1], label=f"кластер {u} (n={len(idx)})", s=28)
    plt.title("Кластерная раскладка узлов C + рёбра kNN")
    plt.xlabel("ось 1"); plt.ylabel("ось 2"); plt.legend(loc="best", fontsize=8)
    fig2_path = os.path.join(outdir, "balanced_multicluster_pca_knn.png")
    plt.savefig(fig2_path, dpi=180, bbox_inches="tight")
    plt.close()

    # (3) спектры лапласианов
    spec_knn = spectrum(L_knn)
    spec_absC = spectrum(L_absC)
    plt.figure()
    plt.plot(spec_knn, marker='o', linestyle='None', label="спектр L (kNN)")
    plt.plot(spec_absC, marker='x', linestyle='None', label="спектр L (|C|)")
    plt.title("Спектры лапласианов")
    plt.xlabel("индекс собственного значения"); plt.ylabel("значение"); plt.legend()
    fig3_path = os.path.join(outdir, "balanced_multicluster_laplacian_spectra.png")
    plt.savefig(fig3_path, dpi=180, bbox_inches="tight")
    plt.close()

    print("\nСохранено:")
    print(f"- Теплокарта: {fig1_path}")
    print(f"- Кластерная картинка: {fig2_path}")
    print(f"- Спектры:   {fig3_path}")

if __name__ == "__main__":
    run_balanced_multicluster_preset()

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

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

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

4.3. Фазовый переход: Критическая точка

Самое интересное началось, когда мы стали плавно менять параметр, отвечающий за силу связей между кластерами. При достижении критического значения (~0.6) в системе произошел резкий качественный скачок.

Характерным признаком фазового перехода является ломаный рост G: график G(y) имеет излом (пороговый скачок наклона). Это означает, что включается гигантская компонента: множество ранее раздельных кластеров сливаются в одну преобладающую связную группу, охватывающую большую часть узлов.
Характерным признаком фазового перехода является ломаный рост G: график G(y) имеет излом (пороговый скачок наклона). Это означает, что включается гигантская компонента: множество ранее раздельных кластеров сливаются в одну преобладающую связную группу, охватывающую большую часть узлов.
Скрытый текст
# --------------------------------------------------------------
# Фазовый переход: многокластер → интеграция
# Автоскан по межгрупповой связности (inter_atten = γ) и сохранение
# четырёх графиков: K(γ), Q(γ), λ2(γ), G(γ).
# Никаких CSV — только картинки в ./out рядом со скриптом.
# --------------------------------------------------------------

import os
import argparse
import numpy as np
import matplotlib.pyplot as plt

# ---------- утилиты ----------
def ensure_outdir(path=""):
    script_dir = os.path.dirname(os.path.abspath(__file__))
    outdir = os.path.join(script_dir, path)
    os.makedirs(outdir, exist_ok=True)
    return outdir

def set_seed(seed=2024):
    np.random.seed(seed)

# ---------- генерация W с мягкой блочностью ----------
def build_W_grain(N, xi=3.0, aniso=0.15, n_groups=5, grain_strength=0.30, inter_atten=0.55):
    i = np.arange(N)[:, None]
    j = np.arange(N)[None, :]
    D = np.abs(i - j).astype(float)
    W = np.exp(-D / xi)
    np.fill_diagonal(W, 0.0)

    if aniso > 0:
        W += aniso * np.exp(-np.abs((i - j) - N/3) / (max(xi/2,1e-6)))
        W += aniso * np.exp(-np.abs((i - j) + N/3) / (max(xi/2,1e-6)))
        np.fill_diagonal(W, 0.0)

    sizes = [N // n_groups + (1 if r < (N % n_groups) else 0) for r in range(n_groups)]
    offs = np.cumsum([0] + sizes)
    group_id = np.zeros(N, dtype=int)
    for g in range(n_groups):
        lo, hi = offs[g], offs[g+1]
        group_id[lo:hi] = g
        W[lo:hi, lo:hi] *= (1.0 + grain_strength)

    G = (group_id[:,None] == group_id[None,:]).astype(float)
    W = W * (G + inter_atten*(1.0-G))
    np.fill_diagonal(W, 0.0)

    s = np.max(np.abs(np.linalg.eigvalsh(W)))
    if s > 1e-9:
        W = W / s
    return W

# ---------- динамика и корреляции ----------
def integrate(N=300, T=14.0, dt=0.05, beta=1.0, gamma=0.8, noise=0.10,
              xi=3.0, aniso=0.15, n_groups=5, grain_strength=0.30, inter_atten=0.55, seed=2024):
    set_seed(seed)
    steps = int(T / dt)
    W = build_W_grain(N, xi=xi, aniso=aniso, n_groups=n_groups,
                      grain_strength=grain_strength, inter_atten=inter_atten)
    x = 0.1 * np.random.randn(N)
    X = np.zeros((steps, N))
    sqrt_dt = np.sqrt(dt)
    for t in range(steps):
        x = (1 - gamma*dt) * x + beta*dt * np.tanh(W @ x) + noise * sqrt_dt * np.random.randn(N)
        X[t] = x

    Xc = X - X.mean(axis=0, keepdims=True)
    std = Xc.std(axis=0, keepdims=True) + 1e-12
    Xn = Xc / std
    C = (Xn.T @ Xn) / (Xn.shape[0] - 1)
    C = np.clip(C, -1.0, 1.0)
    return X, C, W

# ---------- граф и лапласиан ----------
def knn_affinity_from_C(C, k=3):
    S = np.abs(C)
    N = S.shape[0]
    D = 1.0 - S
    A = np.zeros_like(S)
    for i in range(N):
        order = np.argsort(D[i] + np.eye(N)[i]*1e9)
        nbrs = order[:k]
        A[i, nbrs] = S[i, nbrs]
    A = np.maximum(A, A.T)
    np.fill_diagonal(A, 0.0)
    return A

def laplacian_from_affinity(A):
    A = np.maximum(A, 0.0)
    np.fill_diagonal(A, 0.0)
    d = A.sum(axis=1)
    dinv2 = 1.0 / np.sqrt(d + 1e-12)
    L = np.eye(A.shape[0]) - (dinv2[:,None] * A) * dinv2[None,:]
    return L

def normalized_laplacian(A):
    d = A.sum(axis=1)
    dinv2 = 1.0/np.sqrt(d + 1e-12)
    return np.eye(A.shape[0]) - (dinv2[:,None]*A)*dinv2[None,:]

def fiedler_lambda(L):
    vals = np.linalg.eigvalsh(L)
    vals = np.sort(np.real(vals))
    return float(vals[1]) if len(vals) >= 2 else float(vals[0])

# ---------- спектральная кластеризация ----------
def spectral_kmeans(A, K, iters=30, seed=123):
    d = A.sum(axis=1)
    dinv2 = 1.0/np.sqrt(d+1e-12)
    Lsym = np.eye(A.shape[0]) - (dinv2[:,None]*A)*dinv2[None,:]
    vals, vecs = np.linalg.eigh(Lsym)
    X = vecs[:, :K]
    X = X / (np.linalg.norm(X, axis=1, keepdims=True) + 1e-12)
    rng = np.random.default_rng(seed)
    N = X.shape[0]
    centers = X[rng.choice(N, K, replace=False)]
    labels = np.zeros(N, dtype=int)
    for _ in range(iters):
        dists = ((X[:,None,:] - centers[None,:,:])**2).sum(axis=2)
        labels = dists.argmin(axis=1)
        new_centers = np.stack([X[labels==k].mean(axis=0) if np.any(labels==k) else centers[k] for k in range(K)], axis=0)
        if np.allclose(new_centers, centers):
            break
        centers = new_centers
    return labels

def select_K_by_eigengap(A, Kmin=2, Kmax=10):
    Lsym = normalized_laplacian(A)
    vals = np.linalg.eigvalsh(Lsym)
    vals = np.sort(np.real(vals))
    gaps = []
    cand_K = []
    for K in range(Kmin, min(Kmax, len(vals)-1) + 1):
        gaps.append(vals[K] - vals[K-1])
        cand_K.append(K)
    if not cand_K:
        return max(2, min(5, len(vals)))
    return int(cand_K[int(np.argmax(gaps))])

# ---------- метрики ----------
def modularity_Q(A, labels):
    """Взвешенная модульность Ньюмана–Гирвана (векторизованно)."""
    A = np.maximum(A, 0.0)
    m2 = A.sum()  # = 2m
    if m2 <= 1e-12:
        return 0.0
    k = A.sum(axis=1)
    labels = np.asarray(labels)
    uniq = np.unique(labels)
    Q = 0.0
    for s in uniq:
        idx = (labels == s)
        e_s = float(A[np.ix_(idx, idx)].sum()) / m2
        a_s = float(k[idx].sum()) / m2
        Q += e_s - a_s*a_s
    return float(Q)

def giant_component_fraction_from_absC(C, percentile=88.0):
    """Доля узлов в крупнейшей компоненте порогового графа по |C| (вне диагонали)."""
    N = C.shape[0]
    A = np.zeros((N,N), dtype=int)
    S = np.abs(C).copy()
    triu = np.triu_indices(N, 1)
    vals = S[triu]
    thr = float(np.percentile(vals, percentile))
    A[(S >= thr) & (~np.eye(N, dtype=bool))] = 1
    A = np.maximum(A, A.T)
    seen = np.zeros(N, dtype=bool)
    best = 0
    for s in range(N):
        if seen[s]: continue
        q = [s]; seen[s]=True; cnt=1
        while q:
            u = q.pop()
            neigh = np.where(A[u]>0)[0]
            for v in neigh:
                if not seen[v]:
                    seen[v]=True
                    q.append(v)
                    cnt += 1
        if cnt > best: best = cnt
    return best/float(N)

# ---------- основной скан ----------
def run_phase_transition_sweep(
    inter_atten_values=None, percentile=88.0, T=14.0, N=300,
    verbose=False,
    xi=3.0, aniso=0.15, n_groups=5, grain_strength=0.30, k=3,
    beta=1.0, gamma=0.8, noise=0.10, seed=2024
):
    outdir = ensure_outdir("")
    print("=== Фазовый скан: многокластер → интеграция ===")
    print(f"N={N}, T={T}, k={k}, percentile={percentile}, seed={seed}")
    print(f"xi={xi}, aniso={aniso}, n_groups={n_groups}, grain_strength={grain_strength}")
    if inter_atten_values is None:
        inter_atten_values = np.linspace(0.35, 0.95, 12)

    Ks, L2s, L2abs, Qs, Gs = [], [], [], [], []
    xs = []

    for ia in inter_atten_values:
        if verbose:
            print(f"γ={ia:.3f} → интеграция и метрики...")
        X,C,W = integrate(N=N, T=T, dt=0.05, beta=beta, gamma=gamma, noise=noise,
                          xi=xi, aniso=aniso, n_groups=n_groups, grain_strength=grain_strength,
                          inter_atten=float(ia), seed=seed)
        A = knn_affinity_from_C(C, k=k)
        L_knn = laplacian_from_affinity(A)
        lam2_knn = fiedler_lambda(L_knn)
        L_absC = laplacian_from_affinity(np.abs(C))
        lam2_absC = fiedler_lambda(L_absC)

        K = select_K_by_eigengap(A, Kmin=2, Kmax=10)
        labels = spectral_kmeans(A, K, iters=30, seed=seed)
        Q = modularity_Q(A, labels)
        G = giant_component_fraction_from_absC(C, percentile=percentile)

        xs.append(float(ia)); Ks.append(K); L2s.append(lam2_knn); L2abs.append(lam2_absC); Qs.append(Q); Gs.append(G)
        if verbose:
            print(f"   K={K}, λ2(kNN)={lam2_knn:.4f}, Q={Q:.4f}, G={G:.3f}")

    xs = np.array(xs)

    # Плоты (каждый отдельно, без заданных цветов/стилей)
    plt.figure(); plt.plot(xs, Ks, marker='o'); plt.xlabel("inter_atten γ"); plt.ylabel("K (eigengap)")
    plt.title("Число кластеров K по eigengap vs γ")
    plt.savefig(os.path.join(outdir, "phase_K_vs_gamma.png"), dpi=180, bbox_inches="tight"); plt.close()

    plt.figure(); plt.plot(xs, Qs, marker='o'); plt.xlabel("inter_atten γ"); plt.ylabel("модульность Q")
    plt.title("Модульность Q vs γ")
    plt.savefig(os.path.join(outdir, "phase_Q_vs_gamma.png"), dpi=180, bbox_inches="tight"); plt.close()

    plt.figure(); plt.plot(xs, L2s, marker='o'); plt.xlabel("inter_atten γ"); plt.ylabel("λ2 (kNN)")
    plt.title("Второе собственное λ2(kNN) vs γ")
    plt.savefig(os.path.join(outdir, "phase_lambda2_vs_gamma.png"), dpi=180, bbox_inches="tight"); plt.close()

    plt.figure(); plt.plot(xs, Gs, marker='o'); plt.xlabel("inter_atten γ"); plt.ylabel("доля гигантской компоненты G")
    plt.title(f"Перколяция по |C| (порог p={percentile:.0f}%)")
    plt.savefig(os.path.join(outdir, "phase_G_vs_gamma.png"), dpi=180, bbox_inches="tight"); plt.close()

    print("Готово. Сохранены картинки в:", outdir)

# ---------- CLI ----------
def parse_args():
    ap = argparse.ArgumentParser(description="Фазовый переход: многокластер → интеграция")
    ap.add_argument("--quiet", action="store_true", help="не печатать прогресс")
    ap.add_argument("--gamma-start", type=float, default=0.35, help="начальное γ (inter_atten)")
    ap.add_argument("--gamma-end", type=float, default=0.95, help="конечное γ (inter_atten)")
    ap.add_argument("--points", type=int, default=12, help="число точек скана")
    ap.add_argument("--percentile", type=float, default=88.0, help="перцентиль для порога |C| в G")
    ap.add_argument("--T", type=float, default=14.0, help="время интеграции на точку")
    ap.add_argument("--N", type=int, default=300, help="число узлов")
    ap.add_argument("--seed", type=int, default=2024, help="сид")
    # продвинутые (оставим по умолчанию как в мультикластере)
    ap.add_argument("--xi", type=float, default=3.0)
    ap.add_argument("--aniso", type=float, default=0.15)
    ap.add_argument("--n-groups", type=int, default=5)
    ap.add_argument("--grain-strength", type=float, default=0.30)
    ap.add_argument("--k", type=int, default=3)
    ap.add_argument("--beta", type=float, default=1.0)
    ap.add_argument("--gamma", dest="gamma_decay", type=float, default=0.8)
    ap.add_argument("--noise", type=float, default=0.10)
    return ap.parse_args()

if __name__ == "__main__":
    args = parse_args()
    gammas = np.linspace(args.gamma_start, args.gamma_end, args.points)
    run_phase_transition_sweep(
        inter_atten_values=gammas,
        verbose=not args.quiet,
        percentile=args.percentile,
        T=args.T, N=args.N, xi=args.xi, aniso=args.aniso,
        n_groups=args.n_groups, grain_strength=args.grain_strength, k=args.k,
        beta=args.beta, gamma=args.gamma_decay, noise=args.noise, seed=args.seed
    )

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

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

4.4. Дыхание кластеров: Динамика во времени

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

Фрагмент динамики сети. Кластеры не рассыпаются, при дальнейшей симуляции происходит реконфигурация кластеров, но они остаются в рамках своих КП.
Фрагмент динамики сети. Кластеры не рассыпаются, при дальнейшей симуляции происходит реконфигурация кластеров, но они остаются в рамках своих КП.
На проекциях Diffusion Maps в разные моменты видны примерно три облака узлов, соответствующие кластерам, окрашенные тремя цветами для наглядности (см. рисунок time_embed_t_9.50_gamma_0.62.png для среднего времени).
На проекциях Diffusion Maps в разные моменты видны примерно три облака узлов, соответствующие кластерам, окрашенные тремя цветами для наглядности (см. рисунок time_embed_t_9.50_gamma_0.62.png для среднего времени).
Скрытый текст
# ---------------------------------------------------------------------------
# • Один запуск без аргументов
# • Строит метрики во времени (по скользящему окну |C|) и 3 снимка эмбеддинга
# • Стабильность:
#     - ограничение авто-K по eigengap: Kmax=4
#     - burn-in по времени (игнорируем самые ранние окна)
#     - медианное сглаживание метрик (окно=3)
# • (Опционально) сохраняет анимацию эмбеддинга в MP4 (FFmpeg) или GIF (fallback)
# • В терминал печатает ключевые метрики и пути к файлам
# ---------------------------------------------------------------------------

import os
import numpy as np
import matplotlib.pyplot as plt
import itertools

# ===== ПРЕСЕТЫ =====
N = 300
T = 14.0
dt = 0.05
beta = 1.0
gamma_decay = 0.8
noise = 0.10
seed = 2024

xi = 3.0
aniso = 0.15
n_groups = 5
grain_strength = 0.30
k = 3

# Выбранное состояние (около порога)
GAMMA_INTER = 0.62

# Скользящее окно и снимки
WIN_SEC = 3.0
STEP_FRAC = 0.01
BURNIN_SEC = 2.0            # игнорируем самые ранние окна
SNAP_FRACS = [0.25, 0.62, 0.95]

# Стабилизация кластеризации
KMAX_EIGENGAP = 4           # авто-K не выше 4
K_VISUAL = 3                # фиксированное K для раскраски снимков

# Рисование рёбер
DRAW_EDGES = True
INTRA_ONLY = True
EDGE_TOP_PERCENT = 85.0

# Анимация
SMOOTH_ALPHA = 0.99   # 0..1; ближе к 1 — плавнее, но «тяжелее»
MAKE_ANIMATION = True
ANIM_FPS = 25
ANIM_INTRA_ONLY = False          # показывать и межкластерные «мосты»
ANIM_EDGE_TOP_PERCENT = 95.0
ANIM_BASENAME = f"time_embed_anim_gamma_{GAMMA_INTER:.2f}"

# ===== Утилиты =====
import itertools

def procrustes_align(Z, Z_ref):
    """Ортогональное выравнивание текущих координат к предыдущим (снимает повороты/отражения)."""
    # центрируем
    Zc = Z - Z.mean(axis=0, keepdims=True)
    Rc = Z_ref - Z_ref.mean(axis=0, keepdims=True)
    # матрица кросс-ковариации и SVD
    M = Zc.T @ Rc
    U, _, Vt = np.linalg.svd(M)
    R = U @ Vt
    return Zc @ R  # выровненные координаты (как Z_ref по ориентации)

def relabel_to_match(prev_labels, new_labels, Z_ref, Z_curr, K=None):
    """Перенумеровывает кластеры текущего кадра так, чтобы соответствовать предыдущему."""
    if K is None:
        K = len(np.unique(prev_labels))
    # центры в прежнем и текущем кадре
    prev_cent = np.stack([Z_ref[prev_labels==k].mean(axis=0) for k in range(K)], axis=0)
    curr_cent = np.stack([Z_curr[new_labels==k].mean(axis=0) for k in range(K)], axis=0)
    best_perm, best_cost = None, np.inf
    for perm in itertools.permutations(range(K)):
        cost = 0.0
        for i, j in enumerate(perm):
            cost += np.sum((curr_cent[j] - prev_cent[i])**2)
        if cost < best_cost:
            best_cost, best_perm = cost, perm
    # применяем лучшую перестановку
    mapping = {j:i for i, j in enumerate(best_perm)}
    remapped = np.array([mapping[l] for l in new_labels], dtype=int)
    return remapped

def ensure_outdir(path=""):
    script_dir = os.path.dirname(os.path.abspath(__file__))
    outdir = os.path.join(script_dir, path)
    os.makedirs(outdir, exist_ok=True)
    return outdir

def set_seed(s):
    np.random.seed(s)

def rolling_median(x, w=3):
    x = np.asarray(x, dtype=float)
    if w <= 1 or x.size == 0:
        return x
    half = w // 2
    # паддинг краёв значениями краёв
    xp = np.pad(x, (half, half), mode='edge')
    out = np.empty_like(x)
    for i in range(x.size):
        out[i] = np.median(xp[i:i+w])
    return out

# ===== Генерация W =====
def build_W_grain(N, xi=xi, aniso=aniso, n_groups=n_groups, grain_strength=grain_strength, inter_atten=0.55):
    i = np.arange(N)[:, None]; j = np.arange(N)[None, :]
    D = np.abs(i - j).astype(float)
    W = np.exp(-D / xi); np.fill_diagonal(W, 0.0)
    if aniso > 0:
        W += aniso * np.exp(-np.abs((i - j) - N/3) / (max(xi/2,1e-6)))
        W += aniso * np.exp(-np.abs((i - j) + N/3) / (max(xi/2,1e-6)))
        np.fill_diagonal(W, 0.0)
    sizes = [N // n_groups + (1 if r < (N % n_groups) else 0) for r in range(n_groups)]
    offs = np.cumsum([0] + sizes)
    group_id = np.zeros(N, dtype=int)
    for g in range(n_groups):
        lo, hi = offs[g], offs[g+1]
        group_id[lo:hi] = g
        W[lo:hi, lo:hi] *= (1.0 + grain_strength)
    Gm = (group_id[:,None] == group_id[None,:]).astype(float)
    W = W * (Gm + inter_atten*(1.0-Gm))
    np.fill_diagonal(W, 0.0)
    s = np.max(np.abs(np.linalg.eigvalsh(W)))
    if s > 1e-9: W = W / s
    return W

# ===== Интеграция =====
def integrate_all(N=N, T=T, dt=dt, beta=beta, gamma=gamma_decay, noise=noise,
                  xi=xi, aniso=aniso, n_groups=n_groups, grain_strength=grain_strength,
                  inter_atten=GAMMA_INTER, seed=seed):
    set_seed(seed)
    steps = int(T/dt)
    W = build_W_grain(N, xi=xi, aniso=aniso, n_groups=n_groups,
                      grain_strength=grain_strength, inter_atten=inter_atten)
    x = 0.1*np.random.randn(N)
    X = np.zeros((steps, N))
    sqrt_dt = np.sqrt(dt)
    for t in range(steps):
        x = (1 - gamma*dt)*x + beta*dt*np.tanh(W @ x) + noise*sqrt_dt*np.random.randn(N)
        X[t] = x
    return X

def corr_from_window(Xseg):
    Xc = Xseg - Xseg.mean(axis=0, keepdims=True)
    std = Xc.std(axis=0, keepdims=True) + 1e-12
    Xn  = Xc / std
    C = (Xn.T @ Xn) / (Xn.shape[0] - 1)
    return np.clip(C, -1.0, 1.0)

# ===== Граф и метрики =====
def knn_affinity_from_C(C, k=k):
    S = np.abs(C); Nloc = S.shape[0]
    D = 1.0 - S; A = np.zeros_like(S)
    for i in range(Nloc):
        order = np.argsort(D[i] + np.eye(Nloc)[i]*1e9)
        nbrs = order[:k]; A[i, nbrs] = S[i, nbrs]
    A = np.maximum(A, A.T); np.fill_diagonal(A, 0.0)
    return A

def normalized_laplacian(A):
    d = A.sum(axis=1)
    dinv2 = 1.0/np.sqrt(d + 1e-12)
    return np.eye(A.shape[0]) - (dinv2[:,None]*A)*dinv2[None,:]

def laplacian_from_affinity(A):
    d = A.sum(axis=1)
    dinv2 = 1.0/np.sqrt(d + 1e-12)
    return np.eye(A.shape[0]) - (dinv2[:,None]*A)*dinv2[None,:]

def fiedler_lambda(L):
    vals = np.linalg.eigvalsh(L)
    vals = np.sort(np.real(vals))
    return float(vals[1]) if len(vals) >= 2 else float(vals[0])

def modularity_Q(A, labels):
    m = A.sum()/2.0 + 1e-12
    d = A.sum(axis=1)
    Q = 0.0
    for c in np.unique(labels):
        idx = np.where(labels==c)[0]
        As = A[np.ix_(idx, idx)].sum()
        ds = d[idx].sum()
        Q += (As/(2*m) - (ds/(2*m))**2)
    return float(Q)

def spectral_kmeans(A, K, iters=30, seed=seed):
    d = A.sum(axis=1)
    dinv2 = 1.0/np.sqrt(d+1e-12)
    Lsym = np.eye(A.shape[0]) - (dinv2[:,None]*A)*dinv2[None,:]
    vals, vecs = np.linalg.eigh(Lsym)
    Xemb = vecs[:, :K]
    Xemb = Xemb / (np.linalg.norm(Xemb, axis=1, keepdims=True) + 1e-12)
    rng = np.random.default_rng(seed)
    Nloc = Xemb.shape[0]
    centers = Xemb[rng.choice(Nloc, K, replace=False)]
    labels = np.zeros(Nloc, dtype=int)
    for _ in range(iters):
        dists = ((Xemb[:,None,:] - centers[None,:,:])**2).sum(axis=2)
        labels = dists.argmin(axis=1)
        new_centers = np.stack([Xemb[labels==k].mean(axis=0) if np.any(labels==k) else centers[k] for k in range(K)], axis=0)
        if np.allclose(new_centers, centers): break
        centers = new_centers
    return labels

def select_K_by_eigengap(A, Kmin=2, Kmax=KMAX_EIGENGAP):
    Lsym = normalized_laplacian(A)
    vals = np.linalg.eigvalsh(Lsym); vals = np.sort(np.real(vals))
    gaps = []; cand = []
    upper = int(min(Kmax, max(Kmin, len(vals)-1)))
    for K in range(Kmin, upper+1):
        gaps.append(vals[K]-vals[K-1]); cand.append(K)
    if not cand: return max(2, min(5, len(vals)-1))
    return int(cand[int(np.argmax(gaps))])

def giant_component_fraction_from_absC(C, percentile=88.0):
    Nloc = C.shape[0]; S = np.abs(C).copy()
    A = np.zeros((Nloc,Nloc), dtype=int)
    triu = np.triu_indices(Nloc, 1)
    thr = float(np.percentile(S[triu], percentile))
    A[(S >= thr) & (~np.eye(Nloc, dtype=bool))] = 1
    A = np.maximum(A, A.T)
    seen = np.zeros(Nloc, dtype=bool); best = 0
    for s in range(Nloc):
        if seen[s]: continue
        q = [s]; seen[s]=True; cnt=1
        while q:
            u = q.pop()
            for v in np.where(A[u]>0)[0]:
                if not seen[v]:
                    seen[v]=True; q.append(v); cnt+=1
        best = max(best, cnt)
    return best/float(Nloc)

# ===== Эмбеддинг (Diffusion Maps) =====
def embed_diffusion(A, dim=2, t=1.0):
    d = A.sum(axis=1) + 1e-12
    P = (A.T / d).T
    vals, vecs = np.linalg.eig(P)
    vals = np.real(vals); vecs = np.real(vecs)
    order = np.argsort(-np.abs(vals))
    vals = vals[order]; vecs = vecs[:, order]
    U = vecs[:, 1:dim+1]
    lam = vals[1:dim+1]
    U = U * (lam[np.newaxis, :]**t)
    U = (U - U.mean(axis=0)) / (U.std(axis=0) + 1e-9)
    return U

# ===== Рисование =====
def plot_embedding(A, Z, labels, title, path, draw_edges=True, intra_only=True, edge_top_percent=85.0,
                   xlim=None, ylim=None):
    plt.figure()
    ax = plt.gca()
    if draw_edges:
        wpos = A[A > 0.0]
        thr = np.percentile(wpos, edge_top_percent) if wpos.size>0 else np.inf
        wmax = wpos.max() if wpos.size>0 else 1.0
        Nloc = A.shape[0]
        for i in range(Nloc):
            for j in range(i+1, Nloc):
                wij = A[i,j]
                if wij <= thr: continue
                if intra_only and labels[i]!=labels[j]: continue
                alpha = max(0.05, min(0.6, float((wij-thr)/(wmax-thr+1e-9)+0.05)))
                plt.plot([Z[i,0], Z[j,0]], [Z[i,1], Z[j,1]], alpha=alpha)
    for u in np.unique(labels):
        idx = np.where(labels==u)[0]
        plt.scatter(Z[idx,0], Z[idx,1], s=22, alpha=0.9, edgecolors='none', label=f"кластер {int(u)} (n={len(idx)})")

    ax.set_aspect('equal', 'box')
    if xlim is not None: ax.set_xlim(*xlim)
    if ylim is not None: ax.set_ylim(*ylim)
    plt.title(title); plt.legend(loc="best", fontsize=8)
    plt.xlabel("ось 1"); plt.ylabel("ось 2")
    plt.savefig(path, dpi=180, bbox_inches="tight"); plt.close()

# ===== Анимация =====
def save_animation(frames, xlim, ylim, outdir, fps=8, intra_only=False, edge_top_percent=92.0, basename="anim"):
    import matplotlib.pyplot as plt
    import matplotlib.animation as animation

    fig, ax = plt.subplots()
    ax.set_aspect('equal', 'box')
    if xlim is not None: ax.set_xlim(*xlim)
    if ylim is not None: ax.set_ylim(*ylim)

    def draw_frame(idx):
        ax.clear()
        ax.set_aspect('equal', 'box')
        if xlim is not None: ax.set_xlim(*xlim)
        if ylim is not None: ax.set_ylim(*ylim)
        t_mid, A, Z, labels = frames[idx]

        # рёбра (только сильные)
        wpos = A[A > 0.0]
        thr = np.percentile(wpos, edge_top_percent) if wpos.size>0 else np.inf
        wmax = wpos.max() if wpos.size>0 else 1.0
        Nloc = A.shape[0]
        for i in range(Nloc):
            for j in range(i+1, Nloc):
                wij = A[i,j]
                if wij <= thr: continue
                if intra_only and labels[i]!=labels[j]: continue
                alpha = max(0.05, min(0.6, float((wij-thr)/(wmax-thr+1e-9)+0.05)))
                ax.plot([Z[i,0], Z[j,0]], [Z[i,1], Z[j,1]], alpha=alpha)

        # точки
        for u in np.unique(labels):
            idxs = np.where(labels==u)[0]
            ax.scatter(Z[idxs,0], Z[idxs,1], s=22, alpha=0.9, edgecolors='none', label=f"кл {int(u)} (n={len(idxs)})")

        ax.legend(loc="best", fontsize=8)
        ax.set_title(f"Эмерджентное пространство (t={t_mid:.2f}, γ={GAMMA_INTER:.2f})")
        ax.set_xlabel("ось 1"); ax.set_ylabel("ось 2")
        return []

    ani = animation.FuncAnimation(fig, draw_frame, frames=len(frames), interval=1000/fps, blit=False)

    # Сохраняем GIF через PillowWriter (без imageio/ffmpeg)
    try:
        from matplotlib.animation import PillowWriter
        gif_path = os.path.join(outdir, f"{basename}.gif")
        ani.save(gif_path, writer=PillowWriter(fps=fps))
        plt.close(fig)
        return gif_path
    except Exception:
        plt.close(fig)
        return None


# ===== Основной сценарий =====
def run():
    outdir = ensure_outdir("")
    print("=== Динамика во времени (стабилизировано) ===")
    print(f"N={N}, T={T}, k={k}, seed={seed}, γ={GAMMA_INTER}")
    print(f"xi={xi}, aniso={aniso}, n_groups={n_groups}, grain_strength={grain_strength}")
    print(f"окно={WIN_SEC}s, шаг={STEP_FRAC*100:.0f}%, burn-in={BURNIN_SEC}s")

    # Интеграция полного ряда
    X = integrate_all(inter_atten=GAMMA_INTER)

    steps = X.shape[0]
    WINS = int(WIN_SEC/dt)
    STRIDE = max(1, int(WINS*STEP_FRAC))
    BURN = int(BURNIN_SEC/dt)

    times = []
    K_series, Q_series, L2_series, G_series = [], [], [], []
    windows = []

    # Скользящие окна (с учётом burn-in)
    for t1 in range(BURN + WINS, steps+1, STRIDE):
        t0 = t1 - WINS
        Xseg = X[t0:t1]
        C = corr_from_window(Xseg)
        A = knn_affinity_from_C(C, k=k)

        Kauto = select_K_by_eigengap(A, Kmin=2, Kmax=KMAX_EIGENGAP)
        labels = spectral_kmeans(A, Kauto, iters=30, seed=seed)
        Q = modularity_Q(A, labels)
        L2 = fiedler_lambda(laplacian_from_affinity(A))
        G = giant_component_fraction_from_absC(C, percentile=88.0)

        t_mid = (t0 + t1)/2 * dt
        times.append(t_mid); K_series.append(Kauto); Q_series.append(Q); L2_series.append(L2); G_series.append(G)

        windows.append((t_mid, C, A))

    # Сглаживание метрик
    K_sm = rolling_median(K_series, 3)
    Q_sm = rolling_median(Q_series, 3)
    L2_sm = rolling_median(L2_series, 3)
    G_sm = rolling_median(G_series, 3)

    # --- Графики метрик (рисуем только сглаженные) ---
    def lineplot(t, y, ylabel, fname):
        plt.figure()
        plt.plot(t, y, marker='o', lw=1.5)
        plt.xlabel("время"); plt.ylabel(ylabel); plt.title(f"{ylabel} во времени (γ={GAMMA_INTER})")
        plt.grid(alpha=0.25); plt.savefig(os.path.join(outdir, fname), dpi=180, bbox_inches="tight"); plt.close()

    lineplot(times, K_sm, "K (eigengap, сглаж.)", f"time_K_vs_t_gamma_{GAMMA_INTER:.2f}.png")
    lineplot(times, Q_sm, "модульность Q (сглаж.)", f"time_Q_vs_t_gamma_{GAMMA_INTER:.2f}.png")
    lineplot(times, L2_sm, "λ₂(kNN) (сглаж.)", f"time_lambda2_vs_t_gamma_{GAMMA_INTER:.2f}.png")
    lineplot(times, G_sm, "доля G (p=88%, сглаж.)", f"time_G_vs_t_gamma_{GAMMA_INTER:.2f}.png")

    # --- Снимки эмбеддинга (3 шт.) ---
    mids = np.array([w[0] for w in windows])
    picks = []
    for frac in SNAP_FRACS:
        target = BURNIN_SEC + frac * (T - BURNIN_SEC)  # выбираем после burn-in
        if mids.size == 0:
            continue
        idx = int(np.argmin(np.abs(mids - target)))
        picks.append(idx)
    picks = sorted(set(picks))

    # Единые оси для снимков
    Zs = []
    EMB = []
    for idx in picks:
        _, C, A = windows[idx]
        Z = embed_diffusion(A, dim=2, t=1.0)
        labels_vis = spectral_kmeans(A, K_VISUAL, iters=40, seed=seed)
        EMB.append((idx, A, Z, labels_vis))
        Zs.append(Z)
    if Zs:
        allZ = np.vstack(Zs)
        xmin, xmax = float(allZ[:,0].min()), float(allZ[:,0].max())
        ymin, ymax = float(allZ[:,1].min()), float(allZ[:,1].max())
        dx, dy = xmax-xmin, ymax-ymin
        padx, pady = 0.08*dx, 0.08*dy
        xlim = (xmin-padx, xmax+padx); ylim = (ymin-pady, ymax+pady)
    else:
        xlim = ylim = None

    for idx, A, Z, labels_vis in EMB:
        t_mid, C, _ = windows[idx]
        # метрики по этому окну
        Kauto = select_K_by_eigengap(A, Kmin=2, Kmax=KMAX_EIGENGAP)
        L2 = fiedler_lambda(laplacian_from_affinity(A))
        G = giant_component_fraction_from_absC(C, percentile=88.0)
        sizes_vis = [int(np.sum(labels_vis==u)) for u in np.unique(labels_vis)]
        fname = os.path.join(outdir, f"time_embed_t_{t_mid:.2f}_gamma_{GAMMA_INTER:.2f}.png")
        title = (f"Эмерджентное пространство (DiffMaps), t={t_mid:.2f}, γ={GAMMA_INTER:.2f}; "
             f"K_auto={Kauto}, K_vis={K_VISUAL}, λ2={L2:.4f}, G={G:.3f}, "
             f"размеры(K_vis)={sorted(sizes_vis, reverse=True)}")
        plot_embedding(A, Z, labels_vis, title, fname,
                       draw_edges=DRAW_EDGES, intra_only=INTRA_ONLY, edge_top_percent=EDGE_TOP_PERCENT,
                       xlim=xlim, ylim=ylim)
        print(f"   [SNAP] t={t_mid:.2f}: K_auto={Kauto}, λ2={L2:.4f}, G={G:.3f} → {fname}")

    # --- Анимация ---
    # --- Анимация ---
    if MAKE_ANIMATION and len(windows) >= 2:
        # Собираем кадры с выравниванием и EMA-сглаживанием координат
        frames = []
        allZ_anim = []

        Z_ref = None
        labels_ref = None
        for (t_mid, C, A) in windows:
            # эмбеддинг текущего окна
            Z_raw = embed_diffusion(A, dim=2, t=1.0)
            # окраска (фикс. K_VISUAL) — затем стабилизируем перестановку меток
            labels_curr = spectral_kmeans(A, K_VISUAL, iters=30, seed=seed)

            if Z_ref is None:
                # первый кадр — берём как есть
                Z_smooth = Z_raw
            else:
                # 1) выравниваем ориентацию к предыдущему
                Z_aligned = procrustes_align(Z_raw, Z_ref)
                # 2) согласовываем метки по кластерам
                labels_curr = relabel_to_match(labels_ref, labels_curr, Z_ref, Z_aligned, K=K_VISUAL)
                # 3) EMA-сглаживание координат
                Z_smooth = SMOOTH_ALPHA * Z_ref + (1.0 - SMOOTH_ALPHA) * Z_aligned

            frames.append((t_mid, A, Z_smooth, labels_curr))
            allZ_anim.append(Z_smooth)
            Z_ref = Z_smooth
            labels_ref = labels_curr

        # Единые оси для всей анимации
        allZ_anim = np.vstack(allZ_anim)
        xmin, xmax = float(allZ_anim[:,0].min()), float(allZ_anim[:,0].max())
        ymin, ymax = float(allZ_anim[:,1].min()), float(allZ_anim[:,1].max())
        dx, dy = xmax - xmin, ymax - ymin
        padx, pady = 0.08*dx, 0.08*dy
        xlim_anim = (xmin - padx, xmax + padx)
        ylim_anim = (ymin - pady, ymax + pady)

        anim_path = save_animation(frames, xlim_anim, ylim_anim, outdir,
                                fps=ANIM_FPS, intra_only=ANIM_INTRA_ONLY,
                                edge_top_percent=ANIM_EDGE_TOP_PERCENT,
                                basename=ANIM_BASENAME)
        if anim_path:
            print("   [ANIM] Сохранена анимация:", anim_path)
        else:
            print("   [ANIM] Не удалось сохранить анимацию (нет подходящего writer'а)")


    print("Готово. Картинки в:", outdir)

if __name__ == "__main__":
    run()

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

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

4.5. Эмерджентное пространство: Геометрия из ничего

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

На карте эмбеддинга узлы формируют три отчётливо раздельных облака. Хотя внутри каждого облака узлы переплетены сетью связей, между кластерами почти отсутствуют явные мостики.
На карте эмбеддинга узлы формируют три отчётливо раздельных облака. Хотя внутри каждого облака узлы переплетены сетью связей, между кластерами почти отсутствуют явные мостики.
Скрытый текст
# --------------------------------------------------------------
# • Три состояния γ: до / около / после порога (один запуск, без аргументов)
# • Терминал: K_auto, K_vis, размеры, λ2(kNN), G(p=88%), пути к PNG
# • Визуализация:
#   - авто-K ограничен MAX_K=4 (стабильнее)
#   - раскраска по фикс. K_VISUAL=3 (единый вид)
#   - рисуем только сильные ВНУТРИКЛАСТЕРНЫЕ рёбра
#   - ОДНИ И ТЕ ЖЕ ПРЕДЕЛЫ ОСЕЙ для всех кадров (LOCK_AXES=True)
#   - ДОП. КАДР для γ=0.90 с межкластерными рёбрами (видны «мосты»)
# --------------------------------------------------------------

import os
import numpy as np
import matplotlib.pyplot as plt

# ---------- базовые параметры ----------
N = 300
T = 14.0
dt = 0.05
beta = 1.0
gamma_decay = 0.8
noise = 0.10
seed = 2024

xi = 3.0
aniso = 0.15
n_groups = 5
grain_strength = 0.30
k = 3

MAX_K = 4          # авто-K по eigengap не выше 4
K_VISUAL = 3       # фиксированное K для окраски

# три характерные точки γ
gammas = [0.40, 0.62, 0.90]

# отрисовка рёбер (основные кадры)
DRAW_EDGES = True
INTRA_ONLY = True           # только внутрикластерные рёбра (по K_VISUAL)
EDGE_TOP_PERCENT = 85.0     # верхние 15% по силе

# общий масштаб осей для всех кадров
LOCK_AXES = True
AXIS_PADDING = 0.08         # доля от диапазона

# дополнительный кадр «перемычки» для наглядности интеграции
MAKE_ALT_VIEW = True
ALT_VIEW_GAMMA = 0.90
ALT_INTRA_ONLY = False
ALT_EDGE_TOP_PERCENT = 92.0
ALT_SUFFIX = "_bridges"

# ---------- утилиты ----------
def ensure_outdir(path=""):
    script_dir = os.path.dirname(os.path.abspath(__file__))
    outdir = os.path.join(script_dir, path)
    os.makedirs(outdir, exist_ok=True)
    return outdir

def set_seed(s=seed):
    np.random.seed(s)

# ---------- генерация W ----------
def build_W_grain(N, xi=xi, aniso=aniso, n_groups=n_groups, grain_strength=grain_strength, inter_atten=0.55):
    i = np.arange(N)[:, None]
    j = np.arange(N)[None, :]
    D = np.abs(i - j).astype(float)
    W = np.exp(-D / xi)
    np.fill_diagonal(W, 0.0)

    if aniso > 0:
        W += aniso * np.exp(-np.abs((i - j) - N/3) / (max(xi/2,1e-6)))
        W += aniso * np.exp(-np.abs((i - j) + N/3) / (max(xi/2,1e-6)))
        np.fill_diagonal(W, 0.0)

    sizes = [N // n_groups + (1 if r < (N % n_groups) else 0) for r in range(n_groups)]
    offs = np.cumsum([0] + sizes)
    group_id = np.zeros(N, dtype=int)
    for g in range(n_groups):
        lo, hi = offs[g], offs[g+1]
        group_id[lo:hi] = g
        W[lo:hi, lo:hi] *= (1.0 + grain_strength)

    Gm = (group_id[:,None] == group_id[None,:]).astype(float)
    W = W * (Gm + inter_atten*(1.0-Gm))
    np.fill_diagonal(W, 0.0)

    s = np.max(np.abs(np.linalg.eigvalsh(W)))
    if s > 1e-9:
        W = W / s
    return W

# ---------- динамика и корреляции ----------
def integrate(N=N, T=T, dt=dt, beta=beta, gamma=gamma_decay, noise=noise,
              xi=xi, aniso=aniso, n_groups=n_groups, grain_strength=grain_strength, inter_atten=0.55, seed=seed):
    set_seed(seed)
    steps = int(T / dt)
    W = build_W_grain(N, xi=xi, aniso=aniso, n_groups=n_groups,
                      grain_strength=grain_strength, inter_atten=inter_atten)
    x = 0.1 * np.random.randn(N)
    X = np.zeros((steps, N))
    sqrt_dt = np.sqrt(dt)
    for t in range(steps):
        x = (1 - gamma*dt) * x + beta*dt * np.tanh(W @ x) + noise * sqrt_dt * np.random.randn(N)
        X[t] = x

    Xc = X - X.mean(axis=0, keepdims=True)
    std = Xc.std(axis=0, keepdims=True) + 1e-12
    Xn = Xc / std
    C = (Xn.T @ Xn) / (Xn.shape[0] - 1)
    C = np.clip(C, -1.0, 1.0)
    return X, C, W

# ---------- граф, лапласиан ----------
def knn_affinity_from_C(C, k=k):
    S = np.abs(C)
    Nloc = S.shape[0]
    D = 1.0 - S
    A = np.zeros_like(S)
    for i in range(Nloc):
        order = np.argsort(D[i] + np.eye(Nloc)[i]*1e9)
        nbrs = order[:k]
        A[i, nbrs] = S[i, nbrs]
    A = np.maximum(A, A.T)
    np.fill_diagonal(A, 0.0)
    return A

def laplacian_from_affinity(A):
    d = A.sum(axis=1)
    dinv2 = 1.0 / np.sqrt(d + 1e-12)
    L = np.eye(A.shape[0]) - (dinv2[:,None] * A) * dinv2[None,:]
    return L

def fiedler_lambda(L):
    vals = np.linalg.eigvalsh(L)
    vals = np.sort(np.real(vals))
    return float(vals[1]) if len(vals) >= 2 else float(vals[0])

def normalized_laplacian(A):
    d = A.sum(axis=1)
    dinv2 = 1.0/np.sqrt(d + 1e-12)
    return np.eye(A.shape[0]) - (dinv2[:,None]*A)*dinv2[None,:]

# ---------- кластеризация и выбор K ----------
def spectral_kmeans(A, K, iters=30, seed=seed):
    d = A.sum(axis=1)
    dinv2 = 1.0/np.sqrt(d+1e-12)
    Lsym = np.eye(A.shape[0]) - (dinv2[:,None]*A)*dinv2[None,:]
    vals, vecs = np.linalg.eigh(Lsym)
    Xemb = vecs[:, :K]
    Xemb = Xemb / (np.linalg.norm(Xemb, axis=1, keepdims=True) + 1e-12)
    rng = np.random.default_rng(seed)
    Nloc = Xemb.shape[0]
    centers = Xemb[rng.choice(Nloc, K, replace=False)]
    labels = np.zeros(Nloc, dtype=int)
    for _ in range(iters):
        dists = ((Xemb[:,None,:] - centers[None,:,:])**2).sum(axis=2)
        labels = dists.argmin(axis=1)
        new_centers = np.stack([Xemb[labels==k].mean(axis=0) if np.any(labels==k) else centers[k] for k in range(K)], axis=0)
        if np.allclose(new_centers, centers):
            break
        centers = new_centers
    return labels

def select_K_by_eigengap(A, Kmin=2, Kmax=MAX_K):
    Lsym = normalized_laplacian(A)
    vals = np.linalg.eigvalsh(Lsym)
    vals = np.sort(np.real(vals))
    gaps = []
    cand_K = []
    upper = int(min(Kmax, max(Kmin, len(vals)-1)))
    for Kc in range(Kmin, upper + 1):
        gaps.append(vals[Kc] - vals[Kc-1])
        cand_K.append(Kc)
    if not cand_K:
        return max(2, min(5, len(vals)-1))
    return int(cand_K[int(np.argmax(gaps))])

# ---------- перколяция по |C| ----------
def giant_component_fraction_from_absC(C, percentile=88.0):
    Nloc = C.shape[0]
    A = np.zeros((Nloc,Nloc), dtype=int)
    S = np.abs(C).copy()
    triu = np.triu_indices(Nloc, 1)
    vals = S[triu]
    thr = float(np.percentile(vals, percentile))
    A[(S >= thr) & (~np.eye(Nloc, dtype=bool))] = 1
    A = np.maximum(A, A.T)
    seen = np.zeros(Nloc, dtype=bool)
    best = 0
    for s in range(Nloc):
        if seen[s]: continue
        q = [s]; seen[s]=True; cnt=1
        while q:
            u = q.pop()
            neigh = np.where(A[u]>0)[0]
            for v in neigh:
                if not seen[v]:
                    seen[v]=True
                    q.append(v)
                    cnt += 1
        if cnt > best: best = cnt
    return best/float(Nloc)

# ---------- диффузионные карты ----------
def embed_diffusion(A, dim=2, t=1.0):
    d = A.sum(axis=1) + 1e-12
    P = (A.T / d).T  # D^{-1}A
    vals, vecs = np.linalg.eig(P)
    vals = np.real(vals); vecs = np.real(vecs)
    order = np.argsort(-np.abs(vals))
    vals = vals[order]; vecs = vecs[:, order]
    U = vecs[:, 1:dim+1]           # пропускаем тривиальный λ≈1
    lam = vals[1:dim+1]
    U = U * (lam[np.newaxis, :]**t)
    # стандартизируем по осям (без «окружности»)
    U = (U - U.mean(axis=0)) / (U.std(axis=0) + 1e-9)
    return U

# ---------- рисование ----------
def plot_embedded(A, coords, labels, title, path,
                  draw_edges=True, intra_only=True, edge_top_percent=85.0,
                  xlim=None, ylim=None):
    plt.figure()
    ax = plt.gca()

    if draw_edges:
        wpos = A[A > 0.0]
        thr = np.percentile(wpos, edge_top_percent) if wpos.size > 0 else np.inf
        wmax = wpos.max() if wpos.size > 0 else 1.0
        Nloc = A.shape[0]
        for i in range(Nloc):
            for j in range(i+1, Nloc):
                wij = A[i, j]
                if wij <= thr:
                    continue
                if intra_only and labels[i] != labels[j]:
                    continue
                alpha = max(0.05, min(0.6, float((wij - thr) / (wmax - thr + 1e-9) + 0.05)))
                plt.plot([coords[i,0], coords[j,0]], [coords[i,1], coords[j,1]], alpha=alpha)

    uniq = np.unique(labels)
    for u in uniq:
        idx = np.where(labels==u)[0]
        plt.scatter(coords[idx,0], coords[idx,1], s=22, label=f"кластер {int(u)} (n={len(idx)})")

    ax.set_aspect('equal', 'box')
    if xlim is not None and ylim is not None:
        ax.set_xlim(*xlim); ax.set_ylim(*ylim)
    plt.title(title); plt.legend(loc="best", fontsize=8)
    plt.xlabel("ось 1"); plt.ylabel("ось 2")
    plt.savefig(path, dpi=180, bbox_inches="tight"); plt.close()

# ---------- основной сценарий ----------
def run():
    outdir = ensure_outdir("")
    print("=== Эмерджентное пространство (v3) ===")
    print(f"N={N}, T={T}, k={k}, seed={seed}")
    print(f"xi={xi}, aniso={aniso}, n_groups={n_groups}, grain_strength={grain_strength}")
    print(f"gammas={gammas}")

    # 1) Считаем всё и копим для общих осей
    results = []
    for g in gammas:
        print(f"γ={g:.3f} → интеграция, граф, встраивание...")
        X, C, W = integrate(inter_atten=float(g))
        A = knn_affinity_from_C(C, k=k)
        K_auto = select_K_by_eigengap(A, Kmin=2, Kmax=MAX_K)
        labels_vis = spectral_kmeans(A, K_VISUAL, iters=40, seed=seed)
        sizes_vis = sorted([int(np.sum(labels_vis==u)) for u in range(K_VISUAL)], reverse=True)
        L = laplacian_from_affinity(A)
        lam2 = fiedler_lambda(L)
        G = giant_component_fraction_from_absC(C, percentile=88.0)
        Z = embed_diffusion(A, dim=2, t=1.0)

        results.append(dict(
            gamma=g, A=A, Z=Z, labels=labels_vis,
            K_auto=K_auto, sizes=sizes_vis, lam2=lam2, G=G
        ))

    # 2) Общие пределы осей
    xlim = ylim = None
    if LOCK_AXES:
        allZ = np.vstack([r["Z"] for r in results])
        xmin, xmax = float(allZ[:,0].min()), float(allZ[:,0].max())
        ymin, ymax = float(allZ[:,1].min()), float(allZ[:,1].max())
        dx, dy = xmax - xmin, ymax - ymin
        xpad, ypad = AXIS_PADDING*dx, AXIS_PADDING*dy
        xlim = (xmin - xpad, xmax + xpad)
        ylim = (ymin - ypad, ymax + ypad)
        print(f"Оси зафиксированы: xlim={xlim}, ylim={ylim}")

    # 3) Рисуем кадры
    for r in results:
        g = r["gamma"]; A = r["A"]; Z = r["Z"]; labels = r["labels"]
        K_auto = r["K_auto"]; sizes = r["sizes"]; lam2 = r["lam2"]; G = r["G"]

        fname = os.path.join(outdir, f"emergent_space_gamma_{g:.2f}.png")
        title = (f"Эмерджентное пространство (DiffMaps), γ={g:.2f}; "
                 f"K_auto={K_auto}, K_vis={K_VISUAL}, размеры(K_vis)={sizes}")
        plot_embedded(A, Z, labels, title, fname,
                      draw_edges=DRAW_EDGES, intra_only=INTRA_ONLY, edge_top_percent=EDGE_TOP_PERCENT,
                      xlim=xlim, ylim=ylim)
        print(f"   K_auto={K_auto}, K_vis={K_VISUAL}, размеры={sizes}, λ2(kNN)={lam2:.4f}, G(p=88%)={G:.3f} → {fname}")

        # дополнительный кадр с «перемычками»
        if MAKE_ALT_VIEW and abs(g - ALT_VIEW_GAMMA) < 1e-9:
            fname2 = os.path.join(outdir, f"emergent_space_gamma_{g:.2f}{ALT_SUFFIX}.png")
            plot_embedded(A, Z, labels,
                          title + " (межкластерные рёбра)",
                          fname2,
                          draw_edges=True, intra_only=ALT_INTRA_ONLY, edge_top_percent=ALT_EDGE_TOP_PERCENT,
                          xlim=xlim, ylim=ylim)
            print(f"   → Доп. вид (межкластерные рёбра): {fname2}")

    print("Готово. Картинки в:", outdir)

if __name__ == "__main__":
    run()

Эмерджентное пространство визуализируется через диффузионные карты (Coifman & Lafon, 2006), что перекликается с подходами в геометрической глубинном обучении и теориях эмерджентного пространства-времени в квантовой гравитации (Van Raamsdonk, 2010).

Это — косвенное подтверждение одного из центральных тезисов модели: пространство не является фундаментальным контейнером. Оно возникает как производная характеристика силы и структуры отношений между элементами сети.

Заключение по главе

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

  • Эмерджентность: из простых отношений рождаются сложные структуры.

  • Фазовые переходы: система имеет критические точки качественного изменения.

  • Устойчивость: возникающие кластеры сохраняются во времени.

  • Порождение пространства: геометрия возникает из паттернов связей.

Конечно симуляции носят поверхностный, иллюстративный характер, используя упрощенные графовые модели с экспоненциальной связностью; они не моделируют реальные квантовые эффекты, такие как релятивистские поправки, а демонстрируют принципиальную возможность эмерджентного поведения сети КП. Это еще не физика! Для полноценной физической реализации модели требуется куда более сложный формализм, учитывающий, например, локальную лоренц-инвариантность и конкретные группы калибровочных симметрий Стандартной Модели.

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

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

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

Комментарии (0)